source: github/program/include/rcube_user.php @ f8e48df

HEADcourier-fixdev-browser-capabilitiespdorelease-0.7release-0.8
Last change on this file since f8e48df was f8e48df, checked in by alecpl <alec@…>, 22 months ago
  • Merge devel-saved_search branch (Addressbook Saved Searches)
  • Property mode set to 100644
File size: 20.4 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/rcube_user.inc                                        |
6 |                                                                       |
7 | This file is part of the Roundcube Webmail client                     |
8 | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   This class represents a system user linked and provides access      |
13 |   to the related database records.                                    |
14 |                                                                       |
15 +-----------------------------------------------------------------------+
16 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17 +-----------------------------------------------------------------------+
18
19 $Id$
20
21*/
22
23
24/**
25 * Class representing a system user
26 *
27 * @package    Core
28 * @author     Thomas Bruederli <roundcube@gmail.com>
29 */
30class rcube_user
31{
32    public $ID;
33    public $data;
34    public $language;
35
36    /**
37     * Holds database connection.
38     *
39     * @var rcube_mdb2
40     */
41    private $db;
42
43    /**
44     * rcmail object.
45     *
46     * @var rcmail
47     */
48    private $rc;
49
50    const SEARCH_ADDRESSBOOK = 1;
51    const SEARCH_MAIL = 2;
52
53    /**
54     * Object constructor
55     *
56     * @param int   $id      User id
57     * @param array $sql_arr SQL result set
58     */
59    function __construct($id = null, $sql_arr = null)
60    {
61        $this->rc = rcmail::get_instance();
62        $this->db = $this->rc->get_dbh();
63
64        if ($id && !$sql_arr) {
65            $sql_result = $this->db->query(
66                "SELECT * FROM ".get_table_name('users')." WHERE user_id = ?", $id);
67            $sql_arr = $this->db->fetch_assoc($sql_result);
68        }
69
70        if (!empty($sql_arr)) {
71            $this->ID       = $sql_arr['user_id'];
72            $this->data     = $sql_arr;
73            $this->language = $sql_arr['language'];
74        }
75    }
76
77
78    /**
79     * Build a user name string (as e-mail address)
80     *
81     * @param  string $part Username part (empty or 'local' or 'domain')
82     * @return string Full user name or its part
83     */
84    function get_username($part = null)
85    {
86        if ($this->data['username']) {
87            list($local, $domain) = explode('@', $this->data['username']);
88
89            // at least we should always have the local part
90            if ($part == 'local') {
91                return $local;
92            }
93            // if no domain was provided...
94            if (empty($domain)) {
95                $domain = $this->rc->config->mail_domain($this->data['mail_host']);
96            }
97
98            if ($part == 'domain') {
99                return $domain;
100            }
101
102            if (!empty($domain))
103                return $local . '@' . $domain;
104            else
105                return $local;
106        }
107
108        return false;
109    }
110
111
112    /**
113     * Get the preferences saved for this user
114     *
115     * @return array Hash array with prefs
116     */
117    function get_prefs()
118    {
119        if (!empty($this->language))
120            $prefs = array('language' => $this->language);
121
122        if ($this->ID) {
123            // Preferences from session (write-master is unavailable)
124            if (!empty($_SESSION['preferences'])) {
125                // Check last write attempt time, try to write again (every 5 minutes)
126                if ($_SESSION['preferences_time'] < time() - 5 * 60) {
127                    $saved_prefs = unserialize($_SESSION['preferences']);
128                    $this->rc->session->remove('preferences');
129                    $this->rc->session->remove('preferences_time');
130                    $this->save_prefs($saved_prefs);
131                }
132                else {
133                    $this->data['preferences'] = $_SESSION['preferences'];
134                }
135            }
136
137            if ($this->data['preferences']) {
138                $prefs += (array)unserialize($this->data['preferences']);
139            }
140        }
141
142        return $prefs;
143    }
144
145
146    /**
147     * Write the given user prefs to the user's record
148     *
149     * @param array $a_user_prefs User prefs to save
150     * @return boolean True on success, False on failure
151     */
152    function save_prefs($a_user_prefs)
153    {
154        if (!$this->ID)
155            return false;
156
157        $config    = $this->rc->config;
158        $old_prefs = (array)$this->get_prefs();
159
160        // merge (partial) prefs array with existing settings
161        $save_prefs = $a_user_prefs + $old_prefs;
162        unset($save_prefs['language']);
163
164        // don't save prefs with default values if they haven't been changed yet
165        foreach ($a_user_prefs as $key => $value) {
166            if (!isset($old_prefs[$key]) && ($value == $config->get($key)))
167                unset($save_prefs[$key]);
168        }
169
170        $save_prefs = serialize($save_prefs);
171
172        $this->db->query(
173            "UPDATE ".get_table_name('users').
174            " SET preferences = ?".
175                ", language = ?".
176            " WHERE user_id = ?",
177            $save_prefs,
178            $_SESSION['language'],
179            $this->ID);
180
181        $this->language = $_SESSION['language'];
182
183        // Update success
184        if ($this->db->affected_rows() !== false) {
185            $config->set_user_prefs($a_user_prefs);
186            $this->data['preferences'] = $save_prefs;
187
188            if (isset($_SESSION['preferences'])) {
189                $this->rc->session->remove('preferences');
190                $this->rc->session->remove('preferences_time');
191            }
192            return true;
193        }
194        // Update error, but we are using replication (we have read-only DB connection)
195        // and we are storing session not in the SQL database
196        // we can store preferences in session and try to write later (see get_prefs())
197        else if ($this->db->is_replicated() && $config->get('session_storage', 'db') != 'db') {
198            $_SESSION['preferences'] = $save_prefs;
199            $_SESSION['preferences_time'] = time();
200            $config->set_user_prefs($a_user_prefs);
201            $this->data['preferences'] = $save_prefs;
202        }
203
204        return false;
205    }
206
207
208    /**
209     * Get default identity of this user
210     *
211     * @param  int   $id Identity ID. If empty, the default identity is returned
212     * @return array Hash array with all cols of the identity record
213     */
214    function get_identity($id = null)
215    {
216        $result = $this->list_identities($id ? sprintf('AND identity_id = %d', $id) : '');
217        return $result[0];
218    }
219
220
221    /**
222     * Return a list of all identities linked with this user
223     *
224     * @param string $sql_add Optional WHERE clauses
225     * @return array List of identities
226     */
227    function list_identities($sql_add = '')
228    {
229        $result = array();
230
231        $sql_result = $this->db->query(
232            "SELECT * FROM ".get_table_name('identities').
233            " WHERE del <> 1 AND user_id = ?".
234            ($sql_add ? " ".$sql_add : "").
235            " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC",
236            $this->ID);
237
238        while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
239            $result[] = $sql_arr;
240        }
241
242        return $result;
243    }
244
245
246    /**
247     * Update a specific identity record
248     *
249     * @param int    $iid  Identity ID
250     * @param array  $data Hash array with col->value pairs to save
251     * @return boolean True if saved successfully, false if nothing changed
252     */
253    function update_identity($iid, $data)
254    {
255        if (!$this->ID)
256            return false;
257
258        $query_cols = $query_params = array();
259
260        foreach ((array)$data as $col => $value) {
261            $query_cols[]   = $this->db->quoteIdentifier($col) . ' = ?';
262            $query_params[] = $value;
263        }
264        $query_params[] = $iid;
265        $query_params[] = $this->ID;
266
267        $sql = "UPDATE ".get_table_name('identities').
268            " SET changed = ".$this->db->now().", ".join(', ', $query_cols).
269            " WHERE identity_id = ?".
270                " AND user_id = ?".
271                " AND del <> 1";
272
273        call_user_func_array(array($this->db, 'query'),
274            array_merge(array($sql), $query_params));
275
276        return $this->db->affected_rows();
277    }
278
279
280    /**
281     * Create a new identity record linked with this user
282     *
283     * @param array $data Hash array with col->value pairs to save
284     * @return int  The inserted identity ID or false on error
285     */
286    function insert_identity($data)
287    {
288        if (!$this->ID)
289            return false;
290
291        unset($data['user_id']);
292
293        $insert_cols = $insert_values = array();
294        foreach ((array)$data as $col => $value) {
295            $insert_cols[]   = $this->db->quoteIdentifier($col);
296            $insert_values[] = $value;
297        }
298        $insert_cols[]   = 'user_id';
299        $insert_values[] = $this->ID;
300
301        $sql = "INSERT INTO ".get_table_name('identities').
302            " (changed, ".join(', ', $insert_cols).")".
303            " VALUES (".$this->db->now().", ".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
304
305        call_user_func_array(array($this->db, 'query'),
306            array_merge(array($sql), $insert_values));
307
308        return $this->db->insert_id('identities');
309    }
310
311
312    /**
313     * Mark the given identity as deleted
314     *
315     * @param  int     $iid Identity ID
316     * @return boolean True if deleted successfully, false if nothing changed
317     */
318    function delete_identity($iid)
319    {
320        if (!$this->ID)
321            return false;
322
323        $sql_result = $this->db->query(
324            "SELECT count(*) AS ident_count FROM ".get_table_name('identities').
325            " WHERE user_id = ? AND del <> 1",
326            $this->ID);
327
328        $sql_arr = $this->db->fetch_assoc($sql_result);
329
330        // we'll not delete last identity
331        if ($sql_arr['ident_count'] <= 1)
332            return -1;
333
334        $this->db->query(
335            "UPDATE ".get_table_name('identities').
336            " SET del = 1, changed = ".$this->db->now().
337            " WHERE user_id = ?".
338                " AND identity_id = ?",
339            $this->ID,
340            $iid);
341
342        return $this->db->affected_rows();
343    }
344
345
346    /**
347     * Make this identity the default one for this user
348     *
349     * @param int $iid The identity ID
350     */
351    function set_default($iid)
352    {
353        if ($this->ID && $iid) {
354            $this->db->query(
355                "UPDATE ".get_table_name('identities').
356                " SET ".$this->db->quoteIdentifier('standard')." = '0'".
357                " WHERE user_id = ?".
358                    " AND identity_id <> ?".
359                    " AND del <> 1",
360                $this->ID,
361                $iid);
362        }
363    }
364
365
366    /**
367     * Update user's last_login timestamp
368     */
369    function touch()
370    {
371        if ($this->ID) {
372            $this->db->query(
373                "UPDATE ".get_table_name('users').
374                " SET last_login = ".$this->db->now().
375                " WHERE user_id = ?",
376                $this->ID);
377        }
378    }
379
380
381    /**
382     * Clear the saved object state
383     */
384    function reset()
385    {
386        $this->ID = null;
387        $this->data = null;
388    }
389
390
391    /**
392     * Find a user record matching the given name and host
393     *
394     * @param string $user IMAP user name
395     * @param string $host IMAP host name
396     * @return rcube_user New user instance
397     */
398    static function query($user, $host)
399    {
400        $dbh = rcmail::get_instance()->get_dbh();
401
402        // use BINARY (case-sensitive) comparison on MySQL, other engines are case-sensitive
403        $mod = preg_match('/^mysql/', $dbh->db_provider) ? 'BINARY' : '';
404
405        // query for matching user name
406        $query = "SELECT * FROM ".get_table_name('users')." WHERE mail_host = ? AND %s = $mod ?";
407        $sql_result = $dbh->query(sprintf($query, 'username'), $host, $user);
408
409        // query for matching alias
410        if (!($sql_arr = $dbh->fetch_assoc($sql_result))) {
411            $sql_result = $dbh->query(sprintf($query, 'alias'), $host, $user);
412            $sql_arr = $dbh->fetch_assoc($sql_result);
413        }
414
415        // user already registered -> overwrite username
416        if ($sql_arr)
417            return new rcube_user($sql_arr['user_id'], $sql_arr);
418        else
419            return false;
420    }
421
422
423    /**
424     * Create a new user record and return a rcube_user instance
425     *
426     * @param string $user IMAP user name
427     * @param string $host IMAP host
428     * @return rcube_user New user instance
429     */
430    static function create($user, $host)
431    {
432        $user_name  = '';
433        $user_email = '';
434        $rcmail = rcmail::get_instance();
435
436        // try to resolve user in virtuser table and file
437        if ($email_list = self::user2email($user, false, true)) {
438            $user_email = is_array($email_list[0]) ? $email_list[0]['email'] : $email_list[0];
439        }
440
441        $data = $rcmail->plugins->exec_hook('user_create',
442                array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
443
444        // plugin aborted this operation
445        if ($data['abort'])
446            return false;
447
448        $user_name  = $data['user_name'];
449        $user_email = $data['user_email'];
450
451        $dbh = $rcmail->get_dbh();
452
453        $dbh->query(
454            "INSERT INTO ".get_table_name('users').
455            " (created, last_login, username, mail_host, alias, language)".
456            " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?, ?)",
457            strip_newlines($user),
458            strip_newlines($host),
459            strip_newlines($data['alias'] ? $data['alias'] : $user_email),
460            strip_newlines($data['language'] ? $data['language'] : $_SESSION['language']));
461
462        if ($user_id = $dbh->insert_id('users')) {
463            // create rcube_user instance to make plugin hooks work
464            $user_instance = new rcube_user($user_id);
465            $rcmail->user  = $user_instance;
466
467            $mail_domain = $rcmail->config->mail_domain($host);
468
469            if ($user_email == '') {
470                $user_email = strpos($user, '@') ? $user : sprintf('%s@%s', $user, $mail_domain);
471            }
472            if ($user_name == '') {
473                $user_name = $user != $user_email ? $user : '';
474            }
475
476            if (empty($email_list))
477                $email_list[] = strip_newlines($user_email);
478            // identities_level check
479            else if (count($email_list) > 1 && $rcmail->config->get('identities_level', 0) > 1)
480                $email_list = array($email_list[0]);
481
482            // create new identities records
483            $standard = 1;
484            foreach ($email_list as $row) {
485                    $record = array();
486
487                if (is_array($row)) {
488                        $record = $row;
489                }
490                else {
491                    $record['email'] = $row;
492                }
493
494                    if (empty($record['name']))
495                        $record['name'] = $user_name;
496                $record['name'] = strip_newlines($record['name']);
497                $record['user_id'] = $user_id;
498                $record['standard'] = $standard;
499
500                $plugin = $rcmail->plugins->exec_hook('identity_create',
501                        array('login' => true, 'record' => $record));
502
503                if (!$plugin['abort'] && $plugin['record']['email']) {
504                    $rcmail->user->insert_identity($plugin['record']);
505                }
506                $standard = 0;
507            }
508        }
509        else {
510            raise_error(array(
511                'code' => 500,
512                'type' => 'php',
513                'line' => __LINE__,
514                'file' => __FILE__,
515                'message' => "Failed to create new user"), true, false);
516        }
517
518        return $user_id ? $user_instance : false;
519    }
520
521
522    /**
523     * Resolve username using a virtuser plugins
524     *
525     * @param string $email E-mail address to resolve
526     * @return string Resolved IMAP username
527     */
528    static function email2user($email)
529    {
530        $rcmail = rcmail::get_instance();
531        $plugin = $rcmail->plugins->exec_hook('email2user',
532            array('email' => $email, 'user' => NULL));
533
534        return $plugin['user'];
535    }
536
537
538    /**
539     * Resolve e-mail address from virtuser plugins
540     *
541     * @param string $user User name
542     * @param boolean $first If true returns first found entry
543     * @param boolean $extended If true returns email as array (email and name for identity)
544     * @return mixed Resolved e-mail address string or array of strings
545     */
546    static function user2email($user, $first=true, $extended=false)
547    {
548        $rcmail = rcmail::get_instance();
549        $plugin = $rcmail->plugins->exec_hook('user2email',
550            array('email' => NULL, 'user' => $user,
551                'first' => $first, 'extended' => $extended));
552
553        return empty($plugin['email']) ? NULL : $plugin['email'];
554    }
555
556
557    /**
558     * Return a list of saved searches linked with this user
559     *
560     * @param int  $type  Search type
561     *
562     * @return array List of saved searches indexed by search ID
563     */
564    function list_searches($type)
565    {
566        $plugin = $this->rc->plugins->exec_hook('saved_search_list', array('type' => $type));
567
568        if ($plugin['abort']) {
569            return (array) $plugin['result'];
570        }
571
572        $result = array();
573
574        $sql_result = $this->db->query(
575            "SELECT search_id AS id, ".$this->db->quoteIdentifier('name')
576            ." FROM ".get_table_name('searches')
577            ." WHERE user_id = ?"
578                ." AND ".$this->db->quoteIdentifier('type')." = ?"
579            ." ORDER BY ".$this->db->quoteIdentifier('name'),
580            (int) $this->ID, (int) $type);
581
582        while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
583            $sql_arr['data'] = unserialize($sql_arr['data']);
584            $result[$sql_arr['id']] = $sql_arr;
585        }
586
587        return $result;
588    }
589
590
591    /**
592     * Return saved search data.
593     *
594     * @param int  $id  Row identifier
595     *
596     * @return array Data
597     */
598    function get_search($id)
599    {
600        $plugin = $this->rc->plugins->exec_hook('saved_search_get', array('id' => $id));
601
602        if ($plugin['abort']) {
603            return $plugin['result'];
604        }
605
606        $sql_result = $this->db->query(
607            "SELECT ".$this->db->quoteIdentifier('name')
608                .", ".$this->db->quoteIdentifier('data')
609                .", ".$this->db->quoteIdentifier('type')
610            ." FROM ".get_table_name('searches')
611            ." WHERE user_id = ?"
612                ." AND search_id = ?",
613            (int) $this->ID, (int) $id);
614
615        while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
616            return array(
617                'id'   => $id,
618                'name' => $sql_arr['name'],
619                'type' => $sql_arr['type'],
620                'data' => unserialize($sql_arr['data']),
621            );
622        }
623
624        return null;
625    }
626
627
628    /**
629     * Deletes given saved search record
630     *
631     * @param  int  $sid  Search ID
632     *
633     * @return boolean True if deleted successfully, false if nothing changed
634     */
635    function delete_search($sid)
636    {
637        if (!$this->ID)
638            return false;
639
640        $this->db->query(
641            "DELETE FROM ".get_table_name('searches')
642            ." WHERE user_id = ?"
643                ." AND search_id = ?",
644            (int) $this->ID, $sid);
645
646        return $this->db->affected_rows();
647    }
648
649
650    /**
651     * Create a new saved search record linked with this user
652     *
653     * @param array $data Hash array with col->value pairs to save
654     *
655     * @return int  The inserted search ID or false on error
656     */
657    function insert_search($data)
658    {
659        if (!$this->ID)
660            return false;
661
662        $insert_cols[]   = 'user_id';
663        $insert_values[] = (int) $this->ID;
664        $insert_cols[]   = $this->db->quoteIdentifier('type');
665        $insert_values[] = (int) $data['type'];
666        $insert_cols[]   = $this->db->quoteIdentifier('name');
667        $insert_values[] = $data['name'];
668        $insert_cols[]   = $this->db->quoteIdentifier('data');
669        $insert_values[] = serialize($data['data']);
670
671        $sql = "INSERT INTO ".get_table_name('searches')
672            ." (".join(', ', $insert_cols).")"
673            ." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
674
675        call_user_func_array(array($this->db, 'query'),
676            array_merge(array($sql), $insert_values));
677
678        return $this->db->insert_id('searches');
679    }
680
681}
Note: See TracBrowser for help on using the repository browser.