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

/etc/apps/webmail/program/include/rcmail.php

https://github.com/raiman264/zpanelx
PHP | 2077 lines | 1420 code | 282 blank | 375 comment | 350 complexity | e9320e4ae695e98b2774adbfb7b9e23b MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, LGPL-2.1, CC-BY-SA-4.0, GPL-3.0

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

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