vendor/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * This file is part of SwiftMailer.
       
     5  * (c) 2004-2009 Chris Corbyn
       
     6  *
       
     7  * For the full copyright and license information, please view the LICENSE
       
     8  * file that was distributed with this source code.
       
     9  */
       
    10 
       
    11 
       
    12 /**
       
    13  * A MIME entity, in a multipart message.
       
    14  * @package Swift
       
    15  * @subpackage Mime
       
    16  * @author Chris Corbyn
       
    17  */
       
    18 class Swift_Mime_SimpleMimeEntity implements Swift_Mime_MimeEntity
       
    19 {
       
    20   
       
    21   /** A collection of Headers for this mime entity */
       
    22   private $_headers;
       
    23   
       
    24   /** The body as a string, or a stream */
       
    25   private $_body;
       
    26   
       
    27   /** The encoder that encodes the body into a streamable format */
       
    28   private $_encoder;
       
    29 
       
    30   /** The grammar to use for id validation */
       
    31   private $_grammar;
       
    32   
       
    33   /** A mime bounary, if any is used */
       
    34   private $_boundary;
       
    35   
       
    36   /** Mime types to be used based on the nesting level */
       
    37   private $_compositeRanges = array(
       
    38     'multipart/mixed' => array(self::LEVEL_TOP, self::LEVEL_MIXED),
       
    39     'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE),
       
    40     'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED)
       
    41     );
       
    42   
       
    43   /** A set of filter rules to define what level an entity should be nested at */
       
    44   private $_compoundLevelFilters = array();
       
    45     
       
    46   /** The nesting level of this entity */
       
    47   private $_nestingLevel = self::LEVEL_ALTERNATIVE;
       
    48   
       
    49   /** A KeyCache instance used during encoding and streaming */
       
    50   private $_cache;
       
    51   
       
    52   /** Direct descendants of this entity */
       
    53   private $_immediateChildren = array();
       
    54   
       
    55   /** All descendants of this entity */
       
    56   private $_children = array();
       
    57   
       
    58   /** The maximum line length of the body of this entity */
       
    59   private $_maxLineLength = 78;
       
    60   
       
    61   /** The order in which alternative mime types should appear */
       
    62   private $_alternativePartOrder = array(
       
    63     'text/plain' => 1,
       
    64     'text/html' => 2,
       
    65     'multipart/related' => 3
       
    66     );
       
    67   
       
    68   /** The CID of this entity */
       
    69   private $_id;
       
    70   
       
    71   /** The key used for accessing the cache */
       
    72   private $_cacheKey;
       
    73   
       
    74   protected $_userContentType;
       
    75   
       
    76   /**
       
    77    * Create a new SimpleMimeEntity with $headers, $encoder and $cache.
       
    78    * @param Swift_Mime_HeaderSet $headers
       
    79    * @param Swift_Mime_ContentEncoder $encoder
       
    80    * @param Swift_KeyCache $cache
       
    81    * @param Swift_Mime_Grammar $grammar
       
    82    */
       
    83   public function __construct(Swift_Mime_HeaderSet $headers,
       
    84     Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache,
       
    85     Swift_Mime_Grammar $grammar)
       
    86   {
       
    87     $this->_cacheKey = uniqid();
       
    88     $this->_cache = $cache;
       
    89     $this->_headers = $headers;
       
    90     $this->_grammar = $grammar;
       
    91     $this->setEncoder($encoder);
       
    92     $this->_headers->defineOrdering(
       
    93       array('Content-Type', 'Content-Transfer-Encoding')
       
    94       );
       
    95     
       
    96     // This array specifies that, when the entire MIME document contains
       
    97     // $compoundLevel, then for each child within $level, if its Content-Type
       
    98     // is $contentType then it should be treated as if it's level is
       
    99     // $neededLevel instead.  I tried to write that unambiguously! :-\
       
   100     // Data Structure:
       
   101     // array (
       
   102     //   $compoundLevel => array(
       
   103     //     $level => array(
       
   104     //       $contentType => $neededLevel
       
   105     //     )
       
   106     //   )
       
   107     // )
       
   108     
       
   109     $this->_compoundLevelFilters = array(
       
   110       (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array(
       
   111         self::LEVEL_ALTERNATIVE => array(
       
   112           'text/plain' => self::LEVEL_ALTERNATIVE,
       
   113           'text/html' => self::LEVEL_RELATED
       
   114           )
       
   115         )
       
   116       );
       
   117 
       
   118     $this->_id = $this->getRandomId();
       
   119   }
       
   120   
       
   121   /**
       
   122    * Generate a new Content-ID or Message-ID for this MIME entity.
       
   123    * @return string
       
   124    */
       
   125   public function generateId()
       
   126   {
       
   127     $this->setId($this->getRandomId());
       
   128     return $this->_id;
       
   129   }
       
   130   
       
   131   /**
       
   132    * Get the {@link Swift_Mime_HeaderSet} for this entity.
       
   133    * @return Swift_Mime_HeaderSet
       
   134    */
       
   135   public function getHeaders()
       
   136   {
       
   137     return $this->_headers;
       
   138   }
       
   139   
       
   140   /**
       
   141    * Get the nesting level of this entity.
       
   142    * @return int
       
   143    * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
       
   144    */
       
   145   public function getNestingLevel()
       
   146   {
       
   147     return $this->_nestingLevel;
       
   148   }
       
   149   
       
   150   /**
       
   151    * Get the Content-type of this entity.
       
   152    * @return string
       
   153    */
       
   154   public function getContentType()
       
   155   {
       
   156     return $this->_getHeaderFieldModel('Content-Type');
       
   157   }
       
   158   
       
   159   /**
       
   160    * Set the Content-type of this entity.
       
   161    * @param string $type
       
   162    */
       
   163   public function setContentType($type)
       
   164   {
       
   165     $this->_setContentTypeInHeaders($type);
       
   166     // Keep track of the value so that if the content-type changes automatically
       
   167     // due to added child entities, it can be restored if they are later removed
       
   168     $this->_userContentType = $type;
       
   169     return $this;
       
   170   }
       
   171   
       
   172   /**
       
   173    * Get the CID of this entity.
       
   174    * The CID will only be present in headers if a Content-ID header is present.
       
   175    * @return string
       
   176    */
       
   177   public function getId()
       
   178   {
       
   179     return $this->_headers->has($this->_getIdField())
       
   180       ? current((array) $this->_getHeaderFieldModel($this->_getIdField()))
       
   181       : $this->_id;
       
   182   }
       
   183   
       
   184   /**
       
   185    * Set the CID of this entity.
       
   186    * @param string $id
       
   187    */
       
   188   public function setId($id)
       
   189   {
       
   190     if (!$this->_setHeaderFieldModel($this->_getIdField(), $id))
       
   191     {
       
   192       $this->_headers->addIdHeader($this->_getIdField(), $id);
       
   193     }
       
   194     $this->_id = $id;
       
   195     return $this;
       
   196   }
       
   197   
       
   198   /**
       
   199    * Get the description of this entity.
       
   200    * This value comes from the Content-Description header if set.
       
   201    * @return string
       
   202    */
       
   203   public function getDescription()
       
   204   {
       
   205     return $this->_getHeaderFieldModel('Content-Description');
       
   206   }
       
   207   
       
   208   /**
       
   209    * Set the description of this entity.
       
   210    * This method sets a value in the Content-ID header.
       
   211    * @param string $description
       
   212    */
       
   213   public function setDescription($description)
       
   214   {
       
   215     if (!$this->_setHeaderFieldModel('Content-Description', $description))
       
   216     {
       
   217       $this->_headers->addTextHeader('Content-Description', $description);
       
   218     }
       
   219     return $this;
       
   220   }
       
   221   
       
   222   /**
       
   223    * Get the maximum line length of the body of this entity.
       
   224    * @return int
       
   225    */
       
   226   public function getMaxLineLength()
       
   227   {
       
   228     return $this->_maxLineLength;
       
   229   }
       
   230   
       
   231   /**
       
   232    * Set the maximum line length of lines in this body.
       
   233    * Though not enforced by the library, lines should not exceed 1000 chars.
       
   234    * @param int $length
       
   235    */
       
   236   public function setMaxLineLength($length)
       
   237   {
       
   238     $this->_maxLineLength = $length;
       
   239     return $this;
       
   240   }
       
   241   
       
   242   /**
       
   243    * Get all children added to this entity.
       
   244    * @return array of Swift_Mime_Entity
       
   245    */
       
   246   public function getChildren()
       
   247   {
       
   248     return $this->_children;
       
   249   }
       
   250   
       
   251   /**
       
   252    * Set all children of this entity.
       
   253    * @param array $children Swiift_Mime_Entity instances
       
   254    * @param int $compoundLevel For internal use only
       
   255    */
       
   256   public function setChildren(array $children, $compoundLevel = null)
       
   257   {
       
   258     //TODO: Try to refactor this logic
       
   259     
       
   260     $compoundLevel = isset($compoundLevel)
       
   261       ? $compoundLevel
       
   262       : $this->_getCompoundLevel($children)
       
   263       ;
       
   264     
       
   265     $immediateChildren = array();
       
   266     $grandchildren = array();
       
   267     $newContentType = $this->_userContentType;
       
   268     
       
   269     foreach ($children as $child)
       
   270     {
       
   271       $level = $this->_getNeededChildLevel($child, $compoundLevel);
       
   272       if (empty($immediateChildren)) //first iteration
       
   273       {
       
   274         $immediateChildren = array($child);
       
   275       }
       
   276       else
       
   277       {
       
   278         $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
       
   279         if ($nextLevel == $level)
       
   280         {
       
   281           $immediateChildren[] = $child;
       
   282         }
       
   283         elseif ($level < $nextLevel)
       
   284         {
       
   285           //Re-assign immediateChildren to grandchilden
       
   286           $grandchildren = array_merge($grandchildren, $immediateChildren);
       
   287           //Set new children
       
   288           $immediateChildren = array($child);
       
   289         }
       
   290         else
       
   291         {
       
   292           $grandchildren[] = $child;
       
   293         }
       
   294       }
       
   295     }
       
   296     
       
   297     if (!empty($immediateChildren))
       
   298     {
       
   299       $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
       
   300       
       
   301       //Determine which composite media type is needed to accomodate the
       
   302       // immediate children
       
   303       foreach ($this->_compositeRanges as $mediaType => $range)
       
   304       {
       
   305         if ($lowestLevel > $range[0]
       
   306           && $lowestLevel <= $range[1])
       
   307         {
       
   308           $newContentType = $mediaType;
       
   309           break;
       
   310         }
       
   311       }
       
   312       
       
   313       //Put any grandchildren in a subpart
       
   314       if (!empty($grandchildren))
       
   315       {
       
   316         $subentity = $this->_createChild();
       
   317         $subentity->_setNestingLevel($lowestLevel);
       
   318         $subentity->setChildren($grandchildren, $compoundLevel);
       
   319         array_unshift($immediateChildren, $subentity);
       
   320       }
       
   321     }
       
   322     
       
   323     $this->_immediateChildren = $immediateChildren;
       
   324     $this->_children = $children;
       
   325     $this->_setContentTypeInHeaders($newContentType);
       
   326     $this->_fixHeaders();
       
   327     $this->_sortChildren();
       
   328     
       
   329     return $this;
       
   330   }
       
   331   
       
   332   /**
       
   333    * Get the body of this entity as a string.
       
   334    * @return string
       
   335    */
       
   336   public function getBody()
       
   337   {
       
   338     return ($this->_body instanceof Swift_OutputByteStream)
       
   339       ? $this->_readStream($this->_body)
       
   340       : $this->_body;
       
   341   }
       
   342   
       
   343   /**
       
   344    * Set the body of this entity, either as a string, or as an instance of
       
   345    * {@link Swift_OutputByteStream}.
       
   346    * @param mixed $body
       
   347    * @param string $contentType optional
       
   348    */
       
   349   public function setBody($body, $contentType = null)
       
   350   {
       
   351     if ($body !== $this->_body)
       
   352     {
       
   353       $this->_clearCache();
       
   354     }
       
   355     
       
   356     $this->_body = $body;
       
   357     if (isset($contentType))
       
   358     {
       
   359       $this->setContentType($contentType);
       
   360     }
       
   361     return $this;
       
   362   }
       
   363   
       
   364   /**
       
   365    * Get the encoder used for the body of this entity.
       
   366    * @return Swift_Mime_ContentEncoder
       
   367    */
       
   368   public function getEncoder()
       
   369   {
       
   370     return $this->_encoder;
       
   371   }
       
   372   
       
   373   /**
       
   374    * Set the encoder used for the body of this entity.
       
   375    * @param Swift_Mime_ContentEncoder $encoder
       
   376    */
       
   377   public function setEncoder(Swift_Mime_ContentEncoder $encoder)
       
   378   {
       
   379     if ($encoder !== $this->_encoder)
       
   380     {
       
   381       $this->_clearCache();
       
   382     }
       
   383     
       
   384     $this->_encoder = $encoder;
       
   385     $this->_setEncoding($encoder->getName());
       
   386     $this->_notifyEncoderChanged($encoder);
       
   387     return $this;
       
   388   }
       
   389   
       
   390   /**
       
   391    * Get the boundary used to separate children in this entity.
       
   392    * @return string
       
   393    */
       
   394   public function getBoundary()
       
   395   {
       
   396     if (!isset($this->_boundary))
       
   397     {
       
   398       $this->_boundary = '_=_swift_v4_' . time() . uniqid() . '_=_';
       
   399     }
       
   400     return $this->_boundary;
       
   401   }
       
   402   
       
   403   /**
       
   404    * Set the boundary used to separate children in this entity.
       
   405    * @param string $boundary
       
   406    * @throws Swift_RfcComplianceException
       
   407    */
       
   408   public function setBoundary($boundary)
       
   409   {
       
   410     $this->_assertValidBoundary($boundary);
       
   411     $this->_boundary = $boundary;
       
   412     return $this;
       
   413   }
       
   414   
       
   415   /**
       
   416    * Receive notification that the charset of this entity, or a parent entity
       
   417    * has changed.
       
   418    * @param string $charset
       
   419    */
       
   420   public function charsetChanged($charset)
       
   421   {
       
   422     $this->_notifyCharsetChanged($charset);
       
   423   }
       
   424   
       
   425   /**
       
   426    * Receive notification that the encoder of this entity or a parent entity
       
   427    * has changed.
       
   428    * @param Swift_Mime_ContentEncoder $encoder
       
   429    */
       
   430   public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
       
   431   {
       
   432     $this->_notifyEncoderChanged($encoder);
       
   433   }
       
   434   
       
   435   /**
       
   436    * Get this entire entity as a string.
       
   437    * @return string
       
   438    */
       
   439   public function toString()
       
   440   {
       
   441     $string = $this->_headers->toString();
       
   442     if (isset($this->_body) && empty($this->_immediateChildren))
       
   443     {
       
   444       if ($this->_cache->hasKey($this->_cacheKey, 'body'))
       
   445       {
       
   446         $body = $this->_cache->getString($this->_cacheKey, 'body');
       
   447       }
       
   448       else
       
   449       {
       
   450         $body = "\r\n" . $this->_encoder->encodeString($this->getBody(), 0,
       
   451           $this->getMaxLineLength()
       
   452           );
       
   453         $this->_cache->setString($this->_cacheKey, 'body', $body,
       
   454           Swift_KeyCache::MODE_WRITE
       
   455           );
       
   456       }
       
   457       $string .= $body;
       
   458     }
       
   459     
       
   460     if (!empty($this->_immediateChildren))
       
   461     {
       
   462       foreach ($this->_immediateChildren as $child)
       
   463       {
       
   464         $string .= "\r\n\r\n--" . $this->getBoundary() . "\r\n";
       
   465         $string .= $child->toString();
       
   466       }
       
   467       $string .= "\r\n\r\n--" . $this->getBoundary() . "--\r\n";
       
   468     }
       
   469     
       
   470     return $string;
       
   471   }
       
   472   
       
   473   /**
       
   474    * Returns a string representation of this object.
       
   475    *
       
   476    * @return string
       
   477    *
       
   478    * @see toString()
       
   479    */
       
   480   public function __toString()
       
   481   {
       
   482     return $this->toString();
       
   483   }
       
   484   
       
   485   /**
       
   486    * Write this entire entity to a {@link Swift_InputByteStream}.
       
   487    * @param Swift_InputByteStream
       
   488    */
       
   489   public function toByteStream(Swift_InputByteStream $is)
       
   490   {
       
   491     $is->write($this->_headers->toString());
       
   492     $is->commit();
       
   493     
       
   494     if (empty($this->_immediateChildren))
       
   495     {
       
   496       if (isset($this->_body))
       
   497       {
       
   498         if ($this->_cache->hasKey($this->_cacheKey, 'body'))
       
   499         {
       
   500           $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is);
       
   501         }
       
   502         else
       
   503         {
       
   504           $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body');
       
   505           if ($cacheIs)
       
   506           {
       
   507             $is->bind($cacheIs);
       
   508           }
       
   509           
       
   510           $is->write("\r\n");
       
   511           
       
   512           if ($this->_body instanceof Swift_OutputByteStream)
       
   513           {
       
   514             $this->_body->setReadPointer(0);
       
   515             
       
   516             $this->_encoder->encodeByteStream($this->_body, $is, 0,
       
   517               $this->getMaxLineLength()
       
   518               );
       
   519           }
       
   520           else
       
   521           {
       
   522             $is->write($this->_encoder->encodeString(
       
   523               $this->getBody(), 0, $this->getMaxLineLength()
       
   524               ));
       
   525           }
       
   526           
       
   527           if ($cacheIs)
       
   528           {
       
   529             $is->unbind($cacheIs);
       
   530           }
       
   531         }
       
   532       }
       
   533     }
       
   534     
       
   535     if (!empty($this->_immediateChildren))
       
   536     {
       
   537       foreach ($this->_immediateChildren as $child)
       
   538       {
       
   539         $is->write("\r\n\r\n--" . $this->getBoundary() . "\r\n");
       
   540         $child->toByteStream($is);
       
   541       }
       
   542       $is->write("\r\n\r\n--" . $this->getBoundary() . "--\r\n");
       
   543     }
       
   544   }
       
   545   
       
   546   // -- Protected methods
       
   547   
       
   548   /**
       
   549    * Get the name of the header that provides the ID of this entity */
       
   550   protected function _getIdField()
       
   551   {
       
   552     return 'Content-ID';
       
   553   }
       
   554   
       
   555   /**
       
   556    * Get the model data (usually an array or a string) for $field.
       
   557    */
       
   558   protected function _getHeaderFieldModel($field)
       
   559   {
       
   560     if ($this->_headers->has($field))
       
   561     {
       
   562       return $this->_headers->get($field)->getFieldBodyModel();
       
   563     }
       
   564   }
       
   565   
       
   566   /**
       
   567    * Set the model data for $field.
       
   568    */
       
   569   protected function _setHeaderFieldModel($field, $model)
       
   570   {
       
   571     if ($this->_headers->has($field))
       
   572     {
       
   573       $this->_headers->get($field)->setFieldBodyModel($model);
       
   574       return true;
       
   575     }
       
   576     else
       
   577     {
       
   578       return false;
       
   579     }
       
   580   }
       
   581   
       
   582   /**
       
   583    * Get the parameter value of $parameter on $field header.
       
   584    */
       
   585   protected function _getHeaderParameter($field, $parameter)
       
   586   {
       
   587     if ($this->_headers->has($field))
       
   588     {
       
   589       return $this->_headers->get($field)->getParameter($parameter);
       
   590     }
       
   591   }
       
   592   
       
   593   /**
       
   594    * Set the parameter value of $parameter on $field header.
       
   595    */
       
   596   protected function _setHeaderParameter($field, $parameter, $value)
       
   597   {
       
   598     if ($this->_headers->has($field))
       
   599     {
       
   600       $this->_headers->get($field)->setParameter($parameter, $value);
       
   601       return true;
       
   602     }
       
   603     else
       
   604     {
       
   605       return false;
       
   606     }
       
   607   }
       
   608   
       
   609   /**
       
   610    * Re-evaluate what content type and encoding should be used on this entity.
       
   611    */
       
   612   protected function _fixHeaders()
       
   613   {
       
   614     if (count($this->_immediateChildren))
       
   615     {
       
   616       $this->_setHeaderParameter('Content-Type', 'boundary',
       
   617         $this->getBoundary()
       
   618         );
       
   619       $this->_headers->remove('Content-Transfer-Encoding');
       
   620     }
       
   621     else
       
   622     {
       
   623       $this->_setHeaderParameter('Content-Type', 'boundary', null);
       
   624       $this->_setEncoding($this->_encoder->getName());
       
   625     }
       
   626   }
       
   627   
       
   628   /**
       
   629    * Get the KeyCache used in this entity.
       
   630    */
       
   631   protected function _getCache()
       
   632   {
       
   633     return $this->_cache;
       
   634   }
       
   635   
       
   636   /**
       
   637    * Get the grammar used for validation.
       
   638    * @return Swift_Mime_Grammar
       
   639    */
       
   640   protected function _getGrammar()
       
   641   {
       
   642     return $this->_grammar;
       
   643   }
       
   644   
       
   645   /**
       
   646    * Empty the KeyCache for this entity.
       
   647    */
       
   648   protected function _clearCache()
       
   649   {
       
   650     $this->_cache->clearKey($this->_cacheKey, 'body');
       
   651   }
       
   652   
       
   653   /**
       
   654    * Returns a random Content-ID or Message-ID.
       
   655    * @return string
       
   656    */
       
   657   protected function getRandomId()
       
   658   {
       
   659     $idLeft = time() . '.' . uniqid();
       
   660     $idRight = !empty($_SERVER['SERVER_NAME'])
       
   661       ? $_SERVER['SERVER_NAME']
       
   662       : 'swift.generated';
       
   663     $id = $idLeft . '@' . $idRight;
       
   664 
       
   665     try
       
   666     {
       
   667       $this->_assertValidId($id);
       
   668     }
       
   669     catch (Swift_RfcComplianceException $e)
       
   670     {
       
   671       $id = $idLeft . '@swift.generated';
       
   672     }
       
   673 
       
   674     return $id;
       
   675   }
       
   676   
       
   677   // -- Private methods
       
   678   
       
   679   private function _readStream(Swift_OutputByteStream $os)
       
   680   {
       
   681     $string = '';
       
   682     while (false !== $bytes = $os->read(8192))
       
   683     {
       
   684       $string .= $bytes;
       
   685     }
       
   686     return $string;
       
   687   }
       
   688   
       
   689   private function _setEncoding($encoding)
       
   690   {
       
   691     if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding))
       
   692     {
       
   693       $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding);
       
   694     }
       
   695   }
       
   696   
       
   697   private function _assertValidBoundary($boundary)
       
   698   {
       
   699     if (!preg_match(
       
   700       '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di',
       
   701       $boundary))
       
   702     {
       
   703       throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.');
       
   704     }
       
   705   }
       
   706   
       
   707   private function _setContentTypeInHeaders($type)
       
   708   {
       
   709     if (!$this->_setHeaderFieldModel('Content-Type', $type))
       
   710     {
       
   711       $this->_headers->addParameterizedHeader('Content-Type', $type);
       
   712     }
       
   713   }
       
   714   
       
   715   private function _setNestingLevel($level)
       
   716   {
       
   717     $this->_nestingLevel = $level;
       
   718   }
       
   719   
       
   720   private function _getCompoundLevel($children)
       
   721   {
       
   722     $level = 0;
       
   723     foreach ($children as $child)
       
   724     {
       
   725       $level |= $child->getNestingLevel();
       
   726     }
       
   727     return $level;
       
   728   }
       
   729   
       
   730   private function _getNeededChildLevel($child, $compoundLevel)
       
   731   {
       
   732     $filter = array();
       
   733     foreach ($this->_compoundLevelFilters as $bitmask => $rules)
       
   734     {
       
   735       if (($compoundLevel & $bitmask) === $bitmask)
       
   736       {
       
   737         $filter = $rules + $filter;
       
   738       }
       
   739     }
       
   740     
       
   741     $realLevel = $child->getNestingLevel();
       
   742     $lowercaseType = strtolower($child->getContentType());
       
   743     
       
   744     if (isset($filter[$realLevel])
       
   745       && isset($filter[$realLevel][$lowercaseType]))
       
   746     {
       
   747       return $filter[$realLevel][$lowercaseType];
       
   748     }
       
   749     else
       
   750     {
       
   751       return $realLevel;
       
   752     }
       
   753   }
       
   754   
       
   755   private function _createChild()
       
   756   {
       
   757     return new self($this->_headers->newInstance(),
       
   758       $this->_encoder, $this->_cache, $this->_grammar);
       
   759   }
       
   760   
       
   761   private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder)
       
   762   {
       
   763     foreach ($this->_immediateChildren as $child)
       
   764     {
       
   765       $child->encoderChanged($encoder);
       
   766     }
       
   767   }
       
   768   
       
   769   private function _notifyCharsetChanged($charset)
       
   770   {
       
   771     $this->_encoder->charsetChanged($charset);
       
   772     $this->_headers->charsetChanged($charset);
       
   773     foreach ($this->_immediateChildren as $child)
       
   774     {
       
   775       $child->charsetChanged($charset);
       
   776     }
       
   777   }
       
   778   
       
   779   private function _sortChildren()
       
   780   {
       
   781     $shouldSort = false;
       
   782     foreach ($this->_immediateChildren as $child)
       
   783     {
       
   784       //NOTE: This include alternative parts moved into a related part
       
   785       if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE)
       
   786       {
       
   787         $shouldSort = true;
       
   788         break;
       
   789       }
       
   790     }
       
   791     
       
   792     //Sort in order of preference, if there is one
       
   793     if ($shouldSort)
       
   794     {
       
   795       usort($this->_immediateChildren, array($this, '_childSortAlgorithm'));
       
   796     }
       
   797   }
       
   798   
       
   799   private function _childSortAlgorithm($a, $b)
       
   800   {
       
   801     $typePrefs = array();
       
   802     $types = array(
       
   803       strtolower($a->getContentType()),
       
   804       strtolower($b->getContentType())
       
   805       );
       
   806     foreach ($types as $type)
       
   807     {
       
   808       $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder))
       
   809         ? $this->_alternativePartOrder[$type]
       
   810         : (max($this->_alternativePartOrder) + 1);
       
   811     }
       
   812     return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1;
       
   813   }
       
   814   
       
   815   // -- Destructor
       
   816   
       
   817   /**
       
   818    * Empties it's own contents from the cache.
       
   819    */
       
   820   public function __destruct()
       
   821   {
       
   822     $this->_cache->clearAll($this->_cacheKey);
       
   823   }
       
   824   
       
   825   /**
       
   826    * Throws an Exception if the id passed does not comply with RFC 2822.
       
   827    * @param string $id
       
   828    * @throws Swift_RfcComplianceException
       
   829    */
       
   830   private function _assertValidId($id)
       
   831   {
       
   832     if (!preg_match(
       
   833       '/^' . $this->_grammar->getDefinition('id-left') . '@' .
       
   834       $this->_grammar->getDefinition('id-right') . '$/D',
       
   835       $id
       
   836       ))
       
   837     {
       
   838       throw new Swift_RfcComplianceException(
       
   839         'Invalid ID given <' . $id . '>'
       
   840         );
       
   841     }
       
   842   }
       
   843   
       
   844 }