DataPart.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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. list($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. // FIXME: if file is not readable, exception?
  41. if (null === $contentType) {
  42. $ext = strtolower(substr($path, strrpos($path, '.') + 1));
  43. if (null === self::$mimeTypes) {
  44. self::$mimeTypes = new MimeTypes();
  45. }
  46. $contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
  47. }
  48. if (false === $handle = @fopen($path, 'r', false)) {
  49. throw new InvalidArgumentException(sprintf('Unable to open path "%s"', $path));
  50. }
  51. $p = new self($handle, $name ?: basename($path), $contentType);
  52. $p->handle = $handle;
  53. return $p;
  54. }
  55. /**
  56. * @return $this
  57. */
  58. public function asInline()
  59. {
  60. return $this->setDisposition('inline');
  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. private function generateContentId(): string
  94. {
  95. return bin2hex(random_bytes(16)).'@symfony';
  96. }
  97. public function __destruct()
  98. {
  99. if (null !== $this->handle && \is_resource($this->handle)) {
  100. fclose($this->handle);
  101. }
  102. }
  103. /**
  104. * @return array
  105. */
  106. public function __sleep()
  107. {
  108. // converts the body to a string
  109. parent::__sleep();
  110. $this->_parent = [];
  111. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  112. $r = new \ReflectionProperty(TextPart::class, $name);
  113. $r->setAccessible(true);
  114. $this->_parent[$name] = $r->getValue($this);
  115. }
  116. $this->_headers = $this->getHeaders();
  117. return ['_headers', '_parent', 'filename', 'mediaType'];
  118. }
  119. public function __wakeup()
  120. {
  121. $r = new \ReflectionProperty(AbstractPart::class, 'headers');
  122. $r->setAccessible(true);
  123. $r->setValue($this, $this->_headers);
  124. unset($this->_headers);
  125. foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
  126. $r = new \ReflectionProperty(TextPart::class, $name);
  127. $r->setAccessible(true);
  128. $r->setValue($this, $this->_parent[$name]);
  129. }
  130. unset($this->_parent);
  131. }
  132. }