PageRenderTime 76ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/horde-3.3.13/lib/Horde/IMAP/Tree.php

#
PHP | 2010 lines | 958 code | 213 blank | 839 comment | 197 complexity | 15ac26ce8f3621f6191b013e22003a16 MD5 | raw file
Possible License(s): LGPL-2.0
  1. <?php
  2. /* Constants for mailboxElt attributes.
  3. * All versions of c-client (c-client/mail.h) define these constants:
  4. * LATT_NOINFERIORS (long) 0x1 = 1
  5. * LATT_NOSELECT (long) 0x2 = 2
  6. * LATT_MARKED (long) 0x4 = 4
  7. * LATT_UNMARKED (long) 0x8 = 8
  8. *
  9. * Newer versions of c-client (imap-2002 and greater) define these constants:
  10. * LATT_REFERRAL (long) 0x10 = 16
  11. * LATT_HASCHILDREN (long) 0x20 = 32
  12. * LATT_HASNOCHILDREN (long) 0x40 = 64
  13. * ...but these constant names do not appear in PHP until PHP 4.3.5 and 5.0.
  14. *
  15. * Also, no need to define LATT_REFERRAL at the present time since we don't use
  16. * it anywhere.
  17. */
  18. if (!defined('LATT_HASCHILDREN')) {
  19. // @define('LATT_REFERRAL', 16);
  20. @define('LATT_HASCHILDREN', 32);
  21. @define('LATT_HASNOCHILDREN', 64);
  22. }
  23. /* Start at 128 for our local bitmasks to allow for the c-client LATT_*
  24. constants. */
  25. define('IMAPTREE_ELT_HAS_CHILDREN', 128); // DEPRECATED
  26. define('IMAPTREE_ELT_IS_OPEN', 256);
  27. define('IMAPTREE_ELT_IS_SUBSCRIBED', 512);
  28. define('IMAPTREE_ELT_IS_DISCOVERED', 1024);
  29. define('IMAPTREE_ELT_IS_POLLED', 2048);
  30. define('IMAPTREE_ELT_NEED_SORT', 4096);
  31. // '8192' is used by IMP 4.x so do not use it here
  32. // TODO: Renumber to 128 in Horde 4.0
  33. define('IMAPTREE_ELT_NAMESPACE', 16384);
  34. /* The isOpen() expanded mode constants. */
  35. define('IMAPTREE_OPEN_NONE', 0);
  36. define('IMAPTREE_OPEN_ALL', 1);
  37. define('IMAPTREE_OPEN_USER', 2);
  38. /* Which mode of IMAP access are we using. */
  39. define('IMAPTREE_MODE_MAIL', 0);
  40. define('IMAPTREE_MODE_NEWS', 1);
  41. /* The initalization mode to use when creating the tree. */
  42. define('IMAPTREE_INIT_SUB', 1);
  43. define('IMAPTREE_INIT_UNSUB', 2);
  44. define('IMAPTREE_INIT_FETCHALL', 4);
  45. /* The manner to which to traverse the tree when calling next(). */
  46. define('IMAPTREE_NEXT_SHOWCLOSED', 1);
  47. define('IMAPTREE_NEXT_SHOWSUB', 2);
  48. /* The string used to indicate the base of the tree. */
  49. define('IMAPTREE_BASE_ELT', null);
  50. /**
  51. * The IMAP_Tree class provides a tree view of the folders supported with
  52. * the PHP imap extension (IMAP/POP3/NNTP repositories). It provides access
  53. * functions to iterate through this tree and query information about
  54. * individual mailboxes/folders.
  55. *
  56. * $Horde: framework/IMAP/IMAP/Tree.php,v 1.48.2.47 2009/01/06 15:23:11 jan Exp $
  57. *
  58. * Copyright 2000-2009 The Horde Project (http://www.horde.org/)
  59. *
  60. * See the enclosed file COPYING for license information (GPL). If you
  61. * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
  62. *
  63. * @author Chuck Hagenbuch <chuck@horde.org>
  64. * @author Jon Parise <jon@horde.org>
  65. * @author Anil Madhavapeddy <avsm@horde.org>
  66. * @author Michael Slusarz <slusarz@horde.org>
  67. * @since Horde 3.0
  68. * @package Horde_IMAP
  69. */
  70. class IMAP_Tree {
  71. /**
  72. * Associative array containing the mailbox tree.
  73. *
  74. * @var array
  75. */
  76. var $_tree;
  77. /**
  78. * Location of current element in the tree.
  79. *
  80. * @var string
  81. */
  82. var $_currparent = null;
  83. /**
  84. * Location of current element in the tree.
  85. *
  86. * @var integer
  87. */
  88. var $_currkey = null;
  89. /**
  90. * Location of current element in the tree.
  91. *
  92. * @var array
  93. */
  94. var $_currstack = array();
  95. /**
  96. * Show unsubscribed mailboxes?
  97. *
  98. * @var boolean
  99. */
  100. var $_showunsub = false;
  101. /**
  102. * Parent list.
  103. *
  104. * @var array
  105. */
  106. var $_parent = array();
  107. /**
  108. * The cached list of mailboxes to poll.
  109. *
  110. * @var array
  111. */
  112. var $_poll = null;
  113. /**
  114. * The cached list of expanded folders.
  115. *
  116. * @var array
  117. */
  118. var $_expanded = null;
  119. /**
  120. * Cached list of subscribed mailboxes.
  121. *
  122. * @var array
  123. */
  124. var $_subscribed = null;
  125. /**
  126. * Cached list of unsubscribed mailboxes.
  127. *
  128. * @var array
  129. */
  130. var $_unsubscribed = null;
  131. /**
  132. * Init mode flag.
  133. *
  134. * @var integer
  135. */
  136. var $_initmode = 0;
  137. /**
  138. * Tree changed flag. Set when something in the tree has been altered.
  139. *
  140. * @var boolean
  141. */
  142. var $_changed = false;
  143. /**
  144. * Have we shown unsubscribed folders previously?
  145. *
  146. * @var boolean
  147. */
  148. var $_unsubview = false;
  149. /**
  150. * The IMAP_Sort object.
  151. *
  152. * @var IMAP_Sort
  153. */
  154. var $_imap_sort = null;
  155. /**
  156. * Insert an element in the tree that doesn't appear on the IMAP server.
  157. * If set, IMAP_Tree:: will call _getNonIMAPElt() to obtain the element
  158. * to add to the tree.
  159. *
  160. * @var boolean
  161. */
  162. var $_nonimapelt = false;
  163. /**
  164. * The name to use when storing the object in the session.
  165. *
  166. * @var string
  167. */
  168. var $_cachename;
  169. /**
  170. * The application that generated this tree.
  171. * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
  172. *
  173. * @var string
  174. */
  175. var $_app = null;
  176. /**
  177. * The server string for the current server.
  178. * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
  179. *
  180. * @var string
  181. */
  182. var $_server = '';
  183. /**
  184. * Should we use 'mail' mode or 'news' mode?
  185. * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
  186. *
  187. * @var integer
  188. */
  189. var $_mode = null;
  190. /**
  191. * The list of namespaces to add to the tree.
  192. *
  193. * @var array
  194. */
  195. var $_namespaces;
  196. /**
  197. * Does the IMAP server support the children extension?
  198. *
  199. * @var boolean
  200. */
  201. var $_childrensupport = null;
  202. /**
  203. * Used to determine the list of element changes.
  204. *
  205. * @var array
  206. */
  207. var $_eltdiff = null;
  208. /**
  209. * Attempts to return a reference to a concrete IMAP_Tree instance.
  210. *
  211. * If an IMAP_Tree object is currently stored in the local session,
  212. * recreate that object. Else, if $create is true, will create a new
  213. * instance. Ensures that only one IMAP_Tree instance is available
  214. * at any time.
  215. *
  216. * This method must be invoked as:<pre>
  217. * $imap_tree = &IMAP_Tree::singleton($app, $classname, [$create[, $unsub]]);</pre>
  218. *
  219. * @param string $app The current application name.
  220. * @param string $classname The class name to use when instantiating a new
  221. * object.
  222. * @param boolean $create Create a new IMAP_Tree if it doesn't exist in
  223. * the session?
  224. * @param integer $init The initialization mode to use.
  225. * @param string $cachename The name to use when storing in the session.
  226. *
  227. * @return IMAP_Tree The IMAP_Tree object or null.
  228. */
  229. function &singleton($app, $classname, $create = false,
  230. $init = IMAPTREE_INIT_SUB, $cachename = 'imaptree')
  231. {
  232. static $instance = array();
  233. if (isset($instance[$app])) {
  234. return $instance[$app];
  235. }
  236. if (!empty($_SESSION[$app][$cachename])) {
  237. require_once 'Horde/SessionObjects.php';
  238. $cacheSess = &Horde_SessionObjects::singleton();
  239. $instance[$app] = &$cacheSess->query($_SESSION[$app][$cachename]);
  240. register_shutdown_function(array(&$instance[$app], '_store'));
  241. } elseif ($create) {
  242. $instance[$app] = &new $classname($init, $cachename);
  243. }
  244. return $instance[$app];
  245. }
  246. /**
  247. * Constructor.
  248. *
  249. * @param integer $init The initialization mode to use.
  250. * @param string $cachename The name to use when storing in the session.
  251. */
  252. function IMAP_Tree($init = IMAPTREE_INIT_SUB, $cachename = 'imaptree')
  253. {
  254. register_shutdown_function(array(&$this, '_store'));
  255. $this->_cachename = $cachename;
  256. $this->init($init);
  257. }
  258. /**
  259. * Store a serialized version of ourself in the current session.
  260. *
  261. * @access private
  262. */
  263. function _store()
  264. {
  265. /* We only need to restore the object if the tree has changed. */
  266. if (!empty($this->_changed)) {
  267. /* Don't store $_expanded and $_poll - these values are handled
  268. * by the subclasses.
  269. * Don't store $_imap_sort or $_eltdiff - this needs to be
  270. * regenerated for each request.
  271. * Don't store $_currkey, $_currparent, and $_currStack since the
  272. * user MUST call reset() before cycling through the tree.
  273. * Reset the $_changed flag. */
  274. $this->_currkey = $this->_currparent = $this->_eltdiff = $this->_expanded = $this->_imap_sort = $this->_poll = null;
  275. $this->_currStack = array();
  276. $this->_changed = false;
  277. require_once 'Horde/SessionObjects.php';
  278. $cacheSess = &Horde_SessionObjects::singleton();
  279. /* Reuse old session ID if possible. */
  280. if (isset($_SESSION[$this->_app][$this->_cachename])) {
  281. $cacheSess->overwrite($_SESSION[$this->_app][$this->_cachename], $this, false);
  282. } else {
  283. if (!isset($_SESSION[$this->_app])) {
  284. $_SESSION[$this->_app] = array();
  285. }
  286. $_SESSION[$this->_app][$this->_cachename] = $cacheSess->storeOid($this, false);
  287. }
  288. }
  289. }
  290. /**
  291. * Returns a list of folders/mailboxes matching a certain path.
  292. *
  293. * @access private
  294. *
  295. * @param string $path The mailbox path.
  296. *
  297. * @return array A list of mailbox_objects whose name matched $path.
  298. * The server string has been removed from the name.
  299. */
  300. function _getList($path)
  301. {
  302. $unique = array();
  303. if (!$this->_showunsub) {
  304. $this->_initSubscribed();
  305. }
  306. $newboxes = @imap_getmailboxes($this->_getStream(), $this->_server, $path);
  307. if (is_array($newboxes)) {
  308. foreach ($newboxes as $box) {
  309. if ($this->_showunsub ||
  310. !isset($this->_subscribed[$box->name])) {
  311. /* Strip off server string. */
  312. $box = $this->_removeServerString($box);
  313. if ($box->name && !isset($unique[$box->name])) {
  314. $unique[$box->name] = $box;
  315. }
  316. }
  317. }
  318. }
  319. return $unique;
  320. }
  321. /**
  322. * Make a single mailbox tree element.
  323. * An element consists of the following items (we use single letters here
  324. * to save in session storage space):
  325. * 'a' -- Attributes
  326. * 'c' -- Level count
  327. * 'l' -- Label
  328. * 'p' -- Parent node
  329. * 'v' -- Value
  330. *
  331. * @access private
  332. *
  333. * @param object stdClass $ob The object returned by imap_getmailboxes().
  334. *
  335. * @return array See format above.
  336. */
  337. function _makeMailboxTreeElt($ob)
  338. {
  339. $elt['a'] = $ob->attributes;
  340. $elt['c'] = 0;
  341. $elt['p'] = IMAPTREE_BASE_ELT;
  342. $elt['v'] = $ob->name;
  343. /* Set subscribed values. Make sure INBOX is always subscribed
  344. * to (if working with mail) so there is always at least 1
  345. * viewable element. */
  346. if ($elt['v'] == 'INBOX') {
  347. $this->_setSubscribed($elt, true);
  348. } elseif (!$this->isSubscribed($elt)) {
  349. $this->_initSubscribed();
  350. $this->_setSubscribed($elt, isset($this->_subscribed[$elt['v']]));
  351. }
  352. /* Check for polled status. */
  353. if (!$this->isPolled($elt)) {
  354. $this->getPollList();
  355. $this->_setPolled($elt, isset($this->_poll[$elt['v']]));
  356. }
  357. /* Check for open status. */
  358. $this->_setOpen($elt, $this->isOpen($elt));
  359. /* Computed values. */
  360. $ns_info = $this->_getNamespace($elt['v']);
  361. $tmp = explode((is_null($ns_info)) ? $this->_delimiter : $ns_info['delimiter'], $elt['v']);
  362. $elt['c'] = count($tmp) - 1;
  363. $label = $elt['c'];
  364. if (!empty($elt['c']) &&
  365. !preg_match("/\{.*pop3.*\}/", $ob->fullServerPath)) {
  366. $elt['p'] = implode((is_null($ns_info)) ? $this->_delimiter : $ns_info['delimiter'], array_slice($tmp, 0, $elt['c']));
  367. /* Strip personal namespace. */
  368. if (!is_null($ns_info) &&
  369. !empty($ns_info['name']) &&
  370. ($ns_info['type'] == 'personal')) {
  371. $elt['c']--;
  372. if (strpos($elt['p'], $ns_info['delimiter']) === false) {
  373. $elt['p'] = IMAPTREE_BASE_ELT;
  374. } elseif (strpos($elt['v'], $ns_info['name'] . 'INBOX' . $ns_info['delimiter']) === 0) {
  375. $elt['p'] = 'INBOX';
  376. }
  377. }
  378. }
  379. $elt['l'] = String::convertCharset($tmp[$label], 'UTF7-IMAP');
  380. return $elt;
  381. }
  382. /**
  383. * Initalize the list at the top level of the hierarchy.
  384. *
  385. * @param integer $init The initialization mode.
  386. */
  387. function init($init)
  388. {
  389. /* Reset all class variables to the defaults. */
  390. $this->_changed = true;
  391. $this->_currkey = null;
  392. $this->_currparent = null;
  393. $this->_currstack = array();
  394. $this->_tree = array();
  395. $this->_parent = array();
  396. $this->_showunsub = $this->_unsubview = ($init & IMAPTREE_INIT_UNSUB);
  397. $this->_subscribed = null;
  398. $this->_unsubscribed = null;
  399. /* Set initialization mode. */
  400. $this->_initmode = $init;
  401. /* Get the initial list of mailboxes from the subclass. */
  402. $boxes = $this->_init();
  403. /* Create a placeholder element to the base of the tree list so we can
  404. * keep track of whether the base level needs to be sorted. */
  405. $this->_tree[IMAPTREE_BASE_ELT] = array('a' => IMAPTREE_ELT_IS_DISCOVERED);
  406. /* Check to make sure all namespace elements are in the tree. */
  407. /* TODO: Remove namespaces availability check in Horde 4.0 */
  408. if ($this->_namespaces) {
  409. foreach ($this->_namespaces as $key => $val) {
  410. if ($val['type'] != 'personal') {
  411. $name = substr($key, 0, -1 * strlen($val['delimiter']));
  412. if (!isset($this->_tree[$val['name']])) {
  413. $ob = &new stdClass;
  414. $ob->delimiter = $val['delimiter'];
  415. $ob->attributes = LATT_NOSELECT | IMAPTREE_ELT_NAMESPACE;
  416. /* $ob->fullServerPath is not completely set here. */
  417. $ob->fullServerPath = $ob->name = $name;
  418. $elt = $this->_makeMailboxTreeElt($ob);
  419. $this->_insertElt($elt);
  420. }
  421. }
  422. }
  423. }
  424. /* Create the list (INBOX and any other hierarchies). */
  425. $this->_addLevel($boxes);
  426. /* End initialization mode. */
  427. $this->_initmode = 0;
  428. }
  429. /**
  430. * Subclass specific initialization tasks.
  431. * THIS METHOD MUST BE DEFINED IN ALL SUBCLASSES.
  432. *
  433. * @abstract
  434. *
  435. * @access private
  436. *
  437. * @return array The initial list of elements to add to the tree.
  438. */
  439. function _init()
  440. {
  441. return array();
  442. }
  443. /**
  444. * Expand a mail folder.
  445. *
  446. * @param string $folder The folder name to expand.
  447. * @param boolean $expandall Expand all folders under this one?
  448. */
  449. function expand($folder, $expandall = false)
  450. {
  451. $folder = $this->_convertName($folder);
  452. if (!isset($this->_tree[$folder])) {
  453. return;
  454. }
  455. $this->_changed = true;
  456. /* Merge in next level of information. */
  457. if ($this->hasChildren($this->_tree[$folder])) {
  458. if (!$this->isDiscovered($this->_tree[$folder])) {
  459. $info = $this->_childrenInfo($folder);
  460. if (!empty($info)) {
  461. if ($this->_initmode) {
  462. if (($this->_initmode & IMAPTREE_INIT_FETCHALL) ||
  463. $this->isOpen($this->_tree[$folder])) {
  464. $this->_addLevel($info);
  465. }
  466. } else {
  467. $this->_addLevel($info);
  468. $this->_setOpen($this->_tree[$folder], true);
  469. }
  470. }
  471. } else {
  472. if (!($this->_initmode & IMAPTREE_INIT_FETCHALL)) {
  473. $this->_setOpen($this->_tree[$folder], true);
  474. }
  475. /* Expand all children beneath this one. */
  476. if ($expandall && !empty($this->_parent[$folder])) {
  477. foreach ($this->_parent[$folder] as $val) {
  478. $this->expand($this->_tree[$val]['v'], true);
  479. }
  480. }
  481. }
  482. }
  483. }
  484. /**
  485. * Collapse a mail folder.
  486. *
  487. * @param string $folder The folder name to collapse.
  488. */
  489. function collapse($folder)
  490. {
  491. $folder = $this->_convertName($folder);
  492. if (!isset($this->_tree[$folder])) {
  493. return;
  494. }
  495. $this->_changed = true;
  496. /* Set the folder attributes to not expanded. */
  497. $this->_setOpen($this->_tree[$folder], false);
  498. }
  499. /**
  500. * Sets the internal array pointer to the next element, and returns the
  501. * next object.
  502. *
  503. * @param integer $mask A mask with the following elements:
  504. * <pre>
  505. * IMAPTREE_NEXT_SHOWCLOSED - Don't ignore closed elements.
  506. * IMAPTREE_NEXT_SHOWSUB - Only show subscribed elements.
  507. * </pre>
  508. *
  509. * @return mixed Returns the next element or false if the element doesn't
  510. * exist.
  511. */
  512. function next($mask = 0)
  513. {
  514. if (is_null($this->_currkey) && is_null($this->_currparent)) {
  515. return false;
  516. }
  517. $curr = $this->current();
  518. $old_showunsub = $this->_showunsub;
  519. if ($mask & IMAPTREE_NEXT_SHOWSUB) {
  520. $this->_showunsub = false;
  521. }
  522. if ($this->_activeElt($curr) &&
  523. (($mask & IMAPTREE_NEXT_SHOWCLOSED) || $this->isOpen($curr)) &&
  524. ($this->_currparent != $curr['v'])) {
  525. /* If the current element is open, and children exist, move into
  526. * it. */
  527. $this->_currstack[] = array('k' => $this->_currkey, 'p' => $this->_currparent);
  528. $this->_currkey = 0;
  529. $this->_currparent = $curr['v'];
  530. if ($this->_needSort($this->_tree[$curr['v']])) {
  531. $this->_sortList($this->_parent[$curr['v']], is_null($curr['v']));
  532. $this->_setNeedSort($this->_tree[$curr['v']], false);
  533. $this->_changed = true;
  534. }
  535. } else {
  536. /* Else, increment within the current subfolder. */
  537. $this->_currkey++;
  538. }
  539. /* If the pointer doesn't point to an element, try to move back to
  540. the previous subfolder. If there is no previous subfolder,
  541. return false. */
  542. $curr = $this->current();
  543. if (!$curr &&
  544. ($mask & IMAPTREE_NEXT_SHOWCLOSED) &&
  545. !$this->isDiscovered($this->_tree[$this->_currparent]) &&
  546. $this->hasChildren($this->_tree[$this->_currparent])) {
  547. $this->_addLevel($this->_childrenInfo($this->_tree[$this->_currparent]['v']));
  548. $curr = $this->current();
  549. }
  550. $this->_showunsub = $old_showunsub;
  551. if (!$curr) {
  552. if (empty($this->_currstack)) {
  553. $this->_currkey = null;
  554. $this->_currparent = null;
  555. return false;
  556. } else {
  557. do {
  558. $old = array_pop($this->_currstack);
  559. $this->_currkey = $old['k'] + 1;
  560. $this->_currparent = $old['p'];
  561. } while ((($curr = $this->current()) == false) &&
  562. count($this->_currstack));
  563. }
  564. }
  565. if (!$this->_activeElt($curr)) {
  566. /* Skip this entry if:
  567. * 1. We are not showing all elements
  568. * 2. We are not subscribed to this element
  569. * 3. It is not a container -OR-, if it is a container, if there
  570. * are no viewable elements underneath it.
  571. * 4. This is an empty namespace element. */
  572. return $this->next($mask);
  573. }
  574. return $curr;
  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 = IMAPTREE_BASE_ELT;
  587. $this->_currstack = array();
  588. if ($this->_needSort($this->_tree[$this->_currparent])) {
  589. $this->_sortList($this->_parent[$this->_currparent], is_null($this->_currparent));
  590. $this->_setNeedSort($this->_tree[$this->_currparent], false);
  591. $this->_changed = true;
  592. }
  593. /* When resetting in news mode, the first element may not be
  594. * viewable (unlike mail mode, where the first element is INBOX and
  595. * is always viewable). Therefore, we need to search for the first
  596. * viewable item and that will be the base of the viewable tree. */
  597. if ($this->_mode == IMAPTREE_MODE_NEWS) {
  598. $curr = $this->current();
  599. if (!$this->_activeElt($curr)) {
  600. $this->next();
  601. }
  602. }
  603. return $this->current();
  604. }
  605. /**
  606. * Return the current tree element.
  607. *
  608. * @return array The current tree element or false if there is no
  609. * element.
  610. */
  611. function current()
  612. {
  613. if (!isset($this->_parent[$this->_currparent][$this->_currkey])) {
  614. return false;
  615. }
  616. return $this->_addAliases($this->_tree[$this->_parent[$this->_currparent][$this->_currkey]]);
  617. }
  618. /**
  619. * Determines if there are more elements in the current tree level.
  620. *
  621. * @return boolean True if there are more elements, false if this is the
  622. * last element.
  623. */
  624. function peek()
  625. {
  626. for ($i = ($this->_currkey + 1); ; $i++) {
  627. if (!isset($this->_parent[$this->_currparent][$i])) {
  628. return false;
  629. }
  630. if ($this->_activeElt($this->_addAliases($this->_tree[$this->_parent[$this->_currparent][$i]]))) {
  631. return true;
  632. }
  633. }
  634. }
  635. /**
  636. * Adds aliases to a tree element and returns the resulting array.
  637. *
  638. * @access protected
  639. *
  640. * @param array $elt A tree element.
  641. *
  642. * @return array A tree element with the aliases added.
  643. */
  644. function _addAliases($elt)
  645. {
  646. $elt['label'] = $elt['l'];
  647. $elt['level'] = $elt['c'];
  648. $elt['parent'] = $elt['p'];
  649. $elt['value'] = $elt['v'];
  650. return $elt;
  651. }
  652. /**
  653. * Returns the requested element.
  654. *
  655. * @param string $name The name of the tree element.
  656. *
  657. * @return array Returns the requested element or false if not found.
  658. */
  659. function get($name)
  660. {
  661. $name = $this->_convertName($name);
  662. if (isset($this->_tree[$name])) {
  663. return $this->_addAliases($this->_tree[$name]);
  664. } else {
  665. return false;
  666. }
  667. }
  668. /**
  669. * Insert a folder/mailbox into the tree.
  670. *
  671. * @param mixed $id The name of the folder (or a list of folder names)
  672. * to add (must be present on the mail server).
  673. *
  674. * @return boolean True on success, false on error.
  675. */
  676. function insert($id)
  677. {
  678. if ($this->_mode == IMAPTREE_MODE_NEWS) {
  679. return false;
  680. }
  681. if (is_array($id)) {
  682. if (!$this->_nonimapelt) {
  683. /* We want to add from the BASE of the tree up for efficiency
  684. * sake. */
  685. $this->_sortList($id);
  686. }
  687. } else {
  688. $id = array($id);
  689. }
  690. $adds = array();
  691. foreach ($id as $val) {
  692. $ns_info = $this->_getNamespace($val);
  693. if (is_null($ns_info) || $this->_nonimapelt) {
  694. $adds[] = $val;
  695. } else {
  696. /* Break apart the name via the delimiter and go step by step
  697. * through the name to make sure all subfolders exist in the
  698. * tree. */
  699. $parts = explode($ns_info['delimiter'], $val);
  700. $parts[0] = $this->_convertName($parts[0]);
  701. for ($i = 0; $i < count($parts); $i++) {
  702. $adds[] = implode($ns_info['delimiter'], array_slice($parts, 0, $i + 1));
  703. }
  704. }
  705. }
  706. foreach (array_unique($adds) as $id) {
  707. if (isset($this->_tree[$id])) {
  708. continue;
  709. }
  710. $this->_changed = true;
  711. if ($this->_nonimapelt) {
  712. $elt = $this->_getNonIMAPElt($id);
  713. } else {
  714. $ob = $this->_getList($id);
  715. $elt = $this->_makeMailboxTreeElt(reset($ob));
  716. if (!$this->isSubscribed($elt)) {
  717. $tmp = @imap_lsub($this->_getStream(), $this->_server, $elt['v']);
  718. if (!empty($tmp)) {
  719. $this->_setSubscribed($elt, true);
  720. }
  721. }
  722. }
  723. if ($this->_insertElt($elt)) {
  724. /* We know that the parent folder has children. */
  725. if (isset($this->_tree[$elt['p']])) {
  726. $this->_setChildren($this->_tree[$elt['p']], true);
  727. }
  728. /* Make sure we are sorted correctly. */
  729. if (count($this->_parent[$elt['p']]) > 1) {
  730. $this->_setNeedSort($this->_tree[$elt['p']], true);
  731. }
  732. /* Add to subscribed/unsubscribed list. */
  733. if ($this->isSubscribed($elt) &&
  734. !is_null($this->_subscribed)) {
  735. $this->_subscribed[$elt['v']] = 1;
  736. } elseif (!$this->isSubscribed($elt) &&
  737. !is_null($this->_unsubscribed)) {
  738. $this->_unsubscribed[$elt['v']] = 1;
  739. }
  740. }
  741. }
  742. return true;
  743. }
  744. /**
  745. * Insert an element into the tree.
  746. *
  747. * @access private
  748. *
  749. * @param array $elt The element to insert. The key in the tree is the
  750. * 'v' (value) element of the element.
  751. *
  752. * @return boolean True if added to the tree.
  753. */
  754. function _insertElt($elt)
  755. {
  756. /* Don't include the parent directories that UW passes back in
  757. %/% lists. */
  758. if (strlen($elt['l']) &&
  759. (!isset($this->_tree[$elt['v']]) ||
  760. $this->isContainer($this->_tree[$elt['v']])) &&
  761. /* TODO: Remove dotfiles filter in next major release. */
  762. ($this->_dotfiles || ($elt['l'][0] != '.'))) {
  763. /* Set the parent array to the value in $elt['p']. */
  764. if (empty($this->_parent[$elt['p']])) {
  765. $this->_parent[$elt['p']] = array();
  766. }
  767. $this->_parent[$elt['p']][] = $elt['v'];
  768. $this->_tree[$elt['v']] = $elt;
  769. return true;
  770. } else {
  771. return false;
  772. }
  773. }
  774. /**
  775. * Delete an element from the tree.
  776. *
  777. * @param mixed $id The element name or an array of element names.
  778. *
  779. * @return boolean Return true on success, false on error.
  780. */
  781. function delete($id)
  782. {
  783. if (is_array($id)) {
  784. /* We want to delete from the TOP of the tree down to ensure that
  785. * parents have an accurate view of what children are left. */
  786. $this->_sortList($id);
  787. $id = array_reverse($id);
  788. $success = true;
  789. foreach ($id as $val) {
  790. $currsuccess = $this->delete($val);
  791. if (!$currsuccess) {
  792. $success = false;
  793. }
  794. }
  795. return $success;
  796. } else {
  797. $id = $this->_convertName($id, true);
  798. }
  799. $ns_info = $this->_getNamespace($id);
  800. if (($id == 'INBOX') ||
  801. !isset($this->_tree[$id]) ||
  802. ($id == $ns_info['name'])) {
  803. return false;
  804. }
  805. $this->_changed = true;
  806. $elt = &$this->_tree[$id];
  807. /* Do not delete from tree if there are child elements - instead,
  808. * convert to a container element. */
  809. if ($this->hasChildren($elt)) {
  810. $this->_setContainer($elt, true);
  811. return true;
  812. }
  813. $parent = $elt['p'];
  814. /* Delete the tree entries. */
  815. unset($this->_tree[$id]);
  816. unset($this->_subscribed[$id]);
  817. unset($this->_unsubscribed[$id]);
  818. /* Delete the entry from the parent tree. */
  819. $key = array_search($id, $this->_parent[$parent]);
  820. unset($this->_parent[$parent][$key]);
  821. if (empty($this->_parent[$parent])) {
  822. /* This folder is now completely empty (no children). If the
  823. * folder is a container only, we should delete the folder from
  824. * the tree. */
  825. unset($this->_parent[$parent]);
  826. if (isset($this->_tree[$parent])) {
  827. $this->_setChildren($this->_tree[$parent], null);
  828. if ($this->isContainer($this->_tree[$parent]) &&
  829. !$this->isNamespace($this->_tree[$parent])) {
  830. $this->delete($parent);
  831. } elseif (!$this->hasChildren($this->_tree[$parent])) {
  832. $this->_removeExpandedList($parent);
  833. $this->_setChildren($this->_tree[$parent], false);
  834. $this->_setOpen($this->_tree[$parent], false);
  835. }
  836. }
  837. } else {
  838. /* Rebuild the parent tree. */
  839. $this->_parent[$parent] = array_values($this->_parent[$parent]);
  840. }
  841. /* Remove the mailbox from the expanded folders list. */
  842. $this->_removeExpandedList($id);
  843. /* Remove the mailbox from the nav_poll list. */
  844. $this->removePollList($id);
  845. return true;
  846. }
  847. /**
  848. * Subscribe an element to the tree.
  849. *
  850. * @param mixed $id The element name or an array of element names.
  851. */
  852. function subscribe($id)
  853. {
  854. if (!is_array($id)) {
  855. $id = array($id);
  856. }
  857. foreach ($id as $val) {
  858. $val = $this->_convertName($val);
  859. if (isset($this->_tree[$val])) {
  860. $this->_changed = true;
  861. $this->_setSubscribed($this->_tree[$val], true);
  862. $this->_setContainer($this->_tree[$val], false);
  863. if (!is_null($this->_subscribed)) {
  864. $this->_subscribed[$val] = 1;
  865. }
  866. unset($this->_unsubscribed[$val]);
  867. } else {
  868. $this->insert($val);
  869. }
  870. }
  871. }
  872. /**
  873. * Unsubscribe an element from the tree.
  874. *
  875. * @param mixed $id The element name or an array of element names.
  876. */
  877. function unsubscribe($id)
  878. {
  879. if (!is_array($id)) {
  880. $id = array($id);
  881. } else {
  882. /* We want to delete from the TOP of the tree down to ensure that
  883. * parents have an accurate view of what children are left. */
  884. $this->_sortList($id);
  885. $id = array_reverse($id);
  886. }
  887. foreach ($id as $val) {
  888. $val = $this->_convertName($val);
  889. /* INBOX can never be unsubscribed to (if in mail mode). */
  890. if (isset($this->_tree[$val]) && ($val != 'INBOX')) {
  891. $this->_changed = true;
  892. $this->_unsubview = true;
  893. $elt = &$this->_tree[$val];
  894. /* Do not delete from tree if there are child elements -
  895. * instead, convert to a container element. */
  896. if (!$this->_showunsub && $this->hasChildren($elt)) {
  897. $this->_setContainer($elt, true);
  898. }
  899. /* Set as unsubscribed, add to unsubscribed list, and remove
  900. * from subscribed list. */
  901. $this->_setSubscribed($elt, false);
  902. if (!is_null($this->_unsubscribed)) {
  903. $this->_unsubscribed[$val] = 1;
  904. }
  905. unset($this->_subscribed[$val]);
  906. }
  907. }
  908. }
  909. /**
  910. * Add another level of hierarchy to the tree.
  911. *
  912. * @access private
  913. *
  914. * @param array $list A list of stdClass objects in the format returned
  915. * from imap_getmailboxes().
  916. */
  917. function _addLevel($list)
  918. {
  919. $expandall = ($this->_initmode & IMAPTREE_INIT_FETCHALL);
  920. foreach ($list as $val) {
  921. $elt = $this->_makeMailboxTreeElt($val);
  922. $parent = $elt['p'];
  923. $this->_insertElt($elt);
  924. $this->_changed = true;
  925. if ($expandall || $this->isOpen($elt)) {
  926. $this->expand($elt['v']);
  927. }
  928. }
  929. /* Sort the list. */
  930. if (!empty($list) &&
  931. !empty($this->_parent[$parent])) {
  932. $this->_setDiscovered($this->_tree[$parent], true);
  933. if (count($this->_parent[$parent]) > 1) {
  934. $this->_setNeedSort($this->_tree[$parent], true);
  935. }
  936. }
  937. }
  938. /**
  939. * Set an attribute for an element.
  940. *
  941. * @access private
  942. *
  943. * @param array &$elt The tree element.
  944. * @param integer $const The constant to set/remove from the bitmask.
  945. * @param boolean $bool Should the attribute be set?
  946. */
  947. function _setAttribute(&$elt, $const, $bool)
  948. {
  949. if ($bool) {
  950. $elt['a'] |= $const;
  951. } else {
  952. $elt['a'] &= ~$const;
  953. }
  954. }
  955. /**
  956. * Does the element have any active children?
  957. *
  958. * @param array $elt A tree element.
  959. *
  960. * @return boolean True if the element has active children.
  961. */
  962. function hasChildren($elt)
  963. {
  964. static $hasChildrenCache = array();
  965. $is_ns = $this->isNamespace($elt);
  966. /* Don't do the following if we are dealing with a namespace
  967. * container. */
  968. if (!$is_ns) {
  969. /* Not all IMAP servers support the HASCHILDREN flag (like UW!) so
  970. * we need to skip this check if the IMAP server doesn't set
  971. * either HASCHILDREN or HASNOCHILDREN. */
  972. if (!empty($this->_childrensupport) ||
  973. (is_null($this->_childrensupport) &&
  974. ($elt['a'] & LATT_HASCHILDREN) ||
  975. ($elt['a'] & LATT_HASNOCHILDREN))) {
  976. $ret = ($elt['a'] & LATT_HASCHILDREN);
  977. /* CHECK: If we are viewing all folders, and there is a folder
  978. * listed as expanded but it does not contain any children,
  979. * then we should remove it from the expanded list since it
  980. * doesn't exist anymore. */
  981. if ($this->_showunsub && !$ret) {
  982. $this->_initExpandedList();
  983. if (!empty($this->_expanded[$elt['v']])) {
  984. $this->_removeExpandedList($elt['v']);
  985. $this->_setOpen($this->_tree[$elt['v']], false);
  986. }
  987. }
  988. if (!$ret) {
  989. return false;
  990. }
  991. /* If we are viewing all elements (subscribed and unsubscribed)
  992. * and we reach this point we know that there must be viewable
  993. * children so return true. */
  994. if ($this->_showunsub) {
  995. return true;
  996. }
  997. }
  998. }
  999. /* Cache results from below since, most likely if we get this far,
  1000. * this code will be accessed several times in the current request. */
  1001. if (isset($hasChildrenCache[$elt['v']])) {
  1002. return $hasChildrenCache[$elt['v']];
  1003. }
  1004. /* If we reach this point, then we are either in subscribe-only mode
  1005. * or we are dealing with a namespace container. Check for the
  1006. * existence of any subscribed mailboxes below the current node. */
  1007. $this->_initSubscribed();
  1008. $folder_list = $this->_subscribed;
  1009. if ($this->_showunsub) {
  1010. $this->_initUnsubscribed();
  1011. $folder_list += $this->_unsubscribed;
  1012. }
  1013. $ns_info = $this->_getNamespace($elt['v']);
  1014. if (!is_null($ns_info)) {
  1015. $search_str = $elt['v'] . $ns_info['delimiter'];
  1016. if (($elt['v'] == 'INBOX') &&
  1017. ($ns_info['name'] == ('INBOX' . $ns_info['delimiter']))) {
  1018. $search_str .= $ns_info['name'];
  1019. }
  1020. foreach (array_keys($folder_list) as $val) {
  1021. if (strpos($val, $search_str) === 0) {
  1022. $this->_hasChildrenCache[$elt['v']] = true;
  1023. return true;
  1024. }
  1025. }
  1026. }
  1027. /* Do one final check if this is a namespace container - if we get
  1028. * this far, and are viewing all folders, then we know we have no
  1029. * children so make sure the element is not set to expanded/open. */
  1030. if ($is_ns && $this->_showunsub) {
  1031. $this->_initExpandedList();
  1032. if (!empty($this->_expanded[$elt['v']])) {
  1033. $this->_removeExpandedList($elt['v']);
  1034. $this->_setOpen($this->_tree[$elt['v']], false);
  1035. }
  1036. }
  1037. $hasChildrenCache[$elt['v']] = false;
  1038. return false;
  1039. }
  1040. /**
  1041. * Set the children attribute for an element.
  1042. *
  1043. * @access private
  1044. *
  1045. * @param array &$elt A tree element.
  1046. * @param mixed $bool The setting. If null, clears the flag.
  1047. */
  1048. function _setChildren(&$elt, $bool)
  1049. {
  1050. if (is_null($bool)) {
  1051. $this->_setAttribute($elt, LATT_HASCHILDREN, false);
  1052. $this->_setAttribute($elt, LATT_HASNOCHILDREN, false);
  1053. } else {
  1054. $this->_setAttribute($elt, LATT_HASCHILDREN, $bool);
  1055. $this->_setAttribute($elt, LATT_HASNOCHILDREN, !$bool);
  1056. }
  1057. }
  1058. /**
  1059. * Has the tree element been discovered?
  1060. *
  1061. * @param array $elt A tree element.
  1062. *
  1063. * @return integer Non-zero if the element has been discovered.
  1064. */
  1065. function isDiscovered($elt)
  1066. {
  1067. return $elt['a'] & IMAPTREE_ELT_IS_DISCOVERED;
  1068. }
  1069. /**
  1070. * Set the discovered attribute for an element.
  1071. *
  1072. * @access private
  1073. *
  1074. * @param array &$elt A tree element.
  1075. * @param boolean $bool The setting.
  1076. */
  1077. function _setDiscovered(&$elt, $bool)
  1078. {
  1079. $this->_setAttribute($elt, IMAPTREE_ELT_IS_DISCOVERED, $bool);
  1080. }
  1081. /**
  1082. * Is the tree element open?
  1083. *
  1084. * @param array $elt A tree element.
  1085. *
  1086. * @return integer True if the element is open.
  1087. */
  1088. function isOpen($elt)
  1089. {
  1090. if (!$this->_initmode) {
  1091. return (($elt['a'] & IMAPTREE_ELT_IS_OPEN) && $this->hasChildren($elt));
  1092. } else {
  1093. switch ($this->_getInitExpandedMode()) {
  1094. case IMAPTREE_OPEN_NONE:
  1095. return false;
  1096. break;
  1097. case IMAPTREE_OPEN_ALL:
  1098. return true;
  1099. break;
  1100. case IMAPTREE_OPEN_USER:
  1101. $this->_initExpandedList();
  1102. return !empty($this->_expanded[$elt['v']]);
  1103. break;
  1104. }
  1105. }
  1106. }
  1107. /**
  1108. * Set the open attribute for an element.
  1109. *
  1110. * @access private
  1111. *
  1112. * @param array &$elt A tree element.
  1113. * @param boolean $bool The setting.
  1114. */
  1115. function _setOpen(&$elt, $bool)
  1116. {
  1117. $this->_setAttribute($elt, IMAPTREE_ELT_IS_OPEN, $bool);
  1118. if (!$this->_initmode) {
  1119. $this->_initExpandedList();
  1120. if ($bool) {
  1121. $this->_addExpandedList($elt['v']);
  1122. } else {
  1123. $this->_removeExpandedList($elt['v']);
  1124. }
  1125. }
  1126. }
  1127. /**
  1128. * Is this element a container only, not a mailbox (meaning you can
  1129. * not open it)?
  1130. *
  1131. * @param array $elt A tree element.
  1132. *
  1133. * @return integer True if the element is a container.
  1134. */
  1135. function isContainer($elt)
  1136. {
  1137. return (($elt['a'] & LATT_NOSELECT) ||
  1138. (!$this->_showunsub &&
  1139. !$this->isSubscribed($elt) &&
  1140. $this->hasChildren($elt)));
  1141. }
  1142. /**
  1143. * Set the element as a container?
  1144. *
  1145. * @access private
  1146. *
  1147. * @param array &$elt A tree element.
  1148. * @param boolean $bool Is the element a container?
  1149. */
  1150. function _setContainer(&$elt, $bool)
  1151. {
  1152. $this->_setAttribute($elt, LATT_NOSELECT, $bool);
  1153. }
  1154. /**
  1155. * Is the user subscribed to this element?
  1156. *
  1157. * @param array $elt A tree element.
  1158. *
  1159. * @return integer True if the user is subscribed to the element.
  1160. */
  1161. function isSubscribed($elt)
  1162. {
  1163. return $elt['a'] & IMAPTREE_ELT_IS_SUBSCRIBED;
  1164. }
  1165. /**
  1166. * Set the subscription status for an element.
  1167. *
  1168. * @access private
  1169. *
  1170. * @param array &$elt A tree element.
  1171. * @param boolean $bool Is the element subscribed to?
  1172. */
  1173. function _setSubscribed(&$elt, $bool)
  1174. {
  1175. $this->_setAttribute($elt, IMAPTREE_ELT_IS_SUBSCRIBED, $bool);
  1176. }
  1177. /**
  1178. * Is the element a namespace container?
  1179. *
  1180. * @param array $elt A tree element.
  1181. *
  1182. * @return integer True if the element is a namespace container?
  1183. */
  1184. function isNamespace($elt)
  1185. {
  1186. return $elt['a'] & IMAPTREE_ELT_NAMESPACE;
  1187. }
  1188. /**
  1189. * Remove the server string from the 'name' parameter.
  1190. *
  1191. * @access private
  1192. *
  1193. * @param object stdClass $ob An object returned from
  1194. * imap_getmailboxes().
  1195. *
  1196. * @return stdClass The object returned with the server string stripped
  1197. * from the 'name' parameter.
  1198. */
  1199. function _removeServerString($ob)
  1200. {
  1201. $ob->fullServerPath = $ob->name;
  1202. $ob->name = $this->_convertName(substr($ob->name, strpos($ob->name, '}') + 1));
  1203. return $ob;
  1204. }
  1205. /**
  1206. * Initialize the expanded folder list.
  1207. * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
  1208. *
  1209. * @abstract
  1210. *
  1211. * @access private
  1212. */
  1213. function _initExpandedList()
  1214. {
  1215. $this->_expanded = array();
  1216. }
  1217. /**
  1218. * Add an element to the expanded list.
  1219. * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
  1220. *
  1221. * @abstract
  1222. *
  1223. * @access private
  1224. *
  1225. * @param string $id The element name to remove.
  1226. */
  1227. function _addExpandedList($id)
  1228. {
  1229. }
  1230. /**
  1231. * Remove an element from the expanded list.
  1232. * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
  1233. *
  1234. * @abstract
  1235. *
  1236. * @access private
  1237. *
  1238. * @param string $id The element name to remove.
  1239. */
  1240. function _removeExpandedList($id)
  1241. {
  1242. }
  1243. /**
  1244. * Initialize/get the list of elements to poll.
  1245. * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
  1246. *
  1247. * @abstract
  1248. *
  1249. * @return array The list of elements to poll (name in key field).
  1250. */
  1251. function getPollList()
  1252. {
  1253. $this->_poll = array();
  1254. return $this->_poll;
  1255. }
  1256. /**
  1257. * Add element to the poll list.
  1258. * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
  1259. *
  1260. * @abstract
  1261. *
  1262. * @param mixed $id The element name or a list of element names to add.
  1263. */
  1264. function addPollList($id)
  1265. {
  1266. }
  1267. /**
  1268. * Remove element from the poll list.
  1269. * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
  1270. *
  1271. * @abstract
  1272. *
  1273. * @param string $id The folder/mailbox or a list of folders/mailboxes
  1274. * to remove.
  1275. */
  1276. function removePollList($id)
  1277. {
  1278. }
  1279. /**
  1280. * Does the user want to poll this mailbox for new/unseen messages?
  1281. *
  1282. * @param array $elt A tree element.
  1283. *
  1284. * @return integer True if the user wants to poll the element.
  1285. */
  1286. function isPolled($elt)
  1287. {
  1288. return $elt['a'] & IMAPTREE_ELT_IS_POLLED;
  1289. }
  1290. /**
  1291. * Set the polled attribute for an element.
  1292. *
  1293. * @access private
  1294. *
  1295. * @param array &$elt A tree element.
  1296. * @param boolean $bool The setting.
  1297. */
  1298. function _setPolled(&$elt, $bool)
  1299. {
  1300. $this->_setAttribute($elt, IMAPTREE_ELT_IS_POLLED, $bool);
  1301. }
  1302. /**
  1303. * Flag the element as needing its children to be sorted.
  1304. *
  1305. * @access private
  1306. *
  1307. * @param array &$elt A tree element.
  1308. * @param boolean $bool The setting.
  1309. */
  1310. function _setNeedSort(&$elt, $bool)
  1311. {
  1312. $this->_setAttribute($elt, IMAPTREE_ELT_NEED_SORT, $bool);
  1313. }
  1314. /**
  1315. * Does this element's children need sorting?
  1316. *
  1317. * @param array $elt A tree element.
  1318. *
  1319. * @return integer True if the children need to be sorted.
  1320. */
  1321. function _needSort($elt)
  1322. {
  1323. return $elt['a'] & IMAPTREE_ELT_NEED_SORT;
  1324. }
  1325. /**
  1326. * Initialize the list of subscribed mailboxes.
  1327. *
  1328. * @access private
  1329. */
  1330. function _initSubscribed()
  1331. {
  1332. if (is_null($this->_subscribed)) {
  1333. $this->_changed = true;
  1334. $this->_subscribed = array();
  1335. $sublist = array();
  1336. /* INBOX is always subscribed to if we are in mail mode. */
  1337. if ($this->_mode == IMAPTREE_MODE_MAIL) {
  1338. $this->_subscribed['INBOX'] = 1;
  1339. }
  1340. /* TODO: Remove namespaces availability check in Horde 4.0 */
  1341. if ($this->_namespaces) {
  1342. foreach ($this->_namespaces as $val) {
  1343. $tmp = @imap_lsub($this->_getStream(), $this->_server, $val['name'] . '*');
  1344. if (!empty($tmp)) {
  1345. $sublist = array_merge($sublist, $tmp);
  1346. }
  1347. }
  1348. } else {
  1349. $sublist = @imap_lsub($this->_getStream(), $this->_server, $this->_prefix . '*');
  1350. }
  1351. if (!empty($sublist)) {
  1352. foreach ($sublist as $val) {
  1353. $this->_subscribed[substr($val, strpos($val, '}') + 1)] = 1;
  1354. }
  1355. }
  1356. }
  1357. }
  1358. /**
  1359. * Initialize the list of unsubscribed mailboxes.
  1360. *
  1361. * @access private
  1362. */
  1363. function _initUnsubscribed()
  1364. {
  1365. if (is_null($this->_unsubscribed)) {
  1366. $this->_changed = true;
  1367. $this->_initSubscribed();
  1368. $this->_unsubscribed = array();
  1369. $all_list = array();
  1370. /* Get list of all mailboxes. */
  1371. /* TODO: Remove namespaces availability check in Horde 4.0 */
  1372. if ($this->_namespaces) {
  1373. foreach ($this->_namespaces as $val) {
  1374. $tmp = @imap_list($this->_getStream(), $this->_server, $val['name'] . '*');
  1375. if (!empty($tmp)) {
  1376. $all_list = array_merge($all_list, $tmp);
  1377. }
  1378. }
  1379. } else {
  1380. $all_list = @imap_list($this->_getStream(), $this->_server, $this->_prefix . '*');
  1381. }
  1382. if (!empty($all_list)) {
  1383. /* Find all mailboxes that aren't in the subscribed list. */
  1384. foreach ($all_list as $val) {
  1385. $val = substr($val, strpos($val, '}') + 1);
  1386. if (!isset($this->_subscribed[$val])) {
  1387. $this->_unsubscribed[$val] = 1;
  1388. }
  1389. }
  1390. }
  1391. }
  1392. }
  1393. /**
  1394. * Should we expand all elements?
  1395. */
  1396. function expandAll()
  1397. {
  1398. foreach ($this->_parent[null] as $val) {
  1399. $this->expand($val, true);
  1400. }
  1401. }
  1402. /**
  1403. * Should we collapse all elements?
  1404. */
  1405. function collapseAll()
  1406. {
  1407. foreach ($this->_tree as $key => $val) {
  1408. if ($key != IMAPTREE_BASE_ELT) {
  1409. $this->collapse($val['v']);
  1410. }
  1411. }
  1412. /* Clear all entries from the expanded list. */
  1413. $this->_initExpandedList();
  1414. foreach ($this->_expanded as $key => $val) {
  1415. $this->_removeExpandedList($key);
  1416. }
  1417. }
  1418. /**
  1419. * Return the list of mailboxes in the next level.
  1420. *
  1421. * @access private
  1422. *
  1423. * @param string $id The current mailbox.
  1424. *
  1425. * @return array A list of mailbox objects or the empty list.
  1426. * See _getList() for format.
  1427. */
  1428. function _childrenInfo($id)
  1429. {
  1430. $info = array();
  1431. $ns_info = $this->_getNamespace($id);
  1432. if (is_null($ns_info)) {
  1433. return $info;
  1434. }
  1435. if (($id == 'INBOX') &&
  1436. ($ns_info['name'] == ('INBOX' . $ns_info['delimiter']))) {
  1437. $search = $id . $ns_info['delimiter'] . $ns_info['name'];
  1438. } else {
  1439. $search = $id . $ns_info['delimiter'];
  1440. }
  1441. $info = $this->_getList($search . '%');
  1442. if (isset($this->_tree[$id])) {
  1443. $this->_setChildren($this->_tree[$id], !empty($info));
  1444. }
  1445. return $info;
  1446. }
  1447. /**
  1448. * Switch subscribed/unsubscribed viewing.
  1449. *
  1450. * @param boolean $unsub Show unsubscribed elements?
  1451. */
  1452. function showUnsubscribed($unsub)
  1453. {
  1454. if ($unsub === $this->_showunsub) {
  1455. return;
  1456. }
  1457. $this->_showunsub = $unsub;
  1458. $this->_changed = true;
  1459. /* If we are switching from unsubscribed to subscribed, no need
  1460. * to do anything (we just ignore unsubscribed stuff). */
  1461. if ($unsub === false) {
  1462. return;
  1463. }
  1464. /* If we are switching from subscribed to unsubscribed, we need
  1465. * to add all unsubscribed elements that live in currently
  1466. * discovered items. */
  1467. $this->_unsubview = true;
  1468. $this->_initUnsubscribed();
  1469. if (empty($this->_unsubscribed)) {
  1470. return;
  1471. }
  1472. $this->_initmode = IMAPTREE_INIT_UNSUB;
  1473. $this->insert(array_keys($this->_unsubscribed));
  1474. $this->_initmode = 0;
  1475. }
  1476. /**
  1477. * Returns a reference to a currently open IMAP stream.
  1478. * THIS METHOD MUST BE DEFINED IN ALL SUBCLASSES.
  1479. *
  1480. * @abstract
  1481. * @todo Deprecate in Horde 4.0 - just have the subclass define a $_stream
  1482. * variable.
  1483. *
  1484. * @access private
  1485. *
  1486. * @return resource An IMAP resource stream.
  1487. */
  1488. function &_getStream()
  1489. {
  1490. return false;
  1491. }
  1492. /**
  1493. * Returns the currently selected initialization expanded mode.
  1494. * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
  1495. *
  1496. * @abstract
  1497. *
  1498. * @access private
  1499. *
  1500. * @return integer The current initialization expanded mode.
  1501. */
  1502. function _getInitExpandedMode()
  1503. {
  1504. return IMAPTREE_OPEN_NONE;
  1505. }
  1506. /**
  1507. * Get information about new/unseen/total messages for the given element.
  1508. *
  1509. * @param string $name The element name.
  1510. *
  1511. * @return array Array with the following fields:
  1512. * <pre>
  1513. * 'messages' -- Number of total messages.
  1514. * 'newmsg' -- Number of new messages.
  1515. * 'unseen' -- Number of unseen messages.
  1516. * </pre>
  1517. */
  1518. function getElementInfo($name)
  1519. {
  1520. $status = array();
  1521. require_once 'Horde/IMAP/Cache.php';
  1522. $imap_cache = &IMAP_Cache::singleton();
  1523. $sts = $imap_cache->getStatus($this->_getStream(), $this->_server . $name);
  1524. if (!empty($sts)) {
  1525. $status['messages'] = $sts->messages;
  1526. $status['unseen'] = isset($sts->unseen) ? $sts->unseen : 0;
  1527. $status['newmsg'] = isset($sts->recent) ? $sts->recent : 0;
  1528. }
  1529. return $status;
  1530. }
  1531. /**
  1532. * Sorts a list of mailboxes.
  1533. *
  1534. * @access private
  1535. *
  1536. * @param array &$mbox The list of mailboxes to sort.
  1537. * @param boolean $base Are we sorting a list of mailboxes in the base
  1538. * of the tree.
  1539. */
  1540. function _sortList(&$mbox, $base = false)
  1541. {
  1542. if (is_null($this->_imap_sort)) {
  1543. require_once 'Horde/IMAP/Sort.php';
  1544. $this->_imap_sort = &new IMAP_Sort($this->_delimiter);
  1545. }
  1546. if ($base) {
  1547. foreach ($mbox as $val) {
  1548. $basesort[$val] = ($val == 'INBOX') ? 'INBOX' : $this->_tree[$val]['l'];
  1549. }
  1550. $this->_imap_sort->sortMailboxes($basesort, ($this->_mode == IMAPTREE_MODE_MAIL), true, true);
  1551. $mbox = array_keys($basesort);
  1552. } else {
  1553. $this->_imap_sort->sortMailboxes($mbox, ($this->_mode == IMAPTREE_MODE_MAIL));
  1554. }
  1555. }
  1556. /**
  1557. * Return a Non-IMAP mailbox element given an element identifier.
  1558. *
  1559. * @abstract
  1560. *
  1561. * @access private
  1562. *
  1563. * @param string $id The element identifier.
  1564. *
  1565. * @return array A mailbox element.
  1566. */
  1567. function _getNonIMAPElt($id)
  1568. {
  1569. return array();
  1570. }
  1571. /**
  1572. * Is the given element an "active" element (i.e. an element that should
  1573. * be worked with given the current viewing parameters).
  1574. *
  1575. * @access private
  1576. *
  1577. * @param array $elt A tree element.
  1578. *
  1579. * @return boolean True if it is an active element.
  1580. */
  1581. function _activeElt($elt)
  1582. {
  1583. if ($this->_showunsub &&
  1584. $this->isNamespace($elt)) {
  1585. return ($this->hasChildren($elt));
  1586. } else {
  1587. return ($this->_showunsub ||
  1588. ($this->isSubscribed($elt) && !$this->isContainer($elt)) ||
  1589. $this->hasChildren($elt));
  1590. }
  1591. }
  1592. /**
  1593. * Convert a mailbox name to the correct, internal name (i.e. make sure
  1594. * INBOX is always capitalized for IMAP servers).
  1595. *
  1596. * @access private
  1597. *
  1598. * @param string $name The mailbox name.
  1599. *
  1600. * @return string The converted name.
  1601. */
  1602. function _convertName($name)
  1603. {
  1604. return (($this->_mode == IMAPTREE_MODE_MAIL) && (strcasecmp($name, 'INBOX') == 0)) ? 'INBOX' : $name;
  1605. }
  1606. /**
  1607. * Get namespace info for a full folder path.
  1608. *
  1609. * @access private
  1610. *
  1611. * @param string $mailbox The folder path.
  1612. *
  1613. * @return mixed The namespace info for the folder path or null if the
  1614. * path doesn't exist.
  1615. */
  1616. function _getNamespace($mailbox)
  1617. {
  1618. static $namespaceCache = array();
  1619. if (isset($namespaceCache[$mailbox])) {
  1620. return $namespaceCache[$mailbox];
  1621. }
  1622. /* TODO: Remove namespaces availability check in Horde 4.0 */
  1623. if (!empty($this->_namespaces)) {
  1624. foreach ($this->_namespaces as $key => $val) {
  1625. if ((($mailbox . $val['delimiter']) == $key) ||
  1626. (!empty($key) && (strpos($mailbox, $key) === 0))) {
  1627. $namespaceCache[$mailbox] = $val;
  1628. return $val;
  1629. }
  1630. }
  1631. if (isset($this->_namespaces[''])) {
  1632. $namespaceCache[$mailbox] = $this->_namespaces[''];
  1633. return $this->_namespaces[''];
  1634. }
  1635. $namespaceCache[$mailbox] = null;
  1636. return null;
  1637. } else {
  1638. if (substr($this->_prefix, -1) == $this->_delimiter) {
  1639. $name = substr($this->_prefix, 0, strlen($this->_prefix) - 1);
  1640. } else {
  1641. $name = $this->_prefix;
  1642. }
  1643. $namespaceCache[$mailbox] = array('delimiter' => $this->_delimiter, 'name' => $name, 'type' => 'personal');
  1644. return $namespaceCache[$mailbox];
  1645. }
  1646. }
  1647. /**
  1648. * Does the IMAP server support the 'CHILDREN' IMAP extension?
  1649. *
  1650. * @param boolean $support True if the IMAP server supports the CHILDREN
  1651. * extension, false if it doesn't.
  1652. */
  1653. function IMAPchildrenSupport($support)
  1654. {
  1655. $this->_childrensupport = (bool) $support;
  1656. }
  1657. /**
  1658. * Set the start point for determining element differences via eltDiff().
  1659. *
  1660. * @since Horde 3.1
  1661. */
  1662. function eltDiffStart()
  1663. {
  1664. $this->_eltdiff = $this->_tree;
  1665. }
  1666. /**
  1667. * Return the list of elements that have changed since nodeDiffStart()
  1668. * was last called.
  1669. *
  1670. * @since Horde 3.1
  1671. *
  1672. * @return array An array with the following keys:
  1673. * <pre>
  1674. * 'a' => A list of elements that have been added.
  1675. * 'c' => A list of elements that have been changed.
  1676. * 'd' => A list of elements that have been deleted.
  1677. * </pre>
  1678. * Returns false if no changes have occurred.
  1679. */
  1680. function eltDiff()
  1681. {
  1682. if (!$this->_changed || !$this->_eltdiff) {
  1683. return false;
  1684. }
  1685. $added = $changed = $deleted = array();
  1686. /* Determine the deleted items. */
  1687. $deleted = array_values(array_diff(array_keys($this->_eltdiff), array_keys($this->_tree)));
  1688. foreach ($this->_tree as $key => $val) {
  1689. if (!isset($this->_eltdiff[$key])) {
  1690. $added[] = $key;
  1691. } elseif ($val != $this->_eltdiff[$key]) {
  1692. $changed[] = $key;
  1693. }
  1694. }
  1695. if (empty($added) && empty($changed) && empty($deleted)) {
  1696. return false;
  1697. } else {
  1698. return array('a' => $added, 'c' => $changed, 'd' => $deleted);
  1699. }
  1700. }
  1701. /**** Deprecated variables/functions. These remain for BC only. ****/
  1702. /**
  1703. * The prefix without a trailing delimiter.
  1704. *
  1705. * @deprecated since Horde 3.1
  1706. * @var string
  1707. */
  1708. var $_prefixnodelim = '';
  1709. /**
  1710. * The server string used for the delimiter.
  1711. * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
  1712. *
  1713. * @deprecated since Horde 3.1
  1714. * @var string
  1715. */
  1716. var $_delimiter = '/';
  1717. /**
  1718. * Where we start listing folders.
  1719. * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
  1720. *
  1721. * @deprecated since Horde 3.1
  1722. * @var string
  1723. */
  1724. var $_prefix = '';
  1725. /**
  1726. * The location of the first level of folders below the INBOX.
  1727. * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
  1728. *
  1729. * @deprecated since Horde 3.1
  1730. * @var string
  1731. */
  1732. var $_namespace = '';
  1733. /**
  1734. * Should dotfiles be shown?
  1735. * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
  1736. *
  1737. * @var boolean
  1738. */
  1739. var $_dotfiles = false;
  1740. /**
  1741. * The existence of this function in IMAP_Tree indicates that extended
  1742. * namespace support is available.
  1743. *
  1744. * @return boolean Returns true.
  1745. */
  1746. function extendedNamespaceSupport()
  1747. {
  1748. return true;
  1749. }
  1750. /**
  1751. * Return the prefix.
  1752. *
  1753. * @deprecated since Horde 3.1
  1754. * @return string The prefix where folders begin to be listed.
  1755. */
  1756. function getPrefix()
  1757. {
  1758. return $this->_prefix;
  1759. }
  1760. /**
  1761. * Get information about a specific mailbox.
  1762. *
  1763. * @access private
  1764. * @deprecated since Horde 3.1
  1765. *
  1766. * @param string $path The mailbox to query.
  1767. *
  1768. * @return stdClass See imap_getmailboxes(). The server string has
  1769. * already been removed from the 'name' parameter.
  1770. */
  1771. function _getMailbox($path)
  1772. {
  1773. $box = $this->_getList($path);
  1774. if (empty($box)) {
  1775. return false;
  1776. } else {
  1777. return reset($box);
  1778. }
  1779. }
  1780. /**
  1781. * Make sure there is no trailing delimiter on the element name.
  1782. *
  1783. * @param string $name The element name.
  1784. * @deprecated since Horde 3.1
  1785. *
  1786. * @return string The element name with any trailing delimiter stripped
  1787. * off.
  1788. */
  1789. function noTrailingDelimiter($name)
  1790. {
  1791. $ns_info = $this->_getNamespace($name);
  1792. if (substr($name, -1) == $ns_info['delimiter']) {
  1793. $name = substr($name, 0, strlen($name) - 1);
  1794. }
  1795. return $name;
  1796. }
  1797. }