Created
December 11, 2014 09:52
-
-
Save AubreyHewes/cc8a114f6fbf5821fdbe to your computer and use it in GitHub Desktop.
Import Horde contacts/identities/preferences to Roundcube
This is dependent on being placed within the roundcube bin directory.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// | |
// Usage Example: | |
// importhorde.php -r sqlite:////roundcube.sqlite?mode=0646 -h mysql://user:[email protected]:3306/horde -c -p | |
// | |
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); | |
require_once INSTALL_PATH.'program/include/clisetup.php'; | |
ini_set('memory_limit', -1); | |
// get arguments | |
$opts_cfg = array( | |
'r' => 'roundcube-dsn', | |
'h' => 'horde-dsn', | |
'c' => 'import-contacts', | |
'p' => 'import-preferences', | |
'm' => 'import-usermask', | |
'H' => 'mail-host' | |
); | |
$opts = rcube_utils::get_opt($opts_cfg); | |
if (empty($opts['roundcube-dsn']) | |
|| empty($opts['horde-dsn']) | |
|| (empty($opts['import-contacts']) && empty($opts['import-preferences']))) { | |
die('Usage: importhorde.php OPTIONS | |
Required: | |
-r|--roundcube-dsn <dsn> The Roundcube DSN (read/write access required) | |
-h|--horde-dsn <dsn> The Horde DSN (read only access required) | |
See http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php for DSN examples | |
Required (at least one): | |
-c|--import-contacts Import contacts (creates unknown users) | |
-p|--import-preferences Import preferences & identities (creates unknown users) | |
Optional: | |
-m|--import-usermask Only for specified usermask (i.e. "test" would include all users with this in their username) | |
-H|--mail-host Created users will have this mail host preset | |
'); | |
} | |
// connect to horde DB | |
$hordedb = rcube_db::factory($opts['horde-dsn']); | |
$hordedb->db_connect('r'); | |
if (!$hordedb->is_connected() || $hordedb->is_error()) { | |
rcube::raise_error("No Horde DB connection (" . $opts['horde-dsn'] . ")", false, true); | |
} | |
echo "Got Horde DB! :-)\n"; | |
// connect to roundcube DB | |
$RCMAIL = rcube::get_instance('dev'); | |
$RCMAIL->db = false; | |
$RCMAIL->config->set('db_dsnw', $opts['roundcube-dsn']); | |
$db = $RCMAIL->get_dbh(); | |
$db->db_connect('w'); | |
if (!$db->is_connected() || $db->is_error()) { | |
rcube::raise_error("No Roundcube DB connection", false, true); | |
} | |
echo "Got Roundcube DB! :-)\n"; | |
function create_users($usernames) { | |
global $RCMAIL, $opts, $db; | |
// get known users | |
$userIds = []; | |
$sql_result = $db->query('SELECT `user_id`,`username` FROM ' . $db->table_name('users', true) . ' WHERE `username` IN (' | |
. implode(',', array_map(function ($item) use ($db) { return $db->quote($item); }, $usernames)) . ') ORDER BY `user_id`', []); | |
while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) { | |
$userIds[$sql_arr['username']] = $sql_arr['user_id']; | |
} | |
// unknown users | |
$unknown = array_diff($usernames, array_keys($userIds)); | |
// create unknown users | |
$host = $RCMAIL->config->get('default_host', 'localhost'); | |
if (!empty($host)) { | |
if (is_array($host)) { | |
list($key, $val) = each($host); | |
$host = is_numeric($key) ? $val : $key; | |
} | |
$host = rcube_utils::parse_host($host); | |
} | |
foreach ($unknown as $username) { | |
rcube_user::create($username, !empty($opts['mail-host']) ? $opts['mail-host'] : $host); | |
} | |
} | |
function import_contacts($userfilter = '') { | |
global $hordedb, $db; | |
echo "Exporting contacts from Horde...\n"; | |
$sql = 'SELECT * FROM `turba_objects`'; | |
if (!empty($userfilter)) { | |
$sql .= ' WHERE `owner_id` LIKE ' . $hordedb->quote('%' . $userfilter . '%'); | |
} | |
$rs = $hordedb->query($sql); | |
if (!$hordedb->affected_rows()) { | |
return; | |
} | |
/** | |
* @param array $object | |
* | |
* @return array | |
*/ | |
function toContactRecord($object) { | |
static $mapping = [ // horde to roundcube | |
'object_email' => 'email', | |
'object_firstname' => 'firstname', | |
'object_lastname' => 'surname', | |
'object_middlenames' => 'middlename', | |
'object_nameprefix' => 'prefix', | |
'object_namesuffix' => 'suffix', | |
'object_nickname' => 'nickname', | |
'object_alias' => 'gender', | |
'object_bday' => 'birthday', | |
'object_spouse' => 'spouse', | |
'object_anniversary' => 'anniversary', | |
'object_homeemail' => 'email:home', | |
'object_homephone' => 'phone:home', | |
'object_homephone2' => 'phone:home2', | |
'object_homefax' => 'phone:homefax', | |
'object_pager' => 'phone:pager', | |
'object_cellphone' => 'phone:mobile', | |
'object_homestreet' => 'street:home', | |
'object_homecity' => 'locality:home', | |
'object_homepostalcode' => 'zipcode:home', | |
'object_homeprovince' => 'region:home', | |
'object_homecountry' => 'country:home', | |
'object_company' => 'organization', | |
'object_department' => 'department', | |
'object_manager' => 'manager', | |
'object_title' => 'jobtitle', | |
'object_assistant' => 'assistant', | |
'object_workstreet' => 'street:work', | |
'object_workcity' => 'locality:work', | |
'object_workpostalcode' => 'zipcode:work', | |
'object_workprovince' => 'region:work', | |
'object_workcountry' => 'country:work', | |
'object_workphone' => 'phone:work', | |
'object_workphone2' => 'phone:work2', | |
'object_assistantphone' => 'phone:assistant', | |
'object_fax' => 'phone:workfax', | |
'object_carphone' => 'phone:car', | |
'object_workemail' => 'email:work', | |
'object_url' => 'website:homepage', | |
'object_notes' => 'notes', | |
]; | |
$a_record = []; | |
foreach ($object as $key => $value) { | |
if (empty($value) || empty($mapping[$key])) { | |
continue; | |
} | |
$a_record[$mapping[$key]] = $value; | |
} | |
if (empty($a_record)) { | |
return null; | |
} | |
// Generate contact's display name (must be before validation) | |
$a_record['name'] = rcube_addressbook::compose_display_name($a_record, true); | |
// Reset it if equals to email address (from compose_display_name()) | |
$email = rcube_addressbook::get_col_values('email', $a_record, true); | |
if ($a_record['name'] == $email[0]) { | |
$a_record['name'] = ''; | |
} | |
return $a_record; | |
} | |
function isValidEmail($email) { | |
return filter_var($email, FILTER_VALIDATE_EMAIL) | |
&& preg_match('/@.+\./', $email) | |
&& !preg_match('/@\[/', $email) | |
&& !preg_match('/".+@/', $email) | |
&& !preg_match('/=.+@/', $email); | |
} | |
/* @var rcube_result_set[] $items */ | |
$items = []; | |
while ($rs && ($contact = $db->fetch_assoc($rs))) { | |
$key = trim($contact['owner_id']); | |
$a_record = toContactRecord($contact); | |
if (empty($a_record) || !isValidEmail($a_record['email'])) { | |
continue; | |
} | |
if (!isset($items[$key])) { | |
$items[$key] = new rcube_result_set(); | |
} | |
$items[$key]->add($a_record); | |
} | |
// maybe need to create users | |
create_users(array_keys($items)); | |
// import contacts | |
$users = $db->query('SELECT `user_id`,`username` FROM ' . $db->table_name('users', true) . ' WHERE `username` IN (' | |
. implode(',', array_map(function ($item) use ($db) { return $db->quote($item); }, array_keys($items))) . ') ORDER BY `user_id`', []); | |
while ($users && ($user = $db->fetch_assoc($users))) { | |
$set = $items[$user['username']]; | |
echo "Importing " . count($set->records) . " contacts for user " . $user['user_id'] . " (" . $user['username'] . ")... "; | |
$contacts = new rcube_contacts($db, $user['user_id']); | |
$contacts->set_pagesize(1000); | |
$contacts->insertMultiple($set); | |
echo "done.\n"; | |
} | |
} | |
function import_prefs ($userfilter = '') { | |
global $hordedb, $db; | |
echo "Exporting preferences from Horde...\n"; | |
$sql = 'SELECT * FROM `horde_prefs`'; | |
if (!empty($userfilter)) { | |
$sql .= ' WHERE `pref_uid` LIKE ' . $hordedb->quote('%' . $userfilter . '%'); | |
} | |
$rs = $hordedb->query($sql); | |
if (!$hordedb->affected_rows()) { | |
return; | |
} | |
/** | |
* Sanity checks/cleanups on HTML body of signature | |
*/ | |
function rcmail_wash_html($html) | |
{ | |
// Add header with charset spec., washtml cannot work without that | |
$html = '<html><head>' | |
. '<meta http-equiv="Content-Type" content="text/html; charset='.RCUBE_CHARSET.'" />' | |
. '</head><body>' . $html . '</body></html>'; | |
// clean HTML with washhtml by Frederic Motte | |
$wash_opts = array( | |
'show_washed' => false, | |
'allow_remote' => 1, | |
'charset' => RCUBE_CHARSET, | |
'html_elements' => array('body', 'link'), | |
'html_attribs' => array('rel', 'type'), | |
); | |
// initialize HTML washer | |
$washer = new rcube_washtml($wash_opts); | |
//$washer->add_callback('form', 'rcmail_washtml_callback'); | |
//$washer->add_callback('style', 'rcmail_washtml_callback'); | |
// Remove non-UTF8 characters (#1487813) | |
$html = rcube_charset::clean($html); | |
$html = $washer->wash($html); | |
// remove unwanted comments and tags (produced by washtml) | |
$html = preg_replace(array('/<!--[^>]+-->/', '/<\/?body>/'), '', $html); | |
return $html; | |
} | |
/* @var rcube_result_set[] $items */ | |
$items = []; | |
while ($rs && ($pref = $db->fetch_assoc($rs))) { | |
if (!in_array($pref['pref_name'], ['language', 'timezone', 'identities'])) { | |
continue; | |
} | |
$key = trim($pref['pref_uid']); | |
if ($pref['pref_name'] === 'identities' && !empty($pref['pref_value'])) { | |
if (!function_exists("toIdentityRecord")) { | |
function toIdentityRecord($object) { | |
// 'bcc' | |
static $mapping = [ | |
'standard' => 'default_identity', | |
'replyto_addr' => 'reply-to', | |
'fullname' => 'name', | |
'from_addr' => 'email', | |
'signature' => 'signature', | |
'signature_html' => 'signature', | |
]; | |
$record = []; | |
foreach ($object as $key => $value) { | |
if (!isset($mapping[$key]) || empty($object[$key])) { | |
continue; | |
} | |
$record[$mapping[$key]] = $value; | |
} | |
if (is_array($record['reply-to'])) { | |
$record['reply-to'] = empty($record['reply-to']) ? '' : reset($record['reply-to']); | |
} | |
if (!empty($object['signature_html'])) { | |
$record['html_signature'] = 1; | |
$record['signature'] = rcmail_wash_html($record['signature']); | |
} | |
return $record; | |
} | |
} | |
$identities = unserialize($pref['pref_value']); | |
if (empty($identities)) { | |
continue; | |
} | |
$identities = array_filter($identities, function (&$item) { | |
$item = toIdentityRecord($item); | |
return !empty($item['signature']) || !empty($item['html_signature']); | |
}); | |
if (empty($identities)) { | |
continue; | |
} | |
if (empty($items[$key]['identities'])) { | |
$items[$key]['identities'] = []; | |
} | |
$items[$key]['identities'] = array_merge($items[$key]['identities'], $identities); | |
} else { | |
$items[$key]['prefs'][$pref['pref_name']] = $pref['pref_value']; | |
} | |
} | |
// maybe need to create users | |
create_users(array_keys($items)); | |
// import contacts | |
$users = $db->query('SELECT `user_id`,`username` FROM ' . $db->table_name('users', true) . ' WHERE `username` IN (' | |
. implode(',', array_map(function ($item) use ($db) { return $db->quote($item); }, array_keys($items))) . ') ORDER BY `user_id`', []); | |
while ($users && ($data = $db->fetch_assoc($users))) { | |
$user = new rcube_user($data['user_id']); | |
if (!empty($items[$data['username']]['identities'])) { | |
echo "Importing " . count($items[$data['username']]['identities']) . " identities for user " . $data['user_id'] . " (" . $data['username'] . ")... "; | |
foreach ($items[$data['username']]['identities'] as $identity) { | |
$updated = $user->insert_identity($identity); | |
} | |
echo "done.\n"; | |
} | |
if (!empty($items[$data['username']]['prefs'])) { | |
echo "Importing " . count($items[$data['username']]['prefs']) . " preferences for user " . $data['user_id'] . " (" . $data['username'] . ")... "; | |
if (!empty($items[$data['username']]['prefs']['language'])) { | |
// hacky fix for updating the language pref | |
global $_SESSION; | |
$_SESSION['language'] = $items[$data['username']]['prefs']['language']; | |
} | |
$saved = $user->save_prefs($items[$data['username']]['prefs']); | |
echo "done.\n"; | |
} | |
} | |
} | |
if (isset($opts['import-contacts'])) { | |
import_contacts(!empty($opts['import-usermask']) ? $opts['import-usermask'] : ''); | |
} | |
if (isset($opts['import-preferences'])) { | |
import_prefs(!empty($opts['import-preferences']) ? $opts['import-usermask'] : ''); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment