diff options
Diffstat (limited to 'webmail/program/steps/addressbook')
| -rw-r--r-- | webmail/program/steps/addressbook/copy.inc | 123 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/delete.inc | 165 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/edit.inc | 288 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/export.inc | 100 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/func.inc | 808 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/groups.inc | 135 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/import.inc | 282 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/list.inc | 94 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/mailto.inc | 78 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/save.inc | 228 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/search.inc | 341 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/show.inc | 248 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/undo.inc | 80 | ||||
| -rw-r--r-- | webmail/program/steps/addressbook/upload_photo.inc | 89 |
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 .= ' '; + $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'); |
