From: Michael M Slusarz Date: Mon, 27 Sep 2010 21:31:22 +0000 (-0600) Subject: Split IMP_Mailbox into two separate classes. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=92dd1eb28d591865518ac5e9a8016029f1487d94;p=horde.git Split IMP_Mailbox into two separate classes. One holds the sorted list information, one extends to include array index tracking (since this part of the class is only used in the message views of IMP/MIMP). Improve serialization of this data. --- diff --git a/imp/config/hooks.php.dist b/imp/config/hooks.php.dist index 7728c4795..3c46f3159 100644 --- a/imp/config/hooks.php.dist +++ b/imp/config/hooks.php.dist @@ -272,9 +272,9 @@ class IMP_Hooks * mailbox. * * @param array $data The overview information for a message as returned - * from the IMP_Mailbox::getMailboxArray() call (see - * lib/Mailbox.php for documentation on the structure - * of the array). + * from the IMP_Mailbox_List::getMailboxArray() call + * (see lib/Mailbox/List.php for documentation on the + * structure of the array). * @param string $mode Either 'imp' or 'dimp'. * * @return array An array of additional flags to add. These flags must be diff --git a/imp/lib/Ajax/Application.php b/imp/lib/Ajax/Application.php index bb6c81083..b3f005a32 100644 --- a/imp/lib/Ajax/Application.php +++ b/imp/lib/Ajax/Application.php @@ -731,7 +731,7 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application $result->ViewPort = $this->_viewPortData(true); } else { $result->ViewPort = new stdClass; - $result->ViewPort->updatecacheid = $GLOBALS['injector']->getInstance('IMP_Mailbox')->getOb($this->_vars->view)->getCacheID($this->_vars->view); + $result->ViewPort->updatecacheid = $GLOBALS['injector']->getInstance('IMP_Mailbox_List')->getList($this->_vars->view)->getCacheID($this->_vars->view); $result->ViewPort->view = $this->_vars->view; } return $result; @@ -925,7 +925,7 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application $result = $this->_checkUidvalidity($result); } elseif (!$change) { /* Only update cacheid info if it changed. */ - $cacheid = $GLOBALS['injector']->getInstance('IMP_Mailbox')->getOb($this->_vars->view)->getCacheID($this->_vars->view); + $cacheid = $GLOBALS['injector']->getInstance('IMP_Mailbox_List')->getList($this->_vars->view)->getCacheID($this->_vars->view); if ($cacheid != $this->_vars->cacheid) { $result->ViewPort = new stdClass; $result->ViewPort->updatecacheid = $cacheid; @@ -1874,7 +1874,7 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application $result->ViewPort = $this->_viewPortData(true); } else { $result->ViewPort = new stdClass; - $result->ViewPort->updatecacheid = $GLOBALS['injector']->getInstance('IMP_Mailbox')->getOb($this->_vars->view)->getCacheID($this->_vars->view); + $result->ViewPort->updatecacheid = $GLOBALS['injector']->getInstance('IMP_Mailbox_List')->getList($this->_vars->view)->getCacheID($this->_vars->view); $result->ViewPort->view = $this->_vars->view; } @@ -1923,7 +1923,7 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application } } - return ($GLOBALS['injector']->getInstance('IMP_Mailbox')->getOb($this->_vars->view)->getCacheID($this->_vars->view) != $this->_vars->cacheid); + return ($GLOBALS['injector']->getInstance('IMP_Mailbox_List')->getList($this->_vars->view)->getCacheID($this->_vars->view) != $this->_vars->cacheid); } /** diff --git a/imp/lib/Application.php b/imp/lib/Application.php index 270888875..c5a013a4c 100644 --- a/imp/lib/Application.php +++ b/imp/lib/Application.php @@ -99,7 +99,7 @@ class IMP_Application extends Horde_Registry_Application 'IMP_Imap' => 'IMP_Injector_Binder_Imap', 'IMP_Imap_Tree' => 'IMP_Injector_Binder_Imaptree', 'IMP_Mail' => 'IMP_Injector_Binder_Mail', - 'IMP_Mailbox' => 'IMP_Injector_Binder_Mailbox', + 'IMP_Mailbox_List' => 'IMP_Injector_Binder_MailboxList', 'IMP_Mime_Viewer' => 'IMP_Injector_Binder_MimeViewer', 'IMP_Quota' => 'IMP_Injector_Binder_Quota', 'IMP_Search' => 'IMP_Injector_Binder_Search', diff --git a/imp/lib/Indices.php b/imp/lib/Indices.php index b70edafb2..b588a97ba 100644 --- a/imp/lib/Indices.php +++ b/imp/lib/Indices.php @@ -53,7 +53,7 @@ class IMP_Indices implements Countable, Iterator * + IMP_Compose object * + IMP_Contents object * + IMP_Indices object - * + IMP_Mailbox object + * + IMP_Mailbox_List_Track object * + String * Format: IMAP sequence string * @@ -92,7 +92,7 @@ class IMP_Indices implements Countable, Iterator ); } elseif ($data instanceof IMP_Indices) { $indices = $data->indices(); - } elseif ($data instanceof IMP_Mailbox) { + } elseif ($data instanceof IMP_Mailbox_List_Track) { $idx = $data->getIMAPIndex(); $indices = array( $idx['mailbox'] => array($idx['uid']) diff --git a/imp/lib/Injector/Binder/Mailbox.php b/imp/lib/Injector/Binder/Mailbox.php deleted file mode 100644 index 07bae9a55..000000000 --- a/imp/lib/Injector/Binder/Mailbox.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @category Horde - * @license http://www.fsf.org/copyleft/gpl.html GPL - * @package IMP - */ -class IMP_Injector_Binder_Mailbox implements Horde_Injector_Binder -{ - /** - */ - public function create(Horde_Injector $injector) - { - return new IMP_Injector_Factory_Mailbox($injector); - } - - /** - */ - public function equals(Horde_Injector_Binder $binder) - { - return false; - } - -} diff --git a/imp/lib/Injector/Binder/MailboxList.php b/imp/lib/Injector/Binder/MailboxList.php new file mode 100644 index 000000000..b8b329ca2 --- /dev/null +++ b/imp/lib/Injector/Binder/MailboxList.php @@ -0,0 +1,31 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/gpl.html GPL + * @package IMP + */ +class IMP_Injector_Binder_MailboxList implements Horde_Injector_Binder +{ + /** + */ + public function create(Horde_Injector $injector) + { + return new IMP_Injector_Factory_MailboxList($injector); + } + + /** + */ + public function equals(Horde_Injector_Binder $binder) + { + return false; + } + +} diff --git a/imp/lib/Injector/Factory/Mailbox.php b/imp/lib/Injector/Factory/Mailbox.php deleted file mode 100644 index 624b28421..000000000 --- a/imp/lib/Injector/Factory/Mailbox.php +++ /dev/null @@ -1,74 +0,0 @@ - - * @category Horde - * @license http://www.fsf.org/copyleft/gpl.html GPL - * @link http://pear.horde.org/index.php?package=IMP - * @package IMP - */ - -/** - * A Horde_Injector:: based IMP_Mailbox:: factory. - * - * Copyright 2010 The Horde Project (http://www.horde.org/) - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - * - * @author Michael Slusarz - * @category Horde - * @license http://www.fsf.org/copyleft/gpl.html GPL - * @link http://pear.horde.org/index.php?package=IMP - * @package IMP - */ -class IMP_Injector_Factory_Mailbox -{ - /** - * Instances. - * - * @var array - */ - private $_instances = array(); - - /** - * The injector. - * - * @var Horde_Injector - */ - private $_injector; - - /** - * Constructor. - * - * @param Horde_Injector $injector The injector to use. - */ - public function __construct(Horde_Injector $injector) - { - $this->_injector = $injector; - } - - /** - * Return the IMP_Mailbox:: instance. - * - * @param string $mailbox The mailbox name. - * @param IMP_Indices $indices An indices object. - * - * @return IMP_Mailbox The singleton mailbox instance. - * @throws IMP_Exception - */ - public function getOb($mailbox, $indices = null) - { - if (!isset($this->_instances[$mailbox])) { - $this->_instances[$mailbox] = new IMP_Mailbox($mailbox, $indices); - } elseif (!is_null($indices)) { - $this->_instances[$mailbox]->setIndex($indices); - } - - return $this->_instances[$mailbox]; - } - -} diff --git a/imp/lib/Injector/Factory/MailboxList.php b/imp/lib/Injector/Factory/MailboxList.php new file mode 100644 index 000000000..ff4d71d52 --- /dev/null +++ b/imp/lib/Injector/Factory/MailboxList.php @@ -0,0 +1,126 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/gpl.html GPL + * @link http://pear.horde.org/index.php?package=IMP + * @package IMP + */ + +/** + * A Horde_Injector:: based IMP_Mailbox_List:: factory. + * + * Copyright 2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + * + * @author Michael Slusarz + * @category Horde + * @license http://www.fsf.org/copyleft/gpl.html GPL + * @link http://pear.horde.org/index.php?package=IMP + * @package IMP + */ +class IMP_Injector_Factory_MailboxList +{ + /** + * Instances. + * + * @var array + */ + private $_instances = array( + 'list' => array(), + 'track' => array() + ); + + /** + * The injector. + * + * @var Horde_Injector + */ + private $_injector; + + /** + * Constructor. + * + * @param Horde_Injector $injector The injector to use. + */ + public function __construct(Horde_Injector $injector) + { + $this->_injector = $injector; + + register_shutdown_function(array($this, 'shutdown')); + } + + /** + * Return the IMP_Mailbox_List:: instance. + * + * @param string $mailbox The mailbox name. + * + * @return IMP_Mailbox_List The singleton mailbox instance. + * @throws IMP_Exception + */ + public function getList($mailbox) + { + if (!isset($this->_instances['list'][$mailbox])) { + $this->_instances['list'][$mailbox] = new IMP_Mailbox_List($mailbox); + } + + return $this->_instances['list'][$mailbox]; + } + + /** + * Return the IMP_Mailbox_List_Track:: instance. + * + * @param string $mailbox The mailbox name. + * @param IMP_Indices $indices An indices object. + * + * + * @return IMP_Mailbox_List_Track The singleton mailbox instance. + * @throws IMP_Exception + */ + public function getListTrack($mailbox, $indices = null) + { + if (!isset($this->_instances['track'][$mailbox])) { + $ob = null; + if (isset($_SESSION['imp']['cache']['imp_mailbox'][$mailbox])) { + try { + $ob = @unserialize($_SESSION['imp']['cache']['imp_mailbox'][$mailbox]); + } catch (Exception $e) {} + } + + if (!$ob) { + $ob = new IMP_Mailbox_List_Track($mailbox); + } + + $this->_instances['track'][$mailbox] = $ob; + } + + if (!is_null($indices)) { + $this->_instances['track'][$mailbox]->setIndex($indices); + } + + return $this->_instances['track'][$mailbox]; + } + + /** + * Tasks to perform on shutdown. + */ + public function shutdown() + { + /* Cache mailbox information if viewing in standard (IMP) message + * mode. Needed to keep navigation consistent when moving through the + * message list, and to ensure messages aren't marked as missing in + * search mailboxes (e.g. if search is dependent on unseen flag). */ + foreach ($this->_instances['track'] as $key => $val) { + if ($val->changed) { + $_SESSION['imp']['cache']['imp_mailbox'][$key] = serialize($val); + } + } + } + +} diff --git a/imp/lib/Mailbox.php b/imp/lib/Mailbox.php deleted file mode 100644 index 7fd96ec27..000000000 --- a/imp/lib/Mailbox.php +++ /dev/null @@ -1,792 +0,0 @@ - - * @category Horde - * @license http://www.fsf.org/copyleft/gpl.html GPL - * @package IMP - */ -class IMP_Mailbox implements Countable -{ - /** - * The mailbox to work with. - * - * @var string - */ - protected $_mailbox; - - /** - * The location in the sorted array we are at. - * - * @var integer - */ - protected $_arrayIndex = null; - - /** - * The array of sorted indices. - * - * @var array - */ - protected $_sorted = null; - - /** - * The mailboxes corresponding to the sorted indices list. - * If empty, uses $_mailbox. - * - * @var array - */ - protected $_sortedMbox = array(); - - /** - * Is this a search malbox? - * - * @var boolean - */ - protected $_searchmbox; - - /** - * The Horde_Imap_Client_Thread object for the mailbox. - * - * @var Horde_Imap_Client_Thread - */ - protected $_threadob = null; - - /** - * Has the internal message list changed? - * - * @var boolean - */ - protected $_changed = false; - - /** - * Constructor. - * - * @param string $mailbox The mailbox to work with. - * @param IMP_Indices $indices An indices object. - */ - public function __construct($mailbox, $indices = null) - { - $this->_mailbox = $mailbox; - $this->_searchmbox = $GLOBALS['injector']->getInstance('IMP_Search')->isSearchMbox($mailbox); - - if (is_null($indices)) { - unset($_SESSION['imp']['cache']['imp_mailbox'][$mailbox]); - } else { - /* Try to rebuild sorted information from the session cache. */ - if (isset($_SESSION['imp']['cache']['imp_mailbox'][$mailbox])) { - $tmp = json_decode($_SESSION['imp']['cache']['imp_mailbox'][$mailbox]); - $this->_sorted = $this->_searchmbox ? $tmp->s : $tmp; - $this->_sortedMbox = $this->_searchmbox ? $tmp->m : array(); - } - $this->setIndex($indices); - } - - register_shutdown_function(array($this, 'shutdown')); - } - - /** - * Cache mailbox information if viewing in standard (IMP) message mode. - * Needed to keep navigation consistent when moving through the message - * list, and to ensure messages aren't marked as missing in search - * mailboxes (e.g. if search is dependent on unseen flag). - */ - public function shutdown() - { - if ($this->_changed && - ($_SESSION['imp']['view'] == 'imp')) { - /* Casting $_sorted to integers saves a significant amount of - * space when json_encoding (no need to quote every value). Only - * can do for IMAP though (since POP3 UIDs are not limited to - * integers). */ - $sorted = ($_SESSION['imp']['protocol'] == 'pop') - ? $this->_sorted - : array_map('intval', $this->_sorted); - $_SESSION['imp']['cache']['imp_mailbox'][$this->_mailbox] = $this->_searchmbox - ? json_encode(array('m' => $this->_sortedMbox, 's' => $sorted)) - : json_encode($sorted); - } - } - - /** - * Build the array of message information. - * - * @param array $msgnum An array of message sequence numbers. - * @param array $options Additional options: - *
-     * 'headers' - (boolean) Return info on the non-envelope headers
-     *             'Importance', 'List-Post', and 'X-Priority'.
-     *             DEFAULT: false (only envelope headers returned)
-     * 'preview' - (mixed) Include preview information?  If empty, add no
-     *                     preview information. If 1, uses value from prefs.
-     *                     If 2, forces addition of preview info.
-     *                     DEFAULT: No preview information.
-     * 'structure' - (boolean) Get structure information from server.
-     *               Contained in the 'strucutre' entry.
-     *               DEFAULT: false
-     * 
- * - * @return array An array with the following keys: - *
-     * 'overview' - (array) The overview information. Contains the following:
-     *              'envelope' - (array) Envelope information returned from
-     *                           the IMAP server. See
-     *                           Horde_Imap_Client::fetch() for format.
-     *              'flags' - (array) The list of IMAP flags returned from
-     *                        the server. See Horde_Imap_Client::fetch() for
-     *                        the format.
-     *              'headers' - (array) Any headers requested in
-     *                          $options['headers']. Horde_Mime_Headers objects
-     *                          are returned.  See Horde_Imap_Client::fetch()
-     *                          for the format.
-     *              'mailbox' - (string) The mailbox containing the message.
-     *              'preview' - (string) If requested in $options['preview'],
-     *                          the preview text.
-     *              'previewcut'- (boolean) Has the preview text been cut?
-     *              'size' - (integer) The size of the message in bytes.
-     *              'structure'- (array) The structure of the message. Only
-     *                           set if $options['structure'] is true. See
-     *                           Horde_Imap_Client::fetch() for format.
-     *              'uid' - (string) The unique ID of the message.
-     *
-     * 'uids' - (IMP_Indices) An indices object.
-     * 
- */ - public function getMailboxArray($msgnum, $options = array()) - { - $this->_buildMailbox(); - - $overview = $to_process = $uids = array(); - - /* Build the list of mailboxes and messages. */ - foreach ($msgnum as $i) { - /* Make sure that the index is actually in the slice of messages - we're looking at. If we're hiding deleted messages, for - example, there may be gaps here. */ - if (isset($this->_sorted[$i - 1])) { - $mboxname = ($this->_searchmbox) ? $this->_sortedMbox[$i - 1] : $this->_mailbox; - - // $uids - KEY: UID, VALUE: sequence number - $to_process[$mboxname][$this->_sorted[$i - 1]] = $i; - } - } - - $fetch_criteria = array( - Horde_Imap_Client::FETCH_ENVELOPE => true, - Horde_Imap_Client::FETCH_FLAGS => true, - Horde_Imap_Client::FETCH_SIZE => true, - Horde_Imap_Client::FETCH_UID => true, - ); - - if (!empty($options['headers'])) { - $fetch_criteria[Horde_Imap_Client::FETCH_HEADERS] = array(array('cache' => true, 'headers' => array('importance', 'list-post', 'x-priority'), 'label' => 'imp', 'parse' => true, 'peek' => true)); - } - - if (!empty($options['structure'])) { - $fetch_criteria[Horde_Imap_Client::FETCH_STRUCTURE] = array('parse' => true); - } - - $imp_imap = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb(); - - if (empty($options['preview'])) { - $cache = null; - $options['preview'] = 0; - } else { - $cache = $imp_imap->getCache(); - } - - /* Retrieve information from each mailbox. */ - foreach ($to_process as $mbox => $ids) { - try { - $fetch_res = $imp_imap->fetch($mbox, $fetch_criteria, array('ids' => array_keys($ids))); - - if ($options['preview']) { - $preview_info = $tostore = array(); - if ($cache) { - try { - $preview_info = $cache->get($mbox, array_keys($ids), array('IMPpreview', 'IMPpreviewc')); - } catch (Horde_Imap_Client_Exception $e) {} - } - } - - foreach (array_keys($ids) as $k) { - $v = $fetch_res[$k]; - - $v['mailbox'] = $mbox; - if (isset($v['headers']['imp'])) { - $v['headers'] = $v['headers']['imp']; - } - - if (($options['preview'] === 2) || - (($options['preview'] === 1) && - (!$GLOBALS['prefs']->getValue('preview_show_unread') || - !in_array('\\seen', $v['flags'])))) { - if (empty($preview_info[$k])) { - try { - $imp_contents = $GLOBALS['injector']->getInstance('IMP_Contents')->getOb(new IMP_Indices($mbox, $k)); - $prev = $imp_contents->generatePreview(); - $preview_info[$k] = array('IMPpreview' => $prev['text'], 'IMPpreviewc' => $prev['cut']); - if (!is_null($cache)) { - $tostore[$k] = $preview_info[$k]; - } - } catch (Exception $e) { - $preview_info[$k] = array('IMPpreview' => '', 'IMPpreviewc' => false); - } - } - - $v['preview'] = $preview_info[$k]['IMPpreview']; - $v['previewcut'] = $preview_info[$k]['IMPpreviewc']; - } - - $overview[] = $v; - } - - $uids[$mbox] = array_keys($fetch_res); - - if (!is_null($cache) && !empty($tostore)) { - $status = $imp_imap->status($mbox, Horde_Imap_Client::STATUS_UIDVALIDITY); - $cache->set($mbox, $tostore, $status['uidvalidity']); - } - } catch (Horde_Imap_Client_Exception $e) {} - } - - return array( - 'overview' => $overview, - 'uids' => new IMP_Indices($uids) - ); - } - - /** - * Returns true if the mailbox data has been built. - * - * @return boolean True if the mailbox has been built. - */ - public function isBuilt() - { - return !is_null($this->_sorted); - } - - /** - * Builds the sorted list of messages in the mailbox. - */ - protected function _buildMailbox() - { - if ($this->isBuilt()) { - return; - } - - $this->_changed = true; - $this->_sorted = $this->_sortedMbox = array(); - $query = null; - - if ($this->_searchmbox) { - if (IMP::hideDeletedMsgs($this->_mailbox)) { - $query = new Horde_Imap_Client_Search_Query(); - $query->flag('\\deleted', false); - } - - try { - foreach ($GLOBALS['injector']->getInstance('IMP_Search')->runSearch($query, $this->_mailbox) as $mbox => $idx) { - $this->_sorted[] = $idx; - $this->_sortedMbox[] = $mbox; - } - } catch (Horde_Imap_Client_Exception $e) { - $GLOBALS['notification']->push(_("Mailbox listing failed") . ': ' . $e->getMessage(), 'horde.error'); - } - } else { - $sortpref = IMP::getSort($this->_mailbox, true); - if ($sortpref['by'] == Horde_Imap_Client::SORT_THREAD) { - $this->_threadob = null; - $threadob = $this->getThreadOb(); - $this->_sorted = $threadob->messageList((bool)$sortpref['dir']); - } else { - if (($_SESSION['imp']['protocol'] != 'pop') && - IMP::hideDeletedMsgs($this->_mailbox)) { - $query = new Horde_Imap_Client_Search_Query(); - $query->flag('\\deleted', false); - } - try { - $res = $GLOBALS['injector']->getInstance('IMP_Search')->imapSearch($this->_mailbox, $query, array('sort' => array($sortpref['by']), 'reverse' => (bool)$sortpref['dir'])); - $this->_sorted = $res['sort']; - } catch (Horde_Imap_Client_Exception $e) { - $GLOBALS['notification']->push(_("Mailbox listing failed") . ': ' . $e->getMessage(), 'horde.error'); - } - } - } - } - - /** - * Get the list of new messages in the mailbox (IMAP RECENT flag, with - * UNDELETED if we're hiding deleted messages). - * - * @param integer $results A Horde_Imap_Client::SORT_* constant that - * indicates the desired return type. - * @param boolean $uid Return UIDs instead of sequence numbers (for - * $results queries that return message lists). - * - * @return mixed Whatever is requested in $results. - */ - public function newMessages($results, $uid = false) - { - return $this->_msgFlagSearch('recent', $results, $uid); - } - - /** - * Get the list of unseen messages in the mailbox (IMAP UNSEEN flag, with - * UNDELETED if we're hiding deleted messages). - * - * @param integer $results A Horde_Imap_Client::SORT_RESULTS_* constant - * that indicates the desired return type. - * @param boolean $uid Return UIDs instead of sequence numbers (for - * $results queries that return message lists). - * - * @return mixed Whatever is requested in $results. - */ - public function unseenMessages($results, $uid = false) - { - return $this->_msgFlagSearch('unseen', $results, $uid); - } - - /** - * Do a search on a mailbox in the most efficient way available. - * - * @param string $type The search type - either 'recent' or 'unseen'. - * @param integer $results A Horde_Imap_Client::SORT_RESULTS_* constant - * that indicates the desired return type. - * @param boolean $uid Return UIDs instead of sequence numbers (for - * $results queries that return message lists). - * - * @return mixed Whatever is requested in $results. - */ - protected function _msgFlagSearch($type, $results, $uid) - { - $count = ($results == Horde_Imap_Client::SORT_RESULTS_COUNT); - - if ($this->_searchmbox || empty($this->_sorted)) { - if ($count && - $this->_searchmbox && - ($type == 'unseen') && - $GLOBALS['injector']->getInstance('IMP_Search')->isVinbox($this->_mailbox)) { - return count($this); - } - - return $count ? 0 : array(); - } - - $criteria = new Horde_Imap_Client_Search_Query(); - $imp_imap = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb(); - - if (IMP::hideDeletedMsgs($this->_mailbox)) { - $criteria->flag('\\deleted', false); - } elseif ($count) { - try { - $status_res = $imp_imap->status($this->_mailbox, $type == 'recent' ? Horde_Imap_Client::STATUS_RECENT : Horde_Imap_Client::STATUS_UNSEEN); - return $status_res[$type]; - } catch (Horde_Imap_Client_Exception $e) { - return 0; - } - } - - if ($type == 'recent') { - $criteria->flag('\\recent', true); - } else { - $criteria->flag('\\seen', false); - } - - try { - $res = $imp_imap->search($this->_mailbox, $criteria, array('results' => array($results), 'sequence' => !$uid)); - return $count ? $res['count'] : $res; - } catch (Horde_Imap_Client_Exception $e) { - return $count ? 0 : array(); - } - } - - /** - * Returns the current message array index. If the array index has - * run off the end of the message array, will return the last index. - * - * @return integer The message array index. - */ - public function getMessageIndex() - { - return is_null($this->_arrayIndex) ? 1 : $this->_arrayIndex + 1; - } - - /** - * Checks to see if the current index is valid. - * This function is only useful if an index was passed to the constructor. - * - * @return boolean True if index is valid, false if not. - */ - public function isValidIndex($rebuild = true) - { - return !is_null($this->_arrayIndex); - } - - /** - * Returns IMAP mbox/UID information on a message. - * - * @param integer $offset The offset from the current message. - * - * @return array Array with the following entries: - *
-     * 'mailbox' - (string) The mailbox.
-     * 'uid' - (integer) The message UID.
-     * 
- */ - public function getIMAPIndex($offset = 0) - { - $index = $this->_arrayIndex + $offset; - - return isset($this->_sorted[$index]) - ? array( - 'mailbox' => ($this->_searchmbox ? $this->_sortedMbox[$index] : $this->_mailbox), - 'uid' => $this->_sorted[$index] - ) - : array(); - } - - /** - * Using the preferences and the current mailbox, determines the messages - * to view on the current page. - * - * @param integer $page The page number currently being displayed. - * @param integer $start The starting message number. - * - * @return array An array with the following fields: - *
-     * 'anymsg' - (boolean) Are there any messages at all in mailbox? E.g. If
-     *            'msgcount' is 0, there may still be hidden deleted messages.
-     * 'begin' - (integer) The beginning message sequence number of the page.
-     * 'end' - (integer) The ending message sequence number of the page.
-     * 'index' - (integer) The index of the starting message.
-     * 'msgcount' - (integer) The number of viewable messages in the current
-     *              mailbox.
-     * 'page' - (integer) The current page number.
-     * 'pagecount' - (integer) The number of pages in this mailbox.
-     * 
- */ - public function buildMailboxPage($page = 0, $start = 0, $opts = array()) - { - $this->_buildMailbox(); - - $ret = array('msgcount' => count($this->_sorted)); - - $page_size = $GLOBALS['prefs']->getValue('max_msgs'); - - if ($ret['msgcount'] > $page_size) { - $ret['pagecount'] = ceil($ret['msgcount'] / $page_size); - - /* Determine which page to display. */ - if (empty($page) || strcspn($page, '0123456789')) { - if (!empty($start)) { - /* Messages set this when returning to a mailbox. */ - $page = ceil($start / $page_size); - } else { - /* Search for the last visited page first. */ - if (isset($_SESSION['imp']['cache']['mbox_page'][$this->_mailbox])) { - $page = $_SESSION['imp']['cache']['mbox_page'][$this->_mailbox]; - } elseif ($this->_searchmbox) { - $page = 1; - } else { - $page = ceil($this->mailboxStart($ret['msgcount']) / $page_size); - } - } - } - - /* Make sure we're not past the end or before the beginning, and - that we have an integer value. */ - $ret['page'] = intval($page); - if ($ret['page'] > $ret['pagecount']) { - $ret['page'] = $ret['pagecount']; - } elseif ($ret['page'] < 1) { - $ret['page'] = 1; - } - - $ret['begin'] = (($ret['page'] - 1) * $page_size) + 1; - $ret['end'] = $ret['begin'] + $page_size - 1; - if ($ret['end'] > $ret['msgcount']) { - $ret['end'] = $ret['msgcount']; - } - } else { - $ret['begin'] = 1; - $ret['end'] = $ret['msgcount']; - $ret['page'] = 1; - $ret['pagecount'] = 1; - } - - $ret['index'] = ($this->_searchmbox) ? ($ret['begin'] - 1) : $this->_arrayIndex; - - /* If there are no viewable messages, check for deleted messages in - the mailbox. */ - $ret['anymsg'] = true; - if (!$ret['msgcount'] && !$this->_searchmbox) { - try { - $status = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb()->status($this->_mailbox, Horde_Imap_Client::STATUS_MESSAGES); - $ret['anymsg'] = (bool)$status['messages']; - } catch (Horde_Imap_Client_Exception $e) { - $ret['anymsg'] = false; - } - } - - /* Store the page value now. */ - $_SESSION['imp']['cache']['mbox_page'][$this->_mailbox] = $ret['page']; - - return $ret; - } - - /** - * Determines the sequence number of the first message to display, based - * on the user's preferences. - * - * @param integer $total The total number of messages in the mailbox. - * - * @return integer The sequence number in the sorted mailbox. - */ - public function mailboxStart($total) - { - if ($this->_searchmbox) { - return 1; - } - - switch ($GLOBALS['prefs']->getValue('mailbox_start')) { - case IMP::MAILBOX_START_FIRSTPAGE: - return 1; - - case IMP::MAILBOX_START_LASTPAGE: - return $total; - - case IMP::MAILBOX_START_FIRSTUNSEEN: - $sortpref = IMP::getSort($this->_mailbox); - - /* Optimization: if sorting by sequence then first unseen - * information is returned via a SELECT/EXAMINE call. */ - if ($sortpref['by'] == Horde_Imap_Client::SORT_SEQUENCE) { - try { - $res = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb()->status($this->_mailbox, Horde_Imap_Client::STATUS_FIRSTUNSEEN); - if (!is_null($res['firstunseen'])) { - return $res['firstunseen']; - } - } catch (Horde_Imap_Client_Exception $e) {} - - return 1; - } - - $unseen_msgs = $this->unseenMessages(Horde_Imap_Client::SORT_RESULTS_MIN, true); - return empty($unseen_msgs['min']) - ? 1 - : ($this->getArrayIndex($unseen_msgs['min']) + 1); - - case IMP::MAILBOX_START_LASTUNSEEN: - $unseen_msgs = $this->unseenMessages(Horde_Imap_Client::SORT_RESULTS_MAX, true); - return empty($unseen_msgs['max']) - ? 1 - : ($this->getArrayIndex($unseen_msgs['max']) + 1); - } - } - - /** - * Updates the message array index. - * - * @param mixed $data If an integer, the number of messages to increase - * array index by. If an indices object, sets array - * index to the index value. - */ - public function setIndex($data) - { - if ($data instanceof IMP_Indices) { - list($mailbox, $uid) = $data->getSingle(); - $this->_arrayIndex = $this->getArrayIndex($uid, $mailbox); - if (empty($this->_arrayIndex)) { - $this->_rebuild(true); - $this->_arrayIndex = $this->getArrayIndex($uid, $mailbox); - } - } elseif (!is_null($this->_arrayIndex)) { - $index = $this->_arrayIndex += $data; - if (isset($this->_sorted[$this->_arrayIndex])) { - $this->_rebuild(); - } else { - $this->_rebuild(true); - $this->_arrayIndex = isset($this->_sorted[$index]) - ? $index - : null; - } - } - } - - /** - * Get the Horde_Imap_Client_Thread object for the current mailbox. - * - * @return Horde_Imap_Client_Thread The thread object for the current - * mailbox. - */ - public function getThreadOb() - { - if (is_null($this->_threadob)) { - try { - $this->_threadob = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb()->thread($this->_mailbox, array('criteria' => $_SESSION['imp']['imap']['thread'])); - } catch (Horde_Imap_Client_Exception $e) { - $GLOBALS['notification']->push($e); - return new Horde_Imap_Client_Thread(array(), 'uid'); - } - } - - return $this->_threadob; - } - - /** - * Determines if a rebuild is needed, and, if necessary, performs - * the rebuild. - * - * @param boolean $force Force a rebuild? - */ - protected function _rebuild($force = false) - { - if ($force || - (!is_null($this->_arrayIndex) && !$this->getIMAPIndex(1))) { - $this->_sorted = null; - $this->_buildMailbox(); - } - } - - /** - * Returns the array index of the given message UID. - * - * @param integer $uid The message UID. - * @param integer $mbox The message mailbox (defaults to the current - * mailbox). - * - * @return mixed The array index of the location of the message UID in - * the current mailbox. Returns null if not found. - */ - public function getArrayIndex($uid, $mbox = null) - { - $aindex = null; - - $this->_buildMailbox(); - - if ($this->_searchmbox) { - if (is_null($mbox)) { - $mbox = IMP::$thismailbox; - } - - /* Need to compare both mbox name and message UID to obtain the - * correct array index since there may be duplicate UIDs. */ - foreach (array_keys($this->_sorted, $uid) as $key) { - if ($this->_sortedMbox[$key] == $mbox) { - return $key; - } - } - } else { - /* array_search() returns false on no result. We will set an - * unsuccessful result to NULL. */ - if (($aindex = array_search($uid, $this->_sorted)) === false) { - $aindex = null; - } - } - - return $aindex; - } - - /** - * Returns a raw sorted list of the mailbox. - * - * @return array An array with two keys: 's' = sorted UIDS list, 'm' = - * sorted mailboxes list. - */ - public function getSortedList() - { - $this->_buildMailbox(); - - /* For exterior use, the array needs to begin numbering at 1. */ - $s = $this->_sorted; - array_unshift($s, 0); - unset($s[0]); - $m = $this->_sortedMbox; - array_unshift($m, 0); - unset($m[0]); - - return array('s' => $s, 'm' => $m); - } - - /** - * Returns the current sorted array without the given messages. - * - * @param mixed $indices An IMP_Indices object or true to remove all - * messages in the mailbox. - */ - public function removeMsgs($indices) - { - if ($indices === true) { - $this->_rebuild(true); - return; - } - - if (!count($indices)) { - return; - } - - /* Remove the current entry and recalculate the range. */ - foreach ($indices as $mbox => $uid) { - $val = $this->getArrayIndex($uid, $mbox); - unset($this->_sorted[$val]); - if ($this->_searchmbox) { - unset($this->_sortedMbox[$val]); - } - } - - $this->_sorted = array_values($this->_sorted); - $this->_changed = true; - if ($this->_searchmbox) { - $this->_sortedMbox = array_values($this->_sortedMbox); - } - - $this->_threadob = null; - - /* Update the current array index to its new position in the message - * array. */ - $this->setIndex(0); - } - - /** - * Returns a unique identifier for the current mailbox status. - * - * This cache ID is guaranteed to change if messages are added/deleted from - * the mailbox. Additionally, if CONDSTORE is available on the remote - * IMAP server, this ID will change if flag information changes. - * - * @return string The cache ID string, which will change when the - * composition of the mailbox changes. - */ - public function getCacheID() - { - if (!$this->_searchmbox) { - $sortpref = IMP::getSort($this->_mailbox, true); - try { - return $GLOBALS['injector']->getInstance('IMP_Imap')->getOb()->getCacheId($this->_mailbox, array($sortpref['by'], $sortpref['dir'])); - } catch (Horde_Imap_Client_Exception $e) {} - } - - return strval(new Horde_Support_Randomid()); - } - - /* Countable methods. */ - - /** - * Returns the current message count of the mailbox. - * - * @return integer The mailbox message count. - */ - public function count() - { - $this->_buildMailbox(); - return count($this->_sorted); - } - -} diff --git a/imp/lib/Mailbox/List.php b/imp/lib/Mailbox/List.php new file mode 100644 index 000000000..13b8a9db6 --- /dev/null +++ b/imp/lib/Mailbox/List.php @@ -0,0 +1,741 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/gpl.html GPL + * @package IMP + */ +class IMP_Mailbox_List implements Countable, Serializable +{ + /* Serialized version. */ + const VERSION = 1; + + /** + * Has the internal message list changed? + * + * @var boolean + */ + public $changed = false; + + /** + * The mailbox to work with. + * + * @var string + */ + protected $_mailbox; + + /** + * Is this a search malbox? + * + * @var boolean + */ + protected $_searchmbox; + + /** + * The list of additional variables to serialize. + * + * @var array + */ + protected $_slist = array(); + + /** + * The array of sorted indices. + * + * @var array + */ + protected $_sorted = null; + + /** + * The mailboxes corresponding to the sorted indices list. + * If empty, uses $_mailbox. + * + * @var array + */ + protected $_sortedMbox = array(); + + /** + * The Horde_Imap_Client_Thread object for the mailbox. + * + * @var Horde_Imap_Client_Thread + */ + protected $_threadob = null; + + /** + * Constructor. + * + * @param string $mailbox The mailbox to work with. + */ + public function __construct($mailbox) + { + $this->_mailbox = $mailbox; + $this->_searchmbox = $GLOBALS['injector']->getInstance('IMP_Search')->isSearchMbox($mailbox); + } + + /** + * Build the array of message information. + * + * @param array $msgnum An array of message sequence numbers. + * @param array $options Additional options: + *
+     * 'headers' - (boolean) Return info on the non-envelope headers
+     *             'Importance', 'List-Post', and 'X-Priority'.
+     *             DEFAULT: false (only envelope headers returned)
+     * 'preview' - (mixed) Include preview information?  If empty, add no
+     *                     preview information. If 1, uses value from prefs.
+     *                     If 2, forces addition of preview info.
+     *                     DEFAULT: No preview information.
+     * 'structure' - (boolean) Get structure information from server.
+     *               Contained in the 'strucutre' entry.
+     *               DEFAULT: false
+     * 
+ * + * @return array An array with the following keys: + *
+     * 'overview' - (array) The overview information. Contains the following:
+     *              'envelope' - (array) Envelope information returned from
+     *                           the IMAP server. See
+     *                           Horde_Imap_Client::fetch() for format.
+     *              'flags' - (array) The list of IMAP flags returned from
+     *                        the server. See Horde_Imap_Client::fetch() for
+     *                        the format.
+     *              'headers' - (array) Any headers requested in
+     *                          $options['headers']. Horde_Mime_Headers objects
+     *                          are returned.  See Horde_Imap_Client::fetch()
+     *                          for the format.
+     *              'mailbox' - (string) The mailbox containing the message.
+     *              'preview' - (string) If requested in $options['preview'],
+     *                          the preview text.
+     *              'previewcut'- (boolean) Has the preview text been cut?
+     *              'size' - (integer) The size of the message in bytes.
+     *              'structure'- (array) The structure of the message. Only
+     *                           set if $options['structure'] is true. See
+     *                           Horde_Imap_Client::fetch() for format.
+     *              'uid' - (string) The unique ID of the message.
+     *
+     * 'uids' - (IMP_Indices) An indices object.
+     * 
+ */ + public function getMailboxArray($msgnum, $options = array()) + { + $this->_buildMailbox(); + + $overview = $to_process = $uids = array(); + + /* Build the list of mailboxes and messages. */ + foreach ($msgnum as $i) { + /* Make sure that the index is actually in the slice of messages + we're looking at. If we're hiding deleted messages, for + example, there may be gaps here. */ + if (isset($this->_sorted[$i - 1])) { + $mboxname = ($this->_searchmbox) ? $this->_sortedMbox[$i - 1] : $this->_mailbox; + + // $uids - KEY: UID, VALUE: sequence number + $to_process[$mboxname][$this->_sorted[$i - 1]] = $i; + } + } + + $fetch_criteria = array( + Horde_Imap_Client::FETCH_ENVELOPE => true, + Horde_Imap_Client::FETCH_FLAGS => true, + Horde_Imap_Client::FETCH_SIZE => true, + Horde_Imap_Client::FETCH_UID => true, + ); + + if (!empty($options['headers'])) { + $fetch_criteria[Horde_Imap_Client::FETCH_HEADERS] = array(array('cache' => true, 'headers' => array('importance', 'list-post', 'x-priority'), 'label' => 'imp', 'parse' => true, 'peek' => true)); + } + + if (!empty($options['structure'])) { + $fetch_criteria[Horde_Imap_Client::FETCH_STRUCTURE] = array('parse' => true); + } + + $imp_imap = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb(); + + if (empty($options['preview'])) { + $cache = null; + $options['preview'] = 0; + } else { + $cache = $imp_imap->getCache(); + } + + /* Retrieve information from each mailbox. */ + foreach ($to_process as $mbox => $ids) { + try { + $fetch_res = $imp_imap->fetch($mbox, $fetch_criteria, array('ids' => array_keys($ids))); + + if ($options['preview']) { + $preview_info = $tostore = array(); + if ($cache) { + try { + $preview_info = $cache->get($mbox, array_keys($ids), array('IMPpreview', 'IMPpreviewc')); + } catch (Horde_Imap_Client_Exception $e) {} + } + } + + foreach (array_keys($ids) as $k) { + $v = $fetch_res[$k]; + + $v['mailbox'] = $mbox; + if (isset($v['headers']['imp'])) { + $v['headers'] = $v['headers']['imp']; + } + + if (($options['preview'] === 2) || + (($options['preview'] === 1) && + (!$GLOBALS['prefs']->getValue('preview_show_unread') || + !in_array('\\seen', $v['flags'])))) { + if (empty($preview_info[$k])) { + try { + $imp_contents = $GLOBALS['injector']->getInstance('IMP_Contents')->getOb(new IMP_Indices($mbox, $k)); + $prev = $imp_contents->generatePreview(); + $preview_info[$k] = array('IMPpreview' => $prev['text'], 'IMPpreviewc' => $prev['cut']); + if (!is_null($cache)) { + $tostore[$k] = $preview_info[$k]; + } + } catch (Exception $e) { + $preview_info[$k] = array('IMPpreview' => '', 'IMPpreviewc' => false); + } + } + + $v['preview'] = $preview_info[$k]['IMPpreview']; + $v['previewcut'] = $preview_info[$k]['IMPpreviewc']; + } + + $overview[] = $v; + } + + $uids[$mbox] = array_keys($fetch_res); + + if (!is_null($cache) && !empty($tostore)) { + $status = $imp_imap->status($mbox, Horde_Imap_Client::STATUS_UIDVALIDITY); + $cache->set($mbox, $tostore, $status['uidvalidity']); + } + } catch (Horde_Imap_Client_Exception $e) {} + } + + return array( + 'overview' => $overview, + 'uids' => new IMP_Indices($uids) + ); + } + + /** + * Returns true if the mailbox data has been built. + * + * @return boolean True if the mailbox has been built. + */ + public function isBuilt() + { + return !is_null($this->_sorted); + } + + /** + * Builds the sorted list of messages in the mailbox. + */ + protected function _buildMailbox() + { + if ($this->isBuilt()) { + return; + } + + $this->changed = true; + $this->_sorted = $this->_sortedMbox = array(); + $query = null; + + if ($this->_searchmbox) { + if (IMP::hideDeletedMsgs($this->_mailbox)) { + $query = new Horde_Imap_Client_Search_Query(); + $query->flag('\\deleted', false); + } + + try { + foreach ($GLOBALS['injector']->getInstance('IMP_Search')->runSearch($query, $this->_mailbox) as $mbox => $idx) { + $this->_sorted[] = $idx; + $this->_sortedMbox[] = $mbox; + } + } catch (Horde_Imap_Client_Exception $e) { + $GLOBALS['notification']->push(_("Mailbox listing failed") . ': ' . $e->getMessage(), 'horde.error'); + } + } else { + $sortpref = IMP::getSort($this->_mailbox, true); + if ($sortpref['by'] == Horde_Imap_Client::SORT_THREAD) { + $this->_threadob = null; + $threadob = $this->getThreadOb(); + $this->_sorted = $threadob->messageList((bool)$sortpref['dir']); + } else { + if (($_SESSION['imp']['protocol'] != 'pop') && + IMP::hideDeletedMsgs($this->_mailbox)) { + $query = new Horde_Imap_Client_Search_Query(); + $query->flag('\\deleted', false); + } + try { + $res = $GLOBALS['injector']->getInstance('IMP_Search')->imapSearch($this->_mailbox, $query, array('sort' => array($sortpref['by']), 'reverse' => (bool)$sortpref['dir'])); + $this->_sorted = $res['sort']; + } catch (Horde_Imap_Client_Exception $e) { + $GLOBALS['notification']->push(_("Mailbox listing failed") . ': ' . $e->getMessage(), 'horde.error'); + } + } + } + } + + /** + * Get the list of new messages in the mailbox (IMAP RECENT flag, with + * UNDELETED if we're hiding deleted messages). + * + * @param integer $results A Horde_Imap_Client::SORT_* constant that + * indicates the desired return type. + * @param boolean $uid Return UIDs instead of sequence numbers (for + * $results queries that return message lists). + * + * @return mixed Whatever is requested in $results. + */ + public function newMessages($results, $uid = false) + { + return $this->_msgFlagSearch('recent', $results, $uid); + } + + /** + * Get the list of unseen messages in the mailbox (IMAP UNSEEN flag, with + * UNDELETED if we're hiding deleted messages). + * + * @param integer $results A Horde_Imap_Client::SORT_RESULTS_* constant + * that indicates the desired return type. + * @param boolean $uid Return UIDs instead of sequence numbers (for + * $results queries that return message lists). + * + * @return mixed Whatever is requested in $results. + */ + public function unseenMessages($results, $uid = false) + { + return $this->_msgFlagSearch('unseen', $results, $uid); + } + + /** + * Do a search on a mailbox in the most efficient way available. + * + * @param string $type The search type - either 'recent' or 'unseen'. + * @param integer $results A Horde_Imap_Client::SORT_RESULTS_* constant + * that indicates the desired return type. + * @param boolean $uid Return UIDs instead of sequence numbers (for + * $results queries that return message lists). + * + * @return mixed Whatever is requested in $results. + */ + protected function _msgFlagSearch($type, $results, $uid) + { + $count = ($results == Horde_Imap_Client::SORT_RESULTS_COUNT); + + if ($this->_searchmbox || empty($this->_sorted)) { + if ($count && + $this->_searchmbox && + ($type == 'unseen') && + $GLOBALS['injector']->getInstance('IMP_Search')->isVinbox($this->_mailbox)) { + return count($this); + } + + return $count ? 0 : array(); + } + + $criteria = new Horde_Imap_Client_Search_Query(); + $imp_imap = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb(); + + if (IMP::hideDeletedMsgs($this->_mailbox)) { + $criteria->flag('\\deleted', false); + } elseif ($count) { + try { + $status_res = $imp_imap->status($this->_mailbox, $type == 'recent' ? Horde_Imap_Client::STATUS_RECENT : Horde_Imap_Client::STATUS_UNSEEN); + return $status_res[$type]; + } catch (Horde_Imap_Client_Exception $e) { + return 0; + } + } + + if ($type == 'recent') { + $criteria->flag('\\recent', true); + } else { + $criteria->flag('\\seen', false); + } + + try { + $res = $imp_imap->search($this->_mailbox, $criteria, array('results' => array($results), 'sequence' => !$uid)); + return $count ? $res['count'] : $res; + } catch (Horde_Imap_Client_Exception $e) { + return $count ? 0 : array(); + } + } + + /** + * Using the preferences and the current mailbox, determines the messages + * to view on the current page. + * + * @param integer $page The page number currently being displayed. + * @param integer $start The starting message number. + * + * @return array An array with the following fields: + *
+     * 'anymsg' - (boolean) Are there any messages at all in mailbox? E.g. If
+     *            'msgcount' is 0, there may still be hidden deleted messages.
+     * 'begin' - (integer) The beginning message sequence number of the page.
+     * 'end' - (integer) The ending message sequence number of the page.
+     * 'index' - (integer) The index of the starting message.
+     * 'msgcount' - (integer) The number of viewable messages in the current
+     *              mailbox.
+     * 'page' - (integer) The current page number.
+     * 'pagecount' - (integer) The number of pages in this mailbox.
+     * 
+ */ + public function buildMailboxPage($page = 0, $start = 0, $opts = array()) + { + $this->_buildMailbox(); + + $ret = array('msgcount' => count($this->_sorted)); + + $page_size = $GLOBALS['prefs']->getValue('max_msgs'); + + if ($ret['msgcount'] > $page_size) { + $ret['pagecount'] = ceil($ret['msgcount'] / $page_size); + + /* Determine which page to display. */ + if (empty($page) || strcspn($page, '0123456789')) { + if (!empty($start)) { + /* Messages set this when returning to a mailbox. */ + $page = ceil($start / $page_size); + } else { + /* Search for the last visited page first. */ + if (isset($_SESSION['imp']['cache']['mbox_page'][$this->_mailbox])) { + $page = $_SESSION['imp']['cache']['mbox_page'][$this->_mailbox]; + } elseif ($this->_searchmbox) { + $page = 1; + } else { + $page = ceil($this->mailboxStart($ret['msgcount']) / $page_size); + } + } + } + + /* Make sure we're not past the end or before the beginning, and + that we have an integer value. */ + $ret['page'] = intval($page); + if ($ret['page'] > $ret['pagecount']) { + $ret['page'] = $ret['pagecount']; + } elseif ($ret['page'] < 1) { + $ret['page'] = 1; + } + + $ret['begin'] = (($ret['page'] - 1) * $page_size) + 1; + $ret['end'] = $ret['begin'] + $page_size - 1; + if ($ret['end'] > $ret['msgcount']) { + $ret['end'] = $ret['msgcount']; + } + } else { + $ret['begin'] = 1; + $ret['end'] = $ret['msgcount']; + $ret['page'] = 1; + $ret['pagecount'] = 1; + } + + $ret['index'] = $ret['begin'] - 1; + + /* If there are no viewable messages, check for deleted messages in + the mailbox. */ + $ret['anymsg'] = true; + if (!$ret['msgcount'] && !$this->_searchmbox) { + try { + $status = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb()->status($this->_mailbox, Horde_Imap_Client::STATUS_MESSAGES); + $ret['anymsg'] = (bool)$status['messages']; + } catch (Horde_Imap_Client_Exception $e) { + $ret['anymsg'] = false; + } + } + + /* Store the page value now. */ + $_SESSION['imp']['cache']['mbox_page'][$this->_mailbox] = $ret['page']; + + return $ret; + } + + /** + * Determines the sequence number of the first message to display, based + * on the user's preferences. + * + * @param integer $total The total number of messages in the mailbox. + * + * @return integer The sequence number in the sorted mailbox. + */ + public function mailboxStart($total) + { + if ($this->_searchmbox) { + return 1; + } + + switch ($GLOBALS['prefs']->getValue('mailbox_start')) { + case IMP::MAILBOX_START_FIRSTPAGE: + return 1; + + case IMP::MAILBOX_START_LASTPAGE: + return $total; + + case IMP::MAILBOX_START_FIRSTUNSEEN: + $sortpref = IMP::getSort($this->_mailbox); + + /* Optimization: if sorting by sequence then first unseen + * information is returned via a SELECT/EXAMINE call. */ + if ($sortpref['by'] == Horde_Imap_Client::SORT_SEQUENCE) { + try { + $res = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb()->status($this->_mailbox, Horde_Imap_Client::STATUS_FIRSTUNSEEN); + if (!is_null($res['firstunseen'])) { + return $res['firstunseen']; + } + } catch (Horde_Imap_Client_Exception $e) {} + + return 1; + } + + $unseen_msgs = $this->unseenMessages(Horde_Imap_Client::SORT_RESULTS_MIN, true); + return empty($unseen_msgs['min']) + ? 1 + : ($this->getArrayIndex($unseen_msgs['min']) + 1); + + case IMP::MAILBOX_START_LASTUNSEEN: + $unseen_msgs = $this->unseenMessages(Horde_Imap_Client::SORT_RESULTS_MAX, true); + return empty($unseen_msgs['max']) + ? 1 + : ($this->getArrayIndex($unseen_msgs['max']) + 1); + } + } + + /** + * Get the Horde_Imap_Client_Thread object for the current mailbox. + * + * @return Horde_Imap_Client_Thread The thread object for the current + * mailbox. + */ + public function getThreadOb() + { + if (is_null($this->_threadob)) { + try { + $this->_threadob = $GLOBALS['injector']->getInstance('IMP_Imap')->getOb()->thread($this->_mailbox, array('criteria' => $_SESSION['imp']['imap']['thread'])); + } catch (Horde_Imap_Client_Exception $e) { + $GLOBALS['notification']->push($e); + return new Horde_Imap_Client_Thread(array(), 'uid'); + } + } + + return $this->_threadob; + } + + /** + * Determines if a rebuild is needed, and, if necessary, performs + * the rebuild. + * + * @param boolean $force Force a rebuild? + */ + protected function _rebuild($force = false) + { + if ($force) { + $this->_sorted = null; + $this->_buildMailbox(); + } + } + + /** + * Returns the array index of the given message UID. + * + * @param integer $uid The message UID. + * @param integer $mbox The message mailbox (defaults to the current + * mailbox). + * + * @return mixed The array index of the location of the message UID in + * the current mailbox. Returns null if not found. + */ + public function getArrayIndex($uid, $mbox = null) + { + $aindex = null; + + $this->_buildMailbox(); + + if ($this->_searchmbox) { + if (is_null($mbox)) { + $mbox = IMP::$thismailbox; + } + + /* Need to compare both mbox name and message UID to obtain the + * correct array index since there may be duplicate UIDs. */ + foreach (array_keys($this->_sorted, $uid) as $key) { + if ($this->_sortedMbox[$key] == $mbox) { + return $key; + } + } + } else { + /* array_search() returns false on no result. We will set an + * unsuccessful result to NULL. */ + if (($aindex = array_search($uid, $this->_sorted)) === false) { + $aindex = null; + } + } + + return $aindex; + } + + /** + * Returns a raw sorted list of the mailbox. + * + * @return array An array with two keys: 's' = sorted UIDS list, 'm' = + * sorted mailboxes list. + */ + public function getSortedList() + { + $this->_buildMailbox(); + + /* For exterior use, the array needs to begin numbering at 1. */ + $s = $this->_sorted; + array_unshift($s, 0); + unset($s[0]); + $m = $this->_sortedMbox; + array_unshift($m, 0); + unset($m[0]); + + return array('s' => $s, 'm' => $m); + } + + /** + * Returns the current sorted array without the given messages. + * + * @param mixed $indices An IMP_Indices object or true to remove all + * messages in the mailbox. + * + * @return boolean True if the message was removed from the mailbox. + */ + public function removeMsgs($indices) + { + if ($indices === true) { + $this->_rebuild(true); + return false; + } + + if (!count($indices)) { + return false; + } + + /* Remove the current entry and recalculate the range. */ + foreach ($indices as $mbox => $uid) { + $val = $this->getArrayIndex($uid, $mbox); + unset($this->_sorted[$val]); + if ($this->_searchmbox) { + unset($this->_sortedMbox[$val]); + } + } + + $this->changed = true; + $this->_sorted = array_values($this->_sorted); + if ($this->_searchmbox) { + $this->_sortedMbox = array_values($this->_sortedMbox); + } + $this->_threadob = null; + + return true; + } + + /** + * Returns a unique identifier for the current mailbox status. + * + * This cache ID is guaranteed to change if messages are added/deleted from + * the mailbox. Additionally, if CONDSTORE is available on the remote + * IMAP server, this ID will change if flag information changes. + * + * @return string The cache ID string, which will change when the + * composition of the mailbox changes. + */ + public function getCacheID() + { + if (!$this->_searchmbox) { + $sortpref = IMP::getSort($this->_mailbox, true); + try { + return $GLOBALS['injector']->getInstance('IMP_Imap')->getOb()->getCacheId($this->_mailbox, array($sortpref['by'], $sortpref['dir'])); + } catch (Horde_Imap_Client_Exception $e) {} + } + + return strval(new Horde_Support_Randomid()); + } + + /* Countable methods. */ + + /** + * Returns the current message count of the mailbox. + * + * @return integer The mailbox message count. + */ + public function count() + { + $this->_buildMailbox(); + return count($this->_sorted); + } + + /* Serializable methods. */ + + /** + * Serialization. + * + * @return string Serialized data. + */ + public function serialize() + { + $data = array( + 'm' => $this->_mailbox, + 's' => $this->_searchmbox, + 'v' => self::VERSION + ); + + if (!is_null($this->_sorted)) { + $data['so'] = $this->_sorted; + if (!empty($this->_sortedmbox)) { + $data['som'] = $this->_sortedmbox; + } + } + + foreach ($this->_slist as $val) { + $data[$val] = $this->$val; + } + + return json_encode($data); + } + + /** + * Unserialization. + * + * @param string $data Serialized data. + * + * @throws Exception + */ + public function unserialize($data) + { + $data = json_decode($data, true); + if (!is_array($data) || + !isset($data['v']) || + ($data['v'] != self::VERSION)) { + throw new Exception('Cache version change'); + } + + $this->_mailbox = $data['m']; + $this->_searchmbox = $data['s']; + + if (isset($data['so'])) { + $this->_sorted = $data['so']; + if (isset($data['som'])) { + $this->_sortedmbox = $data['som']; + } + } + + foreach ($this->_slist as $val) { + $this->$val = $data[$val]; + } + } + +} diff --git a/imp/lib/Mailbox/List/Track.php b/imp/lib/Mailbox/List/Track.php new file mode 100644 index 000000000..761c836a8 --- /dev/null +++ b/imp/lib/Mailbox/List/Track.php @@ -0,0 +1,155 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/gpl.html GPL + * @package IMP + */ +class IMP_Mailbox_List_Track extends IMP_Mailbox_List +{ + /** + * The location in the sorted array we are at. + * + * @var integer + */ + protected $_index = null; + + /** + * The list of additional variables to serialize. + * + * @var array + */ + protected $_slist = array('_index'); + + /** + * Returns the current message array index. If the array index has + * run off the end of the message array, will return the last index. + * + * @return integer The message array index. + */ + public function getMessageIndex() + { + return $this->isValidIndex() + ? ($this->_index + 1) + : 1; + } + + /** + * Checks to see if the current index is valid. + * + * @return boolean True if index is valid, false if not. + */ + public function isValidIndex() + { + return !is_null($this->_index); + } + + /** + * Returns IMAP mbox/UID information on a message. + * + * @param integer $offset The offset from the current message. + * + * @return array Array with the following entries: + *
+     * 'mailbox' - (string) The mailbox.
+     * 'uid' - (integer) The message UID.
+     * 
+ */ + public function getIMAPIndex($offset = 0) + { + $index = $this->_index + $offset; + + return isset($this->_sorted[$index]) + ? array( + 'mailbox' => ($this->_searchmbox ? $this->_sortedMbox[$index] : $this->_mailbox), + 'uid' => $this->_sorted[$index] + ) + : array(); + } + + /** + * Using the preferences and the current mailbox, determines the messages + * to view on the current page. + * + * @see parent::buildMailboxPage() + */ + public function buildMailboxPage($page = 0, $start = 0, $opts = array()) + { + $ret = parent::buildMailboxPage($page, $start, $opts); + + if (!$this->_searchmbox) { + $ret['index'] = $this->_index; + } + + return $ret; + } + + /** + * Updates the message array index. + * + * @param mixed $data If an integer, the number of messages to increase + * array index by. If an indices object, sets array + * index to the index value. + */ + public function setIndex($data) + { + if ($data instanceof IMP_Indices) { + list($mailbox, $uid) = $data->getSingle(); + $this->_index = $this->getArrayIndex($uid, $mailbox); + if (empty($this->_index)) { + $this->_rebuild(true); + $this->_index = $this->getArrayIndex($uid, $mailbox); + } + } elseif (!is_null($this->_index)) { + $index = $this->_index += $data; + if (isset($this->_sorted[$this->_index])) { + $this->_rebuild(); + } else { + $this->_rebuild(true); + $this->_index = isset($this->_sorted[$index]) + ? $index + : null; + } + } + } + + /** + * Determines if a rebuild is needed, and, if necessary, performs + * the rebuild. + * + * @param boolean $force Force a rebuild? + */ + protected function _rebuild($force = false) + { + if ($force || + (!is_null($this->_index) && !$this->getIMAPIndex(1))) { + $this->_sorted = null; + $this->_buildMailbox(); + } + } + + /** + * Returns the current sorted array without the given messages. + * + * @param mixed $indices An IMP_Indices object or true to remove all + * messages in the mailbox. + */ + public function removeMsgs($indices) + { + if (parent::removeMsgs($indices)) { + /* Update the current array index to its new position in the + * message array. */ + $this->setIndex(0); + } + } + +} diff --git a/imp/lib/Message.php b/imp/lib/Message.php index 7b4321e31..4093d78c1 100644 --- a/imp/lib/Message.php +++ b/imp/lib/Message.php @@ -50,7 +50,7 @@ class IMP_Message *
      * 'create' - (boolean) Should the target mailbox be created?
      *            DEFAULT: false
-     * 'mailboxob' - (IMP_Mailbox) Update this mailbox object.
+     * 'mailboxob' - (IMP_Mailbox_List) Update this mailbox object.
      *               DEFAULT: No update.
      * 
* @@ -167,7 +167,7 @@ class IMP_Message *
      * 'keeplog' - (boolean) Should any history information of the message be
      *             kept?
-     * 'mailboxob' - (IMP_Mailbox) Update this mailbox object.
+     * 'mailboxob' - (IMP_Mailbox_List) Update this mailbox object.
      *               DEFAULT: No update.
      * 'nuke' - (boolean) Override user preferences and nuke (i.e. permanently
      *          delete) the messages instead?
@@ -472,7 +472,7 @@ class IMP_Message
      *                              parts are stripped if null.
      * @param array $opts           Additional options:
      * 
-     * 'mailboxob' - (IMP_Mailbox) Update this mailbox object.
+     * 'mailboxob' - (IMP_Mailbox_List) Update this mailbox object.
      *               DEFAULT: No update.
      * 
* @@ -704,7 +704,7 @@ class IMP_Message *
      * 'list' - (boolean) Return a list of messages expunged.
      *          DEFAULT: false
-     * 'mailboxob' - (IMP_Mailbox) Update this mailbox object.
+     * 'mailboxob' - (IMP_Mailbox_List) Update this mailbox object.
      *               DEFAULT: No update.
      * 
* diff --git a/imp/lib/Spam.php b/imp/lib/Spam.php index 545ec1fd9..c9e3cb23b 100644 --- a/imp/lib/Spam.php +++ b/imp/lib/Spam.php @@ -23,7 +23,7 @@ class IMP_Spam * @param string $action Either 'spam' or 'notspam'. * @param array $opts Additional options: *
-     * 'mailboxob' - (IMP_Mailbox) Update this mailbox object.
+     * 'mailboxob' - (IMP_Mailbox_List) Update this mailbox list object.
      *               DEFAULT: No update.
      * 'noaction' - (boolean) Don't perform any action after reporting?
      *              DEFAULT: false
diff --git a/imp/lib/Views/ListMessages.php b/imp/lib/Views/ListMessages.php
index 6ca9f8eed..648b890e5 100644
--- a/imp/lib/Views/ListMessages.php
+++ b/imp/lib/Views/ListMessages.php
@@ -108,7 +108,7 @@ class IMP_Views_ListMessages
         }
 
         /* Generate the sorted mailbox list now. */
-        $imp_mailbox = $GLOBALS['injector']->getInstance('IMP_Mailbox')->getOb($mbox);
+        $imp_mailbox = $GLOBALS['injector']->getInstance('IMP_Mailbox_List')->getList($mbox);
         $sorted_list = $imp_mailbox->getSortedList();
         $msgcount = count($sorted_list['s']);
 
@@ -379,11 +379,11 @@ class IMP_Views_ListMessages
     /**
      * Obtains IMAP overview data for a given set of message UIDs.
      *
-     * @param IMP_Mailbox $imp_mailbox  An IMP_Mailbox:: object.
-     * @param string $folder            The current folder.
-     * @param array $msglist            The list of message sequence numbers
-     *                                  to process.
-     * @param boolean $search           Is this a search mbox?
+     * @param IMP_Mailbox_List $imp_mailbox  The mailbox list  object.
+     * @param string $folder                 The current folder.
+     * @param array $msglist                 The list of message sequence
+     *                                       numbers to process.
+     * @param boolean $search                Is this a search mbox?
      *
      * @return array  TODO
      * @throws Horde_Exception
diff --git a/imp/mailbox-mimp.php b/imp/mailbox-mimp.php
index 0a982d1fd..0163b5e93 100644
--- a/imp/mailbox-mimp.php
+++ b/imp/mailbox-mimp.php
@@ -127,7 +127,7 @@ case 'rs':
 }
 
 /* Build the list of messages in the mailbox. */
-$imp_mailbox = $injector->getInstance('IMP_Mailbox')->getOb(IMP::$mailbox);
+$imp_mailbox = $injector->getInstance('IMP_Mailbox_List')->getList(IMP::$mailbox);
 $pageOb = $imp_mailbox->buildMailboxPage($vars->p, $vars->s);
 
 /* Generate page title. */
diff --git a/imp/mailbox.php b/imp/mailbox.php
index 4d57ac812..634b3686f 100644
--- a/imp/mailbox.php
+++ b/imp/mailbox.php
@@ -225,7 +225,7 @@ if ($conf['user']['allow_folders']) {
 }
 
 /* Build the list of messages in the mailbox. */
-$imp_mailbox = $injector->getInstance('IMP_Mailbox')->getOb(IMP::$mailbox);
+$imp_mailbox = $injector->getInstance('IMP_Mailbox_List')->getList(IMP::$mailbox);
 $pageOb = $imp_mailbox->buildMailboxPage($vars->page, $start);
 $show_preview = $prefs->getValue('preview_enabled');
 
diff --git a/imp/message-mimp.php b/imp/message-mimp.php
index 328857af9..e82db7e5a 100644
--- a/imp/message-mimp.php
+++ b/imp/message-mimp.php
@@ -29,7 +29,7 @@ Horde_Registry::appInit('imp', array(
 $vars = Horde_Variables::getDefaultVariables();
 
 /* Make sure we have a valid index. */
-$imp_mailbox = $injector->getInstance('IMP_Mailbox')->getOb(IMP::$mailbox, new IMP_Indices(IMP::$thismailbox, IMP::$uid));
+$imp_mailbox = $injector->getInstance('IMP_Mailbox_List')->getListTrack(IMP::$mailbox, new IMP_Indices(IMP::$thismailbox, IMP::$uid));
 if (!$imp_mailbox->isValidIndex()) {
     IMP::generateIMPUrl('mailbox-mimp.php', IMP::$mailbox)->add('a', 'm')->redirect();
 }
diff --git a/imp/message.php b/imp/message.php
index 9afac4098..78189ba31 100644
--- a/imp/message.php
+++ b/imp/message.php
@@ -34,7 +34,7 @@ if (!($search_mbox = $injector->getInstance('IMP_Search')->isSearchMbox(IMP::$ma
 }
 
 /* Make sure we have a valid index. */
-$imp_mailbox = $injector->getInstance('IMP_Mailbox')->getOb(IMP::$mailbox, new IMP_Indices(IMP::$thismailbox, IMP::$uid));
+$imp_mailbox = $injector->getInstance('IMP_Mailbox_List')->getListTrack(IMP::$mailbox, new IMP_Indices(IMP::$thismailbox, IMP::$uid));
 if (!$imp_mailbox->isValidIndex()) {
     _returnToMailbox(null, 'message_missing');
     require IMP_BASE . '/mailbox.php';
diff --git a/imp/rss.php b/imp/rss.php
index f81cd809d..6107ce2ab 100644
--- a/imp/rss.php
+++ b/imp/rss.php
@@ -46,7 +46,7 @@ if (!empty($request)) {
     $new_mail = (isset($request_parts[1]) && ($request_parts[1] === 'new'));
 }
 
-$imp_mailbox = $injector->getInstance('IMP_Mailbox')->getOb($mailbox);
+$imp_mailbox = $injector->getInstance('IMP_Mailbox_List')->getList($mailbox);
 $imp_search = $injector->getInstance('IMP_Search');
 
 /* Obtain some information describing the mailbox state. */
diff --git a/imp/thread.php b/imp/thread.php
index 1f5ccf513..ea4e54141 100644
--- a/imp/thread.php
+++ b/imp/thread.php
@@ -28,7 +28,7 @@ $mode = $vars->mode
     : 'thread';
 
 $imp_imap = $injector->getInstance('IMP_Imap')->getOb();
-$imp_mailbox = $injector->getInstance('IMP_Mailbox')->getOb(IMP::$mailbox, new IMP_Indices(IMP::$thismailbox, IMP::$uid));
+$imp_mailbox = $injector->getInstance('IMP_Mailbox_List')->getListTrack(IMP::$mailbox, new IMP_Indices(IMP::$thismailbox, IMP::$uid));
 
 $error = false;
 if ($mode == 'thread') {