PageRenderTime 31ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/program/include/rcmail.php

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

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