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


17 files modified
1029 ■■■■ changed files
CHANGELOG 2 ●●●●● patch | view | raw | blame | history
config/main.inc.php.dist 1 ●●●● patch | view | raw | blame | history
program/js/app.js 87 ●●●● 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 116 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/delete.inc 136 ●●●● patch | view | raw | blame | history
program/steps/addressbook/edit.inc 61 ●●●● patch | view | raw | blame | history
program/steps/addressbook/export.inc 85 ●●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 209 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/groups.inc 5 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/import.inc 65 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/list.inc 62 ●●●● patch | view | raw | blame | history
program/steps/addressbook/mailto.inc 39 ●●●● patch | view | raw | blame | history
program/steps/addressbook/save.inc 18 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/search.inc 119 ●●●● patch | view | raw | blame | history
program/steps/addressbook/show.inc 22 ●●●●● 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;
@@ -529,12 +529,12 @@
          if (this.env.trash_mailbox)
            this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage');
        }
        else if (this.task=='addressbook') {
        else if (this.task == 'addressbook') {
          if (!this.env.search_request || (props != this.env.source))
            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));
@@ -3817,7 +3842,7 @@
      qs += '&_gid='+urlencode(this.env.group);
    // also send search request to get the right records from the next page
    if (this.env.search_request)
    if (this.env.search_request)
      qs += '&_search='+this.env.search_request;
    // send request to server
@@ -4119,7 +4144,7 @@
            for (childcol in colprop.childs)
              cols.push(childcol);
          }
          for (var i=0; i < cols.length; i++) {
            childcol = cols[i];
            cp = colprop.childs[childcol];
@@ -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 });
          }
        }
@@ -5580,7 +5621,7 @@
    return frame_name;
  };
  // starts interval for keep-alive/check-recent signal
  this.start_keepalive = function()
  {
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,75 +23,91 @@
if (!$OUTPUT->ajax_call)
  return;
$cid = get_input_value('_cid', RCUBE_INPUT_POST);
$target = get_input_value('_to', 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;
  $TARGET = $RCMAIL->get_address_book($target);
$success = 0;
$maxnum  = $RCMAIL->config->get('max_group_members', 0);
  if ($TARGET && $TARGET->ready && !$TARGET->readonly) {
    $arr_cids = explode(',', $cid);
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) {
        break;
    }
    $ids = array();
    foreach ($arr_cids as $cid) {
      $a_record = $CONTACTS->get_record($cid, true);
    foreach ($cid as $cid) {
        $a_record = $CONTACTS->get_record($cid, true);
      // check if contact exists, if so, we'll need it's ID
      $result = $TARGET->search('email', $a_record['email'], true, true);
        // check if contact exists, if so, we'll need it's ID
        $result = $TARGET->search('email', $a_record['email'], true, true);
      // insert contact record
      if (!$result->count) {
        $plugin = $RCMAIL->plugins->exec_hook('contact_create', array(
          'record' => $a_record, 'source' => $target, 'group' => $target_group));
        // insert contact record
        if (!$result->count) {
            $plugin = $RCMAIL->plugins->exec_hook('contact_create', array(
                'record' => $a_record, 'source' => $target, 'group' => $target_group));
        if (!$plugin['abort']) {
          if ($insert_id = $TARGET->insert($plugin['record'], false)) {
            $ids[] = $insert_id;
            $success++;
          }
            if (!$plugin['abort']) {
                if ($insert_id = $TARGET->insert($plugin['record'], false)) {
                    $ids[] = $insert_id;
                    $success++;
                }
            }
            else if ($plugin['result']) {
                $ids = array_merge($ids, $plugin['result']);
                $success++;
            }
        }
        else if ($plugin['result']) {
          $ids = array_merge($ids, $plugin['result']);
          $success++;
        else {
            $record = $result->first();
            $ids[] = $record['ID'];
        }
      }
      else {
        $record = $result->first();
        $ids[] = $record['ID'];
      }
    }
    // assign to group
    if ($target_group && $TARGET->groups && !empty($ids)) {
      $plugin = $RCMAIL->plugins->exec_hook('group_addmembers', array(
        'group_id' => $target_group, 'ids' => $ids, 'source' => $target));
        $plugin = $RCMAIL->plugins->exec_hook('group_addmembers', array(
            'group_id' => $target_group, 'ids' => $ids, 'source' => $target));
      if (!$plugin['abort']) {
        $TARGET->reset();
        $TARGET->set_group($target_group);
        if (!$plugin['abort']) {
            $TARGET->reset();
            $TARGET->set_group($target_group);
        if (($maxnum = $RCMAIL->config->get('max_group_members', 0)) && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
          $OUTPUT->show_message('maxgroupmembersreached', 'warning', array('max' => $maxnum));
          $OUTPUT->send();
            if ($maxnum && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
                $OUTPUT->show_message('maxgroupmembersreached', 'warning', array('max' => $maxnum));
                $OUTPUT->send();
            }
            if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success)
                $success = $cnt;
        }
        if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success)
          $success = $cnt;
      }
      else if ($plugin['result'])
        $success = $plugin['result'];
        else if ($plugin['result']) {
            $success = $plugin['result'];
        }
    }
  }
  if ($success == 0)
    $OUTPUT->show_message('copyerror', 'error');
  else
    $OUTPUT->show_message('copysuccess', 'notice', array('nr' => $success));
}
if ($success == 0)
    $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 {
        $OUTPUT->show_message('contactdeleted', 'confirmation');
        // 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();
        // update message count display
        $OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));
        $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result->count));
        // 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));
        $delcnt += $deleted;
    }
    // send response
    $OUTPUT->send();
}
exit;
$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();
    // 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 / $CONFIG['pagesize']));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
