source: github/program/include/rcube_contacts.php @ b393e54

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since b393e54 was b393e54, checked in by alecpl <alec@…>, 3 years ago
  • Add rcube_addressbook::get_record_groups() (#1487089)
  • Property mode set to 100644
File size: 19.7 KB
Line 
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$
19
20*/
21
22
23/**
24 * Model class for the local address book database
25 *
26 * @package Addressbook
27 */
28class 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}
Note: See TracBrowser for help on using the repository browser.