Avail feature updated
[ninja.git] / application / vendor / swiftmailer / classes / Swift / Mime / SimpleMimeEntity.php
blob16158225b990a2806bffe2d9e849d1206c2bd4f2
1 <?php
3 /*
4 * This file is part of SwiftMailer.
5 * (c) 2004-2009 Chris Corbyn
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
11 //@require 'Swift/Mime/HeaderSet.php';
12 //@require 'Swift/OutputByteStream.php';
13 //@require 'Swift/Mime/ContentEncoder.php';
14 //@require 'Swift/KeyCache.php';
16 /**
17 * A MIME entity, in a multipart message.
18 * @package Swift
19 * @subpackage Mime
20 * @author Chris Corbyn
22 class Swift_Mime_SimpleMimeEntity implements Swift_Mime_MimeEntity
25 /** A collection of Headers for this mime entity */
26 private $_headers;
28 /** The body as a string, or a stream */
29 private $_body;
31 /** The encoder that encodes the body into a streamable format */
32 private $_encoder;
34 /** A mime bounary, if any is used */
35 private $_boundary;
37 /** Mime types to be used based on the nesting level */
38 private $_compositeRanges = array(
39 'multipart/mixed' => array(self::LEVEL_TOP, self::LEVEL_MIXED),
40 'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE),
41 'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED)
44 /** A set of filter rules to define what level an entity should be nested at */
45 private $_compoundLevelFilters = array();
47 /** The nesting level of this entity */
48 private $_nestingLevel = self::LEVEL_ALTERNATIVE;
50 /** A KeyCache instance used during encoding and streaming */
51 private $_cache;
53 /** Direct descendants of this entity */
54 private $_immediateChildren = array();
56 /** All descendants of this entity */
57 private $_children = array();
59 /** The maximum line length of the body of this entity */
60 private $_maxLineLength = 78;
62 /** The order in which alternative mime types should appear */
63 private $_alternativePartOrder = array(
64 'text/plain' => 1,
65 'text/html' => 2,
66 'multipart/related' => 3
69 /** The CID of this entity */
70 private $_id;
72 /** The key used for accessing the cache */
73 private $_cacheKey;
75 protected $_userContentType;
77 /**
78 * Create a new SimpleMimeEntity with $headers, $encoder and $cache.
79 * @param Swift_Mime_HeaderSet $headers
80 * @param Swift_Mime_ContentEncoder $encoder
81 * @param Swift_KeyCache $cache
83 public function __construct(Swift_Mime_HeaderSet $headers,
84 Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache)
86 $this->_cacheKey = uniqid();
87 $this->_cache = $cache;
88 $this->_headers = $headers;
89 $this->setEncoder($encoder);
90 $this->_headers->defineOrdering(
91 array('Content-Type', 'Content-Transfer-Encoding')
94 // This array specifies that, when the entire MIME document contains
95 // $compoundLevel, then for each child within $level, if its Content-Type
96 // is $contentType then it should be treated as if it's level is
97 // $neededLevel instead. I tried to write that unambiguously! :-\
98 // Data Structure:
99 // array (
100 // $compoundLevel => array(
101 // $level => array(
102 // $contentType => $neededLevel
103 // )
104 // )
105 // )
107 $this->_compoundLevelFilters = array(
108 (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array(
109 self::LEVEL_ALTERNATIVE => array(
110 'text/plain' => self::LEVEL_ALTERNATIVE,
111 'text/html' => self::LEVEL_RELATED
116 $this->_id = $this->getRandomId();
120 * Generate a new Content-ID or Message-ID for this MIME entity.
121 * @return string
123 public function generateId()
125 $this->setId($this->getRandomId());
126 return $this->_id;
130 * Get the {@link Swift_Mime_HeaderSet} for this entity.
131 * @return Swift_Mime_HeaderSet
133 public function getHeaders()
135 return $this->_headers;
139 * Get the nesting level of this entity.
140 * @return int
141 * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
143 public function getNestingLevel()
145 return $this->_nestingLevel;
149 * Get the Content-type of this entity.
150 * @return string
152 public function getContentType()
154 return $this->_getHeaderFieldModel('Content-Type');
158 * Set the Content-type of this entity.
159 * @param string $type
161 public function setContentType($type)
163 $this->_setContentTypeInHeaders($type);
164 // Keep track of the value so that if the content-type changes automatically
165 // due to added child entities, it can be restored if they are later removed
166 $this->_userContentType = $type;
167 return $this;
171 * Get the CID of this entity.
172 * The CID will only be present in headers if a Content-ID header is present.
173 * @return string
175 public function getId()
177 return $this->_headers->has($this->_getIdField())
178 ? current((array) $this->_getHeaderFieldModel($this->_getIdField()))
179 : $this->_id;
183 * Set the CID of this entity.
184 * @param string $id
186 public function setId($id)
188 if (!$this->_setHeaderFieldModel($this->_getIdField(), $id))
190 $this->_headers->addIdHeader($this->_getIdField(), $id);
192 $this->_id = $id;
193 return $this;
197 * Get the description of this entity.
198 * This value comes from the Content-Description header if set.
199 * @return string
201 public function getDescription()
203 return $this->_getHeaderFieldModel('Content-Description');
207 * Set the description of this entity.
208 * This method sets a value in the Content-ID header.
209 * @param string $description
211 public function setDescription($description)
213 if (!$this->_setHeaderFieldModel('Content-Description', $description))
215 $this->_headers->addTextHeader('Content-Description', $description);
217 return $this;
221 * Get the maximum line length of the body of this entity.
222 * @return int
224 public function getMaxLineLength()
226 return $this->_maxLineLength;
230 * Set the maximum line length of lines in this body.
231 * Though not enforced by the library, lines should not exceed 1000 chars.
232 * @param int $length
234 public function setMaxLineLength($length)
236 $this->_maxLineLength = $length;
237 return $this;
241 * Get all children added to this entity.
242 * @return array of Swift_Mime_Entity
244 public function getChildren()
246 return $this->_children;
250 * Set all children of this entity.
251 * @param array $children Swiift_Mime_Entity instances
252 * @param int $compoundLevel For internal use only
254 public function setChildren(array $children, $compoundLevel = null)
256 //TODO: Try to refactor this logic
258 $compoundLevel = isset($compoundLevel)
259 ? $compoundLevel
260 : $this->_getCompoundLevel($children)
263 $immediateChildren = array();
264 $grandchildren = array();
265 $newContentType = $this->_userContentType;
267 foreach ($children as $child)
269 $level = $this->_getNeededChildLevel($child, $compoundLevel);
270 if (empty($immediateChildren)) //first iteration
272 $immediateChildren = array($child);
274 else
276 $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
277 if ($nextLevel == $level)
279 $immediateChildren[] = $child;
281 elseif ($level < $nextLevel)
283 //Re-assign immediateChildren to grandchilden
284 $grandchildren = array_merge($grandchildren, $immediateChildren);
285 //Set new children
286 $immediateChildren = array($child);
288 else
290 $grandchildren[] = $child;
295 if (!empty($immediateChildren))
297 $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
299 //Determine which composite media type is needed to accomodate the
300 // immediate children
301 foreach ($this->_compositeRanges as $mediaType => $range)
303 if ($lowestLevel > $range[0]
304 && $lowestLevel <= $range[1])
306 $newContentType = $mediaType;
307 break;
311 //Put any grandchildren in a subpart
312 if (!empty($grandchildren))
314 $subentity = $this->_createChild();
315 $subentity->_setNestingLevel($lowestLevel);
316 $subentity->setChildren($grandchildren, $compoundLevel);
317 array_unshift($immediateChildren, $subentity);
321 $this->_immediateChildren = $immediateChildren;
322 $this->_children = $children;
323 $this->_setContentTypeInHeaders($newContentType);
324 $this->_fixHeaders();
325 $this->_sortChildren();
327 return $this;
331 * Get the body of this entity as a string.
332 * @return string
334 public function getBody()
336 return ($this->_body instanceof Swift_OutputByteStream)
337 ? $this->_readStream($this->_body)
338 : $this->_body;
342 * Set the body of this entity, either as a string, or as an instance of
343 * {@link Swift_OutputByteStream}.
344 * @param mixed $body
345 * @param string $contentType optional
347 public function setBody($body, $contentType = null)
349 if ($body !== $this->_body)
351 $this->_clearCache();
354 $this->_body = $body;
355 if (isset($contentType))
357 $this->setContentType($contentType);
359 return $this;
363 * Get the encoder used for the body of this entity.
364 * @return Swift_Mime_ContentEncoder
366 public function getEncoder()
368 return $this->_encoder;
372 * Set the encoder used for the body of this entity.
373 * @param Swift_Mime_ContentEncoder $encoder
375 public function setEncoder(Swift_Mime_ContentEncoder $encoder)
377 if ($encoder !== $this->_encoder)
379 $this->_clearCache();
382 $this->_encoder = $encoder;
383 $this->_setEncoding($encoder->getName());
384 $this->_notifyEncoderChanged($encoder);
385 return $this;
389 * Get the boundary used to separate children in this entity.
390 * @return string
392 public function getBoundary()
394 if (!isset($this->_boundary))
396 $this->_boundary = '_=_swift_v4_' . time() . uniqid() . '_=_';
398 return $this->_boundary;
402 * Set the boundary used to separate children in this entity.
403 * @param string $boundary
404 * @throws Swift_RfcComplianceException
406 public function setBoundary($boundary)
408 $this->_assertValidBoundary($boundary);
409 $this->_boundary = $boundary;
410 return $this;
414 * Receive notification that the charset of this entity, or a parent entity
415 * has changed.
416 * @param string $charset
418 public function charsetChanged($charset)
420 $this->_notifyCharsetChanged($charset);
424 * Receive notification that the encoder of this entity or a parent entity
425 * has changed.
426 * @param Swift_Mime_ContentEncoder $encoder
428 public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
430 $this->_notifyEncoderChanged($encoder);
434 * Get this entire entity as a string.
435 * @return string
437 public function toString()
439 $string = $this->_headers->toString();
440 if (isset($this->_body) && empty($this->_immediateChildren))
442 if ($this->_cache->hasKey($this->_cacheKey, 'body'))
444 $body = $this->_cache->getString($this->_cacheKey, 'body');
446 else
448 $body = "\r\n" . $this->_encoder->encodeString($this->getBody(), 0,
449 $this->getMaxLineLength()
451 $this->_cache->setString($this->_cacheKey, 'body', $body,
452 Swift_KeyCache::MODE_WRITE
455 $string .= $body;
458 if (!empty($this->_immediateChildren))
460 foreach ($this->_immediateChildren as $child)
462 $string .= "\r\n\r\n--" . $this->getBoundary() . "\r\n";
463 $string .= $child->toString();
465 $string .= "\r\n\r\n--" . $this->getBoundary() . "--\r\n";
468 return $string;
472 * Returns a string representation of this object.
474 * @return string
476 * @see toString()
478 public function __toString()
480 return $this->toString();
484 * Write this entire entity to a {@link Swift_InputByteStream}.
485 * @param Swift_InputByteStream
487 public function toByteStream(Swift_InputByteStream $is)
489 $is->write($this->_headers->toString());
490 $is->commit();
492 if (empty($this->_immediateChildren))
494 if (isset($this->_body))
496 if ($this->_cache->hasKey($this->_cacheKey, 'body'))
498 $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is);
500 else
502 $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body');
503 if ($cacheIs)
505 $is->bind($cacheIs);
508 $is->write("\r\n");
510 if ($this->_body instanceof Swift_OutputByteStream)
512 $this->_body->setReadPointer(0);
514 $this->_encoder->encodeByteStream($this->_body, $is, 0,
515 $this->getMaxLineLength()
518 else
520 $is->write($this->_encoder->encodeString(
521 $this->getBody(), 0, $this->getMaxLineLength()
525 if ($cacheIs)
527 $is->unbind($cacheIs);
533 if (!empty($this->_immediateChildren))
535 foreach ($this->_immediateChildren as $child)
537 $is->write("\r\n\r\n--" . $this->getBoundary() . "\r\n");
538 $child->toByteStream($is);
540 $is->write("\r\n\r\n--" . $this->getBoundary() . "--\r\n");
544 // -- Protected methods
547 * Get the name of the header that provides the ID of this entity */
548 protected function _getIdField()
550 return 'Content-ID';
554 * Get the model data (usually an array or a string) for $field.
556 protected function _getHeaderFieldModel($field)
558 if ($this->_headers->has($field))
560 return $this->_headers->get($field)->getFieldBodyModel();
565 * Set the model data for $field.
567 protected function _setHeaderFieldModel($field, $model)
569 if ($this->_headers->has($field))
571 $this->_headers->get($field)->setFieldBodyModel($model);
572 return true;
574 else
576 return false;
581 * Get the parameter value of $parameter on $field header.
583 protected function _getHeaderParameter($field, $parameter)
585 if ($this->_headers->has($field))
587 return $this->_headers->get($field)->getParameter($parameter);
592 * Set the parameter value of $parameter on $field header.
594 protected function _setHeaderParameter($field, $parameter, $value)
596 if ($this->_headers->has($field))
598 $this->_headers->get($field)->setParameter($parameter, $value);
599 return true;
601 else
603 return false;
608 * Re-evaluate what content type and encoding should be used on this entity.
610 protected function _fixHeaders()
612 if (count($this->_immediateChildren))
614 $this->_setHeaderParameter('Content-Type', 'boundary',
615 $this->getBoundary()
617 $this->_headers->remove('Content-Transfer-Encoding');
619 else
621 $this->_setHeaderParameter('Content-Type', 'boundary', null);
622 $this->_setEncoding($this->_encoder->getName());
627 * Get the KeyCache used in this entity.
629 protected function _getCache()
631 return $this->_cache;
635 * Empty the KeyCache for this entity.
637 protected function _clearCache()
639 $this->_cache->clearKey($this->_cacheKey, 'body');
643 * Returns a random Content-ID or Message-ID.
644 * @return string
646 protected function getRandomId()
648 $idLeft = time() . '.' . uniqid();
649 $idRight = !empty($_SERVER['SERVER_NAME'])
650 ? $_SERVER['SERVER_NAME']
651 : 'swift.generated';
652 return $idLeft . '@' . $idRight;
655 // -- Private methods
657 private function _readStream(Swift_OutputByteStream $os)
659 $string = '';
660 while (false !== $bytes = $os->read(8192))
662 $string .= $bytes;
664 return $string;
667 private function _setEncoding($encoding)
669 if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding))
671 $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding);
675 private function _assertValidBoundary($boundary)
677 if (!preg_match(
678 '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di',
679 $boundary))
681 throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.');
685 private function _setContentTypeInHeaders($type)
687 if (!$this->_setHeaderFieldModel('Content-Type', $type))
689 $this->_headers->addParameterizedHeader('Content-Type', $type);
693 private function _setNestingLevel($level)
695 $this->_nestingLevel = $level;
698 private function _getCompoundLevel($children)
700 $level = 0;
701 foreach ($children as $child)
703 $level |= $child->getNestingLevel();
705 return $level;
708 private function _getNeededChildLevel($child, $compoundLevel)
710 $filter = array();
711 foreach ($this->_compoundLevelFilters as $bitmask => $rules)
713 if (($compoundLevel & $bitmask) === $bitmask)
715 $filter = $rules + $filter;
719 $realLevel = $child->getNestingLevel();
720 $lowercaseType = strtolower($child->getContentType());
722 if (isset($filter[$realLevel])
723 && isset($filter[$realLevel][$lowercaseType]))
725 return $filter[$realLevel][$lowercaseType];
727 else
729 return $realLevel;
733 private function _createChild()
735 return new self($this->_headers->newInstance(),
736 $this->_encoder, $this->_cache);
739 private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder)
741 foreach ($this->_immediateChildren as $child)
743 $child->encoderChanged($encoder);
747 private function _notifyCharsetChanged($charset)
749 $this->_encoder->charsetChanged($charset);
750 $this->_headers->charsetChanged($charset);
751 foreach ($this->_immediateChildren as $child)
753 $child->charsetChanged($charset);
757 private function _sortChildren()
759 $shouldSort = false;
760 foreach ($this->_immediateChildren as $child)
762 //NOTE: This include alternative parts moved into a related part
763 if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE)
765 $shouldSort = true;
766 break;
770 //Sort in order of preference, if there is one
771 if ($shouldSort)
773 usort($this->_immediateChildren, array($this, '_childSortAlgorithm'));
777 private function _childSortAlgorithm($a, $b)
779 $typePrefs = array();
780 $types = array(
781 strtolower($a->getContentType()),
782 strtolower($b->getContentType())
784 foreach ($types as $type)
786 $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder))
787 ? $this->_alternativePartOrder[$type]
788 : (max($this->_alternativePartOrder) + 1);
790 return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1;
793 // -- Destructor
796 * Empties it's own contents from the cache.
798 public function __destruct()
800 $this->_cache->clearAll($this->_cacheKey);