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 * The IMP_Contents:: class contains all functions related to handling the 16 * content and output of mail messages in IMP. 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_Contents 25{ 26 /* Mask entries for getSummary(). */ 27 const SUMMARY_BYTES = 1; 28 const SUMMARY_SIZE = 2; 29 const SUMMARY_ICON = 4; 30 const SUMMARY_ICON_RAW = 16384; 31 const SUMMARY_DESCRIP = 8; 32 const SUMMARY_DESCRIP_LINK = 16; 33 const SUMMARY_DOWNLOAD = 32; 34 const SUMMARY_DOWNLOAD_ZIP = 64; 35 const SUMMARY_IMAGE_SAVE = 128; 36 const SUMMARY_PRINT = 256; 37 const SUMMARY_PRINT_STUB = 512; 38 const SUMMARY_STRIP = 1024; 39 40 /* Rendering mask entries. */ 41 const RENDER_FULL = 1; 42 const RENDER_INLINE = 2; 43 const RENDER_INLINE_DISP_NO = 4; 44 const RENDER_INFO = 8; 45 const RENDER_INLINE_AUTO = 16; 46 const RENDER_RAW = 32; 47 const RENDER_RAW_FALLBACK = 64; 48 49 /* Header return type for getHeader(). */ 50 const HEADER_OB = 1; 51 const HEADER_TEXT = 2; 52 const HEADER_STREAM = 3; 53 54 /** 55 * Have we scanned for embedded parts? 56 * 57 * @var boolean 58 */ 59 protected $_build = false; 60 61 /** 62 * The list of MIME IDs that consist of embedded data. 63 * 64 * @var array 65 */ 66 protected $_embedded = array(); 67 68 /** 69 * Message header. 70 * 71 * @var mixed 72 */ 73 protected $_header; 74 75 /** 76 * The index of the current message. 77 * 78 * @var IMP_Indices_Mailbox 79 */ 80 protected $_indices; 81 82 /** 83 * The Horde_Mime_Part object for the message. 84 * 85 * @var Horde_Mime_Part 86 */ 87 protected $_message; 88 89 /** 90 * Cached data for the MIME Viewer objects. 91 * 92 * @var object 93 */ 94 protected $_viewcache; 95 96 /** 97 * Constructor. 98 * 99 * @param mixed $in An IMP_Indices_Mailbox or Horde_Mime_Part object. 100 * 101 * @throws IMP_Exception 102 */ 103 public function __construct($in) 104 { 105 if ($in instanceof Horde_Mime_Part) { 106 $this->_message = $in; 107 } else { 108 $this->_indices = $in; 109 110 /* Get the Horde_Mime_Part object for the given UID. */ 111 $query = new Horde_Imap_Client_Fetch_Query(); 112 $query->structure(); 113 114 if (!($ret = $this->_fetchData($query))) { 115 $e = new IMP_Exception(_("Error displaying message: message does not exist on server.")); 116 $e->setLogLevel('NOTICE'); 117 throw $e; 118 } 119 120 $this->_message = $ret->getStructure(); 121 } 122 } 123 124 /** 125 * String representation of object. 126 * 127 * @return string The indices string. 128 */ 129 public function __toString() 130 { 131 return strval($this->getIndicesOb()); 132 } 133 134 /** 135 * Returns the IMAP UID for the current message. 136 * 137 * @return integer The message UID. 138 */ 139 public function getUid() 140 { 141 list(,$uid) = $this->_indices->getSingle(); 142 return $uid; 143 } 144 145 /** 146 * Returns the IMAP mailbox for the current message. 147 * 148 * @return IMP_Mailbox The message mailbox. 149 */ 150 public function getMailbox() 151 { 152 list($mbox,) = $this->_indices->getSingle(); 153 return $mbox; 154 } 155 156 /** 157 * Return an IMP_Indices object for the current message. 158 * 159 * @return IMP_Indices An indices object. 160 */ 161 public function getIndicesOb() 162 { 163 return $this->_indices; 164 } 165 166 /** 167 * Returns the entire body of the message. 168 * 169 * @param array $options Additional options: 170 * - stream: (boolean) If true, return a stream. 171 * DEFAULT: No 172 * 173 * @return mixed The text of the part, or a stream resource if 'stream' 174 * is true. 175 */ 176 public function getBody($options = array()) 177 { 178 if (!$this->_indices) { 179 return $this->_message->toString(array( 180 'headers' => true, 181 'stream' => !empty($options['stream']) 182 )); 183 } 184 185 $query = new Horde_Imap_Client_Fetch_Query(); 186 $query->bodytext(array( 187 'peek' => true 188 )); 189 190 return ($res = $this->_fetchData($query)) 191 ? $res->getBodyText(0, !empty($options['stream'])) 192 : ''; 193 } 194 195 /** 196 * Gets the raw text for one section of the message. 197 * 198 * @param integer $id The ID of the MIME part. 199 * @param array $options Additional options: 200 * - decode: (boolean) Attempt to decode the bodypart on the remote 201 * server. 202 * DEFAULT: No 203 * - length: (integer) If set, only download this many bytes of the 204 * bodypart from the server. 205 * DEFAULT: All data is retrieved. 206 * - mimeheaders: (boolean) Include the MIME headers also? 207 * DEFAULT: No 208 * - stream: (boolean) If true, return a stream. 209 * DEFAULT: No 210 * 211 * @return object Object with the following properties: 212 * <pre> 213 * - data: (mixed) The text of the part or a stream resource if 'stream' 214 * option is true. 215 * - decode: (string) If 'decode' option is true, and bodypart decoded 216 * on server, the content-type of the decoded data. 217 * </pre> 218 */ 219 public function getBodyPart($id, $options = array()) 220 { 221 $ret = new stdClass; 222 $ret->data = ''; 223 $ret->decode = null; 224 225 if (empty($id)) { 226 return $ret; 227 } 228 229 if (!$this->_indices || $this->isEmbedded($id)) { 230 if (empty($options['mimeheaders']) || 231 in_array($id, $this->_embedded)) { 232 $ob = $this->getMIMEPart($id, array('nocontents' => true)); 233 234 if (empty($options['stream'])) { 235 if (!is_null($ob)) { 236 $ret->data = $ob->getContents(); 237 } 238 } else { 239 $ret->data = is_null($ob) 240 ? fopen('php://temp', 'r+') 241 : $ob->getContents(array('stream' => true)); 242 } 243 244 return $ret; 245 } 246 247 $base_id = $id; 248 while (!in_array($base_id, $this->_embedded, true)) { 249 $base_id = Horde_Mime::mimeIdArithmetic($base_id, 'up'); 250 if (is_null($base_id)) { 251 return $ret; 252 } 253 } 254 255 $part = $this->getMIMEPart($base_id, array('nocontents' => true)); 256 $txt = $part->addMimeHeaders()->toString() . 257 "\n" . 258 $part->getContents(); 259 260 try { 261 $body = Horde_Mime_Part::getRawPartText($txt, 'header', '1') . 262 "\n\n" . 263 Horde_Mime_Part::getRawPartText($txt, 'body', '1'); 264 } catch (Horde_Mime_Exception $e) { 265 $body = ''; 266 } 267 268 if (empty($options['stream'])) { 269 $ret->data = $body; 270 return $ret; 271 } 272 273 $ret->data = fopen('php://temp', 'r+'); 274 if (strlen($body)) { 275 fwrite($ret->data, $body); 276 fseek($ret->data, 0); 277 } 278 return $ret; 279 } 280 281 $query = new Horde_Imap_Client_Fetch_Query(); 282 if (substr($id, -2) === '.0') { 283 $rfc822 = true; 284 $id = substr($id, 0, -2); 285 } else { 286 $rfc822 = false; 287 } 288 289 if (!isset($options['length']) || !empty($options['length'])) { 290 $bodypart_params = array( 291 'decode' => !empty($options['decode']), 292 'peek' => true 293 ); 294 295 if (isset($options['length'])) { 296 $bodypart_params['start'] = 0; 297 $bodypart_params['length'] = $options['length']; 298 } 299 300 if ($rfc822) { 301 $bodypart_params['id'] = $id; 302 $query->bodyText($bodypart_params); 303 } else { 304 $query->bodyPart($id, $bodypart_params); 305 } 306 } 307 308 if (!empty($options['mimeheaders'])) { 309 if ($rfc822) { 310 $query->headerText(array( 311 'id' => $id, 312 'peek' => true 313 )); 314 } else { 315 $query->mimeHeader($id, array( 316 'peek' => true 317 )); 318 } 319 } 320 321 if ($res = $this->_fetchData($query)) { 322 try { 323 if (empty($options['mimeheaders'])) { 324 $ret->decode = $res->getBodyPartDecode($id); 325 $ret->data = $rfc822 326 ? $res->getBodyText($id, !empty($options['stream'])) 327 : $res->getBodyPart($id, !empty($options['stream'])); 328 return $ret; 329 } elseif (empty($options['stream'])) { 330 $ret->data = $rfc822 331 ? ($res->getHeaderText($id) . $res->getBodyText($id)) 332 : ($res->getMimeHeader($id) . $res->getBodyPart($id)); 333 return $ret; 334 } 335 336 if ($rfc822) { 337 $data = array( 338 $res->getHeaderText($id, Horde_Imap_Client_Data_Fetch::HEADER_STREAM), 339 $res->getBodyText($id, true) 340 ); 341 } else { 342 $data = array( 343 $res->getMimeHeader($id, Horde_Imap_Client_Data_Fetch::HEADER_STREAM), 344 $res->getBodyPart($id, true) 345 ); 346 } 347 348 $ret->data = Horde_Stream_Wrapper_Combine::getStream($data); 349 return $ret; 350 } catch (Horde_Exception $e) {} 351 } 352 353 if (!empty($options['stream'])) { 354 $ret->data = fopen('php://temp', 'r+'); 355 } 356 357 return $ret; 358 } 359 360 /** 361 * Returns the full message text. 362 * 363 * @param array $options Additional options: 364 * - stream: (boolean) If true, return a stream for bodytext. 365 * DEFAULT: No 366 * 367 * @return mixed The full message text or a stream resource if 'stream' 368 * is true. 369 */ 370 public function fullMessageText($options = array()) 371 { 372 if (!$this->_indices) { 373 return $this->_message->toString(); 374 } 375 376 $query = new Horde_Imap_Client_Fetch_Query(); 377 $query->bodyText(array( 378 'peek' => true 379 )); 380 381 if ($res = $this->_fetchData($query)) { 382 try { 383 if (empty($options['stream'])) { 384 return $this->getHeader(self::HEADER_TEXT) . $res->getBodyText(0); 385 } 386 387 return Horde_Stream_Wrapper_Combine::getStream(array( 388 $this->getHeader(self::HEADER_STREAM), 389 $res->getBodyText(0, true) 390 )); 391 } catch (Horde_Exception $e) {} 392 } 393 394 return empty($options['stream']) 395 ? '' 396 : fopen('php://temp', 'r+'); 397 } 398 399 /** 400 * Returns base header information. 401 * 402 * @param integer $type Return type (HEADER_* constant). 403 * 404 * @return mixed Either a Horde_Mime_Headers object (HEADER_OB), header 405 * text (HEADER_TEXT), or a stream resource (HEADER_STREAM). 406 */ 407 public function getHeader($type = self::HEADER_OB) 408 { 409 return $this->_getHeader($type, false); 410 } 411 412 /** 413 * Returns base header information and marks the message as seen. 414 * 415 * @param integer $type See getHeader(). 416 * 417 * @return mixed See getHeader(). 418 */ 419 public function getHeaderAndMarkAsSeen($type = self::HEADER_OB) 420 { 421 $mbox = $this->getMailbox(); 422 423 if ($mbox->readonly) { 424 $seen = false; 425 } else { 426 $seen = true; 427 428 if (isset($this->_header)) { 429 try { 430 $imp_imap = $mbox->imp_imap; 431 $imp_imap->store($mbox, array( 432 'add' => array( 433 Horde_Imap_Client::FLAG_SEEN 434 ), 435 'ids' => $imp_imap->getIdsOb($this->getUid()) 436 )); 437 } catch (Exception $e) {} 438 } 439 } 440 441 return $this->_getHeader($type, $seen); 442 } 443 444 /** 445 * Returns base header information. 446 * 447 * @param integer $type See getHeader(). 448 * @param boolean $seen Mark message as seen? 449 * 450 * @return mixed See getHeader(). 451 */ 452 protected function _getHeader($type, $seen) 453 { 454 if (!isset($this->_header)) { 455 if (!$this->_indices) { 456 $this->_header = $this->_message->addMimeHeaders(); 457 } else { 458 $query = new Horde_Imap_Client_Fetch_Query(); 459 $query->headerText(array( 460 'peek' => !$seen 461 )); 462 463 $this->_header = ($res = $this->_fetchData($query)) 464 ? $res 465 : new Horde_Imap_Client_Data_Fetch(); 466 } 467 } 468 469 switch ($type) { 470 case self::HEADER_OB: 471 return $this->_indices 472 ? $this->_header->getHeaderText(0, Horde_Imap_Client_Data_Fetch::HEADER_PARSE) 473 : $this->_header; 474 475 case self::HEADER_TEXT: 476 return $this->_indices 477 ? $this->_header->getHeaderText() 478 : $this->_header->toString(); 479 480 case self::HEADER_STREAM: 481 if ($this->_indices) { 482 return $this->_header->getHeaderText(0, Horde_Imap_Client_Data_Fetch::HEADER_STREAM); 483 } 484 485 $stream = new Horde_Support_StringStream($this->_header->toString()); 486 $stream->fopen(); 487 return $stream; 488 } 489 } 490 491 /** 492 * Returns the Horde_Mime_Part object. 493 * 494 * @return Horde_Mime_Part A Horde_Mime_Part object. 495 */ 496 public function getMIMEMessage() 497 { 498 return $this->_message; 499 } 500 501 /** 502 * Fetch a part of a MIME message. 503 * 504 * @param integer $id The MIME index of the part requested. 505 * @param array $options Additional options: 506 * - length: (integer) If set, only download this many bytes of the 507 * bodypart from the server. 508 * DEFAULT: All data is retrieved. 509 * - nocontents: (boolean) If true, don't add the contents to the part 510 * DEFAULT: Contents are added to the part 511 * 512 * @return Horde_Mime_Part The raw MIME part asked for (reference). 513 */ 514 public function getMIMEPart($id, $options = array()) 515 { 516 $this->_buildMessage(); 517 518 $part = $this->_message->getPart($id); 519 520 /* Ticket #9201: Treat 'ISO-8859-1' as 'windows-1252'. 1252 has some 521 * characters (e.g. euro sign, back quote) not in 8859-1. There 522 * shouldn't be any issue doing this since the additional code points 523 * in 1252 don't map to anything in 8859-1. */ 524 if ($part && 525 (strcasecmp($part->getCharset(), 'ISO-8859-1') === 0)) { 526 $part->setCharset('windows-1252'); 527 } 528 529 /* Don't download contents of entire body if ID == 0 (indicating the 530 * body of the main multipart message). I'm pretty sure we never 531 * want to download the body of that part here. */ 532 if (!empty($id) && 533 !is_null($part) && 534 (substr($id, -2) != '.0') && 535 empty($options['nocontents']) && 536 $this->_indices && 537 !$part->getContents(array('stream' => true))) { 538 $body = $this->getBodyPart($id, array( 539 'decode' => true, 540 'length' => empty($options['length']) ? null : $options['length'], 541 'stream' => true 542 )); 543 $part->setContents($body->data, array( 544 'encoding' => $body->decode, 545 'usestream' => true 546 )); 547 } 548 549 return $part; 550 } 551 552 /** 553 * Render a MIME Part. 554 * 555 * @param string $mime_id The MIME ID to render. 556 * @param integer $mode One of the RENDER_ constants. 557 * @param array $options Additional options: 558 * - autodetect: (boolean) Attempt to auto-detect MIME type? 559 * - mime_part: (Horde_Mime_Part) The MIME part to render. 560 * - type: (string) Use this MIME type instead of the MIME type 561 * identified in the MIME part. 562 * 563 * @return array See Horde_Mime_Viewer_Base::render(). The following 564 * fields may also be present in addition to the fields 565 * defined in Horde_Mime_Viewer_Base: 566 * - attach: (boolean) Force display of this part as an attachment. 567 * - js: (array) A list of javascript commands to run after the content 568 * is displayed on screen. 569 * - name: (string) Contains the MIME name information. 570 * - wrap: (string) If present, indicates that this part, and all child 571 * parts, will be wrapped in a DIV with the given class name. 572 */ 573 public function renderMIMEPart($mime_id, $mode, array $options = array()) 574 { 575 $this->_buildMessage(); 576 577 $mime_part = empty($options['mime_part']) 578 ? $this->getMIMEPart($mime_id) 579 : $options['mime_part']; 580 if (!$mime_part) { 581 return array($mime_id => null); 582 } 583 584 if (!empty($options['autodetect']) && 585 ($tempfile = Horde::getTempFile()) && 586 ($fp = fopen($tempfile, 'w')) && 587 !is_null($contents = $mime_part->getContents(array('stream' => true)))) { 588 rewind($contents); 589 while (!feof($contents)) { 590 fwrite($fp, fread($contents, 8192)); 591 } 592 fclose($fp); 593 594 $options['type'] = Horde_Mime_Magic::analyzeFile($tempfile, empty($GLOBALS['conf']['mime']['magic_db']) ? null : $GLOBALS['conf']['mime']['magic_db']); 595 } 596 597 $type = empty($options['type']) 598 ? null 599 : $options['type']; 600 601 $viewer = $GLOBALS['injector']->getInstance('IMP_Factory_MimeViewer')->create($mime_part, array('contents' => $this, 'type' => $type)); 602 603 switch ($mode) { 604 case self::RENDER_INLINE: 605 case self::RENDER_INLINE_AUTO: 606 case self::RENDER_INLINE_DISP_NO: 607 $textmode = 'inline'; 608 $limit = $viewer->getConfigParam('limit_inline_size'); 609 610 if ($limit && ($mime_part->getBytes() > $limit)) { 611 $data = ''; 612 $status = new IMP_Mime_Status(array( 613 _("This message part cannot be viewed because it is too large."), 614 sprintf(_("Click %s to download the data."), $this->linkView($mime_part, 'download_attach', _("HERE"))) 615 )); 616 $status->icon('alerts/warning.png', _("Warning")); 617 618 if (method_exists($viewer, 'overLimitText')) { 619 $data = $viewer->overLimitText(); 620 $status->addText(_("The initial portion of this text part is displayed below.")); 621 } 622 623 return array( 624 $mime_id => array( 625 'data' => $data, 626 'name' => '', 627 'status' => $status, 628 'type' => 'text/html; charset=' . 'UTF-8' 629 ) 630 ); 631 } 632 break; 633 634 case self::RENDER_INFO: 635 $textmode = 'info'; 636 break; 637 638 case self::RENDER_RAW: 639 $textmode = 'raw'; 640 break; 641 642 case self::RENDER_RAW_FALLBACK: 643 $textmode = $viewer->canRender('raw') 644 ? 'raw' 645 : 'full'; 646 break; 647 648 case self::RENDER_FULL: 649 default: 650 $textmode = 'full'; 651 break; 652 } 653 654 $ret = $viewer->render($textmode); 655 656 if (empty($ret)) { 657 return ($mode == self::RENDER_INLINE_AUTO) 658 ? $this->renderMIMEPart($mime_id, self::RENDER_INFO, $options) 659 : array(); 660 } 661 662 if (!empty($ret[$mime_id]) && !isset($ret[$mime_id]['name'])) { 663 $ret[$mime_id]['name'] = $mime_part->getName(true); 664 } 665 666 /* Don't show empty parts. */ 667 if (($textmode == 'inline') && 668 !is_null($ret[$mime_id]['data']) && 669 !strlen($ret[$mime_id]['data']) && 670 !isset($ret[$mime_id]['status'])) { 671 $ret[$mime_id] = null; 672 } 673 674 return $ret; 675 } 676 677 /** 678 * Finds the main "body" text part (if any) in a message. 679 * "Body" data is the first text part in the base MIME part. 680 * 681 * @param string $subtype Specifically search for this subtype. 682 * 683 * @return string The MIME ID of the main body part. 684 */ 685 public function findBody($subtype = null) 686 { 687 $this->_buildMessage(); 688 return $this->_message->findBody($subtype); 689 } 690 691 /** 692 * Generate the preview text. 693 * 694 * @return array Array with the following keys: 695 * - cut: (boolean) Was the preview text cut? 696 * - text: (string) The preview text. 697 */ 698 public function generatePreview() 699 { 700 // For preview generation, don't go through overhead of scanning for 701 // embedded parts. Necessary evil, or else very large parts (e.g 702 // 5 MB+ text parts) will take ages to scan. 703 $oldbuild = $this->_build; 704 $this->_build = true; 705 $mimeid = $this->findBody(); 706 707 if (is_null($mimeid)) { 708 $this->_build = $oldbuild; 709 return array('cut' => false, 'text' => ''); 710 } 711 712 $maxlen = empty($GLOBALS['conf']['msgcache']['preview_size']) 713 ? $GLOBALS['prefs']->getValue('preview_maxlen') 714 : $GLOBALS['conf']['msgcache']['preview_size']; 715 716 // Retrieve 3x the size of $maxlen of bodytext data. This should 717 // account for any content-encoding & HTML tags. 718 $pmime = $this->getMIMEPart($mimeid, array('length' => $maxlen * 3)); 719 720 $ptext = Horde_String::convertCharset($pmime->getContents(), $pmime->getCharset(), 'UTF-8'); 721 722 if ($pmime->getType() == 'text/html') { 723 $ptext = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($ptext, 'Html2text'); 724 } 725 726 $this->_build = $oldbuild; 727 728 if (Horde_String::length($ptext) > $maxlen) { 729 return array( 730 'cut' => true, 731 'text' => Horde_String::truncate($ptext, $maxlen) 732 ); 733 } 734 735 return array( 736 'cut' => false, 737 'text' => $ptext 738 ); 739 } 740 741 /** 742 * Get summary info for a MIME ID. 743 * 744 * @param string $id The MIME ID. 745 * @param integer $mask A bitmask indicating what information to return: 746 * <pre> 747 * Always output: 748 * 'type' = MIME type 749 * 750 * IMP_Contents::SUMMARY_BYTES 751 * Output: parts = 'bytes' 752 * 753 * IMP_Contents::SUMMARY_SIZE 754 * Output: parts = 'size' 755 * 756 * IMP_Contents::SUMMARY_ICON 757 * IMP_Contents::SUMMARY_ICON_RAW 758 * Output: parts = 'icon' 759 * 760 * IMP_Contents::SUMMARY_DESCRIP 761 * Output: parts = 'description_raw' 762 * 763 * IMP_Contents::SUMMARY_DESCRIP_LINK 764 * Output: parts = 'description' 765 * 766 * IMP_Contents::SUMMARY_DOWNLOAD 767 * Output: parts = 'download', 'download_url' 768 * 769 * IMP_Contents::SUMMARY_DOWNLOAD_ZIP 770 * Output: parts = 'download_zip' 771 * 772 * IMP_Contents::SUMMARY_IMAGE_SAVE 773 * Output: parts = 'img_save' 774 * 775 * IMP_Contents::SUMMARY_PRINT 776 * IMP_Contents::SUMMARY_PRINT_STUB 777 * Output: parts = 'print' 778 * 779 * IMP_Contents::SUMMARY_STRIP 780 * Output: parts = 'strip' 781 * </pre> 782 * 783 * @return array An array with the requested information. 784 */ 785 public function getSummary($id, $mask = 0) 786 { 787 $autodetect_link = false; 788 $download_zip = (($mask & self::SUMMARY_DOWNLOAD_ZIP) && Horde_Util::extensionExists('zlib')); 789 $param_array = array(); 790 791 $this->_buildMessage(); 792 793 $part = array( 794 'bytes' => null, 795 'download' => null, 796 'download_url' => null, 797 'download_zip' => null, 798 'id' => $id, 799 'img_save' => null, 800 'size' => null, 801 'strip' => null 802 ); 803 804 $mime_part = $this->getMIMEPart($id, array('nocontents' => true)); 805 if (empty($mime_part)) { 806 throw new IMP_Exception('MIME Part not found.'); 807 } 808 $mime_type = $mime_part->getType(); 809 810 /* If this is an attachment that has no specific MIME type info, see 811 * if we can guess a rendering type. */ 812 if (in_array($mime_type, array('application/octet-stream', 'application/base64'))) { 813 $mime_type = Horde_Mime_Magic::filenameToMIME($mime_part->getName()); 814 if ($mime_type == $mime_part->getType()) { 815 $autodetect_link = true; 816 } else { 817 $mime_part = clone $mime_part; 818 $mime_part->setType($mime_type); 819 $param_array['ctype'] = $mime_type; 820 } 821 } 822 $part['type'] = $mime_type; 823 824 /* Is this part an attachment? */ 825 $is_atc = $this->isAttachment($mime_type); 826 827 /* Get bytes/size information. */ 828 if (($mask & self::SUMMARY_BYTES) || 829 $download_zip || 830 ($mask & self::SUMMARY_SIZE)) { 831 $part['bytes'] = $size = $mime_part->getBytes(); 832 $part['size'] = ($size > 1048576) 833 ? sprintf(_("%s MB"), IMP::numberFormat($size / 1048576, 1)) 834 : sprintf(_("%s KB"), max(round($size / 1024), 1)); 835 } 836 837 /* Get part's icon. */ 838 if (($mask & self::SUMMARY_ICON) || 839 ($mask & self::SUMMARY_ICON_RAW)) { 840 $part['icon'] = $GLOBALS['injector']->getInstance('IMP_Factory_MimeViewer')->getIcon($mime_type); 841 if ($mask & self::SUMMARY_ICON) { 842 $part['icon'] = Horde_Themes_Image::tag($part['icon'], array( 843 'attr' => array( 844 'title' => $mime_type 845 ) 846 )); 847 } 848 } else { 849 $part['icon'] = null; 850 } 851 852 /* Get part's description. */ 853 $description = $this->getPartName($mime_part, true); 854 855 if ($mask & self::SUMMARY_DESCRIP_LINK) { 856 if (($can_d = $this->canDisplay($mime_part, self::RENDER_FULL)) || 857 $autodetect_link) { 858 $part['description'] = $this->linkViewJS($mime_part, 'view_attach', htmlspecialchars($description), array('jstext' => sprintf(_("View %s"), $description), 'params' => array_filter(array_merge($param_array, array( 859 'autodetect' => !$can_d 860 ))))); 861 } else { 862 $part['description'] = htmlspecialchars($description); 863 } 864 } 865 if ($mask & self::SUMMARY_DESCRIP) { 866 $part['description_raw'] = $description; 867 } 868 869 /* Download column. */ 870 if (($mask & self::SUMMARY_DOWNLOAD) && 871 $is_atc && 872 (is_null($part['bytes']) || $part['bytes'])) { 873 $part['download'] = $this->linkView($mime_part, 'download_attach', '', array('class' => 'iconImg downloadAtc', 'jstext' => _("Download"))); 874 $part['download_url'] = $this->urlView($mime_part, 'download_attach'); 875 } 876 877 /* Display the compressed download link only if size is greater 878 * than 200 KB. */ 879 if ($is_atc && 880 $download_zip && 881 ($part['bytes'] > 204800)) { 882 $viewer = $GLOBALS['injector']->getInstance('IMP_Factory_MimeViewer')->create($mime_part, array('contents' => $this, 'type' => $mime_type)); 883 if (!$viewer->getMetadata('compressed')) { 884 $part['download_zip'] = $this->linkView($mime_part, 'download_attach', null, array('class' => 'iconImg downloadZipAtc', 'jstext' => sprintf(_("Download %s in .zip Format"), $description), 'params' => array('zip' => 1))); 885 } 886 } 887 888 /* Display the image save link if the required registry calls are 889 * present. */ 890 if (($mask & self::SUMMARY_IMAGE_SAVE) && 891 $GLOBALS['registry']->hasMethod('images/selectGalleries') && 892 ($mime_part->getPrimaryType() == 'image')) { 893 $part['img_save'] = Horde::link('#', _("Save Image in Gallery"), 'iconImg saveImgAtc', null, Horde::popupJs(IMP_Basic_Saveimage::url(), array('params' => array('muid' => strval($this->getIndicesOb()), 'id' => $id), 'height' => 200, 'width' => 450, 'urlencode' => true)) . 'return false;') . '</a>'; 894 } 895 896 /* Add print link? */ 897 if ((($mask & self::SUMMARY_PRINT) || 898 ($mask & self::SUMMARY_PRINT_STUB)) && 899 $this->canDisplay($id, self::RENDER_FULL)) { 900 $part['print'] = ($mask & self::SUMMARY_PRINT) 901 ? $this->linkViewJS($mime_part, 'print_attach', '', array('css' => 'iconImg printAtc', 'jstext' => _("Print"), 'onload' => 'IMP_JS.printWindow', 'params' => $param_array)) 902 : Horde::link('#', _("Print"), 'iconImg printAtc', null, null, null, null, array('mimeid' => $id)) . '</a>'; 903 } 904 905 /* Strip Attachment? Allow stripping of base parts other than the 906 * base multipart and the base text (body) part. */ 907 if (($mask & self::SUMMARY_STRIP) && 908 ($id != 0) && 909 (intval($id) != 1) && 910 (strpos($id, '.') === false)) { 911 $part['strip'] = Horde::link( 912 Horde::selfUrlParams()->add(array( 913 'actionID' => 'strip_attachment', 914 'imapid' => $id, 915 'muid' => strval($this->getIndicesOb()), 916 'token' => $GLOBALS['session']->getToken() 917 )), 918 _("Strip Attachment"), 919 'iconImg deleteImg stripAtc', 920 null, 921 null, 922 null, 923 null, 924 array('mimeid' => $id) 925 ) . '</a>'; 926 } 927 928 return $part; 929 } 930 931 /** 932 * Return the URL to the download/view page. 933 * 934 * @param Horde_Mime_Part $mime_part The MIME part to view. 935 * @param integer $actionID The actionID to perform. 936 * @param array $options Additional options: 937 * - params: (array) A list of any additional parameters that need to be 938 * passed to the download/view page (key => name). 939 * 940 * @return Horde_Url The URL to the download/view page. 941 */ 942 public function urlView($mime_part = null, $actionID = 'view_attach', 943 array $options = array()) 944 { 945 $params = $this->_urlViewParams($mime_part, $actionID, isset($options['params']) ? $options['params'] : array()); 946 947 return (strpos($actionID, 'download_') === 0) 948 ? IMP_Contents_View::downloadUrl($mime_part->getName(true), $params) 949 : Horde::url('view.php', true)->add($params); 950 } 951 952 /** 953 * Generates the necessary URL parameters for the download/view page. 954 * 955 * @param Horde_Mime_Part $mime_part The MIME part to view. 956 * @param integer $actionID The actionID to perform. 957 * @param array $params Additional parameters to pass. 958 * 959 * @return array The array of parameters. 960 */ 961 protected function _urlViewParams($mime_part, $actionID, $params) 962 { 963 /* Add the necessary local parameters. */ 964 $params = array_merge($params, array( 965 'actionID' => $actionID, 966 'id' => isset($params['id']) ? $params['id'] : $mime_part->getMIMEId() 967 )); 968 969 if ($this->_indices) { 970 $params['muid'] = strval($this->getIndicesOb()); 971 } 972 973 return IMP_Contents_View::addToken($params); 974 } 975 976 /** 977 * Generate a link to the download/view page. 978 * 979 * @param Horde_Mime_Part $mime_part The MIME part to view. 980 * @param integer $actionID The actionID value. 981 * @param string $text The ESCAPED (!) link text. 982 * @param array $options Additional parameters: 983 * - class: (string) The CSS class to use. 984 * - jstext: (string) The JS text to use. 985 * - params: (array) A list of any additional parameters that need to be 986 * passed to the download/view page. 987 * 988 * @return string A HTML href link to the download/view page. 989 */ 990 public function linkView($mime_part, $actionID, $text, $options = array()) 991 { 992 $options = array_merge(array( 993 'class' => null, 994 'jstext' => $text, 995 'params' => array() 996 ), $options); 997 998 return Horde::link( 999 $this->urlView($mime_part, $actionID, $options), 1000 $options['jstext'], 1001 $options['class'], 1002 ($actionID == 'download_attach') ? null : strval(new Horde_Support_Randomid()) 1003 ) . $text . '</a>'; 1004 } 1005 1006 /** 1007 * Generate a javascript link to the download/view page. 1008 * 1009 * @param Horde_Mime_Part $mime_part The MIME part to view. 1010 * @param string $actionID The actionID to perform. 1011 * @param string $text The ESCAPED (!) link text. 1012 * @param array $options Additional options: 1013 * - css: (string) The CSS class to use. 1014 * - jstext: (string) The javascript link text. 1015 * - onload: (string) A JS function to run when popup window is 1016 * fully loaded. 1017 * - params: (array) A list of any additional parameters that need to be 1018 * passed to download/view page. (key = name) 1019 * - widget: (boolean) If true use Horde::widget() to generate, 1020 * Horde::link() otherwise. 1021 * 1022 * @return string A HTML href link to the download/view page. 1023 */ 1024 public function linkViewJS($mime_part, $actionID, $text, 1025 $options = array()) 1026 { 1027 if (empty($options['params'])) { 1028 $options['params'] = array(); 1029 } 1030 1031 if (empty($options['jstext'])) { 1032 $options['jstext'] = sprintf(_("View %s"), $mime_part->getDescription(true)); 1033 } 1034 1035 $url = Horde::popupJs(Horde::url('view.php'), array( 1036 'menu' => true, 1037 'onload' => empty($options['onload']) ? 'IMP_JS.resizePopup' : $options['onload'], 1038 'params' => $this->_urlViewParams($mime_part, $actionID, isset($options['params']) ? $options['params'] : array()), 1039 'urlencode' => true 1040 )); 1041 1042 return empty($options['widget']) 1043 ? Horde::link('#', $options['jstext'], empty($options['css']) ? null : $options['css'], null, $url) . $text . '</a>' 1044 : Horde::widget(array('url' => '#', 'class' => empty($options['css']) ? null : $options['css'], 'onclick' => $url, 'title' => $text)); 1045 } 1046 1047 /** 1048 * Determines if a MIME type is an attachment. 1049 * For IMP's purposes, an attachment is any MIME part that can be 1050 * downloaded by itself (i.e. all the data needed to view the part is 1051 * contained within the download data). 1052 * 1053 * @param string $mime_type The MIME type. 1054 * 1055 * @return boolean True if an attachment. 1056 */ 1057 public function isAttachment($mime_type) 1058 { 1059 switch ($mime_type) { 1060 case 'application/ms-tnef': 1061 return false; 1062 } 1063 1064 list($ptype,) = explode('/', $mime_type, 2); 1065 1066 switch ($ptype) { 1067 case 'message': 1068 return in_array($mime_type, array('message/rfc822', 'message/disposition-notification')); 1069 1070 case 'multipart': 1071 return false; 1072 1073 default: 1074 return true; 1075 } 1076 } 1077 1078 /** 1079 * Builds the "virtual" Horde_Mime_Part object by checking for embedded 1080 * parts. 1081 * 1082 * @param array $parts The parts list to process. 1083 */ 1084 protected function _buildMessage($parts = null) 1085 { 1086 global $injector; 1087 1088 if (is_null($parts)) { 1089 if ($this->_build) { 1090 return; 1091 } 1092 $this->_build = true; 1093 $parts = array_keys($this->_message->contentTypeMap()); 1094 $first_id = reset($parts); 1095 } else { 1096 $first_id = null; 1097 } 1098 1099 $last_id = null; 1100 $to_process = array(); 1101 1102 $mv_factory = $injector->getInstance('IMP_Factory_MimeViewer'); 1103 1104 foreach ($parts as $id) { 1105 if (!is_null($last_id) && 1106 (strpos($id, $last_id) === 0)) { 1107 continue; 1108 } 1109 1110 $last_id = null; 1111 1112 $mime_part = $this->getMIMEPart($id, array('nocontents' => true)); 1113 $viewer = $mv_factory->create($mime_part, array('contents' => $this)); 1114 if ($viewer->embeddedMimeParts()) { 1115 $mime_part = $this->getMIMEPart($id); 1116 $viewer->setMIMEPart($mime_part); 1117 $new_part = $viewer->getEmbeddedMimeParts(); 1118 if (!is_null($new_part)) { 1119 $mime_part->addPart($new_part); 1120 $mime_part->buildMimeIds($id); 1121 $this->_embedded[] = $new_part->getMimeId(); 1122 $to_process = array_merge($to_process, array_keys($new_part->contentTypeMap())); 1123 $last_id = $id; 1124 } 1125 } 1126 } 1127 1128 if (!empty($to_process)) { 1129 $this->_buildMessage($to_process); 1130 } 1131 } 1132 1133 /** 1134 * Can this MIME part be displayed in the given mode? 1135 * 1136 * @param mixed $part The MIME part or a MIME ID string. 1137 * @param integer $mask One of the RENDER_ constants. 1138 * @param string $type The type to use (overrides the MIME ID if $id is 1139 * a MIME part). 1140 * 1141 * @return integer The RENDER_ constant of the allowable display. 1142 */ 1143 public function canDisplay($part, $mask, $type = null) 1144 { 1145 if (!is_object($part)) { 1146 $part = $this->getMIMEPart($part, array('nocontents' => true)); 1147 } 1148 if (!$part) { 1149 return 0; 1150 } 1151 $viewer = $GLOBALS['injector']->getInstance('IMP_Factory_MimeViewer')->create($part, array('contents' => $this, 'type' => $type)); 1152 1153 if ($mask & self::RENDER_INLINE_AUTO) { 1154 $mask |= self::RENDER_INLINE | self::RENDER_INFO; 1155 } 1156 1157 if (($mask & self::RENDER_RAW) && $viewer->canRender('raw')) { 1158 return self::RENDER_RAW; 1159 } 1160 1161 if (($mask & self::RENDER_FULL) && $viewer->canRender('full')) { 1162 return self::RENDER_FULL; 1163 } 1164 1165 if ($mask & self::RENDER_INLINE) { 1166 if ($viewer->canRender('inline')) { 1167 return self::RENDER_INLINE; 1168 } 1169 } elseif (($mask & self::RENDER_INLINE_DISP_NO) && 1170 $viewer->canRender('inline')) { 1171 return self::RENDER_INLINE_DISP_NO; 1172 } 1173 1174 if (($mask & self::RENDER_INFO) && $viewer->canRender('info')) { 1175 return self::RENDER_INFO; 1176 } 1177 1178 return 0; 1179 } 1180 1181 /** 1182 * Returns the Content-Type map for the entire message, regenerating 1183 * embedded parts if needed. 1184 * 1185 * @return array See Horde_Mime_Part::contentTypeMap(). 1186 */ 1187 public function getContentTypeMap() 1188 { 1189 $this->_buildMessage(); 1190 return $this->_message->contentTypeMap(); 1191 } 1192 1193 /** 1194 * Returns the MIME part tree of the message. 1195 * 1196 * @param string $renderer Either the tree renderer driver or a full 1197 * class name to use. 1198 * 1199 * @return Horde_Tree_Renderer_Base A tree instance representing the MIME parts. 1200 * @throws Horde_Tree_Exception 1201 */ 1202 public function getTree($renderer = 'Horde_Core_Tree_Renderer_Html') 1203 { 1204 $tree = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Tree')->create('mime-' . $this->getUid(), $renderer, array( 1205 'nosession' => true 1206 )); 1207 $this->_addTreeNodes($tree, $this->_message); 1208 return $tree; 1209 } 1210 1211 /** 1212 * Adds MIME parts to the tree instance. 1213 * 1214 * @param Horde_Tree_Renderer_Base tree A tree instance. 1215 * @param Horde_Mime_Part $part The MIME part to add to the 1216 * tree, including its sub-parts. 1217 * @param string $parent The parent part's MIME id. 1218 */ 1219 protected function _addTreeNodes($tree, $part, $parent = null) 1220 { 1221 $mimeid = $part->getMimeId(); 1222 1223 $summary_mask = self::SUMMARY_ICON_RAW | self::SUMMARY_DESCRIP_LINK | self::SUMMARY_SIZE | self::SUMMARY_DOWNLOAD; 1224 if ($GLOBALS['prefs']->getValue('strip_attachments')) { 1225 $summary_mask += self::SUMMARY_STRIP; 1226 } 1227 1228 $summary = $this->getSummary($mimeid, $summary_mask); 1229 1230 $tree->addNode(array( 1231 'id' => $mimeid, 1232 'parent' => $parent, 1233 'label' => sprintf( 1234 '%s (%s) %s %s', 1235 $summary['description'], 1236 $summary['size'], 1237 $summary['download'], 1238 $summary['strip'] 1239 ), 1240 'params' => array( 1241 'class' => 'partsTreeDiv', 1242 'icon' => $summary['icon'] 1243 ) 1244 )); 1245 1246 foreach ($part->getParts() as $part) { 1247 $this->_addTreeNodes($tree, $part, $mimeid); 1248 } 1249 } 1250 1251 /** 1252 * Get download all list. 1253 * 1254 * @return array An array of downloadable parts. 1255 */ 1256 public function downloadAllList() 1257 { 1258 $ret = array(); 1259 1260 foreach ($this->getContentTypeMap() as $key => $val) { 1261 if ($this->isAttachment($val)) { 1262 $ret[] = $key; 1263 } 1264 } 1265 1266 return $ret; 1267 } 1268 1269 /** 1270 * Injects body contents into the base Horde_Mime_part object. 1271 * 1272 * @param array $ignore A list of MIME IDs to ignore. 1273 * 1274 * @return Horde_Mime_Part The part with body contents set. 1275 */ 1276 public function buildMessageContents($ignore = array()) 1277 { 1278 $message = $this->_message; 1279 $curr_ignore = null; 1280 1281 foreach ($message->contentTypeMap() as $key => $val) { 1282 if (is_null($curr_ignore) && in_array($key, $ignore)) { 1283 $curr_ignore = $key . '.'; 1284 } elseif (is_null($curr_ignore) || 1285 (strpos($key, $curr_ignore) === false)) { 1286 $curr_ignore = null; 1287 if (($key != 0) && 1288 ($val != 'message/rfc822') && 1289 (strpos($val, 'multipart/') === false)) { 1290 $part = $this->getMIMEPart($key); 1291 $message->alterPart($key, $part); 1292 } 1293 } 1294 } 1295 1296 return $message; 1297 } 1298 1299 /** 1300 * Determines if a given MIME part ID is a part of embedded data. 1301 * 1302 * @param string $mime_id The MIME ID. 1303 * 1304 * @return boolean True if the MIME ID is part of embedded data. 1305 */ 1306 public function isEmbedded($mime_id) 1307 { 1308 foreach ($this->_embedded as $val) { 1309 if (($mime_id == $val) || Horde_Mime::isChild($val, $mime_id)) { 1310 return true; 1311 } 1312 } 1313 1314 return false; 1315 } 1316 1317 /** 1318 * Find a MIME type in parent parts. 1319 * 1320 * @param string $id The MIME ID to begin the search at. 1321 * @param string $type The MIME type to search for. 1322 * 1323 * @return mixed Either the requested MIME part, or null if not found. 1324 */ 1325 public function findMimeType($id, $type) 1326 { 1327 $id = Horde_Mime::mimeIdArithmetic($id, 'up'); 1328 1329 while (!is_null($id)) { 1330 if (($part = $this->getMIMEPart($id, array('nocontents' => true))) && 1331 ($part->getType() == $type)) { 1332 return $part; 1333 } 1334 $id = Horde_Mime::mimeIdArithmetic($id, 'up'); 1335 } 1336 1337 return null; 1338 } 1339 1340 /** 1341 * Return the descriptive part label, making sure it is not empty. 1342 * 1343 * @param Horde_Mime_Part $part The MIME Part object. 1344 * @param boolean $use_descrip Use description? If false, uses name. 1345 * 1346 * @return string The part label (non-empty). 1347 */ 1348 public function getPartName(Horde_Mime_Part $part, $use_descrip = false) 1349 { 1350 $name = $use_descrip 1351 ? $part->getDescription(true) 1352 : $part->getName(true); 1353 1354 if ($name) { 1355 return $name; 1356 } 1357 1358 switch ($ptype = $part->getPrimaryType()) { 1359 case 'multipart': 1360 if (($part->getSubType() == 'related') && 1361 ($view_id = $part->getMetaData('viewable_part')) && 1362 ($viewable = $this->getMIMEPart($view_id, array('nocontents' => true)))) { 1363 return $this->getPartName($viewable, $use_descrip); 1364 } 1365 /* Fall-through. */ 1366 1367 case 'application': 1368 case 'model': 1369 $ptype = $part->getSubType(); 1370 break; 1371 } 1372 1373 switch ($ptype) { 1374 case 'audio': 1375 return _("Audio"); 1376 1377 case 'image': 1378 return _("Image"); 1379 1380 case 'message': 1381 case '': 1382 case Horde_Mime_Part::UNKNOWN: 1383 return _("Message"); 1384 1385 case 'multipart': 1386 return _("Multipart"); 1387 1388 case 'text': 1389 return _("Text"); 1390 1391 case 'video': 1392 return _("Video"); 1393 1394 default: 1395 // Attempt to translate this type, if possible. Odds are that 1396 // it won't appear in the dictionary though. 1397 return _(Horde_String::ucfirst($ptype)); 1398 } 1399 } 1400 1401 /** 1402 * Generate inline message display. 1403 * 1404 * @param array $options Options: 1405 * - display_mask: (integer) The mask of display view type to render 1406 * inline (DEFAULT: RENDER_INLINE_AUTO). 1407 * - mask: (integer) The mask needed for a getSummary() call. 1408 * - no_inline_all: (boolean) If true, only display first inline part. 1409 * Subsequent inline parts will be treated as 1410 * attachments. 1411 * - part_info_display: (array) The list of summary fields to display. 1412 * - show_parts: (string) The value of the 'parts_display' pref. 1413 * 1414 * @return array An array with the following keys: 1415 * - atc_parts: (array) The list of attachment MIME IDs. 1416 * - display_ids: (array) The list of display MIME IDs. 1417 * - js_onload: (array) A list of javascript code to run onload. 1418 * - msgtext: (string) The rendered HTML code. 1419 * - one_part: (boolean) If true, the message only consists of one part. 1420 */ 1421 public function getInlineOutput(array $options = array()) 1422 { 1423 global $prefs, $registry; 1424 1425 $atc_parts = $display_ids = $msgtext = $js_onload = $wrap_ids = array(); 1426 $parts_list = $this->getContentTypeMap(); 1427 $text_out = ''; 1428 $view = $registry->getView(); 1429 1430 $contents_mask = isset($options['mask']) 1431 ? $options['mask'] 1432 : 0; 1433 $display_mask = isset($options['display_mask']) 1434 ? $options['display_mask'] 1435 : self::RENDER_INLINE_AUTO; 1436 $no_inline_all = !empty($options['no_inline_all']); 1437 $part_info_display = isset($options['part_info_display']) 1438 ? $options['part_info_display'] 1439 : array(); 1440 $show_parts = isset($options['show_parts']) 1441 ? $options['show_parts'] 1442 : $prefs->getValue('parts_display'); 1443 1444 foreach ($parts_list as $mime_id => $mime_type) { 1445 if (isset($display_ids[$mime_id]) || 1446 isset($atc_parts[$mime_id])) { 1447 continue; 1448 } 1449 1450 if (!($render_mode = $this->canDisplay($mime_id, $display_mask))) { 1451 if ($this->isAttachment($mime_type)) { 1452 if ($show_parts == 'atc') { 1453 $atc_parts[$mime_id] = 1; 1454 } 1455 1456 if ($contents_mask) { 1457 $msgtext[$mime_id] = array( 1458 'text' => $this->_formatSummary($mime_id, $contents_mask, $part_info_display, true) 1459 ); 1460 } 1461 } 1462 continue; 1463 } 1464 1465 $render_part = $this->renderMIMEPart($mime_id, $render_mode); 1466 if (($show_parts == 'atc') && 1467 $this->isAttachment($mime_type) && 1468 (empty($render_part) || 1469 !($render_mode & self::RENDER_INLINE))) { 1470 $atc_parts[$mime_id] = 1; 1471 } 1472 1473 if (empty($render_part)) { 1474 if ($contents_mask && 1475 $this->isAttachment($mime_type)) { 1476 $msgtext[$mime_id] = array( 1477 'text' => $this->_formatSummary($mime_id, $contents_mask, $part_info_display, true) 1478 ); 1479 } 1480 continue; 1481 } 1482 1483 reset($render_part); 1484 while (list($id, $info) = each($render_part)) { 1485 $display_ids[$id] = 1; 1486 1487 if (empty($info)) { 1488 continue; 1489 } 1490 1491 if ($no_inline_all === 1) { 1492 $atc_parts[$id] = 1; 1493 continue; 1494 } 1495 1496 $part_text = ($contents_mask && empty($info['nosummary'])) 1497 ? $this->_formatSummary($id, $contents_mask, $part_info_display, !empty($info['attach'])) 1498 : ''; 1499 1500 if (empty($info['attach'])) { 1501 if (isset($info['status'])) { 1502 if (!is_array($info['status'])) { 1503 $info['status'] = array($info['status']); 1504 } 1505 1506 foreach ($info['status'] as $val) { 1507 if (in_array($view, $val->views)) { 1508 $part_text .= strval($val); 1509 } 1510 } 1511 } 1512 1513 $part_text .= '<div class="mimePartData">' . $info['data'] . '</div>'; 1514 } elseif ($show_parts == 'atc') { 1515 $atc_parts[$id] = 1; 1516 } 1517 1518 $msgtext[$id] = array( 1519 'text' => $part_text, 1520 'wrap' => empty($info['wrap']) ? null : $info['wrap'] 1521 ); 1522 1523 if (isset($info['js'])) { 1524 $js_onload = array_merge($js_onload, $info['js']); 1525 } 1526 1527 if ($no_inline_all) { 1528 $no_inline_all = 1; 1529 } 1530 } 1531 } 1532 1533 if (!empty($msgtext)) { 1534 uksort($msgtext, 'strnatcmp'); 1535 } 1536 1537 reset($msgtext); 1538 while (list($id, $part) = each($msgtext)) { 1539 while (!empty($wrap_ids) && 1540 !Horde_Mime::isChild(end($wrap_ids), $id)) { 1541 array_pop($wrap_ids); 1542 $text_out .= '</div>'; 1543 } 1544 1545 if (!empty($part['wrap'])) { 1546 $text_out .= '<div class="' . $part['wrap'] . '">'; 1547 $wrap_ids[] = $id; 1548 } 1549 1550 $text_out .= '<div class="mimePartBase">' . $part['text'] . '</div>'; 1551 } 1552 1553 $text_out .= str_repeat('</div>', count($wrap_ids)); 1554 1555 if (!strlen($text_out)) { 1556 $text_out = strval(new IMP_Mime_Status(_("There are no parts that can be shown inline."))); 1557 } 1558 1559 $atc_parts = ($show_parts == 'all') 1560 ? array_keys($parts_list) 1561 : array_keys($atc_parts); 1562 1563 return array( 1564 'atc_parts' => $atc_parts, 1565 'display_ids' => array_keys($display_ids), 1566 'js_onload' => $js_onload, 1567 'msgtext' => $text_out, 1568 'one_part' => (count($parts_list) == 1) 1569 ); 1570 } 1571 1572 /** 1573 * Prints out a MIME summary (in HTML). 1574 * 1575 * @param string $id The MIME ID. 1576 * @param integer $mask A bitmask indicating what summary information to 1577 * return. 1578 * @param array $display The fields to display (in this order). 1579 * @param boolean $atc Is this an attachment? 1580 * 1581 * @return string The formatted summary string. 1582 */ 1583 protected function _formatSummary($id, $mask, $display, $atc = false) 1584 { 1585 $summary = $this->getSummary($id, $mask); 1586 $tmp_summary = array(); 1587 1588 foreach ($display as $val) { 1589 if (isset($summary[$val])) { 1590 switch ($val) { 1591 case 'description': 1592 $summary[$val] = '<span class="mimePartInfoDescrip">' . $summary[$val] . '</span>'; 1593 break; 1594 1595 case 'size': 1596 $summary[$val] = '<span class="mimePartInfoSize">(' . $summary[$val] . ')</span>'; 1597 break; 1598 } 1599 $tmp_summary[] = $summary[$val]; 1600 } 1601 } 1602 1603 return '<div class="mimePartInfo' . 1604 ($atc ? ' mimePartInfoAtc' : '') . 1605 '"><div>' . 1606 implode(' ', $tmp_summary) . 1607 '</div></div>'; 1608 } 1609 1610 /** 1611 * Get FETCH data from IMAP server for this message. 1612 * 1613 * @param Horde_Imap_Client_Fetch_Query $query Search query. 1614 * 1615 * @return Horde_Imap_Client_Data_Fetch Fetch data for the message. 1616 */ 1617 protected function _fetchData(Horde_Imap_Client_Fetch_Query $query) 1618 { 1619 try { 1620 $mbox = $this->getMailbox(); 1621 $imp_imap = $mbox->imp_imap; 1622 return $imp_imap->fetch($mbox, $query, array( 1623 'ids' => $imp_imap->getIdsOb($this->getUid()) 1624 ))->first(); 1625 } catch (Horde_Imap_Client_Exception $e) { 1626 return new Horde_Imap_Client_Data_Fetch(); 1627 } 1628 } 1629 1630 /** 1631 * Return the view cache object for this message. 1632 * 1633 * @return object View object. 1634 */ 1635 public function getViewCache() 1636 { 1637 if (!isset($this->_viewcache)) { 1638 $this->_viewcache = new stdClass; 1639 } 1640 1641 return $this->_viewcache; 1642 } 1643 1644} 1645