// add new rows from next page (if any)
if (!empty($records)) {
    rcmail_js_contacts_list($records);
}
// send response
$OUTPUT->send();
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)))
    $OUTPUT->set_env('cid', $record['ID']);
    // Initialize addressbook
    $CONTACTS = rcmail_contact_source($source, true);
// adding not allowed here
if ($CONTACTS->readonly) {
    $OUTPUT->show_message('sourceisreadonly');
    rcmail_overwrite_action('show');
    return;
    // 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);
}
@@ -45,7 +68,7 @@
         $RCMAIL->output->show_message('contactnotfound');
         return false;
     }
     return $record;
}
@@ -90,7 +113,7 @@
    // add some labels to client
    $RCMAIL->output->add_label('noemailwarning', 'nonamewarning');
    // copy (parsed) address template to client
    if (preg_match_all('/\{([a-z0-9]+)\}([^{]*)/i', $RCMAIL->config->get('address_template', ''), $templ, PREG_SET_ORDER))
      $RCMAIL->output->set_env('address_template', $templ);
@@ -123,7 +146,7 @@
            ),
        ),
    );
    if (isset($CONTACT_COLTYPES['notes'])) {
        $form['notes'] = array(
            'name'    => rcube_label('notes'),
@@ -158,11 +181,11 @@
  if ($max_postsize && $max_postsize < $max_filesize)
    $max_filesize = $max_postsize;
  $max_filesize = show_bytes($max_filesize);
  $hidden = new html_hiddenfield(array('name' => '_cid', 'value' => $GLOBALS['cid']));
  $input = new html_inputfield(array('type' => 'file', 'name' => '_photo', 'size' => $attrib['size']));
  $button = new html_inputfield(array('type' => 'button'));
  $out = html::div($attrib,
    $OUTPUT->form_tag(array('name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'),
      $hidden->show() .
@@ -174,7 +197,7 @@
      )
    )
  );
  $OUTPUT->add_label('addphoto','replacephoto');
  $OUTPUT->add_gui_object('uploadbox', $attrib['id']);
  return $out;
@@ -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$
*/
// get contacts for this user
$CONTACTS->set_page(1);
$CONTACTS->set_pagesize(99999);
$result = $CONTACTS->list_records(null, 0, true);
// 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();
@@ -30,25 +71,25 @@
header('Content-Disposition: attachment; filename="rcube_contacts.vcf"');
while ($result && ($row = $result->next())) {
  // we already have a vcard record
  if ($row['vcard'] && $row['name']) {
    echo rcube_vcard::rfc2425_fold($row['vcard']) . "\n";
  }
  // copy values into vcard object
  else {
    $vcard = new rcube_vcard($row['vcard']);
    $vcard->reset();
    foreach ($row as $key => $values) {
      list($field, $section) = explode(':', $key);
      foreach ((array)$values as $value) {
        if (is_array($value) || strlen($value))
          $vcard->set($field, $value, strtoupper($section));
      }
    // we already have a vcard record
    if ($row['vcard'] && $row['name']) {
        echo rcube_vcard::rfc2425_fold($row['vcard']) . "\n";
    }
    // copy values into vcard object
    else {
        $vcard = new rcube_vcard($row['vcard']);
        $vcard->reset();
    echo $vcard->export(true) . "\n";
  }
        foreach ($row as $key => $values) {
            list($field, $section) = explode(':', $key);
            foreach ((array)$values as $value) {
                if (is_array($value) || strlen($value))
                    $vcard->set($field, $value, strtoupper($section));
            }
        }
        echo $vcard->export(true) . "\n";
    }
}
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,19 +56,93 @@
  // TODO: define fields for vcards like GEO, KEY
);
// reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object
if (is_array($CONTACTS->coltypes)) {
    // remove cols not listed by the backend class
    $contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
    $CONTACT_COLTYPES = array_intersect_key($CONTACT_COLTYPES, $contact_cols);
    // add associative coltypes definition
    if (!$CONTACTS->coltypes[0]) {
        foreach ($CONTACTS->coltypes as $col => $colprop)
            $CONTACT_COLTYPES[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], $colprop) : $colprop;
// 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);
}
$OUTPUT->set_env('photocol', is_array($CONTACT_COLTYPES['photo']));
// 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
        $contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
        $CONTACT_COLTYPES = array_intersect_key($CONTACT_COLTYPES, $contact_cols);
        // add associative coltypes definition
        if (!$CONTACTS->coltypes[0]) {
            foreach ($CONTACTS->coltypes as $col => $colprop)
                $CONTACT_COLTYPES[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], $colprop) : $colprop;
        }
    }
    $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]);
