alecpl
2011-06-14 ecf295f6ef2b83c5e51cc74adf833fd8e18b6cfb
- Added searching in all addressbook sources (global-search)
- Added addressbook source selection in contacts import


17 files modified
733 ■■■■ changed files
CHANGELOG 2 ●●●●● patch | view | raw | blame | history
config/main.inc.php.dist 1 ●●●● patch | view | raw | blame | history
program/js/app.js 79 ●●●● patch | view | raw | blame | history
program/localization/en_US/labels.inc 1 ●●●● patch | view | raw | blame | history
program/localization/pl_PL/labels.inc 1 ●●●● patch | view | raw | blame | history
program/steps/addressbook/copy.inc 36 ●●●● patch | view | raw | blame | history
program/steps/addressbook/delete.inc 118 ●●●● patch | view | raw | blame | history
program/steps/addressbook/edit.inc 37 ●●●● patch | view | raw | blame | history
program/steps/addressbook/export.inc 45 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 169 ●●●● patch | view | raw | blame | history
program/steps/addressbook/groups.inc 5 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/import.inc 23 ●●●● patch | view | raw | blame | history
program/steps/addressbook/list.inc 58 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/mailto.inc 19 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/save.inc 2 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/search.inc 119 ●●●● patch | view | raw | blame | history
program/steps/addressbook/show.inc 18 ●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,8 @@
CHANGELOG Roundcube Webmail
===========================
- Added searching in all addressbook sources
- Added addressbook source selection in contacts import
- Implement LDAPv3 Virtual List View (VLV) for paged results listing
- Use 'address_template' config option when adding a new address block (#1487944)
- Added addressbook advanced search
config/main.inc.php.dist
@@ -450,6 +450,7 @@
// In order to enable public ldap search, configure an array like the Verisign
// example further below. if you would like to test, simply uncomment the example.
// Array key must contain only safe characters, ie. a-zA-Z0-9_
$rcmail_config['ldap_public'] = array();
// If you are going to use LDAP for individual address books, you will need to 
program/js/app.js
@@ -336,7 +336,7 @@
        if (this.contact_list && this.contact_list.rowcount > 0)
          this.enable_command('export', true);
        this.enable_command('add', 'import', !this.env.readonly);
        this.enable_command('add', 'import', this.env.writable_source);
        this.enable_command('list', 'listgroup', 'advanced-search', true);
        break;
@@ -534,7 +534,7 @@
            this.reset_qsearch();
          this.list_contacts(props);
          this.enable_command('add', 'import', (this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
          this.enable_command('add', 'import', this.env.writable_source);
        }
        break;
@@ -988,8 +988,14 @@
        if (s && this.env.mailbox)
          this.list_mailbox(this.env.mailbox);
        else if (s && this.task == 'addressbook')
        else if (s && this.task == 'addressbook') {
          if (this.env.source == '') {
            for (var n in this.env.address_sources) break;
            this.env.source = n;
            this.env.group = '';
          }
          this.list_contacts(this.env.source, this.env.group);
        }
        break;
      case 'listgroup':
@@ -3651,15 +3657,34 @@
    if (this.preview_timer)
      clearTimeout(this.preview_timer);
    var id, frame, ref = this;
    var n, id, sid, ref = this, writable = false,
      source = this.env.source ? this.env.address_sources[this.env.source] : null;
    if (id = list.get_single_selection())
      this.preview_timer = window.setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
    else if (this.env.contentframe)
      this.show_contentframe(false);
    // no source = search result, we'll need to detect if any of
    // selected contacts are in writable addressbook to enable edit/delete
    if (list.selection.length) {
      if (!source) {
        for (n in list.selection) {
          sid = String(list.selection[n]).replace(/^[^-]+-/, '');
          if (sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly) {
            writable = true;
            break;
          }
        }
      }
      else {
        writable = !source.readonly;
      }
    }
    this.enable_command('compose', list.selection.length > 0);
    this.enable_command('edit', (id && this.env.address_sources && !this.env.address_sources[this.env.source].readonly) ? true : false);
    this.enable_command('delete', list.selection.length && this.env.address_sources && !this.env.address_sources[this.env.source].readonly);
    this.enable_command('edit', id && writable);
    this.enable_command('delete', list.selection.length && writable);
    return false;
  };
@@ -3797,12 +3822,12 @@
    if (!(selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
      return;
    var id, a_cids = [], qs = '';
    var id, n, a_cids = [], qs = '';
    if (this.env.cid)
      a_cids.push(this.env.cid);
    else {
      for (var n=0; n<selection.length; n++) {
      for (n=0; n<selection.length; n++) {
        id = selection[n];
        a_cids.push(id);
        this.contact_list.remove_row(id, (n == selection.length-1));
@@ -4241,14 +4266,20 @@
      target = window.frames[this.env.contentframe];
      this.contact_list.clear_selection();
    }
    else if (framed)
      return false;
    this.location_href(this.env.comm_path+'&_action=search'+add_url
      +'&_source='+urlencode(this.env.source)
      +(this.env.group ? '&_gid='+urlencode(this.env.group) : ''), target);
    this.location_href(this.env.comm_path+'&_action=search'+add_url, target);
    return true;
  };
  // unselect directory/group
  this.unselect_directory = function()
  {
    if (this.env.address_sources.length > 1 || this.env.group != '') {
      this.select_folder('', (this.env.group ? 'G'+this.env.source+this.env.group : this.env.source));
      this.env.group = '';
      this.env.source = '';
    }
  };
@@ -5468,9 +5499,20 @@
    switch (response.action) {
      case 'delete':
        if (this.task == 'addressbook') {
          var uid = this.contact_list.get_selection();
          var sid, uid = this.contact_list.get_selection(), writable = false;
          if (uid && this.contact_list.rows[uid]) {
            // search results, get source ID from record ID
            if (this.env.source == '') {
              sid = String(uid).replace(/^[^-]+-/, '');
              writable = sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly;
            }
            else {
              writable = !this.env.address_sources[this.env.source].readonly;
            }
          }
          this.enable_command('compose', (uid && this.contact_list.rows[uid]));
          this.enable_command('delete', 'edit', (uid && this.contact_list.rows[uid] && this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
          this.enable_command('delete', 'edit', writable);
          this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
        }
@@ -5519,10 +5561,9 @@
          this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
          if (response.action == 'list' || response.action == 'search') {
            this.enable_command('group-create',
              (this.env.address_sources[this.env.source].groups && !this.env.address_sources[this.env.source].readonly));
            this.enable_command('group-rename', 'group-delete',
              (this.env.address_sources[this.env.source].groups && this.env.group && !this.env.address_sources[this.env.source].readonly));
            var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null;
            this.enable_command('group-create', (source && source.groups && !source.readonly));
            this.enable_command('group-rename', 'group-delete', (source && source.groups && this.env.group && !source.readonly));
            this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
          }
        }
program/localization/en_US/labels.inc
@@ -326,6 +326,7 @@
$labels['import'] = 'Import';
$labels['importcontacts'] = 'Import contacts';
$labels['importfromfile'] = 'Import from file:';
$labels['importtarget'] = 'Add new contacts to address book:';
$labels['importreplace'] = 'Replace the entire address book';
$labels['importtext'] = 'You can upload contacts from an existing address book.<br/>We currently support importing addresses from the <a href="http://en.wikipedia.org/wiki/VCard">vCard</a> data format.';
$labels['done'] = 'Done';
program/localization/pl_PL/labels.inc
@@ -413,5 +413,6 @@
$labels['search'] = 'Szukaj';
$labels['advsearch'] = 'Wyszukiwanie zaawansowane';
$labels['other'] = 'Inne';
$labels['importtarget'] = 'Dodaj nowe kontakty do książki adresowej:';
?>
program/steps/addressbook/copy.inc
@@ -23,20 +23,37 @@
if (!$OUTPUT->ajax_call)
  return;
$cid = get_input_value('_cid', RCUBE_INPUT_POST);
$cids         = rcmail_get_cids();
$target = get_input_value('_to', RCUBE_INPUT_POST);
$target_group = get_input_value('_togid', RCUBE_INPUT_POST);
if ($cid && preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid) && strlen($target) && $target !== $source)
{
  $success = 0;
$maxnum  = $RCMAIL->config->get('max_group_members', 0);
foreach ($cids as $source => $cid)
{
    // Something wrong, target not specified
    if (!strlen($target)) {
        break;
    }
    // It maight happen when copying records from search result
    // Do nothing, go to next source
    if ($target == $source) {
        continue;
    }
    $CONTACTS = $RCMAIL->get_address_book($source);
  $TARGET = $RCMAIL->get_address_book($target);
  if ($TARGET && $TARGET->ready && !$TARGET->readonly) {
    $arr_cids = explode(',', $cid);
    if (!$TARGET || !$TARGET->ready || $TARGET->readonly) {
        break;
    }
    $ids = array();
    foreach ($arr_cids as $cid) {
    foreach ($cid as $cid) {
      $a_record = $CONTACTS->get_record($cid, true);
      // check if contact exists, if so, we'll need it's ID
@@ -73,7 +90,7 @@
        $TARGET->reset();
        $TARGET->set_group($target_group);
        if (($maxnum = $RCMAIL->config->get('max_group_members', 0)) && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
            if ($maxnum && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
          $OUTPUT->show_message('maxgroupmembersreached', 'warning', array('max' => $maxnum));
          $OUTPUT->send();
        }
@@ -81,8 +98,9 @@
        if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success)
          $success = $cnt;
      }
      else if ($plugin['result'])
        else if ($plugin['result']) {
        $success = $plugin['result'];
        }
    }
  }
@@ -90,8 +108,6 @@
    $OUTPUT->show_message('copyerror', 'error');
  else
    $OUTPUT->show_message('copysuccess', 'notice', array('nr' => $success));
}
// send response
$OUTPUT->send();
program/steps/addressbook/delete.inc
@@ -19,42 +19,126 @@
*/
if ($OUTPUT->ajax_call &&
    ($cid = get_input_value('_cid', RCUBE_INPUT_POST)) &&
    preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid)
) {
// process ajax requests only
if (!$OUTPUT->ajax_call)
    return;
$cids   = rcmail_get_cids();
$delcnt = 0;
foreach ($cids as $source => $cid)
{
    $CONTACTS = rcmail_contact_source($source);
    if ($CONTACTS->readonly) {
        // more sources? do nothing, probably we have search results from
        // more than one source, some of these sources can be readonly
        if (count($cids) == 1) {
            $OUTPUT->show_message('contactdelerror', 'error');
            $OUTPUT->command('list_contacts');
        }
        continue;
    }
    $plugin = $RCMAIL->plugins->exec_hook('contact_delete', array(
        'id' => $cid, 'source' => get_input_value('_source', RCUBE_INPUT_GPC)));
        'id' => $cid, 'source' => $source));
    $deleted = !$plugin['abort'] ? $CONTACTS->delete($cid) : $plugin['result'];
    if (!$deleted) {
        $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'contactdelerror', 'error');
        $OUTPUT->command('list_contacts');
        $OUTPUT->send();
    }
    else {
        $delcnt += $deleted;
    }
}
        $OUTPUT->show_message('contactdeleted', 'confirmation');
$page = isset($_SESSION['page']) ? $_SESSION['page'] : 1;
// update saved search after data changed
if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) {
    $search  = (array)$_SESSION['search'][$search_request];
    $records = array();
    // Get records from all sources (refresh search)
    foreach ($search as $s => $set) {
        $source = $RCMAIL->get_address_book($s);
        // reset page
        $source->set_page(1);
        $source->set_pagesize(9999);
        $source->set_search_set($set);
        // get records
        $result = $source->list_records(array('name', 'email'));
        if (!$result->count) {
            unset($search[$s]);
            continue;
        }
        while ($row = $result->next()) {
            $row['sourceid'] = $s;
            $key = $row['name'] . ':' . $row['sourceid'];
            $records[$key] = $row;
        }
        unset($result);
        $search[$s] = $source->get_search_set();
    }
    $_SESSION['search'][$search_request] = $search;
    // create resultset object
    $count  = count($records);
    $first  = ($page-1) * $CONFIG['pagesize'];
    $result = new rcube_result_set($count, $first);
    // get records from the next page to add to the list
    $pages = ceil((count($records) + $delcnt) / $CONFIG['pagesize']);
    if ($_GET['_from'] != 'show' && $pages > 1 && $page < $pages) {
        // sort the records
        ksort($records, SORT_LOCALE_STRING);
        $first += $CONFIG['pagesize'];
        // create resultset object
        $res = new rcube_result_set($count, $first - $delcnt);
        if ($CONFIG['pagesize'] < $count) {
            $records = array_slice($records, $first - $delcnt, $delcnt);
        }
        $res->records = array_values($records);
        $records = $res;
    }
    else {
        unset($records);
    }
}
else {
        // count contacts for this user
        $result = $CONTACTS->count();
        // update saved search after data changed
        if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request]))
            $_SESSION['search'][$search_request] = $CONTACTS->refresh_search();
    // get records from the next page to add to the list
    $pages = ceil(($result->count + $delcnt) / $CONFIG['pagesize']);
    if ($_GET['_from'] != 'show' && $pages > 1 && $page < $pages) {
        $CONTACTS->set_page($page);
        $records = $CONTACTS->list_records(null, -$delcnt);
    }
}
        // update message count display
        $OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));
        $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result->count));
