source: subversion/trunk/roundcubemail/program/include/rcube_user.php @ 6073

Last change on this file since 6073 was 6073, checked in by alec, 14 months ago
  • Merge devel-framework branch, resolved conflicts
  • Property svn:keywords set to Id
File size: 20.6 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 |                                                                       |
10 | Licensed under the GNU General Public License version 3 or            |
11 | any later version with exceptions for skins & plugins.                |
12 | See the README file for a full license statement.                     |
13 |                                                                       |
14 | PURPOSE:                                                              |
15 |   This class represents a system user linked and provides access      |
16 |   to the related database records.                                    |
17 |                                                                       |
18 +-----------------------------------------------------------------------+
19 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
20 +-----------------------------------------------------------------------+
21
22 $Id$
23
24*/
25
26
27/**
28 * Class representing a system user
29 *
30 * @package    Core
31 * @author     Thomas Bruederli <roundcube@gmail.com>
32 */
33class rcube_user
34{
35    public $ID;
36    public $data;
37    public $language;
38
39    /**
40     * Holds database connection.
41     *
42     * @var rcube_mdb2
43     */
44    private $db;
45
46    /**
47     * rcmail object.
48     *
49     * @var rcmail
50     */
51    private $rc;
52
53    const SEARCH_ADDRESSBOOK = 1;
54    const SEARCH_MAIL = 2;
55
56    /**
57     * Object constructor
58     *
59     * @param int   $id      User id
60     * @param array $sql_arr SQL result set
61     */
62    function __construct($id = null, $sql_arr = null)
63    {
64        $this->rc = rcmail::get_instance();
65        $this->db = $this->rc->get_dbh();
66
67        if ($id && !$sql_arr) {
68            $sql_result = $this->db->query(
69                "SELECT * FROM ".$this->db->table_name('users')." WHERE user_id = ?", $id);
70            $sql_arr = $this->db->fetch_assoc($sql_result);
71        }
72
73        if (!empty($sql_arr)) {
74            $this->ID       = $sql_arr['user_id'];
75            $this->data     = $sql_arr;
76            $this->language = $sql_arr['language'];
77        }
78    }
79
80
81    /**
82     * Build a user name string (as e-mail address)
83     *
84     * @param  string $part Username part (empty or 'local' or 'domain')
85     * @return string Full user name or its part
86     */
87    function get_username($part = null)
88    {
89        if ($this->data['username']) {
90            list($local, $domain) = explode('@', $this->data['username']);
91
92            // at least we should always have the local part
93            if ($part == 'local') {
94                return $local;
95            }
96            // if no domain was provided...
97            if (empty($domain)) {
98                $domain = $this->rc->config->mail_domain($this->data['mail_host']);
99            }
100
101            if ($part == 'domain') {
102                return $domain;
103            }
104
105            if (!empty($domain))
106                return $local . '@' . $domain;
107            else
108                return $local;
109        }
110
111        return false;
112    }
113
114
115    /**
116     * Get the preferences saved for this user
117     *
118     * @return array Hash array with prefs
119     */
120    function get_prefs()
121    {
122        if (!empty($this->language))
123            $prefs = array('language' => $this->language);
124
125        if ($this->ID) {
126            // Preferences from session (write-master is unavailable)
127            if (!empty($_SESSION['preferences'])) {
128                // Check last write attempt time, try to write again (every 5 minutes)
129                if ($_SESSION['preferences_time'] < time() - 5 * 60) {
130                    $saved_prefs = unserialize($_SESSION['preferences']);
131                    $this->rc->session->remove('preferences');
132                    $this->rc->session->remove('preferences_time');
133                    $this->save_prefs($saved_prefs);
134                }
135                else {
136                    $this->data['preferences'] = $_SESSION['preferences'];
137                }
138            }
139
140            if ($this->data['preferences']) {
141                $prefs += (array)unserialize($this->data['preferences']);
142            }
143        }
144
145        return $prefs;
146    }
147
148
149    /**
150     * Write the given user prefs to the user's record
151     *
152     * @param array $a_user_prefs User prefs to save
153     * @return boolean True on success, False on failure
154     */
155    function save_prefs($a_user_prefs)
156    {
157        if (!$this->ID)
158            return false;
159
160        $config    = $this->rc->config;
161        $old_prefs = (array)$this->get_prefs();
162
163        // merge (partial) prefs array with existing settings
164        $save_prefs = $a_user_prefs + $old_prefs;
165        unset($save_prefs['language']);
166
167        // don't save prefs with default values if they haven't been changed yet
168        foreach ($a_user_prefs as $key => $value) {
169            if (!isset($old_prefs[$key]) && ($value == $config->get($key)))
170                unset($save_prefs[$key]);
171        }
172
173        $save_prefs = serialize($save_prefs);
174
175        $this->db->query(
176            "UPDATE ".$this->db->table_name('users').
177            " SET preferences = ?".
178                ", language = ?".
179            " WHERE user_id = ?",
180            $save_prefs,
181            $_SESSION['language'],
182            $this->ID);
183
184        $this->language = $_SESSION['language'];
185
186        // Update success
187        if ($this->db->affected_rows() !== false) {
188            $config->set_user_prefs($a_user_prefs);
189            $this->data['preferences'] = $save_prefs;
190
191            if (isset($_SESSION['preferences'])) {
192                $this->rc->session->remove('preferences');
193                $this->rc->session->remove('preferences_time');
194            }
195            return true;
196        }
197        // Update error, but we are using replication (we have read-only DB connection)
198        // and we are storing session not in the SQL database
199        // we can store preferences in session and try to write later (see get_prefs())
200        else if ($this->db->is_replicated() && $config->get('session_storage', 'db') != 'db') {
201            $_SESSION['preferences'] = $save_prefs;
202            $_SESSION['preferences_time'] = time();
203            $config->set_user_prefs($a_user_prefs);
204            $this->data['preferences'] = $save_prefs;
205        }
206
207        return false;
208    }
209
210
211    /**
212     * Get default identity of this user
213     *
214     * @param  int   $id Identity ID. If empty, the default identity is returned
215     * @return array Hash array with all cols of the identity record
216     */
217    function get_identity($id = null)
218    {
219        $result = $this->list_identities($id ? sprintf('AND identity_id = %d', $id) : '');
220        return $result[0];
221    }
222
223
224    /**
225     * Return a list of all identities linked with this user
226     *
227     * @param string $sql_add Optional WHERE clauses
228     * @return array List of identities
229     */
230    function list_identities($sql_add = '')
231    {
232        $result = array();
233
234        $sql_result = $this->db->query(
235            "SELECT * FROM ".$this->db->table_name('identities').
236            " WHERE del <> 1 AND user_id = ?".
237            ($sql_add ? " ".$sql_add : "").
238            " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC",
239            $this->ID);
240
241        while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
242            $result[] = $sql_arr;
243        }
244
245        return $result;
246    }
247
248
249    /**
250     * Update a specific identity record
251     *
252     * @param int    $iid  Identity ID
253     * @param array  $data Hash array with col->value pairs to save
254     * @return boolean True if saved successfully, false if nothing changed
255     */
256    function update_identity($iid, $data)
257    {
258        if (!$this->ID)
259            return false;
260
261        $query_cols = $query_params = array();
262
263        foreach ((array)$data as $col => $value) {
264            $query_cols[]   = $this->db->quoteIdentifier($col) . ' = ?';
265            $query_params[] = $value;
266        }
267        $query_params[] = $iid;
268        $query_params[] = $this->ID;
269
270        $sql = "UPDATE ".$this->db->table_name('identities').
271            " SET changed = ".$this->db->now().", ".join(', ', $query_cols).
272            " WHERE identity_id = ?".
273                " AND user_id = ?".
274                " AND del <> 1";
275
276        call_user_func_array(array($this->db, 'query'),
277            array_merge(array($sql), $query_params));
278
279        return $this->db->affected_rows();
280    }
281
282
283    /**
284     * Create a new identity record linked with this user
285     *
286     * @param array $data Hash array with col->value pairs to save
287     * @return int  The inserted identity ID or false on error
288     */
289    function insert_identity($data)
290    {
291        if (!$this->ID)
292            return false;
293
294        unset($data['user_id']);
295
296        $insert_cols = $insert_values = array();
297        foreach ((array)$data as $col => $value) {
298            $insert_cols[]   = $this->db->quoteIdentifier($col);
299            $insert_values[] = $value;
300        }
301        $insert_cols[]   = 'user_id';
302        $insert_values[] = $this->ID;
303
304        $sql = "INSERT INTO ".$this->db->table_name('identities').
305            " (changed, ".join(', ', $insert_cols).")".
306            " VALUES (".$this->db->now().", ".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
307
308        call_user_func_array(array($this->db, 'query'),
309            array_merge(array($sql), $insert_values));
310
311        return $this->db->insert_id('identities');
312    }
313
314
315    /**
316     * Mark the given identity as deleted
317     *
318     * @param  int     $iid Identity ID
319     * @return boolean True if deleted successfully, false if nothing changed
320     */
321    function delete_identity($iid)
322    {
323        if (!$this->ID)
324            return false;
325
326        $sql_result = $this->db->query(
327            "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities').
328            " WHERE user_id = ? AND del <> 1",
329            $this->ID);
330
331        $sql_arr = $this->db->fetch_assoc($sql_result);
332
333        // we'll not delete last identity
334        if ($sql_arr['ident_count'] <= 1)
335            return -1;
336
337        $this->db->query(
338            "UPDATE ".$this->db->table_name('identities').
339            " SET del = 1, changed = ".$this->db->now().
340            " WHERE user_id = ?".
341                " AND identity_id = ?",
342            $this->ID,
343            $iid);
344
345        return $this->db->affected_rows();
346    }
347
348
349    /**
350     * Make this identity the default one for this user
351     *
352     * @param int $iid The identity ID
353     */
354    function set_default($iid)
355    {
356        if ($this->ID && $iid) {
357            $this->db->query(
358                "UPDATE ".$this->db->table_name('identities').
359                " SET ".$this->db->quoteIdentifier('standard')." = '0'".
360                " WHERE user_id = ?".
361                    " AND identity_id <> ?".
362                    " AND del <> 1",
363                $this->ID,
364                $iid);
365        }
366    }
367
368
369    /**
370     * Update user's last_login timestamp
371     */
372    function touch()
373    {
374        if ($this->ID) {
375            $this->db->query(
376                "UPDATE ".$this->db->table_name('users').
377                " SET last_login = ".$this->db->now().
378                " WHERE user_id = ?",
379                $this->ID);
380        }
381    }
382
383
384    /**
385     * Clear the saved object state
386     */
387    function reset()
388    {
389        $this->ID = null;
390        $this->data = null;
391    }
392
393
394    /**
395     * Find a user record matching the given name and host
396     *
397     * @param string $user IMAP user name
398     * @param string $host IMAP host name
399     * @return rcube_user New user instance
400     */
401    static function query($user, $host)
402    {
403        $dbh = rcmail::get_instance()->get_dbh();
404
405        // query for matching user name
406        $query = "SELECT * FROM ".$dbh->table_name('users')." WHERE mail_host = ? AND %s = ?";
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 ".$dbh->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            rcube::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 ".$this->db->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 ".$this->db->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 ".$this->db->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 ".$this->db->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.