PageRenderTime 49ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/horde-3.3.13/lib/Horde/MIME/Contents.php

#
PHP | 1308 lines | 645 code | 139 blank | 524 comment | 152 complexity | 53e066623bc778ab2f7ca821cdf6395e MD5 | raw file
Possible License(s): LGPL-2.0
  1. <?php
  2. require_once dirname(__FILE__) . '/Message.php';
  3. require_once dirname(__FILE__) . '/Structure.php';
  4. /**
  5. * The name of the URL parameter that holds the MIME_Contents cache
  6. * identifier.
  7. */
  8. define('MIME_CONTENTS_CACHE', 'mimecache');
  9. /**
  10. * Display attachment information in list format.
  11. */
  12. define('MIME_CONTENTS_DISPLAY_TYPE_LIST', 0);
  13. /**
  14. * Display attachment information inline with attachment.
  15. */
  16. define('MIME_CONTENTS_DISPLAY_TYPE_INLINE', 1);
  17. /**
  18. * Display attachment information both in list format and inline with
  19. * attachment.
  20. */
  21. define('MIME_CONTENTS_DISPLAY_TYPE_BOTH', 2);
  22. /**
  23. * The MIME_Contents:: class contains functions related to handling the output
  24. * of MIME content.
  25. *
  26. * $Horde: framework/MIME/MIME/Contents.php,v 1.129.4.48 2010/10/26 23:21:49 slusarz Exp $
  27. *
  28. * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
  29. *
  30. * See the enclosed file COPYING for license information (GPL). If you
  31. * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
  32. *
  33. * @author Michael Slusarz <slusarz@horde.org>
  34. * @package Horde_MIME
  35. */
  36. class MIME_Contents {
  37. /**
  38. * The MIME_Message object for the message.
  39. *
  40. * @var MIME_Message
  41. */
  42. var $_message;
  43. /**
  44. * The MIME_Message object we use when caching.
  45. *
  46. * @var MIME_Message
  47. */
  48. var $_cachemessage;
  49. /**
  50. * The MIME part id of the message body.
  51. *
  52. * @since Horde 3.2
  53. *
  54. * @var integer
  55. */
  56. var $_body_id;
  57. /**
  58. * The attachments list.
  59. *
  60. * @var array
  61. */
  62. var $_atc = array();
  63. /**
  64. * The message parts list.
  65. *
  66. * @var array
  67. */
  68. var $_parts = array();
  69. /**
  70. * List of all downloadable parts.
  71. *
  72. * @since Horde 3.2
  73. *
  74. * @var array
  75. */
  76. var $_downloads = null;
  77. /**
  78. * The summary parts list.
  79. *
  80. * @var array
  81. */
  82. var $_summary = array();
  83. /**
  84. * The summary type.
  85. *
  86. * @var string
  87. */
  88. var $_summaryType = null;
  89. /**
  90. * The Cache_session identifier.
  91. *
  92. * @var string
  93. */
  94. var $_sessionCacheID = null;
  95. /**
  96. * The MIME_Viewer object cache.
  97. *
  98. * @var array
  99. */
  100. var $_viewerCache = array();
  101. /**
  102. * The attachment display type to use.
  103. *
  104. * @var integer
  105. */
  106. var $_displayType = MIME_CONTENTS_DISPLAY_TYPE_BOTH;
  107. /**
  108. * The MIME index key to use.
  109. *
  110. * @var string
  111. */
  112. var $_mimekey = null;
  113. /**
  114. * The actionID value for various actions.
  115. * 'download' -- Downloading a part/attachment.
  116. * 'view' -- Viewing a part/attachment.
  117. *
  118. * @var array
  119. */
  120. var $_viewID = array();
  121. /**
  122. * Show the links in the summaries?
  123. *
  124. * @var boolean
  125. */
  126. var $_links = true;
  127. /**
  128. * The base MIME_Contents object.
  129. *
  130. * @var MIME_Contents
  131. */
  132. var $_base = null;
  133. /**
  134. * The number of message/rfc822 levels labeled as 'attachments' of the
  135. * current part.
  136. *
  137. * @var integer
  138. */
  139. var $_attach822 = 0;
  140. /**
  141. * Constructor.
  142. *
  143. * @param MIME_Message $messageOb The object to work with.
  144. * @param array $viewID The actionID values for viewing
  145. * parts/attachments.
  146. * @param array &$contents Array containing a single value:
  147. * a reference to the base object.
  148. * (This last parameter needs to be handled via an array because PHP <
  149. * 5.0 doesn't handle optional pointer parameters otherwise.)
  150. */
  151. function MIME_Contents($messageOb, $viewID = array(), $contents = array())
  152. {
  153. $ptr = array(&$messageOb);
  154. MIME_Structure::addMultipartInfo($ptr);
  155. $this->_message = $messageOb;
  156. $this->_cachemessage = Util::cloneObject($messageOb);
  157. $this->_viewID = $viewID;
  158. /* Create the pointer to the base object. */
  159. if (!empty($contents)) {
  160. $ptr = reset($contents);
  161. $old_ptr = &$ptr->getBaseObjectPtr();
  162. $this->_base = $old_ptr;
  163. }
  164. }
  165. /**
  166. * Returns the entire body of the message.
  167. * You probably want to override this function in any subclass.
  168. *
  169. * @return string The text of the body of the message.
  170. */
  171. function getBody()
  172. {
  173. return $this->_message->toString();
  174. }
  175. /**
  176. * Returns the raw text for one section of the message.
  177. * You probably want to override this function in any subclass.
  178. *
  179. * @param string $id The ID of the MIME_Part.
  180. *
  181. * @return string The text of the part.
  182. */
  183. function getBodyPart($id)
  184. {
  185. if (($part = $this->getMIMEPart($id))) {
  186. return $part->getContents();
  187. } else {
  188. return '';
  189. }
  190. }
  191. /**
  192. * Returns the MIME_Message object for the mail message.
  193. *
  194. * @return MIME_Message A MIME_Message object.
  195. */
  196. function getMIMEMessage()
  197. {
  198. return $this->_message;
  199. }
  200. /**
  201. * Fetch a part of a MIME message.
  202. *
  203. * @param integer $id The MIME index of the part requested.
  204. *
  205. * @return MIME_Part The raw MIME part asked for.
  206. */
  207. function &getMIMEPart($id)
  208. {
  209. return $this->_message->getPart($id);
  210. }
  211. /**
  212. * Rebuild the MIME_Part structure of a message.
  213. * You probably want to override this function in any subclass.
  214. *
  215. * @return MIME_Message A MIME_Message object with all of the body text
  216. * stored in the individual MIME_Parts.
  217. */
  218. function rebuildMessage()
  219. {
  220. return $this->_message;
  221. }
  222. /**
  223. * Fetch part of a MIME message.
  224. *
  225. * @param integer $id The MIME ID of the part requested.
  226. * @param boolean $all If this is a header part, should we return all text
  227. * in the body?
  228. *
  229. * @return MIME_Part The MIME_Part.
  230. */
  231. function getRawMIMEPart($id, $all = false)
  232. {
  233. $mime_part = $this->getMIMEPart($id);
  234. if (!is_a($mime_part, 'MIME_Part')) {
  235. return null;
  236. }
  237. /* If all text is requested, change the ID now. */
  238. if ($all && $mime_part->getInformation('header')) {
  239. $id = substr($id, 0, strrpos($id, '.'));
  240. }
  241. /* Only set contents if there is currently none in the MIME Part. */
  242. if (!$mime_part->getContents()) {
  243. $mime_part->setContents($this->getBodyPart($id));
  244. }
  245. return $mime_part;
  246. }
  247. /**
  248. * Fetch part of a MIME message and decode it, if it is base_64 or
  249. * qprint encoded.
  250. *
  251. * @param integer $id The MIME ID of the part requested.
  252. * @param boolean $all If this is a header part, should we return all text
  253. * in the body?
  254. *
  255. * @return MIME_Part The MIME_Part with its contents decoded.
  256. */
  257. function &getDecodedMIMEPart($id, $all = false)
  258. {
  259. if (($mime_part = $this->getRawMIMEPart($id, $all))) {
  260. $mime_part->transferDecodeContents();
  261. } else {
  262. $mime_part = null;
  263. }
  264. return $mime_part;
  265. }
  266. /**
  267. * Return the attachment list (HTML table format).
  268. *
  269. * @return string The list of attachments formatted into HTML.
  270. */
  271. function getAttachments()
  272. {
  273. $msg = '';
  274. $akeys = array_keys($this->_atc);
  275. natsort($akeys);
  276. foreach ($akeys as $key) {
  277. $msg .= $this->_arrayToTableRow($this->_atc[$key]);
  278. }
  279. return $msg;
  280. }
  281. /**
  282. * Return the message list (HTML table format).
  283. *
  284. * @param boolean $oneframe Should the output be designed for display in a
  285. * single frame?
  286. *
  287. * @return string The message formatted into HTML.
  288. */
  289. function getMessage($oneframe = false)
  290. {
  291. $msg = '';
  292. $msgCount = count($this->_parts);
  293. $partDisplayed = false;
  294. // TODO: Temporary hack to display header info for a message with one
  295. // MIME part that cannot be displayed inline.
  296. if (!$msgCount || ($msgCount == 1 && !reset($this->_parts))) {
  297. $this->setSummary($this->_message, 'part');
  298. $msgCount = 1;
  299. }
  300. uksort($this->_parts, 'strnatcmp');
  301. reset($this->_parts);
  302. while (list($key, $value) = each($this->_parts)) {
  303. if (isset($this->_summary[$key]) &&
  304. ((($msgCount == 1) && empty($value)) || ($msgCount > 1))) {
  305. $msg .= '<tr><td><table class="mimePartInfo">';
  306. if ($oneframe) {
  307. $summary = $this->_summary[$key];
  308. $summary = array_merge(array_splice($summary, 0, 1), array_splice($summary, 1));
  309. $msg .= $this->_arrayToTableRow($summary);
  310. } else {
  311. $msg .= $this->_arrayToTableRow($this->_summary[$key]);
  312. }
  313. $msg .= '</table></td></tr>';
  314. } elseif ($partDisplayed && !empty($value)) {
  315. $msg .= '<tr><td class="linedRow"></td></tr>';
  316. }
  317. if (!empty($value)) {
  318. $msg .= '<tr><td class="text">' . $value . '</td></tr>';
  319. $partDisplayed = true;
  320. }
  321. }
  322. if (!$partDisplayed) {
  323. $msg .= '<tr><td class="text"><table class="mimeStatusMessage"><tr><td>' . _("There are no parts that can be displayed inline.") . '</td></tr></table></td></tr>';
  324. }
  325. return $msg;
  326. }
  327. /**
  328. * Expands an array into a table row.
  329. *
  330. * @access private
  331. *
  332. * @param array $array The array to expand.
  333. *
  334. * @return string The array expanded to a HTML table row.
  335. */
  336. function _arrayToTableRow($array)
  337. {
  338. $text = '<tr valign="middle">';
  339. foreach ($array as $elem) {
  340. if (!empty($elem)) {
  341. $text .= "<td>$elem</td>\n";
  342. }
  343. }
  344. return $text . "</tr>\n";
  345. }
  346. /**
  347. * Returns the data for a specific MIME index.
  348. *
  349. * @param string $id The MIME index.
  350. * @param string $field The field to return (message, atc, summary)
  351. *
  352. * @return string The text currently set for that index.
  353. */
  354. function getIndex($id, $field)
  355. {
  356. $field = '_' . $field;
  357. if (is_array($this->$field) && array_key_exists($id, $this->$field)) {
  358. $entry = $this->$field;
  359. return $entry[$id];
  360. } else {
  361. return null;
  362. }
  363. }
  364. /**
  365. * Removes the message text and summary for a specific MIME index.
  366. *
  367. * @param string $id The MIME index.
  368. */
  369. function removeIndex($id)
  370. {
  371. unset($this->_parts[$id]);
  372. unset($this->_summary[$id]);
  373. unset($this->_atc[$id]);
  374. }
  375. /**
  376. * Determine if we can (and know how to) inline a MIME Part.
  377. *
  378. * @param MIME_Part &$mime_part A MIME_Part object.
  379. *
  380. * @return boolean True if part can be inlined.
  381. * False if it cannot.
  382. */
  383. function canDisplayInline(&$mime_part)
  384. {
  385. $viewer = $this->getMIMEViewer($mime_part);
  386. if (!$viewer) {
  387. return false;
  388. }
  389. /* First check: The MIME headers allow the part to be inlined.
  390. * However, if we are already in view mode, then we can skip this
  391. * check. */
  392. if (!$this->viewAsAttachment() &&
  393. ($mime_part->getDisposition() != 'inline') &&
  394. !$viewer->forceInlineView()) {
  395. return false;
  396. }
  397. /* Second check (save the most expensive for last):
  398. * Check to see if the driver is set to inline. */
  399. return (is_a($viewer, 'MIME_Viewer') && $viewer->canDisplayInline());
  400. }
  401. /**
  402. * Get MIME_Viewer object.
  403. *
  404. * @param MIME_Part &$mime_part A MIME_Part object.
  405. *
  406. * @return MIME_Viewer The MIME_Viewer object, or false on error.
  407. */
  408. function &getMIMEViewer(&$mime_part, $mime_type = null)
  409. {
  410. /* Make sure we have a MIME_Part to process. */
  411. if (empty($mime_part)) {
  412. $ret = false;
  413. return $ret;
  414. }
  415. if (empty($mime_type)) {
  416. $mime_type = $mime_part->getType();
  417. }
  418. $key = $mime_part->getUniqueID() . '|' . $mime_type;
  419. if (!isset($this->_viewerCache[$key])) {
  420. require_once dirname(__FILE__) . '/Viewer.php';
  421. $this->_viewerCache[$key] = MIME_Viewer::factory($mime_part, $mime_type);
  422. }
  423. return $this->_viewerCache[$key];
  424. }
  425. /**
  426. * Get the MIME Content-Type output by a MIME_Viewer for a particular
  427. * MIME_Part.
  428. *
  429. * @param MIME_Part &$mime_part A MIME_Part object.
  430. *
  431. * @return string The MIME type output by the MIME_Viewer, or false on
  432. * error.
  433. */
  434. function getMIMEViewerType(&$mime_part)
  435. {
  436. if (($viewer = $this->getMIMEViewer($mime_part))) {
  437. return $viewer->getType();
  438. } else {
  439. return false;
  440. }
  441. }
  442. /**
  443. * Returns the key to use for a particular MIME_Part.
  444. *
  445. * @access private
  446. *
  447. * @param MIME_Part &$mime_part A MIME_Part object.
  448. * @param boolean $override Respect the MIME key override value?
  449. *
  450. * @return string The unique identifier of the MIME_Part.
  451. * Returns false if no key found.
  452. */
  453. function _getMIMEKey(&$mime_part, $override = true)
  454. {
  455. if ($override) {
  456. $id = $this->getMIMEKeyOverride();
  457. if (!is_null($id)) {
  458. return $id;
  459. }
  460. }
  461. $id = $mime_part->getMIMEId();
  462. if (is_null($id)) {
  463. return false;
  464. } else {
  465. return $id;
  466. }
  467. }
  468. /**
  469. * Gets the MIME key override.
  470. *
  471. * @return string The MIME key override - null if no override.
  472. */
  473. function getMIMEKeyOverride()
  474. {
  475. return $this->_mimekey;
  476. }
  477. /**
  478. * Sets an override for the MIME key.
  479. *
  480. * @param string $mimekey
  481. */
  482. function setMIMEKeyOverride($mimekey = null)
  483. {
  484. $this->_mimekey = $mimekey;
  485. }
  486. /**
  487. * Should we display links for the summaries?
  488. *
  489. * @param boolean $show Show the summary links?
  490. */
  491. function showSummaryLinks($show = null)
  492. {
  493. if (!is_null($show)) {
  494. $this->_links = $show;
  495. }
  496. return $this->_links;
  497. }
  498. /**
  499. * Render a MIME Part.
  500. *
  501. * @param MIME_Part &$mime_part A MIME_Part object.
  502. *
  503. * @return string The rendered data.
  504. */
  505. function renderMIMEPart(&$mime_part)
  506. {
  507. return $this->_renderMIMEPart($mime_part, false);
  508. }
  509. /**
  510. * Render MIME Part attachment info.
  511. *
  512. * @param MIME_Part &$mime_part A MIME_Part object.
  513. *
  514. * @return string The rendered data.
  515. */
  516. function renderMIMEAttachmentInfo(&$mime_part)
  517. {
  518. return $this->_renderMIMEPart($mime_part, true);
  519. }
  520. /**
  521. * Render MIME Part data.
  522. *
  523. * @access private
  524. *
  525. * @param MIME_Part &$mime_part A MIME_Part object.
  526. * @param boolean $attachment Render MIME Part attachment info?
  527. *
  528. * @return string The rendered data.
  529. */
  530. function _renderMIMEPart(&$mime_part, $attachment = false)
  531. {
  532. /* Get the MIME_Viewer object for this MIME part */
  533. $viewer = &$this->getMIMEViewer($mime_part);
  534. if (!is_a($viewer, 'MIME_Viewer')) {
  535. return '';
  536. }
  537. $msg = '';
  538. $mime_part->transferDecodeContents();
  539. /* If this is a text/* part, AND the text is in a different character
  540. * set than the browser, convert to the current character set.
  541. * Additionally, if the browser does not support UTF-8, give the
  542. * user a link to open the part in a new window with the correct
  543. * character set. */
  544. $charset = $mime_part->getCharset();
  545. if ($charset) {
  546. $charset_upper = String::upper($charset);
  547. if (($charset_upper != 'US-ASCII') &&
  548. !$this->viewAsAttachment()) {
  549. $default_charset = String::upper(NLS::getCharset());
  550. if ($charset_upper != $default_charset) {
  551. $mime_part->setContents(String::convertCharset($mime_part->getContents(), $charset, $default_charset));
  552. $mime_part->setCharset($default_charset);
  553. if ($default_charset != 'UTF-8') {
  554. $status = array(
  555. sprintf(_("This message was written in a character set (%s) other than your own."), htmlspecialchars($charset)),
  556. sprintf(_("If it is not displayed correctly, %s to open it in a new window."), $this->linkViewJS($mime_part, 'view_attach', _("click here")))
  557. );
  558. $msg = $this->formatStatusMsg($status, null, false) . $msg;
  559. }
  560. }
  561. }
  562. }
  563. $viewer->setMIMEPart($mime_part);
  564. $params = array(&$this);
  565. if ($attachment) {
  566. $msg .= $viewer->renderAttachmentInfo($params);
  567. } else {
  568. $msg .= $viewer->render($params);
  569. }
  570. return $msg;
  571. }
  572. /**
  573. * Build the message deciding what MIME Parts to show.
  574. *
  575. * @return boolean False on error.
  576. */
  577. function buildMessage()
  578. {
  579. $this->_atc = array();
  580. $this->_parts = array();
  581. $this->_summary = array();
  582. if (!is_a($this->_message, 'MIME_Message')) {
  583. return false;
  584. }
  585. /* Now display the parts. */
  586. $mime_part = $this->_message->getBasePart();
  587. $this->buildMessagePart($mime_part);
  588. return true;
  589. }
  590. /**
  591. * Processes a MIME_Part and stores the display information in the internal
  592. * class variables.
  593. *
  594. * @param MIME_Part &$mime_part The MIME_Part object to process.
  595. *
  596. * @return string The rendered text.
  597. */
  598. function buildMessagePart(&$mime_part)
  599. {
  600. $msg = '';
  601. /* If we can't display the part inline, add it to the attachment
  602. list. If the MIME ID of the current part is '0', then force a
  603. render of the part (since it is the base part and, without
  604. attempting to render, the message will ALWAYS appear empty. */
  605. if (!$this->canDisplayInline($mime_part) &&
  606. ($mime_part->getMIMEId() != 0)) {
  607. /* Not displaying inline; add to the attachments list. */
  608. if (($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_LIST) ||
  609. ($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_BOTH)) {
  610. $this->setSummary($mime_part, 'attachment');
  611. }
  612. if (($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_INLINE) ||
  613. ($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_BOTH)) {
  614. $this->setSummary($mime_part, 'part');
  615. }
  616. /* Check to see if any attachment information can be rendered by
  617. the MIME_Viewer. */
  618. $msg = $this->renderMIMEAttachmentInfo($mime_part);
  619. if (!empty($msg)) {
  620. $key = $this->_getMIMEKey($mime_part);
  621. $this->_parts[$key] = $msg;
  622. }
  623. } else {
  624. $msg = $this->renderMIMEPart($mime_part);
  625. $key = $this->_getMIMEKey($mime_part);
  626. if (!$this->_attach822) {
  627. $this->_parts[$key] = $msg;
  628. }
  629. /* Some MIME_Viewers set the summary by themelves, so only
  630. * add to attachment/inline lists if nothing has been set
  631. * as of yet. */
  632. if ((($mime_part->getType() != 'multipart/mixed') ||
  633. !empty($msg)) &&
  634. !empty($key) &&
  635. !$this->getIndex($key, 'summary') &&
  636. $this->_attach822 &&
  637. (($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_LIST) ||
  638. ($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_BOTH))) {
  639. $this->setSummary($mime_part, 'attachment');
  640. }
  641. }
  642. if ($mime_part->getInformation('header')) {
  643. /* If this is message/rfc822 part, and it is marked as an
  644. * attachment, we need to let future calls to buildMessagePart()
  645. * know that it should mark embedded parts as not viewable
  646. * inline. */
  647. $increment_822 = false;
  648. if (($mime_part->getType() == 'message/rfc822') &&
  649. ($mime_part->getDisposition() == 'attachment')) {
  650. $this->_attach822++;
  651. $increment_822 = true;
  652. }
  653. $parts = $mime_part->getParts();
  654. reset($parts);
  655. while (list(,$part) = each($parts)) {
  656. $msg .= $this->buildMessagePart($part);
  657. }
  658. if ($increment_822) {
  659. $this->_attach822--;
  660. }
  661. }
  662. return $msg;
  663. }
  664. /**
  665. * Generate the list of MIME IDs to use for download all.
  666. *
  667. * @since Horde 3.2
  668. *
  669. * @return array The list of MIME IDs that should be downloaded when
  670. * downloading all attachments.
  671. */
  672. function getDownloadAllList()
  673. {
  674. if (is_array($this->_downloads)) {
  675. return $this->_downloads;
  676. }
  677. $this->_downloads = array();
  678. $bodyid = $this->findBody();
  679. /* Here is what we consider 'downloadable':
  680. * All parts not 'multipart/*' and 'message/*' except for
  681. * 'message/rfc822'
  682. * All parts that are not PGP or S/MIME signature information
  683. * NOT the body part (if one exists)
  684. * Parts that are either marked a 'attachment' or have a filename. */
  685. foreach ($this->_message->contentTypeMap() as $key => $val) {
  686. if ($key === $bodyid) {
  687. continue;
  688. }
  689. $mime_part = $this->getMIMEPart($key);
  690. if (strpos($val, 'message/') === 0) {
  691. if (strpos($val, 'message/rfc822') === 0) {
  692. $this->_downloads[] = $key;
  693. }
  694. } elseif ((($mime_part->getDisposition() == 'attachment') ||
  695. $mime_part->getContentTypeParameter('name')) &&
  696. ($val != 'application/applefile') &&
  697. (strpos($val, 'multipart/') === false) &&
  698. (strpos($val, 'application/x-pkcs7-signature') === false) &&
  699. (strpos($val, 'application/pkcs7-signature') === false)) {
  700. $this->_downloads[] = $key;
  701. }
  702. }
  703. return $this->_downloads;
  704. }
  705. /**
  706. * Returns a list of attachments and their contents.
  707. *
  708. * @since Horde 3.2
  709. *
  710. * @return array List of hashes with the keys 'name' and 'data'.
  711. */
  712. function getAttachmentContents()
  713. {
  714. $attachments = array();
  715. foreach ($this->getDownloadAllList() as $attachment) {
  716. $part = &$this->_message->getPart($attachment);
  717. $part->transferDecodeContents();
  718. $part_name = $part->getName(true, true);
  719. if (empty($part_name)) {
  720. $part_name = MIME_DEFAULT_DESCRIPTION;
  721. }
  722. $attachments[] = array('name' => $part_name,
  723. 'data' => $part->getContents());
  724. }
  725. return $attachments;
  726. }
  727. /**
  728. * Finds the main "body" text part (if any) in a message.
  729. * "Body" data is the first text part in the base MIME part.
  730. *
  731. * @since Horde 3.2
  732. *
  733. * @param string $subtype Specifically search for this subtype.
  734. *
  735. * @return string The MIME ID of the main "body" part.
  736. */
  737. function findBody($subtype = null)
  738. {
  739. if (isset($this->_body_id) && ($subtype === null)) {
  740. return $this->_body_id;
  741. }
  742. /* Look for potential body parts. */
  743. $part = $this->_message->getBasePart();
  744. $primary_type = $part->getPrimaryType();
  745. if (($primary_type == MIME::type(TYPEMULTIPART)) ||
  746. ($primary_type == MIME::type(TYPETEXT))) {
  747. $body_id = $this->_findBody($part, $subtype);
  748. if ($subtype !== null) {
  749. $this->_body_id = $body_id;
  750. }
  751. return $body_id;
  752. }
  753. return null;
  754. }
  755. /**
  756. * Processes a MIME Part and looks for "body" data.
  757. *
  758. * @since Horde 3.2
  759. *
  760. * @access private
  761. *
  762. * @return string The MIME ID of the main "body" part.
  763. */
  764. function _findBody($mime_part, $subtype)
  765. {
  766. if (intval($mime_part->getMIMEId()) < 2 ||
  767. $mime_part->getInformation('alternative') === 0) {
  768. if ($mime_part->getPrimaryType() == MIME::type(TYPEMULTIPART)) {
  769. $parts = $mime_part->getParts();
  770. while ($part = array_shift($parts)) {
  771. if (($partid = $this->_findBody($part, $subtype))) {
  772. return $partid;
  773. }
  774. }
  775. } elseif ($mime_part->getPrimaryType() == MIME::type(TYPETEXT)) {
  776. if ($mime_part->getDisposition() != 'attachment' &&
  777. (($subtype === null) ||
  778. ($subtype == $mime_part->getSubType())) &&
  779. ($mime_part->getBytes() ||
  780. $this->getBodyPart($mime_part->getMIMEId()))) {
  781. return $mime_part->getMIMEId();
  782. }
  783. }
  784. }
  785. return null;
  786. }
  787. /**
  788. * Are we viewing this page as an attachment through view.php?
  789. * This method can also be called via MIME_Contents::viewAsAttachment().
  790. *
  791. * @param boolean $popup If true, also check if we are viewing attachment
  792. * in popup view window.
  793. *
  794. * @return boolean True if we are viewing this part as an attachment
  795. * through view.php.
  796. */
  797. function viewAsAttachment($popup = false)
  798. {
  799. return ((strpos($_SERVER['PHP_SELF'], 'view.php') !== false) &&
  800. (!$popup || Util::getFormData('popup_view')));
  801. }
  802. /**
  803. * Sets a summary entry.
  804. *
  805. * @param MIME_Part &$mime_part The MIME_Part object.
  806. * @param string $type The summary cache to use.
  807. */
  808. function setSummary(&$mime_part, $type)
  809. {
  810. if ($type == 'attachment') {
  811. $cache = &$this->_atc;
  812. } elseif ($type == 'part') {
  813. $cache = &$this->_summary;
  814. } else {
  815. return;
  816. }
  817. $key = $this->_getMIMEKey($mime_part);
  818. $this->_summaryType = $type;
  819. $summary = $this->partSummary($mime_part, null);
  820. $this->_summaryType = null;
  821. if (!empty($summary)) {
  822. if (!isset($this->_parts[$key])) {
  823. $this->_parts[$key] = null;
  824. }
  825. $cache[$key] = $summary;
  826. }
  827. }
  828. /**
  829. * Returns an array summarizing a part of a MIME message.
  830. *
  831. * @param MIME_Part &$mime_part The MIME_Part to summarize.
  832. * @param boolean $guess Is this a temporary guessed-type part?
  833. *
  834. * @return array The summary of the part.
  835. * [0] = Icon
  836. * [1] = IMAP ID
  837. * [2] = Description
  838. * [3] = MIME Type
  839. * [4] = Size
  840. * [5] = Download link/icon
  841. */
  842. function partSummary(&$mime_part, $guess = false)
  843. {
  844. $attachment = ($mime_part->getDisposition() == 'attachment');
  845. $bytes = $mime_part->getBytes();
  846. $size = $mime_part->getSize(true);
  847. $description = $mime_part->getDescription(true, true);
  848. $summary = array();
  849. $mime_type = $mime_part->getType();
  850. /* If the MIME Type is application/octet-stream or application/base64,
  851. try to use the file extension to determine the actual MIME type. */
  852. $param_array = array();
  853. if ($this->_links &&
  854. !empty($size) &&
  855. ($mime_type == 'application/octet-stream') ||
  856. ($mime_type == 'application/base64')) {
  857. require_once dirname(__FILE__) . '/Magic.php';
  858. $mime_type = MIME_Magic::filenameToMIME(MIME::decode($mime_part->getName()));
  859. $param_array['ctype'] = $mime_type;
  860. }
  861. $viewer = &$this->getMIMEViewer($mime_part, $mime_type);
  862. if (!$viewer) {
  863. return $summary;
  864. }
  865. /* Icon column. */
  866. $summary[] = Horde::img($viewer->getIcon($mime_type), '', array('title' => $mime_type), '');
  867. /* Number column. */
  868. $id = $this->_getMIMEKey($mime_part);
  869. if (($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_BOTH) &&
  870. !is_null($this->_summaryType)) {
  871. $summary[] = '<a id="mime_contents_' . $this->_summaryType . '_' . $id . '" href="#mime_contents_' . (($this->_summaryType == 'attachment') ? 'part' : 'attachment') . '_' . $id . '">' . $id . '</a>';
  872. } else {
  873. $summary[] = $id;
  874. }
  875. /* Name/text part column. */
  876. $description = htmlspecialchars($description);
  877. if (!$this->_links ||
  878. (!$attachment && empty($bytes)) ||
  879. !isset($this->_viewID['view']) ||
  880. is_a($viewer, 'MIME_Viewer_default')) {
  881. $summary[] = $description;
  882. } else {
  883. $summary[] = $this->linkViewJS($mime_part, $this->_viewID['view'], $description, sprintf(_("View %s [%s]"), $description, $mime_type), null, $param_array);
  884. }
  885. /* Size. */
  886. if (!empty($bytes) &&
  887. ($mime_part->getCurrentEncoding() == 'base64')) {
  888. /* From RFC 2045 [6.8]: "...the encoded data are consistently
  889. only about 33 percent larger than the unencoded data." */
  890. $size = max((($bytes * 0.75) / 1024), 1);
  891. if ($size > 1024) {
  892. $size = sprintf(_("%s MB"), NLS::numberFormat(max(($size / 1024), 1)));
  893. } else {
  894. $size = sprintf(_("%s KB"), NLS::numberFormat($size));
  895. }
  896. } else {
  897. if ($size > 1024) {
  898. $size = sprintf(_("%s MB"), NLS::numberFormat(max(($size / 1024), 1)));
  899. } else {
  900. $size = sprintf(_("%s KB"), NLS::numberFormat($size));
  901. }
  902. }
  903. /* Download column. */
  904. if (!$this->_links ||
  905. is_null($size) ||
  906. !isset($this->_viewID['download'])) {
  907. $summary[] = '<span class="download">' . $size . '</span>';
  908. } else {
  909. $summary[] = $this->linkView($mime_part, $this->_viewID['download'], $size, array('class' => 'download', 'jstext' => sprintf(_("Download %s"), $description)), true);
  910. }
  911. return $summary;
  912. }
  913. /**
  914. * Return the URL to the view.php page.
  915. *
  916. * @param MIME_Part &$mime_part The MIME_Part object to view.
  917. * @param integer $actionID The ActionID to perform.
  918. * @param array $params A list of any additional parameters that
  919. * need to be passed to view.php. (key =
  920. * name)
  921. * @param boolean $dload Should we generate a download link?
  922. *
  923. * @return string The URL to view.php.
  924. */
  925. function urlView(&$mime_part, $actionID, $params = array(), $dload = false)
  926. {
  927. /* Get the MIME ID for this part. */
  928. $id = (isset($params['id'])) ? $params['id'] : $mime_part->getMIMEId();
  929. /* Add the necessary local parameters. */
  930. $params['actionID'] = $actionID;
  931. $params['id'] = $id;
  932. $params = array_merge($params, $this->cacheIDURLParam());
  933. if ($dload) {
  934. $url = Horde::downloadUrl($mime_part->getName(true, true), $params);
  935. } else {
  936. $url = Util::addParameter(Horde::applicationUrl('view.php'), $params);
  937. }
  938. return $url;
  939. }
  940. /**
  941. * Generate a link to the view.php page.
  942. *
  943. * Important: the calling code has to make sure that the $text parameter
  944. * is properly escaped!
  945. *
  946. * @param MIME_Part &$mime_part The MIME_Part object to view.
  947. * @param integer $actionID The actionID value.
  948. * @param string $text The ESCAPED link text.
  949. * @param array $params A list of additional parameters.
  950. * 'class' - The CSS class to use.
  951. * 'jstext' - The JS text to use.
  952. * 'viewparams' - A list of any additional parameters that need to be
  953. * passed to view.php.
  954. * @param boolean $dload Should we generate a download link?
  955. *
  956. * @return string A HTML href link to view.php.
  957. */
  958. function linkView(&$mime_part, $actionID, $text, $params = array(),
  959. $dload = false)
  960. {
  961. if (!isset($params['class'])) {
  962. $params['class'] = null;
  963. }
  964. if (!isset($params['jstext'])) {
  965. $params['jstext'] = $text;
  966. }
  967. if (!isset($params['viewparams'])) {
  968. $params['viewparams'] = array();
  969. }
  970. if ($dload) {
  971. $window = null;
  972. } else {
  973. $window = 'view_' . abs(crc32(mt_rand()));
  974. }
  975. return Horde::link($this->urlView($mime_part, $actionID, $params['viewparams'], $dload), $params['jstext'], $params['class'], $window) . $text . '</a>';
  976. }
  977. /**
  978. * Generate a javascript link to the view.php page.
  979. *
  980. * Important: the calling code has to make sure that the $text parameter
  981. * is properly escaped!
  982. *
  983. * @param MIME_Part &$mime_part The MIME_Part object to view.
  984. * @param integer $actionID The ActionID to perform.
  985. * @param string $text The ESCAPED link text.
  986. * @param string $jstext The Javascript link text.
  987. * @param string $css The CSS class to use.
  988. * @param array $params A list of any additional parameters that
  989. * need to be passed to view.php. (key =
  990. * name)
  991. * @param boolean $widget If true use Horde::widget() to generate,
  992. * Horde::link() otherwise.
  993. *
  994. * @return string A HTML href link to view.php.
  995. */
  996. function linkViewJS(&$mime_part, $actionID, $text, $jstext = null,
  997. $css = null, $params = array(), $widget = false)
  998. {
  999. /* If viewing via view.php, we don't want a JS link. */
  1000. if ($this->viewAsAttachment()) {
  1001. return $this->linkView($mime_part, $actionID, $text, $params);
  1002. }
  1003. if (empty($jstext)) {
  1004. $jstext = sprintf(_("View %s"), $mime_part->getDescription(true, true));
  1005. }
  1006. $params['popup_view'] = 1;
  1007. $url = $this->urlView($mime_part, $actionID, $params);
  1008. if (!($id = $mime_part->getMIMEId())) {
  1009. $id = abs(crc32(serialize($mime_part)));
  1010. }
  1011. if ($widget) {
  1012. return Horde::widget('#', $jstext, $css, null, "view('" . $url . "', '" . $id . "'); return false;", $text);
  1013. } else {
  1014. return Horde::link('#', $jstext, $css, null, "view('" . $url . "', '" . $id . "'); return false;") . $text . '</a>';
  1015. }
  1016. }
  1017. /**
  1018. * Prints out the status message for a given MIME Part.
  1019. *
  1020. * @param string $msg The message to output.
  1021. * @param string $img An image link to add to the beginning of the
  1022. * message.
  1023. * @param boolean $print Output this message when in a print view?
  1024. * @param string $class An optional style for the status box.
  1025. *
  1026. * @return string The formatted status message string.
  1027. */
  1028. function formatStatusMsg($msg, $img = null, $printable = true,
  1029. $class = null)
  1030. {
  1031. if (empty($msg)) {
  1032. return '';
  1033. }
  1034. if (!is_array($msg)) {
  1035. $msg = array($msg);
  1036. }
  1037. /* If we are viewing as an attachment, don't print HTML code. */
  1038. if ($this->viewAsAttachment()) {
  1039. return implode("\n", $msg);
  1040. }
  1041. if (is_null($class)) {
  1042. $class = 'mimeStatusMessage';
  1043. }
  1044. $text = '<table class="' . $class . '">';
  1045. /* If no image, simply print out the message. */
  1046. if (is_null($img)) {
  1047. foreach ($msg as $val) {
  1048. $text .= '<tr><td>' . $val . '</td></tr>' . "\n";
  1049. }
  1050. } else {
  1051. $text .= '<tr><td class="mimeStatusIcon">' . $img . '</td><td>';
  1052. if (count($msg) == 1) {
  1053. $text .= $msg[0];
  1054. } else {
  1055. $text .= '<table>';
  1056. foreach ($msg as $val) {
  1057. $text .= '<tr><td>' . $val . '</td></tr>' . "\n";
  1058. }
  1059. $text .= '</table>';
  1060. }
  1061. $text .= '</td></tr>' . "\n";
  1062. }
  1063. return $text . '</table>';
  1064. }
  1065. /**
  1066. * Return a pointer to the base object.
  1067. *
  1068. * @return mixed Returns a pointer to the base object.
  1069. * Returns false if there is no base object.
  1070. */
  1071. function &getBaseObjectPtr()
  1072. {
  1073. if ($this->_base === null) {
  1074. return $this;
  1075. } else {
  1076. return $this->_base;
  1077. }
  1078. }
  1079. /**
  1080. * Set the MIME_Contents:: object to be cached.
  1081. *
  1082. * @access private
  1083. *
  1084. * @param string The cache OID.
  1085. */
  1086. function _addCache()
  1087. {
  1088. if (is_null($this->_sessionCacheID)) {
  1089. $this->_sessionCacheID = $this->_createCacheID();
  1090. register_shutdown_function(array(&$this, '_addCacheShutdown'));
  1091. }
  1092. return $this->_sessionCacheID;
  1093. }
  1094. /**
  1095. * Creates a unique cache ID for this object.
  1096. *
  1097. * @access private
  1098. *
  1099. * @return string A unique cache ID.
  1100. */
  1101. function _createCacheID()
  1102. {
  1103. // Use Auth class, if available, to add uniqueness.
  1104. $entropy = class_exists('Auth')
  1105. ? Auth::getAuth()
  1106. : '';
  1107. return md5(mt_rand() . $entropy . getmypid());
  1108. }
  1109. /**
  1110. * Saves a copy of the MIME_Contents object at the end of a request.
  1111. *
  1112. * @access private
  1113. */
  1114. function _addCacheShutdown()
  1115. {
  1116. /* Don't save the MIME_Viewer cached objects since there is no easy
  1117. way to regenerate them on cache reload. */
  1118. $this->_viewerCache = array();
  1119. /* Copy the MIME_Message cache object to the _message variable. */
  1120. $this->_message = $this->_cachemessage;
  1121. if (!empty($GLOBALS['conf']['cache']['driver'])) {
  1122. require_once 'Horde/Cache.php';
  1123. $cache = &Horde_Cache::singleton($GLOBALS['conf']['cache']['driver'], Horde::getDriverConfig('cache', $GLOBALS['conf']['cache']['driver']));
  1124. $cache->set($this->_sessionCacheID, @serialize($this));
  1125. } else {
  1126. require_once 'Horde/SessionObjects.php';
  1127. $cache = &Horde_SessionObjects::singleton();
  1128. $cache->overwrite($this->_sessionCacheID, $this);
  1129. }
  1130. }
  1131. /**
  1132. * Returns the cached MIME_Contents:: object.
  1133. * This function should be called statically e.g.:
  1134. * $ptr = &MIME_Contents::getCache().
  1135. *
  1136. * @param string $cacheid The cache ID to use. If empty, will use the
  1137. * cache ID located in the URL parameter named
  1138. * MIME_CONTENTS_CACHE.
  1139. *
  1140. * @return MIME_Contents The MIME_Contents object, or null if it does not
  1141. * exist.
  1142. */
  1143. function &getCache($cacheid = null)
  1144. {
  1145. if (is_null($cacheid)) {
  1146. $cacheid = Util::getFormData(MIME_CONTENTS_CACHE);
  1147. }
  1148. if (!empty($GLOBALS['conf']['cache']['driver'])) {
  1149. require_once 'Horde/Cache.php';
  1150. $cache = &Horde_Cache::singleton($GLOBALS['conf']['cache']['driver'], Horde::getDriverConfig('cache', $GLOBALS['conf']['cache']['driver']));
  1151. $contents = @unserialize($cache->get($cacheid, 0));
  1152. } else {
  1153. require_once 'Horde/SessionObjects.php';
  1154. $cache = &Horde_SessionObjects::singleton();
  1155. $contents = &$cache->query($cacheid);
  1156. }
  1157. return $contents;
  1158. }
  1159. /**
  1160. * Add the current object to the cache, and return the cache identifier
  1161. * to be used in URLs.
  1162. *
  1163. * @return array The parameter key/value set to use in URLs.
  1164. */
  1165. function cacheIDURLParam()
  1166. {
  1167. return array(MIME_CONTENTS_CACHE => $this->_addCache());
  1168. }
  1169. }