1<?php 2/** 3 * Copyright 2011-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 2011-2017 Horde LLC 10 * @license http://www.horde.org/licenses/gpl GPL 11 * @package IMP 12 */ 13 14/** 15 * Defines an AJAX variable queue for IMP. These are variables that may be 16 * generated by various IMP code that should be added to the eventual output 17 * sent to the browser. 18 * 19 * @author Michael Slusarz <slusarz@horde.org> 20 * @category Horde 21 * @copyright 2011-2017 Horde LLC 22 * @license http://www.horde.org/licenses/gpl GPL 23 * @package IMP 24 */ 25class IMP_Ajax_Queue 26{ 27 /** 28 * Callback method to use for each ftree element. 29 * 30 * @var callback 31 */ 32 public $ftreeCallback; 33 34 /** 35 * The list of compose autocompleter address error data. 36 * 37 * @var array 38 */ 39 protected $_addr = array(); 40 41 /** 42 * The list of attachments. 43 * 44 * @var array 45 */ 46 protected $_atc = array(); 47 48 /** 49 * The compose object. 50 * 51 * @var IMP_Compose 52 */ 53 protected $_compose; 54 55 /** 56 * Flag entries to add to response. 57 * 58 * @var array 59 */ 60 protected $_flag = array(); 61 62 /** 63 * Add flag configuration to response. 64 * 65 * @var integer 66 */ 67 protected $_flagconfig = 0; 68 69 /** 70 * Mailbox options. 71 * 72 * @var array 73 */ 74 protected $_mailboxOpts = array(); 75 76 /** 77 * Message queue. 78 * 79 * @var array 80 */ 81 protected $_messages = array(); 82 83 /** 84 * Maillog queue. 85 * 86 * @var array 87 */ 88 protected $_maillog = array(); 89 90 /** 91 * Poll mailboxes. 92 * 93 * If null, don't output polled information unless explicitly told to. 94 * 95 * @var array 96 */ 97 protected $_poll = array(); 98 99 /** 100 * Add quota information to response? 101 * 102 * Array w/2 keys: mailbox, force 103 * If null, never sends quota information. 104 * 105 * @var mixed 106 */ 107 protected $_quota = false; 108 109 /** 110 * Generates AJAX response task data from the queue. 111 * 112 * For compose autocomplete address error data (key: 'compose-addr'), an 113 * array with keys as the autocomplete DOM element and the values as 114 * arrays. The value arrays have keys as the autocomplete address ID, and 115 * the value is a space-separated list of classnames to add. 116 * 117 * For compose attachment data (key: 'compose-atc'), an array of objects 118 * with these properties: 119 * - icon: (string) Data url string containing icon information. 120 * - name: (string) The attachment name 121 * - num: (integer) The current attachment number 122 * - size: (string) The size of the attachment 123 * - type: (string) The MIME type of the attachment 124 * - view: (boolean) Link to attachment preivew page 125 * 126 * For compose cacheid data (key: 'compose'), an object with these 127 * properties: 128 * - atclimit: (integer) If set, no further attachments are allowed. 129 * - cacheid: (string) Current cache ID of the compose message. 130 * 131 * For flag data (key: 'flag'), an array of objects with these properties: 132 * - add: (array) The list of flags that were added. 133 * - buids: (string) Indices of the messages that have changed (IMAP 134 * sequence string; mboxes are base64url encoded). 135 * - remove: (array) The list of flags that were removed. 136 * - replace: (array) Replace the flag list with these flags. 137 * 138 * For flag configuration data (key: 'flag-config'), an array containing 139 * flag data. All flags returned in dynamic mode; only flags labeled below 140 * as [sm] are returned in smartmobile mode: 141 * - a: (boolean) Indicates a flag that can be *a*ltered. 142 * - b: (string) Background color [sm]. 143 * - c: (string) CSS class. 144 * - f: (string) Foreground color [sm]. 145 * - i: (string) CSS icon [sm]. 146 * - id: (string) Flag ID (IMAP flag id). 147 * - l: (string) Flag label [sm]. 148 * - s: (boolean) Indicates a flag that can be *s*earched for [sm]. 149 * - u: (boolean) Indicates a *u*ser flag. 150 * 151 * For mailbox data (key: 'mailbox'), an array with these keys: 152 * - a: (array) Mailboxes that were added (base64url encoded). 153 * - all: (integer) TODO 154 * - base: (string) TODO 155 * - c: (array) Mailboxes that were changed (base64url encoded). 156 * - d: (array) Mailboxes that were deleted (base64url encoded). 157 * - expand: (integer) Expand subfolders on load. 158 * - switch: (string) Load this mailbox (base64url encoded). 159 * 160 * For maillog data (key: 'maillog'), an object with these properties: 161 * - buid: (integer) BUID. 162 * - log: (array) List of log entries. 163 * - mbox: (string) Mailbox. 164 * 165 * For message preview data (key: 'message'), an object with these 166 * properties: 167 * - buid: (integer) BUID. 168 * - data: (object) Message viewport data. 169 * - mbox: (string) Mailbox. 170 * 171 * For poll data (key: 'poll'), an array with keys as base64url encoded 172 * mailbox names, values as the number of unseen messages. 173 * 174 * For quota data (key: 'quota'), an array with these keys: 175 * - m: (string) Quota message. 176 * - p: (integer) Quota percentage. 177 * 178 * @param IMP_Ajax_Application $ajax The AJAX object. 179 */ 180 public function add(IMP_Ajax_Application $ajax) 181 { 182 global $injector; 183 184 /* Add autocomplete address error information. */ 185 if (!empty($this->_addr)) { 186 $ajax->addTask('compose-addr', $this->_addr); 187 $this->_addr = array(); 188 } 189 190 /* Add compose attachment information. */ 191 if (!empty($this->_atc)) { 192 $ajax->addTask('compose-atc', $this->_atc); 193 $this->_atc = array(); 194 } 195 196 /* Add compose information. */ 197 if (!is_null($this->_compose)) { 198 $compose = new stdClass; 199 if (!$this->_compose->additionalAttachmentsAllowed()) { 200 $compose->atclimit = 1; 201 } 202 $compose->cacheid = $this->_compose->getCacheId(); 203 $compose->hmac = $this->_compose->getHmac(); 204 205 $ajax->addTask('compose', $compose); 206 $this->_compose = null; 207 } 208 209 /* Add flag information. */ 210 if (!empty($this->_flag)) { 211 $ajax->addTask('flag', array_unique($this->_flag, SORT_REGULAR)); 212 $this->_flag = array(); 213 } 214 215 /* Add flag configuration. */ 216 switch ($this->_flagconfig) { 217 case Horde_Registry::VIEW_DYNAMIC: 218 case Horde_Registry::VIEW_SMARTMOBILE: 219 $flags = array(); 220 foreach ($injector->getInstance('IMP_Flags')->getList() as $val) { 221 $tmp = array( 222 'b' => $val->bgdefault ? null : $val->bgcolor, 223 'f' => $val->fgcolor, 224 'id' => $val->id, 225 'l' => $val->label, 226 's' => intval($val instanceof IMP_Flag_Imap) 227 ); 228 229 if ($this->_flagconfig === Horde_Registry::VIEW_DYNAMIC) { 230 $tmp += array( 231 'a' => $val->canset, 232 'c' => $val->css, 233 'i' => $val->css ? null : $val->cssicon, 234 'u' => intval($val instanceof IMP_Flag_User) 235 ); 236 } 237 238 $flags[] = array_filter($tmp); 239 } 240 $ajax->addTask('flag-config', $flags); 241 break; 242 } 243 244 /* Add folder tree information. */ 245 $this->_addFtreeInfo($ajax); 246 247 /* Add maillog information. */ 248 $this->_addMaillogInfo($ajax); 249 250 /* Add message information. */ 251 if (!empty($this->_messages)) { 252 $ajax->addTask('message', $this->_messages); 253 $this->_messages = array(); 254 } 255 256 /* Add poll information. */ 257 $poll = $poll_list = array(); 258 if (!empty($this->_poll)) { 259 foreach ($this->_poll as $val) { 260 $poll_list[strval($val)] = 1; 261 } 262 } 263 264 if (count($poll_list)) { 265 $imap_ob = $injector->getInstance('IMP_Factory_Imap')->create(); 266 if ($imap_ob->init) { 267 try { 268 foreach ($imap_ob->status(array_keys($poll_list), Horde_Imap_Client::STATUS_UNSEEN) as $key => $val) { 269 $poll[IMP_Mailbox::formTo($key)] = intval($val['unseen']); 270 } 271 } catch (Exception $e) { 272 // Ignore errors in status() calls. 273 } 274 } 275 276 if (!empty($poll)) { 277 $ajax->addTask('poll', $poll); 278 $this->_poll = array(); 279 } 280 } 281 282 /* Add quota information. */ 283 if ($this->_quota && 284 ($quotadata = $injector->getInstance('IMP_Quota_Ui')->quota($this->_quota[0], $this->_quota[1]))) { 285 $ajax->addTask('quota', array( 286 'm' => $quotadata['message'], 287 'p' => round($quotadata['percent']), 288 'l' => $quotadata['percent'] >= 90 289 ? 'alert' 290 : ($quotadata['percent'] >= 75 ? 'warn' : '') 291 )); 292 $this->_quota = false; 293 } 294 } 295 296 /** 297 * Return information about the current attachment(s) for a message. 298 * 299 * @param mixed $ob If an IMP_Compose object, return info on all 300 * attachments. If an IMP_Compose_Attachment object, 301 * only return information on that object. 302 * @param integer $type The compose type. 303 */ 304 public function attachment($ob, $type = IMP_Compose::COMPOSE) 305 { 306 global $injector; 307 308 $parts = ($ob instanceof IMP_Compose) 309 ? iterator_to_array($ob) 310 : array($ob); 311 $viewer = $injector->getInstance('IMP_Factory_MimeViewer'); 312 313 foreach ($parts as $val) { 314 $mime = $val->getPart(); 315 $mtype = $mime->getType(); 316 317 $tmp = array( 318 'icon' => strval(Horde_Url_Data::create('image/png', file_get_contents($viewer->getIcon($mtype)->fs))), 319 'name' => $mime->getName(true), 320 'num' => $val->id, 321 'type' => $mtype, 322 'size' => IMP::sizeFormat($mime->getBytes()) 323 ); 324 325 if ($viewer->create($mime)->canRender('full')) { 326 $tmp['url'] = strval($val->viewUrl()->setRaw(true)); 327 $tmp['view'] = intval(!in_array($type, array(IMP_Compose::FORWARD_ATTACH, IMP_Compose::FORWARD_BOTH)) && ($mtype != 'application/octet-stream')); 328 } 329 330 $this->_atc[] = $tmp; 331 } 332 } 333 334 /** 335 * Add compose data to the output. 336 * 337 * @param IMP_Compose $ob The compose object. 338 */ 339 public function compose(IMP_Compose $ob) 340 { 341 $this->_compose = $ob; 342 } 343 344 /** 345 * Add address autocomplete error info. 346 * 347 * @param string $domid The autocomplete DOM ID. 348 * @param string $itemid The autocomplete address ID. 349 * @param string $class The classname to add to the address entry. 350 */ 351 public function compose_addr($domid, $itemid, $class) 352 { 353 $this->_addr[$domid][$itemid] = $class; 354 } 355 356 /** 357 * Add flag entry to response queue. 358 * 359 * @param array $flags List of flags that have changed. 360 * @param boolean $add Were the flags added? 361 * @param IMP_Indices $indices Indices object. 362 */ 363 public function flag($flags, $add, IMP_Indices $indices) 364 { 365 global $injector; 366 367 if ($indices instanceof IMP_Indices_Mailbox) { 368 if (!count($indices) || !$indices->mailbox->access_flags) { 369 return; 370 } 371 $indices = clone $indices; 372 $indices->add($indices->buids); 373 } 374 375 $changed = $injector->getInstance('IMP_Flags')->changed($flags, $add); 376 377 $result = new stdClass; 378 if (!empty($changed['add'])) { 379 $result->add = array_map('strval', $changed['add']); 380 } 381 if (!empty($changed['remove'])) { 382 $result->remove = array_map('strval', $changed['remove']); 383 } 384 385 $result->buids = $indices->toArray(); 386 $this->_flag[] = $result; 387 } 388 389 /** 390 * Sends replacement flag information for the indices provided. 391 * 392 * @param IMP_Indices $indices Indices object. 393 */ 394 public function flagReplace(IMP_Indices $indices) 395 { 396 global $injector, $prefs; 397 398 $imp_flags = $injector->getInstance('IMP_Flags'); 399 400 foreach ($indices as $ob) { 401 $list_ob = $ob->mbox->list_ob; 402 $msgnum = array(); 403 404 foreach ($ob->uids as $uid) { 405 $msgnum[] = $list_ob->getArrayIndex($uid) + 1; 406 } 407 408 $marray = $list_ob->getMailboxArray($msgnum, array( 409 'headers' => true, 410 'type' => $prefs->getValue('atc_flag') 411 )); 412 413 foreach ($marray['overview'] as $val) { 414 $result = new stdClass; 415 $result->buids = $ob->mbox->toBuids(new IMP_Indices($ob->mbox, $val['uid']))->toArray(); 416 $result->replace = array_map('strval', $imp_flags->parse(array( 417 'flags' => $val['flags'], 418 'headers' => $val['headers'], 419 'runhook' => $val, 420 'personal' => $val['envelope']->to 421 ))); 422 $this->_flag[] = $result; 423 } 424 } 425 } 426 427 /** 428 * Add flag configuration information to response queue. 429 * 430 * @param integer $view The current view. 431 */ 432 public function flagConfig($view) 433 { 434 $this->_flagconfig = $view; 435 } 436 437 /** 438 * Add message data to output. 439 * 440 * @param IMP_Indices $indices Index of the message. 441 * @param boolean $preview Preview data? 442 * @param boolean $peek Don't set seen flag? 443 */ 444 public function message(IMP_Indices $indices, $preview = false, 445 $peek = false) 446 { 447 try { 448 $show_msg = new IMP_Ajax_Application_ShowMessage($indices, $peek); 449 $msg = (object)$show_msg->showMessage(array( 450 'preview' => $preview 451 )); 452 $msg->save_as = strval($msg->save_as); 453 454 if ($indices instanceof IMP_Indices_Mailbox) { 455 $indices = $indices->buids; 456 } 457 458 foreach ($indices as $val) { 459 foreach ($val->uids as $val2) { 460 $ob = new stdClass; 461 $ob->buid = $val2; 462 $ob->data = $msg; 463 $ob->mbox = $val->mbox->form_to; 464 $this->_messages[] = $ob; 465 } 466 } 467 } catch (Exception $e) {} 468 } 469 470 /** 471 * Add maillog data to output. 472 * 473 * @param IMP_Indices $indices Indices object. 474 */ 475 public function maillog(IMP_Indices $indices) 476 { 477 $this->_maillog[] = $indices; 478 } 479 480 /** 481 * Add additional options to the mailbox output. 482 * 483 * @param array $name Option name. 484 * @param mixed $value Option value. 485 */ 486 public function setMailboxOpt($name, $value) 487 { 488 $this->_mailboxOpts[$name] = $value; 489 } 490 491 /** 492 * Add poll entry to response queue. 493 * 494 * @param mixed $mboxes A mailbox name or list of mailbox names. 495 * @param boolean $explicit If true, explicitly output poll information. 496 * Otherwise, add only if not disabled. 497 */ 498 public function poll($mboxes, $explicit = false) 499 { 500 if (is_null($this->_poll)) { 501 if (!$explicit) { 502 return; 503 } 504 $this->_poll = array(); 505 } elseif (empty($this->_poll) && is_null($mboxes)) { 506 $this->_poll = null; 507 return; 508 } 509 510 if (!is_array($mboxes)) { 511 $mboxes = array($mboxes); 512 } 513 514 foreach (IMP_Mailbox::get($mboxes) as $val) { 515 if ($val->polled) { 516 $this->_poll[] = $val; 517 } 518 } 519 } 520 521 /** 522 * Add quota entry to response queue. 523 * 524 * @param string|null $mailbox Mailbox to query for quota. If null, 525 * disables quota output. 526 * @param boolean $force If true, force output. If false, output 527 * based on configured quota interval. 528 */ 529 public function quota($mailbox, $force = true) 530 { 531 if (!is_null($this->_quota)) { 532 if (is_null($mailbox)) { 533 /* Disable quota output entirely. */ 534 $this->_quota = null; 535 } elseif (!is_array($this->_quota) || !$this->_quota[1]) { 536 /* Don't change a previously issued force quota request. */ 537 $this->_quota = array($mailbox, $force); 538 } 539 } 540 } 541 542 /** 543 * Add folder tree information. 544 * 545 * @param IMP_Ajax_Application $ajax The AJAX object. 546 */ 547 protected function _addFtreeInfo(IMP_Ajax_Application $ajax) 548 { 549 global $injector; 550 551 $eltdiff = $injector->getInstance('IMP_Ftree')->eltdiff; 552 $out = $poll = array(); 553 554 if (!$eltdiff->track) { 555 return; 556 } 557 558 if (($add = $eltdiff->add) && 559 ($elts = array_values(array_filter(array_map(array($this, '_ftreeElt'), $add))))) { 560 $out['a'] = $elts; 561 $poll = $add; 562 } 563 564 if (($change = $eltdiff->change) && 565 ($elts = array_values(array_filter(array_map(array($this, '_ftreeElt'), $change))))) { 566 $out['c'] = $elts; 567 $poll = array_merge($poll, $change); 568 } 569 570 if ($delete = $eltdiff->delete) { 571 $out['d'] = IMP_Mailbox::formTo($delete); 572 } 573 574 if (!empty($out)) { 575 $eltdiff->clear(); 576 $ajax->addTask('mailbox', array_merge($out, $this->_mailboxOpts)); 577 $this->poll($poll); 578 } 579 } 580 581 /** 582 * Create a folder tree element. 583 * 584 * @param string $id Tree element ID. 585 * 586 * @return mixed The element object, or null if the element is not 587 * active. Object contains the following properties: 588 * <pre> 589 * - ch: (boolean) [children] Does the mailbox contain children? 590 * DEFAULT: no 591 * - cl: (string) [class] The CSS class. 592 * DEFAULT: 'folderImg' 593 * - co: (boolean) [container] Is this mailbox a container element? 594 * DEFAULT: no 595 * - fs: (boolean) [boolean] Fixed element for sorting purposes. 596 * DEFAULT: no 597 * - i: (string) [icon] A user defined icon to use. 598 * DEFAULT: none 599 * - l: (string) [label] The mailbox display label. 600 * - m: (string) [mbox] The mailbox value (base64url encoded). 601 * - n: (boolean) [non-imap] A non-IMAP element? 602 * DEFAULT: no 603 * - nc: (boolean) [no children] Does the element not allow children? 604 * DEFAULT: no 605 * - ns: (boolean) [no sort] Don't sort on browser. 606 * DEFAULT: no 607 * - pa: (string) [parent] The parent element. 608 * DEFAULT: DimpCore.conf.base_mbox 609 * - po: (boolean) [polled] Is the element polled? 610 * DEFAULT: no 611 * - r: (integer) [remote] Is this a "remote" element? 1 is the remote 612 * container, 2 is a remote account, and 3 is a remote mailbox. 613 * DEFAULT: 0 614 * - s: (boolean) [special] Is this a "special" element? 615 * DEFAULT: no 616 * - t: (string) [title] Mailbox title. 617 * DEFAULT: 'l' val 618 * - un: (boolean) [unsubscribed] Is this mailbox unsubscribed? 619 * DEFAULT: no 620 * - v: (integer) [virtual] Virtual folder? 0 = not vfolder, 1 = system 621 * vfolder, 2 = user vfolder 622 * DEFAULT: 0 623 * </pre> 624 */ 625 protected function _ftreeElt($id) 626 { 627 global $injector; 628 629 $ftree = $injector->getInstance('IMP_Ftree'); 630 if (!($elt = $ftree[$id]) || $elt->base_elt) { 631 return null; 632 } 633 634 $mbox_ob = $elt->mbox_ob; 635 636 $ob = new stdClass; 637 $ob->m = $mbox_ob->form_to; 638 639 if ($elt->children) { 640 $ob->ch = 1; 641 } elseif ($elt->nochildren) { 642 $ob->nc = 1; 643 } 644 645 $ob->l = htmlspecialchars($mbox_ob->abbrev_label); 646 $label = $mbox_ob->display_notranslate; 647 if ($ob->l != $label) { 648 $ob->t = $label; 649 } 650 651 $parent = $elt->parent; 652 if (!$parent->base_elt) { 653 $ob->pa = $parent->mbox_ob->form_to; 654 if ($parent->remote && 655 (strcasecmp($mbox_ob->imap_mbox, 'INBOX') === 0)) { 656 $ob->fs = 1; 657 } 658 } 659 660 if ($elt->vfolder) { 661 $ob->v = $mbox_ob->editvfolder ? 2 : 1; 662 $ob->ns = 1; 663 } 664 665 if ($elt->nonimap) { 666 $ob->n = 1; 667 if ($mbox_ob->remote_container) { 668 $ob->r = 1; 669 } 670 } 671 672 if ($elt->container) { 673 if (empty($ob->ch)) { 674 return null; 675 } 676 $ob->co = 1; 677 } else { 678 if (!$elt->subscribed) { 679 $ob->un = 1; 680 } 681 682 if (isset($ob->n) && isset($ob->r)) { 683 $ob->r = ($mbox_ob->remote_account->imp_imap->init ? 3 : 2); 684 } 685 686 if ($elt->polled) { 687 $ob->po = 1; 688 } 689 690 if ($elt->inbox) { 691 $ob->ns = $ob->s = 1; 692 } elseif ($mbox_ob->special) { 693 $ob->ns = $ob->s = 1; 694 $ob->t = strval($elt); 695 } 696 } 697 698 $icon = $mbox_ob->icon; 699 if ($icon->user_icon) { 700 $ob->cl = 'customimg'; 701 $ob->i = strval($icon->icon); 702 } elseif (!in_array($icon->class, array('folderImg', 'folderopenImg'))) { 703 $ob->cl = $icon->class; 704 } 705 706 if ($this->ftreeCallback) { 707 call_user_func($this->ftreeCallback, $id, $ob); 708 } 709 710 return $ob; 711 } 712 713 /** 714 * Add maillog information. 715 * 716 * @param IMP_Ajax_Application $ajax The AJAX object. 717 */ 718 protected function _addMaillogInfo(IMP_Ajax_Application $ajax) 719 { 720 global $injector; 721 722 if (empty($this->_maillog)) { 723 return; 724 } 725 726 $imp_maillog = $injector->getInstance('IMP_Maillog'); 727 $maillog = array(); 728 729 foreach ($this->_maillog as $val) { 730 /* Need to grab the maillog data from the "real" ID. Then check to 731 * see if a different BUID exists, since the message log may need 732 * to be updated in two locations at once (i.e. search mailbox and 733 * real mailbox). */ 734 foreach ($val as $v) { 735 foreach ($v->uids as $v2) { 736 $msg = new IMP_Maillog_Message( 737 new IMP_Indices($v->mbox, $v2) 738 ); 739 740 $tmp = array(); 741 foreach ($imp_maillog->getLog($msg, array('mdn')) as $v3) { 742 $tmp[] = array( 743 'm' => $v3->message, 744 't' => $v3->action 745 ); 746 } 747 748 if ($tmp) { 749 $indices = $msg->indices; 750 if ($val instanceof IMP_Indices_Mailbox) { 751 $indices->add($val->mailbox->toBuids($indices)); 752 } 753 754 foreach ($indices as $v4) { 755 foreach ($v4->uids as $v5) { 756 $log_ob = new stdClass; 757 $log_ob->buid = intval($v5); 758 $log_ob->log = $tmp; 759 $log_ob->mbox = $v4->mbox->form_to; 760 $maillog[] = $log_ob; 761 } 762 } 763 } 764 } 765 } 766 } 767 768 if (!empty($maillog)) { 769 $ajax->addTask('maillog', $maillog); 770 } 771 } 772 773} 774