diff --git a/Classes/Configuration.php b/Classes/Configuration.php new file mode 100644 index 0000000..e62c795 --- /dev/null +++ b/Classes/Configuration.php @@ -0,0 +1,34 @@ +get('sent_mails'); + + $this->rejectBySearchTerms = GeneralUtility::trimExplode('|', $settings['rejectBySearchTerms'] ?? '', true); + $this->mailAPIUsername = $settings['mailAPIUsername'] ?? ''; + $this->mailAPIPassword = $settings['mailAPIPassword'] ?? ''; + } catch (\Exception $e) { + // do nothing + } + } + +} diff --git a/Classes/EventListener/BeforeMailSentEventListener.php b/Classes/EventListener/BeforeMailSentEventListener.php index cad6884..16398f6 100644 --- a/Classes/EventListener/BeforeMailSentEventListener.php +++ b/Classes/EventListener/BeforeMailSentEventListener.php @@ -12,7 +12,6 @@ use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\StringUtility; -use TYPO3\CMS\Extbase\Utility\DebuggerUtility; class BeforeMailSentEventListener { @@ -36,10 +35,8 @@ public function __invoke(BeforeMailerSentMessageEvent $event): void $sentMessage->getHeaders()->addTextHeader('X-SentMail_Custom', $customId); -// $originalMessage = $sentMessage->getOriginalMessage(); $isReply = get_class($originalMessage) === RawMessage::class; -// DebuggerUtility::var_dump($isReply, '$isReply'); -// + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_sentmail_mail'); $connection->insert('tx_sentmail_mail', [ 'crdate' => time(), diff --git a/Classes/EventListener/MailMessageListener.php b/Classes/EventListener/MailMessageListener.php new file mode 100644 index 0000000..9e21e2a --- /dev/null +++ b/Classes/EventListener/MailMessageListener.php @@ -0,0 +1,30 @@ +getMessage(); + + $messageAsString = $message->toString(); + foreach ($this->configuration->rejectBySearchTerms as $searchTerm) { + if (preg_match(sprintf('/%s/i', $searchTerm), $messageAsString)) { + $event->reject(); + return; + } + } + } +} diff --git a/Classes/Middleware/MailInformation.php b/Classes/Middleware/MailInformation.php new file mode 100644 index 0000000..2573435 --- /dev/null +++ b/Classes/Middleware/MailInformation.php @@ -0,0 +1,77 @@ +isValidRequest($request)) { + return $handler->handle($request); + } + + $response = new JsonResponse(); + if (!$this->validateBasicAuth($request)) { + $response = $response->withAddedHeader('WWW-Authenticate', 'Basic realm="Access check"'); + } + + try { + $searchParam = $this->getSearchParam($request); + $response->setPayload($this->mailRepository->getMailsBySearch($searchParam)); + } catch (\Exception $e) { + $response = $response->withStatus(400); + $response->setPayload(['error' => $e->getMessage()]); + } + return $response; + } + + protected function getSearchParam(ServerRequestInterface $request): string + { + $searchParams = trim($request->getQueryParams()['search'] ?? ''); + if (mb_strlen($searchParams) < 10) { + throw new \InvalidArgumentException('Search parameter must be at least 10 characters long', 1627980733); + } + return $searchParams; + } + + protected function isValidRequest(ServerRequestInterface $request): bool + { + if (!$request->getUri()) { + return false; + } + $uri = $request->getUri(); + + return $uri->getPath() === '/api/mailinformation'; + } + + protected function validateBasicAuth(ServerRequestInterface $request): bool + { + $username = $request->getServerParams()['PHP_AUTH_USER'] ?? false; + $password = $request->getServerParams()['PHP_AUTH_PW'] ?? false; + + return + $this->configuration->mailAPIUsername && + $this->configuration->mailAPIPassword && + $username === $this->configuration->mailAPIUsername && + $password === $this->configuration->mailAPIPassword; + } + +} diff --git a/Classes/Repository/MailRepository.php b/Classes/Repository/MailRepository.php index edddff0..69babfa 100644 --- a/Classes/Repository/MailRepository.php +++ b/Classes/Repository/MailRepository.php @@ -3,13 +3,21 @@ namespace StudioMitte\SentMails\Repository; +use StudioMitte\SentMails\Configuration; use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; class MailRepository { + public function __construct( + protected readonly Configuration $configuration + ) + { + } + public function getMails(): array { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_sentmail_mail'); @@ -26,5 +34,20 @@ public function getMailRow(int $id): array return (array)$row; } + public function getMailsBySearch(string $searchTerm): array + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_sentmail_mail'); + return $queryBuilder + ->select('is_sent') + ->addSelectLiteral('count(*) as count') + ->from('tx_sentmail_mail') + ->where( + $queryBuilder->expr()->gte('crdate', $queryBuilder->createNamedParameter((time() - $this->configuration->mailInformationMaxTime ), Connection::PARAM_INT)), + $queryBuilder->expr()->like('message', $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($searchTerm) . '%')) + ) + ->groupBy('is_sent') + ->executeQuery()->fetchAllAssociative(); + } + } diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php new file mode 100644 index 0000000..52c562e --- /dev/null +++ b/Configuration/RequestMiddlewares.php @@ -0,0 +1,14 @@ + [ + 'typo3/theme/logger' => [ + 'target' => \StudioMitte\SentMails\Middleware\MailInformation::class, + 'before' => [ + 'typo3/cms-frontend/timetracker', + ], + 'after' => [ + ], + ], + ], +]; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index a377d94..7b85e60 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -7,14 +7,25 @@ services: StudioMitte\SentMails\: resource: '../Classes/*' + StudioMitte\SentMails\Repository\MailRepository: + public: true + StudioMitte\SentMails\EventListener\BeforeMailSentEventListener: tags: - name: event.listener - identifier: 'mail-sent-beforemailsent' + identifier: 'sent-mails/beforemailsent' event: TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent StudioMitte\SentMails\EventListener\AfterMailSentEventListener: tags: - name: event.listener - identifier: 'mail-sent-aftermailsent' + identifier: 'sent-mails/aftermailsent' event: TYPO3\CMS\Core\Mail\Event\AfterMailerSentMessageEvent + + StudioMitte\SentMails\EventListener\MailMessageListener: + tags: + - name: event.listener + identifier: 'sent-mails/symfony/mailmessage-listener' + event: Symfony\Component\Mailer\Event\MessageEvent + + diff --git a/Readme.md b/Readme.md index 12dfe28..6ab782f 100644 --- a/Readme.md +++ b/Readme.md @@ -1,11 +1,12 @@ # TYPO3 Extension `sent_mails` -This extension provides a simple way to persist **all** sent mails in the database. -Using a dedicated backend module an user can: +This extension provides a simple way to persist **all** sent mails in the database. +Using a dedicated backend module a user can: * view the mail including plain & HTML view * resend the mail * forward the mail to a different email address +* reject mails with a specific content ## Installation @@ -15,4 +16,7 @@ This extension requires the usage of composer! composer req studiomitte/sent-mails ``` +## Mail-Information API + +Calling `/api/mailinformation?search=somecontent` will return a status information about the sent mails. Basic auth needs to be configured in the extension settings. diff --git a/ext_conf_template.txt b/ext_conf_template.txt new file mode 100644 index 0000000..e4ebf85 --- /dev/null +++ b/ext_conf_template.txt @@ -0,0 +1,8 @@ +# cat=Feature; type=string; label=Search terms leading to rejection:List of search terms (separated by ###) which lead to rejecting the mail sending process +rejectBySearchTerms = + +# cat=Feature; type=string; label=Mail API Username:Provide the username for the mail API +mailAPIUsername = + +# cat=Feature; type=string; label=Mail API Password:Provide the password for the mail API +mailAPIPassword =