Aleksander Machniak
2012-08-06 565c472918e7f9707cd8d7909ad5dbcc5a921fdf
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 */
22
23
24 /**
25  * Class representing a system user
26  *
45f56c 27  * @package    Core
fba1f5 28  * @author     Thomas Bruederli <roundcube@gmail.com>
T 29  */
30 class rcube_user
31 {
40a186 32     public $ID;
A 33     public $data;
34     public $language;
a78901 35
5c461b 36     /**
A 37      * Holds database connection.
38      *
39      * @var rcube_mdb2
40      */
40a186 41     private $db;
A 42
43     /**
be98df 44      * Framework object.
40a186 45      *
be98df 46      * @var rcube
40a186 47      */
A 48     private $rc;
fba1f5 49
f8e48d 50     const SEARCH_ADDRESSBOOK = 1;
A 51     const SEARCH_MAIL = 2;
95987c 52
a78901 53     /**
A 54      * Object constructor
55      *
5c461b 56      * @param int   $id      User id
A 57      * @param array $sql_arr SQL result set
a78901 58      */
A 59     function __construct($id = null, $sql_arr = null)
60     {
be98df 61         $this->rc = rcube::get_instance();
40a186 62         $this->db = $this->rc->get_dbh();
e99991 63
a78901 64         if ($id && !$sql_arr) {
A 65             $sql_result = $this->db->query(
0c2596 66                 "SELECT * FROM ".$this->db->table_name('users')." WHERE user_id = ?", $id);
a78901 67             $sql_arr = $this->db->fetch_assoc($sql_result);
A 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      *
5c461b 81      * @param  string $part Username part (empty or 'local' or 'domain')
8dfe51 82      * @return string Full user name or its part
a78901 83      */
8dfe51 84     function get_username($part = null)
a78901 85     {
A 86         if ($this->data['username']) {
8dfe51 87             list($local, $domain) = explode('@', $this->data['username']);
A 88
89             // at least we should always have the local part
90             if ($part == 'local') {
91                 return $local;
92             }
b0eeaa 93             // if no domain was provided...
A 94             if (empty($domain)) {
40a186 95                 $domain = $this->rc->config->mail_domain($this->data['mail_host']);
b0eeaa 96             }
8dfe51 97
A 98             if ($part == 'domain') {
99                 return $domain;
100             }
101
102             if (!empty($domain))
103                 return $local . '@' . $domain;
a78901 104             else
8dfe51 105                 return $local;
a78901 106         }
A 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);
8dfe51 121
40a186 122         if ($this->ID) {
A 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) {
0c2596 127                     $saved_prefs = unserialize($_SESSION['preferences']);
59ab0c 128                     $this->rc->session->remove('preferences');
0c2596 129                     $this->rc->session->remove('preferences_time');
59ab0c 130                     $this->save_prefs($saved_prefs);
40a186 131                 }
A 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         }
8dfe51 141
a78901 142         return $prefs;
A 143     }
8dfe51 144
A 145
a78901 146     /**
A 147      * Write the given user prefs to the user's record
148      *
5c461b 149      * @param array $a_user_prefs User prefs to save
a78901 150      * @return boolean True on success, False on failure
A 151      */
152     function save_prefs($a_user_prefs)
153     {
154         if (!$this->ID)
155             return false;
e99991 156
40a186 157         $config    = $this->rc->config;
a78901 158         $old_prefs = (array)$this->get_prefs();
fba1f5 159
a78901 160         // merge (partial) prefs array with existing settings
A 161         $save_prefs = $a_user_prefs + $old_prefs;
162         unset($save_prefs['language']);
e99991 163
a78901 164         // don't save prefs with default values if they haven't been changed yet
A 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(
0c2596 173             "UPDATE ".$this->db->table_name('users').
a78901 174             " SET preferences = ?".
A 175                 ", language = ?".
176             " WHERE user_id = ?",
177             $save_prefs,
178             $_SESSION['language'],
179             $this->ID);
180
181         $this->language = $_SESSION['language'];
182
40a186 183         // Update success
80809d 184         if ($this->db->affected_rows() !== false) {
a78901 185             $config->set_user_prefs($a_user_prefs);
A 186             $this->data['preferences'] = $save_prefs;
40a186 187
A 188             if (isset($_SESSION['preferences'])) {
189                 $this->rc->session->remove('preferences');
190                 $this->rc->session->remove('preferences_time');
191             }
a78901 192             return true;
A 193         }
40a186 194         // Update error, but we are using replication (we have read-only DB connection)
A 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         }
a78901 203
A 204         return false;
286420 205     }
f52c93 206
T 207
a78901 208     /**
A 209      * Get default identity of this user
210      *
5c461b 211      * @param  int   $id Identity ID. If empty, the default identity is returned
a78901 212      * @return array Hash array with all cols of the identity record
A 213      */
214     function get_identity($id = null)
fba1f5 215     {
a78901 216         $result = $this->list_identities($id ? sprintf('AND identity_id = %d', $id) : '');
A 217         return $result[0];
fba1f5 218     }
55f54e 219
A 220
a78901 221     /**
A 222      * Return a list of all identities linked with this user
223      *
5c461b 224      * @param string $sql_add Optional WHERE clauses
a78901 225      * @return array List of identities
A 226      */
227     function list_identities($sql_add = '')
fba1f5 228     {
a78901 229         $result = array();
fba1f5 230
a78901 231         $sql_result = $this->db->query(
0c2596 232             "SELECT * FROM ".$this->db->table_name('identities').
a78901 233             " WHERE del <> 1 AND user_id = ?".
A 234             ($sql_add ? " ".$sql_add : "").
235             " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC",
236             $this->ID);
e99991 237
a78901 238         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
A 239             $result[] = $sql_arr;
240         }
e99991 241
a78901 242         return $result;
A 243     }
fba1f5 244
a78901 245
A 246     /**
247      * Update a specific identity record
248      *
5c461b 249      * @param int    $iid  Identity ID
A 250      * @param array  $data Hash array with col->value pairs to save
a78901 251      * @return boolean True if saved successfully, false if nothing changed
A 252      */
253     function update_identity($iid, $data)
fba1f5 254     {
a78901 255         if (!$this->ID)
A 256             return false;
257
258         $query_cols = $query_params = array();
e99991 259
a78901 260         foreach ((array)$data as $col => $value) {
A 261             $query_cols[]   = $this->db->quoteIdentifier($col) . ' = ?';
262             $query_params[] = $value;
263         }
264         $query_params[] = $iid;
265         $query_params[] = $this->ID;
266
0c2596 267         $sql = "UPDATE ".$this->db->table_name('identities').
a78901 268             " SET changed = ".$this->db->now().", ".join(', ', $query_cols).
A 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));
e99991 275
a78901 276         return $this->db->affected_rows();
fba1f5 277     }
e99991 278
A 279
a78901 280     /**
A 281      * Create a new identity record linked with this user
282      *
5c461b 283      * @param array $data Hash array with col->value pairs to save
a78901 284      * @return int  The inserted identity ID or false on error
A 285      */
286     function insert_identity($data)
fba1f5 287     {
a78901 288         if (!$this->ID)
A 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
0c2596 301         $sql = "INSERT INTO ".$this->db->table_name('identities').
a78901 302             " (changed, ".join(', ', $insert_cols).")".
A 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');
fba1f5 309     }
e99991 310
A 311
a78901 312     /**
A 313      * Mark the given identity as deleted
314      *
5c461b 315      * @param  int     $iid Identity ID
a78901 316      * @return boolean True if deleted successfully, false if nothing changed
A 317      */
318     function delete_identity($iid)
fba1f5 319     {
a78901 320         if (!$this->ID)
A 321             return false;
07722a 322
a78901 323         $sql_result = $this->db->query(
0c2596 324             "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities').
a78901 325             " WHERE user_id = ? AND del <> 1",
A 326             $this->ID);
fba1f5 327
a78901 328         $sql_arr = $this->db->fetch_assoc($sql_result);
fba1f5 329
a78901 330         // we'll not delete last identity
A 331         if ($sql_arr['ident_count'] <= 1)
bbb142 332             return -1;
e99991 333
a78901 334         $this->db->query(
0c2596 335             "UPDATE ".$this->db->table_name('identities').
a78901 336             " SET del = 1, changed = ".$this->db->now().
A 337             " WHERE user_id = ?".
338                 " AND identity_id = ?",
339             $this->ID,
340             $iid);
fba1f5 341
a78901 342         return $this->db->affected_rows();
A 343     }
e99991 344
A 345
a78901 346     /**
A 347      * Make this identity the default one for this user
348      *
5c461b 349      * @param int $iid The identity ID
a78901 350      */
A 351     function set_default($iid)
352     {
353         if ($this->ID && $iid) {
354             $this->db->query(
0c2596 355                 "UPDATE ".$this->db->table_name('identities').
a78901 356                 " SET ".$this->db->quoteIdentifier('standard')." = '0'".
A 357                 " WHERE user_id = ?".
358                     " AND identity_id <> ?".
359                     " AND del <> 1",
360                 $this->ID,
361                 $iid);
362         }
363     }
e99991 364
A 365
a78901 366     /**
A 367      * Update user's last_login timestamp
368      */
369     function touch()
370     {
371         if ($this->ID) {
372             $this->db->query(
0c2596 373                 "UPDATE ".$this->db->table_name('users').
a78901 374                 " SET last_login = ".$this->db->now().
A 375                 " WHERE user_id = ?",
376                 $this->ID);
377         }
378     }
e99991 379
A 380
a78901 381     /**
A 382      * Clear the saved object state
383      */
384     function reset()
385     {
386         $this->ID = null;
387         $this->data = null;
388     }
e99991 389
A 390
a78901 391     /**
A 392      * Find a user record matching the given name and host
393      *
5c461b 394      * @param string $user IMAP user name
A 395      * @param string $host IMAP host name
396      * @return rcube_user New user instance
a78901 397      */
A 398     static function query($user, $host)
399     {
565c47 400         $dbh    = rcube::get_instance()->get_dbh();
AM 401         $config = rcube::get_instance()->config;
e99991 402
a78901 403         // query for matching user name
565c47 404         $sql_result = $dbh->query("SELECT * FROM " . $dbh->table_name('users')
AM 405             ." WHERE mail_host = ? AND username = ?", $host, $user);
e99991 406
565c47 407         $sql_arr = $dbh->fetch_assoc($sql_result);
AM 408
409         // username not found, try aliases from identities
410         if (empty($sql_arr) && $config->get('user_aliases') && strpos($user, '@')) {
411             $sql_result = $dbh->limitquery("SELECT u.*"
412                 ." FROM " . $dbh->table_name('users') . " u"
413                 ." JOIN " . $dbh->table_name('identities') . " i ON (i.user_id = u.user_id)"
414                 ." WHERE email = ? AND del <> 1", 0, 1, $user);
415
a78901 416             $sql_arr = $dbh->fetch_assoc($sql_result);
A 417         }
e99991 418
a78901 419         // user already registered -> overwrite username
A 420         if ($sql_arr)
421             return new rcube_user($sql_arr['user_id'], $sql_arr);
422         else
423             return false;
424     }
e99991 425
A 426
a78901 427     /**
A 428      * Create a new user record and return a rcube_user instance
429      *
5c461b 430      * @param string $user IMAP user name
A 431      * @param string $host IMAP host
432      * @return rcube_user New user instance
a78901 433      */
A 434     static function create($user, $host)
435     {
436         $user_name  = '';
437         $user_email = '';
be98df 438         $rcube = rcube::get_instance();
ac9927 439
a78901 440         // try to resolve user in virtuser table and file
A 441         if ($email_list = self::user2email($user, false, true)) {
442             $user_email = is_array($email_list[0]) ? $email_list[0]['email'] : $email_list[0];
443         }
f20971 444
be98df 445         $data = $rcube->plugins->exec_hook('user_create',
94a5a2 446             array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
a78901 447
A 448         // plugin aborted this operation
449         if ($data['abort'])
450             return false;
451
452         $user_name  = $data['user_name'];
453         $user_email = $data['user_email'];
454
be98df 455         $dbh = $rcube->get_dbh();
a78901 456
A 457         $dbh->query(
0c2596 458             "INSERT INTO ".$dbh->table_name('users').
a78901 459             " (created, last_login, username, mail_host, alias, language)".
A 460             " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?, ?)",
461             strip_newlines($user),
462             strip_newlines($host),
463             strip_newlines($data['alias'] ? $data['alias'] : $user_email),
532c25 464             strip_newlines($data['language'] ? $data['language'] : $_SESSION['language']));
a78901 465
A 466         if ($user_id = $dbh->insert_id('users')) {
467             // create rcube_user instance to make plugin hooks work
468             $user_instance = new rcube_user($user_id);
be98df 469             $rcube->user   = $user_instance;
a78901 470
be98df 471             $mail_domain = $rcube->config->mail_domain($host);
a78901 472
A 473             if ($user_email == '') {
474                 $user_email = strpos($user, '@') ? $user : sprintf('%s@%s', $user, $mail_domain);
475             }
476             if ($user_name == '') {
477                 $user_name = $user != $user_email ? $user : '';
478             }
479
480             if (empty($email_list))
481                 $email_list[] = strip_newlines($user_email);
482             // identities_level check
be98df 483             else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1)
a78901 484                 $email_list = array($email_list[0]);
A 485
486             // create new identities records
487             $standard = 1;
488             foreach ($email_list as $row) {
622bce 489                 $record = array();
a78901 490
A 491                 if (is_array($row)) {
622bce 492                     $record = $row;
a78901 493                 }
A 494                 else {
495                     $record['email'] = $row;
496                 }
497
622bce 498                 if (empty($record['name']))
AM 499                     $record['name'] = $user_name;
a78901 500                 $record['name'] = strip_newlines($record['name']);
A 501                 $record['user_id'] = $user_id;
502                 $record['standard'] = $standard;
503
be98df 504                 $plugin = $rcube->plugins->exec_hook('identity_create',
622bce 505                     array('login' => true, 'record' => $record));
e99991 506
a78901 507                 if (!$plugin['abort'] && $plugin['record']['email']) {
be98df 508                     $rcube->user->insert_identity($plugin['record']);
a78901 509                 }
A 510                 $standard = 0;
511             }
08c8c3 512         }
T 513         else {
0c2596 514             rcube::raise_error(array(
a78901 515                 'code' => 500,
A 516                 'type' => 'php',
517                 'line' => __LINE__,
518                 'file' => __FILE__,
519                 'message' => "Failed to create new user"), true, false);
08c8c3 520         }
e99991 521
a78901 522         return $user_id ? $user_instance : false;
A 523     }
e99991 524
A 525
a78901 526     /**
A 527      * Resolve username using a virtuser plugins
528      *
5c461b 529      * @param string $email E-mail address to resolve
a78901 530      * @return string Resolved IMAP username
A 531      */
532     static function email2user($email)
533     {
be98df 534         $rcube = rcube::get_instance();
A 535         $plugin = $rcube->plugins->exec_hook('email2user',
a78901 536             array('email' => $email, 'user' => NULL));
fba1f5 537
a78901 538         return $plugin['user'];
A 539     }
fba1f5 540
T 541
a78901 542     /**
A 543      * Resolve e-mail address from virtuser plugins
544      *
5c461b 545      * @param string $user User name
A 546      * @param boolean $first If true returns first found entry
547      * @param boolean $extended If true returns email as array (email and name for identity)
a78901 548      * @return mixed Resolved e-mail address string or array of strings
A 549      */
550     static function user2email($user, $first=true, $extended=false)
551     {
be98df 552         $rcube = rcube::get_instance();
A 553         $plugin = $rcube->plugins->exec_hook('user2email',
a78901 554             array('email' => NULL, 'user' => $user,
A 555                 'first' => $first, 'extended' => $extended));
fba1f5 556
a78901 557         return empty($plugin['email']) ? NULL : $plugin['email'];
A 558     }
8dfe51 559
f8e48d 560
A 561     /**
562      * Return a list of saved searches linked with this user
563      *
564      * @param int  $type  Search type
565      *
566      * @return array List of saved searches indexed by search ID
567      */
568     function list_searches($type)
569     {
570         $plugin = $this->rc->plugins->exec_hook('saved_search_list', array('type' => $type));
571
572         if ($plugin['abort']) {
573             return (array) $plugin['result'];
574         }
575
576         $result = array();
577
578         $sql_result = $this->db->query(
579             "SELECT search_id AS id, ".$this->db->quoteIdentifier('name')
0c2596 580             ." FROM ".$this->db->table_name('searches')
f8e48d 581             ." WHERE user_id = ?"
A 582                 ." AND ".$this->db->quoteIdentifier('type')." = ?"
583             ." ORDER BY ".$this->db->quoteIdentifier('name'),
584             (int) $this->ID, (int) $type);
585
586         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
587             $sql_arr['data'] = unserialize($sql_arr['data']);
588             $result[$sql_arr['id']] = $sql_arr;
589         }
590
591         return $result;
592     }
593
594
595     /**
596      * Return saved search data.
597      *
598      * @param int  $id  Row identifier
599      *
600      * @return array Data
601      */
602     function get_search($id)
603     {
604         $plugin = $this->rc->plugins->exec_hook('saved_search_get', array('id' => $id));
605
606         if ($plugin['abort']) {
607             return $plugin['result'];
608         }
609
610         $sql_result = $this->db->query(
611             "SELECT ".$this->db->quoteIdentifier('name')
612                 .", ".$this->db->quoteIdentifier('data')
613                 .", ".$this->db->quoteIdentifier('type')
0c2596 614             ." FROM ".$this->db->table_name('searches')
f8e48d 615             ." WHERE user_id = ?"
A 616                 ." AND search_id = ?",
617             (int) $this->ID, (int) $id);
618
619         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
620             return array(
621                 'id'   => $id,
622                 'name' => $sql_arr['name'],
623                 'type' => $sql_arr['type'],
624                 'data' => unserialize($sql_arr['data']),
625             );
626         }
627
628         return null;
629     }
630
631
632     /**
633      * Deletes given saved search record
634      *
635      * @param  int  $sid  Search ID
636      *
637      * @return boolean True if deleted successfully, false if nothing changed
638      */
639     function delete_search($sid)
640     {
641         if (!$this->ID)
642             return false;
643
644         $this->db->query(
0c2596 645             "DELETE FROM ".$this->db->table_name('searches')
f8e48d 646             ." WHERE user_id = ?"
A 647                 ." AND search_id = ?",
648             (int) $this->ID, $sid);
649
650         return $this->db->affected_rows();
651     }
652
653
654     /**
655      * Create a new saved search record linked with this user
656      *
657      * @param array $data Hash array with col->value pairs to save
658      *
659      * @return int  The inserted search ID or false on error
660      */
661     function insert_search($data)
662     {
663         if (!$this->ID)
664             return false;
665
666         $insert_cols[]   = 'user_id';
667         $insert_values[] = (int) $this->ID;
668         $insert_cols[]   = $this->db->quoteIdentifier('type');
669         $insert_values[] = (int) $data['type'];
670         $insert_cols[]   = $this->db->quoteIdentifier('name');
671         $insert_values[] = $data['name'];
672         $insert_cols[]   = $this->db->quoteIdentifier('data');
673         $insert_values[] = serialize($data['data']);
674
0c2596 675         $sql = "INSERT INTO ".$this->db->table_name('searches')
f8e48d 676             ." (".join(', ', $insert_cols).")"
A 677             ." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
678
679         call_user_func_array(array($this->db, 'query'),
680             array_merge(array($sql), $insert_values));
681
682         return $this->db->insert_id('searches');
683     }
684
fba1f5 685 }