PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/program/include/rcmail.php

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

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