$OUTPUT->set_env('pagecount', ceil($result->count / $CONFIG['pagesize']));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
        // add new rows from next page (if any)
        $pages = ceil(($result->count + $deleted) / $CONTACTS->page_size);
        if ($_GET['_from'] != 'show' && $pages > 1 && $CONTACTS->list_page < $pages)
            rcmail_js_contacts_list($CONTACTS->list_records(null, -$deleted));
if (!empty($records)) {
    rcmail_js_contacts_list($records);
    }
    // send response
    $OUTPUT->send();
}
exit;
program/steps/addressbook/edit.inc
@@ -19,15 +19,38 @@
*/
if ($RCMAIL->action == 'edit') {
    // Get contact ID and source ID from request
    $cids   = rcmail_get_cids();
    $source = key($cids);
    $cid    = array_shift($cids[$source]);
if (($cid = get_input_value('_cid', RCUBE_INPUT_GPC)) && ($record = $CONTACTS->get_record($cid, true)))
    // Initialize addressbook
    $CONTACTS = rcmail_contact_source($source, true);
    // Contact edit
    if ($cid && ($record = $CONTACTS->get_record($cid, true))) {
    $OUTPUT->set_env('cid', $record['ID']);
    }
// adding not allowed here
if ($CONTACTS->readonly) {
    $OUTPUT->show_message('sourceisreadonly');
    rcmail_overwrite_action('show');
    return;
    }
}
else {
    $source = get_input_value('_source', RCUBE_INPUT_GPC);
    $CONTACTS = $RCMAIL->get_address_book($source);
    // find writable addressbook
    if (!$CONTACTS || $CONTACTS->readonly)
        $source = rcmail_default_source(true);
    // Initialize addressbook
    $CONTACTS = rcmail_contact_source($source, true);
}
@@ -211,12 +234,14 @@
}
$OUTPUT->add_handler('contactedithead', 'rcmail_contact_edithead');
$OUTPUT->add_handler('contacteditform', 'rcmail_contact_editform');
$OUTPUT->add_handler('contactphoto',    'rcmail_contact_photo');
$OUTPUT->add_handler('photouploadform', 'rcmail_upload_photo_form');
$OUTPUT->add_handlers(array(
    'contactedithead' => 'rcmail_contact_edithead',
    'contacteditform' => 'rcmail_contact_editform',
    'contactphoto'    => 'rcmail_contact_photo',
    'photouploadform' => 'rcmail_upload_photo_form',
));
if (!$CONTACTS->get_result() && $OUTPUT->template_exists('contactadd'))
if ($RCMAIL->action == 'add' && $OUTPUT->template_exists('contactadd'))
    $OUTPUT->send('contactadd');
// this will be executed if no template for addcontact exists
program/steps/addressbook/export.inc
@@ -6,6 +6,7 @@
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
 | Copyright (C) 2011, Kolab Systems AG                                  |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 | PURPOSE:                                                              |
@@ -13,16 +14,56 @@
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <machniak@kolabsys.com>                   |
 +-----------------------------------------------------------------------+
 $Id:  $
 $Id$
*/
// Use search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
{
    $search  = (array)$_SESSION['search'][$_REQUEST['_search']];
    $records = array();
    // Get records from all sources
    foreach ($search as $s => $set) {
        $source = $RCMAIL->get_address_book($s);
        // reset page
        $source->set_page(1);
        $source->set_pagesize(99999);
        $source->set_search_set($set);
        // get records
        $result = $source->list_records();
        while ($row = $result->next()) {
            $row['sourceid'] = $s;
            $key = $row['name'] . ':' . $row['sourceid'];
            $records[$key] = $row;
        }
        unset($result);
    }
    // sort the records
    ksort($records, SORT_LOCALE_STRING);
    // create resultset object
    $count  = count($records);
    $result = new rcube_result_set($count);
    $result->records = array_values($records);
}
// selected directory/group
else {
    $CONTACTS = rcmail_contact_source(null, true);
// get contacts for this user
$CONTACTS->set_page(1);
$CONTACTS->set_pagesize(99999);
$result = $CONTACTS->list_records(null, 0, true);
}
// send downlaod headers
send_nocacheing_headers();
@@ -38,6 +79,7 @@
  else {
    $vcard = new rcube_vcard($row['vcard']);
    $vcard->reset();
    foreach ($row as $key => $values) {
      list($field, $section) = explode(':', $key);
      foreach ((array)$values as $value) {
@@ -51,4 +93,3 @@
}
exit;
program/steps/addressbook/func.inc
@@ -21,46 +21,6 @@
$SEARCH_MODS_DEFAULT = array('name'=>1, 'firstname'=>1, 'surname'=>1, 'email'=>1, '*'=>1);
// select source
$source = get_input_value('_source', RCUBE_INPUT_GPC);
if (!$RCMAIL->action && !$OUTPUT->ajax_call) {
    // add list of address sources to client env
    $js_list = $RCMAIL->get_address_sources();
    // if source is not set use first directory
    if (empty($source))
        $source = $js_list[key($js_list)]['id'];
    $search_mods = $RCMAIL->config->get('addressbook_search_mods', $SEARCH_MODS_DEFAULT);
    $OUTPUT->set_env('search_mods', $search_mods);
    $OUTPUT->set_env('address_sources', $js_list);
}
// instantiate a contacts object according to the given source
$CONTACTS = $RCMAIL->get_address_book($source);
$CONTACTS->set_pagesize($CONFIG['pagesize']);
// set list properties and session vars
if (!empty($_GET['_page']))
    $CONTACTS->set_page(($_SESSION['page'] = intval($_GET['_page'])));
else
    $CONTACTS->set_page(isset($_SESSION['page']) ?$_SESSION['page'] : 1);
if (!empty($_REQUEST['_gid']))
    $CONTACTS->set_group(get_input_value('_gid', RCUBE_INPUT_GPC));
// set data source env
$OUTPUT->set_env('source', $source ? $source : '0');
$OUTPUT->set_env('readonly', $CONTACTS->readonly);
if (!$OUTPUT->ajax_call) {
    $search_mods = $RCMAIL->config->get('addressbook_search_mods', $SEARCH_MODS_DEFAULT);
    $OUTPUT->set_env('search_mods', $search_mods);
    $OUTPUT->set_pagetitle(rcube_label('addressbook'));
}
// general definition of contact coltypes
$CONTACT_COLTYPES = array(
  'name'         => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('name'), 'category' => 'main'),
@@ -96,6 +56,65 @@
  // TODO: define fields for vcards like GEO, KEY
);
// Addressbook UI
if (!$RCMAIL->action && !$OUTPUT->ajax_call) {
    // add list of address sources to client env
    $js_list = $RCMAIL->get_address_sources();
    // use first directory by default
    $source = $js_list[key($js_list)]['id'];
    // find writeable source
    foreach ($js_list as $s) {
        if (!$s['readonly']) {
            $OUTPUT->set_env('writable_source', $s['id']);
            break;
        }
    }
    $search_mods = $RCMAIL->config->get('addressbook_search_mods', $SEARCH_MODS_DEFAULT);
    $OUTPUT->set_env('search_mods', $search_mods);
    $OUTPUT->set_env('address_sources', $js_list);
    $OUTPUT->set_pagetitle(rcube_label('addressbook'));
    $CONTACTS = rcmail_contact_source($source, true);
}
// instantiate a contacts object according to the given source
function rcmail_contact_source($source=null, $init_env=false)
{
    global $RCMAIL, $OUTPUT, $CONFIG, $CONTACT_COLTYPES;
    if (!strlen($source)) {
        $source = get_input_value('_source', RCUBE_INPUT_GPC);
    }
    if (!strlen($source)) {
        return null;
    }
    // Get object
    $CONTACTS = $RCMAIL->get_address_book($source);
    $CONTACTS->set_pagesize($CONFIG['pagesize']);
    // set list properties and session vars
    if (!empty($_GET['_page']))
        $CONTACTS->set_page(($_SESSION['page'] = intval($_GET['_page'])));
    else
        $CONTACTS->set_page(isset($_SESSION['page']) ? $_SESSION['page'] : 1);
    if (!empty($_REQUEST['_gid']))
        $CONTACTS->set_group(get_input_value('_gid', RCUBE_INPUT_GPC));
    if (!$init_env)
        return $CONTACTS;
    $OUTPUT->set_env('readonly', $CONTACTS->readonly);
    $OUTPUT->set_env('source', $source);
// reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object
if (is_array($CONTACTS->coltypes)) {
    // remove cols not listed by the backend class
@@ -109,6 +128,21 @@
}
$OUTPUT->set_env('photocol', is_array($CONTACT_COLTYPES['photo']));
    return $CONTACTS;
}
function rcmail_default_source($writable=false)
{
    global $RCMAIL;
    // get list of address sources
    $list = $RCMAIL->get_address_sources($writable);
    // use first directory by default
    return $list[key($list)]['id'];
}
function rcmail_directory_list($attrib)
@@ -230,6 +264,11 @@
    while ($row = $result->next()) {
        $a_row_cols = array();
        // build contact ID with source ID
        if (isset($row['sourceid'])) {
            $row['ID'] = $row['ID'].'-'.$row['sourceid'];
        }
        // format each col
        foreach ($a_show_cols as $col)
            $a_row_cols[$col] = Q($row[$col]);
@@ -269,12 +308,14 @@
}
function rcmail_get_rowcount_text()
function rcmail_get_rowcount_text($result=null)
{
    global $CONTACTS;
    global $CONTACTS, $CONFIG;
  
    // read nr of contacts
    if (!$result) {
    $result = $CONTACTS->get_result();
    }
    if (!$result) {
        $result = $CONTACTS->count();
    }
@@ -286,7 +327,7 @@
            'name'  => 'contactsfromto',
            'vars'  => array(
            'from'  => $result->first + 1,
            'to'    => min($result->count, $result->first + $CONTACTS->page_size),
            'to'    => min($result->count, $result->first + $CONFIG['pagesize']),
            'count' => $result->count)
        ));
