|
TYPO3 API
SVNRelease
|
00001 <?php 00002 00003 /* 00004 * This file is part of SwiftMailer. 00005 * (c) 2004-2009 Chris Corbyn 00006 * 00007 * For the full copyright and license information, please view the LICENSE 00008 * file that was distributed with this source code. 00009 */ 00010 00011 //@require 'Swift/Mime/HeaderSet.php'; 00012 //@require 'Swift/OutputByteStream.php'; 00013 //@require 'Swift/Mime/ContentEncoder.php'; 00014 //@require 'Swift/KeyCache.php'; 00015 00016 /** 00017 * A MIME entity, in a multipart message. 00018 * @package Swift 00019 * @subpackage Mime 00020 * @author Chris Corbyn 00021 */ 00022 class Swift_Mime_SimpleMimeEntity implements Swift_Mime_MimeEntity 00023 { 00024 00025 /** A collection of Headers for this mime entity */ 00026 private $_headers; 00027 00028 /** The body as a string, or a stream */ 00029 private $_body; 00030 00031 /** The encoder that encodes the body into a streamable format */ 00032 private $_encoder; 00033 00034 /** A mime bounary, if any is used */ 00035 private $_boundary; 00036 00037 /** Mime types to be used based on the nesting level */ 00038 private $_compositeRanges = array( 00039 'multipart/mixed' => array(self::LEVEL_TOP, self::LEVEL_MIXED), 00040 'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE), 00041 'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED) 00042 ); 00043 00044 /** A set of filter rules to define what level an entity should be nested at */ 00045 private $_compoundLevelFilters = array(); 00046 00047 /** The nesting level of this entity */ 00048 private $_nestingLevel = self::LEVEL_ALTERNATIVE; 00049 00050 /** A KeyCache instance used during encoding and streaming */ 00051 private $_cache; 00052 00053 /** Direct descendants of this entity */ 00054 private $_immediateChildren = array(); 00055 00056 /** All descendants of this entity */ 00057 private $_children = array(); 00058 00059 /** The maximum line length of the body of this entity */ 00060 private $_maxLineLength = 78; 00061 00062 /** The order in which alternative mime types should appear */ 00063 private $_alternativePartOrder = array( 00064 'text/plain' => 1, 00065 'text/html' => 2, 00066 'multipart/related' => 3 00067 ); 00068 00069 /** The CID of this entity */ 00070 private $_id; 00071 00072 /** The key used for accessing the cache */ 00073 private $_cacheKey; 00074 00075 protected $_userContentType; 00076 00077 /** 00078 * Create a new SimpleMimeEntity with $headers, $encoder and $cache. 00079 * @param Swift_Mime_HeaderSet $headers 00080 * @param Swift_Mime_ContentEncoder $encoder 00081 * @param Swift_KeyCache $cache 00082 */ 00083 public function __construct(Swift_Mime_HeaderSet $headers, 00084 Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache) 00085 { 00086 $this->_cacheKey = uniqid(); 00087 $this->_cache = $cache; 00088 $this->_headers = $headers; 00089 $this->setEncoder($encoder); 00090 $this->_headers->defineOrdering( 00091 array('Content-Type', 'Content-Transfer-Encoding') 00092 ); 00093 00094 // This array specifies that, when the entire MIME document contains 00095 // $compoundLevel, then for each child within $level, if its Content-Type 00096 // is $contentType then it should be treated as if it's level is 00097 // $neededLevel instead. I tried to write that unambiguously! :-\ 00098 // Data Structure: 00099 // array ( 00100 // $compoundLevel => array( 00101 // $level => array( 00102 // $contentType => $neededLevel 00103 // ) 00104 // ) 00105 // ) 00106 00107 $this->_compoundLevelFilters = array( 00108 (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array( 00109 self::LEVEL_ALTERNATIVE => array( 00110 'text/plain' => self::LEVEL_ALTERNATIVE, 00111 'text/html' => self::LEVEL_RELATED 00112 ) 00113 ) 00114 ); 00115 00116 $this->_id = $this->getRandomId(); 00117 } 00118 00119 /** 00120 * Generate a new Content-ID or Message-ID for this MIME entity. 00121 * @return string 00122 */ 00123 public function generateId() 00124 { 00125 $this->setId($this->getRandomId()); 00126 return $this->_id; 00127 } 00128 00129 /** 00130 * Get the {@link Swift_Mime_HeaderSet} for this entity. 00131 * @return Swift_Mime_HeaderSet 00132 */ 00133 public function getHeaders() 00134 { 00135 return $this->_headers; 00136 } 00137 00138 /** 00139 * Get the nesting level of this entity. 00140 * @return int 00141 * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE 00142 */ 00143 public function getNestingLevel() 00144 { 00145 return $this->_nestingLevel; 00146 } 00147 00148 /** 00149 * Get the Content-type of this entity. 00150 * @return string 00151 */ 00152 public function getContentType() 00153 { 00154 return $this->_getHeaderFieldModel('Content-Type'); 00155 } 00156 00157 /** 00158 * Set the Content-type of this entity. 00159 * @param string $type 00160 */ 00161 public function setContentType($type) 00162 { 00163 $this->_setContentTypeInHeaders($type); 00164 // Keep track of the value so that if the content-type changes automatically 00165 // due to added child entities, it can be restored if they are later removed 00166 $this->_userContentType = $type; 00167 return $this; 00168 } 00169 00170 /** 00171 * Get the CID of this entity. 00172 * The CID will only be present in headers if a Content-ID header is present. 00173 * @return string 00174 */ 00175 public function getId() 00176 { 00177 return $this->_headers->has($this->_getIdField()) 00178 ? current((array) $this->_getHeaderFieldModel($this->_getIdField())) 00179 : $this->_id; 00180 } 00181 00182 /** 00183 * Set the CID of this entity. 00184 * @param string $id 00185 */ 00186 public function setId($id) 00187 { 00188 if (!$this->_setHeaderFieldModel($this->_getIdField(), $id)) 00189 { 00190 $this->_headers->addIdHeader($this->_getIdField(), $id); 00191 } 00192 $this->_id = $id; 00193 return $this; 00194 } 00195 00196 /** 00197 * Get the description of this entity. 00198 * This value comes from the Content-Description header if set. 00199 * @return string 00200 */ 00201 public function getDescription() 00202 { 00203 return $this->_getHeaderFieldModel('Content-Description'); 00204 } 00205 00206 /** 00207 * Set the description of this entity. 00208 * This method sets a value in the Content-ID header. 00209 * @param string $description 00210 */ 00211 public function setDescription($description) 00212 { 00213 if (!$this->_setHeaderFieldModel('Content-Description', $description)) 00214 { 00215 $this->_headers->addTextHeader('Content-Description', $description); 00216 } 00217 return $this; 00218 } 00219 00220 /** 00221 * Get the maximum line length of the body of this entity. 00222 * @return int 00223 */ 00224 public function getMaxLineLength() 00225 { 00226 return $this->_maxLineLength; 00227 } 00228 00229 /** 00230 * Set the maximum line length of lines in this body. 00231 * Though not enforced by the library, lines should not exceed 1000 chars. 00232 * @param int $length 00233 */ 00234 public function setMaxLineLength($length) 00235 { 00236 $this->_maxLineLength = $length; 00237 return $this; 00238 } 00239 00240 /** 00241 * Get all children added to this entity. 00242 * @return array of Swift_Mime_Entity 00243 */ 00244 public function getChildren() 00245 { 00246 return $this->_children; 00247 } 00248 00249 /** 00250 * Set all children of this entity. 00251 * @param array $children Swiift_Mime_Entity instances 00252 * @param int $compoundLevel For internal use only 00253 */ 00254 public function setChildren(array $children, $compoundLevel = null) 00255 { 00256 //TODO: Try to refactor this logic 00257 00258 $compoundLevel = isset($compoundLevel) 00259 ? $compoundLevel 00260 : $this->_getCompoundLevel($children) 00261 ; 00262 00263 $immediateChildren = array(); 00264 $grandchildren = array(); 00265 $newContentType = $this->_userContentType; 00266 00267 foreach ($children as $child) 00268 { 00269 $level = $this->_getNeededChildLevel($child, $compoundLevel); 00270 if (empty($immediateChildren)) //first iteration 00271 { 00272 $immediateChildren = array($child); 00273 } 00274 else 00275 { 00276 $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel); 00277 if ($nextLevel == $level) 00278 { 00279 $immediateChildren[] = $child; 00280 } 00281 elseif ($level < $nextLevel) 00282 { 00283 //Re-assign immediateChildren to grandchilden 00284 $grandchildren = array_merge($grandchildren, $immediateChildren); 00285 //Set new children 00286 $immediateChildren = array($child); 00287 } 00288 else 00289 { 00290 $grandchildren[] = $child; 00291 } 00292 } 00293 } 00294 00295 if (!empty($immediateChildren)) 00296 { 00297 $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel); 00298 00299 //Determine which composite media type is needed to accomodate the 00300 // immediate children 00301 foreach ($this->_compositeRanges as $mediaType => $range) 00302 { 00303 if ($lowestLevel > $range[0] 00304 && $lowestLevel <= $range[1]) 00305 { 00306 $newContentType = $mediaType; 00307 break; 00308 } 00309 } 00310 00311 //Put any grandchildren in a subpart 00312 if (!empty($grandchildren)) 00313 { 00314 $subentity = $this->_createChild(); 00315 $subentity->_setNestingLevel($lowestLevel); 00316 $subentity->setChildren($grandchildren, $compoundLevel); 00317 array_unshift($immediateChildren, $subentity); 00318 } 00319 } 00320 00321 $this->_immediateChildren = $immediateChildren; 00322 $this->_children = $children; 00323 $this->_setContentTypeInHeaders($newContentType); 00324 $this->_fixHeaders(); 00325 $this->_sortChildren(); 00326 00327 return $this; 00328 } 00329 00330 /** 00331 * Get the body of this entity as a string. 00332 * @return string 00333 */ 00334 public function getBody() 00335 { 00336 return ($this->_body instanceof Swift_OutputByteStream) 00337 ? $this->_readStream($this->_body) 00338 : $this->_body; 00339 } 00340 00341 /** 00342 * Set the body of this entity, either as a string, or as an instance of 00343 * {@link Swift_OutputByteStream}. 00344 * @param mixed $body 00345 * @param string $contentType optional 00346 */ 00347 public function setBody($body, $contentType = null) 00348 { 00349 if ($body !== $this->_body) 00350 { 00351 $this->_clearCache(); 00352 } 00353 00354 $this->_body = $body; 00355 if (isset($contentType)) 00356 { 00357 $this->setContentType($contentType); 00358 } 00359 return $this; 00360 } 00361 00362 /** 00363 * Get the encoder used for the body of this entity. 00364 * @return Swift_Mime_ContentEncoder 00365 */ 00366 public function getEncoder() 00367 { 00368 return $this->_encoder; 00369 } 00370 00371 /** 00372 * Set the encoder used for the body of this entity. 00373 * @param Swift_Mime_ContentEncoder $encoder 00374 */ 00375 public function setEncoder(Swift_Mime_ContentEncoder $encoder) 00376 { 00377 if ($encoder !== $this->_encoder) 00378 { 00379 $this->_clearCache(); 00380 } 00381 00382 $this->_encoder = $encoder; 00383 $this->_setEncoding($encoder->getName()); 00384 $this->_notifyEncoderChanged($encoder); 00385 return $this; 00386 } 00387 00388 /** 00389 * Get the boundary used to separate children in this entity. 00390 * @return string 00391 */ 00392 public function getBoundary() 00393 { 00394 if (!isset($this->_boundary)) 00395 { 00396 $this->_boundary = '_=_swift_v4_' . time() . uniqid() . '_=_'; 00397 } 00398 return $this->_boundary; 00399 } 00400 00401 /** 00402 * Set the boundary used to separate children in this entity. 00403 * @param string $boundary 00404 * @throws Swift_RfcComplianceException 00405 */ 00406 public function setBoundary($boundary) 00407 { 00408 $this->_assertValidBoundary($boundary); 00409 $this->_boundary = $boundary; 00410 return $this; 00411 } 00412 00413 /** 00414 * Receive notification that the charset of this entity, or a parent entity 00415 * has changed. 00416 * @param string $charset 00417 */ 00418 public function charsetChanged($charset) 00419 { 00420 $this->_notifyCharsetChanged($charset); 00421 } 00422 00423 /** 00424 * Receive notification that the encoder of this entity or a parent entity 00425 * has changed. 00426 * @param Swift_Mime_ContentEncoder $encoder 00427 */ 00428 public function encoderChanged(Swift_Mime_ContentEncoder $encoder) 00429 { 00430 $this->_notifyEncoderChanged($encoder); 00431 } 00432 00433 /** 00434 * Get this entire entity as a string. 00435 * @return string 00436 */ 00437 public function toString() 00438 { 00439 $string = $this->_headers->toString(); 00440 if (isset($this->_body) && empty($this->_immediateChildren)) 00441 { 00442 if ($this->_cache->hasKey($this->_cacheKey, 'body')) 00443 { 00444 $body = $this->_cache->getString($this->_cacheKey, 'body'); 00445 } 00446 else 00447 { 00448 $body = "\r\n" . $this->_encoder->encodeString($this->getBody(), 0, 00449 $this->getMaxLineLength() 00450 ); 00451 $this->_cache->setString($this->_cacheKey, 'body', $body, 00452 Swift_KeyCache::MODE_WRITE 00453 ); 00454 } 00455 $string .= $body; 00456 } 00457 00458 if (!empty($this->_immediateChildren)) 00459 { 00460 foreach ($this->_immediateChildren as $child) 00461 { 00462 $string .= "\r\n\r\n--" . $this->getBoundary() . "\r\n"; 00463 $string .= $child->toString(); 00464 } 00465 $string .= "\r\n\r\n--" . $this->getBoundary() . "--\r\n"; 00466 } 00467 00468 return $string; 00469 } 00470 00471 /** 00472 * Returns a string representation of this object. 00473 * 00474 * @return string 00475 * 00476 * @see toString() 00477 */ 00478 public function __toString() 00479 { 00480 return $this->toString(); 00481 } 00482 00483 /** 00484 * Write this entire entity to a {@link Swift_InputByteStream}. 00485 * @param Swift_InputByteStream 00486 */ 00487 public function toByteStream(Swift_InputByteStream $is) 00488 { 00489 $is->write($this->_headers->toString()); 00490 $is->commit(); 00491 00492 if (empty($this->_immediateChildren)) 00493 { 00494 if (isset($this->_body)) 00495 { 00496 if ($this->_cache->hasKey($this->_cacheKey, 'body')) 00497 { 00498 $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is); 00499 } 00500 else 00501 { 00502 $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body'); 00503 if ($cacheIs) 00504 { 00505 $is->bind($cacheIs); 00506 } 00507 00508 $is->write("\r\n"); 00509 00510 if ($this->_body instanceof Swift_OutputByteStream) 00511 { 00512 $this->_body->setReadPointer(0); 00513 00514 $this->_encoder->encodeByteStream($this->_body, $is, 0, 00515 $this->getMaxLineLength() 00516 ); 00517 } 00518 else 00519 { 00520 $is->write($this->_encoder->encodeString( 00521 $this->getBody(), 0, $this->getMaxLineLength() 00522 )); 00523 } 00524 00525 if ($cacheIs) 00526 { 00527 $is->unbind($cacheIs); 00528 } 00529 } 00530 } 00531 } 00532 00533 if (!empty($this->_immediateChildren)) 00534 { 00535 foreach ($this->_immediateChildren as $child) 00536 { 00537 $is->write("\r\n\r\n--" . $this->getBoundary() . "\r\n"); 00538 $child->toByteStream($is); 00539 } 00540 $is->write("\r\n\r\n--" . $this->getBoundary() . "--\r\n"); 00541 } 00542 } 00543 00544 // -- Protected methods 00545 00546 /** 00547 * Get the name of the header that provides the ID of this entity */ 00548 protected function _getIdField() 00549 { 00550 return 'Content-ID'; 00551 } 00552 00553 /** 00554 * Get the model data (usually an array or a string) for $field. 00555 */ 00556 protected function _getHeaderFieldModel($field) 00557 { 00558 if ($this->_headers->has($field)) 00559 { 00560 return $this->_headers->get($field)->getFieldBodyModel(); 00561 } 00562 } 00563 00564 /** 00565 * Set the model data for $field. 00566 */ 00567 protected function _setHeaderFieldModel($field, $model) 00568 { 00569 if ($this->_headers->has($field)) 00570 { 00571 $this->_headers->get($field)->setFieldBodyModel($model); 00572 return true; 00573 } 00574 else 00575 { 00576 return false; 00577 } 00578 } 00579 00580 /** 00581 * Get the parameter value of $parameter on $field header. 00582 */ 00583 protected function _getHeaderParameter($field, $parameter) 00584 { 00585 if ($this->_headers->has($field)) 00586 { 00587 return $this->_headers->get($field)->getParameter($parameter); 00588 } 00589 } 00590 00591 /** 00592 * Set the parameter value of $parameter on $field header. 00593 */ 00594 protected function _setHeaderParameter($field, $parameter, $value) 00595 { 00596 if ($this->_headers->has($field)) 00597 { 00598 $this->_headers->get($field)->setParameter($parameter, $value); 00599 return true; 00600 } 00601 else 00602 { 00603 return false; 00604 } 00605 } 00606 00607 /** 00608 * Re-evaluate what content type and encoding should be used on this entity. 00609 */ 00610 protected function _fixHeaders() 00611 { 00612 if (count($this->_immediateChildren)) 00613 { 00614 $this->_setHeaderParameter('Content-Type', 'boundary', 00615 $this->getBoundary() 00616 ); 00617 $this->_headers->remove('Content-Transfer-Encoding'); 00618 } 00619 else 00620 { 00621 $this->_setHeaderParameter('Content-Type', 'boundary', null); 00622 $this->_setEncoding($this->_encoder->getName()); 00623 } 00624 } 00625 00626 /** 00627 * Get the KeyCache used in this entity. 00628 */ 00629 protected function _getCache() 00630 { 00631 return $this->_cache; 00632 } 00633 00634 /** 00635 * Empty the KeyCache for this entity. 00636 */ 00637 protected function _clearCache() 00638 { 00639 $this->_cache->clearKey($this->_cacheKey, 'body'); 00640 } 00641 00642 /** 00643 * Returns a random Content-ID or Message-ID. 00644 * @return string 00645 */ 00646 protected function getRandomId() 00647 { 00648 $idLeft = time() . '.' . uniqid(); 00649 $idRight = !empty($_SERVER['SERVER_NAME']) 00650 ? $_SERVER['SERVER_NAME'] 00651 : 'swift.generated'; 00652 return $idLeft . '@' . $idRight; 00653 } 00654 00655 // -- Private methods 00656 00657 private function _readStream(Swift_OutputByteStream $os) 00658 { 00659 $string = ''; 00660 while (false !== $bytes = $os->read(8192)) 00661 { 00662 $string .= $bytes; 00663 } 00664 return $string; 00665 } 00666 00667 private function _setEncoding($encoding) 00668 { 00669 if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) 00670 { 00671 $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding); 00672 } 00673 } 00674 00675 private function _assertValidBoundary($boundary) 00676 { 00677 if (!preg_match( 00678 '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di', 00679 $boundary)) 00680 { 00681 throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.'); 00682 } 00683 } 00684 00685 private function _setContentTypeInHeaders($type) 00686 { 00687 if (!$this->_setHeaderFieldModel('Content-Type', $type)) 00688 { 00689 $this->_headers->addParameterizedHeader('Content-Type', $type); 00690 } 00691 } 00692 00693 private function _setNestingLevel($level) 00694 { 00695 $this->_nestingLevel = $level; 00696 } 00697 00698 private function _getCompoundLevel($children) 00699 { 00700 $level = 0; 00701 foreach ($children as $child) 00702 { 00703 $level |= $child->getNestingLevel(); 00704 } 00705 return $level; 00706 } 00707 00708 private function _getNeededChildLevel($child, $compoundLevel) 00709 { 00710 $filter = array(); 00711 foreach ($this->_compoundLevelFilters as $bitmask => $rules) 00712 { 00713 if (($compoundLevel & $bitmask) === $bitmask) 00714 { 00715 $filter = $rules + $filter; 00716 } 00717 } 00718 00719 $realLevel = $child->getNestingLevel(); 00720 $lowercaseType = strtolower($child->getContentType()); 00721 00722 if (isset($filter[$realLevel]) 00723 && isset($filter[$realLevel][$lowercaseType])) 00724 { 00725 return $filter[$realLevel][$lowercaseType]; 00726 } 00727 else 00728 { 00729 return $realLevel; 00730 } 00731 } 00732 00733 private function _createChild() 00734 { 00735 return new self($this->_headers->newInstance(), 00736 $this->_encoder, $this->_cache); 00737 } 00738 00739 private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder) 00740 { 00741 foreach ($this->_immediateChildren as $child) 00742 { 00743 $child->encoderChanged($encoder); 00744 } 00745 } 00746 00747 private function _notifyCharsetChanged($charset) 00748 { 00749 $this->_encoder->charsetChanged($charset); 00750 $this->_headers->charsetChanged($charset); 00751 foreach ($this->_immediateChildren as $child) 00752 { 00753 $child->charsetChanged($charset); 00754 } 00755 } 00756 00757 private function _sortChildren() 00758 { 00759 $shouldSort = false; 00760 foreach ($this->_immediateChildren as $child) 00761 { 00762 //NOTE: This include alternative parts moved into a related part 00763 if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE) 00764 { 00765 $shouldSort = true; 00766 break; 00767 } 00768 } 00769 00770 //Sort in order of preference, if there is one 00771 if ($shouldSort) 00772 { 00773 usort($this->_immediateChildren, array($this, '_childSortAlgorithm')); 00774 } 00775 } 00776 00777 private function _childSortAlgorithm($a, $b) 00778 { 00779 $typePrefs = array(); 00780 $types = array( 00781 strtolower($a->getContentType()), 00782 strtolower($b->getContentType()) 00783 ); 00784 foreach ($types as $type) 00785 { 00786 $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder)) 00787 ? $this->_alternativePartOrder[$type] 00788 : (max($this->_alternativePartOrder) + 1); 00789 } 00790 return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1; 00791 } 00792 00793 // -- Destructor 00794 00795 /** 00796 * Empties it's own contents from the cache. 00797 */ 00798 public function __destruct() 00799 { 00800 $this->_cache->clearAll($this->_cacheKey); 00801 } 00802 00803 }
1.8.0