alecpl
2012-04-13 0c259682f65eaaf23ea4ccb56a706d6baf3007e4
commit | author | age
fba1f5 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcube_user.inc                                        |
6  |                                                                       |
e019f2 7  | This file is part of the Roundcube Webmail client                     |
f5e7b3 8  | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
7fe381 9  |                                                                       |
T 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.                     |
fba1f5 13  |                                                                       |
T 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
638fb8 22  $Id$
fba1f5 23
T 24 */
25
26
27 /**
28  * Class representing a system user
29  *
45f56c 30  * @package    Core
fba1f5 31  * @author     Thomas Bruederli <roundcube@gmail.com>
T 32  */
33 class rcube_user
34 {
40a186 35     public $ID;
A 36     public $data;
37     public $language;
a78901 38
5c461b 39     /**
A 40      * Holds database connection.
41      *
42      * @var rcube_mdb2
43      */
40a186 44     private $db;
A 45
46     /**
47      * rcmail object.
48      *
49      * @var rcmail
50      */
51     private $rc;
fba1f5 52
f8e48d 53     const SEARCH_ADDRESSBOOK = 1;
A 54     const SEARCH_MAIL = 2;
95987c 55
a78901 56     /**
A 57      * Object constructor
58      *
5c461b 59      * @param int   $id      User id
A 60      * @param array $sql_arr SQL result set
a78901 61      */
A 62     function __construct($id = null, $sql_arr = null)
63     {
40a186 64         $this->rc = rcmail::get_instance();
A 65         $this->db = $this->rc->get_dbh();
e99991 66
a78901 67         if ($id && !$sql_arr) {
A 68             $sql_result = $this->db->query(
0c2596 69                 "SELECT * FROM ".$this->db->table_name('users')." WHERE user_id = ?", $id);
a78901 70             $sql_arr = $this->db->fetch_assoc($sql_result);
A 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      *
5c461b 84      * @param  string $part Username part (empty or 'local' or 'domain')
8dfe51 85      * @return string Full user name or its part
a78901 86      */
8dfe51 87     function get_username($part = null)
a78901 88     {
A 89         if ($this->data['username']) {
8dfe51 90             list($local, $domain) = explode('@', $this->data['username']);
A 91
92             // at least we should always have the local part
93             if ($part == 'local') {
94                 return $local;
95             }
b0eeaa 96             // if no domain was provided...
A 97             if (empty($domain)) {
40a186 98                 $domain = $this->rc->config->mail_domain($this->data['mail_host']);
b0eeaa 99             }
8dfe51 100
A 101             if ($part == 'domain') {
102                 return $domain;
103             }
104
105             if (!empty($domain))
106                 return $local . '@' . $domain;
a78901 107             else
8dfe51 108                 return $local;
a78901 109         }
A 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);
8dfe51 124
40a186 125         if ($this->ID) {
A 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) {
0c2596 130                     $saved_prefs = unserialize($_SESSION['preferences']);
59ab0c 131                     $this->rc->session->remove('preferences');
0c2596 132                     $this->rc->session->remove('preferences_time');
59ab0c 133                     $this->save_prefs($saved_prefs);
40a186 134                 }
A 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         }
8dfe51 144
a78901 145         return $prefs;
A 146     }
8dfe51 147
A 148
a78901 149     /**
A 150      * Write the given user prefs to the user's record
151      *
5c461b 152      * @param array $a_user_prefs User prefs to save
a78901 153      * @return boolean True on success, False on failure
A 154      */
155     function save_prefs($a_user_prefs)
156     {
157         if (!$this->ID)
158             return false;
e99991 159
40a186 160         $config    = $this->rc->config;
a78901 161         $old_prefs = (array)$this->get_prefs();
fba1f5 162
a78901 163         // merge (partial) prefs array with existing settings
A 164         $save_prefs = $a_user_prefs + $old_prefs;
165         unset($save_prefs['language']);
e99991 166
a78901 167         // don't save prefs with default values if they haven't been changed yet
A 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(
0c2596 176             "UPDATE ".$this->db->table_name('users').
a78901 177             " SET preferences = ?".
A 178                 ", language = ?".
179             " WHERE user_id = ?",
180             $save_prefs,
181             $_SESSION['language'],
182             $this->ID);
183
184         $this->language = $_SESSION['language'];
185
40a186 186         // Update success
80809d 187         if ($this->db->affected_rows() !== false) {
a78901 188             $config->set_user_prefs($a_user_prefs);
A 189             $this->data['preferences'] = $save_prefs;
40a186 190
A 191             if (isset($_SESSION['preferences'])) {
192                 $this->rc->session->remove('preferences');
193                 $this->rc->session->remove('preferences_time');
194             }
a78901 195             return true;
A 196         }
40a186 197         // Update error, but we are using replication (we have read-only DB connection)
A 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         }
a78901 206
A 207         return false;
286420 208     }
f52c93 209
T 210
a78901 211     /**
A 212      * Get default identity of this user
213      *
5c461b 214      * @param  int   $id Identity ID. If empty, the default identity is returned
a78901 215      * @return array Hash array with all cols of the identity record
A 216      */
217     function get_identity($id = null)
fba1f5 218     {
a78901 219         $result = $this->list_identities($id ? sprintf('AND identity_id = %d', $id) : '');
A 220         return $result[0];
fba1f5 221     }
55f54e 222
A 223
a78901 224     /**
A 225      * Return a list of all identities linked with this user
226      *
5c461b 227      * @param string $sql_add Optional WHERE clauses
a78901 228      * @return array List of identities
A 229      */
230     function list_identities($sql_add = '')
fba1f5 231     {
a78901 232         $result = array();
fba1f5 233
a78901 234         $sql_result = $this->db->query(
0c2596 235             "SELECT * FROM ".$this->db->table_name('identities').
a78901 236             " WHERE del <> 1 AND user_id = ?".
A 237             ($sql_add ? " ".$sql_add : "").
238             " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC",
239             $this->ID);
e99991 240
a78901 241         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
A 242             $result[] = $sql_arr;
243         }
e99991 244
a78901 245         return $result;
A 246     }
fba1f5 247
a78901 248
A 249     /**
250      * Update a specific identity record
251      *
5c461b 252      * @param int    $iid  Identity ID
A 253      * @param array  $data Hash array with col->value pairs to save
a78901 254      * @return boolean True if saved successfully, false if nothing changed
A 255      */
256     function update_identity($iid, $data)
fba1f5 257     {
a78901 258         if (!$this->ID)
A 259             return false;
260
261         $query_cols = $query_params = array();
e99991 262
a78901 263         foreach ((array)$data as $col => $value) {
A 264             $query_cols[]   = $this->db->quoteIdentifier($col) . ' = ?';
265             $query_params[] = $value;
266         }
267         $query_params[] = $iid;
268         $query_params[] = $this->ID;
269
0c2596 270         $sql = "UPDATE ".$this->db->table_name('identities').
a78901 271             " SET changed = ".$this->db->now().", ".join(', ', $query_cols).
A 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));
e99991 278
a78901 279         return $this->db->affected_rows();
fba1f5 280     }
e99991 281
A 282
a78901 283     /**
A 284      * Create a new identity record linked with this user
285      *
5c461b 286      * @param array $data Hash array with col->value pairs to save
a78901 287      * @return int  The inserted identity ID or false on error
A 288      */
289     function insert_identity($data)
fba1f5 290     {
a78901 291         if (!$this->ID)
A 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
0c2596 304         $sql = "INSERT INTO ".$this->db->table_name('identities').
a78901 305             " (changed, ".join(', ', $insert_cols).")".
A 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');
fba1f5 312     }
e99991 313
A 314
a78901 315     /**
A 316      * Mark the given identity as deleted
317      *
5c461b 318      * @param  int     $iid Identity ID
a78901 319      * @return boolean True if deleted successfully, false if nothing changed
A 320      */
321     function delete_identity($iid)
fba1f5 322     {
a78901 323         if (!$this->ID)
A 324             return false;
07722a 325
a78901 326         $sql_result = $this->db->query(
0c2596 327             "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities').
a78901 328             " WHERE user_id = ? AND del <> 1",
A 329             $this->ID);
fba1f5 330
a78901 331         $sql_arr = $this->db->fetch_assoc($sql_result);
fba1f5 332
a78901 333         // we'll not delete last identity
A 334         if ($sql_arr['ident_count'] <= 1)
bbb142 335             return -1;
e99991 336
a78901 337         $this->db->query(
0c2596 338             "UPDATE ".$this->db->table_name('identities').
a78901 339             " SET del = 1, changed = ".$this->db->now().
A 340             " WHERE user_id = ?".
341                 " AND identity_id = ?",
342             $this->ID,
343             $iid);
fba1f5 344
a78901 345         return $this->db->affected_rows();
A 346     }
e99991 347
A 348
a78901 349     /**
A 350      * Make this identity the default one for this user
351      *
5c461b 352      * @param int $iid The identity ID
a78901 353      */
A 354     function set_default($iid)
355     {
356         if ($this->ID && $iid) {
357             $this->db->query(
0c2596 358                 "UPDATE ".$this->db->table_name('identities').
a78901 359                 " SET ".$this->db->quoteIdentifier('standard')." = '0'".
A 360                 " WHERE user_id = ?".
361                     " AND identity_id <> ?".
362                     " AND del <> 1",
363                 $this->ID,
364                 $iid);
365         }
366     }
e99991 367
A 368
a78901 369     /**
A 370      * Update user's last_login timestamp
371      */
372     function touch()
373     {
374         if ($this->ID) {
375             $this->db->query(
0c2596 376                 "UPDATE ".$this->db->table_name('users').
a78901 377                 " SET last_login = ".$this->db->now().
A 378                 " WHERE user_id = ?",
379                 $this->ID);
380         }
381     }
e99991 382
A 383
a78901 384     /**
A 385      * Clear the saved object state
386      */
387     function reset()
388     {
389         $this->ID = null;
390         $this->data = null;
391     }
e99991 392
A 393
a78901 394     /**
A 395      * Find a user record matching the given name and host
396      *
5c461b 397      * @param string $user IMAP user name
A 398      * @param string $host IMAP host name
399      * @return rcube_user New user instance
a78901 400      */
A 401     static function query($user, $host)
402     {
403         $dbh = rcmail::get_instance()->get_dbh();
e99991 404
a78901 405         // query for matching user name
0c2596 406         $query = "SELECT * FROM ".$dbh->table_name('users')." WHERE mail_host = ? AND %s = ?";
a1bbc2 407         $sql_result = $dbh->query(sprintf($query, 'username'), $host, $user);
e99991 408
a78901 409         // query for matching alias
A 410         if (!($sql_arr = $dbh->fetch_assoc($sql_result))) {
a1bbc2 411             $sql_result = $dbh->query(sprintf($query, 'alias'), $host, $user);
a78901 412             $sql_arr = $dbh->fetch_assoc($sql_result);
A 413         }
e99991 414
a78901 415         // user already registered -> overwrite username
A 416         if ($sql_arr)
417             return new rcube_user($sql_arr['user_id'], $sql_arr);
418         else
419             return false;
420     }
e99991 421
A 422
a78901 423     /**
A 424      * Create a new user record and return a rcube_user instance
425      *
5c461b 426      * @param string $user IMAP user name
A 427      * @param string $host IMAP host
428      * @return rcube_user New user instance
a78901 429      */
A 430     static function create($user, $host)
431     {
432         $user_name  = '';
433         $user_email = '';
434         $rcmail = rcmail::get_instance();
ac9927 435
a78901 436         // try to resolve user in virtuser table and file
A 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         }
f20971 440
e6ce00 441         $data = $rcmail->plugins->exec_hook('user_create',
94a5a2 442             array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
a78901 443
A 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(
0c2596 454             "INSERT INTO ".$dbh->table_name('users').
a78901 455             " (created, last_login, username, mail_host, alias, language)".
A 456             " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?, ?)",
457             strip_newlines($user),
458             strip_newlines($host),
459             strip_newlines($data['alias'] ? $data['alias'] : $user_email),
532c25 460             strip_newlines($data['language'] ? $data['language'] : $_SESSION['language']));
a78901 461
A 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
e6ce00 500                 $plugin = $rcmail->plugins->exec_hook('identity_create',
a78901 501                     array('login' => true, 'record' => $record));
e99991 502
a78901 503                 if (!$plugin['abort'] && $plugin['record']['email']) {
A 504                     $rcmail->user->insert_identity($plugin['record']);
505                 }
506                 $standard = 0;
507             }
08c8c3 508         }
T 509         else {
0c2596 510             rcube::raise_error(array(
a78901 511                 'code' => 500,
A 512                 'type' => 'php',
513                 'line' => __LINE__,
514                 'file' => __FILE__,
515                 'message' => "Failed to create new user"), true, false);
08c8c3 516         }
e99991 517
a78901 518         return $user_id ? $user_instance : false;
A 519     }
e99991 520
A 521
a78901 522     /**
A 523      * Resolve username using a virtuser plugins
524      *
5c461b 525      * @param string $email E-mail address to resolve
a78901 526      * @return string Resolved IMAP username
A 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));
fba1f5 533
a78901 534         return $plugin['user'];
A 535     }
fba1f5 536
T 537
a78901 538     /**
A 539      * Resolve e-mail address from virtuser plugins
540      *
5c461b 541      * @param string $user User name
A 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)
a78901 544      * @return mixed Resolved e-mail address string or array of strings
A 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));
fba1f5 552
a78901 553         return empty($plugin['email']) ? NULL : $plugin['email'];
A 554     }
8dfe51 555
f8e48d 556
A 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')
0c2596 576             ." FROM ".$this->db->table_name('searches')
f8e48d 577             ." WHERE user_id = ?"
A 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')
0c2596 610             ." FROM ".$this->db->table_name('searches')
f8e48d 611             ." WHERE user_id = ?"
A 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(
0c2596 641             "DELETE FROM ".$this->db->table_name('searches')
f8e48d 642             ." WHERE user_id = ?"
A 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
0c2596 671         $sql = "INSERT INTO ".$this->db->table_name('searches')
f8e48d 672             ." (".join(', ', $insert_cols).")"
A 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
fba1f5 681 }