PageRenderTime 63ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/imp-h3-4.3.10/lib/IMAP/Tree.php

#
PHP | 2114 lines | 1336 code | 200 blank | 578 comment | 215 complexity | a77810e65f8a02e7cbf4352edbdad3c1 MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. require_once 'Horde/Serialize.php';
  3. /**
  4. * The IMP_tree class provides a tree view of the mailboxes in an IMAP/POP3
  5. * repository. It provides access functions to iterate through this tree and
  6. * query information about individual mailboxes.
  7. * In IMP, folders = IMAP mailboxes so the two terms are used interchangably.
  8. *
  9. * $Horde: imp/lib/IMAP/Tree.php,v 1.25.2.73 2010/09/29 18:35:42 slusarz Exp $
  10. *
  11. * Copyright 2000-2009 The Horde Project (http://www.horde.org/)
  12. *
  13. * See the enclosed file COPYING for license information (GPL). If you
  14. * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
  15. *
  16. * @author Chuck Hagenbuch <chuck@horde.org>
  17. * @author Jon Parise <jon@horde.org>
  18. * @author Anil Madhavapeddy <avsm@horde.org>
  19. * @author Michael Slusarz <slusarz@horde.org>
  20. * @package IMP
  21. */
  22. /* Constants for mailboxElt attributes. */
  23. define('IMPTREE_ELT_NOSELECT', 1);
  24. define('IMPTREE_ELT_NAMESPACE', 2);
  25. define('IMPTREE_ELT_IS_OPEN', 4);
  26. define('IMPTREE_ELT_IS_SUBSCRIBED', 8);
  27. define('IMPTREE_ELT_NOSHOW', 16);
  28. define('IMPTREE_ELT_IS_POLLED', 32);
  29. define('IMPTREE_ELT_NEED_SORT', 64);
  30. define('IMPTREE_ELT_VFOLDER', 128);
  31. define('IMPTREE_ELT_NONIMAP', 256);
  32. define('IMPTREE_ELT_INVISIBLE', 512);
  33. /* The isOpen() expanded mode constants. */
  34. define('IMPTREE_OPEN_NONE', 0);
  35. define('IMPTREE_OPEN_ALL', 1);
  36. define('IMPTREE_OPEN_USER', 2);
  37. /* The manner to which to traverse the tree when calling next(). */
  38. define('IMPTREE_NEXT_SHOWCLOSED', 1);
  39. define('IMPTREE_NEXT_SHOWSUB', 2);
  40. /* The string used to indicate the base of the tree. */
  41. define('IMPTREE_BASE_ELT', '%');
  42. /** Defines used with the output from the build() function. */
  43. define('IMPTREE_SPECIAL_INBOX', 1);
  44. define('IMPTREE_SPECIAL_TRASH', 2);
  45. define('IMPTREE_SPECIAL_DRAFT', 3);
  46. define('IMPTREE_SPECIAL_SPAM', 4);
  47. define('IMPTREE_SPECIAL_SENT', 5);
  48. /** Defines used with folderList(). */
  49. define('IMPTREE_FLIST_CONTAINER', 1);
  50. define('IMPTREE_FLIST_UNSUB', 2);
  51. define('IMPTREE_FLIST_OB', 4);
  52. define('IMPTREE_FLIST_VFOLDER', 8);
  53. /* Add a percent to folder key since it allows us to sort by name but never
  54. * conflict with an IMAP mailbox of the same name (since '%' is an invalid
  55. * character in an IMAP mailbox string). */
  56. /** Defines used with virtual folders. */
  57. define('IMPTREE_VFOLDER_LABEL', _("Virtual Folders"));
  58. define('IMPTREE_VFOLDER_KEY', IMPTREE_VFOLDER_LABEL . '%');
  59. /** Defines used with namespace display. */
  60. define('IMPTREE_SHARED_LABEL', _("Shared Folders"));
  61. define('IMPTREE_SHARED_KEY', IMPTREE_SHARED_LABEL . '%');
  62. define('IMPTREE_OTHER_LABEL', _("Other Users' Folders"));
  63. define('IMPTREE_OTHER_KEY', IMPTREE_OTHER_LABEL . '%');
  64. class IMP_Tree {
  65. /**
  66. * Array containing the mailbox tree.
  67. *
  68. * @var array
  69. */
  70. var $_tree;
  71. /**
  72. * Location of current element in the tree.
  73. *
  74. * @var string
  75. */
  76. var $_currparent = null;
  77. /**
  78. * Location of current element in the tree.
  79. *
  80. * @var integer
  81. */
  82. var $_currkey = null;
  83. /**
  84. * Location of current element in the tree.
  85. *
  86. * @var array
  87. */
  88. var $_currstack = array();
  89. /**
  90. * Show unsubscribed mailboxes?
  91. *
  92. * @var boolean
  93. */
  94. var $_showunsub = false;
  95. /**
  96. * Parent list.
  97. *
  98. * @var array
  99. */
  100. var $_parent = array();
  101. /**
  102. * The cached list of mailboxes to poll.
  103. *
  104. * @var array
  105. */
  106. var $_poll = null;
  107. /**
  108. * The cached list of expanded folders.
  109. *
  110. * @var array
  111. */
  112. var $_expanded = null;
  113. /**
  114. * Cached list of subscribed mailboxes.
  115. *
  116. * @var array
  117. */
  118. var $_subscribed = null;
  119. /**
  120. * The cached full list of mailboxes on the server.
  121. *
  122. * @var array
  123. */
  124. var $_fulllist = null;
  125. /**
  126. * Tree changed flag. Set when something in the tree has been altered.
  127. *
  128. * @var boolean
  129. */
  130. var $_changed = false;
  131. /**
  132. * Have we shown unsubscribed folders previously?
  133. *
  134. * @var boolean
  135. */
  136. var $_unsubview = false;
  137. /**
  138. * The IMAP_Sort object.
  139. *
  140. * @var IMAP_Sort
  141. */
  142. var $_imap_sort = null;
  143. /**
  144. * The server string for the current server.
  145. *
  146. * @var string
  147. */
  148. var $_server = '';
  149. /**
  150. * The server string used for the delimiter.
  151. *
  152. * @var string
  153. */
  154. var $_delimiter = '/';
  155. /**
  156. * The list of namespaces to add to the tree.
  157. *
  158. * @var array
  159. */
  160. var $_namespaces = array();
  161. /**
  162. * Used to determine the list of element changes.
  163. *
  164. * @var array
  165. */
  166. var $_eltdiff = null;
  167. /**
  168. * If set, track element changes.
  169. *
  170. * @var boolean
  171. */
  172. var $_trackdiff = true;
  173. /**
  174. * See $open parameter in build().
  175. *
  176. * @var boolean
  177. */
  178. var $_forceopen = false;
  179. /**
  180. * Attempts to return a reference to a concrete IMP_Tree instance.
  181. *
  182. * If an IMP_Tree object is currently stored in the local session,
  183. * recreate that object. Else, create a new instance. Ensures that only
  184. * one IMP_Tree instance is available at any time.
  185. *
  186. * This method must be invoked as:<pre>
  187. * $imp_tree = &IMP_Tree::singleton();
  188. * </pre>
  189. *
  190. * @return IMP_Tree The IMP_Tree object or null.
  191. */
  192. function &singleton()
  193. {
  194. static $instance;
  195. if (!isset($instance)) {
  196. if (!empty($_SESSION['imp']['cache']['imp_tree'])) {
  197. $ptr = &$_SESSION['imp']['cache']['imp_tree'];
  198. $instance = Horde_Serialize::unserialize($ptr['ob'], $ptr['s']);
  199. }
  200. if (empty($instance) || is_a($instance, 'PEAR_Error')) {
  201. $instance = new IMP_Tree();
  202. }
  203. register_shutdown_function(array(&$instance, '_store'));
  204. }
  205. return $instance;
  206. }
  207. /**
  208. * Constructor.
  209. */
  210. function IMP_Tree()
  211. {
  212. $this->_server = IMP::serverString();
  213. if ($_SESSION['imp']['base_protocol'] != 'pop3') {
  214. $ptr = reset($_SESSION['imp']['namespace']);
  215. $this->_delimiter = $ptr['delimiter'];
  216. $this->_namespaces = (empty($GLOBALS['conf']['user']['allow_folders'])) ? array() : $_SESSION['imp']['namespace'];
  217. }
  218. $this->init();
  219. }
  220. /**
  221. * Store a serialized version of ourself in the current session.
  222. *
  223. * @access private
  224. */
  225. function _store()
  226. {
  227. /* We only need to restore the object if the tree has changed. */
  228. if (empty($this->_changed)) {
  229. return;
  230. }
  231. /* Don't store $_expanded and $_poll - these values are handled
  232. * by the subclasses.
  233. * Don't store $_imap_sort or $_eltdiff - these needs to be
  234. * regenerated for each request.
  235. * Don't store $_currkey, $_currparent, and $_currstack since the
  236. * user MUST call reset() before cycling through the tree.
  237. * Don't store $_subscribed and $_fulllist - - this information is
  238. * stored in the elements.
  239. * Reset the $_changed and $_trackdiff flags. */
  240. $this->_currkey = $this->_currparent = $this->_eltdiff = $this->_expanded = $this->_fulllist = $this->_imap_sort = $this->_poll = $this->_subscribed = null;
  241. $this->_currstack = array();
  242. $this->_changed = false;
  243. $this->_trackdiff = true;
  244. if (!isset($_SESSION['imp']['cache']['imp_tree'])) {
  245. $_SESSION['imp']['cache']['imp_tree'] = array(
  246. 's' => (defined('SERIALIZE_LZF') && Horde_Serialize::hasCapability(SERIALIZE_LZF)) ? array(SERIALIZE_LZF, SERIALIZE_BASIC) : array(SERIALIZE_BASIC)
  247. );
  248. }
  249. $ptr = &$_SESSION['imp']['cache']['imp_tree'];
  250. $ptr['ob'] = Horde_Serialize::serialize($this, array_reverse($ptr['s']));
  251. }
  252. /**
  253. * Returns the list of mailboxes on the server.
  254. *
  255. * @access private
  256. *
  257. * @param boolean $showunsub Show unsubscribed mailboxes?
  258. *
  259. * @return array A list of mailbox names.
  260. * The server string has been removed from the name.
  261. */
  262. function _getList($showunsub)
  263. {
  264. if ($showunsub && !is_null($this->_fulllist)) {
  265. return $this->_fulllist;
  266. } elseif (!$showunsub && !is_null($this->_subscribed)) {
  267. return array_keys($this->_subscribed);
  268. }
  269. $imp_imap = &IMP_IMAP::singleton();
  270. $names = array();
  271. $old_error = error_reporting(0);
  272. foreach ($this->_namespaces as $key => $val) {
  273. $newboxes = call_user_func_array($showunsub ? 'imap_list' : 'imap_lsub', array($imp_imap->stream(), $this->_server, $key . '*'));
  274. if (is_array($newboxes)) {
  275. foreach ($newboxes as $box) {
  276. /* Strip off server string. */
  277. $names[$this->_convertName(substr($box, strpos($box, '}') + 1))] = 1;
  278. }
  279. }
  280. }
  281. error_reporting($old_error);
  282. if (!$showunsub) {
  283. // Need to compare to full list to remove non-existent mailboxes
  284. // See RFC 3501 [6.3.9]
  285. $names = array_flip(array_intersect($this->_getList(true), array_keys($names)));
  286. }
  287. if (!isset($names['INBOX'])) {
  288. /* INBOX must always appear. */
  289. $names = array('INBOX' => 1) + $names;
  290. }
  291. if ($showunsub) {
  292. $this->_fulllist = array_keys($names);
  293. return $this->_fulllist;
  294. }
  295. $this->_subscribed = $names;
  296. return array_keys($names);
  297. }
  298. /**
  299. * Make a single mailbox tree element.
  300. * An element consists of the following items (we use single letters here
  301. * to save in session storage space):
  302. * 'a' -- Attributes
  303. * 'c' -- Level count
  304. * 'l' -- Label
  305. * 'p' -- Parent node
  306. * 'v' -- Value
  307. *
  308. * @access private
  309. *
  310. * @param string $name The mailbox name.
  311. * @param integer $attributes The mailbox's attributes.
  312. *
  313. * @return array See above format.
  314. */
  315. function _makeElt($name, $attributes = 0)
  316. {
  317. $elt = array(
  318. 'a' => $attributes,
  319. 'c' => 0,
  320. 'p' => IMPTREE_BASE_ELT,
  321. 'v' => $name
  322. );
  323. /* Set subscribed values. We know the folder is subscribed, without
  324. * query of the IMAP server, in the following situations:
  325. * + Folder is INBOX.
  326. * + We are adding while in subscribe-only mode.
  327. * + Subscriptions are turned off. */
  328. if (!$this->isSubscribed($elt)) {
  329. if (!$this->_showunsub ||
  330. ($elt['v'] == 'INBOX') ||
  331. !$GLOBALS['prefs']->getValue('subscribe')) {
  332. $this->_setSubscribed($elt, true);
  333. } else {
  334. $this->_initSubscribed();
  335. $this->_setSubscribed($elt, isset($this->_subscribed[$elt['v']]));
  336. }
  337. }
  338. /* Check for polled status. */
  339. $this->_initPollList();
  340. $this->_setPolled($elt, isset($this->_poll[$elt['v']]));
  341. /* Check for open status. */
  342. switch ($GLOBALS['prefs']->getValue('nav_expanded')) {
  343. case IMPTREE_OPEN_NONE:
  344. $open = false;
  345. break;
  346. case IMPTREE_OPEN_ALL:
  347. $open = true;
  348. break;
  349. case IMPTREE_OPEN_USER:
  350. $this->_initExpandedList();
  351. $open = !empty($this->_expanded[$elt['v']]);
  352. break;
  353. }
  354. $this->_setOpen($elt, $open);
  355. /* Computed values. */
  356. $ns_info = $this->_getNamespace($elt['v']);
  357. $tmp = explode(is_null($ns_info) ? $this->_delimiter : $ns_info['delimiter'], $elt['v']);
  358. $elt['c'] = count($tmp) - 1;
  359. /* Convert 'INBOX' to localized name. */
  360. $elt['l'] = ($elt['v'] == 'INBOX') ? _("Inbox") : String::convertCharset($tmp[$elt['c']], 'UTF7-IMAP');
  361. if ($_SESSION['imp']['base_protocol'] != 'pop3') {
  362. if (!empty($GLOBALS['conf']['hooks']['display_folder'])) {
  363. $this->_setInvisible($elt, !Horde::callHook('_imp_hook_display_folder', array($elt['v']), 'imp'));
  364. }
  365. if ($elt['c'] != 0) {
  366. $elt['p'] = implode(is_null($ns_info) ? $this->_delimiter : $ns_info['delimiter'], array_slice($tmp, 0, $elt['c']));
  367. }
  368. if (!is_null($ns_info)) {
  369. switch ($ns_info['type']) {
  370. case 'personal':
  371. /* Strip personal namespace. */
  372. if (!empty($ns_info['name']) && ($elt['c'] != 0)) {
  373. --$elt['c'];
  374. if (strpos($elt['p'], $ns_info['delimiter']) === false) {
  375. $elt['p'] = IMPTREE_BASE_ELT;
  376. } elseif (strpos($elt['v'], $ns_info['name'] . 'INBOX' . $ns_info['delimiter']) === 0) {
  377. $elt['p'] = 'INBOX';
  378. }
  379. }
  380. break;
  381. case 'other':
  382. case 'shared':
  383. if (substr($ns_info['name'], 0, -1 * strlen($ns_info['delimiter'])) == $elt['v']) {
  384. $elt['a'] = IMPTREE_ELT_NOSELECT | IMPTREE_ELT_NAMESPACE;
  385. }
  386. if ($GLOBALS['prefs']->getValue('tree_view')) {
  387. $name = ($ns_info['type'] == 'other') ? IMPTREE_OTHER_KEY : IMPTREE_SHARED_KEY;
  388. if ($elt['c'] == 0) {
  389. $elt['p'] = $name;
  390. ++$elt['c'];
  391. } elseif ($this->_tree[$name] && IMPTREE_ELT_NOSHOW) {
  392. if ($elt['c'] == 1) {
  393. $elt['p'] = $name;
  394. }
  395. } else {
  396. ++$elt['c'];
  397. }
  398. }
  399. break;
  400. }
  401. }
  402. }
  403. return $elt;
  404. }
  405. /**
  406. * Initalize the tree.
  407. */
  408. function init()
  409. {
  410. $initmode = (($_SESSION['imp']['base_protocol'] == 'pop3') ||
  411. !$GLOBALS['prefs']->getValue('subscribe') ||
  412. $_SESSION['imp']['showunsub'])
  413. ? 'unsub' : 'sub';
  414. /* Reset class variables to the defaults. */
  415. $this->_changed = true;
  416. $this->_currkey = $this->_currparent = $this->_subscribed = null;
  417. $this->_currstack = $this->_tree = $this->_parent = array();
  418. $this->_showunsub = $this->_unsubview = ($initmode == 'unsub');
  419. /* Create a placeholder element to the base of the tree list so we can
  420. * keep track of whether the base level needs to be sorted. */
  421. $this->_tree[IMPTREE_BASE_ELT] = array(
  422. 'a' => IMPTREE_ELT_NEED_SORT,
  423. 'v' => IMPTREE_BASE_ELT
  424. );
  425. if (empty($GLOBALS['conf']['user']['allow_folders']) ||
  426. ($_SESSION['imp']['base_protocol'] == 'pop3')) {
  427. $this->_insertElt($this->_makeElt('INBOX', IMPTREE_ELT_IS_SUBSCRIBED));
  428. return;
  429. }
  430. /* Add namespace elements. */
  431. foreach ($this->_namespaces as $key => $val) {
  432. if ($val['type'] != 'personal' &&
  433. $GLOBALS['prefs']->getValue('tree_view')) {
  434. $elt = $this->_makeElt(
  435. ($val['type'] == 'other') ? IMPTREE_OTHER_KEY : IMPTREE_SHARED_KEY,
  436. IMPTREE_ELT_NOSELECT | IMPTREE_ELT_NAMESPACE | IMPTREE_ELT_NONIMAP | IMPTREE_ELT_NOSHOW
  437. );
  438. $elt['l'] = ($val['type'] == 'other')
  439. ? IMPTREE_OTHER_LABEL : IMPTREE_SHARED_LABEL;
  440. foreach ($this->_namespaces as $val2) {
  441. if (($val2['type'] == $val['type']) &&
  442. ($val2['name'] != $val['name'])) {
  443. $elt['a'] &= ~IMPTREE_ELT_NOSHOW;
  444. break;
  445. }
  446. }
  447. $this->_insertElt($elt);
  448. }
  449. }
  450. /* Create the list (INBOX and all other hierarchies). */
  451. $this->insert($this->_getList($this->_showunsub));
  452. /* Add virtual folders to the tree. */
  453. $this->insertVFolders($GLOBALS['imp_search']->listQueries(true));
  454. }
  455. /**
  456. * Expand a mail folder.
  457. *
  458. * @param string $folder The folder name to expand.
  459. * @param boolean $expandall Expand all folders under this one?
  460. */
  461. function expand($folder, $expandall = false)
  462. {
  463. $folder = $this->_convertName($folder);
  464. if (!isset($this->_tree[$folder])) {
  465. return;
  466. }
  467. $elt = &$this->_tree[$folder];
  468. if ($this->hasChildren($elt)) {
  469. if (!$this->isOpen($elt)) {
  470. $this->_changed = true;
  471. $this->_setOpen($elt, true);
  472. }
  473. /* Expand all children beneath this one. */
  474. if ($expandall && !empty($this->_parent[$folder])) {
  475. foreach ($this->_parent[$folder] as $val) {
  476. $this->expand($this->_tree[$val]['v'], true);
  477. }
  478. }
  479. }
  480. }
  481. /**
  482. * Collapse a mail folder.
  483. *
  484. * @param string $folder The folder name to collapse.
  485. */
  486. function collapse($folder)
  487. {
  488. $folder = $this->_convertName($folder);
  489. if (!isset($this->_tree[$folder])) {
  490. return;
  491. }
  492. if ($this->isOpen($this->_tree[$folder])) {
  493. $this->_changed = true;
  494. $this->_setOpen($this->_tree[$folder], false);
  495. }
  496. }
  497. /**
  498. * Sets the internal array pointer to the next element, and returns the
  499. * next object.
  500. *
  501. * @param integer $mask A mask with the following elements:
  502. * <pre>
  503. * IMPTREE_NEXT_SHOWCLOSED - Don't ignore closed elements.
  504. * IMPTREE_NEXT_SHOWSUB - Only show subscribed elements.
  505. * </pre>
  506. *
  507. * @return mixed Returns the next element or false if the element doesn't
  508. * exist.
  509. */
  510. function next($mask = 0)
  511. {
  512. do {
  513. $res = $this->_next($mask);
  514. } while (is_null($res));
  515. return $res;
  516. }
  517. /**
  518. * Private helper function to avoid recursion issues (see Bug #7420).
  519. *
  520. * @access private
  521. *
  522. * @param integer $mask See next().
  523. *
  524. * @return mixed Returns the next element, false if the element doesn't
  525. * exist, or null if the current element is not active.
  526. */
  527. function _next($mask = 0)
  528. {
  529. if (is_null($this->_currkey) && is_null($this->_currparent)) {
  530. return false;
  531. }
  532. $curr = $this->current();
  533. $old_showunsub = $this->_showunsub;
  534. if ($mask & IMPTREE_NEXT_SHOWSUB) {
  535. $this->_showunsub = false;
  536. }
  537. if ($this->_activeElt($curr) &&
  538. (($mask & IMPTREE_NEXT_SHOWCLOSED) || $this->isOpen($curr)) &&
  539. ($this->_currparent != $curr['v'])) {
  540. /* If the current element is open, and children exist, move into
  541. * it. */
  542. $this->_currstack[] = array('k' => $this->_currkey, 'p' => $this->_currparent);
  543. $this->_currkey = 0;
  544. $this->_currparent = $curr['v'];
  545. $this->_sortLevel($curr['v']);
  546. $curr = $this->current();
  547. if ($GLOBALS['prefs']->getValue('tree_view') &&
  548. $this->isNamespace($curr) &&
  549. !$this->_isNonIMAPElt($curr) &&
  550. ($this->_tree[$curr['p']] && IMPTREE_ELT_NOSHOW)) {
  551. $this->next($mask);
  552. }
  553. } else {
  554. /* Else, increment within the current subfolder. */
  555. $this->_currkey++;
  556. }
  557. $curr = $this->current();
  558. if (!$curr) {
  559. if (empty($this->_currstack)) {
  560. $this->_currkey = $this->_currparent = null;
  561. $this->_showunsub = $old_showunsub;
  562. return false;
  563. } else {
  564. do {
  565. $old = array_pop($this->_currstack);
  566. $this->_currkey = $old['k'] + 1;
  567. $this->_currparent = $old['p'];
  568. } while ((($curr = $this->current()) == false) &&
  569. count($this->_currstack));
  570. }
  571. }
  572. $res = $this->_activeElt($curr);
  573. $this->_showunsub = $old_showunsub;
  574. return ($res) ? $curr : null;
  575. }
  576. /**
  577. * Set internal pointer to the head of the tree.
  578. * This MUST be called before you can traverse the tree with next().
  579. *
  580. * @return mixed Returns the element at the head of the tree or false
  581. * if the element doesn't exist.
  582. */
  583. function reset()
  584. {
  585. $this->_currkey = 0;
  586. $this->_currparent = IMPTREE_BASE_ELT;
  587. $this->_currstack = array();
  588. $this->_sortLevel($this->_currparent);
  589. return $this->current();
  590. }
  591. /**
  592. * Return the current tree element.
  593. *
  594. * @return array The current tree element or false if there is no
  595. * element.
  596. */
  597. function current()
  598. {
  599. return (!isset($this->_parent[$this->_currparent][$this->_currkey]))
  600. ? false
  601. : $this->_tree[$this->_parent[$this->_currparent][$this->_currkey]];
  602. }
  603. /**
  604. * Determines if there are more elements in the current tree level.
  605. *
  606. * @return boolean True if there are more elements, false if this is the
  607. * last element.
  608. */
  609. function peek()
  610. {
  611. for ($i = ($this->_currkey + 1); ; ++$i) {
  612. if (!isset($this->_parent[$this->_currparent][$i])) {
  613. return false;
  614. }
  615. if ($this->_activeElt($this->_tree[$this->_parent[$this->_currparent][$i]])) {
  616. return true;
  617. }
  618. }
  619. }
  620. /**
  621. * Returns the requested element.
  622. *
  623. * @param string $name The name of the tree element.
  624. *
  625. * @return array Returns the requested element or false if not found.
  626. */
  627. function get($name)
  628. {
  629. $name = $this->_convertName($name);
  630. return (isset($this->_tree[$name])) ? $this->_tree[$name] : false;
  631. }
  632. /**
  633. * Insert a folder/mailbox into the tree.
  634. *
  635. * @param mixed $id The name of the folder (or a list of folder names)
  636. * to add.
  637. */
  638. function insert($id)
  639. {
  640. if (is_array($id)) {
  641. /* We want to add from the BASE of the tree up for efficiency
  642. * sake. */
  643. $this->_sortList($id);
  644. } else {
  645. $id = array($id);
  646. }
  647. foreach ($id as $val) {
  648. if (isset($this->_tree[$val]) &&
  649. !$this->isContainer($this->_tree[$val])) {
  650. continue;
  651. }
  652. $ns_info = $this->_getNamespace($val);
  653. if (is_null($ns_info)) {
  654. if (strpos($val, IMPTREE_VFOLDER_KEY . $this->_delimiter) === 0) {
  655. $elt = $this->_makeElt(IMPTREE_VFOLDER_KEY, IMPTREE_ELT_VFOLDER | IMPTREE_ELT_NOSELECT | IMPTREE_ELT_NONIMAP);
  656. $elt['l'] = IMPTREE_VFOLDER_LABEL;
  657. $this->_insertElt($elt);
  658. }
  659. $elt = $this->_makeElt($val, IMPTREE_ELT_VFOLDER | IMPTREE_ELT_IS_SUBSCRIBED);
  660. $elt['l'] = $elt['v'] = String::substr($val, String::length(IMPTREE_VFOLDER_KEY) + String::length($this->_delimiter));
  661. $this->_insertElt($elt);
  662. } else {
  663. /* Break apart the name via the delimiter and go step by
  664. * step through the name to make sure all subfolders exist
  665. * in the tree. */
  666. $parts = explode($ns_info['delimiter'], $val);
  667. $parts[0] = $this->_convertName($parts[0]);
  668. $parts_count = count($parts);
  669. for ($i = 0; $i < $parts_count; ++$i) {
  670. $part = implode($ns_info['delimiter'], array_slice($parts, 0, $i + 1));
  671. if (isset($this->_tree[$part])) {
  672. if (($part == $val) &&
  673. $this->isContainer($this->_tree[$part])) {
  674. $this->_setContainer($this->_tree[$part], false);
  675. }
  676. } else {
  677. $this->_insertElt(($part == $val) ? $this->_makeElt($part) : $this->_makeElt($part, IMPTREE_ELT_NOSELECT));
  678. }
  679. }
  680. }
  681. }
  682. }
  683. /**
  684. * Insert an element into the tree.
  685. *
  686. * @access private
  687. *
  688. * @param array $elt The element to insert. The key in the tree is the
  689. * 'v' (value) element of the element.
  690. */
  691. function _insertElt($elt)
  692. {
  693. if (!strlen($elt['l']) || isset($this->_tree[$elt['v']])) {
  694. return;
  695. }
  696. // UW fix - it may return both 'foo' and 'foo/' as folder names.
  697. // Only add one of these (without the namespace character) to
  698. // the tree. See Ticket #5764.
  699. $ns_info = $this->_getNamespace($elt['v']);
  700. if (isset($this->_tree[rtrim($elt['v'], is_null($ns_info) ? $this->_delimiter : $ns_info['delimiter'])])) {
  701. return;
  702. }
  703. $this->_changed = true;
  704. /* Set the parent array to the value in $elt['p']. */
  705. if (empty($this->_parent[$elt['p']])) {
  706. $this->_parent[$elt['p']] = array();
  707. // This is a case where it is possible that the parent element has
  708. // changed (it now has children) but we can't catch it via the
  709. // bitflag (since hasChildren() is dynamically determined).
  710. if ($this->_trackdiff && !is_null($this->_eltdiff)) {
  711. $this->_eltdiff['c'][$elt['p']] = 1;
  712. }
  713. }
  714. $this->_parent[$elt['p']][] = $elt['v'];
  715. $this->_tree[$elt['v']] = $elt;
  716. if ($this->_trackdiff && !is_null($this->_eltdiff)) {
  717. $this->_eltdiff['a'][$elt['v']] = 1;
  718. }
  719. /* Make sure we are sorted correctly. */
  720. if (count($this->_parent[$elt['p']]) > 1) {
  721. $this->_setNeedSort($this->_tree[$elt['p']], true);
  722. }
  723. }
  724. /**
  725. * Delete an element from the tree.
  726. *
  727. * @param mixed $id The element name or an array of element names.
  728. *
  729. * @return boolean Return true on success, false on error.
  730. */
  731. function delete($id)
  732. {
  733. if (is_array($id)) {
  734. /* We want to delete from the TOP of the tree down to ensure that
  735. * parents have an accurate view of what children are left. */
  736. $this->_sortList($id);
  737. $id = array_reverse($id);
  738. $success = true;
  739. foreach ($id as $val) {
  740. $currsuccess = $this->delete($val);
  741. if (!$currsuccess) {
  742. $success = false;
  743. }
  744. }
  745. return $success;
  746. } else {
  747. $id = $this->_convertName($id, true);
  748. }
  749. $vfolder_base = ($id == IMPTREE_VFOLDER_LABEL);
  750. $search_id = $GLOBALS['imp_search']->createSearchID($id);
  751. if ($vfolder_base ||
  752. (isset($this->_tree[$search_id]) &&
  753. $this->isVFolder($this->_tree[$search_id]))) {
  754. if (!$vfolder_base) {
  755. $id = $search_id;
  756. }
  757. $parent = $this->_tree[$id]['p'];
  758. unset($this->_tree[$id]);
  759. /* Delete the entry from the parent tree. */
  760. $key = array_search($id, $this->_parent[$parent]);
  761. unset($this->_parent[$parent][$key]);
  762. /* Rebuild the parent tree. */
  763. if (!$vfolder_base && empty($this->_parent[$parent])) {
  764. $this->delete($parent);
  765. } else {
  766. $this->_parent[$parent] = array_values($this->_parent[$parent]);
  767. }
  768. $this->_changed = true;
  769. return true;
  770. }
  771. $ns_info = $this->_getNamespace($id);
  772. if (($id == 'INBOX') ||
  773. !isset($this->_tree[$id]) ||
  774. ($id == $ns_info['name'])) {
  775. return false;
  776. }
  777. $this->_changed = true;
  778. $elt = &$this->_tree[$id];
  779. /* Delete the entry from the folder list cache(s). */
  780. foreach (array('_subscribed', '_fulllist') as $var) {
  781. if (!is_null($this->$var)) {
  782. $this->$var = array_values(array_diff($this->$var, array($id)));
  783. }
  784. }
  785. /* Do not delete from tree if there are child elements - instead,
  786. * convert to a container element. */
  787. if ($this->hasChildren($elt)) {
  788. $this->_setContainer($elt, true);
  789. return true;
  790. }
  791. $parent = $elt['p'];
  792. /* Delete the tree entry. */
  793. unset($this->_tree[$id]);
  794. /* Delete the entry from the parent tree. */
  795. $key = array_search($id, $this->_parent[$parent]);
  796. unset($this->_parent[$parent][$key]);
  797. if (!is_null($this->_eltdiff)) {
  798. $this->_eltdiff['d'][$id] = 1;
  799. }
  800. if (empty($this->_parent[$parent])) {
  801. /* This folder is now completely empty (no children). If the
  802. * folder is a container only, we should delete the folder from
  803. * the tree. */
  804. unset($this->_parent[$parent]);
  805. if (isset($this->_tree[$parent])) {
  806. if ($this->isContainer($this->_tree[$parent]) &&
  807. !$this->isNamespace($this->_tree[$parent])) {
  808. $this->delete($parent);
  809. } else {
  810. $this->_modifyExpandedList($parent, 'remove');
  811. $this->_setOpen($this->_tree[$parent], false);
  812. /* This is a case where it is possible that the parent
  813. * element has changed (it no longer has children) but
  814. * we can't catch it via the bitflag (since hasChildren()
  815. * is dynamically determined). */
  816. if (!is_null($this->_eltdiff)) {
  817. $this->_eltdiff['c'][$parent] = 1;
  818. }
  819. }
  820. }
  821. } else {
  822. /* Rebuild the parent tree. */
  823. $this->_parent[$parent] = array_values($this->_parent[$parent]);
  824. }
  825. /* Remove the mailbox from the expanded folders list. */
  826. $this->_modifyExpandedList($id, 'remove');
  827. /* Remove the mailbox from the nav_poll list. */
  828. $this->removePollList($id);
  829. return true;
  830. }
  831. /**
  832. * Subscribe an element to the tree.
  833. *
  834. * @param mixed $id The element name or an array of element names.
  835. */
  836. function subscribe($id)
  837. {
  838. if (!is_array($id)) {
  839. $id = array($id);
  840. }
  841. foreach ($id as $val) {
  842. $val = $this->_convertName($val);
  843. if (isset($this->_tree[$val])) {
  844. $this->_changed = true;
  845. $this->_setSubscribed($this->_tree[$val], true);
  846. $this->_setContainer($this->_tree[$val], false);
  847. }
  848. }
  849. }
  850. /**
  851. * Unsubscribe an element from the tree.
  852. *
  853. * @param mixed $id The element name or an array of element names.
  854. */
  855. function unsubscribe($id)
  856. {
  857. if (!is_array($id)) {
  858. $id = array($id);
  859. } else {
  860. /* We want to delete from the TOP of the tree down to ensure that
  861. * parents have an accurate view of what children are left. */
  862. $this->_sortList($id);
  863. $id = array_reverse($id);
  864. }
  865. foreach ($id as $val) {
  866. $val = $this->_convertName($val);
  867. /* INBOX can never be unsubscribed to. */
  868. if (isset($this->_tree[$val]) && ($val != 'INBOX')) {
  869. $this->_changed = $this->_unsubview = true;
  870. $elt = &$this->_tree[$val];
  871. /* Do not delete from tree if there are child elements -
  872. * instead, convert to a container element. */
  873. if (!$this->_showunsub && $this->hasChildren($elt)) {
  874. $this->_setContainer($elt, true);
  875. }
  876. /* Set as unsubscribed, add to unsubscribed list, and remove
  877. * from subscribed list. */
  878. $this->_setSubscribed($elt, false);
  879. }
  880. }
  881. }
  882. /**
  883. * Set an attribute for an element.
  884. *
  885. * @access private
  886. *
  887. * @param array &$elt The tree element.
  888. * @param integer $const The constant to set/remove from the bitmask.
  889. * @param boolean $bool Should the attribute be set?
  890. */
  891. function _setAttribute(&$elt, $const, $bool)
  892. {
  893. if ($bool) {
  894. $elt['a'] |= $const;
  895. } else {
  896. $elt['a'] &= ~$const;
  897. }
  898. }
  899. /**
  900. * Does the element have any active children?
  901. *
  902. * @param array $elt A tree element.
  903. *
  904. * @return boolean True if the element has active children.
  905. */
  906. function hasChildren($elt)
  907. {
  908. if (isset($this->_parent[$elt['v']])) {
  909. if ($this->_showunsub) {
  910. return true;
  911. }
  912. foreach ($this->_parent[$elt['v']] as $val) {
  913. $child = &$this->_tree[$val];
  914. if ($this->isSubscribed($child) ||
  915. $this->hasChildren($this->_tree[$val])) {
  916. return true;
  917. }
  918. }
  919. }
  920. return false;
  921. }
  922. /**
  923. * Is the tree element open?
  924. *
  925. * @param array $elt A tree element.
  926. *
  927. * @return integer True if the element is open.
  928. */
  929. function isOpen($elt)
  930. {
  931. return (($elt['a'] & IMPTREE_ELT_IS_OPEN) && $this->hasChildren($elt));
  932. }
  933. /**
  934. * Set the open attribute for an element.
  935. *
  936. * @access private
  937. *
  938. * @param array &$elt A tree element.
  939. * @param boolean $bool The setting.
  940. */
  941. function _setOpen(&$elt, $bool)
  942. {
  943. $this->_setAttribute($elt, IMPTREE_ELT_IS_OPEN, $bool);
  944. $this->_modifyExpandedList($elt['v'], $bool ? 'add' : 'remove');
  945. }
  946. /**
  947. * Is this element a container only, not a mailbox (meaning you can
  948. * not open it)?
  949. *
  950. * @param array $elt A tree element.
  951. *
  952. * @return integer True if the element is a container.
  953. */
  954. function isContainer($elt)
  955. {
  956. return (($elt['a'] & IMPTREE_ELT_NOSELECT) ||
  957. (!$this->_showunsub &&
  958. !$this->isSubscribed($elt) &&
  959. $this->hasChildren($elt)));
  960. }
  961. /**
  962. * Set the element as a container?
  963. *
  964. * @access private
  965. *
  966. * @param array &$elt A tree element.
  967. * @param boolean $bool Is the element a container?
  968. */
  969. function _setContainer(&$elt, $bool)
  970. {
  971. $this->_setAttribute($elt, IMPTREE_ELT_NOSELECT, $bool);
  972. }
  973. /**
  974. * Is the user subscribed to this element?
  975. *
  976. * @param array $elt A tree element.
  977. *
  978. * @return integer True if the user is subscribed to the element.
  979. */
  980. function isSubscribed($elt)
  981. {
  982. return $elt['a'] & IMPTREE_ELT_IS_SUBSCRIBED;
  983. }
  984. /**
  985. * Set the subscription status for an element.
  986. *
  987. * @access private
  988. *
  989. * @param array &$elt A tree element.
  990. * @param boolean $bool Is the element subscribed to?
  991. */
  992. function _setSubscribed(&$elt, $bool)
  993. {
  994. $this->_setAttribute($elt, IMPTREE_ELT_IS_SUBSCRIBED, $bool);
  995. if (!is_null($this->_subscribed)) {
  996. if ($bool) {
  997. $this->_subscribed[$elt['v']] = 1;
  998. } else {
  999. unset($this->_subscribed[$elt['v']]);
  1000. }
  1001. }
  1002. }
  1003. /**
  1004. * Is the element a namespace container?
  1005. *
  1006. * @param array $elt A tree element.
  1007. *
  1008. * @return integer True if the element is a namespace container.
  1009. */
  1010. function isNamespace($elt)
  1011. {
  1012. return $elt['a'] & IMPTREE_ELT_NAMESPACE;
  1013. }
  1014. /**
  1015. * Is the element a non-IMAP element?
  1016. *
  1017. * @param array $elt A tree element.
  1018. *
  1019. * @return integer True if the element is a non-IMAP element.
  1020. */
  1021. function _isNonIMAPElt($elt)
  1022. {
  1023. return $elt['a'] & IMPTREE_ELT_NONIMAP;
  1024. }
  1025. /**
  1026. * Initialize the expanded folder list.
  1027. *
  1028. * @access private
  1029. */
  1030. function _initExpandedList()
  1031. {
  1032. if (is_null($this->_expanded)) {
  1033. $serialized = $GLOBALS['prefs']->getValue('expanded_folders');
  1034. $this->_expanded = ($serialized) ? unserialize($serialized) : array();
  1035. }
  1036. }
  1037. /**
  1038. * Add/remove an element to the expanded list.
  1039. *
  1040. * @access private
  1041. *
  1042. * @param string $id The element name to add/remove.
  1043. * @param string $action Either 'add' or 'remove';
  1044. */
  1045. function _modifyExpandedList($id, $action)
  1046. {
  1047. $this->_initExpandedList();
  1048. if ($action == 'add') {
  1049. $change = empty($this->_expanded[$id]);
  1050. $this->_expanded[$id] = true;
  1051. } else {
  1052. $change = !empty($this->_expanded[$id]);
  1053. unset($this->_expanded[$id]);
  1054. }
  1055. if ($change) {
  1056. $GLOBALS['prefs']->setValue('expanded_folders', serialize($this->_expanded));
  1057. }
  1058. }
  1059. /**
  1060. * Initializes and returns the list of mailboxes to poll.
  1061. *
  1062. * @param boolean $prune Prune non-existant folders from list?
  1063. * @param boolean $sort Sort the directory list?
  1064. *
  1065. * @return array The list of mailboxes to poll.
  1066. */
  1067. function getPollList($prune = false, $sort = false)
  1068. {
  1069. $this->_initPollList();
  1070. $plist = ($prune) ? array_values(array_intersect(array_keys($this->_poll), $this->folderList())) : $this->_poll;
  1071. if ($sort) {
  1072. require_once IMP_BASE . '/lib/IMAP/Sort.php';
  1073. $ns_new = $this->_getNamespace(null);
  1074. $imap_sort = new IMP_IMAP_Sort($ns_new['delimiter']);
  1075. $imap_sort->sortMailboxes($plist);
  1076. }
  1077. return $plist;
  1078. }
  1079. /**
  1080. * Init the poll list. Called once per session.
  1081. *
  1082. * @access private
  1083. */
  1084. function _initPollList()
  1085. {
  1086. if (is_null($this->_poll)) {
  1087. /* We ALWAYS poll the INBOX. */
  1088. $this->_poll = array('INBOX' => 1);
  1089. /* Add the list of polled mailboxes from the prefs. */
  1090. if ($GLOBALS['prefs']->getValue('nav_poll_all')) {
  1091. $navPollList = array_flip($this->_getList(true));
  1092. } else {
  1093. $old_error = error_reporting(0);
  1094. $navPollList = unserialize($GLOBALS['prefs']->getValue('nav_poll'));
  1095. error_reporting($old_error);
  1096. }
  1097. if ($navPollList) {
  1098. $this->_poll += $navPollList;
  1099. }
  1100. }
  1101. }
  1102. /**
  1103. * Add element to the poll list.
  1104. *
  1105. * @param mixed $id The element name or a list of element names to add.
  1106. */
  1107. function addPollList($id)
  1108. {
  1109. if (!is_array($id)) {
  1110. $id = array($id);
  1111. }
  1112. if (!empty($id) && !$GLOBALS['prefs']->isLocked('nav_poll')) {
  1113. require_once IMP_BASE . '/lib/Folder.php';
  1114. $imp_folder = &IMP_Folder::singleton();
  1115. $this->getPollList();
  1116. foreach ($id as $val) {
  1117. if (!$this->isSubscribed($this->_tree[$val])) {
  1118. $imp_folder->subscribe(array($val));
  1119. }
  1120. $this->_poll[$val] = true;
  1121. $this->_setPolled($this->_tree[$val], true);
  1122. }
  1123. $GLOBALS['prefs']->setValue('nav_poll', serialize($this->_poll));
  1124. $this->_changed = true;
  1125. }
  1126. }
  1127. /**
  1128. * Remove element from the poll list.
  1129. *
  1130. * @param string $id The folder/mailbox or a list of folders/mailboxes
  1131. * to remove.
  1132. */
  1133. function removePollList($id)
  1134. {
  1135. if (!is_array($id)) {
  1136. $id = array($id);
  1137. }
  1138. $removed = false;
  1139. if (!$GLOBALS['prefs']->isLocked('nav_poll')) {
  1140. $this->getPollList();
  1141. foreach ($id as $val) {
  1142. if ($val != 'INBOX') {
  1143. unset($this->_poll[$val]);
  1144. if (isset($this->_tree[$val])) {
  1145. $this->_setPolled($this->_tree[$val], false);
  1146. }
  1147. $removed = true;
  1148. }
  1149. }
  1150. if ($removed) {
  1151. $GLOBALS['prefs']->setValue('nav_poll', serialize($this->_poll));
  1152. $this->_changed = true;
  1153. }
  1154. }
  1155. }
  1156. /**
  1157. * Does the user want to poll this mailbox for new/unseen messages?
  1158. *
  1159. * @param array $elt A tree element.
  1160. *
  1161. * @return integer True if the user wants to poll the element.
  1162. */
  1163. function isPolled($elt)
  1164. {
  1165. return ($GLOBALS['prefs']->getValue('nav_poll_all')) ? true : ($elt['a'] & IMPTREE_ELT_IS_POLLED);
  1166. }
  1167. /**
  1168. * Set the polled attribute for an element.
  1169. *
  1170. * @access private
  1171. *
  1172. * @param array &$elt A tree element.
  1173. * @param boolean $bool The setting.
  1174. */
  1175. function _setPolled(&$elt, $bool)
  1176. {
  1177. $this->_setAttribute($elt, IMPTREE_ELT_IS_POLLED, $bool);
  1178. }
  1179. /**
  1180. * Is the element invisible?
  1181. *
  1182. * @since IMP 4.3.4
  1183. *
  1184. * @param array $elt A tree element.
  1185. *
  1186. * @return integer True if the element is marked as invisible.
  1187. */
  1188. function isInvisible($elt)
  1189. {
  1190. return $elt['a'] & IMPTREE_ELT_INVISIBLE;
  1191. }
  1192. /**
  1193. * Set the invisible attribute for an element.
  1194. *
  1195. * @access private
  1196. * @since IMP 4.3.4
  1197. *
  1198. * @param array &$elt A tree element.
  1199. * @param boolean $bool The setting.
  1200. */
  1201. function _setInvisible(&$elt, $bool)
  1202. {
  1203. $this->_setAttribute($elt, IMPTREE_ELT_INVISIBLE, $bool);
  1204. }
  1205. /**
  1206. * Flag the element as needing its children to be sorted.
  1207. *
  1208. * @access private
  1209. *
  1210. * @param array &$elt A tree element.
  1211. * @param boolean $bool The setting.
  1212. */
  1213. function _setNeedSort(&$elt, $bool)
  1214. {
  1215. $this->_setAttribute($elt, IMPTREE_ELT_NEED_SORT, $bool);
  1216. }
  1217. /**
  1218. * Does this element's children need sorting?
  1219. *
  1220. * @param array $elt A tree element.
  1221. *
  1222. * @return integer True if the children need to be sorted.
  1223. */
  1224. function _needSort($elt)
  1225. {
  1226. return (($elt['a'] & IMPTREE_ELT_NEED_SORT) && (count($this->_parent[$elt['v']]) > 1));
  1227. }
  1228. /**
  1229. * Initialize the list of subscribed mailboxes.
  1230. *
  1231. * @access private
  1232. */
  1233. function _initSubscribed()
  1234. {
  1235. if (is_null($this->_subscribed)) {
  1236. $this->_getList(false);
  1237. }
  1238. }
  1239. /**
  1240. * Should we expand all elements?
  1241. */
  1242. function expandAll()
  1243. {
  1244. foreach ($this->_parent[IMPTREE_BASE_ELT] as $val) {
  1245. $this->expand($val, true);
  1246. }
  1247. }
  1248. /**
  1249. * Should we collapse all elements?
  1250. */
  1251. function collapseAll()
  1252. {
  1253. foreach ($this->_tree as $key => $val) {
  1254. if ($key !== IMPTREE_BASE_ELT) {
  1255. $this->collapse($val['v']);
  1256. }
  1257. }
  1258. }
  1259. /**
  1260. * Switch subscribed/unsubscribed viewing.
  1261. *
  1262. * @param boolean $unsub Show unsubscribed elements?
  1263. */
  1264. function showUnsubscribed($unsub)
  1265. {
  1266. if ((bool)$unsub === $this->_showunsub) {
  1267. return;
  1268. }
  1269. $this->_showunsub = $unsub;
  1270. $this->_changed = true;
  1271. /* If we are switching from unsubscribed to subscribed, no need
  1272. * to do anything (we just ignore unsubscribed stuff). */
  1273. if ($unsub === false) {
  1274. return;
  1275. }
  1276. /* If we are switching from subscribed to unsubscribed, we need
  1277. * to add all unsubscribed elements that live in currently
  1278. * discovered items. */
  1279. $this->_unsubview = true;
  1280. $this->_trackdiff = false;
  1281. $this->insert($this->_getList(true));
  1282. $this->_trackdiff = true;
  1283. }
  1284. /**
  1285. * Get information about new/unseen/total messages for the given element.
  1286. *
  1287. * @param string $name The element name.
  1288. *
  1289. * @return array Array with the following fields:
  1290. * <pre>
  1291. * 'messages' -- Number of total messages.
  1292. * 'newmsg' -- Number of new messages.
  1293. * 'unseen' -- Number of unseen messages.
  1294. * </pre>
  1295. */
  1296. function getElementInfo($name)
  1297. {
  1298. $status = array();
  1299. require_once IMP_BASE . '/lib/IMAP/Cache.php';
  1300. $imap_cache = &IMP_IMAP_Cache::singleton();
  1301. $sts = $imap_cache->getStatus(null, $name);
  1302. if (!empty($sts)) {
  1303. $status = array(
  1304. 'messages' => $sts->messages,
  1305. 'unseen' => (isset($sts->unseen) ? $sts->unseen : 0),
  1306. 'newmsg' => (isset($sts->recent) ? $sts->recent : 0)
  1307. );
  1308. }
  1309. return $status;
  1310. }
  1311. /**
  1312. * Sorts a list of mailboxes.
  1313. *
  1314. * @access private
  1315. *
  1316. * @param array &$mbox The list of mailboxes to sort.
  1317. * @param boolean $base Are we sorting a list of mailboxes in the base
  1318. * of the tree.
  1319. */
  1320. function _sortList(&$mbox, $base = false)
  1321. {
  1322. if (is_null($this->_imap_sort)) {
  1323. require_once 'Horde/IMAP/Sort.php';
  1324. $this->_imap_sort = &new IMAP_Sort($this->_delimiter);
  1325. }
  1326. if ($base) {
  1327. $basesort = array();
  1328. foreach ($mbox as $val) {
  1329. $basesort[$val] = ($val == 'INBOX') ? 'INBOX' : $this->_tree[$val]['l'];
  1330. }
  1331. $this->_imap_sort->sortMailboxes($basesort, true, true, true);
  1332. $mbox = array_keys($basesort);
  1333. } else {
  1334. $this->_imap_sort->sortMailboxes($mbox, true);
  1335. }
  1336. if ($base) {
  1337. for ($i = 0, $count = count($mbox); $i < $count; ++$i) {
  1338. if ($this->_isNonIMAPElt($this->_tree[$mbox[$i]])) {
  1339. /* Already sorted by name - simply move to the end of
  1340. * the array. */
  1341. $mbox[] = $mbox[$i];
  1342. unset($mbox[$i]);
  1343. }
  1344. }
  1345. $mbox = array_values($mbox);
  1346. }
  1347. }
  1348. /**
  1349. * Is the given element an "active" element (i.e. an element that should
  1350. * be worked with given the current viewing parameters).
  1351. *
  1352. * @access private
  1353. *
  1354. * @param array $elt A tree element.
  1355. *
  1356. * @return boolean True if it is an active element.
  1357. */
  1358. function _activeElt($elt)
  1359. {
  1360. return (!$this->isInvisible($elt) &&
  1361. ($this->_showunsub ||
  1362. ($this->isSubscribed($elt) && !$this->isContainer($elt)) ||
  1363. $this->hasChildren($elt)));
  1364. }
  1365. /**
  1366. * Convert a mailbox name to the correct, internal name (i.e. make sure
  1367. * INBOX is always capitalized for IMAP servers).
  1368. *
  1369. * @access private
  1370. *
  1371. * @param string $name The mailbox name.
  1372. *
  1373. * @return string The converted name.
  1374. */
  1375. function _convertName($name)
  1376. {
  1377. return (strcasecmp($name, 'INBOX') == 0) ? 'INBOX' : $name;
  1378. }
  1379. /**
  1380. * Get namespace info for a full folder path.
  1381. *
  1382. * @access private
  1383. *
  1384. * @param string $mailbox The folder path.
  1385. *
  1386. * @return mixed The namespace info for the folder path or null if the
  1387. * path doesn't exist.
  1388. */
  1389. function _getNamespace($mailbox)
  1390. {
  1391. if (!in_array($mailbox, array(IMPTREE_OTHER_KEY, IMPTREE_SHARED_KEY, IMPTREE_VFOLDER_KEY)) &&
  1392. (strpos($mailbox, IMPTREE_VFOLDER_KEY . $this->_delimiter) !== 0)) {
  1393. return IMP::getNamespace($mailbox);
  1394. }
  1395. return null;
  1396. }
  1397. /**
  1398. * Set the start point for determining element differences via eltDiff().
  1399. *
  1400. * @since IMP 4.1
  1401. */
  1402. function eltDiffStart()
  1403. {
  1404. $this->_eltdiff = array('a' => array(), 'c' => array(), 'd' => array());
  1405. }
  1406. /**
  1407. * Return the list of elements that have changed since nodeDiffStart()
  1408. * was last called.
  1409. *
  1410. * @since IMP 4.1
  1411. *
  1412. * @return array Returns false if no changes have occurred, or an array
  1413. * with the following keys:
  1414. * <pre>
  1415. * 'a' => A list of elements that have been added.
  1416. * 'c' => A list of elements that have been changed.
  1417. * 'd' => A list of elements that have been deleted.
  1418. * </pre>
  1419. */
  1420. function eltDiff()
  1421. {
  1422. if (is_null($this->_eltdiff) || !$this->_changed) {
  1423. return false;
  1424. }
  1425. $ret = array(
  1426. 'a' => array_keys($this->_eltdiff['a']),
  1427. 'c' => array_keys($this->_eltdiff['c']),
  1428. 'd' => array_keys($this->_eltdiff['d'])
  1429. );
  1430. $this->_eltdiff = null;
  1431. return $ret;
  1432. }
  1433. /**
  1434. * Inserts virtual folders into the tree.
  1435. *
  1436. * @param array $id_list An array with the folder IDs to add as the key
  1437. * and the labels as the value.
  1438. */
  1439. function insertVFolders($id_list)
  1440. {
  1441. if (empty($id_list) ||
  1442. empty($GLOBALS['conf']['user']['allow_folders'])) {
  1443. return;
  1444. }
  1445. $adds = $id = array();
  1446. foreach ($id_list as $key => $val) {
  1447. $id[$GLOBALS['imp_search']->createSearchID($key)] = $val;
  1448. }
  1449. foreach (array_keys($id) as $key) {
  1450. $id_key = IMPTREE_VFOLDER_KEY . $this->_delimiter . $key;
  1451. if (!isset($this->_tree[$id_key])) {
  1452. $adds[] = $id_key;
  1453. }
  1454. }
  1455. if (empty($adds)) {
  1456. return;
  1457. }
  1458. $this->insert($adds);
  1459. foreach ($id as $key => $val) {
  1460. $this->_tree[$key]['l'] = $val;
  1461. }
  1462. /* Sort the Virtual Folder list in the object, if necessary. */
  1463. if ($this->_needSort($this->_tree[IMPTREE_VFOLDER_KEY])) {
  1464. $vsort = array();
  1465. foreach ($this->_parent[IMPTREE_VFOLDER_KEY] as $val) {
  1466. $vsort[$val] = $this->_tree[$val]['l'];
  1467. }
  1468. natcasesort($vsort);
  1469. $this->_parent[IMPTREE_VFOLDER_KEY] = array_keys($vsort);
  1470. $this->_setNeedSort($this->_tree[IMPTREE_VFOLDER_KEY], false);
  1471. $this->_changed = true;
  1472. }
  1473. }
  1474. /**
  1475. * Builds a list of folders, suitable to render a folder tree.
  1476. *
  1477. * @param integer $mask The mask to pass to next().
  1478. * @param boolean $open If using the base folder icons, display a
  1479. * different icon whether the folder is opened or
  1480. * closed.
  1481. *
  1482. * @return array An array with three elements: the folder list, the total
  1483. * number of new messages, and a list with folder names
  1484. * suitable for user interaction.
  1485. * The folder list array contains the following added
  1486. * entries on top of the entries provided by element():
  1487. * <pre>
  1488. * 'display' - The mailbox name run through IMP::displayFolder().
  1489. * 'peek' - See peek().
  1490. * </pre>
  1491. */
  1492. function build($mask = 0, $open = true)
  1493. {
  1494. $displayNames = $newmsgs = $rows = array();
  1495. $this->_forceopen = $open;
  1496. /* Start iterating through the list of mailboxes, displaying them. */
  1497. $mailbox = $this->reset();
  1498. do {
  1499. $row = $this->element($mailbox['v']);
  1500. $row['display'] = ($this->_isNonIMAPElt($mailbox)) ? $mailbox['l'] : IMP::displayFolder($mailbox['v']);
  1501. $row['peek'] = $this->peek();
  1502. if (!empty($row['newmsg'])) {
  1503. $newmsgs[$row['value']] = $row['newmsg'];
  1504. }
  1505. /* Hide folder prefixes from the user. */
  1506. if ($row['level'] >= 0) {
  1507. $rows[] = $row;
  1508. $displayNames[] = addslashes($row['display']);
  1509. }
  1510. } while (($mailbox = $this->next($mask)));
  1511. $this->_forceopen = false;
  1512. return array($rows, $newmsgs, $displayNames);
  1513. }
  1514. /**
  1515. * Get any custom icon configured for the given element.
  1516. *
  1517. * @params array $elt A tree element.
  1518. *
  1519. * @return array An array with the 'icon', 'icondir', and 'alt'
  1520. * information for the element, or false if no icon
  1521. * available.
  1522. */
  1523. function getCustomIcon($elt)
  1524. {
  1525. static $mbox_icons;
  1526. if (isset($mbox_icons) && !$mbox_icons) {
  1527. return false;
  1528. }
  1529. /* Call the mailbox icon hook, if requested. */
  1530. if (empty($GLOBALS['conf']['hooks']['mbox_icon'])) {
  1531. $mbox_icons = false;
  1532. return false;
  1533. }
  1534. if (!isset($mbox_icons)) {
  1535. $mbox_icons = Horde::callHook('_imp_hook_mbox_icons', array(),
  1536. 'imp', false);
  1537. if (!$mbox_icons) {
  1538. return false;
  1539. }
  1540. }
  1541. if (isset($mbox_icons[$elt['v']])) {
  1542. return array('icon' => $mbox_icons[$elt['v']]['icon'], 'icondir' => $mbox_icons[$elt['v']]['icondir'], 'alt' => $mbox_icons[$elt['v']]['alt']);
  1543. }
  1544. return false;
  1545. }
  1546. /**
  1547. * Returns whether this element is a virtual folder.
  1548. *
  1549. * @param array $elt A tree element.
  1550. *
  1551. * @return integer True if the element is a virtual folder.
  1552. */
  1553. function isVFolder($elt)
  1554. {
  1555. return $elt['a'] & IMPTREE_ELT_VFOLDER;
  1556. }
  1557. /**
  1558. * Rename a current folder.
  1559. *
  1560. * @since IMP 4.1
  1561. *
  1562. * @param array $old The old folder names.
  1563. * @param array $new The new folder names.
  1564. */
  1565. function rename($old, $new)
  1566. {
  1567. foreach ($old as $key => $val) {
  1568. $polled = (isset($this->_tree[$val])) ? $this->isPolled($this->_tree[$val]) : false;
  1569. if ($this->delete($val)) {
  1570. $this->insert($new[$key]);
  1571. if ($polled) {
  1572. $this->addPollList($new[$key]);
  1573. }
  1574. }
  1575. }
  1576. }
  1577. /**
  1578. * Returns a list of all IMAP mailboxes in the tree.
  1579. *
  1580. * @since IMP 4.1
  1581. *
  1582. * @param integer $mask A mask with the following elements:
  1583. * <pre>
  1584. * IMPTREE_FLIST_CONTAINER - Show container elements.
  1585. * IMPTREE_FLIST_UNSUB - Show unsubscribed elements.
  1586. * IMPTREE_FLIST_OB - Return full tree object.
  1587. * IMPTREE_FLIST_VFOLDER - Show Virtual Folders. (since IMP 4.2.1)
  1588. * </pre>
  1589. * @param string $base Return all mailboxes below this element. (since
  1590. * IMP 4.2.1)
  1591. *
  1592. * @return array An array of IMAP mailbox names.
  1593. */
  1594. function folderList($mask = 0, $base = null)
  1595. {
  1596. $baseindex = null;
  1597. $ret_array = array();
  1598. $diff_unsub = (($mask & IMPTREE_FLIST_UNSUB) != $this->_showunsub) ? $this->_showunsub : null;
  1599. $this->showUnsubscribed($mask & IMPTREE_FLIST_UNSUB);
  1600. $mailbox = $this->reset();
  1601. // Search to base element.
  1602. if (!is_null($base)) {
  1603. while ($mailbox && $mailbox['v'] != $base) {
  1604. $mailbox = $this->next(IMPTREE_NEXT_SHOWCLOSED);
  1605. }
  1606. if ($mailbox) {
  1607. $baseindex = count($this->_currstack);
  1608. $baseparent = $this->_currparent;
  1609. $basekey = $this->_currkey;
  1610. $mailbox = $this->next(IMPTREE_NEXT_SHOWCLOSED);
  1611. }
  1612. }
  1613. if ($mailbox) {
  1614. do {
  1615. if (!is_null($baseindex) &&
  1616. (!isset($this->_currstack[$baseindex]) ||
  1617. ($this->_currstack[$baseindex]['k'] != $basekey) ||
  1618. ($this->_currstack[$baseindex]['p'] != $baseparent))) {
  1619. break;
  1620. }
  1621. if ((($mask & IMPTREE_FLIST_CONTAINER) ||
  1622. !$this->isContainer($mailbox)) &&
  1623. (($mask & IMPTREE_FLIST_VFOLDER) ||
  1624. !$this->isVFolder($mailbox))) {
  1625. $ret_array[] = ($mask & IMPTREE_FLIST_OB) ? $mailbox : $mailbox['v'];
  1626. }
  1627. } while (($mailbox = $this->next(IMPTREE_NEXT_SHOWCLOSED)));
  1628. }
  1629. if (!is_null($diff_unsub)) {
  1630. $this->showUnsubscribed($diff_unsub);
  1631. }
  1632. return $ret_array;
  1633. }
  1634. /**
  1635. * Is the mailbox open in the sidebar?
  1636. *
  1637. * @since IMP 4.1.1
  1638. *
  1639. * @param array $mbox A mailbox name.
  1640. *
  1641. * @return integer True if the mailbox is open in the sidebar.
  1642. */
  1643. function isOpenSidebar($mbox)
  1644. {
  1645. switch ($GLOBALS['prefs']->getValue('nav_expanded_sidebar')) {
  1646. case IMPTREE_OPEN_USER:
  1647. $this->_initExpandedList();
  1648. return !empty($this->_expanded[$mbox]);
  1649. break;
  1650. case IMPTREE_OPEN_ALL:
  1651. return true;
  1652. break;
  1653. case IMPTREE_OPEN_NONE:
  1654. default:
  1655. return false;
  1656. break;
  1657. }
  1658. }
  1659. /**
  1660. * Init frequently used element() data.
  1661. *
  1662. * @access private
  1663. */
  1664. function _initElement()
  1665. {
  1666. global $prefs, $registry;
  1667. /* Initialize the user's identities. */
  1668. require_once 'Horde/Identity.php';
  1669. $identity = &Identity::singleton(array('imp', 'imp'));
  1670. return array(
  1671. 'trash' => IMP::folderPref($prefs->getValue('trash_folder'), true),
  1672. 'draft' => IMP::folderPref($prefs->getValue('drafts_folder'), true),
  1673. 'spam' => IMP::folderPref($prefs->getValue('spam_folder'), true),
  1674. 'sent' => $identity->getAllSentmailFolders(),
  1675. 'image_dir' => $registry->getImageDir(),
  1676. );
  1677. }
  1678. /**
  1679. * Return extended information on an element.
  1680. *
  1681. * @since IMP 4.2
  1682. *
  1683. * @param string $name The name of the tree element.
  1684. *
  1685. * @return array Returns the element with extended information, or false
  1686. * if not found. The information returned is as follows:
  1687. * <pre>
  1688. * 'alt' - The alt text for the icon.
  1689. * 'base_elt' - The return from get().
  1690. * 'children' - Does the element have children?
  1691. * 'container' - Is this a container element?
  1692. * 'editvfolder' - Can this virtual folder be edited?
  1693. * 'icon' - The name of the icon graphic to use.
  1694. * 'icondir' - The path of the icon directory.
  1695. * 'level' - The deepness level of this element.
  1696. * 'mbox_val' - A html-ized version of 'value'.
  1697. * 'msgs' - The number of total messages in the element (if polled).
  1698. * 'name' - A html-ized version of 'label'.
  1699. * 'newmsg' - The number of new messages in the element (if polled).
  1700. * 'parent' - The parent element value.
  1701. * 'polled' - Show polled information?
  1702. * 'special' - An integer mask indicating if this is a "special" element.
  1703. * 'specialvfolder' - Is this a "special" virtual folder?
  1704. * 'unseen' - The number of unseen messages in the element (if polled).
  1705. * 'user_icon' - Use a user defined icon?
  1706. * 'value' - The value of this element (i.e. element id).
  1707. * 'vfolder' - Is this a virtual folder?
  1708. * </pre>
  1709. */
  1710. function element($mailbox)
  1711. {
  1712. static $elt;
  1713. $mailbox = $this->get($mailbox);
  1714. if (!$mailbox) {
  1715. return false;
  1716. }
  1717. if (!isset($elt)) {
  1718. $elt = $this->_initElement();
  1719. }
  1720. $row = array(
  1721. 'base_elt' => $mailbox,
  1722. 'children' => $this->hasChildren($mailbox),
  1723. 'container' => false,
  1724. 'editvfolder' => false,
  1725. 'icondir' => $elt['image_dir'],
  1726. 'iconopen' => null,
  1727. 'level' => $mailbox['c'],
  1728. 'mbox_val' => htmlspecialchars($mailbox['v']),
  1729. 'name' => htmlspecialchars($mailbox['l']),
  1730. 'newmsg' => 0,
  1731. 'parent' => $mailbox['p'],
  1732. 'polled' => false,
  1733. 'special' => 0,
  1734. 'specialvfolder' => false,
  1735. 'user_icon' => false,
  1736. 'value' => $mailbox['v'],
  1737. 'vfolder' => false,
  1738. );
  1739. $icon = $this->getCustomIcon($mailbox);
  1740. if (!$this->isContainer($mailbox)) {
  1741. /* We are dealing with mailboxes here.
  1742. * Determine if we need to poll this mailbox for new messages. */
  1743. if ($this->isPolled($mailbox)) {
  1744. /* If we need message information for this folder, update
  1745. * it now. */
  1746. $msgs_info = $this->getElementInfo($mailbox['v']);
  1747. if (!empty($msgs_info)) {
  1748. $row['polled'] = true;
  1749. if (!empty($msgs_info['newmsg'])) {
  1750. $row['newmsg'] = $msgs_info['newmsg'];
  1751. }
  1752. $row['msgs'] = $msgs_info['messages'];
  1753. $row['unseen'] = $msgs_info['unseen'];
  1754. }
  1755. }
  1756. switch ($mailbox['v']) {
  1757. case 'INBOX':
  1758. $row['icon'] = 'folders/inbox.png';
  1759. $row['alt'] = _("Inbox");
  1760. $row['special'] = IMPTREE_SPECIAL_INBOX;
  1761. break;
  1762. case $elt['trash']:
  1763. if ($GLOBALS['prefs']->getValue('use_vtrash')) {
  1764. $row['icon'] = ($this->isOpen($mailbox)) ? 'folders/folder_open.png' : 'folders/folder.png';
  1765. $row['alt'] = _("Mailbox");
  1766. } else {
  1767. $row['icon'] = 'folders/trash.png';
  1768. $row['alt'] = _("Trash folder");
  1769. $row['special'] = IMPTREE_SPECIAL_TRASH;
  1770. }
  1771. break;
  1772. case $elt['draft']:
  1773. $row['icon'] = 'folders/drafts.png';
  1774. $row['alt'] = _("Draft folder");
  1775. $row['special'] = IMPTREE_SPECIAL_DRAFT;
  1776. break;
  1777. case $elt['spam']:
  1778. $row['icon'] = 'folders/spam.png';
  1779. $row['alt'] = _("Spam folder");
  1780. $row['special'] = IMPTREE_SPECIAL_SPAM;
  1781. break;
  1782. default:
  1783. if (in_array($mailbox['v'], $elt['sent'])) {
  1784. $row['icon'] = 'folders/sent.png';
  1785. $row['alt'] = _("Sent mail folder");
  1786. $row['special'] = IMPTREE_SPECIAL_SENT;
  1787. } else {
  1788. $row['icon'] = ($this->isOpen($mailbox)) ? 'folders/folder_open.png' : 'folders/folder.png';
  1789. $row['alt'] = _("Mailbox");
  1790. }
  1791. break;
  1792. }
  1793. /* Virtual folders. */
  1794. if ($this->isVFolder($mailbox)) {
  1795. $row['vfolder'] = true;
  1796. $row['editvfolder'] = $GLOBALS['imp_search']->isEditableVFolder($mailbox['v']);
  1797. if ($GLOBALS['imp_search']->isVTrashFolder($mailbox['v'])) {
  1798. $row['specialvfolder'] = true;
  1799. $row['icon'] = 'folders/trash.png';
  1800. $row['alt'] = _("Virtual Trash Folder");
  1801. } elseif ($GLOBALS['imp_search']->isVINBOXFolder($mailbox['v'])) {
  1802. $row['specialvfolder'] = true;
  1803. $row['icon'] = 'folders/inbox.png';
  1804. $row['alt'] = _("Virtual INBOX Folder");
  1805. }
  1806. }
  1807. } else {
  1808. /* We are dealing with folders here. */
  1809. $row['container'] = true;
  1810. if ($this->_forceopen && $this->isOpen($mailbox)) {
  1811. $row['icon'] = 'folders/folder_open.png';
  1812. $row['alt'] = _("Opened Folder");
  1813. } else {
  1814. $row['icon'] = 'folders/folder.png';
  1815. $row['iconopen'] = 'folders/folder_open.png';
  1816. $row['alt'] = ($this->_forceopen) ? _("Closed Folder") : _("Folder");
  1817. }
  1818. if ($this->isVFolder($mailbox)) {
  1819. $row['vfolder'] = true;
  1820. }
  1821. }
  1822. /* Overwrite the icon information now. */
  1823. if (!empty($icon)) {
  1824. $row['icon'] = $icon['icon'];
  1825. $row['icondir'] = $icon['icondir'];
  1826. if (!empty($icon['alt'])) {
  1827. $row['alt'] = $icon['alt'];
  1828. }
  1829. $row['iconopen'] = isset($icon['iconopen']) ? $icon['iconopen'] : null;
  1830. $row['user_icon'] = true;
  1831. }
  1832. return $row;
  1833. }
  1834. /**
  1835. * Sort a level in the tree.
  1836. *
  1837. * @access private
  1838. *
  1839. * @param string $id The parent folder whose children need to be sorted.
  1840. */
  1841. function _sortLevel($id)
  1842. {
  1843. if ($this->_needSort($this->_tree[$id])) {
  1844. $this->_sortList($this->_parent[$id], ($id === IMPTREE_BASE_ELT));
  1845. $this->_setNeedSort($this->_tree[$id], false);
  1846. $this->_changed = true;
  1847. }
  1848. }
  1849. /**
  1850. * Determines the mailbox name to create given a parent and the new name.
  1851. *
  1852. * @param string $parent The parent name.
  1853. * @param string $parent The new mailbox name.
  1854. *
  1855. * @return string The full path to the new mailbox, or PEAR_Error.
  1856. */
  1857. function createMailboxName($parent, $new)
  1858. {
  1859. $ns_info = (empty($parent)) ? IMP::defaultNamespace() : $this->_getNamespace($parent);
  1860. if (is_null($ns_info)) {
  1861. if ($this->isNamespace($this->_tree[$parent])) {
  1862. $ns_info = $this->_getNamespace($new);
  1863. if (in_array($ns_info['type'], array('other', 'shared'))) {
  1864. return $new;
  1865. }
  1866. }
  1867. return PEAR::raiseError(_("Cannot directly create mailbox in this folder."), 'horde.error');
  1868. }
  1869. $mbox = $ns_info['name'];
  1870. if (!empty($parent)) {
  1871. $mbox .= substr_replace($parent, '', 0, strlen($ns_info['name']));
  1872. $mbox = rtrim($mbox, $ns_info['delimiter']) . $ns_info['delimiter'];
  1873. }
  1874. return $mbox . $new;
  1875. }
  1876. }