thomascube
2011-08-18 fbe54043cf598b19a753dc2b21a7ed558d23fd15
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                     |
63e992 8  | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
197601 9  | Licensed under the GNU GPL                                            |
T 10  |                                                                       |
11  | PURPOSE:                                                              |
12  |   Application class providing core functions and holding              |
13  |   instances of all 'global' objects like db- and imap-connections     |
14  +-----------------------------------------------------------------------+
15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16  +-----------------------------------------------------------------------+
17
638fb8 18  $Id$
197601 19
T 20 */
21
22
23 /**
e019f2 24  * Application class of Roundcube Webmail
197601 25  * implemented as singleton
T 26  *
27  * @package Core
28  */
29 class rcmail
30 {
5c461b 31   /**
A 32    * Main tasks.
33    *
34    * @var array
35    */
677e1f 36   static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
A 37
5c461b 38   /**
A 39    * Singleton instace of rcmail
40    *
41    * @var rcmail
42    */
197601 43   static private $instance;
677e1f 44
5c461b 45   /**
A 46    * Stores instance of rcube_config.
47    *
48    * @var rcube_config
49    */
197601 50   public $config;
5c461b 51
A 52   /**
53    * Stores rcube_user instance.
54    *
55    * @var rcube_user
56    */
197601 57   public $user;
5c461b 58
A 59   /**
60    * Instace of database class.
61    *
62    * @var rcube_mdb2
63    */
197601 64   public $db;
5c461b 65
A 66   /**
76d401 67    * Instace of Memcache class.
T 68    *
69    * @var rcube_mdb2
70    */
71   public $memcache;
72
73   /**
5c461b 74    * Instace of rcube_session class.
A 75    *
76    * @var rcube_session
77    */
929a50 78   public $session;
5c461b 79
A 80   /**
81    * Instance of rcube_smtp class.
82    *
83    * @var rcube_smtp
84    */
2c3d81 85   public $smtp;
5c461b 86
A 87   /**
88    * Instance of rcube_imap class.
89    *
90    * @var rcube_imap
91    */
197601 92   public $imap;
5c461b 93
A 94   /**
95    * Instance of rcube_template class.
96    *
97    * @var rcube_template
98    */
197601 99   public $output;
5c461b 100
A 101   /**
102    * Instance of rcube_plugin_api.
103    *
104    * @var rcube_plugin_api
105    */
cc97ea 106   public $plugins;
5c461b 107
A 108   /**
109    * Current task.
110    *
111    * @var string
112    */
9b94eb 113   public $task;
5c461b 114
A 115   /**
116    * Current action.
117    *
118    * @var string
119    */
197601 120   public $action = '';
T 121   public $comm_path = './';
677e1f 122
197601 123   private $texts;
0501b6 124   private $address_books = array();
5cf5ee 125   private $caches = array();
68d2d5 126   private $action_map = array();
580adc 127   private $shutdown_functions = array();
677e1f 128
A 129
197601 130   /**
T 131    * This implements the 'singleton' design pattern
132    *
5c461b 133    * @return rcmail The one and only instance
197601 134    */
T 135   static function get_instance()
136   {
137     if (!self::$instance) {
138       self::$instance = new rcmail();
139       self::$instance->startup();  // init AFTER object was linked with self::$instance
140     }
141
142     return self::$instance;
143   }
b62a0d 144
A 145
197601 146   /**
T 147    * Private constructor
148    */
149   private function __construct()
150   {
151     // load configuration
152     $this->config = new rcube_config();
b62a0d 153
197601 154     register_shutdown_function(array($this, 'shutdown'));
T 155   }
b62a0d 156
A 157
197601 158   /**
T 159    * Initial startup function
160    * to register session, create database and imap connections
161    *
162    * @todo Remove global vars $DB, $USER
163    */
164   private function startup()
165   {
b77d0d 166     // initialize syslog
A 167     if ($this->config->get('log_driver') == 'syslog') {
168       $syslog_id = $this->config->get('syslog_id', 'roundcube');
169       $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
170       openlog($syslog_id, LOG_ODELAY, $syslog_facility);
171     }
cc97ea 172
197601 173     // connect to database
T 174     $GLOBALS['DB'] = $this->get_dbh();
175
929a50 176     // start session
A 177     $this->session_init();
197601 178
T 179     // create user object
180     $this->set_user(new rcube_user($_SESSION['user_id']));
929a50 181
A 182     // configure session (after user config merge!)
183     $this->session_configure();
197601 184
9b94eb 185     // set task and action properties
A 186     $this->set_task(get_input_value('_task', RCUBE_INPUT_GPC));
187     $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
188
197601 189     // reset some session parameters when changing task
677e1f 190     if ($this->task != 'utils') {
A 191       if ($this->session && $_SESSION['task'] != $this->task)
192         $this->session->remove('page');
193       // set current task to session
194       $_SESSION['task'] = $this->task;
195     }
197601 196
48bc52 197     // init output class
A 198     if (!empty($_REQUEST['_remote']))
929a50 199       $GLOBALS['OUTPUT'] = $this->json_init();
48bc52 200     else
A 201       $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
202
cc97ea 203     // create plugin API and load plugins
T 204     $this->plugins = rcube_plugin_api::get_instance();
48bc52 205
A 206     // init plugins
207     $this->plugins->init();
197601 208   }
b62a0d 209
A 210
197601 211   /**
T 212    * Setter for application task
213    *
214    * @param string Task to set
215    */
216   public function set_task($task)
217   {
1c932d 218     $task = asciiwords($task);
9b94eb 219
A 220     if ($this->user && $this->user->ID)
c3be8e 221       $task = !$task ? 'mail' : $task;
9b94eb 222     else
A 223       $task = 'login';
224
225     $this->task = $task;
1c932d 226     $this->comm_path = $this->url(array('task' => $this->task));
b62a0d 227
197601 228     if ($this->output)
1c932d 229       $this->output->set_env('task', $this->task);
197601 230   }
b62a0d 231
A 232
197601 233   /**
T 234    * Setter for system user object
235    *
5c461b 236    * @param rcube_user Current user instance
197601 237    */
T 238   public function set_user($user)
239   {
240     if (is_object($user)) {
241       $this->user = $user;
242       $GLOBALS['USER'] = $this->user;
b62a0d 243
197601 244       // overwrite config with user preferences
b545d3 245       $this->config->set_user_prefs((array)$this->user->get_prefs());
197601 246     }
b62a0d 247
c8ae24 248     $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
531abb 249
197601 250     // set localization
e80f50 251     setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
14de18 252
b62a0d 253     // workaround for http://bugs.php.net/bug.php?id=18556
A 254     if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
255       setlocale(LC_CTYPE, 'en_US' . '.utf8');
197601 256   }
b62a0d 257
A 258
197601 259   /**
T 260    * Check the given string and return a valid language code
261    *
262    * @param string Language code
263    * @return string Valid language code
264    */
265   private function language_prop($lang)
266   {
267     static $rcube_languages, $rcube_language_aliases;
b62a0d 268
c8ae24 269     // user HTTP_ACCEPT_LANGUAGE if no language is specified
T 270     if (empty($lang) || $lang == 'auto') {
271        $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
272        $lang = str_replace('-', '_', $accept_langs[0]);
273      }
b62a0d 274
197601 275     if (empty($rcube_languages)) {
T 276       @include(INSTALL_PATH . 'program/localization/index.inc');
277     }
b62a0d 278
197601 279     // check if we have an alias for that language
T 280     if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
281       $lang = $rcube_language_aliases[$lang];
282     }
283     // try the first two chars
c3ab75 284     else if (!isset($rcube_languages[$lang])) {
7e78b2 285       $short = substr($lang, 0, 2);
b62a0d 286
235086 287       // check if we have an alias for the short language code
T 288       if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
289         $lang = $rcube_language_aliases[$short];
290       }
c3ab75 291       // expand 'nn' to 'nn_NN'
T 292       else if (!isset($rcube_languages[$short])) {
235086 293         $lang = $short.'_'.strtoupper($short);
T 294       }
197601 295     }
T 296
1854c4 297     if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
197601 298       $lang = 'en_US';
T 299     }
300
301     return $lang;
302   }
b62a0d 303
A 304
197601 305   /**
T 306    * Get the current database connection
307    *
5c461b 308    * @return rcube_mdb2  Database connection object
197601 309    */
T 310   public function get_dbh()
311   {
312     if (!$this->db) {
313       $config_all = $this->config->all();
314
9e8e5f 315       $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
197601 316       $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
T 317       $this->db->set_debug((bool)$config_all['sql_debug']);
318     }
319
320     return $this->db;
321   }
76d401 322   
T 323   
324   /**
325    * Get global handle for memcache access
326    *
327    * @return object Memcache
328    */
329   public function get_memcache()
330   {
331     if (!isset($this->memcache)) {
332       // no memcache support in PHP
333       if (!class_exists('Memcache')) {
334         $this->memcache = false;
335         return false;
336       }
7ad8e2 337
76d401 338       $this->memcache = new Memcache;
T 339       $mc_available = 0;
340       foreach ($this->config->get('memcache_hosts', array()) as $host) {
341         list($host, $port) = explode(':', $host);
342         if (!$port) $port = 11211;
343         // add server and attempt to connect if not already done yet
344         if ($this->memcache->addServer($host, $port) && !$mc_available)
345           $mc_available += intval($this->memcache->connect($host, $port));
346       }
7ad8e2 347
76d401 348       if (!$mc_available)
T 349         $this->memcache = false;
350     }
7ad8e2 351
76d401 352     return $this->memcache;
T 353   }
b62a0d 354
A 355
197601 356   /**
5cf5ee 357    * Initialize and get cache object
A 358    *
c9f4e9 359    * @param string $name   Cache identifier
A 360    * @param string $type   Cache type ('db', 'apc' or 'memcache')
361    * @param int    $ttl    Expiration time for cache items in seconds
362    * @param bool   $packed Enables/disables data serialization
5cf5ee 363    *
A 364    * @return rcube_cache Cache object
365    */
c9f4e9 366   public function get_cache($name, $type='db', $ttl=0, $packed=true)
5cf5ee 367   {
A 368     if (!isset($this->caches[$name])) {
c9f4e9 369       $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed);
5cf5ee 370     }
A 371
372     return $this->caches[$name];
373   }
374
375
376   /**
ade8e1 377    * Return instance of the internal address book class
T 378    *
3704b7 379    * @param string  Address book identifier
ade8e1 380    * @param boolean True if the address book needs to be writeable
7f7ed2 381    *
5c461b 382    * @return rcube_contacts Address book object
ade8e1 383    */
T 384   public function get_address_book($id, $writeable = false)
385   {
b896b1 386     $contacts    = null;
ade8e1 387     $ldap_config = (array)$this->config->get('ldap_public');
b896b1 388     $abook_type  = strtolower($this->config->get('address_book_type'));
cc97ea 389
f03d89 390     // 'sql' is the alias for '0' used by autocomplete
A 391     if ($id == 'sql')
392         $id = '0';
393
0501b6 394     // use existing instance
b896b1 395     if (isset($this->address_books[$id]) && is_a($this->address_books[$id], 'rcube_addressbook') && (!$writeable || !$this->address_books[$id]->readonly)) {
0501b6 396       $contacts = $this->address_books[$id];
T 397     }
cc97ea 398     else if ($id && $ldap_config[$id]) {
010274 399       $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
cc97ea 400     }
T 401     else if ($id === '0') {
402       $contacts = new rcube_contacts($this->db, $this->user->ID);
ade8e1 403     }
b896b1 404     else {
A 405       $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
406
407       // plugin returned instance of a rcube_addressbook
408       if ($plugin['instance'] instanceof rcube_addressbook) {
409         $contacts = $plugin['instance'];
410       }
7f7ed2 411       // get first source from the list
5ed119 412       else if (!$id) {
7f7ed2 413         $source = reset($this->get_address_sources($writeable));
A 414         if (!empty($source)) {
415           $contacts = $this->get_address_book($source['id']);
416           if ($contacts)
417             $id = $source['id'];
5ed119 418         }
ade8e1 419       }
5ed119 420     }
A 421
422     if (!$contacts) {
423       raise_error(array(
782d85 424         'code' => 700, 'type' => 'php',
5ed119 425         'file' => __FILE__, 'line' => __LINE__,
A 426         'message' => "Addressbook source ($id) not found!"),
427         true, true);
ade8e1 428     }
457373 429
A 430     // add to the 'books' array for shutdown function
0501b6 431     if (!isset($this->address_books[$id]))
T 432       $this->address_books[$id] = $contacts;
b62a0d 433
ade8e1 434     return $contacts;
T 435   }
3704b7 436
A 437
438   /**
439    * Return address books list
440    *
441    * @param boolean True if the address book needs to be writeable
5c9d1f 442    *
3704b7 443    * @return array  Address books array
A 444    */
445   public function get_address_sources($writeable = false)
446   {
447     $abook_type = strtolower($this->config->get('address_book_type'));
7fdb9d 448     $ldap_config = $this->config->get('ldap_public');
A 449     $autocomplete = (array) $this->config->get('autocomplete_addressbooks');
3704b7 450     $list = array();
A 451
452     // We are using the DB address book
453     if ($abook_type != 'ldap') {
0501b6 454       if (!isset($this->address_books['0']))
T 455         $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID);
3704b7 456       $list['0'] = array(
5c9d1f 457         'id'       => '0',
A 458         'name'     => rcube_label('personaladrbook'),
459         'groups'   => $this->address_books['0']->groups,
8c263e 460         'readonly' => $this->address_books['0']->readonly,
a61bbb 461         'autocomplete' => in_array('sql', $autocomplete)
3704b7 462       );
A 463     }
464
7fdb9d 465     if ($ldap_config) {
A 466       $ldap_config = (array) $ldap_config;
3704b7 467       foreach ($ldap_config as $id => $prop)
A 468         $list[$id] = array(
5c9d1f 469           'id'       => $id,
A 470           'name'     => $prop['name'],
471           'groups'   => is_array($prop['groups']),
a61bbb 472           'readonly' => !$prop['writable'],
5c9d1f 473           'hidden'   => $prop['hidden'],
A 474           'autocomplete' => in_array($id, $autocomplete)
3704b7 475         );
A 476     }
477
e6ce00 478     $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
3704b7 479     $list = $plugin['sources'];
A 480
0501b6 481     foreach ($list as $idx => $item) {
T 482       // register source for shutdown function
483       if (!is_object($this->address_books[$item['id']]))
484         $this->address_books[$item['id']] = $item;
485       // remove from list if not writeable as requested
486       if ($writeable && $item['readonly'])
c0297f 487           unset($list[$idx]);
3704b7 488     }
8c263e 489
3704b7 490     return $list;
A 491   }
b62a0d 492
A 493
ade8e1 494   /**
197601 495    * Init output object for GUI and add common scripts.
T 496    * This will instantiate a rcmail_template object and set
497    * environment vars according to the current session and configuration
0ece58 498    *
T 499    * @param boolean True if this request is loaded in a (i)frame
5c461b 500    * @return rcube_template Reference to HTML output object
197601 501    */
T 502   public function load_gui($framed = false)
503   {
504     // init output page
0ece58 505     if (!($this->output instanceof rcube_template))
T 506       $this->output = new rcube_template($this->task, $framed);
197601 507
95d90f 508     // set keep-alive/check-recent interval
bf67d6 509     if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
929a50 510       $this->output->set_env('keep_alive', $keep_alive);
95d90f 511     }
197601 512
T 513     if ($framed) {
514       $this->comm_path .= '&_framed=1';
515       $this->output->set_env('framed', true);
516     }
517
518     $this->output->set_env('task', $this->task);
519     $this->output->set_env('action', $this->action);
520     $this->output->set_env('comm_path', $this->comm_path);
79c45f 521     $this->output->set_charset(RCMAIL_CHARSET);
197601 522
7f5a84 523     // add some basic labels to client
74d421 524     $this->output->add_label('loading', 'servererror');
b62a0d 525
197601 526     return $this->output;
T 527   }
b62a0d 528
A 529
197601 530   /**
T 531    * Create an output object for JSON responses
0ece58 532    *
5c461b 533    * @return rcube_json_output Reference to JSON output object
197601 534    */
929a50 535   public function json_init()
197601 536   {
0ece58 537     if (!($this->output instanceof rcube_json_output))
T 538       $this->output = new rcube_json_output($this->task);
b62a0d 539
197601 540     return $this->output;
2c3d81 541   }
A 542
543
544   /**
545    * Create SMTP object and connect to server
546    *
547    * @param boolean True if connection should be established
548    */
549   public function smtp_init($connect = false)
550   {
551     $this->smtp = new rcube_smtp();
b62a0d 552
2c3d81 553     if ($connect)
A 554       $this->smtp->connect();
197601 555   }
b62a0d 556
A 557
197601 558   /**
T 559    * Create global IMAP object and connect to server
560    *
561    * @param boolean True if connection should be established
562    * @todo Remove global $IMAP
563    */
1854c4 564   public function imap_init($connect = false)
197601 565   {
47d8d3 566     // already initialized
T 567     if (is_object($this->imap))
568       return;
b62a0d 569
5cf5ee 570     $this->imap = new rcube_imap();
197601 571     $this->imap->debug_level = $this->config->get('debug_level');
T 572     $this->imap->skip_deleted = $this->config->get('skip_deleted');
573
574     // enable caching of imap data
5cf5ee 575     $imap_cache = $this->config->get('imap_cache');
A 576     $messages_cache = $this->config->get('messages_cache');
577     // for backward compatybility
578     if ($imap_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
579         $imap_cache     = 'db';
580         $messages_cache = true;
197601 581     }
5cf5ee 582     if ($imap_cache)
A 583         $this->imap->set_caching($imap_cache);
584     if ($messages_cache)
585         $this->imap->set_messages_caching(true);
197601 586
T 587     // set pagesize from config
588     $this->imap->set_pagesize($this->config->get('pagesize', 50));
b62a0d 589
600981 590     // Setting root and delimiter before establishing the connection
b62a0d 591     // can save time detecting them using NAMESPACE and LIST
230f94 592     $options = array(
bdab2c 593       'auth_method' => $this->config->get('imap_auth_type', 'check'),
a1fe6b 594       'auth_cid'    => $this->config->get('imap_auth_cid'),
A 595       'auth_pw'     => $this->config->get('imap_auth_pw'),
7f1da4 596       'debug'       => (bool) $this->config->get('imap_debug', 0),
f07d23 597       'force_caps'  => (bool) $this->config->get('imap_force_caps'),
A 598       'timeout'     => (int) $this->config->get('imap_timeout', 0),
230f94 599     );
76db10 600
230f94 601     $this->imap->set_options($options);
b62a0d 602
197601 603     // set global object for backward compatibility
T 604     $GLOBALS['IMAP'] = $this->imap;
48bc52 605
A 606     $hook = $this->plugins->exec_hook('imap_init', array('fetch_headers' => $this->imap->fetch_add_headers));
607     if ($hook['fetch_headers'])
608       $this->imap->fetch_add_headers = $hook['fetch_headers'];
b62a0d 609
47d8d3 610     // support this parameter for backward compatibility but log warning
T 611     if ($connect) {
1854c4 612       $this->imap_connect();
13ffa2 613       raise_error(array(
A 614         'code' => 800, 'type' => 'imap',
615         'file' => __FILE__, 'line' => __LINE__,
616         'message' => "rcube::imap_init(true) is deprecated, use rcube::imap_connect() instead"),
617         true, false);
47d8d3 618     }
1854c4 619   }
T 620
621
622   /**
623    * Connect to IMAP server with stored session data
624    *
625    * @return bool True on success, false on error
626    */
627   public function imap_connect()
628   {
48bc52 629     if (!$this->imap)
A 630       $this->imap_init();
b62a0d 631
59c216 632     if ($_SESSION['imap_host'] && !$this->imap->conn->connected()) {
A 633       if (!$this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])) {
1854c4 634         if ($this->output)
0f0c17 635           $this->output->show_message($this->imap->get_error_code() == -1 ? 'imaperror' : 'sessionerror', 'error');
1854c4 636       }
59c216 637       else {
A 638         $this->set_imap_prop();
639         return $this->imap->conn;
640       }
1854c4 641     }
T 642
59c216 643     return false;
929a50 644   }
A 645
646
647   /**
648    * Create session object and start the session.
649    */
650   public function session_init()
651   {
bf67d6 652     // session started (Installer?)
A 653     if (session_id())
654       return;
655
929a50 656     // set session domain
A 657     if ($domain = $this->config->get('session_domain')) {
658       ini_set('session.cookie_domain', $domain);
659     }
660     // set session garbage collecting time according to session_lifetime
63e992 661     $lifetime = $this->config->get('session_lifetime', 0) * 60;
929a50 662     if ($lifetime) {
A 663       ini_set('session.gc_maxlifetime', $lifetime * 2);
664     }
665
666     ini_set('session.cookie_secure', rcube_https_check());
667     ini_set('session.name', 'roundcube_sessid');
668     ini_set('session.use_cookies', 1);
b62a0d 669     ini_set('session.use_only_cookies', 1);
929a50 670     ini_set('session.serialize_handler', 'php');
A 671
672     // use database for storing session data
63e992 673     $this->session = new rcube_session($this->get_dbh(), $this->config);
929a50 674
A 675     $this->session->register_gc_handler('rcmail_temp_gc');
676     if ($this->config->get('enable_caching'))
677       $this->session->register_gc_handler('rcmail_cache_gc');
678
679     // start PHP session (if not in CLI mode)
680     if ($_SERVER['REMOTE_ADDR'])
681       session_start();
682
683     // set initial session vars
cf2da2 684     if (!$_SESSION['user_id'])
929a50 685       $_SESSION['temp'] = true;
A 686   }
687
688
689   /**
690    * Configure session object internals
691    */
692   public function session_configure()
693   {
bf67d6 694     if (!$this->session)
A 695       return;
696
929a50 697     $lifetime = $this->config->get('session_lifetime', 0) * 60;
A 698
699     // set keep-alive/check-recent interval
700     if ($keep_alive = $this->config->get('keep_alive')) {
701       // be sure that it's less than session lifetime
702       if ($lifetime)
703         $keep_alive = min($keep_alive, $lifetime - 30);
704       $keep_alive = max(60, $keep_alive);
705       $this->session->set_keep_alive($keep_alive);
706     }
cf2da2 707     
T 708     $this->session->set_secret($this->config->get('des_key') . $_SERVER['HTTP_USER_AGENT']);
709     $this->session->set_ip_check($this->config->get('ip_check'));
197601 710   }
T 711
712
713   /**
714    * Perfom login to the IMAP server and to the webmail service.
715    * This will also create a new user entry if auto_create_user is configured.
716    *
717    * @param string IMAP user name
718    * @param string IMAP password
719    * @param string IMAP host
720    * @return boolean True on success, False on failure
721    */
722   function login($username, $pass, $host=NULL)
723   {
724     $user = NULL;
725     $config = $this->config->all();
726
727     if (!$host)
728       $host = $config['default_host'];
729
730     // Validate that selected host is in the list of configured hosts
731     if (is_array($config['default_host'])) {
732       $allowed = false;
733       foreach ($config['default_host'] as $key => $host_allowed) {
734         if (!is_numeric($key))
735           $host_allowed = $key;
736         if ($host == $host_allowed) {
737           $allowed = true;
738           break;
739         }
740       }
741       if (!$allowed)
742         return false;
743       }
bb8721 744     else if (!empty($config['default_host']) && $host != rcube_parse_host($config['default_host']))
197601 745       return false;
T 746
747     // parse $host URL
748     $a_host = parse_url($host);
749     if ($a_host['host']) {
750       $host = $a_host['host'];
751       $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
e99991 752       if (!empty($a_host['port']))
f86e8f 753         $imap_port = $a_host['port'];
904809 754       else if ($imap_ssl && $imap_ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
f86e8f 755         $imap_port = 993;
197601 756     }
b62a0d 757
f86e8f 758     $imap_port = $imap_port ? $imap_port : $config['default_port'];
197601 759
b62a0d 760     /* Modify username with domain if required
197601 761        Inspired by Marco <P0L0_notspam_binware.org>
T 762     */
763     // Check if we need to add domain
c16fab 764     if (!empty($config['username_domain']) && strpos($username, '@') === false) {
197601 765       if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
a76cbd 766         $username .= '@'.rcube_parse_host($config['username_domain'][$host], $host);
197601 767       else if (is_string($config['username_domain']))
a76cbd 768         $username .= '@'.rcube_parse_host($config['username_domain'], $host);
197601 769     }
T 770
e17553 771     // Convert username to lowercase. If IMAP backend
A 772     // is case-insensitive we need to store always the same username (#1487113)
773     if ($config['login_lc']) {
774       $username = mb_strtolower($username);
775     }
776
942069 777     // try to resolve email address from virtuser table
e17553 778     if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
A 779       $username = $virtuser;
780     }
197601 781
f1adbf 782     // Here we need IDNA ASCII
A 783     // Only rcube_contacts class is using domain names in Unicode
e8d5bd 784     $host = rcube_idn_to_ascii($host);
f1adbf 785     if (strpos($username, '@')) {
8f94b1 786       // lowercase domain name
A 787       list($local, $domain) = explode('@', $username);
788       $username = $local . '@' . mb_strtolower($domain);
e8d5bd 789       $username = rcube_idn_to_ascii($username);
f1adbf 790     }
A 791
197601 792     // user already registered -> overwrite username
T 793     if ($user = rcube_user::query($username, $host))
794       $username = $user->data['username'];
795
48bc52 796     if (!$this->imap)
A 797       $this->imap_init();
798
6d94ab 799     // try IMAP login
T 800     if (!($imap_login = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl))) {
f1adbf 801       // try with lowercase
6d94ab 802       $username_lc = mb_strtolower($username);
e17553 803       if ($username_lc != $username) {
A 804         // try to find user record again -> overwrite username
805         if (!$user && ($user = rcube_user::query($username_lc, $host)))
806           $username_lc = $user->data['username'];
807
808         if ($imap_login = $this->imap->connect($host, $username_lc, $pass, $imap_port, $imap_ssl))
809           $username = $username_lc;
810       }
6d94ab 811     }
T 812
197601 813     // exit if IMAP login failed
6d94ab 814     if (!$imap_login)
197601 815       return false;
T 816
b5846e 817     $this->set_imap_prop();
A 818
197601 819     // user already registered -> update user's record
T 820     if (is_object($user)) {
d08333 821       // fix some old settings according to namespace prefix
A 822       $this->fix_namespace_settings($user);
823
b5846e 824       // create default folders on first login
A 825       if (!$user->data['last_login'] && $config['create_default_folders'])
826         $this->imap->create_default_folders();
d08333 827       // update last login timestamp
197601 828       $user->touch();
T 829     }
830     // create new system user
831     else if ($config['auto_create_user']) {
832       if ($created = rcube_user::create($username, $host)) {
833         $user = $created;
b5846e 834         // create default folders on first login
A 835         if ($config['create_default_folders'])
836           $this->imap->create_default_folders();
197601 837       }
f879f4 838       else {
T 839         raise_error(array(
782d85 840           'code' => 620, 'type' => 'php',
6d94ab 841           'file' => __FILE__, 'line' => __LINE__,
f879f4 842           'message' => "Failed to create a user record. Maybe aborted by a plugin?"
10eedb 843           ), true, false);
f879f4 844       }
197601 845     }
T 846     else {
847       raise_error(array(
782d85 848         'code' => 621, 'type' => 'php',
10eedb 849         'file' => __FILE__, 'line' => __LINE__,
782d85 850         'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
197601 851         ), true, false);
T 852     }
853
854     // login succeeded
855     if (is_object($user) && $user->ID) {
856       $this->set_user($user);
88ca38 857       $this->session_configure();
197601 858
T 859       // set session vars
860       $_SESSION['user_id']   = $user->ID;
861       $_SESSION['username']  = $user->data['username'];
862       $_SESSION['imap_host'] = $host;
863       $_SESSION['imap_port'] = $imap_port;
864       $_SESSION['imap_ssl']  = $imap_ssl;
2471d3 865       $_SESSION['password']  = $this->encrypt($pass);
197601 866       $_SESSION['login_time'] = mktime();
cf2da2 867       
b62a0d 868       if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
c8ae24 869         $_SESSION['timezone'] = floatval($_REQUEST['_timezone']);
197601 870
T 871       // force reloading complete list of subscribed mailboxes
ccc059 872       $this->imap->clear_cache('mailboxes', true);
197601 873
T 874       return true;
875     }
876
877     return false;
878   }
879
880
881   /**
882    * Set root dir and last stored mailbox
883    * This must be done AFTER connecting to the server!
884    */
885   public function set_imap_prop()
886   {
887     $this->imap->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
888
889     if ($default_folders = $this->config->get('default_imap_folders')) {
890       $this->imap->set_default_mailboxes($default_folders);
891     }
448409 892     if (isset($_SESSION['mbox'])) {
197601 893       $this->imap->set_mailbox($_SESSION['mbox']);
T 894     }
895     if (isset($_SESSION['page'])) {
896       $this->imap->set_page($_SESSION['page']);
897     }
898   }
899
1854c4 900
T 901   /**
902    * Auto-select IMAP host based on the posted login information
903    *
904    * @return string Selected IMAP host
905    */
906   public function autoselect_host()
907   {
908     $default_host = $this->config->get('default_host');
257f88 909     $host = null;
b62a0d 910
257f88 911     if (is_array($default_host)) {
T 912       $post_host = get_input_value('_host', RCUBE_INPUT_POST);
b62a0d 913
257f88 914       // direct match in default_host array
T 915       if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
916         $host = $post_host;
917       }
b62a0d 918
257f88 919       // try to select host by mail domain
1854c4 920       list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST));
T 921       if (!empty($domain)) {
257f88 922         foreach ($default_host as $imap_host => $mail_domains) {
1854c4 923           if (is_array($mail_domains) && in_array($domain, $mail_domains)) {
T 924             $host = $imap_host;
925             break;
926           }
927         }
928       }
929
930       // take the first entry if $host is still an array
257f88 931       if (empty($host)) {
T 932         $host = array_shift($default_host);
933       }
934     }
935     else if (empty($default_host)) {
936       $host = get_input_value('_host', RCUBE_INPUT_POST);
1854c4 937     }
eec34e 938     else
bb8721 939       $host = rcube_parse_host($default_host);
1854c4 940
T 941     return $host;
942   }
943
944
945   /**
946    * Get localized text in the desired language
947    *
948    * @param mixed Named parameters array or label name
949    * @return string Localized text
950    */
cc97ea 951   public function gettext($attrib, $domain=null)
1854c4 952   {
T 953     // load localization files if not done yet
954     if (empty($this->texts))
955       $this->load_language();
b62a0d 956
1854c4 957     // extract attributes
T 958     if (is_string($attrib))
959       $attrib = array('name' => $attrib);
960
961     $nr = is_numeric($attrib['nr']) ? $attrib['nr'] : 1;
9898fe 962     $name = $attrib['name'] ? $attrib['name'] : '';
ae39c4 963     
T 964     // attrib contain text values: use them from now
965     if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
966         $this->texts[$name] = $setval;
1854c4 967
cc97ea 968     // check for text with domain
9898fe 969     if ($domain && ($text_item = $this->texts[$domain.'.'.$name]))
cc97ea 970       ;
1854c4 971     // text does not exist
9898fe 972     else if (!($text_item = $this->texts[$name])) {
A 973       return "[$name]";
1854c4 974     }
T 975
b62a0d 976     // make text item array
1854c4 977     $a_text_item = is_array($text_item) ? $text_item : array('single' => $text_item);
T 978
979     // decide which text to use
980     if ($nr == 1) {
981       $text = $a_text_item['single'];
982     }
983     else if ($nr > 0) {
984       $text = $a_text_item['multiple'];
985     }
986     else if ($nr == 0) {
987       if ($a_text_item['none'])
988         $text = $a_text_item['none'];
989       else if ($a_text_item['single'])
990         $text = $a_text_item['single'];
991       else if ($a_text_item['multiple'])
992         $text = $a_text_item['multiple'];
993     }
994
995     // default text is single
996     if ($text == '') {
997       $text = $a_text_item['single'];
998     }
999
1000     // replace vars in text
1001     if (is_array($attrib['vars'])) {
1002       foreach ($attrib['vars'] as $var_key => $var_value)
9898fe 1003         $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
1854c4 1004     }
T 1005
1006     // format output
1007     if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
1008       return ucfirst($text);
1009     else if ($attrib['uppercase'])
2aa2b3 1010       return mb_strtoupper($text);
1854c4 1011     else if ($attrib['lowercase'])
2aa2b3 1012       return mb_strtolower($text);
1854c4 1013
T 1014     return $text;
1015   }
1016
1017
1018   /**
07b95d 1019    * Check if the given text lable exists
T 1020    *
1021    * @param string Label name
1022    * @return boolean True if text exists (either in the current language or in en_US)
1023    */
1024   public function text_exists($name, $domain=null)
1025   {
1026     // load localization files if not done yet
1027     if (empty($this->texts))
1028       $this->load_language();
1029
1030     // check for text with domain first
1031     return ($domain && isset($this->texts[$domain.'.'.$name])) || isset($this->texts[$name]);
1032   }
1033
1034   /**
1854c4 1035    * Load a localization package
T 1036    *
1037    * @param string Language ID
1038    */
cc97ea 1039   public function load_language($lang = null, $add = array())
1854c4 1040   {
c8ae24 1041     $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
b62a0d 1042
1854c4 1043     // load localized texts
T 1044     if (empty($this->texts) || $lang != $_SESSION['language']) {
1045       $this->texts = array();
1046
7c9850 1047       // handle empty lines after closing PHP tag in localization files
A 1048       ob_start();
1049
1854c4 1050       // get english labels (these should be complete)
T 1051       @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
1052       @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
1053
1054       if (is_array($labels))
9d9f8d 1055         $this->texts = $labels;
1854c4 1056       if (is_array($messages))
9d9f8d 1057         $this->texts = array_merge($this->texts, $messages);
1854c4 1058
T 1059       // include user language files
1060       if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
1061         include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
1062         include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
1063
1064         if (is_array($labels))
1065           $this->texts = array_merge($this->texts, $labels);
1066         if (is_array($messages))
1067           $this->texts = array_merge($this->texts, $messages);
1068       }
b62a0d 1069
7c9850 1070       ob_end_clean();
A 1071
1854c4 1072       $_SESSION['language'] = $lang;
T 1073     }
cc97ea 1074
T 1075     // append additional texts (from plugin)
1076     if (is_array($add) && !empty($add))
1077       $this->texts += $add;
1854c4 1078   }
T 1079
1080
1081   /**
1082    * Read directory program/localization and return a list of available languages
1083    *
1084    * @return array List of available localizations
1085    */
1086   public function list_languages()
1087   {
1088     static $sa_languages = array();
1089
1090     if (!sizeof($sa_languages)) {
1091       @include(INSTALL_PATH . 'program/localization/index.inc');
1092
1093       if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
1094         while (($name = readdir($dh)) !== false) {
2aa2b3 1095           if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
1854c4 1096             continue;
T 1097
1098           if ($label = $rcube_languages[$name])
7d5178 1099             $sa_languages[$name] = $label;
1854c4 1100         }
T 1101         closedir($dh);
1102       }
1103     }
1104
1105     return $sa_languages;
1106   }
1107
1108
1109   /**
1110    * Destroy session data and remove cookie
1111    */
1112   public function kill_session()
1113   {
e6ce00 1114     $this->plugins->exec_hook('session_destroy');
b62a0d 1115
cf2da2 1116     $this->session->kill();
T 1117     $_SESSION = array('language' => $this->user->language, 'temp' => true);
1854c4 1118     $this->user->reset();
T 1119   }
1120
1121
1122   /**
1123    * Do server side actions on logout
1124    */
1125   public function logout_actions()
1126   {
1127     $config = $this->config->all();
b62a0d 1128
A 1129     // on logout action we're not connected to imap server
1854c4 1130     if (($config['logout_purge'] && !empty($config['trash_mbox'])) || $config['logout_expunge']) {
cf2da2 1131       if (!$this->session->check_auth())
1854c4 1132         return;
T 1133
47d8d3 1134       $this->imap_connect();
1854c4 1135     }
T 1136
1137     if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
1138       $this->imap->clear_mailbox($config['trash_mbox']);
1139     }
1140
1141     if ($config['logout_expunge']) {
1142       $this->imap->expunge('INBOX');
1143     }
40a186 1144
A 1145     // Try to save unsaved user preferences
1146     if (!empty($_SESSION['preferences'])) {
1147       $this->user->save_prefs(unserialize($_SESSION['preferences']));
1148     }
1854c4 1149   }
T 1150
1151
1152   /**
1153    * Function to be executed in script shutdown
1154    * Registered with register_shutdown_function()
1155    */
197601 1156   public function shutdown()
T 1157   {
580adc 1158     foreach ($this->shutdown_functions as $function)
A 1159       call_user_func($function);
1160
2c3d81 1161     if (is_object($this->smtp))
A 1162       $this->smtp->disconnect();
1163
0501b6 1164     foreach ($this->address_books as $book) {
T 1165       if (is_a($book, 'rcube_addressbook'))
457373 1166         $book->close();
0501b6 1167     }
197601 1168
5cf5ee 1169     foreach ($this->caches as $cache) {
A 1170         if (is_object($cache))
1171             $cache->close();
1172     }
1173
dd07e7 1174     if (is_object($this->imap))
A 1175       $this->imap->close();
1176
197601 1177     // before closing the database connection, write session data
609c5e 1178     if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
4591de 1179       $this->session->cleanup();
75da0b 1180       session_write_close();
4591de 1181     }
2b35c5 1182
A 1183     // write performance stats to logs/console
1184     if ($this->config->get('devel_mode')) {
1185       if (function_exists('memory_get_usage'))
1186         $mem = show_bytes(memory_get_usage());
1187       if (function_exists('memory_get_peak_usage'))
1188         $mem .= '/'.show_bytes(memory_get_peak_usage());
1189
1190       $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
bf67d6 1191       if (defined('RCMAIL_START'))
A 1192         rcube_print_time(RCMAIL_START, $log);
1193       else
1194         console($log);
2b35c5 1195     }
197601 1196   }
b62a0d 1197
A 1198
1854c4 1199   /**
580adc 1200    * Registers shutdown function to be executed on shutdown.
A 1201    * The functions will be executed before destroying any
1202    * objects like smtp, imap, session, etc.
1203    *
1204    * @param callback Function callback
1205    */
1206   public function add_shutdown_function($function)
1207   {
1208     $this->shutdown_functions[] = $function;
1209   }
1210
1211
1212   /**
57f0c8 1213    * Generate a unique token to be used in a form request
T 1214    *
1215    * @return string The request token
1216    */
549933 1217   public function get_request_token()
57f0c8 1218   {
ec045b 1219     $sess_id = $_COOKIE[ini_get('session.name')];
c9f2c4 1220     if (!$sess_id) $sess_id = session_id();
ef27a6 1221     $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->task . $this->config->get('des_key') . $sess_id)));
T 1222     return $plugin['value'];
57f0c8 1223   }
b62a0d 1224
A 1225
57f0c8 1226   /**
T 1227    * Check if the current request contains a valid token
1228    *
549933 1229    * @param int Request method
57f0c8 1230    * @return boolean True if request token is valid false if not
T 1231    */
549933 1232   public function check_request($mode = RCUBE_INPUT_POST)
57f0c8 1233   {
T 1234     $token = get_input_value('_token', $mode);
ec045b 1235     $sess_id = $_COOKIE[ini_get('session.name')];
T 1236     return !empty($sess_id) && $token == $this->get_request_token();
57f0c8 1237   }
b62a0d 1238
A 1239
57f0c8 1240   /**
1854c4 1241    * Create unique authorization hash
T 1242    *
1243    * @param string Session ID
1244    * @param int Timestamp
1245    * @return string The generated auth hash
1246    */
1247   private function get_auth_hash($sess_id, $ts)
1248   {
1249     $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
1250       $sess_id,
1251       $ts,
1252       $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
1253       $_SERVER['HTTP_USER_AGENT']);
1254
1255     if (function_exists('sha1'))
1256       return sha1($auth_string);
1257     else
1258       return md5($auth_string);
1259   }
1260
2471d3 1261
1854c4 1262   /**
2471d3 1263    * Encrypt using 3DES
1854c4 1264    *
2471d3 1265    * @param string $clear clear text input
A 1266    * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
1267    * @param boolean $base64 whether or not to base64_encode() the result before returning
1268    *
1269    * @return string encrypted text
1854c4 1270    */
2471d3 1271   public function encrypt($clear, $key = 'des_key', $base64 = true)
1854c4 1272   {
713a66 1273     if (!$clear)
A 1274       return '';
2471d3 1275     /*-
A 1276      * Add a single canary byte to the end of the clear text, which
1277      * will help find out how much of padding will need to be removed
1278      * upon decryption; see http://php.net/mcrypt_generic#68082
1279      */
1280     $clear = pack("a*H2", $clear, "80");
b62a0d 1281
2471d3 1282     if (function_exists('mcrypt_module_open') &&
A 1283         ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
1284     {
564741 1285       $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
2471d3 1286       mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
A 1287       $cipher = $iv . mcrypt_generic($td, $clear);
1854c4 1288       mcrypt_generic_deinit($td);
T 1289       mcrypt_module_close($td);
1290     }
44155c 1291     else {
926948 1292       @include_once 'des.inc';
44155c 1293
A 1294       if (function_exists('des')) {
1295         $des_iv_size = 8;
564741 1296         $iv = $this->create_iv($des_iv_size);
44155c 1297         $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
A 1298       }
1299       else {
1300         raise_error(array(
1301           'code' => 500, 'type' => 'php',
1302           'file' => __FILE__, 'line' => __LINE__,
1303           'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
1304         ), true, true);
1305       }
1854c4 1306     }
44155c 1307
2471d3 1308     return $base64 ? base64_encode($cipher) : $cipher;
1854c4 1309   }
T 1310
1311   /**
2471d3 1312    * Decrypt 3DES-encrypted string
1854c4 1313    *
2471d3 1314    * @param string $cipher encrypted text
A 1315    * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
1316    * @param boolean $base64 whether or not input is base64-encoded
1317    *
1318    * @return string decrypted text
1854c4 1319    */
2471d3 1320   public function decrypt($cipher, $key = 'des_key', $base64 = true)
1854c4 1321   {
713a66 1322     if (!$cipher)
A 1323       return '';
b62a0d 1324
2471d3 1325     $cipher = $base64 ? base64_decode($cipher) : $cipher;
A 1326
1327     if (function_exists('mcrypt_module_open') &&
1328         ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
1329     {
9016a8 1330       $iv_size = mcrypt_enc_get_iv_size($td);
A 1331       $iv = substr($cipher, 0, $iv_size);
1332
1333       // session corruption? (#1485970)
1334       if (strlen($iv) < $iv_size)
1335         return '';
1336
1337       $cipher = substr($cipher, $iv_size);
2471d3 1338       mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
A 1339       $clear = mdecrypt_generic($td, $cipher);
1854c4 1340       mcrypt_generic_deinit($td);
T 1341       mcrypt_module_close($td);
1342     }
44155c 1343     else {
926948 1344       @include_once 'des.inc';
b62a0d 1345
44155c 1346       if (function_exists('des')) {
A 1347         $des_iv_size = 8;
1348         $iv = substr($cipher, 0, $des_iv_size);
1349         $cipher = substr($cipher, $des_iv_size);
1350         $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
1351       }
1352       else {
1353         raise_error(array(
1354           'code' => 500, 'type' => 'php',
1355           'file' => __FILE__, 'line' => __LINE__,
1356           'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
1357         ), true, true);
1358       }
1854c4 1359     }
44155c 1360
2471d3 1361     /*-
A 1362      * Trim PHP's padding and the canary byte; see note in
1363      * rcmail::encrypt() and http://php.net/mcrypt_generic#68082
1364      */
1365     $clear = substr(rtrim($clear, "\0"), 0, -1);
b62a0d 1366
2471d3 1367     return $clear;
1854c4 1368   }
c719f3 1369
T 1370   /**
564741 1371    * Generates encryption initialization vector (IV)
A 1372    *
1373    * @param int Vector size
1374    * @return string Vector string
1375    */
1376   private function create_iv($size)
1377   {
1378     // mcrypt_create_iv() can be slow when system lacks entrophy
1379     // we'll generate IV vector manually
1380     $iv = '';
1381     for ($i = 0; $i < $size; $i++)
1382         $iv .= chr(mt_rand(0, 255));
1383     return $iv;
1384   }
1385
1386   /**
e019f2 1387    * Build a valid URL to this instance of Roundcube
c719f3 1388    *
T 1389    * @param mixed Either a string with the action or url parameters as key-value pairs
1390    * @return string Valid application URL
1391    */
1392   public function url($p)
1393   {
1394     if (!is_array($p))
fde466 1395       $p = array('_action' => @func_get_arg(0));
b62a0d 1396
1c932d 1397     $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
cc97ea 1398     $p['_task'] = $task;
1038a6 1399     unset($p['task']);
A 1400
cf1777 1401     $url = './';
T 1402     $delm = '?';
77406b 1403     foreach (array_reverse($p) as $key => $val) {
A 1404       if ($val !== '') {
cc97ea 1405         $par = $key[0] == '_' ? $key : '_'.$key;
cf1777 1406         $url .= $delm.urlencode($par).'='.urlencode($val);
T 1407         $delm = '&';
1408       }
1409     }
c719f3 1410     return $url;
T 1411   }
cefd1d 1412
T 1413
1414   /**
0501b6 1415    * Use imagemagick or GD lib to read image properties
T 1416    *
1417    * @param string Absolute file path
1418    * @return mixed Hash array with image props like type, width, height or False on error
1419    */
1420   public static function imageprops($filepath)
1421   {
1422     $rcmail = rcmail::get_instance();
1423     if ($cmd = $rcmail->config->get('im_identify_path', false)) {
1424       list(, $type, $size) = explode(' ', strtolower(rcmail::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath))));
1425       if ($size)
1426         list($width, $height) = explode('x', $size);
1427     }
1428     else if (function_exists('getimagesize')) {
1429       $imsize = @getimagesize($filepath);
1430       $width = $imsize[0];
1431       $height = $imsize[1];
1432       $type = preg_replace('!image/!', '', $imsize['mime']);
1433     }
1434
1435     return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false;
1436   }
1437
1438
1439   /**
1440    * Convert an image to a given size and type using imagemagick (ensures input is an image)
1441    *
1442    * @param $p['in']  Input filename (mandatory)
1443    * @param $p['out'] Output filename (mandatory)
1444    * @param $p['size']  Width x height of resulting image, e.g. "160x60"
1445    * @param $p['type']  Output file type, e.g. "jpg"
1446    * @param $p['-opts'] Custom command line options to ImageMagick convert
1447    * @return Success of convert as true/false
1448    */
1449   public static function imageconvert($p)
1450   {
1451     $result = false;
1452     $rcmail = rcmail::get_instance();
1453     $convert  = $rcmail->config->get('im_convert_path', false);
1454     $identify = $rcmail->config->get('im_identify_path', false);
4351f7 1455
0501b6 1456     // imagemagick is required for this
T 1457     if (!$convert)
1458         return false;
1459
1460     if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false))))
1461       list(, $type) = explode(' ', strtolower(rcmail::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps
1462
1463     $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
1464     $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
1465     $p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts'];
1466
1467     if (in_array($type, explode(',', $p['types']))) # Valid type?
1468       $result = rcmail::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === "";
1469
1470     return $result;
1471   }
1472
1473
1474   /**
1475    * Construct shell command, execute it and return output as string.
1476    * Keywords {keyword} are replaced with arguments
1477    *
1478    * @param $cmd Format string with {keywords} to be replaced
1479    * @param $values (zero, one or more arrays can be passed)
1480    * @return output of command. shell errors not detectable
1481    */
1482   public static function exec(/* $cmd, $values1 = array(), ... */)
1483   {
1484     $args = func_get_args();
1485     $cmd = array_shift($args);
1486     $values = $replacements = array();
1487
1488     // merge values into one array
1489     foreach ($args as $arg)
1490       $values += (array)$arg;
1491
1492     preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
1493     foreach ($matches as $tags) {
1494       list(, $tag, $option, $key) = $tags;
1495       $parts = array();
1496
1497       if ($option) {
1498         foreach ((array)$values["-$key"] as $key => $value) {
1499           if ($value === true || $value === false || $value === null)
1500             $parts[] = $value ? $key : "";
1501           else foreach ((array)$value as $val)
1502             $parts[] = "$key " . escapeshellarg($val);
1503         }
1504       }
1505       else {
1506         foreach ((array)$values[$key] as $value)
1507           $parts[] = escapeshellarg($value);
1508       }
1509
1510       $replacements[$tag] = join(" ", $parts);
1511     }
1512
1513     // use strtr behaviour of going through source string once
1514     $cmd = strtr($cmd, $replacements);
1515     
1516     return (string)shell_exec($cmd);
1517   }
1518
1519
1520   /**
cefd1d 1521    * Helper method to set a cookie with the current path and host settings
T 1522    *
1523    * @param string Cookie name
1524    * @param string Cookie value
1525    * @param string Expiration time
1526    */
1527   public static function setcookie($name, $value, $exp = 0)
1528   {
317a7d 1529     if (headers_sent())
A 1530       return;
1531
cefd1d 1532     $cookie = session_get_cookie_params();
2273d4 1533
cefd1d 1534     setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
c96c5a 1535       rcube_https_check(), true);
cefd1d 1536   }
68d2d5 1537
A 1538   /**
1539    * Registers action aliases for current task
1540    *
1541    * @param array $map Alias-to-filename hash array
1542    */
1543   public function register_action_map($map)
1544   {
1545     if (is_array($map)) {
1546       foreach ($map as $idx => $val) {
1547         $this->action_map[$idx] = $val;
1548       }
1549     }
1550   }
1551   
1552   /**
1553    * Returns current action filename
1554    *
1555    * @param array $map Alias-to-filename hash array
1556    */
1557   public function get_action_file()
1558   {
1559     if (!empty($this->action_map[$this->action])) {
1560       return $this->action_map[$this->action];
1561     }
1562
1563     return strtr($this->action, '-', '_') . '.inc';
1564   }
1565
d08333 1566   /**
A 1567    * Fixes some user preferences according to namespace handling change.
1568    * Old Roundcube versions were using folder names with removed namespace prefix.
1569    * Now we need to add the prefix on servers where personal namespace has prefix.
1570    *
1571    * @param rcube_user $user User object
1572    */
1573   private function fix_namespace_settings($user)
1574   {
1575     $prefix     = $this->imap->get_namespace('prefix');
1576     $prefix_len = strlen($prefix);
1577
1578     if (!$prefix_len)
1579       return;
1580
1581     $prefs = $user->get_prefs();
1582     if (empty($prefs) || $prefs['namespace_fixed'])
1583       return;
1584
1585     // Build namespace prefix regexp
1586     $ns     = $this->imap->get_namespace();
1587     $regexp = array();
1588
1589     foreach ($ns as $entry) {
1590       if (!empty($entry)) {
1591         foreach ($entry as $item) {
1592           if (strlen($item[0])) {
1593             $regexp[] = preg_quote($item[0], '/');
1594           }
1595         }
1596       }
1597     }
1598     $regexp = '/^('. implode('|', $regexp).')/';
1599
1600     // Fix preferences
1601     $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox');
1602     foreach ($opts as $opt) {
1603       if ($value = $prefs[$opt]) {
1604         if ($value != 'INBOX' && !preg_match($regexp, $value)) {
1605           $prefs[$opt] = $prefix.$value;
1606         }
1607       }
1608     }
1609
1610     if (!empty($prefs['default_imap_folders'])) {
1611       foreach ($prefs['default_imap_folders'] as $idx => $name) {
1612         if ($name != 'INBOX' && !preg_match($regexp, $name)) {
1613           $prefs['default_imap_folders'][$idx] = $prefix.$name;
1614         }
1615       }
1616     }
1617
1618     if (!empty($prefs['search_mods'])) {
1619       $folders = array();
1620       foreach ($prefs['search_mods'] as $idx => $value) {
1621         if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) {
1622           $idx = $prefix.$idx;
1623         }
1624         $folders[$idx] = $value;
1625       }
1626       $prefs['search_mods'] = $folders;
1627     }
1628
1629     if (!empty($prefs['message_threading'])) {
1630       $folders = array();
1631       foreach ($prefs['message_threading'] as $idx => $value) {
1632         if ($idx != 'INBOX' && !preg_match($regexp, $idx)) {
1633           $idx = $prefix.$idx;
1634         }
1635         $folders[$prefix.$idx] = $value;
1636       }
1637       $prefs['message_threading'] = $folders;
1638     }
1639
1640     if (!empty($prefs['collapsed_folders'])) {
1641       $folders     = explode('&&', $prefs['collapsed_folders']);
1642       $count       = count($folders);
1643       $folders_str = '';
1644
1645       if ($count) {
1646           $folders[0]        = substr($folders[0], 1);
1647           $folders[$count-1] = substr($folders[$count-1], 0, -1);
1648       }
1649
1650       foreach ($folders as $value) {
1651         if ($value != 'INBOX' && !preg_match($regexp, $value)) {
1652           $value = $prefix.$value;
1653         }
1654         $folders_str .= '&'.$value.'&';
1655       }
1656       $prefs['collapsed_folders'] = $folders_str;
1657     }
1658
1659     $prefs['namespace_fixed'] = true;
1660
1661     // save updated preferences and reset imap settings (default folders)
1662     $user->save_prefs($prefs);
1663     $this->set_imap_prop();
1664   }
1665
197601 1666 }