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