Skip to content

Commit

Permalink
Add config for blacklist split to services (#5)
Browse files Browse the repository at this point in the history
* add blacklist functionality

* use interface instead of model
remove phpdoc
invert condition

* fix codestyle

* splitting validation by blacklist and log email to separate services

* remove unnecessary try catch from regexp by pattern
move call $proceed to service from plugin
  • Loading branch information
MarsArt committed Jul 17, 2024
1 parent 80213a2 commit 5389404
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 68 deletions.
76 changes: 10 additions & 66 deletions Mail/Transport.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,26 @@
namespace Mygento\Smtp\Mail;

use Closure;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\MailException;
use Magento\Framework\Mail\Address;
use Magento\Framework\Mail\EmailMessageInterface;
use Magento\Framework\Mail\TransportInterface;
use Mygento\Smtp\Api\Data;
use Mygento\Smtp\Api\LogRepositoryInterface;
use Mygento\Smtp\Model\Config;
use Mygento\Smtp\Model\Source\Status;
use Psr\Log\LoggerInterface;
use Mygento\Smtp\Model\Mail\Processor;
use Mygento\Smtp\Model\Mail\Validator;

class Transport
{
public function __construct(
private LogRepositoryInterface $repo,
private Data\LogInterfaceFactory $factory,
private Processor $mailProcessor,
private Validator $blackListValidator,
private Config $config,
private LoggerInterface $logger
) {
}

/**
* @param TransportInterface $subject
* @param Closure $proceed
* @throws MailException
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
Expand All @@ -43,65 +39,13 @@ public function aroundSendMessage(TransportInterface $subject, Closure $proceed)

return;
}
/** @var EmailMessageInterface $message */
$message = $subject->getMessage();

/** @var Data\LogInterface $entity */
$entity = $this->factory->create();

try {
$this->fillEntity($entity, $subject->getMessage());
$proceed();

$entity->setStatus(Status::STATUS_SUCCESS);
$this->repo->save($entity);
} catch (MailException $e) {
$entity->setError($e->getPrevious()->getMessage());
$entity->setStatus(Status::STATUS_ERROR);

throw $e;
} catch (LocalizedException $e) {
$entity->setStatus(Status::STATUS_ERROR);
$entity->setError($e->getMessage());
$this->logger->error($e->getMessage(), ['exception' => $e]);
} finally {
$this->repo->save($entity);
}
}

private function formatList(iterable $list): string
{
return implode(
',',
array_map(fn (Address $address) => $address->getEmail(), $list)
);
}

private function fillEntity(Data\LogInterface $entity, EmailMessageInterface $message): Data\LogInterface
{
if ($message->getSender()) {
$entity->setSender(
$message->getSender()->getName() . ' <' . $message->getSender()->getEmail() . '>'
);
} elseif (is_iterable($message->getFrom()) && count($message->getFrom()) > 0) {
$f = $message->getFrom();
$from = reset($f);
$entity->setSender(
$from->getName() . ' <' . $from->getEmail() . '>'
);
}

$entity->setRecipient($this->formatList($message->getTo()));
$entity->setSubject($message->getSubject());
$messageBody = quoted_printable_decode($message->getBodyText());
$content = htmlspecialchars($messageBody);
$entity->setContent($content);

if (is_iterable($message->getCc()) && count($message->getCc()) > 0) {
$entity->setCc($this->formatList($message->getCc()));
}
if (is_iterable($message->getBcc()) && count($message->getBcc()) > 0) {
$entity->setBcc($this->formatList($message->getBcc()));
if (!$this->blackListValidator->isValid($message)) {
return;
}

return $entity;
$this->mailProcessor->process($proceed, $message);
}
}
9 changes: 9 additions & 0 deletions Model/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Config
{
private const XML_PATH_EMAIL_LOG = 'system/smtp/log';
private const XML_PATH_CLEAN_EMAIL_PERIOD = 'system/smtp/clean_email_period';
private const XML_PATH_BLACKLIST = 'system/smtp/blacklist';

public function __construct(
private ScopeConfigInterface $scopeConfig
Expand All @@ -35,4 +36,12 @@ public function getCleanEmailPeriod(): ?string
{
return $this->scopeConfig->getValue(self::XML_PATH_CLEAN_EMAIL_PERIOD);
}

/**
* @return string|null
*/
public function getBlacklist(): ?string
{
return $this->scopeConfig->getValue(self::XML_PATH_BLACKLIST);
}
}
90 changes: 90 additions & 0 deletions Model/Mail/Processor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

/**
* @author Mygento Team
* @copyright 2023 Mygento (https://www.mygento.com)
* @package Mygento_Smtp
*/

namespace Mygento\Smtp\Model\Mail;

use Closure;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\MailException;
use Magento\Framework\Mail\Address;
use Magento\Framework\Mail\EmailMessageInterface;
use Mygento\Smtp\Api\Data;
use Mygento\Smtp\Api\LogRepositoryInterface;
use Mygento\Smtp\Model\Source\Status;
use Psr\Log\LoggerInterface;

class Processor
{
public function __construct(
private LogRepositoryInterface $repo,
private Data\LogInterfaceFactory $factory,
private LoggerInterface $logger
) {
}

public function process(Closure $proceed, EmailMessageInterface $message): void
{
/** @var Data\LogInterface $entity */
$entity = $this->factory->create();

try {
$this->fillEntity($entity, $message);
$proceed();
$entity->setStatus(Status::STATUS_SUCCESS);
} catch (MailException $e) {
$entity->setError($e->getPrevious()->getMessage());
$entity->setStatus(Status::STATUS_ERROR);

throw $e;
} catch (LocalizedException $e) {
$entity->setStatus(Status::STATUS_ERROR);
$entity->setError($e->getMessage());
$this->logger->error($e->getMessage(), ['exception' => $e]);
} finally {
$this->repo->save($entity);
}
}

private function formatList(iterable $list): string
{
return implode(
',',
array_map(fn (Address $address) => $address->getEmail(), $list)
);
}

private function fillEntity(Data\LogInterface $entity, EmailMessageInterface $message): Data\LogInterface
{
if ($message->getSender()) {
$entity->setSender(
$message->getSender()->getName() . ' <' . $message->getSender()->getEmail() . '>'
);
} elseif (is_iterable($message->getFrom()) && count($message->getFrom()) > 0) {
$f = $message->getFrom();
$from = reset($f);
$entity->setSender(
$from->getName() . ' <' . $from->getEmail() . '>'
);
}

$entity->setRecipient($this->formatList($message->getTo()));
$entity->setSubject($message->getSubject());
$messageBody = quoted_printable_decode($message->getBodyText());
$content = htmlspecialchars($messageBody);
$entity->setContent($content);

if (is_iterable($message->getCc()) && count($message->getCc()) > 0) {
$entity->setCc($this->formatList($message->getCc()));
}
if (is_iterable($message->getBcc()) && count($message->getBcc()) > 0) {
$entity->setBcc($this->formatList($message->getBcc()));
}

return $entity;
}
}
56 changes: 56 additions & 0 deletions Model/Mail/Validator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/**
* @author Mygento Team
* @copyright 2023 Mygento (https://www.mygento.com)
* @package Mygento_Smtp
*/

namespace Mygento\Smtp\Model\Mail;

use Magento\Framework\Mail\EmailMessageInterface;
use Mygento\Smtp\Model\Config;

class Validator
{
public function __construct(
private Config $config,
) {
}

public function isValid(EmailMessageInterface $message): bool
{
return !$this->inBlacklist($message);
}

private function inBlacklist(EmailMessageInterface $message): bool
{
$blacklist = $this->config->getBlacklist();

if (!$blacklist) {
return false;
}

$recipient = $this->getRecipient($message);
$patterns = array_unique(explode(PHP_EOL, $blacklist));
foreach ($patterns as $pattern) {
if (preg_match($pattern, $recipient)) {
return true;
}
}

return false;
}

private function getRecipient(EmailMessageInterface $message): string
{
$emails = [];
if ($message->getTo()) {
foreach ($message->getTo() as $address) {
$emails[] = $address->getEmail();
}
}

return implode(',', $emails);
}
}
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

Extends Magento_Email module.
The module adds a feature to log email messages to the database even when email sending is disabled.
Adds a blacklist to make it possible to skip sending emails to emails from the blacklist

## Admin panel
`Stores -> SMTP -> SMTP Log`: The grid with email logs

## Configuration
Add Yes/No field `Log Email` to `Stores -> Configuration -> Advanced -> System -> Mail Sending Settings`
Add days(int) field `Clean Email Log Every` to `Stores -> Configuration -> Advanced -> System -> Mail Sending Settings`
Add Yes/No field `Log Email` to `Stores -> Configuration -> Advanced -> System -> Mail Sending Settings`
Add days(int) field `Clean Email Log Every` to `Stores -> Configuration -> Advanced -> System -> Mail Sending Settings`
Add regex with email to field `Blacklist` (could be multiline as several expr) to skip sending messages to these emails to `Stores -> Configuration -> Advanced -> System -> Mail Sending Settings`

## Plugins
* `aroundSendMessage`
Expand Down
8 changes: 8 additions & 0 deletions etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
<field id="log">1</field>
</depends>
</field>
<field id="blacklist" translate="label comment" type="textarea" sortOrder="170" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Blacklist</label>
<comment><![CDATA[Enter the main email pattern format. Separated by line breaks.<br />
For the email having the same pattern format with this main pattern, it can not receive email from the system.<br />
For example: Enter the pattern: /^[0-9][a-z0-9\$\%\&]+@[a-z]+\.[a-z]{2,}$/ means that email format must start with number, the following characters can be normal text, number or special character ($, %, &). The domain after @ with normal-text characters will not receive email from system. For instance: [email protected]</br>
]]>
</comment>
</field>
</group>
</section>
</system>
Expand Down

0 comments on commit 5389404

Please sign in to comment.