Advertisement
Guest User

RCUBE_IMAP CONFIG Chromium Facs

a guest
Apr 28th, 2016
346
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 143.78 KB | None | 0 0
  1. <?php
  2.  
  3. /*
  4. +-----------------------------------------------------------------------+
  5. | This file is part of the Roundcube Webmail client |
  6. | Copyright (C) 2005-2012, The Roundcube Dev Team |
  7. | Copyright (C) 2011-2012, Kolab Systems AG |
  8. | |
  9. | Licensed under the GNU General Public License version 3 or |
  10. | any later version with exceptions for skins & plugins. |
  11. | See the README file for a full license statement. |
  12. | |
  13. | PURPOSE: |
  14. | IMAP Storage Engine |
  15. +-----------------------------------------------------------------------+
  16. | Author: Thomas Bruederli <roundcube@gmail.com> |
  17. | Author: Aleksander Machniak <alec@alec.pl> |
  18. +-----------------------------------------------------------------------+
  19. */
  20.  
  21. /**
  22. * Interface class for accessing an IMAP server
  23. *
  24. * @package Framework
  25. * @subpackage Storage
  26. * @author Thomas Bruederli <roundcube@gmail.com>
  27. * @author Aleksander Machniak <alec@alec.pl>
  28. */
  29. class rcube_imap extends rcube_storage
  30. {
  31. /**
  32. * Instance of rcube_imap_generic
  33. *
  34. * @var rcube_imap_generic
  35. */
  36. public $conn;
  37.  
  38. /**
  39. * Instance of rcube_imap_cache
  40. *
  41. * @var rcube_imap_cache
  42. */
  43. protected $mcache;
  44.  
  45. /**
  46. * Instance of rcube_cache
  47. *
  48. * @var rcube_cache
  49. */
  50. protected $cache;
  51.  
  52. /**
  53. * Internal (in-memory) cache
  54. *
  55. * @var array
  56. */
  57. protected $icache = array();
  58.  
  59. protected $plugins;
  60. protected $delimiter;
  61. protected $namespace;
  62. protected $sort_field = '';
  63. protected $sort_order = 'DESC';
  64. protected $struct_charset;
  65. protected $uid_id_map = array();
  66. protected $msg_headers = array();
  67. protected $search_set;
  68. protected $search_string = '';
  69. protected $search_charset = '';
  70. protected $search_sort_field = '';
  71. protected $search_threads = false;
  72. protected $search_sorted = false;
  73. protected $options = array('auth_type' => 'check');
  74. protected $caching = false;
  75. protected $messages_caching = false;
  76. protected $threading = false;
  77.  
  78.  
  79. /**
  80. * Object constructor.
  81. */
  82. public function __construct()
  83. {
  84. $this->conn = new rcube_imap_generic();
  85. $this->plugins = rcube::get_instance()->plugins;
  86.  
  87. // Set namespace and delimiter from session,
  88. // so some methods would work before connection
  89. if (isset($_SESSION['imap_namespace'])) {
  90. $this->namespace = $_SESSION['imap_namespace'];
  91. }
  92. if (isset($_SESSION['imap_delimiter'])) {
  93. $this->delimiter = $_SESSION['imap_delimiter'];
  94. }
  95. }
  96.  
  97.  
  98. /**
  99. * Magic getter for backward compat.
  100. *
  101. * @deprecated.
  102. */
  103. public function __get($name)
  104. {
  105. if (isset($this->{$name})) {
  106. return $this->{$name};
  107. }
  108. }
  109.  
  110.  
  111. /**
  112. * Connect to an IMAP server
  113. *
  114. * @param string $host Host to connect
  115. * @param string $user Username for IMAP account
  116. * @param string $pass Password for IMAP account
  117. * @param integer $port Port to connect to
  118. * @param string $use_ssl SSL schema (either ssl or tls) or null if plain connection
  119. *
  120. * @return boolean True on success, False on failure
  121. */
  122. public function connect($host, $user, $pass, $port=143, $use_ssl=null)
  123. {
  124. // check for OpenSSL support in PHP build
  125. if ($use_ssl && extension_loaded('openssl')) {
  126. $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
  127. }
  128. else if ($use_ssl) {
  129. rcube::raise_error(array('code' => 403, 'type' => 'imap',
  130. 'file' => __FILE__, 'line' => __LINE__,
  131. 'message' => "OpenSSL not available"), true, false);
  132. $port = 143;
  133. }
  134.  
  135. $this->options['port'] = $port;
  136.  
  137. if ($this->options['debug']) {
  138. $this->set_debug(true);
  139.  
  140. $this->options['ident'] = array(
  141. 'name' => 'Roundcube',
  142. 'version' => RCUBE_VERSION,
  143. 'php' => PHP_VERSION,
  144. 'os' => PHP_OS,
  145. 'command' => $_SERVER['REQUEST_URI'],
  146. );
  147. }
  148.  
  149. $attempt = 0;
  150. do {
  151. $data = $this->plugins->exec_hook('storage_connect',
  152. array_merge($this->options, array('host' => $host, 'user' => $user,
  153. 'attempt' => ++$attempt)));
  154.  
  155. if (!empty($data['pass'])) {
  156. $pass = $data['pass'];
  157. }
  158.  
  159. $this->conn->connect($data['host'], $data['user'], $pass, $data);
  160. } while(!$this->conn->connected() && $data['retry']);
  161.  
  162. $config = array(
  163. 'host' => $data['host'],
  164. 'user' => $data['user'],
  165. 'password' => $pass,
  166. 'port' => $port,
  167. 'ssl' => $use_ssl,
  168. );
  169.  
  170. $this->options = array_merge($this->options, $config);
  171. $this->connect_done = true;
  172.  
  173. if ($this->conn->connected()) {
  174. // check for session identifier
  175. $session = null;
  176. if (preg_match('/\s+SESSIONID=([^=\s]+)/', $this->conn->result, $m)) {
  177. $session = $m[1];
  178. }
  179.  
  180. // get namespace and delimiter
  181. $this->set_env();
  182.  
  183. // trigger post-connect hook
  184. $this->plugins->exec_hook('storage_connected', array(
  185. 'host' => $host, 'user' => $user, 'session' => $session
  186. ));
  187.  
  188. return true;
  189. }
  190. // write error log
  191. else if ($this->conn->error) {
  192. if ($pass && $user) {
  193. $message = sprintf("Login failed for %s from %s. %s",
  194. $user, rcube_utils::remote_ip(), $this->conn->error);
  195.  
  196. rcube::raise_error(array('code' => 403, 'type' => 'imap',
  197. 'file' => __FILE__, 'line' => __LINE__,
  198. 'message' => $message), true, false);
  199. }
  200. }
  201.  
  202. return false;
  203. }
  204.  
  205.  
  206. /**
  207. * Close IMAP connection.
  208. * Usually done on script shutdown
  209. */
  210. public function close()
  211. {
  212. $this->conn->closeConnection();
  213. if ($this->mcache) {
  214. $this->mcache->close();
  215. }
  216. }
  217.  
  218.  
  219. /**
  220. * Check connection state, connect if not connected.
  221. *
  222. * @return bool Connection state.
  223. */
  224. public function check_connection()
  225. {
  226. // Establish connection if it wasn't done yet
  227. if (!$this->connect_done && !empty($this->options['user'])) {
  228. return $this->connect(
  229. $this->options['host'],
  230. $this->options['user'],
  231. $this->options['password'],
  232. $this->options['port'],
  233. $this->options['ssl']
  234. );
  235. }
  236.  
  237. return $this->is_connected();
  238. }
  239.  
  240.  
  241. /**
  242. * Checks IMAP connection.
  243. *
  244. * @return boolean TRUE on success, FALSE on failure
  245. */
  246. public function is_connected()
  247. {
  248. return $this->conn->connected();
  249. }
  250.  
  251.  
  252. /**
  253. * Returns code of last error
  254. *
  255. * @return int Error code
  256. */
  257. public function get_error_code()
  258. {
  259. return $this->conn->errornum;
  260. }
  261.  
  262.  
  263. /**
  264. * Returns text of last error
  265. *
  266. * @return string Error string
  267. */
  268. public function get_error_str()
  269. {
  270. return $this->conn->error;
  271. }
  272.  
  273.  
  274. /**
  275. * Returns code of last command response
  276. *
  277. * @return int Response code
  278. */
  279. public function get_response_code()
  280. {
  281. switch ($this->conn->resultcode) {
  282. case 'NOPERM':
  283. return self::NOPERM;
  284. case 'READ-ONLY':
  285. return self::READONLY;
  286. case 'TRYCREATE':
  287. return self::TRYCREATE;
  288. case 'INUSE':
  289. return self::INUSE;
  290. case 'OVERQUOTA':
  291. return self::OVERQUOTA;
  292. case 'ALREADYEXISTS':
  293. return self::ALREADYEXISTS;
  294. case 'NONEXISTENT':
  295. return self::NONEXISTENT;
  296. case 'CONTACTADMIN':
  297. return self::CONTACTADMIN;
  298. default:
  299. return self::UNKNOWN;
  300. }
  301. }
  302.  
  303.  
  304. /**
  305. * Activate/deactivate debug mode
  306. *
  307. * @param boolean $dbg True if IMAP conversation should be logged
  308. */
  309. public function set_debug($dbg = true)
  310. {
  311. $this->options['debug'] = $dbg;
  312. $this->conn->setDebug($dbg, array($this, 'debug_handler'));
  313. }
  314.  
  315.  
  316. /**
  317. * Set internal folder reference.
  318. * All operations will be perfomed on this folder.
  319. *
  320. * @param string $folder Folder name
  321. */
  322. public function set_folder($folder)
  323. {
  324. $this->folder = $folder;
  325. }
  326.  
  327.  
  328. /**
  329. * Save a search result for future message listing methods
  330. *
  331. * @param array $set Search set, result from rcube_imap::get_search_set():
  332. * 0 - searching criteria, string
  333. * 1 - search result, rcube_result_index|rcube_result_thread
  334. * 2 - searching character set, string
  335. * 3 - sorting field, string
  336. * 4 - true if sorted, bool
  337. */
  338. public function set_search_set($set)
  339. {
  340. $set = (array)$set;
  341.  
  342. $this->search_string = $set[0];
  343. $this->search_set = $set[1];
  344. $this->search_charset = $set[2];
  345. $this->search_sort_field = $set[3];
  346. $this->search_sorted = $set[4];
  347. $this->search_threads = is_a($this->search_set, 'rcube_result_thread');
  348.  
  349. if (is_a($this->search_set, 'rcube_result_multifolder')) {
  350. $this->set_threading(false);
  351. }
  352. }
  353.  
  354.  
  355. /**
  356. * Return the saved search set as hash array
  357. *
  358. * @return array Search set
  359. */
  360. public function get_search_set()
  361. {
  362. if (empty($this->search_set)) {
  363. return null;
  364. }
  365.  
  366. return array(
  367. $this->search_string,
  368. $this->search_set,
  369. $this->search_charset,
  370. $this->search_sort_field,
  371. $this->search_sorted,
  372. );
  373. }
  374.  
  375.  
  376. /**
  377. * Returns the IMAP server's capability.
  378. *
  379. * @param string $cap Capability name
  380. *
  381. * @return mixed Capability value or TRUE if supported, FALSE if not
  382. */
  383. public function get_capability($cap)
  384. {
  385. $cap = strtoupper($cap);
  386. $sess_key = "STORAGE_$cap";
  387.  
  388. if (!isset($_SESSION[$sess_key])) {
  389. if (!$this->check_connection()) {
  390. return false;
  391. }
  392.  
  393. $_SESSION[$sess_key] = $this->conn->getCapability($cap);
  394. }
  395.  
  396. return $_SESSION[$sess_key];
  397. }
  398.  
  399.  
  400. /**
  401. * Checks the PERMANENTFLAGS capability of the current folder
  402. * and returns true if the given flag is supported by the IMAP server
  403. *
  404. * @param string $flag Permanentflag name
  405. *
  406. * @return boolean True if this flag is supported
  407. */
  408. public function check_permflag($flag)
  409. {
  410. $flag = strtoupper($flag);
  411. $perm_flags = $this->get_permflags($this->folder);
  412. $imap_flag = $this->conn->flags[$flag];
  413.  
  414. return $imap_flag && !empty($perm_flags) && in_array_nocase($imap_flag, $perm_flags);
  415. }
  416.  
  417.  
  418. /**
  419. * Returns PERMANENTFLAGS of the specified folder
  420. *
  421. * @param string $folder Folder name
  422. *
  423. * @return array Flags
  424. */
  425. public function get_permflags($folder)
  426. {
  427. if (!strlen($folder)) {
  428. return array();
  429. }
  430.  
  431. if (!$this->check_connection()) {
  432. return array();
  433. }
  434.  
  435. if ($this->conn->select($folder)) {
  436. $permflags = $this->conn->data['PERMANENTFLAGS'];
  437. }
  438. else {
  439. return array();
  440. }
  441.  
  442. if (!is_array($permflags)) {
  443. $permflags = array();
  444. }
  445.  
  446. return $permflags;
  447. }
  448.  
  449.  
  450. /**
  451. * Returns the delimiter that is used by the IMAP server for folder separation
  452. *
  453. * @return string Delimiter string
  454. * @access public
  455. */
  456. public function get_hierarchy_delimiter()
  457. {
  458. return $this->delimiter;
  459. }
  460.  
  461.  
  462. /**
  463. * Get namespace
  464. *
  465. * @param string $name Namespace array index: personal, other, shared, prefix
  466. *
  467. * @return array Namespace data
  468. */
  469. public function get_namespace($name = null)
  470. {
  471. $ns = $this->namespace;
  472.  
  473. if ($name) {
  474. return isset($ns[$name]) ? $ns[$name] : null;
  475. }
  476.  
  477. unset($ns['prefix']);
  478. return $ns;
  479. }
  480.  
  481.  
  482. /**
  483. * Sets delimiter and namespaces
  484. */
  485. protected function set_env()
  486. {
  487. if ($this->delimiter !== null && $this->namespace !== null) {
  488. return;
  489. }
  490.  
  491. $config = rcube::get_instance()->config;
  492. $imap_personal = $config->get('imap_ns_personal');
  493. $imap_other = $config->get('imap_ns_other');
  494. $imap_shared = $config->get('imap_ns_shared');
  495. $imap_delimiter = $config->get('imap_delimiter');
  496.  
  497. if (!$this->check_connection()) {
  498. return;
  499. }
  500.  
  501. $ns = $this->conn->getNamespace();
  502.  
  503. // Set namespaces (NAMESPACE supported)
  504. if (is_array($ns)) {
  505. $this->namespace = $ns;
  506. }
  507. else {
  508. $this->namespace = array(
  509. 'personal' => NULL,
  510. 'other' => NULL,
  511. 'shared' => NULL,
  512. );
  513. }
  514.  
  515. if ($imap_delimiter) {
  516. $this->delimiter = $imap_delimiter;
  517. }
  518. if (empty($this->delimiter)) {
  519. $this->delimiter = $this->namespace['personal'][0][1];
  520. }
  521. if (empty($this->delimiter)) {
  522. $this->delimiter = $this->conn->getHierarchyDelimiter();
  523. }
  524. if (empty($this->delimiter)) {
  525. $this->delimiter = '/';
  526. }
  527.  
  528. // Overwrite namespaces
  529. if ($imap_personal !== null) {
  530. $this->namespace['personal'] = NULL;
  531. foreach ((array)$imap_personal as $dir) {
  532. $this->namespace['personal'][] = array($dir, $this->delimiter);
  533. }
  534. }
  535. if ($imap_other !== null) {
  536. $this->namespace['other'] = NULL;
  537. foreach ((array)$imap_other as $dir) {
  538. if ($dir) {
  539. $this->namespace['other'][] = array($dir, $this->delimiter);
  540. }
  541. }
  542. }
  543. if ($imap_shared !== null) {
  544. $this->namespace['shared'] = NULL;
  545. foreach ((array)$imap_shared as $dir) {
  546. if ($dir) {
  547. $this->namespace['shared'][] = array($dir, $this->delimiter);
  548. }
  549. }
  550. }
  551.  
  552. // Find personal namespace prefix for mod_folder()
  553. // Prefix can be removed when there is only one personal namespace
  554. if (is_array($this->namespace['personal']) && count($this->namespace['personal']) == 1) {
  555. $this->namespace['prefix'] = $this->namespace['personal'][0][0];
  556. }
  557.  
  558. $_SESSION['imap_namespace'] = $this->namespace;
  559. $_SESSION['imap_delimiter'] = $this->delimiter;
  560. }
  561.  
  562.  
  563. /**
  564. * Get message count for a specific folder
  565. *
  566. * @param string $folder Folder name
  567. * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
  568. * @param boolean $force Force reading from server and update cache
  569. * @param boolean $status Enables storing folder status info (max UID/count),
  570. * required for folder_status()
  571. *
  572. * @return int Number of messages
  573. */
  574. public function count($folder='', $mode='ALL', $force=false, $status=true)
  575. {
  576. if (!strlen($folder)) {
  577. $folder = $this->folder;
  578. }
  579.  
  580. return $this->countmessages($folder, $mode, $force, $status);
  581. }
  582.  
  583.  
  584. /**
  585. * Protected method for getting number of messages
  586. *
  587. * @param string $folder Folder name
  588. * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
  589. * @param boolean $force Force reading from server and update cache
  590. * @param boolean $status Enables storing folder status info (max UID/count),
  591. * required for folder_status()
  592. * @param boolean $no_search Ignore current search result
  593. *
  594. * @return int Number of messages
  595. * @see rcube_imap::count()
  596. */
  597. protected function countmessages($folder, $mode = 'ALL', $force = false, $status = true, $no_search = false)
  598. {
  599. $mode = strtoupper($mode);
  600.  
  601. // Count search set, assume search set is always up-to-date (don't check $force flag)
  602. // @TODO: this could be handled in more reliable way, e.g. a separate method
  603. // maybe in rcube_imap_search
  604. if (!$no_search && $this->search_string && $folder == $this->folder) {
  605. if ($mode == 'ALL') {
  606. return $this->search_set->count_messages();
  607. }
  608. else if ($mode == 'THREADS') {
  609. return $this->search_set->count();
  610. }
  611. }
  612.  
  613. // EXISTS is a special alias for ALL, it allows to get the number
  614. // of all messages in a folder also when search is active and with
  615. // any skip_deleted setting
  616.  
  617. $a_folder_cache = $this->get_cache('messagecount');
  618.  
  619. // return cached value
  620. if (!$force && is_array($a_folder_cache[$folder]) && isset($a_folder_cache[$folder][$mode])) {
  621. return $a_folder_cache[$folder][$mode];
  622. }
  623.  
  624. if (!is_array($a_folder_cache[$folder])) {
  625. $a_folder_cache[$folder] = array();
  626. }
  627.  
  628. if ($mode == 'THREADS') {
  629. $res = $this->threads($folder);
  630. $count = $res->count();
  631.  
  632. if ($status) {
  633. $msg_count = $res->count_messages();
  634. $this->set_folder_stats($folder, 'cnt', $msg_count);
  635. $this->set_folder_stats($folder, 'maxuid', $msg_count ? $this->id2uid($msg_count, $folder) : 0);
  636. }
  637. }
  638. // Need connection here
  639. else if (!$this->check_connection()) {
  640. return 0;
  641. }
  642. // RECENT count is fetched a bit different
  643. else if ($mode == 'RECENT') {
  644. $count = $this->conn->countRecent($folder);
  645. }
  646. // use SEARCH for message counting
  647. else if ($mode != 'EXISTS' && !empty($this->options['skip_deleted'])) {
  648. $search_str = "ALL UNDELETED";
  649. $keys = array('COUNT');
  650.  
  651. if ($mode == 'UNSEEN') {
  652. $search_str .= " UNSEEN";
  653. }
  654. else {
  655. if ($this->messages_caching) {
  656. $keys[] = 'ALL';
  657. }
  658. if ($status) {
  659. $keys[] = 'MAX';
  660. }
  661. }
  662.  
  663. // @TODO: if $mode == 'ALL' we could try to use cache index here
  664.  
  665. // get message count using (E)SEARCH
  666. // not very performant but more precise (using UNDELETED)
  667. $index = $this->conn->search($folder, $search_str, true, $keys);
  668. $count = $index->count();
  669.  
  670. if ($mode == 'ALL') {
  671. // Cache index data, will be used in index_direct()
  672. $this->icache['undeleted_idx'] = $index;
  673.  
  674. if ($status) {
  675. $this->set_folder_stats($folder, 'cnt', $count);
  676. $this->set_folder_stats($folder, 'maxuid', $index->max());
  677. }
  678. }
  679. }
  680. else {
  681. if ($mode == 'UNSEEN') {
  682. $count = $this->conn->countUnseen($folder);
  683. }
  684. else {
  685. $count = $this->conn->countMessages($folder);
  686. if ($status && $mode == 'ALL') {
  687. $this->set_folder_stats($folder, 'cnt', $count);
  688. $this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0);
  689. }
  690. }
  691. }
  692.  
  693. $a_folder_cache[$folder][$mode] = (int)$count;
  694.  
  695. // write back to cache
  696. $this->update_cache('messagecount', $a_folder_cache);
  697.  
  698. return (int)$count;
  699. }
  700.  
  701.  
  702. /**
  703. * Public method for listing message flags
  704. *
  705. * @param string $folder Folder name
  706. * @param array $uids Message UIDs
  707. * @param int $mod_seq Optional MODSEQ value (of last flag update)
  708. *
  709. * @return array Indexed array with message flags
  710. */
  711. public function list_flags($folder, $uids, $mod_seq = null)
  712. {
  713. if (!strlen($folder)) {
  714. $folder = $this->folder;
  715. }
  716.  
  717. if (!$this->check_connection()) {
  718. return array();
  719. }
  720.  
  721. // @TODO: when cache was synchronized in this request
  722. // we might already have asked for flag updates, use it.
  723.  
  724. $flags = $this->conn->fetch($folder, $uids, true, array('FLAGS'), $mod_seq);
  725. $result = array();
  726.  
  727. if (!empty($flags)) {
  728. foreach ($flags as $message) {
  729. $result[$message->uid] = $message->flags;
  730. }
  731. }
  732.  
  733. return $result;
  734. }
  735.  
  736.  
  737. /**
  738. * Public method for listing headers
  739. *
  740. * @param string $folder Folder name
  741. * @param int $page Current page to list
  742. * @param string $sort_field Header field to sort by
  743. * @param string $sort_order Sort order [ASC|DESC]
  744. * @param int $slice Number of slice items to extract from result array
  745. *
  746. * @return array Indexed array with message header objects
  747. */
  748. public function list_messages($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
  749. {
  750. if (!strlen($folder)) {
  751. $folder = $this->folder;
  752. }
  753.  
  754. return $this->_list_messages($folder, $page, $sort_field, $sort_order, $slice);
  755. }
  756.  
  757.  
  758. /**
  759. * protected method for listing message headers
  760. *
  761. * @param string $folder Folder name
  762. * @param int $page Current page to list
  763. * @param string $sort_field Header field to sort by
  764. * @param string $sort_order Sort order [ASC|DESC]
  765. * @param int $slice Number of slice items to extract from result array
  766. *
  767. * @return array Indexed array with message header objects
  768. * @see rcube_imap::list_messages
  769. */
  770. protected function _list_messages($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
  771. {
  772. if (!strlen($folder)) {
  773. return array();
  774. }
  775.  
  776. $this->set_sort_order($sort_field, $sort_order);
  777. $page = $page ? $page : $this->list_page;
  778.  
  779. // use saved message set
  780. if ($this->search_string) {
  781. return $this->list_search_messages($folder, $page, $slice);
  782. }
  783.  
  784. if ($this->threading) {
  785. return $this->list_thread_messages($folder, $page, $slice);
  786. }
  787.  
  788. // get UIDs of all messages in the folder, sorted
  789. $index = $this->index($folder, $this->sort_field, $this->sort_order);
  790.  
  791. if ($index->is_empty()) {
  792. return array();
  793. }
  794.  
  795. $from = ($page-1) * $this->page_size;
  796. $to = $from + $this->page_size;
  797.  
  798. $index->slice($from, $to - $from);
  799.  
  800. if ($slice) {
  801. $index->slice(-$slice, $slice);
  802. }
  803.  
  804. // fetch reqested messages headers
  805. $a_index = $index->get();
  806. $a_msg_headers = $this->fetch_headers($folder, $a_index);
  807.  
  808. return array_values($a_msg_headers);
  809. }
  810.  
  811.  
  812. /**
  813. * protected method for listing message headers using threads
  814. *
  815. * @param string $folder Folder name
  816. * @param int $page Current page to list
  817. * @param int $slice Number of slice items to extract from result array
  818. *
  819. * @return array Indexed array with message header objects
  820. * @see rcube_imap::list_messages
  821. */
  822. protected function list_thread_messages($folder, $page, $slice=0)
  823. {
  824. // get all threads (not sorted)
  825. if ($mcache = $this->get_mcache_engine()) {
  826. $threads = $mcache->get_thread($folder);
  827. }
  828. else {
  829. $threads = $this->threads($folder);
  830. }
  831.  
  832. return $this->fetch_thread_headers($folder, $threads, $page, $slice);
  833. }
  834.  
  835. /**
  836. * Method for fetching threads data
  837. *
  838. * @param string $folder Folder name
  839. *
  840. * @return rcube_imap_thread Thread data object
  841. */
  842. function threads($folder)
  843. {
  844. if ($mcache = $this->get_mcache_engine()) {
  845. // don't store in self's internal cache, cache has it's own internal cache
  846. return $mcache->get_thread($folder);
  847. }
  848.  
  849. if (!empty($this->icache['threads'])) {
  850. if ($this->icache['threads']->get_parameters('MAILBOX') == $folder) {
  851. return $this->icache['threads'];
  852. }
  853. }
  854.  
  855. // get all threads
  856. $result = $this->threads_direct($folder);
  857.  
  858. // add to internal (fast) cache
  859. return $this->icache['threads'] = $result;
  860. }
  861.  
  862.  
  863. /**
  864. * Method for direct fetching of threads data
  865. *
  866. * @param string $folder Folder name
  867. *
  868. * @return rcube_imap_thread Thread data object
  869. */
  870. function threads_direct($folder)
  871. {
  872. if (!$this->check_connection()) {
  873. return new rcube_result_thread();
  874. }
  875.  
  876. // get all threads
  877. return $this->conn->thread($folder, $this->threading,
  878. $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
  879. }
  880.  
  881.  
  882. /**
  883. * protected method for fetching threaded messages headers
  884. *
  885. * @param string $folder Folder name
  886. * @param rcube_result_thread $threads Threads data object
  887. * @param int $page List page number
  888. * @param int $slice Number of threads to slice
  889. *
  890. * @return array Messages headers
  891. */
  892. protected function fetch_thread_headers($folder, $threads, $page, $slice=0)
  893. {
  894. // Sort thread structure
  895. $this->sort_threads($threads);
  896.  
  897. $from = ($page-1) * $this->page_size;
  898. $to = $from + $this->page_size;
  899.  
  900. $threads->slice($from, $to - $from);
  901.  
  902. if ($slice) {
  903. $threads->slice(-$slice, $slice);
  904. }
  905.  
  906. // Get UIDs of all messages in all threads
  907. $a_index = $threads->get();
  908.  
  909. // fetch reqested headers from server
  910. $a_msg_headers = $this->fetch_headers($folder, $a_index);
  911.  
  912. unset($a_index);
  913.  
  914. // Set depth, has_children and unread_children fields in headers
  915. $this->set_thread_flags($a_msg_headers, $threads);
  916.  
  917. return array_values($a_msg_headers);
  918. }
  919.  
  920.  
  921. /**
  922. * protected method for setting threaded messages flags:
  923. * depth, has_children and unread_children
  924. *
  925. * @param array $headers Reference to headers array indexed by message UID
  926. * @param rcube_result_thread $threads Threads data object
  927. *
  928. * @return array Message headers array indexed by message UID
  929. */
  930. protected function set_thread_flags(&$headers, $threads)
  931. {
  932. $parents = array();
  933.  
  934. list ($msg_depth, $msg_children) = $threads->get_thread_data();
  935.  
  936. foreach ($headers as $uid => $header) {
  937. $depth = $msg_depth[$uid];
  938. $parents = array_slice($parents, 0, $depth);
  939.  
  940. if (!empty($parents)) {
  941. $headers[$uid]->parent_uid = end($parents);
  942. if (empty($header->flags['SEEN']))
  943. $headers[$parents[0]]->unread_children++;
  944. }
  945. array_push($parents, $uid);
  946.  
  947. $headers[$uid]->depth = $depth;
  948. $headers[$uid]->has_children = $msg_children[$uid];
  949. }
  950. }
  951.  
  952.  
  953. /**
  954. * protected method for listing a set of message headers (search results)
  955. *
  956. * @param string $folder Folder name
  957. * @param int $page Current page to list
  958. * @param int $slice Number of slice items to extract from result array
  959. *
  960. * @return array Indexed array with message header objects
  961. */
  962. protected function list_search_messages($folder, $page, $slice=0)
  963. {
  964. if (!strlen($folder) || empty($this->search_set) || $this->search_set->is_empty()) {
  965. return array();
  966. }
  967.  
  968. // gather messages from a multi-folder search
  969. if ($this->search_set->multi) {
  970. $page_size = $this->page_size;
  971. $sort_field = $this->sort_field;
  972. $search_set = $this->search_set;
  973.  
  974. // prepare paging
  975. $cnt = $search_set->count();
  976. $from = ($page-1) * $page_size;
  977. $to = $from + $page_size;
  978. $slice_length = min($page_size, $cnt - $from);
  979.  
  980. // fetch resultset headers, sort and slice them
  981. if (!empty($sort_field)) {
  982. $this->sort_field = null;
  983. $this->page_size = 1000; // fetch up to 1000 matching messages per folder
  984. $this->threading = false;
  985.  
  986. $a_msg_headers = array();
  987. foreach ($search_set->sets as $resultset) {
  988. if (!$resultset->is_empty()) {
  989. $this->search_set = $resultset;
  990. $this->search_threads = $resultset instanceof rcube_result_thread;
  991. $a_msg_headers = array_merge($a_msg_headers, $this->list_search_messages($resultset->get_parameters('MAILBOX'), 1));
  992. }
  993. }
  994.  
  995. // sort headers
  996. if (!empty($a_msg_headers)) {
  997. $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $sort_field, $this->sort_order);
  998. }
  999.  
  1000. // store (sorted) message index
  1001. $search_set->set_message_index($a_msg_headers, $sort_field, $this->sort_order);
  1002.  
  1003. // only return the requested part of the set
  1004. $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length);
  1005. }
  1006. else {
  1007. if ($this->sort_order != $search_set->get_parameters('ORDER')) {
  1008. $search_set->revert();
  1009. }
  1010.  
  1011. // slice resultset first...
  1012. $fetch = array();
  1013. foreach (array_slice($search_set->get(), $from, $slice_length) as $msg_id) {
  1014. list($uid, $folder) = explode('-', $msg_id, 2);
  1015. $fetch[$folder][] = $uid;
  1016. }
  1017.  
  1018. // ... and fetch the requested set of headers
  1019. $a_msg_headers = array();
  1020. foreach ($fetch as $folder => $a_index) {
  1021. $a_msg_headers = array_merge($a_msg_headers, array_values($this->fetch_headers($folder, $a_index)));
  1022. }
  1023. }
  1024.  
  1025. if ($slice) {
  1026. $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice);
  1027. }
  1028.  
  1029. // restore members
  1030. $this->sort_field = $sort_field;
  1031. $this->page_size = $page_size;
  1032. $this->search_set = $search_set;
  1033.  
  1034. return $a_msg_headers;
  1035. }
  1036.  
  1037. // use saved messages from searching
  1038. if ($this->threading) {
  1039. return $this->list_search_thread_messages($folder, $page, $slice);
  1040. }
  1041.  
  1042. // search set is threaded, we need a new one
  1043. if ($this->search_threads) {
  1044. $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
  1045. }
  1046.  
  1047. $index = clone $this->search_set;
  1048. $from = ($page-1) * $this->page_size;
  1049. $to = $from + $this->page_size;
  1050.  
  1051. // return empty array if no messages found
  1052. if ($index->is_empty()) {
  1053. return array();
  1054. }
  1055.  
  1056. // quickest method (default sorting)
  1057. if (!$this->search_sort_field && !$this->sort_field) {
  1058. $got_index = true;
  1059. }
  1060. // sorted messages, so we can first slice array and then fetch only wanted headers
  1061. else if ($this->search_sorted) { // SORT searching result
  1062. $got_index = true;
  1063. // reset search set if sorting field has been changed
  1064. if ($this->sort_field && $this->search_sort_field != $this->sort_field) {
  1065. $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
  1066.  
  1067. $index = clone $this->search_set;
  1068.  
  1069. // return empty array if no messages found
  1070. if ($index->is_empty()) {
  1071. return array();
  1072. }
  1073. }
  1074. }
  1075.  
  1076. if ($got_index) {
  1077. if ($this->sort_order != $index->get_parameters('ORDER')) {
  1078. $index->revert();
  1079. }
  1080.  
  1081. // get messages uids for one page
  1082. $index->slice($from, $to-$from);
  1083.  
  1084. if ($slice) {
  1085. $index->slice(-$slice, $slice);
  1086. }
  1087.  
  1088. // fetch headers
  1089. $a_index = $index->get();
  1090. $a_msg_headers = $this->fetch_headers($folder, $a_index);
  1091.  
  1092. return array_values($a_msg_headers);
  1093. }
  1094.  
  1095. // SEARCH result, need sorting
  1096. $cnt = $index->count();
  1097.  
  1098. // 300: experimantal value for best result
  1099. if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) {
  1100. // use memory less expensive (and quick) method for big result set
  1101. $index = clone $this->index('', $this->sort_field, $this->sort_order);
  1102. // get messages uids for one page...
  1103. $index->slice($from, min($cnt-$from, $this->page_size));
  1104.  
  1105. if ($slice) {
  1106. $index->slice(-$slice, $slice);
  1107. }
  1108.  
  1109. // ...and fetch headers
  1110. $a_index = $index->get();
  1111. $a_msg_headers = $this->fetch_headers($folder, $a_index);
  1112.  
  1113. return array_values($a_msg_headers);
  1114. }
  1115. else {
  1116. // for small result set we can fetch all messages headers
  1117. $a_index = $index->get();
  1118. $a_msg_headers = $this->fetch_headers($folder, $a_index, false);
  1119.  
  1120. // return empty array if no messages found
  1121. if (!is_array($a_msg_headers) || empty($a_msg_headers)) {
  1122. return array();
  1123. }
  1124.  
  1125. if (!$this->check_connection()) {
  1126. return array();
  1127. }
  1128.  
  1129. // if not already sorted
  1130. $a_msg_headers = $this->conn->sortHeaders(
  1131. $a_msg_headers, $this->sort_field, $this->sort_order);
  1132.  
  1133. // only return the requested part of the set
  1134. $slice_length = min($this->page_size, $cnt - ($to > $cnt ? $from : $to));
  1135. $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length);
  1136.  
  1137. if ($slice) {
  1138. $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice);
  1139. }
  1140.  
  1141. return $a_msg_headers;
  1142. }
  1143. }
  1144.  
  1145.  
  1146. /**
  1147. * protected method for listing a set of threaded message headers (search results)
  1148. *
  1149. * @param string $folder Folder name
  1150. * @param int $page Current page to list
  1151. * @param int $slice Number of slice items to extract from result array
  1152. *
  1153. * @return array Indexed array with message header objects
  1154. * @see rcube_imap::list_search_messages()
  1155. */
  1156. protected function list_search_thread_messages($folder, $page, $slice=0)
  1157. {
  1158. // update search_set if previous data was fetched with disabled threading
  1159. if (!$this->search_threads) {
  1160. if ($this->search_set->is_empty()) {
  1161. return array();
  1162. }
  1163. $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
  1164. }
  1165.  
  1166. return $this->fetch_thread_headers($folder, clone $this->search_set, $page, $slice);
  1167. }
  1168.  
  1169.  
  1170. /**
  1171. * Fetches messages headers (by UID)
  1172. *
  1173. * @param string $folder Folder name
  1174. * @param array $msgs Message UIDs
  1175. * @param bool $sort Enables result sorting by $msgs
  1176. * @param bool $force Disables cache use
  1177. *
  1178. * @return array Messages headers indexed by UID
  1179. */
  1180. function fetch_headers($folder, $msgs, $sort = true, $force = false)
  1181. {
  1182. if (empty($msgs)) {
  1183. return array();
  1184. }
  1185.  
  1186. if (!$force && ($mcache = $this->get_mcache_engine())) {
  1187. $headers = $mcache->get_messages($folder, $msgs);
  1188. }
  1189. else if (!$this->check_connection()) {
  1190. return array();
  1191. }
  1192. else {
  1193. // fetch reqested headers from server
  1194. $headers = $this->conn->fetchHeaders(
  1195. $folder, $msgs, true, false, $this->get_fetch_headers());
  1196. }
  1197.  
  1198. if (empty($headers)) {
  1199. return array();
  1200. }
  1201.  
  1202. foreach ($headers as $h) {
  1203. $h->folder = $folder;
  1204. $a_msg_headers[$h->uid] = $h;
  1205. }
  1206.  
  1207. if ($sort) {
  1208. // use this class for message sorting
  1209. $sorter = new rcube_message_header_sorter();
  1210. $sorter->set_index($msgs);
  1211. $sorter->sort_headers($a_msg_headers);
  1212. }
  1213.  
  1214. return $a_msg_headers;
  1215. }
  1216.  
  1217.  
  1218. /**
  1219. * Returns current status of a folder (compared to the last time use)
  1220. *
  1221. * We compare the maximum UID to determine the number of
  1222. * new messages because the RECENT flag is not reliable.
  1223. *
  1224. * @param string $folder Folder name
  1225. * @param array $diff Difference data
  1226. *
  1227. * @return int Folder status
  1228. */
  1229. public function folder_status($folder = null, &$diff = array())
  1230. {
  1231. if (!strlen($folder)) {
  1232. $folder = $this->folder;
  1233. }
  1234. $old = $this->get_folder_stats($folder);
  1235.  
  1236. // refresh message count -> will update
  1237. $this->countmessages($folder, 'ALL', true, true, true);
  1238.  
  1239. $result = 0;
  1240.  
  1241. if (empty($old)) {
  1242. return $result;
  1243. }
  1244.  
  1245. $new = $this->get_folder_stats($folder);
  1246.  
  1247. // got new messages
  1248. if ($new['maxuid'] > $old['maxuid']) {
  1249. $result += 1;
  1250. // get new message UIDs range, that can be used for example
  1251. // to get the data of these messages
  1252. $diff['new'] = ($old['maxuid'] + 1 < $new['maxuid'] ? ($old['maxuid']+1).':' : '') . $new['maxuid'];
  1253. }
  1254. // some messages has been deleted
  1255. if ($new['cnt'] < $old['cnt']) {
  1256. $result += 2;
  1257. }
  1258.  
  1259. // @TODO: optional checking for messages flags changes (?)
  1260. // @TODO: UIDVALIDITY checking
  1261.  
  1262. return $result;
  1263. }
  1264.  
  1265.  
  1266. /**
  1267. * Stores folder statistic data in session
  1268. * @TODO: move to separate DB table (cache?)
  1269. *
  1270. * @param string $folder Folder name
  1271. * @param string $name Data name
  1272. * @param mixed $data Data value
  1273. */
  1274. protected function set_folder_stats($folder, $name, $data)
  1275. {
  1276. $_SESSION['folders'][$folder][$name] = $data;
  1277. }
  1278.  
  1279.  
  1280. /**
  1281. * Gets folder statistic data
  1282. *
  1283. * @param string $folder Folder name
  1284. *
  1285. * @return array Stats data
  1286. */
  1287. protected function get_folder_stats($folder)
  1288. {
  1289. if ($_SESSION['folders'][$folder]) {
  1290. return (array) $_SESSION['folders'][$folder];
  1291. }
  1292.  
  1293. return array();
  1294. }
  1295.  
  1296.  
  1297. /**
  1298. * Return sorted list of message UIDs
  1299. *
  1300. * @param string $folder Folder to get index from
  1301. * @param string $sort_field Sort column
  1302. * @param string $sort_order Sort order [ASC, DESC]
  1303. * @param bool $no_threads Get not threaded index
  1304. * @param bool $no_search Get index not limited to search result (optionally)
  1305. *
  1306. * @return rcube_result_index|rcube_result_thread List of messages (UIDs)
  1307. */
  1308. public function index($folder = '', $sort_field = NULL, $sort_order = NULL,
  1309. $no_threads = false, $no_search = false
  1310. ) {
  1311. if (!$no_threads && $this->threading) {
  1312. return $this->thread_index($folder, $sort_field, $sort_order);
  1313. }
  1314.  
  1315. $this->set_sort_order($sort_field, $sort_order);
  1316.  
  1317. if (!strlen($folder)) {
  1318. $folder = $this->folder;
  1319. }
  1320.  
  1321. // we have a saved search result, get index from there
  1322. if ($this->search_string) {
  1323. if ($this->search_set->is_empty()) {
  1324. return new rcube_result_index($folder, '* SORT');
  1325. }
  1326.  
  1327. if ($this->search_set instanceof rcube_result_multifolder) {
  1328. $index = $this->search_set;
  1329. $index->folder = $folder;
  1330. // TODO: handle changed sorting
  1331. }
  1332. // search result is an index with the same sorting?
  1333. else if (($this->search_set instanceof rcube_result_index)
  1334. && ((!$this->sort_field && !$this->search_sorted) ||
  1335. ($this->search_sorted && $this->search_sort_field == $this->sort_field))
  1336. ) {
  1337. $index = $this->search_set;
  1338. }
  1339. // $no_search is enabled when we are not interested in
  1340. // fetching index for search result, e.g. to sort
  1341. // threaded search result we can use full mailbox index.
  1342. // This makes possible to use index from cache
  1343. else if (!$no_search) {
  1344. if (!$this->sort_field) {
  1345. // No sorting needed, just build index from the search result
  1346. // @TODO: do we need to sort by UID here?
  1347. $search = $this->search_set->get_compressed();
  1348. $index = new rcube_result_index($folder, '* ESEARCH ALL ' . $search);
  1349. }
  1350. else {
  1351. $index = $this->index_direct($folder, $this->search_charset,
  1352. $this->sort_field, $this->search_set);
  1353. }
  1354. }
  1355.  
  1356. if (isset($index)) {
  1357. if ($this->sort_order != $index->get_parameters('ORDER')) {
  1358. $index->revert();
  1359. }
  1360.  
  1361. return $index;
  1362. }
  1363. }
  1364.  
  1365. // check local cache
  1366. if ($mcache = $this->get_mcache_engine()) {
  1367. return $mcache->get_index($folder, $this->sort_field, $this->sort_order);
  1368. }
  1369.  
  1370. // fetch from IMAP server
  1371. return $this->index_direct($folder, $this->sort_field, $this->sort_order);
  1372. }
  1373.  
  1374.  
  1375. /**
  1376. * Return sorted list of message UIDs ignoring current search settings.
  1377. * Doesn't uses cache by default.
  1378. *
  1379. * @param string $folder Folder to get index from
  1380. * @param string $sort_field Sort column
  1381. * @param string $sort_order Sort order [ASC, DESC]
  1382. * @param rcube_result_* $search Optional messages set to limit the result
  1383. *
  1384. * @return rcube_result_index Sorted list of message UIDs
  1385. */
  1386. public function index_direct($folder, $sort_field = null, $sort_order = null, $search = null)
  1387. {
  1388. if (!empty($search)) {
  1389. $search = $search->get_compressed();
  1390. }
  1391.  
  1392. // use message index sort as default sorting
  1393. if (!$sort_field) {
  1394. // use search result from count() if possible
  1395. if (empty($search) && $this->options['skip_deleted']
  1396. && !empty($this->icache['undeleted_idx'])
  1397. && $this->icache['undeleted_idx']->get_parameters('ALL') !== null
  1398. && $this->icache['undeleted_idx']->get_parameters('MAILBOX') == $folder
  1399. ) {
  1400. $index = $this->icache['undeleted_idx'];
  1401. }
  1402. else if (!$this->check_connection()) {
  1403. return new rcube_result_index();
  1404. }
  1405. else {
  1406. $query = $this->options['skip_deleted'] ? 'UNDELETED' : '';
  1407. if ($search) {
  1408. $query = trim($query . ' UID ' . $search);
  1409. }
  1410.  
  1411. $index = $this->conn->search($folder, $query, true);
  1412. }
  1413. }
  1414. else if (!$this->check_connection()) {
  1415. return new rcube_result_index();
  1416. }
  1417. // fetch complete message index
  1418. else {
  1419. if ($this->get_capability('SORT')) {
  1420. $query = $this->options['skip_deleted'] ? 'UNDELETED' : '';
  1421. if ($search) {
  1422. $query = trim($query . ' UID ' . $search);
  1423. }
  1424.  
  1425. $index = $this->conn->sort($folder, $sort_field, $query, true);
  1426. }
  1427.  
  1428. if (empty($index) || $index->is_error()) {
  1429. $index = $this->conn->index($folder, $search ? $search : "1:*",
  1430. $sort_field, $this->options['skip_deleted'],
  1431. $search ? true : false, true);
  1432. }
  1433. }
  1434.  
  1435. if ($sort_order != $index->get_parameters('ORDER')) {
  1436. $index->revert();
  1437. }
  1438.  
  1439. return $index;
  1440. }
  1441.  
  1442.  
  1443. /**
  1444. * Return index of threaded message UIDs
  1445. *
  1446. * @param string $folder Folder to get index from
  1447. * @param string $sort_field Sort column
  1448. * @param string $sort_order Sort order [ASC, DESC]
  1449. *
  1450. * @return rcube_result_thread Message UIDs
  1451. */
  1452. public function thread_index($folder='', $sort_field=NULL, $sort_order=NULL)
  1453. {
  1454. if (!strlen($folder)) {
  1455. $folder = $this->folder;
  1456. }
  1457.  
  1458. // we have a saved search result, get index from there
  1459. if ($this->search_string && $this->search_threads && $folder == $this->folder) {
  1460. $threads = $this->search_set;
  1461. }
  1462. else {
  1463. // get all threads (default sort order)
  1464. $threads = $this->threads($folder);
  1465. }
  1466.  
  1467. $this->set_sort_order($sort_field, $sort_order);
  1468. $this->sort_threads($threads);
  1469.  
  1470. return $threads;
  1471. }
  1472.  
  1473.  
  1474. /**
  1475. * Sort threaded result, using THREAD=REFS method if available.
  1476. * If not, use any method and re-sort the result in THREAD=REFS way.
  1477. *
  1478. * @param rcube_result_thread $threads Threads result set
  1479. */
  1480. protected function sort_threads($threads)
  1481. {
  1482. if ($threads->is_empty()) {
  1483. return;
  1484. }
  1485.  
  1486. // THREAD=ORDEREDSUBJECT: sorting by sent date of root message
  1487. // THREAD=REFERENCES: sorting by sent date of root message
  1488. // THREAD=REFS: sorting by the most recent date in each thread
  1489.  
  1490. if ($this->threading != 'REFS' || ($this->sort_field && $this->sort_field != 'date')) {
  1491. $sortby = $this->sort_field ? $this->sort_field : 'date';
  1492. $index = $this->index($this->folder, $sortby, $this->sort_order, true, true);
  1493.  
  1494. if (!$index->is_empty()) {
  1495. $threads->sort($index);
  1496. }
  1497. }
  1498. else if ($this->sort_order != $threads->get_parameters('ORDER')) {
  1499. $threads->revert();
  1500. }
  1501. }
  1502.  
  1503.  
  1504. /**
  1505. * Invoke search request to IMAP server
  1506. *
  1507. * @param string $folder Folder name to search in
  1508. * @param string $search Search criteria
  1509. * @param string $charset Search charset
  1510. * @param string $sort_field Header field to sort by
  1511. *
  1512. * @return rcube_result_index Search result object
  1513. * @todo: Search criteria should be provided in non-IMAP format, eg. array
  1514. */
  1515. public function search($folder = '', $search = 'ALL', $charset = null, $sort_field = null)
  1516. {
  1517. if (!$search) {
  1518. $search = 'ALL';
  1519. }
  1520.  
  1521. if ((is_array($folder) && empty($folder)) || (!is_array($folder) && !strlen($folder))) {
  1522. $folder = $this->folder;
  1523. }
  1524.  
  1525. $plugin = $this->plugins->exec_hook('imap_search_before', array(
  1526. 'folder' => $folder,
  1527. 'search' => $search,
  1528. 'charset' => $charset,
  1529. 'sort_field' => $sort_field,
  1530. 'threading' => $this->threading,
  1531. ));
  1532.  
  1533. $folder = $plugin['folder'];
  1534. $search = $plugin['search'];
  1535. $charset = $plugin['charset'];
  1536. $sort_field = $plugin['sort_field'];
  1537. $results = $plugin['result'];
  1538.  
  1539. // multi-folder search
  1540. if (!$results && is_array($folder) && count($folder) > 1 && $search != 'ALL') {
  1541. // connect IMAP to have all the required classes and settings loaded
  1542. $this->check_connection();
  1543.  
  1544. // disable threading
  1545. $this->threading = false;
  1546.  
  1547. $searcher = new rcube_imap_search($this->options, $this->conn);
  1548.  
  1549. // set limit to not exceed the client's request timeout
  1550. $searcher->set_timelimit(60);
  1551.  
  1552. // continue existing incomplete search
  1553. if (!empty($this->search_set) && $this->search_set->incomplete && $search == $this->search_string) {
  1554. $searcher->set_results($this->search_set);
  1555. }
  1556.  
  1557. // execute the search
  1558. $results = $searcher->exec(
  1559. $folder,
  1560. $search,
  1561. $charset ? $charset : $this->default_charset,
  1562. $sort_field && $this->get_capability('SORT') ? $sort_field : null,
  1563. $this->threading
  1564. );
  1565. }
  1566. else if (!$results) {
  1567. $folder = is_array($folder) ? $folder[0] : $folder;
  1568. $search = is_array($search) ? $search[$folder] : $search;
  1569. $results = $this->search_index($folder, $search, $charset, $sort_field);
  1570. }
  1571.  
  1572. $sorted = $this->threading || $this->search_sorted || $plugin['search_sorted'] ? true : false;
  1573.  
  1574. $this->set_search_set(array($search, $results, $charset, $sort_field, $sorted));
  1575.  
  1576. return $results;
  1577. }
  1578.  
  1579.  
  1580. /**
  1581. * Direct (real and simple) SEARCH request (without result sorting and caching).
  1582. *
  1583. * @param string $mailbox Mailbox name to search in
  1584. * @param string $str Search string
  1585. *
  1586. * @return rcube_result_index Search result (UIDs)
  1587. */
  1588. public function search_once($folder = null, $str = 'ALL')
  1589. {
  1590. if (!$this->check_connection()) {
  1591. return new rcube_result_index();
  1592. }
  1593.  
  1594. if (!$str) {
  1595. $str = 'ALL';
  1596. }
  1597.  
  1598. // multi-folder search
  1599. if (is_array($folder) && count($folder) > 1) {
  1600. $searcher = new rcube_imap_search($this->options, $this->conn);
  1601. $index = $searcher->exec($folder, $str, $this->default_charset);
  1602. }
  1603. else {
  1604. $folder = is_array($folder) ? $folder[0] : $folder;
  1605. if (!strlen($folder)) {
  1606. $folder = $this->folder;
  1607. }
  1608. $index = $this->conn->search($folder, $str, true);
  1609. }
  1610.  
  1611. return $index;
  1612. }
  1613.  
  1614.  
  1615. /**
  1616. * protected search method
  1617. *
  1618. * @param string $folder Folder name
  1619. * @param string $criteria Search criteria
  1620. * @param string $charset Charset
  1621. * @param string $sort_field Sorting field
  1622. *
  1623. * @return rcube_result_index|rcube_result_thread Search results (UIDs)
  1624. * @see rcube_imap::search()
  1625. */
  1626. protected function search_index($folder, $criteria='ALL', $charset=NULL, $sort_field=NULL)
  1627. {
  1628. if (!$this->check_connection()) {
  1629. if ($this->threading) {
  1630. return new rcube_result_thread();
  1631. }
  1632. else {
  1633. return new rcube_result_index();
  1634. }
  1635. }
  1636.  
  1637. if ($this->options['skip_deleted'] && !preg_match('/UNDELETED/', $criteria)) {
  1638. $criteria = 'UNDELETED '.$criteria;
  1639. }
  1640.  
  1641. // unset CHARSET if criteria string is ASCII, this way
  1642. // SEARCH won't be re-sent after "unsupported charset" response
  1643. if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) {
  1644. $charset = 'US-ASCII';
  1645. }
  1646.  
  1647. if ($this->threading) {
  1648. $threads = $this->conn->thread($folder, $this->threading, $criteria, true, $charset);
  1649.  
  1650. // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
  1651. // but I've seen that Courier doesn't support UTF-8)
  1652. if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
  1653. $threads = $this->conn->thread($folder, $this->threading,
  1654. self::convert_criteria($criteria, $charset), true, 'US-ASCII');
  1655. }
  1656.  
  1657. return $threads;
  1658. }
  1659.  
  1660. if ($sort_field && $this->get_capability('SORT')) {
  1661. $charset = $charset ? $charset : $this->default_charset;
  1662. $messages = $this->conn->sort($folder, $sort_field, $criteria, true, $charset);
  1663.  
  1664. // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
  1665. // but I've seen Courier with disabled UTF-8 support)
  1666. if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
  1667. $messages = $this->conn->sort($folder, $sort_field,
  1668. self::convert_criteria($criteria, $charset), true, 'US-ASCII');
  1669. }
  1670.  
  1671. if (!$messages->is_error()) {
  1672. $this->search_sorted = true;
  1673. return $messages;
  1674. }
  1675. }
  1676.  
  1677. $messages = $this->conn->search($folder,
  1678. ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true);
  1679.  
  1680. // Error, try with US-ASCII (some servers may support only US-ASCII)
  1681. if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
  1682. $messages = $this->conn->search($folder,
  1683. self::convert_criteria($criteria, $charset), true);
  1684. }
  1685.  
  1686. $this->search_sorted = false;
  1687.  
  1688. return $messages;
  1689. }
  1690.  
  1691.  
  1692. /**
  1693. * Converts charset of search criteria string
  1694. *
  1695. * @param string $str Search string
  1696. * @param string $charset Original charset
  1697. * @param string $dest_charset Destination charset (default US-ASCII)
  1698. *
  1699. * @return string Search string
  1700. */
  1701. public static function convert_criteria($str, $charset, $dest_charset='US-ASCII')
  1702. {
  1703. // convert strings to US_ASCII
  1704. if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
  1705. $last = 0; $res = '';
  1706. foreach ($matches[1] as $m) {
  1707. $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
  1708. $string = substr($str, $string_offset - 1, $m[0]);
  1709. $string = rcube_charset::convert($string, $charset, $dest_charset);
  1710.  
  1711. if ($string === false || !strlen($string)) {
  1712. continue;
  1713. }
  1714.  
  1715. $res .= substr($str, $last, $m[1] - $last - 1) . rcube_imap_generic::escape($string);
  1716. $last = $m[0] + $string_offset - 1;
  1717. }
  1718.  
  1719. if ($last < strlen($str)) {
  1720. $res .= substr($str, $last, strlen($str)-$last);
  1721. }
  1722. }
  1723. // strings for conversion not found
  1724. else {
  1725. $res = $str;
  1726. }
  1727.  
  1728. return $res;
  1729. }
  1730.  
  1731.  
  1732. /**
  1733. * Refresh saved search set
  1734. *
  1735. * @return array Current search set
  1736. */
  1737. public function refresh_search()
  1738. {
  1739. if (!empty($this->search_string)) {
  1740. $this->search(
  1741. is_object($this->search_set) ? $this->search_set->get_parameters('MAILBOX') : '',
  1742. $this->search_string,
  1743. $this->search_charset,
  1744. $this->search_sort_field
  1745. );
  1746. }
  1747.  
  1748. return $this->get_search_set();
  1749. }
  1750.  
  1751. /**
  1752. * Flag certain result subsets as 'incomplete'.
  1753. * For subsequent refresh_search() calls to only refresh the updated parts.
  1754. */
  1755. protected function set_search_dirty($folder)
  1756. {
  1757. if ($this->search_set && is_a($this->search_set, 'rcube_result_multifolder')) {
  1758. if ($subset = $this->search_set->get_set($folder)) {
  1759. $subset->incomplete = $this->search_set->incomplete = true;
  1760. }
  1761. }
  1762. }
  1763.  
  1764.  
  1765. /**
  1766. * Return message headers object of a specific message
  1767. *
  1768. * @param int $id Message UID
  1769. * @param string $folder Folder to read from
  1770. * @param bool $force True to skip cache
  1771. *
  1772. * @return rcube_message_header Message headers
  1773. */
  1774. public function get_message_headers($uid, $folder = null, $force = false)
  1775. {
  1776. // decode combined UID-folder identifier
  1777. if (preg_match('/^\d+-.+/', $uid)) {
  1778. list($uid, $folder) = explode('-', $uid, 2);
  1779. }
  1780.  
  1781. if (!strlen($folder)) {
  1782. $folder = $this->folder;
  1783. }
  1784.  
  1785. // get cached headers
  1786. if (!$force && $uid && ($mcache = $this->get_mcache_engine())) {
  1787. $headers = $mcache->get_message($folder, $uid);
  1788. }
  1789. else if (!$this->check_connection()) {
  1790. $headers = false;
  1791. }
  1792. else {
  1793. $headers = $this->conn->fetchHeader(
  1794. $folder, $uid, true, true, $this->get_fetch_headers());
  1795.  
  1796. if (is_object($headers))
  1797. $headers->folder = $folder;
  1798. }
  1799.  
  1800. return $headers;
  1801. }
  1802.  
  1803.  
  1804. /**
  1805. * Fetch message headers and body structure from the IMAP server and build
  1806. * an object structure.
  1807. *
  1808. * @param int $uid Message UID to fetch
  1809. * @param string $folder Folder to read from
  1810. *
  1811. * @return object rcube_message_header Message data
  1812. */
  1813. public function get_message($uid, $folder = null)
  1814. {
  1815. if (!strlen($folder)) {
  1816. $folder = $this->folder;
  1817. }
  1818.  
  1819. // decode combined UID-folder identifier
  1820. if (preg_match('/^\d+-.+/', $uid)) {
  1821. list($uid, $folder) = explode('-', $uid, 2);
  1822. }
  1823.  
  1824. // Check internal cache
  1825. if (!empty($this->icache['message'])) {
  1826. if (($headers = $this->icache['message']) && $headers->uid == $uid) {
  1827. return $headers;
  1828. }
  1829. }
  1830.  
  1831. $headers = $this->get_message_headers($uid, $folder);
  1832.  
  1833. // message doesn't exist?
  1834. if (empty($headers)) {
  1835. return null;
  1836. }
  1837.  
  1838. // structure might be cached
  1839. if (!empty($headers->structure)) {
  1840. return $headers;
  1841. }
  1842.  
  1843. $this->msg_uid = $uid;
  1844.  
  1845. if (!$this->check_connection()) {
  1846. return $headers;
  1847. }
  1848.  
  1849. if (empty($headers->bodystructure)) {
  1850. $headers->bodystructure = $this->conn->getStructure($folder, $uid, true);
  1851. }
  1852.  
  1853. $structure = $headers->bodystructure;
  1854.  
  1855. if (empty($structure)) {
  1856. return $headers;
  1857. }
  1858.  
  1859. // set message charset from message headers
  1860. if ($headers->charset) {
  1861. $this->struct_charset = $headers->charset;
  1862. }
  1863. else {
  1864. $this->struct_charset = $this->structure_charset($structure);
  1865. }
  1866.  
  1867. $headers->ctype = @strtolower($headers->ctype);
  1868.  
  1869. // Here we can recognize malformed BODYSTRUCTURE and
  1870. // 1. [@TODO] parse the message in other way to create our own message structure
  1871. // 2. or just show the raw message body.
  1872. // Example of structure for malformed MIME message:
  1873. // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL)
  1874. if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain'
  1875. && strtolower($structure[0].'/'.$structure[1]) == 'text/plain'
  1876. ) {
  1877. // A special known case "Content-type: text" (#1488968)
  1878. if ($headers->ctype == 'text') {
  1879. $structure[1] = 'plain';
  1880. $headers->ctype = 'text/plain';
  1881. }
  1882. // we can handle single-part messages, by simple fix in structure (#1486898)
  1883. else if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
  1884. $structure[0] = $m[1];
  1885. $structure[1] = $m[2];
  1886. }
  1887. else {
  1888. // Try to parse the message using rcube_mime_decode.
  1889. // We need a better solution, it parses message
  1890. // in memory, which wouldn't work for very big messages,
  1891. // (it uses up to 10x more memory than the message size)
  1892. // it's also buggy and not actively developed
  1893. if ($headers->size && rcube_utils::mem_check($headers->size * 10)) {
  1894. $raw_msg = $this->get_raw_body($uid);
  1895. $struct = rcube_mime::parse_message($raw_msg);
  1896. }
  1897. else {
  1898. return $headers;
  1899. }
  1900. }
  1901. }
  1902.  
  1903. if (empty($struct)) {
  1904. $struct = $this->structure_part($structure, 0, '', $headers);
  1905. }
  1906.  
  1907. // some workarounds on simple messages...
  1908. if (empty($struct->parts)) {
  1909. // ...don't trust given content-type
  1910. if (!empty($headers->ctype)) {
  1911. $struct->mime_id = '1';
  1912. $struct->mimetype = strtolower($headers->ctype);
  1913. list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
  1914. }
  1915.  
  1916. // ...and charset (there's a case described in #1488968 where invalid content-type
  1917. // results in invalid charset in BODYSTRUCTURE)
  1918. if (!empty($headers->charset) && $headers->charset != $struct->ctype_parameters['charset']) {
  1919. $struct->charset = $headers->charset;
  1920. $struct->ctype_parameters['charset'] = $headers->charset;
  1921. }
  1922. }
  1923.  
  1924. $headers->structure = $struct;
  1925.  
  1926. return $this->icache['message'] = $headers;
  1927. }
  1928.  
  1929.  
  1930. /**
  1931. * Build message part object
  1932. *
  1933. * @param array $part
  1934. * @param int $count
  1935. * @param string $parent
  1936. */
  1937. protected function structure_part($part, $count=0, $parent='', $mime_headers=null)
  1938. {
  1939. $struct = new rcube_message_part;
  1940. $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
  1941.  
  1942. // multipart
  1943. if (is_array($part[0])) {
  1944. $struct->ctype_primary = 'multipart';
  1945.  
  1946. /* RFC3501: BODYSTRUCTURE fields of multipart part
  1947. part1 array
  1948. part2 array
  1949. part3 array
  1950. ....
  1951. 1. subtype
  1952. 2. parameters (optional)
  1953. 3. description (optional)
  1954. 4. language (optional)
  1955. 5. location (optional)
  1956. */
  1957.  
  1958. // find first non-array entry
  1959. for ($i=1; $i<count($part); $i++) {
  1960. if (!is_array($part[$i])) {
  1961. $struct->ctype_secondary = strtolower($part[$i]);
  1962. break;
  1963. }
  1964. }
  1965.  
  1966. $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
  1967.  
  1968. // build parts list for headers pre-fetching
  1969. for ($i=0; $i<count($part); $i++) {
  1970. if (!is_array($part[$i])) {
  1971. break;
  1972. }
  1973. // fetch message headers if message/rfc822
  1974. // or named part (could contain Content-Location header)
  1975. if (!is_array($part[$i][0])) {
  1976. $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
  1977. if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') {
  1978. $mime_part_headers[] = $tmp_part_id;
  1979. }
  1980. else if (in_array('name', (array)$part[$i][2]) && empty($part[$i][3])) {
  1981. $mime_part_headers[] = $tmp_part_id;
  1982. }
  1983. }
  1984. }
  1985.  
  1986. // pre-fetch headers of all parts (in one command for better performance)
  1987. // @TODO: we could do this before _structure_part() call, to fetch
  1988. // headers for parts on all levels
  1989. if ($mime_part_headers) {
  1990. $mime_part_headers = $this->conn->fetchMIMEHeaders($this->folder,
  1991. $this->msg_uid, $mime_part_headers);
  1992. }
  1993.  
  1994. $struct->parts = array();
  1995. for ($i=0, $count=0; $i<count($part); $i++) {
  1996. if (!is_array($part[$i])) {
  1997. break;
  1998. }
  1999. $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
  2000. $struct->parts[] = $this->structure_part($part[$i], ++$count, $struct->mime_id,
  2001. $mime_part_headers[$tmp_part_id]);
  2002. }
  2003.  
  2004. return $struct;
  2005. }
  2006.  
  2007. /* RFC3501: BODYSTRUCTURE fields of non-multipart part
  2008. 0. type
  2009. 1. subtype
  2010. 2. parameters
  2011. 3. id
  2012. 4. description
  2013. 5. encoding
  2014. 6. size
  2015. -- text
  2016. 7. lines
  2017. -- message/rfc822
  2018. 7. envelope structure
  2019. 8. body structure
  2020. 9. lines
  2021. --
  2022. x. md5 (optional)
  2023. x. disposition (optional)
  2024. x. language (optional)
  2025. x. location (optional)
  2026. */
  2027.  
  2028. // regular part
  2029. $struct->ctype_primary = strtolower($part[0]);
  2030. $struct->ctype_secondary = strtolower($part[1]);
  2031. $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary;
  2032.  
  2033. // read content type parameters
  2034. if (is_array($part[2])) {
  2035. $struct->ctype_parameters = array();
  2036. for ($i=0; $i<count($part[2]); $i+=2) {
  2037. $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
  2038. }
  2039.  
  2040. if (isset($struct->ctype_parameters['charset'])) {
  2041. $struct->charset = $struct->ctype_parameters['charset'];
  2042. }
  2043. }
  2044.  
  2045. // #1487700: workaround for lack of charset in malformed structure
  2046. if (empty($struct->charset) && !empty($mime_headers) && $mime_headers->charset) {
  2047. $struct->charset = $mime_headers->charset;
  2048. }
  2049.  
  2050. // read content encoding
  2051. if (!empty($part[5])) {
  2052. $struct->encoding = strtolower($part[5]);
  2053. $struct->headers['content-transfer-encoding'] = $struct->encoding;
  2054. }
  2055.  
  2056. // get part size
  2057. if (!empty($part[6])) {
  2058. $struct->size = intval($part[6]);
  2059. }
  2060.  
  2061. // read part disposition
  2062. $di = 8;
  2063. if ($struct->ctype_primary == 'text') {
  2064. $di += 1;
  2065. }
  2066. else if ($struct->mimetype == 'message/rfc822') {
  2067. $di += 3;
  2068. }
  2069.  
  2070. if (is_array($part[$di]) && count($part[$di]) == 2) {
  2071. $struct->disposition = strtolower($part[$di][0]);
  2072.  
  2073. if (is_array($part[$di][1])) {
  2074. for ($n=0; $n<count($part[$di][1]); $n+=2) {
  2075. $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
  2076. }
  2077. }
  2078. }
  2079.  
  2080. // get message/rfc822's child-parts
  2081. if (is_array($part[8]) && $di != 8) {
  2082. $struct->parts = array();
  2083. for ($i=0, $count=0; $i<count($part[8]); $i++) {
  2084. if (!is_array($part[8][$i])) {
  2085. break;
  2086. }
  2087. $struct->parts[] = $this->structure_part($part[8][$i], ++$count, $struct->mime_id);
  2088. }
  2089. }
  2090.  
  2091. // get part ID
  2092. if (!empty($part[3])) {
  2093. $struct->content_id = $part[3];
  2094. $struct->headers['content-id'] = $part[3];
  2095.  
  2096. if (empty($struct->disposition)) {
  2097. $struct->disposition = 'inline';
  2098. }
  2099. }
  2100.  
  2101. // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
  2102. if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) {
  2103. if (empty($mime_headers)) {
  2104. $mime_headers = $this->conn->fetchPartHeader(
  2105. $this->folder, $this->msg_uid, true, $struct->mime_id);
  2106. }
  2107.  
  2108. if (is_string($mime_headers)) {
  2109. $struct->headers = rcube_mime::parse_headers($mime_headers) + $struct->headers;
  2110. }
  2111. else if (is_object($mime_headers)) {
  2112. $struct->headers = get_object_vars($mime_headers) + $struct->headers;
  2113. }
  2114.  
  2115. // get real content-type of message/rfc822
  2116. if ($struct->mimetype == 'message/rfc822') {
  2117. // single-part
  2118. if (!is_array($part[8][0])) {
  2119. $struct->real_mimetype = strtolower($part[8][0] . '/' . $part[8][1]);
  2120. }
  2121. // multi-part
  2122. else {
  2123. for ($n=0; $n<count($part[8]); $n++) {
  2124. if (!is_array($part[8][$n])) {
  2125. break;
  2126. }
  2127. }
  2128. $struct->real_mimetype = 'multipart/' . strtolower($part[8][$n]);
  2129. }
  2130. }
  2131.  
  2132. if ($struct->ctype_primary == 'message' && empty($struct->parts)) {
  2133. if (is_array($part[8]) && $di != 8) {
  2134. $struct->parts[] = $this->structure_part($part[8], ++$count, $struct->mime_id);
  2135. }
  2136. }
  2137. }
  2138.  
  2139. // normalize filename property
  2140. $this->set_part_filename($struct, $mime_headers);
  2141.  
  2142. return $struct;
  2143. }
  2144.  
  2145.  
  2146. /**
  2147. * Set attachment filename from message part structure
  2148. *
  2149. * @param rcube_message_part $part Part object
  2150. * @param string $headers Part's raw headers
  2151. */
  2152. protected function set_part_filename(&$part, $headers=null)
  2153. {
  2154. if (!empty($part->d_parameters['filename'])) {
  2155. $filename_mime = $part->d_parameters['filename'];
  2156. }
  2157. else if (!empty($part->d_parameters['filename*'])) {
  2158. $filename_encoded = $part->d_parameters['filename*'];
  2159. }
  2160. else if (!empty($part->ctype_parameters['name*'])) {
  2161. $filename_encoded = $part->ctype_parameters['name*'];
  2162. }
  2163. // RFC2231 value continuations
  2164. // TODO: this should be rewrited to support RFC2231 4.1 combinations
  2165. else if (!empty($part->d_parameters['filename*0'])) {
  2166. $i = 0;
  2167. while (isset($part->d_parameters['filename*'.$i])) {
  2168. $filename_mime .= $part->d_parameters['filename*'.$i];
  2169. $i++;
  2170. }
  2171. // some servers (eg. dovecot-1.x) have no support for parameter value continuations
  2172. // we must fetch and parse headers "manually"
  2173. if ($i<2) {
  2174. if (!$headers) {
  2175. $headers = $this->conn->fetchPartHeader(
  2176. $this->folder, $this->msg_uid, true, $part->mime_id);
  2177. }
  2178. $filename_mime = '';
  2179. $i = 0;
  2180. while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
  2181. $filename_mime .= $matches[1];
  2182. $i++;
  2183. }
  2184. }
  2185. }
  2186. else if (!empty($part->d_parameters['filename*0*'])) {
  2187. $i = 0;
  2188. while (isset($part->d_parameters['filename*'.$i.'*'])) {
  2189. $filename_encoded .= $part->d_parameters['filename*'.$i.'*'];
  2190. $i++;
  2191. }
  2192. if ($i<2) {
  2193. if (!$headers) {
  2194. $headers = $this->conn->fetchPartHeader(
  2195. $this->folder, $this->msg_uid, true, $part->mime_id);
  2196. }
  2197. $filename_encoded = '';
  2198. $i = 0; $matches = array();
  2199. while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
  2200. $filename_encoded .= $matches[1];
  2201. $i++;
  2202. }
  2203. }
  2204. }
  2205. else if (!empty($part->ctype_parameters['name*0'])) {
  2206. $i = 0;
  2207. while (isset($part->ctype_parameters['name*'.$i])) {
  2208. $filename_mime .= $part->ctype_parameters['name*'.$i];
  2209. $i++;
  2210. }
  2211. if ($i<2) {
  2212. if (!$headers) {
  2213. $headers = $this->conn->fetchPartHeader(
  2214. $this->folder, $this->msg_uid, true, $part->mime_id);
  2215. }
  2216. $filename_mime = '';
  2217. $i = 0; $matches = array();
  2218. while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
  2219. $filename_mime .= $matches[1];
  2220. $i++;
  2221. }
  2222. }
  2223. }
  2224. else if (!empty($part->ctype_parameters['name*0*'])) {
  2225. $i = 0;
  2226. while (isset($part->ctype_parameters['name*'.$i.'*'])) {
  2227. $filename_encoded .= $part->ctype_parameters['name*'.$i.'*'];
  2228. $i++;
  2229. }
  2230. if ($i<2) {
  2231. if (!$headers) {
  2232. $headers = $this->conn->fetchPartHeader(
  2233. $this->folder, $this->msg_uid, true, $part->mime_id);
  2234. }
  2235. $filename_encoded = '';
  2236. $i = 0; $matches = array();
  2237. while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
  2238. $filename_encoded .= $matches[1];
  2239. $i++;
  2240. }
  2241. }
  2242. }
  2243. // read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird)
  2244. else if (!empty($part->ctype_parameters['name'])) {
  2245. $filename_mime = $part->ctype_parameters['name'];
  2246. }
  2247. // Content-Disposition
  2248. else if (!empty($part->headers['content-description'])) {
  2249. $filename_mime = $part->headers['content-description'];
  2250. }
  2251. else {
  2252. return;
  2253. }
  2254.  
  2255. // decode filename
  2256. if (!empty($filename_mime)) {
  2257. if (!empty($part->charset)) {
  2258. $charset = $part->charset;
  2259. }
  2260. else if (!empty($this->struct_charset)) {
  2261. $charset = $this->struct_charset;
  2262. }
  2263. else {
  2264. $charset = rcube_charset::detect($filename_mime, $this->default_charset);
  2265. }
  2266.  
  2267. $part->filename = rcube_mime::decode_mime_string($filename_mime, $charset);
  2268. }
  2269. else if (!empty($filename_encoded)) {
  2270. // decode filename according to RFC 2231, Section 4
  2271. if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) {
  2272. $filename_charset = $fmatches[1];
  2273. $filename_encoded = $fmatches[2];
  2274. }
  2275.  
  2276. $part->filename = rcube_charset::convert(urldecode($filename_encoded), $filename_charset);
  2277. }
  2278. }
  2279.  
  2280.  
  2281. /**
  2282. * Get charset name from message structure (first part)
  2283. *
  2284. * @param array $structure Message structure
  2285. *
  2286. * @return string Charset name
  2287. */
  2288. protected function structure_charset($structure)
  2289. {
  2290. while (is_array($structure)) {
  2291. if (is_array($structure[2]) && $structure[2][0] == 'charset') {
  2292. return $structure[2][1];
  2293. }
  2294. $structure = $structure[0];
  2295. }
  2296. }
  2297.  
  2298.  
  2299. /**
  2300. * Fetch message body of a specific message from the server
  2301. *
  2302. * @param int Message UID
  2303. * @param string Part number
  2304. * @param rcube_message_part Part object created by get_structure()
  2305. * @param mixed True to print part, resource to write part contents in
  2306. * @param resource File pointer to save the message part
  2307. * @param boolean Disables charset conversion
  2308. * @param int Only read this number of bytes
  2309. * @param boolean Enables formatting of text/* parts bodies
  2310. *
  2311. * @return string Message/part body if not printed
  2312. */
  2313. public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0, $formatted=true)
  2314. {
  2315. if (!$this->check_connection()) {
  2316. return null;
  2317. }
  2318.  
  2319. // get part data if not provided
  2320. if (!is_object($o_part)) {
  2321. $structure = $this->conn->getStructure($this->folder, $uid, true);
  2322. $part_data = rcube_imap_generic::getStructurePartData($structure, $part);
  2323.  
  2324. $o_part = new rcube_message_part;
  2325. $o_part->ctype_primary = $part_data['type'];
  2326. $o_part->encoding = $part_data['encoding'];
  2327. $o_part->charset = $part_data['charset'];
  2328. $o_part->size = $part_data['size'];
  2329. }
  2330.  
  2331. if ($o_part && $o_part->size) {
  2332. $formatted = $formatted && $o_part->ctype_primary == 'text';
  2333. $body = $this->conn->handlePartBody($this->folder, $uid, true,
  2334. $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $formatted, $max_bytes);
  2335. }
  2336.  
  2337. if ($fp || $print) {
  2338. return true;
  2339. }
  2340.  
  2341. // convert charset (if text or message part)
  2342. if ($body && preg_match('/^(text|message)$/', $o_part->ctype_primary)) {
  2343. // Remove NULL characters if any (#1486189)
  2344. if ($formatted && strpos($body, "\x00") !== false) {
  2345. $body = str_replace("\x00", '', $body);
  2346. }
  2347.  
  2348. if (!$skip_charset_conv) {
  2349. if (!$o_part->charset || strtoupper($o_part->charset) == 'US-ASCII') {
  2350. // try to extract charset information from HTML meta tag (#1488125)
  2351. if ($o_part->ctype_secondary == 'html' && preg_match('/<meta[^>]+charset=([a-z0-9-_]+)/i', $body, $m)) {
  2352. $o_part->charset = strtoupper($m[1]);
  2353. }
  2354. else {
  2355. $o_part->charset = $this->default_charset;
  2356. }
  2357. }
  2358. $body = rcube_charset::convert($body, $o_part->charset);
  2359. }
  2360. }
  2361.  
  2362. return $body;
  2363. }
  2364.  
  2365.  
  2366. /**
  2367. * Returns the whole message source as string (or saves to a file)
  2368. *
  2369. * @param int $uid Message UID
  2370. * @param resource $fp File pointer to save the message
  2371. *
  2372. * @return string Message source string
  2373. */
  2374. public function get_raw_body($uid, $fp=null)
  2375. {
  2376. if (!$this->check_connection()) {
  2377. return null;
  2378. }
  2379.  
  2380. return $this->conn->handlePartBody($this->folder, $uid,
  2381. true, null, null, false, $fp);
  2382. }
  2383.  
  2384.  
  2385. /**
  2386. * Returns the message headers as string
  2387. *
  2388. * @param int $uid Message UID
  2389. *
  2390. * @return string Message headers string
  2391. */
  2392. public function get_raw_headers($uid)
  2393. {
  2394. if (!$this->check_connection()) {
  2395. return null;
  2396. }
  2397.  
  2398. return $this->conn->fetchPartHeader($this->folder, $uid, true);
  2399. }
  2400.  
  2401.  
  2402. /**
  2403. * Sends the whole message source to stdout
  2404. *
  2405. * @param int $uid Message UID
  2406. * @param bool $formatted Enables line-ending formatting
  2407. */
  2408. public function print_raw_body($uid, $formatted = true)
  2409. {
  2410. if (!$this->check_connection()) {
  2411. return;
  2412. }
  2413.  
  2414. $this->conn->handlePartBody($this->folder, $uid, true, null, null, true, null, $formatted);
  2415. }
  2416.  
  2417.  
  2418. /**
  2419. * Set message flag to one or several messages
  2420. *
  2421. * @param mixed $uids Message UIDs as array or comma-separated string, or '*'
  2422. * @param string $flag Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
  2423. * @param string $folder Folder name
  2424. * @param boolean $skip_cache True to skip message cache clean up
  2425. *
  2426. * @return boolean Operation status
  2427. */
  2428. public function set_flag($uids, $flag, $folder=null, $skip_cache=false)
  2429. {
  2430. if (!strlen($folder)) {
  2431. $folder = $this->folder;
  2432. }
  2433.  
  2434. if (!$this->check_connection()) {
  2435. return false;
  2436. }
  2437.  
  2438. $flag = strtoupper($flag);
  2439. list($uids, $all_mode) = $this->parse_uids($uids);
  2440.  
  2441. if (strpos($flag, 'UN') === 0) {
  2442. $result = $this->conn->unflag($folder, $uids, substr($flag, 2));
  2443. }
  2444. else {
  2445. $result = $this->conn->flag($folder, $uids, $flag);
  2446. }
  2447.  
  2448. if ($result && !$skip_cache) {
  2449. // reload message headers if cached
  2450. // update flags instead removing from cache
  2451. if ($mcache = $this->get_mcache_engine()) {
  2452. $status = strpos($flag, 'UN') !== 0;
  2453. $mflag = preg_replace('/^UN/', '', $flag);
  2454. $mcache->change_flag($folder, $all_mode ? null : explode(',', $uids),
  2455. $mflag, $status);
  2456. }
  2457.  
  2458. // clear cached counters
  2459. if ($flag == 'SEEN' || $flag == 'UNSEEN') {
  2460. $this->clear_messagecount($folder, 'SEEN');
  2461. $this->clear_messagecount($folder, 'UNSEEN');
  2462. }
  2463. else if ($flag == 'DELETED' || $flag == 'UNDELETED') {
  2464. $this->clear_messagecount($folder, 'DELETED');
  2465. // remove cached messages
  2466. if ($this->options['skip_deleted']) {
  2467. $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
  2468. }
  2469. }
  2470.  
  2471. $this->set_search_dirty($folder);
  2472. }
  2473.  
  2474. return $result;
  2475. }
  2476.  
  2477.  
  2478. /**
  2479. * Append a mail message (source) to a specific folder
  2480. *
  2481. * @param string $folder Target folder
  2482. * @param string|array $message The message source string or filename
  2483. * or array (of strings and file pointers)
  2484. * @param string $headers Headers string if $message contains only the body
  2485. * @param boolean $is_file True if $message is a filename
  2486. * @param array $flags Message flags
  2487. * @param mixed $date Message internal date
  2488. * @param bool $binary Enables BINARY append
  2489. *
  2490. * @return int|bool Appended message UID or True on success, False on error
  2491. */
  2492. public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null, $binary = false)
  2493. {
  2494. if (!strlen($folder)) {
  2495. $folder = $this->folder;
  2496. }
  2497.  
  2498. if (!$this->check_connection()) {
  2499. return false;
  2500. }
  2501.  
  2502. // make sure folder exists
  2503. if (!$this->folder_exists($folder)) {
  2504. return false;
  2505. }
  2506.  
  2507. $date = $this->date_format($date);
  2508.  
  2509. if ($is_file) {
  2510. $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date, $binary);
  2511. }
  2512. else {
  2513. $saved = $this->conn->append($folder, $message, $flags, $date, $binary);
  2514. }
  2515.  
  2516. if ($saved) {
  2517. // increase messagecount of the target folder
  2518. $this->set_messagecount($folder, 'ALL', 1);
  2519.  
  2520. $this->plugins->exec_hook('message_saved', array(
  2521. 'folder' => $folder,
  2522. 'message' => $message,
  2523. 'headers' => $headers,
  2524. 'is_file' => $is_file,
  2525. 'flags' => $flags,
  2526. 'date' => $date,
  2527. 'binary' => $binary,
  2528. 'result' => $saved,
  2529. ));
  2530. }
  2531.  
  2532. return $saved;
  2533. }
  2534.  
  2535.  
  2536. /**
  2537. * Move a message from one folder to another
  2538. *
  2539. * @param mixed $uids Message UIDs as array or comma-separated string, or '*'
  2540. * @param string $to_mbox Target folder
  2541. * @param string $from_mbox Source folder
  2542. *
  2543. * @return boolean True on success, False on error
  2544. */
  2545. public function move_message($uids, $to_mbox, $from_mbox='')
  2546. {
  2547. if (!strlen($from_mbox)) {
  2548. $from_mbox = $this->folder;
  2549. }
  2550.  
  2551. if ($to_mbox === $from_mbox) {
  2552. return false;
  2553. }
  2554.  
  2555. list($uids, $all_mode) = $this->parse_uids($uids);
  2556.  
  2557. // exit if no message uids are specified
  2558. if (empty($uids)) {
  2559. return false;
  2560. }
  2561.  
  2562. if (!$this->check_connection()) {
  2563. return false;
  2564. }
  2565.  
  2566. $config = rcube::get_instance()->config;
  2567. $to_trash = $to_mbox == $config->get('trash_mbox');
  2568.  
  2569. // flag messages as read before moving them
  2570. if ($to_trash && $config->get('read_when_deleted')) {
  2571. // don't flush cache (4th argument)
  2572. $this->set_flag($uids, 'SEEN', $from_mbox, true);
  2573. }
  2574.  
  2575. // move messages
  2576. $moved = $this->conn->move($uids, $from_mbox, $to_mbox);
  2577.  
  2578. if ($moved) {
  2579. $this->clear_messagecount($from_mbox);
  2580. $this->clear_messagecount($to_mbox);
  2581.  
  2582. $this->set_search_dirty($from_mbox);
  2583. $this->set_search_dirty($to_mbox);
  2584. }
  2585. // moving failed
  2586. else if ($to_trash && $config->get('delete_always', false)) {
  2587. $moved = $this->delete_message($uids, $from_mbox);
  2588. }
  2589.  
  2590. if ($moved) {
  2591. // unset threads internal cache
  2592. unset($this->icache['threads']);
  2593.  
  2594. // remove message ids from search set
  2595. if ($this->search_set && $from_mbox == $this->folder) {
  2596. // threads are too complicated to just remove messages from set
  2597. if ($this->search_threads || $all_mode) {
  2598. $this->refresh_search();
  2599. }
  2600. else if (!$this->search_set->incomplete) {
  2601. $this->search_set->filter(explode(',', $uids), $this->folder);
  2602. }
  2603. }
  2604.  
  2605. // remove cached messages
  2606. // @TODO: do cache update instead of clearing it
  2607. $this->clear_message_cache($from_mbox, $all_mode ? null : explode(',', $uids));
  2608. }
  2609.  
  2610. return $moved;
  2611. }
  2612.  
  2613.  
  2614. /**
  2615. * Copy a message from one folder to another
  2616. *
  2617. * @param mixed $uids Message UIDs as array or comma-separated string, or '*'
  2618. * @param string $to_mbox Target folder
  2619. * @param string $from_mbox Source folder
  2620. *
  2621. * @return boolean True on success, False on error
  2622. */
  2623. public function copy_message($uids, $to_mbox, $from_mbox='')
  2624. {
  2625. if (!strlen($from_mbox)) {
  2626. $from_mbox = $this->folder;
  2627. }
  2628.  
  2629. list($uids, $all_mode) = $this->parse_uids($uids);
  2630.  
  2631. // exit if no message uids are specified
  2632. if (empty($uids)) {
  2633. return false;
  2634. }
  2635.  
  2636. if (!$this->check_connection()) {
  2637. return false;
  2638. }
  2639.  
  2640. // copy messages
  2641. $copied = $this->conn->copy($uids, $from_mbox, $to_mbox);
  2642.  
  2643. if ($copied) {
  2644. $this->clear_messagecount($to_mbox);
  2645. }
  2646.  
  2647. return $copied;
  2648. }
  2649.  
  2650.  
  2651. /**
  2652. * Mark messages as deleted and expunge them
  2653. *
  2654. * @param mixed $uids Message UIDs as array or comma-separated string, or '*'
  2655. * @param string $folder Source folder
  2656. *
  2657. * @return boolean True on success, False on error
  2658. */
  2659. public function delete_message($uids, $folder='')
  2660. {
  2661. if (!strlen($folder)) {
  2662. $folder = $this->folder;
  2663. }
  2664.  
  2665. list($uids, $all_mode) = $this->parse_uids($uids);
  2666.  
  2667. // exit if no message uids are specified
  2668. if (empty($uids)) {
  2669. return false;
  2670. }
  2671.  
  2672. if (!$this->check_connection()) {
  2673. return false;
  2674. }
  2675.  
  2676. $deleted = $this->conn->flag($folder, $uids, 'DELETED');
  2677.  
  2678. if ($deleted) {
  2679. // send expunge command in order to have the deleted message
  2680. // really deleted from the folder
  2681. $this->expunge_message($uids, $folder, false);
  2682. $this->clear_messagecount($folder);
  2683. unset($this->uid_id_map[$folder]);
  2684.  
  2685. // unset threads internal cache
  2686. unset($this->icache['threads']);
  2687.  
  2688. $this->set_search_dirty($folder);
  2689.  
  2690. // remove message ids from search set
  2691. if ($this->search_set && $folder == $this->folder) {
  2692. // threads are too complicated to just remove messages from set
  2693. if ($this->search_threads || $all_mode) {
  2694. $this->refresh_search();
  2695. }
  2696. else if (!$this->search_set->incomplete) {
  2697. $this->search_set->filter(explode(',', $uids));
  2698. }
  2699. }
  2700.  
  2701. // remove cached messages
  2702. $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
  2703. }
  2704.  
  2705. return $deleted;
  2706. }
  2707.  
  2708.  
  2709. /**
  2710. * Send IMAP expunge command and clear cache
  2711. *
  2712. * @param mixed $uids Message UIDs as array or comma-separated string, or '*'
  2713. * @param string $folder Folder name
  2714. * @param boolean $clear_cache False if cache should not be cleared
  2715. *
  2716. * @return boolean True on success, False on failure
  2717. */
  2718. public function expunge_message($uids, $folder = null, $clear_cache = true)
  2719. {
  2720. if ($uids && $this->get_capability('UIDPLUS')) {
  2721. list($uids, $all_mode) = $this->parse_uids($uids);
  2722. }
  2723. else {
  2724. $uids = null;
  2725. }
  2726.  
  2727. if (!strlen($folder)) {
  2728. $folder = $this->folder;
  2729. }
  2730.  
  2731. if (!$this->check_connection()) {
  2732. return false;
  2733. }
  2734.  
  2735. // force folder selection and check if folder is writeable
  2736. // to prevent a situation when CLOSE is executed on closed
  2737. // or EXPUNGE on read-only folder
  2738. $result = $this->conn->select($folder);
  2739. if (!$result) {
  2740. return false;
  2741. }
  2742.  
  2743. if (!$this->conn->data['READ-WRITE']) {
  2744. $this->conn->setError(rcube_imap_generic::ERROR_READONLY, "Folder is read-only");
  2745. return false;
  2746. }
  2747.  
  2748. // CLOSE(+SELECT) should be faster than EXPUNGE
  2749. if (empty($uids) || $all_mode) {
  2750. $result = $this->conn->close();
  2751. }
  2752. else {
  2753. $result = $this->conn->expunge($folder, $uids);
  2754. }
  2755.  
  2756. if ($result && $clear_cache) {
  2757. $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
  2758. $this->clear_messagecount($folder);
  2759. }
  2760.  
  2761. return $result;
  2762. }
  2763.  
  2764.  
  2765. /* --------------------------------
  2766. * folder managment
  2767. * --------------------------------*/
  2768.  
  2769. /**
  2770. * Public method for listing subscribed folders.
  2771. *
  2772. * @param string $root Optional root folder
  2773. * @param string $name Optional name pattern
  2774. * @param string $filter Optional filter
  2775. * @param string $rights Optional ACL requirements
  2776. * @param bool $skip_sort Enable to return unsorted list (for better performance)
  2777. *
  2778. * @return array List of folders
  2779. */
  2780. public function list_folders_subscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
  2781. {
  2782. $cache_key = $root.':'.$name;
  2783. if (!empty($filter)) {
  2784. $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
  2785. }
  2786. $cache_key .= ':'.$rights;
  2787. $cache_key = 'mailboxes.'.md5($cache_key);
  2788.  
  2789. // get cached folder list
  2790. $a_mboxes = $this->get_cache($cache_key);
  2791. if (is_array($a_mboxes)) {
  2792. return $a_mboxes;
  2793. }
  2794.  
  2795. // Give plugins a chance to provide a list of folders
  2796. $data = $this->plugins->exec_hook('storage_folders',
  2797. array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB'));
  2798.  
  2799. if (isset($data['folders'])) {
  2800. $a_mboxes = $data['folders'];
  2801. }
  2802. else {
  2803. $a_mboxes = $this->list_folders_subscribed_direct($root, $name);
  2804. }
  2805.  
  2806. if (!is_array($a_mboxes)) {
  2807. return array();
  2808. }
  2809.  
  2810. // filter folders list according to rights requirements
  2811. if ($rights && $this->get_capability('ACL')) {
  2812. $a_mboxes = $this->filter_rights($a_mboxes, $rights);
  2813. }
  2814.  
  2815. // INBOX should always be available
  2816. if (!strlen($root) && (!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
  2817. array_unshift($a_mboxes, 'INBOX');
  2818. }
  2819.  
  2820. // sort folders (always sort for cache)
  2821. if (!$skip_sort || $this->cache) {
  2822. $a_mboxes = $this->sort_folder_list($a_mboxes);
  2823. }
  2824.  
  2825. // write folders list to cache
  2826. $this->update_cache($cache_key, $a_mboxes);
  2827.  
  2828. return $a_mboxes;
  2829. }
  2830.  
  2831.  
  2832. /**
  2833. * Method for direct folders listing (LSUB)
  2834. *
  2835. * @param string $root Optional root folder
  2836. * @param string $name Optional name pattern
  2837. *
  2838. * @return array List of subscribed folders
  2839. * @see rcube_imap::list_folders_subscribed()
  2840. */
  2841. public function list_folders_subscribed_direct($root='', $name='*')
  2842. {
  2843. if (!$this->check_connection()) {
  2844. return null;
  2845. }
  2846.  
  2847. $config = rcube::get_instance()->config;
  2848.  
  2849. // Server supports LIST-EXTENDED, we can use selection options
  2850. // #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED
  2851. $list_extended = !$config->get('imap_force_lsub') && $this->get_capability('LIST-EXTENDED');
  2852. if ($list_extended) {
  2853. // This will also set folder options, LSUB doesn't do that
  2854. $a_folders = $this->conn->listMailboxes($root, $name,
  2855. NULL, array('SUBSCRIBED'));
  2856. }
  2857. else {
  2858. // retrieve list of folders from IMAP server using LSUB
  2859. $a_folders = $this->conn->listSubscribed($root, $name);
  2860. }
  2861.  
  2862. if (!is_array($a_folders)) {
  2863. return array();
  2864. }
  2865.  
  2866. // #1486796: some server configurations doesn't return folders in all namespaces
  2867. if ($root == '' && $name == '*' && $config->get('imap_force_ns')) {
  2868. $this->list_folders_update($a_folders, ($list_extended ? 'ext-' : '') . 'subscribed');
  2869. }
  2870.  
  2871. if ($list_extended) {
  2872. // unsubscribe non-existent folders, remove from the list
  2873. if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) {
  2874. foreach ($a_folders as $idx => $folder) {
  2875. if (($opts = $this->conn->data['LIST'][$folder])
  2876. && in_array_nocase('\\NonExistent', $opts)
  2877. ) {
  2878. $this->conn->unsubscribe($folder);
  2879. unset($a_folders[$idx]);
  2880. }
  2881. }
  2882. }
  2883. }
  2884. else {
  2885. // unsubscribe non-existent folders, remove them from the list
  2886. if (is_array($a_folders) && !empty($a_folders) && $name == '*') {
  2887. $existing = $this->list_folders($root, $name);
  2888. $nonexisting = array_diff($a_folders, $existing);
  2889. $a_folders = array_diff($a_folders, $nonexisting);
  2890.  
  2891. foreach ($nonexisting as $folder) {
  2892. $this->conn->unsubscribe($folder);
  2893. }
  2894. }
  2895. }
  2896.  
  2897. return $a_folders;
  2898. }
  2899.  
  2900.  
  2901. /**
  2902. * Get a list of all folders available on the server
  2903. *
  2904. * @param string $root IMAP root dir
  2905. * @param string $name Optional name pattern
  2906. * @param mixed $filter Optional filter
  2907. * @param string $rights Optional ACL requirements
  2908. * @param bool $skip_sort Enable to return unsorted list (for better performance)
  2909. *
  2910. * @return array Indexed array with folder names
  2911. */
  2912. public function list_folders($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
  2913. {
  2914. $cache_key = $root.':'.$name;
  2915. if (!empty($filter)) {
  2916. $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
  2917. }
  2918. $cache_key .= ':'.$rights;
  2919. $cache_key = 'mailboxes.list.'.md5($cache_key);
  2920.  
  2921. // get cached folder list
  2922. $a_mboxes = $this->get_cache($cache_key);
  2923. if (is_array($a_mboxes)) {
  2924. return $a_mboxes;
  2925. }
  2926.  
  2927. // Give plugins a chance to provide a list of folders
  2928. $data = $this->plugins->exec_hook('storage_folders',
  2929. array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST'));
  2930.  
  2931. if (isset($data['folders'])) {
  2932. $a_mboxes = $data['folders'];
  2933. }
  2934. else {
  2935. // retrieve list of folders from IMAP server
  2936. $a_mboxes = $this->list_folders_direct($root, $name);
  2937. }
  2938.  
  2939. if (!is_array($a_mboxes)) {
  2940. $a_mboxes = array();
  2941. }
  2942.  
  2943. // INBOX should always be available
  2944. if (!strlen($root) && (!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
  2945. array_unshift($a_mboxes, 'INBOX');
  2946. }
  2947.  
  2948. // cache folder attributes
  2949. if ($root == '' && $name == '*' && empty($filter) && !empty($this->conn->data)) {
  2950. $this->update_cache('mailboxes.attributes', $this->conn->data['LIST']);
  2951. }
  2952.  
  2953. // filter folders list according to rights requirements
  2954. if ($rights && $this->get_capability('ACL')) {
  2955. $a_mboxes = $this->filter_rights($a_mboxes, $rights);
  2956. }
  2957.  
  2958. // filter folders and sort them
  2959. if (!$skip_sort) {
  2960. $a_mboxes = $this->sort_folder_list($a_mboxes);
  2961. }
  2962.  
  2963. // write folders list to cache
  2964. $this->update_cache($cache_key, $a_mboxes);
  2965.  
  2966. return $a_mboxes;
  2967. }
  2968.  
  2969.  
  2970. /**
  2971. * Method for direct folders listing (LIST)
  2972. *
  2973. * @param string $root Optional root folder
  2974. * @param string $name Optional name pattern
  2975. *
  2976. * @return array List of folders
  2977. * @see rcube_imap::list_folders()
  2978. */
  2979. public function list_folders_direct($root='', $name='*')
  2980. {
  2981. if (!$this->check_connection()) {
  2982. return null;
  2983. }
  2984.  
  2985. $result = $this->conn->listMailboxes($root, $name);
  2986.  
  2987. if (!is_array($result)) {
  2988. return array();
  2989. }
  2990.  
  2991. $config = rcube::get_instance()->config;
  2992.  
  2993. // #1486796: some server configurations doesn't return folders in all namespaces
  2994. if ($root == '' && $name == '*' && $config->get('imap_force_ns')) {
  2995. $this->list_folders_update($result);
  2996. }
  2997.  
  2998. return $result;
  2999. }
  3000.  
  3001.  
  3002. /**
  3003. * Fix folders list by adding folders from other namespaces.
  3004. * Needed on some servers eg. Courier IMAP
  3005. *
  3006. * @param array $result Reference to folders list
  3007. * @param string $type Listing type (ext-subscribed, subscribed or all)
  3008. */
  3009. protected function list_folders_update(&$result, $type = null)
  3010. {
  3011. $namespace = $this->get_namespace();
  3012. $search = array();
  3013.  
  3014. // build list of namespace prefixes
  3015. foreach ((array)$namespace as $ns) {
  3016. if (is_array($ns)) {
  3017. foreach ($ns as $ns_data) {
  3018. if (strlen($ns_data[0])) {
  3019. $search[] = $ns_data[0];
  3020. }
  3021. }
  3022. }
  3023. }
  3024.  
  3025. if (!empty($search)) {
  3026. // go through all folders detecting namespace usage
  3027. foreach ($result as $folder) {
  3028. foreach ($search as $idx => $prefix) {
  3029. if (strpos($folder, $prefix) === 0) {
  3030. unset($search[$idx]);
  3031. }
  3032. }
  3033. if (empty($search)) {
  3034. break;
  3035. }
  3036. }
  3037.  
  3038. // get folders in hidden namespaces and add to the result
  3039. foreach ($search as $prefix) {
  3040. if ($type == 'ext-subscribed') {
  3041. $list = $this->conn->listMailboxes('', $prefix . '*', null, array('SUBSCRIBED'));
  3042. }
  3043. else if ($type == 'subscribed') {
  3044. $list = $this->conn->listSubscribed('', $prefix . '*');
  3045. }
  3046. else {
  3047. $list = $this->conn->listMailboxes('', $prefix . '*');
  3048. }
  3049.  
  3050. if (!empty($list)) {
  3051. $result = array_merge($result, $list);
  3052. }
  3053. }
  3054. }
  3055. }
  3056.  
  3057.  
  3058. /**
  3059. * Filter the given list of folders according to access rights
  3060. *
  3061. * For performance reasons we assume user has full rights
  3062. * on all personal folders.
  3063. */
  3064. protected function filter_rights($a_folders, $rights)
  3065. {
  3066. $regex = '/('.$rights.')/';
  3067.  
  3068. foreach ($a_folders as $idx => $folder) {
  3069. if ($this->folder_namespace($folder) == 'personal') {
  3070. continue;
  3071. }
  3072.  
  3073. $myrights = join('', (array)$this->my_rights($folder));
  3074.  
  3075. if ($myrights !== null && !preg_match($regex, $myrights)) {
  3076. unset($a_folders[$idx]);
  3077. }
  3078. }
  3079.  
  3080. return $a_folders;
  3081. }
  3082.  
  3083.  
  3084. /**
  3085. * Get mailbox quota information
  3086. *
  3087. * @param string $folder Folder name
  3088. *
  3089. * @return mixed Quota info or False if not supported
  3090. */
  3091. public function get_quota($folder = null)
  3092. {
  3093. if ($this->get_capability('QUOTA') && $this->check_connection()) {
  3094. return $this->conn->getQuota($folder);
  3095. }
  3096.  
  3097. return false;
  3098. }
  3099.  
  3100.  
  3101. /**
  3102. * Get folder size (size of all messages in a folder)
  3103. *
  3104. * @param string $folder Folder name
  3105. *
  3106. * @return int Folder size in bytes, False on error
  3107. */
  3108. public function folder_size($folder)
  3109. {
  3110. if (!$this->check_connection()) {
  3111. return 0;
  3112. }
  3113.  
  3114. // @TODO: could we try to use QUOTA here?
  3115. $result = $this->conn->fetchHeaderIndex($folder, '1:*', 'SIZE', false);
  3116.  
  3117. if (is_array($result)) {
  3118. $result = array_sum($result);
  3119. }
  3120.  
  3121. return $result;
  3122. }
  3123.  
  3124.  
  3125. /**
  3126. * Subscribe to a specific folder(s)
  3127. *
  3128. * @param array $folders Folder name(s)
  3129. *
  3130. * @return boolean True on success
  3131. */
  3132. public function subscribe($folders)
  3133. {
  3134. // let this common function do the main work
  3135. return $this->change_subscription($folders, 'subscribe');
  3136. }
  3137.  
  3138.  
  3139. /**
  3140. * Unsubscribe folder(s)
  3141. *
  3142. * @param array $a_mboxes Folder name(s)
  3143. *
  3144. * @return boolean True on success
  3145. */
  3146. public function unsubscribe($folders)
  3147. {
  3148. // let this common function do the main work
  3149. return $this->change_subscription($folders, 'unsubscribe');
  3150. }
  3151.  
  3152.  
  3153. /**
  3154. * Create a new folder on the server and register it in local cache
  3155. *
  3156. * @param string $folder New folder name
  3157. * @param boolean $subscribe True if the new folder should be subscribed
  3158. * @param string $type Optional folder type (junk, trash, drafts, sent, archive)
  3159. *
  3160. * @return boolean True on success
  3161. */
  3162. public function create_folder($folder, $subscribe = false, $type = null)
  3163. {
  3164. if (!$this->check_connection()) {
  3165. return false;
  3166. }
  3167.  
  3168. $result = $this->conn->createFolder($folder, $type ? array("\\" . ucfirst($type)) : null);
  3169.  
  3170. // try to subscribe it
  3171. if ($result) {
  3172. // clear cache
  3173. $this->clear_cache('mailboxes', true);
  3174.  
  3175. if ($subscribe) {
  3176. $this->subscribe($folder);
  3177. }
  3178. }
  3179.  
  3180. return $result;
  3181. }
  3182.  
  3183.  
  3184. /**
  3185. * Set a new name to an existing folder
  3186. *
  3187. * @param string $folder Folder to rename
  3188. * @param string $new_name New folder name
  3189. *
  3190. * @return boolean True on success
  3191. */
  3192. public function rename_folder($folder, $new_name)
  3193. {
  3194. if (!strlen($new_name)) {
  3195. return false;
  3196. }
  3197.  
  3198. if (!$this->check_connection()) {
  3199. return false;
  3200. }
  3201.  
  3202. $delm = $this->get_hierarchy_delimiter();
  3203.  
  3204. // get list of subscribed folders
  3205. if ((strpos($folder, '%') === false) && (strpos($folder, '*') === false)) {
  3206. $a_subscribed = $this->list_folders_subscribed('', $folder . $delm . '*');
  3207. $subscribed = $this->folder_exists($folder, true);
  3208. }
  3209. else {
  3210. $a_subscribed = $this->list_folders_subscribed();
  3211. $subscribed = in_array($folder, $a_subscribed);
  3212. }
  3213.  
  3214. $result = $this->conn->renameFolder($folder, $new_name);
  3215.  
  3216. if ($result) {
  3217. // unsubscribe the old folder, subscribe the new one
  3218. if ($subscribed) {
  3219. $this->conn->unsubscribe($folder);
  3220. $this->conn->subscribe($new_name);
  3221. }
  3222.  
  3223. // check if folder children are subscribed
  3224. foreach ($a_subscribed as $c_subscribed) {
  3225. if (strpos($c_subscribed, $folder.$delm) === 0) {
  3226. $this->conn->unsubscribe($c_subscribed);
  3227. $this->conn->subscribe(preg_replace('/^'.preg_quote($folder, '/').'/',
  3228. $new_name, $c_subscribed));
  3229.  
  3230. // clear cache
  3231. $this->clear_message_cache($c_subscribed);
  3232. }
  3233. }
  3234.  
  3235. // clear cache
  3236. $this->clear_message_cache($folder);
  3237. $this->clear_cache('mailboxes', true);
  3238. }
  3239.  
  3240. return $result;
  3241. }
  3242.  
  3243.  
  3244. /**
  3245. * Remove folder from server
  3246. *
  3247. * @param string $folder Folder name
  3248. *
  3249. * @return boolean True on success
  3250. */
  3251. function delete_folder($folder)
  3252. {
  3253. $delm = $this->get_hierarchy_delimiter();
  3254.  
  3255. if (!$this->check_connection()) {
  3256. return false;
  3257. }
  3258.  
  3259. // get list of folders
  3260. if ((strpos($folder, '%') === false) && (strpos($folder, '*') === false)) {
  3261. $sub_mboxes = $this->list_folders('', $folder . $delm . '*');
  3262. }
  3263. else {
  3264. $sub_mboxes = $this->list_folders();
  3265. }
  3266.  
  3267. // send delete command to server
  3268. $result = $this->conn->deleteFolder($folder);
  3269.  
  3270. if ($result) {
  3271. // unsubscribe folder
  3272. $this->conn->unsubscribe($folder);
  3273.  
  3274. foreach ($sub_mboxes as $c_mbox) {
  3275. if (strpos($c_mbox, $folder.$delm) === 0) {
  3276. $this->conn->unsubscribe($c_mbox);
  3277. if ($this->conn->deleteFolder($c_mbox)) {
  3278. $this->clear_message_cache($c_mbox);
  3279. }
  3280. }
  3281. }
  3282.  
  3283. // clear folder-related cache
  3284. $this->clear_message_cache($folder);
  3285. $this->clear_cache('mailboxes', true);
  3286. }
  3287.  
  3288. return $result;
  3289. }
  3290.  
  3291.  
  3292. /**
  3293. * Detect special folder associations stored in storage backend
  3294. */
  3295. public function get_special_folders($forced = false)
  3296. {
  3297. $result = parent::get_special_folders();
  3298. $rcube = rcube::get_instance();
  3299.  
  3300. // Lock SPECIAL-USE after user preferences change (#4782)
  3301. if ($rcube->config->get('lock_special_folders')) {
  3302. return $result;
  3303. }
  3304.  
  3305. if (isset($this->icache['special-use'])) {
  3306. return array_merge($result, $this->icache['special-use']);
  3307. }
  3308.  
  3309. if (!$forced || !$this->get_capability('SPECIAL-USE')) {
  3310. return $result;
  3311. }
  3312.  
  3313. if (!$this->check_connection()) {
  3314. return $result;
  3315. }
  3316.  
  3317. $types = array_map(function($value) { return "\\" . ucfirst($value); }, rcube_storage::$folder_types);
  3318. $special = array();
  3319.  
  3320. // request \Subscribed flag in LIST response as performance improvement for folder_exists()
  3321. $folders = $this->conn->listMailboxes('', '*', array('SUBSCRIBED'), array('SPECIAL-USE'));
  3322.  
  3323. if (!empty($folders)) {
  3324. foreach ($folders as $folder) {
  3325. if ($flags = $this->conn->data['LIST'][$folder]) {
  3326. foreach ($types as $type) {
  3327. if (in_array($type, $flags)) {
  3328. $type = strtolower(substr($type, 1));
  3329. $special[$type] = $folder;
  3330. }
  3331. }
  3332. }
  3333. }
  3334. }
  3335.  
  3336. $this->icache['special-use'] = $special;
  3337. unset($this->icache['special-folders']);
  3338.  
  3339. return array_merge($result, $special);
  3340. }
  3341.  
  3342.  
  3343. /**
  3344. * Set special folder associations stored in storage backend
  3345. */
  3346. public function set_special_folders($specials)
  3347. {
  3348. if (!$this->get_capability('SPECIAL-USE') || !$this->get_capability('METADATA')) {
  3349. return false;
  3350. }
  3351.  
  3352. if (!$this->check_connection()) {
  3353. return false;
  3354. }
  3355.  
  3356. $folders = $this->get_special_folders(true);
  3357. $old = (array) $this->icache['special-use'];
  3358.  
  3359. foreach ($specials as $type => $folder) {
  3360. if (in_array($type, rcube_storage::$folder_types)) {
  3361. $old_folder = $old[$type];
  3362. if ($old_folder !== $folder) {
  3363. // unset old-folder metadata
  3364. if ($old_folder !== null) {
  3365. $this->delete_metadata($old_folder, array('/private/specialuse'));
  3366. }
  3367. // set new folder metadata
  3368. if ($folder) {
  3369. $this->set_metadata($folder, array('/private/specialuse' => "\\" . ucfirst($type)));
  3370. }
  3371. }
  3372. }
  3373. }
  3374.  
  3375. $this->icache['special-use'] = $specials;
  3376. unset($this->icache['special-folders']);
  3377.  
  3378. return true;
  3379. }
  3380.  
  3381.  
  3382. /**
  3383. * Checks if folder exists and is subscribed
  3384. *
  3385. * @param string $folder Folder name
  3386. * @param boolean $subscription Enable subscription checking
  3387. *
  3388. * @return boolean TRUE or FALSE
  3389. */
  3390. public function folder_exists($folder, $subscription = false)
  3391. {
  3392. if ($folder == 'INBOX') {
  3393. return true;
  3394. }
  3395.  
  3396. $key = $subscription ? 'subscribed' : 'existing';
  3397.  
  3398. if (is_array($this->icache[$key]) && in_array($folder, $this->icache[$key])) {
  3399. return true;
  3400. }
  3401.  
  3402. if (!$this->check_connection()) {
  3403. return false;
  3404. }
  3405.  
  3406. if ($subscription) {
  3407. // It's possible we already called LIST command, check LIST data
  3408. if (!empty($this->conn->data['LIST']) && !empty($this->conn->data['LIST'][$folder])
  3409. && in_array_nocase('\\Subscribed', $this->conn->data['LIST'][$folder])
  3410. ) {
  3411. $a_folders = array($folder);
  3412. }
  3413. else {
  3414. $a_folders = $this->conn->listSubscribed('', $folder);
  3415. }
  3416. }
  3417. else {
  3418. // It's possible we already called LIST command, check LIST data
  3419. if (!empty($this->conn->data['LIST']) && isset($this->conn->data['LIST'][$folder])) {
  3420. $a_folders = array($folder);
  3421. }
  3422. else {
  3423. $a_folders = $this->conn->listMailboxes('', $folder);
  3424. }
  3425. }
  3426.  
  3427. if (is_array($a_folders) && in_array($folder, $a_folders)) {
  3428. $this->icache[$key][] = $folder;
  3429. return true;
  3430. }
  3431.  
  3432. return false;
  3433. }
  3434.  
  3435.  
  3436. /**
  3437. * Returns the namespace where the folder is in
  3438. *
  3439. * @param string $folder Folder name
  3440. *
  3441. * @return string One of 'personal', 'other' or 'shared'
  3442. */
  3443. public function folder_namespace($folder)
  3444. {
  3445. if ($folder == 'INBOX') {
  3446. return 'personal';
  3447. }
  3448.  
  3449. foreach ($this->namespace as $type => $namespace) {
  3450. if (is_array($namespace)) {
  3451. foreach ($namespace as $ns) {
  3452. if ($len = strlen($ns[0])) {
  3453. if (($len > 1 && $folder == substr($ns[0], 0, -1))
  3454. || strpos($folder, $ns[0]) === 0
  3455. ) {
  3456. return $type;
  3457. }
  3458. }
  3459. }
  3460. }
  3461. }
  3462.  
  3463. return 'personal';
  3464. }
  3465.  
  3466.  
  3467. /**
  3468. * Modify folder name according to namespace.
  3469. * For output it removes prefix of the personal namespace if it's possible.
  3470. * For input it adds the prefix. Use it before creating a folder in root
  3471. * of the folders tree.
  3472. *
  3473. * @param string $folder Folder name
  3474. * @param string $mode Mode name (out/in)
  3475. *
  3476. * @return string Folder name
  3477. */
  3478. public function mod_folder($folder, $mode = 'out')
  3479. {
  3480. if (!strlen($folder)) {
  3481. return $folder;
  3482. }
  3483.  
  3484. $prefix = $this->namespace['prefix']; // see set_env()
  3485. $prefix_len = strlen($prefix);
  3486.  
  3487. if (!$prefix_len) {
  3488. return $folder;
  3489. }
  3490.  
  3491. // remove prefix for output
  3492. if ($mode == 'out') {
  3493. if (substr($folder, 0, $prefix_len) === $prefix) {
  3494. return substr($folder, $prefix_len);
  3495. }
  3496. }
  3497. // add prefix for input (e.g. folder creation)
  3498. else {
  3499. return $prefix . $folder;
  3500. }
  3501.  
  3502. return $folder;
  3503. }
  3504.  
  3505.  
  3506. /**
  3507. * Gets folder attributes from LIST response, e.g. \Noselect, \Noinferiors
  3508. *
  3509. * @param string $folder Folder name
  3510. * @param bool $force Set to True if attributes should be refreshed
  3511. *
  3512. * @return array Options list
  3513. */
  3514. public function folder_attributes($folder, $force=false)
  3515. {
  3516. // get attributes directly from LIST command
  3517. if (!empty($this->conn->data['LIST']) && is_array($this->conn->data['LIST'][$folder])) {
  3518. $opts = $this->conn->data['LIST'][$folder];
  3519. }
  3520. // get cached folder attributes
  3521. else if (!$force) {
  3522. $opts = $this->get_cache('mailboxes.attributes');
  3523. $opts = $opts[$folder];
  3524. }
  3525.  
  3526. if (!is_array($opts)) {
  3527. if (!$this->check_connection()) {
  3528. return array();
  3529. }
  3530.  
  3531. $this->conn->listMailboxes('', $folder);
  3532. $opts = $this->conn->data['LIST'][$folder];
  3533. }
  3534.  
  3535. return is_array($opts) ? $opts : array();
  3536. }
  3537.  
  3538.  
  3539. /**
  3540. * Gets connection (and current folder) data: UIDVALIDITY, EXISTS, RECENT,
  3541. * PERMANENTFLAGS, UIDNEXT, UNSEEN
  3542. *
  3543. * @param string $folder Folder name
  3544. *
  3545. * @return array Data
  3546. */
  3547. public function folder_data($folder)
  3548. {
  3549. if (!strlen($folder)) {
  3550. $folder = $this->folder !== null ? $this->folder : 'INBOX';
  3551. }
  3552.  
  3553. if ($this->conn->selected != $folder) {
  3554. if (!$this->check_connection()) {
  3555. return array();
  3556. }
  3557.  
  3558. if ($this->conn->select($folder)) {
  3559. $this->folder = $folder;
  3560. }
  3561. else {
  3562. return null;
  3563. }
  3564. }
  3565.  
  3566. $data = $this->conn->data;
  3567.  
  3568. // add (E)SEARCH result for ALL UNDELETED query
  3569. if (!empty($this->icache['undeleted_idx'])
  3570. && $this->icache['undeleted_idx']->get_parameters('MAILBOX') == $folder
  3571. ) {
  3572. $data['UNDELETED'] = $this->icache['undeleted_idx'];
  3573. }
  3574.  
  3575. return $data;
  3576. }
  3577.  
  3578.  
  3579. /**
  3580. * Returns extended information about the folder
  3581. *
  3582. * @param string $folder Folder name
  3583. *
  3584. * @return array Data
  3585. */
  3586. public function folder_info($folder)
  3587. {
  3588. if ($this->icache['options'] && $this->icache['options']['name'] == $folder) {
  3589. return $this->icache['options'];
  3590. }
  3591.  
  3592. // get cached metadata
  3593. $cache_key = 'mailboxes.folder-info.' . $folder;
  3594. $cached = $this->get_cache($cache_key);
  3595.  
  3596. if (is_array($cached)) {
  3597. return $cached;
  3598. }
  3599.  
  3600. $acl = $this->get_capability('ACL');
  3601. $namespace = $this->get_namespace();
  3602. $options = array();
  3603.  
  3604. // check if the folder is a namespace prefix
  3605. if (!empty($namespace)) {
  3606. $mbox = $folder . $this->delimiter;
  3607. foreach ($namespace as $ns) {
  3608. if (!empty($ns)) {
  3609. foreach ($ns as $item) {
  3610. if ($item[0] === $mbox) {
  3611. $options['is_root'] = true;
  3612. break 2;
  3613. }
  3614. }
  3615. }
  3616. }
  3617. }
  3618. // check if the folder is other user virtual-root
  3619. if (!$options['is_root'] && !empty($namespace) && !empty($namespace['other'])) {
  3620. $parts = explode($this->delimiter, $folder);
  3621. if (count($parts) == 2) {
  3622. $mbox = $parts[0] . $this->delimiter;
  3623. foreach ($namespace['other'] as $item) {
  3624. if ($item[0] === $mbox) {
  3625. $options['is_root'] = true;
  3626. break;
  3627. }
  3628. }
  3629. }
  3630. }
  3631.  
  3632. $options['name'] = $folder;
  3633. $options['attributes'] = $this->folder_attributes($folder, true);
  3634. $options['namespace'] = $this->folder_namespace($folder);
  3635. $options['special'] = $this->is_special_folder($folder);
  3636.  
  3637. // Set 'noselect' flag
  3638. if (is_array($options['attributes'])) {
  3639. foreach ($options['attributes'] as $attrib) {
  3640. $attrib = strtolower($attrib);
  3641. if ($attrib == '\noselect' || $attrib == '\nonexistent') {
  3642. $options['noselect'] = true;
  3643. }
  3644. }
  3645. }
  3646. else {
  3647. $options['noselect'] = true;
  3648. }
  3649.  
  3650. // Get folder rights (MYRIGHTS)
  3651. if ($acl && ($rights = $this->my_rights($folder))) {
  3652. $options['rights'] = $rights;
  3653. }
  3654.  
  3655. // Set 'norename' flag
  3656. if (!empty($options['rights'])) {
  3657. $options['norename'] = !in_array('x', $options['rights']) && !in_array('d', $options['rights']);
  3658.  
  3659. if (!$options['noselect']) {
  3660. $options['noselect'] = !in_array('r', $options['rights']);
  3661. }
  3662. }
  3663. else {
  3664. $options['norename'] = $options['is_root'] || $options['namespace'] != 'personal';
  3665. }
  3666.  
  3667. // update caches
  3668. $this->icache['options'] = $options;
  3669. $this->update_cache($cache_key, $options);
  3670.  
  3671. return $options;
  3672. }
  3673.  
  3674.  
  3675. /**
  3676. * Synchronizes messages cache.
  3677. *
  3678. * @param string $folder Folder name
  3679. */
  3680. public function folder_sync($folder)
  3681. {
  3682. if ($mcache = $this->get_mcache_engine()) {
  3683. $mcache->synchronize($folder);
  3684. }
  3685. }
  3686.  
  3687.  
  3688. /**
  3689. * Get message header names for rcube_imap_generic::fetchHeader(s)
  3690. *
  3691. * @return string Space-separated list of header names
  3692. */
  3693. protected function get_fetch_headers()
  3694. {
  3695. if (!empty($this->options['fetch_headers'])) {
  3696. $headers = explode(' ', $this->options['fetch_headers']);
  3697. }
  3698. else {
  3699. $headers = array();
  3700. }
  3701.  
  3702. if ($this->messages_caching || $this->options['all_headers']) {
  3703. $headers = array_merge($headers, $this->all_headers);
  3704. }
  3705.  
  3706. return $headers;
  3707. }
  3708.  
  3709.  
  3710. /* -----------------------------------------
  3711. * ACL and METADATA/ANNOTATEMORE methods
  3712. * ----------------------------------------*/
  3713.  
  3714. /**
  3715. * Changes the ACL on the specified folder (SETACL)
  3716. *
  3717. * @param string $folder Folder name
  3718. * @param string $user User name
  3719. * @param string $acl ACL string
  3720. *
  3721. * @return boolean True on success, False on failure
  3722. * @since 0.5-beta
  3723. */
  3724. public function set_acl($folder, $user, $acl)
  3725. {
  3726. if (!$this->get_capability('ACL')) {
  3727. return false;
  3728. }
  3729.  
  3730. if (!$this->check_connection()) {
  3731. return false;
  3732. }
  3733.  
  3734. $this->clear_cache('mailboxes.folder-info.' . $folder);
  3735.  
  3736. return $this->conn->setACL($folder, $user, $acl);
  3737. }
  3738.  
  3739.  
  3740. /**
  3741. * Removes any <identifier,rights> pair for the
  3742. * specified user from the ACL for the specified
  3743. * folder (DELETEACL)
  3744. *
  3745. * @param string $folder Folder name
  3746. * @param string $user User name
  3747. *
  3748. * @return boolean True on success, False on failure
  3749. * @since 0.5-beta
  3750. */
  3751. public function delete_acl($folder, $user)
  3752. {
  3753. if (!$this->get_capability('ACL')) {
  3754. return false;
  3755. }
  3756.  
  3757. if (!$this->check_connection()) {
  3758. return false;
  3759. }
  3760.  
  3761. return $this->conn->deleteACL($folder, $user);
  3762. }
  3763.  
  3764.  
  3765. /**
  3766. * Returns the access control list for folder (GETACL)
  3767. *
  3768. * @param string $folder Folder name
  3769. *
  3770. * @return array User-rights array on success, NULL on error
  3771. * @since 0.5-beta
  3772. */
  3773. public function get_acl($folder)
  3774. {
  3775. if (!$this->get_capability('ACL')) {
  3776. return null;
  3777. }
  3778.  
  3779. if (!$this->check_connection()) {
  3780. return null;
  3781. }
  3782.  
  3783. return $this->conn->getACL($folder);
  3784. }
  3785.  
  3786.  
  3787. /**
  3788. * Returns information about what rights can be granted to the
  3789. * user (identifier) in the ACL for the folder (LISTRIGHTS)
  3790. *
  3791. * @param string $folder Folder name
  3792. * @param string $user User name
  3793. *
  3794. * @return array List of user rights
  3795. * @since 0.5-beta
  3796. */
  3797. public function list_rights($folder, $user)
  3798. {
  3799. if (!$this->get_capability('ACL')) {
  3800. return null;
  3801. }
  3802.  
  3803. if (!$this->check_connection()) {
  3804. return null;
  3805. }
  3806.  
  3807. return $this->conn->listRights($folder, $user);
  3808. }
  3809.  
  3810.  
  3811. /**
  3812. * Returns the set of rights that the current user has to
  3813. * folder (MYRIGHTS)
  3814. *
  3815. * @param string $folder Folder name
  3816. *
  3817. * @return array MYRIGHTS response on success, NULL on error
  3818. * @since 0.5-beta
  3819. */
  3820. public function my_rights($folder)
  3821. {
  3822. if (!$this->get_capability('ACL')) {
  3823. return null;
  3824. }
  3825.  
  3826. if (!$this->check_connection()) {
  3827. return null;
  3828. }
  3829.  
  3830. return $this->conn->myRights($folder);
  3831. }
  3832.  
  3833.  
  3834. /**
  3835. * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
  3836. *
  3837. * @param string $folder Folder name (empty for server metadata)
  3838. * @param array $entries Entry-value array (use NULL value as NIL)
  3839. *
  3840. * @return boolean True on success, False on failure
  3841. * @since 0.5-beta
  3842. */
  3843. public function set_metadata($folder, $entries)
  3844. {
  3845. if (!$this->check_connection()) {
  3846. return false;
  3847. }
  3848.  
  3849. $this->clear_cache('mailboxes.metadata.', true);
  3850.  
  3851. if ($this->get_capability('METADATA') ||
  3852. (!strlen($folder) && $this->get_capability('METADATA-SERVER'))
  3853. ) {
  3854. return $this->conn->setMetadata($folder, $entries);
  3855. }
  3856. else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
  3857. foreach ((array)$entries as $entry => $value) {
  3858. list($ent, $attr) = $this->md2annotate($entry);
  3859. $entries[$entry] = array($ent, $attr, $value);
  3860. }
  3861. return $this->conn->setAnnotation($folder, $entries);
  3862. }
  3863.  
  3864. return false;
  3865. }
  3866.  
  3867.  
  3868. /**
  3869. * Unsets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
  3870. *
  3871. * @param string $folder Folder name (empty for server metadata)
  3872. * @param array $entries Entry names array
  3873. *
  3874. * @return boolean True on success, False on failure
  3875. * @since 0.5-beta
  3876. */
  3877. public function delete_metadata($folder, $entries)
  3878. {
  3879. if (!$this->check_connection()) {
  3880. return false;
  3881. }
  3882.  
  3883. $this->clear_cache('mailboxes.metadata.', true);
  3884.  
  3885. if ($this->get_capability('METADATA') ||
  3886. (!strlen($folder) && $this->get_capability('METADATA-SERVER'))
  3887. ) {
  3888. return $this->conn->deleteMetadata($folder, $entries);
  3889. }
  3890. else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
  3891. foreach ((array)$entries as $idx => $entry) {
  3892. list($ent, $attr) = $this->md2annotate($entry);
  3893. $entries[$idx] = array($ent, $attr, NULL);
  3894. }
  3895. return $this->conn->setAnnotation($folder, $entries);
  3896. }
  3897.  
  3898. return false;
  3899. }
  3900.  
  3901.  
  3902. /**
  3903. * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
  3904. *
  3905. * @param string $folder Folder name (empty for server metadata)
  3906. * @param array $entries Entries
  3907. * @param array $options Command options (with MAXSIZE and DEPTH keys)
  3908. *
  3909. * @return array Metadata entry-value hash array on success, NULL on error
  3910. * @since 0.5-beta
  3911. */
  3912. public function get_metadata($folder, $entries, $options=array())
  3913. {
  3914. $entries = (array)$entries;
  3915.  
  3916. // create cache key
  3917. // @TODO: this is the simplest solution, but we do the same with folders list
  3918. // maybe we should store data per-entry and merge on request
  3919. sort($options);
  3920. sort($entries);
  3921. $cache_key = 'mailboxes.metadata.' . $folder;
  3922. $cache_key .= '.' . md5(serialize($options).serialize($entries));
  3923.  
  3924. // get cached data
  3925. $cached_data = $this->get_cache($cache_key);
  3926.  
  3927. if (is_array($cached_data)) {
  3928. return $cached_data;
  3929. }
  3930.  
  3931. if (!$this->check_connection()) {
  3932. return null;
  3933. }
  3934.  
  3935. if ($this->get_capability('METADATA') ||
  3936. (!strlen($folder) && $this->get_capability('METADATA-SERVER'))
  3937. ) {
  3938. $res = $this->conn->getMetadata($folder, $entries, $options);
  3939. }
  3940. else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
  3941. $queries = array();
  3942. $res = array();
  3943.  
  3944. // Convert entry names
  3945. foreach ($entries as $entry) {
  3946. list($ent, $attr) = $this->md2annotate($entry);
  3947. $queries[$attr][] = $ent;
  3948. }
  3949.  
  3950. // @TODO: Honor MAXSIZE and DEPTH options
  3951. foreach ($queries as $attrib => $entry) {
  3952. $result = $this->conn->getAnnotation($folder, $entry, $attrib);
  3953.  
  3954. // an error, invalidate any previous getAnnotation() results
  3955. if (!is_array($result)) {
  3956. return null;
  3957. }
  3958. else {
  3959. foreach ($result as $fldr => $data) {
  3960. $res[$fldr] = array_merge((array) $res[$fldr], $data);
  3961. }
  3962. }
  3963. }
  3964. }
  3965.  
  3966. if (isset($res)) {
  3967. $this->update_cache($cache_key, $res);
  3968. return $res;
  3969. }
  3970.  
  3971. return null;
  3972. }
  3973.  
  3974.  
  3975. /**
  3976. * Converts the METADATA extension entry name into the correct
  3977. * entry-attrib names for older ANNOTATEMORE version.
  3978. *
  3979. * @param string $entry Entry name
  3980. *
  3981. * @return array Entry-attribute list, NULL if not supported (?)
  3982. */
  3983. protected function md2annotate($entry)
  3984. {
  3985. if (substr($entry, 0, 7) == '/shared') {
  3986. return array(substr($entry, 7), 'value.shared');
  3987. }
  3988. else if (substr($entry, 0, 8) == '/private') {
  3989. return array(substr($entry, 8), 'value.priv');
  3990. }
  3991.  
  3992. // @TODO: log error
  3993. return null;
  3994. }
  3995.  
  3996.  
  3997. /* --------------------------------
  3998. * internal caching methods
  3999. * --------------------------------*/
  4000.  
  4001. /**
  4002. * Enable or disable indexes caching
  4003. *
  4004. * @param string $type Cache type (@see rcube::get_cache)
  4005. */
  4006. public function set_caching($type)
  4007. {
  4008. if ($type) {
  4009. $this->caching = $type;
  4010. }
  4011. else {
  4012. if ($this->cache) {
  4013. $this->cache->close();
  4014. }
  4015. $this->cache = null;
  4016. $this->caching = false;
  4017. }
  4018. }
  4019.  
  4020. /**
  4021. * Getter for IMAP cache object
  4022. */
  4023. protected function get_cache_engine()
  4024. {
  4025. if ($this->caching && !$this->cache) {
  4026. $rcube = rcube::get_instance();
  4027. $ttl = $rcube->config->get('imap_cache_ttl', '10d');
  4028. $this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl);
  4029. }
  4030.  
  4031. return $this->cache;
  4032. }
  4033.  
  4034. /**
  4035. * Returns cached value
  4036. *
  4037. * @param string $key Cache key
  4038. *
  4039. * @return mixed
  4040. */
  4041. public function get_cache($key)
  4042. {
  4043. if ($cache = $this->get_cache_engine()) {
  4044. return $cache->get($key);
  4045. }
  4046. }
  4047.  
  4048. /**
  4049. * Update cache
  4050. *
  4051. * @param string $key Cache key
  4052. * @param mixed $data Data
  4053. */
  4054. public function update_cache($key, $data)
  4055. {
  4056. if ($cache = $this->get_cache_engine()) {
  4057. $cache->set($key, $data);
  4058. }
  4059. }
  4060.  
  4061. /**
  4062. * Clears the cache.
  4063. *
  4064. * @param string $key Cache key name or pattern
  4065. * @param boolean $prefix_mode Enable it to clear all keys starting
  4066. * with prefix specified in $key
  4067. */
  4068. public function clear_cache($key = null, $prefix_mode = false)
  4069. {
  4070. if ($cache = $this->get_cache_engine()) {
  4071. $cache->remove($key, $prefix_mode);
  4072. }
  4073. }
  4074.  
  4075.  
  4076. /* --------------------------------
  4077. * message caching methods
  4078. * --------------------------------*/
  4079.  
  4080. /**
  4081. * Enable or disable messages caching
  4082. *
  4083. * @param boolean $set Flag
  4084. * @param int $mode Cache mode
  4085. */
  4086. public function set_messages_caching($set, $mode = null)
  4087. {
  4088. if ($set) {
  4089. $this->messages_caching = true;
  4090.  
  4091. if ($mode && ($cache = $this->get_mcache_engine())) {
  4092. $cache->set_mode($mode);
  4093. }
  4094. }
  4095. else {
  4096. if ($this->mcache) {
  4097. $this->mcache->close();
  4098. }
  4099. $this->mcache = null;
  4100. $this->messages_caching = false;
  4101. }
  4102. }
  4103.  
  4104.  
  4105. /**
  4106. * Getter for messages cache object
  4107. */
  4108. protected function get_mcache_engine()
  4109. {
  4110. if ($this->messages_caching && !$this->mcache) {
  4111. $rcube = rcube::get_instance();
  4112. if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) {
  4113. $ttl = $rcube->config->get('messages_cache_ttl', '10d');
  4114. $threshold = $rcube->config->get('messages_cache_threshold', 50);
  4115. $this->mcache = new rcube_imap_cache(
  4116. $dbh, $this, $userid, $this->options['skip_deleted'], $ttl, $threshold);
  4117. }
  4118. }
  4119.  
  4120. return $this->mcache;
  4121. }
  4122.  
  4123.  
  4124. /**
  4125. * Clears the messages cache.
  4126. *
  4127. * @param string $folder Folder name
  4128. * @param array $uids Optional message UIDs to remove from cache
  4129. */
  4130. protected function clear_message_cache($folder = null, $uids = null)
  4131. {
  4132. if ($mcache = $this->get_mcache_engine()) {
  4133. $mcache->clear($folder, $uids);
  4134. }
  4135. }
  4136.  
  4137.  
  4138. /**
  4139. * Delete outdated cache entries
  4140. */
  4141. function cache_gc()
  4142. {
  4143. rcube_imap_cache::gc();
  4144. }
  4145.  
  4146.  
  4147. /* --------------------------------
  4148. * protected methods
  4149. * --------------------------------*/
  4150.  
  4151. /**
  4152. * Validate the given input and save to local properties
  4153. *
  4154. * @param string $sort_field Sort column
  4155. * @param string $sort_order Sort order
  4156. */
  4157. protected function set_sort_order($sort_field, $sort_order)
  4158. {
  4159. if ($sort_field != null) {
  4160. $this->sort_field = asciiwords($sort_field);
  4161. }
  4162. if ($sort_order != null) {
  4163. $this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
  4164. }
  4165. }
  4166.  
  4167.  
  4168. /**
  4169. * Sort folders first by default folders and then in alphabethical order
  4170. *
  4171. * @param array $a_folders Folders list
  4172. * @param bool $skip_default Skip default folders handling
  4173. *
  4174. * @return array Sorted list
  4175. */
  4176. public function sort_folder_list($a_folders, $skip_default = false)
  4177. {
  4178. $specials = array_merge(array('INBOX'), array_values($this->get_special_folders()));
  4179. $folders = array();
  4180.  
  4181. // convert names to UTF-8 and skip folders starting with '.'
  4182. foreach ($a_folders as $folder) {
  4183. if ($folder[0] != '.') {
  4184. // for better performance skip encoding conversion
  4185. // if the string does not look like UTF7-IMAP
  4186. $folders[$folder] = strpos($folder, '&') === false ? $folder : rcube_charset::convert($folder, 'UTF7-IMAP');
  4187. }
  4188. }
  4189.  
  4190. // sort folders
  4191. // asort($folders, SORT_LOCALE_STRING) is not properly sorting case sensitive names
  4192. uasort($folders, array($this, 'sort_folder_comparator'));
  4193.  
  4194. $folders = array_keys($folders);
  4195.  
  4196. if ($skip_default) {
  4197. return $folders;
  4198. }
  4199.  
  4200. // force the type of folder name variable (#1485527)
  4201. $folders = array_map('strval', $folders);
  4202. $out = array();
  4203.  
  4204. // finally we must put special folders on top and rebuild the list
  4205. // to move their subfolders where they belong...
  4206. $specials = array_unique(array_intersect($specials, $folders));
  4207. $folders = array_merge($specials, array_diff($folders, $specials));
  4208.  
  4209. $this->sort_folder_specials(null, $folders, $specials, $out);
  4210.  
  4211. return $out;
  4212. }
  4213.  
  4214. /**
  4215. * Recursive function to put subfolders of special folders in place
  4216. */
  4217. protected function sort_folder_specials($folder, &$list, &$specials, &$out)
  4218. {
  4219. while (list($key, $name) = each($list)) {
  4220. if ($folder === null || strpos($name, $folder.$this->delimiter) === 0) {
  4221. $out[] = $name;
  4222. unset($list[$key]);
  4223.  
  4224. if (!empty($specials) && ($found = array_search($name, $specials)) !== false) {
  4225. unset($specials[$found]);
  4226. $this->sort_folder_specials($name, $list, $specials, $out);
  4227. }
  4228. }
  4229. }
  4230.  
  4231. reset($list);
  4232. }
  4233.  
  4234. /**
  4235. * Callback for uasort() that implements correct
  4236. * locale-aware case-sensitive sorting
  4237. */
  4238. protected function sort_folder_comparator($str1, $str2)
  4239. {
  4240. $path1 = explode($this->delimiter, $str1);
  4241. $path2 = explode($this->delimiter, $str2);
  4242.  
  4243. foreach ($path1 as $idx => $folder1) {
  4244. $folder2 = $path2[$idx];
  4245.  
  4246. if ($folder1 === $folder2) {
  4247. continue;
  4248. }
  4249.  
  4250. return strcoll($folder1, $folder2);
  4251. }
  4252. }
  4253.  
  4254.  
  4255. /**
  4256. * Find UID of the specified message sequence ID
  4257. *
  4258. * @param int $id Message (sequence) ID
  4259. * @param string $folder Folder name
  4260. *
  4261. * @return int Message UID
  4262. */
  4263. public function id2uid($id, $folder = null)
  4264. {
  4265. if (!strlen($folder)) {
  4266. $folder = $this->folder;
  4267. }
  4268.  
  4269. if ($uid = array_search($id, (array)$this->uid_id_map[$folder])) {
  4270. return $uid;
  4271. }
  4272.  
  4273. if (!$this->check_connection()) {
  4274. return null;
  4275. }
  4276.  
  4277. $uid = $this->conn->ID2UID($folder, $id);
  4278.  
  4279. $this->uid_id_map[$folder][$uid] = $id;
  4280.  
  4281. return $uid;
  4282. }
  4283.  
  4284.  
  4285. /**
  4286. * Subscribe/unsubscribe a list of folders and update local cache
  4287. */
  4288. protected function change_subscription($folders, $mode)
  4289. {
  4290. $updated = false;
  4291.  
  4292. if (!empty($folders)) {
  4293. if (!$this->check_connection()) {
  4294. return false;
  4295. }
  4296.  
  4297. foreach ((array)$folders as $i => $folder) {
  4298. $folders[$i] = $folder;
  4299.  
  4300. if ($mode == 'subscribe') {
  4301. $updated = $this->conn->subscribe($folder);
  4302. }
  4303. else if ($mode == 'unsubscribe') {
  4304. $updated = $this->conn->unsubscribe($folder);
  4305. }
  4306. }
  4307. }
  4308.  
  4309. // clear cached folders list(s)
  4310. if ($updated) {
  4311. $this->clear_cache('mailboxes', true);
  4312. }
  4313.  
  4314. return $updated;
  4315. }
  4316.  
  4317.  
  4318. /**
  4319. * Increde/decrese messagecount for a specific folder
  4320. */
  4321. protected function set_messagecount($folder, $mode, $increment)
  4322. {
  4323. if (!is_numeric($increment)) {
  4324. return false;
  4325. }
  4326.  
  4327. $mode = strtoupper($mode);
  4328. $a_folder_cache = $this->get_cache('messagecount');
  4329.  
  4330. if (!is_array($a_folder_cache[$folder]) || !isset($a_folder_cache[$folder][$mode])) {
  4331. return false;
  4332. }
  4333.  
  4334. // add incremental value to messagecount
  4335. $a_folder_cache[$folder][$mode] += $increment;
  4336.  
  4337. // there's something wrong, delete from cache
  4338. if ($a_folder_cache[$folder][$mode] < 0) {
  4339. unset($a_folder_cache[$folder][$mode]);
  4340. }
  4341.  
  4342. // write back to cache
  4343. $this->update_cache('messagecount', $a_folder_cache);
  4344.  
  4345. return true;
  4346. }
  4347.  
  4348.  
  4349. /**
  4350. * Remove messagecount of a specific folder from cache
  4351. */
  4352. protected function clear_messagecount($folder, $mode=null)
  4353. {
  4354. $a_folder_cache = $this->get_cache('messagecount');
  4355.  
  4356. if (is_array($a_folder_cache[$folder])) {
  4357. if ($mode) {
  4358. unset($a_folder_cache[$folder][$mode]);
  4359. }
  4360. else {
  4361. unset($a_folder_cache[$folder]);
  4362. }
  4363. $this->update_cache('messagecount', $a_folder_cache);
  4364. }
  4365. }
  4366.  
  4367.  
  4368. /**
  4369. * Converts date string/object into IMAP date/time format
  4370. */
  4371. protected function date_format($date)
  4372. {
  4373. if (empty($date)) {
  4374. return null;
  4375. }
  4376.  
  4377. if (!is_object($date) || !is_a($date, 'DateTime')) {
  4378. try {
  4379. $timestamp = rcube_utils::strtotime($date);
  4380. $date = new DateTime("@".$timestamp);
  4381. }
  4382. catch (Exception $e) {
  4383. return null;
  4384. }
  4385. }
  4386.  
  4387. return $date->format('d-M-Y H:i:s O');
  4388. }
  4389.  
  4390.  
  4391. /**
  4392. * This is our own debug handler for the IMAP connection
  4393. * @access public
  4394. */
  4395. public function debug_handler(&$imap, $message)
  4396. {
  4397. rcube::write_log('imap', $message);
  4398. }
  4399.  
  4400.  
  4401. /**
  4402. * Deprecated methods (to be removed)
  4403. */
  4404.  
  4405. public function decode_address_list($input, $max = null, $decode = true, $fallback = null)
  4406. {
  4407. return rcube_mime::decode_address_list($input, $max, $decode, $fallback);
  4408. }
  4409.  
  4410. public function decode_header($input, $fallback = null)
  4411. {
  4412. return rcube_mime::decode_mime_string((string)$input, $fallback);
  4413. }
  4414.  
  4415. public static function decode_mime_string($input, $fallback = null)
  4416. {
  4417. return rcube_mime::decode_mime_string($input, $fallback);
  4418. }
  4419.  
  4420. public function mime_decode($input, $encoding = '7bit')
  4421. {
  4422. return rcube_mime::decode($input, $encoding);
  4423. }
  4424.  
  4425. public static function explode_header_string($separator, $str, $remove_comments = false)
  4426. {
  4427. return rcube_mime::explode_header_string($separator, $str, $remove_comments);
  4428. }
  4429.  
  4430. public function select_mailbox($mailbox)
  4431. {
  4432. // do nothing
  4433. }
  4434.  
  4435. public function set_mailbox($folder)
  4436. {
  4437. $this->set_folder($folder);
  4438. }
  4439.  
  4440. public function get_mailbox_name()
  4441. {
  4442. return $this->get_folder();
  4443. }
  4444.  
  4445. public function list_headers($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
  4446. {
  4447. return $this->list_messages($folder, $page, $sort_field, $sort_order, $slice);
  4448. }
  4449.  
  4450. public function get_headers($uid, $folder = null, $force = false)
  4451. {
  4452. return $this->get_message_headers($uid, $folder, $force);
  4453. }
  4454.  
  4455. public function mailbox_status($folder = null)
  4456. {
  4457. return $this->folder_status($folder);
  4458. }
  4459.  
  4460. public function message_index($folder = '', $sort_field = NULL, $sort_order = NULL)
  4461. {
  4462. return $this->index($folder, $sort_field, $sort_order);
  4463. }
  4464.  
  4465. public function message_index_direct($folder, $sort_field = null, $sort_order = null)
  4466. {
  4467. return $this->index_direct($folder, $sort_field, $sort_order);
  4468. }
  4469.  
  4470. public function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
  4471. {
  4472. return $this->list_folders_subscribed($root, $name, $filter, $rights, $skip_sort);
  4473. }
  4474.  
  4475. public function list_unsubscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
  4476. {
  4477. return $this->list_folders($root, $name, $filter, $rights, $skip_sort);
  4478. }
  4479.  
  4480. public function get_mailbox_size($folder)
  4481. {
  4482. return $this->folder_size($folder);
  4483. }
  4484.  
  4485. public function create_mailbox($folder, $subscribe=false)
  4486. {
  4487. return $this->create_folder($folder, $subscribe);
  4488. }
  4489.  
  4490. public function rename_mailbox($folder, $new_name)
  4491. {
  4492. return $this->rename_folder($folder, $new_name);
  4493. }
  4494.  
  4495. function delete_mailbox($folder)
  4496. {
  4497. return $this->delete_folder($folder);
  4498. }
  4499.  
  4500. function clear_mailbox($folder = null)
  4501. {
  4502. return $this->clear_folder($folder);
  4503. }
  4504.  
  4505. public function mailbox_exists($folder, $subscription=false)
  4506. {
  4507. return $this->folder_exists($folder, $subscription);
  4508. }
  4509.  
  4510. public function mailbox_namespace($folder)
  4511. {
  4512. return $this->folder_namespace($folder);
  4513. }
  4514.  
  4515. public function mod_mailbox($folder, $mode = 'out')
  4516. {
  4517. return $this->mod_folder($folder, $mode);
  4518. }
  4519.  
  4520. public function mailbox_attributes($folder, $force=false)
  4521. {
  4522. return $this->folder_attributes($folder, $force);
  4523. }
  4524.  
  4525. public function mailbox_data($folder)
  4526. {
  4527. return $this->folder_data($folder);
  4528. }
  4529.  
  4530. public function mailbox_info($folder)
  4531. {
  4532. return $this->folder_info($folder);
  4533. }
  4534.  
  4535. public function mailbox_sync($folder)
  4536. {
  4537. return $this->folder_sync($folder);
  4538. }
  4539.  
  4540. public function expunge($folder='', $clear_cache=true)
  4541. {
  4542. return $this->expunge_folder($folder, $clear_cache);
  4543. }
  4544.  
  4545. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement