Skip to content

Instantly share code, notes, and snippets.

@AubreyHewes
Created December 11, 2014 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AubreyHewes/cc8a114f6fbf5821fdbe to your computer and use it in GitHub Desktop.
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.
<?php
//
// Usage Example:
// importhorde.php -r sqlite:////roundcube.sqlite?mode=0646 -h mysql://user:pass@127.0.0.1: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