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