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 }