Aleksander Machniak
2016-05-16 0b7e26c1bf6bc7a684eb3a214d92d3927306cd8a
commit | author | age
fba1f5 1 <?php
T 2
a95874 3 /**
fba1f5 4  +-----------------------------------------------------------------------+
e019f2 5  | This file is part of the Roundcube Webmail client                     |
9ab346 6  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
7fe381 7  |                                                                       |
T 8  | Licensed under the GNU General Public License version 3 or            |
9  | any later version with exceptions for skins & plugins.                |
10  | See the README file for a full license statement.                     |
fba1f5 11  |                                                                       |
T 12  | PURPOSE:                                                              |
13  |   This class represents a system user linked and provides access      |
14  |   to the related database records.                                    |
15  +-----------------------------------------------------------------------+
16  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
9ab346 17  | Author: Aleksander Machniak <alec@alec.pl>                            |
fba1f5 18  +-----------------------------------------------------------------------+
T 19 */
20
21 /**
22  * Class representing a system user
23  *
9ab346 24  * @package    Framework
AM 25  * @subpackage Core
fba1f5 26  */
T 27 class rcube_user
28 {
40a186 29     public $ID;
A 30     public $data;
31     public $language;
a74d02 32     public $prefs;
a78901 33
5c461b 34     /**
A 35      * Holds database connection.
36      *
0d94fd 37      * @var rcube_db
5c461b 38      */
40a186 39     private $db;
A 40
41     /**
be98df 42      * Framework object.
40a186 43      *
be98df 44      * @var rcube
40a186 45      */
A 46     private $rc;
fba1f5 47
f410c9 48     /**
AM 49      * Internal identities cache
50      *
51      * @var array
52      */
53     private $identities = array();
54
2f4678 55     /**
AM 56      * Internal emails cache
57      *
58      * @var array
59      */
60     private $emails;
61
62
f8e48d 63     const SEARCH_ADDRESSBOOK = 1;
A 64     const SEARCH_MAIL = 2;
95987c 65
a78901 66     /**
A 67      * Object constructor
68      *
5c461b 69      * @param int   $id      User id
A 70      * @param array $sql_arr SQL result set
a78901 71      */
A 72     function __construct($id = null, $sql_arr = null)
73     {
be98df 74         $this->rc = rcube::get_instance();
40a186 75         $this->db = $this->rc->get_dbh();
e99991 76
a78901 77         if ($id && !$sql_arr) {
A 78             $sql_result = $this->db->query(
34a090 79                 "SELECT * FROM " . $this->db->table_name('users', true)
AM 80                 . " WHERE `user_id` = ?", $id);
a78901 81             $sql_arr = $this->db->fetch_assoc($sql_result);
A 82         }
83
84         if (!empty($sql_arr)) {
85             $this->ID       = $sql_arr['user_id'];
86             $this->data     = $sql_arr;
87             $this->language = $sql_arr['language'];
88         }
89     }
90
91     /**
92      * Build a user name string (as e-mail address)
93      *
789e59 94      * @param  string $part Username part (empty or 'local' or 'domain', 'mail')
8dfe51 95      * @return string Full user name or its part
a78901 96      */
8dfe51 97     function get_username($part = null)
a78901 98     {
A 99         if ($this->data['username']) {
789e59 100             // return real name
AM 101             if (!$part) {
102                 return $this->data['username'];
103             }
104
8dfe51 105             list($local, $domain) = explode('@', $this->data['username']);
A 106
107             // at least we should always have the local part
108             if ($part == 'local') {
109                 return $local;
110             }
b0eeaa 111             // if no domain was provided...
A 112             if (empty($domain)) {
40a186 113                 $domain = $this->rc->config->mail_domain($this->data['mail_host']);
b0eeaa 114             }
8dfe51 115
A 116             if ($part == 'domain') {
117                 return $domain;
118             }
119
120             if (!empty($domain))
121                 return $local . '@' . $domain;
a78901 122             else
8dfe51 123                 return $local;
a78901 124         }
A 125
126         return false;
127     }
128
129     /**
130      * Get the preferences saved for this user
131      *
132      * @return array Hash array with prefs
133      */
134     function get_prefs()
135     {
a74d02 136         if (isset($this->prefs)) {
TB 137             return $this->prefs;
138         }
139
140         $this->prefs = array();
e59471 141
a78901 142         if (!empty($this->language))
a74d02 143             $this->prefs['language'] = $this->language;
8dfe51 144
40a186 145         if ($this->ID) {
A 146             // Preferences from session (write-master is unavailable)
147             if (!empty($_SESSION['preferences'])) {
148                 // Check last write attempt time, try to write again (every 5 minutes)
149                 if ($_SESSION['preferences_time'] < time() - 5 * 60) {
0c2596 150                     $saved_prefs = unserialize($_SESSION['preferences']);
59ab0c 151                     $this->rc->session->remove('preferences');
0c2596 152                     $this->rc->session->remove('preferences_time');
59ab0c 153                     $this->save_prefs($saved_prefs);
40a186 154                 }
A 155                 else {
156                     $this->data['preferences'] = $_SESSION['preferences'];
157                 }
158             }
159
160             if ($this->data['preferences']) {
a74d02 161                 $this->prefs += (array)unserialize($this->data['preferences']);
40a186 162             }
A 163         }
8dfe51 164
a74d02 165         return $this->prefs;
a78901 166     }
8dfe51 167
a78901 168     /**
A 169      * Write the given user prefs to the user's record
170      *
5c461b 171      * @param array $a_user_prefs User prefs to save
0c08b0 172      * @param bool  $no_session   Simplified language/preferences handling
AM 173      *
a78901 174      * @return boolean True on success, False on failure
A 175      */
0c08b0 176     function save_prefs($a_user_prefs, $no_session = false)
a78901 177     {
A 178         if (!$this->ID)
179             return false;
e99991 180
4f432f 181         $plugin = $this->rc->plugins->exec_hook('preferences_update', array(
TB 182             'userid' => $this->ID, 'prefs' => $a_user_prefs, 'old' => (array)$this->get_prefs()));
183
184         if (!empty($plugin['abort'])) {
185             return;
186         }
187
188         $a_user_prefs = $plugin['prefs'];
189         $old_prefs    = $plugin['old'];
190         $config       = $this->rc->config;
fba1f5 191
a78901 192         // merge (partial) prefs array with existing settings
a74d02 193         $this->prefs = $save_prefs = $a_user_prefs + $old_prefs;
a78901 194         unset($save_prefs['language']);
e99991 195
a78901 196         // don't save prefs with default values if they haven't been changed yet
A 197         foreach ($a_user_prefs as $key => $value) {
26a00e 198             if ($value === null || (!isset($old_prefs[$key]) && ($value == $config->get($key)))) {
a78901 199                 unset($save_prefs[$key]);
26a00e 200             }
a78901 201         }
A 202
203         $save_prefs = serialize($save_prefs);
0c08b0 204         if (!$no_session) {
AM 205             $this->language = $_SESSION['language'];
206         }
a78901 207
A 208         $this->db->query(
34a090 209             "UPDATE ".$this->db->table_name('users', true).
AM 210             " SET `preferences` = ?, `language` = ?".
211             " WHERE `user_id` = ?",
a78901 212             $save_prefs,
0c08b0 213             $this->language,
a78901 214             $this->ID);
A 215
40a186 216         // Update success
80809d 217         if ($this->db->affected_rows() !== false) {
a78901 218             $this->data['preferences'] = $save_prefs;
40a186 219
0c08b0 220             if (!$no_session) {
26a00e 221                 $config->set_user_prefs($this->prefs);
0c08b0 222
AM 223                 if (isset($_SESSION['preferences'])) {
224                     $this->rc->session->remove('preferences');
225                     $this->rc->session->remove('preferences_time');
226                 }
40a186 227             }
0c08b0 228
a78901 229             return true;
A 230         }
40a186 231         // Update error, but we are using replication (we have read-only DB connection)
A 232         // and we are storing session not in the SQL database
233         // we can store preferences in session and try to write later (see get_prefs())
0c08b0 234         else if (!$no_session && $this->db->is_replicated()
AM 235             && $config->get('session_storage', 'db') != 'db'
236         ) {
40a186 237             $_SESSION['preferences'] = $save_prefs;
A 238             $_SESSION['preferences_time'] = time();
26a00e 239             $config->set_user_prefs($this->prefs);
40a186 240             $this->data['preferences'] = $save_prefs;
A 241         }
a78901 242
A 243         return false;
286420 244     }
f52c93 245
85e60a 246     /**
a74d02 247      * Generate a unique hash to identify this user whith
85e60a 248      */
TB 249     function get_hash()
250     {
a74d02 251         $prefs = $this->get_prefs();
TB 252
253         // generate a random hash and store it in user prefs
254         if (empty($prefs['client_hash'])) {
255             $prefs['client_hash'] = md5($this->data['username'] . mt_rand() . $this->data['mail_host']);
256             $this->save_prefs(array('client_hash' => $prefs['client_hash']));
257         }
258
259         return $prefs['client_hash'];
85e60a 260     }
f52c93 261
a78901 262     /**
2f4678 263      * Return a list of all user emails (from identities)
AM 264      *
692011 265      * @param bool Return only default identity
AM 266      *
2f4678 267      * @return array List of emails (identity_id, name, email)
AM 268      */
692011 269     function list_emails($default = false)
2f4678 270     {
AM 271         if ($this->emails === null) {
272             $this->emails = array();
273
274             $sql_result = $this->db->query(
275                 "SELECT `identity_id`, `name`, `email`"
276                 ." FROM " . $this->db->table_name('identities', true)
277                 ." WHERE `user_id` = ? AND `del` <> 1"
278                 ." ORDER BY `standard` DESC, `name` ASC, `email` ASC, `identity_id` ASC",
279                 $this->ID);
280
281             while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
282                 $this->emails[] = $sql_arr;
283             }
284         }
285
692011 286         return $default ? $this->emails[0] : $this->emails;
2f4678 287     }
AM 288
289     /**
a78901 290      * Get default identity of this user
A 291      *
5c461b 292      * @param  int   $id Identity ID. If empty, the default identity is returned
a78901 293      * @return array Hash array with all cols of the identity record
A 294      */
295     function get_identity($id = null)
fba1f5 296     {
f410c9 297         $id = (int)$id;
AM 298         // cache identities for better performance
299         if (!array_key_exists($id, $this->identities)) {
34a090 300             $result = $this->list_identities($id ? "AND `identity_id` = $id" : '');
f410c9 301             $this->identities[$id] = $result[0];
AM 302         }
303
304         return $this->identities[$id];
fba1f5 305     }
55f54e 306
a78901 307     /**
A 308      * Return a list of all identities linked with this user
309      *
0247b8 310      * @param string $sql_add   Optional WHERE clauses
AM 311      * @param bool   $formatted Format identity email and name
312      *
a78901 313      * @return array List of identities
A 314      */
0247b8 315     function list_identities($sql_add = '', $formatted = false)
fba1f5 316     {
a78901 317         $result = array();
fba1f5 318
a78901 319         $sql_result = $this->db->query(
34a090 320             "SELECT * FROM ".$this->db->table_name('identities', true).
AM 321             " WHERE `del` <> 1 AND `user_id` = ?".
a78901 322             ($sql_add ? " ".$sql_add : "").
34a090 323             " ORDER BY `standard` DESC, `name` ASC, `email` ASC, `identity_id` ASC",
a78901 324             $this->ID);
e99991 325
a78901 326         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
0247b8 327             if ($formatted) {
AM 328                 $ascii_email = format_email($sql_arr['email']);
329                 $utf8_email  = format_email(rcube_utils::idn_to_utf8($ascii_email));
330
331                 $sql_arr['email_ascii'] = $ascii_email;
332                 $sql_arr['email']       = $utf8_email;
7c5d4b 333                 $sql_arr['ident']       = format_email_recipient($ascii_email, $sql_arr['name']);
0247b8 334             }
AM 335
a78901 336             $result[] = $sql_arr;
A 337         }
e99991 338
a78901 339         return $result;
A 340     }
fba1f5 341
a78901 342     /**
A 343      * Update a specific identity record
344      *
5c461b 345      * @param int    $iid  Identity ID
A 346      * @param array  $data Hash array with col->value pairs to save
a78901 347      * @return boolean True if saved successfully, false if nothing changed
A 348      */
349     function update_identity($iid, $data)
fba1f5 350     {
a78901 351         if (!$this->ID)
A 352             return false;
353
354         $query_cols = $query_params = array();
e99991 355
a78901 356         foreach ((array)$data as $col => $value) {
51fe04 357             $query_cols[]   = $this->db->quote_identifier($col) . ' = ?';
a78901 358             $query_params[] = $value;
A 359         }
360         $query_params[] = $iid;
361         $query_params[] = $this->ID;
362
34a090 363         $sql = "UPDATE ".$this->db->table_name('identities', true).
AM 364             " SET `changed` = ".$this->db->now().", ".join(', ', $query_cols).
365             " WHERE `identity_id` = ?".
366                 " AND `user_id` = ?".
367                 " AND `del` <> 1";
a78901 368
A 369         call_user_func_array(array($this->db, 'query'),
370             array_merge(array($sql), $query_params));
e99991 371
2f4678 372         // clear the cache
f410c9 373         $this->identities = array();
2f4678 374         $this->emails     = null;
f410c9 375
a78901 376         return $this->db->affected_rows();
fba1f5 377     }
e99991 378
a78901 379     /**
A 380      * Create a new identity record linked with this user
381      *
5c461b 382      * @param array $data Hash array with col->value pairs to save
a78901 383      * @return int  The inserted identity ID or false on error
A 384      */
385     function insert_identity($data)
fba1f5 386     {
a78901 387         if (!$this->ID)
A 388             return false;
389
390         unset($data['user_id']);
391
392         $insert_cols = $insert_values = array();
393         foreach ((array)$data as $col => $value) {
51fe04 394             $insert_cols[]   = $this->db->quote_identifier($col);
a78901 395             $insert_values[] = $value;
A 396         }
83a642 397         $insert_cols[]   = $this->db->quote_identifier('user_id');
a78901 398         $insert_values[] = $this->ID;
A 399
34a090 400         $sql = "INSERT INTO ".$this->db->table_name('identities', true).
AM 401             " (`changed`, ".join(', ', $insert_cols).")".
a78901 402             " VALUES (".$this->db->now().", ".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
A 403
404         call_user_func_array(array($this->db, 'query'),
405             array_merge(array($sql), $insert_values));
f410c9 406
2f4678 407         // clear the cache
f410c9 408         $this->identities = array();
2f4678 409         $this->emails     = null;
a78901 410
A 411         return $this->db->insert_id('identities');
fba1f5 412     }
e99991 413
a78901 414     /**
A 415      * Mark the given identity as deleted
416      *
5c461b 417      * @param  int     $iid Identity ID
a78901 418      * @return boolean True if deleted successfully, false if nothing changed
A 419      */
420     function delete_identity($iid)
fba1f5 421     {
a78901 422         if (!$this->ID)
A 423             return false;
07722a 424
a78901 425         $sql_result = $this->db->query(
34a090 426             "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities', true).
AM 427             " WHERE `user_id` = ? AND `del` <> 1",
a78901 428             $this->ID);
fba1f5 429
a78901 430         $sql_arr = $this->db->fetch_assoc($sql_result);
fba1f5 431
a78901 432         // we'll not delete last identity
A 433         if ($sql_arr['ident_count'] <= 1)
bbb142 434             return -1;
e99991 435
a78901 436         $this->db->query(
34a090 437             "UPDATE ".$this->db->table_name('identities', true).
AM 438             " SET `del` = 1, `changed` = ".$this->db->now().
439             " WHERE `user_id` = ?".
440                 " AND `identity_id` = ?",
a78901 441             $this->ID,
A 442             $iid);
fba1f5 443
2f4678 444         // clear the cache
f410c9 445         $this->identities = array();
2f4678 446         $this->emails     = null;
f410c9 447
a78901 448         return $this->db->affected_rows();
A 449     }
e99991 450
a78901 451     /**
A 452      * Make this identity the default one for this user
453      *
5c461b 454      * @param int $iid The identity ID
a78901 455      */
A 456     function set_default($iid)
457     {
458         if ($this->ID && $iid) {
459             $this->db->query(
34a090 460                 "UPDATE ".$this->db->table_name('identities', true).
AM 461                 " SET `standard` = '0'".
462                 " WHERE `user_id` = ? AND `identity_id` <> ?",
a78901 463                 $this->ID,
A 464                 $iid);
f410c9 465
AM 466             unset($this->identities[0]);
a78901 467         }
A 468     }
e99991 469
a78901 470     /**
A 471      * Update user's last_login timestamp
472      */
473     function touch()
474     {
475         if ($this->ID) {
476             $this->db->query(
34a090 477                 "UPDATE ".$this->db->table_name('users', true).
AM 478                 " SET `last_login` = ".$this->db->now().
479                 " WHERE `user_id` = ?",
a78901 480                 $this->ID);
A 481         }
482     }
e99991 483
a78901 484     /**
a15d87 485      * Update user's failed_login timestamp and counter
AM 486      */
487     function failed_login()
488     {
489         if ($this->ID && ($rate = (int) $this->rc->config->get('login_rate_limit', 3))) {
490             if (empty($this->data['failed_login'])) {
491                 $failed_login = new DateTime('now');
492                 $counter      = 1;
493             }
494             else {
495                 $failed_login = new DateTime($this->data['failed_login']);
496                 $threshold    = new DateTime('- 60 seconds');
497
498                 if ($failed_login < $threshold) {
499                     $failed_login = new DateTime('now');
500                     $counter      = 1;
501                 }
502             }
503
504             $this->db->query(
505                 "UPDATE " . $this->db->table_name('users', true)
506                     . " SET `failed_login` = " . $this->db->fromunixtime($failed_login->format('U'))
507                     . ", `failed_login_counter` = " . ($counter ?: "`failed_login_counter` + 1")
508                 . " WHERE `user_id` = ?",
509                 $this->ID);
510         }
511     }
512
513     /**
514      * Checks if the account is locked, e.g. as a result of brute-force prevention
515      */
516     function is_locked()
517     {
518         if (empty($this->data['failed_login'])) {
519             return false;
520         }
521
522         if ($rate = (int) $this->rc->config->get('login_rate_limit', 3)) {
523             $last_failed = new DateTime($this->data['failed_login']);
524             $threshold   = new DateTime('- 60 seconds');
525
526             if ($last_failed > $threshold && $this->data['failed_login_counter'] >= $rate) {
527                 return true;
528             }
529         }
530
531         return false;
532     }
533
534     /**
a78901 535      * Clear the saved object state
A 536      */
537     function reset()
538     {
a15d87 539         $this->ID   = null;
a78901 540         $this->data = null;
A 541     }
e99991 542
a78901 543     /**
A 544      * Find a user record matching the given name and host
545      *
5c461b 546      * @param string $user IMAP user name
A 547      * @param string $host IMAP host name
548      * @return rcube_user New user instance
a78901 549      */
A 550     static function query($user, $host)
551     {
565c47 552         $dbh    = rcube::get_instance()->get_dbh();
AM 553         $config = rcube::get_instance()->config;
e99991 554
a78901 555         // query for matching user name
34a090 556         $sql_result = $dbh->query("SELECT * FROM " . $dbh->table_name('users', true)
AM 557             ." WHERE `mail_host` = ? AND `username` = ?", $host, $user);
e99991 558
565c47 559         $sql_arr = $dbh->fetch_assoc($sql_result);
AM 560
561         // username not found, try aliases from identities
562         if (empty($sql_arr) && $config->get('user_aliases') && strpos($user, '@')) {
563             $sql_result = $dbh->limitquery("SELECT u.*"
34a090 564                 ." FROM " . $dbh->table_name('users', true) . " u"
AM 565                 ." JOIN " . $dbh->table_name('identities', true) . " i ON (i.`user_id` = u.`user_id`)"
566                 ." WHERE `email` = ? AND `del` <> 1", 0, 1, $user);
565c47 567
a78901 568             $sql_arr = $dbh->fetch_assoc($sql_result);
A 569         }
e99991 570
a78901 571         // user already registered -> overwrite username
a15d87 572         if ($sql_arr) {
a78901 573             return new rcube_user($sql_arr['user_id'], $sql_arr);
a15d87 574         }
AM 575
576         return false;
a78901 577     }
e99991 578
a78901 579     /**
A 580      * Create a new user record and return a rcube_user instance
581      *
5c461b 582      * @param string $user IMAP user name
A 583      * @param string $host IMAP host
584      * @return rcube_user New user instance
a78901 585      */
A 586     static function create($user, $host)
587     {
588         $user_name  = '';
589         $user_email = '';
f708c8 590         $rcube      = rcube::get_instance();
AM 591         $dbh        = $rcube->get_dbh();
ac9927 592
a78901 593         // try to resolve user in virtuser table and file
A 594         if ($email_list = self::user2email($user, false, true)) {
595             $user_email = is_array($email_list[0]) ? $email_list[0]['email'] : $email_list[0];
596         }
f20971 597
f708c8 598         $data = $rcube->plugins->exec_hook('user_create', array(
AM 599             'host'       => $host,
600             'user'       => $user,
601             'user_name'  => $user_name,
602             'user_email' => $user_email,
603             'email_list' => $email_list,
540de5 604             'language'   =>  $_SESSION['language'],
f708c8 605         ));
a78901 606
A 607         // plugin aborted this operation
f708c8 608         if ($data['abort']) {
a78901 609             return false;
f708c8 610         }
a78901 611
A 612         $dbh->query(
34a090 613             "INSERT INTO ".$dbh->table_name('users', true).
AM 614             " (`created`, `last_login`, `username`, `mail_host`, `language`)".
ee2187 615             " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)",
1d67fe 616             $data['user'],
AM 617             $data['host'],
618             $data['language']);
a78901 619
A 620         if ($user_id = $dbh->insert_id('users')) {
621             // create rcube_user instance to make plugin hooks work
540de5 622             $user_instance = new rcube_user($user_id, array(
AM 623                 'user_id'   => $user_id,
624                 'username'  => $data['user'],
625                 'mail_host' => $data['host'],
626                 'language'  => $data['language'],
627             ));
628             $rcube->user = $user_instance;
f708c8 629             $mail_domain = $rcube->config->mail_domain($data['host']);
AM 630             $user_name   = $data['user_name'];
631             $user_email  = $data['user_email'];
632             $email_list  = $data['email_list'];
a78901 633
f708c8 634             if (empty($email_list)) {
AM 635                 if (empty($user_email)) {
636                     $user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain);
637                 }
1d67fe 638                 $email_list[] = $user_email;
f708c8 639             }
a78901 640             // identities_level check
f708c8 641             else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) {
a78901 642                 $email_list = array($email_list[0]);
f708c8 643             }
AM 644
645             if (empty($user_name)) {
646                 $user_name = $data['user'];
647             }
a78901 648
A 649             // create new identities records
650             $standard = 1;
651             foreach ($email_list as $row) {
622bce 652                 $record = array();
a78901 653
A 654                 if (is_array($row)) {
f708c8 655                     if (empty($row['email'])) {
AM 656                         continue;
657                     }
622bce 658                     $record = $row;
a78901 659                 }
A 660                 else {
661                     $record['email'] = $row;
662                 }
663
f708c8 664                 if (empty($record['name'])) {
AM 665                     $record['name'] = $user_name != $record['email'] ? $user_name : '';
666                 }
667
668                 $record['user_id']  = $user_id;
a78901 669                 $record['standard'] = $standard;
A 670
be98df 671                 $plugin = $rcube->plugins->exec_hook('identity_create',
622bce 672                     array('login' => true, 'record' => $record));
e99991 673
a78901 674                 if (!$plugin['abort'] && $plugin['record']['email']) {
be98df 675                     $rcube->user->insert_identity($plugin['record']);
a78901 676                 }
A 677                 $standard = 0;
678             }
08c8c3 679         }
T 680         else {
0c2596 681             rcube::raise_error(array(
a78901 682                 'code' => 500,
A 683                 'type' => 'php',
684                 'line' => __LINE__,
685                 'file' => __FILE__,
686                 'message' => "Failed to create new user"), true, false);
08c8c3 687         }
e99991 688
a78901 689         return $user_id ? $user_instance : false;
A 690     }
e99991 691
a78901 692     /**
A 693      * Resolve username using a virtuser plugins
694      *
5c461b 695      * @param string $email E-mail address to resolve
a78901 696      * @return string Resolved IMAP username
A 697      */
698     static function email2user($email)
699     {
be98df 700         $rcube = rcube::get_instance();
A 701         $plugin = $rcube->plugins->exec_hook('email2user',
a78901 702             array('email' => $email, 'user' => NULL));
fba1f5 703
a78901 704         return $plugin['user'];
A 705     }
fba1f5 706
a78901 707     /**
A 708      * Resolve e-mail address from virtuser plugins
709      *
5c461b 710      * @param string $user User name
A 711      * @param boolean $first If true returns first found entry
712      * @param boolean $extended If true returns email as array (email and name for identity)
a78901 713      * @return mixed Resolved e-mail address string or array of strings
A 714      */
715     static function user2email($user, $first=true, $extended=false)
716     {
be98df 717         $rcube = rcube::get_instance();
A 718         $plugin = $rcube->plugins->exec_hook('user2email',
a78901 719             array('email' => NULL, 'user' => $user,
A 720                 'first' => $first, 'extended' => $extended));
fba1f5 721
a78901 722         return empty($plugin['email']) ? NULL : $plugin['email'];
A 723     }
f8e48d 724
A 725     /**
726      * Return a list of saved searches linked with this user
727      *
728      * @param int  $type  Search type
729      *
730      * @return array List of saved searches indexed by search ID
731      */
732     function list_searches($type)
733     {
734         $plugin = $this->rc->plugins->exec_hook('saved_search_list', array('type' => $type));
735
736         if ($plugin['abort']) {
737             return (array) $plugin['result'];
738         }
739
740         $result = array();
741
742         $sql_result = $this->db->query(
34a090 743             "SELECT `search_id` AS id, `name`"
AM 744             ." FROM ".$this->db->table_name('searches', true)
745             ." WHERE `user_id` = ? AND `type` = ?"
746             ." ORDER BY `name`",
f8e48d 747             (int) $this->ID, (int) $type);
A 748
749         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
750             $sql_arr['data'] = unserialize($sql_arr['data']);
751             $result[$sql_arr['id']] = $sql_arr;
752         }
753
754         return $result;
755     }
756
757     /**
758      * Return saved search data.
759      *
760      * @param int  $id  Row identifier
761      *
762      * @return array Data
763      */
764     function get_search($id)
765     {
766         $plugin = $this->rc->plugins->exec_hook('saved_search_get', array('id' => $id));
767
768         if ($plugin['abort']) {
769             return $plugin['result'];
770         }
771
772         $sql_result = $this->db->query(
34a090 773             "SELECT `name`, `data`, `type`"
AM 774             . " FROM ".$this->db->table_name('searches', true)
775             . " WHERE `user_id` = ?"
776                 ." AND `search_id` = ?",
f8e48d 777             (int) $this->ID, (int) $id);
A 778
779         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
780             return array(
781                 'id'   => $id,
782                 'name' => $sql_arr['name'],
783                 'type' => $sql_arr['type'],
784                 'data' => unserialize($sql_arr['data']),
785             );
786         }
787
788         return null;
789     }
790
791     /**
792      * Deletes given saved search record
793      *
794      * @param  int  $sid  Search ID
795      *
796      * @return boolean True if deleted successfully, false if nothing changed
797      */
798     function delete_search($sid)
799     {
800         if (!$this->ID)
801             return false;
802
803         $this->db->query(
34a090 804             "DELETE FROM ".$this->db->table_name('searches', true)
AM 805             ." WHERE `user_id` = ?"
806                 ." AND `search_id` = ?",
f8e48d 807             (int) $this->ID, $sid);
A 808
809         return $this->db->affected_rows();
810     }
811
812     /**
813      * Create a new saved search record linked with this user
814      *
815      * @param array $data Hash array with col->value pairs to save
816      *
817      * @return int  The inserted search ID or false on error
818      */
819     function insert_search($data)
820     {
821         if (!$this->ID)
822             return false;
823
824         $insert_cols[]   = 'user_id';
825         $insert_values[] = (int) $this->ID;
51fe04 826         $insert_cols[]   = $this->db->quote_identifier('type');
f8e48d 827         $insert_values[] = (int) $data['type'];
51fe04 828         $insert_cols[]   = $this->db->quote_identifier('name');
f8e48d 829         $insert_values[] = $data['name'];
51fe04 830         $insert_cols[]   = $this->db->quote_identifier('data');
f8e48d 831         $insert_values[] = serialize($data['data']);
A 832
34a090 833         $sql = "INSERT INTO ".$this->db->table_name('searches', true)
f8e48d 834             ." (".join(', ', $insert_cols).")"
A 835             ." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
836
837         call_user_func_array(array($this->db, 'query'),
838             array_merge(array($sql), $insert_values));
839
840         return $this->db->insert_id('searches');
841     }
fba1f5 842 }