DataPart.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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. /**
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. */
  16. class DataPart extends TextPart
  17. {
  18. /** @internal, to be removed in 8.0 */
  19. protected array $_parent;
  20. private ?string $filename = null;
  21. private string $mediaType;
  22. private ?string $cid = null;
  23. /**
  24. * @param resource|string|File $body Use a File instance to defer loading the file until rendering
  25. */
  26. public function __construct($body, ?string $filename = null, ?string $contentType = null, ?string $encoding = null)
  27. {
  28. if ($body instanceof File && !$filename) {
  29. $filename = $body->getFilename();
  30. }
  31. $contentType ??= $body instanceof File ? $body->getContentType() : 'application/octet-stream';
  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. return new self(new File($path), $name, $contentType);
  43. }
  44. /**
  45. * @return $this
  46. */
  47. public function asInline(): static
  48. {
  49. return $this->setDisposition('inline');
  50. }
  51. /**
  52. * @return $this
  53. */
  54. public function setContentId(string $cid): static
  55. {
  56. if (!str_contains($cid, '@')) {
  57. throw new InvalidArgumentException(\sprintf('The "%s" CID is invalid as it doesn\'t contain an "@".', $cid));
  58. }
  59. $this->cid = $cid;
  60. return $this;
  61. }
  62. public function getContentId(): string
  63. {
  64. return $this->cid ?: $this->cid = $this->generateContentId();
  65. }
  66. public function hasContentId(): bool
  67. {
  68. return null !== $this->cid;
  69. }
  70. public function getMediaType(): string
  71. {
  72. return $this->mediaType;
  73. }
  74. public function getPreparedHeaders(): Headers
  75. {
  76. $headers = parent::getPreparedHeaders();
  77. if (null !== $this->cid) {
  78. $headers->setHeaderBody('Id', 'Content-ID', $this->cid);
  79. }
  80. if (null !== $this->filename) {
  81. $headers->setHeaderParameter('Content-Disposition', 'filename', $this->filename);
  82. }
  83. return $headers;
  84. }
  85. public function asDebugString(): string
  86. {
  87. $str = parent::asDebugString();
  88. if (null !== $this->filename) {
  89. $str .= ' filename: '.$this->filename;
  90. }
  91. return $str;
  92. }
  93. public function getFilename(): ?string
  94. {
  95. return $this->filename;
  96. }
  97. public function getContentType(): string
  98. {
  99. return implode('/', [$this->getMediaType(), $this->getMediaSubtype()]);
  100. }
  101. private function generateContentId(): string
  102. {
  103. return bin2hex(random_bytes(16)).'@symfony';
  104. }
  105. public function __serialize(): array
  106. {
  107. if (self::class === (new \ReflectionMethod($this, '__sleep'))->class || self::class !== (new \ReflectionMethod($this, '__serialize'))->class) {
  108. $parent = parent::__serialize();
  109. $headers = $parent['_headers'];
  110. unset($parent['_headers']);
  111. return [
  112. '_headers' => $headers,
  113. '_parent' => $parent,
  114. 'filename' => $this->filename,
  115. 'mediaType' => $this->mediaType,
  116. 'cid' => $this->cid,
  117. ];
  118. }
  119. trigger_deprecation('symfony/mime', '7.4', 'Implementing "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
  120. $data = [];
  121. foreach ($this->__sleep() as $key) {
  122. try {
  123. if (($r = new \ReflectionProperty($this, $key))->isInitialized($this)) {
  124. $data[$key] = $r->getValue($this);
  125. }
  126. } catch (\ReflectionException) {
  127. $data[$key] = $this->$key;
  128. }
  129. }
  130. return $data;
  131. }
  132. public function __unserialize(array $data): void
  133. {
  134. if ($wakeup = self::class !== (new \ReflectionMethod($this, '__wakeup'))->class && self::class === (new \ReflectionMethod($this, '__unserialize'))->class) {
  135. trigger_deprecation('symfony/mime', '7.4', 'Implementing "%s::__wakeup()" is deprecated, use "__unserialize()" instead.', get_debug_type($this));
  136. }
  137. if (['_headers', '_parent', 'filename', 'mediaType'] === array_keys($data) || ['_headers', '_parent', 'filename', 'mediaType', 'cid'] === array_keys($data)) {
  138. parent::__unserialize(['_headers' => $data['_headers'], ...$data['_parent']]);
  139. $this->filename = $data['filename'];
  140. $this->mediaType = $data['mediaType'];
  141. $this->cid = $data['cid'] ?? null;
  142. if ($wakeup) {
  143. $this->__wakeup();
  144. }
  145. return;
  146. }
  147. if (["\0*\0_headers", "\0*\0_parent", "\0".self::class."\0filename", "\0".self::class."\0mediaType"] === array_keys($data)) {
  148. parent::__unserialize(['_headers' => $data["\0*\0_headers"], ...$data["\0*\0_parent"]]);
  149. $this->filename = $data["\0".self::class."\0filename"];
  150. $this->mediaType = $data["\0".self::class."\0mediaType"];
  151. if ($wakeup) {
  152. $this->__wakeup();
  153. }
  154. return;
  155. }
  156. trigger_deprecation('symfony/mime', '7.4', 'Passing extra keys to "%s::__unserialize()" is deprecated, populate properties in "%s::__unserialize()" instead.', self::class, get_debug_type($this));
  157. \Closure::bind(function ($data) use ($wakeup) {
  158. foreach ($data as $key => $value) {
  159. $this->{("\0" === $key[0] ?? '') ? substr($key, 1 + strrpos($key, "\0")) : $key} = $value;
  160. }
  161. if ($wakeup) {
  162. $this->__wakeup();
  163. }
  164. }, $this, static::class)($data);
  165. }
  166. /**
  167. * @deprecated since Symfony 7.4, will be replaced by `__serialize()` in 8.0
  168. */
  169. public function __sleep(): array
  170. {
  171. trigger_deprecation('symfony/mime', '7.4', 'Calling "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
  172. // converts the body to a string
  173. parent::__sleep();
  174. $this->_parent = [];
  175. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  176. $r = new \ReflectionProperty(TextPart::class, $name);
  177. $this->_parent[$name] = $r->getValue($this);
  178. }
  179. $this->_headers = $this->getHeaders();
  180. return ['_headers', '_parent', 'filename', 'mediaType', 'cid'];
  181. }
  182. /**
  183. * @deprecated since Symfony 7.4, will be replaced by `__unserialize()` in 8.0
  184. */
  185. public function __wakeup(): void
  186. {
  187. $r = new \ReflectionProperty(AbstractPart::class, 'headers');
  188. $r->setValue($this, $this->_headers);
  189. unset($this->_headers);
  190. if (!\is_array($this->_parent)) {
  191. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  192. }
  193. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  194. if (null !== $this->_parent[$name] && !\is_string($this->_parent[$name]) && !$this->_parent[$name] instanceof File) {
  195. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  196. }
  197. $r = new \ReflectionProperty(TextPart::class, $name);
  198. $r->setValue($this, $this->_parent[$name]);
  199. }
  200. unset($this->_parent);
  201. }
  202. }