0
|
1 <?php
|
|
2
|
|
3 /**
|
|
4 +-----------------------------------------------------------------------+
|
|
5 | This file is part of the Roundcube Webmail client |
|
|
6 | Copyright (C) 2006-2013, The Roundcube Dev Team |
|
|
7 | |
|
|
8 | Licensed under the GNU General Public License version 3 or |
|
|
9 | any later version with exceptions for skins & plugins. |
|
|
10 | See the README file for a full license statement. |
|
|
11 | |
|
|
12 | PURPOSE: |
|
|
13 | Interface to the local address book database |
|
|
14 +-----------------------------------------------------------------------+
|
|
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
|
|
16 +-----------------------------------------------------------------------+
|
|
17 */
|
|
18
|
|
19 /**
|
|
20 * Abstract skeleton of an address book/repository
|
|
21 *
|
|
22 * @package Framework
|
|
23 * @subpackage Addressbook
|
|
24 */
|
|
25 abstract class rcube_addressbook
|
|
26 {
|
|
27 // constants for error reporting
|
|
28 const ERROR_READ_ONLY = 1;
|
|
29 const ERROR_NO_CONNECTION = 2;
|
|
30 const ERROR_VALIDATE = 3;
|
|
31 const ERROR_SAVING = 4;
|
|
32 const ERROR_SEARCH = 5;
|
|
33
|
|
34 // search modes
|
|
35 const SEARCH_ALL = 0;
|
|
36 const SEARCH_STRICT = 1;
|
|
37 const SEARCH_PREFIX = 2;
|
|
38 const SEARCH_GROUPS = 4;
|
|
39
|
|
40 // public properties (mandatory)
|
|
41 public $primary_key;
|
|
42 public $groups = false;
|
|
43 public $export_groups = true;
|
|
44 public $readonly = true;
|
|
45 public $searchonly = false;
|
|
46 public $undelete = false;
|
|
47 public $ready = false;
|
|
48 public $group_id = null;
|
|
49 public $list_page = 1;
|
|
50 public $page_size = 10;
|
|
51 public $sort_col = 'name';
|
|
52 public $sort_order = 'ASC';
|
|
53 public $date_cols = array();
|
|
54 public $coltypes = array(
|
|
55 'name' => array('limit'=>1),
|
|
56 'firstname' => array('limit'=>1),
|
|
57 'surname' => array('limit'=>1),
|
|
58 'email' => array('limit'=>1)
|
|
59 );
|
|
60
|
|
61 protected $error;
|
|
62
|
|
63 /**
|
|
64 * Returns addressbook name (e.g. for addressbooks listing)
|
|
65 */
|
|
66 abstract function get_name();
|
|
67
|
|
68 /**
|
|
69 * Save a search string for future listings
|
|
70 *
|
|
71 * @param mixed $filter Search params to use in listing method, obtained by get_search_set()
|
|
72 */
|
|
73 abstract function set_search_set($filter);
|
|
74
|
|
75 /**
|
|
76 * Getter for saved search properties
|
|
77 *
|
|
78 * @return mixed Search properties used by this class
|
|
79 */
|
|
80 abstract function get_search_set();
|
|
81
|
|
82 /**
|
|
83 * Reset saved results and search parameters
|
|
84 */
|
|
85 abstract function reset();
|
|
86
|
|
87 /**
|
|
88 * Refresh saved search set after data has changed
|
|
89 *
|
|
90 * @return mixed New search set
|
|
91 */
|
|
92 function refresh_search()
|
|
93 {
|
|
94 return $this->get_search_set();
|
|
95 }
|
|
96
|
|
97 /**
|
|
98 * List the current set of contact records
|
|
99 *
|
|
100 * @param array $cols List of cols to show
|
|
101 * @param int $subset Only return this number of records, use negative values for tail
|
|
102 *
|
|
103 * @return array Indexed list of contact records, each a hash array
|
|
104 */
|
|
105 abstract function list_records($cols=null, $subset=0);
|
|
106
|
|
107 /**
|
|
108 * Search records
|
|
109 *
|
|
110 * @param array $fields List of fields to search in
|
|
111 * @param string $value Search value
|
|
112 * @param int $mode Search mode. Sum of self::SEARCH_*.
|
|
113 * @param boolean $select True if results are requested, False if count only
|
|
114 * @param boolean $nocount True to skip the count query (select only)
|
|
115 * @param array $required List of fields that cannot be empty
|
|
116 *
|
|
117 * @return object rcube_result_set List of contact records and 'count' value
|
|
118 */
|
|
119 abstract function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array());
|
|
120
|
|
121 /**
|
|
122 * Count number of available contacts in database
|
|
123 *
|
|
124 * @return rcube_result_set Result set with values for 'count' and 'first'
|
|
125 */
|
|
126 abstract function count();
|
|
127
|
|
128 /**
|
|
129 * Return the last result set
|
|
130 *
|
|
131 * @return rcube_result_set Current result set or NULL if nothing selected yet
|
|
132 */
|
|
133 abstract function get_result();
|
|
134
|
|
135 /**
|
|
136 * Get a specific contact record
|
|
137 *
|
|
138 * @param mixed $id Record identifier(s)
|
|
139 * @param boolean $assoc True to return record as associative array, otherwise a result set is returned
|
|
140 *
|
|
141 * @return rcube_result_set|array Result object with all record fields
|
|
142 */
|
|
143 abstract function get_record($id, $assoc=false);
|
|
144
|
|
145 /**
|
|
146 * Returns the last error occurred (e.g. when updating/inserting failed)
|
|
147 *
|
|
148 * @return array Hash array with the following fields: type, message
|
|
149 */
|
|
150 function get_error()
|
|
151 {
|
|
152 return $this->error;
|
|
153 }
|
|
154
|
|
155 /**
|
|
156 * Setter for errors for internal use
|
|
157 *
|
|
158 * @param int $type Error type (one of this class' error constants)
|
|
159 * @param string $message Error message (name of a text label)
|
|
160 */
|
|
161 protected function set_error($type, $message)
|
|
162 {
|
|
163 $this->error = array('type' => $type, 'message' => $message);
|
|
164 }
|
|
165
|
|
166 /**
|
|
167 * Close connection to source
|
|
168 * Called on script shutdown
|
|
169 */
|
|
170 function close() { }
|
|
171
|
|
172 /**
|
|
173 * Set internal list page
|
|
174 *
|
|
175 * @param number $page Page number to list
|
|
176 */
|
|
177 function set_page($page)
|
|
178 {
|
|
179 $this->list_page = (int)$page;
|
|
180 }
|
|
181
|
|
182 /**
|
|
183 * Set internal page size
|
|
184 *
|
|
185 * @param number $size Number of messages to display on one page
|
|
186 */
|
|
187 function set_pagesize($size)
|
|
188 {
|
|
189 $this->page_size = (int)$size;
|
|
190 }
|
|
191
|
|
192 /**
|
|
193 * Set internal sort settings
|
|
194 *
|
|
195 * @param string $sort_col Sort column
|
|
196 * @param string $sort_order Sort order
|
|
197 */
|
|
198 function set_sort_order($sort_col, $sort_order = null)
|
|
199 {
|
|
200 if ($sort_col != null && ($this->coltypes[$sort_col] || in_array($sort_col, $this->coltypes))) {
|
|
201 $this->sort_col = $sort_col;
|
|
202 }
|
|
203 if ($sort_order != null) {
|
|
204 $this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
|
|
205 }
|
|
206 }
|
|
207
|
|
208 /**
|
|
209 * Check the given data before saving.
|
|
210 * If input isn't valid, the message to display can be fetched using get_error()
|
|
211 *
|
|
212 * @param array &$save_data Associative array with data to save
|
|
213 * @param boolean $autofix Attempt to fix/complete record automatically
|
|
214 *
|
|
215 * @return boolean True if input is valid, False if not.
|
|
216 */
|
|
217 public function validate(&$save_data, $autofix = false)
|
|
218 {
|
|
219 $rcube = rcube::get_instance();
|
|
220 $valid = true;
|
|
221
|
|
222 // check validity of email addresses
|
|
223 foreach ($this->get_col_values('email', $save_data, true) as $email) {
|
|
224 if (strlen($email)) {
|
|
225 if (!rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) {
|
|
226 $error = $rcube->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
|
|
227 $this->set_error(self::ERROR_VALIDATE, $error);
|
|
228 $valid = false;
|
|
229 break;
|
|
230 }
|
|
231 }
|
|
232 }
|
|
233
|
|
234 // allow plugins to do contact validation and auto-fixing
|
|
235 $plugin = $rcube->plugins->exec_hook('contact_validate', array(
|
|
236 'record' => $save_data,
|
|
237 'autofix' => $autofix,
|
|
238 'valid' => $valid,
|
|
239 ));
|
|
240
|
|
241 if ($valid && !$plugin['valid']) {
|
|
242 $this->set_error(self::ERROR_VALIDATE, $plugin['error']);
|
|
243 }
|
|
244
|
|
245 if (is_array($plugin['record'])) {
|
|
246 $save_data = $plugin['record'];
|
|
247 }
|
|
248
|
|
249 return $plugin['valid'];
|
|
250 }
|
|
251
|
|
252 /**
|
|
253 * Create a new contact record
|
|
254 *
|
|
255 * @param array $save_data Associative array with save data
|
|
256 * Keys: Field name with optional section in the form FIELD:SECTION
|
|
257 * Values: Field value. Can be either a string or an array of strings for multiple values
|
|
258 * @param boolean $check True to check for duplicates first
|
|
259 *
|
|
260 * @return mixed The created record ID on success, False on error
|
|
261 */
|
|
262 function insert($save_data, $check=false)
|
|
263 {
|
|
264 /* empty for read-only address books */
|
|
265 }
|
|
266
|
|
267 /**
|
|
268 * Create new contact records for every item in the record set
|
|
269 *
|
|
270 * @param rcube_result_set $recset Recordset to insert
|
|
271 * @param boolean $check True to check for duplicates first
|
|
272 *
|
|
273 * @return array List of created record IDs
|
|
274 */
|
|
275 function insertMultiple($recset, $check=false)
|
|
276 {
|
|
277 $ids = array();
|
|
278 if (is_object($recset) && is_a($recset, rcube_result_set)) {
|
|
279 while ($row = $recset->next()) {
|
|
280 if ($insert = $this->insert($row, $check))
|
|
281 $ids[] = $insert;
|
|
282 }
|
|
283 }
|
|
284 return $ids;
|
|
285 }
|
|
286
|
|
287 /**
|
|
288 * Update a specific contact record
|
|
289 *
|
|
290 * @param mixed $id Record identifier
|
|
291 * @param array $save_cols Associative array with save data
|
|
292 * Keys: Field name with optional section in the form FIELD:SECTION
|
|
293 * Values: Field value. Can be either a string or an array of strings for multiple values
|
|
294 *
|
|
295 * @return mixed On success if ID has been changed returns ID, otherwise True, False on error
|
|
296 */
|
|
297 function update($id, $save_cols)
|
|
298 {
|
|
299 /* empty for read-only address books */
|
|
300 }
|
|
301
|
|
302 /**
|
|
303 * Mark one or more contact records as deleted
|
|
304 *
|
|
305 * @param array $ids Record identifiers
|
|
306 * @param bool $force Remove records irreversible (see self::undelete)
|
|
307 */
|
|
308 function delete($ids, $force = true)
|
|
309 {
|
|
310 /* empty for read-only address books */
|
|
311 }
|
|
312
|
|
313 /**
|
|
314 * Unmark delete flag on contact record(s)
|
|
315 *
|
|
316 * @param array $ids Record identifiers
|
|
317 */
|
|
318 function undelete($ids)
|
|
319 {
|
|
320 /* empty for read-only address books */
|
|
321 }
|
|
322
|
|
323 /**
|
|
324 * Mark all records in database as deleted
|
|
325 *
|
|
326 * @param bool $with_groups Remove also groups
|
|
327 */
|
|
328 function delete_all($with_groups = false)
|
|
329 {
|
|
330 /* empty for read-only address books */
|
|
331 }
|
|
332
|
|
333 /**
|
|
334 * Setter for the current group
|
|
335 * (empty, has to be re-implemented by extending class)
|
|
336 */
|
|
337 function set_group($group_id) { }
|
|
338
|
|
339 /**
|
|
340 * List all active contact groups of this source
|
|
341 *
|
|
342 * @param string $search Optional search string to match group name
|
|
343 * @param int $mode Search mode. Sum of self::SEARCH_*
|
|
344 *
|
|
345 * @return array Indexed list of contact groups, each a hash array
|
|
346 */
|
|
347 function list_groups($search = null, $mode = 0)
|
|
348 {
|
|
349 /* empty for address books don't supporting groups */
|
|
350 return array();
|
|
351 }
|
|
352
|
|
353 /**
|
|
354 * Get group properties such as name and email address(es)
|
|
355 *
|
|
356 * @param string $group_id Group identifier
|
|
357 *
|
|
358 * @return array Group properties as hash array
|
|
359 */
|
|
360 function get_group($group_id)
|
|
361 {
|
|
362 /* empty for address books don't supporting groups */
|
|
363 return null;
|
|
364 }
|
|
365
|
|
366 /**
|
|
367 * Create a contact group with the given name
|
|
368 *
|
|
369 * @param string $name The group name
|
|
370 *
|
|
371 * @return mixed False on error, array with record props in success
|
|
372 */
|
|
373 function create_group($name)
|
|
374 {
|
|
375 /* empty for address books don't supporting groups */
|
|
376 return false;
|
|
377 }
|
|
378
|
|
379 /**
|
|
380 * Delete the given group and all linked group members
|
|
381 *
|
|
382 * @param string $group_id Group identifier
|
|
383 *
|
|
384 * @return boolean True on success, false if no data was changed
|
|
385 */
|
|
386 function delete_group($group_id)
|
|
387 {
|
|
388 /* empty for address books don't supporting groups */
|
|
389 return false;
|
|
390 }
|
|
391
|
|
392 /**
|
|
393 * Rename a specific contact group
|
|
394 *
|
|
395 * @param string $group_id Group identifier
|
|
396 * @param string $newname New name to set for this group
|
|
397 * @param string &$newid New group identifier (if changed, otherwise don't set)
|
|
398 *
|
|
399 * @return boolean New name on success, false if no data was changed
|
|
400 */
|
|
401 function rename_group($group_id, $newname, &$newid)
|
|
402 {
|
|
403 /* empty for address books don't supporting groups */
|
|
404 return false;
|
|
405 }
|
|
406
|
|
407 /**
|
|
408 * Add the given contact records the a certain group
|
|
409 *
|
|
410 * @param string $group_id Group identifier
|
|
411 * @param array|string $ids List of contact identifiers to be added
|
|
412 *
|
|
413 * @return int Number of contacts added
|
|
414 */
|
|
415 function add_to_group($group_id, $ids)
|
|
416 {
|
|
417 /* empty for address books don't supporting groups */
|
|
418 return 0;
|
|
419 }
|
|
420
|
|
421 /**
|
|
422 * Remove the given contact records from a certain group
|
|
423 *
|
|
424 * @param string $group_id Group identifier
|
|
425 * @param array|string $ids List of contact identifiers to be removed
|
|
426 *
|
|
427 * @return int Number of deleted group members
|
|
428 */
|
|
429 function remove_from_group($group_id, $ids)
|
|
430 {
|
|
431 /* empty for address books don't supporting groups */
|
|
432 return 0;
|
|
433 }
|
|
434
|
|
435 /**
|
|
436 * Get group assignments of a specific contact record
|
|
437 *
|
|
438 * @param mixed Record identifier
|
|
439 *
|
|
440 * @return array $id List of assigned groups as ID=>Name pairs
|
|
441 * @since 0.5-beta
|
|
442 */
|
|
443 function get_record_groups($id)
|
|
444 {
|
|
445 /* empty for address books don't supporting groups */
|
|
446 return array();
|
|
447 }
|
|
448
|
|
449 /**
|
|
450 * Utility function to return all values of a certain data column
|
|
451 * either as flat list or grouped by subtype
|
|
452 *
|
|
453 * @param string $col Col name
|
|
454 * @param array $data Record data array as used for saving
|
|
455 * @param bool $flat True to return one array with all values,
|
|
456 * False for hash array with values grouped by type
|
|
457 *
|
|
458 * @return array List of column values
|
|
459 */
|
|
460 public static function get_col_values($col, $data, $flat = false)
|
|
461 {
|
|
462 $out = array();
|
|
463 foreach ((array)$data as $c => $values) {
|
|
464 if ($c === $col || strpos($c, $col.':') === 0) {
|
|
465 if ($flat) {
|
|
466 $out = array_merge($out, (array)$values);
|
|
467 }
|
|
468 else {
|
|
469 list(, $type) = explode(':', $c);
|
|
470 $out[$type] = array_merge((array)$out[$type], (array)$values);
|
|
471 }
|
|
472 }
|
|
473 }
|
|
474
|
|
475 // remove duplicates
|
|
476 if ($flat && !empty($out)) {
|
|
477 $out = array_unique($out);
|
|
478 }
|
|
479
|
|
480 return $out;
|
|
481 }
|
|
482
|
|
483 /**
|
|
484 * Normalize the given string for fulltext search.
|
|
485 * Currently only optimized for Latin-1 characters; to be extended
|
|
486 *
|
|
487 * @param string $str Input string (UTF-8)
|
|
488 * @return string Normalized string
|
|
489 * @deprecated since 0.9-beta
|
|
490 */
|
|
491 protected static function normalize_string($str)
|
|
492 {
|
|
493 return rcube_utils::normalize_string($str);
|
|
494 }
|
|
495
|
|
496 /**
|
|
497 * Compose a valid display name from the given structured contact data
|
|
498 *
|
|
499 * @param array $contact Hash array with contact data as key-value pairs
|
|
500 * @param bool $full_email Don't attempt to extract components from the email address
|
|
501 *
|
|
502 * @return string Display name
|
|
503 */
|
|
504 public static function compose_display_name($contact, $full_email = false)
|
|
505 {
|
|
506 $contact = rcube::get_instance()->plugins->exec_hook('contact_displayname', $contact);
|
|
507 $fn = $contact['name'];
|
|
508
|
|
509 // default display name composition according to vcard standard
|
|
510 if (!$fn) {
|
|
511 $fn = join(' ', array_filter(array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix'])));
|
|
512 $fn = trim(preg_replace('/\s+/', ' ', $fn));
|
|
513 }
|
|
514
|
|
515 // use email address part for name
|
|
516 $email = self::get_col_values('email', $contact, true);
|
|
517 $email = $email[0];
|
|
518
|
|
519 if ($email && (empty($fn) || $fn == $email)) {
|
|
520 // return full email
|
|
521 if ($full_email)
|
|
522 return $email;
|
|
523
|
|
524 list($emailname) = explode('@', $email);
|
|
525 if (preg_match('/(.*)[\.\-\_](.*)/', $emailname, $match))
|
|
526 $fn = trim(ucfirst($match[1]).' '.ucfirst($match[2]));
|
|
527 else
|
|
528 $fn = ucfirst($emailname);
|
|
529 }
|
|
530
|
|
531 return $fn;
|
|
532 }
|
|
533
|
|
534 /**
|
|
535 * Compose the name to display in the contacts list for the given contact record.
|
|
536 * This respects the settings parameter how to list conacts.
|
|
537 *
|
|
538 * @param array $contact Hash array with contact data as key-value pairs
|
|
539 *
|
|
540 * @return string List name
|
|
541 */
|
|
542 public static function compose_list_name($contact)
|
|
543 {
|
|
544 static $compose_mode;
|
|
545
|
|
546 if (!isset($compose_mode)) // cache this
|
|
547 $compose_mode = rcube::get_instance()->config->get('addressbook_name_listing', 0);
|
|
548
|
|
549 if ($compose_mode == 3)
|
|
550 $fn = join(' ', array($contact['surname'] . ',', $contact['firstname'], $contact['middlename']));
|
|
551 else if ($compose_mode == 2)
|
|
552 $fn = join(' ', array($contact['surname'], $contact['firstname'], $contact['middlename']));
|
|
553 else if ($compose_mode == 1)
|
|
554 $fn = join(' ', array($contact['firstname'], $contact['middlename'], $contact['surname']));
|
|
555 else if ($compose_mode == 0)
|
|
556 $fn = $contact['name'] ?: join(' ', array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']));
|
|
557 else {
|
|
558 $plugin = rcube::get_instance()->plugins->exec_hook('contact_listname', array('contact' => $contact));
|
|
559 $fn = $plugin['fn'];
|
|
560 }
|
|
561
|
|
562 $fn = trim($fn, ', ');
|
|
563 $fn = preg_replace('/\s+/', ' ', $fn);
|
|
564
|
|
565 // fallbacks...
|
|
566 if ($fn === '') {
|
|
567 // ... display name
|
|
568 if ($name = trim($contact['name'])) {
|
|
569 $fn = $name;
|
|
570 }
|
|
571 // ... organization
|
|
572 else if ($org = trim($contact['organization'])) {
|
|
573 $fn = $org;
|
|
574 }
|
|
575 // ... email address
|
|
576 else if (($email = self::get_col_values('email', $contact, true)) && !empty($email)) {
|
|
577 $fn = $email[0];
|
|
578 }
|
|
579 }
|
|
580
|
|
581 return $fn;
|
|
582 }
|
|
583
|
|
584 /**
|
|
585 * Build contact display name for autocomplete listing
|
|
586 *
|
|
587 * @param array $contact Hash array with contact data as key-value pairs
|
|
588 * @param string $email Optional email address
|
|
589 * @param string $name Optional name (self::compose_list_name() result)
|
|
590 * @param string $templ Optional template to use (defaults to the 'contact_search_name' config option)
|
|
591 *
|
|
592 * @return string Display name
|
|
593 */
|
|
594 public static function compose_search_name($contact, $email = null, $name = null, $templ = null)
|
|
595 {
|
|
596 static $template;
|
|
597
|
|
598 if (empty($templ) && !isset($template)) { // cache this
|
|
599 $template = rcube::get_instance()->config->get('contact_search_name');
|
|
600 if (empty($template)) {
|
|
601 $template = '{name} <{email}>';
|
|
602 }
|
|
603 }
|
|
604
|
|
605 $result = $templ ?: $template;
|
|
606
|
|
607 if (preg_match_all('/\{[a-z]+\}/', $result, $matches)) {
|
|
608 foreach ($matches[0] as $key) {
|
|
609 $key = trim($key, '{}');
|
|
610 $value = '';
|
|
611
|
|
612 switch ($key) {
|
|
613 case 'name':
|
|
614 $value = $name ?: self::compose_list_name($contact);
|
|
615
|
|
616 // If name(s) are undefined compose_list_name() may return an email address
|
|
617 // here we prevent from returning the same name and email
|
|
618 if ($name === $email && strpos($result, '{email}') !== false) {
|
|
619 $value = '';
|
|
620 }
|
|
621
|
|
622 break;
|
|
623
|
|
624 case 'email':
|
|
625 $value = $email;
|
|
626 break;
|
|
627 }
|
|
628
|
|
629 if (empty($value)) {
|
|
630 $value = strpos($key, ':') ? $contact[$key] : self::get_col_values($key, $contact, true);
|
|
631 if (is_array($value)) {
|
|
632 $value = $value[0];
|
|
633 }
|
|
634 }
|
|
635
|
|
636 $result = str_replace('{' . $key . '}', $value, $result);
|
|
637 }
|
|
638 }
|
|
639
|
|
640 $result = preg_replace('/\s+/', ' ', $result);
|
|
641 $result = preg_replace('/\s*(<>|\(\)|\[\])/', '', $result);
|
|
642 $result = trim($result, '/ ');
|
|
643
|
|
644 return $result;
|
|
645 }
|
|
646
|
|
647 /**
|
|
648 * Create a unique key for sorting contacts
|
|
649 *
|
|
650 * @param array $contact Contact record
|
|
651 * @param string $sort_col Sorting column name
|
|
652 *
|
|
653 * @return string Unique key
|
|
654 */
|
|
655 public static function compose_contact_key($contact, $sort_col)
|
|
656 {
|
|
657 $key = $contact[$sort_col] . ':' . $contact['sourceid'];
|
|
658
|
|
659 // add email to a key to not skip contacts with the same name (#1488375)
|
|
660 if (($email = self::get_col_values('email', $contact, true)) && !empty($email)) {
|
|
661 $key .= ':' . implode(':', (array)$email);
|
|
662 }
|
|
663
|
|
664 return $key;
|
|
665 }
|
|
666
|
|
667 /**
|
|
668 * Compare search value with contact data
|
|
669 *
|
|
670 * @param string $colname Data name
|
|
671 * @param string|array $value Data value
|
|
672 * @param string $search Search value
|
|
673 * @param int $mode Search mode
|
|
674 *
|
|
675 * @return bool Comparison result
|
|
676 */
|
|
677 protected function compare_search_value($colname, $value, $search, $mode)
|
|
678 {
|
|
679 // The value is a date string, for date we'll
|
|
680 // use only strict comparison (mode = 1)
|
|
681 // @TODO: partial search, e.g. match only day and month
|
|
682 if (in_array($colname, $this->date_cols)) {
|
|
683 return (($value = rcube_utils::anytodatetime($value))
|
|
684 && ($search = rcube_utils::anytodatetime($search))
|
|
685 && $value->format('Ymd') == $search->format('Ymd'));
|
|
686 }
|
|
687
|
|
688 // Gender is a special value, must use strict comparison (#5757)
|
|
689 if ($colname == 'gender') {
|
|
690 $mode = self::SEARCH_STRICT;
|
|
691 }
|
|
692
|
|
693 // composite field, e.g. address
|
|
694 foreach ((array)$value as $val) {
|
|
695 $val = mb_strtolower($val);
|
|
696
|
|
697 if ($mode & self::SEARCH_STRICT) {
|
|
698 $got = ($val == $search);
|
|
699 }
|
|
700 else if ($mode & self::SEARCH_PREFIX) {
|
|
701 $got = ($search == substr($val, 0, strlen($search)));
|
|
702 }
|
|
703 else {
|
|
704 $got = (strpos($val, $search) !== false);
|
|
705 }
|
|
706
|
|
707 if ($got) {
|
|
708 return true;
|
|
709 }
|
|
710 }
|
|
711
|
|
712 return false;
|
|
713 }
|
|
714 }
|