source: github/program/include/rcube_user.php @ 40a1860

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