Mercurial > hg > rc2
comparison program/steps/addressbook/func.inc @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:52:31 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4681f974d28b |
---|---|
1 <?php | |
2 | |
3 /** | |
4 +-----------------------------------------------------------------------+ | |
5 | program/steps/addressbook/func.inc | | |
6 | | | |
7 | This file is part of the Roundcube Webmail client | | |
8 | Copyright (C) 2005-2013, The Roundcube Dev Team | | |
9 | | | |
10 | Licensed under the GNU General Public License version 3 or | | |
11 | any later version with exceptions for skins & plugins. | | |
12 | See the README file for a full license statement. | | |
13 | | | |
14 | PURPOSE: | | |
15 | Provide addressbook functionality and GUI objects | | |
16 | | | |
17 +-----------------------------------------------------------------------+ | |
18 | Author: Thomas Bruederli <roundcube@gmail.com> | | |
19 +-----------------------------------------------------------------------+ | |
20 */ | |
21 | |
22 $SEARCH_MODS_DEFAULT = array('name'=>1, 'firstname'=>1, 'surname'=>1, 'email'=>1, '*'=>1); | |
23 | |
24 // general definition of contact coltypes | |
25 $CONTACT_COLTYPES = array( | |
26 'name' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('name'), 'category' => 'main'), | |
27 'firstname' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('firstname'), 'category' => 'main'), | |
28 'surname' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('surname'), 'category' => 'main'), | |
29 'email' => array('type' => 'text', 'size' => 40, 'maxlength' => 254, 'label' => $RCMAIL->gettext('email'), 'subtypes' => array('home','work','other'), 'category' => 'main'), | |
30 'middlename' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('middlename'), 'category' => 'main'), | |
31 'prefix' => array('type' => 'text', 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => $RCMAIL->gettext('nameprefix'), 'category' => 'main'), | |
32 'suffix' => array('type' => 'text', 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => $RCMAIL->gettext('namesuffix'), 'category' => 'main'), | |
33 'nickname' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('nickname'), 'category' => 'main'), | |
34 'jobtitle' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('jobtitle'), 'category' => 'main'), | |
35 'organization' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('organization'), 'category' => 'main'), | |
36 'department' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('department'), 'category' => 'main'), | |
37 'gender' => array('type' => 'select', 'limit' => 1, 'label' => $RCMAIL->gettext('gender'), 'options' => array('male' => $RCMAIL->gettext('male'), 'female' => $RCMAIL->gettext('female')), 'category' => 'personal'), | |
38 'maidenname' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('maidenname'), 'category' => 'personal'), | |
39 'phone' => array('type' => 'text', 'size' => 40, 'maxlength' => 20, 'label' => $RCMAIL->gettext('phone'), 'subtypes' => array('home','home2','work','work2','mobile','main','homefax','workfax','car','pager','video','assistant','other'), 'category' => 'main'), | |
40 'address' => array('type' => 'composite', 'label' => $RCMAIL->gettext('address'), 'subtypes' => array('home','work','other'), 'childs' => array( | |
41 'street' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => $RCMAIL->gettext('street'), 'category' => 'main'), | |
42 'locality' => array('type' => 'text', 'size' => 28, 'maxlength' => 50, 'label' => $RCMAIL->gettext('locality'), 'category' => 'main'), | |
43 'zipcode' => array('type' => 'text', 'size' => 8, 'maxlength' => 15, 'label' => $RCMAIL->gettext('zipcode'), 'category' => 'main'), | |
44 'region' => array('type' => 'text', 'size' => 12, 'maxlength' => 50, 'label' => $RCMAIL->gettext('region'), 'category' => 'main'), | |
45 'country' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => $RCMAIL->gettext('country'), 'category' => 'main'), | |
46 ), 'category' => 'main'), | |
47 'birthday' => array('type' => 'date', 'size' => 12, 'maxlength' => 16, 'label' => $RCMAIL->gettext('birthday'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col', 'category' => 'personal'), | |
48 'anniversary' => array('type' => 'date', 'size' => 12, 'maxlength' => 16, 'label' => $RCMAIL->gettext('anniversary'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col', 'category' => 'personal'), | |
49 'website' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => $RCMAIL->gettext('website'), 'subtypes' => array('homepage','work','blog','profile','other'), 'category' => 'main'), | |
50 'im' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => $RCMAIL->gettext('instantmessenger'), 'subtypes' => array('aim','icq','msn','yahoo','jabber','skype','other'), 'category' => 'main'), | |
51 'notes' => array('type' => 'textarea', 'size' => 40, 'rows' => 15, 'maxlength' => 500, 'label' => $RCMAIL->gettext('notes'), 'limit' => 1), | |
52 'photo' => array('type' => 'image', 'limit' => 1, 'category' => 'main'), | |
53 'assistant' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('assistant'), 'category' => 'personal'), | |
54 'manager' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('manager'), 'category' => 'personal'), | |
55 'spouse' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => $RCMAIL->gettext('spouse'), 'category' => 'personal'), | |
56 // TODO: define fields for vcards like GEO, KEY | |
57 ); | |
58 | |
59 $PAGE_SIZE = $RCMAIL->config->get('addressbook_pagesize', $RCMAIL->config->get('pagesize', 50)); | |
60 | |
61 // Addressbook UI | |
62 if (!$RCMAIL->action && !$OUTPUT->ajax_call) { | |
63 // add list of address sources to client env | |
64 $js_list = $RCMAIL->get_address_sources(); | |
65 | |
66 // count all/writeable sources | |
67 $writeable = 0; | |
68 $count = 0; | |
69 foreach ($js_list as $sid => $s) { | |
70 $count++; | |
71 if (!$s['readonly']) { | |
72 $writeable++; | |
73 } | |
74 // unset hidden sources | |
75 if ($s['hidden']) { | |
76 unset($js_list[$sid]); | |
77 } | |
78 } | |
79 | |
80 $search_mods = $RCMAIL->config->get('addressbook_search_mods', $SEARCH_MODS_DEFAULT); | |
81 $OUTPUT->set_env('search_mods', $search_mods); | |
82 $OUTPUT->set_env('address_sources', $js_list); | |
83 $OUTPUT->set_env('writable_source', $writeable); | |
84 | |
85 $OUTPUT->set_pagetitle($RCMAIL->gettext('contacts')); | |
86 $_SESSION['addressbooks_count'] = $count; | |
87 $_SESSION['addressbooks_count_writeable'] = $writeable; | |
88 | |
89 // select address book | |
90 $source = rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); | |
91 | |
92 // use first directory by default | |
93 if (!strlen($source) || !isset($js_list[$source])) { | |
94 $source = $RCMAIL->config->get('default_addressbook'); | |
95 if (!strlen($source) || !isset($js_list[$source])) { | |
96 $source = strval(key($js_list)); | |
97 } | |
98 } | |
99 | |
100 $CONTACTS = rcmail_contact_source($source, true); | |
101 } | |
102 | |
103 // remove undo information... | |
104 if ($undo = $_SESSION['contact_undo']) { | |
105 // ...after timeout | |
106 $undo_time = $RCMAIL->config->get('undo_timeout', 0); | |
107 if ($undo['ts'] < time() - $undo_time) | |
108 $RCMAIL->session->remove('contact_undo'); | |
109 } | |
110 | |
111 // register UI objects | |
112 $OUTPUT->add_handlers(array( | |
113 'directorylist' => 'rcmail_directory_list', | |
114 'savedsearchlist' => 'rcmail_savedsearch_list', | |
115 'addresslist' => 'rcmail_contacts_list', | |
116 'addresslisttitle' => 'rcmail_contacts_list_title', | |
117 'addressframe' => 'rcmail_contact_frame', | |
118 'recordscountdisplay' => 'rcmail_rowcount_display', | |
119 'searchform' => array($OUTPUT, 'search_form') | |
120 )); | |
121 | |
122 // register action aliases | |
123 $RCMAIL->register_action_map(array( | |
124 'add' => 'edit.inc', | |
125 'group-create' => 'groups.inc', | |
126 'group-rename' => 'groups.inc', | |
127 'group-delete' => 'groups.inc', | |
128 'group-addmembers' => 'groups.inc', | |
129 'group-delmembers' => 'groups.inc', | |
130 'search-create' => 'search.inc', | |
131 'search-delete' => 'search.inc', | |
132 )); | |
133 | |
134 | |
135 | |
136 // instantiate a contacts object according to the given source | |
137 function rcmail_contact_source($source=null, $init_env=false, $writable=false) | |
138 { | |
139 global $RCMAIL, $OUTPUT, $CONTACT_COLTYPES, $PAGE_SIZE; | |
140 | |
141 if (!strlen($source)) { | |
142 $source = rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); | |
143 } | |
144 | |
145 // Get object | |
146 $CONTACTS = $RCMAIL->get_address_book($source, $writable); | |
147 $CONTACTS->set_pagesize($PAGE_SIZE); | |
148 | |
149 // set list properties and session vars | |
150 if (!empty($_GET['_page'])) | |
151 $CONTACTS->set_page(($_SESSION['page'] = intval($_GET['_page']))); | |
152 else | |
153 $CONTACTS->set_page(isset($_SESSION['page']) ? $_SESSION['page'] : 1); | |
154 | |
155 if ($group = rcube_utils::get_input_value('_gid', rcube_utils::INPUT_GP)) { | |
156 $CONTACTS->set_group($group); | |
157 } | |
158 | |
159 if (!$init_env) { | |
160 return $CONTACTS; | |
161 } | |
162 | |
163 $OUTPUT->set_env('readonly', $CONTACTS->readonly); | |
164 $OUTPUT->set_env('source', (string) $source); | |
165 $OUTPUT->set_env('group', $group); | |
166 | |
167 // reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object | |
168 if (is_array($CONTACTS->coltypes)) { | |
169 // remove cols not listed by the backend class | |
170 $contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes; | |
171 $CONTACT_COLTYPES = array_intersect_key($CONTACT_COLTYPES, $contact_cols); | |
172 // add associative coltypes definition | |
173 if (!$CONTACTS->coltypes[0]) { | |
174 foreach ($CONTACTS->coltypes as $col => $colprop) { | |
175 if (is_array($colprop['childs'])) { | |
176 foreach ($colprop['childs'] as $childcol => $childprop) | |
177 $colprop['childs'][$childcol] = array_merge((array)$CONTACT_COLTYPES[$col]['childs'][$childcol], $childprop); | |
178 } | |
179 $CONTACT_COLTYPES[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], $colprop) : $colprop; | |
180 } | |
181 } | |
182 } | |
183 | |
184 $OUTPUT->set_env('photocol', is_array($CONTACT_COLTYPES['photo'])); | |
185 | |
186 return $CONTACTS; | |
187 } | |
188 | |
189 | |
190 function rcmail_set_sourcename($abook) | |
191 { | |
192 global $OUTPUT, $RCMAIL; | |
193 | |
194 // get address book name (for display) | |
195 if ($abook && $_SESSION['addressbooks_count'] > 1) { | |
196 $name = $abook->get_name(); | |
197 if (!$name) { | |
198 $name = $RCMAIL->gettext('personaladrbook'); | |
199 } | |
200 $OUTPUT->set_env('sourcename', html_entity_decode($name, ENT_COMPAT, 'UTF-8')); | |
201 } | |
202 } | |
203 | |
204 | |
205 function rcmail_directory_list($attrib) | |
206 { | |
207 global $RCMAIL, $OUTPUT; | |
208 | |
209 if (!$attrib['id']) | |
210 $attrib['id'] = 'rcmdirectorylist'; | |
211 | |
212 $out = ''; | |
213 $jsdata = array(); | |
214 | |
215 $line_templ = html::tag('li', array( | |
216 'id' => 'rcmli%s', 'class' => '%s', 'noclose' => true), | |
217 html::a(array('href' => '%s', | |
218 'rel' => '%s', | |
219 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('list','%s',this)"), '%s')); | |
220 | |
221 $sources = (array) $OUTPUT->get_env('address_sources'); | |
222 reset($sources); | |
223 | |
224 // currently selected source | |
225 $current = rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); | |
226 | |
227 foreach ($sources as $j => $source) { | |
228 $id = strval(strlen($source['id']) ? $source['id'] : $j); | |
229 $js_id = rcube::JQ($id); | |
230 | |
231 // set class name(s) | |
232 $class_name = 'addressbook'; | |
233 if ($current === $id) | |
234 $class_name .= ' selected'; | |
235 if ($source['readonly']) | |
236 $class_name .= ' readonly'; | |
237 if ($source['class_name']) | |
238 $class_name .= ' ' . $source['class_name']; | |
239 | |
240 $name = $source['name'] ?: $id; | |
241 $out .= sprintf($line_templ, | |
242 rcube_utils::html_identifier($id, true), | |
243 $class_name, | |
244 rcube::Q($RCMAIL->url(array('_source' => $id))), | |
245 $source['id'], | |
246 $js_id, $name); | |
247 | |
248 $groupdata = array('out' => $out, 'jsdata' => $jsdata, 'source' => $id); | |
249 if ($source['groups']) | |
250 $groupdata = rcmail_contact_groups($groupdata); | |
251 $jsdata = $groupdata['jsdata']; | |
252 $out = $groupdata['out']; | |
253 $out .= '</li>'; | |
254 } | |
255 | |
256 $OUTPUT->set_env('contactgroups', $jsdata); | |
257 $OUTPUT->set_env('collapsed_abooks', (string)$RCMAIL->config->get('collapsed_abooks','')); | |
258 $OUTPUT->add_gui_object('folderlist', $attrib['id']); | |
259 $OUTPUT->include_script('treelist.js'); | |
260 | |
261 // add some labels to client | |
262 $OUTPUT->add_label('deletegroupconfirm', 'groupdeleting', 'addingmember', 'removingmember', | |
263 'newgroup', 'grouprename', 'searchsave', 'namex', 'save' | |
264 ); | |
265 | |
266 return html::tag('ul', $attrib, $out, html::$common_attrib); | |
267 } | |
268 | |
269 | |
270 function rcmail_savedsearch_list($attrib) | |
271 { | |
272 global $RCMAIL, $OUTPUT; | |
273 | |
274 if (!$attrib['id']) | |
275 $attrib['id'] = 'rcmsavedsearchlist'; | |
276 | |
277 $out = ''; | |
278 $line_templ = html::tag('li', array( | |
279 'id' => 'rcmli%s', 'class' => '%s'), | |
280 html::a(array('href' => '#', 'rel' => 'S%s', | |
281 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('listsearch', '%s', this)"), '%s')); | |
282 | |
283 // Saved searches | |
284 $sources = $RCMAIL->user->list_searches(rcube_user::SEARCH_ADDRESSBOOK); | |
285 foreach ($sources as $source) { | |
286 $id = $source['id']; | |
287 $js_id = rcube::JQ($id); | |
288 | |
289 // set class name(s) | |
290 $classes = array('contactsearch'); | |
291 if (!empty($source['class_name'])) | |
292 $classes[] = $source['class_name']; | |
293 | |
294 $out .= sprintf($line_templ, | |
295 rcube_utils::html_identifier('S'.$id, true), | |
296 join(' ', $classes), | |
297 $id, | |
298 $js_id, rcube::Q($source['name'] ?: $id) | |
299 ); | |
300 } | |
301 | |
302 $OUTPUT->add_gui_object('savedsearchlist', $attrib['id']); | |
303 | |
304 return html::tag('ul', $attrib, $out, html::$common_attrib); | |
305 } | |
306 | |
307 | |
308 function rcmail_contact_groups($args) | |
309 { | |
310 global $RCMAIL; | |
311 | |
312 $groups_html = ''; | |
313 $groups = $RCMAIL->get_address_book($args['source'])->list_groups(); | |
314 | |
315 if (!empty($groups)) { | |
316 $line_templ = html::tag('li', array( | |
317 'id' => 'rcmli%s', 'class' => 'contactgroup'), | |
318 html::a(array('href' => '#', | |
319 'rel' => '%s:%s', | |
320 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('listgroup',{'source':'%s','id':'%s'},this)"), '%s')); | |
321 | |
322 // append collapse/expand toggle and open a new <ul> | |
323 $is_collapsed = strpos($RCMAIL->config->get('collapsed_abooks',''), '&'.rawurlencode($args['source']).'&') !== false; | |
324 $args['out'] .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), ' '); | |
325 | |
326 foreach ($groups as $group) { | |
327 $groups_html .= sprintf($line_templ, | |
328 rcube_utils::html_identifier('G' . $args['source'] . $group['ID'], true), | |
329 $args['source'], $group['ID'], | |
330 $args['source'], $group['ID'], rcube::Q($group['name']) | |
331 ); | |
332 $args['jsdata']['G'.$args['source'].$group['ID']] = array( | |
333 'source' => $args['source'], 'id' => $group['ID'], | |
334 'name' => $group['name'], 'type' => 'group'); | |
335 } | |
336 } | |
337 | |
338 $args['out'] .= html::tag('ul', | |
339 array('class' => 'groups', 'style' => ($is_collapsed || empty($groups) ? "display:none;" : null)), | |
340 $groups_html); | |
341 | |
342 return $args; | |
343 } | |
344 | |
345 | |
346 // return the contacts list as HTML table | |
347 function rcmail_contacts_list($attrib) | |
348 { | |
349 global $RCMAIL, $CONTACTS, $OUTPUT; | |
350 | |
351 // define list of cols to be displayed | |
352 $a_show_cols = array('name','action'); | |
353 | |
354 // add id to message list table if not specified | |
355 if (!strlen($attrib['id'])) | |
356 $attrib['id'] = 'rcmAddressList'; | |
357 | |
358 // create XHTML table | |
359 $out = $RCMAIL->table_output($attrib, array(), $a_show_cols, $CONTACTS->primary_key); | |
360 | |
361 // set client env | |
362 $OUTPUT->add_gui_object('contactslist', $attrib['id']); | |
363 $OUTPUT->set_env('current_page', (int)$CONTACTS->list_page); | |
364 $OUTPUT->include_script('list.js'); | |
365 | |
366 // add some labels to client | |
367 $OUTPUT->add_label('deletecontactconfirm', 'copyingcontact', 'movingcontact', 'contactdeleting'); | |
368 | |
369 return $out; | |
370 } | |
371 | |
372 | |
373 function rcmail_js_contacts_list($result, $prefix='') | |
374 { | |
375 global $OUTPUT, $RCMAIL; | |
376 | |
377 if (empty($result) || $result->count == 0) { | |
378 return; | |
379 } | |
380 | |
381 // define list of cols to be displayed | |
382 $a_show_cols = array('name','action'); | |
383 | |
384 while ($row = $result->next()) { | |
385 $emails = rcube_addressbook::get_col_values('email', $row, true); | |
386 $row['CID'] = $row['ID']; | |
387 $row['email'] = reset($emails); | |
388 | |
389 $source_id = $OUTPUT->get_env('source'); | |
390 $a_row_cols = array(); | |
391 $classes = array($row['_type'] ?: 'person'); | |
392 | |
393 // build contact ID with source ID | |
394 if (isset($row['sourceid'])) { | |
395 $row['ID'] = $row['ID'].'-'.$row['sourceid']; | |
396 $source_id = $row['sourceid']; | |
397 } | |
398 | |
399 // format each col | |
400 foreach ($a_show_cols as $col) { | |
401 $val = ''; | |
402 switch ($col) { | |
403 case 'name': | |
404 $val = rcube::Q(rcube_addressbook::compose_list_name($row)); | |
405 break; | |
406 | |
407 case 'action': | |
408 if ($row['_type'] == 'group') { | |
409 $val = html::a(array( | |
410 'href' => '#list', | |
411 'rel' => $row['ID'], | |
412 'title' => $RCMAIL->gettext('listgroup'), | |
413 'onclick' => sprintf("return %s.command('pushgroup',{'source':'%s','id':'%s'},this,event)", rcmail_output::JS_OBJECT_NAME, $source_id, $row['CID']), | |
414 ), '»'); | |
415 } | |
416 else | |
417 $val = ''; | |
418 break; | |
419 | |
420 default: | |
421 $val = rcube::Q($row[$col]); | |
422 break; | |
423 } | |
424 | |
425 $a_row_cols[$col] = $val; | |
426 } | |
427 | |
428 if ($row['readonly']) | |
429 $classes[] = 'readonly'; | |
430 | |
431 $OUTPUT->command($prefix.'add_contact_row', $row['ID'], $a_row_cols, join(' ', $classes), array_intersect_key($row, array('ID'=>1,'readonly'=>1,'_type'=>1,'email'=>1,'name'=>1))); | |
432 } | |
433 } | |
434 | |
435 | |
436 function rcmail_contacts_list_title($attrib) | |
437 { | |
438 global $OUTPUT, $RCMAIL; | |
439 | |
440 $attrib += array('label' => 'contacts', 'id' => 'rcmabooklisttitle', 'tag' => 'span'); | |
441 unset($attrib['name']); | |
442 | |
443 $OUTPUT->add_gui_object('addresslist_title', $attrib['id']); | |
444 $OUTPUT->add_label('contacts','uponelevel'); | |
445 | |
446 return html::tag($attrib['tag'], $attrib, $RCMAIL->gettext($attrib['label']), html::$common_attrib); | |
447 } | |
448 | |
449 | |
450 // similar function as /steps/settings/identities.inc::rcmail_identity_frame() | |
451 function rcmail_contact_frame($attrib) | |
452 { | |
453 global $OUTPUT; | |
454 | |
455 if (!$attrib['id']) | |
456 $attrib['id'] = 'rcmcontactframe'; | |
457 | |
458 return $OUTPUT->frame($attrib, true); | |
459 } | |
460 | |
461 | |
462 function rcmail_rowcount_display($attrib) | |
463 { | |
464 global $RCMAIL; | |
465 | |
466 if (!$attrib['id']) | |
467 $attrib['id'] = 'rcmcountdisplay'; | |
468 | |
469 $RCMAIL->output->add_gui_object('countdisplay', $attrib['id']); | |
470 | |
471 if ($attrib['label']) | |
472 $_SESSION['contactcountdisplay'] = $attrib['label']; | |
473 | |
474 return html::span($attrib, $RCMAIL->gettext('loading')); | |
475 } | |
476 | |
477 | |
478 function rcmail_get_rowcount_text($result=null) | |
479 { | |
480 global $RCMAIL, $CONTACTS, $PAGE_SIZE; | |
481 | |
482 // read nr of contacts | |
483 if (!$result) { | |
484 $result = $CONTACTS->get_result(); | |
485 } | |
486 | |
487 if ($result->count == 0) | |
488 $out = $RCMAIL->gettext('nocontactsfound'); | |
489 else | |
490 $out = $RCMAIL->gettext(array( | |
491 'name' => $_SESSION['contactcountdisplay'] ?: 'contactsfromto', | |
492 'vars' => array( | |
493 'from' => $result->first + 1, | |
494 'to' => min($result->count, $result->first + $PAGE_SIZE), | |
495 'count' => $result->count) | |
496 )); | |
497 | |
498 return $out; | |
499 } | |
500 | |
501 | |
502 function rcmail_get_type_label($type) | |
503 { | |
504 global $RCMAIL; | |
505 | |
506 $label = 'type'.$type; | |
507 if ($RCMAIL->text_exists($label, '*', $domain)) | |
508 return $RCMAIL->gettext($label, $domain); | |
509 else if (preg_match('/\w+(\d+)$/', $label, $m) | |
510 && ($label = preg_replace('/(\d+)$/', '', $label)) | |
511 && $RCMAIL->text_exists($label, '*', $domain)) | |
512 return $RCMAIL->gettext($label, $domain) . ' ' . $m[1]; | |
513 | |
514 return ucfirst($type); | |
515 } | |
516 | |
517 | |
518 function rcmail_contact_form($form, $record, $attrib = null) | |
519 { | |
520 global $RCMAIL; | |
521 | |
522 // group fields | |
523 $head_fields = array( | |
524 'names' => array('prefix','firstname','middlename','surname','suffix'), | |
525 'displayname' => array('name'), | |
526 'nickname' => array('nickname'), | |
527 'organization' => array('organization'), | |
528 'department' => array('department'), | |
529 'jobtitle' => array('jobtitle'), | |
530 ); | |
531 | |
532 // Allow plugins to modify contact form content | |
533 $plugin = $RCMAIL->plugins->exec_hook('contact_form', array( | |
534 'form' => $form, 'record' => $record, 'head_fields' => $head_fields)); | |
535 | |
536 $form = $plugin['form']; | |
537 $record = $plugin['record']; | |
538 $head_fields = $plugin['head_fields']; | |
539 $edit_mode = $RCMAIL->action != 'show' && $RCMAIL->action != 'print'; | |
540 $del_button = $attrib['deleteicon'] ? html::img(array('src' => $RCMAIL->output->get_skin_file($attrib['deleteicon']), 'alt' => $RCMAIL->gettext('delete'))) : $RCMAIL->gettext('delete'); | |
541 $out = ''; | |
542 | |
543 unset($attrib['deleteicon']); | |
544 | |
545 // get default coltypes | |
546 $coltypes = $GLOBALS['CONTACT_COLTYPES']; | |
547 $coltype_labels = array(); | |
548 | |
549 foreach ($coltypes as $col => $prop) { | |
550 if ($prop['subtypes']) { | |
551 $subtype_names = array_map('rcmail_get_type_label', $prop['subtypes']); | |
552 $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype', 'title' => $prop['label'] . ' ' . $RCMAIL->gettext('type'))); | |
553 $select_subtype->add($subtype_names, $prop['subtypes']); | |
554 $coltypes[$col]['subtypes_select'] = $select_subtype->show(); | |
555 } | |
556 if ($prop['childs']) { | |
557 foreach ($prop['childs'] as $childcol => $cp) | |
558 $coltype_labels[$childcol] = array('label' => $cp['label']); | |
559 } | |
560 } | |
561 | |
562 foreach ($form as $section => $fieldset) { | |
563 // skip empty sections | |
564 if (empty($fieldset['content'])) { | |
565 continue; | |
566 } | |
567 | |
568 $select_add = new html_select(array('class' => 'addfieldmenu', 'rel' => $section)); | |
569 $select_add->add($RCMAIL->gettext('addfield'), ''); | |
570 | |
571 // render head section with name fields (not a regular list of rows) | |
572 if ($section == 'head') { | |
573 $content = ''; | |
574 | |
575 // unset display name if it is composed from name parts | |
576 if ($record['name'] == rcube_addressbook::compose_display_name(array('name' => '') + (array)$record)) { | |
577 unset($record['name']); | |
578 } | |
579 | |
580 foreach ($head_fields as $blockname => $colnames) { | |
581 $fields = ''; | |
582 foreach ($colnames as $col) { | |
583 // skip cols unknown to the backend | |
584 if (!$coltypes[$col]) | |
585 continue; | |
586 | |
587 // skip cols not listed in the form definition | |
588 if (is_array($fieldset['content']) && !in_array($col, array_keys($fieldset['content']))) { | |
589 continue; | |
590 } | |
591 | |
592 // only string values are expected here | |
593 if (is_array($record[$col])) | |
594 $record[$col] = join(' ', $record[$col]); | |
595 | |
596 if (!$edit_mode) { | |
597 if (!empty($record[$col])) | |
598 $fields .= html::span('namefield ' . $col, rcube::Q($record[$col])) . " "; | |
599 } | |
600 else { | |
601 $colprop = (array)$fieldset['content'][$col] + (array)$coltypes[$col]; | |
602 $colprop['id'] = 'ff_'.$col; | |
603 if (empty($record[$col]) && !$colprop['visible']) { | |
604 $colprop['style'] = 'display:none'; | |
605 $select_add->add($colprop['label'], $col); | |
606 } | |
607 $fields .= rcube_output::get_edit_field($col, $record[$col], $colprop, $colprop['type']); | |
608 } | |
609 } | |
610 $content .= html::div($blockname, $fields); | |
611 } | |
612 | |
613 if ($edit_mode) | |
614 $content .= html::p('addfield', $select_add->show(null)); | |
615 | |
616 $out .= html::tag('fieldset', $attrib, (!empty($fieldset['name']) ? html::tag('legend', null, rcube::Q($fieldset['name'])) : '') . $content) ."\n"; | |
617 continue; | |
618 } | |
619 | |
620 $content = ''; | |
621 if (is_array($fieldset['content'])) { | |
622 foreach ($fieldset['content'] as $col => $colprop) { | |
623 // remove subtype part of col name | |
624 list($field, $subtype) = explode(':', $col); | |
625 if (!$subtype) $subtype = 'home'; | |
626 $fullkey = $col.':'.$subtype; | |
627 | |
628 // skip cols unknown to the backend | |
629 if (!$coltypes[$field] && empty($colprop['value'])) { | |
630 continue; | |
631 } | |
632 | |
633 // merge colprop with global coltype configuration | |
634 if ($coltypes[$field]) { | |
635 $colprop += $coltypes[$field]; | |
636 } | |
637 | |
638 $label = isset($colprop['label']) ? $colprop['label'] : $RCMAIL->gettext($col); | |
639 | |
640 // prepare subtype selector in edit mode | |
641 if ($edit_mode && is_array($colprop['subtypes'])) { | |
642 $subtype_names = array_map('rcmail_get_type_label', $colprop['subtypes']); | |
643 $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype', 'title' => $colprop['label'] . ' ' . $RCMAIL->gettext('type'))); | |
644 $select_subtype->add($subtype_names, $colprop['subtypes']); | |
645 } | |
646 else { | |
647 $select_subtype = null; | |
648 } | |
649 | |
650 if (!empty($colprop['value'])) { | |
651 $values = (array)$colprop['value']; | |
652 } | |
653 else { | |
654 // iterate over possible subtypes and collect values with their subtype | |
655 if (is_array($colprop['subtypes'])) { | |
656 $values = $subtypes = array(); | |
657 foreach (rcube_addressbook::get_col_values($field, $record) as $st => $vals) { | |
658 foreach ((array)$vals as $value) { | |
659 $i = count($values); | |
660 $subtypes[$i] = $st; | |
661 $values[$i] = $value; | |
662 } | |
663 // TODO: add $st to $select_subtype if missing ? | |
664 } | |
665 } | |
666 else { | |
667 $values = $record[$fullkey] ?: $record[$field]; | |
668 $subtypes = null; | |
669 } | |
670 } | |
671 | |
672 // hack: create empty values array to force this field to be displayed | |
673 if (empty($values) && $colprop['visible']) { | |
674 $values = array(''); | |
675 } | |
676 | |
677 if (!is_array($values)) { | |
678 // $values can be an object, don't use (array)$values syntax | |
679 $values = !empty($values) ? array($values) : array(); | |
680 } | |
681 | |
682 $rows = ''; | |
683 foreach ($values as $i => $val) { | |
684 if ($subtypes[$i]) { | |
685 $subtype = $subtypes[$i]; | |
686 } | |
687 | |
688 $colprop['id'] = 'ff_' . $col . intval($coltypes[$field]['count']); | |
689 | |
690 // render composite field | |
691 if ($colprop['type'] == 'composite') { | |
692 $composite = array(); $j = 0; | |
693 $template = $RCMAIL->config->get($col . '_template', '{'.join('} {', array_keys($colprop['childs'])).'}'); | |
694 foreach ($colprop['childs'] as $childcol => $cp) { | |
695 if (!empty($val) && is_array($val)) { | |
696 $childvalue = $val[$childcol] ?: $val[$j]; | |
697 } | |
698 else { | |
699 $childvalue = ''; | |
700 } | |
701 | |
702 if ($edit_mode) { | |
703 if ($colprop['subtypes'] || $colprop['limit'] != 1) $cp['array'] = true; | |
704 $composite['{'.$childcol.'}'] = rcube_output::get_edit_field($childcol, $childvalue, $cp, $cp['type']) . " "; | |
705 } | |
706 else { | |
707 $childval = $cp['render_func'] ? call_user_func($cp['render_func'], $childvalue, $childcol) : rcube::Q($childvalue); | |
708 $composite['{'.$childcol.'}'] = html::span('data ' . $childcol, $childval) . " "; | |
709 } | |
710 $j++; | |
711 } | |
712 | |
713 $coltypes[$field] += (array)$colprop; | |
714 $coltypes[$field]['count']++; | |
715 $val = preg_replace('/\{\w+\}/', '', strtr($template, $composite)); | |
716 } | |
717 else if ($edit_mode) { | |
718 // call callback to render/format value | |
719 if ($colprop['render_func']) | |
720 $val = call_user_func($colprop['render_func'], $val, $col); | |
721 | |
722 $coltypes[$field] = (array)$colprop + $coltypes[$field]; | |
723 | |
724 if ($colprop['subtypes'] || $colprop['limit'] != 1) | |
725 $colprop['array'] = true; | |
726 | |
727 // load jquery UI datepicker for date fields | |
728 if ($colprop['type'] == 'date') { | |
729 $colprop['class'] .= ($colprop['class'] ? ' ' : '') . 'datepicker'; | |
730 if (!$colprop['render_func']) | |
731 $val = rcmail_format_date_col($val); | |
732 } | |
733 | |
734 $val = rcube_output::get_edit_field($col, $val, $colprop, $colprop['type']); | |
735 $coltypes[$field]['count']++; | |
736 } | |
737 else if ($colprop['render_func']) | |
738 $val = call_user_func($colprop['render_func'], $val, $col); | |
739 else if (is_array($colprop['options']) && isset($colprop['options'][$val])) | |
740 $val = $colprop['options'][$val]; | |
741 else | |
742 $val = rcube::Q($val); | |
743 | |
744 // use subtype as label | |
745 if ($colprop['subtypes']) | |
746 $label = rcmail_get_type_label($subtype); | |
747 | |
748 // add delete button/link | |
749 if ($edit_mode && !($colprop['visible'] && $colprop['limit'] == 1)) | |
750 $val .= html::a(array('href' => '#del', 'class' => 'contactfieldbutton deletebutton', 'title' => $RCMAIL->gettext('delete'), 'rel' => $col), $del_button); | |
751 | |
752 // display row with label | |
753 if ($label) { | |
754 if ($RCMAIL->action == 'print') { | |
755 $_label = rcube::Q($colprop['label'] . ($label != $colprop['label'] ? ' (' . $label . ')' : '')); | |
756 } | |
757 else { | |
758 $_label = $select_subtype ? $select_subtype->show($subtype) : html::label($colprop['id'], rcube::Q($label)); | |
759 } | |
760 | |
761 $rows .= html::div('row', | |
762 html::div('contactfieldlabel label', $_label) . | |
763 html::div('contactfieldcontent '.$colprop['type'], $val)); | |
764 } | |
765 // row without label | |
766 else { | |
767 $rows .= html::div('row', html::div('contactfield', $val)); | |
768 } | |
769 } | |
770 | |
771 // add option to the add-field menu | |
772 if (!$colprop['limit'] || $coltypes[$field]['count'] < $colprop['limit']) { | |
773 $select_add->add($colprop['label'], $col); | |
774 $select_add->_count++; | |
775 } | |
776 | |
777 // wrap rows in fieldgroup container | |
778 if ($rows) { | |
779 $c_class = 'contactfieldgroup ' . ($colprop['subtypes'] ? 'contactfieldgroupmulti ' : '') . 'contactcontroller' . $col; | |
780 $with_label = $colprop['subtypes'] && $RCMAIL->action != 'print'; | |
781 $content .= html::tag( | |
782 'fieldset', | |
783 array('class' => $c_class, 'style' => ($rows ? null : 'display:none')), | |
784 ($with_label ? html::tag('legend', null, rcube::Q($colprop['label'])) : ' ') . $rows | |
785 ); | |
786 } | |
787 } | |
788 | |
789 if (!$content && (!$edit_mode || !$select_add->_count)) | |
790 continue; | |
791 | |
792 // also render add-field selector | |
793 if ($edit_mode) | |
794 $content .= html::p('addfield', $select_add->show(null, array('style' => $select_add->_count ? null : 'display:none'))); | |
795 | |
796 $content = html::div(array('id' => 'contactsection' . $section), $content); | |
797 } | |
798 else { | |
799 $content = $fieldset['content']; | |
800 } | |
801 | |
802 if ($content) | |
803 $out .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($fieldset['name'])) . $content) ."\n"; | |
804 } | |
805 | |
806 if ($edit_mode) { | |
807 $RCMAIL->output->set_env('coltypes', $coltypes + $coltype_labels); | |
808 $RCMAIL->output->set_env('delbutton', $del_button); | |
809 $RCMAIL->output->add_label('delete'); | |
810 } | |
811 | |
812 return $out; | |
813 } | |
814 | |
815 | |
816 function rcmail_contact_photo($attrib) | |
817 { | |
818 global $SOURCE_ID, $CONTACTS, $CONTACT_COLTYPES, $RCMAIL; | |
819 | |
820 if ($result = $CONTACTS->get_result()) | |
821 $record = $result->first(); | |
822 | |
823 $photo_img = $attrib['placeholder'] ? $RCMAIL->output->abs_url($attrib['placeholder'], true) : 'program/resources/blank.gif'; | |
824 if ($record['_type'] == 'group' && $attrib['placeholdergroup']) | |
825 $photo_img = $RCMAIL->output->abs_url($attrib['placeholdergroup'], true); | |
826 | |
827 $RCMAIL->output->set_env('photo_placeholder', $RCMAIL->output->asset_url($photo_img)); | |
828 | |
829 unset($attrib['placeholder']); | |
830 | |
831 $plugin = $RCMAIL->plugins->exec_hook('contact_photo', array('record' => $record, 'data' => $record['photo'])); | |
832 | |
833 // check if we have photo data from contact form | |
834 if ($GLOBALS['EDIT_RECORD']) { | |
835 $rec = $GLOBALS['EDIT_RECORD']; | |
836 if ($rec['photo'] == '-del-') { | |
837 $record['photo'] = ''; | |
838 } | |
839 else if ($_SESSION['contacts']['files'][$rec['photo']]) { | |
840 $record['photo'] = $file_id = $rec['photo']; | |
841 } | |
842 } | |
843 | |
844 if ($plugin['url']) | |
845 $photo_img = $plugin['url']; | |
846 else if (preg_match('!^https?://!i', $record['photo'])) | |
847 $photo_img = $record['photo']; | |
848 else if ($record['photo']) { | |
849 $url = array('_action' => 'photo', '_cid' => $record['ID'], '_source' => $SOURCE_ID); | |
850 if ($file_id) { | |
851 $url['_photo'] = $ff_value = $file_id; | |
852 } | |
853 $photo_img = $RCMAIL->url($url); | |
854 } | |
855 else { | |
856 $ff_value = '-del-'; // will disable delete-photo action | |
857 } | |
858 | |
859 $content = html::div($attrib, html::img(array( | |
860 'src' => $photo_img, | |
861 'alt' => $RCMAIL->gettext('contactphoto'), | |
862 'onerror' => 'this.src = rcmail.env.photo_placeholder', | |
863 ))); | |
864 | |
865 if ($CONTACT_COLTYPES['photo'] && ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add')) { | |
866 $RCMAIL->output->add_gui_object('contactphoto', $attrib['id']); | |
867 $hidden = new html_hiddenfield(array('name' => '_photo', 'id' => 'ff_photo', 'value' => $ff_value)); | |
868 $content .= $hidden->show(); | |
869 } | |
870 | |
871 return $content; | |
872 } | |
873 | |
874 | |
875 function rcmail_format_date_col($val) | |
876 { | |
877 global $RCMAIL; | |
878 return $RCMAIL->format_date($val, $RCMAIL->config->get('date_format', 'Y-m-d'), false); | |
879 } | |
880 | |
881 /** | |
882 * Updates saved search after data changed | |
883 */ | |
884 function rcmail_search_update($return = false) | |
885 { | |
886 global $RCMAIL; | |
887 | |
888 if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) { | |
889 $search = (array)$_SESSION['search'][$search_request]; | |
890 $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name'); | |
891 $afields = $return ? $RCMAIL->config->get('contactlist_fields') : array('name', 'email'); | |
892 $records = array(); | |
893 | |
894 foreach ($search as $s => $set) { | |
895 $source = $RCMAIL->get_address_book($s); | |
896 | |
897 // reset page | |
898 $source->set_page(1); | |
899 $source->set_pagesize(9999); | |
900 $source->set_search_set($set); | |
901 | |
902 // get records | |
903 $result = $source->list_records($afields); | |
904 | |
905 if (!$result->count) { | |
906 unset($search[$s]); | |
907 continue; | |
908 } | |
909 | |
910 if ($return) { | |
911 while ($row = $result->next()) { | |
912 $row['sourceid'] = $s; | |
913 $key = rcube_addressbook::compose_contact_key($row, $sort_col); | |
914 $records[$key] = $row; | |
915 } | |
916 unset($result); | |
917 } | |
918 | |
919 $search[$s] = $source->get_search_set(); | |
920 } | |
921 | |
922 $_SESSION['search'][$search_request] = $search; | |
923 | |
924 return $records; | |
925 } | |
926 | |
927 return false; | |
928 } | |
929 | |
930 /** | |
931 * Returns contact ID(s) and source(s) from GET/POST data | |
932 * | |
933 * @return array List of contact IDs per-source | |
934 */ | |
935 function rcmail_get_cids($filter = null, $request_type = rcube_utils::INPUT_GPC) | |
936 { | |
937 // contact ID (or comma-separated list of IDs) is provided in two | |
938 // forms. If _source is an empty string then the ID is a string | |
939 // containing contact ID and source name in form: <ID>-<SOURCE> | |
940 | |
941 $cid = rcube_utils::get_input_value('_cid', $request_type); | |
942 $source = (string) rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); | |
943 | |
944 if (is_array($cid)) { | |
945 return $cid; | |
946 } | |
947 | |
948 if (!preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid)) { | |
949 return array(); | |
950 } | |
951 | |
952 $cid = explode(',', $cid); | |
953 $got_source = strlen($source); | |
954 $result = array(); | |
955 | |
956 // create per-source contact IDs array | |
957 foreach ($cid as $id) { | |
958 // extract source ID from contact ID (it's there in search mode) | |
959 // see #1488959 and #1488862 for reference | |
960 if (!$got_source) { | |
961 if ($sep = strrpos($id, '-')) { | |
962 $contact_id = substr($id, 0, $sep); | |
963 $source_id = (string) substr($id, $sep+1); | |
964 if (strlen($source_id)) { | |
965 $result[$source_id][] = $contact_id; | |
966 } | |
967 } | |
968 } | |
969 else { | |
970 if (substr($id, -($got_source+1)) === "-$source") { | |
971 $id = substr($id, 0, -($got_source+1)); | |
972 } | |
973 $result[$source][] = $id; | |
974 } | |
975 } | |
976 | |
977 return $filter !== null ? $result[$filter] : $result; | |
978 } |