33 private const PRIORITY_MAP = [
34 self::PRIORITY_HIGHEST =>
'Highest',
35 self::PRIORITY_HIGH =>
'High',
36 self::PRIORITY_NORMAL =>
'Normal',
37 self::PRIORITY_LOW =>
'Low',
38 self::PRIORITY_LOWEST =>
'Lowest',
45 private $attachments = [];
52 return $this->setHeaderBody(
'Text',
'Subject', $subject);
57 return $this->
getHeaders()->getHeaderBody(
'Subject');
63 public function date(\DateTimeInterface $dateTime)
65 return $this->setHeaderBody(
'Date',
'Date', $dateTime);
68 public function getDate(): ?\DateTimeImmutable
70 return $this->
getHeaders()->getHeaderBody(
'Date');
80 return $this->setHeaderBody(
'Path',
'Return-Path',
Address::create($address));
85 return $this->
getHeaders()->getHeaderBody(
'Return-Path');
95 return $this->setHeaderBody(
'Mailbox',
'Sender',
Address::create($address));
100 return $this->
getHeaders()->getHeaderBody(
'Sender');
110 return $this->addListAddressHeaderBody(
'From', $addresses);
118 public function from(...$addresses)
120 return $this->setListAddressHeaderBody(
'From', $addresses);
128 return $this->
getHeaders()->getHeaderBody(
'From') ?: [];
138 return $this->addListAddressHeaderBody(
'Reply-To', $addresses);
148 return $this->setListAddressHeaderBody(
'Reply-To', $addresses);
156 return $this->
getHeaders()->getHeaderBody(
'Reply-To') ?: [];
164 public function addTo(...$addresses)
166 return $this->addListAddressHeaderBody(
'To', $addresses);
174 public function to(...$addresses)
176 return $this->setListAddressHeaderBody(
'To', $addresses);
184 return $this->
getHeaders()->getHeaderBody(
'To') ?: [];
192 public function addCc(...$addresses)
194 return $this->addListAddressHeaderBody(
'Cc', $addresses);
202 public function cc(...$addresses)
204 return $this->setListAddressHeaderBody(
'Cc', $addresses);
212 return $this->
getHeaders()->getHeaderBody(
'Cc') ?: [];
222 return $this->addListAddressHeaderBody(
'Bcc', $addresses);
230 public function bcc(...$addresses)
232 return $this->setListAddressHeaderBody(
'Bcc', $addresses);
240 return $this->
getHeaders()->getHeaderBody(
'Bcc') ?: [];
254 } elseif ($priority < 1) {
258 return $this->setHeaderBody(
'Text',
'X-Priority', sprintf(
'%d (%s)', $priority, self::PRIORITY_MAP[$priority]));
269 list($priority) = sscanf($this->
getHeaders()->getHeaderBody(
'X-Priority'),
'%[1-5]');
271 return $priority ?? 3;
279 public function text($body,
string $charset =
'utf-8')
282 $this->textCharset = $charset;
297 return $this->textCharset;
305 public function html($body,
string $charset =
'utf-8')
308 $this->htmlCharset = $charset;
323 return $this->htmlCharset;
331 public function attach($body,
string $name =
null,
string $contentType =
null)
333 $this->attachments[] = [
'body' => $body,
'name' => $name,
'content-type' => $contentType,
'inline' =>
false];
341 public function attachFromPath(
string $path,
string $name =
null,
string $contentType =
null)
343 $this->attachments[] = [
'path' => $path,
'name' => $name,
'content-type' => $contentType,
'inline' =>
false];
353 public function embed($body,
string $name =
null,
string $contentType =
null)
355 $this->attachments[] = [
'body' => $body,
'name' => $name,
'content-type' => $contentType,
'inline' =>
true];
363 public function embedFromPath(
string $path,
string $name =
null,
string $contentType =
null)
365 $this->attachments[] = [
'path' => $path,
'name' => $name,
'content-type' => $contentType,
'inline' =>
true];
375 $this->attachments[] = [
'part' => $part];
386 foreach ($this->attachments as $attachment) {
387 $parts[] = $this->createDataPart($attachment);
395 if (
null !== $body = parent::getBody()) {
399 return $this->generateBody();
404 if (
null === $this->
text &&
null === $this->
html && !$this->attachments) {
405 throw new LogicException(
'A message must have a text or an HTML part or attachments.');
408 parent::ensureValidity();
435 [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts();
437 $part =
null === $this->
text ? null :
new TextPart($this->
text, $this->textCharset);
438 if (
null !== $htmlPart) {
439 if (
null !== $part) {
447 $part =
new RelatedPart($part, ...$inlineParts);
450 if ($attachmentParts) {
452 $part =
new MixedPart($part, ...$attachmentParts);
454 $part =
new MixedPart(...$attachmentParts);
461 private function prepareParts(): ?array
466 if (
null !== $this->
html) {
467 if (\is_resource($html)) {
468 if (stream_get_meta_data($html)[
'seekable'] ??
false) {
472 $html = stream_get_contents($html);
474 $htmlPart =
new TextPart($html, $this->htmlCharset,
'html');
475 preg_match_all(
'(<img\s+[^>]*src\s*=\s*(?:([\'"])cid:([^"]+)\\1|cid:([^>\s]+)))i', $html, $names);
476 $names = array_filter(array_unique(array_merge($names[2], $names[3])));
479 $attachmentParts = $inlineParts = [];
480 foreach ($this->attachments as $attachment) {
481 foreach ($names as $name) {
482 if (isset($attachment[
'part'])) {
485 if ($name !== $attachment[
'name']) {
488 if (isset($inlineParts[$name])) {
491 $attachment[
'inline'] =
true;
492 $inlineParts[$name] = $part = $this->createDataPart($attachment);
493 $html = str_replace(
'cid:'.$name,
'cid:'.$part->getContentId(), $html);
496 $attachmentParts[] = $this->createDataPart($attachment);
498 if (
null !== $htmlPart) {
499 $htmlPart =
new TextPart($html, $this->htmlCharset,
'html');
502 return [$htmlPart, $attachmentParts, array_values($inlineParts)];
505 private function createDataPart(array $attachment): DataPart
507 if (isset($attachment[
'part'])) {
508 return $attachment[
'part'];
511 if (isset($attachment[
'body'])) {
512 $part =
new DataPart($attachment[
'body'], $attachment[
'name'] ??
null, $attachment[
'content-type'] ??
null);
514 $part =
DataPart::fromPath($attachment[
'path'] ??
'', $attachment[
'name'] ??
null, $attachment[
'content-type'] ??
null);
516 if ($attachment[
'inline']) {
526 private function setHeaderBody(
string $type,
string $name, $body)
528 $this->
getHeaders()->setHeaderBody($type, $name, $body);
533 private function addListAddressHeaderBody(
string $name, array $addresses)
535 if (!$header = $this->
getHeaders()->
get($name)) {
536 return $this->setListAddressHeaderBody($name, $addresses);
543 private function setListAddressHeaderBody(
string $name, array $addresses)
547 if ($header = $headers->get($name)) {
548 $header->setAddresses($addresses);
550 $headers->addMailboxListHeader($name, $addresses);
561 if (\is_resource($this->
text)) {
562 if (stream_get_meta_data($this->
text)[
'seekable'] ??
false) {
566 $this->
text = stream_get_contents($this->
text);
569 if (\is_resource($this->
html)) {
570 if (stream_get_meta_data($this->
html)[
'seekable'] ??
false) {
574 $this->
html = stream_get_contents($this->
html);
577 foreach ($this->attachments as $i => $attachment) {
578 if (isset($attachment[
'body']) && \is_resource($attachment[
'body'])) {
579 if (stream_get_meta_data($attachment[
'body'])[
'seekable'] ??
false) {
580 rewind($attachment[
'body']);
583 $this->attachments[$i][
'body'] = stream_get_contents($attachment[
'body']);
587 return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
595 [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data;
597 parent::__unserialize($parentData);