diff options
author | thomascube <thomas@roundcube.net> | 2011-01-18 18:00:57 +0000 |
---|---|---|
committer | thomascube <thomas@roundcube.net> | 2011-01-18 18:00:57 +0000 |
commit | 0501b637a3177cce441166b5fcfe27c9bd9fbe0f (patch) | |
tree | 5460128ef65d2510a7538c4a0e7336987e090ca1 /program/steps | |
parent | e81a30752b244394b03cbcaa0df254c93b379782 (diff) |
Merge branch devel-addressbook (r4193:4382) back into trunk
Diffstat (limited to 'program/steps')
-rw-r--r-- | program/steps/addressbook/copy.inc | 2 | ||||
-rw-r--r-- | program/steps/addressbook/delete.inc | 4 | ||||
-rw-r--r-- | program/steps/addressbook/edit.inc | 118 | ||||
-rw-r--r-- | program/steps/addressbook/export.inc | 25 | ||||
-rw-r--r-- | program/steps/addressbook/func.inc | 345 | ||||
-rw-r--r-- | program/steps/addressbook/groups.inc | 3 | ||||
-rw-r--r-- | program/steps/addressbook/import.inc | 9 | ||||
-rw-r--r-- | program/steps/addressbook/list.inc | 2 | ||||
-rw-r--r-- | program/steps/addressbook/mailto.inc | 6 | ||||
-rw-r--r-- | program/steps/addressbook/save.inc | 162 | ||||
-rw-r--r-- | program/steps/addressbook/search.inc | 8 | ||||
-rw-r--r-- | program/steps/addressbook/show.inc | 133 | ||||
-rw-r--r-- | program/steps/mail/autocomplete.inc | 20 | ||||
-rw-r--r-- | program/steps/mail/compose.inc | 2 |
14 files changed, 711 insertions, 128 deletions
diff --git a/program/steps/addressbook/copy.inc b/program/steps/addressbook/copy.inc index 152add2e1..b891e012c 100644 --- a/program/steps/addressbook/copy.inc +++ b/program/steps/addressbook/copy.inc @@ -48,7 +48,7 @@ if ($cid && preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid) & 'record' => $a_record, 'source' => $target, 'group' => $target_group)); if (!$plugin['abort']) { - if ($insert_id = $TARGET->insert($a_record, false)) { + if ($insert_id = $TARGET->insert($plugin['record'], false)) { $ids[] = $insert_id; $success++; } diff --git a/program/steps/addressbook/delete.inc b/program/steps/addressbook/delete.inc index ea67746f4..1cd4f3550 100644 --- a/program/steps/addressbook/delete.inc +++ b/program/steps/addressbook/delete.inc @@ -38,6 +38,10 @@ if ($OUTPUT->ajax_call && // count contacts for this user $result = $CONTACTS->count(); + // update saved search after data changed + if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) + $_SESSION['search'][$search_request] = $CONTACTS->refresh_search(); + // update message count display $OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size)); $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result->count)); diff --git a/program/steps/addressbook/edit.inc b/program/steps/addressbook/edit.inc index a65eafef1..747e12a3f 100644 --- a/program/steps/addressbook/edit.inc +++ b/program/steps/addressbook/edit.inc @@ -31,48 +31,143 @@ if ($CONTACTS->readonly) { } +function rcmail_contact_edithead($attrib) +{ + global $RCMAIL, $CONTACTS; + + // check if we have a valid result + if ($RCMAIL->action != 'add' + && !(($result = $CONTACTS->get_result()) && ($record = $result->first())) + ) { + $RCMAIL->output->show_message('contactnotfound'); + return false; + } + + $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), + 'company' => array('size' => $i_size), + 'department' => array('size' => $i_size), + 'jobtitle' => array('size' => $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, $CONTACTS, $OUTPUT; + global $RCMAIL, $CONTACTS, $CONTACT_COLTYPES; // check if we have a valid result if ($RCMAIL->action != 'add' && !(($result = $CONTACTS->get_result()) && ($record = $result->first())) ) { - $OUTPUT->show_message('contactnotfound'); + $RCMAIL->output->show_message('contactnotfound'); return false; } // add some labels to client - $OUTPUT->add_label('noemailwarning', 'nonamewarning'); + $RCMAIL->output->add_label('noemailwarning', 'nonamewarning'); $i_size = !empty($attrib['size']) ? $attrib['size'] : 40; - $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 6; + $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 10; $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40; $form = array( 'info' => array( 'name' => rcube_label('contactproperties'), 'content' => array( - 'name' => array('type' => 'text', 'size' => $i_size), - 'firstname' => array('type' => 'text', 'size' => $i_size), - 'surname' => array('type' => 'text', 'size' => $i_size), - 'email' => array('type' => 'text', 'size' => $i_size), + 'gender' => array('visible' => false), + 'maidenname' => array('size' => $i_size), + 'email' => array('size' => $i_size, 'visible' => true), + 'phone' => array('size' => $i_size, 'visible' => true), + 'address' => array('visible' => true), + 'birthday' => array('size' => 12), + 'anniversary' => array('size' => $i_size), + 'website' => array('size' => $i_size), + 'im' => 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('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); + $out = rcmail_contact_form($form, $record, $attrib); return $form_start . $out . $form_end; } +function rcmail_upload_photo_form($attrib) +{ + global $OUTPUT; + + // add ID if not given + if (!$attrib['id']) + $attrib['id'] = 'rcmUploadbox'; + + // 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('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)))) . + 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('uploadbox', $attrib['id']); + return $out; +} + + // similar function as in /steps/settings/edit_identity.inc function get_form_tags($attrib) { @@ -103,7 +198,10 @@ function get_form_tags($attrib) } +$OUTPUT->add_handler('contactedithead', 'rcmail_contact_edithead'); $OUTPUT->add_handler('contacteditform', 'rcmail_contact_editform'); +$OUTPUT->add_handler('contactphoto', 'rcmail_contact_photo'); +$OUTPUT->add_handler('photouploadform', 'rcmail_upload_photo_form'); if (!$CONTACTS->get_result() && $OUTPUT->template_exists('contactadd')) $OUTPUT->send('contactadd'); diff --git a/program/steps/addressbook/export.inc b/program/steps/addressbook/export.inc index 1b2e02944..509be596f 100644 --- a/program/steps/addressbook/export.inc +++ b/program/steps/addressbook/export.inc @@ -30,13 +30,24 @@ header('Content-Type: text/x-vcard; charset='.RCMAIL_CHARSET); header('Content-Disposition: attachment; filename="rcube_contacts.vcf"'); while ($result && ($row = $result->next())) { - $vcard = new rcube_vcard($row['vcard']); - $vcard->set('displayname', $row['name']); - $vcard->set('firstname', $row['firstname']); - $vcard->set('surname', $row['surname']); - $vcard->set('email', $row['email']); - - echo $vcard->export(); + // we already have a vcard record + if ($row['vcard']) { + echo $row['vcard']; + } + // copy values into vcard object + else { + $vcard = new rcube_vcard($row['vcard']); + $vcard->reset(); + foreach ($row as $key => $values) { + list($field, $section) = explode(':', $key); + foreach ((array)$values as $value) { + if (is_array($value) || strlen($value)) + $vcard->set($field, $value, strtoupper($section)); + } + } + + echo $vcard->export(); + } } exit; diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index e8b05edb4..e9b3dc8f2 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -56,6 +56,56 @@ if (!$OUTPUT->ajax_call) { } +// general definition of contact coltypes +$CONTACT_COLTYPES = array( + 'name' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('name')), + 'firstname' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('firstname')), + 'surname' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('surname')), + 'middlename' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('middlename')), + 'prefix' => array('type' => 'text', 'size' => 8, 'limit' => 1, 'label' => rcube_label('nameprefix')), + 'suffix' => array('type' => 'text', 'size' => 8, 'limit' => 1, 'label' => rcube_label('namesuffix')), + 'nickname' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('nickname')), + 'jobtitle' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('jobtitle')), + 'organization' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('organization')), + 'department' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('department')), + 'gender' => array('type' => 'select', 'limit' => 1, 'label' => rcube_label('gender'), 'options' => array('male' => rcube_label('male'), 'female' => rcube_label('female'))), + 'maidenname' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('maidenname')), + 'email' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('email'), 'subtypes' => array('home','work','other')), + 'phone' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('phone'), 'subtypes' => array('home','home2','work','work2','mobile','main','homefax','workfax','car','pager','video','assistant','other')), + 'address' => array('type' => 'composite', 'label' => rcube_label('address'), 'subtypes' => array('home','work','other'), 'childs' => array( + 'street' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('street')), + 'locality' => array('type' => 'text', 'size' => 28, 'label' => rcube_label('locality')), + 'zipcode' => array('type' => 'text', 'size' => 8, 'label' => rcube_label('zipcode')), + 'region' => array('type' => 'text', 'size' => 12, 'label' => rcube_label('region')), + 'country' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('country')), + )), + 'birthday' => array('type' => 'date', 'size' => 12, 'label' => rcube_label('birthday'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col'), + 'anniversary' => array('type' => 'date', 'size' => 12, 'label' => rcube_label('anniversary'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col'), + 'website' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('website'), 'subtypes' => array('homepage','work','blog','other')), + 'im' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('instantmessenger'), 'subtypes' => array('aim','icq','msn','yahoo','jabber','skype','other')), + 'notes' => array('type' => 'textarea', 'size' => 40, 'rows' => 15, 'label' => rcube_label('notes'), 'limit' => 1), + 'photo' => array('type' => 'image', 'limit' => 1), + 'assistant' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('assistant')), + 'manager' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('manager')), + 'spouse' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('spouse')), + // TODO: define fields for vcards like GEO, KEY +); + +// reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object +if (is_array($CONTACTS->coltypes)) { + // remove cols not listed by the backend class + $contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes; + $CONTACT_COLTYPES = array_intersect_key($CONTACT_COLTYPES, $contact_cols); + // add associative coltypes definition + if (!$CONTACTS->coltypes[0]) { + foreach ($CONTACTS->coltypes as $col => $colprop) + $CONTACT_COLTYPES[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], $colprop) : $colprop; + } +} + +$OUTPUT->set_env('photocol', is_array($CONTACT_COLTYPES['photo'])); + + function rcmail_directory_list($attrib) { global $RCMAIL, $OUTPUT; @@ -72,23 +122,21 @@ function rcmail_directory_list($attrib) html::a(array('href' => '%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s')); - if (!$current && strtolower($RCMAIL->config->get('address_book_type', 'sql')) != 'ldap') { - $current = '0'; - } - else if (!$current) { - // DB address book not used, see if a source is set, if not use the - // first LDAP directory. - $current = key((array)$RCMAIL->config->get('ldap_public', array())); - } + // currently selected is the first address source in the list + if (!isset($current)) + $current = strval(key((array)$OUTPUT->env['address_sources'])); foreach ((array)$OUTPUT->env['address_sources'] as $j => $source) { - $id = $source['id'] ? $source['id'] : $j; + $id = strval($source['id'] ? $source['id'] : $j); $js_id = JQ($id); - $dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id); - $out .= sprintf($line_templ, $dom_id, ($current == $id ? 'selected' : ''), + $dom_id = preg_replace('/[^a-z0-9\-_]/i', '_', $id); + $out .= sprintf($line_templ, $dom_id, ($current === $id ? 'selected' : ''), Q(rcmail_url(null, array('_source' => $id))), $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id))); - $groupdata = rcmail_contact_groups(array('out' => $out, 'jsdata' => $jsdata, 'source' => $id)); + + $groupdata = array('out' => $out, 'jsdata' => $jsdata, 'source' => $id); + if ($source['groups']) + $groupdata = rcmail_contact_groups($groupdata); $jsdata = $groupdata['jsdata']; $out = $groupdata['out']; } @@ -130,16 +178,16 @@ function rcmail_contacts_list($attrib) { global $CONTACTS, $OUTPUT; + // define list of cols to be displayed + $a_show_cols = array('name'); + // count contacts for this user - $result = $CONTACTS->list_records(); + $result = $CONTACTS->list_records($a_show_cols); // add id to message list table if not specified if (!strlen($attrib['id'])) $attrib['id'] = 'rcmAddressList'; - // define list of cols to be displayed - $a_show_cols = array('name'); - // create XHTML table $out = rcube_table_output($attrib, $result->records, $a_show_cols, $CONTACTS->primary_key); @@ -233,9 +281,9 @@ function rcmail_get_rowcount_text() } -function rcmail_contact_form($form, $record) +function rcmail_contact_form($form, $record, $attrib = null) { - global $RCMAIL; + global $RCMAIL, $CONFIG; // Allow plugins to modify contact form content $plugin = $RCMAIL->plugins->exec_hook('contact_form', array( @@ -243,35 +291,222 @@ function rcmail_contact_form($form, $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_lables = array(); + + foreach ($coltypes as $col => $prop) { + if ($prop['subtypes']) { + $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype')); + $select_subtype->add($prop['subtypes']); + $coltypes[$col]['subtypes_select'] = $select_subtype->show(); + } + if ($prop['childs']) { + foreach ($prop['childs'] as $childcol => $cp) + $coltype_lables[$childcol] = array('label' => $cp['label']); + } + } - foreach ($form as $fieldset) { + 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 = ''; + + $names_arr = array($record['prefix'], $record['firstname'], $record['middlename'], $record['surname'], $record['suffix']); + if ($record['name'] == join(' ', array_filter($names_arr))) + unset($record['name']); + + // group fields + $field_blocks = array( + 'names' => array('prefix','firstname','middlename','surname','suffix'), + 'displayname' => array('name'), + 'nickname' => array('nickname'), + 'jobnames' => array('organization','department','jobtitle'), + ); + foreach ($field_blocks as $blockname => $colnames) { + $fields = ''; + foreach ($colnames as $col) { + // skip cols unknown to the backend + if (!$coltypes[$col]) + continue; + + 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'])) { - $table = new html_table(array('cols' => 2)); - foreach ($fieldset['content'] as $col => $colprop) { - $colprop['id'] = 'rcmfd_'.$col; - - $label = !empty($colprop['label']) ? $colprop['label'] : rcube_label($col); + // 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'])) { + $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype')); + $select_subtype->add($colprop['subtypes']); + } + else + $select_subtype = null; if (!empty($colprop['value'])) { - $value = $colprop['value']; - } - else if ($RCMAIL->action == 'show') { - $value = $record[$col]; + $values = (array)$colprop['value']; } else { - $value = rcmail_get_edit_field($col, $record[$col], $colprop, $colprop['type']); + // 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; + } } - $table->add('title', sprintf('<label for="%s">%s</label>', $colprop['id'], Q($label))); - $table->add(null, $value); + // hack: create empty values array to force this field to be displayed + if (empty($values) && $colprop['visible']) + $values[] = ''; + + $rows = ''; + foreach ((array)$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) { + $childvalue = $val[$childcol] ? $val[$childcol] : $val[$j]; + + 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 = 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; + + $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 = $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 + $content .= html::tag('fieldset', array('class' => 'contactfieldgroup contactcontroller' . $col, 'style' => ($rows ? null : 'display:none')), + ($colprop['subtypes'] ? html::tag('legend', null, Q($colprop['label'])) : ' ') . + $rows); } - $content = $table->show(); + + // 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']; @@ -279,11 +514,53 @@ function rcmail_contact_form($form, $record) $out .= html::tag('fieldset', null, html::tag('legend', null, Q($fieldset['name'])) . $content) ."\n"; } - + + if ($edit_mode) { + $RCMAIL->output->set_env('coltypes', $coltypes + $coltype_lables); + $RCMAIL->output->set_env('delbutton', $del_button); + $RCMAIL->output->add_label('delete'); + } + return $out; } +function rcmail_contact_photo($attrib) +{ + global $CONTACTS, $CONTACT_COLTYPES, $RCMAIL, $CONFIG; + + if ($result = $CONTACTS->get_result()) + $record = $result->first(); + + $photo_img = $attrib['placeholder'] ? $CONFIG['skin_path'] . $attrib['placeholder'] : 'program/blank.gif'; + unset($attrib['placeholder']); + + if ($CONTACT_COLTYPES['photo']) { + $RCMAIL->output->set_env('photo_placeholder', $photo_img); + + if ($record['photo']) + $photo_img = $RCMAIL->url(array('_action' => 'photo', '_cid' => $record['ID'], '_source' => $_REQUEST['_source'])); + $img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => '')); + $content = html::div($attrib, $img); + + if ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add') { + $RCMAIL->output->add_gui_object('contactphoto', $attrib['id']); + $hidden = new html_hiddenfield(array('name' => '_photo', 'id' => 'ff_photo')); + $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')); +} + + // register UI objects $OUTPUT->add_handlers(array( 'directorylist' => 'rcmail_directory_list', @@ -297,6 +574,8 @@ $OUTPUT->add_handlers(array( // register action aliases $RCMAIL->register_action_map(array( 'add' => 'edit.inc', + 'photo' => 'show.inc', + 'upload-photo' => 'save.inc', 'group-create' => 'groups.inc', 'group-rename' => 'groups.inc', 'group-delete' => 'groups.inc', diff --git a/program/steps/addressbook/groups.inc b/program/steps/addressbook/groups.inc index b7fdb2fff..b70bbf265 100644 --- a/program/steps/addressbook/groups.inc +++ b/program/steps/addressbook/groups.inc @@ -79,8 +79,7 @@ else if ($RCMAIL->action == 'group-create') { if ($created && $OUTPUT->ajax_call) { $OUTPUT->show_message('groupcreated', 'confirmation'); - $OUTPUT->command('insert_contact_group', array( - 'source' => $source, 'id' => $created['id'], 'name' => $created['name'])); + $OUTPUT->command('insert_contact_group', array('source' => $source) + $created); } else if (!$created) { $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error'); diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc index 61b757f43..532afdbcd 100644 --- a/program/steps/addressbook/import.inc +++ b/program/steps/addressbook/import.inc @@ -150,13 +150,8 @@ if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name' } } - $a_record = array( - 'name' => $vcard->displayname, - 'firstname' => $vcard->firstname, - 'surname' => $vcard->surname, - 'email' => $email, - 'vcard' => $vcard->export(), - ); + $a_record = $vcard->get_assoc(); + $a_record['vcard'] = $vcard->export(); $plugin = $RCMAIL->plugins->exec_hook('contact_create', array('record' => $a_record, 'source' => null)); $a_record = $plugin['record']; diff --git a/program/steps/addressbook/list.inc b/program/steps/addressbook/list.inc index 018f6e2a6..234e1a633 100644 --- a/program/steps/addressbook/list.inc +++ b/program/steps/addressbook/list.inc @@ -20,7 +20,7 @@ */ // get contacts for this user -$result = $CONTACTS->list_records(); +$result = $CONTACTS->list_records(array('name')); // update message count display $OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size)); diff --git a/program/steps/addressbook/mailto.inc b/program/steps/addressbook/mailto.inc index d38ae9e62..702e1a61b 100644 --- a/program/steps/addressbook/mailto.inc +++ b/program/steps/addressbook/mailto.inc @@ -29,8 +29,10 @@ if ($cid && preg_match('/^[a-z0-9\+\/=_-]+(,[a-z0-9\+\/=_-]+)*$/i', $cid) && $CO $CONTACTS->set_pagesize(100); $recipients = $CONTACTS->search($CONTACTS->primary_key, $cid); - while (is_object($recipients) && ($rec = $recipients->iterate())) - $mailto[] = format_email_recipient($rec['email'], $rec['name']); + 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)) diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc index add8ef4e3..7d29b6fb2 100644 --- a/program/steps/addressbook/save.inc +++ b/program/steps/addressbook/save.inc @@ -5,7 +5,7 @@ | program/steps/addressbook/save.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2009, The Roundcube Dev Team | + | Copyright (C) 2005-2011, The Roundcube Dev Team | | Licensed under the GNU GPL | | | | PURPOSE: | @@ -29,33 +29,147 @@ if ($CONTACTS->readonly) { return; } -// Basic input checks -if ((!get_input_value('_name', RCUBE_INPUT_POST) || !get_input_value('_email', RCUBE_INPUT_POST))) { - $OUTPUT->show_message('formincomplete', 'warning'); - rcmail_overwrite_action($return_action); - return; -} +// handle photo upload for contacts +if ($RCMAIL->action == 'upload-photo') { + // clear all stored output properties (like scripts and env vars) + $OUTPUT->reset(); + + if ($filepath = $_FILES['_photo']['tmp_name']) { + // check file type and resize image + $imageprop = rcmail::imageprops($_FILES['_photo']['tmp_name']); + + if ($imageprop['width'] && $imageprop['height']) { + $maxsize = intval($RCMAIL->config->get('contact_photo_size', 160)); + $tmpfname = tempnam($RCMAIL->config->get('temp_dir'), 'rcmImgConvert'); + $save_hook = 'attachment_upload'; + + // scale image to a maximum size + if (($imageprop['width'] > $maxsize || $imageprop['height'] > $maxsize) && + (rcmail::imageconvert(array('in' => $filepath, 'out' => $tmpfname, 'size' => $maxsize.'x'.$maxsize, 'type' => $imageprop['type'])) !== false)) { + $filepath = $tmpfname; + $save_hook = 'attachment_save'; + } + + // save uploaded file in storage backend + $attachment = $RCMAIL->plugins->exec_hook($save_hook, array( + 'path' => $filepath, + 'size' => $_FILES['_photo']['size'], + 'name' => $_FILES['_photo']['name'], + 'mimetype' => 'image/' . $imageprop['type'], + )); + } + 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'); +} -// setup some vars we need -$a_save_cols = array('name', 'firstname', 'surname', 'email'); -$a_record = array(); // read POST values into hash array -foreach ($a_save_cols as $col) { +$a_record = array(); +foreach ($GLOBALS['CONTACT_COLTYPES'] as $col => $colprop) { $fname = '_'.$col; - if (isset($_POST[$fname])) + 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); + foreach ((array)$vals as $i => $val) + $values[$i][$childcol] = $val; + } + $subtypes = get_input_value('_subtype_' . $col, RCUBE_INPUT_POST); + foreach ($subtypes as $i => $subtype) + if ($values[$i]) + $a_record[$col.':'.$subtype][] = $values[$i]; + } + // assign values and subtypes + else if (is_array($_POST[$fname])) { + $values = get_input_value($fname, RCUBE_INPUT_POST); + $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); + } } -// Validity checks -$_email = idn_to_ascii($a_record['email']); -if (!check_email($_email, false)) { - $OUTPUT->show_message('emailformaterror', 'warning', array('email' => $_email)); +if (empty($a_record['name'])) + $a_record['name'] = join(' ', array_filter(array($a_record['prefix'], $a_record['firstname'], $a_record['middlename'], $a_record['surname'], $a_record['suffix'],))); + +#var_dump($a_record); + +// Basic input checks (TODO: delegate to $CONTACTS instance) +if (empty($a_record['name'])/* || empty($a_record['email'])*/) { + $OUTPUT->show_message('formincomplete', 'warning'); rcmail_overwrite_action($return_action); return; } +// Validity checks +foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) { + if (strlen($email)) { + $_email = idn_to_ascii($email); + if (!check_email($_email, false)) { + $OUTPUT->show_message('emailformaterror', 'warning', array('email' => $email)); + 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()); + $RCMAIL->session->remove('contacts'); +} + // update an existing contact if (!empty($cid)) { @@ -92,7 +206,8 @@ if (!empty($cid)) } else { // show error message - $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error', null, false); + $err = $CONTACTS->get_error(); + $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false); rcmail_overwrite_action('show'); } } @@ -100,10 +215,16 @@ if (!empty($cid)) // insert a new contact else { // check for existing contacts - $existing = $CONTACTS->search('email', $a_record['email'], true, false); + $existing = false; + foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) { + if (($res = $CONTACTS->search('email', $email, true, false)) && $res->count) { + $existing = true; + break; + } + } // show warning message - if ($existing->count) { + if ($existing) { $OUTPUT->show_message('contactexists', 'warning', null, false); rcmail_overwrite_action('add'); return; @@ -138,7 +259,8 @@ else { } else { // show error message - $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error', null, false); + $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/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc index 8cacbd9b1..9e40abad7 100644 --- a/program/steps/addressbook/search.inc +++ b/program/steps/addressbook/search.inc @@ -5,7 +5,7 @@ | program/steps/addressbook/search.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2007, The Roundcube Dev Team | + | Copyright (C) 2005-2011, The Roundcube Dev Team | | Licensed under the GNU GPL | | | | PURPOSE: | @@ -28,11 +28,11 @@ $search_request = md5('addr'.$search); // get contacts for this user $result = $CONTACTS->search(array('name','email'), $search); +// save search settings in session +$_SESSION['search'][$search_request] = $CONTACTS->get_search_set(); + if ($result->count > 0) { - // save search settings in session - $_SESSION['search'][$search_request] = $CONTACTS->get_search_set(); - // create javascript list rcmail_js_contacts_list($result); } diff --git a/program/steps/addressbook/show.inc b/program/steps/addressbook/show.inc index 43ded2a6e..eb26450e6 100644 --- a/program/steps/addressbook/show.inc +++ b/program/steps/addressbook/show.inc @@ -25,8 +25,30 @@ if (($cid = get_input_value('_cid', RCUBE_INPUT_GPC)) && ($record = $CONTACTS->g $OUTPUT->set_env('cid', $record['ID']); } +// return raw photo of the given contact +if ($RCMAIL->action == 'photo') { + 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); + } + + header('Content-Type: ' . rc_image_content_type($data)); + echo $data ? $data : file_get_contents('program/blank.gif'); + exit; +} -function rcmail_contact_details($attrib) + +function rcmail_contact_head($attrib) { global $CONTACTS, $RCMAIL; @@ -36,54 +58,99 @@ function rcmail_contact_details($attrib) return false; } - $i_size = !empty($attrib['size']) ? $attrib['size'] : 40; - $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 6; - $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40; - $microformats = array('name' => 'fn', 'email' => 'email'); $form = array( - 'info' => array( - 'name' => rcube_label('contactproperties'), + 'head' => array( // section 'head' is magic! 'content' => array( - 'name' => array('type' => 'text', 'size' => $i_size), - 'firstname' => array('type' => 'text', 'size' => $i_size), - 'surname' => array('type' => 'text', 'size' => $i_size), - 'email' => array('type' => 'text', 'size' => $i_size), + 'prefix' => array('type' => 'text'), + 'firstname' => array('type' => 'text'), + 'middlename' => array('type' => 'text'), + 'surname' => array('type' => 'text'), + 'suffix' => array('type' => 'text'), ), ), - 'groups' => array( - 'name' => rcube_label('groups'), - 'content' => '', - ), ); - // Get content of groups fieldset - if ($groups = rcmail_contact_record_groups($record['ID'])) { - $form['groups']['content'] = $groups; - } - else { - unset($form['groups']); + 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; } - if (!empty($record['email'])) { - $form['info']['content']['email']['value'] = html::a(array( - 'href' => 'mailto:' . $record['email'], - 'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($record['email'])), - 'title' => rcube_label('composeto'), - 'class' => $microformats['email'], - ), Q($record['email'])); + $i_size = !empty($attrib['size']) ? $attrib['size'] : 40; + + $form = array( + 'info' => array( + 'name' => rcube_label('contactproperties'), + 'content' => array( + 'gender' => array('size' => $i_size), + 'maidenname' => array('size' => $i_size), + 'email' => array('size' => $i_size, 'render_func' => 'rcmail_render_email_value'), + 'phone' => array('size' => $i_size), + 'address' => array(), + 'birthday' => array('size' => $i_size), + 'anniversary' => array('size' => $i_size), + 'website' => array('size' => $i_size, 'render_func' => 'rcmail_render_url_value'), + 'im' => 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), + ), + ); } - foreach (array('name', 'firstname', 'surname') as $col) { - if ($record[$col]) { - $form['info']['content'][$col]['value'] = html::span($microformats[$col], Q($record[$col])); - } + + 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('![htfps]+://!', $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; @@ -124,6 +191,8 @@ function rcmail_contact_record_groups($contact_id) //$OUTPUT->framed = $_framed; +$OUTPUT->add_handler('contacthead', 'rcmail_contact_head'); $OUTPUT->add_handler('contactdetails', 'rcmail_contact_details'); +$OUTPUT->add_handler('contactphoto', 'rcmail_contact_photo'); $OUTPUT->send('contact'); diff --git a/program/steps/mail/autocomplete.inc b/program/steps/mail/autocomplete.inc index b42ebf87b..36542caec 100644 --- a/program/steps/mail/autocomplete.inc +++ b/program/steps/mail/autocomplete.inc @@ -29,8 +29,10 @@ if ($RCMAIL->action == 'group-expand') { $abook->set_group($gid); $abook->set_pagesize(1000); // TODO: limit number of group members by config $result = $abook->list_records(array('email','name')); - while ($result && ($sql_arr = $result->iterate())) - $members[] = format_email_recipient($sql_arr['email'], $sql_arr['name']); + while ($result && ($sql_arr = $result->iterate())) { + foreach ((array)$sql_arr['email'] as $email) + $members[] = format_email_recipient($email, $sql_arr['name']); + } $OUTPUT->command('replace_group_recipients', $gid, join(', ', $members)); } @@ -45,12 +47,14 @@ else if ($book_types && $search = get_input_value('_search', RCUBE_INPUT_GPC, tr if ($result = $abook->search(array('email','name'), $search, false, true, true, 'email')) { while ($sql_arr = $result->iterate()) { - $contact = format_email_recipient($sql_arr['email'], $sql_arr['name']); - // when we've got more than one book, we need to skip duplicates - if ($books_num == 1 || !in_array($contact, $contacts)) { - $contacts[] = $contact; - if (count($contacts) >= $MAXNUM) - break 2; + foreach ((array)$abook->get_col_values('email', $sql_arr, true) as $email) { + $contact = format_email_recipient($email, $sql_arr['name']); + // when we've got more than one book, we need to skip duplicates + if ($books_num == 1 || !in_array($contact, $contacts)) { + $contacts[] = $contact; + if (count($contacts) >= $MAXNUM) + break 2; + } } } } diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 2b342a9f0..335945c5c 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -1133,7 +1133,7 @@ function rcmail_compose_attachment_form($attrib) $out = html::div($attrib, $OUTPUT->form_tag(array('name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'), - html::div(null, rcmail_compose_attachment_field(array('size' => $attrib[attachmentfieldsize]))) . + html::div(null, rcmail_compose_attachment_field(array('size' => $attrib['attachmentfieldsize']))) . html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) . html::div('buttons', $button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' . |