PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/email/vendor/swift/classes/Swift/Mime/SimpleMimeEntity.php

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