1<?php 2/** 3 * Copyright 2002-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING for license information (GPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/gpl. 7 * 8 * @category Horde 9 * @copyright 2002-2017 Horde LLC 10 * @license http://www.horde.org/licenses/gpl GPL 11 * @package IMP 12 */ 13 14/** 15 * This class contains code related to generating and handling a mailbox 16 * message list. 17 * 18 * @author Michael Slusarz <slusarz@horde.org> 19 * @category Horde 20 * @copyright 2002-2017 Horde LLC 21 * @license http://www.horde.org/licenses/gpl GPL 22 * @package IMP 23 */ 24class IMP_Mailbox_List 25implements ArrayAccess, Countable, Iterator, Serializable 26{ 27 /* The UID count at which the sorted list will undergo UID compression 28 * when being serialized. */ 29 const SERIALIZE_LIMIT = 500; 30 31 /** 32 * Has the internal message list changed? 33 * 34 * @var boolean 35 */ 36 public $changed = false; 37 38 /** 39 * Max assigned browser-UID. 40 * 41 * @var integer 42 */ 43 protected $_buidmax = 0; 44 45 /** 46 * Mapping of browser-UIDs to UIDs. 47 * 48 * @var array 49 */ 50 protected $_buids = array(); 51 52 /** 53 * The IMAP cache ID of the mailbox. 54 * 55 * @var string 56 */ 57 protected $_cacheid = null; 58 59 /** 60 * The location in the sorted array we are at. 61 * 62 * @var integer 63 */ 64 protected $_index = null; 65 66 /** 67 * The mailbox to work with. 68 * 69 * @var IMP_Mailbox 70 */ 71 protected $_mailbox; 72 73 /** 74 * The array of sorted indices. 75 * 76 * @var array 77 */ 78 protected $_sorted = null; 79 80 /** 81 * The thread object representation(s) for the mailbox. 82 * 83 * @var array 84 */ 85 protected $_thread = array(); 86 87 /** 88 * The thread tree UI cached data. 89 * 90 * @var array 91 */ 92 protected $_threadui = array(); 93 94 /** 95 * Constructor. 96 * 97 * @param string $mbox The mailbox to work with. 98 */ 99 public function __construct($mbox) 100 { 101 $this->_mailbox = IMP_Mailbox::get($mbox); 102 } 103 104 /** 105 * Build the array of message information. 106 * 107 * @param array $msgnum An array of index numbers. 108 * @param array $options Additional options: 109 * - headers: (boolean) Return info on the non-envelope headers 110 * 'Importance', 'List-Post', and 'X-Priority'. 111 * DEFAULT: false (only envelope headers returned) 112 * - preview: (mixed) Include preview information? If empty, add no 113 * preview information. If 1, uses value from prefs. 114 * If 2, forces addition of preview info. 115 * DEFAULT: No preview information. 116 * - type: (boolean) Return info on the MIME Content-Type of the base 117 * message part ('Content-Type' header). 118 * DEFAULT: false 119 * 120 * @return array An array with the following keys: 121 * - overview: (array) The overview information. Contains the following: 122 * - envelope: (Horde_Imap_Client_Data_Envelope) Envelope information 123 * returned from the IMAP server. 124 * - flags: (array) The list of IMAP flags returned from the server. 125 * - headers: (array) Horde_Mime_Headers objects containing header data 126 * if either $options['headers'] or $options['type'] are 127 * true. 128 * - idx: (integer) Array index of this message. 129 * - mailbox: (string) The mailbox containing the message. 130 * - preview: (string) If requested in $options['preview'], the preview 131 * text. 132 * - previewcut: (boolean) Has the preview text been cut? 133 * - size: (integer) The size of the message in bytes. 134 * - uid: (string) The unique ID of the message. 135 * - uids: (IMP_Indices) An indices object. 136 */ 137 public function getMailboxArray($msgnum, $options = array()) 138 { 139 $this->_buildMailbox(); 140 141 $headers = $overview = $to_process = $uids = array(); 142 143 /* Build the list of mailboxes and messages. */ 144 foreach ($msgnum as $i) { 145 /* Make sure that the index is actually in the slice of messages 146 we're looking at. If we're hiding deleted messages, for 147 example, there may be gaps here. */ 148 if (isset($this->_sorted[$i - 1])) { 149 $to_process[strval($this->_getMbox($i - 1))][$i] = $this->_sorted[$i - 1]; 150 } 151 } 152 153 $fetch_query = new Horde_Imap_Client_Fetch_Query(); 154 $fetch_query->envelope(); 155 $fetch_query->flags(); 156 $fetch_query->size(); 157 $fetch_query->uid(); 158 159 if (!empty($options['headers'])) { 160 $headers = array_merge($headers, array( 161 'importance', 162 'list-post', 163 'x-priority' 164 )); 165 } 166 167 if (!empty($options['type'])) { 168 $headers[] = 'content-type'; 169 } 170 171 if (!empty($headers)) { 172 $fetch_query->headers('imp', $headers, array( 173 'cache' => true, 174 'peek' => true 175 )); 176 } 177 178 if (empty($options['preview'])) { 179 $cache = null; 180 $options['preview'] = 0; 181 } else { 182 $cache = $this->_mailbox->imp_imap->getCache(); 183 } 184 185 /* Retrieve information from each mailbox. */ 186 foreach ($to_process as $mbox => $ids) { 187 try { 188 $imp_imap = IMP_Mailbox::get($mbox)->imp_imap; 189 $fetch_res = $imp_imap->fetch($mbox, $fetch_query, array( 190 'ids' => $imp_imap->getIdsOb($ids) 191 )); 192 193 if ($options['preview']) { 194 $preview_info = $tostore = array(); 195 if ($cache) { 196 try { 197 $preview_info = $cache->get($mbox, $ids, array('IMPpreview', 'IMPpreviewc')); 198 } catch (IMP_Imap_Exception $e) {} 199 } 200 } 201 202 $mbox_ids = array(); 203 204 foreach ($ids as $k => $v) { 205 if (!isset($fetch_res[$v])) { 206 continue; 207 } 208 209 $f = $fetch_res[$v]; 210 $uid = $f->getUid(); 211 $v = array( 212 'envelope' => $f->getEnvelope(), 213 'flags' => $f->getFlags(), 214 'headers' => $f->getHeaders('imp', Horde_Imap_Client_Data_Fetch::HEADER_PARSE), 215 'idx' => $k, 216 'mailbox' => $mbox, 217 'size' => $f->getSize(), 218 'uid' => $uid 219 ); 220 221 if (($options['preview'] === 2) || 222 (($options['preview'] === 1) && 223 (!$GLOBALS['prefs']->getValue('preview_show_unread') || 224 !in_array(Horde_Imap_Client::FLAG_SEEN, $v['flags'])))) { 225 if (empty($preview_info[$uid])) { 226 try { 227 $imp_contents = $GLOBALS['injector']->getInstance('IMP_Factory_Contents')->create(new IMP_Indices($mbox, $uid)); 228 $prev = $imp_contents->generatePreview(); 229 $preview_info[$uid] = array( 230 'IMPpreview' => $prev['text'], 231 'IMPpreviewc' => $prev['cut'] 232 ); 233 if (!is_null($cache)) { 234 $tostore[$uid] = $preview_info[$uid]; 235 } 236 } catch (Exception $e) { 237 $preview_info[$uid] = array( 238 'IMPpreview' => '', 239 'IMPpreviewc' => false 240 ); 241 } 242 } 243 244 $v['preview'] = $preview_info[$uid]['IMPpreview']; 245 $v['previewcut'] = $preview_info[$uid]['IMPpreviewc']; 246 } 247 248 $overview[] = $v; 249 $mbox_ids[] = $uid; 250 } 251 252 $uids[$mbox] = $mbox_ids; 253 254 if (!is_null($cache) && !empty($tostore)) { 255 $status = $imp_imap->status($mbox, Horde_Imap_Client::STATUS_UIDVALIDITY); 256 $cache->set($mbox, $tostore, $status['uidvalidity']); 257 } 258 } catch (IMP_Imap_Exception $e) {} 259 } 260 261 return array( 262 'overview' => $overview, 263 'uids' => new IMP_Indices($uids) 264 ); 265 } 266 267 /** 268 * Using the preferences and the current mailbox, determines the messages 269 * to view on the current page (if using a paged view). 270 * 271 * @param integer $page The page number currently being displayed. 272 * @param integer $start The starting message number. 273 * 274 * @return array An array with the following fields: 275 * - anymsg: (boolean) Are there any messages at all in mailbox? E.g. If 276 * 'msgcount' is 0, there may still be hidden deleted messages. 277 * - begin: (integer) The beginning message sequence number of the page. 278 * - end: (integer) The ending message sequence number of the page. 279 * - msgcount: (integer) The number of viewable messages in the current 280 * mailbox. 281 * - page: (integer) The current page number. 282 * - pagecount: (integer) The number of pages in this mailbox. 283 */ 284 public function buildMailboxPage($page = 0, $start = 0) 285 { 286 global $prefs, $session; 287 288 $this->_buildMailbox(); 289 290 $ret = array('msgcount' => count($this->_sorted)); 291 292 $page_size = max($prefs->getValue('max_msgs'), 1); 293 294 if ($ret['msgcount'] > $page_size) { 295 $ret['pagecount'] = ceil($ret['msgcount'] / $page_size); 296 297 /* Determine which page to display. */ 298 if (empty($page) || strcspn($page, '0123456789')) { 299 if (!empty($start)) { 300 /* Messages set this when returning to a mailbox. */ 301 $page = ceil($start / $page_size); 302 } else { 303 /* Search for the last visited page first. */ 304 $page = $session->exists('imp', 'mbox_page/' . $this->_mailbox) 305 ? $session->get('imp', 'mbox_page/' . $this->_mailbox) 306 : ceil($this->mailboxStart($ret['msgcount']) / $page_size); 307 } 308 } 309 310 /* Make sure we're not past the end or before the beginning, and 311 that we have an integer value. */ 312 $ret['page'] = intval($page); 313 if ($ret['page'] > $ret['pagecount']) { 314 $ret['page'] = $ret['pagecount']; 315 } elseif ($ret['page'] < 1) { 316 $ret['page'] = 1; 317 } 318 319 $ret['begin'] = (($ret['page'] - 1) * $page_size) + 1; 320 $ret['end'] = $ret['begin'] + $page_size - 1; 321 if ($ret['end'] > $ret['msgcount']) { 322 $ret['end'] = $ret['msgcount']; 323 } 324 } else { 325 $ret['begin'] = 1; 326 $ret['end'] = $ret['msgcount']; 327 $ret['page'] = 1; 328 $ret['pagecount'] = 1; 329 } 330 331 /* If there are no viewable messages, check for deleted messages in 332 the mailbox. */ 333 $ret['anymsg'] = true; 334 if (!$ret['msgcount'] && !$this->_mailbox->search) { 335 try { 336 $status = $this->_mailbox->imp_imap->status($this->_mailbox, Horde_Imap_Client::STATUS_MESSAGES); 337 $ret['anymsg'] = (bool)$status['messages']; 338 } catch (IMP_Imap_Exception $e) { 339 $ret['anymsg'] = false; 340 } 341 } 342 343 /* Store the page value now. */ 344 $session->set('imp', 'mbox_page/' . $this->_mailbox, $ret['page']); 345 346 return $ret; 347 } 348 349 /** 350 * Returns true if the mailbox data has been built. 351 * 352 * @return boolean True if the mailbox has been built. 353 */ 354 public function isBuilt() 355 { 356 return !is_null($this->_sorted); 357 } 358 359 /** 360 * Builds the sorted list of messages in the mailbox. 361 */ 362 protected function _buildMailbox() 363 { 364 $cacheid = $this->_mailbox->cacheid; 365 366 if ($this->isBuilt() && ($this->_cacheid == $cacheid)) { 367 return; 368 } 369 370 $this->changed = true; 371 $this->_cacheid = $cacheid; 372 $this->_sorted = array(); 373 374 $query_ob = $this->_buildMailboxQuery(); 375 $sortpref = $this->_mailbox->getSort(true); 376 $thread_sort = ($sortpref->sortby == Horde_Imap_Client::SORT_THREAD); 377 378 if ($this->_mailbox->access_search && 379 $this->_mailbox->hideDeletedMsgs()) { 380 $delete_query = new Horde_Imap_Client_Search_Query(); 381 $delete_query->flag(Horde_Imap_Client::FLAG_DELETED, false); 382 383 if (is_null($query_ob)) { 384 $query_ob = array(strval($this->_mailbox) => $delete_query); 385 } else { 386 foreach ($query_ob as $val) { 387 $val->andSearch($delete_query); 388 } 389 } 390 } 391 392 if (is_null($query_ob)) { 393 $query_ob = array(strval($this->_mailbox) => null); 394 } 395 396 if ($thread_sort) { 397 $this->_thread = $this->_threadui = array(); 398 } 399 400 foreach ($query_ob as $mbox => $val) { 401 if ($thread_sort) { 402 $this->_getThread($mbox, $val ? array('search' => $val) : array()); 403 $sorted = $this->_thread[$mbox]->messageList()->ids; 404 if ($sortpref->sortdir) { 405 $sorted = array_reverse($sorted); 406 } 407 } else { 408 $mbox_ob = IMP_Mailbox::get($mbox); 409 if ($mbox_ob->container) { 410 continue; 411 } 412 413 $res = $mbox_ob->imp_imap->search($mbox, $val, array( 414 'sort' => array($sortpref->sortby) 415 )); 416 if ($sortpref->sortdir) { 417 $res['match']->reverse(); 418 } 419 $sorted = $res['match']->ids; 420 } 421 422 $this->_sorted = array_merge($this->_sorted, $sorted); 423 $this->_buildMailboxProcess($mbox, $sorted); 424 } 425 } 426 427 /** 428 */ 429 protected function _buildMailboxQuery() 430 { 431 return null; 432 } 433 434 /** 435 */ 436 protected function _buildMailboxProcess($mbox, $sorted) 437 { 438 } 439 440 /** 441 * Get the list of unseen messages in the mailbox (IMAP UNSEEN flag, with 442 * UNDELETED if we're hiding deleted messages). 443 * 444 * @param integer $results A Horde_Imap_Client::SEARCH_RESULTS_* constant 445 * that indicates the desired return type. 446 * @param array $opts Additional options: 447 * - sort: (array) List of sort criteria to use. 448 * - uids: (boolean) Return UIDs instead of sequence numbers (for 449 * $results queries that return message lists). 450 * DEFAULT: false 451 * 452 * @return mixed Whatever is requested in $results. 453 */ 454 public function unseenMessages($results, array $opts = array()) 455 { 456 $count = ($results == Horde_Imap_Client::SEARCH_RESULTS_COUNT); 457 458 if (empty($this->_sorted)) { 459 return $count ? 0 : array(); 460 } 461 462 $criteria = new Horde_Imap_Client_Search_Query(); 463 $imp_imap = $this->_mailbox->imp_imap; 464 465 if ($this->_mailbox->hideDeletedMsgs()) { 466 $criteria->flag(Horde_Imap_Client::FLAG_DELETED, false); 467 } elseif ($count) { 468 try { 469 $status_res = $imp_imap->status($this->_mailbox, Horde_Imap_Client::STATUS_UNSEEN); 470 return $status_res['unseen']; 471 } catch (IMP_Imap_Exception $e) { 472 return 0; 473 } 474 } 475 476 $criteria->flag(Horde_Imap_Client::FLAG_SEEN, false); 477 478 try { 479 $res = $imp_imap->search($this->_mailbox, $criteria, array( 480 'results' => array($results), 481 'sequence' => empty($opts['uids']), 482 'sort' => empty($opts['sort']) ? null : $opts['sort'] 483 )); 484 return $count ? $res['count'] : $res; 485 } catch (IMP_Imap_Exception $e) { 486 return $count ? 0 : array(); 487 } 488 } 489 490 /** 491 * Determines the sequence number of the first message to display, based 492 * on the user's preferences. 493 * 494 * @param integer $total The total number of messages in the mailbox. 495 * 496 * @return integer The sequence number in the sorted mailbox. 497 */ 498 public function mailboxStart($total) 499 { 500 switch ($GLOBALS['prefs']->getValue('mailbox_start')) { 501 case IMP::MAILBOX_START_FIRSTPAGE: 502 return 1; 503 504 case IMP::MAILBOX_START_LASTPAGE: 505 return $total; 506 507 case IMP::MAILBOX_START_FIRSTUNSEEN: 508 if (!$this->_mailbox->access_sort) { 509 return 1; 510 } 511 512 $sortpref = $this->_mailbox->getSort(); 513 514 /* Optimization: if sorting by sequence then first unseen 515 * information is returned via a SELECT/EXAMINE call. */ 516 if ($sortpref->sortby == Horde_Imap_Client::SORT_SEQUENCE) { 517 try { 518 $res = $this->_mailbox->imp_imap->status($this->_mailbox, Horde_Imap_Client::STATUS_FIRSTUNSEEN | Horde_Imap_Client::STATUS_MESSAGES); 519 if (!is_null($res['firstunseen'])) { 520 return $sortpref->sortdir 521 ? ($res['messages'] - $res['firstunseen'] + 1) 522 : $res['firstunseen']; 523 } 524 } catch (IMP_Imap_Exception $e) {} 525 526 return 1; 527 } 528 529 $unseen_msgs = $this->unseenMessages(Horde_Imap_Client::SEARCH_RESULTS_MIN, array( 530 'sort' => array(Horde_Imap_Client::SORT_DATE), 531 'uids' => true 532 )); 533 return empty($unseen_msgs['min']) 534 ? 1 535 : ($this->getArrayIndex($unseen_msgs['min']) + 1); 536 537 case IMP::MAILBOX_START_LASTUNSEEN: 538 if (!$this->_mailbox->access_sort) { 539 return 1; 540 } 541 542 $unseen_msgs = $this->unseenMessages(Horde_Imap_Client::SEARCH_RESULTS_MAX, array( 543 'sort' => array(Horde_Imap_Client::SORT_DATE), 544 'uids' => true 545 )); 546 return empty($unseen_msgs['max']) 547 ? 1 548 : ($this->getArrayIndex($unseen_msgs['max']) + 1); 549 } 550 } 551 552 /** 553 * Rebuilds/resets the mailbox list. 554 * 555 * @param boolean $reset If true, resets the list instead of rebuilding. 556 */ 557 public function rebuild($reset = false) 558 { 559 $this->_cacheid = $this->_sorted = null; 560 561 if ($reset) { 562 $this->_buidmax = 0; 563 $this->_buids = array(); 564 $this->changed = true; 565 } else { 566 $this->_buildMailbox(); 567 } 568 } 569 570 /** 571 * Returns the array index of the given message UID. 572 * 573 * @param integer $uid The message UID. 574 * @param string $mbox The message mailbox (defaults to the current 575 * mailbox). 576 * 577 * @return mixed The array index of the location of the message UID in 578 * the current mailbox. Returns null if not found. 579 */ 580 public function getArrayIndex($uid, $mbox = null) 581 { 582 $this->_buildMailbox(); 583 584 /* array_search() returns false on no result. We will set an 585 * unsuccessful result to NULL. */ 586 return (($aindex = array_search($uid, $this->_sorted)) === false) 587 ? null 588 : $aindex; 589 } 590 591 /** 592 * Generate an IMP_Indices object out of the contents of this mailbox. 593 * 594 * @return IMP_Indices An indices object. 595 */ 596 public function getIndicesOb() 597 { 598 $this->_buildMailbox(); 599 600 return new IMP_Indices($this->_mailbox, $this->_sorted); 601 } 602 603 /** 604 * Removes messages from the mailbox. 605 * 606 * @param mixed $indices An IMP_Indices object or true to remove all 607 * messages in the mailbox. 608 * 609 * @return boolean True if the message was removed from the mailbox. 610 */ 611 public function removeMsgs($indices) 612 { 613 if ($indices === true) { 614 $this->rebuild(); 615 return false; 616 } 617 618 if (!count($indices)) { 619 return false; 620 } 621 622 /* Remove the current entry and recalculate the range. */ 623 foreach ($indices as $ob) { 624 foreach ($ob->uids as $uid) { 625 unset($this->_sorted[$this->getArrayIndex($uid, $ob->mbox)]); 626 } 627 } 628 629 $this->changed = true; 630 $this->_sorted = array_values($this->_sorted); 631 632 if (isset($this->_thread[strval($ob->mbox)])) { 633 unset($this->_thread[strval($ob->mbox)], $this->_threadui[strval($ob->mbox)]); 634 } 635 636 if (!is_null($this->_index)) { 637 $this->setIndex(0); 638 } 639 640 return true; 641 } 642 643 /** 644 * Returns the list of UIDs for an entire thread given one message in 645 * that thread. 646 * 647 * @param integer $uid The message UID. 648 * @param string $mbox The message mailbox (defaults to the current 649 * mailbox). 650 * 651 * @return IMP_Indices An indices object. 652 */ 653 public function getFullThread($uid, $mbox = null) 654 { 655 if (is_null($mbox)) { 656 $mbox = $this->_mailbox; 657 } 658 659 return new IMP_Indices($mbox, array_keys($this->_getThread($mbox)->getThread($uid))); 660 } 661 662 /** 663 * Returns a thread object for a message. 664 * 665 * @param integer $offset Sequence number of message. 666 * 667 * @return IMP_Mailbox_List_Thread The thread object. 668 */ 669 public function getThreadOb($offset) 670 { 671 $entry = $this[$offset]; 672 $mbox = strval($entry['m']); 673 $uid = $entry['u']; 674 675 if (!isset($this->_threadui[$mbox][$uid])) { 676 $thread_level = array(); 677 $t_ob = $this->_getThread($mbox); 678 679 foreach ($t_ob->getThread($uid) as $key => $val) { 680 if (is_null($val->base) || 681 ($val->last && ($val->base == $key))) { 682 $this->_threadui[$mbox][$key] = ''; 683 continue; 684 } 685 686 if ($val->last) { 687 $join = IMP_Mailbox_List_Thread::JOINBOTTOM; 688 } else { 689 $join = (!$val->level && ($val->base == $key)) 690 ? IMP_Mailbox_List_Thread::JOINBOTTOM_DOWN 691 : IMP_Mailbox_List_Thread::JOIN; 692 } 693 694 $thread_level[$val->level] = $val->last; 695 $line = ''; 696 697 for ($i = 0; $i < $val->level; ++$i) { 698 if (isset($thread_level[$i])) { 699 $line .= (isset($thread_level[$i]) && !$thread_level[$i]) 700 ? IMP_Mailbox_List_Thread::LINE 701 : IMP_Mailbox_List_Thread::BLANK; 702 } 703 } 704 705 $this->_threadui[$mbox][$key] = $line . $join; 706 } 707 } 708 709 return new IMP_Mailbox_List_Thread($this->_threadui[$mbox][$uid]); 710 } 711 712 /** 713 * Returns the thread object for a mailbox. 714 * 715 * @param string $mbox The mailbox. 716 * @param array $extra Extra options to pass to IMAP thread() command. 717 * 718 * @return Horde_Imap_Client_Data_Thread Thread object. 719 */ 720 protected function _getThread($mbox, array $extra = array()) 721 { 722 if (!isset($this->_thread[strval($mbox)])) { 723 $imp_imap = IMP_Mailbox::get($mbox)->imp_imap; 724 725 try { 726 $thread = $imp_imap->thread($mbox, array_merge($extra, array( 727 'criteria' => $imp_imap->thread_algo 728 ))); 729 } catch (Horde_Imap_Client_Exception $e) { 730 $thread = new Horde_Imap_Client_Data_Thread(array(), 'uid'); 731 } 732 733 $this->_thread[strval($mbox)] = $thread; 734 } 735 736 return $this->_thread[strval($mbox)]; 737 } 738 739 /** 740 * Get the mailbox for a sequence ID. 741 * 742 * @param integer $id Sequence ID. 743 * 744 * @return IMP_Mailbox The mailbox. 745 */ 746 protected function _getMbox($id) 747 { 748 return $this->_mailbox; 749 } 750 751 /* Pseudo-UID related methods. */ 752 753 /** 754 * Create a browser-UID from a mail UID. 755 * 756 * @param string $mbox The mailbox. 757 * @param integer $uid UID. 758 * 759 * @return integer Browser-UID. 760 */ 761 public function getBuid($mbox, $uid) 762 { 763 return $uid; 764 } 765 766 /** 767 * Resolve a mail UID from a browser-UID. 768 * 769 * @param integer $buid Browser-UID. 770 * 771 * @return array Two-element array: 772 * - m: (IMP_Mailbox) Mailbox of message. 773 * - u: (string) UID of message. 774 */ 775 public function resolveBuid($buid) 776 { 777 return array( 778 'm' => $this->_mailbox, 779 'u' => intval($buid) 780 ); 781 } 782 783 /* Tracking related methods. */ 784 785 /** 786 * Returns the current message array index. If the array index has 787 * run off the end of the message array, will return the first index. 788 * 789 * @return integer The message array index. 790 */ 791 public function getIndex() 792 { 793 return $this->isValidIndex() 794 ? ($this->_index + 1) 795 : 1; 796 } 797 798 /** 799 * Checks to see if the current index is valid. 800 * 801 * @return boolean True if index is valid, false if not. 802 */ 803 public function isValidIndex() 804 { 805 return !is_null($this->_index); 806 } 807 808 /** 809 * Updates the message array index. 810 * 811 * @param mixed $data If an integer, the number of messages to increase 812 * the array index by. If an indices object, sets 813 * array index to the index value. 814 */ 815 public function setIndex($data) 816 { 817 if ($data instanceof IMP_Indices) { 818 list($mailbox, $uid) = $data->getSingle(); 819 $this->_index = $this->getArrayIndex($uid, $mailbox); 820 if (is_null($this->_index)) { 821 $this->rebuild(); 822 $this->_index = $this->getArrayIndex($uid, $mailbox); 823 } 824 } else { 825 $index = $this->_index += $data; 826 if (isset($this->_sorted[$this->_index])) { 827 if (!isset($this->_sorted[$this->_index + 1])) { 828 $this->rebuild(); 829 } 830 } else { 831 $this->rebuild(); 832 $this->_index = isset($this->_sorted[$index]) 833 ? $index 834 : null; 835 } 836 } 837 } 838 839 /* ArrayAccess methods. */ 840 841 /** 842 * @param integer $offset Sequence number of message. 843 */ 844 public function offsetExists($offset) 845 { 846 return isset($this->_sorted[$offset - 1]); 847 } 848 849 /** 850 * @param integer $offset Sequence number of message. 851 * 852 * @return array Two-element array: 853 * - m: (IMP_Mailbox) Mailbox of message. 854 * - u: (string) UID of message. 855 */ 856 public function offsetGet($offset) 857 { 858 if (!isset($this->_sorted[$offset - 1])) { 859 return null; 860 } 861 862 $ret = array( 863 'm' => $this->_getMbox($offset - 1), 864 'u' => $this->_sorted[$offset - 1] 865 ); 866 867 return $ret; 868 } 869 870 /** 871 * @throws BadMethodCallException 872 */ 873 public function offsetSet($offset, $value) 874 { 875 throw new BadMethodCallException('Not supported'); 876 } 877 878 /** 879 * @throws BadMethodCallException 880 */ 881 public function offsetUnset($offset) 882 { 883 throw new BadMethodCallException('Not supported'); 884 } 885 886 /* Countable methods. */ 887 888 /** 889 * Returns the current message count of the mailbox. 890 * 891 * @return integer The mailbox message count. 892 */ 893 public function count() 894 { 895 $this->_buildMailbox(); 896 return count($this->_sorted); 897 } 898 899 /* Iterator methods. */ 900 901 /** 902 * @return array Two-element array: 903 * - m: (IMP_Mailbox) Mailbox of message. 904 * - u: (string) UID of message. 905 */ 906 public function current() 907 { 908 return $this[key($this->_sorted) + 1]; 909 } 910 911 /** 912 * @return integer Sequence number of message. 913 */ 914 public function key() 915 { 916 return (key($this->_sorted) + 1); 917 } 918 919 /** 920 */ 921 public function next() 922 { 923 next($this->_sorted); 924 } 925 926 /** 927 */ 928 public function rewind() 929 { 930 reset($this->_sorted); 931 } 932 933 /** 934 */ 935 public function valid() 936 { 937 return (key($this->_sorted) !== null); 938 } 939 940 /* Serializable methods. */ 941 942 /** 943 * Serialization. 944 * 945 * @return string Serialized data. 946 */ 947 public function serialize() 948 { 949 return $GLOBALS['injector']->getInstance('Horde_Pack')->pack( 950 $this->_serialize(), 951 array( 952 'compression' => false, 953 'phpob' => true 954 ) 955 ); 956 } 957 958 /** 959 */ 960 protected function _serialize() 961 { 962 $data = array( 963 'm' => $this->_mailbox 964 ); 965 966 if ($this->_buidmax) { 967 $data['bm'] = $this->_buidmax; 968 if (!empty($this->_buids)) { 969 $data['b'] = $this->_buids; 970 } 971 } 972 973 if (!is_null($this->_cacheid)) { 974 $data['c'] = $this->_cacheid; 975 } 976 977 if (!is_null($this->_sorted)) { 978 /* Store UIDs in sequence string to save on storage space. */ 979 if (count($this->_sorted) > self::SERIALIZE_LIMIT) { 980 $ids = $this->_mailbox->imp_imap->getIdsOb(); 981 /* Optimization: we know there are no duplicates in sorted. */ 982 $ids->duplicates = true; 983 $ids->add($this->_sorted); 984 $data['so'] = $ids; 985 } else { 986 $data['so'] = $this->_sorted; 987 } 988 } 989 990 return $data; 991 } 992 993 /** 994 * Unserialization. 995 * 996 * @param string $data Serialized data. 997 * 998 * @throws Exception 999 */ 1000 public function unserialize($data) 1001 { 1002 $this->_unserialize( 1003 $GLOBALS['injector']->getInstance('Horde_Pack')->unpack($data) 1004 ); 1005 } 1006 1007 /** 1008 */ 1009 protected function _unserialize($data) 1010 { 1011 $this->_mailbox = $data['m']; 1012 1013 if (isset($data['bm'])) { 1014 $this->_buidmax = $data['bm']; 1015 if (isset($data['b'])) { 1016 $this->_buids = $data['b']; 1017 } 1018 } 1019 1020 if (isset($data['c'])) { 1021 $this->_cacheid = $data['c']; 1022 } 1023 1024 if (isset($data['so'])) { 1025 $this->_sorted = is_object($data['so']) 1026 ? $data['so']->ids 1027 : $data['so']; 1028 } 1029 } 1030 1031} 1032