You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
4.7 KiB

3 years ago
  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. $this->filename = $filename;
  35. $this->setName($filename);
  36. $this->setDisposition('attachment');
  37. }
  38. public static function fromPath(string $path, string $name = null, string $contentType = null): self
  39. {
  40. if (null === $contentType) {
  41. $ext = strtolower(substr($path, strrpos($path, '.') + 1));
  42. if (null === self::$mimeTypes) {
  43. self::$mimeTypes = new MimeTypes();
  44. }
  45. $contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
  46. }
  47. if (false === is_readable($path)) {
  48. throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
  49. }
  50. if (false === $handle = @fopen($path, 'r', false)) {
  51. throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
  52. }
  53. $p = new self($handle, $name ?: basename($path), $contentType);
  54. $p->handle = $handle;
  55. return $p;
  56. }
  57. /**
  58. * @return $this
  59. */
  60. public function asInline()
  61. {
  62. return $this->setDisposition('inline');
  63. }
  64. public function getContentId(): string
  65. {
  66. return $this->cid ?: $this->cid = $this->generateContentId();
  67. }
  68. public function hasContentId(): bool
  69. {
  70. return null !== $this->cid;
  71. }
  72. public function getMediaType(): string
  73. {
  74. return $this->mediaType;
  75. }
  76. public function getPreparedHeaders(): Headers
  77. {
  78. $headers = parent::getPreparedHeaders();
  79. if (null !== $this->cid) {
  80. $headers->setHeaderBody('Id', 'Content-ID', $this->cid);
  81. }
  82. if (null !== $this->filename) {
  83. $headers->setHeaderParameter('Content-Disposition', 'filename', $this->filename);
  84. }
  85. return $headers;
  86. }
  87. public function asDebugString(): string
  88. {
  89. $str = parent::asDebugString();
  90. if (null !== $this->filename) {
  91. $str .= ' filename: '.$this->filename;
  92. }
  93. return $str;
  94. }
  95. private function generateContentId(): string
  96. {
  97. return bin2hex(random_bytes(16)).'@symfony';
  98. }
  99. public function __destruct()
  100. {
  101. if (null !== $this->handle && \is_resource($this->handle)) {
  102. fclose($this->handle);
  103. }
  104. }
  105. /**
  106. * @return array
  107. */
  108. public function __sleep()
  109. {
  110. // converts the body to a string
  111. parent::__sleep();
  112. $this->_parent = [];
  113. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  114. $r = new \ReflectionProperty(TextPart::class, $name);
  115. $r->setAccessible(true);
  116. $this->_parent[$name] = $r->getValue($this);
  117. }
  118. $this->_headers = $this->getHeaders();
  119. return ['_headers', '_parent', 'filename', 'mediaType'];
  120. }
  121. public function __wakeup()
  122. {
  123. $r = new \ReflectionProperty(AbstractPart::class, 'headers');
  124. $r->setAccessible(true);
  125. $r->setValue($this, $this->_headers);
  126. unset($this->_headers);
  127. if (!\is_array($this->_parent)) {
  128. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  129. }
  130. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  131. if (null !== $this->_parent[$name] && !\is_string($this->_parent[$name])) {
  132. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  133. }
  134. $r = new \ReflectionProperty(TextPart::class, $name);
  135. $r->setAccessible(true);
  136. $r->setValue($this, $this->_parent[$name]);
  137. }
  138. unset($this->_parent);
  139. }
  140. }