1<?php 2/** 3 * Copyright 2012-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 2012-2017 Horde LLC 10 * @license http://www.horde.org/licenses/gpl GPL 11 * @package IMP 12 */ 13 14/** 15 * Defines AJAX actions used exclusively in the IMP dynamic view. 16 * 17 * @author Michael Slusarz <slusarz@horde.org> 18 * @category Horde 19 * @copyright 2012-2017 Horde LLC 20 * @license http://www.horde.org/licenses/gpl GPL 21 * @package IMP 22 */ 23class IMP_Ajax_Application_Handler_Dynamic 24extends Horde_Core_Ajax_Application_Handler 25{ 26 /** 27 * The list of actions that require readonly access to the session. 28 * 29 * @var array 30 */ 31 protected $_readOnly = array( 32 'html2Text', 'text2Html' 33 ); 34 35 /** 36 * AJAX action: Check access rights for creation of a submailbox. 37 * 38 * Variables used: 39 * - mbox: (string) The name of the mailbox to check (base64url 40 * encoded). 41 * 42 * @return object Object with the following properties: 43 * - result: (boolean) True if submailboxes can be created. 44 */ 45 public function createMailboxPrepare() 46 { 47 $mbox = IMP_Mailbox::formFrom($this->vars->mbox); 48 $ret = new stdClass; 49 $ret->result = true; 50 51 if (!$mbox->access_creatembox) { 52 $GLOBALS['notification']->push(sprintf(_("You may not create child mailboxes in \"%s\"."), $mbox->display), 'horde.error'); 53 $ret->result = false; 54 } 55 56 return $ret; 57 } 58 59 /** 60 * AJAX action: Create a mailbox. 61 * 62 * Variables used: 63 * - mbox: (string) The name of the new mailbox. 64 * - parent: (string) The parent mailbox (base64url encoded). 65 * 66 * @return boolean True on success, false on failure. 67 */ 68 public function createMailbox() 69 { 70 if (!isset($this->vars->mbox)) { 71 return false; 72 } 73 74 $result = false; 75 76 $parent = isset($this->vars->parent) 77 ? IMP_Mailbox::formFrom($this->vars->parent) 78 : IMP_Mailbox::get(IMP_Ftree::BASE_ELT); 79 $new_mbox = $parent->createMailboxName($this->vars->mbox); 80 81 if ($new_mbox->exists) { 82 $GLOBALS['notification']->push(sprintf(_("Mailbox \"%s\" already exists."), $new_mbox->display), 'horde.warning'); 83 } elseif ($new_mbox->create()) { 84 $result = true; 85 } 86 87 return $result; 88 } 89 90 /** 91 * AJAX action: Check access rights for deletion/rename of mailbox. 92 * 93 * Variables used: 94 * - mbox: (string) The name of the mailbox to check (base64url 95 * encoded). 96 * - type: (string) Either 'delete' or 'rename'. 97 * 98 * @return object Object with the following properties: 99 * - result: (boolean) True if mailbox can be deleted/renamed. 100 */ 101 public function deleteMailboxPrepare() 102 { 103 $mbox = IMP_Mailbox::formFrom($this->vars->mbox); 104 $ret = new stdClass; 105 106 if ($mbox->access_deletembox) { 107 $ret->result = true; 108 return $ret; 109 } 110 111 switch ($this->vars->type) { 112 case 'delete': 113 $GLOBALS['notification']->push(sprintf(_("You may not delete \"%s\"."), $mbox->display), 'horde.error'); 114 break; 115 116 case 'rename': 117 $GLOBALS['notification']->push(sprintf(_("You may not rename \"%s\"."), $mbox->display), 'horde.error'); 118 break; 119 } 120 121 $ret->result = false; 122 return $ret; 123 } 124 125 /** 126 * AJAX action: Delete a mailbox. 127 * 128 * Variables used: 129 * - container: (boolean) True if base element is a container. 130 * - mbox: (string) The full mailbox name to delete (base64url encoded). 131 * - subfolders: (boolean) Delete all subfolders? 132 * 133 * @return boolean True on success, false on failure. 134 */ 135 public function deleteMailbox() 136 { 137 return ($this->vars->mbox && IMP_Mailbox::formFrom($this->vars->mbox)->delete(array( 138 'subfolders' => !empty($this->vars->subfolders), 139 'subfolders_only' => !empty($this->vars->container) 140 ))); 141 } 142 143 /** 144 * AJAX action: Rename a mailbox. 145 * 146 * Variables used: 147 * - new_name: (string) New mailbox name (child node) (UTF-8). 148 * - new_parent: (string) New parent name (UTF-8; base64url encoded). 149 * If not present, uses old parent. 150 * - old_name: (string) Full name of old mailbox (base64url encoded). 151 * 152 * @return boolean True on success, false on failure. 153 */ 154 public function renameMailbox() 155 { 156 if (!$this->vars->old_name || !$this->vars->new_name) { 157 return false; 158 } 159 160 $old_name = IMP_Mailbox::formFrom($this->vars->old_name); 161 if (isset($this->vars->new_parent)) { 162 $parent = strlen($this->vars->new_parent) 163 ? IMP_Mailbox::formFrom($this->vars->new_parent) 164 : IMP_Mailbox::get(IMP_Ftree::BASE_ELT); 165 } else { 166 $parent = IMP_Mailbox::get($old_name->parent); 167 } 168 169 if ($parent) { 170 $new_name = $parent->createMailboxName($this->vars->new_name); 171 172 if (($old_name != $new_name) && $old_name->rename($new_name)) { 173 $this->_base->queue->setMailboxOpt('switch', $new_name->form_to); 174 return true; 175 } 176 } 177 178 return false; 179 } 180 181 /** 182 * AJAX action: Check access rights for a mailbox, and provide number of 183 * messages to be emptied. 184 * 185 * Variables used: 186 * - mbox: (string) The name of the mailbox to check (base64url 187 * encoded). 188 * 189 * @return object Object with the following properties: 190 * - result: (integer) The number of messages to be deleted. 191 */ 192 public function emptyMailboxPrepare() 193 { 194 $mbox = IMP_Mailbox::formFrom($this->vars->mbox); 195 $res = new stdClass; 196 $res->result = 0; 197 198 if (!$mbox->access_empty) { 199 $GLOBALS['notification']->push(sprintf(_("The mailbox \"%s\" may not be emptied."), $mbox->display), 'horde.error'); 200 } else { 201 $poll_info = $mbox->poll_info; 202 if (!($res->result = $poll_info->msgs)) { 203 $GLOBALS['notification']->push(sprintf(_("The mailbox \"%s\" is already empty."), $mbox->display), 'horde.message'); 204 } 205 } 206 207 return $res; 208 } 209 210 /** 211 * AJAX action: Empty a mailbox. 212 * 213 * Variables used: 214 * - mbox: (string) The full mailbox name to empty (base64url encoded). 215 * 216 * @return boolean True on success, false on failure. 217 */ 218 public function emptyMailbox() 219 { 220 if (!$this->vars->mbox) { 221 return false; 222 } 223 224 $mbox = IMP_Mailbox::formFrom($this->vars->mbox); 225 226 $GLOBALS['injector']->getInstance('IMP_Message')->emptyMailbox(array($mbox)); 227 228 $this->_base->queue->poll($mbox); 229 230 $vp = new IMP_Ajax_Application_Viewport($mbox); 231 $vp->data_reset = 1; 232 $vp->rowlist_reset = 1; 233 $this->_base->addTask('viewport', $vp); 234 235 return true; 236 } 237 238 /** 239 * AJAX action: Flag all messages in a mailbox. 240 * 241 * Variables used: 242 * - add: (integer) Add the flags? 243 * - flags: (string) The IMAP flags to add/remove (JSON serialized 244 * array). 245 * - mbox: (string) The full mailbox name (base64url encoded). 246 * 247 * @return boolean True on success, false on failure. 248 */ 249 public function flagAll() 250 { 251 $flags = json_decode($this->vars->flags); 252 if (!$this->vars->mbox || empty($flags)) { 253 return false; 254 } 255 256 $mbox = IMP_Mailbox::formFrom($this->vars->mbox); 257 258 if (!$GLOBALS['injector']->getInstance('IMP_Message')->flagAllInMailbox($flags, array($mbox), $this->vars->add)) { 259 return false; 260 } 261 262 $this->_base->queue->poll($mbox); 263 264 return true; 265 } 266 267 /** 268 * AJAX action: List mailboxes. 269 * 270 * Variables used: 271 * - all: (integer) 1 to show all mailboxes. 272 * - base: (string) The base mailbox. 273 * - expall: (boolean) 1 to expand all (requires 'all'). 274 * - initial: (string) 1 to indicate the initial request for mailbox 275 * list. 276 * - mboxes: (string) The list of mailboxes to process (JSON encoded 277 * array; mailboxes are base64url encoded). 278 * - reload: (integer) 1 to force reload of mailboxes. 279 * - unsub: (integer) 1 to show unsubscribed mailboxes. 280 * 281 * @return boolean True. 282 */ 283 public function listMailboxes() 284 { 285 global $injector, $prefs, $session; 286 287 $ftree = $injector->getInstance('IMP_Ftree'); 288 $iterator = new AppendIterator(); 289 290 /* This might be a long running operation. */ 291 if ($this->vars->initial) { 292 $session->close(); 293 $ftree->eltdiff->clear(); 294 295 /* @todo: Correctly handle unsubscribed mailboxes in ftree. */ 296 if ($ftree->unsubscribed_loaded && !$this->vars->reload) { 297 $ftree->init(); 298 } 299 } 300 301 if ($this->vars->reload) { 302 $ftree->init(); 303 } 304 305 $filter = new IMP_Ftree_IteratorFilter($ftree); 306 if ($this->vars->unsub) { 307 $ftree->loadUnsubscribed(); 308 $filter->remove($filter::UNSUB); 309 } 310 311 if (isset($this->vars->base)) { 312 $this->_base->queue->setMailboxOpt('base', $this->vars->base); 313 } 314 315 if ($this->vars->all) { 316 $this->_base->queue->setMailboxOpt('all', 1); 317 $iterator->append($filter); 318 if ($this->vars->expall) { 319 $this->vars->action = 'expand'; 320 $this->_base->callAction('toggleMailboxes'); 321 } 322 } elseif ($this->vars->initial || $this->vars->reload) { 323 $special = new ArrayIterator(); 324 $special->append($ftree['INBOX']); 325 326 /* Add special mailboxes explicitly to the initial folder list, 327 * since they are ALWAYS displayed, may appear outside of the 328 * folder slice requested, and need to be sorted logically. */ 329 $s_elts = array(); 330 foreach (IMP_Mailbox::getSpecialMailboxesSort() as $val) { 331 if (isset($ftree[$val])) { 332 $special->append($val); 333 $s_elts[] = $ftree[$val]; 334 } 335 } 336 $iterator->append($special); 337 338 /* Go through and find any parent elements that contain only 339 * special mailbox children - this need to be suppressed in 340 * display. */ 341 $filter2 = clone $filter; 342 $filter2->add(array($filter2::CONTAINERS, $filter2::SPECIALMBOXES)); 343 $no_children = array(); 344 345 foreach (array_unique($s_elts) as $val) { 346 while (($val = $val->parent) && !$val->base_elt) { 347 $filter2->iterator = new IMP_Ftree_Iterator($val); 348 foreach ($filter2 as $val) { 349 /* If we found at least one viewable mailbox, this 350 * element needs its children to be displayed. */ 351 break 2; 352 } 353 $no_children[] = strval($val); 354 } 355 } 356 357 if (!empty($no_children)) { 358 $this->_base->queue->ftreeCallback = function($id, $ob) use ($no_children) { 359 if (in_array($id, $no_children)) { 360 unset($ob->ch); 361 } 362 }; 363 } 364 365 /* Add regular mailboxes. */ 366 $no_mbox = false; 367 368 switch ($prefs->getValue('nav_expanded')) { 369 case IMP_Ftree_Prefs_Expanded::NO: 370 $filter->add($filter::CHILDREN); 371 break; 372 373 case IMP_Ftree_Prefs_Expanded::YES: 374 $this->_base->queue->setMailboxOpt('expand', 1); 375 $no_mbox = true; 376 break; 377 378 case IMP_Ftree_Prefs_Expanded::LAST: 379 $filter->add($filter::EXPANDED); 380 $this->_base->queue->setMailboxOpt('expand', 1); 381 break; 382 } 383 384 $filter->mboxes = array('INBOX'); 385 $iterator->append($filter); 386 387 if (!$no_mbox) { 388 $mboxes = IMP_Mailbox::formFrom(json_decode($this->vars->mboxes)); 389 foreach ($mboxes as $val) { 390 if (!$val->inbox) { 391 $ancestors = new IMP_Ftree_IteratorFilter( 392 new IMP_Ftree_Iterator_Ancestors($val->tree_elt) 393 ); 394 if ($this->vars->unsub) { 395 $ancestors->remove($ancestors::UNSUB); 396 } 397 $iterator->append($ancestors); 398 } 399 } 400 } 401 } else { 402 $filter->add($filter::EXPANDED); 403 $this->_base->queue->setMailboxOpt('expand', 1); 404 405 foreach (array_filter(IMP_Mailbox::formFrom(json_decode($this->vars->mboxes))) as $val) { 406 $filter->iterator = new IMP_Ftree_Iterator($val->tree_elt); 407 $iterator->append($filter); 408 $ftree->expand($val); 409 } 410 } 411 412 array_map( 413 array($ftree->eltdiff, 'add'), 414 array_unique(iterator_to_array($iterator, false)) 415 ); 416 417 if ($this->vars->initial) { 418 $session->start(); 419 420 /* We need at least 1 changed mailbox. If not, something went 421 * wrong and we should reinitialize the folder list. */ 422 if (!$ftree->eltdiff->changed_elts) { 423 $this->vars->reload = true; 424 $this->listMailboxes(); 425 $this->vars->reload = false; 426 } 427 } 428 429 return true; 430 } 431 432 /** 433 * AJAX action: Initialize dynamic view. 434 * 435 * @see IMP_Ajax_Application_Handler_Common#viewPort() 436 * @see listMailboxes() 437 * 438 * @return boolean True. 439 */ 440 public function dynamicInit() 441 { 442 $this->_base->callAction('viewPort'); 443 444 $this->vars->initial = 1; 445 $this->vars->mboxes = json_encode(array($this->vars->mailbox)); 446 $this->listMailboxes(); 447 448 $this->_base->queue->flagConfig(Horde_Registry::VIEW_DYNAMIC); 449 450 return true; 451 } 452 453 /** 454 * AJAX action: Modify list of polled mailboxes. 455 * 456 * Variables used: 457 * - add: (integer) 1 to add to the poll list, 0 to remove. 458 * - mbox: (string) The full mailbox name to modify (base64url encoded). 459 * 460 * @return mixed False on failure, or an object with the following 461 * entries: 462 * - add: (integer) 1 if added to the poll list, 0 if removed. 463 * - mbox: (string) The full mailbox name modified. 464 */ 465 public function modifyPoll() 466 { 467 global $injector; 468 469 if (!$this->vars->mbox) { 470 return false; 471 } 472 473 $mbox = IMP_Mailbox::formFrom($this->vars->mbox); 474 475 $result = new stdClass; 476 $result->add = intval($this->vars->add); 477 $result->mbox = $this->vars->mbox; 478 479 if ($this->vars->add) { 480 $injector->getInstance('IMP_Ftree')->poll->addPollList($mbox); 481 $this->_base->queue->poll($mbox); 482 $GLOBALS['notification']->push(sprintf(_("\"%s\" mailbox now polled for new mail."), $mbox->display), 'horde.success'); 483 } else { 484 $injector->getInstance('IMP_Ftree')->poll->removePollList($mbox); 485 $GLOBALS['notification']->push(sprintf(_("\"%s\" mailbox no longer polled for new mail."), $mbox->display), 'horde.success'); 486 } 487 488 return $result; 489 } 490 491 /** 492 * AJAX action: [un]Subscribe to a mailbox. 493 * 494 * Variables used: 495 * - mbox: (string) The full mailbox name to [un]subscribe to (base64url 496 * encoded). 497 * - sub: (integer) 1 to subscribe, empty to unsubscribe. 498 * - subfolders: (boolean) [Un]subscribe to all subfolders? 499 * 500 * @return boolean True on success, false on failure. 501 */ 502 public function subscribe() 503 { 504 return IMP_Mailbox::formFrom($this->vars->mbox)->subscribe($this->vars->sub, array( 505 'subfolders' => !empty($this->vars->subfolders) 506 )); 507 } 508 509 /** 510 * AJAX action: Import a mailbox. 511 * 512 * Variables used: 513 * - import_mbox: (string) The mailbox to import into (base64url 514 * encoded). 515 * 516 * @return object Returns response object to display JSON HTML-encoded: 517 * - action: (string) The action name (importMailbox). 518 * - mbox: (string) The mailbox the messages were imported to (base64url 519 * encoded). 520 */ 521 public function importMailbox() 522 { 523 global $injector, $notification; 524 525 $mbox = IMP_Mailbox::formFrom($this->vars->import_mbox); 526 527 try { 528 $notification->push($injector->getInstance('IMP_Mbox_Import')->import($mbox, 'import_file'), 'horde.success'); 529 $this->_base->queue->poll($mbox); 530 } catch (Horde_Exception $e) { 531 $notification->push($e); 532 } 533 534 $result = new stdClass; 535 $result->action = 'importMailbox'; 536 $result->mbox = $this->vars->import_mbox; 537 538 return new Horde_Core_Ajax_Response_HordeCore_JsonHtml($result); 539 } 540 541 /** 542 * AJAX action: Flag messages. 543 * 544 * See the list of variables needed for IMP_Ajax_Application#changed() and 545 * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form 546 * parameters needed. Additional variables used: 547 * - add: (integer) Set the flag? 548 * - flags: (string) The flags to set (JSON serialized array). 549 * 550 * @return boolean True on success, false on failure. 551 */ 552 public function flagMessages() 553 { 554 global $injector; 555 556 if (!$this->vars->flags || !count($this->_base->indices)) { 557 return false; 558 } 559 560 $change = $this->_base->changed(true); 561 562 if (is_null($change)) { 563 return false; 564 } 565 566 $flags = json_decode($this->vars->flags); 567 568 /* Check for non-system flags. If we find any, and the server supports 569 * CONDSTORE, we should make sure that these flags are only updated if 570 * nobody else has altered the flags. */ 571 $system_flags = array( 572 Horde_Imap_Client::FLAG_ANSWERED, 573 Horde_Imap_Client::FLAG_DELETED, 574 Horde_Imap_Client::FLAG_DRAFT, 575 Horde_Imap_Client::FLAG_FLAGGED, 576 Horde_Imap_Client::FLAG_RECENT, 577 Horde_Imap_Client::FLAG_SEEN 578 ); 579 580 $unchangedsince = null; 581 if (!$this->_base->indices->mailbox->search && 582 $this->vars->viewport->cacheid && 583 array_diff($flags, $system_flags)) { 584 $imp_imap = $this->_base->indices->mailbox->imp_imap; 585 $parsed = $imp_imap->parseCacheId($this->vars->viewport->cacheid); 586 587 try { 588 $unchangedsince[strval($this->_base->indices->mailbox)] = $imp_imap->sync($this->_base->indices->mailbox, $parsed['token'], array( 589 'criteria' => Horde_Imap_Client::SYNC_UIDVALIDITY 590 ))->highestmodseq; 591 } catch (Horde_Imap_Client_Exception_Sync $e) {} 592 } 593 594 $res = $injector->getInstance('IMP_Message')->flag(array( 595 ($this->vars->add ? 'add' : 'remove') => $flags 596 ), $this->_base->indices, array( 597 'unchangedsince' => $unchangedsince 598 )); 599 600 if (!$res) { 601 $this->_base->checkUidvalidity(); 602 return false; 603 } 604 605 if (in_array(Horde_Imap_Client::FLAG_SEEN, $flags)) { 606 $this->_base->queue->poll(array_keys($this->_base->indices->indices())); 607 } 608 609 $this->_base->addTask('viewport', $change ? $this->_base->viewPortData(true) : new IMP_Ajax_Application_Viewport($this->_base->indices->mailbox)); 610 611 return true; 612 } 613 614 /** 615 * AJAX action: Add contact. 616 * 617 * Variables used: 618 * - addr: (string) [JSON array] Address list. 619 * 620 * @return boolean True on success, false on failure. 621 */ 622 public function addContact() 623 { 624 global $injector, $notification; 625 626 $addr_ob = $injector->getInstance('IMP_Dynamic_AddressList')->parseAddressList($this->vars->addr); 627 628 // TODO: Currently supports only a single, non-group contact. 629 $ob = $addr_ob[0]; 630 if (!$ob) { 631 return false; 632 } elseif ($ob instanceof Horde_Mail_Rfc822_Group) { 633 $notification->push(_("Adding group lists not currently supported."), 'horde.warning'); 634 return false; 635 } 636 637 try { 638 $injector->getInstance('IMP_Contacts')->addAddress($ob->bare_address, $ob->personal); 639 $notification->push(sprintf(_("%s was successfully added to your address book."), $ob->label), 'horde.success'); 640 return true; 641 } catch (Horde_Exception $e) { 642 $notification->push($e); 643 return false; 644 } 645 } 646 647 /** 648 * AJAX action: Blacklist/whitelist addresses from messages. 649 * 650 * See the list of variables needed for IMP_Ajax_Application#changed(), 651 * IMP_Ajax_Application#deleteMsgs(), and 652 * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form 653 * parameters needed. Additional variables used: 654 * - blacklist: (integer) 1 to blacklist, 0 to whitelist. 655 * 656 * @return boolean True on success. 657 */ 658 public function blacklist() 659 { 660 if (!count($this->_base->indices)) { 661 return false; 662 } 663 664 if ($this->vars->blacklist) { 665 $change = $this->_base->changed(false); 666 if (!is_null($change)) { 667 try { 668 if ($GLOBALS['injector']->getInstance('IMP_Filter')->blacklistMessage($this->_base->indices, false)) { 669 $this->_base->deleteMsgs($this->_base->indices, $change); 670 return true; 671 } 672 } catch (Horde_Exception $e) { 673 $this->_base->checkUidvalidity(); 674 } 675 } 676 } else { 677 try { 678 $GLOBALS['injector']->getInstance('IMP_Filter')->whitelistMessage($this->_base->indices, false); 679 return true; 680 } catch (Horde_Exception $e) { 681 $this->_base->checkUidvalidity(); 682 } 683 } 684 685 return false; 686 } 687 688 /** 689 * AJAX action: Return the MIME tree representation of the message. 690 * 691 * See the list of variables needed for IMP_Ajax_Application#changed() and 692 * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form 693 * parameters needed. Additional variables used: 694 * - preview: (integer) If set, return preview data. Otherwise, return 695 * full data. 696 * 697 * @return mixed On error will return null. 698 * Otherwise an object with the following entries: 699 * - tree: (string) The MIME tree representation of the message. 700 * If viewing preview, on error this object will contain error 701 * and errortype properties. 702 */ 703 public function messageMimeTree() 704 { 705 $result = new stdClass; 706 707 try { 708 $imp_contents = $GLOBALS['injector']->getInstance('IMP_Factory_Contents')->create($this->_base->indices); 709 $result->tree = $imp_contents->getTree()->getTree(true); 710 } catch (IMP_Exception $e) { 711 if (!$this->vars->preview) { 712 throw $e; 713 } 714 715 $result->preview->error = $e->getMessage(); 716 $result->preview->errortype = 'horde.error'; 717 $result->preview->buid = $this->vars->buid; 718 $result->preview->view = $this->vars->view; 719 } 720 721 return $result; 722 } 723 724 /** 725 * AJAX action: Return a list of address objects used to build an address 726 * header for a message. 727 * 728 * See the list of variables needed for IMP_Ajax_Application#changed() and 729 * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form 730 * parameters needed. Additional variables used: 731 * - header: (integer) If set, return preview data. Otherwise, return 732 * full data. 733 * 734 * @return object An object with the following entries: 735 * - hdr_data: (object) Contains header names as keys and lists of 736 * address objects as values. 737 * @throws IMP_Exception 738 */ 739 public function addressHeader() 740 { 741 $show_msg = new IMP_Ajax_Application_ShowMessage($this->_base->indices); 742 743 $hdr = $this->vars->header; 744 745 $result = new stdClass; 746 $result->hdr_data->$hdr = (object)$show_msg->getAddressHeader($this->vars->header, null); 747 748 return $result; 749 } 750 751 /** 752 * AJAX action: Delete an attachment from compose data. 753 * 754 * Variables used: 755 * - atc_indices: (string) [JSON array] Attachment IDs to delete. 756 * - imp_compose: (string) The IMP_Compose cache identifier. 757 * - quiet: (boolean) If true, don't output notifications. 758 * 759 * @return array The list of attchment IDs that were deleted. 760 */ 761 public function deleteAttach() 762 { 763 global $injector, $notification; 764 765 $result = array(); 766 767 if (isset($this->vars->atc_indices)) { 768 $imp_compose = $injector->getInstance('IMP_Factory_Compose')->create($this->vars->imp_compose); 769 foreach (json_decode($this->vars->atc_indices) as $val) { 770 if (isset($imp_compose[$val])) { 771 if (empty($this->vars->quiet)) { 772 $notification->push(sprintf(_("Deleted attachment \"%s\"."), Horde_Mime::decode($imp_compose[$val]->getPart()->getName(true))), 'horde.success'); 773 } 774 unset($imp_compose[$val]); 775 $result[] = $val; 776 $this->_base->queue->compose($imp_compose); 777 } 778 } 779 } 780 781 if (empty($result) && empty($this->vars->quiet)) { 782 $notification->push(_("At least one attachment could not be deleted."), 'horde.error'); 783 } 784 785 return $result; 786 } 787 788 /** 789 * AJAX action: Purge deleted messages. 790 * 791 * See the list of variables needed for IMP_Ajax_Application#changed() and 792 * IMP_Ajax_Application#deleteMsgs(). 793 * 794 * @return boolean True on success. 795 */ 796 public function purgeDeleted() 797 { 798 global $injector; 799 800 $change = $this->_base->changed(true); 801 if (is_null($change)) { 802 return false; 803 } 804 805 if (!$change) { 806 $change = ($this->_base->indices->mailbox->getSort()->sortby == Horde_Imap_Client::SORT_THREAD); 807 } 808 809 $expunged = $injector->getInstance('IMP_Message')->expungeMailbox(array(strval($this->_base->indices->mailbox) => 1), array('list' => true)); 810 811 if (!($expunge_count = count($expunged))) { 812 return false; 813 } 814 815 $GLOBALS['notification']->push(sprintf(ngettext("%d message was purged from \"%s\".", "%d messages were purged from \"%s\".", $expunge_count), $expunge_count, $this->_base->indices->mailbox->display), 'horde.success'); 816 817 $indices = new IMP_Indices_Mailbox(); 818 $indices->buids = $this->_base->indices->mailbox->toBuids($expunged); 819 $indices->mailbox = $this->_base->indices->mailbox; 820 $indices->indices = $expunged; 821 822 $this->_base->deleteMsgs($indices, $change, true); 823 $this->_base->queue->poll($this->_base->indices->mailbox); 824 825 return true; 826 } 827 828 /** 829 * AJAX action: Send a Message Disposition Notification (MDN). 830 * 831 * Mailbox/indices form parameters needed. 832 * 833 * @return mixed False on failure, or an object with these properties: 834 * - buid: (integer) BUID of message. 835 * - mbox: (string) Mailbox of message (base64url encoded). Only 836 * returned if view is a search mailbox. 837 * - uid: (string) Mailbox of message (base64url encoded). Only 838 * returned if view is a search mailbox. 839 * - view: (string) The view ID. 840 */ 841 public function sendMDN() 842 { 843 global $injector, $notification; 844 845 if (count($this->_base->indices) != 1) { 846 return false; 847 } 848 849 try { 850 $contents = $injector->getInstance('IMP_Factory_Contents')->create($this->_base->indices); 851 } catch (IMP_Imap_Exception $e) { 852 $e->notify(_("The Message Disposition Notification was not sent. This is what the server said") . ': ' . $e->getMessage()); 853 return false; 854 } 855 856 list($mbox, $uid) = $this->_base->indices->getSingle(); 857 try { 858 $injector->getInstance('IMP_Message_Ui')->MDNCheck( 859 new IMP_Indices($mbox, $uid), 860 $contents->getHeaderAndMarkAsSeen(), 861 true 862 ); 863 } catch (Horde_Exception $e) { 864 $notification->push(_("The Message Disposition Notification was not sent. This is what the server said") . ': ' . $e->getMessage(), 'horde.warning'); 865 return false; 866 } 867 868 $notification->push(_("The Message Disposition Notification was sent successfully."), 'horde.success'); 869 870 list($view, $buid) = $this->_base->indices->buids->getSingle(); 871 list($mbox, $uid) = $this->_base->indices->getSingle(); 872 873 $result = new stdClass; 874 $result->buid = $buid; 875 $result->view = $view->form_to; 876 877 if ($view != $mbox) { 878 $result->mbox = $mbox->form_to; 879 $result->uid = $uid; 880 } 881 882 return $result; 883 } 884 885 /** 886 * AJAX action: strip attachment. 887 * 888 * See the list of variables needed for IMP_Ajax_Application#changed() and 889 * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form 890 * parameters needed. 891 * 892 * @return mixed False on failure, or an object with these properties: 893 * - newbuid: (integer) BUID of new message. 894 * - newmbox: (string) Mailbox of new message (base64url encoded). 895 */ 896 public function stripAttachment() 897 { 898 global $injector, $notification; 899 900 if (count($this->_base->indices) != 1) { 901 return false; 902 } 903 904 $change = $this->_base->changed(true); 905 if (is_null($change)) { 906 return false; 907 } 908 909 try { 910 $this->_base->indices = new IMP_Indices_Mailbox( 911 $this->_base->indices->mailbox, 912 $injector->getInstance('IMP_Message')->stripPart($this->_base->indices, $this->vars->id) 913 ); 914 } catch (IMP_Exception $e) { 915 $notification->push($e); 916 return false; 917 } 918 919 $notification->push(_("Attachment successfully stripped."), 'horde.success'); 920 921 $result = new stdClass; 922 list($result->newmbox, $result->newbuid) = $this->_base->indices->getSingle(); 923 $result->newmbox = $result->newmbox->form_to; 924 925 $this->_base->queue->message($this->_base->indices, true); 926 $this->_base->addTask('viewport', $this->_base->viewPortData(true)); 927 928 return $result; 929 } 930 931 /** 932 * AJAX action: Convert HTML to text (compose data). 933 * 934 * Variables used: 935 * - data: (string) [JSON array] List of data to convert. Keys are UIDs 936 * used to idetify the return values. Values are arrays with 937 * these keys: 938 * - changed: (integer) Has the text changed from the original? 939 * - text: (string) The text to convert. 940 * - imp_compose: (string) The IMP_Compose cache identifier. 941 * 942 * @return object An object with the following entries: 943 * - text: (array) Array with keys as UIDs and values as the converted 944 * text string. 945 */ 946 public function html2Text() 947 { 948 return $this->_convertText('text'); 949 } 950 951 /** 952 * AJAX action: Convert text to HTML (compose data). 953 * 954 * Variables used: 955 * - data: (string) [JSON array] List of data to convert. Keys are UIDs 956 * used to idetify the return values. Values are arrays with 957 * these keys: 958 * - changed: (integer) Has the text changed from the original? 959 * - text: (string) The text to convert. 960 * - imp_compose: (string) The IMP_Compose cache identifier. 961 * 962 * @return object An object with the following entries: 963 * - text: (array) Array with keys as UIDs and values as the converted 964 * text string. 965 */ 966 public function text2Html() 967 { 968 return $this->_convertText('html'); 969 } 970 971 /** 972 * Helper for html2Text() and text2Html(). 973 * 974 * @internal 975 */ 976 protected function _convertText($mode) 977 { 978 global $injector; 979 980 $compose = null; 981 982 $result = new stdClass; 983 $result->text = array(); 984 985 foreach (json_decode($this->vars->data, true) as $key => $val) { 986 $tmp = null; 987 988 if (empty($val['changed'])) { 989 if (!$compose) { 990 $compose = $this->_base->initCompose(); 991 } 992 993 switch ($compose->compose->replyType()) { 994 case IMP_Compose::FORWARD_BODY: 995 case IMP_Compose::FORWARD_BOTH: 996 $data = $compose->compose->forwardMessageText($compose->contents, array( 997 'format' => $mode 998 )); 999 $tmp = $data['body']; 1000 break; 1001 1002 case IMP_Compose::REPLY_ALL: 1003 case IMP_Compose::REPLY_LIST: 1004 case IMP_Compose::REPLY_SENDER: 1005 $data = $compose->compose->replyMessageText($compose->contents, array( 1006 'format' => $mode 1007 )); 1008 $tmp = $data['body']; 1009 break; 1010 } 1011 } 1012 1013 $result->text[$key] = is_null($tmp) 1014 ? $injector->getInstance('IMP_Compose_Ui')->convertComposeText($val['text'], $mode) 1015 : $tmp; 1016 } 1017 1018 return $result; 1019 } 1020 1021 /** 1022 * AJAX action: Add an attachment to a compose message (from the ckeditor 1023 * plugin). 1024 * 1025 * Variables used: 1026 * - CKEditorFuncNum: (integer) CKEditor function identifier to call 1027 * when returning URL data 1028 * - composeCache: (string) The IMP_Compose cache identifier. 1029 * 1030 * @return Horde_Core_Ajax_Response_Raw text/html return containing 1031 * javascript code to update the 1032 * URL parameter in CKEditor. 1033 */ 1034 public function addAttachmentCkeditor() 1035 { 1036 global $injector; 1037 1038 $data = $url = null; 1039 1040 if (isset($this->vars->composeCache)) { 1041 $imp_compose = $injector->getInstance('IMP_Factory_Compose')->create($this->vars->composeCache); 1042 1043 if ($imp_compose->canUploadAttachment()) { 1044 try { 1045 $atc_ob = $imp_compose->addAttachmentFromUpload('upload'); 1046 if ($atc_ob[0] instanceof IMP_Compose_Exception) { 1047 throw $atc_ob[0]; 1048 } 1049 1050 $atc_ob[0]->related = true; 1051 1052 $data = array( 1053 IMP_Compose::RELATED_ATTR => 'src;' . $atc_ob[0]->id 1054 ); 1055 $url = strval($atc_ob[0]->viewUrl()); 1056 } catch (IMP_Compose_Exception $e) { 1057 $data = $e->getMessage(); 1058 } 1059 } else { 1060 $data = _("Uploading attachments has been disabled on this server."); 1061 } 1062 } else { 1063 $data = _("Your attachment was not uploaded. Most likely, the file exceeded the maximum size allowed by the server configuration."); 1064 } 1065 1066 return new Horde_Core_Ajax_Response_Raw( 1067 '<html>' . 1068 Horde::wrapInlineScript(array( 1069 'window.parent.CKEDITOR.tools.callFunction(' . $this->vars->CKEditorFuncNum . ',' . json_encode($url) . ',' . json_encode($data) . ')' 1070 )) . 1071 '</html>', 1072 'text/html' 1073 ); 1074 } 1075 1076 /** 1077 * AJAX action: Is the given mailbox fixed? Called dynamically to delay 1078 * retrieval of ACLs of all visible mailboxes at initialization. 1079 * 1080 * Variables used: 1081 * - mbox: (integer) The mailbox name. 1082 * 1083 * @return object An object with the following entires: 1084 * - fixed: (boolean) True if the mailbox is fixed. 1085 */ 1086 public function isFixedMbox() 1087 { 1088 $result = new stdClass; 1089 $result->fixed = !(IMP_Mailbox::formFrom($this->vars->mbox)->access_deletembox); 1090 return $result; 1091 1092 } 1093 1094 /** 1095 * AJAX action: Create an IMAP flag. 1096 * 1097 * Variables used: 1098 * - flagcolor: (string) Background color for flag label. 1099 * - flagname: (string) Flag name. 1100 * 1101 * @return object An object with the following properties: 1102 * <pre> 1103 * - success: (boolean) True if successful. 1104 * </pre> 1105 */ 1106 public function createFlag() 1107 { 1108 global $injector, $notification; 1109 1110 $ret = new stdClass; 1111 $ret->success = true; 1112 1113 $imp_flags = $injector->getInstance('IMP_Flags'); 1114 1115 try { 1116 $imapflag = $imp_flags->addFlag($this->vars->flagname); 1117 } catch (IMP_Exception $e) { 1118 $notification->push($e, 'horde.error'); 1119 $ret->success = false; 1120 return $ret; 1121 } 1122 1123 if (!empty($this->vars->flagcolor)) { 1124 $imp_flags->updateFlag($this->vars->flagname, 'bgcolor', $this->vars->flagcolor); 1125 } 1126 1127 $this->vars->add = true; 1128 $this->vars->flags = json_encode(array($imapflag)); 1129 $this->flagMessages(); 1130 1131 $this->_base->queue->flagConfig(Horde_Registry::VIEW_DYNAMIC); 1132 1133 $name = 'imp:viewport'; 1134 if ($this->_base->tasks->$name) { 1135 $this->_base->tasks->$name->addFlagMetadata(); 1136 } 1137 1138 return $ret; 1139 } 1140 1141 /** 1142 * AJAX action: Generate the sent-mail select list. 1143 * 1144 * Variables used: NONE 1145 * 1146 * @return object An object with the following properties: 1147 * <pre> 1148 * - flist: (array) TODO 1149 * </pre> 1150 */ 1151 public function sentMailList() 1152 { 1153 global $injector; 1154 1155 /* Check to make sure the sent-mail mailboxes are created; they need 1156 * to exist to show up in drop-down list. */ 1157 $identity = $injector->getInstance('IMP_Identity'); 1158 foreach (array_keys($identity->getAll('id')) as $ident) { 1159 $mbox = $identity->getValue(IMP_Mailbox::MBOX_SENT, $ident); 1160 if ($mbox instanceof IMP_Mailbox) { 1161 $mbox->create(); 1162 } 1163 } 1164 1165 $flist = array(); 1166 $iterator = new IMP_Ftree_IteratorFilter($injector->getInstance('IMP_Ftree')); 1167 $iterator->add($iterator::NONIMAP); 1168 1169 foreach ($iterator as $val) { 1170 $mbox_ob = $val->mbox_ob; 1171 $tmp = array( 1172 'f' => $mbox_ob->display, 1173 'l' => Horde_String::abbreviate(str_repeat(' ', 2 * $val->level) . $mbox_ob->display, 30), 1174 'v' => $val->container ? '' : $mbox_ob->form_to 1175 ); 1176 if ($tmp['f'] == $tmp['v']) { 1177 unset($tmp['f']); 1178 } 1179 $flist[] = $tmp; 1180 } 1181 1182 $ret = new stdClass; 1183 $ret->flist = $flist; 1184 1185 return $ret; 1186 } 1187 1188 /** 1189 * AJAX action: Redirect to the filter edit page and pre-populate with 1190 * an e-mail address. 1191 * 1192 * Requires EITHER 'addr' -or- mailbox/indices from form params. 1193 * 1194 * Variables used: 1195 * <pre> 1196 * - addr: (string) The e-mail address to use. 1197 * </pre> 1198 * 1199 * @return Horde_Core_Ajax_Response_HordeCore_Reload Object with URL to 1200 * redirect to. 1201 */ 1202 public function newFilter() 1203 { 1204 global $injector, $notification, $registry; 1205 1206 if (isset($this->vars->addr)) { 1207 $addr_ob = $injector->getInstance('IMP_Dynamic_AddressList')->parseAddressList($this->vars->addr); 1208 } else { 1209 $query = new Horde_Imap_Client_Fetch_Query(); 1210 $query->envelope(); 1211 1212 $imp_imap = $this->_base->indices->mailbox->imp_imap; 1213 list($mbox, $uid) = $this->_base->indices->getSingle(); 1214 $ret = $imp_imap->fetch($mbox, $query, array( 1215 'ids' => $imp_imap->getIdsOb($uid) 1216 )); 1217 1218 $addr_ob = $ret[$uid]->getEnvelope()->from; 1219 } 1220 1221 // TODO: Currently supports only a single, non-group contact. 1222 $ob = $addr_ob[0]; 1223 if (!$ob) { 1224 return false; 1225 } elseif ($ob instanceof Horde_Mail_Rfc822_Group) { 1226 $notification->push(_("Editing group lists not currently supported."), 'horde.warning'); 1227 return false; 1228 } 1229 1230 try { 1231 return new Horde_Core_Ajax_Response_HordeCore_Reload( 1232 $registry->link('mail/newEmailFilter', array( 1233 'email' => $ob->bare_address 1234 )) 1235 ); 1236 } catch (Horde_Exception $e) { 1237 return false; 1238 } 1239 } 1240 1241 /** 1242 * AJAX action: Return the contacts images for a given e-mail address. 1243 * 1244 * Variables used: 1245 * <pre> 1246 * - addr: (string) The e-mail address. 1247 * </pre> 1248 * 1249 * @return object An object with the following properties: 1250 * <pre> 1251 * - avatar: (string) The URL of the avatar image. 1252 * - flag: (string) The URL of the sender's country flag image. 1253 * - flagname: (string) The name of the country of the sender. 1254 * </pre> 1255 */ 1256 public function getContactsImage() 1257 { 1258 $contacts_img = new IMP_Contacts_Image($this->vars->addr); 1259 $out = new stdClass; 1260 1261 try { 1262 $res = $contacts_img->getImage($contacts_img::AVATAR); 1263 $out->avatar = strval($res['url']); 1264 } catch (IMP_Exception $e) {} 1265 1266 try { 1267 $res = $contacts_img->getImage($contacts_img::FLAG); 1268 $out->flag = strval($res['url']); 1269 $out->flagname = $res['desc']; 1270 } catch (IMP_Exception $e) {} 1271 1272 return $out; 1273 } 1274 1275 /** 1276 * AJAX action: Determine the size of a mailbox. 1277 * 1278 * Variables used: 1279 * - mbox: (string) The name of the mailbox to check (base64url 1280 * encoded). 1281 * 1282 * @return object An object with the following properties: 1283 * <pre> 1284 * - size: (string) Formatted size string. 1285 * </pre> 1286 */ 1287 public function mailboxSize() 1288 { 1289 $mbox = IMP_Mailbox::formFrom($this->vars->mbox); 1290 1291 $ret = new stdClass; 1292 $ret->size = $mbox->size; 1293 1294 return $ret; 1295 } 1296 1297} 1298