Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'parameters' => [
|
||||
'messenger_dsn_email' => 'sync://', // sync means no queue
|
||||
'messenger_dsn_hit' => 'sync://', // sync means no queue
|
||||
'messenger_dsn_failed' => null, // failed transport is optional
|
||||
'messenger_retry_strategy_max_retries' => 3, // Maximum number of retries for a failed send
|
||||
'messenger_retry_strategy_delay' => 1000, // Delay in milliseconds between retries
|
||||
'messenger_retry_strategy_multiplier' => 2.0, // Delay multiplier between retries e.g. 1 second delay, 2 seconds, 4 seconds
|
||||
'messenger_retry_strategy_max_delay' => 0, // maximum delay in milliseconds between retries
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
use Symfony\Component\Messenger\Transport\TransportFactory;
|
||||
|
||||
return function (ContainerConfigurator $configurator): void {
|
||||
$services = $configurator->services()
|
||||
->defaults()
|
||||
->autowire()
|
||||
->autoconfigure();
|
||||
|
||||
$services
|
||||
->load('Mautic\\MessengerBundle\\', '../')
|
||||
->exclude('../{Config,Tests,Message}');
|
||||
|
||||
$services->alias(TransportFactory::class, 'messenger.transport_factory');
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\AjaxController as CommonAjaxController;
|
||||
use Mautic\MessengerBundle\Service\TestMessageFactory;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
class AjaxController extends CommonAjaxController
|
||||
{
|
||||
public function sendTestMessageAction(
|
||||
Request $request,
|
||||
MessageBusInterface $bus,
|
||||
TestMessageFactory $messageFactory,
|
||||
): Response {
|
||||
try {
|
||||
$message = $messageFactory->crateMessageByDsnKey((string) $request->request->get('key'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$data = [
|
||||
'success' => 1,
|
||||
'message' => $this->translator->trans('mautic.core.success'),
|
||||
];
|
||||
|
||||
try {
|
||||
$bus->dispatch($message);
|
||||
} catch (\Throwable $e) {
|
||||
$data['success'] = 0;
|
||||
$data['message'] = $this->translator->trans('mautic.messenger.config.dsn.test_message_failed', ['%message%' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->sendJsonResponse($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\DependencyInjection\EnvProcessor;
|
||||
|
||||
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
|
||||
|
||||
class MessengerNullableEnvVarProcessor implements EnvVarProcessorInterface
|
||||
{
|
||||
public function getEnv(string $prefix, string $name, \Closure $getEnv): string
|
||||
{
|
||||
return $getEnv($name) ?: 'null://';
|
||||
}
|
||||
|
||||
public static function getProvidedTypes(): array
|
||||
{
|
||||
return [
|
||||
'messenger-nullable' => 'string',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
|
||||
class MauticMessengerExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $configs
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Config'));
|
||||
$loader->load('services.php');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\EventListener;
|
||||
|
||||
use Mautic\ConfigBundle\ConfigEvents;
|
||||
use Mautic\ConfigBundle\Event\ConfigBuilderEvent;
|
||||
use Mautic\MessengerBundle\Form\Type\ConfigType;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class ConfigSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
ConfigEvents::CONFIG_ON_GENERATE => ['onConfigGenerate', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onConfigGenerate(ConfigBuilderEvent $event): void
|
||||
{
|
||||
$event->addForm([
|
||||
'bundle' => 'MessengerBundle',
|
||||
'formAlias' => 'messengerconfig',
|
||||
'formType' => ConfigType::class,
|
||||
'formTheme' => '@MauticMessenger/FormTheme/Config/_config_messengerconfig_widget.html.twig',
|
||||
'parameters' => $event->getParametersFromConfig('MauticMessengerBundle'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Exceptions;
|
||||
|
||||
use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;
|
||||
|
||||
class InvalidPayloadException extends MauticMessengerException implements UnrecoverableExceptionInterface
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $payload
|
||||
*/
|
||||
public function __construct(string $message = '', array $payload = [], ?\Throwable $previous = null)
|
||||
{
|
||||
$message .= json_encode($payload);
|
||||
parent::__construct($message, 400, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Exceptions;
|
||||
|
||||
class MauticMessengerException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Form\Type;
|
||||
|
||||
use Mautic\ConfigBundle\Form\Type\DsnType;
|
||||
use Mautic\MessengerBundle\Validator\Dsn;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ConfigType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$testButton = [
|
||||
'action' => 'messenger:sendTestMessage',
|
||||
'label' => $this->translator->trans('mautic.messenger.config.dsn.send_test_message'),
|
||||
];
|
||||
|
||||
$builder->add(
|
||||
'messenger_dsn_email',
|
||||
DsnType::class,
|
||||
[
|
||||
'constraints' => [new Dsn()],
|
||||
'test_button' => $testButton,
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'messenger_dsn_hit',
|
||||
DsnType::class,
|
||||
[
|
||||
'constraints' => [new Dsn()],
|
||||
'test_button' => $testButton,
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'messenger_dsn_failed',
|
||||
DsnType::class,
|
||||
[
|
||||
'constraints' => [new Dsn()],
|
||||
'required' => false,
|
||||
'test_button' => $testButton,
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'messenger_retry_strategy_max_retries',
|
||||
NumberType::class,
|
||||
[
|
||||
'label' => 'mautic.messenger.config.retry_strategy.max_retries',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'messenger_retry_strategy_delay',
|
||||
NumberType::class,
|
||||
[
|
||||
'label' => 'mautic.messenger.config.retry_strategy.delay',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'messenger_retry_strategy_multiplier',
|
||||
NumberType::class,
|
||||
[
|
||||
'scale' => 2,
|
||||
'label' => 'mautic.messenger.config.retry_strategy.multiplier',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'messenger_retry_strategy_max_delay',
|
||||
NumberType::class,
|
||||
[
|
||||
'label' => 'mautic.messenger.config.retry_strategy.max_delay',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'messengerconfig';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class MauticMessengerBundle extends Bundle
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Message;
|
||||
|
||||
use Mautic\MessengerBundle\Message\Traits\MessageRequestTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class EmailHitNotification
|
||||
{
|
||||
use MessageRequestTrait;
|
||||
|
||||
public function __construct(
|
||||
private string $statId,
|
||||
private Request $request,
|
||||
?\DateTimeInterface $eventTime = null,
|
||||
) {
|
||||
$this->setEventTime($eventTime ?? new \DateTimeImmutable());
|
||||
}
|
||||
|
||||
public function getStatId(): string
|
||||
{
|
||||
return $this->statId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Message;
|
||||
|
||||
use Mautic\MessengerBundle\Message\Traits\MessageRequestTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
final class PageHitNotification
|
||||
{
|
||||
use MessageRequestTrait;
|
||||
|
||||
public function __construct(
|
||||
private int $hitId,
|
||||
private Request $request,
|
||||
private bool $isNew,
|
||||
private bool $isRedirect,
|
||||
private ?int $pageId = null,
|
||||
private ?int $leadId = null,
|
||||
?\DateTimeInterface $eventTime = null,
|
||||
) {
|
||||
$this->setEventTime($eventTime ?? new \DateTimeImmutable());
|
||||
}
|
||||
|
||||
public function getHitId(): int
|
||||
{
|
||||
return $this->hitId;
|
||||
}
|
||||
|
||||
public function getPageId(): ?int
|
||||
{
|
||||
return $this->pageId;
|
||||
}
|
||||
|
||||
public function getLeadId(): ?int
|
||||
{
|
||||
return $this->leadId;
|
||||
}
|
||||
|
||||
public function isNew(): bool
|
||||
{
|
||||
return $this->isNew;
|
||||
}
|
||||
|
||||
public function isRedirect(): bool
|
||||
{
|
||||
return $this->isRedirect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Message;
|
||||
|
||||
class TestEmail
|
||||
{
|
||||
public function __construct(
|
||||
public int $userId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Message;
|
||||
|
||||
class TestFailed
|
||||
{
|
||||
public function __construct(
|
||||
public int $userId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Message;
|
||||
|
||||
class TestHit
|
||||
{
|
||||
public function __construct(
|
||||
public int $userId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Message\Traits;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
trait MessageRequestTrait
|
||||
{
|
||||
private ?\DateTimeInterface $eventTime = null;
|
||||
|
||||
private Request $request;
|
||||
|
||||
public function getEventTime(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->eventTime;
|
||||
}
|
||||
|
||||
public function setEventTime(?\DateTimeInterface $eventTime = null): self
|
||||
{
|
||||
$this->eventTime = $eventTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRequest(): Request
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
public function __serialize(): array
|
||||
{
|
||||
$data = get_object_vars($this);
|
||||
$data['request'] = array_filter([
|
||||
'attributes' => $this->request->attributes->all(),
|
||||
'request' => $this->request->request->all(),
|
||||
'query' => $this->request->query->all(),
|
||||
'cookies' => $this->request->cookies->all(),
|
||||
'files' => $this->request->files->all(),
|
||||
'server' => $this->request->server->all(),
|
||||
]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$requestData = $data['request'];
|
||||
$data['request'] = new Request(
|
||||
$requestData['query'] ?? [],
|
||||
$requestData['request'] ?? [],
|
||||
$requestData['attributes'] ?? [],
|
||||
$requestData['cookies'] ?? [],
|
||||
$requestData['files'] ?? [],
|
||||
$requestData['server'] ?? []
|
||||
);
|
||||
|
||||
foreach ($data as $key => $item) {
|
||||
$this->$key = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\MessageHandler;
|
||||
|
||||
use Doctrine\DBAL\Exception\RetryableException;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\EmailBundle\Model\EmailModel;
|
||||
use Mautic\MessengerBundle\Message\EmailHitNotification;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException;
|
||||
use Symfony\Component\Messenger\Handler\Acknowledger;
|
||||
|
||||
#[AsMessageHandler]
|
||||
class EmailHitNotificationHandler
|
||||
{
|
||||
private bool $isSyncTransport;
|
||||
|
||||
public function __construct(
|
||||
private EmailModel $emailModel,
|
||||
CoreParametersHelper $parametersHelper,
|
||||
) {
|
||||
$this->isSyncTransport = str_starts_with($parametersHelper->get('messenger_dsn_hit'), 'sync://');
|
||||
}
|
||||
|
||||
public function __invoke(EmailHitNotification $message, ?Acknowledger $ack = null): void
|
||||
{
|
||||
try {
|
||||
$this->emailModel->hitEmail(
|
||||
$message->getStatId(),
|
||||
$message->getRequest(),
|
||||
false,
|
||||
$this->isSyncTransport,
|
||||
$message->getEventTime(),
|
||||
true
|
||||
);
|
||||
} catch (RetryableException $e) {
|
||||
throw new RecoverableMessageHandlingException($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\MessageHandler;
|
||||
|
||||
use Mautic\LeadBundle\Entity\LeadRepository;
|
||||
use Mautic\MessengerBundle\Exceptions\InvalidPayloadException;
|
||||
use Mautic\MessengerBundle\Message\PageHitNotification;
|
||||
use Mautic\PageBundle\Entity\Hit;
|
||||
use Mautic\PageBundle\Entity\HitRepository;
|
||||
use Mautic\PageBundle\Entity\PageRepository;
|
||||
use Mautic\PageBundle\Entity\RedirectRepository;
|
||||
use Mautic\PageBundle\Model\PageModel;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Symfony\Component\Messenger\Handler\Acknowledger;
|
||||
|
||||
#[AsMessageHandler]
|
||||
class PageHitNotificationHandler
|
||||
{
|
||||
public function __construct(
|
||||
private PageRepository $pageRepository,
|
||||
private HitRepository $hitRepository,
|
||||
private LeadRepository $leadRepository,
|
||||
private LoggerInterface $logger,
|
||||
private RedirectRepository $redirectRepository,
|
||||
private PageModel $pageModel,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidPayloadException
|
||||
*/
|
||||
public function __invoke(PageHitNotification $message, ?Acknowledger $ack = null): void
|
||||
{
|
||||
$parsed = $this->parseMessage($message);
|
||||
$this->pageModel->processPageHit(...$parsed);
|
||||
$this->logger->info('processed page hit #'.$message->getHitId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*
|
||||
* @throws InvalidPayloadException
|
||||
*/
|
||||
private function parseMessage(PageHitNotification $message): array
|
||||
{
|
||||
$hit = $message->getHitId() > 0 ? $this->hitRepository->find($message->getHitId()) : null;
|
||||
|
||||
$pageObject = null;
|
||||
if (null !== $message->getPageId()) {
|
||||
try {
|
||||
$pageObject = $message->isRedirect()
|
||||
? $this->redirectRepository->find($message->getPageId())
|
||||
: $this->pageRepository->find($message->getPageId());
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->error(
|
||||
sprintf('Invalid page/redirect, exception. #%s', $message->getPageId()),
|
||||
['message' => $message]
|
||||
);
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if (null === $pageObject) {
|
||||
$this->logger->error(
|
||||
sprintf('Invalid page/redirect, id not found. #%s', $message->getPageId())
|
||||
);
|
||||
throw new InvalidPayloadException('Missing required information', ['message' => $message]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hit instanceof Hit && $message->getHitId() > 0) {
|
||||
$this->logger->warning('Invalid hit id #'.$message->getHitId(), ['message' => $message]);
|
||||
|
||||
throw new InvalidPayloadException('Invalid hit id #'.$message->getHitId(), (array) $message);
|
||||
}
|
||||
|
||||
// Lead IS mandatory field
|
||||
if (null === $lead = $this->leadRepository->find($message->getLeadId())) {
|
||||
$this->logger->error('Invalid lead id #'.$message->getLeadId(), ['message' => $message]);
|
||||
|
||||
throw new InvalidPayloadException('Invalid lead id', (array) $message);
|
||||
}
|
||||
|
||||
return [
|
||||
'hit' => $hit,
|
||||
'page' => $pageObject,
|
||||
'request' => $message->getRequest(),
|
||||
'lead' => $lead,
|
||||
'trackingNewlyGenerated' => $message->isNew(),
|
||||
'activeRequest' => false,
|
||||
'hitDate' => $message->getEventTime(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\MessageHandler;
|
||||
|
||||
use Mautic\CoreBundle\Helper\FilePathResolver;
|
||||
use Mautic\ReportBundle\Model\ExportHandler;
|
||||
use Symfony\Component\Mailer\Messenger\SendEmailMessage;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
#[AsMessageHandler(priority: -1000)]
|
||||
class RemoveReportAttachmentHandler
|
||||
{
|
||||
public function __construct(private ExportHandler $exportHandler, private FilePathResolver $filePathResolver)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(SendEmailMessage $message): void
|
||||
{
|
||||
$email = $message->getMessage();
|
||||
|
||||
if (!$email instanceof Email) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attachments = $email->getAttachments();
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
$headers = $attachment->getPreparedHeaders();
|
||||
if (!$headers->has('Content-Disposition')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filename = $headers->getHeaderParameter('Content-Disposition', 'filename');
|
||||
if (null === $filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attachmentPath = $this->exportHandler->getPath(pathinfo($filename, \PATHINFO_FILENAME));
|
||||
|
||||
$this->filePathResolver->delete($attachmentPath);
|
||||
// str_replace as in \Mautic\ReportBundle\Scheduler\Model\FileHandler::zipIt
|
||||
$this->filePathResolver->delete(str_replace('.csv', '.zip', $attachmentPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
# Messenger Bundle
|
||||
|
||||
The bundle makes use of [Symfony's messenger component](https://symfony.com/doc/5.4/messenger.html) to dispatch and handle messages.
|
||||
|
||||
## Transports
|
||||
|
||||
It creates `synchronous` transport only, it is necessary for processing in case external messenger is not used.
|
||||
|
||||
https://symfony.com/doc/current/messenger.html#transports-async-queued-messages
|
||||
|
||||
The only thing you need to do is to map the routing key to the transport you wish to use.
|
||||
|
||||
https://symfony.com/doc/current/messenger.html#routing-messages-to-a-transport
|
||||
|
||||
By default, the transport is set to **synchronous**, meaning no AMQP/Doctrine or whatsoever is used and the request is handled directly and the message is marked as synchronous process if it implements **RequestStatusInterface**.
|
||||
|
||||
[Currently defined routes](MauticMessengerRoutes.php) are EMAIL, FAILED, SYNC and HIT although in default configuration only the SYNC is used.
|
||||
|
||||
> https://symfony.com/doc/5.4/messenger.html#routing-messages-to-a-transport
|
||||
|
||||
Here [a sample configuration](#sample-configuration)
|
||||
|
||||
## Notifications
|
||||
|
||||
Currently, 2 messages are defined.
|
||||
* [EmailHitNotification](app/bundles/MessengerBundle/Message/EmailHitNotification.php)
|
||||
* [PageHitNotification](app/bundles/MessengerBundle/Message/PageHitNotification.php)
|
||||
|
||||
## Configuring transports
|
||||
> there is new serializer available, that uses JSON and has smaller payload than native php **'messenger.transport.jms_serializer'** that is recommended to use. You can place the following configuration sample to any config file where `$container` is available. For example `config/config_local.php`
|
||||
```php
|
||||
$container->loadFromExtension('framework', [
|
||||
'messenger' => [
|
||||
'routing' => [
|
||||
\Mautic\MessengerBundle\Message\PageHitNotification::class => \Mautic\MessengerBundle\MauticMessengerTransports::HIT,
|
||||
\Mautic\MessengerBundle\Message\EmailHitNotification::class => \Mautic\MessengerBundle\MauticMessengerTransports::HIT,
|
||||
],
|
||||
'failure_transport' => 'failed', // Define other than default if you wish
|
||||
'transports' => [
|
||||
'failed' => [
|
||||
'dsn' => 'doctrine://default?queue_name=failed',
|
||||
],
|
||||
\Mautic\MessengerBundle\MauticMessengerTransports::SYNC => 'sync://',
|
||||
\Mautic\MessengerBundle\MauticMessengerTransports::HIT => [
|
||||
'dsn' => '%env(MAUTIC_MESSENGER_TRANSPORT_DSN)%',
|
||||
'serializer' => 'messenger.transport.jms_serializer',
|
||||
'options' => [
|
||||
'heartbeat' => 1,
|
||||
'persistent' => true,
|
||||
'vhost' => '/',
|
||||
'exchange' => [
|
||||
'name' => 'mautic',
|
||||
'type' => 'direct',
|
||||
'default_publish_routing_key' => 'hit',
|
||||
],
|
||||
'queues' => [
|
||||
'hits' => [
|
||||
'binding_keys' => ['hit'],
|
||||
'arguments' => [
|
||||
'x-expires' => 60 * 60 * 24 * 21 * 1000, // queue ttl without consumer using it
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'serializer' => 'messenger.transport.native_php_serializer',
|
||||
'retry_strategy' => [
|
||||
'max_retries' => 3,
|
||||
'delay' => 500,
|
||||
'multiplier' => 3,
|
||||
'max_delay' => 0,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
In order to run consumer, simply run:
|
||||
|
||||
```shell
|
||||
sudo -uwww-data bin/console messenger:consume hit
|
||||
```
|
||||
|
||||
> Where *hit* stands for your transport's name. In the example above; it is the value of `\Mautic\MessengerBundle\MauticMessengerTransports::HIT`
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
{% block _config_messengerconfig_widget %}
|
||||
<div class="alert alert-info mt-md" role="alert">
|
||||
{% trans with {'%link%': 'https://symfony.com/doc/5.4/messenger.html#transport-configuration' } %}mautic.messenger.config.dsn_help_general{% endtrans %}
|
||||
</div>
|
||||
|
||||
<h4 class="fw-sb mt-48 mb-xs">{{ 'mautic.messenger.config.dsn_email'|trans }}</h4>
|
||||
<div class="text-muted small pb-md">{{ 'mautic.core.config.header.messenger.email.description'|trans }}</div>
|
||||
<div class="row">
|
||||
<div class="panel panel-default mb-md">
|
||||
<div class="panel-body">
|
||||
{{ form_row(form.messenger_dsn_email) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="fw-sb mt-48 mb-xs">{{ 'mautic.messenger.config.dsn_hit'|trans }}</h4>
|
||||
<div class="text-muted small pb-md">{{ 'mautic.core.config.header.messenger.hit.description'|trans }}</div>
|
||||
<div class="row">
|
||||
<div class="panel panel-default mb-md">
|
||||
<div class="panel-body">
|
||||
{{ form_row(form.messenger_dsn_hit) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
{% trans with {'%link%': 'https://symfony.com/doc/5.4/messenger.html#retries-failures' } %}mautic.messenger.config.dsn_help_retry_strategy{% endtrans %}
|
||||
</div>
|
||||
|
||||
<h4 class="fw-sb mt-48 mb-xs">{{ 'mautic.messenger.config.retry_strategy'|trans }}</h4>
|
||||
<div class="text-muted small pb-md">{{ 'mautic.core.config.header.messenger.retry.description'|trans }}</div>
|
||||
<div class="row">
|
||||
<div class="panel panel-default mb-md">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-lg-3">
|
||||
{{ form_row(form.messenger_retry_strategy_max_retries) }}
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-3">
|
||||
{{ form_row(form.messenger_retry_strategy_delay) }}
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-3">
|
||||
{{ form_row(form.messenger_retry_strategy_multiplier) }}
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-3">
|
||||
{{ form_row(form.messenger_retry_strategy_max_delay) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
{% trans with {'%link%': 'https://symfony.com/doc/5.4/messenger.html#saving-retrying-failed-messages' } %}mautic.messenger.config.dsn_help_failed{% endtrans %}
|
||||
</div>
|
||||
|
||||
<h4 class="fw-sb mt-48 mb-xs">{{ 'mautic.messenger.config.dsn_failed'|trans }}</h4>
|
||||
<div class="text-muted small pb-md">{{ 'mautic.core.config.header.messenger.failed.description'|trans }}</div>
|
||||
<div class="row">
|
||||
<div class="panel panel-default mb-md">
|
||||
<div class="panel-body">
|
||||
{{ form_row(form.messenger_dsn_failed) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Retry;
|
||||
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy;
|
||||
use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
|
||||
|
||||
class RetryStrategy implements RetryStrategyInterface
|
||||
{
|
||||
private RetryStrategyInterface $retryStrategy;
|
||||
|
||||
public function __construct(
|
||||
private CoreParametersHelper $parametersHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isRetryable(Envelope $message, ?\Throwable $throwable = null): bool
|
||||
{
|
||||
return $this->getRetryStrategy()->isRetryable($message);
|
||||
}
|
||||
|
||||
public function getWaitingTime(Envelope $message, ?\Throwable $throwable = null): int
|
||||
{
|
||||
return $this->getRetryStrategy()->getWaitingTime($message);
|
||||
}
|
||||
|
||||
private function getRetryStrategy(): RetryStrategyInterface
|
||||
{
|
||||
if (!isset($this->retryStrategy)) {
|
||||
$this->retryStrategy = new MultiplierRetryStrategy(
|
||||
(int) $this->parametersHelper->get('messenger_retry_strategy_max_retries'),
|
||||
(int) $this->parametersHelper->get('messenger_retry_strategy_delay'),
|
||||
(float) $this->parametersHelper->get('messenger_retry_strategy_multiplier'),
|
||||
(int) $this->parametersHelper->get('messenger_retry_strategy_max_delay'),
|
||||
);
|
||||
}
|
||||
|
||||
return $this->retryStrategy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Service;
|
||||
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\MessengerBundle\Message\TestEmail;
|
||||
use Mautic\MessengerBundle\Message\TestFailed;
|
||||
use Mautic\MessengerBundle\Message\TestHit;
|
||||
|
||||
class TestMessageFactory
|
||||
{
|
||||
public function __construct(
|
||||
private UserHelper $userHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public function crateMessageByDsnKey(string $key): object
|
||||
{
|
||||
return match ($key) {
|
||||
'messenger_dsn_email' => new TestEmail($this->userHelper->getUser()->getId()),
|
||||
'messenger_dsn_hit' => new TestHit($this->userHelper->getUser()->getId()),
|
||||
'messenger_dsn_failed' => new TestFailed($this->userHelper->getUser()->getId()),
|
||||
default => throw new \InvalidArgumentException(sprintf('Unsupported key: "%s"', $key)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\MessengerBundle\Tests\Message;
|
||||
|
||||
use Mautic\MessengerBundle\Message\EmailHitNotification;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class EmailHitNotificationTest extends TestCase
|
||||
{
|
||||
public function testConstruct(): void
|
||||
{
|
||||
$request = new Request();
|
||||
$request->query->set('testMe', 'Hit me once');
|
||||
|
||||
$message = new EmailHitNotification('statid', $request);
|
||||
|
||||
$this->assertArrayHasKey('testMe', $message->getRequest()->query->all());
|
||||
$this->assertEquals($request, $message->getRequest());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\MessengerBundle\Tests\Message;
|
||||
|
||||
use Mautic\MessengerBundle\Message\PageHitNotification;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class PageHitNotificationTest extends TestCase
|
||||
{
|
||||
public function testConstruct(): void
|
||||
{
|
||||
$request = new Request();
|
||||
$request->query->set('testMe', 'Hit me once');
|
||||
|
||||
$message = new PageHitNotification(78, $request, false, false, 3, 1);
|
||||
|
||||
$this->assertArrayHasKey('testMe', $message->getRequest()->query->all());
|
||||
$this->assertEquals($request, $message->getRequest());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\MessengerBundle\Tests\MessageHandler;
|
||||
|
||||
use Doctrine\DBAL\Exception\RetryableException;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\EmailBundle\Model\EmailModel;
|
||||
use Mautic\MessengerBundle\Message\EmailHitNotification;
|
||||
use Mautic\MessengerBundle\MessageHandler\EmailHitNotificationHandler;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException;
|
||||
|
||||
class EmailHitNotificationHandlerTest extends TestCase
|
||||
{
|
||||
public function testInvoke(): void
|
||||
{
|
||||
$hitId = sha1((string) random_int(0, 1_000_000));
|
||||
$request = new Request();
|
||||
$request->query->set('testMe', 'I am here');
|
||||
|
||||
/** @var MockObject|EmailModel $emailModelMock */
|
||||
$emailModelMock = $this->createMock(EmailModel::class);
|
||||
$emailModelMock
|
||||
->expects($this->exactly(1))
|
||||
->method('hitEmail')
|
||||
->with($hitId, $request);
|
||||
|
||||
/** @var MockObject&CoreParametersHelper $parametersHelper */
|
||||
$parametersHelper = $this->createMock(CoreParametersHelper::class);
|
||||
$parametersHelper->method('get')
|
||||
->willReturn('sync://');
|
||||
|
||||
$message = new EmailHitNotification($hitId, $request);
|
||||
|
||||
$handler = new EmailHitNotificationHandler($emailModelMock, $parametersHelper);
|
||||
$handler->__invoke($message);
|
||||
}
|
||||
|
||||
public function testInvokeThrowsRecoverableExceptionOnDBLock(): void
|
||||
{
|
||||
$hitId = sha1((string) random_int(0, 1_000_000));
|
||||
$request = new Request();
|
||||
$request->query->set('testMe', 'I am here');
|
||||
|
||||
/** @var MockObject|EmailModel $emailModelMock */
|
||||
$emailModelMock = $this->createMock(EmailModel::class);
|
||||
$emailModelMock
|
||||
->expects($this->exactly(1))
|
||||
->method('hitEmail')
|
||||
->willThrowException($this->createMock(RetryableException::class));
|
||||
|
||||
/** @var MockObject&CoreParametersHelper $parametersHelper */
|
||||
$parametersHelper = $this->createMock(CoreParametersHelper::class);
|
||||
$parametersHelper->method('get')
|
||||
->willReturn('sync://');
|
||||
|
||||
$message = new EmailHitNotification($hitId, $request);
|
||||
|
||||
$handler = new EmailHitNotificationHandler($emailModelMock, $parametersHelper);
|
||||
$this->expectException(RecoverableMessageHandlingException::class);
|
||||
$handler->__invoke($message);
|
||||
}
|
||||
|
||||
public function testInvokeLogsUnrecoverableException(): void
|
||||
{
|
||||
$hitId = sha1((string) random_int(0, 1_000_000));
|
||||
$request = new Request();
|
||||
$request->query->set('testMe', 'I am here');
|
||||
|
||||
/** @var MockObject|EmailModel $emailModelMock */
|
||||
$emailModelMock = $this->createMock(EmailModel::class);
|
||||
$emailModelMock
|
||||
->expects($this->exactly(1))
|
||||
->method('hitEmail')
|
||||
->willThrowException(new \InvalidArgumentException('got my argument?'));
|
||||
|
||||
/** @var MockObject&CoreParametersHelper $parametersHelper */
|
||||
$parametersHelper = $this->createMock(CoreParametersHelper::class);
|
||||
$parametersHelper->method('get')
|
||||
->willReturn('sync://');
|
||||
|
||||
$message = new EmailHitNotification($hitId, $request);
|
||||
$handler = new EmailHitNotificationHandler($emailModelMock, $parametersHelper);
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('got my argument?');
|
||||
$handler->__invoke($message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\MessengerBundle\Tests\MessageHandler;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadRepository;
|
||||
use Mautic\MessengerBundle\Message\PageHitNotification;
|
||||
use Mautic\MessengerBundle\MessageHandler\PageHitNotificationHandler;
|
||||
use Mautic\PageBundle\Entity\Hit;
|
||||
use Mautic\PageBundle\Entity\HitRepository;
|
||||
use Mautic\PageBundle\Entity\Page;
|
||||
use Mautic\PageBundle\Entity\PageRepository;
|
||||
use Mautic\PageBundle\Entity\Redirect;
|
||||
use Mautic\PageBundle\Entity\RedirectRepository;
|
||||
use Mautic\PageBundle\Model\PageModel;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class PageHitNotificationHandlerTest extends TestCase
|
||||
{
|
||||
public function testInvoke(): void
|
||||
{
|
||||
[$hitId, $pageId, $leadId, $redirectId] = [random_int(1, 1000), random_int(1, 1000), random_int(1, 1000), random_int(1, 1000)];
|
||||
|
||||
$redirectObject = new Redirect();
|
||||
$redirectObject->setRedirectId((string) $redirectId);
|
||||
|
||||
[$hitObject, $pageObject, $leadObject] = [
|
||||
(new Hit())->setCode(7),
|
||||
(new Page())->setAlias('james_bond'),
|
||||
(new Lead())->setId($leadId),
|
||||
];
|
||||
|
||||
$hitRepoMock = $this->createMock(HitRepository::class);
|
||||
$hitRepoMock
|
||||
->expects($this->once())
|
||||
->method('find')
|
||||
->with($hitId)
|
||||
->willReturn($hitObject);
|
||||
|
||||
$pageRepoMock = $this->createMock(PageRepository::class);
|
||||
$pageRepoMock->expects($this->once())
|
||||
->method('find')
|
||||
->with($pageId)
|
||||
->willReturn($pageObject);
|
||||
|
||||
$redirectRepoMock = $this->createMock(RedirectRepository::class);
|
||||
$redirectRepoMock
|
||||
->expects($this->never())
|
||||
->method('find')
|
||||
->with($redirectId)
|
||||
->willReturn($redirectObject);
|
||||
|
||||
$leadRepoMock = $this->createMock(LeadRepository::class);
|
||||
$leadRepoMock
|
||||
->expects($this->once())
|
||||
->method('find')
|
||||
->with($leadId)
|
||||
->willReturn($leadObject);
|
||||
|
||||
$request = new Request();
|
||||
$request->query->set('testMe', 'I am here');
|
||||
|
||||
/** @var MockObject|PageModel $pageModelMock */
|
||||
$pageModelMock = $this->createMock(PageModel::class);
|
||||
$pageModelMock
|
||||
->expects($this->exactly(1))
|
||||
->method('processPageHit')
|
||||
->with($hitObject, $pageObject, $request, $leadObject, false, false);
|
||||
|
||||
$message = new PageHitNotification($hitId, $request, false, false, $pageId, $leadId);
|
||||
|
||||
/** @var MockObject|LoggerInterface $loggerMock */
|
||||
$loggerMock = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$handler = new PageHitNotificationHandler(
|
||||
$pageRepoMock, $hitRepoMock, $leadRepoMock, $loggerMock, $redirectRepoMock, $pageModelMock
|
||||
);
|
||||
|
||||
$handler->__invoke($message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
mautic.config.tab.messengerconfig="Queue Settings"
|
||||
mautic.messenger.config.retry_strategy="Retry strategy"
|
||||
mautic.messenger.config.retry_strategy.max_retries="Max retries"
|
||||
mautic.messenger.config.retry_strategy.delay="Delay"
|
||||
mautic.messenger.config.retry_strategy.multiplier="Multiplier"
|
||||
mautic.messenger.config.retry_strategy.max_delay="Max delay"
|
||||
mautic.messenger.config.dsn_help_general="Queuing is not enabled by default (scheme is set to 'sync'). If you want to start using a queue, please follow the documentation at <a href="%link%">%link%</a>."
|
||||
mautic.messenger.config.dsn_help_retry_strategy="When the processing of a message fails, the message is sent back to the queue for another try. You can adjust this behaviour in the following section. See the documentation on <a href="%link%">%link%</a> for more details."
|
||||
mautic.messenger.config.dsn_help_failed="If a message fails all its retries, it's discarded by default. To avoid this happening, you can optionally configure a queue for failures. For more details see the documentation on <a href="%link%">%link%</a>."
|
||||
mautic.messenger.config.dsn_email="Queue for email (SMS and push messages)"
|
||||
mautic.messenger.config.dsn_hit="Queue for hits (page and email)"
|
||||
mautic.messenger.config.dsn_failed="Queue for failures"
|
||||
mautic.messenger.config.dsn.send_test_message="Send test message"
|
||||
mautic.messenger.config.dsn.test_message_failed="The test message could not be sent due to the following error: '"%message%"'."
|
||||
mautic.messenger.config.dsn.test_message_processed="The test message for DSN '"%type%"' was successfully processed."
|
||||
mautic.core.config.header.messenger.email.description="Configure message queuing settings for email, SMS, and push notifications."
|
||||
mautic.core.config.header.messenger.hit.description="Manage queuing settings for page views and email tracking data."
|
||||
mautic.core.config.header.messenger.retry.description="Define how failed messages are handled and retried in the queue system."
|
||||
mautic.core.config.header.messenger.failed.description="Set up storage for failed messages that exceed retry attempts."
|
||||
@@ -0,0 +1,2 @@
|
||||
mautic.messenger.dsn.invalid_dsn="Invalid DSN. Please make sure you entered all the required fields. Usually the fields 'scheme' and 'host' are required."
|
||||
mautic.messenger.dsn.unsupported_scheme="Unsupported scheme. Please make sure the entered scheme matches one of the supported schemes. You might need to install a package supporting the scheme first. For more details see https://symfony.com/doc/5.4/messenger.html#transport-configuration"
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Transport\TransportInterface;
|
||||
|
||||
class NullTransport implements TransportInterface
|
||||
{
|
||||
public function get(): iterable
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function ack(Envelope $envelope): void
|
||||
{
|
||||
}
|
||||
|
||||
public function reject(Envelope $envelope): void
|
||||
{
|
||||
}
|
||||
|
||||
public function send(Envelope $envelope): Envelope
|
||||
{
|
||||
return $envelope;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Transport;
|
||||
|
||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
|
||||
use Symfony\Component\Messenger\Transport\TransportInterface;
|
||||
|
||||
/**
|
||||
* Needed for the E2E tests.
|
||||
*/
|
||||
class NullTransportFactory implements TransportFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $options
|
||||
*/
|
||||
public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
|
||||
{
|
||||
return new NullTransport();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $options
|
||||
*/
|
||||
public function supports(string $dsn, array $options): bool
|
||||
{
|
||||
return str_starts_with($dsn, 'null://');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
class Dsn extends Constraint
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\MessengerBundle\Validator;
|
||||
|
||||
use Mautic\CoreBundle\Helper\Dsn\Dsn as CoreDsn;
|
||||
use Mautic\MessengerBundle\Validator\Dsn as DsnConstraint;
|
||||
use Symfony\Component\Messenger\Transport\TransportFactory;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
class DsnValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(
|
||||
private TransportFactory $transportFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
throw new UnexpectedTypeException($value, 'string');
|
||||
}
|
||||
|
||||
if (!$constraint instanceof DsnConstraint) {
|
||||
throw new UnexpectedTypeException($constraint, DsnConstraint::class);
|
||||
}
|
||||
|
||||
if (!$value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = CoreDsn::fromString($value);
|
||||
} catch (\InvalidArgumentException) {
|
||||
$this->context->addViolation('mautic.messenger.dsn.invalid_dsn');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->transportFactory->supports($value, $dsn->getOptions())) {
|
||||
$this->context->addViolation('mautic.messenger.dsn.unsupported_scheme');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user