@@ -246,7 +285,7 @@
    if (!$attrib['id'])
        $attrib['id'] = 'rcmcontactframe';
    $attrib['name'] = $attrib['id'];
    $OUTPUT->set_env('contentframe', $attrib['name']);
@@ -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
    $result = $CONTACTS->get_result();
    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)
        ));
@@ -303,7 +344,7 @@
            && ($label = preg_replace('/(\d+)$/', '', $label))
            && rcube_label_exists($label))
        return rcube_label($label) . ' ' . $m[1];
    return ucfirst($type);
}
@@ -322,11 +363,11 @@
    $del_button = $attrib['deleteicon'] ? html::img(array('src' => $CONFIG['skin_path'] . $attrib['deleteicon'], 'alt' => rcube_label('delete'))) : rcube_label('delete');
    unset($attrib['deleteicon']);
    $out = '';
    // get default coltypes
    $coltypes = $GLOBALS['CONTACT_COLTYPES'];
    $coltype_labels = array();
    foreach ($coltypes as $col => $prop) {
        if ($prop['subtypes']) {
            $subtype_names = array_map('rcmail_get_type_label', $prop['subtypes']);
@@ -369,7 +410,7 @@
                    // skip cols unknown to the backend
                    if (!$coltypes[$col])
                        continue;
                    // only string values are expected here
                    if (is_array($record[$col]))
                        $record[$col] = join(' ', $record[$col]);
@@ -390,7 +431,7 @@
                }
                $content .= html::div($blockname, $fields);
            }
            if ($edit_mode)
                $content .= html::p('addfield', $select_add->show(null));
