Overview

Packages

  • IMP
  • None

Classes

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