]> git.donarmstrong.com Git - roundcube.git/blob - program/include/rcube_contacts.php
Imported Upstream version 0.5
[roundcube.git] / program / include / rcube_contacts.php
1 <?php
2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcube_contacts.php                                    |
6  |                                                                       |
7  | This file is part of the Roundcube Webmail client                     |
8  | Copyright (C) 2006-2010, Roundcube Dev. - Switzerland                 |
9  | Licensed under the GNU GPL                                            |
10  |                                                                       |
11  | PURPOSE:                                                              |
12  |   Interface to the local address book database                        |
13  |                                                                       |
14  +-----------------------------------------------------------------------+
15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16  +-----------------------------------------------------------------------+
17
18  $Id: rcube_contacts.php 4145 2010-10-27 07:23:57Z alec $
19
20 */
21
22
23 /**
24  * Model class for the local address book database
25  *
26  * @package Addressbook
27  */
28 class rcube_contacts extends rcube_addressbook
29 {
30     // protected for backward compat. with some plugins
31     protected $db_name = 'contacts';
32     protected $db_groups = 'contactgroups';
33     protected $db_groupmembers = 'contactgroupmembers';
34
35     /**
36      * Store database connection.
37      *
38      * @var rcube_mdb2
39      */
40     private $db = null;
41     private $user_id = 0;
42     private $filter = null;
43     private $result = null;
44     private $search_fields;
45     private $search_string;
46     private $cache;
47     private $table_cols = array('name', 'email', 'firstname', 'surname', 'vcard');
48
49     // public properties
50     var $primary_key = 'contact_id';
51     var $readonly = false;
52     var $groups = true;
53     var $list_page = 1;
54     var $page_size = 10;
55     var $group_id = 0;
56     var $ready = false;
57
58
59     /**
60      * Object constructor
61      *
62      * @param object  Instance of the rcube_db class
63      * @param integer User-ID
64      */
65     function __construct($dbconn, $user)
66     {
67         $this->db = $dbconn;
68         $this->user_id = $user;
69         $this->ready = $this->db && !$this->db->is_error();
70     }
71
72
73     /**
74      * Save a search string for future listings
75      *
76      * @param  string SQL params to use in listing method
77      */
78     function set_search_set($filter)
79     {
80         $this->filter = $filter;
81         $this->cache = null;
82     }
83
84
85     /**
86      * Getter for saved search properties
87      *
88      * @return mixed Search properties used by this class
89      */
90     function get_search_set()
91     {
92         return $this->filter;
93     }
94
95
96     /**
97      * Setter for the current group
98      * (empty, has to be re-implemented by extending class)
99      */
100     function set_group($gid)
101     {
102         $this->group_id = $gid;
103         $this->cache = null;
104     }
105
106
107     /**
108      * Reset all saved results and search parameters
109      */
110     function reset()
111     {
112         $this->result = null;
113         $this->filter = null;
114         $this->search_fields = null;
115         $this->search_string = null;
116         $this->cache = null;
117     }
118
119
120     /**
121      * List all active contact groups of this source
122      *
123      * @param string  Search string to match group name
124      * @return array  Indexed list of contact groups, each a hash array
125      */
126     function list_groups($search = null)
127     {
128         $results = array();
129
130         if (!$this->groups)
131             return $results;
132
133         $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : '';
134
135         $sql_result = $this->db->query(
136             "SELECT * FROM ".get_table_name($this->db_groups).
137             " WHERE del<>1".
138             " AND user_id=?".
139             $sql_filter.
140             " ORDER BY name",
141             $this->user_id);
142
143         while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
144             $sql_arr['ID'] = $sql_arr['contactgroup_id'];
145             $results[]     = $sql_arr;
146         }
147
148         return $results;
149     }
150
151
152     /**
153      * List the current set of contact records
154      *
155      * @param  array   List of cols to show
156      * @param  int     Only return this number of records, use negative values for tail
157      * @param  boolean True to skip the count query (select only)
158      * @return array  Indexed list of contact records, each a hash array
159      */
160     function list_records($cols=null, $subset=0, $nocount=false)
161     {
162         if ($nocount || $this->list_page <= 1) {
163             // create dummy result, we don't need a count now
164             $this->result = new rcube_result_set();
165         } else {
166             // count all records
167             $this->result = $this->count();
168         }
169
170         $start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first;
171         $length = $subset != 0 ? abs($subset) : $this->page_size;
172
173         if ($this->group_id)
174             $join = " LEFT JOIN ".get_table_name($this->db_groupmembers)." AS m".
175                 " ON (m.contact_id = c.".$this->primary_key.")";
176
177         $sql_result = $this->db->limitquery(
178             "SELECT * FROM ".get_table_name($this->db_name)." AS c" .
179             $join .
180             " WHERE c.del<>1" .
181                 " AND c.user_id=?" .
182                 ($this->group_id ? " AND m.contactgroup_id=?" : "").
183                 ($this->filter ? " AND (".$this->filter.")" : "") .
184             " ORDER BY c.name",
185             $start_row,
186             $length,
187             $this->user_id,
188             $this->group_id);
189
190         while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
191             $sql_arr['ID'] = $sql_arr[$this->primary_key];
192             // make sure we have a name to display
193             if (empty($sql_arr['name']))
194                 $sql_arr['name'] = $sql_arr['email'];
195             $this->result->add($sql_arr);
196         }
197
198         $cnt = count($this->result->records);
199
200         // update counter
201         if ($nocount)
202             $this->result->count = $cnt;
203         else if ($this->list_page <= 1) {
204             if ($cnt < $this->page_size && $subset == 0)
205                 $this->result->count = $cnt;
206             else if (isset($this->cache['count']))
207                 $this->result->count = $this->cache['count'];
208             else
209                 $this->result->count = $this->_count();
210         }
211
212         return $this->result;
213     }
214
215
216     /**
217      * Search contacts
218      *
219      * @param array   List of fields to search in
220      * @param string  Search value
221      * @param boolean True for strict (=), False for partial (LIKE) matching
222      * @param boolean True if results are requested, False if count only
223      * @param boolean True to skip the count query (select only)
224      * @param array   List of fields that cannot be empty
225      * @return Indexed list of contact records and 'count' value
226      */
227     function search($fields, $value, $strict=false, $select=true, $nocount=false, $required=array())
228     {
229         if (!is_array($fields))
230             $fields = array($fields);
231         if (!is_array($required) && !empty($required))
232             $required = array($required);
233
234         $where = $and_where = array();
235
236         foreach ($fields as $col) {
237             if ($col == 'ID' || $col == $this->primary_key) {
238                 $ids     = !is_array($value) ? explode(',', $value) : $value;
239                 $ids     = $this->db->array2list($ids, 'integer');
240                 $where[] = 'c.' . $this->primary_key.' IN ('.$ids.')';
241             }
242             else if ($strict)
243                 $where[] = $this->db->quoteIdentifier($col).' = '.$this->db->quote($value);
244             else
245                 $where[] = $this->db->ilike($col, '%'.$value.'%');
246         }
247
248         foreach ($required as $col) {
249             $and_where[] = $this->db->quoteIdentifier($col).' <> '.$this->db->quote('');
250         }
251
252         if (!empty($where))
253             $where = join(' OR ', $where);
254
255         if (!empty($and_where))
256             $where = ($where ? "($where) AND " : '') . join(' AND ', $and_where);
257
258         if (!empty($where)) {
259             $this->set_search_set($where);
260             if ($select)
261                 $this->list_records(null, 0, $nocount);
262             else
263                 $this->result = $this->count();
264         }
265
266         return $this->result; 
267     }
268
269
270     /**
271      * Count number of available contacts in database
272      *
273      * @return rcube_result_set Result object
274      */
275     function count()
276     {
277         $count = isset($this->cache['count']) ? $this->cache['count'] : $this->_count();
278
279         return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
280     }
281
282
283     /**
284      * Count number of available contacts in database
285      *
286      * @return int Contacts count
287      */
288     private function _count()
289     {
290         if ($this->group_id)
291             $join = " LEFT JOIN ".get_table_name($this->db_groupmembers)." AS m".
292                 " ON (m.contact_id=c.".$this->primary_key.")";
293
294         // count contacts for this user
295         $sql_result = $this->db->query(
296             "SELECT COUNT(c.contact_id) AS rows".
297             " FROM ".get_table_name($this->db_name)." AS c".
298                 $join.
299             " WHERE c.del<>1".
300             " AND c.user_id=?".
301             ($this->group_id ? " AND m.contactgroup_id=?" : "").
302             ($this->filter ? " AND (".$this->filter.")" : ""),
303             $this->user_id,
304             $this->group_id
305         );
306
307         $sql_arr = $this->db->fetch_assoc($sql_result);
308
309         $this->cache['count'] = (int) $sql_arr['rows'];
310
311         return $this->cache['count'];
312     }
313
314
315     /**
316      * Return the last result set
317      *
318      * @return mixed Result array or NULL if nothing selected yet
319      */
320     function get_result()
321     {
322         return $this->result;
323     }
324
325
326     /**
327      * Get a specific contact record
328      *
329      * @param mixed record identifier(s)
330      * @return mixed Result object with all record fields or False if not found
331      */
332     function get_record($id, $assoc=false)
333     {
334         // return cached result
335         if ($this->result && ($first = $this->result->first()) && $first[$this->primary_key] == $id)
336             return $assoc ? $first : $this->result;
337
338         $this->db->query(
339             "SELECT * FROM ".get_table_name($this->db_name).
340             " WHERE contact_id=?".
341                 " AND user_id=?".
342                 " AND del<>1",
343             $id,
344             $this->user_id
345         );
346
347         if ($sql_arr = $this->db->fetch_assoc()) {
348             $sql_arr['ID'] = $sql_arr[$this->primary_key];
349             $this->result = new rcube_result_set(1);
350             $this->result->add($sql_arr);
351         }
352
353         return $assoc && $sql_arr ? $sql_arr : $this->result;
354     }
355
356
357     /**
358      * Get group assignments of a specific contact record
359      *
360      * @param mixed Record identifier
361      * @return array List of assigned groups as ID=>Name pairs
362      */
363     function get_record_groups($id)
364     {
365       $results = array();
366
367       if (!$this->groups)
368           return $results;
369
370       $sql_result = $this->db->query(
371         "SELECT cgm.contactgroup_id, cg.name FROM " . get_table_name($this->db_groupmembers) . " AS cgm" .
372         " LEFT JOIN " . get_table_name($this->db_groups) . " AS cg ON (cgm.contactgroup_id = cg.contactgroup_id AND cg.del<>1)" .
373         " WHERE cgm.contact_id=?",
374         $id
375       );
376       while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
377         $results[$sql_arr['contactgroup_id']] = $sql_arr['name'];
378       }
379
380       return $results;
381     }
382
383
384     /**
385      * Create a new contact record
386      *
387      * @param array Associative array with save data
388      * @return integer|boolean The created record ID on success, False on error
389      */
390     function insert($save_data, $check=false)
391     {
392         if (is_object($save_data) && is_a($save_data, rcube_result_set))
393             return $this->insert_recset($save_data, $check);
394
395         $insert_id = $existing = false;
396
397         if ($check)
398             $existing = $this->search('email', $save_data['email'], true, false);
399
400         $a_insert_cols = $a_insert_values = array();
401
402         foreach ($this->table_cols as $col)
403             if (isset($save_data[$col])) {
404                 $a_insert_cols[]   = $this->db->quoteIdentifier($col);
405                 $a_insert_values[] = $this->db->quote($save_data[$col]);
406             }
407
408         if (!$existing->count && !empty($a_insert_cols)) {
409             $this->db->query(
410                 "INSERT INTO ".get_table_name($this->db_name).
411                 " (user_id, changed, del, ".join(', ', $a_insert_cols).")".
412                 " VALUES (".intval($this->user_id).", ".$this->db->now().", 0, ".join(', ', $a_insert_values).")"
413             );
414
415             $insert_id = $this->db->insert_id($this->db_name);
416         }
417
418         // also add the newly created contact to the active group
419         if ($insert_id && $this->group_id)
420             $this->add_to_group($this->group_id, $insert_id);
421
422         $this->cache = null;
423
424         return $insert_id;
425     }
426
427
428     /**
429      * Insert new contacts for each row in set
430      */
431     function insert_recset($result, $check=false)
432     {
433         $ids = array();
434         while ($row = $result->next()) {
435             if ($insert = $this->insert($row, $check))
436                 $ids[] = $insert;
437         }
438         return $ids;
439     }
440
441
442     /**
443      * Update a specific contact record
444      *
445      * @param mixed Record identifier
446      * @param array Assoziative array with save data
447      * @return boolean True on success, False on error
448      */
449     function update($id, $save_cols)
450     {
451         $updated = false;
452         $write_sql = array();
453
454         foreach ($this->table_cols as $col)
455             if (isset($save_cols[$col]))
456                 $write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col),
457                     $this->db->quote($save_cols[$col]));
458
459         if (!empty($write_sql)) {
460             $this->db->query(
461                 "UPDATE ".get_table_name($this->db_name).
462                 " SET changed=".$this->db->now().", ".join(', ', $write_sql).
463                 " WHERE contact_id=?".
464                     " AND user_id=?".
465                     " AND del<>1",
466                 $id,
467                 $this->user_id
468             );
469
470             $updated = $this->db->affected_rows();
471         }
472
473         return $updated;
474     }
475
476
477     /**
478      * Mark one or more contact records as deleted
479      *
480      * @param array  Record identifiers
481      */
482     function delete($ids)
483     {
484         if (!is_array($ids))
485             $ids = explode(',', $ids);
486
487         $ids = $this->db->array2list($ids, 'integer');
488
489         // flag record as deleted
490         $this->db->query(
491             "UPDATE ".get_table_name($this->db_name).
492             " SET del=1, changed=".$this->db->now().
493             " WHERE user_id=?".
494                 " AND contact_id IN ($ids)",
495             $this->user_id
496         );
497
498         $this->cache = null;
499
500         return $this->db->affected_rows();
501     }
502
503
504     /**
505      * Remove all records from the database
506      */
507     function delete_all()
508     {
509         $this->db->query("DELETE FROM ".get_table_name($this->db_name)." WHERE user_id = ?", $this->user_id);
510         $this->cache = null;
511         return $this->db->affected_rows();
512     }
513
514
515     /**
516      * Create a contact group with the given name
517      *
518      * @param string The group name
519      * @return mixed False on error, array with record props in success
520      */
521     function create_group($name)
522     {
523         $result = false;
524
525         // make sure we have a unique name
526         $name = $this->unique_groupname($name);
527
528         $this->db->query(
529             "INSERT INTO ".get_table_name($this->db_groups).
530             " (user_id, changed, name)".
531             " VALUES (".intval($this->user_id).", ".$this->db->now().", ".$this->db->quote($name).")"
532         );
533
534         if ($insert_id = $this->db->insert_id($this->db_groups))
535             $result = array('id' => $insert_id, 'name' => $name);
536
537         return $result;
538     }
539
540
541     /**
542      * Delete the given group (and all linked group members)
543      *
544      * @param string Group identifier
545      * @return boolean True on success, false if no data was changed
546      */
547     function delete_group($gid)
548     {
549         // flag group record as deleted
550         $sql_result = $this->db->query(
551             "UPDATE ".get_table_name($this->db_groups).
552             " SET del=1, changed=".$this->db->now().
553             " WHERE contactgroup_id=?",
554             $gid
555         );
556
557         $this->cache = null;
558
559         return $this->db->affected_rows();
560     }
561
562
563     /**
564      * Rename a specific contact group
565      *
566      * @param string Group identifier
567      * @param string New name to set for this group
568      * @return boolean New name on success, false if no data was changed
569      */
570     function rename_group($gid, $newname)
571     {
572         // make sure we have a unique name
573         $name = $this->unique_groupname($newname);
574
575         $sql_result = $this->db->query(
576             "UPDATE ".get_table_name($this->db_groups).
577             " SET name=?, changed=".$this->db->now().
578             " WHERE contactgroup_id=?",
579             $name, $gid
580         );
581
582         return $this->db->affected_rows() ? $name : false;
583     }
584
585
586     /**
587      * Add the given contact records the a certain group
588      *
589      * @param string  Group identifier
590      * @param array   List of contact identifiers to be added
591      * @return int    Number of contacts added 
592      */
593     function add_to_group($group_id, $ids)
594     {
595         if (!is_array($ids))
596             $ids = explode(',', $ids);
597
598         $added = 0;
599         $exists = array();
600
601         // get existing assignments ...
602         $sql_result = $this->db->query(
603             "SELECT contact_id FROM ".get_table_name($this->db_groupmembers).
604             " WHERE contactgroup_id=?".
605                 " AND contact_id IN (".$this->db->array2list($ids, 'integer').")",
606             $group_id
607         );
608         while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
609             $exists[] = $sql_arr['contact_id'];
610         }
611         // ... and remove them from the list
612         $ids = array_diff($ids, $exists);
613
614         foreach ($ids as $contact_id) {
615             $this->db->query(
616                 "INSERT INTO ".get_table_name($this->db_groupmembers).
617                 " (contactgroup_id, contact_id, created)".
618                 " VALUES (?, ?, ".$this->db->now().")",
619                 $group_id,
620                 $contact_id
621             );
622
623             if (!$this->db->db_error)
624                 $added++;
625         }
626
627         return $added;
628     }
629
630
631     /**
632      * Remove the given contact records from a certain group
633      *
634      * @param string  Group identifier
635      * @param array   List of contact identifiers to be removed
636      * @return int    Number of deleted group members
637      */
638     function remove_from_group($group_id, $ids)
639     {
640         if (!is_array($ids))
641             $ids = explode(',', $ids);
642
643         $ids = $this->db->array2list($ids, 'integer');
644
645         $sql_result = $this->db->query(
646             "DELETE FROM ".get_table_name($this->db_groupmembers).
647             " WHERE contactgroup_id=?".
648                 " AND contact_id IN ($ids)",
649             $group_id
650         );
651
652         return $this->db->affected_rows();
653     }
654
655
656     /**
657      * Check for existing groups with the same name
658      *
659      * @param string Name to check
660      * @return string A group name which is unique for the current use
661      */
662     private function unique_groupname($name)
663     {
664         $checkname = $name;
665         $num = 2; $hit = false;
666
667         do {
668             $sql_result = $this->db->query(
669                 "SELECT 1 FROM ".get_table_name($this->db_groups).
670                 " WHERE del<>1".
671                     " AND user_id=?".
672                     " AND name=?",
673                 $this->user_id,
674                 $checkname);
675
676             // append number to make name unique
677             if ($hit = $this->db->num_rows($sql_result))
678                 $checkname = $name . ' ' . $num++;
679         } while ($hit > 0);
680
681         return $checkname;
682     }
683
684 }