Thomas Bruederli
2013-02-05 ec2185a1cb2a0d412ab9e916618972bc4c70f68c
commit | author | age
197601 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcmail.php                                            |
6  |                                                                       |
e019f2 7  | This file is part of the Roundcube Webmail client                     |
0c2596 8  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
A 9  | Copyright (C) 2011-2012, Kolab Systems AG                             |
7fe381 10  |                                                                       |
T 11  | Licensed under the GNU General Public License version 3 or            |
12  | any later version with exceptions for skins & plugins.                |
13  | See the README file for a full license statement.                     |
197601 14  |                                                                       |
T 15  | PURPOSE:                                                              |
16  |   Application class providing core functions and holding              |
17  |   instances of all 'global' objects like db- and imap-connections     |
18  +-----------------------------------------------------------------------+
19  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
1aceb9 20  | Author: Aleksander Machniak <alec@alec.pl>                            |
197601 21  +-----------------------------------------------------------------------+
T 22 */
23
24
25 /**
e019f2 26  * Application class of Roundcube Webmail
197601 27  * implemented as singleton
T 28  *
29  * @package Core
30  */
0c2596 31 class rcmail extends rcube
197601 32 {
5c461b 33   /**
A 34    * Main tasks.
35    *
36    * @var array
37    */
677e1f 38   static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
5c461b 39
A 40   /**
41    * Current task.
42    *
43    * @var string
44    */
9b94eb 45   public $task;
5c461b 46
A 47   /**
48    * Current action.
49    *
50    * @var string
51    */
197601 52   public $action = '';
T 53   public $comm_path = './';
677e1f 54
0501b6 55   private $address_books = array();
68d2d5 56   private $action_map = array();
677e1f 57
A 58
7c8fd8 59   const ERROR_STORAGE          = -2;
AM 60   const ERROR_INVALID_REQUEST  = 1;
61   const ERROR_INVALID_HOST     = 2;
62   const ERROR_COOKIES_DISABLED = 3;
63
64
197601 65   /**
T 66    * This implements the 'singleton' design pattern
67    *
5c461b 68    * @return rcmail The one and only instance
197601 69    */
T 70   static function get_instance()
71   {
0c2596 72     if (!self::$instance || !is_a(self::$instance, 'rcmail')) {
197601 73       self::$instance = new rcmail();
T 74       self::$instance->startup();  // init AFTER object was linked with self::$instance
75     }
76
77     return self::$instance;
78   }
b62a0d 79
A 80
197601 81   /**
T 82    * Initial startup function
83    * to register session, create database and imap connections
84    */
0c2596 85   protected function startup()
197601 86   {
0c2596 87     $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS);
a90ad2 88
929a50 89     // start session
A 90     $this->session_init();
197601 91
T 92     // create user object
93     $this->set_user(new rcube_user($_SESSION['user_id']));
929a50 94
9b94eb 95     // set task and action properties
1aceb9 96     $this->set_task(rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC));
A 97     $this->action = asciiwords(rcube_utils::get_input_value('_action', rcube_utils::INPUT_GPC));
9b94eb 98
197601 99     // reset some session parameters when changing task
677e1f 100     if ($this->task != 'utils') {
A 101       if ($this->session && $_SESSION['task'] != $this->task)
102         $this->session->remove('page');
103       // set current task to session
104       $_SESSION['task'] = $this->task;
105     }
197601 106
48bc52 107     // init output class
A 108     if (!empty($_REQUEST['_remote']))
929a50 109       $GLOBALS['OUTPUT'] = $this->json_init();
48bc52 110     else
A 111       $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
112
0c2596 113     // load plugins
A 114     $this->plugins->init($this, $this->task);
115     $this->plugins->load_plugins((array)$this->config->get('plugins', array()), array('filesystem_attachments', 'jqueryui'));
197601 116   }
b62a0d 117
A 118
197601 119   /**
T 120    * Setter for application task
121    *
122    * @param string Task to set
123    */
124   public function set_task($task)
125   {
1c932d 126     $task = asciiwords($task);
9b94eb 127
A 128     if ($this->user && $this->user->ID)
c3be8e 129       $task = !$task ? 'mail' : $task;
9b94eb 130     else
A 131       $task = 'login';
132
133     $this->task = $task;
1c932d 134     $this->comm_path = $this->url(array('task' => $this->task));
b62a0d 135
197601 136     if ($this->output)
1c932d 137       $this->output->set_env('task', $this->task);
197601 138   }
b62a0d 139
A 140
197601 141   /**
T 142    * Setter for system user object
143    *
5c461b 144    * @param rcube_user Current user instance
197601 145    */
T 146   public function set_user($user)
147   {
148     if (is_object($user)) {
149       $this->user = $user;
b62a0d 150
197601 151       // overwrite config with user preferences
b545d3 152       $this->config->set_user_prefs((array)$this->user->get_prefs());
197601 153     }
b62a0d 154
553225 155     $lang = $this->language_prop($this->config->get('language', $_SESSION['language']));
AM 156     $_SESSION['language'] = $this->user->language = $lang;
531abb 157
197601 158     // set localization
553225 159     setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8');
14de18 160
b62a0d 161     // workaround for http://bugs.php.net/bug.php?id=18556
553225 162     if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) {
AM 163       setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8');
164     }
197601 165   }
b62a0d 166
A 167
197601 168   /**
ade8e1 169    * Return instance of the internal address book class
T 170    *
840b4d 171    * @param string  Address book identifier (-1 for default addressbook)
ade8e1 172    * @param boolean True if the address book needs to be writeable
7f7ed2 173    *
5c461b 174    * @return rcube_contacts Address book object
ade8e1 175    */
T 176   public function get_address_book($id, $writeable = false)
177   {
b896b1 178     $contacts    = null;
ade8e1 179     $ldap_config = (array)$this->config->get('ldap_public');
cc97ea 180
f03d89 181     // 'sql' is the alias for '0' used by autocomplete
A 182     if ($id == 'sql')
840b4d 183       $id = '0';
AM 184     else if ($id == -1) {
185       $id = $this->config->get('default_addressbook');
186       $default = true;
187     }
f03d89 188
0501b6 189     // use existing instance
840b4d 190     if (isset($this->address_books[$id]) && ($this->address_books[$id] instanceof rcube_addressbook)) {
0501b6 191       $contacts = $this->address_books[$id];
T 192     }
cc97ea 193     else if ($id && $ldap_config[$id]) {
c321a9 194       $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['storage_host']));
cc97ea 195     }
T 196     else if ($id === '0') {
0c2596 197       $contacts = new rcube_contacts($this->db, $this->get_user_id());
ade8e1 198     }
b896b1 199     else {
A 200       $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
201
202       // plugin returned instance of a rcube_addressbook
203       if ($plugin['instance'] instanceof rcube_addressbook) {
204         $contacts = $plugin['instance'];
205       }
840b4d 206     }
AM 207
65dff8 208     // when user requested default writeable addressbook
AM 209     // we need to check if default is writeable, if not we
210     // will return first writeable book (if any exist)
211     if ($contacts && $default && $contacts->readonly && $writeable) {
212       $contacts = null;
213     }
214
840b4d 215     // Get first addressbook from the list if configured default doesn't exist
AM 216     // This can happen when user deleted the addressbook (e.g. Kolab folder)
217     if (!$contacts && (!$id || $default)) {
65dff8 218       $source = reset($this->get_address_sources($writeable, !$default));
840b4d 219       if (!empty($source)) {
AM 220         $contacts = $this->get_address_book($source['id']);
65dff8 221         if ($contacts) {
840b4d 222           $id = $source['id'];
65dff8 223         }
ade8e1 224       }
5ed119 225     }
A 226
227     if (!$contacts) {
0c2596 228       self::raise_error(array(
782d85 229         'code' => 700, 'type' => 'php',
5ed119 230         'file' => __FILE__, 'line' => __LINE__,
A 231         'message' => "Addressbook source ($id) not found!"),
232         true, true);
ade8e1 233     }
457373 234
65dff8 235     // add to the 'books' array for shutdown function
AM 236     $this->address_books[$id] = $contacts;
237
840b4d 238     if ($writeable && $contacts->readonly) {
AM 239       return null;
240     }
241
438753 242     // set configured sort order
65dff8 243     if ($sort_col = $this->config->get('addressbook_sort_col')) {
438753 244         $contacts->set_sort_order($sort_col);
65dff8 245     }
b62a0d 246
ade8e1 247     return $contacts;
T 248   }
3704b7 249
A 250
251   /**
252    * Return address books list
253    *
254    * @param boolean True if the address book needs to be writeable
65dff8 255    * @param boolean True if the address book needs to be not hidden
5c9d1f 256    *
3704b7 257    * @return array  Address books array
A 258    */
65dff8 259   public function get_address_sources($writeable = false, $skip_hidden = false)
3704b7 260   {
A 261     $abook_type = strtolower($this->config->get('address_book_type'));
7fdb9d 262     $ldap_config = $this->config->get('ldap_public');
A 263     $autocomplete = (array) $this->config->get('autocomplete_addressbooks');
3704b7 264     $list = array();
A 265
5526f9 266     // We are using the DB address book or a plugin address book
JD 267     if ($abook_type != 'ldap' && $abook_type != '') {
0501b6 268       if (!isset($this->address_books['0']))
0c2596 269         $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id());
3704b7 270       $list['0'] = array(
5c9d1f 271         'id'       => '0',
0c2596 272         'name'     => $this->gettext('personaladrbook'),
5c9d1f 273         'groups'   => $this->address_books['0']->groups,
8c263e 274         'readonly' => $this->address_books['0']->readonly,
d06e57 275         'autocomplete' => in_array('sql', $autocomplete),
T 276         'undelete' => $this->address_books['0']->undelete && $this->config->get('undo_timeout'),
3704b7 277       );
A 278     }
279
7fdb9d 280     if ($ldap_config) {
A 281       $ldap_config = (array) $ldap_config;
08b7b6 282       foreach ($ldap_config as $id => $prop) {
A 283         // handle misconfiguration
284         if (empty($prop) || !is_array($prop)) {
285           continue;
286         }
3704b7 287         $list[$id] = array(
5c9d1f 288           'id'       => $id,
1b9923 289           'name'     => html::quote($prop['name']),
ec2185 290           'groups'   => !empty($prop['groups']) || !empty($prop['group_filters']),
a61bbb 291           'readonly' => !$prop['writable'],
5c9d1f 292           'hidden'   => $prop['hidden'],
A 293           'autocomplete' => in_array($id, $autocomplete)
3704b7 294         );
08b7b6 295       }
3704b7 296     }
A 297
e6ce00 298     $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
3704b7 299     $list = $plugin['sources'];
A 300
0501b6 301     foreach ($list as $idx => $item) {
T 302       // register source for shutdown function
65dff8 303       if (!is_object($this->address_books[$item['id']])) {
0501b6 304         $this->address_books[$item['id']] = $item;
65dff8 305       }
0501b6 306       // remove from list if not writeable as requested
65dff8 307       if ($writeable && $item['readonly']) {
c0297f 308           unset($list[$idx]);
65dff8 309       }
AM 310       // remove from list if hidden as requested
311       else if ($skip_hidden && $item['hidden']) {
312           unset($list[$idx]);
313       }
3704b7 314     }
8c263e 315
3704b7 316     return $list;
A 317   }
b62a0d 318
A 319
ade8e1 320   /**
197601 321    * Init output object for GUI and add common scripts.
60226a 322    * This will instantiate a rcmail_output_html object and set
197601 323    * environment vars according to the current session and configuration
0ece58 324    *
T 325    * @param boolean True if this request is loaded in a (i)frame
60226a 326    * @return rcube_output Reference to HTML output object
197601 327    */
T 328   public function load_gui($framed = false)
329   {
330     // init output page
60226a 331     if (!($this->output instanceof rcmail_output_html))
TB 332       $this->output = new rcmail_output_html($this->task, $framed);
197601 333
f22654 334     // set refresh interval
AM 335     $this->output->set_env('refresh_interval', $this->config->get('refresh_interval', 0));
c442f8 336     $this->output->set_env('session_lifetime', $this->config->get('session_lifetime', 0) * 60);
197601 337
T 338     if ($framed) {
339       $this->comm_path .= '&_framed=1';
340       $this->output->set_env('framed', true);
341     }
342
343     $this->output->set_env('task', $this->task);
344     $this->output->set_env('action', $this->action);
345     $this->output->set_env('comm_path', $this->comm_path);
a92beb 346     $this->output->set_charset(RCUBE_CHARSET);
197601 347
7f5a84 348     // add some basic labels to client
77de23 349     $this->output->add_label('loading', 'servererror', 'requesttimedout', 'refreshing');
b62a0d 350
197601 351     return $this->output;
T 352   }
b62a0d 353
A 354
197601 355   /**
T 356    * Create an output object for JSON responses
0ece58 357    *
60226a 358    * @return rcube_output Reference to JSON output object
197601 359    */
929a50 360   public function json_init()
197601 361   {
60226a 362     if (!($this->output instanceof rcmail_output_json))
TB 363       $this->output = new rcmail_output_json($this->task);
b62a0d 364
197601 365     return $this->output;
929a50 366   }
A 367
368
369   /**
370    * Create session object and start the session.
371    */
372   public function session_init()
373   {
963a10 374     parent::session_init();
929a50 375
A 376     // set initial session vars
cf2da2 377     if (!$_SESSION['user_id'])
929a50 378       $_SESSION['temp'] = true;
40d246 379
T 380     // restore skin selection after logout
381     if ($_SESSION['temp'] && !empty($_SESSION['skin']))
382       $this->config->set('skin', $_SESSION['skin']);
197601 383   }
T 384
385
386   /**
c321a9 387    * Perfom login to the mail server and to the webmail service.
197601 388    * This will also create a new user entry if auto_create_user is configured.
T 389    *
c321a9 390    * @param string Mail storage (IMAP) user name
T 391    * @param string Mail storage (IMAP) password
392    * @param string Mail storage (IMAP) host
7c8fd8 393    * @param bool   Enables cookie check
fdff34 394    *
197601 395    * @return boolean True on success, False on failure
T 396    */
7c8fd8 397   function login($username, $pass, $host = null, $cookiecheck = false)
197601 398   {
7c8fd8 399     $this->login_error = null;
AM 400
fdff34 401     if (empty($username)) {
7c8fd8 402       return false;
AM 403     }
404
405     if ($cookiecheck && empty($_COOKIE)) {
406       $this->login_error = self::ERROR_COOKIES_DISABLED;
fdff34 407       return false;
A 408     }
409
197601 410     $config = $this->config->all();
T 411
412     if (!$host)
413       $host = $config['default_host'];
414
415     // Validate that selected host is in the list of configured hosts
416     if (is_array($config['default_host'])) {
417       $allowed = false;
418       foreach ($config['default_host'] as $key => $host_allowed) {
419         if (!is_numeric($key))
420           $host_allowed = $key;
421         if ($host == $host_allowed) {
422           $allowed = true;
423           break;
424         }
425       }
7c8fd8 426       if (!$allowed) {
AM 427         $host = null;
197601 428       }
7c8fd8 429     }
AM 430     else if (!empty($config['default_host']) && $host != rcube_utils::parse_host($config['default_host'])) {
431       $host = null;
432     }
433
434     if (!$host) {
435       $this->login_error = self::ERROR_INVALID_HOST;
197601 436       return false;
7c8fd8 437     }
197601 438
T 439     // parse $host URL
440     $a_host = parse_url($host);
441     if ($a_host['host']) {
442       $host = $a_host['host'];
c321a9 443       $ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
e99991 444       if (!empty($a_host['port']))
c321a9 445         $port = $a_host['port'];
T 446       else if ($ssl && $ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
447         $port = 993;
197601 448     }
b62a0d 449
c321a9 450     if (!$port) {
T 451         $port = $config['default_port'];
452     }
197601 453
b62a0d 454     /* Modify username with domain if required
197601 455        Inspired by Marco <P0L0_notspam_binware.org>
T 456     */
457     // Check if we need to add domain
c16fab 458     if (!empty($config['username_domain']) && strpos($username, '@') === false) {
197601 459       if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
1aceb9 460         $username .= '@'.rcube_utils::parse_host($config['username_domain'][$host], $host);
197601 461       else if (is_string($config['username_domain']))
1aceb9 462         $username .= '@'.rcube_utils::parse_host($config['username_domain'], $host);
197601 463     }
T 464
df95e7 465     if (!isset($config['login_lc'])) {
AM 466       $config['login_lc'] = 2; // default
467     }
468
c321a9 469     // Convert username to lowercase. If storage backend
e17553 470     // is case-insensitive we need to store always the same username (#1487113)
A 471     if ($config['login_lc']) {
c72325 472       if ($config['login_lc'] == 2 || $config['login_lc'] === true) {
AM 473         $username = mb_strtolower($username);
474       }
475       else if (strpos($username, '@')) {
476         // lowercase domain name
477         list($local, $domain) = explode('@', $username);
478         $username = $local . '@' . mb_strtolower($domain);
479       }
e17553 480     }
A 481
942069 482     // try to resolve email address from virtuser table
e17553 483     if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
A 484       $username = $virtuser;
485     }
197601 486
f1adbf 487     // Here we need IDNA ASCII
A 488     // Only rcube_contacts class is using domain names in Unicode
c72325 489     $host     = rcube_utils::idn_to_ascii($host);
AM 490     $username = rcube_utils::idn_to_ascii($username);
f1adbf 491
197601 492     // user already registered -> overwrite username
c72325 493     if ($user = rcube_user::query($username, $host)) {
197601 494       $username = $user->data['username'];
c72325 495     }
197601 496
1aceb9 497     $storage = $this->get_storage();
48bc52 498
c321a9 499     // try to log in
df95e7 500     if (!$storage->connect($host, $username, $pass, $port, $ssl)) {
197601 501       return false;
c321a9 502     }
197601 503
T 504     // user already registered -> update user's record
505     if (is_object($user)) {
d08333 506       // update last login timestamp
197601 507       $user->touch();
T 508     }
509     // create new system user
510     else if ($config['auto_create_user']) {
511       if ($created = rcube_user::create($username, $host)) {
512         $user = $created;
513       }
f879f4 514       else {
0c2596 515         self::raise_error(array(
782d85 516           'code' => 620, 'type' => 'php',
6d94ab 517           'file' => __FILE__, 'line' => __LINE__,
f879f4 518           'message' => "Failed to create a user record. Maybe aborted by a plugin?"
10eedb 519           ), true, false);
f879f4 520       }
197601 521     }
T 522     else {
0c2596 523       self::raise_error(array(
782d85 524         'code' => 621, 'type' => 'php',
10eedb 525         'file' => __FILE__, 'line' => __LINE__,
782d85 526         'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
197601 527         ), true, false);
T 528     }
529
530     // login succeeded
531     if (is_object($user) && $user->ID) {
f53750 532       // Configure environment
197601 533       $this->set_user($user);
c321a9 534       $this->set_storage_prop();
f53750 535
A 536       // fix some old settings according to namespace prefix
537       $this->fix_namespace_settings($user);
538
539       // create default folders on first login
540       if ($config['create_default_folders'] && (!empty($created) || empty($user->data['last_login']))) {
1aceb9 541         $storage->create_default_folders();
f53750 542       }
197601 543
T 544       // set session vars
c321a9 545       $_SESSION['user_id']      = $user->ID;
T 546       $_SESSION['username']     = $user->data['username'];
547       $_SESSION['storage_host'] = $host;
548       $_SESSION['storage_port'] = $port;
549       $_SESSION['storage_ssl']  = $ssl;
550       $_SESSION['password']     = $this->encrypt($pass);
6a8b4c 551       $_SESSION['login_time']   = time();
f53750 552
b62a0d 553       if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
086b15 554         $_SESSION['timezone'] = rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_GPC);
197601 555
T 556       // force reloading complete list of subscribed mailboxes
1aceb9 557       $storage->clear_cache('mailboxes', true);
197601 558
T 559       return true;
560     }
561
562     return false;
563   }
564
565
7c8fd8 566     /**
AM 567      * Returns error code of last login operation
568      *
569      * @return int Error code
570      */
571     public function login_error()
572     {
573         if ($this->login_error) {
574             return $this->login_error;
575         }
576
577         if ($this->storage && $this->storage->get_error_code() < -1) {
578             return self::ERROR_STORAGE;
579         }
580     }
581
582
197601 583   /**
1854c4 584    * Auto-select IMAP host based on the posted login information
T 585    *
586    * @return string Selected IMAP host
587    */
588   public function autoselect_host()
589   {
590     $default_host = $this->config->get('default_host');
257f88 591     $host = null;
b62a0d 592
257f88 593     if (is_array($default_host)) {
1aceb9 594       $post_host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
45dd7c 595       $post_user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST);
AM 596
597       list($user, $domain) = explode('@', $post_user);
b62a0d 598
257f88 599       // direct match in default_host array
T 600       if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
601         $host = $post_host;
602       }
603       // try to select host by mail domain
45dd7c 604       else if (!empty($domain)) {
c321a9 605         foreach ($default_host as $storage_host => $mail_domains) {
48f04d 606           if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
c321a9 607             $host = $storage_host;
48f04d 608             break;
T 609           }
610           else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) {
611             $host = is_numeric($storage_host) ? $mail_domains : $storage_host;
1854c4 612             break;
T 613           }
614         }
615       }
616
48f04d 617       // take the first entry if $host is still not set
257f88 618       if (empty($host)) {
48f04d 619         list($key, $val) = each($default_host);
T 620         $host = is_numeric($key) ? $val : $key;
257f88 621       }
T 622     }
623     else if (empty($default_host)) {
1aceb9 624       $host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
1854c4 625     }
eec34e 626     else
1aceb9 627       $host = rcube_utils::parse_host($default_host);
1854c4 628
T 629     return $host;
630   }
631
632
633   /**
634    * Destroy session data and remove cookie
635    */
636   public function kill_session()
637   {
e6ce00 638     $this->plugins->exec_hook('session_destroy');
b62a0d 639
cf2da2 640     $this->session->kill();
40d246 641     $_SESSION = array('language' => $this->user->language, 'temp' => true, 'skin' => $this->config->get('skin'));
1854c4 642     $this->user->reset();
T 643   }
644
645
646   /**
647    * Do server side actions on logout
648    */
649   public function logout_actions()
650   {
1aceb9 651     $config  = $this->config->all();
A 652     $storage = $this->get_storage();
1854c4 653
T 654     if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
1aceb9 655       $storage->clear_folder($config['trash_mbox']);
1854c4 656     }
T 657
658     if ($config['logout_expunge']) {
1aceb9 659       $storage->expunge_folder('INBOX');
1854c4 660     }
40a186 661
A 662     // Try to save unsaved user preferences
663     if (!empty($_SESSION['preferences'])) {
664       $this->user->save_prefs(unserialize($_SESSION['preferences']));
665     }
fec2d8 666   }
T 667
668
669   /**
57f0c8 670    * Generate a unique token to be used in a form request
T 671    *
672    * @return string The request token
673    */
549933 674   public function get_request_token()
57f0c8 675   {
ec045b 676     $sess_id = $_COOKIE[ini_get('session.name')];
c9f2c4 677     if (!$sess_id) $sess_id = session_id();
1aceb9 678
A 679     $plugin = $this->plugins->exec_hook('request_token', array(
680         'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id)));
681
ef27a6 682     return $plugin['value'];
57f0c8 683   }
b62a0d 684
A 685
57f0c8 686   /**
T 687    * Check if the current request contains a valid token
688    *
549933 689    * @param int Request method
57f0c8 690    * @return boolean True if request token is valid false if not
T 691    */
1aceb9 692   public function check_request($mode = rcube_utils::INPUT_POST)
57f0c8 693   {
1aceb9 694     $token = rcube_utils::get_input_value('_token', $mode);
ec045b 695     $sess_id = $_COOKIE[ini_get('session.name')];
T 696     return !empty($sess_id) && $token == $this->get_request_token();
57f0c8 697   }
b62a0d 698
A 699
57f0c8 700   /**
1854c4 701    * Create unique authorization hash
T 702    *
703    * @param string Session ID
704    * @param int Timestamp
705    * @return string The generated auth hash
706    */
707   private function get_auth_hash($sess_id, $ts)
708   {
709     $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
710       $sess_id,
711       $ts,
712       $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
713       $_SERVER['HTTP_USER_AGENT']);
714
715     if (function_exists('sha1'))
716       return sha1($auth_string);
717     else
718       return md5($auth_string);
719   }
720
564741 721
A 722   /**
e019f2 723    * Build a valid URL to this instance of Roundcube
c719f3 724    *
T 725    * @param mixed Either a string with the action or url parameters as key-value pairs
1aceb9 726    *
c719f3 727    * @return string Valid application URL
T 728    */
729   public function url($p)
730   {
88fb56 731     if (!is_array($p)) {
TB 732       if (strpos($p, 'http') === 0)
733         return $p;
734
fde466 735       $p = array('_action' => @func_get_arg(0));
88fb56 736     }
b62a0d 737
1c932d 738     $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
cc97ea 739     $p['_task'] = $task;
1038a6 740     unset($p['task']);
A 741
cf1777 742     $url = './';
T 743     $delm = '?';
77406b 744     foreach (array_reverse($p) as $key => $val) {
a7321e 745       if ($val !== '' && $val !== null) {
cc97ea 746         $par = $key[0] == '_' ? $key : '_'.$key;
cf1777 747         $url .= $delm.urlencode($par).'='.urlencode($val);
T 748         $delm = '&';
749       }
750     }
c719f3 751     return $url;
T 752   }
cefd1d 753
T 754
755   /**
0c2596 756    * Function to be executed in script shutdown
0501b6 757    */
0c2596 758   public function shutdown()
0501b6 759   {
0c2596 760     parent::shutdown();
0501b6 761
0c2596 762     foreach ($this->address_books as $book) {
A 763       if (is_object($book) && is_a($book, 'rcube_addressbook'))
764         $book->close();
0501b6 765     }
T 766
0c2596 767     // before closing the database connection, write session data
A 768     if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
769       session_write_close();
770     }
f53750 771
0c2596 772     // write performance stats to logs/console
A 773     if ($this->config->get('devel_mode')) {
774       if (function_exists('memory_get_usage'))
1aceb9 775         $mem = $this->show_bytes(memory_get_usage());
0c2596 776       if (function_exists('memory_get_peak_usage'))
1aceb9 777         $mem .= '/'.$this->show_bytes(memory_get_peak_usage());
0c2596 778
A 779       $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
780       if (defined('RCMAIL_START'))
781         self::print_timer(RCMAIL_START, $log);
782       else
783         self::console($log);
784     }
cefd1d 785   }
68d2d5 786
77de23 787
68d2d5 788   /**
A 789    * Registers action aliases for current task
790    *
791    * @param array $map Alias-to-filename hash array
792    */
793   public function register_action_map($map)
794   {
795     if (is_array($map)) {
796       foreach ($map as $idx => $val) {
797         $this->action_map[$idx] = $val;
798       }
799     }
800   }
f53750 801
77de23 802
68d2d5 803   /**
A 804    * Returns current action filename
805    *
806    * @param array $map Alias-to-filename hash array
807    */
808   public function get_action_file()
809   {
810     if (!empty($this->action_map[$this->action])) {
811       return $this->action_map[$this->action];
812     }
813
814     return strtr($this->action, '-', '_') . '.inc';
815   }
816
77de23 817
d08333 818   /**
A 819    * Fixes some user preferences according to namespace handling change.
820    * Old Roundcube versions were using folder names with removed namespace prefix.
821    * Now we need to add the prefix on servers where personal namespace has prefix.
822    *
823    * @param rcube_user $user User object
824    */
825   private function fix_namespace_settings($user)
826   {
c321a9 827     $prefix     = $this->storage->get_namespace('prefix');
d08333 828     $prefix_len = strlen($prefix);
A 829
830     if (!$prefix_len)
831       return;
832
f53750 833     $prefs = $this->config->all();
A 834     if (!empty($prefs['namespace_fixed']))
d08333 835       return;
A 836
837     // Build namespace prefix regexp
c321a9 838     $ns     = $this->storage->get_namespace();
d08333 839     $regexp = array();
A 840
841     foreach ($ns as $entry) {
842       if (!empty($entry)) {
843         foreach ($entry as $item) {
844           if (strlen($item[0])) {
845             $regexp[] = preg_quote($item[0], '/');
846           }
847         }
848       }
849     }
850     $regexp = '/^('. implode('|', $regexp).')/';
851
852     // Fix preferences
853     $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox');
854     foreach ($opts as $opt) {
855       if ($value = $prefs[$opt]) {
856         if ($value != 'INBOX' && !preg_match($regexp, $value)) {
857           $prefs[$opt] = $prefix.$value;
858         }
859       }
860     }
861
c321a9 862     if (!empty($prefs['default_folders'])) {
T 863       foreach ($prefs['default_folders'] as $idx => $name) {
d08333 864         if ($name != 'INBOX' && !preg_match($regexp, $name)) {
c321a9 865           $prefs['default_folders'][$idx] = $prefix.$name;
d08333 866         }
A 867       }
868     }
869
870     if (!empty($prefs['search_mods'])) {
871       $folders = array();
872       foreach ($prefs['search_mods'] as $idx => $value) {
873         if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) {
874           $idx = $prefix.$idx;
875         }
876         $folders[$idx] = $value;
877       }
878       $prefs['search_mods'] = $folders;
879     }
880
881     if (!empty($prefs['message_threading'])) {
882       $folders = array();
883       foreach ($prefs['message_threading'] as $idx => $value) {
884         if ($idx != 'INBOX' && !preg_match($regexp, $idx)) {
885           $idx = $prefix.$idx;
886         }
887         $folders[$prefix.$idx] = $value;
888       }
889       $prefs['message_threading'] = $folders;
890     }
891
892     if (!empty($prefs['collapsed_folders'])) {
893       $folders     = explode('&&', $prefs['collapsed_folders']);
894       $count       = count($folders);
895       $folders_str = '';
896
897       if ($count) {
898           $folders[0]        = substr($folders[0], 1);
899           $folders[$count-1] = substr($folders[$count-1], 0, -1);
900       }
901
902       foreach ($folders as $value) {
903         if ($value != 'INBOX' && !preg_match($regexp, $value)) {
904           $value = $prefix.$value;
905         }
906         $folders_str .= '&'.$value.'&';
907       }
908       $prefs['collapsed_folders'] = $folders_str;
909     }
910
911     $prefs['namespace_fixed'] = true;
912
913     // save updated preferences and reset imap settings (default folders)
914     $user->save_prefs($prefs);
c321a9 915     $this->set_storage_prop();
d08333 916   }
A 917
0c2596 918
A 919     /**
920      * Overwrite action variable
921      *
922      * @param string New action value
923      */
924     public function overwrite_action($action)
925     {
926         $this->action = $action;
927         $this->output->set_env('action', $action);
928     }
929
930
931     /**
932      * Send the given message using the configured method.
933      *
934      * @param object $message    Reference to Mail_MIME object
935      * @param string $from       Sender address string
936      * @param array  $mailto     Array of recipient address strings
dfc578 937      * @param array  $error      SMTP error array (reference)
0c2596 938      * @param string $body_file  Location of file with saved message body (reference),
A 939      *                           used when delay_file_io is enabled
dfc578 940      * @param array  $options    SMTP options (e.g. DSN request)
0c2596 941      *
A 942      * @return boolean Send status.
943      */
dfc578 944     public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null)
0c2596 945     {
dfc578 946         $plugin = $this->plugins->exec_hook('message_before_send', array(
AM 947             'message' => $message,
948             'from'    => $from,
949             'mailto'  => $mailto,
950             'options' => $options,
951         ));
952
953         $from    = $plugin['from'];
954         $mailto  = $plugin['mailto'];
955         $options = $plugin['options'];
956         $message = $plugin['message'];
0c2596 957         $headers = $message->headers();
A 958
959         // send thru SMTP server using custom SMTP library
960         if ($this->config->get('smtp_server')) {
961             // generate list of recipients
962             $a_recipients = array($mailto);
963
964             if (strlen($headers['Cc']))
965                 $a_recipients[] = $headers['Cc'];
966             if (strlen($headers['Bcc']))
967                 $a_recipients[] = $headers['Bcc'];
968
969             // clean Bcc from header for recipients
970             $send_headers = $headers;
971             unset($send_headers['Bcc']);
972             // here too, it because txtHeaders() below use $message->_headers not only $send_headers
973             unset($message->_headers['Bcc']);
974
975             $smtp_headers = $message->txtHeaders($send_headers, true);
976
977             if ($message->getParam('delay_file_io')) {
978                 // use common temp dir
979                 $temp_dir = $this->config->get('temp_dir');
980                 $body_file = tempnam($temp_dir, 'rcmMsg');
981                 if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
982                     self::raise_error(array('code' => 650, 'type' => 'php',
983                         'file' => __FILE__, 'line' => __LINE__,
984                         'message' => "Could not create message: ".$mime_result->getMessage()),
985                         TRUE, FALSE);
986                     return false;
987                 }
988                 $msg_body = fopen($body_file, 'r');
989             }
990             else {
991                 $msg_body = $message->get();
992             }
993
994             // send message
995             if (!is_object($this->smtp)) {
996                 $this->smtp_init(true);
997             }
998
dfc578 999             $sent     = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options);
AM 1000             $response = $this->smtp->get_response();
1001             $error    = $this->smtp->get_error();
0c2596 1002
A 1003             // log error
1004             if (!$sent) {
1005                 self::raise_error(array('code' => 800, 'type' => 'smtp',
1006                     'line' => __LINE__, 'file' => __FILE__,
dfc578 1007                     'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE);
0c2596 1008             }
A 1009         }
1010         // send mail using PHP's mail() function
1011         else {
1012             // unset some headers because they will be added by the mail() function
1013             $headers_enc = $message->headers($headers);
1014             $headers_php = $message->_headers;
1015             unset($headers_php['To'], $headers_php['Subject']);
1016
1017             // reset stored headers and overwrite
1018             $message->_headers = array();
1019             $header_str = $message->txtHeaders($headers_php);
1020
1021             // #1485779
1022             if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1023                 if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
1024                     $headers_enc['To'] = implode(', ', $m[1]);
1025                 }
1026             }
1027
1028             $msg_body = $message->get();
1029
1030             if (PEAR::isError($msg_body)) {
1031                 self::raise_error(array('code' => 650, 'type' => 'php',
1032                     'file' => __FILE__, 'line' => __LINE__,
1033                     'message' => "Could not create message: ".$msg_body->getMessage()),
1034                     TRUE, FALSE);
1035             }
1036             else {
1037                 $delim   = $this->config->header_delimiter();
1038                 $to      = $headers_enc['To'];
1039                 $subject = $headers_enc['Subject'];
1040                 $header_str = rtrim($header_str);
1041
1042                 if ($delim != "\r\n") {
1043                     $header_str = str_replace("\r\n", $delim, $header_str);
1044                     $msg_body   = str_replace("\r\n", $delim, $msg_body);
1045                     $to         = str_replace("\r\n", $delim, $to);
1046                     $subject    = str_replace("\r\n", $delim, $subject);
1047                 }
1048
1049                 if (ini_get('safe_mode'))
1050                     $sent = mail($to, $subject, $msg_body, $header_str);
1051                 else
1052                     $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
1053             }
1054         }
1055
1056         if ($sent) {
1057             $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
1058
1059             // remove MDN headers after sending
1060             unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1061
1062             // get all recipients
1063             if ($headers['Cc'])
1064                 $mailto .= $headers['Cc'];
1065             if ($headers['Bcc'])
1066                 $mailto .= $headers['Bcc'];
1067             if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
1068                 $mailto = implode(', ', array_unique($m[1]));
1069
1070             if ($this->config->get('smtp_log')) {
1071                 self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1072                     $this->user->get_username(),
1073                     $_SERVER['REMOTE_ADDR'],
1074                     $mailto,
dfc578 1075                     !empty($response) ? join('; ', $response) : ''));
0c2596 1076             }
A 1077         }
1078
1079         if (is_resource($msg_body)) {
1080             fclose($msg_body);
1081         }
1082
1083         $message->_headers = array();
1084         $message->headers($headers);
1085
1086         return $sent;
1087     }
1088
1089
1090     /**
1091      * Unique Message-ID generator.
1092      *
1093      * @return string Message-ID
1094      */
1095     public function gen_message_id()
1096     {
1097         $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
1098         $domain_part = $this->user->get_username('domain');
1099
1100         // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
1101         if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
1102             foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
1103                 $host = preg_replace('/:[0-9]+$/', '', $host);
1104                 if ($host && preg_match('/\.[a-z]+$/i', $host)) {
1105                     $domain_part = $host;
1106                 }
1107             }
1108         }
1109
1110         return sprintf('<%s@%s>', $local_part, $domain_part);
1111     }
1112
1113
1114     /**
1115      * Returns RFC2822 formatted current date in user's timezone
1116      *
1117      * @return string Date
1118      */
1119     public function user_date()
1120     {
1121         // get user's timezone
1122         try {
1123             $tz   = new DateTimeZone($this->config->get('timezone'));
1124             $date = new DateTime('now', $tz);
1125         }
1126         catch (Exception $e) {
1127             $date = new DateTime();
1128         }
1129
1130         return $date->format('r');
1131     }
1132
1133
1134     /**
1135      * Write login data (name, ID, IP address) to the 'userlogins' log file.
1136      */
1137     public function log_login()
1138     {
1139         if (!$this->config->get('log_logins')) {
1140             return;
1141         }
1142
1143         $user_name = $this->get_user_name();
1144         $user_id   = $this->get_user_id();
1145
1146         if (!$user_id) {
1147             return;
1148         }
1149
1150         self::write_log('userlogins',
1151             sprintf('Successful login for %s (ID: %d) from %s in session %s',
1aceb9 1152                 $user_name, $user_id, rcube_utils::remote_ip(), session_id()));
0c2596 1153     }
A 1154
1aceb9 1155
A 1156     /**
1157      * Create a HTML table based on the given data
1158      *
1159      * @param  array  Named table attributes
1160      * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
1161      * @param  array  List of cols to show
1162      * @param  string Name of the identifier col
1163      *
1164      * @return string HTML table code
1165      */
1166     public function table_output($attrib, $table_data, $a_show_cols, $id_col)
1167     {
1168         $table = new html_table(/*array('cols' => count($a_show_cols))*/);
1169
1170         // add table header
1171         if (!$attrib['noheader']) {
1172             foreach ($a_show_cols as $col) {
1173                 $table->add_header($col, $this->Q($this->gettext($col)));
1174             }
1175         }
1176
1177         if (!is_array($table_data)) {
1178             $db = $this->get_dbh();
1179             while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) {
1180                 $table->add_row(array('id' => 'rcmrow' . rcube_utils::html_identifier($sql_arr[$id_col])));
1181
1182                 // format each col
1183                 foreach ($a_show_cols as $col) {
1184                     $table->add($col, $this->Q($sql_arr[$col]));
1185                 }
1186             }
1187         }
1188         else {
1189             foreach ($table_data as $row_data) {
1190                 $class = !empty($row_data['class']) ? $row_data['class'] : '';
1191                 $rowid = 'rcmrow' . rcube_utils::html_identifier($row_data[$id_col]);
1192
1193                 $table->add_row(array('id' => $rowid, 'class' => $class));
1194
1195                 // format each col
1196                 foreach ($a_show_cols as $col) {
1197                     $table->add($col, $this->Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
1198                 }
1199             }
1200         }
1201
1202         return $table->show($attrib);
1203     }
1204
1205
1206     /**
1207      * Convert the given date to a human readable form
1208      * This uses the date formatting properties from config
1209      *
1210      * @param mixed  Date representation (string, timestamp or DateTime object)
1211      * @param string Date format to use
1212      * @param bool   Enables date convertion according to user timezone
1213      *
1214      * @return string Formatted date string
1215      */
1216     public function format_date($date, $format = null, $convert = true)
1217     {
1218         if (is_object($date) && is_a($date, 'DateTime')) {
1219             $timestamp = $date->format('U');
1220         }
1221         else {
1222             if (!empty($date)) {
6075f0 1223                 $timestamp = rcube_utils::strtotime($date);
1aceb9 1224             }
A 1225
1226             if (empty($timestamp)) {
1227                 return '';
1228             }
1229
1230             try {
1231                 $date = new DateTime("@".$timestamp);
1232             }
1233             catch (Exception $e) {
1234                 return '';
1235             }
1236         }
1237
1238         if ($convert) {
1239             try {
1240                 // convert to the right timezone
1241                 $stz = date_default_timezone_get();
1242                 $tz = new DateTimeZone($this->config->get('timezone'));
1243                 $date->setTimezone($tz);
1244                 date_default_timezone_set($tz->getName());
1245
1246                 $timestamp = $date->format('U');
1247             }
1248             catch (Exception $e) {
1249             }
1250         }
1251
1252         // define date format depending on current time
1253         if (!$format) {
1254             $now         = time();
1255             $now_date    = getdate($now);
1256             $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1257             $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1258             $pretty_date = $this->config->get('prettydate');
1259
1260             if ($pretty_date && $timestamp > $today_limit && $timestamp < $now) {
1261                 $format = $this->config->get('date_today', $this->config->get('time_format', 'H:i'));
1262                 $today  = true;
1263             }
1264             else if ($pretty_date && $timestamp > $week_limit && $timestamp < $now) {
1265                 $format = $this->config->get('date_short', 'D H:i');
1266             }
1267             else {
1268                 $format = $this->config->get('date_long', 'Y-m-d H:i');
1269             }
1270         }
1271
1272         // strftime() format
1273         if (preg_match('/%[a-z]+/i', $format)) {
1274             $format = strftime($format, $timestamp);
1275             if ($stz) {
1276                 date_default_timezone_set($stz);
1277             }
1278             return $today ? ($this->gettext('today') . ' ' . $format) : $format;
1279         }
1280
1281         // parse format string manually in order to provide localized weekday and month names
1282         // an alternative would be to convert the date() format string to fit with strftime()
1283         $out = '';
1284         for ($i=0; $i<strlen($format); $i++) {
1285             if ($format[$i] == "\\") {  // skip escape chars
1286                 continue;
1287             }
1288
1289             // write char "as-is"
1290             if ($format[$i] == ' ' || $format[$i-1] == "\\") {
1291                 $out .= $format[$i];
1292             }
1293             // weekday (short)
1294             else if ($format[$i] == 'D') {
1295                 $out .= $this->gettext(strtolower(date('D', $timestamp)));
1296             }
1297             // weekday long
1298             else if ($format[$i] == 'l') {
1299                 $out .= $this->gettext(strtolower(date('l', $timestamp)));
1300             }
1301             // month name (short)
1302             else if ($format[$i] == 'M') {
1303                 $out .= $this->gettext(strtolower(date('M', $timestamp)));
1304             }
1305             // month name (long)
1306             else if ($format[$i] == 'F') {
1307                 $out .= $this->gettext('long'.strtolower(date('M', $timestamp)));
1308             }
1309             else if ($format[$i] == 'x') {
1310                 $out .= strftime('%x %X', $timestamp);
1311             }
1312             else {
1313                 $out .= date($format[$i], $timestamp);
1314             }
1315         }
1316
1317         if ($today) {
1318             $label = $this->gettext('today');
1319             // replcae $ character with "Today" label (#1486120)
1320             if (strpos($out, '$') !== false) {
1321                 $out = preg_replace('/\$/', $label, $out, 1);
1322             }
1323             else {
1324                 $out = $label . ' ' . $out;
1325             }
1326         }
1327
1328         if ($stz) {
1329             date_default_timezone_set($stz);
1330         }
1331
1332         return $out;
1333     }
1334
1335
1336     /**
1337      * Return folders list in HTML
1338      *
1339      * @param array $attrib Named parameters
1340      *
1341      * @return string HTML code for the gui object
1342      */
1343     public function folder_list($attrib)
1344     {
1345         static $a_mailboxes;
1346
1347         $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
1348
1349         $rcmail  = rcmail::get_instance();
1350         $storage = $rcmail->get_storage();
1351
1352         // add some labels to client
1353         $rcmail->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1354
1355         $type = $attrib['type'] ? $attrib['type'] : 'ul';
1356         unset($attrib['type']);
1357
1358         if ($type == 'ul' && !$attrib['id']) {
1359             $attrib['id'] = 'rcmboxlist';
1360         }
1361
1362         if (empty($attrib['folder_name'])) {
1363             $attrib['folder_name'] = '*';
1364         }
1365
1366         // get current folder
1367         $mbox_name = $storage->get_folder();
1368
1369         // build the folders tree
1370         if (empty($a_mailboxes)) {
1371             // get mailbox list
1372             $a_folders = $storage->list_folders_subscribed(
1373                 '', $attrib['folder_name'], $attrib['folder_filter']);
1374             $delimiter = $storage->get_hierarchy_delimiter();
1375             $a_mailboxes = array();
1376
1377             foreach ($a_folders as $folder) {
1378                 $rcmail->build_folder_tree($a_mailboxes, $folder, $delimiter);
1379             }
1380         }
1381
1382         // allow plugins to alter the folder tree or to localize folder names
1383         $hook = $rcmail->plugins->exec_hook('render_mailboxlist', array(
1384             'list'      => $a_mailboxes,
1385             'delimiter' => $delimiter,
1386             'type'      => $type,
1387             'attribs'   => $attrib,
1388         ));
1389
1390         $a_mailboxes = $hook['list'];
1391         $attrib      = $hook['attribs'];
1392
1393         if ($type == 'select') {
0a1dd5 1394             $attrib['is_escaped'] = true;
1aceb9 1395             $select = new html_select($attrib);
A 1396
1397             // add no-selection option
1398             if ($attrib['noselection']) {
0a1dd5 1399                 $select->add(html::quote($rcmail->gettext($attrib['noselection'])), '');
1aceb9 1400             }
A 1401
1402             $rcmail->render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1403             $out = $select->show($attrib['default']);
1404         }
1405         else {
1406             $js_mailboxlist = array();
1407             $out = html::tag('ul', $attrib, $rcmail->render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1408
3c309a 1409             $rcmail->output->include_script('treelist.js');
1aceb9 1410             $rcmail->output->add_gui_object('mailboxlist', $attrib['id']);
A 1411             $rcmail->output->set_env('mailboxes', $js_mailboxlist);
1412             $rcmail->output->set_env('unreadwrap', $attrib['unreadwrap']);
1413             $rcmail->output->set_env('collapsed_folders', (string)$rcmail->config->get('collapsed_folders'));
1414         }
1415
1416         return $out;
1417     }
1418
1419
1420     /**
1421      * Return folders list as html_select object
1422      *
1423      * @param array $p  Named parameters
1424      *
1425      * @return html_select HTML drop-down object
1426      */
1427     public function folder_selector($p = array())
1428     {
0a1dd5 1429         $p += array('maxlength' => 100, 'realnames' => false, 'is_escaped' => true);
1aceb9 1430         $a_mailboxes = array();
A 1431         $storage = $this->get_storage();
1432
1433         if (empty($p['folder_name'])) {
1434             $p['folder_name'] = '*';
1435         }
1436
1437         if ($p['unsubscribed']) {
1438             $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
1439         }
1440         else {
1441             $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
1442         }
1443
1444         $delimiter = $storage->get_hierarchy_delimiter();
1445
1446         foreach ($list as $folder) {
1447             if (empty($p['exceptions']) || !in_array($folder, $p['exceptions'])) {
1448                 $this->build_folder_tree($a_mailboxes, $folder, $delimiter);
1449             }
1450         }
1451
1452         $select = new html_select($p);
1453
1454         if ($p['noselection']) {
0a1dd5 1455             $select->add(html::quote($p['noselection']), '');
1aceb9 1456         }
A 1457
1458         $this->render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
1459
1460         return $select;
1461     }
1462
1463
1464     /**
1465      * Create a hierarchical array of the mailbox list
1466      */
1467     public function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
1468     {
1469         // Handle namespace prefix
1470         $prefix = '';
1471         if (!$path) {
1472             $n_folder = $folder;
1473             $folder = $this->storage->mod_folder($folder);
1474
1475             if ($n_folder != $folder) {
1476                 $prefix = substr($n_folder, 0, -strlen($folder));
1477             }
1478         }
1479
1480         $pos = strpos($folder, $delm);
1481
1482         if ($pos !== false) {
1483             $subFolders    = substr($folder, $pos+1);
1484             $currentFolder = substr($folder, 0, $pos);
1485
1486             // sometimes folder has a delimiter as the last character
1487             if (!strlen($subFolders)) {
1488                 $virtual = false;
1489             }
1490             else if (!isset($arrFolders[$currentFolder])) {
1491                 $virtual = true;
1492             }
1493             else {
1494                 $virtual = $arrFolders[$currentFolder]['virtual'];
1495             }
1496         }
1497         else {
1498             $subFolders    = false;
1499             $currentFolder = $folder;
1500             $virtual       = false;
1501         }
1502
1503         $path .= $prefix . $currentFolder;
1504
1505         if (!isset($arrFolders[$currentFolder])) {
1506             $arrFolders[$currentFolder] = array(
1507                 'id' => $path,
1508                 'name' => rcube_charset::convert($currentFolder, 'UTF7-IMAP'),
1509                 'virtual' => $virtual,
1510                 'folders' => array());
1511         }
1512         else {
1513             $arrFolders[$currentFolder]['virtual'] = $virtual;
1514         }
1515
1516         if (strlen($subFolders)) {
1517             $this->build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1518         }
1519     }
1520
1521
1522     /**
1523      * Return html for a structured list &lt;ul&gt; for the mailbox tree
1524      */
1525     public function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
1526     {
1527         $maxlength = intval($attrib['maxlength']);
1528         $realnames = (bool)$attrib['realnames'];
1529         $msgcounts = $this->storage->get_cache('messagecount');
1530         $collapsed = $this->config->get('collapsed_folders');
1531
1532         $out = '';
1533         foreach ($arrFolders as $key => $folder) {
1534             $title        = null;
1535             $folder_class = $this->folder_classname($folder['id']);
1536             $is_collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
1537             $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1538
1539             if ($folder_class && !$realnames) {
1540                 $foldername = $this->gettext($folder_class);
1541             }
1542             else {
1543                 $foldername = $folder['name'];
1544
1545                 // shorten the folder name to a given length
1546                 if ($maxlength && $maxlength > 1) {
1547                     $fname = abbreviate_string($foldername, $maxlength);
1548                     if ($fname != $foldername) {
1549                         $title = $foldername;
1550                     }
1551                     $foldername = $fname;
1552                 }
1553             }
1554
1555             // make folder name safe for ids and class names
1556             $folder_id = rcube_utils::html_identifier($folder['id'], true);
1557             $classes   = array('mailbox');
1558
1559             // set special class for Sent, Drafts, Trash and Junk
1560             if ($folder_class) {
1561                 $classes[] = $folder_class;
1562             }
1563
1564             if ($folder['id'] == $mbox_name) {
1565                 $classes[] = 'selected';
1566             }
1567
1568             if ($folder['virtual']) {
1569                 $classes[] = 'virtual';
1570             }
1571             else if ($unread) {
1572                 $classes[] = 'unread';
1573             }
1574
1575             $js_name = $this->JQ($folder['id']);
1576             $html_name = $this->Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
1577             $link_attrib = $folder['virtual'] ? array() : array(
1578                 'href' => $this->url(array('_mbox' => $folder['id'])),
60226a 1579                 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail_output::JS_OBJECT_NAME, $js_name),
1aceb9 1580                 'rel' => $folder['id'],
A 1581                 'title' => $title,
1582             );
1583
1584             $out .= html::tag('li', array(
1585                 'id' => "rcmli".$folder_id,
1586                 'class' => join(' ', $classes),
1587                 'noclose' => true),
3c309a 1588                 html::a($link_attrib, $html_name));
1aceb9 1589
3c309a 1590             if (!empty($folder['folders'])) {
TB 1591                 $out .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), '&nbsp;');
1592             }
1593
1594             $jslist[$folder['id']] = array(
1aceb9 1595                 'id'      => $folder['id'],
A 1596                 'name'    => $foldername,
1597                 'virtual' => $folder['virtual']
1598             );
1599
1600             if (!empty($folder['folders'])) {
1601                 $out .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)),
1602                     $this->render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1603             }
1604
1605             $out .= "</li>\n";
1606         }
1607
1608         return $out;
1609     }
1610
1611
1612     /**
1613      * Return html for a flat list <select> for the mailbox tree
1614      */
1615     public function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = array())
1616     {
1617         $out = '';
1618
1619         foreach ($arrFolders as $key => $folder) {
1620             // skip exceptions (and its subfolders)
1621             if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
1622                 continue;
1623             }
1624
1625             // skip folders in which it isn't possible to create subfolders
1626             if (!empty($opts['skip_noinferiors'])) {
1627                 $attrs = $this->storage->folder_attributes($folder['id']);
1628                 if ($attrs && in_array('\\Noinferiors', $attrs)) {
1629                     continue;
1630                 }
1631             }
1632
1633             if (!$realnames && ($folder_class = $this->folder_classname($folder['id']))) {
1634                 $foldername = $this->gettext($folder_class);
1635             }
1636             else {
1637                 $foldername = $folder['name'];
1638
1639                 // shorten the folder name to a given length
1640                 if ($maxlength && $maxlength > 1) {
1641                     $foldername = abbreviate_string($foldername, $maxlength);
1642                 }
e7ca04 1643             }
1aceb9 1644
0a1dd5 1645             $select->add(str_repeat('&nbsp;', $nestLevel*4) . html::quote($foldername), $folder['id']);
1aceb9 1646
e7ca04 1647             if (!empty($folder['folders'])) {
A 1648                 $out .= $this->render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
1649                     $select, $realnames, $nestLevel+1, $opts);
1aceb9 1650             }
A 1651         }
1652
1653         return $out;
1654     }
1655
1656
1657     /**
1658      * Return internal name for the given folder if it matches the configured special folders
1659      */
1660     public function folder_classname($folder_id)
1661     {
1662         if ($folder_id == 'INBOX') {
1663             return 'inbox';
1664         }
1665
1666         // for these mailboxes we have localized labels and css classes
1667         foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1668         {
1669             if ($folder_id === $this->config->get($smbx.'_mbox')) {
1670                 return $smbx;
1671             }
1672         }
1673     }
1674
1675
1676     /**
1677      * Try to localize the given IMAP folder name.
1678      * UTF-7 decode it in case no localized text was found
1679      *
4d7964 1680      * @param string $name      Folder name
AM 1681      * @param bool   $with_path Enable path localization
1aceb9 1682      *
A 1683      * @return string Localized folder name in UTF-8 encoding
1684      */
4d7964 1685     public function localize_foldername($name, $with_path = true)
1aceb9 1686     {
4d7964 1687         // try to localize path of the folder
AM 1688         if ($with_path) {
1689             $storage   = $this->get_storage();
1690             $delimiter = $storage->get_hierarchy_delimiter();
1691             $path      = explode($delimiter, $name);
1692             $count     = count($path);
1693
1694             if ($count > 1) {
61be82 1695                 for ($i = 0; $i < $count; $i++) {
4d7964 1696                     $folder = implode($delimiter, array_slice($path, 0, -$i));
AM 1697                     if ($folder_class = $this->folder_classname($folder)) {
1698                         $name = implode($delimiter, array_slice($path, $count - $i));
1699                         return $this->gettext($folder_class) . $delimiter . rcube_charset::convert($name, 'UTF7-IMAP');
1700                     }
1701                 }
1702             }
1703         }
1704
1aceb9 1705         if ($folder_class = $this->folder_classname($name)) {
A 1706             return $this->gettext($folder_class);
1707         }
1708         else {
1709             return rcube_charset::convert($name, 'UTF7-IMAP');
1710         }
1711     }
1712
1713
1714     public function localize_folderpath($path)
1715     {
1716         $protect_folders = $this->config->get('protect_default_folders');
1717         $default_folders = (array) $this->config->get('default_folders');
1718         $delimiter       = $this->storage->get_hierarchy_delimiter();
1719         $path            = explode($delimiter, $path);
1720         $result          = array();
1721
1722         foreach ($path as $idx => $dir) {
1723             $directory = implode($delimiter, array_slice($path, 0, $idx+1));
1724             if ($protect_folders && in_array($directory, $default_folders)) {
1725                 unset($result);
1726                 $result[] = $this->localize_foldername($directory);
1727             }
1728             else {
1729                 $result[] = rcube_charset::convert($dir, 'UTF7-IMAP');
1730             }
1731         }
1732
1733         return implode($delimiter, $result);
1734     }
1735
1736
1737     public static function quota_display($attrib)
1738     {
1739         $rcmail = rcmail::get_instance();
1740
1741         if (!$attrib['id']) {
1742             $attrib['id'] = 'rcmquotadisplay';
1743         }
1744
1745         $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
1746
1747         $rcmail->output->add_gui_object('quotadisplay', $attrib['id']);
1748
1749         $quota = $rcmail->quota_content($attrib);
1750
1751         $rcmail->output->add_script('rcmail.set_quota('.rcube_output::json_serialize($quota).');', 'docready');
1752
1753         return html::span($attrib, '');
1754     }
1755
1756
1757     public function quota_content($attrib = null)
1758     {
1759         $quota = $this->storage->get_quota();
1760         $quota = $this->plugins->exec_hook('quota', $quota);
1761
1762         $quota_result = (array) $quota;
1763         $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1764
1765         if (!$quota['total'] && $this->config->get('quota_zero_as_unlimited')) {
1766             $quota_result['title']   = $this->gettext('unlimited');
1767             $quota_result['percent'] = 0;
1768         }
1769         else if ($quota['total']) {
1770             if (!isset($quota['percent'])) {
1771                 $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1772             }
1773
1774             $title = sprintf('%s / %s (%.0f%%)',
1775                 $this->show_bytes($quota['used'] * 1024), $this->show_bytes($quota['total'] * 1024),
1776                 $quota_result['percent']);
1777
1778             $quota_result['title'] = $title;
1779
1780             if ($attrib['width']) {
1781                 $quota_result['width'] = $attrib['width'];
1782             }
1783             if ($attrib['height']) {
1784                 $quota_result['height']    = $attrib['height'];
1785             }
1786         }
1787         else {
1788             $quota_result['title']   = $this->gettext('unknown');
1789             $quota_result['percent'] = 0;
1790         }
1791
1792         return $quota_result;
1793     }
1794
1795
1796     /**
1797      * Outputs error message according to server error/response codes
1798      *
1799      * @param string $fallback       Fallback message label
1800      * @param array  $fallback_args  Fallback message label arguments
1801      */
1802     public function display_server_error($fallback = null, $fallback_args = null)
1803     {
1804         $err_code = $this->storage->get_error_code();
1805         $res_code = $this->storage->get_response_code();
1806
524e48 1807         if ($res_code == rcube_storage::NOPERM) {
1aceb9 1808             $this->output->show_message('errornoperm', 'error');
A 1809         }
1810         else if ($res_code == rcube_storage::READONLY) {
1811             $this->output->show_message('errorreadonly', 'error');
1812         }
1813         else if ($err_code && ($err_str = $this->storage->get_error_str())) {
1814             // try to detect access rights problem and display appropriate message
1815             if (stripos($err_str, 'Permission denied') !== false) {
1816                 $this->output->show_message('errornoperm', 'error');
1817             }
1818             else {
1819                 $this->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1820             }
1821         }
524e48 1822         else if ($err_code < 0) {
AM 1823             $this->output->show_message('storageerror', 'error');
1824         }
1aceb9 1825         else if ($fallback) {
A 1826             $this->output->show_message($fallback, 'error', $fallback_args);
1827         }
1828     }
1829
1830
1831     /**
1832      * Output HTML editor scripts
1833      *
1834      * @param string $mode  Editor mode
1835      */
1836     public function html_editor($mode = '')
1837     {
1838         $hook = $this->plugins->exec_hook('html_editor', array('mode' => $mode));
1839
1840         if ($hook['abort']) {
1841             return;
1842         }
1843
1844         $lang = strtolower($_SESSION['language']);
1845
1846         // TinyMCE uses two-letter lang codes, with exception of Chinese
1847         if (strpos($lang, 'zh_') === 0) {
1848             $lang = str_replace('_', '-', $lang);
1849         }
1850         else {
1851             $lang = substr($lang, 0, 2);
1852         }
1853
1854         if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js')) {
1855             $lang = 'en';
1856         }
1857
1858         $script = json_encode(array(
1859             'mode'       => $mode,
1860             'lang'       => $lang,
1861             'skin_path'  => $this->output->get_skin_path(),
1862             'spellcheck' => intval($this->config->get('enable_spellcheck')),
1863             'spelldict'  => intval($this->config->get('spellcheck_dictionary'))
1864         ));
1865
1866         $this->output->include_script('tiny_mce/tiny_mce.js');
1867         $this->output->include_script('editor.js');
1868         $this->output->add_script("rcmail_editor_init($script)", 'docready');
1869     }
1870
1871
1872     /**
1873      * Replaces TinyMCE's emoticon images with plain-text representation
1874      *
1875      * @param string $html  HTML content
1876      *
1877      * @return string HTML content
1878      */
1879     public static function replace_emoticons($html)
1880     {
1881         $emoticons = array(
1882             '8-)' => 'smiley-cool',
1883             ':-#' => 'smiley-foot-in-mouth',
1884             ':-*' => 'smiley-kiss',
1885             ':-X' => 'smiley-sealed',
1886             ':-P' => 'smiley-tongue-out',
1887             ':-@' => 'smiley-yell',
1888             ":'(" => 'smiley-cry',
1889             ':-(' => 'smiley-frown',
1890             ':-D' => 'smiley-laughing',
1891             ':-)' => 'smiley-smile',
1892             ':-S' => 'smiley-undecided',
1893             ':-$' => 'smiley-embarassed',
1894             'O:-)' => 'smiley-innocent',
1895             ':-|' => 'smiley-money-mouth',
1896             ':-O' => 'smiley-surprised',
1897             ';-)' => 'smiley-wink',
1898         );
1899
1900         foreach ($emoticons as $idx => $file) {
1901             // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1902             $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1903             $replace[] = $idx;
1904         }
1905
1906         return preg_replace($search, $replace, $html);
1907     }
1908
1909
1910     /**
1911      * File upload progress handler.
1912      */
1913     public function upload_progress()
1914     {
1915         $prefix = ini_get('apc.rfc1867_prefix');
1916         $params = array(
1917             'action' => $this->action,
1918             'name' => rcube_utils::get_input_value('_progress', rcube_utils::INPUT_GET),
1919         );
1920
1921         if (function_exists('apc_fetch')) {
1922             $status = apc_fetch($prefix . $params['name']);
1923
1924             if (!empty($status)) {
1925                 $status['percent'] = round($status['current']/$status['total']*100);
1926                 $params = array_merge($status, $params);
1927             }
1928         }
1929
1930         if (isset($params['percent']))
1931             $params['text'] = $this->gettext(array('name' => 'uploadprogress', 'vars' => array(
1932                 'percent' => $params['percent'] . '%',
1933                 'current' => $this->show_bytes($params['current']),
1934                 'total'   => $this->show_bytes($params['total'])
1935         )));
1936
1937         $this->output->command('upload_progress_update', $params);
1938         $this->output->send();
1939     }
1940
1941
1942     /**
1943      * Initializes file uploading interface.
1944      */
1945     public function upload_init()
1946     {
1947         // Enable upload progress bar
1948         if (($seconds = $this->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
1949             if ($field_name = ini_get('apc.rfc1867_name')) {
1950                 $this->output->set_env('upload_progress_name', $field_name);
1951                 $this->output->set_env('upload_progress_time', (int) $seconds);
1952             }
1953         }
1954
1955         // find max filesize value
1956         $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
1957         $max_postsize = parse_bytes(ini_get('post_max_size'));
1958         if ($max_postsize && $max_postsize < $max_filesize) {
1959             $max_filesize = $max_postsize;
1960         }
1961
1962         $this->output->set_env('max_filesize', $max_filesize);
1963         $max_filesize = self::show_bytes($max_filesize);
1964         $this->output->set_env('filesizeerror', $this->gettext(array(
1965             'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
1966
1967         return $max_filesize;
1968     }
1969
1970
1971     /**
1972      * Initializes client-side autocompletion.
1973      */
1974     public function autocomplete_init()
1975     {
1976         static $init;
1977
1978         if ($init) {
1979             return;
1980         }
1981
1982         $init = 1;
1983
1984         if (($threads = (int)$this->config->get('autocomplete_threads')) > 0) {
1985             $book_types = (array) $this->config->get('autocomplete_addressbooks', 'sql');
1986             if (count($book_types) > 1) {
1987                 $this->output->set_env('autocomplete_threads', $threads);
1988                 $this->output->set_env('autocomplete_sources', $book_types);
1989             }
1990         }
1991
1992         $this->output->set_env('autocomplete_max', (int)$this->config->get('autocomplete_max', 15));
1993         $this->output->set_env('autocomplete_min_length', $this->config->get('autocomplete_min_length'));
1994         $this->output->add_label('autocompletechars', 'autocompletemore');
1995     }
1996
1997
1998     /**
1999      * Returns supported font-family specifications
2000      *
2001      * @param string $font  Font name
2002      *
2003      * @param string|array Font-family specification array or string (if $font is used)
2004      */
2005     public static function font_defs($font = null)
2006     {
2007         $fonts = array(
2008             'Andale Mono'   => '"Andale Mono",Times,monospace',
2009             'Arial'         => 'Arial,Helvetica,sans-serif',
2010             'Arial Black'   => '"Arial Black","Avant Garde",sans-serif',
2011             'Book Antiqua'  => '"Book Antiqua",Palatino,serif',
2012             'Courier New'   => '"Courier New",Courier,monospace',
2013             'Georgia'       => 'Georgia,Palatino,serif',
2014             'Helvetica'     => 'Helvetica,Arial,sans-serif',
2015             'Impact'        => 'Impact,Chicago,sans-serif',
2016             'Tahoma'        => 'Tahoma,Arial,Helvetica,sans-serif',
2017             'Terminal'      => 'Terminal,Monaco,monospace',
2018             'Times New Roman' => '"Times New Roman",Times,serif',
2019             'Trebuchet MS'  => '"Trebuchet MS",Geneva,sans-serif',
2020             'Verdana'       => 'Verdana,Geneva,sans-serif',
2021         );
2022
2023         if ($font) {
2024             return $fonts[$font];
2025         }
2026
2027         return $fonts;
2028     }
2029
2030
2031     /**
2032      * Create a human readable string for a number of bytes
2033      *
2034      * @param int Number of bytes
2035      *
2036      * @return string Byte string
2037      */
2038     public function show_bytes($bytes)
2039     {
2040         if ($bytes >= 1073741824) {
2041             $gb  = $bytes/1073741824;
2042             $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . $this->gettext('GB');
2043         }
2044         else if ($bytes >= 1048576) {
2045             $mb  = $bytes/1048576;
2046             $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . $this->gettext('MB');
2047         }
2048         else if ($bytes >= 1024) {
2049             $str = sprintf("%d ",  round($bytes/1024)) . $this->gettext('KB');
2050         }
2051         else {
2052             $str = sprintf('%d ', $bytes) . $this->gettext('B');
2053         }
2054
2055         return $str;
2056     }
2057
2058
2059     /**
8749e9 2060      * Returns real size (calculated) of the message part
AM 2061      *
2062      * @param rcube_message_part  Message part
2063      *
2064      * @return string Part size (and unit)
2065      */
2066     public function message_part_size($part)
2067     {
2068         if (isset($part->d_parameters['size'])) {
2069             $size = $this->show_bytes((int)$part->d_parameters['size']);
2070         }
2071         else {
2072           $size = $part->size;
2073           if ($part->encoding == 'base64') {
2074             $size = $size / 1.33;
2075           }
2076
2077           $size = '~' . $this->show_bytes($size);
2078         }
2079
2080         return $size;
2081     }
2082
2083
1aceb9 2084     /************************************************************************
A 2085      *********          Deprecated methods (to be removed)          *********
2086      ***********************************************************************/
2087
2088     public static function setcookie($name, $value, $exp = 0)
2089     {
2090         rcube_utils::setcookie($name, $value, $exp);
2091     }
38a08c 2092
AM 2093     public function imap_connect()
2094     {
2095         return $this->storage_connect();
2096     }
5a575b 2097
b97f21 2098     public function imap_init()
TB 2099     {
2100         return $this->storage_init();
2101     }
2102
5a575b 2103     /**
AM 2104      * Connect to the mail storage server with stored session data
2105      *
2106      * @return bool True on success, False on error
2107      */
2108     public function storage_connect()
2109     {
2110         $storage = $this->get_storage();
2111
2112         if ($_SESSION['storage_host'] && !$storage->is_connected()) {
2113             $host = $_SESSION['storage_host'];
2114             $user = $_SESSION['username'];
2115             $port = $_SESSION['storage_port'];
2116             $ssl  = $_SESSION['storage_ssl'];
2117             $pass = $this->decrypt($_SESSION['password']);
2118
2119             if (!$storage->connect($host, $user, $pass, $port, $ssl)) {
2120                 if (is_object($this->output)) {
7ea292 2121                     $this->output->show_message('storageerror', 'error');
5a575b 2122                 }
AM 2123             }
2124             else {
2125                 $this->set_storage_prop();
2126             }
2127         }
2128
5f6c71 2129         return $storage->is_connected();
5a575b 2130     }
197601 2131 }