diff --git a/Classes/Command/FlushQueueCommand.php b/Classes/Command/FlushQueueCommand.php new file mode 100644 index 0000000..8441cbd --- /dev/null +++ b/Classes/Command/FlushQueueCommand.php @@ -0,0 +1,192 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\Typo3Mailqueue\Command; + +use CPSIT\Typo3Mailqueue\Mail; +use Symfony\Component\Console; +use TYPO3\CMS\Core; + +/** + * FlushQueueCommand + * + * @author Elias Häußler + * @license GPL-2.0-or-later + */ +final class FlushQueueCommand extends Console\Command\Command +{ + private ?Console\Output\OutputInterface $jsonOutput = null; + private Console\Style\SymfonyStyle $io; + + public function __construct( + private readonly Core\Mail\Mailer $mailer, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this->addOption( + 'json', + 'j', + Console\Input\InputOption::VALUE_NONE, + 'Output results in JSON format', + ); + $this->addOption( + 'limit', + 'l', + Console\Input\InputOption::VALUE_REQUIRED, + 'Maximum number of mails to send in one iteration', + ); + } + + protected function initialize(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): void + { + if ($input->getOption('json')) { + $this->jsonOutput = $output; + + if ($output instanceof Console\Output\ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } else { + $output = new Console\Output\NullOutput(); + } + } + + $this->io = new Console\Style\SymfonyStyle($input, $output); + } + + protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int + { + $limit = $input->getOption('limit'); + $transport = $this->mailer->getTransport(); + + // Early return if unsupported mail transport is configured + if (!($transport instanceof Mail\Transport\QueueableTransport)) { + $message = sprintf( + 'The configured mail transport "%s" is not supported. Please configure a mail spooler as mail transport.', + $transport::class, + ); + + $this->io->error($message); + $this->writeJsonResult([ + 'error' => $message, + ]); + + return self::INVALID; + } + + $mailQueue = $transport->getMailQueue(); + $numberOfMailsInQueue = count($mailQueue); + $realTransport = $this->mailer->getRealTransport(); + + // Early return if mail queue is empty + if ($numberOfMailsInQueue === 0) { + $message = 'No mails are currently in queue.'; + + $this->io->success($message); + $this->writeJsonResult([ + 'result' => $message, + ]); + + return self::SUCCESS; + } + + // Limit number of sent mails from command option or send all mails + if ($limit !== null) { + $limit = (int)$limit; + } else { + $limit = $numberOfMailsInQueue; + } + + // Early return if invalid limit is provided + if ($limit < 1) { + $message = 'Limit must be a number greater than or equal to 1.'; + + $this->io->error($message); + $this->writeJsonResult([ + 'error' => $message, + ]); + + return self::INVALID; + } + + $this->io->section('Flushing mail queue'); + + $progressBar = $this->io->createProgressBar($limit); + + // Send mails from queue + foreach ($progressBar->iterate($mailQueue, $limit) as $i => $item) { + if ($i >= $limit) { + $progressBar->finish(); + break; + } + + $transport->dequeue($item, $realTransport); + + --$numberOfMailsInQueue; + } + + $this->io->newLine(2); + + if ($numberOfMailsInQueue > 0) { + $this->io->success( + sprintf( + 'Successfully sent %d mail%s, %d mail%s still enqueued.', + $limit, + $limit === 1 ? '' : 's', + $numberOfMailsInQueue, + $numberOfMailsInQueue === 1 ? ' is' : 's are', + ), + ); + } else { + $this->io->success( + sprintf( + 'Successfully flushed mail queue (sent %d mail%s).', + $limit, + $limit === 1 ? '' : 's', + ), + ); + } + + $this->writeJsonResult([ + 'sent' => $limit, + 'remaining' => $numberOfMailsInQueue, + ]); + + return self::SUCCESS; + } + + /** + * @param array $json + */ + private function writeJsonResult(array $json): void + { + if ($this->jsonOutput === null) { + return; + } + + $this->jsonOutput->writeln( + json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), + ); + } +} diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index ea7cb99..ddfda2a 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -16,6 +16,13 @@ services: # @todo Replace with #[AsController] attribute once support for TYPO3 v11 is dropped tags: ['backend.controller'] + CPSIT\Typo3Mailqueue\Command\FlushQueueCommand: + # @todo Replace with #[AsCommand] attribute once support for TYPO3 v11 is dropped + tags: + - name: console.command + command: 'mailqueue:flushqueue' + description: 'Flush mail queue by sending mails using the configured real transport' + CPSIT\Typo3Mailqueue\Command\ListQueueCommand: # @todo Replace with #[AsCommand] attribute once support for TYPO3 v11 is dropped tags: diff --git a/Documentation/Images/ScreenshotFlushQueueCommand.png b/Documentation/Images/ScreenshotFlushQueueCommand.png new file mode 100644 index 0000000..3db2535 Binary files /dev/null and b/Documentation/Images/ScreenshotFlushQueueCommand.png differ diff --git a/Documentation/Images/ScreenshotConsoleCommand.png b/Documentation/Images/ScreenshotListQueueCommand.png similarity index 100% rename from Documentation/Images/ScreenshotConsoleCommand.png rename to Documentation/Images/ScreenshotListQueueCommand.png diff --git a/README.md b/README.md index b073dad..23d7d14 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,14 @@ An extension for TYPO3 CMS that extends TYPO3's mail spooling functionality with an extended queueable mail transport interface. In addition, it provides an improved version of TYPO3's `FileSpooler`. In order to make mails in the mail queue visible, the extension provides an (admin-only) backend module and -a console command. +console commands to list and flush the mail queue. ## 🚀 Features * Extended interface for queueable mail transports * Improved queueable file transport with failure metadata * Backend module to list mails in queue -* Console command to list mails in queue +* Console command to list queue and flush mails * Compatible with TYPO3 11.5 LTS and 12.4 LTS ## 🔥 Installation @@ -98,9 +98,27 @@ be used to get a quick overview about the health state of the mail queue. It also allows to dequeue single mails from the mail queue by sending them with the configured real transport. -### Console command +### Console commands -![Screenshot console command](Documentation/Images/ScreenshotConsoleCommand.png) +#### Flush queue + +![Screenshot "mailqueue:flushqueue" console command](Documentation/Images/ScreenshotFlushQueueCommand.png) + +The extension provides a console command to flush the mail queue: + +```bash +typo3 mailqueue:flushqueue [-l|--limit] [-j|--json] +``` + +The number of mails to be sent can be limited with `--limit` (or `-l`). If +no limit is passed, the whole mail queue is flushed. + +When using `--json` (or `-j`), user-oriented output is written to stderr and +result messages are written in JSON format to stdout. + +#### List queue + +![Screenshot "mailqueue:listqueue" console command](Documentation/Images/ScreenshotListQueueCommand.png) The extension provides a console command to list enqueued mails: diff --git a/Tests/Build/console-application.php b/Tests/Build/console-application.php index 63a325c..06b1cf7 100644 --- a/Tests/Build/console-application.php +++ b/Tests/Build/console-application.php @@ -28,14 +28,18 @@ require dirname(__DIR__, 2) . '/.Build/vendor/autoload.php'; -$command = new Command\ListQueueCommand( - new Core\Mail\Mailer( - new Mailer\Transport\NullTransport(), - ), +$mailer = new Core\Mail\Mailer( + new Mailer\Transport\NullTransport(), ); -$command->setName('mailqueue:listqueue'); + +$flushCommand = new Command\FlushQueueCommand($mailer); +$flushCommand->setName('mailqueue:flushqueue'); + +$listCommand = new Command\ListQueueCommand($mailer); +$listCommand->setName('mailqueue:listqueue'); $application = new Console\Application(); -$application->add($command); +$application->add($flushCommand); +$application->add($listCommand); return $application;