source: github/program/include/rcube_contacts.php @ 3b67e33

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 3b67e33 was 3b67e33, checked in by thomascube <thomas@…>, 3 years ago

Allow derived classes of rcube_contacts to override table and sequence names

  • Property mode set to 100644
File size: 19.0 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    // deprecated: re-implement $this->get_table_name() instead
32    protected $db_name = '';
33    protected $db_groups = '';
34    protected $db_groupmembers = '';
35   
36    private $db = null;
37    private $user_id = 0;
38    private $filter = null;
39    private $result = null;
40    private $search_fields;
41    private $search_string;
42    private $cache;
43    private $table_cols = array('name', 'email', 'firstname', 'surname', 'vcard');
44
45    /** public properties */
46    var $primary_key = 'contact_id';
47    var $readonly = false;
48    var $groups = true;
49    var $list_page = 1;
50    var $page_size = 10;
51    var $group_id = 0;
52    var $ready = false;
53
54
55    /**
56     * Object constructor
57     *
58     * @param object  Instance of the rcube_db class
59     * @param integer User-ID
60     */
61    function __construct($dbconn, $user)
62    {
63        $this->db = $dbconn;
64        $this->db_name = $this->get_table_name('contacts');
65        $this->db_groups = $this->get_table_name('contactgroups');
66        $this->db_groupmembers = $this->get_table_name('contactgroupmembers');
67       
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 ".$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 ".$this->db_groupmembers." AS m".
175                " ON (m.contact_id = c.".$this->primary_key.")";
176
177        $sql_result = $this->db->limitquery(
178            "SELECT * FROM ".$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 ".$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 ".$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 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 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 ".$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     * Create a new contact record
359     *
360     * @param array Assoziative array with save data
361     * @return The created record ID on success, False on error
362     */
363    function insert($save_data, $check=false)
364    {
365        if (is_object($save_data) && is_a($save_data, rcube_result_set))
366            return $this->insert_recset($save_data, $check);
367
368        $insert_id = $existing = false;
369
370        if ($check)
371            $existing = $this->search('email', $save_data['email'], true, false);
372
373        $a_insert_cols = $a_insert_values = array();
374
375        foreach ($this->table_cols as $col)
376            if (isset($save_data[$col])) {
377                $a_insert_cols[]   = $this->db->quoteIdentifier($col);
378                $a_insert_values[] = $this->db->quote($save_data[$col]);
379            }
380
381        if (!$existing->count && !empty($a_insert_cols)) {
382            $this->db->query(
383                "INSERT INTO ".$this->db_name.
384                " (user_id, changed, del, ".join(', ', $a_insert_cols).")".
385                " VALUES (".intval($this->user_id).", ".$this->db->now().", 0, ".join(', ', $a_insert_values).")"
386            );
387
388            $insert_id = $this->db->insert_id($this->get_sequence_name('contacts'));
389        }
390
391        // also add the newly created contact to the active group
392        if ($insert_id && $this->group_id)
393            $this->add_to_group($this->group_id, $insert_id);
394
395        $this->cache = null;
396
397        return $insert_id;
398    }
399
400
401    /**
402     * Insert new contacts for each row in set
403     */
404    function insert_recset($result, $check=false)
405    {
406        $ids = array();
407        while ($row = $result->next()) {
408            if ($insert = $this->insert($row, $check))
409                $ids[] = $insert;
410        }
411        return $ids;
412    }
413
414
415    /**
416     * Update a specific contact record
417     *
418     * @param mixed Record identifier
419     * @param array Assoziative array with save data
420     * @return True on success, False on error
421     */
422    function update($id, $save_cols)
423    {
424        $updated = false;
425        $write_sql = array();
426
427        foreach ($this->table_cols as $col)
428            if (isset($save_cols[$col]))
429                $write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col),
430                    $this->db->quote($save_cols[$col]));
431
432        if (!empty($write_sql)) {
433            $this->db->query(
434                "UPDATE ".$this->db_name.
435                " SET changed=".$this->db->now().", ".join(', ', $write_sql).
436                " WHERE contact_id=?".
437                    " AND user_id=?".
438                    " AND del<>1",
439                $id,
440                $this->user_id
441            );
442
443            $updated = $this->db->affected_rows();
444        }
445
446        return $updated;
447    }
448
449
450    /**
451     * Mark one or more contact records as deleted
452     *
453     * @param array  Record identifiers
454     */
455    function delete($ids)
456    {
457        if (!is_array($ids))
458            $ids = explode(',', $ids);
459
460        $ids = $this->db->array2list($ids, 'integer');
461
462        // flag record as deleted
463        $this->db->query(
464            "UPDATE ".$this->db_name.
465            " SET del=1, changed=".$this->db->now().
466            " WHERE user_id=?".
467                " AND contact_id IN ($ids)",
468            $this->user_id
469        );
470
471        $this->cache = null;
472
473        return $this->db->affected_rows();
474    }
475
476
477    /**
478     * Remove all records from the database
479     */
480    function delete_all()
481    {
482        $this->db->query("DELETE FROM {$this->db_name} WHERE user_id=?", $this->user_id);
483        $this->cache = null;
484        return $this->db->affected_rows();
485    }
486
487
488    /**
489     * Create a contact group with the given name
490     *
491     * @param string The group name
492     * @return False on error, array with record props in success
493     */
494    function create_group($name)
495    {
496        $result = false;
497
498        // make sure we have a unique name
499        $name = $this->unique_groupname($name);
500
501        $this->db->query(
502            "INSERT INTO ".$this->db_groups.
503            " (user_id, changed, name)".
504            " VALUES (".intval($this->user_id).", ".$this->db->now().", ".$this->db->quote($name).")"
505        );
506
507        if ($insert_id = $this->db->insert_id($this->get_sequence_name('contactgroups')))
508            $result = array('id' => $insert_id, 'name' => $name);
509
510        return $result;
511    }
512
513
514    /**
515     * Delete the given group (and all linked group members)
516     *
517     * @param string Group identifier
518     * @return boolean True on success, false if no data was changed
519     */
520    function delete_group($gid)
521    {
522        // flag group record as deleted
523        $sql_result = $this->db->query(
524            "UPDATE ".$this->db_groups.
525            " SET del=1, changed=".$this->db->now().
526            " WHERE contactgroup_id=?",
527            $gid
528        );
529
530        $this->cache = null;
531
532        return $this->db->affected_rows();
533    }
534
535
536    /**
537     * Rename a specific contact group
538     *
539     * @param string Group identifier
540     * @param string New name to set for this group
541     * @return boolean New name on success, false if no data was changed
542     */
543    function rename_group($gid, $newname)
544    {
545        // make sure we have a unique name
546        $name = $this->unique_groupname($newname);
547
548        $sql_result = $this->db->query(
549            "UPDATE ".$this->db_groups.
550            " SET name=?, changed=".$this->db->now().
551            " WHERE contactgroup_id=?",
552            $name, $gid
553        );
554
555        return $this->db->affected_rows() ? $name : false;
556    }
557
558
559    /**
560     * Add the given contact records the a certain group
561     *
562     * @param string  Group identifier
563     * @param array   List of contact identifiers to be added
564     * @return int    Number of contacts added
565     */
566    function add_to_group($group_id, $ids)
567    {
568        if (!is_array($ids))
569            $ids = explode(',', $ids);
570
571        $added = 0;
572
573        foreach ($ids as $contact_id) {
574            $sql_result = $this->db->query(
575                "SELECT 1 FROM ".$this->db_groupmembers.
576                " WHERE contactgroup_id=?".
577                    " AND contact_id=?",
578                $group_id,
579                $contact_id
580            );
581
582            if (!$this->db->num_rows($sql_result)) {
583                $this->db->query(
584                    "INSERT INTO ".$this->db_groupmembers.
585                    " (contactgroup_id, contact_id, created)".
586                    " VALUES (?, ?, ".$this->db->now().")",
587                    $group_id,
588                    $contact_id
589                );
590
591                if (!$this->db->db_error)
592                    $added++;
593            }
594        }
595
596        return $added;
597    }
598
599
600    /**
601     * Remove the given contact records from a certain group
602     *
603     * @param string  Group identifier
604     * @param array   List of contact identifiers to be removed
605     * @return int    Number of deleted group members
606     */
607    function remove_from_group($group_id, $ids)
608    {
609        if (!is_array($ids))
610            $ids = explode(',', $ids);
611
612        $ids = $this->db->array2list($ids, 'integer');
613
614        $sql_result = $this->db->query(
615            "DELETE FROM ".$this->db_groupmembers.
616            " WHERE contactgroup_id=?".
617                " AND contact_id IN ($ids)",
618            $group_id
619        );
620
621        return $this->db->affected_rows();
622    }
623
624
625    /**
626     * Check for existing groups with the same name
627     *
628     * @param string Name to check
629     * @return string A group name which is unique for the current use
630     */
631    private function unique_groupname($name)
632    {
633        $checkname = $name;
634        $num = 2; $hit = false;
635
636        do {
637            $sql_result = $this->db->query(
638                "SELECT 1 FROM ".$this->db_groups.
639                " WHERE del<>1".
640                    " AND user_id=?".
641                    " AND name=?",
642                $this->user_id,
643                $checkname);
644
645            // append number to make name unique
646            if ($hit = $this->db->num_rows($sql_result))
647                $checkname = $name . ' ' . $num++;
648        } while ($hit > 0);
649
650        return $checkname;
651    }
652
653
654    /**
655     * Wrapper for global get_table_name() which can be re-implemented
656     * by a derived class
657     */
658    protected function get_table_name($table)
659    {
660        return get_table_name($table);
661    }
662
663    /**
664     * Wrapper for global get_sequence_name() which can be re-implemented
665     * by a derived class
666     */
667    protected function get_sequence_name($table)
668    {
669        return get_sequence_name($table);
670    }
671
672}
Note: See TracBrowser for help on using the repository browser.