summaryrefslogtreecommitdiff
path: root/webmail/program/steps/addressbook
diff options
context:
space:
mode:
Diffstat (limited to 'webmail/program/steps/addressbook')
-rw-r--r--webmail/program/steps/addressbook/copy.inc123
-rw-r--r--webmail/program/steps/addressbook/delete.inc165
-rw-r--r--webmail/program/steps/addressbook/edit.inc288
-rw-r--r--webmail/program/steps/addressbook/export.inc100
-rw-r--r--webmail/program/steps/addressbook/func.inc808
-rw-r--r--webmail/program/steps/addressbook/groups.inc135
-rw-r--r--webmail/program/steps/addressbook/import.inc282
-rw-r--r--webmail/program/steps/addressbook/list.inc94
-rw-r--r--webmail/program/steps/addressbook/mailto.inc78
-rw-r--r--webmail/program/steps/addressbook/save.inc228
-rw-r--r--webmail/program/steps/addressbook/search.inc341
-rw-r--r--webmail/program/steps/addressbook/show.inc248
-rw-r--r--webmail/program/steps/addressbook/undo.inc80
-rw-r--r--webmail/program/steps/addressbook/upload_photo.inc89
14 files changed, 3059 insertions, 0 deletions
diff --git a/webmail/program/steps/addressbook/copy.inc b/webmail/program/steps/addressbook/copy.inc
new file mode 100644
index 0000000..480a9b5
--- /dev/null
+++ b/webmail/program/steps/addressbook/copy.inc
@@ -0,0 +1,123 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/copy.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2007, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Copy a contact record from one direcotry to another |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+// only process ajax requests
+if (!$OUTPUT->ajax_call)
+ return;
+
+
+$cids = rcmail_get_cids();
+$target = get_input_value('_to', RCUBE_INPUT_POST);
+$target_group = get_input_value('_togid', RCUBE_INPUT_POST);
+
+$success = 0;
+$errormsg = 'copyerror';
+$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 ((string)$target == (string)$source) {
+ continue;
+ }
+
+ $CONTACTS = $RCMAIL->get_address_book($source);
+ $TARGET = $RCMAIL->get_address_book($target);
+
+ if (!$TARGET || !$TARGET->ready || $TARGET->readonly) {
+ break;
+ }
+
+ $ids = array();
+
+ foreach ($cid as $cid) {
+ $a_record = $CONTACTS->get_record($cid, true);
+
+ // Check if contact exists, if so, we'll need it's ID
+ // Note: Some addressbooks allows empty email address field
+ if (!empty($a_record['email']))
+ $result = $TARGET->search('email', $a_record['email'], 1, true, true);
+ else if (!empty($a_record['name']))
+ $result = $TARGET->search('name', $a_record['name'], 1, true, true);
+ else
+ $result = new rcube_result_set();
+
+ // 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++;
+ }
+ }
+ else if ($plugin['result']) {
+ $ids = array_merge($ids, $plugin['result']);
+ $success++;
+ }
+ }
+ else {
+ $record = $result->first();
+ $ids[] = $record['ID'];
+ $errormsg = empty($a_record['email']) ? 'contactnameexists' : 'contactexists';
+ }
+ }
+
+ // 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));
+
+ if (!$plugin['abort']) {
+ $TARGET->reset();
+ $TARGET->set_group($target_group);
+
+ 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;
+ }
+ else if ($plugin['result']) {
+ $success = $plugin['result'];
+ }
+
+ $errormsg = $plugin['message'] ? $plugin['message'] : 'copyerror';
+ }
+}
+
+if ($success == 0)
+ $OUTPUT->show_message($errormsg, 'error');
+else
+ $OUTPUT->show_message('copysuccess', 'notice', array('nr' => $success));
+
+// send response
+$OUTPUT->send();
diff --git a/webmail/program/steps/addressbook/delete.inc b/webmail/program/steps/addressbook/delete.inc
new file mode 100644
index 0000000..5611858
--- /dev/null
+++ b/webmail/program/steps/addressbook/delete.inc
@@ -0,0 +1,165 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/delete.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2009, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Delete the submitted contacts (CIDs) from the users address book |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+// process ajax requests only
+if (!$OUTPUT->ajax_call)
+ return;
+
+$cids = rcmail_get_cids();
+$delcnt = 0;
+
+// remove previous deletes
+$undo_time = $RCMAIL->config->get('undo_timeout', 0);
+$RCMAIL->session->remove('contact_undo');
+
+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');
+ $OUTPUT->send();
+ }
+ continue;
+ }
+
+ $plugin = $RCMAIL->plugins->exec_hook('contact_delete', array(
+ 'id' => $cid, 'source' => $source));
+
+ $deleted = !$plugin['abort'] ? $CONTACTS->delete($cid, $undo_time < 1) : $plugin['result'];
+
+ if (!$deleted) {
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'contactdelerror', 'error');
+ $OUTPUT->command('list_contacts');
+ $OUTPUT->send();
+ }
+ else {
+ $delcnt += $deleted;
+
+ // store deleted contacts IDs in session for undo action
+ if ($undo_time > 0 && $CONTACTS->undelete) {
+ $_SESSION['contact_undo']['data'][$source] = $cid;
+ }
+ }
+}
+
+$page = isset($_SESSION['page']) ? $_SESSION['page'] : 1;
+
+// update saved search after data changed
+if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) {
+ $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name');
+ $afields = $RCMAIL->config->get('contactlist_fields');
+ $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($afields);
+
+ if (!$result->count) {
+ unset($search[$s]);
+ continue;
+ }
+
+ while ($row = $result->next()) {
+ $row['sourceid'] = $s;
+ $key = rcube_addressbook::compose_contact_key($row, $sort_col);
+ $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) * $PAGE_SIZE;
+ $result = new rcube_result_set($count, $first);
+
+ // get records from the next page to add to the list
+ $pages = ceil((count($records) + $delcnt) / $PAGE_SIZE);
+ if ($_GET['_from'] != 'show' && $pages > 1 && $page < $pages) {
+ // sort the records
+ ksort($records, SORT_LOCALE_STRING);
+
+ $first += $PAGE_SIZE;
+ // create resultset object
+ $res = new rcube_result_set($count, $first - $delcnt);
+
+ if ($PAGE_SIZE < $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) / $PAGE_SIZE);
+ 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 / $PAGE_SIZE));
+$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
+
+if (!empty($_SESSION['contact_undo'])) {
+ $_SESSION['contact_undo']['ts'] = time();
+ $msg = html::span(null, rcube_label('contactdeleted'))
+ . ' ' . html::a(array('onclick' => JS_OBJECT_NAME.".command('undo', '', this)"), rcube_label('undo'));
+
+ $OUTPUT->show_message($msg, 'confirmation', null, true, $undo_time);
+}
+else {
+ $OUTPUT->show_message('contactdeleted', 'confirmation');
+}
+
+// add new rows from next page (if any)
+if (!empty($records)) {
+ rcmail_js_contacts_list($records);
+}
+
+// send response
+$OUTPUT->send();
diff --git a/webmail/program/steps/addressbook/edit.inc b/webmail/program/steps/addressbook/edit.inc
new file mode 100644
index 0000000..7ddd3e5
--- /dev/null
+++ b/webmail/program/steps/addressbook/edit.inc
@@ -0,0 +1,288 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/edit.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2007, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Show edit form for a contact entry or to add a new one |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+if ($RCMAIL->action == 'edit') {
+ // Get contact ID and source ID from request
+ $cids = rcmail_get_cids();
+ $source = key($cids);
+ $cid = array_shift($cids[$source]);
+
+ // Initialize addressbook
+ $CONTACTS = rcmail_contact_source($source, true);
+
+ // Contact edit
+ if ($cid && ($record = $CONTACTS->get_record($cid, true))) {
+ $OUTPUT->set_env('cid', $record['ID']);
+ }
+
+ // editing not allowed here
+ if ($CONTACTS->readonly || $record['readonly']) {
+ $OUTPUT->show_message('sourceisreadonly');
+ rcmail_overwrite_action('show');
+ return;
+ }
+}
+else {
+ $source = get_input_value('_source', RCUBE_INPUT_GPC);
+
+ if (strlen($source)) {
+ $CONTACTS = $RCMAIL->get_address_book($source, true);
+ }
+
+ if (!$CONTACTS || $CONTACTS->readonly) {
+ $CONTACTS = $RCMAIL->get_address_book(-1, true);
+ $source = $RCMAIL->get_address_book_id($CONTACTS);
+ }
+
+ // Initialize addressbook
+ $CONTACTS = rcmail_contact_source($source, true);
+}
+
+$SOURCE_ID = $source;
+rcmail_set_sourcename($CONTACTS);
+
+function rcmail_get_edit_record()
+{
+ global $RCMAIL, $CONTACTS;
+
+ // check if we have a valid result
+ if ($GLOBALS['EDIT_RECORD']) {
+ $record = $GLOBALS['EDIT_RECORD'];
+ }
+ else if ($RCMAIL->action != 'add'
+ && !(($result = $CONTACTS->get_result()) && ($record = $result->first()))
+ ) {
+ $RCMAIL->output->show_message('contactnotfound');
+ return false;
+ }
+
+ return $record;
+}
+
+function rcmail_contact_edithead($attrib)
+{
+ // check if we have a valid result
+ $record = rcmail_get_edit_record();
+ $i_size = !empty($attrib['size']) ? $attrib['size'] : 20;
+
+ $form = array(
+ 'head' => array(
+ 'content' => array(
+ 'prefix' => array('size' => $i_size),
+ 'firstname' => array('size' => $i_size, 'visible' => true),
+ 'middlename' => array('size' => $i_size),
+ 'surname' => array('size' => $i_size, 'visible' => true),
+ 'suffix' => array('size' => $i_size),
+ 'name' => array('size' => 2*$i_size),
+ 'nickname' => array('size' => 2*$i_size),
+ 'organization' => array('size' => 2*$i_size),
+ 'department' => array('size' => 2*$i_size),
+ 'jobtitle' => array('size' => 2*$i_size),
+ )
+ )
+ );
+
+ list($form_start, $form_end) = get_form_tags($attrib);
+ unset($attrib['form'], $attrib['name'], $attrib['size']);
+
+ // return the address edit form
+ $out = rcmail_contact_form($form, $record, $attrib);
+
+ return $form_start . $out . $form_end;
+}
+
+function rcmail_contact_editform($attrib)
+{
+ global $RCMAIL, $CONTACT_COLTYPES;
+
+ $record = rcmail_get_edit_record();
+
+ // 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);
+
+ $i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
+ $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 10;
+ $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40;
+
+ $form = array(
+ 'contact' => array(
+ 'name' => rcube_label('properties'),
+ 'content' => array(
+ 'email' => array('size' => $i_size, 'visible' => true),
+ 'phone' => array('size' => $i_size, 'visible' => true),
+ 'address' => array('visible' => true),
+ 'website' => array('size' => $i_size),
+ 'im' => array('size' => $i_size),
+ ),
+ ),
+ 'personal' => array(
+ 'name' => rcube_label('personalinfo'),
+ 'content' => array(
+ 'gender' => array('visible' => true),
+ 'maidenname' => array('size' => $i_size),
+ 'birthday' => array('visible' => true),
+ 'anniversary' => array(),
+ 'manager' => array('size' => $i_size),
+ 'assistant' => array('size' => $i_size),
+ 'spouse' => array('size' => $i_size),
+ ),
+ ),
+ );
+
+ if (isset($CONTACT_COLTYPES['notes'])) {
+ $form['notes'] = array(
+ 'name' => rcube_label('notes'),
+ 'content' => array(
+ 'notes' => array('size' => $t_cols, 'rows' => $t_rows, 'label' => false, 'visible' => true, 'limit' => 1),
+ ),
+ 'single' => true,
+ );
+ }
+
+ list($form_start, $form_end) = get_form_tags($attrib);
+ unset($attrib['form']);
+
+ // return the complete address edit form as table
+ $out = rcmail_contact_form($form, $record, $attrib);
+
+ return $form_start . $out . $form_end;
+}
+
+function rcmail_upload_photo_form($attrib)
+{
+ global $OUTPUT;
+
+ // set defaults
+ $attrib += array('id' => 'rcmUploadform', 'buttons' => 'yes');
+
+ // find max filesize value
+ $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
+ $max_postsize = parse_bytes(ini_get('post_max_size'));
+ 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('id' => $attrib['id'].'Frm', 'name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'),
+ $hidden->show() .
+ html::div(null, $input->show()) .
+ html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) .
+ (get_boolean($attrib['buttons']) ? html::div('buttons',
+ $button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' .
+ $button->show(rcube_label('upload'), array('class' => 'button mainaction', 'onclick' => JS_OBJECT_NAME . ".command('upload-photo', this.form)"))
+ ) : '')
+ )
+ );
+
+ $OUTPUT->add_label('addphoto','replacephoto');
+ $OUTPUT->add_gui_object('uploadform', $attrib['id'].'Frm');
+ return $out;
+}
+
+// similar function as in /steps/settings/edit_identity.inc
+function get_form_tags($attrib)
+{
+ global $CONTACTS, $EDIT_FORM, $RCMAIL, $SOURCE_ID;
+
+ $form_start = $form_end = '';
+
+ if (empty($EDIT_FORM)) {
+ $hiddenfields = new html_hiddenfield();
+
+ if ($RCMAIL->action == 'edit')
+ $hiddenfields->add(array('name' => '_source', 'value' => $SOURCE_ID));
+ $hiddenfields->add(array('name' => '_gid', 'value' => $CONTACTS->group_id));
+
+ if (($result = $CONTACTS->get_result()) && ($record = $result->first()))
+ $hiddenfields->add(array('name' => '_cid', 'value' => $record['ID']));
+
+ $form_start = $RCMAIL->output->request_form(array(
+ 'name' => "form", 'method' => "post",
+ 'task' => $RCMAIL->task, 'action' => 'save',
+ 'request' => 'save.'.intval($record['ID']),
+ 'noclose' => true) + $attrib, $hiddenfields->show());
+ $form_end = !strlen($attrib['form']) ? '</form>' : '';
+
+ $EDIT_FORM = !empty($attrib['form']) ? $attrib['form'] : 'form';
+ $RCMAIL->output->add_gui_object('editform', $EDIT_FORM);
+ }
+
+ return array($form_start, $form_end);
+}
+
+function rcmail_source_selector($attrib)
+{
+ global $RCMAIL, $SOURCE_ID;
+
+ $sources_list = $RCMAIL->get_address_sources(true, true);
+
+ if (count($sources_list) < 2) {
+ $source = $sources_list[$SOURCE_ID];
+ $hiddenfield = new html_hiddenfield(array('name' => '_source', 'value' => $SOURCE_ID));
+ return html::span($attrib, $source['name'] . $hiddenfield->show());
+ }
+
+ $attrib['name'] = '_source';
+ $attrib['is_escaped'] = true;
+ $attrib['onchange'] = JS_OBJECT_NAME . ".command('save', 'reload', this.form)";
+
+ $select = new html_select($attrib);
+
+ foreach ($sources_list as $source)
+ $select->add($source['name'], $source['id']);
+
+ return $select->show($SOURCE_ID);
+}
+
+
+/**
+ * Register container as active area to drop photos onto
+ */
+function rcmail_photo_drop_area($attrib)
+{
+ global $OUTPUT;
+
+ if ($attrib['id']) {
+ $OUTPUT->add_gui_object('filedrop', $attrib['id']);
+ $OUTPUT->set_env('filedrop', array('action' => 'upload-photo', 'fieldname' => '_photo', 'single' => 1, 'filter' => '^image/.+'));
+ }
+}
+
+
+$OUTPUT->add_handlers(array(
+ 'contactedithead' => 'rcmail_contact_edithead',
+ 'contacteditform' => 'rcmail_contact_editform',
+ 'contactphoto' => 'rcmail_contact_photo',
+ 'photouploadform' => 'rcmail_upload_photo_form',
+ 'sourceselector' => 'rcmail_source_selector',
+ 'filedroparea' => 'rcmail_photo_drop_area',
+));
+
+if ($RCMAIL->action == 'add' && $OUTPUT->template_exists('contactadd'))
+ $OUTPUT->send('contactadd');
+
+// this will be executed if no template for addcontact exists
+$OUTPUT->send('contactedit');
diff --git a/webmail/program/steps/addressbook/export.inc b/webmail/program/steps/addressbook/export.inc
new file mode 100644
index 0000000..11c9ca4
--- /dev/null
+++ b/webmail/program/steps/addressbook/export.inc
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/export.inc |
+ | |
+ | 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 General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Export the selected address book as vCard file |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ | Author: Aleksander Machniak <machniak@kolabsys.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+// Use search result
+if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
+{
+ $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name');
+ $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 = rcube_addressbook::compose_contact_key($row, $sort_col);
+ $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
+header('Content-Type: text/x-vcard; charset='.RCMAIL_CHARSET);
+header('Content-Disposition: attachment; filename="rcube_contacts.vcf"');
+
+while ($result && ($row = $result->next())) {
+ // we already have a vcard record
+ if ($row['vcard'] && $row['name']) {
+ // fix folding and end-of-line chars
+ $row['vcard'] = preg_replace('/\r|\n\s+/', '', $row['vcard']);
+ $row['vcard'] = preg_replace('/\n/', rcube_vcard::$eol, $row['vcard']);
+ echo rcube_vcard::rfc2425_fold($row['vcard']) . rcube_vcard::$eol;
+ }
+ // copy values into vcard object
+ else {
+ $vcard = new rcube_vcard();
+ $vcard->extend_fieldmap($CONTACTS->vcard_map);
+ $vcard->load($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));
+ }
+ }
+
+ echo $vcard->export(true) . rcube_vcard::$eol;
+ }
+}
+
+exit;
diff --git a/webmail/program/steps/addressbook/func.inc b/webmail/program/steps/addressbook/func.inc
new file mode 100644
index 0000000..989b7c1
--- /dev/null
+++ b/webmail/program/steps/addressbook/func.inc
@@ -0,0 +1,808 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/func.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Provide addressbook functionality and GUI objects |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+$SEARCH_MODS_DEFAULT = array('name'=>1, 'firstname'=>1, 'surname'=>1, 'email'=>1, '*'=>1);
+
+// general definition of contact coltypes
+$CONTACT_COLTYPES = array(
+ 'name' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('name'), 'category' => 'main'),
+ 'firstname' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('firstname'), 'category' => 'main'),
+ 'surname' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('surname'), 'category' => 'main'),
+ 'email' => array('type' => 'text', 'size' => 40, 'maxlength' => 254, 'label' => rcube_label('email'), 'subtypes' => array('home','work','other'), 'category' => 'main'),
+ 'middlename' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('middlename'), 'category' => 'main'),
+ 'prefix' => array('type' => 'text', 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => rcube_label('nameprefix'), 'category' => 'main'),
+ 'suffix' => array('type' => 'text', 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => rcube_label('namesuffix'), 'category' => 'main'),
+ 'nickname' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('nickname'), 'category' => 'main'),
+ 'jobtitle' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('jobtitle'), 'category' => 'main'),
+ 'organization' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('organization'), 'category' => 'main'),
+ 'department' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('department'), 'category' => 'main'),
+ 'gender' => array('type' => 'select', 'limit' => 1, 'label' => rcube_label('gender'), 'options' => array('male' => rcube_label('male'), 'female' => rcube_label('female')), 'category' => 'personal'),
+ 'maidenname' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('maidenname'), 'category' => 'personal'),
+ 'phone' => array('type' => 'text', 'size' => 40, 'maxlength' => 20, 'label' => rcube_label('phone'), 'subtypes' => array('home','home2','work','work2','mobile','main','homefax','workfax','car','pager','video','assistant','other'), 'category' => 'main'),
+ 'address' => array('type' => 'composite', 'label' => rcube_label('address'), 'subtypes' => array('home','work','other'), 'childs' => array(
+ 'street' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('street'), 'category' => 'main'),
+ 'locality' => array('type' => 'text', 'size' => 28, 'maxlength' => 50, 'label' => rcube_label('locality'), 'category' => 'main'),
+ 'zipcode' => array('type' => 'text', 'size' => 8, 'maxlength' => 15, 'label' => rcube_label('zipcode'), 'category' => 'main'),
+ 'region' => array('type' => 'text', 'size' => 12, 'maxlength' => 50, 'label' => rcube_label('region'), 'category' => 'main'),
+ 'country' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('country'), 'category' => 'main'),
+ ), 'category' => 'main'),
+ 'birthday' => array('type' => 'date', 'size' => 12, 'maxlength' => 16, 'label' => rcube_label('birthday'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col', 'category' => 'personal'),
+ 'anniversary' => array('type' => 'date', 'size' => 12, 'maxlength' => 16, 'label' => rcube_label('anniversary'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col', 'category' => 'personal'),
+ 'website' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('website'), 'subtypes' => array('homepage','work','blog','profile','other'), 'category' => 'main'),
+ 'im' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('instantmessenger'), 'subtypes' => array('aim','icq','msn','yahoo','jabber','skype','other'), 'category' => 'main'),
+ 'notes' => array('type' => 'textarea', 'size' => 40, 'rows' => 15, 'maxlength' => 500, 'label' => rcube_label('notes'), 'limit' => 1),
+ 'photo' => array('type' => 'image', 'limit' => 1, 'category' => 'main'),
+ 'assistant' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('assistant'), 'category' => 'personal'),
+ 'manager' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('manager'), 'category' => 'personal'),
+ 'spouse' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('spouse'), 'category' => 'personal'),
+ // TODO: define fields for vcards like GEO, KEY
+);
+
+$PAGE_SIZE = $RCMAIL->config->get('addressbook_pagesize', $RCMAIL->config->get('pagesize', 50));
+
+// Addressbook UI
+if (!$RCMAIL->action && !$OUTPUT->ajax_call) {
+ // add list of address sources to client env
+ $js_list = $RCMAIL->get_address_sources();
+
+ // count all/writeable sources
+ $writeable = 0;
+ $count = 0;
+ foreach ($js_list as $sid => $s) {
+ $count++;
+ if (!$s['readonly']) {
+ $writeable++;
+ }
+ // unset hidden sources
+ if ($s['hidden']) {
+ unset($js_list[$sid]);
+ }
+ }
+
+ $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_env('writable_source', $writeable);
+ $OUTPUT->set_env('compose_extwin', $RCMAIL->config->get('compose_extwin',false));
+
+ $OUTPUT->set_pagetitle(rcube_label('addressbook'));
+ $_SESSION['addressbooks_count'] = $count;
+ $_SESSION['addressbooks_count_writeable'] = $writeable;
+
+ // select address book
+ $source = get_input_value('_source', RCUBE_INPUT_GPC);
+
+ // use first directory by default
+ if (!strlen($source) || !isset($js_list[$source])) {
+ $source = $RCMAIL->config->get('default_addressbook');
+ if (!strlen($source) || !isset($js_list[$source])) {
+ $source = strval(key($js_list));
+ }
+ }
+
+ $CONTACTS = rcmail_contact_source($source, true);
+}
+
+// remove undo information...
+if ($undo = $_SESSION['contact_undo']) {
+ // ...after timeout
+ $undo_time = $RCMAIL->config->get('undo_timeout', 0);
+ if ($undo['ts'] < time() - $undo_time)
+ $RCMAIL->session->remove('contact_undo');
+}
+
+// instantiate a contacts object according to the given source
+function rcmail_contact_source($source=null, $init_env=false, $writable=false)
+{
+ global $RCMAIL, $OUTPUT, $CONTACT_COLTYPES, $PAGE_SIZE;
+
+ if (!strlen($source)) {
+ $source = get_input_value('_source', RCUBE_INPUT_GPC);
+ }
+
+ // Get object
+ $CONTACTS = $RCMAIL->get_address_book($source, $writable);
+ $CONTACTS->set_pagesize($PAGE_SIZE);
+
+ // 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) {
+ if (is_array($colprop['childs'])) {
+ foreach ($colprop['childs'] as $childcol => $childprop)
+ $colprop['childs'][$childcol] = array_merge((array)$CONTACT_COLTYPES[$col]['childs'][$childcol], $childprop);
+ }
+ $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_set_sourcename($abook)
+{
+ global $OUTPUT;
+
+ // get address book name (for display)
+ if ($abook && $_SESSION['addressbooks_count'] > 1) {
+ $name = $abook->get_name();
+ if (!$name) {
+ $name = rcube_label('personaladrbook');
+ }
+ $OUTPUT->set_env('sourcename', html_entity_decode($name, ENT_COMPAT, 'UTF-8'));
+ }
+}
+
+
+function rcmail_directory_list($attrib)
+{
+ global $RCMAIL, $OUTPUT;
+
+ if (!$attrib['id'])
+ $attrib['id'] = 'rcmdirectorylist';
+
+ $out = '';
+ $local_id = '0';
+ $jsdata = array();
+
+ $line_templ = html::tag('li', array(
+ 'id' => 'rcmli%s', 'class' => '%s'),
+ html::a(array('href' => '%s',
+ 'rel' => '%s',
+ 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
+
+ $sources = (array) $OUTPUT->get_env('address_sources');
+ reset($sources);
+
+ // currently selected source
+ $current = get_input_value('_source', RCUBE_INPUT_GPC);
+
+ foreach ($sources as $j => $source) {
+ $id = strval(strlen($source['id']) ? $source['id'] : $j);
+ $js_id = JQ($id);
+
+ // set class name(s)
+ $class_name = 'addressbook';
+ if ($current === $id)
+ $class_name .= ' selected';
+ if ($source['readonly'])
+ $class_name .= ' readonly';
+ if ($source['class_name'])
+ $class_name .= ' ' . $source['class_name'];
+
+ $name = !empty($source['name']) ? $source['name'] : $id;
+ $out .= sprintf($line_templ,
+ html_identifier($id),
+ $class_name,
+ Q(rcmail_url(null, array('_source' => $id))),
+ $source['id'],
+ $js_id, $name);
+
+ $groupdata = array('out' => $out, 'jsdata' => $jsdata, 'source' => $id);
+ if ($source['groups'])
+ $groupdata = rcmail_contact_groups($groupdata);
+ $jsdata = $groupdata['jsdata'];
+ $out = $groupdata['out'];
+ }
+
+ $line_templ = html::tag('li', array(
+ 'id' => 'rcmliS%s', 'class' => '%s'),
+ html::a(array('href' => '#', 'rel' => 'S%s',
+ 'onclick' => "return ".JS_OBJECT_NAME.".command('listsearch', '%s', this)"), '%s'));
+
+ // Saved searches
+ $sources = $RCMAIL->user->list_searches(rcube_user::SEARCH_ADDRESSBOOK);
+ foreach ($sources as $j => $source) {
+ $id = $source['id'];
+ $js_id = JQ($id);
+
+ // set class name(s)
+ $class_name = 'contactsearch';
+ if ($current === $id)
+ $class_name .= ' selected';
+ if ($source['class_name'])
+ $class_name .= ' ' . $source['class_name'];
+
+ $out .= sprintf($line_templ,
+ html_identifier($id),
+ $class_name,
+ $id,
+ $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id)));
+ }
+
+ $OUTPUT->set_env('contactgroups', $jsdata);
+ $OUTPUT->add_gui_object('folderlist', $attrib['id']);
+ // add some labels to client
+ $OUTPUT->add_label('deletegroupconfirm', 'groupdeleting', 'addingmember', 'removingmember');
+
+ return html::tag('ul', $attrib, $out, html::$common_attrib);
+}
+
+
+function rcmail_contact_groups($args)
+{
+ global $RCMAIL;
+
+ $groups = $RCMAIL->get_address_book($args['source'])->list_groups();
+
+ if (!empty($groups)) {
+ $line_templ = html::tag('li', array(
+ 'id' => 'rcmliG%s', 'class' => 'contactgroup'),
+ html::a(array('href' => '#',
+ 'rel' => '%s:%s',
+ 'onclick' => "return ".JS_OBJECT_NAME.".command('listgroup',{'source':'%s','id':'%s'},this)"), '%s'));
+
+ $jsdata = array();
+ foreach ($groups as $group) {
+ $args['out'] .= sprintf($line_templ,
+ html_identifier($args['source'] . $group['ID']),
+ $args['source'], $group['ID'],
+ $args['source'], $group['ID'], Q($group['name'])
+ );
+ $args['jsdata']['G'.$args['source'].$group['ID']] = array(
+ 'source' => $args['source'], 'id' => $group['ID'],
+ 'name' => $group['name'], 'type' => 'group');
+ }
+ }
+
+ return $args;
+}
+
+
+// return the contacts list as HTML table
+function rcmail_contacts_list($attrib)
+{
+ global $CONTACTS, $OUTPUT;
+
+ // define list of cols to be displayed
+ $a_show_cols = array('name');
+
+ // add id to message list table if not specified
+ if (!strlen($attrib['id']))
+ $attrib['id'] = 'rcmAddressList';
+
+ // create XHTML table
+ $out = rcube_table_output($attrib, array(), $a_show_cols, $CONTACTS->primary_key);
+
+ // set client env
+ $OUTPUT->add_gui_object('contactslist', $attrib['id']);
+ $OUTPUT->set_env('current_page', (int)$CONTACTS->list_page);
+ $OUTPUT->include_script('list.js');
+
+ // add some labels to client
+ $OUTPUT->add_label('deletecontactconfirm', 'copyingcontact', 'contactdeleting');
+
+ return $out;
+}
+
+
+function rcmail_js_contacts_list($result, $prefix='')
+{
+ global $OUTPUT;
+
+ if (empty($result) || $result->count == 0)
+ return;
+
+ // define list of cols to be displayed
+ $a_show_cols = array('name');
+
+ while ($row = $result->next()) {
+ $a_row_cols = array();
+ $classes = array('person'); // org records will follow some day
+
+ // 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) {
+ $val = $col == 'name' ? rcube_addressbook::compose_list_name($row) : $row[$col];
+ $a_row_cols[$col] = Q($val);
+ }
+
+ if ($row['readonly'])
+ $classes[] = 'readonly';
+
+ $OUTPUT->command($prefix.'add_contact_row', $row['ID'], $a_row_cols, join(' ', $classes));
+ }
+}
+
+
+// similar function as /steps/settings/identities.inc::rcmail_identity_frame()
+function rcmail_contact_frame($attrib)
+{
+ global $OUTPUT;
+
+ if (!$attrib['id'])
+ $attrib['id'] = 'rcmcontactframe';
+
+ return $OUTPUT->frame($attrib, true);
+}
+
+
+function rcmail_rowcount_display($attrib)
+{
+ global $OUTPUT;
+
+ if (!$attrib['id'])
+ $attrib['id'] = 'rcmcountdisplay';
+
+ $OUTPUT->add_gui_object('countdisplay', $attrib['id']);
+
+ if ($attrib['label'])
+ $_SESSION['contactcountdisplay'] = $attrib['label'];
+
+ return html::span($attrib, rcube_label('loading'));
+}
+
+
+function rcmail_get_rowcount_text($result=null)
+{
+ global $CONTACTS, $PAGE_SIZE;
+
+ // read nr of contacts
+ if (!$result) {
+ $result = $CONTACTS->get_result();
+ }
+
+ if ($result->count == 0)
+ $out = rcube_label('nocontactsfound');
+ else
+ $out = rcube_label(array(
+ 'name' => $_SESSION['contactcountdisplay'] ? $_SESSION['contactcountdisplay'] : 'contactsfromto',
+ 'vars' => array(
+ 'from' => $result->first + 1,
+ 'to' => min($result->count, $result->first + $PAGE_SIZE),
+ 'count' => $result->count)
+ ));
+
+ return $out;
+}
+
+
+function rcmail_get_type_label($type)
+{
+ $label = 'type'.$type;
+ if (rcube_label_exists($label, '*', $domain))
+ return rcube_label($label, $domain);
+ else if (preg_match('/\w+(\d+)$/', $label, $m)
+ && ($label = preg_replace('/(\d+)$/', '', $label))
+ && rcube_label_exists($label, '*', $domain))
+ return rcube_label($label, $domain) . ' ' . $m[1];
+
+ return ucfirst($type);
+}
+
+
+function rcmail_contact_form($form, $record, $attrib = null)
+{
+ global $RCMAIL, $CONFIG;
+
+ // Allow plugins to modify contact form content
+ $plugin = $RCMAIL->plugins->exec_hook('contact_form', array(
+ 'form' => $form, 'record' => $record));
+
+ $form = $plugin['form'];
+ $record = $plugin['record'];
+ $edit_mode = $RCMAIL->action != 'show';
+ $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']);
+ $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype'));
+ $select_subtype->add($subtype_names, $prop['subtypes']);
+ $coltypes[$col]['subtypes_select'] = $select_subtype->show();
+ }
+ if ($prop['childs']) {
+ foreach ($prop['childs'] as $childcol => $cp)
+ $coltype_labels[$childcol] = array('label' => $cp['label']);
+ }
+ }
+
+ foreach ($form as $section => $fieldset) {
+ // skip empty sections
+ if (empty($fieldset['content']))
+ continue;
+
+ $select_add = new html_select(array('class' => 'addfieldmenu', 'rel' => $section));
+ $select_add->add(rcube_label('addfield'), '');
+
+ // render head section with name fields (not a regular list of rows)
+ if ($section == 'head') {
+ $content = '';
+
+ // unset display name if it is composed from name parts
+ if ($record['name'] == rcube_addressbook::compose_display_name(array('name' => '') + (array)$record))
+ unset($record['name']);
+
+ // group fields
+ $field_blocks = array(
+ 'names' => array('prefix','firstname','middlename','surname','suffix'),
+ 'displayname' => array('name'),
+ 'nickname' => array('nickname'),
+ 'organization' => array('organization'),
+ 'department' => array('department'),
+ 'jobtitle' => array('jobtitle'),
+ );
+ foreach ($field_blocks as $blockname => $colnames) {
+ $fields = '';
+ foreach ($colnames as $col) {
+ // 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]);
+
+ if ($RCMAIL->action == 'show') {
+ if (!empty($record[$col]))
+ $fields .= html::span('namefield ' . $col, Q($record[$col])) . " ";
+ }
+ else {
+ $colprop = (array)$fieldset['content'][$col] + (array)$coltypes[$col];
+ $colprop['id'] = 'ff_'.$col;
+ if (empty($record[$col]) && !$colprop['visible']) {
+ $colprop['style'] = 'display:none';
+ $select_add->add($colprop['label'], $col);
+ }
+ $fields .= rcmail_get_edit_field($col, $record[$col], $colprop, $colprop['type']);
+ }
+ }
+ $content .= html::div($blockname, $fields);
+ }
+
+ if ($edit_mode)
+ $content .= html::p('addfield', $select_add->show(null));
+
+ $out .= html::tag('fieldset', $attrib, (!empty($fieldset['name']) ? html::tag('legend', null, Q($fieldset['name'])) : '') . $content) ."\n";
+ continue;
+ }
+
+ $content = '';
+ if (is_array($fieldset['content'])) {
+ foreach ($fieldset['content'] as $col => $colprop) {
+ // remove subtype part of col name
+ list($field, $subtype) = explode(':', $col);
+ if (!$subtype) $subtype = 'home';
+ $fullkey = $col.':'.$subtype;
+
+ // skip cols unknown to the backend
+ if (!$coltypes[$field])
+ continue;
+
+ // merge colprop with global coltype configuration
+ $colprop += $coltypes[$field];
+ $label = isset($colprop['label']) ? $colprop['label'] : rcube_label($col);
+
+ // prepare subtype selector in edit mode
+ if ($edit_mode && is_array($colprop['subtypes'])) {
+ $subtype_names = array_map('rcmail_get_type_label', $colprop['subtypes']);
+ $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype'));
+ $select_subtype->add($subtype_names, $colprop['subtypes']);
+ }
+ else
+ $select_subtype = null;
+
+ if (!empty($colprop['value'])) {
+ $values = (array)$colprop['value'];
+ }
+ else {
+ // iterate over possible subtypes and collect values with their subtype
+ if (is_array($colprop['subtypes'])) {
+ $values = $subtypes = array();
+ foreach ($colprop['subtypes'] as $i => $st) {
+ $newval = false;
+ if ($record[$field.':'.$st]) {
+ $subtypes[count($values)] = $st;
+ $newval = $record[$field.':'.$st];
+ }
+ else if ($i == 0 && $record[$field]) {
+ $subtypes[count($values)] = $st;
+ $newval = $record[$field];
+ }
+ if ($newval !== false) {
+ if (is_array($newval) && isset($newval[0]))
+ $values = array_merge($values, $newval);
+ else
+ $values[] = $newval;
+ }
+ }
+ }
+ else {
+ $values = $record[$fullkey] ? $record[$fullkey] : $record[$field];
+ $subtypes = null;
+ }
+ }
+
+ // hack: create empty values array to force this field to be displayed
+ if (empty($values) && $colprop['visible'])
+ $values[] = '';
+
+ if (!is_array($values)) {
+ // $values can be an object, don't use (array)$values syntax
+ $values = !empty($values) ? array($values) : array();
+ }
+
+ $rows = '';
+ foreach ($values as $i => $val) {
+ if ($subtypes[$i])
+ $subtype = $subtypes[$i];
+
+ // render composite field
+ if ($colprop['type'] == 'composite') {
+ $composite = array(); $j = 0;
+ $template = $RCMAIL->config->get($col . '_template', '{'.join('} {', array_keys($colprop['childs'])).'}');
+ foreach ($colprop['childs'] as $childcol => $cp) {
+ if (!empty($val) && is_array($val)) {
+ $childvalue = $val[$childcol] ? $val[$childcol] : $val[$j];
+ }
+ else {
+ $childvalue = '';
+ }
+
+ if ($edit_mode) {
+ if ($colprop['subtypes'] || $colprop['limit'] != 1) $cp['array'] = true;
+ $composite['{'.$childcol.'}'] = rcmail_get_edit_field($childcol, $childvalue, $cp, $cp['type']) . " ";
+ }
+ else {
+ $childval = $cp['render_func'] ? call_user_func($cp['render_func'], $childvalue, $childcol) : Q($childvalue);
+ $composite['{'.$childcol.'}'] = html::span('data ' . $childcol, $childval) . " ";
+ }
+ $j++;
+ }
+
+ $coltypes[$field] += (array)$colprop;
+ $coltypes[$field]['count']++;
+ $val = preg_replace('/\{\w+\}/', '', strtr($template, $composite));
+ }
+ else if ($edit_mode) {
+ // call callback to render/format value
+ if ($colprop['render_func'])
+ $val = call_user_func($colprop['render_func'], $val, $col);
+
+ $coltypes[$field] = (array)$colprop + $coltypes[$field];
+
+ if ($colprop['subtypes'] || $colprop['limit'] != 1)
+ $colprop['array'] = true;
+
+ // load jquery UI datepicker for date fields
+ if ($colprop['type'] == 'date') {
+ $colprop['class'] .= ($colprop['class'] ? ' ' : '') . 'datepicker';
+ if (!$colprop['render_func'])
+ $val = rcmail_format_date_col($val);
+ }
+
+ $val = rcmail_get_edit_field($col, $val, $colprop, $colprop['type']);
+ $coltypes[$field]['count']++;
+ }
+ else if ($colprop['render_func'])
+ $val = call_user_func($colprop['render_func'], $val, $col);
+ else if (is_array($colprop['options']) && isset($colprop['options'][$val]))
+ $val = $colprop['options'][$val];
+ else
+ $val = Q($val);
+
+ // use subtype as label
+ if ($colprop['subtypes'])
+ $label = rcmail_get_type_label($subtype);
+
+ // add delete button/link
+ if ($edit_mode && !($colprop['visible'] && $colprop['limit'] == 1))
+ $val .= html::a(array('href' => '#del', 'class' => 'contactfieldbutton deletebutton', 'title' => rcube_label('delete'), 'rel' => $col), $del_button);
+
+ // display row with label
+ if ($label) {
+ $rows .= html::div('row',
+ html::div('contactfieldlabel label', $select_subtype ? $select_subtype->show($subtype) : Q($label)) .
+ html::div('contactfieldcontent '.$colprop['type'], $val));
+ }
+ 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
+ if ($rows) {
+ $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'])) : ' ') .
+ $rows);
+ }
+ }
+
+ if (!$content && (!$edit_mode || !$select_add->_count))
+ continue;
+
+ // also render add-field selector
+ if ($edit_mode)
+ $content .= html::p('addfield', $select_add->show(null, array('style' => $select_add->_count ? null : 'display:none')));
+
+ $content = html::div(array('id' => 'contactsection' . $section), $content);
+ }
+ else {
+ $content = $fieldset['content'];
+ }
+
+ if ($content)
+ $out .= html::tag('fieldset', null, html::tag('legend', null, Q($fieldset['name'])) . $content) ."\n";
+ }
+
+ if ($edit_mode) {
+ $RCMAIL->output->set_env('coltypes', $coltypes + $coltype_labels);
+ $RCMAIL->output->set_env('delbutton', $del_button);
+ $RCMAIL->output->add_label('delete');
+ }
+
+ return $out;
+}
+
+
+function rcmail_contact_photo($attrib)
+{
+ global $SOURCE_ID, $CONTACTS, $CONTACT_COLTYPES, $RCMAIL, $CONFIG;
+
+ if ($result = $CONTACTS->get_result())
+ $record = $result->first();
+
+ $photo_img = $attrib['placeholder'] ? $CONFIG['skin_path'] . $attrib['placeholder'] : 'program/resources/blank.gif';
+ $RCMAIL->output->set_env('photo_placeholder', $photo_img);
+ unset($attrib['placeholder']);
+
+ $plugin = $RCMAIL->plugins->exec_hook('contact_photo', array('record' => $record, 'data' => $record['photo']));
+
+ if ($plugin['url'])
+ $photo_img = $plugin['url'];
+ else if (preg_match('!^https?://!i', $record['photo']))
+ $photo_img = $record['photo'];
+ else if ($record['photo'])
+ $photo_img = $RCMAIL->url(array('_action' => 'photo', '_cid' => $record['ID'], '_source' => $SOURCE_ID));
+ else
+ $ff_value = '-del-'; // will disable delete-photo action
+
+ $img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => ''));
+ $content = html::div($attrib, $img);
+
+ if ($CONTACT_COLTYPES['photo'] && ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add')) {
+ $RCMAIL->output->add_gui_object('contactphoto', $attrib['id']);
+ $hidden = new html_hiddenfield(array('name' => '_photo', 'id' => 'ff_photo', 'value' => $ff_value));
+ $content .= $hidden->show();
+ }
+
+ return $content;
+}
+
+
+function rcmail_format_date_col($val)
+{
+ global $RCMAIL;
+ return format_date($val, $RCMAIL->config->get('date_format', 'Y-m-d'), false);
+}
+
+
+/**
+ * Returns contact ID(s) and source(s) from GET/POST data
+ *
+ * @return array List of contact IDs per-source
+ */
+function rcmail_get_cids($filter = null)
+{
+ // 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 = (string) get_input_value('_source', RCUBE_INPUT_GPC);
+
+ if (is_array($cid)) {
+ return $cid;
+ }
+
+ 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) {
+ // extract source ID from contact ID (it's there in search mode)
+ // see #1488959 and #1488862 for reference
+ if (!$got_source) {
+ if ($sep = strrpos($id, '-')) {
+ $contact_id = substr($id, 0, $sep);
+ $source_id = (string) substr($id, $sep+1);
+ if (strlen($source_id)) {
+ $result[$source_id][] = $contact_id;
+ }
+ }
+ }
+ else {
+ if (substr($id, -($got_source+1)) === "-$source") {
+ $id = substr($id, 0, -($got_source+1));
+ }
+ $result[$source][] = $id;
+ }
+ }
+
+ return $filter !== null ? $result[$filter] : $result;
+}
+
+
+// register UI objects
+$OUTPUT->add_handlers(array(
+ 'directorylist' => 'rcmail_directory_list',
+// 'groupslist' => 'rcmail_contact_groups',
+ 'addresslist' => 'rcmail_contacts_list',
+ 'addressframe' => 'rcmail_contact_frame',
+ 'recordscountdisplay' => 'rcmail_rowcount_display',
+ 'searchform' => array($OUTPUT, 'search_form')
+));
+
+// register action aliases
+$RCMAIL->register_action_map(array(
+ 'add' => 'edit.inc',
+ 'photo' => 'show.inc',
+ 'group-create' => 'groups.inc',
+ 'group-rename' => 'groups.inc',
+ 'group-delete' => 'groups.inc',
+ 'group-addmembers' => 'groups.inc',
+ 'group-delmembers' => 'groups.inc',
+ 'search-create' => 'search.inc',
+ 'search-delete' => 'search.inc',
+));
diff --git a/webmail/program/steps/addressbook/groups.inc b/webmail/program/steps/addressbook/groups.inc
new file mode 100644
index 0000000..3b9288a
--- /dev/null
+++ b/webmail/program/steps/addressbook/groups.inc
@@ -0,0 +1,135 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/groups.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2010, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Create/delete/rename contact groups and assign/remove contacts |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+$source = get_input_value('_source', RCUBE_INPUT_GPC);
+$CONTACTS = rcmail_contact_source($source);
+
+if ($CONTACTS->readonly || !$CONTACTS->groups) {
+ $OUTPUT->show_message('sourceisreadonly', 'warning');
+ $OUTPUT->send();
+}
+
+if ($RCMAIL->action == 'group-addmembers') {
+ if (($gid = get_input_value('_gid', RCUBE_INPUT_POST)) && ($ids = rcmail_get_cids($source))) {
+ $plugin = $RCMAIL->plugins->exec_hook('group_addmembers', array('group_id' => $gid, 'ids' => $ids, 'source' => $source));
+
+ $CONTACTS->set_group($gid);
+ $num2add = count($plugin['ids']);
+
+ if (!$plugin['abort']) {
+ if (($maxnum = $RCMAIL->config->get('max_group_members', 0)) && ($CONTACTS->count()->count + $num2add > $maxnum)) {
+ $OUTPUT->show_message('maxgroupmembersreached', 'warning', array('max' => $maxnum));
+ $OUTPUT->send();
+ }
+ $result = $CONTACTS->add_to_group($gid, $plugin['ids']);
+ }
+ else {
+ $result = $plugin['result'];
+ }
+
+ if ($result)
+ $OUTPUT->show_message('contactaddedtogroup');
+ else if ($plugin['abort'] || $CONTACTS->get_error())
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error');
+ else
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'nogroupassignmentschanged');
+ }
+}
+
+else if ($RCMAIL->action == 'group-delmembers') {
+ if (($gid = get_input_value('_gid', RCUBE_INPUT_POST)) && ($ids = rcmail_get_cids($source))) {
+ $plugin = $RCMAIL->plugins->exec_hook('group_delmembers', array('group_id' => $gid, 'ids' => $ids, 'source' => $source));
+
+ if (!$plugin['abort'])
+ $result = $CONTACTS->remove_from_group($gid, $plugin['ids']);
+ else
+ $result = $plugin['result'];
+
+ if ($result) {
+ $OUTPUT->show_message('contactremovedfromgroup');
+ $OUTPUT->command('remove_group_contacts',array('source' => $source, 'gid' => $gid));
+ }
+ else {
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error');
+ }
+ }
+}
+
+else if ($RCMAIL->action == 'group-create') {
+ if ($name = trim(get_input_value('_name', RCUBE_INPUT_POST, true))) {
+ $plugin = $RCMAIL->plugins->exec_hook('group_create', array('name' => $name, 'source' => $source));
+
+ if (!$plugin['abort'])
+ $created = $CONTACTS->create_group($plugin['name']);
+ else
+ $created = $plugin['result'];
+ }
+
+ if ($created && $OUTPUT->ajax_call) {
+ $created['name'] = Q($created['name']);
+ $OUTPUT->show_message('groupcreated', 'confirmation');
+ $OUTPUT->command('insert_contact_group', array('source' => $source) + $created);
+ }
+ else if (!$created) {
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error');
+ }
+}
+
+else if ($RCMAIL->action == 'group-rename') {
+ if (($gid = get_input_value('_gid', RCUBE_INPUT_POST)) && ($name = trim(get_input_value('_name', RCUBE_INPUT_POST, true)))) {
+ $plugin = $RCMAIL->plugins->exec_hook('group_rename', array('group_id' => $gid, 'name' => $name, 'source' => $source));
+
+ if (!$plugin['abort'])
+ $newname = $CONTACTS->rename_group($gid, $plugin['name'], $newgid);
+ else
+ $newname = $plugin['result'];
+ }
+
+ if ($newname && $OUTPUT->ajax_call) {
+ $OUTPUT->show_message('grouprenamed', 'confirmation');
+ $OUTPUT->command('update_contact_group', array(
+ 'source' => $source, 'id' => $gid, 'name' => Q($newname), 'newid' => $newgid));
+ }
+ else if (!$newname)
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error');
+}
+
+else if ($RCMAIL->action == 'group-delete') {
+ if ($gid = get_input_value('_gid', RCUBE_INPUT_POST)) {
+ $plugin = $RCMAIL->plugins->exec_hook('group_delete', array('group_id' => $gid, 'source' => $source));
+
+ if (!$plugin['abort'])
+ $deleted = $CONTACTS->delete_group($gid);
+ else
+ $deleted = $plugin['result'];
+ }
+
+ if ($deleted) {
+ $OUTPUT->show_message('groupdeleted', 'confirmation');
+ $OUTPUT->command('remove_group_item', array('source' => $source, 'id' => $gid));
+ }
+ else
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error');
+}
+
+// send response
+$OUTPUT->send();
+
diff --git a/webmail/program/steps/addressbook/import.inc b/webmail/program/steps/addressbook/import.inc
new file mode 100644
index 0000000..915aac8
--- /dev/null
+++ b/webmail/program/steps/addressbook/import.inc
@@ -0,0 +1,282 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/import.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2008-2009, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Import contacts from a vCard or CSV file |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ | Author: Aleksander Machniak <machniak@kolabsys.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Handler function to display the import/upload form
+ */
+function rcmail_import_form($attrib)
+{
+ global $RCMAIL, $OUTPUT;
+ $target = get_input_value('_target', RCUBE_INPUT_GPC);
+
+ $attrib += array('id' => "rcmImportForm");
+
+ $writable_books = $RCMAIL->get_address_sources(true, true);
+
+ $upload = new html_inputfield(array(
+ 'type' => 'file',
+ 'name' => '_file[]',
+ 'id' => 'rcmimportfile',
+ 'size' => 40,
+ 'multiple' => 'multiple',
+ ));
+ $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', 'is_escaped' => true));
+
+ 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->set_env('writable_source', !empty($writable_books));
+ $OUTPUT->add_label('selectimportfile','importwait');
+ $OUTPUT->add_gui_object('importform', $attrib['id']);
+
+ $out = html::p(null, Q(rcube_label('importdesc'), 'show'));
+
+ $out .= $OUTPUT->form_tag(array(
+ 'action' => $RCMAIL->url('import'),
+ 'method' => 'post',
+ 'enctype' => 'multipart/form-data') + $attrib,
+ $form);
+
+ return $out;
+}
+
+
+/**
+ * Render the confirmation page for the import process
+ */
+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' => $IMPORT_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',
+ 'nr' => $IMPORT_STATS->skipped,
+ 'vars' => $vars,
+ )) . ':');
+ $content .= html::p('em', join(', ', array_map('Q', $IMPORT_STATS->skipped_names)));
+ }
+
+ return html::div($attrib, $content);
+}
+
+
+/**
+ * Create navigation buttons for the current import step
+ */
+function rcmail_import_buttons($attrib)
+{
+ 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);
+ }
+ else {
+ $out = $OUTPUT->button(array('command' => 'list', 'label' => 'cancel') + $attrib);
+ $out .= '&nbsp;';
+ $attrib['class'] = trim($attrib['class'] . ' mainaction');
+ $out .= $OUTPUT->button(array('command' => 'import', 'label' => 'import') + $attrib);
+ }
+
+ return $out;
+}
+
+
+/** The import process **/
+
+$importstep = 'rcmail_import_form';
+
+if (is_array($_FILES['_file'])) {
+ $replace = (bool)get_input_value('_replace', RCUBE_INPUT_GPC);
+ $target = get_input_value('_target', RCUBE_INPUT_GPC);
+
+ $vcards = array();
+ $upload_error = null;
+
+ $CONTACTS = $RCMAIL->get_address_book($target, true);
+
+ if ($CONTACTS->readonly) {
+ $OUTPUT->show_message('addresswriterror', 'error');
+ }
+ else {
+ foreach ((array)$_FILES['_file']['tmp_name'] as $i => $filepath) {
+ // Process uploaded file if there is no error
+ $err = $_FILES['_file']['error'][$i];
+
+ if ($err) {
+ $upload_error = $err;
+ }
+ else {
+ $file_content = file_get_contents($filepath);
+
+ // let rcube_vcard do the hard work :-)
+ $vcard_o = new rcube_vcard();
+ $vcard_o->extend_fieldmap($CONTACTS->vcard_map);
+ $v_list = $vcard_o->import($file_content);
+
+ if (!empty($v_list)) {
+ $vcards = array_merge($vcards, $v_list);
+ continue;
+ }
+
+ // no vCards found, try CSV
+ $csv = new rcube_csv2vcard($_SESSION['language']);
+ $csv->import($file_content);
+ $v_list = $csv->export();
+
+ if (!empty($v_list)) {
+ $vcards = array_merge($vcards, $v_list);
+ }
+ }
+ }
+ }
+
+ // no vcards detected
+ if (!count($vcards)) {
+ if ($upload_error == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
+ $OUTPUT->show_message('filesizeerror', 'error', array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize')))));
+ }
+ else if ($upload_error) {
+ $OUTPUT->show_message('fileuploaderror', 'error');
+ }
+ else {
+ $OUTPUT->show_message('importformaterror', 'error');
+ }
+ }
+ else {
+ $IMPORT_STATS = new stdClass;
+ $IMPORT_STATS->names = array();
+ $IMPORT_STATS->skipped_names = array();
+ $IMPORT_STATS->count = count($vcards);
+ $IMPORT_STATS->inserted = $IMPORT_STATS->skipped = $IMPORT_STATS->invalid = $IMPORT_STATS->errors = 0;
+
+ if ($replace) {
+ $CONTACTS->delete_all();
+ }
+
+ foreach ($vcards as $vcard) {
+ $a_record = $vcard->get_assoc();
+
+ // Generate contact's display name (must be before validation), the same we do in save.inc
+ if (empty($a_record['name'])) {
+ $a_record['name'] = rcube_addressbook::compose_display_name($a_record, true);
+ // Reset it if equals to email address (from compose_display_name())
+ if ($a_record['name'] == $a_record['email'][0]) {
+ $a_record['name'] = '';
+ }
+ }
+
+ // skip invalid (incomplete) entries
+ if (!$CONTACTS->validate($a_record, true)) {
+ $IMPORT_STATS->invalid++;
+ continue;
+ }
+
+ // We're using UTF8 internally
+ $email = $vcard->email[0];
+ $email = rcube_idn_to_utf8($email);
+
+ if (!$replace) {
+ $existing = null;
+ // compare e-mail address
+ if ($email) {
+ $existing = $CONTACTS->search('email', $email, 1, false);
+ }
+ // compare display name if email not found
+ if ((!$existing || !$existing->count) && $vcard->displayname) {
+ $existing = $CONTACTS->search('name', $vcard->displayname, 1, false);
+ }
+ if ($existing && $existing->count) {
+ $IMPORT_STATS->skipped++;
+ $IMPORT_STATS->skipped_names[] = $vcard->displayname ? $vcard->displayname : $email;
+ continue;
+ }
+ }
+
+ $a_record['vcard'] = $vcard->export();
+
+ $plugin = $RCMAIL->plugins->exec_hook('contact_create',
+ array('record' => $a_record, 'source' => null));
+ $a_record = $plugin['record'];
+
+ // insert record and send response
+ if (!$plugin['abort'])
+ $success = $CONTACTS->insert($a_record);
+ else
+ $success = $plugin['result'];
+
+ if ($success) {
+ $IMPORT_STATS->inserted++;
+ $IMPORT_STATS->names[] = $a_record['name'] ? $a_record['name'] : $email;
+ }
+ else {
+ $IMPORT_STATS->errors++;
+ }
+ }
+
+ $importstep = 'rcmail_import_confirm';
+ }
+}
+
+
+$OUTPUT->set_pagetitle(rcube_label('importcontacts'));
+
+$OUTPUT->add_handlers(array(
+ 'importstep' => $importstep,
+ 'importnav' => 'rcmail_import_buttons',
+));
+
+// render page
+$OUTPUT->send('importcontacts');
diff --git a/webmail/program/steps/addressbook/list.inc b/webmail/program/steps/addressbook/list.inc
new file mode 100644
index 0000000..1bb2865
--- /dev/null
+++ b/webmail/program/steps/addressbook/list.inc
@@ -0,0 +1,94 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/list.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Send contacts list to client (as remote response) |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+$afields = $RCMAIL->config->get('contactlist_fields');
+
+// Use search result
+if (!empty($_REQUEST['_search']) && isset($_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;
+ $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name');
+
+ // 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($afields);
+
+ while ($row = $result->next()) {
+ $row['sourceid'] = $s;
+ $key = rcube_addressbook::compose_contact_key($row, $sort_col);
+ $records[$key] = $row;
+ }
+ unset($result);
+ }
+
+ // sort the records
+ ksort($records, SORT_LOCALE_STRING);
+
+ // create resultset object
+ $count = count($records);
+ $first = ($page-1) * $PAGE_SIZE;
+ $result = new rcube_result_set($count, $first);
+
+ // we need only records for current page
+ if ($PAGE_SIZE < $count) {
+ $records = array_slice($records, $first, $PAGE_SIZE);
+ }
+
+ $result->records = array_values($records);
+}
+// List selected directory
+else {
+ $CONTACTS = rcmail_contact_source(null, true);
+
+ // get contacts for this user
+ $result = $CONTACTS->list_records($afields);
+
+ if (!$result->count && $result->searchonly) {
+ $OUTPUT->show_message('contactsearchonly', 'notice');
+ $OUTPUT->command('command', 'advanced-search');
+ }
+}
+
+// update message count display
+$OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE));
+$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
+
+// create javascript list
+rcmail_js_contacts_list($result);
+
+// send response
+$OUTPUT->send();
diff --git a/webmail/program/steps/addressbook/mailto.inc b/webmail/program/steps/addressbook/mailto.inc
new file mode 100644
index 0000000..c3cbcad
--- /dev/null
+++ b/webmail/program/steps/addressbook/mailto.inc
@@ -0,0 +1,78 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/mailto.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2007, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Compose a recipient list with all selected contacts |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+$cids = rcmail_get_cids();
+$mailto = array();
+$recipients = null;
+
+foreach ($cids as $source => $cid)
+{
+ $CONTACTS = $RCMAIL->get_address_book($source);
+
+ 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, 0, true, true, 'email');
+ }
+}
+
+if (!empty($_REQUEST['_gid']) && isset($_REQUEST['_source']))
+{
+ $source = get_input_value('_source', RCUBE_INPUT_GPC);
+ $CONTACTS = $RCMAIL->get_address_book($source);
+
+ $group_id = get_input_value('_gid', RCUBE_INPUT_GPC);
+ $group_data = $CONTACTS->get_group($group_id);
+
+ // group has an email address assigned: use that
+ if ($group_data['email']) {
+ $mailto[] = format_email_recipient($group_data['email'][0], $group_data['name']);
+ }
+ else if ($CONTACTS->ready) {
+ $CONTACTS->set_group($group_id);
+ $CONTACTS->set_page(1);
+ $CONTACTS->set_pagesize(200); // limit somehow
+ $recipients = $CONTACTS->list_records();
+ }
+}
+
+if ($recipients)
+{
+ 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->command('open_compose_step', array('_mailto' => $mailto_id));
+}
+else {
+ $OUTPUT->show_message('nocontactsfound', 'warning');
+}
+
+// send response
+$OUTPUT->send();
diff --git a/webmail/program/steps/addressbook/save.inc b/webmail/program/steps/addressbook/save.inc
new file mode 100644
index 0000000..25bfbd4
--- /dev/null
+++ b/webmail/program/steps/addressbook/save.inc
@@ -0,0 +1,228 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/save.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2011, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Save a contact entry or to add a new one |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+$CONTACTS = rcmail_contact_source(null, true, true);
+$cid = get_input_value('_cid', RCUBE_INPUT_POST);
+$return_action = empty($cid) ? 'add' : 'edit';
+
+// Source changed, display the form again
+if (!empty($_GET['_reload'])) {
+ rcmail_overwrite_action($return_action);
+ return;
+}
+
+// cannot edit record
+if ($CONTACTS->readonly) {
+ $OUTPUT->show_message('contactreadonly', 'error');
+ rcmail_overwrite_action($return_action);
+ return;
+}
+
+// read POST values into hash array
+$a_record = array();
+foreach ($GLOBALS['CONTACT_COLTYPES'] as $col => $colprop) {
+ $fname = '_'.$col;
+ if ($colprop['composite'])
+ continue;
+ // gather form data of composite fields
+ if ($colprop['childs']) {
+ $values = array();
+ foreach ($colprop['childs'] as $childcol => $cp) {
+ $vals = get_input_value('_'.$childcol, RCUBE_INPUT_POST, true);
+ foreach ((array)$vals as $i => $val)
+ $values[$i][$childcol] = $val;
+ }
+ $subtypes = isset($_REQUEST['_subtype_' . $col]) ? (array)get_input_value('_subtype_' . $col, RCUBE_INPUT_POST) : array('');
+ foreach ($subtypes as $i => $subtype) {
+ $suffix = $subtype ? ':'.$subtype : '';
+ if ($values[$i])
+ $a_record[$col.$suffix][] = $values[$i];
+ }
+ }
+ // assign values and subtypes
+ else if (is_array($_POST[$fname])) {
+ $values = get_input_value($fname, RCUBE_INPUT_POST, true);
+ $subtypes = get_input_value('_subtype_' . $col, RCUBE_INPUT_POST);
+ foreach ($values as $i => $val) {
+ $subtype = $subtypes[$i] ? ':'.$subtypes[$i] : '';
+ $a_record[$col.$subtype][] = $val;
+ }
+ }
+ else if (isset($_POST[$fname])) {
+ $a_record[$col] = get_input_value($fname, RCUBE_INPUT_POST, true);
+ }
+}
+
+// Generate contact's display name (must be before validation)
+if (empty($a_record['name'])) {
+ $a_record['name'] = rcube_addressbook::compose_display_name($a_record, true);
+ // Reset it if equals to email address (from compose_display_name())
+ if ($a_record['name'] == $a_record['email'][0])
+ $a_record['name'] = '';
+}
+
+// do input checks (delegated to $CONTACTS instance)
+if (!$CONTACTS->validate($a_record)) {
+ $err = (array)$CONTACTS->get_error();
+ $OUTPUT->show_message($err['message'] ? Q($err['message']) : 'formincomplete', 'warning');
+ $GLOBALS['EDIT_RECORD'] = $a_record; // store submitted data to be used in edit form
+ rcmail_overwrite_action($return_action);
+ return;
+}
+
+// get raw photo data if changed
+if (isset($a_record['photo'])) {
+ if ($a_record['photo'] == '-del-') {
+ $a_record['photo'] = '';
+ }
+ else if ($tempfile = $_SESSION['contacts']['files'][$a_record['photo']]) {
+ $tempfile = $RCMAIL->plugins->exec_hook('attachment_get', $tempfile);
+ if ($tempfile['status'])
+ $a_record['photo'] = $tempfile['data'] ? $tempfile['data'] : @file_get_contents($tempfile['path']);
+ }
+ else
+ unset($a_record['photo']);
+
+ // cleanup session data
+ $RCMAIL->plugins->exec_hook('attachments_cleanup', array('group' => 'contact'));
+ $RCMAIL->session->remove('contacts');
+}
+
+$source = get_input_value('_source', RCUBE_INPUT_GPC);
+
+// update an existing contact
+if (!empty($cid))
+{
+ $plugin = $RCMAIL->plugins->exec_hook('contact_update',
+ array('id' => $cid, 'record' => $a_record, 'source' => $source));
+ $a_record = $plugin['record'];
+
+ if (!$plugin['abort'])
+ $result = $CONTACTS->update($cid, $a_record);
+ else
+ $result = $plugin['result'];
+
+ if ($result) {
+ // LDAP DN change
+ if (is_string($result) && strlen($result)>1) {
+ $newcid = $result;
+ // change cid in POST for 'show' action
+ $_POST['_cid'] = $newcid;
+ }
+
+ // define list of cols to be displayed
+ $a_js_cols = array();
+ $record = $CONTACTS->get_record($newcid ? $newcid : $cid, true);
+ $record['email'] = reset($CONTACTS->get_col_values('email', $record, true));
+ $record['name'] = rcube_addressbook::compose_list_name($record);
+
+ foreach (array('name', 'email') as $col)
+ $a_js_cols[] = Q((string)$record[$col]);
+
+ // update the changed col in list
+ $OUTPUT->command('parent.update_contact_row', $cid, $a_js_cols, $newcid, $source);
+
+ // show confirmation
+ $OUTPUT->show_message('successfullysaved', 'confirmation', null, false);
+ rcmail_overwrite_action('show');
+ }
+ else {
+ // show error message
+ $err = $CONTACTS->get_error();
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false);
+ rcmail_overwrite_action('show');
+ }
+}
+
+// insert a new contact
+else {
+ // Name of the addressbook already selected on the list
+ $orig_source = get_input_value('_orig_source', RCUBE_INPUT_GPC);
+
+ if (!strlen($source))
+ $source = $orig_source;
+
+ // show notice if existing contacts with same e-mail are found
+ foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) {
+ if ($email && ($res = $CONTACTS->search('email', $email, 1, false, true)) && $res->count) {
+ $OUTPUT->show_message('contactexists', 'notice', null, false);
+ break;
+ }
+ }
+
+ $plugin = $RCMAIL->plugins->exec_hook('contact_create', array(
+ 'record' => $a_record, 'source' => $source));
+ $a_record = $plugin['record'];
+
+ // insert record and send response
+ if (!$plugin['abort'])
+ $insert_id = $CONTACTS->insert($a_record);
+ else
+ $insert_id = $plugin['result'];
+
+ if ($insert_id) {
+ $CONTACTS->reset();
+
+ // add new contact to the specified group
+ if ($CONTACTS->groups && $CONTACTS->group_id) {
+ $plugin = $RCMAIL->plugins->exec_hook('group_addmembers', array(
+ 'group_id' => $CONTACTS->group_id, 'ids' => $insert_id, 'source' => $source));
+
+ $counts = $CONTACTS->count();
+
+ if (!$plugin['abort']) {
+ if (($maxnum = $RCMAIL->config->get('max_group_members', 0)) && ($counts->count + 1 > $maxnum))
+ $OUTPUT->show_message('maxgroupmembersreached', 'warning', array('max' => $maxnum));
+
+ $CONTACTS->add_to_group($plugin['group_id'], $plugin['ids']);
+ }
+ }
+ else
+ $counts = $CONTACTS->count();
+
+ if ((string)$source === (string)$orig_source) {
+ // add contact row or jump to the page where it should appear
+ $CONTACTS->reset();
+ $result = $CONTACTS->search($CONTACTS->primary_key, $insert_id);
+
+ rcmail_js_contacts_list($result, 'parent.');
+ $OUTPUT->command('parent.contact_list.select', html_identifier($insert_id));
+
+ // update record count display
+ $CONTACTS->reset();
+ $OUTPUT->command('parent.set_rowcount', rcmail_get_rowcount_text($counts));
+ }
+ else {
+ // re-set iframe
+ $OUTPUT->command('parent.show_contentframe');
+ }
+
+ // show confirmation
+ $OUTPUT->show_message('successfullysaved', 'confirmation', null, false);
+ $OUTPUT->send('iframe');
+ }
+ else {
+ // show error message
+ $err = $CONTACTS->get_error();
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false);
+ rcmail_overwrite_action('add');
+ }
+}
diff --git a/webmail/program/steps/addressbook/search.inc b/webmail/program/steps/addressbook/search.inc
new file mode 100644
index 0000000..d153c25
--- /dev/null
+++ b/webmail/program/steps/addressbook/search.inc
@@ -0,0 +1,341 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/search.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2011, The Roundcube Dev Team |
+ | Copyright (C) 2011, Kolab Systems AG |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Search action (and form) for address book contacts |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ | Author: Aleksander Machniak <machniak@kolabsys.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+if ($RCMAIL->action == 'search-create') {
+ $id = get_input_value('_search', RCUBE_INPUT_POST);
+ $name = get_input_value('_name', RCUBE_INPUT_POST, true);
+
+ if (($params = $_SESSION['search_params']) && $params['id'] == $id) {
+
+ $data = array(
+ 'type' => rcube_user::SEARCH_ADDRESSBOOK,
+ 'name' => $name,
+ 'data' => array(
+ 'fields' => $params['data'][0],
+ 'search' => $params['data'][1],
+ ),
+ );
+
+ $plugin = $RCMAIL->plugins->exec_hook('saved_search_create', array('data' => $data));
+
+ if (!$plugin['abort'])
+ $result = $RCMAIL->user->insert_search($plugin['data']);
+ else
+ $result = $plugin['result'];
+ }
+
+ if ($result) {
+ $OUTPUT->show_message('savedsearchcreated', 'confirmation');
+ $OUTPUT->command('insert_saved_search', Q($name), Q($result));
+ }
+ else
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'savedsearchcreateerror', 'error');
+
+ $OUTPUT->send();
+}
+
+if ($RCMAIL->action == 'search-delete') {
+ $id = get_input_value('_sid', RCUBE_INPUT_POST);
+
+ $plugin = $RCMAIL->plugins->exec_hook('saved_search_delete', array('id' => $id));
+
+ if (!$plugin['abort'])
+ $result = $RCMAIL->user->delete_search($id);
+ else
+ $result = $plugin['result'];
+
+ if ($result) {
+ $OUTPUT->show_message('savedsearchdeleted', 'confirmation');
+ $OUTPUT->command('remove_search_item', Q($id));
+ // contact list will be cleared, clear also page counter
+ $OUTPUT->command('set_rowcount', rcube_label('nocontactsfound'));
+ $OUTPUT->set_env('pagecount', 0);
+ }
+ else
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'savedsearchdeleteerror', 'error');
+
+ $OUTPUT->send();
+}
+
+
+if (!isset($_GET['_form'])) {
+ rcmail_contact_search();
+}
+
+$OUTPUT->add_handler('searchform', 'rcmail_contact_search_form');
+$OUTPUT->send('contactsearch');
+
+
+function rcmail_contact_search()
+{
+ global $RCMAIL, $OUTPUT, $SEARCH_MODS_DEFAULT, $PAGE_SIZE;
+
+ $adv = isset($_POST['_adv']);
+ $sid = get_input_value('_sid', RCUBE_INPUT_GET);
+
+ // get search criteria from saved search
+ if ($sid && ($search = $RCMAIL->user->get_search($sid))) {
+ $fields = $search['data']['fields'];
+ $search = $search['data']['search'];
+ }
+ // get fields/values from advanced search form
+ else if ($adv) {
+ 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[] = $m[1];
+ }
+ }
+
+ if (empty($fields)) {
+ // do nothing, show the form again
+ return;
+ }
+ }
+ // quick-search
+ else {
+ $search = trim(get_input_value('_q', RCUBE_INPUT_GET, true));
+ $fields = explode(',', get_input_value('_headers', RCUBE_INPUT_GET));
+
+ if (empty($fields)) {
+ $fields = array_keys($SEARCH_MODS_DEFAULT);
+ }
+ else {
+ $fields = array_filter($fields);
+ }
+
+ // update search_mods setting
+ $old_mods = $RCMAIL->config->get('addressbook_search_mods');
+ $search_mods = array_fill_keys($fields, 1);
+ if ($old_mods != $search_mods) {
+ $RCMAIL->user->save_prefs(array('addressbook_search_mods' => $search_mods));
+ }
+
+ if (in_array('*', $fields)) {
+ $fields = '*';
+ }
+ }
+
+ // Values matching mode
+ $mode = (int) $RCMAIL->config->get('addressbook_search_mode');
+
+ // get sources list
+ $sources = $RCMAIL->get_address_sources();
+ $search_set = array();
+ $records = array();
+ $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name');
+ $afields = $RCMAIL->config->get('contactlist_fields');
+
+ foreach ($sources as $s) {
+ $source = $RCMAIL->get_address_book($s['id']);
+
+ // check if 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 ++;
+ }
+ }
+
+ // in advanced search we require all fields (AND operator)
+ // in quick search we require at least one field (OR operator)
+ if (($adv && $supported < count($fields)) || (!$adv && !$supported)) {
+ continue;
+ }
+ }
+
+ // reset page
+ $source->set_page(1);
+ $source->set_pagesize(9999);
+
+ // get contacts count
+ $result = $source->search($fields, $search, $mode, false);
+
+ if (!$result->count) {
+ continue;
+ }
+
+ // get records
+ $result = $source->list_records($afields);
+
+ while ($row = $result->next()) {
+ $row['sourceid'] = $s['id'];
+ $key = rcube_addressbook::compose_contact_key($row, $sort_col);
+ $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 ($PAGE_SIZE < $count) {
+ $records = array_slice($records, 0, $PAGE_SIZE);
+ }
+
+ $result->records = array_values($records);
+
+ // search request ID
+ $search_request = md5('addr'
+ .(is_array($fields) ? implode($fields, ',') : $fields)
+ .(is_array($search) ? implode($search, ',') : $search));
+
+ // save search settings in session
+ $_SESSION['search'][$search_request] = $search_set;
+ $_SESSION['search_params'] = array('id' => $search_request, 'data' => array($fields, $search));
+ $_SESSION['page'] = 1;
+
+ if ($adv)
+ $OUTPUT->command('list_contacts_clear');
+
+ if ($result->count > 0) {
+ // create javascript list
+ rcmail_js_contacts_list($result);
+ $OUTPUT->show_message('contactsearchsuccessful', 'confirmation', array('nr' => $result->count));
+ }
+ else {
+ $OUTPUT->show_message('nocontactsfound', 'notice');
+ }
+
+ // update message count display
+ $OUTPUT->command('set_env', 'search_request', $search_request);
+ $OUTPUT->command('set_env', 'pagecount', ceil($result->count / $PAGE_SIZE));
+ $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
+ // Re-set current source
+ $OUTPUT->command('set_env', 'search_id', $sid);
+ $OUTPUT->command('set_env', 'source', '');
+ $OUTPUT->command('set_env', 'group', '');
+
+ if (!$sid) {
+ // unselect currently selected directory/group
+ $OUTPUT->command('unselect_directory');
+ // enable "Save search" command
+ $OUTPUT->command('enable_command', 'search-create', true);
+ }
+ $OUTPUT->command('update_group_commands');
+
+ // send response
+ $OUTPUT->send($adv ? 'iframe' : null);
+}
+
+function rcmail_contact_search_form($attrib)
+{
+ global $RCMAIL, $CONTACT_COLTYPES;
+
+ $i_size = !empty($attrib['size']) ? $attrib['size'] : 30;
+
+ $form = array(
+ 'main' => array(
+ 'name' => rcube_label('properties'),
+ 'content' => array(
+ ),
+ ),
+ 'personal' => array(
+ 'name' => rcube_label('personalinfo'),
+ 'content' => array(
+ ),
+ ),
+ 'other' => array(
+ 'name' => rcube_label('other'),
+ 'content' => array(
+ ),
+ ),
+ );
+
+ // 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'])
+ {
+ $ftype = $colprop['type'] == 'select' ? 'select' : 'text';
+ $label = isset($colprop['label']) ? $colprop['label'] : rcube_label($col);
+ $category = $colprop['category'] ? $colprop['category'] : 'other';
+
+ // load jquery UI datepicker for date fields
+ if ($colprop['type'] == 'date')
+ $colprop['class'] .= ($colprop['class'] ? ' ' : '') . 'datepicker';
+ else if ($ftype == 'text')
+ $colprop['size'] = $i_size;
+
+
+ $content = html::div('row', html::div('contactfieldlabel label', Q($label))
+ . html::div('contactfieldcontent', rcmail_get_edit_field('search_'.$col, '', $colprop, $ftype)));
+
+ $form[$category]['content'][] = $content;
+ }
+ }
+
+ $hiddenfields = new html_hiddenfield();
+ $hiddenfields->add(array('name' => '_adv', 'value' => 1));
+
+ $out = $RCMAIL->output->request_form(array(
+ 'name' => 'form', 'method' => 'post',
+ 'task' => $RCMAIL->task, 'action' => 'search',
+ 'noclose' => true) + $attrib, $hiddenfields->show());
+
+ $RCMAIL->output->add_gui_object('editform', $attrib['id']);
+
+ unset($attrib['name']);
+ unset($attrib['id']);
+
+ foreach ($form as $f) {
+ if (!empty($f['content'])) {
+ $content = html::div('contactfieldgroup', join("\n", $f['content']));
+
+ $out .= html::tag('fieldset', $attrib,
+ html::tag('legend', null, Q($f['name']))
+ . $content) . "\n";
+ }
+ }
+
+ return $out . '</form>';
+}
diff --git a/webmail/program/steps/addressbook/show.inc b/webmail/program/steps/addressbook/show.inc
new file mode 100644
index 0000000..d583a6d
--- /dev/null
+++ b/webmail/program/steps/addressbook/show.inc
@@ -0,0 +1,248 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/show.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Show contact details |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+// Get contact ID and source ID from request
+$cids = rcmail_get_cids();
+$source = key($cids);
+$cid = $cids ? array_shift($cids[$source]) : null;
+
+// Initialize addressbook source
+$CONTACTS = rcmail_contact_source($source, true);
+$SOURCE_ID = $source;
+
+// read contact record
+if ($cid && ($record = $CONTACTS->get_record($cid, true))) {
+ $OUTPUT->set_env('readonly', $CONTACTS->readonly || $record['readonly']);
+ $OUTPUT->set_env('cid', $record['ID']);
+ $OUTPUT->set_env('compose_extwin', $RCMAIL->config->get('compose_extwin',false));
+}
+
+// get address book name (for display)
+rcmail_set_sourcename($CONTACTS);
+
+// return raw photo of the given contact
+if ($RCMAIL->action == 'photo') {
+ // search for contact first
+ if (!$record && ($email = get_input_value('_email', RCUBE_INPUT_GPC))) {
+ foreach ($RCMAIL->get_address_sources() as $s) {
+ $abook = $RCMAIL->get_address_book($s['id']);
+ $result = $abook->search(array('email'), $email, 1, true, true, 'photo');
+ while ($result && ($record = $result->iterate())) {
+ if ($record['photo'])
+ break 2;
+ }
+ }
+ }
+
+ // read the referenced file
+ if (($file_id = get_input_value('_photo', RCUBE_INPUT_GPC)) && ($tempfile = $_SESSION['contacts']['files'][$file_id])) {
+ $tempfile = $RCMAIL->plugins->exec_hook('attachment_display', $tempfile);
+ if ($tempfile['status']) {
+ if ($tempfile['data'])
+ $data = $tempfile['data'];
+ else if ($tempfile['path'])
+ $data = file_get_contents($tempfile['path']);
+ }
+ }
+ else if ($record['photo']) {
+ $data = is_array($record['photo']) ? $record['photo'][0] : $record['photo'];
+ if (!preg_match('![^a-z0-9/=+-]!i', $data))
+ $data = base64_decode($data, true);
+ }
+
+ // let plugins do fancy things with contact photos
+ $plugin = $RCMAIL->plugins->exec_hook('contact_photo', array('record' => $record, 'email' => $email, 'data' => $data));
+
+ // redirect to url provided by a plugin
+ if ($plugin['url'])
+ $RCMAIL->output->redirect($plugin['url']);
+ else
+ $data = $plugin['data'];
+
+ // deliver alt image
+ if (!$data && ($alt_img = get_input_value('_alt', RCUBE_INPUT_GPC)) && is_file($alt_img))
+ $data = file_get_contents($alt_img);
+
+ // cache for one day if requested by email
+ if (!$cid && $email)
+ $RCMAIL->output->future_expire_header(86400);
+
+ header('Content-Type: ' . rc_image_content_type($data));
+ echo $data ? $data : file_get_contents('program/resources/blank.gif');
+ exit;
+}
+
+
+function rcmail_contact_head($attrib)
+{
+ global $CONTACTS, $RCMAIL;
+
+ // check if we have a valid result
+ if (!(($result = $CONTACTS->get_result()) && ($record = $result->first()))) {
+ $RCMAIL->output->show_message('contactnotfound');
+ return false;
+ }
+
+ $microformats = array('name' => 'fn', 'email' => 'email');
+
+ $form = array(
+ 'head' => array( // section 'head' is magic!
+ 'content' => array(
+ 'prefix' => array('type' => 'text'),
+ 'firstname' => array('type' => 'text'),
+ 'middlename' => array('type' => 'text'),
+ 'surname' => array('type' => 'text'),
+ 'suffix' => array('type' => 'text'),
+ ),
+ ),
+ );
+
+ unset($attrib['name']);
+ return rcmail_contact_form($form, $record, $attrib);
+}
+
+
+function rcmail_contact_details($attrib)
+{
+ global $CONTACTS, $RCMAIL, $CONTACT_COLTYPES;
+
+ // check if we have a valid result
+ if (!(($result = $CONTACTS->get_result()) && ($record = $result->first()))) {
+ //$RCMAIL->output->show_message('contactnotfound');
+ return false;
+ }
+
+ $i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
+
+ $form = array(
+ 'contact' => array(
+ 'name' => rcube_label('properties'),
+ 'content' => array(
+ 'email' => array('size' => $i_size, 'render_func' => 'rcmail_render_email_value'),
+ 'phone' => array('size' => $i_size),
+ 'address' => array(),
+ 'website' => array('size' => $i_size, 'render_func' => 'rcmail_render_url_value'),
+ 'im' => array('size' => $i_size),
+ ),
+ ),
+ 'personal' => array(
+ 'name' => rcube_label('personalinfo'),
+ 'content' => array(
+ 'gender' => array('size' => $i_size),
+ 'maidenname' => array('size' => $i_size),
+ 'birthday' => array('size' => $i_size),
+ 'anniversary' => array('size' => $i_size),
+ 'manager' => array('size' => $i_size),
+ 'assistant' => array('size' => $i_size),
+ 'spouse' => array('size' => $i_size),
+ ),
+ ),
+ );
+
+ if (isset($CONTACT_COLTYPES['notes'])) {
+ $form['notes'] = array(
+ 'name' => rcube_label('notes'),
+ 'content' => array(
+ 'notes' => array('type' => 'textarea', 'label' => false),
+ ),
+ );
+ }
+
+ if ($CONTACTS->groups) {
+ $form['groups'] = array(
+ 'name' => rcube_label('groups'),
+ 'content' => rcmail_contact_record_groups($record['ID']),
+ );
+ }
+
+ return rcmail_contact_form($form, $record);
+}
+
+
+function rcmail_render_email_value($email, $col)
+{
+ return html::a(array(
+ 'href' => 'mailto:' . $email,
+ 'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($email)),
+ 'title' => rcube_label('composeto'),
+ 'class' => 'email',
+ ), Q($email));
+}
+
+
+function rcmail_render_url_value($url, $col)
+{
+ $prefix = preg_match('!^(http|ftp)s?://!', $url) ? '' : 'http://';
+ return html::a(array(
+ 'href' => $prefix . $url,
+ 'target' => '_blank',
+ 'class' => 'url',
+ ), Q($url));
+}
+
+
+function rcmail_contact_record_groups($contact_id)
+{
+ global $RCMAIL, $CONTACTS, $GROUPS;
+
+ $GROUPS = $CONTACTS->list_groups();
+
+ if (empty($GROUPS)) {
+ return '';
+ }
+
+ $table = new html_table(array('cols' => 2, 'cellspacing' => 0, 'border' => 0));
+
+ $members = $CONTACTS->get_record_groups($contact_id);
+ $checkbox = new html_checkbox(array('name' => '_gid[]',
+ 'class' => 'groupmember', 'disabled' => $CONTACTS->readonly));
+
+ foreach ($GROUPS as $group) {
+ $gid = $group['ID'];
+ $table->add(null, $checkbox->show($members[$gid] ? $gid : null,
+ array('value' => $gid, 'id' => 'ff_gid' . $gid)));
+ $table->add(null, html::label('ff_gid' . $gid, Q($group['name'])));
+ }
+
+ $hiddenfields = new html_hiddenfield(array('name' => '_source', 'value' => get_input_value('_source', RCUBE_INPUT_GPC)));
+ $hiddenfields->add(array('name' => '_cid', 'value' => $contact_id));
+
+ $form_start = $RCMAIL->output->request_form(array(
+ 'name' => "form", 'method' => "post",
+ 'task' => $RCMAIL->task, 'action' => 'save',
+ 'request' => 'save.'.intval($contact_id),
+ 'noclose' => true), $hiddenfields->show());
+ $form_end = '</form>';
+
+ $RCMAIL->output->add_gui_object('editform', 'form');
+ $RCMAIL->output->add_label('addingmember', 'removingmember');
+
+ return $form_start . html::tag('fieldset', 'contactfieldgroup contactgroups', $table->show()) . $form_end;
+}
+
+
+$OUTPUT->add_handlers(array(
+ 'contacthead' => 'rcmail_contact_head',
+ 'contactdetails' => 'rcmail_contact_details',
+ 'contactphoto' => 'rcmail_contact_photo',
+));
+
+$OUTPUT->send('contact');
diff --git a/webmail/program/steps/addressbook/undo.inc b/webmail/program/steps/addressbook/undo.inc
new file mode 100644
index 0000000..9c17114
--- /dev/null
+++ b/webmail/program/steps/addressbook/undo.inc
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/undo.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2011, Kolab Systems AG |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Undelete contacts (CIDs) from last delete action |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak@kolabsys.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+// process ajax requests only
+if (!$OUTPUT->ajax_call)
+ return;
+
+$undo = $_SESSION['contact_undo'];
+$delcnt = 0;
+
+foreach ((array)$undo['data'] as $source => $cid)
+{
+ $CONTACTS = rcmail_contact_source($source);
+
+ $plugin = $RCMAIL->plugins->exec_hook('contact_undelete', array(
+ 'id' => $cid, 'source' => $source));
+
+ $restored = !$plugin['abort'] ? $CONTACTS->undelete($cid) : $plugin['result'];
+
+ if (!$restored) {
+ $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'contactrestoreerror', 'error');
+ $OUTPUT->command('list_contacts');
+ $OUTPUT->send();
+ }
+ else {
+ $delcnt += $restored;
+ }
+}
+
+// update saved search after data changed
+if ($delcnt && ($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) {
+ $search = (array)$_SESSION['search'][$search_request];
+
+ 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;
+ }
+
+ $search[$s] = $source->get_search_set();
+ }
+
+ $_SESSION['search'][$search_request] = $search;
+}
+
+$RCMAIL->session->remove('contact_undo');
+
+$OUTPUT->show_message('contactrestored', 'confirmation');
+$OUTPUT->command('list_contacts');
+
+// send response
+$OUTPUT->send();
diff --git a/webmail/program/steps/addressbook/upload_photo.inc b/webmail/program/steps/addressbook/upload_photo.inc
new file mode 100644
index 0000000..035d67e
--- /dev/null
+++ b/webmail/program/steps/addressbook/upload_photo.inc
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/upload_photo.inc |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2011, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Handles contact photo uploads |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+// Supported image format types
+// ImageMagick works with other non-image types (e.g.pdf) we don't want here
+$IMAGE_TYPES = explode(',', 'jpeg,jpg,jp2,tiff,tif,bmp,eps,gif,png,png8,png24,png32,svg,ico');
+
+// clear all stored output properties (like scripts and env vars)
+$OUTPUT->reset();
+
+if ($filepath = $_FILES['_photo']['tmp_name']) {
+ // check file type and resize image
+ $image = new rcube_image($_FILES['_photo']['tmp_name']);
+ $imageprop = $image->props();
+
+ if (in_array(strtolower($imageprop['type']), $IMAGE_TYPES)
+ && $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) && $image->resize($maxsize, $tmpfname)) {
+ $filepath = $tmpfname;
+ $save_hook = 'attachment_save';
+ }
+
+ // save uploaded file in storage backend
+ $attachment = $RCMAIL->plugins->exec_hook($save_hook, array(
+ 'path' => $filepath,
+ 'size' => $_FILES['_photo']['size'],
+ 'name' => $_FILES['_photo']['name'],
+ 'mimetype' => 'image/' . $imageprop['type'],
+ 'group' => 'contact',
+ ));
+ }
+ else {
+ $attachment['error'] = rcube_label('invalidimageformat');
+ }
+
+ if ($attachment['status'] && !$attachment['abort']) {
+ $file_id = $attachment['id'];
+ $_SESSION['contacts']['files'][$file_id] = $attachment;
+ $OUTPUT->command('replace_contact_photo', $file_id);
+ }
+ else { // upload failed
+ $err = $_FILES['_photo']['error'];
+ if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE)
+ $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
+ else if ($attachment['error'])
+ $msg = $attachment['error'];
+ else
+ $msg = rcube_label('fileuploaderror');
+
+ $OUTPUT->command('display_message', $msg, 'error');
+ }
+}
+else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ // if filesize exceeds post_max_size then $_FILES array is empty,
+ // show filesizeerror instead of fileuploaderror
+ if ($maxsize = ini_get('post_max_size'))
+ $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');