Mercurial > hg > rc2
comparison program/lib/Roundcube/rcube_addressbook.php @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
| author | Charlie Root |
|---|---|
| date | Thu, 04 Jan 2018 15:52:31 -0500 |
| parents | |
| children | bea5a38be938 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4681f974d28b |
|---|---|
| 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 } |
