XOOPS RMCommon Utilities  2.1.8.91RC
 All Classes Namespaces Files Functions Variables
SimpleMimeEntity.php
Go to the documentation of this file.
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 //@require 'Swift/Mime/HeaderSet.php';
12 //@require 'Swift/OutputByteStream.php';
13 //@require 'Swift/Mime/ContentEncoder.php';
14 //@require 'Swift/KeyCache.php';
15 
23 {
24 
26  private $_headers;
27 
29  private $_body;
30 
32  private $_encoder;
33 
35  private $_boundary;
36 
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)
42  );
43 
45  private $_compoundLevelFilters = array();
46 
48  private $_nestingLevel = self::LEVEL_ALTERNATIVE;
49 
51  private $_cache;
52 
54  private $_immediateChildren = array();
55 
57  private $_children = array();
58 
60  private $_maxLineLength = 78;
61 
63  private $_alternativePartOrder = array(
64  'text/plain' => 1,
65  'text/html' => 2,
66  'multipart/related' => 3
67  );
68 
70  private $_id;
71 
73  private $_cacheKey;
74 
75  protected $_userContentType;
76 
83  public function __construct(Swift_Mime_HeaderSet $headers,
85  {
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')
92  );
93 
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  // )
106 
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
112  )
113  )
114  );
115 
116  $this->_id = $this->getRandomId();
117  }
118 
123  public function generateId()
124  {
125  $this->setId($this->getRandomId());
126  return $this->_id;
127  }
128 
133  public function getHeaders()
134  {
135  return $this->_headers;
136  }
137 
143  public function getNestingLevel()
144  {
145  return $this->_nestingLevel;
146  }
147 
152  public function getContentType()
153  {
154  return $this->_getHeaderFieldModel('Content-Type');
155  }
156 
161  public function setContentType($type)
162  {
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;
168  }
169 
175  public function getId()
176  {
177  return $this->_headers->has($this->_getIdField())
178  ? current((array) $this->_getHeaderFieldModel($this->_getIdField()))
179  : $this->_id;
180  }
181 
186  public function setId($id)
187  {
188  if (!$this->_setHeaderFieldModel($this->_getIdField(), $id))
189  {
190  $this->_headers->addIdHeader($this->_getIdField(), $id);
191  }
192  $this->_id = $id;
193  return $this;
194  }
195 
201  public function getDescription()
202  {
203  return $this->_getHeaderFieldModel('Content-Description');
204  }
205 
211  public function setDescription($description)
212  {
213  if (!$this->_setHeaderFieldModel('Content-Description', $description))
214  {
215  $this->_headers->addTextHeader('Content-Description', $description);
216  }
217  return $this;
218  }
219 
224  public function getMaxLineLength()
225  {
226  return $this->_maxLineLength;
227  }
228 
234  public function setMaxLineLength($length)
235  {
236  $this->_maxLineLength = $length;
237  return $this;
238  }
239 
244  public function getChildren()
245  {
246  return $this->_children;
247  }
248 
254  public function setChildren(array $children, $compoundLevel = null)
255  {
256  //TODO: Try to refactor this logic
257 
258  $compoundLevel = isset($compoundLevel)
259  ? $compoundLevel
260  : $this->_getCompoundLevel($children)
261  ;
262 
263  $immediateChildren = array();
264  $grandchildren = array();
265  $newContentType = $this->_userContentType;
266 
267  foreach ($children as $child)
268  {
269  $level = $this->_getNeededChildLevel($child, $compoundLevel);
270  if (empty($immediateChildren)) //first iteration
271  {
272  $immediateChildren = array($child);
273  }
274  else
275  {
276  $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
277  if ($nextLevel == $level)
278  {
279  $immediateChildren[] = $child;
280  }
281  elseif ($level < $nextLevel)
282  {
283  //Re-assign immediateChildren to grandchilden
284  $grandchildren = array_merge($grandchildren, $immediateChildren);
285  //Set new children
286  $immediateChildren = array($child);
287  }
288  else
289  {
290  $grandchildren[] = $child;
291  }
292  }
293  }
294 
295  if (!empty($immediateChildren))
296  {
297  $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
298 
299  //Determine which composite media type is needed to accomodate the
300  // immediate children
301  foreach ($this->_compositeRanges as $mediaType => $range)
302  {
303  if ($lowestLevel > $range[0]
304  && $lowestLevel <= $range[1])
305  {
306  $newContentType = $mediaType;
307  break;
308  }
309  }
310 
311  //Put any grandchildren in a subpart
312  if (!empty($grandchildren))
313  {
314  $subentity = $this->_createChild();
315  $subentity->_setNestingLevel($lowestLevel);
316  $subentity->setChildren($grandchildren, $compoundLevel);
317  array_unshift($immediateChildren, $subentity);
318  }
319  }
320 
321  $this->_immediateChildren = $immediateChildren;
322  $this->_children = $children;
323  $this->_setContentTypeInHeaders($newContentType);
324  $this->_fixHeaders();
325  $this->_sortChildren();
326 
327  return $this;
328  }
329 
334  public function getBody()
335  {
336  return ($this->_body instanceof Swift_OutputByteStream)
337  ? $this->_readStream($this->_body)
338  : $this->_body;
339  }
340 
347  public function setBody($body, $contentType = null)
348  {
349  if ($body !== $this->_body)
350  {
351  $this->_clearCache();
352  }
353 
354  $this->_body = $body;
355  if (isset($contentType))
356  {
357  $this->setContentType($contentType);
358  }
359  return $this;
360  }
361 
366  public function getEncoder()
367  {
368  return $this->_encoder;
369  }
370 
375  public function setEncoder(Swift_Mime_ContentEncoder $encoder)
376  {
377  if ($encoder !== $this->_encoder)
378  {
379  $this->_clearCache();
380  }
381 
382  $this->_encoder = $encoder;
383  $this->_setEncoding($encoder->getName());
384  $this->_notifyEncoderChanged($encoder);
385  return $this;
386  }
387 
392  public function getBoundary()
393  {
394  if (!isset($this->_boundary))
395  {
396  $this->_boundary = '_=_swift_v4_' . time() . uniqid() . '_=_';
397  }
398  return $this->_boundary;
399  }
400 
406  public function setBoundary($boundary)
407  {
408  $this->_assertValidBoundary($boundary);
409  $this->_boundary = $boundary;
410  return $this;
411  }
412 
418  public function charsetChanged($charset)
419  {
420  $this->_notifyCharsetChanged($charset);
421  }
422 
428  public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
429  {
430  $this->_notifyEncoderChanged($encoder);
431  }
432 
437  public function toString()
438  {
439  $string = $this->_headers->toString();
440  if (isset($this->_body) && empty($this->_immediateChildren))
441  {
442  if ($this->_cache->hasKey($this->_cacheKey, 'body'))
443  {
444  $body = $this->_cache->getString($this->_cacheKey, 'body');
445  }
446  else
447  {
448  $body = "\r\n" . $this->_encoder->encodeString($this->getBody(), 0,
449  $this->getMaxLineLength()
450  );
451  $this->_cache->setString($this->_cacheKey, 'body', $body,
453  );
454  }
455  $string .= $body;
456  }
457 
458  if (!empty($this->_immediateChildren))
459  {
460  foreach ($this->_immediateChildren as $child)
461  {
462  $string .= "\r\n\r\n--" . $this->getBoundary() . "\r\n";
463  $string .= $child->toString();
464  }
465  $string .= "\r\n\r\n--" . $this->getBoundary() . "--\r\n";
466  }
467 
468  return $string;
469  }
470 
478  public function __toString()
479  {
480  return $this->toString();
481  }
482 
487  public function toByteStream(Swift_InputByteStream $is)
488  {
489  $is->write($this->_headers->toString());
490  $is->commit();
491 
492  if (empty($this->_immediateChildren))
493  {
494  if (isset($this->_body))
495  {
496  if ($this->_cache->hasKey($this->_cacheKey, 'body'))
497  {
498  $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is);
499  }
500  else
501  {
502  $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body');
503  if ($cacheIs)
504  {
505  $is->bind($cacheIs);
506  }
507 
508  $is->write("\r\n");
509 
510  if ($this->_body instanceof Swift_OutputByteStream)
511  {
512  $this->_body->setReadPointer(0);
513 
514  $this->_encoder->encodeByteStream($this->_body, $is, 0,
515  $this->getMaxLineLength()
516  );
517  }
518  else
519  {
520  $is->write($this->_encoder->encodeString(
521  $this->getBody(), 0, $this->getMaxLineLength()
522  ));
523  }
524 
525  if ($cacheIs)
526  {
527  $is->unbind($cacheIs);
528  }
529  }
530  }
531  }
532 
533  if (!empty($this->_immediateChildren))
534  {
535  foreach ($this->_immediateChildren as $child)
536  {
537  $is->write("\r\n\r\n--" . $this->getBoundary() . "\r\n");
538  $child->toByteStream($is);
539  }
540  $is->write("\r\n\r\n--" . $this->getBoundary() . "--\r\n");
541  }
542  }
543 
544  // -- Protected methods
545 
548  protected function _getIdField()
549  {
550  return 'Content-ID';
551  }
552 
556  protected function _getHeaderFieldModel($field)
557  {
558  if ($this->_headers->has($field))
559  {
560  return $this->_headers->get($field)->getFieldBodyModel();
561  }
562  }
563 
567  protected function _setHeaderFieldModel($field, $model)
568  {
569  if ($this->_headers->has($field))
570  {
571  $this->_headers->get($field)->setFieldBodyModel($model);
572  return true;
573  }
574  else
575  {
576  return false;
577  }
578  }
579 
583  protected function _getHeaderParameter($field, $parameter)
584  {
585  if ($this->_headers->has($field))
586  {
587  return $this->_headers->get($field)->getParameter($parameter);
588  }
589  }
590 
594  protected function _setHeaderParameter($field, $parameter, $value)
595  {
596  if ($this->_headers->has($field))
597  {
598  $this->_headers->get($field)->setParameter($parameter, $value);
599  return true;
600  }
601  else
602  {
603  return false;
604  }
605  }
606 
610  protected function _fixHeaders()
611  {
612  if (count($this->_immediateChildren))
613  {
614  $this->_setHeaderParameter('Content-Type', 'boundary',
615  $this->getBoundary()
616  );
617  $this->_headers->remove('Content-Transfer-Encoding');
618  }
619  else
620  {
621  $this->_setHeaderParameter('Content-Type', 'boundary', null);
622  $this->_setEncoding($this->_encoder->getName());
623  }
624  }
625 
629  protected function _getCache()
630  {
631  return $this->_cache;
632  }
633 
637  protected function _clearCache()
638  {
639  $this->_cache->clearKey($this->_cacheKey, 'body');
640  }
641 
646  protected function getRandomId()
647  {
648  $idLeft = time() . '.' . uniqid();
649  $idRight = !empty($_SERVER['SERVER_NAME'])
650  ? $_SERVER['SERVER_NAME']
651  : 'swift.generated';
652  return $idLeft . '@' . $idRight;
653  }
654 
655  // -- Private methods
656 
657  private function _readStream(Swift_OutputByteStream $os)
658  {
659  $string = '';
660  while (false !== $bytes = $os->read(8192))
661  {
662  $string .= $bytes;
663  }
664  return $string;
665  }
666 
667  private function _setEncoding($encoding)
668  {
669  if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding))
670  {
671  $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding);
672  }
673  }
674 
675  private function _assertValidBoundary($boundary)
676  {
677  if (!preg_match(
678  '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di',
679  $boundary))
680  {
681  throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.');
682  }
683  }
684 
685  private function _setContentTypeInHeaders($type)
686  {
687  if (!$this->_setHeaderFieldModel('Content-Type', $type))
688  {
689  $this->_headers->addParameterizedHeader('Content-Type', $type);
690  }
691  }
692 
693  private function _setNestingLevel($level)
694  {
695  $this->_nestingLevel = $level;
696  }
697 
698  private function _getCompoundLevel($children)
699  {
700  $level = 0;
701  foreach ($children as $child)
702  {
703  $level |= $child->getNestingLevel();
704  }
705  return $level;
706  }
707 
708  private function _getNeededChildLevel($child, $compoundLevel)
709  {
710  $filter = array();
711  foreach ($this->_compoundLevelFilters as $bitmask => $rules)
712  {
713  if (($compoundLevel & $bitmask) === $bitmask)
714  {
715  $filter = $rules + $filter;
716  }
717  }
718 
719  $realLevel = $child->getNestingLevel();
720  $lowercaseType = strtolower($child->getContentType());
721 
722  if (isset($filter[$realLevel])
723  && isset($filter[$realLevel][$lowercaseType]))
724  {
725  return $filter[$realLevel][$lowercaseType];
726  }
727  else
728  {
729  return $realLevel;
730  }
731  }
732 
733  private function _createChild()
734  {
735  return new self($this->_headers->newInstance(),
737  }
738 
740  {
741  foreach ($this->_immediateChildren as $child)
742  {
743  $child->encoderChanged($encoder);
744  }
745  }
746 
747  private function _notifyCharsetChanged($charset)
748  {
749  $this->_encoder->charsetChanged($charset);
750  $this->_headers->charsetChanged($charset);
751  foreach ($this->_immediateChildren as $child)
752  {
753  $child->charsetChanged($charset);
754  }
755  }
756 
757  private function _sortChildren()
758  {
759  $shouldSort = false;
760  foreach ($this->_immediateChildren as $child)
761  {
762  //NOTE: This include alternative parts moved into a related part
763  if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE)
764  {
765  $shouldSort = true;
766  break;
767  }
768  }
769 
770  //Sort in order of preference, if there is one
771  if ($shouldSort)
772  {
773  usort($this->_immediateChildren, array($this, '_childSortAlgorithm'));
774  }
775  }
776 
777  private function _childSortAlgorithm($a, $b)
778  {
779  $typePrefs = array();
780  $types = array(
781  strtolower($a->getContentType()),
782  strtolower($b->getContentType())
783  );
784  foreach ($types as $type)
785  {
786  $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder))
787  ? $this->_alternativePartOrder[$type]
788  : (max($this->_alternativePartOrder) + 1);
789  }
790  return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1;
791  }
792 
793  // -- Destructor
794 
798  public function __destruct()
799  {
800  $this->_cache->clearAll($this->_cacheKey);
801  }
802 
803 }