@@ -599,6 +640,48 @@
}
/**
 * Returns contact ID(s) and source(s) from GET/POST data
 *
 * @return array List of contact IDs per-source
 */
function rcmail_get_cids()
{
    // contact ID (or comma-separated list of IDs) is provided in two
    // forms. If _source is an empty string then the ID is a string
    // containing contact ID and source name in form: <ID>-<SOURCE>
    $cid    = get_input_value('_cid', RCUBE_INPUT_GPC);
    $source = get_input_value('_source', RCUBE_INPUT_GPC);
    if (!preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid)) {
        return array();
    }
    $cid        = explode(',', $cid);
    $got_source = strlen($source);
    $result     = array();
    // create per-source contact IDs array
    foreach ($cid as $id) {
        // if _source is not specified we'll find it from decoded ID
        if (!$got_source) {
            list ($c, $s) = explode('-', $id, 2);
            if (strlen($s)) {
                $result[$s][] = $c;
            }
            else if (strlen($source)) {
                $result[$source][] = $c;
            }
        }
        else {
            $result[$source][] = $id;
        }
    }
    return $result;
}
// register UI objects
$OUTPUT->add_handlers(array(
    'directorylist' => 'rcmail_directory_list',
program/steps/addressbook/groups.inc
@@ -19,12 +19,13 @@
*/
$source = get_input_value('_source', RCUBE_INPUT_GPC);
$CONTACTS = rcmail_contact_source($source, true);
if ($CONTACTS->readonly || !$CONTACTS->groups) {
  $OUTPUT->show_message('sourceisreadonly', 'warning');
  $OUTPUT->send();
}
$source = get_input_value('_source', RCUBE_INPUT_GPC);
if ($RCMAIL->action == 'group-addmembers') {
  if (($gid = get_input_value('_gid', RCUBE_INPUT_POST)) && ($ids = get_input_value('_cid', RCUBE_INPUT_POST))) {
program/steps/addressbook/import.inc
@@ -13,9 +13,10 @@
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <machniak@kolabsys.com>                   |
 +-----------------------------------------------------------------------+
 $Id: $
 $Id$
*/
@@ -29,11 +30,25 @@
  
  $attrib += array('id' => "rcmImportForm");
  
  $abook = new html_hiddenfield(array('name' => '_target', 'value' => $target));
  $form = $abook->show();
  $writable_books = $RCMAIL->get_address_sources(true);
  $upload = new html_inputfield(array('type' => 'file', 'name' => '_file', 'id' => 'rcmimportfile', 'size' => 40));
  $form .= html::p(null, html::label('rcmimportfile', rcube_label('importfromfile')) . html::br() . $upload->show());
  $form = html::p(null, html::label('rcmimportfile', rcube_label('importfromfile')) . $upload->show());
  // addressbook selector
  if (count($writable_books) > 1) {
    $select = new html_select(array('name' => '_target', 'id' => 'rcmimporttarget'));
    foreach ($writable_books as $book)
        $select->add($book['name'], $book['id']);
    $form .= html::p(null, html::label('rcmimporttarget', rcube_label('importtarget'))
        . $select->show($target));
  }
  else {
    $abook = new html_hiddenfield(array('name' => '_target', 'value' => key($writable_books)));
    $form .= $abook->show();
  }
  
  $check_replace = new html_checkbox(array('name' => '_replace', 'value' => 1, 'id' => 'rcmimportreplace'));
  $form .= html::p(null, $check_replace->show(get_input_value('_replace', RCUBE_INPUT_GPC)) .
program/steps/addressbook/list.inc
@@ -19,20 +19,68 @@
*/
// set message set for search result
// Use search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
    $CONTACTS->set_search_set($_SESSION['search'][$_REQUEST['_search']]);
{
    $search  = (array)$_SESSION['search'][$_REQUEST['_search']];
    $records = array();
    if (!empty($_GET['_page']))
        $page = intval($_GET['_page']);
    else
        $page = isset($_SESSION['page']) ? $_SESSION['page'] : 1;
    $_SESSION['page'] = $page;
    // Get records from all sources
    foreach ($search as $s => $set) {
        $source = $RCMAIL->get_address_book($s);
        // reset page
        $source->set_page(1);
        $source->set_pagesize(9999);
        $source->set_search_set($set);
        // get records
        $result = $source->list_records(array('name', 'email'));
        while ($row = $result->next()) {
            $row['sourceid'] = $s;
            $key = $row['name'] . ':' . $row['sourceid'];
            $records[$key] = $row;
        }
        unset($result);
    }
    // sort the records
    ksort($records, SORT_LOCALE_STRING);
    // create resultset object
    $count  = count($records);
    $first  = ($page-1) * $CONFIG['pagesize'];
    $result = new rcube_result_set($count, $first);
    // we need only records for current page
    if ($CONFIG['pagesize'] < $count) {
        $records = array_slice($records, $first, $CONFIG['pagesize']);
    }
    $result->records = array_values($records);
}
// List selected directory
else {
    $CONTACTS = rcmail_contact_source(null, true);
// get contacts for this user
$result = $CONTACTS->list_records(array('name'));
}
// update message count display
$OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($rowcount));
$OUTPUT->set_env('pagecount', ceil($result->count / $CONFIG['pagesize']));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
// create javascript list
rcmail_js_contacts_list($result);
// send response
$OUTPUT->send();
program/steps/addressbook/mailto.inc
@@ -19,19 +19,23 @@
*/
$cid = get_input_value('_cid', RCUBE_INPUT_POST);
$recipients = null;
$cids   = rcmail_get_cids();
$mailto = array();
if ($cid && preg_match('/^[a-z0-9\+\/=_-]+(,[a-z0-9\+\/=_-]+)*$/i', $cid) && $CONTACTS->ready)
foreach ($cids as $source => $cid)
{
    $CONTACTS = $RCMAIL->get_address_book($source);
    if ($CONTACTS->ready)
{
  $CONTACTS->set_page(1);
  $CONTACTS->set_pagesize(substr_count($cid, ',')+2); // +2 to skip counting query
  $recipients = $CONTACTS->search($CONTACTS->primary_key, $cid);
        $CONTACTS->set_pagesize(count($cid) + 2); // +2 to skip counting query
        $recipients = $CONTACTS->search($CONTACTS->primary_key, $cid, false, true, true, 'email');
  while (is_object($recipients) && ($rec = $recipients->iterate())) {
    $emails = $CONTACTS->get_col_values('email', $rec, true);
    $mailto[] = format_email_recipient($emails[0], $rec['name']);
        }
  }
}
@@ -42,10 +46,9 @@
  $_SESSION['mailto'][$mailto_id] = urlencode($mailto_str);
  $OUTPUT->redirect(array('task' => 'mail', '_action' => 'compose', '_mailto' => $mailto_id));
}
else
else {
  $OUTPUT->show_message('nocontactsfound', 'warning');
}
// send response
$OUTPUT->send();
program/steps/addressbook/save.inc
@@ -19,9 +19,11 @@
*/
$CONTACTS = rcmail_contact_source(null, true);
$cid = get_input_value('_cid', RCUBE_INPUT_POST);
$return_action = empty($cid) ? 'add' : 'edit';
// cannot edit record
if ($CONTACTS->readonly) {
  $OUTPUT->show_message('contactreadonly', 'error');
program/steps/addressbook/search.inc
@@ -31,17 +31,17 @@
function rcmail_contact_search()
{
    global $RCMAIL, $OUTPUT, $CONTACTS, $CONTACT_COLTYPES, $SEARCH_MODS_DEFAULT;
    global $RCMAIL, $OUTPUT, $CONFIG, $SEARCH_MODS_DEFAULT;
    $adv = isset($_POST['_adv']);
    // get fields/values from advanced search form
    if ($adv) {
        foreach ($CONTACT_COLTYPES as $col => $colprop) {
            $s = trim(get_input_value('_'.$col, RCUBE_INPUT_POST, true));
            if (strlen($s)) {
        foreach (array_keys($_POST) as $key) {
            $s = trim(get_input_value($key, RCUBE_INPUT_POST, true));
            if (strlen($s) && preg_match('/^_search_([a-zA-Z0-9_-]+)$/', $key, $m)) {
                $search[] = $s;
                $fields[] = $col;
                $fields[] = $m[1];
            }
        }
@@ -71,20 +71,77 @@
        }
    }
    // get sources list
    $sources    = $RCMAIL->get_address_sources();
    $search_set = array();
    $records    = array();
    foreach ($sources as $s) {
        $source = $RCMAIL->get_address_book($s['id']);
        // check if all search fields are supported....
        if (is_array($fields)) {
            $cols = $source->coltypes[0] ? array_flip($source->coltypes) : $source->coltypes;
            $supported = 0;
            foreach ($fields as $f) {
                if (array_key_exists($f, $cols)) {
                    $supported ++;
                }
            }
            // ...if not, we can skip this source
            if ($supported < count($fields)) {
                continue;
            }
        }
        // reset page
        $source->set_page(1);
        $source->set_pagesize(9999);
        // get contacts count
        $result = $source->search($fields, $search, false, false);
        if (!$result->count) {
            continue;
        }
        // get records
        $result = $source->list_records(array('name', 'email'));
        while ($row = $result->next()) {
            $row['sourceid'] = $s['id'];
            $key = $row['name'] . ':' . $row['sourceid'];
            $records[$key] = $row;
        }
        unset($result);
        $search_set[$s['id']] = $source->get_search_set();
    }
    // sort the records
    ksort($records, SORT_LOCALE_STRING);
    // create resultset object
    $count  = count($records);
    $result = new rcube_result_set($count);
    // cut first-page records
    if ($CONFIG['pagesize'] < $count) {
        $records = array_slice($records, 0, $CONFIG['pagesize']);
    }
    $result->records = array_values($records);
    // search request ID
    $search_request = md5('addr'
        .(is_array($fields) ? implode($fields, ',') : $fields)
        .(is_array($search) ? implode($search, ',') : $search));
    // reset page
    $CONTACTS->set_page(1);
    $_SESSION['page'] = 1;
    // get contacts for this user
    $result = $CONTACTS->search($fields, $search);
    // save search settings in session
    $_SESSION['search'][$search_request] = $CONTACTS->get_search_set();
    $_SESSION['search'][$search_request] = $search_set;
    $_SESSION['page'] = 1;
    if ($adv)
        $OUTPUT->command('list_contacts_clear');
@@ -99,8 +156,11 @@
    // update message count display
    $OUTPUT->command('set_env', 'search_request', $search_request);
    $OUTPUT->command('set_env', 'pagecount', ceil($result->count / $CONTACTS->page_size));
    $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text());
    $OUTPUT->command('set_env', 'pagecount', ceil($result->count / $CONFIG['pagesize']));
    $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
    // unselect currently selected directory/group
    $OUTPUT->command('unselect_directory');
    // send response
    $OUTPUT->send($adv ? 'iframe' : null);
@@ -108,7 +168,7 @@
function rcmail_contact_search_form($attrib)
{
    global $RCMAIL, $CONTACTS, $CONTACT_COLTYPES;
    global $RCMAIL, $CONTACT_COLTYPES;
    $i_size = !empty($attrib['size']) ? $attrib['size'] : 30;
@@ -130,7 +190,26 @@
        ),
    );
    foreach ($CONTACT_COLTYPES as $col => $colprop)
    // get supported coltypes from all address sources
    $sources  = $RCMAIL->get_address_sources();
    $coltypes = array();
    foreach ($sources as $s) {
        $CONTACTS = $RCMAIL->get_address_book($s['id']);
        if (is_array($CONTACTS->coltypes)) {
            $contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
            $coltypes = array_merge($coltypes, $contact_cols);
        }
    }
    // merge supported coltypes with $CONTACT_COLTYPES
    foreach ($coltypes as $col => $colprop) {
        $coltypes[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], (array)$colprop) : (array)$colprop;
    }
    // build form fields list
    foreach ($coltypes as $col => $colprop)
    {
        if ($colprop['type'] != 'image' && !$colprop['nosearch'])
        {
@@ -142,15 +221,13 @@
                $colprop['size'] = $i_size;
            $content  = html::div('row', html::div('contactfieldlabel label', Q($label))
                . html::div('contactfieldcontent', rcmail_get_edit_field($col, '', $colprop, $ftype)));
                . html::div('contactfieldcontent', rcmail_get_edit_field('search_'.$col, '', $colprop, $ftype)));
            $form[$category]['content'][] = $content;
        }
    }
    $hiddenfields = new html_hiddenfield(array(
        'name' => '_source', 'value' => get_input_value('_source', RCUBE_INPUT_GPC)));
    $hiddenfields->add(array('name' => '_gid', 'value' => $CONTACTS->group_id));
    $hiddenfields = new html_hiddenfield();
    $hiddenfields->add(array('name' => '_adv', 'value' => 1));
    $out = $RCMAIL->output->request_form(array(
program/steps/addressbook/show.inc
@@ -19,9 +19,16 @@
*/
// Get contact ID and source ID from request
$cids   = rcmail_get_cids();
$source = key($cids);
$cid    = array_shift($cids[$source]);
// Initialize addressbook source
$CONTACTS = rcmail_contact_source($source, true);
// read contact record
if (($cid = get_input_value('_cid', RCUBE_INPUT_GPC)) && ($record = $CONTACTS->get_record($cid, true))) {
if ($cid && ($record = $CONTACTS->get_record($cid, true))) {
    $OUTPUT->set_env('cid', $record['ID']);
}
@@ -195,9 +202,10 @@
}
//$OUTPUT->framed = $_framed;
$OUTPUT->add_handler('contacthead', 'rcmail_contact_head');
$OUTPUT->add_handler('contactdetails', 'rcmail_contact_details');
$OUTPUT->add_handler('contactphoto', 'rcmail_contact_photo');
$OUTPUT->add_handlers(array(
    'contacthead'    => 'rcmail_contact_head',
    'contactdetails' => 'rcmail_contact_details',
    'contactphoto'   => 'rcmail_contact_photo',
));
$OUTPUT->send('contact');