alecpl
2012-04-14 1495ac7913095ae8284c3501b7d4e6dd31a484ec
commit | author | age
cc97ea 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcube_addressbook.php                                 |
6  |                                                                       |
e019f2 7  | This file is part of the Roundcube Webmail client                     |
438753 8  | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
7fe381 9  |                                                                       |
T 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.                     |
cc97ea 13  |                                                                       |
T 14  | PURPOSE:                                                              |
15  |   Interface to the local address book database                        |
16  |                                                                       |
17  +-----------------------------------------------------------------------+
18  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
19  +-----------------------------------------------------------------------+
20
1d786c 21  $Id$
cc97ea 22
T 23 */
24
25
26 /**
27  * Abstract skeleton of an address book/repository
28  *
29  * @package Addressbook
30  */
31 abstract class rcube_addressbook
32 {
0501b6 33     /** constants for error reporting **/
T 34     const ERROR_READ_ONLY = 1;
35     const ERROR_NO_CONNECTION = 2;
39cafa 36     const ERROR_VALIDATE = 3;
0501b6 37     const ERROR_SAVING = 4;
3e2637 38     const ERROR_SEARCH = 5;
cc90ed 39
0501b6 40     /** public properties (mandatory) */
T 41     public $primary_key;
42     public $groups = false;
43     public $readonly = true;
2d3e2b 44     public $searchonly = false;
7f5a84 45     public $undelete = false;
0501b6 46     public $ready = false;
06670e 47     public $group_id = null;
0501b6 48     public $list_page = 1;
T 49     public $page_size = 10;
438753 50     public $sort_col = 'name';
T 51     public $sort_order = 'ASC';
0501b6 52     public $coltypes = array('name' => array('limit'=>1), 'firstname' => array('limit'=>1), 'surname' => array('limit'=>1), 'email' => array('limit'=>1));
cc90ed 53
0501b6 54     protected $error;
cc90ed 55
A 56     /**
57      * Returns addressbook name (e.g. for addressbooks listing)
58      */
59     abstract function get_name();
cc97ea 60
T 61     /**
62      * Save a search string for future listings
63      *
64      * @param mixed Search params to use in listing method, obtained by get_search_set()
65      */
66     abstract function set_search_set($filter);
67
68     /**
69      * Getter for saved search properties
70      *
71      * @return mixed Search properties used by this class
72      */
73     abstract function get_search_set();
74
75     /**
76      * Reset saved results and search parameters
77      */
78     abstract function reset();
79
80     /**
0501b6 81      * Refresh saved search set after data has changed
T 82      *
83      * @return mixed New search set
84      */
85     function refresh_search()
86     {
87         return $this->get_search_set();
88     }
89
90     /**
cc97ea 91      * List the current set of contact records
T 92      *
93      * @param  array  List of cols to show
94      * @param  int    Only return this number of records, use negative values for tail
95      * @return array  Indexed list of contact records, each a hash array
96      */
97     abstract function list_records($cols=null, $subset=0);
a61bbb 98
T 99     /**
cc97ea 100      * Search records
T 101      *
102      * @param array   List of fields to search in
103      * @param string  Search value
f21a04 104      * @param int     Matching mode:
A 105      *                0 - partial (*abc*),
106      *                1 - strict (=),
107      *                2 - prefix (abc*)
cc97ea 108      * @param boolean True if results are requested, False if count only
0501b6 109      * @param boolean True to skip the count query (select only)
T 110      * @param array   List of fields that cannot be empty
111      * @return object rcube_result_set List of contact records and 'count' value
cc97ea 112      */
f21a04 113     abstract function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array());
cc97ea 114
T 115     /**
116      * Count number of available contacts in database
117      *
5c461b 118      * @return rcube_result_set Result set with values for 'count' and 'first'
cc97ea 119      */
T 120     abstract function count();
121
122     /**
123      * Return the last result set
124      *
5c461b 125      * @return rcube_result_set Current result set or NULL if nothing selected yet
cc97ea 126      */
T 127     abstract function get_result();
128
129     /**
130      * Get a specific contact record
131      *
132      * @param mixed record identifier(s)
133      * @param boolean True to return record as associative array, otherwise a result set is returned
b393e5 134      *
cc97ea 135      * @return mixed Result object with all record fields or False if not found
T 136      */
137     abstract function get_record($id, $assoc=false);
0501b6 138
T 139     /**
140      * Returns the last error occured (e.g. when updating/inserting failed)
141      *
142      * @return array Hash array with the following fields: type, message
143      */
144     function get_error()
145     {
146       return $this->error;
147     }
cc90ed 148
0501b6 149     /**
T 150      * Setter for errors for internal use
151      *
152      * @param int Error type (one of this class' error constants)
153      * @param string Error message (name of a text label)
154      */
155     protected function set_error($type, $message)
156     {
157       $this->error = array('type' => $type, 'message' => $message);
158     }
cc97ea 159
T 160     /**
161      * Close connection to source
162      * Called on script shutdown
163      */
164     function close() { }
165
166     /**
167      * Set internal list page
168      *
169      * @param  number  Page number to list
170      * @access public
171      */
172     function set_page($page)
173     {
2eb794 174         $this->list_page = (int)$page;
cc97ea 175     }
T 176
177     /**
178      * Set internal page size
179      *
180      * @param  number  Number of messages to display on one page
181      * @access public
182      */
183     function set_pagesize($size)
184     {
2eb794 185         $this->page_size = (int)$size;
cc97ea 186     }
a61bbb 187
438753 188     /**
T 189      * Set internal sort settings
190      *
191      * @param string $sort_col Sort column
192      * @param string $sort_order Sort order
193      */
194     function set_sort_order($sort_col, $sort_order = null)
195     {
196         if ($sort_col != null && ($this->coltypes[$sort_col] || in_array($sort_col, $this->coltypes))) {
197             $this->sort_col = $sort_col;
198         }
199         if ($sort_order != null) {
200             $this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
201         }
202     }
07b95d 203
T 204     /**
205      * Check the given data before saving.
e84818 206      * If input isn't valid, the message to display can be fetched using get_error()
07b95d 207      *
T 208      * @param array Assoziative array with data to save
39cafa 209      * @param boolean Attempt to fix/complete record automatically
07b95d 210      * @return boolean True if input is valid, False if not.
T 211      */
39cafa 212     public function validate(&$save_data, $autofix = false)
07b95d 213     {
0c2596 214         $rcmail = rcmail::get_instance();
A 215
07b95d 216         // check validity of email addresses
T 217         foreach ($this->get_col_values('email', $save_data, true) as $email) {
218             if (strlen($email)) {
0c2596 219                 if (!$rcmail->check_email(rcube_idn_to_ascii($email))) {
A 220                     $error = $rcmail->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
221                     $this->set_error(self::ERROR_VALIDATE, $error);
07b95d 222                     return false;
T 223                 }
224             }
225         }
226
227         return true;
228     }
229
230
a61bbb 231     /**
cc97ea 232      * Create a new contact record
T 233      *
234      * @param array Assoziative array with save data
0501b6 235      *  Keys:   Field name with optional section in the form FIELD:SECTION
T 236      *  Values: Field value. Can be either a string or an array of strings for multiple values
cc97ea 237      * @param boolean True to check for duplicates first
5c461b 238      * @return mixed The created record ID on success, False on error
cc97ea 239      */
T 240     function insert($save_data, $check=false)
241     {
2eb794 242         /* empty for read-only address books */
cc97ea 243     }
T 244
245     /**
0501b6 246      * Create new contact records for every item in the record set
T 247      *
248      * @param object rcube_result_set Recordset to insert
249      * @param boolean True to check for duplicates first
250      * @return array List of created record IDs
251      */
252     function insertMultiple($recset, $check=false)
253     {
254         $ids = array();
255         if (is_object($recset) && is_a($recset, rcube_result_set)) {
256             while ($row = $recset->next()) {
257                 if ($insert = $this->insert($row, $check))
258                     $ids[] = $insert;
259             }
260         }
261         return $ids;
262     }
263
264     /**
cc97ea 265      * Update a specific contact record
T 266      *
267      * @param mixed Record identifier
268      * @param array Assoziative array with save data
0501b6 269      *  Keys:   Field name with optional section in the form FIELD:SECTION
T 270      *  Values: Field value. Can be either a string or an array of strings for multiple values
5c461b 271      * @return boolean True on success, False on error
cc97ea 272      */
T 273     function update($id, $save_cols)
274     {
2eb794 275         /* empty for read-only address books */
cc97ea 276     }
T 277
278     /**
279      * Mark one or more contact records as deleted
280      *
281      * @param array  Record identifiers
63fda8 282      * @param bool   Remove records irreversible (see self::undelete)
cc97ea 283      */
63fda8 284     function delete($ids, $force=true)
cc97ea 285     {
2eb794 286         /* empty for read-only address books */
cc97ea 287     }
T 288
289     /**
7f5a84 290      * Unmark delete flag on contact record(s)
A 291      *
292      * @param array  Record identifiers
293      */
294     function undelete($ids)
295     {
296         /* empty for read-only address books */
297     }
298
299     /**
300      * Mark all records in database as deleted
cc97ea 301      */
T 302     function delete_all()
303     {
2eb794 304         /* empty for read-only address books */
cc97ea 305     }
T 306
04adaa 307     /**
b393e5 308      * Setter for the current group
A 309      * (empty, has to be re-implemented by extending class)
310      */
311     function set_group($gid) { }
312
313     /**
314      * List all active contact groups of this source
315      *
0501b6 316      * @param string  Optional search string to match group name
b393e5 317      * @return array  Indexed list of contact groups, each a hash array
A 318      */
0501b6 319     function list_groups($search = null)
b393e5 320     {
A 321         /* empty for address books don't supporting groups */
322         return array();
323     }
324
325     /**
dc6c4f 326      * Get group properties such as name and email address(es)
T 327      *
328      * @param string Group identifier
329      * @return array Group properties as hash array
330      */
331     function get_group($group_id)
332     {
333         /* empty for address books don't supporting groups */
334         return null;
335     }
336
337     /**
04adaa 338      * Create a contact group with the given name
T 339      *
340      * @param string The group name
5c461b 341      * @return mixed False on error, array with record props in success
04adaa 342      */
T 343     function create_group($name)
344     {
2eb794 345         /* empty for address books don't supporting groups */
A 346         return false;
04adaa 347     }
b393e5 348
04adaa 349     /**
T 350      * Delete the given group and all linked group members
351      *
352      * @param string Group identifier
353      * @return boolean True on success, false if no data was changed
354      */
355     function delete_group($gid)
356     {
2eb794 357         /* empty for address books don't supporting groups */
A 358         return false;
04adaa 359     }
b393e5 360
04adaa 361     /**
T 362      * Rename a specific contact group
363      *
364      * @param string Group identifier
365      * @param string New name to set for this group
360bd3 366      * @param string New group identifier (if changed, otherwise don't set)
04adaa 367      * @return boolean New name on success, false if no data was changed
T 368      */
360bd3 369     function rename_group($gid, $newname, &$newid)
04adaa 370     {
2eb794 371         /* empty for address books don't supporting groups */
A 372         return false;
04adaa 373     }
b393e5 374
04adaa 375     /**
T 376      * Add the given contact records the a certain group
377      *
378      * @param string  Group identifier
379      * @param array   List of contact identifiers to be added
b393e5 380      * @return int    Number of contacts added
04adaa 381      */
T 382     function add_to_group($group_id, $ids)
383     {
2eb794 384         /* empty for address books don't supporting groups */
A 385         return 0;
04adaa 386     }
b393e5 387
04adaa 388     /**
T 389      * Remove the given contact records from a certain group
390      *
391      * @param string  Group identifier
392      * @param array   List of contact identifiers to be removed
393      * @return int    Number of deleted group members
394      */
395     function remove_from_group($group_id, $ids)
396     {
2eb794 397         /* empty for address books don't supporting groups */
A 398         return 0;
04adaa 399     }
b393e5 400
A 401     /**
402      * Get group assignments of a specific contact record
403      *
404      * @param mixed Record identifier
405      *
406      * @return array List of assigned groups as ID=>Name pairs
407      * @since 0.5-beta
408      */
409     function get_record_groups($id)
410     {
411         /* empty for address books don't supporting groups */
412         return array();
413     }
0501b6 414
T 415
416     /**
417      * Utility function to return all values of a certain data column
418      * either as flat list or grouped by subtype
419      *
420      * @param string Col name
421      * @param array  Record data array as used for saving
422      * @param boolean True to return one array with all values, False for hash array with values grouped by type
423      * @return array List of column values
424      */
425     function get_col_values($col, $data, $flat = false)
426     {
427         $out = array();
428         foreach ($data as $c => $values) {
bed577 429             if ($c === $col || strpos($c, $col.':') === 0) {
0501b6 430                 if ($flat) {
T 431                     $out = array_merge($out, (array)$values);
432                 }
433                 else {
434                     list($f, $type) = explode(':', $c);
435                     $out[$type] = array_merge((array)$out[$type], (array)$values);
436                 }
437             }
438         }
cc90ed 439
0501b6 440         return $out;
T 441     }
3e2637 442
T 443
444     /**
445      * Normalize the given string for fulltext search.
446      * Currently only optimized for Latin-1 characters; to be extended
447      *
448      * @param string Input string (UTF-8)
449      * @return string Normalized string
450      */
451     protected static function normalize_string($str)
452     {
12dac4 453         // split by words
T 454         $arr = explode(" ", preg_replace(
455             array('/[\s;\+\-\/]+/i', '/(\d)[-.\s]+(\d)/', '/\s\w{1,3}\s/'),
3e2637 456             array(' ', '\\1\\2', ' '),
12dac4 457             $str));
cc90ed 458
12dac4 459         foreach ($arr as $i => $part) {
T 460             if (utf8_encode(utf8_decode($part)) == $part) {  // is latin-1 ?
e5e1eb 461                 $arr[$i] = utf8_encode(strtr(strtolower(strtr(utf8_decode($part),
12dac4 462                     'ÇçäâàåéêëèïîìÅÉöôòüûùÿøØáíóúñÑÁÂÀãÃÊËÈÍÎÏÓÔõÕÚÛÙýÝ',
T 463                     'ccaaaaeeeeiiiaeooouuuyooaiounnaaaaaeeeiiioooouuuyy')),
e5e1eb 464                     array('ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u')));
12dac4 465             }
T 466             else
34d728 467                 $arr[$i] = mb_strtolower($part);
12dac4 468         }
cc90ed 469
12dac4 470         return join(" ", $arr);
3e2637 471     }
e84818 472
T 473
474     /**
475      * Compose a valid display name from the given structured contact data
476      *
477      * @param array  Hash array with contact data as key-value pairs
f9a967 478      * @param bool   Don't attempt to extract components from the email address
71e8cc 479      *
e84818 480      * @return string Display name
T 481      */
f9a967 482     public static function compose_display_name($contact, $full_email = false)
e84818 483     {
T 484         $contact = rcmail::get_instance()->plugins->exec_hook('contact_displayname', $contact);
485         $fn = $contact['name'];
486
f9a967 487         if (!$fn)  // default display name composition according to vcard standard
e84818 488             $fn = join(' ', array_filter(array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix'])));
T 489
490         // use email address part for name
491         $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
71e8cc 492
e84818 493         if ($email && (empty($fn) || $fn == $email)) {
f9a967 494             // return full email
T 495             if ($full_email)
71e8cc 496                 return $email;
A 497
e84818 498             list($emailname) = explode('@', $email);
T 499             if (preg_match('/(.*)[\.\-\_](.*)/', $emailname, $match))
500                 $fn = trim(ucfirst($match[1]).' '.ucfirst($match[2]));
501             else
502                 $fn = ucfirst($emailname);
503         }
504
505         return $fn;
506     }
507
f9a967 508
T 509     /**
510      * Compose the name to display in the contacts list for the given contact record.
511      * This respects the settings parameter how to list conacts.
512      *
513      * @param array  Hash array with contact data as key-value pairs
514      * @return string List name
515      */
516     public static function compose_list_name($contact)
517     {
518         static $compose_mode;
519
520         if (!isset($compose_mode))  // cache this
521             $compose_mode = rcmail::get_instance()->config->get('addressbook_name_listing', 0);
522
523         if ($compose_mode == 3)
524             $fn = join(' ', array($contact['surname'] . ',', $contact['firstname'], $contact['middlename']));
525         else if ($compose_mode == 2)
526             $fn = join(' ', array($contact['surname'], $contact['firstname'], $contact['middlename']));
527         else if ($compose_mode == 1)
528             $fn = join(' ', array($contact['firstname'], $contact['middlename'], $contact['surname']));
529         else
530             $fn = !empty($contact['name']) ? $contact['name'] : join(' ', array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']));
531
532         $fn = trim($fn, ', ');
533
534         // fallback to display name
535         if (empty($fn) && $contact['name'])
536             $fn = $contact['name'];
537
538         // fallback to email address
539         $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
540         if (empty($fn) && $email)
541             return $email;
542
543         return $fn;
544     }
545
cc97ea 546 }
b393e5 547