PageRenderTime 47ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/program/include/rcmail.php

https://github.com/netconstructor/roundcubemail
PHP | 2111 lines | 1435 code | 306 blank | 370 comment | 341 complexity | 2225c23dfcf466b31538c676584d3a25 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. +-----------------------------------------------------------------------+
  4. | program/include/rcmail.php |
  5. | |
  6. | This file is part of the Roundcube Webmail client |
  7. | Copyright (C) 2008-2012, The Roundcube Dev Team |
  8. | Copyright (C) 2011-2012, Kolab Systems AG |
  9. | |
  10. | Licensed under the GNU General Public License version 3 or |
  11. | any later version with exceptions for skins & plugins. |
  12. | See the README file for a full license statement. |
  13. | |
  14. | PURPOSE: |
  15. | Application class providing core functions and holding |
  16. | instances of all 'global' objects like db- and imap-connections |
  17. +-----------------------------------------------------------------------+
  18. | Author: Thomas Bruederli <roundcube@gmail.com> |
  19. | Author: Aleksander Machniak <alec@alec.pl> |
  20. +-----------------------------------------------------------------------+
  21. */
  22. /**
  23. * Application class of Roundcube Webmail
  24. * implemented as singleton
  25. *
  26. * @package Core
  27. */
  28. class rcmail extends rcube
  29. {
  30. /**
  31. * Main tasks.
  32. *
  33. * @var array
  34. */
  35. static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
  36. /**
  37. * Current task.
  38. *
  39. * @var string
  40. */
  41. public $task;
  42. /**
  43. * Current action.
  44. *
  45. * @var string
  46. */
  47. public $action = '';
  48. public $comm_path = './';
  49. private $address_books = array();
  50. private $action_map = array();
  51. const JS_OBJECT_NAME = 'rcmail';
  52. const ERROR_STORAGE = -2;
  53. const ERROR_INVALID_REQUEST = 1;
  54. const ERROR_INVALID_HOST = 2;
  55. const ERROR_COOKIES_DISABLED = 3;
  56. /**
  57. * This implements the 'singleton' design pattern
  58. *
  59. * @return rcmail The one and only instance
  60. */
  61. static function get_instance()
  62. {
  63. if (!self::$instance || !is_a(self::$instance, 'rcmail')) {
  64. self::$instance = new rcmail();
  65. self::$instance->startup(); // init AFTER object was linked with self::$instance
  66. }
  67. return self::$instance;
  68. }
  69. /**
  70. * Initial startup function
  71. * to register session, create database and imap connections
  72. */
  73. protected function startup()
  74. {
  75. $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS);
  76. // start session
  77. $this->session_init();
  78. // create user object
  79. $this->set_user(new rcube_user($_SESSION['user_id']));
  80. // set task and action properties
  81. $this->set_task(rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC));
  82. $this->action = asciiwords(rcube_utils::get_input_value('_action', rcube_utils::INPUT_GPC));
  83. // reset some session parameters when changing task
  84. if ($this->task != 'utils') {
  85. if ($this->session && $_SESSION['task'] != $this->task)
  86. $this->session->remove('page');
  87. // set current task to session
  88. $_SESSION['task'] = $this->task;
  89. }
  90. // init output class
  91. if (!empty($_REQUEST['_remote']))
  92. $GLOBALS['OUTPUT'] = $this->json_init();
  93. else
  94. $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
  95. // load plugins
  96. $this->plugins->init($this, $this->task);
  97. $this->plugins->load_plugins((array)$this->config->get('plugins', array()), array('filesystem_attachments', 'jqueryui'));
  98. }
  99. /**
  100. * Setter for application task
  101. *
  102. * @param string Task to set
  103. */
  104. public function set_task($task)
  105. {
  106. $task = asciiwords($task);
  107. if ($this->user && $this->user->ID)
  108. $task = !$task ? 'mail' : $task;
  109. else
  110. $task = 'login';
  111. $this->task = $task;
  112. $this->comm_path = $this->url(array('task' => $this->task));
  113. if ($this->output)
  114. $this->output->set_env('task', $this->task);
  115. }
  116. /**
  117. * Setter for system user object
  118. *
  119. * @param rcube_user Current user instance
  120. */
  121. public function set_user($user)
  122. {
  123. if (is_object($user)) {
  124. $this->user = $user;
  125. // overwrite config with user preferences
  126. $this->config->set_user_prefs((array)$this->user->get_prefs());
  127. }
  128. $lang = $this->language_prop($this->config->get('language', $_SESSION['language']));
  129. $_SESSION['language'] = $this->user->language = $lang;
  130. // set localization
  131. setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8');
  132. // workaround for http://bugs.php.net/bug.php?id=18556
  133. if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) {
  134. setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8');
  135. }
  136. }
  137. /**
  138. * Return instance of the internal address book class
  139. *
  140. * @param string Address book identifier (-1 for default addressbook)
  141. * @param boolean True if the address book needs to be writeable
  142. *
  143. * @return rcube_contacts Address book object
  144. */
  145. public function get_address_book($id, $writeable = false)
  146. {
  147. $contacts = null;
  148. $ldap_config = (array)$this->config->get('ldap_public');
  149. // 'sql' is the alias for '0' used by autocomplete
  150. if ($id == 'sql')
  151. $id = '0';
  152. else if ($id == -1) {
  153. $id = $this->config->get('default_addressbook');
  154. $default = true;
  155. }
  156. // use existing instance
  157. if (isset($this->address_books[$id]) && ($this->address_books[$id] instanceof rcube_addressbook)) {
  158. $contacts = $this->address_books[$id];
  159. }
  160. else if ($id && $ldap_config[$id]) {
  161. $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['storage_host']));
  162. }
  163. else if ($id === '0') {
  164. $contacts = new rcube_contacts($this->db, $this->get_user_id());
  165. }
  166. else {
  167. $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
  168. // plugin returned instance of a rcube_addressbook
  169. if ($plugin['instance'] instanceof rcube_addressbook) {
  170. $contacts = $plugin['instance'];
  171. }
  172. }
  173. // Get first addressbook from the list if configured default doesn't exist
  174. // This can happen when user deleted the addressbook (e.g. Kolab folder)
  175. if (!$contacts && (!$id || $default)) {
  176. $source = reset($this->get_address_sources($writeable));
  177. if (!empty($source)) {
  178. $contacts = $this->get_address_book($source['id']);
  179. if ($contacts)
  180. $id = $source['id'];
  181. }
  182. }
  183. if (!$contacts) {
  184. self::raise_error(array(
  185. 'code' => 700, 'type' => 'php',
  186. 'file' => __FILE__, 'line' => __LINE__,
  187. 'message' => "Addressbook source ($id) not found!"),
  188. true, true);
  189. }
  190. if ($writeable && $contacts->readonly) {
  191. return null;
  192. }
  193. // set configured sort order
  194. if ($sort_col = $this->config->get('addressbook_sort_col'))
  195. $contacts->set_sort_order($sort_col);
  196. // add to the 'books' array for shutdown function
  197. $this->address_books[$id] = $contacts;
  198. return $contacts;
  199. }
  200. /**
  201. * Return address books list
  202. *
  203. * @param boolean True if the address book needs to be writeable
  204. *
  205. * @return array Address books array
  206. */
  207. public function get_address_sources($writeable = false)
  208. {
  209. $abook_type = strtolower($this->config->get('address_book_type'));
  210. $ldap_config = $this->config->get('ldap_public');
  211. $autocomplete = (array) $this->config->get('autocomplete_addressbooks');
  212. $list = array();
  213. // We are using the DB address book or a plugin address book
  214. if ($abook_type != 'ldap' && $abook_type != '') {
  215. if (!isset($this->address_books['0']))
  216. $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id());
  217. $list['0'] = array(
  218. 'id' => '0',
  219. 'name' => $this->gettext('personaladrbook'),
  220. 'groups' => $this->address_books['0']->groups,
  221. 'readonly' => $this->address_books['0']->readonly,
  222. 'autocomplete' => in_array('sql', $autocomplete),
  223. 'undelete' => $this->address_books['0']->undelete && $this->config->get('undo_timeout'),
  224. );
  225. }
  226. if ($ldap_config) {
  227. $ldap_config = (array) $ldap_config;
  228. foreach ($ldap_config as $id => $prop) {
  229. // handle misconfiguration
  230. if (empty($prop) || !is_array($prop)) {
  231. continue;
  232. }
  233. $list[$id] = array(
  234. 'id' => $id,
  235. 'name' => html::quote($prop['name']),
  236. 'groups' => is_array($prop['groups']),
  237. 'readonly' => !$prop['writable'],
  238. 'hidden' => $prop['hidden'],
  239. 'autocomplete' => in_array($id, $autocomplete)
  240. );
  241. }
  242. }
  243. $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
  244. $list = $plugin['sources'];
  245. foreach ($list as $idx => $item) {
  246. // register source for shutdown function
  247. if (!is_object($this->address_books[$item['id']]))
  248. $this->address_books[$item['id']] = $item;
  249. // remove from list if not writeable as requested
  250. if ($writeable && $item['readonly'])
  251. unset($list[$idx]);
  252. }
  253. return $list;
  254. }
  255. /**
  256. * Init output object for GUI and add common scripts.
  257. * This will instantiate a rcube_output_html object and set
  258. * environment vars according to the current session and configuration
  259. *
  260. * @param boolean True if this request is loaded in a (i)frame
  261. * @return rcube_output_html Reference to HTML output object
  262. */
  263. public function load_gui($framed = false)
  264. {
  265. // init output page
  266. if (!($this->output instanceof rcube_output_html))
  267. $this->output = new rcube_output_html($this->task, $framed);
  268. // set refresh interval
  269. $this->output->set_env('refresh_interval', $this->config->get('refresh_interval', 0));
  270. $this->output->set_env('session_lifetime', $this->config->get('session_lifetime', 0) * 60);
  271. if ($framed) {
  272. $this->comm_path .= '&_framed=1';
  273. $this->output->set_env('framed', true);
  274. }
  275. $this->output->set_env('task', $this->task);
  276. $this->output->set_env('action', $this->action);
  277. $this->output->set_env('comm_path', $this->comm_path);
  278. $this->output->set_charset(RCMAIL_CHARSET);
  279. // add some basic labels to client
  280. $this->output->add_label('loading', 'servererror', 'requesttimedout', 'refreshing');
  281. return $this->output;
  282. }
  283. /**
  284. * Create an output object for JSON responses
  285. *
  286. * @return rcube_output_json Reference to JSON output object
  287. */
  288. public function json_init()
  289. {
  290. if (!($this->output instanceof rcube_output_json))
  291. $this->output = new rcube_output_json($this->task);
  292. return $this->output;
  293. }
  294. /**
  295. * Create session object and start the session.
  296. */
  297. public function session_init()
  298. {
  299. parent::session_init();
  300. // set initial session vars
  301. if (!$_SESSION['user_id'])
  302. $_SESSION['temp'] = true;
  303. // restore skin selection after logout
  304. if ($_SESSION['temp'] && !empty($_SESSION['skin']))
  305. $this->config->set('skin', $_SESSION['skin']);
  306. }
  307. /**
  308. * Perfom login to the mail server and to the webmail service.
  309. * This will also create a new user entry if auto_create_user is configured.
  310. *
  311. * @param string Mail storage (IMAP) user name
  312. * @param string Mail storage (IMAP) password
  313. * @param string Mail storage (IMAP) host
  314. * @param bool Enables cookie check
  315. *
  316. * @return boolean True on success, False on failure
  317. */
  318. function login($username, $pass, $host = null, $cookiecheck = false)
  319. {
  320. $this->login_error = null;
  321. if (empty($username)) {
  322. return false;
  323. }
  324. if ($cookiecheck && empty($_COOKIE)) {
  325. $this->login_error = self::ERROR_COOKIES_DISABLED;
  326. return false;
  327. }
  328. $config = $this->config->all();
  329. if (!$host)
  330. $host = $config['default_host'];
  331. // Validate that selected host is in the list of configured hosts
  332. if (is_array($config['default_host'])) {
  333. $allowed = false;
  334. foreach ($config['default_host'] as $key => $host_allowed) {
  335. if (!is_numeric($key))
  336. $host_allowed = $key;
  337. if ($host == $host_allowed) {
  338. $allowed = true;
  339. break;
  340. }
  341. }
  342. if (!$allowed) {
  343. $host = null;
  344. }
  345. }
  346. else if (!empty($config['default_host']) && $host != rcube_utils::parse_host($config['default_host'])) {
  347. $host = null;
  348. }
  349. if (!$host) {
  350. $this->login_error = self::ERROR_INVALID_HOST;
  351. return false;
  352. }
  353. // parse $host URL
  354. $a_host = parse_url($host);
  355. if ($a_host['host']) {
  356. $host = $a_host['host'];
  357. $ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
  358. if (!empty($a_host['port']))
  359. $port = $a_host['port'];
  360. else if ($ssl && $ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
  361. $port = 993;
  362. }
  363. if (!$port) {
  364. $port = $config['default_port'];
  365. }
  366. /* Modify username with domain if required
  367. Inspired by Marco <P0L0_notspam_binware.org>
  368. */
  369. // Check if we need to add domain
  370. if (!empty($config['username_domain']) && strpos($username, '@') === false) {
  371. if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
  372. $username .= '@'.rcube_utils::parse_host($config['username_domain'][$host], $host);
  373. else if (is_string($config['username_domain']))
  374. $username .= '@'.rcube_utils::parse_host($config['username_domain'], $host);
  375. }
  376. if (!isset($config['login_lc'])) {
  377. $config['login_lc'] = 2; // default
  378. }
  379. // Convert username to lowercase. If storage backend
  380. // is case-insensitive we need to store always the same username (#1487113)
  381. if ($config['login_lc']) {
  382. if ($config['login_lc'] == 2 || $config['login_lc'] === true) {
  383. $username = mb_strtolower($username);
  384. }
  385. else if (strpos($username, '@')) {
  386. // lowercase domain name
  387. list($local, $domain) = explode('@', $username);
  388. $username = $local . '@' . mb_strtolower($domain);
  389. }
  390. }
  391. // try to resolve email address from virtuser table
  392. if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
  393. $username = $virtuser;
  394. }
  395. // Here we need IDNA ASCII
  396. // Only rcube_contacts class is using domain names in Unicode
  397. $host = rcube_utils::idn_to_ascii($host);
  398. $username = rcube_utils::idn_to_ascii($username);
  399. // user already registered -> overwrite username
  400. if ($user = rcube_user::query($username, $host)) {
  401. $username = $user->data['username'];
  402. }
  403. $storage = $this->get_storage();
  404. // try to log in
  405. if (!$storage->connect($host, $username, $pass, $port, $ssl)) {
  406. return false;
  407. }
  408. // user already registered -> update user's record
  409. if (is_object($user)) {
  410. // update last login timestamp
  411. $user->touch();
  412. }
  413. // create new system user
  414. else if ($config['auto_create_user']) {
  415. if ($created = rcube_user::create($username, $host)) {
  416. $user = $created;
  417. }
  418. else {
  419. self::raise_error(array(
  420. 'code' => 620, 'type' => 'php',
  421. 'file' => __FILE__, 'line' => __LINE__,
  422. 'message' => "Failed to create a user record. Maybe aborted by a plugin?"
  423. ), true, false);
  424. }
  425. }
  426. else {
  427. self::raise_error(array(
  428. 'code' => 621, 'type' => 'php',
  429. 'file' => __FILE__, 'line' => __LINE__,
  430. 'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
  431. ), true, false);
  432. }
  433. // login succeeded
  434. if (is_object($user) && $user->ID) {
  435. // Configure environment
  436. $this->set_user($user);
  437. $this->set_storage_prop();
  438. // fix some old settings according to namespace prefix
  439. $this->fix_namespace_settings($user);
  440. // create default folders on first login
  441. if ($config['create_default_folders'] && (!empty($created) || empty($user->data['last_login']))) {
  442. $storage->create_default_folders();
  443. }
  444. // set session vars
  445. $_SESSION['user_id'] = $user->ID;
  446. $_SESSION['username'] = $user->data['username'];
  447. $_SESSION['storage_host'] = $host;
  448. $_SESSION['storage_port'] = $port;
  449. $_SESSION['storage_ssl'] = $ssl;
  450. $_SESSION['password'] = $this->encrypt($pass);
  451. $_SESSION['login_time'] = time();
  452. if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
  453. $_SESSION['timezone'] = rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_GPC);
  454. // force reloading complete list of subscribed mailboxes
  455. $storage->clear_cache('mailboxes', true);
  456. return true;
  457. }
  458. return false;
  459. }
  460. /**
  461. * Returns error code of last login operation
  462. *
  463. * @return int Error code
  464. */
  465. public function login_error()
  466. {
  467. if ($this->login_error) {
  468. return $this->login_error;
  469. }
  470. if ($this->storage && $this->storage->get_error_code() < -1) {
  471. return self::ERROR_STORAGE;
  472. }
  473. }
  474. /**
  475. * Auto-select IMAP host based on the posted login information
  476. *
  477. * @return string Selected IMAP host
  478. */
  479. public function autoselect_host()
  480. {
  481. $default_host = $this->config->get('default_host');
  482. $host = null;
  483. if (is_array($default_host)) {
  484. $post_host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
  485. $post_user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST);
  486. list($user, $domain) = explode('@', $post_user);
  487. // direct match in default_host array
  488. if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
  489. $host = $post_host;
  490. }
  491. // try to select host by mail domain
  492. else if (!empty($domain)) {
  493. foreach ($default_host as $storage_host => $mail_domains) {
  494. if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
  495. $host = $storage_host;
  496. break;
  497. }
  498. else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) {
  499. $host = is_numeric($storage_host) ? $mail_domains : $storage_host;
  500. break;
  501. }
  502. }
  503. }
  504. // take the first entry if $host is still not set
  505. if (empty($host)) {
  506. list($key, $val) = each($default_host);
  507. $host = is_numeric($key) ? $val : $key;
  508. }
  509. }
  510. else if (empty($default_host)) {
  511. $host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
  512. }
  513. else
  514. $host = rcube_utils::parse_host($default_host);
  515. return $host;
  516. }
  517. /**
  518. * Destroy session data and remove cookie
  519. */
  520. public function kill_session()
  521. {
  522. $this->plugins->exec_hook('session_destroy');
  523. $this->session->kill();
  524. $_SESSION = array('language' => $this->user->language, 'temp' => true, 'skin' => $this->config->get('skin'));
  525. $this->user->reset();
  526. }
  527. /**
  528. * Do server side actions on logout
  529. */
  530. public function logout_actions()
  531. {
  532. $config = $this->config->all();
  533. $storage = $this->get_storage();
  534. if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
  535. $storage->clear_folder($config['trash_mbox']);
  536. }
  537. if ($config['logout_expunge']) {
  538. $storage->expunge_folder('INBOX');
  539. }
  540. // Try to save unsaved user preferences
  541. if (!empty($_SESSION['preferences'])) {
  542. $this->user->save_prefs(unserialize($_SESSION['preferences']));
  543. }
  544. }
  545. /**
  546. * Generate a unique token to be used in a form request
  547. *
  548. * @return string The request token
  549. */
  550. public function get_request_token()
  551. {
  552. $sess_id = $_COOKIE[ini_get('session.name')];
  553. if (!$sess_id) $sess_id = session_id();
  554. $plugin = $this->plugins->exec_hook('request_token', array(
  555. 'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id)));
  556. return $plugin['value'];
  557. }
  558. /**
  559. * Check if the current request contains a valid token
  560. *
  561. * @param int Request method
  562. * @return boolean True if request token is valid false if not
  563. */
  564. public function check_request($mode = rcube_utils::INPUT_POST)
  565. {
  566. $token = rcube_utils::get_input_value('_token', $mode);
  567. $sess_id = $_COOKIE[ini_get('session.name')];
  568. return !empty($sess_id) && $token == $this->get_request_token();
  569. }
  570. /**
  571. * Create unique authorization hash
  572. *
  573. * @param string Session ID
  574. * @param int Timestamp
  575. * @return string The generated auth hash
  576. */
  577. private function get_auth_hash($sess_id, $ts)
  578. {
  579. $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
  580. $sess_id,
  581. $ts,
  582. $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
  583. $_SERVER['HTTP_USER_AGENT']);
  584. if (function_exists('sha1'))
  585. return sha1($auth_string);
  586. else
  587. return md5($auth_string);
  588. }
  589. /**
  590. * Build a valid URL to this instance of Roundcube
  591. *
  592. * @param mixed Either a string with the action or url parameters as key-value pairs
  593. *
  594. * @return string Valid application URL
  595. */
  596. public function url($p)
  597. {
  598. if (!is_array($p)) {
  599. if (strpos($p, 'http') === 0)
  600. return $p;
  601. $p = array('_action' => @func_get_arg(0));
  602. }
  603. $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
  604. $p['_task'] = $task;
  605. unset($p['task']);
  606. $url = './';
  607. $delm = '?';
  608. foreach (array_reverse($p) as $key => $val) {
  609. if ($val !== '' && $val !== null) {
  610. $par = $key[0] == '_' ? $key : '_'.$key;
  611. $url .= $delm.urlencode($par).'='.urlencode($val);
  612. $delm = '&';
  613. }
  614. }
  615. return $url;
  616. }
  617. /**
  618. * Function to be executed in script shutdown
  619. */
  620. public function shutdown()
  621. {
  622. parent::shutdown();
  623. foreach ($this->address_books as $book) {
  624. if (is_object($book) && is_a($book, 'rcube_addressbook'))
  625. $book->close();
  626. }
  627. // before closing the database connection, write session data
  628. if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
  629. session_write_close();
  630. }
  631. // write performance stats to logs/console
  632. if ($this->config->get('devel_mode')) {
  633. if (function_exists('memory_get_usage'))
  634. $mem = $this->show_bytes(memory_get_usage());
  635. if (function_exists('memory_get_peak_usage'))
  636. $mem .= '/'.$this->show_bytes(memory_get_peak_usage());
  637. $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
  638. if (defined('RCMAIL_START'))
  639. self::print_timer(RCMAIL_START, $log);
  640. else
  641. self::console($log);
  642. }
  643. }
  644. /**
  645. * Registers action aliases for current task
  646. *
  647. * @param array $map Alias-to-filename hash array
  648. */
  649. public function register_action_map($map)
  650. {
  651. if (is_array($map)) {
  652. foreach ($map as $idx => $val) {
  653. $this->action_map[$idx] = $val;
  654. }
  655. }
  656. }
  657. /**
  658. * Returns current action filename
  659. *
  660. * @param array $map Alias-to-filename hash array
  661. */
  662. public function get_action_file()
  663. {
  664. if (!empty($this->action_map[$this->action])) {
  665. return $this->action_map[$this->action];
  666. }
  667. return strtr($this->action, '-', '_') . '.inc';
  668. }
  669. /**
  670. * Fixes some user preferences according to namespace handling change.
  671. * Old Roundcube versions were using folder names with removed namespace prefix.
  672. * Now we need to add the prefix on servers where personal namespace has prefix.
  673. *
  674. * @param rcube_user $user User object
  675. */
  676. private function fix_namespace_settings($user)
  677. {
  678. $prefix = $this->storage->get_namespace('prefix');
  679. $prefix_len = strlen($prefix);
  680. if (!$prefix_len)
  681. return;
  682. $prefs = $this->config->all();
  683. if (!empty($prefs['namespace_fixed']))
  684. return;
  685. // Build namespace prefix regexp
  686. $ns = $this->storage->get_namespace();
  687. $regexp = array();
  688. foreach ($ns as $entry) {
  689. if (!empty($entry)) {
  690. foreach ($entry as $item) {
  691. if (strlen($item[0])) {
  692. $regexp[] = preg_quote($item[0], '/');
  693. }
  694. }
  695. }
  696. }
  697. $regexp = '/^('. implode('|', $regexp).')/';
  698. // Fix preferences
  699. $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox');
  700. foreach ($opts as $opt) {
  701. if ($value = $prefs[$opt]) {
  702. if ($value != 'INBOX' && !preg_match($regexp, $value)) {
  703. $prefs[$opt] = $prefix.$value;
  704. }
  705. }
  706. }
  707. if (!empty($prefs['default_folders'])) {
  708. foreach ($prefs['default_folders'] as $idx => $name) {
  709. if ($name != 'INBOX' && !preg_match($regexp, $name)) {
  710. $prefs['default_folders'][$idx] = $prefix.$name;
  711. }
  712. }
  713. }
  714. if (!empty($prefs['search_mods'])) {
  715. $folders = array();
  716. foreach ($prefs['search_mods'] as $idx => $value) {
  717. if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) {
  718. $idx = $prefix.$idx;
  719. }
  720. $folders[$idx] = $value;
  721. }
  722. $prefs['search_mods'] = $folders;
  723. }
  724. if (!empty($prefs['message_threading'])) {
  725. $folders = array();
  726. foreach ($prefs['message_threading'] as $idx => $value) {
  727. if ($idx != 'INBOX' && !preg_match($regexp, $idx)) {
  728. $idx = $prefix.$idx;
  729. }
  730. $folders[$prefix.$idx] = $value;
  731. }
  732. $prefs['message_threading'] = $folders;
  733. }
  734. if (!empty($prefs['collapsed_folders'])) {
  735. $folders = explode('&&', $prefs['collapsed_folders']);
  736. $count = count($folders);
  737. $folders_str = '';
  738. if ($count) {
  739. $folders[0] = substr($folders[0], 1);
  740. $folders[$count-1] = substr($folders[$count-1], 0, -1);
  741. }
  742. foreach ($folders as $value) {
  743. if ($value != 'INBOX' && !preg_match($regexp, $value)) {
  744. $value = $prefix.$value;
  745. }
  746. $folders_str .= '&'.$value.'&';
  747. }
  748. $prefs['collapsed_folders'] = $folders_str;
  749. }
  750. $prefs['namespace_fixed'] = true;
  751. // save updated preferences and reset imap settings (default folders)
  752. $user->save_prefs($prefs);
  753. $this->set_storage_prop();
  754. }
  755. /**
  756. * Overwrite action variable
  757. *
  758. * @param string New action value
  759. */
  760. public function overwrite_action($action)
  761. {
  762. $this->action = $action;
  763. $this->output->set_env('action', $action);
  764. }
  765. /**
  766. * Send the given message using the configured method.
  767. *
  768. * @param object $message Reference to Mail_MIME object
  769. * @param string $from Sender address string
  770. * @param array $mailto Array of recipient address strings
  771. * @param array $smtp_error SMTP error array (reference)
  772. * @param string $body_file Location of file with saved message body (reference),
  773. * used when delay_file_io is enabled
  774. * @param array $smtp_opts SMTP options (e.g. DSN request)
  775. *
  776. * @return boolean Send status.
  777. */
  778. public function deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file = null, $smtp_opts = null)
  779. {
  780. $headers = $message->headers();
  781. // send thru SMTP server using custom SMTP library
  782. if ($this->config->get('smtp_server')) {
  783. // generate list of recipients
  784. $a_recipients = array($mailto);
  785. if (strlen($headers['Cc']))
  786. $a_recipients[] = $headers['Cc'];
  787. if (strlen($headers['Bcc']))
  788. $a_recipients[] = $headers['Bcc'];
  789. // clean Bcc from header for recipients
  790. $send_headers = $headers;
  791. unset($send_headers['Bcc']);
  792. // here too, it because txtHeaders() below use $message->_headers not only $send_headers
  793. unset($message->_headers['Bcc']);
  794. $smtp_headers = $message->txtHeaders($send_headers, true);
  795. if ($message->getParam('delay_file_io')) {
  796. // use common temp dir
  797. $temp_dir = $this->config->get('temp_dir');
  798. $body_file = tempnam($temp_dir, 'rcmMsg');
  799. if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
  800. self::raise_error(array('code' => 650, 'type' => 'php',
  801. 'file' => __FILE__, 'line' => __LINE__,
  802. 'message' => "Could not create message: ".$mime_result->getMessage()),
  803. TRUE, FALSE);
  804. return false;
  805. }
  806. $msg_body = fopen($body_file, 'r');
  807. }
  808. else {
  809. $msg_body = $message->get();
  810. }
  811. // send message
  812. if (!is_object($this->smtp)) {
  813. $this->smtp_init(true);
  814. }
  815. $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
  816. $smtp_response = $this->smtp->get_response();
  817. $smtp_error = $this->smtp->get_error();
  818. // log error
  819. if (!$sent) {
  820. self::raise_error(array('code' => 800, 'type' => 'smtp',
  821. 'line' => __LINE__, 'file' => __FILE__,
  822. 'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
  823. }
  824. }
  825. // send mail using PHP's mail() function
  826. else {
  827. // unset some headers because they will be added by the mail() function
  828. $headers_enc = $message->headers($headers);
  829. $headers_php = $message->_headers;
  830. unset($headers_php['To'], $headers_php['Subject']);
  831. // reset stored headers and overwrite
  832. $message->_headers = array();
  833. $header_str = $message->txtHeaders($headers_php);
  834. // #1485779
  835. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  836. if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
  837. $headers_enc['To'] = implode(', ', $m[1]);
  838. }
  839. }
  840. $msg_body = $message->get();
  841. if (PEAR::isError($msg_body)) {
  842. self::raise_error(array('code' => 650, 'type' => 'php',
  843. 'file' => __FILE__, 'line' => __LINE__,
  844. 'message' => "Could not create message: ".$msg_body->getMessage()),
  845. TRUE, FALSE);
  846. }
  847. else {
  848. $delim = $this->config->header_delimiter();
  849. $to = $headers_enc['To'];
  850. $subject = $headers_enc['Subject'];
  851. $header_str = rtrim($header_str);
  852. if ($delim != "\r\n") {
  853. $header_str = str_replace("\r\n", $delim, $header_str);
  854. $msg_body = str_replace("\r\n", $delim, $msg_body);
  855. $to = str_replace("\r\n", $delim, $to);
  856. $subject = str_replace("\r\n", $delim, $subject);
  857. }
  858. if (ini_get('safe_mode'))
  859. $sent = mail($to, $subject, $msg_body, $header_str);
  860. else
  861. $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
  862. }
  863. }
  864. if ($sent) {
  865. $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
  866. // remove MDN headers after sending
  867. unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
  868. // get all recipients
  869. if ($headers['Cc'])
  870. $mailto .= $headers['Cc'];
  871. if ($headers['Bcc'])
  872. $mailto .= $headers['Bcc'];
  873. if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
  874. $mailto = implode(', ', array_unique($m[1]));
  875. if ($this->config->get('smtp_log')) {
  876. self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
  877. $this->user->get_username(),
  878. $_SERVER['REMOTE_ADDR'],
  879. $mailto,
  880. !empty($smtp_response) ? join('; ', $smtp_response) : ''));
  881. }
  882. }
  883. if (is_resource($msg_body)) {
  884. fclose($msg_body);
  885. }
  886. $message->_headers = array();
  887. $message->headers($headers);
  888. return $sent;
  889. }
  890. /**
  891. * Unique Message-ID generator.
  892. *
  893. * @return string Message-ID
  894. */
  895. public function gen_message_id()
  896. {
  897. $local_part = md5(uniqid('rcmail'.mt_rand(),true));
  898. $domain_part = $this->user->get_username('domain');
  899. // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
  900. if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
  901. foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
  902. $host = preg_replace('/:[0-9]+$/', '', $host);
  903. if ($host && preg_match('/\.[a-z]+$/i', $host)) {
  904. $domain_part = $host;
  905. }
  906. }
  907. }
  908. return sprintf('<%s@%s>', $local_part, $domain_part);
  909. }
  910. /**
  911. * Returns RFC2822 formatted current date in user's timezone
  912. *
  913. * @return string Date
  914. */
  915. public function user_date()
  916. {
  917. // get user's timezone
  918. try {
  919. $tz = new DateTimeZone($this->config->get('timezone'));
  920. $date = new DateTime('now', $tz);
  921. }
  922. catch (Exception $e) {
  923. $date = new DateTime();
  924. }
  925. return $date->format('r');
  926. }
  927. /**
  928. * Write login data (name, ID, IP address) to the 'userlogins' log file.
  929. */
  930. public function log_login()
  931. {
  932. if (!$this->config->get('log_logins')) {
  933. return;
  934. }
  935. $user_name = $this->get_user_name();
  936. $user_id = $this->get_user_id();
  937. if (!$user_id) {
  938. return;
  939. }
  940. self::write_log('userlogins',
  941. sprintf('Successful login for %s (ID: %d) from %s in session %s',
  942. $user_name, $user_id, rcube_utils::remote_ip(), session_id()));
  943. }
  944. /**
  945. * Create a HTML table based on the given data
  946. *
  947. * @param array Named table attributes
  948. * @param mixed Table row data. Either a two-dimensional array or a valid SQL result set
  949. * @param array List of cols to show
  950. * @param string Name of the identifier col
  951. *
  952. * @return string HTML table code
  953. */
  954. public function table_output($attrib, $table_data, $a_show_cols, $id_col)
  955. {
  956. $table = new html_table(/*array('cols' => count($a_show_cols))*/);
  957. // add table header
  958. if (!$attrib['noheader']) {
  959. foreach ($a_show_cols as $col) {
  960. $table->add_header($col, $this->Q($this->gettext($col)));
  961. }
  962. }
  963. if (!is_array($table_data)) {
  964. $db = $this->get_dbh();
  965. while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) {
  966. $table->add_row(array('id' => 'rcmrow' . rcube_utils::html_identifier($sql_arr[$id_col])));
  967. // format each col
  968. foreach ($a_show_cols as $col) {
  969. $table->add($col, $this->Q($sql_arr[$col]));
  970. }
  971. }
  972. }
  973. else {
  974. foreach ($table_data as $row_data) {
  975. $class = !empty($row_data['class']) ? $row_data['class'] : '';
  976. $rowid = 'rcmrow' . rcube_utils::html_identifier($row_data[$id_col]);
  977. $table->add_row(array('id' => $rowid, 'class' => $class));
  978. // format each col
  979. foreach ($a_show_cols as $col) {
  980. $table->add($col, $this->Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
  981. }
  982. }
  983. }
  984. return $table->show($attrib);
  985. }
  986. /**
  987. * Convert the given date to a human readable form
  988. * This uses the date formatting properties from config
  989. *
  990. * @param mixed Date representation (string, timestamp or DateTime object)
  991. * @param string Date format to use
  992. * @param bool Enables date convertion according to user timezone
  993. *
  994. * @return string Formatted date string
  995. */
  996. public function format_date($date, $format = null, $convert = true)
  997. {
  998. if (is_object($date) && is_a($date, 'DateTime')) {
  999. $timestamp = $date->format('U');
  1000. }
  1001. else {
  1002. if (!empty($date)) {
  1003. $timestamp = rcube_utils::strtotime($date);
  1004. }
  1005. if (empty($timestamp)) {
  1006. return '';
  1007. }
  1008. try {
  1009. $date = new DateTime("@".$timestamp);
  1010. }
  1011. catch (Exception $e) {
  1012. return '';
  1013. }
  1014. }
  1015. if ($convert) {
  1016. try {
  1017. // convert to the right timezone
  1018. $stz = date_default_timezone_get();
  1019. $tz = new DateTimeZone($this->config->get('timezone'));
  1020. $date->setTimezone($tz);
  1021. date_default_timezone_set($tz->getName());
  1022. $timestamp = $date->format('U');
  1023. }
  1024. catch (Exception $e) {
  1025. }
  1026. }
  1027. // define date format depending on current time
  1028. if (!$format) {
  1029. $now = time();
  1030. $now_date = getdate($now);
  1031. $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
  1032. $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
  1033. $pretty_date = $this->config->get('prettydate');
  1034. if ($pretty_date && $timestamp > $today_limit && $timestamp < $now) {
  1035. $format = $this->config->get('date_today', $this->config->get('time_format', 'H:i'));
  1036. $today = true;
  1037. }
  1038. else if ($pretty_date && $timestamp > $week_limit && $timestamp < $now) {
  1039. $format = $this->config->get('date_short', 'D H:i');
  1040. }
  1041. else {
  1042. $format = $this->config->get('date_long', 'Y-m-d H:i');
  1043. }
  1044. }
  1045. // strftime() format
  1046. if (preg_match('/%[a-z]+/i', $format)) {
  1047. $format = strftime($format, $timestamp);
  1048. if ($stz) {
  1049. date_default_timezone_set($stz);
  1050. }
  1051. return $today ? ($this->gettext('today') . ' ' . $format) : $format;
  1052. }
  1053. // parse format string manually in order to provide localized weekday and month names
  1054. // an alternative would be to convert the date() format string to fit with strftime()
  1055. $out = '';
  1056. for ($i=0; $i<strlen($format); $i++) {
  1057. if ($format[$i] == "\\") { // skip escape chars
  1058. continue;
  1059. }
  1060. // write char "as-is"
  1061. if ($format[$i] == ' ' || $format[$i-1] == "\\") {
  1062. $out .= $format[$i];
  1063. }
  1064. // weekday (short)
  1065. else if ($format[$i] == 'D') {
  1066. $out .= $this->gettext(strtolower(date('D', $timestamp)));
  1067. }
  1068. // weekday long
  1069. else if ($format[$i] == 'l') {
  1070. $out .= $this->gettext(strtolower(date('l', $timestamp)));
  1071. }
  1072. // month name (short)
  1073. else if ($format[$i] == 'M') {
  1074. $out .= $this->gettext(strtolower(date('M', $timestamp)));
  1075. }
  1076. // month name (long)
  1077. else if ($format[$i] == 'F') {
  1078. $out .= $this->gettext('long'.strtolower(date('M', $timestamp)));
  1079. }
  1080. else if ($format[$i] == 'x') {
  1081. $out .= strftime('%x %X', $timestamp);
  1082. }
  1083. else {
  1084. $out .= date($format[$i], $timestamp);
  1085. }
  1086. }
  1087. if ($today) {
  1088. $label = $this->gettext('today');
  1089. // replcae $ character with "Today" label (#1486120)
  1090. if (strpos($out, '$') !== false) {
  1091. $out = preg_replace('/\$/', $label, $out, 1);
  1092. }
  1093. else {
  1094. $out = $label . ' ' . $out;
  1095. }
  1096. }
  1097. if ($stz) {
  1098. date_default_timezone_set($stz);
  1099. }
  1100. return $out;
  1101. }
  1102. /**
  1103. * Return folders list in HTML
  1104. *
  1105. * @param array $attrib Named parameters
  1106. *
  1107. * @return string HTML code for the gui object
  1108. */
  1109. public function folder_list($attrib)
  1110. {
  1111. static $a_mailboxes;
  1112. $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
  1113. $rcmail = rcmail::get_instance();
  1114. $storage = $rcmail->get_storage();
  1115. // add some labels to client
  1116. $rcmail->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
  1117. $type = $attrib['type'] ? $attrib['type'] : 'ul';
  1118. unset($attrib['type']);
  1119. if ($type == 'ul' && !$attrib['id']) {
  1120. $attrib['id'] = 'rcmboxlist';
  1121. }
  1122. if (empty($attrib['folder_name'])) {
  1123. $attrib['folder_name'] = '*';
  1124. }
  1125. // get current folder
  1126. $mbox_name = $storage->get_folder();
  1127. // build the folders tree
  1128. if (empty($a_mailboxes)) {
  1129. // get mailbox list
  1130. $a_folders = $storage->list_folders_subscribed(
  1131. '', $attrib['folder_name'], $attrib['folder_filter']);
  1132. $delimiter = $storage->get_hierarchy_delimiter();
  1133. $a_mailboxes = array();
  1134. foreach ($a_folders as $folder) {
  1135. $rcmail->build_folder_tree($a_mailboxes, $folder, $delimiter);
  1136. }
  1137. }
  1138. // allow plugins to alter the folder tree or to localize folder names
  1139. $hook = $rcmail->plugins->exec_hook('render_mailboxlist', array(
  1140. 'list' => $a_mailboxes,
  1141. 'delimiter' => $delimiter,
  1142. 'type' => $type,
  1143. 'attribs' => $attrib,
  1144. ));
  1145. $a_mailboxes = $hook['list'];
  1146. $attrib = $hook['attribs'];
  1147. if ($type == 'select') {
  1148. $attrib['is_escaped'] = true;
  1149. $select = new html_select($attrib);
  1150. // add no-selection option
  1151. if ($attrib['noselection']) {
  1152. $select->add(html::quote($rcmail->gettext($attrib['noselection'])), '');
  1153. }
  1154. $rcmail->render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
  1155. $out = $select->show($attrib['default']);
  1156. }
  1157. else {
  1158. $js_mailboxlist = array();
  1159. $out = html::tag('ul', $attrib, $rcmail->render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
  1160. $rcmail->output->add_gui_object('mailboxlist', $attrib['id']);
  1161. $rcmail->output->set_env('mailboxes', $js_mailboxlist);
  1162. $rcmail->output->set_env('unreadwrap', $attrib['unreadwrap']);
  1163. $rcmail->output->set_env('collapsed_folders', (string)$rcmail->config->get('collapsed_folders'));
  1164. }
  1165. return $out;
  1166. }
  1167. /**
  1168. * Return folders list as html_select object
  1169. *
  1170. * @param array $p Named parameters
  1171. *
  1172. * @return html_select HTML drop-down object
  1173. */
  1174. public function folder_selector($p = array())
  1175. {
  1176. $p += array('maxlength' => 100, 'realnames' => false, 'is_escaped' => true);
  1177. $a_mailboxes = array();
  1178. $storage = $this->get_storage();
  1179. if (empty($p['folder_name'])) {
  1180. $p['folder_name'] = '*';
  1181. }
  1182. if ($p['unsubscribed']) {
  1183. $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
  1184. }
  1185. else {
  1186. $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
  1187. }
  1188. $delimiter = $storage->get_hierarchy_delimiter();
  1189. foreach ($list as $folder) {
  1190. if (empty($p['exceptions']) || !in_array($folder, $p['exceptions'])) {
  1191. $this->build_folder_tree($a_mailboxes, $folder, $delimiter);
  1192. }
  1193. }
  1194. $select = new html_select($p);
  1195. if ($p['noselection']) {
  1196. $select->add(html::quote($p['noselection']), '');
  1197. }
  1198. $this->render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
  1199. return $select;
  1200. }
  1201. /**
  1202. * Create a hierarchical array of the mailbox list
  1203. */
  1204. public function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
  1205. {
  1206. // Handle namespace prefix
  1207. $prefix = '';
  1208. if (!$path) {
  1209. $n_folder = $folder;
  1210. $folder = $this->storage->mod_folder($folder);
  1211. if ($n_folder != $folder) {
  1212. $prefix = substr($n_folder, 0, -strlen($folder));
  1213. }
  1214. }
  1215. $pos = strpos($folder, $delm);
  1216. if ($pos !== false) {
  1217. $subFolders = substr($folder, $pos+1);
  1218. $currentFolder = substr($folder, 0, $pos);
  1219. // sometimes folder has a delimiter as the last character
  1220. if (!strlen($subFolders)) {
  1221. $virtual = false;
  1222. }
  1223. else if (!isset($arrFolders[$currentFolder])) {
  1224. $virtual = true;
  1225. }
  1226. else {
  1227. $virtual = $arrFolders[$currentFolder]['virtual'];
  1228. }
  1229. }
  1230. else {
  1231. $subFolders = false;
  1232. $currentFolder = $folder;
  1233. $virtual = false;
  1234. }
  1235. $path .= $prefix . $currentFolder;
  1236. if (!isset($arrFolders[$currentFolder])) {
  1237. $arrFolders[$currentFolder] = array(
  1238. 'id' => $path,
  1239. 'name' => rcube_charset::convert($currentFolder, 'UTF7-IMAP'),
  1240. 'virtual' => $virtual,
  1241. 'folders' => array());
  1242. }
  1243. else {
  1244. $arrFolders[$currentFolder]['virtual'] = $virtual;
  1245. }
  1246. if (strlen($subFolders)) {
  1247. $this->build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
  1248. }
  1249. }
  1250. /**
  1251. * Return html for a structured list &lt;ul&gt; for the mailbox tree
  1252. */
  1253. public function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
  1254. {
  1255. $maxlength = intval($attrib['maxlength']);
  1256. $realnames = (bool)$attrib['realnames'];
  1257. $msgcounts = $this->storage->get_cache('messagecount');
  1258. $collapsed = $this->config->get('collapsed_folders');
  1259. $out = '';
  1260. foreach ($arrFolders as $key => $folder) {
  1261. $title = null;
  1262. $folder_class = $this->folder_classname($folder['id']);
  1263. $is_collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
  1264. $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
  1265. if ($folder_class && !$realnames) {
  1266. $foldername = $this->gettext($folder_class);
  1267. }
  1268. else {
  1269. $foldername = $folder['name'];
  1270. // shorten the folder name to a given length
  1271. if ($maxlength && $maxlength > 1) {
  1272. $fname = abbreviate_string($foldername, $maxlength);
  1273. if ($fname != $foldername) {
  1274. $title = $foldername;
  1275. }
  1276. $foldername = $fname;
  1277. }
  1278. }
  1279. // make folder name safe for ids and class names
  1280. $folder_id = rcube_utils::html_identifier($folder['id'], true);
  1281. $classes = array('mailbox');
  1282. // set special class for Sent, Drafts, Trash and Junk
  1283. if ($folder_class) {
  1284. $classes[] = $folder_class;
  1285. }
  1286. if ($folder['id'] == $mbox_name) {
  1287. $classes[] = 'selected';
  1288. }
  1289. if ($folder['virtual']) {
  1290. $classes[] = 'virtual';
  1291. }
  1292. else if ($unread) {
  1293. $classes[] = 'unread';
  1294. }
  1295. $js_name = $this->JQ($folder['id']);
  1296. $html_name = $this->Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
  1297. $link_attrib = $folder['virtual'] ? array() : array(
  1298. 'href' => $this->url(array('_mbox' => $folder['id'])),
  1299. 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail::JS_OBJECT_NAME, $js_name),
  1300. 'rel' => $folder['id'],
  1301. 'title' => $title,
  1302. );
  1303. $out .= html::tag('li', array(
  1304. 'id' => "rcmli".$folder_id,
  1305. 'class' => join(' ', $classes),
  1306. 'noclose' => true),
  1307. html::a($link_attrib, $html_name) .
  1308. (!empty($folder['folders']) ? html::div(array(
  1309. 'class' => ($is_collapsed ? 'collapsed' : 'expanded'),
  1310. 'style' => "position:absolute",
  1311. 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail::JS_OBJECT_NAME, $js_name)
  1312. ), '&nbsp;') : ''));
  1313. $jslist[$folder_id] = array(
  1314. 'id' => $folder['id'],
  1315. 'name' => $foldername,
  1316. 'virtual' => $folder['virtual']

Large files files are truncated, but you can click here to view the full file