File indexing completed on 2024-12-22 05:36:51

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Mail
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 
0023 /**
0024  * @see Zend_Mime_Decode
0025  */
0026 // require_once 'Zend/Mime/Decode.php';
0027 
0028 /**
0029  * @see Zend_Mail_Header_HeaderName
0030  */
0031 // require_once 'Zend/Mail/Header/HeaderName.php';
0032 
0033 /**
0034  * @see Zend_Mail_Header_HeaderValue
0035  */
0036 // require_once 'Zend/Mail/Header/HeaderValue.php';
0037 
0038 /**
0039  * @see Zend_Mail_Part_Interface
0040  */
0041 // require_once 'Zend/Mail/Part/Interface.php';
0042 
0043 
0044 /**
0045  * @category   Zend
0046  * @package    Zend_Mail
0047  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0048  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0049  */
0050 class Zend_Mail_Part implements RecursiveIterator, Zend_Mail_Part_Interface
0051 {
0052     /**
0053      * headers of part as array
0054      * @var null|array
0055      */
0056     protected $_headers;
0057 
0058     /**
0059      * raw part body
0060      * @var null|string
0061      */
0062     protected $_content;
0063 
0064     /**
0065      * toplines as fetched with headers
0066      * @var string
0067      */
0068     protected $_topLines = '';
0069 
0070     /**
0071      * parts of multipart message
0072      * @var array
0073      */
0074     protected $_parts = array();
0075 
0076     /**
0077      * count of parts of a multipart message
0078      * @var null|int
0079      */
0080     protected $_countParts;
0081 
0082     /**
0083      * current position of iterator
0084      * @var int
0085      */
0086     protected $_iterationPos = 1;
0087 
0088     /**
0089      * mail handler, if late fetch is active
0090      * @var null|Zend_Mail_Storage_Abstract
0091      */
0092     protected $_mail;
0093 
0094     /**
0095      * message number for mail handler
0096      * @var int
0097      */
0098     protected $_messageNum = 0;
0099     
0100     /**
0101      * Class to use when creating message parts
0102      * @var string 
0103      */
0104     protected $_partClass;
0105 
0106     /**
0107      * Public constructor
0108      *
0109      * Zend_Mail_Part supports different sources for content. The possible params are:
0110      * - handler    a instance of Zend_Mail_Storage_Abstract for late fetch
0111      * - id         number of message for handler
0112      * - raw        raw content with header and body as string
0113      * - headers    headers as array (name => value) or string, if a content part is found it's used as toplines
0114      * - noToplines ignore content found after headers in param 'headers'
0115      * - content    content as string
0116      *
0117      * @param   array $params  full message with or without headers
0118      * @throws  Zend_Mail_Exception
0119      */
0120     public function __construct(array $params)
0121     {
0122         if (isset($params['handler'])) {
0123             if (!$params['handler'] instanceof Zend_Mail_Storage_Abstract) {
0124                 /**
0125                  * @see Zend_Mail_Exception
0126                  */
0127                 // require_once 'Zend/Mail/Exception.php';
0128                 throw new Zend_Mail_Exception('handler is not a valid mail handler');
0129             }
0130             if (!isset($params['id'])) {
0131                 /**
0132                  * @see Zend_Mail_Exception
0133                  */
0134                 // require_once 'Zend/Mail/Exception.php';
0135                 throw new Zend_Mail_Exception('need a message id with a handler');
0136             }
0137 
0138             $this->_mail       = $params['handler'];
0139             $this->_messageNum = $params['id'];
0140         }
0141         
0142         if (isset($params['partclass'])) {
0143             $this->setPartClass($params['partclass']);
0144         }
0145 
0146         if (isset($params['raw'])) {
0147             Zend_Mime_Decode::splitMessage($params['raw'], $this->_headers, $this->_content, "\r\n");
0148         } else if (isset($params['headers'])) {
0149             if (is_array($params['headers'])) {
0150                 $this->_headers = $params['headers'];
0151                 $this->_validateHeaders($this->_headers);
0152             } else {
0153                 if (!empty($params['noToplines'])) {
0154                     Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $null, "\r\n");
0155                 } else {
0156                     Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $this->_topLines, "\r\n");
0157                 }
0158             }
0159 
0160             if (isset($params['content'])) {
0161                 $this->_content = $params['content'];
0162             }
0163         }
0164     }
0165     
0166     /**
0167      * Set name pf class used to encapsulate message parts
0168      * @param string $class
0169      * @return Zend_Mail_Part
0170      */
0171     public function setPartClass($class)
0172     {
0173         if ( !class_exists($class) ) {
0174             /**
0175              * @see Zend_Mail_Exception
0176              */
0177             // require_once 'Zend/Mail/Exception.php';
0178             throw new Zend_Mail_Exception("Class '{$class}' does not exist");
0179         }
0180         if ( !is_subclass_of($class, 'Zend_Mail_Part_Interface') ) {
0181             /**
0182              * @see Zend_Mail_Exception
0183              */
0184             // require_once 'Zend/Mail/Exception.php';
0185             throw new Zend_Mail_Exception("Class '{$class}' must implement Zend_Mail_Part_Interface");
0186         }
0187         
0188         $this->_partClass = $class;
0189         return $this;
0190     }
0191     
0192     /**
0193      * Retrieve the class name used to encapsulate message parts
0194      * @return string 
0195      */
0196     public function getPartClass()
0197     {
0198         if ( !$this->_partClass ) {
0199             $this->_partClass = __CLASS__;
0200         }
0201         return $this->_partClass;
0202     }
0203 
0204     /**
0205      * Check if part is a multipart message
0206      *
0207      * @return bool if part is multipart
0208      */
0209     public function isMultipart()
0210     {
0211         try {
0212             return stripos($this->contentType, 'multipart/') === 0;
0213         } catch(Zend_Mail_Exception $e) {
0214             return false;
0215         }
0216     }
0217 
0218 
0219     /**
0220      * Body of part
0221      *
0222      * If part is multipart the raw content of this part with all sub parts is returned
0223      *
0224      * @return string body
0225      * @throws Zend_Mail_Exception
0226      */
0227     public function getContent()
0228     {
0229         if ($this->_content !== null) {
0230             return $this->_content;
0231         }
0232 
0233         if ($this->_mail) {
0234             return $this->_mail->getRawContent($this->_messageNum);
0235         } else {
0236             /**
0237              * @see Zend_Mail_Exception
0238              */
0239             // require_once 'Zend/Mail/Exception.php';
0240             throw new Zend_Mail_Exception('no content');
0241         }
0242     }
0243 
0244     /**
0245      * Return size of part
0246      *
0247      * Quite simple implemented currently (not decoding). Handle with care.
0248      *
0249      * @return int size
0250      */
0251     public function getSize() {
0252         return strlen($this->getContent());
0253     }
0254 
0255 
0256     /**
0257      * Cache content and split in parts if multipart
0258      *
0259      * @return null
0260      * @throws Zend_Mail_Exception
0261      */
0262     protected function _cacheContent()
0263     {
0264         // caching content if we can't fetch parts
0265         if ($this->_content === null && $this->_mail) {
0266             $this->_content = $this->_mail->getRawContent($this->_messageNum);
0267         }
0268 
0269         if (!$this->isMultipart()) {
0270             return;
0271         }
0272 
0273         // split content in parts
0274         $boundary = $this->getHeaderField('content-type', 'boundary');
0275         if (!$boundary) {
0276             /**
0277              * @see Zend_Mail_Exception
0278              */
0279             // require_once 'Zend/Mail/Exception.php';
0280             throw new Zend_Mail_Exception('no boundary found in content type to split message');
0281         }
0282         $parts = Zend_Mime_Decode::splitMessageStruct($this->_content, $boundary);
0283         if ($parts === null) {
0284             return;
0285         }
0286         $partClass = $this->getPartClass();
0287         $counter = 1;
0288         foreach ($parts as $part) {
0289             $this->_parts[$counter++] = new $partClass(array('headers' => $part['header'], 'content' => $part['body']));
0290         }
0291     }
0292 
0293     /**
0294      * Get part of multipart message
0295      *
0296      * @param  int $num number of part starting with 1 for first part
0297      * @return Zend_Mail_Part wanted part
0298      * @throws Zend_Mail_Exception
0299      */
0300     public function getPart($num)
0301     {
0302         if (isset($this->_parts[$num])) {
0303             return $this->_parts[$num];
0304         }
0305 
0306         if (!$this->_mail && $this->_content === null) {
0307             /**
0308              * @see Zend_Mail_Exception
0309              */
0310             // require_once 'Zend/Mail/Exception.php';
0311             throw new Zend_Mail_Exception('part not found');
0312         }
0313 
0314         if ($this->_mail && $this->_mail->hasFetchPart) {
0315             // TODO: fetch part
0316             // return
0317         }
0318 
0319         $this->_cacheContent();
0320 
0321         if (!isset($this->_parts[$num])) {
0322             /**
0323              * @see Zend_Mail_Exception
0324              */
0325             // require_once 'Zend/Mail/Exception.php';
0326             throw new Zend_Mail_Exception('part not found');
0327         }
0328 
0329         return $this->_parts[$num];
0330     }
0331 
0332     /**
0333      * Count parts of a multipart part
0334      *
0335      * @return int number of sub-parts
0336      */
0337     public function countParts()
0338     {
0339         if ($this->_countParts) {
0340             return $this->_countParts;
0341         }
0342 
0343         $this->_countParts = count($this->_parts);
0344         if ($this->_countParts) {
0345             return $this->_countParts;
0346         }
0347 
0348         if ($this->_mail && $this->_mail->hasFetchPart) {
0349             // TODO: fetch part
0350             // return
0351         }
0352 
0353         $this->_cacheContent();
0354 
0355         $this->_countParts = count($this->_parts);
0356         return $this->_countParts;
0357     }
0358 
0359 
0360     /**
0361      * Get all headers
0362      *
0363      * The returned headers are as saved internally. All names are lowercased. The value is a string or an array
0364      * if a header with the same name occurs more than once.
0365      *
0366      * @return array headers as array(name => value)
0367      */
0368     public function getHeaders()
0369     {
0370         if ($this->_headers === null) {
0371             if (!$this->_mail) {
0372                 $this->_headers = array();
0373             } else {
0374                 $part = $this->_mail->getRawHeader($this->_messageNum);
0375                 Zend_Mime_Decode::splitMessage($part, $this->_headers, $null);
0376             }
0377         }
0378 
0379         return $this->_headers;
0380     }
0381 
0382     /**
0383      * Get a header in specificed format
0384      *
0385      * Internally headers that occur more than once are saved as array, all other as string. If $format
0386      * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim).
0387      *
0388      * @param  string $name   name of header, matches case-insensitive, but camel-case is replaced with dashes
0389      * @param  string $format change type of return value to 'string' or 'array'
0390      * @return string|array value of header in wanted or internal format
0391      * @throws Zend_Mail_Exception
0392      */
0393     public function getHeader($name, $format = null)
0394     {
0395         if ($this->_headers === null) {
0396             $this->getHeaders();
0397         }
0398 
0399         $lowerName = strtolower($name);
0400 
0401         if ($this->headerExists($name) == false) {
0402             $lowerName = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $name));
0403             if($this->headerExists($lowerName) == false) {
0404                 /**
0405                  * @see Zend_Mail_Exception
0406                  */
0407                 // require_once 'Zend/Mail/Exception.php';
0408                 throw new Zend_Mail_Exception("no Header with Name $name or $lowerName found");
0409             }
0410         }
0411         $name = $lowerName;
0412 
0413         $header = $this->_headers[$name];
0414 
0415         switch ($format) {
0416             case 'string':
0417                 if (is_array($header)) {
0418                     $header = implode(Zend_Mime::LINEEND, $header);
0419                 }
0420                 break;
0421             case 'array':
0422                 $header = (array)$header;
0423             default:
0424                 // do nothing
0425         }
0426 
0427         return $header;
0428     }
0429 
0430     /**
0431      * Check wheater the Mail part has a specific header.
0432      *
0433      * @param  string $name
0434      * @return boolean
0435      */
0436     public function headerExists($name)
0437     {
0438         $name = strtolower($name);
0439         if(isset($this->_headers[$name])) {
0440             return true;
0441         } else {
0442             return false;
0443         }
0444     }
0445 
0446     /**
0447      * Get a specific field from a header like content type or all fields as array
0448      *
0449      * If the header occurs more than once, only the value from the first header
0450      * is returned.
0451      *
0452      * Throws a Zend_Mail_Exception if the requested header does not exist. If
0453      * the specific header field does not exist, returns null.
0454      *
0455      * @param  string $name       name of header, like in getHeader()
0456      * @param  string $wantedPart the wanted part, default is first, if null an array with all parts is returned
0457      * @param  string $firstName  key name for the first part
0458      * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
0459      * @throws Zend_Exception, Zend_Mail_Exception
0460      */
0461     public function getHeaderField($name, $wantedPart = 0, $firstName = 0) {
0462         return Zend_Mime_Decode::splitHeaderField(current($this->getHeader($name, 'array')), $wantedPart, $firstName);
0463     }
0464 
0465 
0466     /**
0467      * Getter for mail headers - name is matched in lowercase
0468      *
0469      * This getter is short for Zend_Mail_Part::getHeader($name, 'string')
0470      *
0471      * @see Zend_Mail_Part::getHeader()
0472      *
0473      * @param  string $name header name
0474      * @return string value of header
0475      * @throws Zend_Mail_Exception
0476      */
0477     public function __get($name)
0478     {
0479         return $this->getHeader($name, 'string');
0480     }
0481 
0482     /**
0483      * Isset magic method proxy to hasHeader
0484      *
0485      * This method is short syntax for Zend_Mail_Part::hasHeader($name);
0486      *
0487      * @see Zend_Mail_Part::hasHeader
0488      *
0489      * @param  string
0490      * @return boolean
0491      */
0492     public function __isset($name)
0493     {
0494         return $this->headerExists($name);
0495     }
0496 
0497     /**
0498      * magic method to get content of part
0499      *
0500      * @return string content
0501      */
0502     public function __toString()
0503     {
0504         return $this->getContent();
0505     }
0506 
0507     /**
0508      * implements RecursiveIterator::hasChildren()
0509      *
0510      * @return bool current element has children/is multipart
0511      */
0512     public function hasChildren()
0513     {
0514         $current = $this->current();
0515         return $current && $current instanceof Zend_Mail_Part && $current->isMultipart();
0516     }
0517 
0518     /**
0519      * implements RecursiveIterator::getChildren()
0520      *
0521      * @return Zend_Mail_Part same as self::current()
0522      */
0523     public function getChildren()
0524     {
0525         return $this->current();
0526     }
0527 
0528     /**
0529      * implements Iterator::valid()
0530      *
0531      * @return bool check if there's a current element
0532      */
0533     public function valid()
0534     {
0535         if ($this->_countParts === null) {
0536             $this->countParts();
0537         }
0538         return $this->_iterationPos && $this->_iterationPos <= $this->_countParts;
0539     }
0540 
0541     /**
0542      * implements Iterator::next()
0543      *
0544      * @return null
0545      */
0546     public function next()
0547     {
0548         ++$this->_iterationPos;
0549     }
0550 
0551     /**
0552      * implements Iterator::key()
0553      *
0554      * @return string key/number of current part
0555      */
0556     public function key()
0557     {
0558         return $this->_iterationPos;
0559     }
0560 
0561     /**
0562      * implements Iterator::current()
0563      *
0564      * @return Zend_Mail_Part current part
0565      */
0566     public function current()
0567     {
0568         return $this->getPart($this->_iterationPos);
0569     }
0570 
0571     /**
0572      * implements Iterator::rewind()
0573      *
0574      * @return null
0575      */
0576     public function rewind()
0577     {
0578         $this->countParts();
0579         $this->_iterationPos = 1;
0580     }
0581 
0582     /**
0583      * Ensure headers do not contain invalid characters
0584      *
0585      * @param array $headers
0586      * @param bool $assertNames
0587      */
0588     protected function _validateHeaders(array $headers, $assertNames = true)
0589     {
0590         foreach ($headers as $name => $value) {
0591             if ($assertNames) {
0592                 Zend_Mail_Header_HeaderName::assertValid($name);
0593             }
0594 
0595             if (is_array($value)) {
0596                 $this->_validateHeaders($value, false);
0597                 continue;
0598             }
0599 
0600             Zend_Mail_Header_HeaderValue::assertValid($value);
0601         }
0602     }
0603 }