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