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