PageRenderTime 87ms CodeModel.GetById 20ms app.highlight 57ms RepoModel.GetById 1ms app.codeStats 1ms

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

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