vendor/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php
changeset 0 7f95f8617b0b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php	Sat Sep 24 15:40:41 2011 +0200
@@ -0,0 +1,844 @@
+<?php
+
+/*
+ * This file is part of SwiftMailer.
+ * (c) 2004-2009 Chris Corbyn
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+
+/**
+ * A MIME entity, in a multipart message.
+ * @package Swift
+ * @subpackage Mime
+ * @author Chris Corbyn
+ */
+class Swift_Mime_SimpleMimeEntity implements Swift_Mime_MimeEntity
+{
+  
+  /** A collection of Headers for this mime entity */
+  private $_headers;
+  
+  /** The body as a string, or a stream */
+  private $_body;
+  
+  /** The encoder that encodes the body into a streamable format */
+  private $_encoder;
+
+  /** The grammar to use for id validation */
+  private $_grammar;
+  
+  /** A mime bounary, if any is used */
+  private $_boundary;
+  
+  /** Mime types to be used based on the nesting level */
+  private $_compositeRanges = array(
+    'multipart/mixed' => array(self::LEVEL_TOP, self::LEVEL_MIXED),
+    'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE),
+    'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED)
+    );
+  
+  /** A set of filter rules to define what level an entity should be nested at */
+  private $_compoundLevelFilters = array();
+    
+  /** The nesting level of this entity */
+  private $_nestingLevel = self::LEVEL_ALTERNATIVE;
+  
+  /** A KeyCache instance used during encoding and streaming */
+  private $_cache;
+  
+  /** Direct descendants of this entity */
+  private $_immediateChildren = array();
+  
+  /** All descendants of this entity */
+  private $_children = array();
+  
+  /** The maximum line length of the body of this entity */
+  private $_maxLineLength = 78;
+  
+  /** The order in which alternative mime types should appear */
+  private $_alternativePartOrder = array(
+    'text/plain' => 1,
+    'text/html' => 2,
+    'multipart/related' => 3
+    );
+  
+  /** The CID of this entity */
+  private $_id;
+  
+  /** The key used for accessing the cache */
+  private $_cacheKey;
+  
+  protected $_userContentType;
+  
+  /**
+   * Create a new SimpleMimeEntity with $headers, $encoder and $cache.
+   * @param Swift_Mime_HeaderSet $headers
+   * @param Swift_Mime_ContentEncoder $encoder
+   * @param Swift_KeyCache $cache
+   * @param Swift_Mime_Grammar $grammar
+   */
+  public function __construct(Swift_Mime_HeaderSet $headers,
+    Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache,
+    Swift_Mime_Grammar $grammar)
+  {
+    $this->_cacheKey = uniqid();
+    $this->_cache = $cache;
+    $this->_headers = $headers;
+    $this->_grammar = $grammar;
+    $this->setEncoder($encoder);
+    $this->_headers->defineOrdering(
+      array('Content-Type', 'Content-Transfer-Encoding')
+      );
+    
+    // This array specifies that, when the entire MIME document contains
+    // $compoundLevel, then for each child within $level, if its Content-Type
+    // is $contentType then it should be treated as if it's level is
+    // $neededLevel instead.  I tried to write that unambiguously! :-\
+    // Data Structure:
+    // array (
+    //   $compoundLevel => array(
+    //     $level => array(
+    //       $contentType => $neededLevel
+    //     )
+    //   )
+    // )
+    
+    $this->_compoundLevelFilters = array(
+      (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array(
+        self::LEVEL_ALTERNATIVE => array(
+          'text/plain' => self::LEVEL_ALTERNATIVE,
+          'text/html' => self::LEVEL_RELATED
+          )
+        )
+      );
+
+    $this->_id = $this->getRandomId();
+  }
+  
+  /**
+   * Generate a new Content-ID or Message-ID for this MIME entity.
+   * @return string
+   */
+  public function generateId()
+  {
+    $this->setId($this->getRandomId());
+    return $this->_id;
+  }
+  
+  /**
+   * Get the {@link Swift_Mime_HeaderSet} for this entity.
+   * @return Swift_Mime_HeaderSet
+   */
+  public function getHeaders()
+  {
+    return $this->_headers;
+  }
+  
+  /**
+   * Get the nesting level of this entity.
+   * @return int
+   * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
+   */
+  public function getNestingLevel()
+  {
+    return $this->_nestingLevel;
+  }
+  
+  /**
+   * Get the Content-type of this entity.
+   * @return string
+   */
+  public function getContentType()
+  {
+    return $this->_getHeaderFieldModel('Content-Type');
+  }
+  
+  /**
+   * Set the Content-type of this entity.
+   * @param string $type
+   */
+  public function setContentType($type)
+  {
+    $this->_setContentTypeInHeaders($type);
+    // Keep track of the value so that if the content-type changes automatically
+    // due to added child entities, it can be restored if they are later removed
+    $this->_userContentType = $type;
+    return $this;
+  }
+  
+  /**
+   * Get the CID of this entity.
+   * The CID will only be present in headers if a Content-ID header is present.
+   * @return string
+   */
+  public function getId()
+  {
+    return $this->_headers->has($this->_getIdField())
+      ? current((array) $this->_getHeaderFieldModel($this->_getIdField()))
+      : $this->_id;
+  }
+  
+  /**
+   * Set the CID of this entity.
+   * @param string $id
+   */
+  public function setId($id)
+  {
+    if (!$this->_setHeaderFieldModel($this->_getIdField(), $id))
+    {
+      $this->_headers->addIdHeader($this->_getIdField(), $id);
+    }
+    $this->_id = $id;
+    return $this;
+  }
+  
+  /**
+   * Get the description of this entity.
+   * This value comes from the Content-Description header if set.
+   * @return string
+   */
+  public function getDescription()
+  {
+    return $this->_getHeaderFieldModel('Content-Description');
+  }
+  
+  /**
+   * Set the description of this entity.
+   * This method sets a value in the Content-ID header.
+   * @param string $description
+   */
+  public function setDescription($description)
+  {
+    if (!$this->_setHeaderFieldModel('Content-Description', $description))
+    {
+      $this->_headers->addTextHeader('Content-Description', $description);
+    }
+    return $this;
+  }
+  
+  /**
+   * Get the maximum line length of the body of this entity.
+   * @return int
+   */
+  public function getMaxLineLength()
+  {
+    return $this->_maxLineLength;
+  }
+  
+  /**
+   * Set the maximum line length of lines in this body.
+   * Though not enforced by the library, lines should not exceed 1000 chars.
+   * @param int $length
+   */
+  public function setMaxLineLength($length)
+  {
+    $this->_maxLineLength = $length;
+    return $this;
+  }
+  
+  /**
+   * Get all children added to this entity.
+   * @return array of Swift_Mime_Entity
+   */
+  public function getChildren()
+  {
+    return $this->_children;
+  }
+  
+  /**
+   * Set all children of this entity.
+   * @param array $children Swiift_Mime_Entity instances
+   * @param int $compoundLevel For internal use only
+   */
+  public function setChildren(array $children, $compoundLevel = null)
+  {
+    //TODO: Try to refactor this logic
+    
+    $compoundLevel = isset($compoundLevel)
+      ? $compoundLevel
+      : $this->_getCompoundLevel($children)
+      ;
+    
+    $immediateChildren = array();
+    $grandchildren = array();
+    $newContentType = $this->_userContentType;
+    
+    foreach ($children as $child)
+    {
+      $level = $this->_getNeededChildLevel($child, $compoundLevel);
+      if (empty($immediateChildren)) //first iteration
+      {
+        $immediateChildren = array($child);
+      }
+      else
+      {
+        $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
+        if ($nextLevel == $level)
+        {
+          $immediateChildren[] = $child;
+        }
+        elseif ($level < $nextLevel)
+        {
+          //Re-assign immediateChildren to grandchilden
+          $grandchildren = array_merge($grandchildren, $immediateChildren);
+          //Set new children
+          $immediateChildren = array($child);
+        }
+        else
+        {
+          $grandchildren[] = $child;
+        }
+      }
+    }
+    
+    if (!empty($immediateChildren))
+    {
+      $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
+      
+      //Determine which composite media type is needed to accomodate the
+      // immediate children
+      foreach ($this->_compositeRanges as $mediaType => $range)
+      {
+        if ($lowestLevel > $range[0]
+          && $lowestLevel <= $range[1])
+        {
+          $newContentType = $mediaType;
+          break;
+        }
+      }
+      
+      //Put any grandchildren in a subpart
+      if (!empty($grandchildren))
+      {
+        $subentity = $this->_createChild();
+        $subentity->_setNestingLevel($lowestLevel);
+        $subentity->setChildren($grandchildren, $compoundLevel);
+        array_unshift($immediateChildren, $subentity);
+      }
+    }
+    
+    $this->_immediateChildren = $immediateChildren;
+    $this->_children = $children;
+    $this->_setContentTypeInHeaders($newContentType);
+    $this->_fixHeaders();
+    $this->_sortChildren();
+    
+    return $this;
+  }
+  
+  /**
+   * Get the body of this entity as a string.
+   * @return string
+   */
+  public function getBody()
+  {
+    return ($this->_body instanceof Swift_OutputByteStream)
+      ? $this->_readStream($this->_body)
+      : $this->_body;
+  }
+  
+  /**
+   * Set the body of this entity, either as a string, or as an instance of
+   * {@link Swift_OutputByteStream}.
+   * @param mixed $body
+   * @param string $contentType optional
+   */
+  public function setBody($body, $contentType = null)
+  {
+    if ($body !== $this->_body)
+    {
+      $this->_clearCache();
+    }
+    
+    $this->_body = $body;
+    if (isset($contentType))
+    {
+      $this->setContentType($contentType);
+    }
+    return $this;
+  }
+  
+  /**
+   * Get the encoder used for the body of this entity.
+   * @return Swift_Mime_ContentEncoder
+   */
+  public function getEncoder()
+  {
+    return $this->_encoder;
+  }
+  
+  /**
+   * Set the encoder used for the body of this entity.
+   * @param Swift_Mime_ContentEncoder $encoder
+   */
+  public function setEncoder(Swift_Mime_ContentEncoder $encoder)
+  {
+    if ($encoder !== $this->_encoder)
+    {
+      $this->_clearCache();
+    }
+    
+    $this->_encoder = $encoder;
+    $this->_setEncoding($encoder->getName());
+    $this->_notifyEncoderChanged($encoder);
+    return $this;
+  }
+  
+  /**
+   * Get the boundary used to separate children in this entity.
+   * @return string
+   */
+  public function getBoundary()
+  {
+    if (!isset($this->_boundary))
+    {
+      $this->_boundary = '_=_swift_v4_' . time() . uniqid() . '_=_';
+    }
+    return $this->_boundary;
+  }
+  
+  /**
+   * Set the boundary used to separate children in this entity.
+   * @param string $boundary
+   * @throws Swift_RfcComplianceException
+   */
+  public function setBoundary($boundary)
+  {
+    $this->_assertValidBoundary($boundary);
+    $this->_boundary = $boundary;
+    return $this;
+  }
+  
+  /**
+   * Receive notification that the charset of this entity, or a parent entity
+   * has changed.
+   * @param string $charset
+   */
+  public function charsetChanged($charset)
+  {
+    $this->_notifyCharsetChanged($charset);
+  }
+  
+  /**
+   * Receive notification that the encoder of this entity or a parent entity
+   * has changed.
+   * @param Swift_Mime_ContentEncoder $encoder
+   */
+  public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
+  {
+    $this->_notifyEncoderChanged($encoder);
+  }
+  
+  /**
+   * Get this entire entity as a string.
+   * @return string
+   */
+  public function toString()
+  {
+    $string = $this->_headers->toString();
+    if (isset($this->_body) && empty($this->_immediateChildren))
+    {
+      if ($this->_cache->hasKey($this->_cacheKey, 'body'))
+      {
+        $body = $this->_cache->getString($this->_cacheKey, 'body');
+      }
+      else
+      {
+        $body = "\r\n" . $this->_encoder->encodeString($this->getBody(), 0,
+          $this->getMaxLineLength()
+          );
+        $this->_cache->setString($this->_cacheKey, 'body', $body,
+          Swift_KeyCache::MODE_WRITE
+          );
+      }
+      $string .= $body;
+    }
+    
+    if (!empty($this->_immediateChildren))
+    {
+      foreach ($this->_immediateChildren as $child)
+      {
+        $string .= "\r\n\r\n--" . $this->getBoundary() . "\r\n";
+        $string .= $child->toString();
+      }
+      $string .= "\r\n\r\n--" . $this->getBoundary() . "--\r\n";
+    }
+    
+    return $string;
+  }
+  
+  /**
+   * Returns a string representation of this object.
+   *
+   * @return string
+   *
+   * @see toString()
+   */
+  public function __toString()
+  {
+    return $this->toString();
+  }
+  
+  /**
+   * Write this entire entity to a {@link Swift_InputByteStream}.
+   * @param Swift_InputByteStream
+   */
+  public function toByteStream(Swift_InputByteStream $is)
+  {
+    $is->write($this->_headers->toString());
+    $is->commit();
+    
+    if (empty($this->_immediateChildren))
+    {
+      if (isset($this->_body))
+      {
+        if ($this->_cache->hasKey($this->_cacheKey, 'body'))
+        {
+          $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is);
+        }
+        else
+        {
+          $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body');
+          if ($cacheIs)
+          {
+            $is->bind($cacheIs);
+          }
+          
+          $is->write("\r\n");
+          
+          if ($this->_body instanceof Swift_OutputByteStream)
+          {
+            $this->_body->setReadPointer(0);
+            
+            $this->_encoder->encodeByteStream($this->_body, $is, 0,
+              $this->getMaxLineLength()
+              );
+          }
+          else
+          {
+            $is->write($this->_encoder->encodeString(
+              $this->getBody(), 0, $this->getMaxLineLength()
+              ));
+          }
+          
+          if ($cacheIs)
+          {
+            $is->unbind($cacheIs);
+          }
+        }
+      }
+    }
+    
+    if (!empty($this->_immediateChildren))
+    {
+      foreach ($this->_immediateChildren as $child)
+      {
+        $is->write("\r\n\r\n--" . $this->getBoundary() . "\r\n");
+        $child->toByteStream($is);
+      }
+      $is->write("\r\n\r\n--" . $this->getBoundary() . "--\r\n");
+    }
+  }
+  
+  // -- Protected methods
+  
+  /**
+   * Get the name of the header that provides the ID of this entity */
+  protected function _getIdField()
+  {
+    return 'Content-ID';
+  }
+  
+  /**
+   * Get the model data (usually an array or a string) for $field.
+   */
+  protected function _getHeaderFieldModel($field)
+  {
+    if ($this->_headers->has($field))
+    {
+      return $this->_headers->get($field)->getFieldBodyModel();
+    }
+  }
+  
+  /**
+   * Set the model data for $field.
+   */
+  protected function _setHeaderFieldModel($field, $model)
+  {
+    if ($this->_headers->has($field))
+    {
+      $this->_headers->get($field)->setFieldBodyModel($model);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+  
+  /**
+   * Get the parameter value of $parameter on $field header.
+   */
+  protected function _getHeaderParameter($field, $parameter)
+  {
+    if ($this->_headers->has($field))
+    {
+      return $this->_headers->get($field)->getParameter($parameter);
+    }
+  }
+  
+  /**
+   * Set the parameter value of $parameter on $field header.
+   */
+  protected function _setHeaderParameter($field, $parameter, $value)
+  {
+    if ($this->_headers->has($field))
+    {
+      $this->_headers->get($field)->setParameter($parameter, $value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+  
+  /**
+   * Re-evaluate what content type and encoding should be used on this entity.
+   */
+  protected function _fixHeaders()
+  {
+    if (count($this->_immediateChildren))
+    {
+      $this->_setHeaderParameter('Content-Type', 'boundary',
+        $this->getBoundary()
+        );
+      $this->_headers->remove('Content-Transfer-Encoding');
+    }
+    else
+    {
+      $this->_setHeaderParameter('Content-Type', 'boundary', null);
+      $this->_setEncoding($this->_encoder->getName());
+    }
+  }
+  
+  /**
+   * Get the KeyCache used in this entity.
+   */
+  protected function _getCache()
+  {
+    return $this->_cache;
+  }
+  
+  /**
+   * Get the grammar used for validation.
+   * @return Swift_Mime_Grammar
+   */
+  protected function _getGrammar()
+  {
+    return $this->_grammar;
+  }
+  
+  /**
+   * Empty the KeyCache for this entity.
+   */
+  protected function _clearCache()
+  {
+    $this->_cache->clearKey($this->_cacheKey, 'body');
+  }
+  
+  /**
+   * Returns a random Content-ID or Message-ID.
+   * @return string
+   */
+  protected function getRandomId()
+  {
+    $idLeft = time() . '.' . uniqid();
+    $idRight = !empty($_SERVER['SERVER_NAME'])
+      ? $_SERVER['SERVER_NAME']
+      : 'swift.generated';
+    $id = $idLeft . '@' . $idRight;
+
+    try
+    {
+      $this->_assertValidId($id);
+    }
+    catch (Swift_RfcComplianceException $e)
+    {
+      $id = $idLeft . '@swift.generated';
+    }
+
+    return $id;
+  }
+  
+  // -- Private methods
+  
+  private function _readStream(Swift_OutputByteStream $os)
+  {
+    $string = '';
+    while (false !== $bytes = $os->read(8192))
+    {
+      $string .= $bytes;
+    }
+    return $string;
+  }
+  
+  private function _setEncoding($encoding)
+  {
+    if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding))
+    {
+      $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding);
+    }
+  }
+  
+  private function _assertValidBoundary($boundary)
+  {
+    if (!preg_match(
+      '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di',
+      $boundary))
+    {
+      throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.');
+    }
+  }
+  
+  private function _setContentTypeInHeaders($type)
+  {
+    if (!$this->_setHeaderFieldModel('Content-Type', $type))
+    {
+      $this->_headers->addParameterizedHeader('Content-Type', $type);
+    }
+  }
+  
+  private function _setNestingLevel($level)
+  {
+    $this->_nestingLevel = $level;
+  }
+  
+  private function _getCompoundLevel($children)
+  {
+    $level = 0;
+    foreach ($children as $child)
+    {
+      $level |= $child->getNestingLevel();
+    }
+    return $level;
+  }
+  
+  private function _getNeededChildLevel($child, $compoundLevel)
+  {
+    $filter = array();
+    foreach ($this->_compoundLevelFilters as $bitmask => $rules)
+    {
+      if (($compoundLevel & $bitmask) === $bitmask)
+      {
+        $filter = $rules + $filter;
+      }
+    }
+    
+    $realLevel = $child->getNestingLevel();
+    $lowercaseType = strtolower($child->getContentType());
+    
+    if (isset($filter[$realLevel])
+      && isset($filter[$realLevel][$lowercaseType]))
+    {
+      return $filter[$realLevel][$lowercaseType];
+    }
+    else
+    {
+      return $realLevel;
+    }
+  }
+  
+  private function _createChild()
+  {
+    return new self($this->_headers->newInstance(),
+      $this->_encoder, $this->_cache, $this->_grammar);
+  }
+  
+  private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder)
+  {
+    foreach ($this->_immediateChildren as $child)
+    {
+      $child->encoderChanged($encoder);
+    }
+  }
+  
+  private function _notifyCharsetChanged($charset)
+  {
+    $this->_encoder->charsetChanged($charset);
+    $this->_headers->charsetChanged($charset);
+    foreach ($this->_immediateChildren as $child)
+    {
+      $child->charsetChanged($charset);
+    }
+  }
+  
+  private function _sortChildren()
+  {
+    $shouldSort = false;
+    foreach ($this->_immediateChildren as $child)
+    {
+      //NOTE: This include alternative parts moved into a related part
+      if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE)
+      {
+        $shouldSort = true;
+        break;
+      }
+    }
+    
+    //Sort in order of preference, if there is one
+    if ($shouldSort)
+    {
+      usort($this->_immediateChildren, array($this, '_childSortAlgorithm'));
+    }
+  }
+  
+  private function _childSortAlgorithm($a, $b)
+  {
+    $typePrefs = array();
+    $types = array(
+      strtolower($a->getContentType()),
+      strtolower($b->getContentType())
+      );
+    foreach ($types as $type)
+    {
+      $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder))
+        ? $this->_alternativePartOrder[$type]
+        : (max($this->_alternativePartOrder) + 1);
+    }
+    return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1;
+  }
+  
+  // -- Destructor
+  
+  /**
+   * Empties it's own contents from the cache.
+   */
+  public function __destruct()
+  {
+    $this->_cache->clearAll($this->_cacheKey);
+  }
+  
+  /**
+   * Throws an Exception if the id passed does not comply with RFC 2822.
+   * @param string $id
+   * @throws Swift_RfcComplianceException
+   */
+  private function _assertValidId($id)
+  {
+    if (!preg_match(
+      '/^' . $this->_grammar->getDefinition('id-left') . '@' .
+      $this->_grammar->getDefinition('id-right') . '$/D',
+      $id
+      ))
+    {
+      throw new Swift_RfcComplianceException(
+        'Invalid ID given <' . $id . '>'
+        );
+    }
+  }
+  
+}