DataPart.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Mime\Part;
  11. use Symfony\Component\Mime\Exception\InvalidArgumentException;
  12. use Symfony\Component\Mime\Header\Headers;
  13. use Symfony\Component\Mime\MimeTypes;
  14. /**
  15. * @author Fabien Potencier <fabien@symfony.com>
  16. */
  17. class DataPart extends TextPart
  18. {
  19. private static $mimeTypes;
  20. private $filename;
  21. private $mediaType;
  22. private $cid;
  23. private $handle;
  24. /**
  25. * @param resource|string $body
  26. */
  27. public function __construct($body, string $filename = null, string $contentType = null, string $encoding = null)
  28. {
  29. if (null === $contentType) {
  30. $contentType = 'application/octet-stream';
  31. }
  32. [$this->mediaType, $subtype] = explode('/', $contentType);
  33. parent::__construct($body, null, $subtype, $encoding);
  34. if (null !== $filename) {
  35. $this->filename = $filename;
  36. $this->setName($filename);
  37. }
  38. $this->setDisposition('attachment');
  39. }
  40. public static function fromPath(string $path, string $name = null, string $contentType = null): self
  41. {
  42. if (null === $contentType) {
  43. $ext = strtolower(substr($path, strrpos($path, '.') + 1));
  44. if (null === self::$mimeTypes) {
  45. self::$mimeTypes = new MimeTypes();
  46. }
  47. $contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
  48. }
  49. if (false === is_readable($path)) {
  50. throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
  51. }
  52. if (false === $handle = @fopen($path, 'r', false)) {
  53. throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
  54. }
  55. $p = new self($handle, $name ?: basename($path), $contentType);
  56. $p->handle = $handle;
  57. return $p;
  58. }
  59. /**
  60. * @return $this
  61. */
  62. public function asInline()
  63. {
  64. return $this->setDisposition('inline');
  65. }
  66. public function getContentId(): string
  67. {
  68. return $this->cid ?: $this->cid = $this->generateContentId();
  69. }
  70. public function hasContentId(): bool
  71. {
  72. return null !== $this->cid;
  73. }
  74. public function getMediaType(): string
  75. {
  76. return $this->mediaType;
  77. }
  78. public function getPreparedHeaders(): Headers
  79. {
  80. $headers = parent::getPreparedHeaders();
  81. if (null !== $this->cid) {
  82. $headers->setHeaderBody('Id', 'Content-ID', $this->cid);
  83. }
  84. if (null !== $this->filename) {
  85. $headers->setHeaderParameter('Content-Disposition', 'filename', $this->filename);
  86. }
  87. return $headers;
  88. }
  89. public function asDebugString(): string
  90. {
  91. $str = parent::asDebugString();
  92. if (null !== $this->filename) {
  93. $str .= ' filename: '.$this->filename;
  94. }
  95. return $str;
  96. }
  97. private function generateContentId(): string
  98. {
  99. return bin2hex(random_bytes(16)).'@symfony';
  100. }
  101. public function __destruct()
  102. {
  103. if (null !== $this->handle && \is_resource($this->handle)) {
  104. fclose($this->handle);
  105. }
  106. }
  107. /**
  108. * @return array
  109. */
  110. public function __sleep()
  111. {
  112. // converts the body to a string
  113. parent::__sleep();
  114. $this->_parent = [];
  115. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  116. $r = new \ReflectionProperty(TextPart::class, $name);
  117. $r->setAccessible(true);
  118. $this->_parent[$name] = $r->getValue($this);
  119. }
  120. $this->_headers = $this->getHeaders();
  121. return ['_headers', '_parent', 'filename', 'mediaType'];
  122. }
  123. public function __wakeup()
  124. {
  125. $r = new \ReflectionProperty(AbstractPart::class, 'headers');
  126. $r->setAccessible(true);
  127. $r->setValue($this, $this->_headers);
  128. unset($this->_headers);
  129. if (!\is_array($this->_parent)) {
  130. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  131. }
  132. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  133. if (null !== $this->_parent[$name] && !\is_string($this->_parent[$name])) {
  134. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  135. }
  136. $r = new \ReflectionProperty(TextPart::class, $name);
  137. $r->setAccessible(true);
  138. $r->setValue($this, $this->_parent[$name]);
  139. }
  140. unset($this->_parent);
  141. }
  142. }