@@ -522,13 +563,13 @@
                    else   // row without label
                        $rows .= html::div('row', html::div('contactfield', $val));
                }
                // add option to the add-field menu
                if (!$colprop['limit'] || $coltypes[$field]['count'] < $colprop['limit']) {
                    $select_add->add($colprop['label'], $col);
                    $select_add->_count++;
                }
                // wrap rows in fieldgroup container
                $content .= html::tag('fieldset', array('class' => 'contactfieldgroup ' . ($colprop['subtypes'] ? 'contactfieldgroupmulti ' : '') . 'contactcontroller' . $col, 'style' => ($rows ? null : 'display:none')),
                  ($colprop['subtypes'] ? html::tag('legend', null, Q($colprop['label'])) : ' ') .
@@ -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$
*/
@@ -26,30 +27,44 @@
{
  global $RCMAIL, $OUTPUT;
  $target = get_input_value('_target', RCUBE_INPUT_GPC);
  $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)) .
    html::label('rcmimportreplace', rcube_label('importreplace')));
  $OUTPUT->add_label('selectimportfile','importwait');
  $OUTPUT->add_gui_object('importform', $attrib['id']);
  $out = html::p(null, Q(rcube_label('importtext'), 'show'));
  $out .= $OUTPUT->form_tag(array(
      'action' => $RCMAIL->url('import'),
      'method' => 'post',
      'enctype' => 'multipart/form-data') + $attrib,
    $form);
  return $out;
}
@@ -60,19 +75,19 @@
function rcmail_import_confirm($attrib)
{
  global $IMPORT_STATS;
  $vars = get_object_vars($IMPORT_STATS);
  $vars['names'] = $vars['skipped_names'] = '';
  $content = html::p(null, rcube_label(array(
      'name' => 'importconfirm',
      'nr' => $IMORT_STATS->inserted,
      'vars' => $vars,
    )) . ($IMPORT_STATS->names ? ':' : '.'));
  if ($IMPORT_STATS->names)
    $content .= html::p('em', join(', ', array_map('Q', $IMPORT_STATS->names)));
  if ($IMPORT_STATS->skipped) {
      $content .= html::p(null, rcube_label(array(
          'name' => 'importconfirmskipped',
@@ -81,7 +96,7 @@
        )) . ':');
      $content .= html::p('em', join(', ', array_map('Q', $IMPORT_STATS->skipped_names)));
  }
  return html::div($attrib, $content);
}
@@ -93,10 +108,10 @@
{
  global $IMPORT_STATS, $OUTPUT;
  $target = get_input_value('_target', RCUBE_INPUT_GPC);
  $attrib += array('type' => 'input');
  unset($attrib['name']);
  if (is_object($IMPORT_STATS)) {
    $attrib['class'] = trim($attrib['class'] . ' mainaction');
    $out = $OUTPUT->button(array('command' => 'list', 'prop' => $target, 'label' => 'done') + $attrib);
@@ -107,7 +122,7 @@
    $attrib['class'] = trim($attrib['class'] . ' mainaction');
    $out .= $OUTPUT->button(array('command' => 'import', 'label' => 'import') + $attrib);
  }
  return $out;
}
@@ -137,13 +152,13 @@
    $IMPORT_STATS->skipped_names = array();
    $IMPORT_STATS->count = count($vcards);
    $IMPORT_STATS->inserted = $IMPORT_STATS->skipped = $IMPORT_STATS->nomail = $IMPORT_STATS->errors = 0;
    if ($replace)
      $CONTACTS->delete_all();
    foreach ($vcards as $vcard) {
      $email = $vcard->email[0];
      // skip entries without an e-mail address
      if (empty($email)) {
        $IMPORT_STATS->nomail++;
@@ -152,7 +167,7 @@
      // We're using UTF8 internally
      $email = rcube_idn_to_utf8($email);
      if (!$replace && $email) {
        // compare e-mail address
        $existing = $CONTACTS->search('email', $email, false, false);
@@ -165,10 +180,10 @@
          continue;
        }
      }
      $a_record = $vcard->get_assoc();
      $a_record['vcard'] = $vcard->export();
      $plugin = $RCMAIL->plugins->exec_hook('contact_create', array('record' => $a_record, 'source' => null));
      $a_record = $plugin['record'];
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();
// get contacts for this user
$result = $CONTACTS->list_records(array('name'));
    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,33 +19,36 @@
*/
$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->set_page(1);
  $CONTACTS->set_pagesize(substr_count($cid, ',')+2); // +2 to skip counting query
  $recipients = $CONTACTS->search($CONTACTS->primary_key, $cid);
    $CONTACTS = $RCMAIL->get_address_book($source);
  while (is_object($recipients) && ($rec = $recipients->iterate())) {
    $emails = $CONTACTS->get_col_values('email', $rec, true);
    $mailto[] = format_email_recipient($emails[0], $rec['name']);
  }
    if ($CONTACTS->ready)
    {
        $CONTACTS->set_page(1);
        $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']);
        }
    }
}
if (!empty($mailto))
{
  $mailto_str = join(', ', $mailto);
  $mailto_id = substr(md5($mailto_str), 0, 16);
  $_SESSION['mailto'][$mailto_id] = urlencode($mailto_str);
  $OUTPUT->redirect(array('task' => 'mail', '_action' => 'compose', '_mailto' => $mailto_id));
    $mailto_str = join(', ', $mailto);
    $mailto_id = substr(md5($mailto_str), 0, 16);
    $_SESSION['mailto'][$mailto_id] = urlencode($mailto_str);
    $OUTPUT->redirect(array('task' => 'mail', '_action' => 'compose', '_mailto' => $mailto_id));
}
else
  $OUTPUT->show_message('nocontactsfound', 'warning');
