]> git.donarmstrong.com Git - roundcube.git/blob - program/include/rcube_user.php
19d08117d7e5ffa2f02de41cd77ce04936987af2
[roundcube.git] / program / include / rcube_user.php
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  | Licensed under the GNU GPL                                            |
10  |                                                                       |
11  | PURPOSE:                                                              |
12  |   This class represents a system user linked and provides access      |
13  |   to the related database records.                                    |
14  |                                                                       |
15  +-----------------------------------------------------------------------+
16  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17  +-----------------------------------------------------------------------+
18
19  $Id: rcube_user.php 5165 2011-09-05 08:49:04Z thomasb $
20
21 */
22
23
24 /**
25  * Class representing a system user
26  *
27  * @package    Core
28  * @author     Thomas Bruederli <roundcube@gmail.com>
29  */
30 class rcube_user
31 {
32     public $ID;
33     public $data;
34     public $language;
35
36     /**
37      * Holds database connection.
38      *
39      * @var rcube_mdb2
40      */
41     private $db;
42
43     /**
44      * rcmail object.
45      *
46      * @var rcmail
47      */
48     private $rc;
49
50
51     /**
52      * Object constructor
53      *
54      * @param int   $id      User id
55      * @param array $sql_arr SQL result set
56      */
57     function __construct($id = null, $sql_arr = null)
58     {
59         $this->rc = rcmail::get_instance();
60         $this->db = $this->rc->get_dbh();
61
62         if ($id && !$sql_arr) {
63             $sql_result = $this->db->query(
64                 "SELECT * FROM ".get_table_name('users')." WHERE user_id = ?", $id);
65             $sql_arr = $this->db->fetch_assoc($sql_result);
66         }
67
68         if (!empty($sql_arr)) {
69             $this->ID       = $sql_arr['user_id'];
70             $this->data     = $sql_arr;
71             $this->language = $sql_arr['language'];
72         }
73     }
74
75
76     /**
77      * Build a user name string (as e-mail address)
78      *
79      * @param  string $part Username part (empty or 'local' or 'domain')
80      * @return string Full user name or its part
81      */
82     function get_username($part = null)
83     {
84         if ($this->data['username']) {
85             list($local, $domain) = explode('@', $this->data['username']);
86
87             // at least we should always have the local part
88             if ($part == 'local') {
89                 return $local;
90             }
91             // if no domain was provided...
92             if (empty($domain)) {
93                 $domain = $this->rc->config->mail_domain($this->data['mail_host']);
94             }
95
96             if ($part == 'domain') {
97                 return $domain;
98             }
99
100             if (!empty($domain))
101                 return $local . '@' . $domain;
102             else
103                 return $local;
104         }
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);
119
120         if ($this->ID) {
121             // Preferences from session (write-master is unavailable)
122             if (!empty($_SESSION['preferences'])) {
123                 // Check last write attempt time, try to write again (every 5 minutes)
124                 if ($_SESSION['preferences_time'] < time() - 5 * 60) {
125                     $saved_prefs = unserialize($_SESSION['preferences']);
126                     $this->rc->session->remove('preferences');
127                     $this->rc->session->remove('preferences_time');
128                     $this->save_prefs($saved_prefs);
129                 }
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         }
139
140         return $prefs;
141     }
142
143
144     /**
145      * Write the given user prefs to the user's record
146      *
147      * @param array $a_user_prefs User prefs to save
148      * @return boolean True on success, False on failure
149      */
150     function save_prefs($a_user_prefs)
151     {
152         if (!$this->ID)
153             return false;
154
155         $config    = $this->rc->config;
156         $old_prefs = (array)$this->get_prefs();
157
158         // merge (partial) prefs array with existing settings
159         $save_prefs = $a_user_prefs + $old_prefs;
160         unset($save_prefs['language']);
161
162         // don't save prefs with default values if they haven't been changed yet
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
181         // Update success
182         if ($this->db->affected_rows() !== false) {
183             $config->set_user_prefs($a_user_prefs);
184             $this->data['preferences'] = $save_prefs;
185
186             if (isset($_SESSION['preferences'])) {
187                 $this->rc->session->remove('preferences');
188                 $this->rc->session->remove('preferences_time');
189             }
190             return true;
191         }
192         // Update error, but we are using replication (we have read-only DB connection)
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         }
201
202         return false;
203     }
204
205
206     /**
207      * Get default identity of this user
208      *
209      * @param  int   $id Identity ID. If empty, the default identity is returned
210      * @return array Hash array with all cols of the identity record
211      */
212     function get_identity($id = null)
213     {
214         $result = $this->list_identities($id ? sprintf('AND identity_id = %d', $id) : '');
215         return $result[0];
216     }
217
218
219     /**
220      * Return a list of all identities linked with this user
221      *
222      * @param string $sql_add Optional WHERE clauses
223      * @return array List of identities
224      */
225     function list_identities($sql_add = '')
226     {
227         $result = array();
228
229         $sql_result = $this->db->query(
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);
235
236         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
237             $result[] = $sql_arr;
238         }
239
240         return $result;
241     }
242
243
244     /**
245      * Update a specific identity record
246      *
247      * @param int    $iid  Identity ID
248      * @param array  $data Hash array with col->value pairs to save
249      * @return boolean True if saved successfully, false if nothing changed
250      */
251     function update_identity($iid, $data)
252     {
253         if (!$this->ID)
254             return false;
255
256         $query_cols = $query_params = array();
257
258         foreach ((array)$data as $col => $value) {
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));
273
274         return $this->db->affected_rows();
275     }
276
277
278     /**
279      * Create a new identity record linked with this user
280      *
281      * @param array $data Hash array with col->value pairs to save
282      * @return int  The inserted identity ID or false on error
283      */
284     function insert_identity($data)
285     {
286         if (!$this->ID)
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');
307     }
308
309
310     /**
311      * Mark the given identity as deleted
312      *
313      * @param  int     $iid Identity ID
314      * @return boolean True if deleted successfully, false if nothing changed
315      */
316     function delete_identity($iid)
317     {
318         if (!$this->ID)
319             return false;
320
321         $sql_result = $this->db->query(
322             "SELECT count(*) AS ident_count FROM ".get_table_name('identities').
323             " WHERE user_id = ? AND del <> 1",
324             $this->ID);
325
326         $sql_arr = $this->db->fetch_assoc($sql_result);
327
328         // we'll not delete last identity
329         if ($sql_arr['ident_count'] <= 1)
330             return -1;
331
332         $this->db->query(
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);
339
340         return $this->db->affected_rows();
341     }
342
343
344     /**
345      * Make this identity the default one for this user
346      *
347      * @param int $iid The identity ID
348      */
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     }
362
363
364     /**
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     }
377
378
379     /**
380      * Clear the saved object state
381      */
382     function reset()
383     {
384         $this->ID = null;
385         $this->data = null;
386     }
387
388
389     /**
390      * Find a user record matching the given name and host
391      *
392      * @param string $user IMAP user name
393      * @param string $host IMAP host name
394      * @return rcube_user New user instance
395      */
396     static function query($user, $host)
397     {
398         $dbh = rcmail::get_instance()->get_dbh();
399
400         // use BINARY (case-sensitive) comparison on MySQL, other engines are case-sensitive
401         $mod = preg_match('/^mysql/', $dbh->db_provider) ? 'BINARY' : '';
402
403         // query for matching user name
404         $query = "SELECT * FROM ".get_table_name('users')." WHERE mail_host = ? AND %s = $mod ?";
405         $sql_result = $dbh->query(sprintf($query, 'username'), $host, $user);
406
407         // query for matching alias
408         if (!($sql_arr = $dbh->fetch_assoc($sql_result))) {
409             $sql_result = $dbh->query(sprintf($query, 'alias'), $host, $user);
410             $sql_arr = $dbh->fetch_assoc($sql_result);
411         }
412
413         // user already registered -> overwrite username
414         if ($sql_arr)
415             return new rcube_user($sql_arr['user_id'], $sql_arr);
416         else
417             return false;
418     }
419
420
421     /**
422      * Create a new user record and return a rcube_user instance
423      *
424      * @param string $user IMAP user name
425      * @param string $host IMAP host
426      * @return rcube_user New user instance
427      */
428     static function create($user, $host)
429     {
430         $user_name  = '';
431         $user_email = '';
432         $rcmail = rcmail::get_instance();
433
434         // try to resolve user in virtuser table and file
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         }
438
439         $data = $rcmail->plugins->exec_hook('user_create',
440                 array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
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),
458             strip_newlines($data['language'] ? $data['language'] : $_SESSION['language']));
459
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
498                 $plugin = $rcmail->plugins->exec_hook('identity_create',
499                         array('login' => true, 'record' => $record));
500
501                 if (!$plugin['abort'] && $plugin['record']['email']) {
502                     $rcmail->user->insert_identity($plugin['record']);
503                 }
504                 $standard = 0;
505             }
506         }
507         else {
508             raise_error(array(
509                 'code' => 500,
510                 'type' => 'php',
511                 'line' => __LINE__,
512                 'file' => __FILE__,
513                 'message' => "Failed to create new user"), true, false);
514         }
515
516         return $user_id ? $user_instance : false;
517     }
518
519
520     /**
521      * Resolve username using a virtuser plugins
522      *
523      * @param string $email E-mail address to resolve
524      * @return string Resolved IMAP username
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));
531
532         return $plugin['user'];
533     }
534
535
536     /**
537      * Resolve e-mail address from virtuser plugins
538      *
539      * @param string $user User name
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)
542      * @return mixed Resolved e-mail address string or array of strings
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));
550
551         return empty($plugin['email']) ? NULL : $plugin['email'];
552     }
553
554 }