DataPart.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. ];
  117. }
  118. trigger_deprecation('symfony/mime', '7.4', 'Implementing "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
  119. $data = [];
  120. foreach ($this->__sleep() as $key) {
  121. try {
  122. if (($r = new \ReflectionProperty($this, $key))->isInitialized($this)) {
  123. $data[$key] = $r->getValue($this);
  124. }
  125. } catch (\ReflectionException) {
  126. $data[$key] = $this->$key;
  127. }
  128. }
  129. return $data;
  130. }
  131. public function __unserialize(array $data): void
  132. {
  133. if ($wakeup = self::class !== (new \ReflectionMethod($this, '__wakeup'))->class && self::class === (new \ReflectionMethod($this, '__unserialize'))->class) {
  134. trigger_deprecation('symfony/mime', '7.4', 'Implementing "%s::__wakeup()" is deprecated, use "__unserialize()" instead.', get_debug_type($this));
  135. }
  136. if (['_headers', '_parent', 'filename', 'mediaType'] === array_keys($data)) {
  137. parent::__unserialize(['_headers' => $data['_headers'], ...$data['_parent']]);
  138. $this->filename = $data['filename'];
  139. $this->mediaType = $data['mediaType'];
  140. if ($wakeup) {
  141. $this->__wakeup();
  142. }
  143. return;
  144. }
  145. if (["\0*\0_headers", "\0*\0_parent", "\0".self::class."\0filename", "\0".self::class."\0mediaType"] === array_keys($data)) {
  146. parent::__unserialize(['_headers' => $data["\0*\0_headers"], ...$data["\0*\0_parent"]]);
  147. $this->filename = $data["\0".self::class."\0filename"];
  148. $this->mediaType = $data["\0".self::class."\0mediaType"];
  149. if ($wakeup) {
  150. $this->__wakeup();
  151. }
  152. return;
  153. }
  154. 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));
  155. \Closure::bind(function ($data) use ($wakeup) {
  156. foreach ($data as $key => $value) {
  157. $this->{("\0" === $key[0] ?? '') ? substr($key, 1 + strrpos($key, "\0")) : $key} = $value;
  158. }
  159. if ($wakeup) {
  160. $this->__wakeup();
  161. }
  162. }, $this, static::class)($data);
  163. }
  164. /**
  165. * @deprecated since Symfony 7.4, will be replaced by `__serialize()` in 8.0
  166. */
  167. public function __sleep(): array
  168. {
  169. trigger_deprecation('symfony/mime', '7.4', 'Calling "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this));
  170. // converts the body to a string
  171. parent::__sleep();
  172. $this->_parent = [];
  173. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  174. $r = new \ReflectionProperty(TextPart::class, $name);
  175. $this->_parent[$name] = $r->getValue($this);
  176. }
  177. $this->_headers = $this->getHeaders();
  178. return ['_headers', '_parent', 'filename', 'mediaType'];
  179. }
  180. /**
  181. * @deprecated since Symfony 7.4, will be replaced by `__unserialize()` in 8.0
  182. */
  183. public function __wakeup(): void
  184. {
  185. $r = new \ReflectionProperty(AbstractPart::class, 'headers');
  186. $r->setValue($this, $this->_headers);
  187. unset($this->_headers);
  188. if (!\is_array($this->_parent)) {
  189. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  190. }
  191. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  192. if (null !== $this->_parent[$name] && !\is_string($this->_parent[$name]) && !$this->_parent[$name] instanceof File) {
  193. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  194. }
  195. $r = new \ReflectionProperty(TextPart::class, $name);
  196. $r->setValue($this, $this->_parent[$name]);
  197. }
  198. unset($this->_parent);
  199. }
  200. }