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