1<?php 2 3declare(strict_types=1); 4 5/* 6 * This file is part of the TYPO3 CMS project. 7 * 8 * It is free software; you can redistribute it and/or modify it under 9 * the terms of the GNU General Public License, either version 2 10 * of the License, or any later version. 11 * 12 * For the full copyright and license information, please read the 13 * LICENSE.txt file that was distributed with this source code. 14 * 15 * The TYPO3 project - inspiring people to share! 16 */ 17 18namespace TYPO3\CMS\Core\Mail; 19 20use Psr\Log\LoggerInterface; 21use Symfony\Component\Mailer\Exception\TransportExceptionInterface; 22use Symfony\Component\Mailer\SentMessage; 23use Symfony\Component\Mailer\Transport\AbstractTransport; 24use Symfony\Component\Mailer\Transport\TransportInterface; 25use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; 26use TYPO3\CMS\Core\Security\BlockSerializationTrait; 27use TYPO3\CMS\Core\SingletonInterface; 28use TYPO3\CMS\Core\Utility\GeneralUtility; 29 30/** 31 * Because TYPO3 doesn't offer a terminate signal or hook, 32 * and taking in account the risk that extensions do some redirects or even exit, 33 * we simply use the destructor of a singleton class which should be pretty much 34 * at the end of a request. 35 * 36 * To have only one memory spool per request seems to be more appropriate anyway. 37 * 38 * @internal This class is experimental and subject to change! 39 */ 40class MemorySpool extends AbstractTransport implements SingletonInterface, DelayedTransportInterface 41{ 42 use BlockSerializationTrait; 43 44 /** 45 * The logger instance. 46 * 47 * @var LoggerInterface 48 */ 49 protected $logger; 50 51 /** 52 * @var SentMessage[] 53 */ 54 protected $queuedMessages = []; 55 56 /** 57 * Maximum number of retries when the real transport has failed. 58 * 59 * @var int 60 */ 61 protected $retries = 3; 62 63 /** 64 * Create a new MemorySpool 65 * 66 * @param EventDispatcherInterface $dispatcher 67 * @param LoggerInterface $logger 68 */ 69 public function __construct( 70 EventDispatcherInterface $dispatcher = null, 71 LoggerInterface $logger = null 72 ) { 73 parent::__construct($dispatcher, $logger); 74 75 $this->logger = $logger; 76 77 $this->setMaxPerSecond(0); 78 } 79 80 /** 81 * Sends out the messages in the memory 82 */ 83 public function __destruct() 84 { 85 $mailer = GeneralUtility::makeInstance(Mailer::class); 86 try { 87 $this->flushQueue($mailer->getRealTransport()); 88 } catch (TransportExceptionInterface $exception) { 89 $this->logger->error('An Exception occurred while flushing email queue: ' . $exception->getMessage()); 90 } 91 } 92 93 /** 94 * @inheritdoc 95 */ 96 public function flushQueue(TransportInterface $transport): int 97 { 98 if ($this->queuedMessages === []) { 99 return 0; 100 } 101 102 $retries = $this->retries; 103 $message = null; 104 $count = 0; 105 while ($retries--) { 106 try { 107 while ($message = array_pop($this->queuedMessages)) { 108 $transport->send($message->getMessage(), $message->getEnvelope()); 109 $count++; 110 } 111 } catch (TransportExceptionInterface $exception) { 112 if ($retries && $message) { 113 // re-queue the message at the end of the queue to give a chance 114 // to the other messages to be sent, in case the failure was due to 115 // this message and not just the transport failing 116 array_unshift($this->queuedMessages, $message); 117 118 // wait half a second before we try again 119 usleep(500000); 120 } else { 121 throw $exception; 122 } 123 } 124 } 125 return $count; 126 } 127 128 /** 129 * Stores a message in the queue. 130 * @param SentMessage $message 131 */ 132 protected function doSend(SentMessage $message): void 133 { 134 $this->queuedMessages[] = $message; 135 } 136 137 public function __toString(): string 138 { 139 return 'MemorySpool'; 140 } 141} 142