else {
    $OUTPUT->show_message('nocontactsfound', 'warning');
}
// send response
$OUTPUT->send();
program/steps/addressbook/save.inc
@@ -19,8 +19,10 @@
*/
$cid = get_input_value('_cid', RCUBE_INPUT_POST);
$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) {
@@ -38,19 +40,19 @@
    if ($filepath = $_FILES['_photo']['tmp_name']) {
        // check file type and resize image
        $imageprop = rcmail::imageprops($_FILES['_photo']['tmp_name']);
        if ($imageprop['width'] && $imageprop['height']) {
            $maxsize = intval($RCMAIL->config->get('contact_photo_size', 160));
            $tmpfname = tempnam($RCMAIL->config->get('temp_dir'), 'rcmImgConvert');
            $save_hook = 'attachment_upload';
            // scale image to a maximum size
            if (($imageprop['width'] > $maxsize || $imageprop['height'] > $maxsize) &&
                  (rcmail::imageconvert(array('in' => $filepath, 'out' => $tmpfname, 'size' => $maxsize.'x'.$maxsize, 'type' => $imageprop['type'])) !== false)) {
                $filepath = $tmpfname;
                $save_hook = 'attachment_save';
            }
            // save uploaded file in storage backend
            $attachment = $RCMAIL->plugins->exec_hook($save_hook, array(
                'path' => $filepath,
@@ -87,10 +89,10 @@
            $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array('size' => show_bytes(parse_bytes($maxsize)))));
        else
            $msg = rcube_label('fileuploaderror');
        $OUTPUT->command('display_message', $msg, 'error');
    }
    $OUTPUT->command('photo_upload_end');
    $OUTPUT->send('iframe');
}
@@ -155,7 +157,7 @@
    }
    else
        unset($a_record['photo']);
    // cleanup session data
    $RCMAIL->plugins->exec_hook('attachments_cleanup', array('group' => 'contact'));
    $RCMAIL->session->remove('contacts');
@@ -240,7 +242,7 @@
        $CONTACTS->add_to_group($gid, $plugin['ids']);
      }
    }
    // add contact row or jump to the page where it should appear
    $CONTACTS->reset();
    $result = $CONTACTS->search($CONTACTS->primary_key, $insert_id);
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']);
}
@@ -41,7 +48,7 @@
        if (!preg_match('![^a-z0-9/=+-]!i', $data))
            $data = base64_decode($data, true);
    }
    header('Content-Type: ' . rc_image_content_type($data));
    echo $data ? $data : file_get_contents('program/blank.gif');
    exit;
@@ -190,14 +197,15 @@
    $form_end = '</form>';
    $RCMAIL->output->add_gui_object('editform', 'form');
    return $form_start . $table->show() . $form_end;
}
//$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');