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