+ */
+final class AmqpStamp implements NonSendableStampInterface
+{
+ private $routingKey;
+ private $flags;
+ private $attributes;
+ private $isRetryAttempt = false;
+
+ public function __construct(string $routingKey = null, int $flags = \AMQP_NOPARAM, array $attributes = [])
+ {
+ $this->routingKey = $routingKey;
+ $this->flags = $flags;
+ $this->attributes = $attributes;
+ }
+
+ public function getRoutingKey(): ?string
+ {
+ return $this->routingKey;
+ }
+
+ public function getFlags(): int
+ {
+ return $this->flags;
+ }
+
+ public function getAttributes(): array
+ {
+ return $this->attributes;
+ }
+
+ public static function createFromAmqpEnvelope(\AMQPEnvelope $amqpEnvelope, self $previousStamp = null, string $retryRoutingKey = null): self
+ {
+ $attr = $previousStamp->attributes ?? [];
+
+ $attr['headers'] = $attr['headers'] ?? $amqpEnvelope->getHeaders();
+ $attr['content_type'] = $attr['content_type'] ?? $amqpEnvelope->getContentType();
+ $attr['content_encoding'] = $attr['content_encoding'] ?? $amqpEnvelope->getContentEncoding();
+ $attr['delivery_mode'] = $attr['delivery_mode'] ?? $amqpEnvelope->getDeliveryMode();
+ $attr['priority'] = $attr['priority'] ?? $amqpEnvelope->getPriority();
+ $attr['timestamp'] = $attr['timestamp'] ?? $amqpEnvelope->getTimestamp();
+ $attr['app_id'] = $attr['app_id'] ?? $amqpEnvelope->getAppId();
+ $attr['message_id'] = $attr['message_id'] ?? $amqpEnvelope->getMessageId();
+ $attr['user_id'] = $attr['user_id'] ?? $amqpEnvelope->getUserId();
+ $attr['expiration'] = $attr['expiration'] ?? $amqpEnvelope->getExpiration();
+ $attr['type'] = $attr['type'] ?? $amqpEnvelope->getType();
+ $attr['reply_to'] = $attr['reply_to'] ?? $amqpEnvelope->getReplyTo();
+ $attr['correlation_id'] = $attr['correlation_id'] ?? $amqpEnvelope->getCorrelationId();
+
+ if (null === $retryRoutingKey) {
+ $stamp = new self($previousStamp->routingKey ?? $amqpEnvelope->getRoutingKey(), $previousStamp->flags ?? \AMQP_NOPARAM, $attr);
+ } else {
+ $stamp = new self($retryRoutingKey, $previousStamp->flags ?? \AMQP_NOPARAM, $attr);
+ $stamp->isRetryAttempt = true;
+ }
+
+ return $stamp;
+ }
+
+ public function isRetryAttempt(): bool
+ {
+ return $this->isRetryAttempt;
+ }
+
+ public static function createWithAttributes(array $attributes, self $previousStamp = null): self
+ {
+ return new self(
+ $previousStamp->routingKey ?? null,
+ $previousStamp->flags ?? \AMQP_NOPARAM,
+ array_merge($previousStamp->attributes ?? [], $attributes)
+ );
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class, false)) {
+ class_alias(AmqpStamp::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class);
+}
diff --git a/plugins/email/vendor/symfony/amqp-messenger/Transport/AmqpTransport.php b/plugins/email/vendor/symfony/amqp-messenger/Transport/AmqpTransport.php
new file mode 100644
index 0000000..9ffda47
--- /dev/null
+++ b/plugins/email/vendor/symfony/amqp-messenger/Transport/AmqpTransport.php
@@ -0,0 +1,107 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
+use Symfony\Component\Messenger\Transport\Receiver\QueueReceiverInterface;
+use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class AmqpTransport implements QueueReceiverInterface, TransportInterface, SetupableTransportInterface, MessageCountAwareInterface
+{
+ private $serializer;
+ private $connection;
+ private $receiver;
+ private $sender;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer = null)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer ?? new PhpSerializer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ return ($this->receiver ?? $this->getReceiver())->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFromQueues(array $queueNames): iterable
+ {
+ return ($this->receiver ?? $this->getReceiver())->getFromQueues($queueNames);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->ack($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->reject($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ return ($this->sender ?? $this->getSender())->send($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setup(): void
+ {
+ $this->connection->setup();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMessageCount(): int
+ {
+ return ($this->receiver ?? $this->getReceiver())->getMessageCount();
+ }
+
+ private function getReceiver(): AmqpReceiver
+ {
+ return $this->receiver = new AmqpReceiver($this->connection, $this->serializer);
+ }
+
+ private function getSender(): AmqpSender
+ {
+ return $this->sender = new AmqpSender($this->connection, $this->serializer);
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class, false)) {
+ class_alias(AmqpTransport::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class);
+}
diff --git a/plugins/email/vendor/symfony/amqp-messenger/Transport/AmqpTransportFactory.php b/plugins/email/vendor/symfony/amqp-messenger/Transport/AmqpTransportFactory.php
new file mode 100644
index 0000000..420cf7a
--- /dev/null
+++ b/plugins/email/vendor/symfony/amqp-messenger/Transport/AmqpTransportFactory.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
+
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Samuel Roze
+ */
+class AmqpTransportFactory implements TransportFactoryInterface
+{
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ unset($options['transport_name']);
+
+ return new AmqpTransport(Connection::fromDsn($dsn, $options), $serializer);
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ return 0 === strpos($dsn, 'amqp://') || 0 === strpos($dsn, 'amqps://');
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class, false)) {
+ class_alias(AmqpTransportFactory::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class);
+}
diff --git a/plugins/email/vendor/symfony/amqp-messenger/Transport/Connection.php b/plugins/email/vendor/symfony/amqp-messenger/Transport/Connection.php
new file mode 100644
index 0000000..e5a9d59
--- /dev/null
+++ b/plugins/email/vendor/symfony/amqp-messenger/Transport/Connection.php
@@ -0,0 +1,583 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
+
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Exception\LogicException;
+
+/**
+ * An AMQP connection.
+ *
+ * @author Samuel Roze
+ *
+ * @final
+ */
+class Connection
+{
+ private const ARGUMENTS_AS_INTEGER = [
+ 'x-delay',
+ 'x-expires',
+ 'x-max-length',
+ 'x-max-length-bytes',
+ 'x-max-priority',
+ 'x-message-ttl',
+ ];
+
+ private const AVAILABLE_OPTIONS = [
+ 'host',
+ 'port',
+ 'vhost',
+ 'user',
+ 'login',
+ 'password',
+ 'queues',
+ 'exchange',
+ 'delay',
+ 'auto_setup',
+ 'prefetch_count',
+ 'retry',
+ 'persistent',
+ 'frame_max',
+ 'channel_max',
+ 'heartbeat',
+ 'read_timeout',
+ 'write_timeout',
+ 'confirm_timeout',
+ 'connect_timeout',
+ 'cacert',
+ 'cert',
+ 'key',
+ 'verify',
+ 'sasl_method',
+ ];
+
+ private const AVAILABLE_QUEUE_OPTIONS = [
+ 'binding_keys',
+ 'binding_arguments',
+ 'flags',
+ 'arguments',
+ ];
+
+ private const AVAILABLE_EXCHANGE_OPTIONS = [
+ 'name',
+ 'type',
+ 'default_publish_routing_key',
+ 'flags',
+ 'arguments',
+ ];
+
+ private $connectionOptions;
+ private $exchangeOptions;
+ private $queuesOptions;
+ private $amqpFactory;
+ private $autoSetupExchange;
+ private $autoSetupDelayExchange;
+
+ /**
+ * @var \AMQPChannel|null
+ */
+ private $amqpChannel;
+
+ /**
+ * @var \AMQPExchange|null
+ */
+ private $amqpExchange;
+
+ /**
+ * @var \AMQPQueue[]|null
+ */
+ private $amqpQueues = [];
+
+ /**
+ * @var \AMQPExchange|null
+ */
+ private $amqpDelayExchange;
+
+ public function __construct(array $connectionOptions, array $exchangeOptions, array $queuesOptions, AmqpFactory $amqpFactory = null)
+ {
+ if (!\extension_loaded('amqp')) {
+ throw new LogicException(sprintf('You cannot use the "%s" as the "amqp" extension is not installed.', __CLASS__));
+ }
+
+ $this->connectionOptions = array_replace_recursive([
+ 'delay' => [
+ 'exchange_name' => 'delays',
+ 'queue_name_pattern' => 'delay_%exchange_name%_%routing_key%_%delay%',
+ ],
+ ], $connectionOptions);
+ $this->autoSetupExchange = $this->autoSetupDelayExchange = $connectionOptions['auto_setup'] ?? true;
+ $this->exchangeOptions = $exchangeOptions;
+ $this->queuesOptions = $queuesOptions;
+ $this->amqpFactory = $amqpFactory ?? new AmqpFactory();
+ }
+
+ /**
+ * Creates a connection based on the DSN and options.
+ *
+ * Available options:
+ *
+ * * host: Hostname of the AMQP service
+ * * port: Port of the AMQP service
+ * * vhost: Virtual Host to use with the AMQP service
+ * * user|login: Username to use to connect the AMQP service
+ * * password: Password to use to connect to the AMQP service
+ * * read_timeout: Timeout in for income activity. Note: 0 or greater seconds. May be fractional.
+ * * write_timeout: Timeout in for outcome activity. Note: 0 or greater seconds. May be fractional.
+ * * connect_timeout: Connection timeout. Note: 0 or greater seconds. May be fractional.
+ * * confirm_timeout: Timeout in seconds for confirmation, if none specified transport will not wait for message confirmation. Note: 0 or greater seconds. May be fractional.
+ * * queues[name]: An array of queues, keyed by the name
+ * * binding_keys: The binding keys (if any) to bind to this queue
+ * * binding_arguments: Arguments to be used while binding the queue.
+ * * flags: Queue flags (Default: AMQP_DURABLE)
+ * * arguments: Extra arguments
+ * * exchange:
+ * * name: Name of the exchange
+ * * type: Type of exchange (Default: fanout)
+ * * default_publish_routing_key: Routing key to use when publishing, if none is specified on the message
+ * * flags: Exchange flags (Default: AMQP_DURABLE)
+ * * arguments: Extra arguments
+ * * delay:
+ * * queue_name_pattern: Pattern to use to create the queues (Default: "delay_%exchange_name%_%routing_key%_%delay%")
+ * * exchange_name: Name of the exchange to be used for the delayed/retried messages (Default: "delays")
+ * * auto_setup: Enable or not the auto-setup of queues and exchanges (Default: true)
+ *
+ * * Connection tuning options (see http://www.rabbitmq.com/amqp-0-9-1-reference.html#connection.tune for details):
+ * * channel_max: Specifies highest channel number that the server permits. 0 means standard extension limit
+ * (see PHP_AMQP_MAX_CHANNELS constant)
+ * * frame_max: The largest frame size that the server proposes for the connection, including frame header
+ * and end-byte. 0 means standard extension limit (depends on librabbimq default frame size limit)
+ * * heartbeat: The delay, in seconds, of the connection heartbeat that the server wants.
+ * 0 means the server does not want a heartbeat. Note, librabbitmq has limited heartbeat support,
+ * which means heartbeats checked only during blocking calls.
+ *
+ * TLS support (see https://www.rabbitmq.com/ssl.html for details):
+ * * cacert: Path to the CA cert file in PEM format.
+ * * cert: Path to the client certificate in PEM format.
+ * * key: Path to the client key in PEM format.
+ * * verify: Enable or disable peer verification. If peer verification is enabled then the common name in the
+ * server certificate must match the server name. Peer verification is enabled by default.
+ */
+ public static function fromDsn(string $dsn, array $options = [], AmqpFactory $amqpFactory = null): self
+ {
+ if (false === $parsedUrl = parse_url($dsn)) {
+ // this is a valid URI that parse_url cannot handle when you want to pass all parameters as options
+ if (!\in_array($dsn, ['amqp://', 'amqps://'])) {
+ throw new InvalidArgumentException(sprintf('The given AMQP DSN "%s" is invalid.', $dsn));
+ }
+
+ $parsedUrl = [];
+ }
+
+ $useAmqps = 0 === strpos($dsn, 'amqps://');
+ $pathParts = isset($parsedUrl['path']) ? explode('/', trim($parsedUrl['path'], '/')) : [];
+ $exchangeName = $pathParts[1] ?? 'messages';
+ parse_str($parsedUrl['query'] ?? '', $parsedQuery);
+ $port = $useAmqps ? 5671 : 5672;
+
+ $amqpOptions = array_replace_recursive([
+ 'host' => $parsedUrl['host'] ?? 'localhost',
+ 'port' => $parsedUrl['port'] ?? $port,
+ 'vhost' => isset($pathParts[0]) ? urldecode($pathParts[0]) : '/',
+ 'exchange' => [
+ 'name' => $exchangeName,
+ ],
+ ], $options, $parsedQuery);
+
+ self::validateOptions($amqpOptions);
+
+ if (isset($parsedUrl['user'])) {
+ $amqpOptions['login'] = urldecode($parsedUrl['user']);
+ }
+
+ if (isset($parsedUrl['pass'])) {
+ $amqpOptions['password'] = urldecode($parsedUrl['pass']);
+ }
+
+ if (!isset($amqpOptions['queues'])) {
+ $amqpOptions['queues'][$exchangeName] = [];
+ }
+
+ $exchangeOptions = $amqpOptions['exchange'];
+ $queuesOptions = $amqpOptions['queues'];
+ unset($amqpOptions['queues'], $amqpOptions['exchange']);
+ if (isset($amqpOptions['auto_setup'])) {
+ $amqpOptions['auto_setup'] = filter_var($amqpOptions['auto_setup'], \FILTER_VALIDATE_BOOLEAN);
+ }
+
+ $queuesOptions = array_map(function ($queueOptions) {
+ if (!\is_array($queueOptions)) {
+ $queueOptions = [];
+ }
+ if (\is_array($queueOptions['arguments'] ?? false)) {
+ $queueOptions['arguments'] = self::normalizeQueueArguments($queueOptions['arguments']);
+ }
+
+ return $queueOptions;
+ }, $queuesOptions);
+
+ if (!$useAmqps) {
+ unset($amqpOptions['cacert'], $amqpOptions['cert'], $amqpOptions['key'], $amqpOptions['verify']);
+ }
+
+ if ($useAmqps && !self::hasCaCertConfigured($amqpOptions)) {
+ throw new InvalidArgumentException('No CA certificate has been provided. Set "amqp.cacert" in your php.ini or pass the "cacert" parameter in the DSN to use SSL. Alternatively, you can use amqp:// to use without SSL.');
+ }
+
+ return new self($amqpOptions, $exchangeOptions, $queuesOptions, $amqpFactory);
+ }
+
+ private static function validateOptions(array $options): void
+ {
+ if (0 < \count($invalidOptions = array_diff(array_keys($options), self::AVAILABLE_OPTIONS))) {
+ trigger_deprecation('symfony/messenger', '5.1', 'Invalid option(s) "%s" passed to the AMQP Messenger transport. Passing invalid options is deprecated.', implode('", "', $invalidOptions));
+ }
+
+ if (isset($options['prefetch_count'])) {
+ trigger_deprecation('symfony/messenger', '5.3', 'The "prefetch_count" option passed to the AMQP Messenger transport has no effect and should not be used.');
+ }
+
+ if (\is_array($options['queues'] ?? false)) {
+ foreach ($options['queues'] as $queue) {
+ if (!\is_array($queue)) {
+ continue;
+ }
+
+ if (0 < \count($invalidQueueOptions = array_diff(array_keys($queue), self::AVAILABLE_QUEUE_OPTIONS))) {
+ trigger_deprecation('symfony/messenger', '5.1', 'Invalid queue option(s) "%s" passed to the AMQP Messenger transport. Passing invalid queue options is deprecated.', implode('", "', $invalidQueueOptions));
+ }
+ }
+ }
+
+ if (\is_array($options['exchange'] ?? false)
+ && 0 < \count($invalidExchangeOptions = array_diff(array_keys($options['exchange']), self::AVAILABLE_EXCHANGE_OPTIONS))) {
+ trigger_deprecation('symfony/messenger', '5.1', 'Invalid exchange option(s) "%s" passed to the AMQP Messenger transport. Passing invalid exchange options is deprecated.', implode('", "', $invalidExchangeOptions));
+ }
+ }
+
+ private static function normalizeQueueArguments(array $arguments): array
+ {
+ foreach (self::ARGUMENTS_AS_INTEGER as $key) {
+ if (!\array_key_exists($key, $arguments)) {
+ continue;
+ }
+
+ if (!is_numeric($arguments[$key])) {
+ throw new InvalidArgumentException(sprintf('Integer expected for queue argument "%s", "%s" given.', $key, get_debug_type($arguments[$key])));
+ }
+
+ $arguments[$key] = (int) $arguments[$key];
+ }
+
+ return $arguments;
+ }
+
+ private static function hasCaCertConfigured(array $amqpOptions): bool
+ {
+ return (isset($amqpOptions['cacert']) && '' !== $amqpOptions['cacert']) || '' !== \ini_get('amqp.cacert');
+ }
+
+ /**
+ * @throws \AMQPException
+ */
+ public function publish(string $body, array $headers = [], int $delayInMs = 0, AmqpStamp $amqpStamp = null): void
+ {
+ $this->clearWhenDisconnected();
+
+ if ($this->autoSetupExchange) {
+ $this->setupExchangeAndQueues(); // also setup normal exchange for delayed messages so delay queue can DLX messages to it
+ }
+
+ if (0 !== $delayInMs) {
+ $this->publishWithDelay($body, $headers, $delayInMs, $amqpStamp);
+
+ return;
+ }
+
+ $this->publishOnExchange(
+ $this->exchange(),
+ $body,
+ $this->getRoutingKeyForMessage($amqpStamp),
+ $headers,
+ $amqpStamp
+ );
+ }
+
+ /**
+ * Returns an approximate count of the messages in defined queues.
+ */
+ public function countMessagesInQueues(): int
+ {
+ return array_sum(array_map(function ($queueName) {
+ return $this->queue($queueName)->declareQueue();
+ }, $this->getQueueNames()));
+ }
+
+ /**
+ * @throws \AMQPException
+ */
+ private function publishWithDelay(string $body, array $headers, int $delay, AmqpStamp $amqpStamp = null)
+ {
+ $routingKey = $this->getRoutingKeyForMessage($amqpStamp);
+ $isRetryAttempt = $amqpStamp ? $amqpStamp->isRetryAttempt() : false;
+
+ $this->setupDelay($delay, $routingKey, $isRetryAttempt);
+
+ $this->publishOnExchange(
+ $this->getDelayExchange(),
+ $body,
+ $this->getRoutingKeyForDelay($delay, $routingKey, $isRetryAttempt),
+ $headers,
+ $amqpStamp
+ );
+ }
+
+ private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $headers = [], AmqpStamp $amqpStamp = null)
+ {
+ $attributes = $amqpStamp ? $amqpStamp->getAttributes() : [];
+ $attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers);
+ $attributes['delivery_mode'] = $attributes['delivery_mode'] ?? 2;
+ $attributes['timestamp'] = $attributes['timestamp'] ?? time();
+
+ $exchange->publish(
+ $body,
+ $routingKey,
+ $amqpStamp ? $amqpStamp->getFlags() : \AMQP_NOPARAM,
+ $attributes
+ );
+
+ if ('' !== ($this->connectionOptions['confirm_timeout'] ?? '')) {
+ $this->channel()->waitForConfirm((float) $this->connectionOptions['confirm_timeout']);
+ }
+ }
+
+ private function setupDelay(int $delay, ?string $routingKey, bool $isRetryAttempt)
+ {
+ if ($this->autoSetupDelayExchange) {
+ $this->setupDelayExchange();
+ }
+
+ $queue = $this->createDelayQueue($delay, $routingKey, $isRetryAttempt);
+ $queue->declareQueue(); // the delay queue always need to be declared because the name is dynamic and cannot be declared in advance
+ $queue->bind($this->connectionOptions['delay']['exchange_name'], $this->getRoutingKeyForDelay($delay, $routingKey, $isRetryAttempt));
+ }
+
+ private function getDelayExchange(): \AMQPExchange
+ {
+ if (null === $this->amqpDelayExchange) {
+ $this->amqpDelayExchange = $this->amqpFactory->createExchange($this->channel());
+ $this->amqpDelayExchange->setName($this->connectionOptions['delay']['exchange_name']);
+ $this->amqpDelayExchange->setType(\AMQP_EX_TYPE_DIRECT);
+ $this->amqpDelayExchange->setFlags(\AMQP_DURABLE);
+ }
+
+ return $this->amqpDelayExchange;
+ }
+
+ /**
+ * Creates a delay queue that will delay for a certain amount of time.
+ *
+ * This works by setting message TTL for the delay and pointing
+ * the dead letter exchange to the original exchange. The result
+ * is that after the TTL, the message is sent to the dead-letter-exchange,
+ * which is the original exchange, resulting on it being put back into
+ * the original queue.
+ */
+ private function createDelayQueue(int $delay, ?string $routingKey, bool $isRetryAttempt): \AMQPQueue
+ {
+ $queue = $this->amqpFactory->createQueue($this->channel());
+ $queue->setName($this->getRoutingKeyForDelay($delay, $routingKey, $isRetryAttempt));
+ $queue->setFlags(\AMQP_DURABLE);
+ $queue->setArguments([
+ 'x-message-ttl' => $delay,
+ // delete the delay queue 10 seconds after the message expires
+ // publishing another message redeclares the queue which renews the lease
+ 'x-expires' => $delay + 10000,
+ // message should be broadcasted to all consumers during delay, but to only one queue during retry
+ // empty name is default direct exchange
+ 'x-dead-letter-exchange' => $isRetryAttempt ? '' : $this->exchangeOptions['name'],
+ // after being released from to DLX, make sure the original routing key will be used
+ // we must use an empty string instead of null for the argument to be picked up
+ 'x-dead-letter-routing-key' => $routingKey ?? '',
+ ]);
+
+ return $queue;
+ }
+
+ private function getRoutingKeyForDelay(int $delay, ?string $finalRoutingKey, bool $isRetryAttempt): string
+ {
+ $action = $isRetryAttempt ? '_retry' : '_delay';
+
+ return str_replace(
+ ['%delay%', '%exchange_name%', '%routing_key%'],
+ [$delay, $this->exchangeOptions['name'], $finalRoutingKey ?? ''],
+ $this->connectionOptions['delay']['queue_name_pattern']
+ ).$action;
+ }
+
+ /**
+ * Gets a message from the specified queue.
+ *
+ * @throws \AMQPException
+ */
+ public function get(string $queueName): ?\AMQPEnvelope
+ {
+ $this->clearWhenDisconnected();
+
+ if ($this->autoSetupExchange) {
+ $this->setupExchangeAndQueues();
+ }
+
+ if (false !== $message = $this->queue($queueName)->get()) {
+ return $message;
+ }
+
+ return null;
+ }
+
+ public function ack(\AMQPEnvelope $message, string $queueName): bool
+ {
+ return $this->queue($queueName)->ack($message->getDeliveryTag());
+ }
+
+ public function nack(\AMQPEnvelope $message, string $queueName, int $flags = \AMQP_NOPARAM): bool
+ {
+ return $this->queue($queueName)->nack($message->getDeliveryTag(), $flags);
+ }
+
+ public function setup(): void
+ {
+ $this->setupExchangeAndQueues();
+ $this->setupDelayExchange();
+ }
+
+ private function setupExchangeAndQueues(): void
+ {
+ $this->exchange()->declareExchange();
+
+ foreach ($this->queuesOptions as $queueName => $queueConfig) {
+ $this->queue($queueName)->declareQueue();
+ foreach ($queueConfig['binding_keys'] ?? [null] as $bindingKey) {
+ $this->queue($queueName)->bind($this->exchangeOptions['name'], $bindingKey, $queueConfig['binding_arguments'] ?? []);
+ }
+ }
+ $this->autoSetupExchange = false;
+ }
+
+ private function setupDelayExchange(): void
+ {
+ $this->getDelayExchange()->declareExchange();
+ $this->autoSetupDelayExchange = false;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getQueueNames(): array
+ {
+ return array_keys($this->queuesOptions);
+ }
+
+ public function channel(): \AMQPChannel
+ {
+ if (null === $this->amqpChannel) {
+ $connection = $this->amqpFactory->createConnection($this->connectionOptions);
+ $connectMethod = 'true' === ($this->connectionOptions['persistent'] ?? 'false') ? 'pconnect' : 'connect';
+
+ try {
+ $connection->{$connectMethod}();
+ } catch (\AMQPConnectionException $e) {
+ throw new \AMQPException('Could not connect to the AMQP server. Please verify the provided DSN.', 0, $e);
+ }
+ $this->amqpChannel = $this->amqpFactory->createChannel($connection);
+
+ if ('' !== ($this->connectionOptions['confirm_timeout'] ?? '')) {
+ $this->amqpChannel->confirmSelect();
+ $this->amqpChannel->setConfirmCallback(
+ static function (): bool {
+ return false;
+ },
+ static function (): bool {
+ return false;
+ }
+ );
+ }
+ }
+
+ return $this->amqpChannel;
+ }
+
+ public function queue(string $queueName): \AMQPQueue
+ {
+ if (!isset($this->amqpQueues[$queueName])) {
+ $queueConfig = $this->queuesOptions[$queueName];
+
+ $amqpQueue = $this->amqpFactory->createQueue($this->channel());
+ $amqpQueue->setName($queueName);
+ $amqpQueue->setFlags($queueConfig['flags'] ?? \AMQP_DURABLE);
+
+ if (isset($queueConfig['arguments'])) {
+ $amqpQueue->setArguments($queueConfig['arguments']);
+ }
+
+ $this->amqpQueues[$queueName] = $amqpQueue;
+ }
+
+ return $this->amqpQueues[$queueName];
+ }
+
+ public function exchange(): \AMQPExchange
+ {
+ if (null === $this->amqpExchange) {
+ $this->amqpExchange = $this->amqpFactory->createExchange($this->channel());
+ $this->amqpExchange->setName($this->exchangeOptions['name']);
+ $this->amqpExchange->setType($this->exchangeOptions['type'] ?? \AMQP_EX_TYPE_FANOUT);
+ $this->amqpExchange->setFlags($this->exchangeOptions['flags'] ?? \AMQP_DURABLE);
+
+ if (isset($this->exchangeOptions['arguments'])) {
+ $this->amqpExchange->setArguments($this->exchangeOptions['arguments']);
+ }
+ }
+
+ return $this->amqpExchange;
+ }
+
+ private function clearWhenDisconnected(): void
+ {
+ if (!$this->channel()->isConnected()) {
+ $this->amqpChannel = null;
+ $this->amqpQueues = [];
+ $this->amqpExchange = null;
+ $this->amqpDelayExchange = null;
+ }
+ }
+
+ private function getDefaultPublishRoutingKey(): ?string
+ {
+ return $this->exchangeOptions['default_publish_routing_key'] ?? null;
+ }
+
+ public function purgeQueues()
+ {
+ foreach ($this->getQueueNames() as $queueName) {
+ $this->queue($queueName)->purge();
+ }
+ }
+
+ private function getRoutingKeyForMessage(?AmqpStamp $amqpStamp): ?string
+ {
+ return (null !== $amqpStamp ? $amqpStamp->getRoutingKey() : null) ?? $this->getDefaultPublishRoutingKey();
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\Connection::class, false)) {
+ class_alias(Connection::class, \Symfony\Component\Messenger\Transport\AmqpExt\Connection::class);
+}
diff --git a/plugins/email/vendor/symfony/amqp-messenger/composer.json b/plugins/email/vendor/symfony/amqp-messenger/composer.json
new file mode 100644
index 0000000..dd0ecf9
--- /dev/null
+++ b/plugins/email/vendor/symfony/amqp-messenger/composer.json
@@ -0,0 +1,36 @@
+{
+ "name": "symfony/amqp-messenger",
+ "type": "symfony-messenger-bridge",
+ "description": "Symfony AMQP extension Messenger Bridge",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/messenger": "^5.3|^6.0"
+ },
+ "require-dev": {
+ "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
+ "symfony/process": "^4.4|^5.0|^6.0",
+ "symfony/property-access": "^4.4|^5.0|^6.0",
+ "symfony/serializer": "^4.4|^5.0|^6.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Amqp\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/plugins/email/vendor/symfony/deprecation-contracts/.gitignore b/plugins/email/vendor/symfony/deprecation-contracts/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/plugins/email/vendor/symfony/deprecation-contracts/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/plugins/email/vendor/symfony/deprecation-contracts/CHANGELOG.md b/plugins/email/vendor/symfony/deprecation-contracts/CHANGELOG.md
new file mode 100644
index 0000000..7932e26
--- /dev/null
+++ b/plugins/email/vendor/symfony/deprecation-contracts/CHANGELOG.md
@@ -0,0 +1,5 @@
+CHANGELOG
+=========
+
+The changelog is maintained for all Symfony contracts at the following URL:
+https://github.com/symfony/contracts/blob/main/CHANGELOG.md
diff --git a/plugins/email/vendor/symfony/deprecation-contracts/LICENSE b/plugins/email/vendor/symfony/deprecation-contracts/LICENSE
new file mode 100644
index 0000000..406242f
--- /dev/null
+++ b/plugins/email/vendor/symfony/deprecation-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2020-2022 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugins/email/vendor/symfony/deprecation-contracts/README.md b/plugins/email/vendor/symfony/deprecation-contracts/README.md
new file mode 100644
index 0000000..4957933
--- /dev/null
+++ b/plugins/email/vendor/symfony/deprecation-contracts/README.md
@@ -0,0 +1,26 @@
+Symfony Deprecation Contracts
+=============================
+
+A generic function and convention to trigger deprecation notices.
+
+This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.
+
+By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
+the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.
+
+The function requires at least 3 arguments:
+ - the name of the Composer package that is triggering the deprecation
+ - the version of the package that introduced the deprecation
+ - the message of the deprecation
+ - more arguments can be provided: they will be inserted in the message using `printf()` formatting
+
+Example:
+```php
+trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
+```
+
+This will generate the following message:
+`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
+
+While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
+`function trigger_deprecation() {}` in your application.
diff --git a/plugins/email/vendor/symfony/deprecation-contracts/composer.json b/plugins/email/vendor/symfony/deprecation-contracts/composer.json
new file mode 100644
index 0000000..cc7cc12
--- /dev/null
+++ b/plugins/email/vendor/symfony/deprecation-contracts/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "symfony/deprecation-contracts",
+ "type": "library",
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.1"
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/deprecation-contracts/function.php b/plugins/email/vendor/symfony/deprecation-contracts/function.php
new file mode 100644
index 0000000..d437150
--- /dev/null
+++ b/plugins/email/vendor/symfony/deprecation-contracts/function.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (!function_exists('trigger_deprecation')) {
+ /**
+ * Triggers a silenced deprecation notice.
+ *
+ * @param string $package The name of the Composer package that is triggering the deprecation
+ * @param string $version The version of the package that introduced the deprecation
+ * @param string $message The message of the deprecation
+ * @param mixed ...$args Values to insert in the message using printf() formatting
+ *
+ * @author Nicolas Grekas
+ */
+ function trigger_deprecation(string $package, string $version, string $message, ...$args): void
+ {
+ @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
+ }
+}
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/CHANGELOG.md b/plugins/email/vendor/symfony/doctrine-messenger/CHANGELOG.md
new file mode 100644
index 0000000..aaed248
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/CHANGELOG.md
@@ -0,0 +1,8 @@
+CHANGELOG
+=========
+
+5.1.0
+-----
+
+ * Introduced the Doctrine bridge.
+ * Added support for PostgreSQL `LISTEN`/`NOTIFY`.
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/LICENSE b/plugins/email/vendor/symfony/doctrine-messenger/LICENSE
new file mode 100644
index 0000000..74cdc2d
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2022 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/README.md b/plugins/email/vendor/symfony/doctrine-messenger/README.md
new file mode 100644
index 0000000..d29c9c3
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/README.md
@@ -0,0 +1,12 @@
+Doctrine Messenger
+==================
+
+Provides Doctrine integration for Symfony Messenger.
+
+Resources
+---------
+
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/Transport/Connection.php b/plugins/email/vendor/symfony/doctrine-messenger/Transport/Connection.php
new file mode 100644
index 0000000..b760df6
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/Transport/Connection.php
@@ -0,0 +1,508 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\Connection as DBALConnection;
+use Doctrine\DBAL\Driver\Exception as DriverException;
+use Doctrine\DBAL\Driver\Result as DriverResult;
+use Doctrine\DBAL\Exception as DBALException;
+use Doctrine\DBAL\Exception\TableNotFoundException;
+use Doctrine\DBAL\LockMode;
+use Doctrine\DBAL\Platforms\MySQLPlatform;
+use Doctrine\DBAL\Platforms\OraclePlatform;
+use Doctrine\DBAL\Query\QueryBuilder;
+use Doctrine\DBAL\Result;
+use Doctrine\DBAL\Schema\AbstractSchemaManager;
+use Doctrine\DBAL\Schema\Comparator;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\SchemaDiff;
+use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
+use Doctrine\DBAL\Schema\Table;
+use Doctrine\DBAL\Types\Types;
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Exception\TransportException;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @internal since Symfony 5.1
+ *
+ * @author Vincent Touzet
+ * @author Kévin Dunglas
+ */
+class Connection implements ResetInterface
+{
+ protected const TABLE_OPTION_NAME = '_symfony_messenger_table_name';
+
+ protected const DEFAULT_OPTIONS = [
+ 'table_name' => 'messenger_messages',
+ 'queue_name' => 'default',
+ 'redeliver_timeout' => 3600,
+ 'auto_setup' => true,
+ ];
+
+ /**
+ * Configuration of the connection.
+ *
+ * Available options:
+ *
+ * * table_name: name of the table
+ * * connection: name of the Doctrine's entity manager
+ * * queue_name: name of the queue
+ * * redeliver_timeout: Timeout before redeliver messages still in handling state (i.e: delivered_at is not null and message is still in table). Default: 3600
+ * * auto_setup: Whether the table should be created automatically during send / get. Default: true
+ */
+ protected $configuration = [];
+ protected $driverConnection;
+ protected $queueEmptiedAt;
+ private $schemaSynchronizer;
+ private $autoSetup;
+
+ public function __construct(array $configuration, DBALConnection $driverConnection, SchemaSynchronizer $schemaSynchronizer = null)
+ {
+ $this->configuration = array_replace_recursive(static::DEFAULT_OPTIONS, $configuration);
+ $this->driverConnection = $driverConnection;
+ $this->schemaSynchronizer = $schemaSynchronizer;
+ $this->autoSetup = $this->configuration['auto_setup'];
+ }
+
+ public function reset()
+ {
+ $this->queueEmptiedAt = null;
+ }
+
+ public function getConfiguration(): array
+ {
+ return $this->configuration;
+ }
+
+ public static function buildConfiguration(string $dsn, array $options = []): array
+ {
+ if (false === $components = parse_url($dsn)) {
+ throw new InvalidArgumentException(sprintf('The given Doctrine Messenger DSN "%s" is invalid.', $dsn));
+ }
+
+ $query = [];
+ if (isset($components['query'])) {
+ parse_str($components['query'], $query);
+ }
+
+ $configuration = ['connection' => $components['host']];
+ $configuration += $query + $options + static::DEFAULT_OPTIONS;
+
+ $configuration['auto_setup'] = filter_var($configuration['auto_setup'], \FILTER_VALIDATE_BOOLEAN);
+
+ // check for extra keys in options
+ $optionsExtraKeys = array_diff(array_keys($options), array_keys(static::DEFAULT_OPTIONS));
+ if (0 < \count($optionsExtraKeys)) {
+ throw new InvalidArgumentException(sprintf('Unknown option found: [%s]. Allowed options are [%s].', implode(', ', $optionsExtraKeys), implode(', ', array_keys(static::DEFAULT_OPTIONS))));
+ }
+
+ // check for extra keys in options
+ $queryExtraKeys = array_diff(array_keys($query), array_keys(static::DEFAULT_OPTIONS));
+ if (0 < \count($queryExtraKeys)) {
+ throw new InvalidArgumentException(sprintf('Unknown option found in DSN: [%s]. Allowed options are [%s].', implode(', ', $queryExtraKeys), implode(', ', array_keys(static::DEFAULT_OPTIONS))));
+ }
+
+ return $configuration;
+ }
+
+ /**
+ * @param int $delay The delay in milliseconds
+ *
+ * @return string The inserted id
+ *
+ * @throws DBALException
+ */
+ public function send(string $body, array $headers, int $delay = 0): string
+ {
+ $now = new \DateTime();
+ $availableAt = (clone $now)->modify(sprintf('+%d seconds', $delay / 1000));
+
+ $queryBuilder = $this->driverConnection->createQueryBuilder()
+ ->insert($this->configuration['table_name'])
+ ->values([
+ 'body' => '?',
+ 'headers' => '?',
+ 'queue_name' => '?',
+ 'created_at' => '?',
+ 'available_at' => '?',
+ ]);
+
+ $this->executeStatement($queryBuilder->getSQL(), [
+ $body,
+ json_encode($headers),
+ $this->configuration['queue_name'],
+ $now,
+ $availableAt,
+ ], [
+ null,
+ null,
+ null,
+ Types::DATETIME_MUTABLE,
+ Types::DATETIME_MUTABLE,
+ ]);
+
+ return $this->driverConnection->lastInsertId();
+ }
+
+ public function get(): ?array
+ {
+ if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) {
+ try {
+ $this->driverConnection->delete($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59']);
+ } catch (DriverException $e) {
+ // Ignore the exception
+ }
+ }
+
+ get:
+ $this->driverConnection->beginTransaction();
+ try {
+ $query = $this->createAvailableMessagesQueryBuilder()
+ ->orderBy('available_at', 'ASC')
+ ->setMaxResults(1);
+
+ // Append pessimistic write lock to FROM clause if db platform supports it
+ $sql = $query->getSQL();
+ if (($fromPart = $query->getQueryPart('from')) &&
+ ($table = $fromPart[0]['table'] ?? null) &&
+ ($alias = $fromPart[0]['alias'] ?? null)
+ ) {
+ $fromClause = sprintf('%s %s', $table, $alias);
+ $sql = str_replace(
+ sprintf('FROM %s WHERE', $fromClause),
+ sprintf('FROM %s WHERE', $this->driverConnection->getDatabasePlatform()->appendLockHint($fromClause, LockMode::PESSIMISTIC_WRITE)),
+ $sql
+ );
+ }
+
+ // Wrap the rownum query in a sub-query to allow writelocks without ORA-02014 error
+ if ($this->driverConnection->getDatabasePlatform() instanceof OraclePlatform) {
+ $sql = str_replace('SELECT a.* FROM', 'SELECT a.id FROM', $sql);
+
+ $wrappedQuery = $this->driverConnection->createQueryBuilder()
+ ->select(
+ 'w.id AS "id", w.body AS "body", w.headers AS "headers", w.queue_name AS "queue_name", '.
+ 'w.created_at AS "created_at", w.available_at AS "available_at", '.
+ 'w.delivered_at AS "delivered_at"'
+ )
+ ->from($this->configuration['table_name'], 'w')
+ ->where('w.id IN('.$sql.')');
+
+ $sql = $wrappedQuery->getSQL();
+ }
+
+ // use SELECT ... FOR UPDATE to lock table
+ $stmt = $this->executeQuery(
+ $sql.' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(),
+ $query->getParameters(),
+ $query->getParameterTypes()
+ );
+ $doctrineEnvelope = $stmt instanceof Result || $stmt instanceof DriverResult ? $stmt->fetchAssociative() : $stmt->fetch();
+
+ if (false === $doctrineEnvelope) {
+ $this->driverConnection->commit();
+ $this->queueEmptiedAt = microtime(true) * 1000;
+
+ return null;
+ }
+ // Postgres can "group" notifications having the same channel and payload
+ // We need to be sure to empty the queue before blocking again
+ $this->queueEmptiedAt = null;
+
+ $doctrineEnvelope = $this->decodeEnvelopeHeaders($doctrineEnvelope);
+
+ $queryBuilder = $this->driverConnection->createQueryBuilder()
+ ->update($this->configuration['table_name'])
+ ->set('delivered_at', '?')
+ ->where('id = ?');
+ $now = new \DateTime();
+ $this->executeStatement($queryBuilder->getSQL(), [
+ $now,
+ $doctrineEnvelope['id'],
+ ], [
+ Types::DATETIME_MUTABLE,
+ ]);
+
+ $this->driverConnection->commit();
+
+ return $doctrineEnvelope;
+ } catch (\Throwable $e) {
+ $this->driverConnection->rollBack();
+
+ if ($this->autoSetup && $e instanceof TableNotFoundException) {
+ $this->setup();
+ goto get;
+ }
+
+ throw $e;
+ }
+ }
+
+ public function ack(string $id): bool
+ {
+ try {
+ if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) {
+ return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59'], ['id' => $id]) > 0;
+ }
+
+ return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0;
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ public function reject(string $id): bool
+ {
+ try {
+ if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) {
+ return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59'], ['id' => $id]) > 0;
+ }
+
+ return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0;
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ public function setup(): void
+ {
+ $configuration = $this->driverConnection->getConfiguration();
+ $assetFilter = $configuration->getSchemaAssetsFilter();
+ $configuration->setSchemaAssetsFilter(null);
+ $this->updateSchema();
+ $configuration->setSchemaAssetsFilter($assetFilter);
+ $this->autoSetup = false;
+ }
+
+ public function getMessageCount(): int
+ {
+ $queryBuilder = $this->createAvailableMessagesQueryBuilder()
+ ->select('COUNT(m.id) as message_count')
+ ->setMaxResults(1);
+
+ $stmt = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes());
+
+ return $stmt instanceof Result || $stmt instanceof DriverResult ? $stmt->fetchOne() : $stmt->fetchColumn();
+ }
+
+ public function findAll(int $limit = null): array
+ {
+ $queryBuilder = $this->createAvailableMessagesQueryBuilder();
+ if (null !== $limit) {
+ $queryBuilder->setMaxResults($limit);
+ }
+
+ $stmt = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes());
+ $data = $stmt instanceof Result || $stmt instanceof DriverResult ? $stmt->fetchAllAssociative() : $stmt->fetchAll();
+
+ return array_map(function ($doctrineEnvelope) {
+ return $this->decodeEnvelopeHeaders($doctrineEnvelope);
+ }, $data);
+ }
+
+ public function find($id): ?array
+ {
+ $queryBuilder = $this->createQueryBuilder()
+ ->where('m.id = ? and m.queue_name = ?');
+
+ $stmt = $this->executeQuery($queryBuilder->getSQL(), [$id, $this->configuration['queue_name']]);
+ $data = $stmt instanceof Result || $stmt instanceof DriverResult ? $stmt->fetchAssociative() : $stmt->fetch();
+
+ return false === $data ? null : $this->decodeEnvelopeHeaders($data);
+ }
+
+ /**
+ * @internal
+ */
+ public function configureSchema(Schema $schema, DBALConnection $forConnection): void
+ {
+ // only update the schema for this connection
+ if ($forConnection !== $this->driverConnection) {
+ return;
+ }
+
+ if ($schema->hasTable($this->configuration['table_name'])) {
+ return;
+ }
+
+ $this->addTableToSchema($schema);
+ }
+
+ /**
+ * @internal
+ */
+ public function getExtraSetupSqlForTable(Table $createdTable): array
+ {
+ return [];
+ }
+
+ private function createAvailableMessagesQueryBuilder(): QueryBuilder
+ {
+ $now = new \DateTime();
+ $redeliverLimit = (clone $now)->modify(sprintf('-%d seconds', $this->configuration['redeliver_timeout']));
+
+ return $this->createQueryBuilder()
+ ->where('m.delivered_at is null OR m.delivered_at < ?')
+ ->andWhere('m.available_at <= ?')
+ ->andWhere('m.queue_name = ?')
+ ->setParameters([
+ $redeliverLimit,
+ $now,
+ $this->configuration['queue_name'],
+ ], [
+ Types::DATETIME_MUTABLE,
+ Types::DATETIME_MUTABLE,
+ ]);
+ }
+
+ private function createQueryBuilder(): QueryBuilder
+ {
+ return $this->driverConnection->createQueryBuilder()
+ ->select('m.*')
+ ->from($this->configuration['table_name'], 'm');
+ }
+
+ private function executeQuery(string $sql, array $parameters = [], array $types = [])
+ {
+ try {
+ $stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
+ } catch (TableNotFoundException $e) {
+ if ($this->driverConnection->isTransactionActive()) {
+ throw $e;
+ }
+
+ // create table
+ if ($this->autoSetup) {
+ $this->setup();
+ }
+ $stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
+ }
+
+ return $stmt;
+ }
+
+ protected function executeStatement(string $sql, array $parameters = [], array $types = [])
+ {
+ try {
+ if (method_exists($this->driverConnection, 'executeStatement')) {
+ $stmt = $this->driverConnection->executeStatement($sql, $parameters, $types);
+ } else {
+ $stmt = $this->driverConnection->executeUpdate($sql, $parameters, $types);
+ }
+ } catch (TableNotFoundException $e) {
+ if ($this->driverConnection->isTransactionActive()) {
+ throw $e;
+ }
+
+ // create table
+ if ($this->autoSetup) {
+ $this->setup();
+ }
+ if (method_exists($this->driverConnection, 'executeStatement')) {
+ $stmt = $this->driverConnection->executeStatement($sql, $parameters, $types);
+ } else {
+ $stmt = $this->driverConnection->executeUpdate($sql, $parameters, $types);
+ }
+ }
+
+ return $stmt;
+ }
+
+ private function getSchema(): Schema
+ {
+ $schema = new Schema([], [], $this->createSchemaManager()->createSchemaConfig());
+ $this->addTableToSchema($schema);
+
+ return $schema;
+ }
+
+ private function addTableToSchema(Schema $schema): void
+ {
+ $table = $schema->createTable($this->configuration['table_name']);
+ // add an internal option to mark that we created this & the non-namespaced table name
+ $table->addOption(self::TABLE_OPTION_NAME, $this->configuration['table_name']);
+ $table->addColumn('id', Types::BIGINT)
+ ->setAutoincrement(true)
+ ->setNotnull(true);
+ $table->addColumn('body', Types::TEXT)
+ ->setNotnull(true);
+ $table->addColumn('headers', Types::TEXT)
+ ->setNotnull(true);
+ $table->addColumn('queue_name', Types::STRING)
+ ->setLength(190) // MySQL 5.6 only supports 191 characters on an indexed column in utf8mb4 mode
+ ->setNotnull(true);
+ $table->addColumn('created_at', Types::DATETIME_MUTABLE)
+ ->setNotnull(true);
+ $table->addColumn('available_at', Types::DATETIME_MUTABLE)
+ ->setNotnull(true);
+ $table->addColumn('delivered_at', Types::DATETIME_MUTABLE)
+ ->setNotnull(false);
+ $table->setPrimaryKey(['id']);
+ $table->addIndex(['queue_name']);
+ $table->addIndex(['available_at']);
+ $table->addIndex(['delivered_at']);
+ }
+
+ private function decodeEnvelopeHeaders(array $doctrineEnvelope): array
+ {
+ $doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true);
+
+ return $doctrineEnvelope;
+ }
+
+ private function updateSchema(): void
+ {
+ if (null !== $this->schemaSynchronizer) {
+ $this->schemaSynchronizer->updateSchema($this->getSchema(), true);
+
+ return;
+ }
+
+ $schemaManager = $this->createSchemaManager();
+ $comparator = $this->createComparator($schemaManager);
+ $schemaDiff = $this->compareSchemas($comparator, $schemaManager->createSchema(), $this->getSchema());
+
+ foreach ($schemaDiff->toSaveSql($this->driverConnection->getDatabasePlatform()) as $sql) {
+ if (method_exists($this->driverConnection, 'executeStatement')) {
+ $this->driverConnection->executeStatement($sql);
+ } else {
+ $this->driverConnection->exec($sql);
+ }
+ }
+ }
+
+ private function createSchemaManager(): AbstractSchemaManager
+ {
+ return method_exists($this->driverConnection, 'createSchemaManager')
+ ? $this->driverConnection->createSchemaManager()
+ : $this->driverConnection->getSchemaManager();
+ }
+
+ private function createComparator(AbstractSchemaManager $schemaManager): Comparator
+ {
+ return method_exists($schemaManager, 'createComparator')
+ ? $schemaManager->createComparator()
+ : new Comparator();
+ }
+
+ private function compareSchemas(Comparator $comparator, Schema $from, Schema $to): SchemaDiff
+ {
+ return method_exists($comparator, 'compareSchemas')
+ ? $comparator->compareSchemas($from, $to)
+ : $comparator->compare($from, $to);
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\Connection::class, false)) {
+ class_alias(Connection::class, \Symfony\Component\Messenger\Transport\Doctrine\Connection::class);
+}
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineReceivedStamp.php b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineReceivedStamp.php
new file mode 100644
index 0000000..7f0dee1
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineReceivedStamp.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineReceivedStamp implements NonSendableStampInterface
+{
+ private $id;
+
+ public function __construct(string $id)
+ {
+ $this->id = $id;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceivedStamp::class, false)) {
+ class_alias(DoctrineReceivedStamp::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceivedStamp::class);
+}
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineReceiver.php b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineReceiver.php
new file mode 100644
index 0000000..69cf0ed
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineReceiver.php
@@ -0,0 +1,175 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\Exception as DBALException;
+use Doctrine\DBAL\Exception\RetryableException;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
+use Symfony\Component\Messenger\Exception\TransportException;
+use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
+use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
+use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
+use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineReceiver implements ListableReceiverInterface, MessageCountAwareInterface
+{
+ private const MAX_RETRIES = 3;
+ private $retryingSafetyCounter = 0;
+ private $connection;
+ private $serializer;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer = null)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer ?? new PhpSerializer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ try {
+ $doctrineEnvelope = $this->connection->get();
+ $this->retryingSafetyCounter = 0; // reset counter
+ } catch (RetryableException $exception) {
+ // Do nothing when RetryableException occurs less than "MAX_RETRIES"
+ // as it will likely be resolved on the next call to get()
+ // Problem with concurrent consumers and database deadlocks
+ if (++$this->retryingSafetyCounter >= self::MAX_RETRIES) {
+ $this->retryingSafetyCounter = 0; // reset counter
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ return [];
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ if (null === $doctrineEnvelope) {
+ return [];
+ }
+
+ return [$this->createEnvelopeFromData($doctrineEnvelope)];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ try {
+ $this->connection->ack($this->findDoctrineReceivedStamp($envelope)->getId());
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ try {
+ $this->connection->reject($this->findDoctrineReceivedStamp($envelope)->getId());
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMessageCount(): int
+ {
+ try {
+ return $this->connection->getMessageCount();
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all(int $limit = null): iterable
+ {
+ try {
+ $doctrineEnvelopes = $this->connection->findAll($limit);
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ foreach ($doctrineEnvelopes as $doctrineEnvelope) {
+ yield $this->createEnvelopeFromData($doctrineEnvelope);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function find($id): ?Envelope
+ {
+ try {
+ $doctrineEnvelope = $this->connection->find($id);
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ if (null === $doctrineEnvelope) {
+ return null;
+ }
+
+ return $this->createEnvelopeFromData($doctrineEnvelope);
+ }
+
+ private function findDoctrineReceivedStamp(Envelope $envelope): DoctrineReceivedStamp
+ {
+ /** @var DoctrineReceivedStamp|null $doctrineReceivedStamp */
+ $doctrineReceivedStamp = $envelope->last(DoctrineReceivedStamp::class);
+
+ if (null === $doctrineReceivedStamp) {
+ throw new LogicException('No DoctrineReceivedStamp found on the Envelope.');
+ }
+
+ return $doctrineReceivedStamp;
+ }
+
+ private function createEnvelopeFromData(array $data): Envelope
+ {
+ try {
+ $envelope = $this->serializer->decode([
+ 'body' => $data['body'],
+ 'headers' => $data['headers'],
+ ]);
+ } catch (MessageDecodingFailedException $exception) {
+ $this->connection->reject($data['id']);
+
+ throw $exception;
+ }
+
+ return $envelope->with(
+ new DoctrineReceivedStamp($data['id']),
+ new TransportMessageIdStamp($data['id'])
+ );
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceiver::class, false)) {
+ class_alias(DoctrineReceiver::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceiver::class);
+}
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineSender.php b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineSender.php
new file mode 100644
index 0000000..6e5aa60
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineSender.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\Exception as DBALException;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\TransportException;
+use Symfony\Component\Messenger\Stamp\DelayStamp;
+use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
+use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
+use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineSender implements SenderInterface
+{
+ private $connection;
+ private $serializer;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer = null)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer ?? new PhpSerializer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ $encodedMessage = $this->serializer->encode($envelope);
+
+ /** @var DelayStamp|null $delayStamp */
+ $delayStamp = $envelope->last(DelayStamp::class);
+ $delay = null !== $delayStamp ? $delayStamp->getDelay() : 0;
+
+ try {
+ $id = $this->connection->send($encodedMessage['body'], $encodedMessage['headers'] ?? [], $delay);
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ return $envelope->with(new TransportMessageIdStamp($id));
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineSender::class, false)) {
+ class_alias(DoctrineSender::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineSender::class);
+}
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineTransport.php b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineTransport.php
new file mode 100644
index 0000000..97bc6fa
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineTransport.php
@@ -0,0 +1,135 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\Connection as DbalConnection;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\Table;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
+use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineTransport implements TransportInterface, SetupableTransportInterface, MessageCountAwareInterface, ListableReceiverInterface
+{
+ private $connection;
+ private $serializer;
+ private $receiver;
+ private $sender;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ return ($this->receiver ?? $this->getReceiver())->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->ack($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->reject($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMessageCount(): int
+ {
+ return ($this->receiver ?? $this->getReceiver())->getMessageCount();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all(int $limit = null): iterable
+ {
+ return ($this->receiver ?? $this->getReceiver())->all($limit);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function find($id): ?Envelope
+ {
+ return ($this->receiver ?? $this->getReceiver())->find($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ return ($this->sender ?? $this->getSender())->send($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setup(): void
+ {
+ $this->connection->setup();
+ }
+
+ /**
+ * Adds the Table to the Schema if this transport uses this connection.
+ */
+ public function configureSchema(Schema $schema, DbalConnection $forConnection): void
+ {
+ $this->connection->configureSchema($schema, $forConnection);
+ }
+
+ /**
+ * Adds extra SQL if the given table was created by the Connection.
+ *
+ * @return string[]
+ */
+ public function getExtraSetupSqlForTable(Table $createdTable): array
+ {
+ return $this->connection->getExtraSetupSqlForTable($createdTable);
+ }
+
+ private function getReceiver(): DoctrineReceiver
+ {
+ return $this->receiver = new DoctrineReceiver($this->connection, $this->serializer);
+ }
+
+ private function getSender(): DoctrineSender
+ {
+ return $this->sender = new DoctrineSender($this->connection, $this->serializer);
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport::class, false)) {
+ class_alias(DoctrineTransport::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport::class);
+}
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineTransportFactory.php b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineTransportFactory.php
new file mode 100644
index 0000000..f634e55
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/Transport/DoctrineTransportFactory.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
+use Doctrine\Persistence\ConnectionRegistry;
+use Symfony\Bridge\Doctrine\RegistryInterface;
+use Symfony\Component\Messenger\Exception\TransportException;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineTransportFactory implements TransportFactoryInterface
+{
+ private $registry;
+
+ public function __construct($registry)
+ {
+ if (!$registry instanceof RegistryInterface && !$registry instanceof ConnectionRegistry) {
+ throw new \TypeError(sprintf('Expected an instance of "%s" or "%s", but got "%s".', RegistryInterface::class, ConnectionRegistry::class, get_debug_type($registry)));
+ }
+
+ $this->registry = $registry;
+ }
+
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ $useNotify = ($options['use_notify'] ?? true);
+ unset($options['transport_name'], $options['use_notify']);
+ // Always allow PostgreSQL-specific keys, to be able to transparently fallback to the native driver when LISTEN/NOTIFY isn't available
+ $configuration = PostgreSqlConnection::buildConfiguration($dsn, $options);
+
+ try {
+ $driverConnection = $this->registry->getConnection($configuration['connection']);
+ } catch (\InvalidArgumentException $e) {
+ throw new TransportException(sprintf('Could not find Doctrine connection from Messenger DSN "%s".', $dsn), 0, $e);
+ }
+
+ if ($useNotify && $driverConnection->getDatabasePlatform() instanceof PostgreSQLPlatform) {
+ $connection = new PostgreSqlConnection($configuration, $driverConnection);
+ } else {
+ $connection = new Connection($configuration, $driverConnection);
+ }
+
+ return new DoctrineTransport($connection, $serializer);
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ return 0 === strpos($dsn, 'doctrine://');
+ }
+}
+
+if (!class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class, false)) {
+ class_alias(DoctrineTransportFactory::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class);
+}
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/Transport/PostgreSqlConnection.php b/plugins/email/vendor/symfony/doctrine-messenger/Transport/PostgreSqlConnection.php
new file mode 100644
index 0000000..3691a93
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/Transport/PostgreSqlConnection.php
@@ -0,0 +1,150 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\Schema\Table;
+
+/**
+ * Uses PostgreSQL LISTEN/NOTIFY to push messages to workers.
+ *
+ * @internal
+ *
+ * @author Kévin Dunglas
+ */
+final class PostgreSqlConnection extends Connection
+{
+ /**
+ * * use_notify: Set to false to disable the use of LISTEN/NOTIFY. Default: true
+ * * check_delayed_interval: The interval to check for delayed messages, in milliseconds. Set to 0 to disable checks. Default: 60000 (1 minute)
+ * * get_notify_timeout: The length of time to wait for a response when calling PDO::pgsqlGetNotify, in milliseconds. Default: 0.
+ */
+ protected const DEFAULT_OPTIONS = parent::DEFAULT_OPTIONS + [
+ 'use_notify' => true,
+ 'check_delayed_interval' => 60000,
+ 'get_notify_timeout' => 0,
+ ];
+
+ public function __sleep(): array
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ $this->unlisten();
+ }
+
+ public function reset()
+ {
+ parent::reset();
+ $this->unlisten();
+ }
+
+ public function get(): ?array
+ {
+ if (null === $this->queueEmptiedAt) {
+ return parent::get();
+ }
+
+ // This is secure because the table name must be a valid identifier:
+ // https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
+ $this->executeStatement(sprintf('LISTEN "%s"', $this->configuration['table_name']));
+
+ if (method_exists($this->driverConnection, 'getNativeConnection')) {
+ $wrappedConnection = $this->driverConnection->getNativeConnection();
+ } else {
+ $wrappedConnection = $this->driverConnection;
+ while (method_exists($wrappedConnection, 'getWrappedConnection')) {
+ $wrappedConnection = $wrappedConnection->getWrappedConnection();
+ }
+ }
+
+ $notification = $wrappedConnection->pgsqlGetNotify(\PDO::FETCH_ASSOC, $this->configuration['get_notify_timeout']);
+ if (
+ // no notifications, or for another table or queue
+ (false === $notification || $notification['message'] !== $this->configuration['table_name'] || $notification['payload'] !== $this->configuration['queue_name']) &&
+ // delayed messages
+ (microtime(true) * 1000 - $this->queueEmptiedAt < $this->configuration['check_delayed_interval'])
+ ) {
+ usleep(1000);
+
+ return null;
+ }
+
+ return parent::get();
+ }
+
+ public function setup(): void
+ {
+ parent::setup();
+
+ $this->executeStatement(implode("\n", $this->getTriggerSql()));
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getExtraSetupSqlForTable(Table $createdTable): array
+ {
+ if (!$createdTable->hasOption(self::TABLE_OPTION_NAME)) {
+ return [];
+ }
+
+ if ($createdTable->getOption(self::TABLE_OPTION_NAME) !== $this->configuration['table_name']) {
+ return [];
+ }
+
+ return $this->getTriggerSql();
+ }
+
+ private function getTriggerSql(): array
+ {
+ $functionName = $this->createTriggerFunctionName();
+
+ return [
+ // create trigger function
+ sprintf(<<<'SQL'
+CREATE OR REPLACE FUNCTION %1$s() RETURNS TRIGGER AS $$
+ BEGIN
+ PERFORM pg_notify('%2$s', NEW.queue_name::text);
+ RETURN NEW;
+ END;
+$$ LANGUAGE plpgsql;
+SQL
+ , $functionName, $this->configuration['table_name']),
+ // register trigger
+ sprintf('DROP TRIGGER IF EXISTS notify_trigger ON %s;', $this->configuration['table_name']),
+ sprintf('CREATE TRIGGER notify_trigger AFTER INSERT OR UPDATE ON %1$s FOR EACH ROW EXECUTE PROCEDURE %2$s();', $this->configuration['table_name'], $functionName),
+ ];
+ }
+
+ private function createTriggerFunctionName(): string
+ {
+ $tableConfig = explode('.', $this->configuration['table_name']);
+
+ if (1 === \count($tableConfig)) {
+ return sprintf('notify_%1$s', $tableConfig[0]);
+ }
+
+ return sprintf('%1$s.notify_%2$s', $tableConfig[0], $tableConfig[1]);
+ }
+
+ private function unlisten()
+ {
+ $this->executeStatement(sprintf('UNLISTEN "%s"', $this->configuration['table_name']));
+ }
+}
diff --git a/plugins/email/vendor/symfony/doctrine-messenger/composer.json b/plugins/email/vendor/symfony/doctrine-messenger/composer.json
new file mode 100644
index 0000000..3a9494a
--- /dev/null
+++ b/plugins/email/vendor/symfony/doctrine-messenger/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "symfony/doctrine-messenger",
+ "type": "symfony-messenger-bridge",
+ "description": "Symfony Doctrine Messenger Bridge",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/messenger": "^5.1|^6.0",
+ "symfony/service-contracts": "^1.1|^2|^3"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^2.13|^3.0",
+ "doctrine/persistence": "^1.3|^2|^3",
+ "symfony/property-access": "^4.4|^5.0|^6.0",
+ "symfony/serializer": "^4.4|^5.0|^6.0"
+ },
+ "conflict": {
+ "doctrine/dbal": "<2.13",
+ "doctrine/persistence": "<1.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher-contracts/.gitignore b/plugins/email/vendor/symfony/event-dispatcher-contracts/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher-contracts/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/plugins/email/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md b/plugins/email/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md
new file mode 100644
index 0000000..7932e26
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md
@@ -0,0 +1,5 @@
+CHANGELOG
+=========
+
+The changelog is maintained for all Symfony contracts at the following URL:
+https://github.com/symfony/contracts/blob/main/CHANGELOG.md
diff --git a/plugins/email/vendor/symfony/event-dispatcher-contracts/Event.php b/plugins/email/vendor/symfony/event-dispatcher-contracts/Event.php
new file mode 100644
index 0000000..46dcb2b
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher-contracts/Event.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\EventDispatcher;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+
+/**
+ * Event is the base class for classes containing event data.
+ *
+ * This class contains no event data. It is used by events that do not pass
+ * state information to an event handler when an event is raised.
+ *
+ * You can call the method stopPropagation() to abort the execution of
+ * further listeners in your event listener.
+ *
+ * @author Guilherme Blanco
+ * @author Jonathan Wage
+ * @author Roman Borschel
+ * @author Bernhard Schussek
+ * @author Nicolas Grekas
+ */
+class Event implements StoppableEventInterface
+{
+ private $propagationStopped = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isPropagationStopped(): bool
+ {
+ return $this->propagationStopped;
+ }
+
+ /**
+ * Stops the propagation of the event to further event listeners.
+ *
+ * If multiple event listeners are connected to the same event, no
+ * further event listener will be triggered once any trigger calls
+ * stopPropagation().
+ */
+ public function stopPropagation(): void
+ {
+ $this->propagationStopped = true;
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/plugins/email/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php
new file mode 100644
index 0000000..351dc51
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\EventDispatcher;
+
+use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface;
+
+/**
+ * Allows providing hooks on domain-specific lifecycles by dispatching events.
+ */
+interface EventDispatcherInterface extends PsrEventDispatcherInterface
+{
+ /**
+ * Dispatches an event to all registered listeners.
+ *
+ * @param object $event The event to pass to the event handlers/listeners
+ * @param string|null $eventName The name of the event to dispatch. If not supplied,
+ * the class of $event should be used instead.
+ *
+ * @return object The passed $event MUST be returned
+ */
+ public function dispatch(object $event, string $eventName = null): object;
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher-contracts/LICENSE b/plugins/email/vendor/symfony/event-dispatcher-contracts/LICENSE
new file mode 100644
index 0000000..74cdc2d
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2022 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugins/email/vendor/symfony/event-dispatcher-contracts/README.md b/plugins/email/vendor/symfony/event-dispatcher-contracts/README.md
new file mode 100644
index 0000000..b1ab4c0
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher-contracts/README.md
@@ -0,0 +1,9 @@
+Symfony EventDispatcher Contracts
+=================================
+
+A set of abstractions extracted out of the Symfony components.
+
+Can be used to build on semantics that the Symfony components proved useful - and
+that already have battle tested implementations.
+
+See https://github.com/symfony/contracts/blob/main/README.md for more information.
diff --git a/plugins/email/vendor/symfony/event-dispatcher-contracts/composer.json b/plugins/email/vendor/symfony/event-dispatcher-contracts/composer.json
new file mode 100644
index 0000000..660df81
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher-contracts/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "symfony/event-dispatcher-contracts",
+ "type": "library",
+ "description": "Generic abstractions related to dispatching event",
+ "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "psr/event-dispatcher": "^1"
+ },
+ "suggest": {
+ "symfony/event-dispatcher-implementation": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" }
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php b/plugins/email/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php
new file mode 100644
index 0000000..bb931b8
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Attribute;
+
+/**
+ * Service tag to autoconfigure event listeners.
+ *
+ * @author Alexander M. Turek
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class AsEventListener
+{
+ public function __construct(
+ public ?string $event = null,
+ public ?string $method = null,
+ public int $priority = 0,
+ public ?string $dispatcher = null,
+ ) {
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/CHANGELOG.md b/plugins/email/vendor/symfony/event-dispatcher/CHANGELOG.md
new file mode 100644
index 0000000..0f98598
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/CHANGELOG.md
@@ -0,0 +1,91 @@
+CHANGELOG
+=========
+
+5.4
+---
+
+ * Allow `#[AsEventListener]` attribute on methods
+
+5.3
+---
+
+ * Add `#[AsEventListener]` attribute for declaring listeners on PHP 8
+
+5.1.0
+-----
+
+ * The `LegacyEventDispatcherProxy` class has been deprecated.
+ * Added an optional `dispatcher` attribute to the listener and subscriber tags in `RegisterListenerPass`.
+
+5.0.0
+-----
+
+ * The signature of the `EventDispatcherInterface::dispatch()` method has been changed to `dispatch($event, string $eventName = null): object`.
+ * The `Event` class has been removed in favor of `Symfony\Contracts\EventDispatcher\Event`.
+ * The `TraceableEventDispatcherInterface` has been removed.
+ * The `WrappedListener` class is now final.
+
+4.4.0
+-----
+
+ * `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`.
+ * Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events.
+
+4.3.0
+-----
+
+ * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated
+ * deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead
+
+4.1.0
+-----
+
+ * added support for invokable event listeners tagged with `kernel.event_listener` by default
+ * The `TraceableEventDispatcher::getOrphanedEvents()` method has been added.
+ * The `TraceableEventDispatcherInterface` has been deprecated.
+
+4.0.0
+-----
+
+ * removed the `ContainerAwareEventDispatcher` class
+ * added the `reset()` method to the `TraceableEventDispatcherInterface`
+
+3.4.0
+-----
+
+ * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated.
+
+3.3.0
+-----
+
+ * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead.
+
+3.0.0
+-----
+
+ * The method `getListenerPriority($eventName, $listener)` has been added to the
+ `EventDispatcherInterface`.
+ * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()`
+ and `Event::getName()` have been removed.
+ The event dispatcher and the event name are passed to the listener call.
+
+2.5.0
+-----
+
+ * added Debug\TraceableEventDispatcher (originally in HttpKernel)
+ * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
+ * added RegisterListenersPass (originally in HttpKernel)
+
+2.1.0
+-----
+
+ * added TraceableEventDispatcherInterface
+ * added ContainerAwareEventDispatcher
+ * added a reference to the EventDispatcher on the Event
+ * added a reference to the Event name on the event
+ * added fluid interface to the dispatch() method which now returns the Event
+ object
+ * added GenericEvent event class
+ * added the possibility for subscribers to subscribe several times for the
+ same event
+ * added ImmutableEventDispatcher
diff --git a/plugins/email/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/plugins/email/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
new file mode 100644
index 0000000..acfbf61
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
@@ -0,0 +1,366 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Stopwatch\Stopwatch;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Collects some data about event listeners.
+ *
+ * This event dispatcher delegates the dispatching to another one.
+ *
+ * @author Fabien Potencier
+ */
+class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
+{
+ protected $logger;
+ protected $stopwatch;
+
+ /**
+ * @var \SplObjectStorage
+ */
+ private $callStack;
+ private $dispatcher;
+ private $wrappedListeners;
+ private $orphanedEvents;
+ private $requestStack;
+ private $currentRequestHash = '';
+
+ public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null)
+ {
+ $this->dispatcher = $dispatcher;
+ $this->stopwatch = $stopwatch;
+ $this->logger = $logger;
+ $this->wrappedListeners = [];
+ $this->orphanedEvents = [];
+ $this->requestStack = $requestStack;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener(string $eventName, $listener, int $priority = 0)
+ {
+ $this->dispatcher->addListener($eventName, $listener, $priority);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ $this->dispatcher->addSubscriber($subscriber);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener(string $eventName, $listener)
+ {
+ if (isset($this->wrappedListeners[$eventName])) {
+ foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
+ if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
+ $listener = $wrappedListener;
+ unset($this->wrappedListeners[$eventName][$index]);
+ break;
+ }
+ }
+ }
+
+ return $this->dispatcher->removeListener($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ return $this->dispatcher->removeSubscriber($subscriber);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners(string $eventName = null)
+ {
+ return $this->dispatcher->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority(string $eventName, $listener)
+ {
+ // we might have wrapped listeners for the event (if called while dispatching)
+ // in that case get the priority by wrapper
+ if (isset($this->wrappedListeners[$eventName])) {
+ foreach ($this->wrappedListeners[$eventName] as $wrappedListener) {
+ if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
+ return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
+ }
+ }
+ }
+
+ return $this->dispatcher->getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners(string $eventName = null)
+ {
+ return $this->dispatcher->hasListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch(object $event, string $eventName = null): object
+ {
+ $eventName = $eventName ?? \get_class($event);
+
+ if (null === $this->callStack) {
+ $this->callStack = new \SplObjectStorage();
+ }
+
+ $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
+
+ if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
+ $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
+ }
+
+ $this->preProcess($eventName);
+ try {
+ $this->beforeDispatch($eventName, $event);
+ try {
+ $e = $this->stopwatch->start($eventName, 'section');
+ try {
+ $this->dispatcher->dispatch($event, $eventName);
+ } finally {
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+ }
+ } finally {
+ $this->afterDispatch($eventName, $event);
+ }
+ } finally {
+ $this->currentRequestHash = $currentRequestHash;
+ $this->postProcess($eventName);
+ }
+
+ return $event;
+ }
+
+ /**
+ * @return array
+ */
+ public function getCalledListeners(Request $request = null)
+ {
+ if (null === $this->callStack) {
+ return [];
+ }
+
+ $hash = $request ? spl_object_hash($request) : null;
+ $called = [];
+ foreach ($this->callStack as $listener) {
+ [$eventName, $requestHash] = $this->callStack->getInfo();
+ if (null === $hash || $hash === $requestHash) {
+ $called[] = $listener->getInfo($eventName);
+ }
+ }
+
+ return $called;
+ }
+
+ /**
+ * @return array
+ */
+ public function getNotCalledListeners(Request $request = null)
+ {
+ try {
+ $allListeners = $this->getListeners();
+ } catch (\Exception $e) {
+ if (null !== $this->logger) {
+ $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
+ }
+
+ // unable to retrieve the uncalled listeners
+ return [];
+ }
+
+ $hash = $request ? spl_object_hash($request) : null;
+ $calledListeners = [];
+
+ if (null !== $this->callStack) {
+ foreach ($this->callStack as $calledListener) {
+ [, $requestHash] = $this->callStack->getInfo();
+
+ if (null === $hash || $hash === $requestHash) {
+ $calledListeners[] = $calledListener->getWrappedListener();
+ }
+ }
+ }
+
+ $notCalled = [];
+ foreach ($allListeners as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ if (!\in_array($listener, $calledListeners, true)) {
+ if (!$listener instanceof WrappedListener) {
+ $listener = new WrappedListener($listener, null, $this->stopwatch, $this);
+ }
+ $notCalled[] = $listener->getInfo($eventName);
+ }
+ }
+ }
+
+ uasort($notCalled, [$this, 'sortNotCalledListeners']);
+
+ return $notCalled;
+ }
+
+ public function getOrphanedEvents(Request $request = null): array
+ {
+ if ($request) {
+ return $this->orphanedEvents[spl_object_hash($request)] ?? [];
+ }
+
+ if (!$this->orphanedEvents) {
+ return [];
+ }
+
+ return array_merge(...array_values($this->orphanedEvents));
+ }
+
+ public function reset()
+ {
+ $this->callStack = null;
+ $this->orphanedEvents = [];
+ $this->currentRequestHash = '';
+ }
+
+ /**
+ * Proxies all method calls to the original event dispatcher.
+ *
+ * @param string $method The method name
+ * @param array $arguments The method arguments
+ *
+ * @return mixed
+ */
+ public function __call(string $method, array $arguments)
+ {
+ return $this->dispatcher->{$method}(...$arguments);
+ }
+
+ /**
+ * Called before dispatching the event.
+ */
+ protected function beforeDispatch(string $eventName, object $event)
+ {
+ }
+
+ /**
+ * Called after dispatching the event.
+ */
+ protected function afterDispatch(string $eventName, object $event)
+ {
+ }
+
+ private function preProcess(string $eventName): void
+ {
+ if (!$this->dispatcher->hasListeners($eventName)) {
+ $this->orphanedEvents[$this->currentRequestHash][] = $eventName;
+
+ return;
+ }
+
+ foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+ $priority = $this->getListenerPriority($eventName, $listener);
+ $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);
+ $this->wrappedListeners[$eventName][] = $wrappedListener;
+ $this->dispatcher->removeListener($eventName, $listener);
+ $this->dispatcher->addListener($eventName, $wrappedListener, $priority);
+ $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]);
+ }
+ }
+
+ private function postProcess(string $eventName): void
+ {
+ unset($this->wrappedListeners[$eventName]);
+ $skipped = false;
+ foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+ if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
+ continue;
+ }
+ // Unwrap listener
+ $priority = $this->getListenerPriority($eventName, $listener);
+ $this->dispatcher->removeListener($eventName, $listener);
+ $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
+
+ if (null !== $this->logger) {
+ $context = ['event' => $eventName, 'listener' => $listener->getPretty()];
+ }
+
+ if ($listener->wasCalled()) {
+ if (null !== $this->logger) {
+ $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
+ }
+ } else {
+ $this->callStack->detach($listener);
+ }
+
+ if (null !== $this->logger && $skipped) {
+ $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
+ }
+
+ if ($listener->stoppedPropagation()) {
+ if (null !== $this->logger) {
+ $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
+ }
+
+ $skipped = true;
+ }
+ }
+ }
+
+ private function sortNotCalledListeners(array $a, array $b)
+ {
+ if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
+ return $cmp;
+ }
+
+ if (\is_int($a['priority']) && !\is_int($b['priority'])) {
+ return 1;
+ }
+
+ if (!\is_int($a['priority']) && \is_int($b['priority'])) {
+ return -1;
+ }
+
+ if ($a['priority'] === $b['priority']) {
+ return 0;
+ }
+
+ if ($a['priority'] > $b['priority']) {
+ return -1;
+ }
+
+ return 1;
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/plugins/email/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
new file mode 100644
index 0000000..3916716
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
@@ -0,0 +1,127 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+use Symfony\Component\VarDumper\Caster\ClassStub;
+
+/**
+ * @author Fabien Potencier
+ */
+final class WrappedListener
+{
+ private $listener;
+ private $optimizedListener;
+ private $name;
+ private $called;
+ private $stoppedPropagation;
+ private $stopwatch;
+ private $dispatcher;
+ private $pretty;
+ private $stub;
+ private $priority;
+ private static $hasClassStub;
+
+ public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
+ {
+ $this->listener = $listener;
+ $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null);
+ $this->stopwatch = $stopwatch;
+ $this->dispatcher = $dispatcher;
+ $this->called = false;
+ $this->stoppedPropagation = false;
+
+ if (\is_array($listener)) {
+ $this->name = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0];
+ $this->pretty = $this->name.'::'.$listener[1];
+ } elseif ($listener instanceof \Closure) {
+ $r = new \ReflectionFunction($listener);
+ if (str_contains($r->name, '{closure}')) {
+ $this->pretty = $this->name = 'closure';
+ } elseif ($class = $r->getClosureScopeClass()) {
+ $this->name = $class->name;
+ $this->pretty = $this->name.'::'.$r->name;
+ } else {
+ $this->pretty = $this->name = $r->name;
+ }
+ } elseif (\is_string($listener)) {
+ $this->pretty = $this->name = $listener;
+ } else {
+ $this->name = get_debug_type($listener);
+ $this->pretty = $this->name.'::__invoke';
+ }
+
+ if (null !== $name) {
+ $this->name = $name;
+ }
+
+ if (null === self::$hasClassStub) {
+ self::$hasClassStub = class_exists(ClassStub::class);
+ }
+ }
+
+ public function getWrappedListener()
+ {
+ return $this->listener;
+ }
+
+ public function wasCalled(): bool
+ {
+ return $this->called;
+ }
+
+ public function stoppedPropagation(): bool
+ {
+ return $this->stoppedPropagation;
+ }
+
+ public function getPretty(): string
+ {
+ return $this->pretty;
+ }
+
+ public function getInfo(string $eventName): array
+ {
+ if (null === $this->stub) {
+ $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
+ }
+
+ return [
+ 'event' => $eventName,
+ 'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null),
+ 'pretty' => $this->pretty,
+ 'stub' => $this->stub,
+ ];
+ }
+
+ public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void
+ {
+ $dispatcher = $this->dispatcher ?: $dispatcher;
+
+ $this->called = true;
+ $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener);
+
+ $e = $this->stopwatch->start($this->name, 'event_listener');
+
+ ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
+
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+
+ if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
+ $this->stoppedPropagation = true;
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/plugins/email/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php
new file mode 100644
index 0000000..6e7292b
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * This pass allows bundles to extend the list of event aliases.
+ *
+ * @author Alexander M. Turek
+ */
+class AddEventAliasesPass implements CompilerPassInterface
+{
+ private $eventAliases;
+ private $eventAliasesParameter;
+
+ public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases')
+ {
+ if (1 < \func_num_args()) {
+ trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->eventAliases = $eventAliases;
+ $this->eventAliasesParameter = $eventAliasesParameter;
+ }
+
+ public function process(ContainerBuilder $container): void
+ {
+ $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : [];
+
+ $container->setParameter(
+ $this->eventAliasesParameter,
+ array_merge($eventAliases, $this->eventAliases)
+ );
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/plugins/email/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
new file mode 100644
index 0000000..8eabe7d
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
@@ -0,0 +1,238 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Contracts\EventDispatcher\Event;
+
+/**
+ * Compiler pass to register tagged services for an event dispatcher.
+ */
+class RegisterListenersPass implements CompilerPassInterface
+{
+ protected $dispatcherService;
+ protected $listenerTag;
+ protected $subscriberTag;
+ protected $eventAliasesParameter;
+
+ private $hotPathEvents = [];
+ private $hotPathTagName = 'container.hot_path';
+ private $noPreloadEvents = [];
+ private $noPreloadTagName = 'container.no_preload';
+
+ public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->dispatcherService = $dispatcherService;
+ $this->listenerTag = $listenerTag;
+ $this->subscriberTag = $subscriberTag;
+ $this->eventAliasesParameter = $eventAliasesParameter;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setHotPathEvents(array $hotPathEvents)
+ {
+ $this->hotPathEvents = array_flip($hotPathEvents);
+
+ if (1 < \func_num_args()) {
+ trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__);
+ $this->hotPathTagName = func_get_arg(1);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setNoPreloadEvents(array $noPreloadEvents): self
+ {
+ $this->noPreloadEvents = array_flip($noPreloadEvents);
+
+ if (1 < \func_num_args()) {
+ trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__);
+ $this->noPreloadTagName = func_get_arg(1);
+ }
+
+ return $this;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
+ return;
+ }
+
+ $aliases = [];
+
+ if ($container->hasParameter($this->eventAliasesParameter)) {
+ $aliases = $container->getParameter($this->eventAliasesParameter);
+ }
+
+ $globalDispatcherDefinition = $container->findDefinition($this->dispatcherService);
+
+ foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
+ $noPreload = 0;
+
+ foreach ($events as $event) {
+ $priority = $event['priority'] ?? 0;
+
+ if (!isset($event['event'])) {
+ if ($container->getDefinition($id)->hasTag($this->subscriberTag)) {
+ continue;
+ }
+
+ $event['method'] = $event['method'] ?? '__invoke';
+ $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
+ }
+
+ $event['event'] = $aliases[$event['event']] ?? $event['event'];
+
+ if (!isset($event['method'])) {
+ $event['method'] = 'on'.preg_replace_callback([
+ '/(?<=\b|_)[a-z]/i',
+ '/[^a-z0-9]/i',
+ ], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
+ $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
+
+ if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) {
+ $event['method'] = '__invoke';
+ }
+ }
+
+ $dispatcherDefinition = $globalDispatcherDefinition;
+ if (isset($event['dispatcher'])) {
+ $dispatcherDefinition = $container->getDefinition($event['dispatcher']);
+ }
+
+ $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
+
+ if (isset($this->hotPathEvents[$event['event']])) {
+ $container->getDefinition($id)->addTag($this->hotPathTagName);
+ } elseif (isset($this->noPreloadEvents[$event['event']])) {
+ ++$noPreload;
+ }
+ }
+
+ if ($noPreload && \count($events) === $noPreload) {
+ $container->getDefinition($id)->addTag($this->noPreloadTagName);
+ }
+ }
+
+ $extractingDispatcher = new ExtractingEventDispatcher();
+
+ foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $tags) {
+ $def = $container->getDefinition($id);
+
+ // We must assume that the class value has been correctly filled, even if the service is created by a factory
+ $class = $def->getClass();
+
+ if (!$r = $container->getReflectionClass($class)) {
+ throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+ }
+ if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
+ throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
+ }
+ $class = $r->name;
+
+ $dispatcherDefinitions = [];
+ foreach ($tags as $attributes) {
+ if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) {
+ continue;
+ }
+
+ $dispatcherDefinitions[$attributes['dispatcher']] = $container->getDefinition($attributes['dispatcher']);
+ }
+
+ if (!$dispatcherDefinitions) {
+ $dispatcherDefinitions = [$globalDispatcherDefinition];
+ }
+
+ $noPreload = 0;
+ ExtractingEventDispatcher::$aliases = $aliases;
+ ExtractingEventDispatcher::$subscriber = $class;
+ $extractingDispatcher->addSubscriber($extractingDispatcher);
+ foreach ($extractingDispatcher->listeners as $args) {
+ $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
+ foreach ($dispatcherDefinitions as $dispatcherDefinition) {
+ $dispatcherDefinition->addMethodCall('addListener', $args);
+ }
+
+ if (isset($this->hotPathEvents[$args[0]])) {
+ $container->getDefinition($id)->addTag($this->hotPathTagName);
+ } elseif (isset($this->noPreloadEvents[$args[0]])) {
+ ++$noPreload;
+ }
+ }
+ if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
+ $container->getDefinition($id)->addTag($this->noPreloadTagName);
+ }
+ $extractingDispatcher->listeners = [];
+ ExtractingEventDispatcher::$aliases = [];
+ }
+ }
+
+ private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
+ {
+ if (
+ null === ($class = $container->getDefinition($id)->getClass())
+ || !($r = $container->getReflectionClass($class, false))
+ || !$r->hasMethod($method)
+ || 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
+ || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
+ || $type->isBuiltin()
+ || Event::class === ($name = $type->getName())
+ ) {
+ throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
+ }
+
+ return $name;
+ }
+}
+
+/**
+ * @internal
+ */
+class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
+{
+ public $listeners = [];
+
+ public static $aliases = [];
+ public static $subscriber;
+
+ public function addListener(string $eventName, $listener, int $priority = 0)
+ {
+ $this->listeners[] = [$eventName, $listener[1], $priority];
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ $events = [];
+
+ foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
+ $events[self::$aliases[$eventName] ?? $eventName] = $params;
+ }
+
+ return $events;
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/EventDispatcher.php b/plugins/email/vendor/symfony/event-dispatcher/EventDispatcher.php
new file mode 100644
index 0000000..8fe8fb5
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/EventDispatcher.php
@@ -0,0 +1,280 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+use Symfony\Component\EventDispatcher\Debug\WrappedListener;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ *
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Guilherme Blanco
+ * @author Jonathan Wage
+ * @author Roman Borschel
+ * @author Bernhard Schussek
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @author Jordan Alliot
+ * @author Nicolas Grekas
+ */
+class EventDispatcher implements EventDispatcherInterface
+{
+ private $listeners = [];
+ private $sorted = [];
+ private $optimized;
+
+ public function __construct()
+ {
+ if (__CLASS__ === static::class) {
+ $this->optimized = [];
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch(object $event, string $eventName = null): object
+ {
+ $eventName = $eventName ?? \get_class($event);
+
+ if (null !== $this->optimized) {
+ $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
+ } else {
+ $listeners = $this->getListeners($eventName);
+ }
+
+ if ($listeners) {
+ $this->callListeners($listeners, $eventName, $event);
+ }
+
+ return $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners(string $eventName = null)
+ {
+ if (null !== $eventName) {
+ if (empty($this->listeners[$eventName])) {
+ return [];
+ }
+
+ if (!isset($this->sorted[$eventName])) {
+ $this->sortListeners($eventName);
+ }
+
+ return $this->sorted[$eventName];
+ }
+
+ foreach ($this->listeners as $eventName => $eventListeners) {
+ if (!isset($this->sorted[$eventName])) {
+ $this->sortListeners($eventName);
+ }
+ }
+
+ return array_filter($this->sorted);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority(string $eventName, $listener)
+ {
+ if (empty($this->listeners[$eventName])) {
+ return null;
+ }
+
+ if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
+ $listener[0] = $listener[0]();
+ $listener[1] = $listener[1] ?? '__invoke';
+ }
+
+ foreach ($this->listeners[$eventName] as $priority => &$listeners) {
+ foreach ($listeners as &$v) {
+ if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
+ $v[0] = $v[0]();
+ $v[1] = $v[1] ?? '__invoke';
+ }
+ if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
+ return $priority;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners(string $eventName = null)
+ {
+ if (null !== $eventName) {
+ return !empty($this->listeners[$eventName]);
+ }
+
+ foreach ($this->listeners as $eventListeners) {
+ if ($eventListeners) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener(string $eventName, $listener, int $priority = 0)
+ {
+ $this->listeners[$eventName][$priority][] = $listener;
+ unset($this->sorted[$eventName], $this->optimized[$eventName]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener(string $eventName, $listener)
+ {
+ if (empty($this->listeners[$eventName])) {
+ return;
+ }
+
+ if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
+ $listener[0] = $listener[0]();
+ $listener[1] = $listener[1] ?? '__invoke';
+ }
+
+ foreach ($this->listeners[$eventName] as $priority => &$listeners) {
+ foreach ($listeners as $k => &$v) {
+ if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
+ $v[0] = $v[0]();
+ $v[1] = $v[1] ?? '__invoke';
+ }
+ if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
+ unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
+ }
+ }
+
+ if (!$listeners) {
+ unset($this->listeners[$eventName][$priority]);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (\is_string($params)) {
+ $this->addListener($eventName, [$subscriber, $params]);
+ } elseif (\is_string($params[0])) {
+ $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0);
+ } else {
+ foreach ($params as $listener) {
+ $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (\is_array($params) && \is_array($params[0])) {
+ foreach ($params as $listener) {
+ $this->removeListener($eventName, [$subscriber, $listener[0]]);
+ }
+ } else {
+ $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
+ }
+ }
+ }
+
+ /**
+ * Triggers the listeners of an event.
+ *
+ * This method can be overridden to add functionality that is executed
+ * for each listener.
+ *
+ * @param callable[] $listeners The event listeners
+ * @param string $eventName The name of the event to dispatch
+ * @param object $event The event object to pass to the event handlers/listeners
+ */
+ protected function callListeners(iterable $listeners, string $eventName, object $event)
+ {
+ $stoppable = $event instanceof StoppableEventInterface;
+
+ foreach ($listeners as $listener) {
+ if ($stoppable && $event->isPropagationStopped()) {
+ break;
+ }
+ $listener($event, $eventName, $this);
+ }
+ }
+
+ /**
+ * Sorts the internal list of listeners for the given event by priority.
+ */
+ private function sortListeners(string $eventName)
+ {
+ krsort($this->listeners[$eventName]);
+ $this->sorted[$eventName] = [];
+
+ foreach ($this->listeners[$eventName] as &$listeners) {
+ foreach ($listeners as $k => &$listener) {
+ if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
+ $listener[0] = $listener[0]();
+ $listener[1] = $listener[1] ?? '__invoke';
+ }
+ $this->sorted[$eventName][] = $listener;
+ }
+ }
+ }
+
+ /**
+ * Optimizes the internal list of listeners for the given event by priority.
+ */
+ private function optimizeListeners(string $eventName): array
+ {
+ krsort($this->listeners[$eventName]);
+ $this->optimized[$eventName] = [];
+
+ foreach ($this->listeners[$eventName] as &$listeners) {
+ foreach ($listeners as &$listener) {
+ $closure = &$this->optimized[$eventName][];
+ if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
+ $closure = static function (...$args) use (&$listener, &$closure) {
+ if ($listener[0] instanceof \Closure) {
+ $listener[0] = $listener[0]();
+ $listener[1] = $listener[1] ?? '__invoke';
+ }
+ ($closure = \Closure::fromCallable($listener))(...$args);
+ };
+ } else {
+ $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener);
+ }
+ }
+ }
+
+ return $this->optimized[$eventName];
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/plugins/email/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
new file mode 100644
index 0000000..cc324e1
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Bernhard Schussek
+ */
+interface EventDispatcherInterface extends ContractsEventDispatcherInterface
+{
+ /**
+ * Adds an event listener that listens on the specified events.
+ *
+ * @param int $priority The higher this value, the earlier an event
+ * listener will be triggered in the chain (defaults to 0)
+ */
+ public function addListener(string $eventName, callable $listener, int $priority = 0);
+
+ /**
+ * Adds an event subscriber.
+ *
+ * The subscriber is asked for all the events it is
+ * interested in and added as a listener for these events.
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber);
+
+ /**
+ * Removes an event listener from the specified events.
+ */
+ public function removeListener(string $eventName, callable $listener);
+
+ public function removeSubscriber(EventSubscriberInterface $subscriber);
+
+ /**
+ * Gets the listeners of a specific event or all listeners sorted by descending priority.
+ *
+ * @return array
+ */
+ public function getListeners(string $eventName = null);
+
+ /**
+ * Gets the listener priority for a specific event.
+ *
+ * Returns null if the event or the listener does not exist.
+ *
+ * @return int|null
+ */
+ public function getListenerPriority(string $eventName, callable $listener);
+
+ /**
+ * Checks whether an event has any registered listeners.
+ *
+ * @return bool
+ */
+ public function hasListeners(string $eventName = null);
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/plugins/email/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
new file mode 100644
index 0000000..2085e42
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * An EventSubscriber knows itself what events it is interested in.
+ * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @author Guilherme Blanco
+ * @author Jonathan Wage
+ * @author Roman Borschel
+ * @author Bernhard Schussek
+ */
+interface EventSubscriberInterface
+{
+ /**
+ * Returns an array of event names this subscriber wants to listen to.
+ *
+ * The array keys are event names and the value can be:
+ *
+ * * The method name to call (priority defaults to 0)
+ * * An array composed of the method name to call and the priority
+ * * An array of arrays composed of the method names to call and respective
+ * priorities, or 0 if unset
+ *
+ * For instance:
+ *
+ * * ['eventName' => 'methodName']
+ * * ['eventName' => ['methodName', $priority]]
+ * * ['eventName' => [['methodName1', $priority], ['methodName2']]]
+ *
+ * The code must not depend on runtime state as it will only be called at compile time.
+ * All logic depending on runtime state must be put into the individual methods handling the events.
+ *
+ * @return array>
+ */
+ public static function getSubscribedEvents();
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/GenericEvent.php b/plugins/email/vendor/symfony/event-dispatcher/GenericEvent.php
new file mode 100644
index 0000000..b32a301
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/GenericEvent.php
@@ -0,0 +1,182 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+use Symfony\Contracts\EventDispatcher\Event;
+
+/**
+ * Event encapsulation class.
+ *
+ * Encapsulates events thus decoupling the observer from the subject they encapsulate.
+ *
+ * @author Drak
+ *
+ * @implements \ArrayAccess
+ * @implements \IteratorAggregate
+ */
+class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
+{
+ protected $subject;
+ protected $arguments;
+
+ /**
+ * Encapsulate an event with $subject and $args.
+ *
+ * @param mixed $subject The subject of the event, usually an object or a callable
+ * @param array $arguments Arguments to store in the event
+ */
+ public function __construct($subject = null, array $arguments = [])
+ {
+ $this->subject = $subject;
+ $this->arguments = $arguments;
+ }
+
+ /**
+ * Getter for subject property.
+ *
+ * @return mixed
+ */
+ public function getSubject()
+ {
+ return $this->subject;
+ }
+
+ /**
+ * Get argument by key.
+ *
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException if key is not found
+ */
+ public function getArgument(string $key)
+ {
+ if ($this->hasArgument($key)) {
+ return $this->arguments[$key];
+ }
+
+ throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
+ }
+
+ /**
+ * Add argument to event.
+ *
+ * @param mixed $value Value
+ *
+ * @return $this
+ */
+ public function setArgument(string $key, $value)
+ {
+ $this->arguments[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Getter for all arguments.
+ *
+ * @return array
+ */
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ /**
+ * Set args property.
+ *
+ * @return $this
+ */
+ public function setArguments(array $args = [])
+ {
+ $this->arguments = $args;
+
+ return $this;
+ }
+
+ /**
+ * Has argument.
+ *
+ * @return bool
+ */
+ public function hasArgument(string $key)
+ {
+ return \array_key_exists($key, $this->arguments);
+ }
+
+ /**
+ * ArrayAccess for argument getter.
+ *
+ * @param string $key Array key
+ *
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException if key does not exist in $this->args
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($key)
+ {
+ return $this->getArgument($key);
+ }
+
+ /**
+ * ArrayAccess for argument setter.
+ *
+ * @param string $key Array key to set
+ * @param mixed $value Value
+ *
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet($key, $value)
+ {
+ $this->setArgument($key, $value);
+ }
+
+ /**
+ * ArrayAccess for unset argument.
+ *
+ * @param string $key Array key
+ *
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($key)
+ {
+ if ($this->hasArgument($key)) {
+ unset($this->arguments[$key]);
+ }
+ }
+
+ /**
+ * ArrayAccess has argument.
+ *
+ * @param string $key Array key
+ *
+ * @return bool
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($key)
+ {
+ return $this->hasArgument($key);
+ }
+
+ /**
+ * IteratorAggregate for iterating over the object like an array.
+ *
+ * @return \ArrayIterator
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->arguments);
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/plugins/email/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
new file mode 100644
index 0000000..568d79c
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
@@ -0,0 +1,91 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * A read-only proxy for an event dispatcher.
+ *
+ * @author Bernhard Schussek
+ */
+class ImmutableEventDispatcher implements EventDispatcherInterface
+{
+ private $dispatcher;
+
+ public function __construct(EventDispatcherInterface $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch(object $event, string $eventName = null): object
+ {
+ return $this->dispatcher->dispatch($event, $eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener(string $eventName, $listener, int $priority = 0)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener(string $eventName, $listener)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners(string $eventName = null)
+ {
+ return $this->dispatcher->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority(string $eventName, $listener)
+ {
+ return $this->dispatcher->getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners(string $eventName = null)
+ {
+ return $this->dispatcher->hasListeners($eventName);
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/LICENSE b/plugins/email/vendor/symfony/event-dispatcher/LICENSE
new file mode 100644
index 0000000..88bf75b
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2022 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugins/email/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php b/plugins/email/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php
new file mode 100644
index 0000000..6e17c8f
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+
+trigger_deprecation('symfony/event-dispatcher', '5.1', '%s is deprecated, use the event dispatcher without the proxy.', LegacyEventDispatcherProxy::class);
+
+/**
+ * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
+ *
+ * @author Nicolas Grekas
+ *
+ * @deprecated since Symfony 5.1
+ */
+final class LegacyEventDispatcherProxy
+{
+ public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface
+ {
+ return $dispatcher;
+ }
+}
diff --git a/plugins/email/vendor/symfony/event-dispatcher/README.md b/plugins/email/vendor/symfony/event-dispatcher/README.md
new file mode 100644
index 0000000..dcdb68d
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/README.md
@@ -0,0 +1,15 @@
+EventDispatcher Component
+=========================
+
+The EventDispatcher component provides tools that allow your application
+components to communicate with each other by dispatching events and listening to
+them.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/plugins/email/vendor/symfony/event-dispatcher/composer.json b/plugins/email/vendor/symfony/event-dispatcher/composer.json
new file mode 100644
index 0000000..32b42e4
--- /dev/null
+++ b/plugins/email/vendor/symfony/event-dispatcher/composer.json
@@ -0,0 +1,52 @@
+{
+ "name": "symfony/event-dispatcher",
+ "type": "library",
+ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/event-dispatcher-contracts": "^2|^3",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "require-dev": {
+ "symfony/dependency-injection": "^4.4|^5.0|^6.0",
+ "symfony/expression-language": "^4.4|^5.0|^6.0",
+ "symfony/config": "^4.4|^5.0|^6.0",
+ "symfony/error-handler": "^4.4|^5.0|^6.0",
+ "symfony/http-foundation": "^4.4|^5.0|^6.0",
+ "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/stopwatch": "^4.4|^5.0|^6.0",
+ "psr/log": "^1|^2|^3"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<4.4"
+ },
+ "provide": {
+ "psr/event-dispatcher-implementation": "1.0",
+ "symfony/event-dispatcher-implementation": "2.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/plugins/email/vendor/symfony/mailer/CHANGELOG.md b/plugins/email/vendor/symfony/mailer/CHANGELOG.md
new file mode 100644
index 0000000..cc6cd6f
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/CHANGELOG.md
@@ -0,0 +1,61 @@
+CHANGELOG
+=========
+
+5.4
+---
+
+ * Enable the mailer to operate on any PSR-14-compatible event dispatcher
+
+5.3
+---
+
+ * added the `mailer` monolog channel and set it on all transport definitions
+
+5.2.0
+-----
+
+ * added `NativeTransportFactory` to configure a transport based on php.ini settings
+ * added `local_domain`, `restart_threshold`, `restart_threshold_sleep` and `ping_threshold` options for `smtp`
+ * added `command` option for `sendmail`
+
+4.4.0
+-----
+
+ * [BC BREAK] changed the `NullTransport` DSN from `smtp://null` to `null://null`
+ * [BC BREAK] renamed `SmtpEnvelope` to `Envelope`, renamed `DelayedSmtpEnvelope` to
+ `DelayedEnvelope`
+ * [BC BREAK] changed the syntax for failover and roundrobin DSNs
+
+ Before:
+
+ dummy://a || dummy://b (for failover)
+ dummy://a && dummy://b (for roundrobin)
+
+ After:
+
+ failover(dummy://a dummy://b)
+ roundrobin(dummy://a dummy://b)
+
+ * added support for multiple transports on a `Mailer` instance
+ * [BC BREAK] removed the `auth_mode` DSN option (it is now always determined automatically)
+ * STARTTLS cannot be enabled anymore (it is used automatically if TLS is disabled and the server supports STARTTLS)
+ * [BC BREAK] Removed the `encryption` DSN option (use `smtps` instead)
+ * Added support for the `smtps` protocol (does the same as using `smtp` and port `465`)
+ * Added PHPUnit constraints
+ * Added `MessageDataCollector`
+ * Added `MessageEvents` and `MessageLoggerListener` to allow collecting sent emails
+ * [BC BREAK] `TransportInterface` has a new `__toString()` method
+ * [BC BREAK] Classes `AbstractApiTransport` and `AbstractHttpTransport` moved under `Transport` sub-namespace.
+ * [BC BREAK] Transports depend on `Symfony\Contracts\EventDispatcher\EventDispatcherInterface`
+ instead of `Symfony\Component\EventDispatcher\EventDispatcherInterface`.
+ * Added possibility to register custom transport for dsn by implementing
+ `Symfony\Component\Mailer\Transport\TransportFactoryInterface` and tagging with `mailer.transport_factory` tag in DI.
+ * Added `Symfony\Component\Mailer\Test\TransportFactoryTestCase` to ease testing custom transport factories.
+ * Added `SentMessage::getDebug()` and `TransportExceptionInterface::getDebug` to help debugging
+ * Made `MessageEvent` final
+ * add DSN parameter `verify_peer` to disable TLS peer verification for SMTP transport
+
+4.3.0
+-----
+
+ * Added the component.
diff --git a/plugins/email/vendor/symfony/mailer/DataCollector/MessageDataCollector.php b/plugins/email/vendor/symfony/mailer/DataCollector/MessageDataCollector.php
new file mode 100644
index 0000000..07f77b2
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/DataCollector/MessageDataCollector.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\DataCollector;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\Mailer\Event\MessageEvents;
+use Symfony\Component\Mailer\EventListener\MessageLoggerListener;
+
+/**
+ * @author Fabien Potencier
+ */
+final class MessageDataCollector extends DataCollector
+{
+ private $events;
+
+ public function __construct(MessageLoggerListener $logger)
+ {
+ $this->events = $logger->getEvents();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function collect(Request $request, Response $response, \Throwable $exception = null)
+ {
+ $this->data['events'] = $this->events;
+ }
+
+ public function getEvents(): MessageEvents
+ {
+ return $this->data['events'];
+ }
+
+ /**
+ * @internal
+ */
+ public function base64Encode(string $data): string
+ {
+ return base64_encode($data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ $this->data = [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName(): string
+ {
+ return 'mailer';
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/DelayedEnvelope.php b/plugins/email/vendor/symfony/mailer/DelayedEnvelope.php
new file mode 100644
index 0000000..b78b6f2
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/DelayedEnvelope.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer;
+
+use Symfony\Component\Mailer\Exception\LogicException;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Message;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+final class DelayedEnvelope extends Envelope
+{
+ private $senderSet = false;
+ private $recipientsSet = false;
+ private $message;
+
+ public function __construct(Message $message)
+ {
+ $this->message = $message;
+ }
+
+ public function setSender(Address $sender): void
+ {
+ parent::setSender($sender);
+
+ $this->senderSet = true;
+ }
+
+ public function getSender(): Address
+ {
+ if (!$this->senderSet) {
+ parent::setSender(self::getSenderFromHeaders($this->message->getHeaders()));
+ }
+
+ return parent::getSender();
+ }
+
+ public function setRecipients(array $recipients): void
+ {
+ parent::setRecipients($recipients);
+
+ $this->recipientsSet = parent::getRecipients();
+ }
+
+ /**
+ * @return Address[]
+ */
+ public function getRecipients(): array
+ {
+ if ($this->recipientsSet) {
+ return parent::getRecipients();
+ }
+
+ return self::getRecipientsFromHeaders($this->message->getHeaders());
+ }
+
+ private static function getRecipientsFromHeaders(Headers $headers): array
+ {
+ $recipients = [];
+ foreach (['to', 'cc', 'bcc'] as $name) {
+ foreach ($headers->all($name) as $header) {
+ foreach ($header->getAddresses() as $address) {
+ $recipients[] = $address;
+ }
+ }
+ }
+
+ return $recipients;
+ }
+
+ private static function getSenderFromHeaders(Headers $headers): Address
+ {
+ if ($sender = $headers->get('Sender')) {
+ return $sender->getAddress();
+ }
+ if ($return = $headers->get('Return-Path')) {
+ return $return->getAddress();
+ }
+ if ($from = $headers->get('From')) {
+ return $from->getAddresses()[0];
+ }
+
+ throw new LogicException('Unable to determine the sender of the message.');
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Envelope.php b/plugins/email/vendor/symfony/mailer/Envelope.php
new file mode 100644
index 0000000..97c8d85
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Envelope.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer;
+
+use Symfony\Component\Mailer\Exception\InvalidArgumentException;
+use Symfony\Component\Mailer\Exception\LogicException;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * @author Fabien Potencier
+ */
+class Envelope
+{
+ private $sender;
+ private $recipients = [];
+
+ /**
+ * @param Address[] $recipients
+ */
+ public function __construct(Address $sender, array $recipients)
+ {
+ $this->setSender($sender);
+ $this->setRecipients($recipients);
+ }
+
+ public static function create(RawMessage $message): self
+ {
+ if (RawMessage::class === \get_class($message)) {
+ throw new LogicException('Cannot send a RawMessage instance without an explicit Envelope.');
+ }
+
+ return new DelayedEnvelope($message);
+ }
+
+ public function setSender(Address $sender): void
+ {
+ // to ensure deliverability of bounce emails independent of UTF-8 capabilities of SMTP servers
+ if (!preg_match('/^[^@\x80-\xFF]++@/', $sender->getAddress())) {
+ throw new InvalidArgumentException(sprintf('Invalid sender "%s": non-ASCII characters not supported in local-part of email.', $sender->getAddress()));
+ }
+ $this->sender = $sender;
+ }
+
+ /**
+ * @return Address Returns a "mailbox" as specified by RFC 2822
+ * Must be converted to an "addr-spec" when used as a "MAIL FROM" value in SMTP (use getAddress())
+ */
+ public function getSender(): Address
+ {
+ return $this->sender;
+ }
+
+ /**
+ * @param Address[] $recipients
+ */
+ public function setRecipients(array $recipients): void
+ {
+ if (!$recipients) {
+ throw new InvalidArgumentException('An envelope must have at least one recipient.');
+ }
+
+ $this->recipients = [];
+ foreach ($recipients as $recipient) {
+ if (!$recipient instanceof Address) {
+ throw new InvalidArgumentException(sprintf('A recipient must be an instance of "%s" (got "%s").', Address::class, get_debug_type($recipient)));
+ }
+ $this->recipients[] = new Address($recipient->getAddress());
+ }
+ }
+
+ /**
+ * @return Address[]
+ */
+ public function getRecipients(): array
+ {
+ return $this->recipients;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Event/MessageEvent.php b/plugins/email/vendor/symfony/mailer/Event/MessageEvent.php
new file mode 100644
index 0000000..d6b7894
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Event/MessageEvent.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Event;
+
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mime\RawMessage;
+use Symfony\Contracts\EventDispatcher\Event;
+
+/**
+ * Allows the transformation of a Message and the Envelope before the email is sent.
+ *
+ * @author Fabien Potencier
+ */
+final class MessageEvent extends Event
+{
+ private $message;
+ private $envelope;
+ private $transport;
+ private $queued;
+
+ public function __construct(RawMessage $message, Envelope $envelope, string $transport, bool $queued = false)
+ {
+ $this->message = $message;
+ $this->envelope = $envelope;
+ $this->transport = $transport;
+ $this->queued = $queued;
+ }
+
+ public function getMessage(): RawMessage
+ {
+ return $this->message;
+ }
+
+ public function setMessage(RawMessage $message): void
+ {
+ $this->message = $message;
+ }
+
+ public function getEnvelope(): Envelope
+ {
+ return $this->envelope;
+ }
+
+ public function setEnvelope(Envelope $envelope): void
+ {
+ $this->envelope = $envelope;
+ }
+
+ public function getTransport(): string
+ {
+ return $this->transport;
+ }
+
+ public function isQueued(): bool
+ {
+ return $this->queued;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Event/MessageEvents.php b/plugins/email/vendor/symfony/mailer/Event/MessageEvents.php
new file mode 100644
index 0000000..b526649
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Event/MessageEvents.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Event;
+
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * @author Fabien Potencier
+ */
+class MessageEvents
+{
+ private $events = [];
+ private $transports = [];
+
+ public function add(MessageEvent $event): void
+ {
+ $this->events[] = $event;
+ $this->transports[$event->getTransport()] = true;
+ }
+
+ public function getTransports(): array
+ {
+ return array_keys($this->transports);
+ }
+
+ /**
+ * @return MessageEvent[]
+ */
+ public function getEvents(string $name = null): array
+ {
+ if (null === $name) {
+ return $this->events;
+ }
+
+ $events = [];
+ foreach ($this->events as $event) {
+ if ($name === $event->getTransport()) {
+ $events[] = $event;
+ }
+ }
+
+ return $events;
+ }
+
+ /**
+ * @return RawMessage[]
+ */
+ public function getMessages(string $name = null): array
+ {
+ $events = $this->getEvents($name);
+ $messages = [];
+ foreach ($events as $event) {
+ $messages[] = $event->getMessage();
+ }
+
+ return $messages;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/EventListener/EnvelopeListener.php b/plugins/email/vendor/symfony/mailer/EventListener/EnvelopeListener.php
new file mode 100644
index 0000000..b2980bc
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/EventListener/EnvelopeListener.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Mailer\Event\MessageEvent;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Message;
+
+/**
+ * Manipulates the Envelope of a Message.
+ *
+ * @author Fabien Potencier
+ */
+class EnvelopeListener implements EventSubscriberInterface
+{
+ private $sender;
+ private $recipients;
+
+ /**
+ * @param Address|string $sender
+ * @param array $recipients
+ */
+ public function __construct($sender = null, array $recipients = null)
+ {
+ if (null !== $sender) {
+ $this->sender = Address::create($sender);
+ }
+ if (null !== $recipients) {
+ $this->recipients = Address::createArray($recipients);
+ }
+ }
+
+ public function onMessage(MessageEvent $event): void
+ {
+ if ($this->sender) {
+ $event->getEnvelope()->setSender($this->sender);
+
+ $message = $event->getMessage();
+ if ($message instanceof Message) {
+ if (!$message->getHeaders()->has('Sender') && !$message->getHeaders()->has('From')) {
+ $message->getHeaders()->addMailboxHeader('Sender', $this->sender);
+ }
+ }
+ }
+
+ if ($this->recipients) {
+ $event->getEnvelope()->setRecipients($this->recipients);
+ }
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ // should be the last one to allow header changes by other listeners first
+ MessageEvent::class => ['onMessage', -255],
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/EventListener/MessageListener.php b/plugins/email/vendor/symfony/mailer/EventListener/MessageListener.php
new file mode 100644
index 0000000..f23c69d
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/EventListener/MessageListener.php
@@ -0,0 +1,134 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Mailer\Event\MessageEvent;
+use Symfony\Component\Mailer\Exception\InvalidArgumentException;
+use Symfony\Component\Mailer\Exception\RuntimeException;
+use Symfony\Component\Mime\BodyRendererInterface;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Header\MailboxListHeader;
+use Symfony\Component\Mime\Message;
+
+/**
+ * Manipulates the headers and the body of a Message.
+ *
+ * @author Fabien Potencier
+ */
+class MessageListener implements EventSubscriberInterface
+{
+ public const HEADER_SET_IF_EMPTY = 1;
+ public const HEADER_ADD = 2;
+ public const HEADER_REPLACE = 3;
+ public const DEFAULT_RULES = [
+ 'from' => self::HEADER_SET_IF_EMPTY,
+ 'return-path' => self::HEADER_SET_IF_EMPTY,
+ 'reply-to' => self::HEADER_ADD,
+ 'to' => self::HEADER_SET_IF_EMPTY,
+ 'cc' => self::HEADER_ADD,
+ 'bcc' => self::HEADER_ADD,
+ ];
+
+ private $headers;
+ private $headerRules = [];
+ private $renderer;
+
+ public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES)
+ {
+ $this->headers = $headers;
+ $this->renderer = $renderer;
+ foreach ($headerRules as $headerName => $rule) {
+ $this->addHeaderRule($headerName, $rule);
+ }
+ }
+
+ public function addHeaderRule(string $headerName, int $rule): void
+ {
+ if ($rule < 1 || $rule > 3) {
+ throw new InvalidArgumentException(sprintf('The "%d" rule is not supported.', $rule));
+ }
+
+ $this->headerRules[strtolower($headerName)] = $rule;
+ }
+
+ public function onMessage(MessageEvent $event): void
+ {
+ $message = $event->getMessage();
+ if (!$message instanceof Message) {
+ return;
+ }
+
+ $this->setHeaders($message);
+ $this->renderMessage($message);
+ }
+
+ private function setHeaders(Message $message): void
+ {
+ if (!$this->headers) {
+ return;
+ }
+
+ $headers = $message->getHeaders();
+ foreach ($this->headers->all() as $name => $header) {
+ if (!$headers->has($name)) {
+ $headers->add($header);
+
+ continue;
+ }
+
+ switch ($this->headerRules[$name] ?? self::HEADER_SET_IF_EMPTY) {
+ case self::HEADER_SET_IF_EMPTY:
+ break;
+
+ case self::HEADER_REPLACE:
+ $headers->remove($name);
+ $headers->add($header);
+
+ break;
+
+ case self::HEADER_ADD:
+ if (!Headers::isUniqueHeader($name)) {
+ $headers->add($header);
+
+ break;
+ }
+
+ $h = $headers->get($name);
+ if (!$h instanceof MailboxListHeader) {
+ throw new RuntimeException(sprintf('Unable to set header "%s".', $name));
+ }
+
+ Headers::checkHeaderClass($header);
+ foreach ($header->getAddresses() as $address) {
+ $h->addAddress($address);
+ }
+ }
+ }
+ }
+
+ private function renderMessage(Message $message): void
+ {
+ if (!$this->renderer) {
+ return;
+ }
+
+ $this->renderer->render($message);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ MessageEvent::class => 'onMessage',
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/EventListener/MessageLoggerListener.php b/plugins/email/vendor/symfony/mailer/EventListener/MessageLoggerListener.php
new file mode 100644
index 0000000..093bf2b
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/EventListener/MessageLoggerListener.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Mailer\Event\MessageEvent;
+use Symfony\Component\Mailer\Event\MessageEvents;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Logs Messages.
+ *
+ * @author Fabien Potencier
+ */
+class MessageLoggerListener implements EventSubscriberInterface, ResetInterface
+{
+ private $events;
+
+ public function __construct()
+ {
+ $this->events = new MessageEvents();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ $this->events = new MessageEvents();
+ }
+
+ public function onMessage(MessageEvent $event): void
+ {
+ $this->events->add($event);
+ }
+
+ public function getEvents(): MessageEvents
+ {
+ return $this->events;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ MessageEvent::class => ['onMessage', -255],
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/ExceptionInterface.php b/plugins/email/vendor/symfony/mailer/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..2f0f3a6
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+/**
+ * Exception interface for all exceptions thrown by the component.
+ *
+ * @author Fabien Potencier
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/HttpTransportException.php b/plugins/email/vendor/symfony/mailer/Exception/HttpTransportException.php
new file mode 100644
index 0000000..c72eb6c
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/HttpTransportException.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+use Symfony\Contracts\HttpClient\ResponseInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+class HttpTransportException extends TransportException
+{
+ private $response;
+
+ public function __construct(?string $message, ResponseInterface $response, int $code = 0, \Throwable $previous = null)
+ {
+ if (null === $message) {
+ trigger_deprecation('symfony/mailer', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__);
+
+ $message = '';
+ }
+
+ parent::__construct($message, $code, $previous);
+
+ $this->response = $response;
+ }
+
+ public function getResponse(): ResponseInterface
+ {
+ return $this->response;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/IncompleteDsnException.php b/plugins/email/vendor/symfony/mailer/Exception/IncompleteDsnException.php
new file mode 100644
index 0000000..f2618b6
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/IncompleteDsnException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+/**
+ * @author Konstantin Myakshin
+ */
+class IncompleteDsnException extends InvalidArgumentException
+{
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/InvalidArgumentException.php b/plugins/email/vendor/symfony/mailer/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..ba53334
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/InvalidArgumentException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+/**
+ * @author Fabien Potencier
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/LogicException.php b/plugins/email/vendor/symfony/mailer/Exception/LogicException.php
new file mode 100644
index 0000000..487c0a3
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/LogicException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+/**
+ * @author Fabien Potencier
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/RuntimeException.php b/plugins/email/vendor/symfony/mailer/Exception/RuntimeException.php
new file mode 100644
index 0000000..44b79cc
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/RuntimeException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+/**
+ * @author Fabien Potencier
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/TransportException.php b/plugins/email/vendor/symfony/mailer/Exception/TransportException.php
new file mode 100644
index 0000000..dfad0c4
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/TransportException.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+/**
+ * @author Fabien Potencier
+ */
+class TransportException extends RuntimeException implements TransportExceptionInterface
+{
+ private $debug = '';
+
+ public function getDebug(): string
+ {
+ return $this->debug;
+ }
+
+ public function appendDebug(string $debug): void
+ {
+ $this->debug .= $debug;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/TransportExceptionInterface.php b/plugins/email/vendor/symfony/mailer/Exception/TransportExceptionInterface.php
new file mode 100644
index 0000000..4318f5c
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/TransportExceptionInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+/**
+ * @author Fabien Potencier
+ */
+interface TransportExceptionInterface extends ExceptionInterface
+{
+ public function getDebug(): string;
+
+ public function appendDebug(string $debug): void;
+}
diff --git a/plugins/email/vendor/symfony/mailer/Exception/UnsupportedSchemeException.php b/plugins/email/vendor/symfony/mailer/Exception/UnsupportedSchemeException.php
new file mode 100644
index 0000000..e47a129
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Exception/UnsupportedSchemeException.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Exception;
+
+use Symfony\Component\Mailer\Bridge;
+use Symfony\Component\Mailer\Transport\Dsn;
+
+/**
+ * @author Konstantin Myakshin
+ */
+class UnsupportedSchemeException extends LogicException
+{
+ private const SCHEME_TO_PACKAGE_MAP = [
+ 'gmail' => [
+ 'class' => Bridge\Google\Transport\GmailTransportFactory::class,
+ 'package' => 'symfony/google-mailer',
+ ],
+ 'mailgun' => [
+ 'class' => Bridge\Mailgun\Transport\MailgunTransportFactory::class,
+ 'package' => 'symfony/mailgun-mailer',
+ ],
+ 'mailjet' => [
+ 'class' => Bridge\Mailjet\Transport\MailjetTransportFactory::class,
+ 'package' => 'symfony/mailjet-mailer',
+ ],
+ 'mandrill' => [
+ 'class' => Bridge\Mailchimp\Transport\MandrillTransportFactory::class,
+ 'package' => 'symfony/mailchimp-mailer',
+ ],
+ 'ohmysmtp' => [
+ 'class' => Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory::class,
+ 'package' => 'symfony/oh-my-smtp-mailer',
+ ],
+ 'postmark' => [
+ 'class' => Bridge\Postmark\Transport\PostmarkTransportFactory::class,
+ 'package' => 'symfony/postmark-mailer',
+ ],
+ 'sendgrid' => [
+ 'class' => Bridge\Sendgrid\Transport\SendgridTransportFactory::class,
+ 'package' => 'symfony/sendgrid-mailer',
+ ],
+ 'sendinblue' => [
+ 'class' => Bridge\Sendinblue\Transport\SendinblueTransportFactory::class,
+ 'package' => 'symfony/sendinblue-mailer',
+ ],
+ 'ses' => [
+ 'class' => Bridge\Amazon\Transport\SesTransportFactory::class,
+ 'package' => 'symfony/amazon-mailer',
+ ],
+ ];
+
+ public function __construct(Dsn $dsn, string $name = null, array $supported = [])
+ {
+ $provider = $dsn->getScheme();
+ if (false !== $pos = strpos($provider, '+')) {
+ $provider = substr($provider, 0, $pos);
+ }
+ $package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null;
+ if ($package && !class_exists($package['class'])) {
+ parent::__construct(sprintf('Unable to send emails via "%s" as the bridge is not installed; try running "composer require %s".', $provider, $package['package']));
+
+ return;
+ }
+
+ $message = sprintf('The "%s" scheme is not supported', $dsn->getScheme());
+ if ($name && $supported) {
+ $message .= sprintf('; supported schemes for mailer "%s" are: "%s"', $name, implode('", "', $supported));
+ }
+
+ parent::__construct($message.'.');
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Header/MetadataHeader.php b/plugins/email/vendor/symfony/mailer/Header/MetadataHeader.php
new file mode 100644
index 0000000..d56acb1
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Header/MetadataHeader.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Header;
+
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+
+/**
+ * @author Kevin Bond
+ */
+final class MetadataHeader extends UnstructuredHeader
+{
+ private $key;
+
+ public function __construct(string $key, string $value)
+ {
+ $this->key = $key;
+
+ parent::__construct('X-Metadata-'.$key, $value);
+ }
+
+ public function getKey(): string
+ {
+ return $this->key;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Header/TagHeader.php b/plugins/email/vendor/symfony/mailer/Header/TagHeader.php
new file mode 100644
index 0000000..7115cae
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Header/TagHeader.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Header;
+
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+
+/**
+ * @author Kevin Bond
+ */
+final class TagHeader extends UnstructuredHeader
+{
+ public function __construct(string $value)
+ {
+ parent::__construct('X-Tag', $value);
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/LICENSE b/plugins/email/vendor/symfony/mailer/LICENSE
new file mode 100644
index 0000000..9c907a4
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2019-2022 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugins/email/vendor/symfony/mailer/Mailer.php b/plugins/email/vendor/symfony/mailer/Mailer.php
new file mode 100644
index 0000000..cbdcdf2
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Mailer.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
+use Symfony\Component\Mailer\Event\MessageEvent;
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mailer\Messenger\SendEmailMessage;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+use Symfony\Component\Messenger\Exception\HandlerFailedException;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Mime\RawMessage;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+final class Mailer implements MailerInterface
+{
+ private $transport;
+ private $bus;
+ private $dispatcher;
+
+ public function __construct(TransportInterface $transport, MessageBusInterface $bus = null, EventDispatcherInterface $dispatcher = null)
+ {
+ $this->transport = $transport;
+ $this->bus = $bus;
+ $this->dispatcher = class_exists(Event::class) && $dispatcher instanceof SymfonyEventDispatcherInterface ? LegacyEventDispatcherProxy::decorate($dispatcher) : $dispatcher;
+ }
+
+ public function send(RawMessage $message, Envelope $envelope = null): void
+ {
+ if (null === $this->bus) {
+ $this->transport->send($message, $envelope);
+
+ return;
+ }
+
+ if (null !== $this->dispatcher) {
+ // The dispatched event here has `queued` set to `true`; the goal is NOT to render the message, but to let
+ // listeners do something before a message is sent to the queue.
+ // We are using a cloned message as we still want to dispatch the **original** message, not the one modified by listeners.
+ // That's because the listeners will run again when the email is sent via Messenger by the transport (see `AbstractTransport`).
+ // Listeners should act depending on the `$queued` argument of the `MessageEvent` instance.
+ $clonedMessage = clone $message;
+ $clonedEnvelope = null !== $envelope ? clone $envelope : Envelope::create($clonedMessage);
+ $event = new MessageEvent($clonedMessage, $clonedEnvelope, (string) $this->transport, true);
+ $this->dispatcher->dispatch($event);
+ }
+
+ try {
+ $this->bus->dispatch(new SendEmailMessage($message, $envelope));
+ } catch (HandlerFailedException $e) {
+ foreach ($e->getNestedExceptions() as $nested) {
+ if ($nested instanceof TransportExceptionInterface) {
+ throw $nested;
+ }
+ }
+ throw $e;
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/MailerInterface.php b/plugins/email/vendor/symfony/mailer/MailerInterface.php
new file mode 100644
index 0000000..eb44cf6
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/MailerInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer;
+
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * Interface for mailers able to send emails synchronous and/or asynchronous.
+ *
+ * Implementations must support synchronous and asynchronous sending.
+ *
+ * @author Fabien Potencier
+ */
+interface MailerInterface
+{
+ /**
+ * @throws TransportExceptionInterface
+ */
+ public function send(RawMessage $message, Envelope $envelope = null): void;
+}
diff --git a/plugins/email/vendor/symfony/mailer/Messenger/MessageHandler.php b/plugins/email/vendor/symfony/mailer/Messenger/MessageHandler.php
new file mode 100644
index 0000000..fefae9d
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Messenger/MessageHandler.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Messenger;
+
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+class MessageHandler
+{
+ private $transport;
+
+ public function __construct(TransportInterface $transport)
+ {
+ $this->transport = $transport;
+ }
+
+ public function __invoke(SendEmailMessage $message): ?SentMessage
+ {
+ return $this->transport->send($message->getMessage(), $message->getEnvelope());
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Messenger/SendEmailMessage.php b/plugins/email/vendor/symfony/mailer/Messenger/SendEmailMessage.php
new file mode 100644
index 0000000..b06ac83
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Messenger/SendEmailMessage.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Messenger;
+
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * @author Fabien Potencier
+ */
+class SendEmailMessage
+{
+ private $message;
+ private $envelope;
+
+ public function __construct(RawMessage $message, Envelope $envelope = null)
+ {
+ $this->message = $message;
+ $this->envelope = $envelope;
+ }
+
+ public function getMessage(): RawMessage
+ {
+ return $this->message;
+ }
+
+ public function getEnvelope(): ?Envelope
+ {
+ return $this->envelope;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/README.md b/plugins/email/vendor/symfony/mailer/README.md
new file mode 100644
index 0000000..93a9585
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/README.md
@@ -0,0 +1,74 @@
+Mailer Component
+================
+
+The Mailer component helps sending emails.
+
+Getting Started
+---------------
+
+```
+$ composer require symfony/mailer
+```
+
+```php
+use Symfony\Component\Mailer\Transport;
+use Symfony\Component\Mailer\Mailer;
+use Symfony\Component\Mime\Email;
+
+$transport = Transport::fromDsn('smtp://localhost');
+$mailer = new Mailer($transport);
+
+$email = (new Email())
+ ->from('hello@example.com')
+ ->to('you@example.com')
+ //->cc('cc@example.com')
+ //->bcc('bcc@example.com')
+ //->replyTo('fabien@example.com')
+ //->priority(Email::PRIORITY_HIGH)
+ ->subject('Time for Symfony Mailer!')
+ ->text('Sending emails is fun again!')
+ ->html('See Twig integration for better HTML integration!
');
+
+$mailer->send($email);
+```
+
+To enable the Twig integration of the Mailer, require `symfony/twig-bridge` and
+set up the `BodyRenderer`:
+
+```php
+use Symfony\Bridge\Twig\Mime\BodyRenderer;
+use Symfony\Bridge\Twig\Mime\TemplatedEmail;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\Mailer\EventListener\MessageListener;
+use Symfony\Component\Mailer\Mailer;
+use Symfony\Component\Mailer\Transport;
+use Twig\Environment as TwigEnvironment;
+
+$twig = new TwigEnvironment(...);
+$messageListener = new MessageListener(null, new BodyRenderer($twig));
+
+$eventDispatcher = new EventDispatcher();
+$eventDispatcher->addSubscriber($messageListener);
+
+$transport = Transport::fromDsn('smtp://localhost', $eventDispatcher);
+$mailer = new Mailer($transport, null, $eventDispatcher);
+
+$email = (new TemplatedEmail())
+ // ...
+ ->htmlTemplate('emails/signup.html.twig')
+ ->context([
+ 'expiration_date' => new \DateTime('+7 days'),
+ 'username' => 'foo',
+ ])
+;
+$mailer->send($email);
+```
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/mailer.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/plugins/email/vendor/symfony/mailer/SentMessage.php b/plugins/email/vendor/symfony/mailer/SentMessage.php
new file mode 100644
index 0000000..1a12d07
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/SentMessage.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer;
+
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * @author Fabien Potencier
+ */
+class SentMessage
+{
+ private $original;
+ private $raw;
+ private $envelope;
+ private $messageId;
+ private $debug = '';
+
+ /**
+ * @internal
+ */
+ public function __construct(RawMessage $message, Envelope $envelope)
+ {
+ $message->ensureValidity();
+
+ $this->original = $message;
+ $this->envelope = $envelope;
+
+ if ($message instanceof Message) {
+ $message = clone $message;
+ $headers = $message->getHeaders();
+ if (!$headers->has('Message-ID')) {
+ $headers->addIdHeader('Message-ID', $message->generateMessageId());
+ }
+ $this->messageId = $headers->get('Message-ID')->getId();
+ $this->raw = new RawMessage($message->toIterable());
+ } else {
+ $this->raw = $message;
+ }
+ }
+
+ public function getMessage(): RawMessage
+ {
+ return $this->raw;
+ }
+
+ public function getOriginalMessage(): RawMessage
+ {
+ return $this->original;
+ }
+
+ public function getEnvelope(): Envelope
+ {
+ return $this->envelope;
+ }
+
+ public function setMessageId(string $id): void
+ {
+ $this->messageId = $id;
+ }
+
+ public function getMessageId(): string
+ {
+ return $this->messageId;
+ }
+
+ public function getDebug(): string
+ {
+ return $this->debug;
+ }
+
+ public function appendDebug(string $debug): void
+ {
+ $this->debug .= $debug;
+ }
+
+ public function toString(): string
+ {
+ return $this->raw->toString();
+ }
+
+ public function toIterable(): iterable
+ {
+ return $this->raw->toIterable();
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Test/Constraint/EmailCount.php b/plugins/email/vendor/symfony/mailer/Test/Constraint/EmailCount.php
new file mode 100644
index 0000000..59a7812
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Test/Constraint/EmailCount.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\Mailer\Event\MessageEvents;
+
+final class EmailCount extends Constraint
+{
+ private $expectedValue;
+ private $transport;
+ private $queued;
+
+ public function __construct(int $expectedValue, string $transport = null, bool $queued = false)
+ {
+ $this->expectedValue = $expectedValue;
+ $this->transport = $transport;
+ $this->queued = $queued;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toString(): string
+ {
+ return sprintf('%shas %s "%d" emails', $this->transport ? $this->transport.' ' : '', $this->queued ? 'queued' : 'sent', $this->expectedValue);
+ }
+
+ /**
+ * @param MessageEvents $events
+ *
+ * {@inheritdoc}
+ */
+ protected function matches($events): bool
+ {
+ return $this->expectedValue === $this->countEmails($events);
+ }
+
+ /**
+ * @param MessageEvents $events
+ *
+ * {@inheritdoc}
+ */
+ protected function failureDescription($events): string
+ {
+ return sprintf('the Transport %s (%d %s)', $this->toString(), $this->countEmails($events), $this->queued ? 'queued' : 'sent');
+ }
+
+ private function countEmails(MessageEvents $events): int
+ {
+ $count = 0;
+ foreach ($events->getEvents($this->transport) as $event) {
+ if (
+ ($this->queued && $event->isQueued())
+ ||
+ (!$this->queued && !$event->isQueued())
+ ) {
+ ++$count;
+ }
+ }
+
+ return $count;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Test/Constraint/EmailIsQueued.php b/plugins/email/vendor/symfony/mailer/Test/Constraint/EmailIsQueued.php
new file mode 100644
index 0000000..be9dac8
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Test/Constraint/EmailIsQueued.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\Mailer\Event\MessageEvent;
+
+final class EmailIsQueued extends Constraint
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function toString(): string
+ {
+ return 'is queued';
+ }
+
+ /**
+ * @param MessageEvent $event
+ *
+ * {@inheritdoc}
+ */
+ protected function matches($event): bool
+ {
+ return $event->isQueued();
+ }
+
+ /**
+ * @param MessageEvent $event
+ *
+ * {@inheritdoc}
+ */
+ protected function failureDescription($event): string
+ {
+ return 'the Email '.$this->toString();
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Test/TransportFactoryTestCase.php b/plugins/email/vendor/symfony/mailer/Test/TransportFactoryTestCase.php
new file mode 100644
index 0000000..c2426a0
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Test/TransportFactoryTestCase.php
@@ -0,0 +1,117 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Test;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Mailer\Exception\IncompleteDsnException;
+use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
+use Symfony\Component\Mailer\Transport\Dsn;
+use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+/**
+ * A test case to ease testing Transport Factory.
+ *
+ * @author Konstantin Myakshin
+ */
+abstract class TransportFactoryTestCase extends TestCase
+{
+ protected const USER = 'u$er';
+ protected const PASSWORD = 'pa$s';
+
+ protected $dispatcher;
+ protected $client;
+ protected $logger;
+
+ abstract public function getFactory(): TransportFactoryInterface;
+
+ abstract public function supportsProvider(): iterable;
+
+ abstract public function createProvider(): iterable;
+
+ public function unsupportedSchemeProvider(): iterable
+ {
+ return [];
+ }
+
+ public function incompleteDsnProvider(): iterable
+ {
+ return [];
+ }
+
+ /**
+ * @dataProvider supportsProvider
+ */
+ public function testSupports(Dsn $dsn, bool $supports)
+ {
+ $factory = $this->getFactory();
+
+ $this->assertSame($supports, $factory->supports($dsn));
+ }
+
+ /**
+ * @dataProvider createProvider
+ */
+ public function testCreate(Dsn $dsn, TransportInterface $transport)
+ {
+ $factory = $this->getFactory();
+
+ $this->assertEquals($transport, $factory->create($dsn));
+ if (str_contains('smtp', $dsn->getScheme())) {
+ $this->assertStringMatchesFormat($dsn->getScheme().'://%S'.$dsn->getHost().'%S', (string) $transport);
+ }
+ }
+
+ /**
+ * @dataProvider unsupportedSchemeProvider
+ */
+ public function testUnsupportedSchemeException(Dsn $dsn, string $message = null)
+ {
+ $factory = $this->getFactory();
+
+ $this->expectException(UnsupportedSchemeException::class);
+ if (null !== $message) {
+ $this->expectExceptionMessage($message);
+ }
+
+ $factory->create($dsn);
+ }
+
+ /**
+ * @dataProvider incompleteDsnProvider
+ */
+ public function testIncompleteDsnException(Dsn $dsn)
+ {
+ $factory = $this->getFactory();
+
+ $this->expectException(IncompleteDsnException::class);
+ $factory->create($dsn);
+ }
+
+ protected function getDispatcher(): EventDispatcherInterface
+ {
+ return $this->dispatcher ?? $this->dispatcher = $this->createMock(EventDispatcherInterface::class);
+ }
+
+ protected function getClient(): HttpClientInterface
+ {
+ return $this->client ?? $this->client = $this->createMock(HttpClientInterface::class);
+ }
+
+ protected function getLogger(): LoggerInterface
+ {
+ return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class);
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport.php b/plugins/email/vendor/symfony/mailer/Transport.php
new file mode 100644
index 0000000..c2b813f
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport.php
@@ -0,0 +1,206 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory;
+use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory;
+use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory;
+use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
+use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory;
+use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory;
+use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
+use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
+use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
+use Symfony\Component\Mailer\Exception\InvalidArgumentException;
+use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
+use Symfony\Component\Mailer\Transport\Dsn;
+use Symfony\Component\Mailer\Transport\FailoverTransport;
+use Symfony\Component\Mailer\Transport\NativeTransportFactory;
+use Symfony\Component\Mailer\Transport\NullTransportFactory;
+use Symfony\Component\Mailer\Transport\RoundRobinTransport;
+use Symfony\Component\Mailer\Transport\SendmailTransportFactory;
+use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;
+use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+use Symfony\Component\Mailer\Transport\Transports;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+/**
+ * @author Fabien Potencier
+ * @author Konstantin Myakshin
+ *
+ * @final since Symfony 5.4
+ */
+class Transport
+{
+ private const FACTORY_CLASSES = [
+ GmailTransportFactory::class,
+ MailgunTransportFactory::class,
+ MailjetTransportFactory::class,
+ MandrillTransportFactory::class,
+ OhMySmtpTransportFactory::class,
+ PostmarkTransportFactory::class,
+ SendgridTransportFactory::class,
+ SendinblueTransportFactory::class,
+ SesTransportFactory::class,
+ ];
+
+ private $factories;
+
+ /**
+ * @param EventDispatcherInterface|null $dispatcher
+ * @param HttpClientInterface|null $client
+ * @param LoggerInterface|null $logger
+ */
+ public static function fromDsn(string $dsn/* , EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null */): TransportInterface
+ {
+ $dispatcher = 2 <= \func_num_args() ? func_get_arg(1) : null;
+ $client = 3 <= \func_num_args() ? func_get_arg(2) : null;
+ $logger = 4 <= \func_num_args() ? func_get_arg(3) : null;
+
+ $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
+
+ return $factory->fromString($dsn);
+ }
+
+ /**
+ * @param EventDispatcherInterface|null $dispatcher
+ * @param HttpClientInterface|null $client
+ * @param LoggerInterface|null $logger
+ */
+ public static function fromDsns(array $dsns/* , EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null */): TransportInterface
+ {
+ $dispatcher = 2 <= \func_num_args() ? func_get_arg(1) : null;
+ $client = 3 <= \func_num_args() ? func_get_arg(2) : null;
+ $logger = 4 <= \func_num_args() ? func_get_arg(3) : null;
+
+ $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
+
+ return $factory->fromStrings($dsns);
+ }
+
+ /**
+ * @param TransportFactoryInterface[] $factories
+ */
+ public function __construct(iterable $factories)
+ {
+ $this->factories = $factories;
+ }
+
+ public function fromStrings(array $dsns): Transports
+ {
+ $transports = [];
+ foreach ($dsns as $name => $dsn) {
+ $transports[$name] = $this->fromString($dsn);
+ }
+
+ return new Transports($transports);
+ }
+
+ public function fromString(string $dsn): TransportInterface
+ {
+ [$transport, $offset] = $this->parseDsn($dsn);
+ if ($offset !== \strlen($dsn)) {
+ throw new InvalidArgumentException(sprintf('The DSN has some garbage at the end: "%s".', substr($dsn, $offset)));
+ }
+
+ return $transport;
+ }
+
+ private function parseDsn(string $dsn, int $offset = 0): array
+ {
+ static $keywords = [
+ 'failover' => FailoverTransport::class,
+ 'roundrobin' => RoundRobinTransport::class,
+ ];
+
+ while (true) {
+ foreach ($keywords as $name => $class) {
+ $name .= '(';
+ if ($name === substr($dsn, $offset, \strlen($name))) {
+ $offset += \strlen($name) - 1;
+ preg_match('{\(([^()]|(?R))*\)}A', $dsn, $matches, 0, $offset);
+ if (!isset($matches[0])) {
+ continue;
+ }
+
+ ++$offset;
+ $args = [];
+ while (true) {
+ [$arg, $offset] = $this->parseDsn($dsn, $offset);
+ $args[] = $arg;
+ if (\strlen($dsn) === $offset) {
+ break;
+ }
+ ++$offset;
+ if (')' === $dsn[$offset - 1]) {
+ break;
+ }
+ }
+
+ return [new $class($args), $offset];
+ }
+ }
+
+ if (preg_match('{(\w+)\(}A', $dsn, $matches, 0, $offset)) {
+ throw new InvalidArgumentException(sprintf('The "%s" keyword is not valid (valid ones are "%s"), ', $matches[1], implode('", "', array_keys($keywords))));
+ }
+
+ if ($pos = strcspn($dsn, ' )', $offset)) {
+ return [$this->fromDsnObject(Dsn::fromString(substr($dsn, $offset, $pos))), $offset + $pos];
+ }
+
+ return [$this->fromDsnObject(Dsn::fromString(substr($dsn, $offset))), \strlen($dsn)];
+ }
+ }
+
+ public function fromDsnObject(Dsn $dsn): TransportInterface
+ {
+ foreach ($this->factories as $factory) {
+ if ($factory->supports($dsn)) {
+ return $factory->create($dsn);
+ }
+ }
+
+ throw new UnsupportedSchemeException($dsn);
+ }
+
+ /**
+ * @param EventDispatcherInterface|null $dispatcher
+ * @param HttpClientInterface|null $client
+ * @param LoggerInterface|null $logger
+ *
+ * @return \Traversable
+ */
+ public static function getDefaultFactories(/* EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null */): iterable
+ {
+ $dispatcher = 1 <= \func_num_args() ? func_get_arg(0) : null;
+ $client = 2 <= \func_num_args() ? func_get_arg(1) : null;
+ $logger = 3 <= \func_num_args() ? func_get_arg(2) : null;
+
+ foreach (self::FACTORY_CLASSES as $factoryClass) {
+ if (class_exists($factoryClass)) {
+ yield new $factoryClass($dispatcher, $client, $logger);
+ }
+ }
+
+ yield new NullTransportFactory($dispatcher, $client, $logger);
+
+ yield new SendmailTransportFactory($dispatcher, $client, $logger);
+
+ yield new EsmtpTransportFactory($dispatcher, $client, $logger);
+
+ yield new NativeTransportFactory($dispatcher, $client, $logger);
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/AbstractApiTransport.php b/plugins/email/vendor/symfony/mailer/Transport/AbstractApiTransport.php
new file mode 100644
index 0000000..392484d
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/AbstractApiTransport.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\Exception\RuntimeException;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Email;
+use Symfony\Component\Mime\MessageConverter;
+use Symfony\Contracts\HttpClient\ResponseInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+abstract class AbstractApiTransport extends AbstractHttpTransport
+{
+ abstract protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface;
+
+ protected function doSendHttp(SentMessage $message): ResponseInterface
+ {
+ try {
+ $email = MessageConverter::toEmail($message->getOriginalMessage());
+ } catch (\Exception $e) {
+ throw new RuntimeException(sprintf('Unable to send message with the "%s" transport: ', __CLASS__).$e->getMessage(), 0, $e);
+ }
+
+ return $this->doSendApi($message, $email, $message->getEnvelope());
+ }
+
+ protected function getRecipients(Email $email, Envelope $envelope): array
+ {
+ return array_filter($envelope->getRecipients(), function (Address $address) use ($email) {
+ return false === \in_array($address, array_merge($email->getCc(), $email->getBcc()), true);
+ });
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/AbstractHttpTransport.php b/plugins/email/vendor/symfony/mailer/Transport/AbstractHttpTransport.php
new file mode 100644
index 0000000..2317a0d
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/AbstractHttpTransport.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\HttpClient\HttpClient;
+use Symfony\Component\Mailer\Exception\HttpTransportException;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+use Symfony\Contracts\HttpClient\ResponseInterface;
+
+/**
+ * @author Victor Bocharsky
+ */
+abstract class AbstractHttpTransport extends AbstractTransport
+{
+ protected $host;
+ protected $port;
+ protected $client;
+
+ public function __construct(HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
+ {
+ $this->client = $client;
+ if (null === $client) {
+ if (!class_exists(HttpClient::class)) {
+ throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
+ }
+
+ $this->client = HttpClient::create();
+ }
+
+ parent::__construct($dispatcher, $logger);
+ }
+
+ /**
+ * @return $this
+ */
+ public function setHost(?string $host)
+ {
+ $this->host = $host;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setPort(?int $port)
+ {
+ $this->port = $port;
+
+ return $this;
+ }
+
+ abstract protected function doSendHttp(SentMessage $message): ResponseInterface;
+
+ protected function doSend(SentMessage $message): void
+ {
+ $response = null;
+ try {
+ $response = $this->doSendHttp($message);
+ $message->appendDebug($response->getInfo('debug') ?? '');
+ } catch (HttpTransportException $e) {
+ $e->appendDebug($e->getResponse()->getInfo('debug') ?? '');
+
+ throw $e;
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/AbstractTransport.php b/plugins/email/vendor/symfony/mailer/Transport/AbstractTransport.php
new file mode 100644
index 0000000..77d810b
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/AbstractTransport.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\Event\MessageEvent;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\RawMessage;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+abstract class AbstractTransport implements TransportInterface
+{
+ private $dispatcher;
+ private $logger;
+ private $rate = 0;
+ private $lastSent = 0;
+
+ public function __construct(EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
+ {
+ $this->dispatcher = class_exists(Event::class) && $dispatcher instanceof SymfonyEventDispatcherInterface ? LegacyEventDispatcherProxy::decorate($dispatcher) : $dispatcher;
+ $this->logger = $logger ?? new NullLogger();
+ }
+
+ /**
+ * Sets the maximum number of messages to send per second (0 to disable).
+ *
+ * @return $this
+ */
+ public function setMaxPerSecond(float $rate): self
+ {
+ if (0 >= $rate) {
+ $rate = 0;
+ }
+
+ $this->rate = $rate;
+ $this->lastSent = 0;
+
+ return $this;
+ }
+
+ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
+ {
+ $message = clone $message;
+ $envelope = null !== $envelope ? clone $envelope : Envelope::create($message);
+
+ if (null !== $this->dispatcher) {
+ $event = new MessageEvent($message, $envelope, (string) $this);
+ $this->dispatcher->dispatch($event);
+ $envelope = $event->getEnvelope();
+ }
+
+ $message = new SentMessage($message, $envelope);
+ $this->doSend($message);
+
+ $this->checkThrottling();
+
+ return $message;
+ }
+
+ abstract protected function doSend(SentMessage $message): void;
+
+ /**
+ * @param Address[] $addresses
+ *
+ * @return string[]
+ */
+ protected function stringifyAddresses(array $addresses): array
+ {
+ return array_map(function (Address $a) {
+ return $a->toString();
+ }, $addresses);
+ }
+
+ protected function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ private function checkThrottling()
+ {
+ if (0 == $this->rate) {
+ return;
+ }
+
+ $sleep = (1 / $this->rate) - (microtime(true) - $this->lastSent);
+ if (0 < $sleep) {
+ $this->logger->debug(sprintf('Email transport "%s" sleeps for %.2f seconds', __CLASS__, $sleep));
+ usleep($sleep * 1000000);
+ }
+ $this->lastSent = microtime(true);
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/AbstractTransportFactory.php b/plugins/email/vendor/symfony/mailer/Transport/AbstractTransportFactory.php
new file mode 100644
index 0000000..e1617d2
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/AbstractTransportFactory.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Mailer\Exception\IncompleteDsnException;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+/**
+ * @author Konstantin Myakshin
+ */
+abstract class AbstractTransportFactory implements TransportFactoryInterface
+{
+ protected $dispatcher;
+ protected $client;
+ protected $logger;
+
+ public function __construct(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null)
+ {
+ $this->dispatcher = $dispatcher;
+ $this->client = $client;
+ $this->logger = $logger;
+ }
+
+ public function supports(Dsn $dsn): bool
+ {
+ return \in_array($dsn->getScheme(), $this->getSupportedSchemes());
+ }
+
+ abstract protected function getSupportedSchemes(): array;
+
+ protected function getUser(Dsn $dsn): string
+ {
+ $user = $dsn->getUser();
+ if (null === $user) {
+ throw new IncompleteDsnException('User is not set.');
+ }
+
+ return $user;
+ }
+
+ protected function getPassword(Dsn $dsn): string
+ {
+ $password = $dsn->getPassword();
+ if (null === $password) {
+ throw new IncompleteDsnException('Password is not set.');
+ }
+
+ return $password;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Dsn.php b/plugins/email/vendor/symfony/mailer/Transport/Dsn.php
new file mode 100644
index 0000000..04d3540
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Dsn.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Exception\InvalidArgumentException;
+
+/**
+ * @author Konstantin Myakshin
+ */
+final class Dsn
+{
+ private $scheme;
+ private $host;
+ private $user;
+ private $password;
+ private $port;
+ private $options;
+
+ public function __construct(string $scheme, string $host, string $user = null, string $password = null, int $port = null, array $options = [])
+ {
+ $this->scheme = $scheme;
+ $this->host = $host;
+ $this->user = $user;
+ $this->password = $password;
+ $this->port = $port;
+ $this->options = $options;
+ }
+
+ public static function fromString(string $dsn): self
+ {
+ if (false === $parsedDsn = parse_url($dsn)) {
+ throw new InvalidArgumentException(sprintf('The "%s" mailer DSN is invalid.', $dsn));
+ }
+
+ if (!isset($parsedDsn['scheme'])) {
+ throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a scheme.', $dsn));
+ }
+
+ if (!isset($parsedDsn['host'])) {
+ throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a host (use "default" by default).', $dsn));
+ }
+
+ $user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null;
+ $password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null;
+ $port = $parsedDsn['port'] ?? null;
+ parse_str($parsedDsn['query'] ?? '', $query);
+
+ return new self($parsedDsn['scheme'], $parsedDsn['host'], $user, $password, $port, $query);
+ }
+
+ public function getScheme(): string
+ {
+ return $this->scheme;
+ }
+
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ public function getUser(): ?string
+ {
+ return $this->user;
+ }
+
+ public function getPassword(): ?string
+ {
+ return $this->password;
+ }
+
+ public function getPort(int $default = null): ?int
+ {
+ return $this->port ?? $default;
+ }
+
+ public function getOption(string $key, $default = null)
+ {
+ return $this->options[$key] ?? $default;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/FailoverTransport.php b/plugins/email/vendor/symfony/mailer/Transport/FailoverTransport.php
new file mode 100644
index 0000000..4913901
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/FailoverTransport.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+/**
+ * Uses several Transports using a failover algorithm.
+ *
+ * @author Fabien Potencier
+ */
+class FailoverTransport extends RoundRobinTransport
+{
+ private $currentTransport;
+
+ protected function getNextTransport(): ?TransportInterface
+ {
+ if (null === $this->currentTransport || $this->isTransportDead($this->currentTransport)) {
+ $this->currentTransport = parent::getNextTransport();
+ }
+
+ return $this->currentTransport;
+ }
+
+ protected function getInitialCursor(): int
+ {
+ return 0;
+ }
+
+ protected function getNameSymbol(): string
+ {
+ return 'failover';
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/NativeTransportFactory.php b/plugins/email/vendor/symfony/mailer/Transport/NativeTransportFactory.php
new file mode 100644
index 0000000..8afa53c
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/NativeTransportFactory.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Exception\TransportException;
+use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
+use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport;
+use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
+
+/**
+ * Factory that configures a transport (sendmail or SMTP) based on php.ini settings.
+ *
+ * @author Laurent VOULLEMIER
+ */
+final class NativeTransportFactory extends AbstractTransportFactory
+{
+ public function create(Dsn $dsn): TransportInterface
+ {
+ if (!\in_array($dsn->getScheme(), $this->getSupportedSchemes(), true)) {
+ throw new UnsupportedSchemeException($dsn, 'native', $this->getSupportedSchemes());
+ }
+
+ if ($sendMailPath = ini_get('sendmail_path')) {
+ return new SendmailTransport($sendMailPath, $this->dispatcher, $this->logger);
+ }
+
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ throw new TransportException('sendmail_path is not configured in php.ini.');
+ }
+
+ // Only for windows hosts; at this point non-windows
+ // host have already thrown an exception or returned a transport
+ $host = ini_get('SMTP');
+ $port = (int) ini_get('smtp_port');
+
+ if (!$host || !$port) {
+ throw new TransportException('smtp or smtp_port is not configured in php.ini.');
+ }
+
+ $socketStream = new SocketStream();
+ $socketStream->setHost($host);
+ $socketStream->setPort($port);
+ if (465 !== $port) {
+ $socketStream->disableTls();
+ }
+
+ return new SmtpTransport($socketStream, $this->dispatcher, $this->logger);
+ }
+
+ protected function getSupportedSchemes(): array
+ {
+ return ['native'];
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/NullTransport.php b/plugins/email/vendor/symfony/mailer/Transport/NullTransport.php
new file mode 100644
index 0000000..92fb82a
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/NullTransport.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\SentMessage;
+
+/**
+ * Pretends messages have been sent, but just ignores them.
+ *
+ * @author Fabien Potencier
+ */
+final class NullTransport extends AbstractTransport
+{
+ protected function doSend(SentMessage $message): void
+ {
+ }
+
+ public function __toString(): string
+ {
+ return 'null://';
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/NullTransportFactory.php b/plugins/email/vendor/symfony/mailer/Transport/NullTransportFactory.php
new file mode 100644
index 0000000..4c45f39
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/NullTransportFactory.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
+
+/**
+ * @author Konstantin Myakshin
+ */
+final class NullTransportFactory extends AbstractTransportFactory
+{
+ public function create(Dsn $dsn): TransportInterface
+ {
+ if ('null' === $dsn->getScheme()) {
+ return new NullTransport($this->dispatcher, $this->logger);
+ }
+
+ throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes());
+ }
+
+ protected function getSupportedSchemes(): array
+ {
+ return ['null'];
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/RoundRobinTransport.php b/plugins/email/vendor/symfony/mailer/Transport/RoundRobinTransport.php
new file mode 100644
index 0000000..2f35138
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/RoundRobinTransport.php
@@ -0,0 +1,121 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\Exception\TransportException;
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * Uses several Transports using a round robin algorithm.
+ *
+ * @author Fabien Potencier
+ */
+class RoundRobinTransport implements TransportInterface
+{
+ /**
+ * @var \SplObjectStorage
+ */
+ private $deadTransports;
+ private $transports = [];
+ private $retryPeriod;
+ private $cursor = -1;
+
+ /**
+ * @param TransportInterface[] $transports
+ */
+ public function __construct(array $transports, int $retryPeriod = 60)
+ {
+ if (!$transports) {
+ throw new TransportException(sprintf('"%s" must have at least one transport configured.', static::class));
+ }
+
+ $this->transports = $transports;
+ $this->deadTransports = new \SplObjectStorage();
+ $this->retryPeriod = $retryPeriod;
+ }
+
+ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
+ {
+ while ($transport = $this->getNextTransport()) {
+ try {
+ return $transport->send($message, $envelope);
+ } catch (TransportExceptionInterface $e) {
+ $this->deadTransports[$transport] = microtime(true);
+ }
+ }
+
+ throw new TransportException('All transports failed.');
+ }
+
+ public function __toString(): string
+ {
+ return $this->getNameSymbol().'('.implode(' ', array_map('strval', $this->transports)).')';
+ }
+
+ /**
+ * Rotates the transport list around and returns the first instance.
+ */
+ protected function getNextTransport(): ?TransportInterface
+ {
+ if (-1 === $this->cursor) {
+ $this->cursor = $this->getInitialCursor();
+ }
+
+ $cursor = $this->cursor;
+ while (true) {
+ $transport = $this->transports[$cursor];
+
+ if (!$this->isTransportDead($transport)) {
+ break;
+ }
+
+ if ((microtime(true) - $this->deadTransports[$transport]) > $this->retryPeriod) {
+ $this->deadTransports->detach($transport);
+
+ break;
+ }
+
+ if ($this->cursor === $cursor = $this->moveCursor($cursor)) {
+ return null;
+ }
+ }
+
+ $this->cursor = $this->moveCursor($cursor);
+
+ return $transport;
+ }
+
+ protected function isTransportDead(TransportInterface $transport): bool
+ {
+ return $this->deadTransports->contains($transport);
+ }
+
+ protected function getInitialCursor(): int
+ {
+ // the cursor initial value is randomized so that
+ // when are not in a daemon, we are still rotating the transports
+ return mt_rand(0, \count($this->transports) - 1);
+ }
+
+ protected function getNameSymbol(): string
+ {
+ return 'roundrobin';
+ }
+
+ private function moveCursor(int $cursor): int
+ {
+ return ++$cursor >= \count($this->transports) ? 0 : $cursor;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/SendmailTransport.php b/plugins/email/vendor/symfony/mailer/Transport/SendmailTransport.php
new file mode 100644
index 0000000..43d0920
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/SendmailTransport.php
@@ -0,0 +1,123 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport;
+use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
+use Symfony\Component\Mailer\Transport\Smtp\Stream\ProcessStream;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * SendmailTransport for sending mail through a Sendmail/Postfix (etc..) binary.
+ *
+ * Transport can be instanciated through SendmailTransportFactory or NativeTransportFactory:
+ *
+ * - SendmailTransportFactory to use most common sendmail path and recommanded options
+ * - NativeTransportFactory when configuration is set via php.ini
+ *
+ * @author Fabien Potencier
+ * @author Chris Corbyn
+ */
+class SendmailTransport extends AbstractTransport
+{
+ private $command = '/usr/sbin/sendmail -bs';
+ private $stream;
+ private $transport;
+
+ /**
+ * Constructor.
+ *
+ * Supported modes are -bs and -t, with any additional flags desired.
+ *
+ * The recommended mode is "-bs" since it is interactive and failure notifications are hence possible.
+ * Note that the -t mode does not support error reporting and does not support Bcc properly (the Bcc headers are not removed).
+ *
+ * If using -t mode, you are strongly advised to include -oi or -i in the flags (like /usr/sbin/sendmail -oi -t)
+ *
+ * -f flag will be appended automatically if one is not present.
+ */
+ public function __construct(string $command = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
+ {
+ parent::__construct($dispatcher, $logger);
+
+ if (null !== $command) {
+ if (!str_contains($command, ' -bs') && !str_contains($command, ' -t')) {
+ throw new \InvalidArgumentException(sprintf('Unsupported sendmail command flags "%s"; must be one of "-bs" or "-t" but can include additional flags.', $command));
+ }
+
+ $this->command = $command;
+ }
+
+ $this->stream = new ProcessStream();
+ if (str_contains($this->command, ' -bs')) {
+ $this->stream->setCommand($this->command);
+ $this->transport = new SmtpTransport($this->stream, $dispatcher, $logger);
+ }
+ }
+
+ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
+ {
+ if ($this->transport) {
+ return $this->transport->send($message, $envelope);
+ }
+
+ return parent::send($message, $envelope);
+ }
+
+ public function __toString(): string
+ {
+ if ($this->transport) {
+ return (string) $this->transport;
+ }
+
+ return 'smtp://sendmail';
+ }
+
+ protected function doSend(SentMessage $message): void
+ {
+ $this->getLogger()->debug(sprintf('Email transport "%s" starting', __CLASS__));
+
+ $command = $this->command;
+
+ if ($recipients = $message->getEnvelope()->getRecipients()) {
+ $command = str_replace(' -t', '', $command);
+ }
+
+ if (!str_contains($command, ' -f')) {
+ $command .= ' -f'.escapeshellarg($message->getEnvelope()->getSender()->getEncodedAddress());
+ }
+
+ $chunks = AbstractStream::replace("\r\n", "\n", $message->toIterable());
+
+ if (!str_contains($command, ' -i') && !str_contains($command, ' -oi')) {
+ $chunks = AbstractStream::replace("\n.", "\n..", $chunks);
+ }
+
+ foreach ($recipients as $recipient) {
+ $command .= ' '.escapeshellarg($recipient->getEncodedAddress());
+ }
+
+ $this->stream->setCommand($command);
+ $this->stream->initialize();
+ foreach ($chunks as $chunk) {
+ $this->stream->write($chunk);
+ }
+ $this->stream->flush();
+ $this->stream->terminate();
+
+ $this->getLogger()->debug(sprintf('Email transport "%s" stopped', __CLASS__));
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/SendmailTransportFactory.php b/plugins/email/vendor/symfony/mailer/Transport/SendmailTransportFactory.php
new file mode 100644
index 0000000..6d977e7
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/SendmailTransportFactory.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
+
+/**
+ * @author Konstantin Myakshin
+ */
+final class SendmailTransportFactory extends AbstractTransportFactory
+{
+ public function create(Dsn $dsn): TransportInterface
+ {
+ if ('sendmail+smtp' === $dsn->getScheme() || 'sendmail' === $dsn->getScheme()) {
+ return new SendmailTransport($dsn->getOption('command'), $this->dispatcher, $this->logger);
+ }
+
+ throw new UnsupportedSchemeException($dsn, 'sendmail', $this->getSupportedSchemes());
+ }
+
+ protected function getSupportedSchemes(): array
+ {
+ return ['sendmail', 'sendmail+smtp'];
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/AuthenticatorInterface.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/AuthenticatorInterface.php
new file mode 100644
index 0000000..98ea2d4
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/AuthenticatorInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp\Auth;
+
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
+
+/**
+ * An Authentication mechanism.
+ *
+ * @author Chris Corbyn
+ */
+interface AuthenticatorInterface
+{
+ /**
+ * Tries to authenticate the user.
+ *
+ * @throws TransportExceptionInterface
+ */
+ public function authenticate(EsmtpTransport $client): void;
+
+ /**
+ * Gets the name of the AUTH mechanism this Authenticator handles.
+ */
+ public function getAuthKeyword(): string;
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/CramMd5Authenticator.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/CramMd5Authenticator.php
new file mode 100644
index 0000000..b2ec7b0
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/CramMd5Authenticator.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp\Auth;
+
+use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
+
+/**
+ * Handles CRAM-MD5 authentication.
+ *
+ * @author Chris Corbyn
+ */
+class CramMd5Authenticator implements AuthenticatorInterface
+{
+ public function getAuthKeyword(): string
+ {
+ return 'CRAM-MD5';
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see https://www.ietf.org/rfc/rfc4954.txt
+ */
+ public function authenticate(EsmtpTransport $client): void
+ {
+ $challenge = $client->executeCommand("AUTH CRAM-MD5\r\n", [334]);
+ $challenge = base64_decode(substr($challenge, 4));
+ $message = base64_encode($client->getUsername().' '.$this->getResponse($client->getPassword(), $challenge));
+ $client->executeCommand(sprintf("%s\r\n", $message), [235]);
+ }
+
+ /**
+ * Generates a CRAM-MD5 response from a server challenge.
+ */
+ private function getResponse(string $secret, string $challenge): string
+ {
+ if (\strlen($secret) > 64) {
+ $secret = pack('H32', md5($secret));
+ }
+
+ if (\strlen($secret) < 64) {
+ $secret = str_pad($secret, 64, \chr(0));
+ }
+
+ $kipad = substr($secret, 0, 64) ^ str_repeat(\chr(0x36), 64);
+ $kopad = substr($secret, 0, 64) ^ str_repeat(\chr(0x5C), 64);
+
+ $inner = pack('H32', md5($kipad.$challenge));
+ $digest = md5($kopad.$inner);
+
+ return $digest;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/LoginAuthenticator.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/LoginAuthenticator.php
new file mode 100644
index 0000000..1ce321d
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/LoginAuthenticator.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp\Auth;
+
+use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
+
+/**
+ * Handles LOGIN authentication.
+ *
+ * @author Chris Corbyn
+ */
+class LoginAuthenticator implements AuthenticatorInterface
+{
+ public function getAuthKeyword(): string
+ {
+ return 'LOGIN';
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see https://www.ietf.org/rfc/rfc4954.txt
+ */
+ public function authenticate(EsmtpTransport $client): void
+ {
+ $client->executeCommand("AUTH LOGIN\r\n", [334]);
+ $client->executeCommand(sprintf("%s\r\n", base64_encode($client->getUsername())), [334]);
+ $client->executeCommand(sprintf("%s\r\n", base64_encode($client->getPassword())), [235]);
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/PlainAuthenticator.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/PlainAuthenticator.php
new file mode 100644
index 0000000..8d60690
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/PlainAuthenticator.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp\Auth;
+
+use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
+
+/**
+ * Handles PLAIN authentication.
+ *
+ * @author Chris Corbyn
+ */
+class PlainAuthenticator implements AuthenticatorInterface
+{
+ public function getAuthKeyword(): string
+ {
+ return 'PLAIN';
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see https://www.ietf.org/rfc/rfc4954.txt
+ */
+ public function authenticate(EsmtpTransport $client): void
+ {
+ $client->executeCommand(sprintf("AUTH PLAIN %s\r\n", base64_encode($client->getUsername().\chr(0).$client->getUsername().\chr(0).$client->getPassword())), [235]);
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php
new file mode 100644
index 0000000..7941776
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp\Auth;
+
+use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
+
+/**
+ * Handles XOAUTH2 authentication.
+ *
+ * @author xu.li
+ *
+ * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol
+ */
+class XOAuth2Authenticator implements AuthenticatorInterface
+{
+ public function getAuthKeyword(): string
+ {
+ return 'XOAUTH2';
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism
+ */
+ public function authenticate(EsmtpTransport $client): void
+ {
+ $client->executeCommand('AUTH XOAUTH2 '.base64_encode('user='.$client->getUsername()."\1auth=Bearer ".$client->getPassword()."\1\1")."\r\n", [235]);
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/EsmtpTransport.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/EsmtpTransport.php
new file mode 100644
index 0000000..1dcb53f
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/EsmtpTransport.php
@@ -0,0 +1,200 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Mailer\Exception\TransportException;
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mailer\Transport\Smtp\Auth\AuthenticatorInterface;
+use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
+
+/**
+ * Sends Emails over SMTP with ESMTP support.
+ *
+ * @author Fabien Potencier
+ * @author Chris Corbyn
+ */
+class EsmtpTransport extends SmtpTransport
+{
+ private $authenticators = [];
+ private $username = '';
+ private $password = '';
+
+ public function __construct(string $host = 'localhost', int $port = 0, bool $tls = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
+ {
+ parent::__construct(null, $dispatcher, $logger);
+
+ // order is important here (roughly most secure and popular first)
+ $this->authenticators = [
+ new Auth\CramMd5Authenticator(),
+ new Auth\LoginAuthenticator(),
+ new Auth\PlainAuthenticator(),
+ new Auth\XOAuth2Authenticator(),
+ ];
+
+ /** @var SocketStream $stream */
+ $stream = $this->getStream();
+
+ if (null === $tls) {
+ if (465 === $port) {
+ $tls = true;
+ } else {
+ $tls = \defined('OPENSSL_VERSION_NUMBER') && 0 === $port && 'localhost' !== $host;
+ }
+ }
+ if (!$tls) {
+ $stream->disableTls();
+ }
+ if (0 === $port) {
+ $port = $tls ? 465 : 25;
+ }
+
+ $stream->setHost($host);
+ $stream->setPort($port);
+ }
+
+ /**
+ * @return $this
+ */
+ public function setUsername(string $username): self
+ {
+ $this->username = $username;
+
+ return $this;
+ }
+
+ public function getUsername(): string
+ {
+ return $this->username;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setPassword(string $password): self
+ {
+ $this->password = $password;
+
+ return $this;
+ }
+
+ public function getPassword(): string
+ {
+ return $this->password;
+ }
+
+ public function addAuthenticator(AuthenticatorInterface $authenticator): void
+ {
+ $this->authenticators[] = $authenticator;
+ }
+
+ protected function doHeloCommand(): void
+ {
+ if (!$capabilities = $this->callHeloCommand()) {
+ return;
+ }
+
+ /** @var SocketStream $stream */
+ $stream = $this->getStream();
+ // WARNING: !$stream->isTLS() is right, 100% sure :)
+ // if you think that the ! should be removed, read the code again
+ // if doing so "fixes" your issue then it probably means your SMTP server behaves incorrectly or is wrongly configured
+ if (!$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS', $capabilities)) {
+ $this->executeCommand("STARTTLS\r\n", [220]);
+
+ if (!$stream->startTLS()) {
+ throw new TransportException('Unable to connect with STARTTLS.');
+ }
+
+ $capabilities = $this->callHeloCommand();
+ }
+
+ if (\array_key_exists('AUTH', $capabilities)) {
+ $this->handleAuth($capabilities['AUTH']);
+ }
+ }
+
+ private function callHeloCommand(): array
+ {
+ try {
+ $response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]);
+ } catch (TransportExceptionInterface $e) {
+ try {
+ parent::doHeloCommand();
+
+ return [];
+ } catch (TransportExceptionInterface $ex) {
+ if (!$ex->getCode()) {
+ throw $e;
+ }
+
+ throw $ex;
+ }
+ }
+
+ $capabilities = [];
+ $lines = explode("\r\n", trim($response));
+ array_shift($lines);
+ foreach ($lines as $line) {
+ if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) {
+ $value = strtoupper(ltrim($matches[2], ' ='));
+ $capabilities[strtoupper($matches[1])] = $value ? explode(' ', $value) : [];
+ }
+ }
+
+ return $capabilities;
+ }
+
+ private function handleAuth(array $modes): void
+ {
+ if (!$this->username) {
+ return;
+ }
+
+ $authNames = [];
+ $errors = [];
+ $modes = array_map('strtolower', $modes);
+ foreach ($this->authenticators as $authenticator) {
+ if (!\in_array(strtolower($authenticator->getAuthKeyword()), $modes, true)) {
+ continue;
+ }
+
+ $authNames[] = $authenticator->getAuthKeyword();
+ try {
+ $authenticator->authenticate($this);
+
+ return;
+ } catch (TransportExceptionInterface $e) {
+ try {
+ $this->executeCommand("RSET\r\n", [250]);
+ } catch (TransportExceptionInterface $_) {
+ // ignore this exception as it probably means that the server error was final
+ }
+
+ // keep the error message, but tries the other authenticators
+ $errors[$authenticator->getAuthKeyword()] = $e->getMessage();
+ }
+ }
+
+ if (!$authNames) {
+ throw new TransportException(sprintf('Failed to find an authenticator supported by the SMTP server, which currently supports: "%s".', implode('", "', $modes)));
+ }
+
+ $message = sprintf('Failed to authenticate on SMTP server with username "%s" using the following authenticators: "%s".', $this->username, implode('", "', $authNames));
+ foreach ($errors as $name => $error) {
+ $message .= sprintf(' Authenticator "%s" returned "%s".', $name, $error);
+ }
+
+ throw new TransportException($message);
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/EsmtpTransportFactory.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/EsmtpTransportFactory.php
new file mode 100644
index 0000000..e2a280e
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/EsmtpTransportFactory.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp;
+
+use Symfony\Component\Mailer\Transport\AbstractTransportFactory;
+use Symfony\Component\Mailer\Transport\Dsn;
+use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+
+/**
+ * @author Konstantin Myakshin
+ */
+final class EsmtpTransportFactory extends AbstractTransportFactory
+{
+ public function create(Dsn $dsn): TransportInterface
+ {
+ $tls = 'smtps' === $dsn->getScheme() ? true : null;
+ $port = $dsn->getPort(0);
+ $host = $dsn->getHost();
+
+ $transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger);
+
+ if ('' !== $dsn->getOption('verify_peer') && !filter_var($dsn->getOption('verify_peer', true), \FILTER_VALIDATE_BOOLEAN)) {
+ /** @var SocketStream $stream */
+ $stream = $transport->getStream();
+ $streamOptions = $stream->getStreamOptions();
+
+ $streamOptions['ssl']['verify_peer'] = false;
+ $streamOptions['ssl']['verify_peer_name'] = false;
+
+ $stream->setStreamOptions($streamOptions);
+ }
+
+ if ($user = $dsn->getUser()) {
+ $transport->setUsername($user);
+ }
+
+ if ($password = $dsn->getPassword()) {
+ $transport->setPassword($password);
+ }
+
+ if (null !== ($localDomain = $dsn->getOption('local_domain'))) {
+ $transport->setLocalDomain($localDomain);
+ }
+
+ if (null !== ($restartThreshold = $dsn->getOption('restart_threshold'))) {
+ $transport->setRestartThreshold((int) $restartThreshold, (int) $dsn->getOption('restart_threshold_sleep', 0));
+ }
+
+ if (null !== ($pingThreshold = $dsn->getOption('ping_threshold'))) {
+ $transport->setPingThreshold((int) $pingThreshold);
+ }
+
+ return $transport;
+ }
+
+ protected function getSupportedSchemes(): array
+ {
+ return ['smtp', 'smtps'];
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/SmtpTransport.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/SmtpTransport.php
new file mode 100644
index 0000000..92af6aa
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/SmtpTransport.php
@@ -0,0 +1,361 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\Exception\LogicException;
+use Symfony\Component\Mailer\Exception\TransportException;
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mailer\Transport\AbstractTransport;
+use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
+use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * Sends emails over SMTP.
+ *
+ * @author Fabien Potencier
+ * @author Chris Corbyn
+ */
+class SmtpTransport extends AbstractTransport
+{
+ private $started = false;
+ private $restartThreshold = 100;
+ private $restartThresholdSleep = 0;
+ private $restartCounter;
+ private $pingThreshold = 100;
+ private $lastMessageTime = 0;
+ private $stream;
+ private $domain = '[127.0.0.1]';
+
+ public function __construct(AbstractStream $stream = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
+ {
+ parent::__construct($dispatcher, $logger);
+
+ $this->stream = $stream ?? new SocketStream();
+ }
+
+ public function getStream(): AbstractStream
+ {
+ return $this->stream;
+ }
+
+ /**
+ * Sets the maximum number of messages to send before re-starting the transport.
+ *
+ * By default, the threshold is set to 100 (and no sleep at restart).
+ *
+ * @param int $threshold The maximum number of messages (0 to disable)
+ * @param int $sleep The number of seconds to sleep between stopping and re-starting the transport
+ *
+ * @return $this
+ */
+ public function setRestartThreshold(int $threshold, int $sleep = 0): self
+ {
+ $this->restartThreshold = $threshold;
+ $this->restartThresholdSleep = $sleep;
+
+ return $this;
+ }
+
+ /**
+ * Sets the minimum number of seconds required between two messages, before the server is pinged.
+ * If the transport wants to send a message and the time since the last message exceeds the specified threshold,
+ * the transport will ping the server first (NOOP command) to check if the connection is still alive.
+ * Otherwise the message will be sent without pinging the server first.
+ *
+ * Do not set the threshold too low, as the SMTP server may drop the connection if there are too many
+ * non-mail commands (like pinging the server with NOOP).
+ *
+ * By default, the threshold is set to 100 seconds.
+ *
+ * @param int $seconds The minimum number of seconds between two messages required to ping the server
+ *
+ * @return $this
+ */
+ public function setPingThreshold(int $seconds): self
+ {
+ $this->pingThreshold = $seconds;
+
+ return $this;
+ }
+
+ /**
+ * Sets the name of the local domain that will be used in HELO.
+ *
+ * This should be a fully-qualified domain name and should be truly the domain
+ * you're using.
+ *
+ * If your server does not have a domain name, use the IP address. This will
+ * automatically be wrapped in square brackets as described in RFC 5321,
+ * section 4.1.3.
+ *
+ * @return $this
+ */
+ public function setLocalDomain(string $domain): self
+ {
+ if ('' !== $domain && '[' !== $domain[0]) {
+ if (filter_var($domain, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
+ $domain = '['.$domain.']';
+ } elseif (filter_var($domain, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
+ $domain = '[IPv6:'.$domain.']';
+ }
+ }
+
+ $this->domain = $domain;
+
+ return $this;
+ }
+
+ /**
+ * Gets the name of the domain that will be used in HELO.
+ *
+ * If an IP address was specified, this will be returned wrapped in square
+ * brackets as described in RFC 5321, section 4.1.3.
+ */
+ public function getLocalDomain(): string
+ {
+ return $this->domain;
+ }
+
+ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
+ {
+ try {
+ $message = parent::send($message, $envelope);
+ } catch (TransportExceptionInterface $e) {
+ if ($this->started) {
+ try {
+ $this->executeCommand("RSET\r\n", [250]);
+ } catch (TransportExceptionInterface $_) {
+ // ignore this exception as it probably means that the server error was final
+ }
+ }
+
+ throw $e;
+ }
+
+ $this->checkRestartThreshold();
+
+ return $message;
+ }
+
+ public function __toString(): string
+ {
+ if ($this->stream instanceof SocketStream) {
+ $name = sprintf('smtp%s://%s', ($tls = $this->stream->isTLS()) ? 's' : '', $this->stream->getHost());
+ $port = $this->stream->getPort();
+ if (!(25 === $port || ($tls && 465 === $port))) {
+ $name .= ':'.$port;
+ }
+
+ return $name;
+ }
+
+ return 'smtp://sendmail';
+ }
+
+ /**
+ * Runs a command against the stream, expecting the given response codes.
+ *
+ * @param int[] $codes
+ *
+ * @throws TransportException when an invalid response if received
+ *
+ * @internal
+ */
+ public function executeCommand(string $command, array $codes): string
+ {
+ $this->stream->write($command);
+ $response = $this->getFullResponse();
+ $this->assertResponseCode($response, $codes);
+
+ return $response;
+ }
+
+ protected function doSend(SentMessage $message): void
+ {
+ if (microtime(true) - $this->lastMessageTime > $this->pingThreshold) {
+ $this->ping();
+ }
+
+ if (!$this->started) {
+ $this->start();
+ }
+
+ try {
+ $envelope = $message->getEnvelope();
+ $this->doMailFromCommand($envelope->getSender()->getEncodedAddress());
+ foreach ($envelope->getRecipients() as $recipient) {
+ $this->doRcptToCommand($recipient->getEncodedAddress());
+ }
+
+ $this->executeCommand("DATA\r\n", [354]);
+ try {
+ foreach (AbstractStream::replace("\r\n.", "\r\n..", $message->toIterable()) as $chunk) {
+ $this->stream->write($chunk, false);
+ }
+ $this->stream->flush();
+ } catch (TransportExceptionInterface $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ $this->stream->terminate();
+ $this->started = false;
+ $this->getLogger()->debug(sprintf('Email transport "%s" stopped', __CLASS__));
+ throw $e;
+ }
+ $this->executeCommand("\r\n.\r\n", [250]);
+ $message->appendDebug($this->stream->getDebug());
+ $this->lastMessageTime = microtime(true);
+ } catch (TransportExceptionInterface $e) {
+ $e->appendDebug($this->stream->getDebug());
+ $this->lastMessageTime = 0;
+ throw $e;
+ }
+ }
+
+ protected function doHeloCommand(): void
+ {
+ $this->executeCommand(sprintf("HELO %s\r\n", $this->domain), [250]);
+ }
+
+ private function doMailFromCommand(string $address): void
+ {
+ $this->executeCommand(sprintf("MAIL FROM:<%s>\r\n", $address), [250]);
+ }
+
+ private function doRcptToCommand(string $address): void
+ {
+ $this->executeCommand(sprintf("RCPT TO:<%s>\r\n", $address), [250, 251, 252]);
+ }
+
+ private function start(): void
+ {
+ if ($this->started) {
+ return;
+ }
+
+ $this->getLogger()->debug(sprintf('Email transport "%s" starting', __CLASS__));
+
+ $this->stream->initialize();
+ $this->assertResponseCode($this->getFullResponse(), [220]);
+ $this->doHeloCommand();
+ $this->started = true;
+ $this->lastMessageTime = 0;
+
+ $this->getLogger()->debug(sprintf('Email transport "%s" started', __CLASS__));
+ }
+
+ private function stop(): void
+ {
+ if (!$this->started) {
+ return;
+ }
+
+ $this->getLogger()->debug(sprintf('Email transport "%s" stopping', __CLASS__));
+
+ try {
+ $this->executeCommand("QUIT\r\n", [221]);
+ } catch (TransportExceptionInterface $e) {
+ } finally {
+ $this->stream->terminate();
+ $this->started = false;
+ $this->getLogger()->debug(sprintf('Email transport "%s" stopped', __CLASS__));
+ }
+ }
+
+ private function ping(): void
+ {
+ if (!$this->started) {
+ return;
+ }
+
+ try {
+ $this->executeCommand("NOOP\r\n", [250]);
+ } catch (TransportExceptionInterface $e) {
+ $this->stop();
+ }
+ }
+
+ /**
+ * @throws TransportException if a response code is incorrect
+ */
+ private function assertResponseCode(string $response, array $codes): void
+ {
+ if (!$codes) {
+ throw new LogicException('You must set the expected response code.');
+ }
+
+ [$code] = sscanf($response, '%3d');
+ $valid = \in_array($code, $codes);
+
+ if (!$valid || !$response) {
+ $codeStr = $code ? sprintf('code "%s"', $code) : 'empty code';
+ $responseStr = $response ? sprintf(', with message "%s"', trim($response)) : '';
+
+ throw new TransportException(sprintf('Expected response code "%s" but got ', implode('/', $codes)).$codeStr.$responseStr.'.', $code ?: 0);
+ }
+ }
+
+ private function getFullResponse(): string
+ {
+ $response = '';
+ do {
+ $line = $this->stream->readLine();
+ $response .= $line;
+ } while ($line && isset($line[3]) && ' ' !== $line[3]);
+
+ return $response;
+ }
+
+ private function checkRestartThreshold(): void
+ {
+ // when using sendmail via non-interactive mode, the transport is never "started"
+ if (!$this->started) {
+ return;
+ }
+
+ ++$this->restartCounter;
+ if ($this->restartCounter < $this->restartThreshold) {
+ return;
+ }
+
+ $this->stop();
+ if (0 < $sleep = $this->restartThresholdSleep) {
+ $this->getLogger()->debug(sprintf('Email transport "%s" sleeps for %d seconds after stopping', __CLASS__, $sleep));
+
+ sleep($sleep);
+ }
+ $this->start();
+ $this->restartCounter = 0;
+ }
+
+ /**
+ * @return array
+ */
+ public function __sleep()
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ $this->stop();
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/AbstractStream.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/AbstractStream.php
new file mode 100644
index 0000000..28cb616
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/AbstractStream.php
@@ -0,0 +1,138 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp\Stream;
+
+use Symfony\Component\Mailer\Exception\TransportException;
+
+/**
+ * A stream supporting remote sockets and local processes.
+ *
+ * @author Fabien Potencier
+ * @author Nicolas Grekas
+ * @author Chris Corbyn
+ *
+ * @internal
+ */
+abstract class AbstractStream
+{
+ protected $stream;
+ protected $in;
+ protected $out;
+
+ private $debug = '';
+
+ public function write(string $bytes, bool $debug = true): void
+ {
+ if ($debug) {
+ foreach (explode("\n", trim($bytes)) as $line) {
+ $this->debug .= sprintf("> %s\n", $line);
+ }
+ }
+
+ $bytesToWrite = \strlen($bytes);
+ $totalBytesWritten = 0;
+ while ($totalBytesWritten < $bytesToWrite) {
+ $bytesWritten = @fwrite($this->in, substr($bytes, $totalBytesWritten));
+ if (false === $bytesWritten || 0 === $bytesWritten) {
+ throw new TransportException('Unable to write bytes on the wire.');
+ }
+
+ $totalBytesWritten += $bytesWritten;
+ }
+ }
+
+ /**
+ * Flushes the contents of the stream (empty it) and set the internal pointer to the beginning.
+ */
+ public function flush(): void
+ {
+ fflush($this->in);
+ }
+
+ /**
+ * Performs any initialization needed.
+ */
+ abstract public function initialize(): void;
+
+ public function terminate(): void
+ {
+ $this->stream = $this->out = $this->in = null;
+ }
+
+ public function readLine(): string
+ {
+ if (feof($this->out)) {
+ return '';
+ }
+
+ $line = fgets($this->out);
+ if ('' === $line) {
+ $metas = stream_get_meta_data($this->out);
+ if ($metas['timed_out']) {
+ throw new TransportException(sprintf('Connection to "%s" timed out.', $this->getReadConnectionDescription()));
+ }
+ if ($metas['eof']) {
+ throw new TransportException(sprintf('Connection to "%s" has been closed unexpectedly.', $this->getReadConnectionDescription()));
+ }
+ }
+
+ $this->debug .= sprintf('< %s', $line);
+
+ return $line;
+ }
+
+ public function getDebug(): string
+ {
+ $debug = $this->debug;
+ $this->debug = '';
+
+ return $debug;
+ }
+
+ public static function replace(string $from, string $to, iterable $chunks): \Generator
+ {
+ if ('' === $from) {
+ yield from $chunks;
+
+ return;
+ }
+
+ $carry = '';
+ $fromLen = \strlen($from);
+
+ foreach ($chunks as $chunk) {
+ if ('' === $chunk = $carry.$chunk) {
+ continue;
+ }
+
+ if (str_contains($chunk, $from)) {
+ $chunk = explode($from, $chunk);
+ $carry = array_pop($chunk);
+
+ yield implode($to, $chunk).$to;
+ } else {
+ $carry = $chunk;
+ }
+
+ if (\strlen($carry) > $fromLen) {
+ yield substr($carry, 0, -$fromLen);
+ $carry = substr($carry, -$fromLen);
+ }
+ }
+
+ if ('' !== $carry) {
+ yield $carry;
+ }
+ }
+
+ abstract protected function getReadConnectionDescription(): string;
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/ProcessStream.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/ProcessStream.php
new file mode 100644
index 0000000..a8a8603
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/ProcessStream.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp\Stream;
+
+use Symfony\Component\Mailer\Exception\TransportException;
+
+/**
+ * A stream supporting local processes.
+ *
+ * @author Fabien Potencier
+ * @author Chris Corbyn
+ *
+ * @internal
+ */
+final class ProcessStream extends AbstractStream
+{
+ private $command;
+
+ public function setCommand(string $command)
+ {
+ $this->command = $command;
+ }
+
+ public function initialize(): void
+ {
+ $descriptorSpec = [
+ 0 => ['pipe', 'r'],
+ 1 => ['pipe', 'w'],
+ 2 => ['pipe', 'w'],
+ ];
+ $pipes = [];
+ $this->stream = proc_open($this->command, $descriptorSpec, $pipes);
+ stream_set_blocking($pipes[2], false);
+ if ($err = stream_get_contents($pipes[2])) {
+ throw new TransportException('Process could not be started: '.$err);
+ }
+ $this->in = &$pipes[0];
+ $this->out = &$pipes[1];
+ }
+
+ public function terminate(): void
+ {
+ if (null !== $this->stream) {
+ fclose($this->in);
+ fclose($this->out);
+ proc_close($this->stream);
+ }
+
+ parent::terminate();
+ }
+
+ protected function getReadConnectionDescription(): string
+ {
+ return 'process '.$this->command;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/SocketStream.php b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/SocketStream.php
new file mode 100644
index 0000000..144ab7f
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Smtp/Stream/SocketStream.php
@@ -0,0 +1,193 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport\Smtp\Stream;
+
+use Symfony\Component\Mailer\Exception\TransportException;
+
+/**
+ * A stream supporting remote sockets.
+ *
+ * @author Fabien Potencier
+ * @author Chris Corbyn
+ *
+ * @internal
+ */
+final class SocketStream extends AbstractStream
+{
+ private $url;
+ private $host = 'localhost';
+ private $port = 465;
+ private $timeout;
+ private $tls = true;
+ private $sourceIp;
+ private $streamContextOptions = [];
+
+ /**
+ * @return $this
+ */
+ public function setTimeout(float $timeout): self
+ {
+ $this->timeout = $timeout;
+
+ return $this;
+ }
+
+ public function getTimeout(): float
+ {
+ return $this->timeout ?? (float) \ini_get('default_socket_timeout');
+ }
+
+ /**
+ * Literal IPv6 addresses should be wrapped in square brackets.
+ *
+ * @return $this
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+
+ return $this;
+ }
+
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+
+ return $this;
+ }
+
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * Sets the TLS/SSL on the socket (disables STARTTLS).
+ *
+ * @return $this
+ */
+ public function disableTls(): self
+ {
+ $this->tls = false;
+
+ return $this;
+ }
+
+ public function isTLS(): bool
+ {
+ return $this->tls;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setStreamOptions(array $options): self
+ {
+ $this->streamContextOptions = $options;
+
+ return $this;
+ }
+
+ public function getStreamOptions(): array
+ {
+ return $this->streamContextOptions;
+ }
+
+ /**
+ * Sets the source IP.
+ *
+ * IPv6 addresses should be wrapped in square brackets.
+ *
+ * @return $this
+ */
+ public function setSourceIp(string $ip): self
+ {
+ $this->sourceIp = $ip;
+
+ return $this;
+ }
+
+ /**
+ * Returns the IP used to connect to the destination.
+ */
+ public function getSourceIp(): ?string
+ {
+ return $this->sourceIp;
+ }
+
+ public function initialize(): void
+ {
+ $this->url = $this->host.':'.$this->port;
+ if ($this->tls) {
+ $this->url = 'ssl://'.$this->url;
+ }
+ $options = [];
+ if ($this->sourceIp) {
+ $options['socket']['bindto'] = $this->sourceIp.':0';
+ }
+ if ($this->streamContextOptions) {
+ $options = array_merge($options, $this->streamContextOptions);
+ }
+ // do it unconditionnally as it will be used by STARTTLS as well if supported
+ $options['ssl']['crypto_method'] = $options['ssl']['crypto_method'] ?? \STREAM_CRYPTO_METHOD_TLS_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
+ $streamContext = stream_context_create($options);
+
+ $timeout = $this->getTimeout();
+ set_error_handler(function ($type, $msg) {
+ throw new TransportException(sprintf('Connection could not be established with host "%s": ', $this->url).$msg);
+ });
+ try {
+ $this->stream = stream_socket_client($this->url, $errno, $errstr, $timeout, \STREAM_CLIENT_CONNECT, $streamContext);
+ } finally {
+ restore_error_handler();
+ }
+
+ stream_set_blocking($this->stream, true);
+ stream_set_timeout($this->stream, $timeout);
+ $this->in = &$this->stream;
+ $this->out = &$this->stream;
+ }
+
+ public function startTLS(): bool
+ {
+ set_error_handler(function ($type, $msg) {
+ throw new TransportException('Unable to connect with STARTTLS: '.$msg);
+ });
+ try {
+ return stream_socket_enable_crypto($this->stream, true);
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ public function terminate(): void
+ {
+ if (null !== $this->stream) {
+ fclose($this->stream);
+ }
+
+ parent::terminate();
+ }
+
+ protected function getReadConnectionDescription(): string
+ {
+ return $this->url;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/TransportFactoryInterface.php b/plugins/email/vendor/symfony/mailer/Transport/TransportFactoryInterface.php
new file mode 100644
index 0000000..9785ae8
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/TransportFactoryInterface.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Exception\IncompleteDsnException;
+use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
+
+/**
+ * @author Konstantin Myakshin
+ */
+interface TransportFactoryInterface
+{
+ /**
+ * @throws UnsupportedSchemeException
+ * @throws IncompleteDsnException
+ */
+ public function create(Dsn $dsn): TransportInterface;
+
+ public function supports(Dsn $dsn): bool;
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/TransportInterface.php b/plugins/email/vendor/symfony/mailer/Transport/TransportInterface.php
new file mode 100644
index 0000000..ed562cf
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/TransportInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * Interface for all mailer transports.
+ *
+ * When sending emails, you should prefer MailerInterface implementations
+ * as they allow asynchronous sending.
+ *
+ * @author Fabien Potencier
+ */
+interface TransportInterface
+{
+ /**
+ * @throws TransportExceptionInterface
+ */
+ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage;
+
+ public function __toString(): string;
+}
diff --git a/plugins/email/vendor/symfony/mailer/Transport/Transports.php b/plugins/email/vendor/symfony/mailer/Transport/Transports.php
new file mode 100644
index 0000000..702fc5c
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/Transport/Transports.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Transport;
+
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\Exception\InvalidArgumentException;
+use Symfony\Component\Mailer\Exception\LogicException;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * @author Fabien Potencier
+ */
+final class Transports implements TransportInterface
+{
+ private $transports;
+ private $default;
+
+ /**
+ * @param TransportInterface[] $transports
+ */
+ public function __construct(iterable $transports)
+ {
+ $this->transports = [];
+ foreach ($transports as $name => $transport) {
+ if (null === $this->default) {
+ $this->default = $transport;
+ }
+ $this->transports[$name] = $transport;
+ }
+
+ if (!$this->transports) {
+ throw new LogicException(sprintf('"%s" must have at least one transport configured.', __CLASS__));
+ }
+ }
+
+ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
+ {
+ /** @var Message $message */
+ if (RawMessage::class === \get_class($message) || !$message->getHeaders()->has('X-Transport')) {
+ return $this->default->send($message, $envelope);
+ }
+
+ $headers = $message->getHeaders();
+ $transport = $headers->get('X-Transport')->getBody();
+ $headers->remove('X-Transport');
+
+ if (!isset($this->transports[$transport])) {
+ throw new InvalidArgumentException(sprintf('The "%s" transport does not exist (available transports: "%s").', $transport, implode('", "', array_keys($this->transports))));
+ }
+
+ try {
+ return $this->transports[$transport]->send($message, $envelope);
+ } catch (\Throwable $e) {
+ $headers->addTextHeader('X-Transport', $transport);
+
+ throw $e;
+ }
+ }
+
+ public function __toString(): string
+ {
+ return '['.implode(',', array_keys($this->transports)).']';
+ }
+}
diff --git a/plugins/email/vendor/symfony/mailer/composer.json b/plugins/email/vendor/symfony/mailer/composer.json
new file mode 100644
index 0000000..53cf0f5
--- /dev/null
+++ b/plugins/email/vendor/symfony/mailer/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "symfony/mailer",
+ "type": "library",
+ "description": "Helps sending emails",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "egulias/email-validator": "^2.1.10|^3",
+ "psr/event-dispatcher": "^1",
+ "psr/log": "^1|^2|^3",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
+ "symfony/mime": "^5.2.6|^6.0",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/service-contracts": "^1.1|^2|^3"
+ },
+ "require-dev": {
+ "symfony/http-client-contracts": "^1.1|^2|^3",
+ "symfony/messenger": "^4.4|^5.0|^6.0"
+ },
+ "conflict": {
+ "symfony/http-kernel": "<4.4"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Mailer\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/plugins/email/vendor/symfony/messenger/Attribute/AsMessageHandler.php b/plugins/email/vendor/symfony/messenger/Attribute/AsMessageHandler.php
new file mode 100644
index 0000000..8c12a80
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Attribute/AsMessageHandler.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Attribute;
+
+/**
+ * Service tag to autoconfigure message handlers.
+ *
+ * @author Alireza Mirsepassi
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class AsMessageHandler
+{
+ public function __construct(
+ public ?string $bus = null,
+ public ?string $fromTransport = null,
+ public ?string $handles = null,
+ public ?string $method = null,
+ public int $priority = 0,
+ ) {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/CHANGELOG.md b/plugins/email/vendor/symfony/messenger/CHANGELOG.md
new file mode 100644
index 0000000..ede59fe
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/CHANGELOG.md
@@ -0,0 +1,212 @@
+CHANGELOG
+=========
+
+5.4
+---
+
+ * Add `AsMessageHandler` attribute for declaring message handlers on PHP 8.
+ * Add support for handling messages in batches with `BatchHandlerInterface` and corresponding trait
+ * Add `StopWorkerExceptionInterface` and its implementation `StopWorkerException` to stop the worker.
+ * Add support for resetting container services after each messenger message.
+ * Added `WorkerMetadata` class which allows you to access the configuration details of a worker, like `queueNames` and `transportNames` it consumes from.
+ * New method `getMetadata()` was added to `Worker` class which returns the `WorkerMetadata` object.
+ * Deprecate not setting the `reset_on_message` config option, its default value will change to `true` in 6.0
+ * Add log when worker should stop.
+ * Add log when `SIGTERM` is received.
+
+5.3
+---
+
+ * Add the `RouterContextMiddleware` to restore the original router context when handling a message
+ * `InMemoryTransport` can perform message serialization through dsn `in-memory://?serialize=true`.
+ * Added `queues` option to `Worker` to only fetch messages from a specific queue from a receiver implementing `QueueReceiverInterface`.
+
+5.2.0
+-----
+
+ * The `RedeliveryStamp` will no longer be populated with error data. This information is now stored in the `ErrorDetailsStamp` instead.
+ * Added `FlattenExceptionNormalizer` to give more information about the exception on Messenger background processes. The `FlattenExceptionNormalizer` has a higher priority than `ProblemNormalizer` and it is only used when the Messenger serialization context is set.
+ * Added factory methods `DelayStamp::delayFor(\DateInterval)` and `DelayStamp::delayUntil(\DateTimeInterface)`.
+ * Removed the exception when dispatching a message with a `DispatchAfterCurrentBusStamp` and not in a context of another dispatch call
+ * Added `WorkerMessageRetriedEvent`
+ * Added `WorkerMessageReceivedEvent::setEnvelope()` and made event mutable
+
+5.1.0
+-----
+
+ * Moved AmqpExt transport to package `symfony/amqp-messenger`. All classes in `Symfony\Component\Messenger\Transport\AmqpExt` have been moved to `Symfony\Component\Messenger\Bridge\Amqp\Transport`
+ * Moved Doctrine transport to package `symfony/doctrine-messenger`. All classes in `Symfony\Component\Messenger\Transport\Doctrine` have been moved to `Symfony\Component\Messenger\Bridge\Doctrine\Transport`
+ * Moved RedisExt transport to package `symfony/redis-messenger`. All classes in `Symfony\Component\Messenger\Transport\RedisExt` have been moved to `Symfony\Component\Messenger\Bridge\Redis\Transport`
+ * Added support for passing a `\Throwable` argument to `RetryStrategyInterface` methods. This allows to define strategies based on the reason of the handling failure.
+ * Added `StopWorkerOnFailureLimitListener` to stop the worker after a specified amount of failed messages is reached.
+ * Added `RecoverableExceptionInterface` interface to force retry.
+
+5.0.0
+-----
+
+ * The `LoggingMiddleware` class has been removed, pass a logger to `SendMessageMiddleware` instead.
+ * made `SendersLocator` require a `ContainerInterface` as 2nd argument
+
+4.4.0
+-----
+
+ * Added support for auto trimming of Redis streams.
+ * `InMemoryTransport` handle acknowledged and rejected messages.
+ * Made all dispatched worker event classes final.
+ * Added support for `from_transport` attribute on `messenger.message_handler` tag.
+ * Added support for passing `dbindex` as a query parameter to the redis transport DSN.
+ * Added `WorkerStartedEvent` and `WorkerRunningEvent`
+ * [BC BREAK] Removed `SendersLocatorInterface::getSenderByAlias` added in 4.3.
+ * [BC BREAK] Removed `$retryStrategies` argument from `Worker::__construct`.
+ * [BC BREAK] Changed arguments of `ConsumeMessagesCommand::__construct`.
+ * [BC BREAK] Removed `$senderClassOrAlias` argument from `RedeliveryStamp::__construct`.
+ * [BC BREAK] Removed `UnknownSenderException`.
+ * [BC BREAK] Removed `WorkerInterface`.
+ * [BC BREAK] Removed `$onHandledCallback` of `Worker::run(array $options = [], callable $onHandledCallback = null)`.
+ * [BC BREAK] Removed `StopWhenMemoryUsageIsExceededWorker` in favor of `StopWorkerOnMemoryLimitListener`.
+ * [BC BREAK] Removed `StopWhenMessageCountIsExceededWorker` in favor of `StopWorkerOnMessageLimitListener`.
+ * [BC BREAK] Removed `StopWhenTimeLimitIsReachedWorker` in favor of `StopWorkerOnTimeLimitListener`.
+ * [BC BREAK] Removed `StopWhenRestartSignalIsReceived` in favor of `StopWorkerOnRestartSignalListener`.
+ * The component is not marked as `@experimental` anymore.
+ * Marked the `MessengerDataCollector` class as `@final`.
+ * Added support for `DelayStamp` to the `redis` transport.
+
+4.3.0
+-----
+
+ * Added `NonSendableStampInterface` that a stamp can implement if
+ it should not be sent to a transport. Transport serializers
+ must now check for these stamps and not encode them.
+ * [BC BREAK] `SendersLocatorInterface` has an additional method:
+ `getSenderByAlias()`.
+ * Removed argument `?bool &$handle = false` from `SendersLocatorInterface::getSenders`
+ * A new `ListableReceiverInterface` was added, which a receiver
+ can implement (when applicable) to enable listing and fetching
+ individual messages by id (used in the new "Failed Messages" commands).
+ * Both `SenderInterface::send()` and `ReceiverInterface::get()`
+ should now (when applicable) add a `TransportMessageIdStamp`.
+ * Added `WorkerStoppedEvent` dispatched when a worker is stopped.
+ * Added optional `MessageCountAwareInterface` that receivers can implement
+ to give information about how many messages are waiting to be processed.
+ * [BC BREAK] The `Envelope::__construct()` signature changed:
+ you can no longer pass an unlimited number of stamps as the second,
+ third, fourth, arguments etc: stamps are now an array passed to the
+ second argument.
+ * [BC BREAK] The `MessageBusInterface::dispatch()` signature changed:
+ a second argument `array $stamps = []` was added.
+ * Added new `messenger:stop-workers` command that sends a signal
+ to stop all `messenger:consume` workers.
+ * [BC BREAK] The `TransportFactoryInterface::createTransport()` signature
+ changed: a required 3rd `SerializerInterface` argument was added.
+ * Added a new `SyncTransport` to explicitly handle messages synchronously.
+ * Added `AmqpStamp` allowing to provide a routing key, flags and attributes on message publishing.
+ * [BC BREAK] Removed publishing with a `routing_key` option from queue configuration, for
+ AMQP. Use exchange `default_publish_routing_key` or `AmqpStamp` instead.
+ * [BC BREAK] Changed the `queue` option in the AMQP transport DSN to be `queues[name]`. You can
+ therefore name the queue but also configure `binding_keys`, `flags` and `arguments`.
+ * [BC BREAK] The methods `get`, `ack`, `nack` and `queue` of the AMQP `Connection`
+ have a new argument: the queue name.
+ * Added optional parameter `prefetch_count` in connection configuration,
+ to setup channel prefetch count.
+ * New classes: `RoutableMessageBus`, `AddBusNameStampMiddleware`
+ and `BusNameStamp` were added, which allow you to add a bus identifier
+ to the `Envelope` then find the correct bus when receiving from
+ the transport. See `ConsumeMessagesCommand`.
+ * The optional `$busNames` constructor argument of the class `ConsumeMessagesCommand` was removed.
+ * [BC BREAK] 3 new methods were added to `ReceiverInterface`:
+ `ack()`, `reject()` and `get()`. The methods `receive()`
+ and `stop()` were removed.
+ * [BC BREAK] Error handling was moved from the receivers into
+ `Worker`. Implementations of `ReceiverInterface::handle()`
+ should now allow all exceptions to be thrown, except for transport
+ exceptions. They should also not retry (e.g. if there's a queue,
+ remove from the queue) if there is a problem decoding the message.
+ * [BC BREAK] `RejectMessageExceptionInterface` was removed and replaced
+ by `Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException`,
+ which has the same behavior: a message will not be retried
+ * The default command name for `ConsumeMessagesCommand` was
+ changed from `messenger:consume-messages` to `messenger:consume`
+ * `ConsumeMessagesCommand` has two new optional constructor arguments
+ * [BC BREAK] The first argument to Worker changed from a single
+ `ReceiverInterface` to an array of `ReceiverInterface`.
+ * `Worker` has 3 new optional constructor arguments.
+ * The `Worker` class now handles calling `pcntl_signal_dispatch()` the
+ receiver no longer needs to call this.
+ * The `AmqpSender` will now retry messages using a dead-letter exchange
+ and delayed queues, instead of retrying via `nack()`
+ * Senders now receive the `Envelope` with the `SentStamp` on it. Previously,
+ the `Envelope` was passed to the sender and *then* the `SentStamp`
+ was added.
+ * `SerializerInterface` implementations should now throw a
+ `Symfony\Component\Messenger\Exception\MessageDecodingFailedException`
+ if `decode()` fails for any reason.
+ * [BC BREAK] The default `Serializer` will now throw a
+ `MessageDecodingFailedException` if `decode()` fails, instead
+ of the underlying exceptions from the Serializer component.
+ * Added `PhpSerializer` which uses PHP's native `serialize()` and
+ `unserialize()` to serialize messages to a transport
+ * [BC BREAK] If no serializer were passed, the default serializer
+ changed from `Serializer` to `PhpSerializer` inside `AmqpReceiver`,
+ `AmqpSender`, `AmqpTransport` and `AmqpTransportFactory`.
+ * Added `TransportException` to mark an exception transport-related
+ * [BC BREAK] If listening to exceptions while using `AmqpSender` or `AmqpReceiver`, `\AMQPException` is
+ no longer thrown in favor of `TransportException`.
+ * Deprecated `LoggingMiddleware`, pass a logger to `SendMessageMiddleware` instead.
+ * [BC BREAK] `Connection::__construct()` and `Connection::fromDsn()`
+ both no longer have `$isDebug` arguments.
+ * [BC BREAK] The Amqp Transport now automatically sets up the exchanges
+ and queues by default. Previously, this was done when in "debug" mode
+ only. Pass the `auto_setup` connection option to control this.
+ * Added a `SetupTransportsCommand` command to setup the transports
+ * Added a Doctrine transport. For example, use the `doctrine://default` DSN (this uses the `default` Doctrine entity manager)
+ * [BC BREAK] The `getConnectionConfiguration` method on Amqp's `Connection` has been removed.
+ * [BC BREAK] A `HandlerFailedException` exception will be thrown if one or more handler fails.
+ * [BC BREAK] The `HandlersLocationInterface::getHandlers` method needs to return `HandlerDescriptor`
+ instances instead of callables.
+ * [BC BREAK] The `HandledStamp` stamp has changed: `handlerAlias` has been renamed to `handlerName`,
+ `getCallableName` has been removed and its constructor only has 2 arguments now.
+ * [BC BREAK] The `ReceivedStamp` needs to exposes the name of the transport from which the message
+ has been received.
+
+4.2.0
+-----
+
+ * Added `HandleTrait` leveraging a message bus instance to return a single
+ synchronous message handling result
+ * Added `HandledStamp` & `SentStamp` stamps
+ * All the changes below are BC BREAKS
+ * Senders and handlers subscribing to parent interfaces now receive *all* matching messages, wildcard included
+ * `MessageBusInterface::dispatch()`, `MiddlewareInterface::handle()` and `SenderInterface::send()` return `Envelope`
+ * `MiddlewareInterface::handle()` now require an `Envelope` as first argument and a `StackInterface` as second
+ * `EnvelopeAwareInterface` has been removed
+ * The signature of `Amqp*` classes changed to take a `Connection` as a first argument and an optional
+ `Serializer` as a second argument.
+ * `MessageSubscriberInterface::getHandledMessages()` return value has changed. The value of an array item
+ needs to be an associative array or the method name.
+ * `StampInterface` replaces `EnvelopeItemInterface` and doesn't extend `Serializable` anymore
+ * The `ConsumeMessagesCommand` class now takes an instance of `Psr\Container\ContainerInterface`
+ as first constructor argument
+ * The `EncoderInterface` and `DecoderInterface` have been replaced by a unified `Symfony\Component\Messenger\Transport\Serialization\SerializerInterface`.
+ * Renamed `EnvelopeItemInterface` to `StampInterface`
+ * `Envelope`'s constructor and `with()` method now accept `StampInterface` objects as variadic parameters
+ * Renamed and moved `ReceivedMessage`, `ValidationConfiguration` and `SerializerConfiguration` in the `Stamp` namespace
+ * Removed the `WrapIntoReceivedMessage` class
+ * `MessengerDataCollector::getMessages()` returns an iterable, not just an array anymore
+ * `HandlerLocatorInterface::resolve()` has been removed, use `HandlersLocator::getHandlers()` instead
+ * `SenderLocatorInterface::getSenderForMessage()` has been removed, use `SendersLocator::getSenders()` instead
+ * Classes in the `Middleware\Enhancers` sub-namespace have been moved to the `Middleware` one
+ * Classes in the `Asynchronous\Routing` sub-namespace have been moved to the `Transport\Sender\Locator` sub-namespace
+ * The `Asynchronous/Middleware/SendMessageMiddleware` class has been moved to the `Middleware` namespace
+ * `SenderInterface` has been moved to the `Transport\Sender` sub-namespace
+ * The `ChainHandler` and `ChainSender` classes have been removed
+ * `ReceiverInterface` and its implementations have been moved to the `Transport\Receiver` sub-namespace
+ * `ActivationMiddlewareDecorator` has been renamed `ActivationMiddleware`
+ * `AllowNoHandlerMiddleware` has been removed in favor of a new constructor argument on `HandleMessageMiddleware`
+ * The `ContainerHandlerLocator`, `AbstractHandlerLocator`, `SenderLocator` and `AbstractSenderLocator` classes have been removed
+ * `Envelope::all()` takes a new optional `$stampFqcn` argument and returns the stamps for the specified FQCN, or all stamps by their class name
+ * `Envelope::get()` has been renamed `Envelope::last()`
+
+4.1.0
+-----
+
+ * Introduced the component as experimental
diff --git a/plugins/email/vendor/symfony/messenger/Command/AbstractFailedMessagesCommand.php b/plugins/email/vendor/symfony/messenger/Command/AbstractFailedMessagesCommand.php
new file mode 100644
index 0000000..7a6fc21
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Command/AbstractFailedMessagesCommand.php
@@ -0,0 +1,298 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Helper\Dumper;
+use Symfony\Component\Console\Question\ChoiceQuestion;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+use Symfony\Component\ErrorHandler\Exception\FlattenException;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
+use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
+use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
+use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
+use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
+use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
+use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Caster\TraceStub;
+use Symfony\Component\VarDumper\Cloner\ClonerInterface;
+use Symfony\Component\VarDumper\Cloner\Stub;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Contracts\Service\ServiceProviderInterface;
+
+/**
+ * @author Ryan Weaver
+ *
+ * @internal
+ */
+abstract class AbstractFailedMessagesCommand extends Command
+{
+ protected const DEFAULT_TRANSPORT_OPTION = 'choose';
+
+ protected $failureTransports;
+
+ private $globalFailureReceiverName;
+
+ /**
+ * @param ServiceProviderInterface $failureTransports
+ */
+ public function __construct(?string $globalFailureReceiverName, $failureTransports)
+ {
+ $this->failureTransports = $failureTransports;
+ if (!$failureTransports instanceof ServiceProviderInterface) {
+ trigger_deprecation('symfony/messenger', '5.3', 'Passing a receiver as 2nd argument to "%s()" is deprecated, pass a service locator instead.', __METHOD__);
+
+ if (null === $globalFailureReceiverName) {
+ throw new InvalidArgumentException(sprintf('The argument "globalFailureReceiver" from method "%s()" must be not null if 2nd argument is not a ServiceLocator.', __METHOD__));
+ }
+
+ $this->failureTransports = new ServiceLocator([$globalFailureReceiverName => static function () use ($failureTransports) { return $failureTransports; }]);
+ }
+ $this->globalFailureReceiverName = $globalFailureReceiverName;
+
+ parent::__construct();
+ }
+
+ protected function getReceiverName(): string
+ {
+ trigger_deprecation('symfony/messenger', '5.3', 'The method "%s()" is deprecated, use getGlobalFailureReceiverName() instead.', __METHOD__);
+
+ return $this->globalFailureReceiverName;
+ }
+
+ protected function getGlobalFailureReceiverName(): ?string
+ {
+ return $this->globalFailureReceiverName;
+ }
+
+ /**
+ * @return mixed
+ */
+ protected function getMessageId(Envelope $envelope)
+ {
+ /** @var TransportMessageIdStamp $stamp */
+ $stamp = $envelope->last(TransportMessageIdStamp::class);
+
+ return null !== $stamp ? $stamp->getId() : null;
+ }
+
+ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io)
+ {
+ $io->title('Failed Message Details');
+
+ /** @var SentToFailureTransportStamp|null $sentToFailureTransportStamp */
+ $sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
+ /** @var RedeliveryStamp|null $lastRedeliveryStamp */
+ $lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
+ /** @var ErrorDetailsStamp|null $lastErrorDetailsStamp */
+ $lastErrorDetailsStamp = $envelope->last(ErrorDetailsStamp::class);
+ $lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope, true);
+
+ $rows = [
+ ['Class', \get_class($envelope->getMessage())],
+ ];
+
+ if (null !== $id = $this->getMessageId($envelope)) {
+ $rows[] = ['Message Id', $id];
+ }
+
+ if (null === $sentToFailureTransportStamp) {
+ $io->warning('Message does not appear to have been sent to this transport after failing');
+ } else {
+ $failedAt = '';
+ $errorMessage = '';
+ $errorCode = '';
+ $errorClass = '(unknown)';
+
+ if (null !== $lastRedeliveryStamp) {
+ $failedAt = $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s');
+ }
+
+ if (null !== $lastErrorDetailsStamp) {
+ $errorMessage = $lastErrorDetailsStamp->getExceptionMessage();
+ $errorCode = $lastErrorDetailsStamp->getExceptionCode();
+ $errorClass = $lastErrorDetailsStamp->getExceptionClass();
+ } elseif (null !== $lastRedeliveryStampWithException) {
+ // Try reading the errorMessage for messages that are still in the queue without the new ErrorDetailStamps.
+ $errorMessage = $lastRedeliveryStampWithException->getExceptionMessage();
+ if (null !== $lastRedeliveryStampWithException->getFlattenException()) {
+ $errorClass = $lastRedeliveryStampWithException->getFlattenException()->getClass();
+ }
+ }
+
+ $rows = array_merge($rows, [
+ ['Failed at', $failedAt],
+ ['Error', $errorMessage],
+ ['Error Code', $errorCode],
+ ['Error Class', $errorClass],
+ ['Transport', $sentToFailureTransportStamp->getOriginalReceiverName()],
+ ]);
+ }
+
+ $io->table([], $rows);
+
+ /** @var RedeliveryStamp[] $redeliveryStamps */
+ $redeliveryStamps = $envelope->all(RedeliveryStamp::class);
+ $io->writeln(' Message history:');
+ foreach ($redeliveryStamps as $redeliveryStamp) {
+ $io->writeln(sprintf(' * Message failed at %s and was redelivered', $redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')));
+ }
+ $io->newLine();
+
+ if ($io->isVeryVerbose()) {
+ $io->title('Message:');
+ $dump = new Dumper($io, null, $this->createCloner());
+ $io->writeln($dump($envelope->getMessage()));
+ $io->title('Exception:');
+ $flattenException = null;
+ if (null !== $lastErrorDetailsStamp) {
+ $flattenException = $lastErrorDetailsStamp->getFlattenException();
+ } elseif (null !== $lastRedeliveryStampWithException) {
+ $flattenException = $lastRedeliveryStampWithException->getFlattenException();
+ }
+ $io->writeln(null === $flattenException ? '(no data)' : $dump($flattenException));
+ } else {
+ $io->writeln(' Re-run command with -vv to see more message & error details.');
+ }
+ }
+
+ protected function printPendingMessagesMessage(ReceiverInterface $receiver, SymfonyStyle $io)
+ {
+ if ($receiver instanceof MessageCountAwareInterface) {
+ if (1 === $receiver->getMessageCount()) {
+ $io->writeln('There is 1 message pending in the failure transport.');
+ } else {
+ $io->writeln(sprintf('There are %d messages pending in the failure transport.', $receiver->getMessageCount()));
+ }
+ }
+ }
+
+ /**
+ * @param string|null $name
+ */
+ protected function getReceiver(/* string $name = null */): ReceiverInterface
+ {
+ if (1 > \func_num_args() && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) {
+ trigger_deprecation('symfony/messenger', '5.3', 'The "%s()" method will have a new "string $name" argument in version 6.0, not defining it is deprecated.', __METHOD__);
+ }
+ $name = \func_num_args() > 0 ? func_get_arg(0) : null;
+
+ if (null === $name = $name ?? $this->globalFailureReceiverName) {
+ throw new InvalidArgumentException(sprintf('No default failure transport is defined. Available transports are: "%s".', implode('", "', array_keys($this->failureTransports->getProvidedServices()))));
+ }
+
+ if (!$this->failureTransports->has($name)) {
+ throw new InvalidArgumentException(sprintf('The "%s" failure transport was not found. Available transports are: "%s".', $name, implode('", "', array_keys($this->failureTransports->getProvidedServices()))));
+ }
+
+ return $this->failureTransports->get($name);
+ }
+
+ protected function getLastRedeliveryStampWithException(Envelope $envelope): ?RedeliveryStamp
+ {
+ if (null === \func_get_args()[1]) {
+ trigger_deprecation('symfony/messenger', '5.2', sprintf('Using the "getLastRedeliveryStampWithException" method in the "%s" class is deprecated, use the "Envelope::last(%s)" instead.', self::class, ErrorDetailsStamp::class));
+ }
+
+ // Use ErrorDetailsStamp instead if it is available
+ if (null !== $envelope->last(ErrorDetailsStamp::class)) {
+ return null;
+ }
+
+ /** @var RedeliveryStamp $stamp */
+ foreach (array_reverse($envelope->all(RedeliveryStamp::class)) as $stamp) {
+ if (null !== $stamp->getExceptionMessage()) {
+ return $stamp;
+ }
+ }
+
+ return null;
+ }
+
+ private function createCloner(): ?ClonerInterface
+ {
+ if (!class_exists(VarCloner::class)) {
+ return null;
+ }
+
+ $cloner = new VarCloner();
+ $cloner->addCasters([FlattenException::class => function (FlattenException $flattenException, array $a, Stub $stub): array {
+ $stub->class = $flattenException->getClass();
+
+ return [
+ Caster::PREFIX_VIRTUAL.'message' => $flattenException->getMessage(),
+ Caster::PREFIX_VIRTUAL.'code' => $flattenException->getCode(),
+ Caster::PREFIX_VIRTUAL.'file' => $flattenException->getFile(),
+ Caster::PREFIX_VIRTUAL.'line' => $flattenException->getLine(),
+ Caster::PREFIX_VIRTUAL.'trace' => new TraceStub($flattenException->getTrace()),
+ ];
+ }]);
+
+ return $cloner;
+ }
+
+ protected function printWarningAvailableFailureTransports(SymfonyStyle $io, ?string $failureTransportName): void
+ {
+ $failureTransports = array_keys($this->failureTransports->getProvidedServices());
+ $failureTransportsCount = \count($failureTransports);
+ if ($failureTransportsCount > 1) {
+ $io->writeln([
+ sprintf('> Loading messages from the global failure transport %s.', $failureTransportName),
+ '> To use a different failure transport, pass --transport=.',
+ sprintf('> Available failure transports are: %s', implode(', ', $failureTransports)),
+ "\n",
+ ]);
+ }
+ }
+
+ protected function interactiveChooseFailureTransport(SymfonyStyle $io)
+ {
+ $failedTransports = array_keys($this->failureTransports->getProvidedServices());
+ $question = new ChoiceQuestion('Select failed transport:', $failedTransports, 0);
+ $question->setMultiselect(false);
+
+ return $io->askQuestion($question);
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestOptionValuesFor('transport')) {
+ $suggestions->suggestValues(array_keys($this->failureTransports->getProvidedServices()));
+
+ return;
+ }
+
+ if ($input->mustSuggestArgumentValuesFor('id')) {
+ $transport = $input->getOption('transport');
+ $transport = self::DEFAULT_TRANSPORT_OPTION === $transport ? $this->getGlobalFailureReceiverName() : $transport;
+ $receiver = $this->getReceiver($transport);
+
+ if (!$receiver instanceof ListableReceiverInterface) {
+ return;
+ }
+
+ $ids = [];
+ foreach ($receiver->all(50) as $envelope) {
+ $ids[] = $this->getMessageId($envelope);
+ }
+ $suggestions->suggestValues($ids);
+
+ return;
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php b/plugins/email/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php
new file mode 100644
index 0000000..6e9a02b
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php
@@ -0,0 +1,267 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Command;
+
+use Psr\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ChoiceQuestion;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\Messenger\EventListener\ResetServicesListener;
+use Symfony\Component\Messenger\EventListener\StopWorkerOnFailureLimitListener;
+use Symfony\Component\Messenger\EventListener\StopWorkerOnMemoryLimitListener;
+use Symfony\Component\Messenger\EventListener\StopWorkerOnMessageLimitListener;
+use Symfony\Component\Messenger\EventListener\StopWorkerOnTimeLimitListener;
+use Symfony\Component\Messenger\RoutableMessageBus;
+use Symfony\Component\Messenger\Worker;
+
+/**
+ * @author Samuel Roze
+ */
+class ConsumeMessagesCommand extends Command
+{
+ protected static $defaultName = 'messenger:consume';
+ protected static $defaultDescription = 'Consume messages';
+
+ private $routableBus;
+ private $receiverLocator;
+ private $eventDispatcher;
+ private $logger;
+ private $receiverNames;
+ private $resetServicesListener;
+ private $busIds;
+
+ public function __construct(RoutableMessageBus $routableBus, ContainerInterface $receiverLocator, EventDispatcherInterface $eventDispatcher, LoggerInterface $logger = null, array $receiverNames = [], ResetServicesListener $resetServicesListener = null, array $busIds = [])
+ {
+ $this->routableBus = $routableBus;
+ $this->receiverLocator = $receiverLocator;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->logger = $logger;
+ $this->receiverNames = $receiverNames;
+ $this->resetServicesListener = $resetServicesListener;
+ $this->busIds = $busIds;
+
+ parent::__construct();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure(): void
+ {
+ $defaultReceiverName = 1 === \count($this->receiverNames) ? current($this->receiverNames) : null;
+
+ $this
+ ->setDefinition([
+ new InputArgument('receivers', InputArgument::IS_ARRAY, 'Names of the receivers/transports to consume in order of priority', $defaultReceiverName ? [$defaultReceiverName] : []),
+ new InputOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limit the number of received messages'),
+ new InputOption('failure-limit', 'f', InputOption::VALUE_REQUIRED, 'The number of failed messages the worker can consume'),
+ new InputOption('memory-limit', 'm', InputOption::VALUE_REQUIRED, 'The memory limit the worker can consume'),
+ new InputOption('time-limit', 't', InputOption::VALUE_REQUIRED, 'The time limit in seconds the worker can handle new messages'),
+ new InputOption('sleep', null, InputOption::VALUE_REQUIRED, 'Seconds to sleep before asking for new messages after no messages were found', 1),
+ new InputOption('bus', 'b', InputOption::VALUE_REQUIRED, 'Name of the bus to which received messages should be dispatched (if not passed, bus is determined automatically)'),
+ new InputOption('queues', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Limit receivers to only consume from the specified queues'),
+ new InputOption('no-reset', null, InputOption::VALUE_NONE, 'Do not reset container services after each message'),
+ ])
+ ->setDescription(self::$defaultDescription)
+ ->setHelp(<<<'EOF'
+The %command.name% command consumes messages and dispatches them to the message bus.
+
+ php %command.full_name%
+
+To receive from multiple transports, pass each name:
+
+ php %command.full_name% receiver1 receiver2
+
+Use the --limit option to limit the number of messages received:
+
+ php %command.full_name% --limit=10
+
+Use the --failure-limit option to stop the worker when the given number of failed messages is reached:
+
+ php %command.full_name% --failure-limit=2
+
+Use the --memory-limit option to stop the worker if it exceeds a given memory usage limit. You can use shorthand byte values [K, M or G]:
+
+ php %command.full_name% --memory-limit=128M
+
+Use the --time-limit option to stop the worker when the given time limit (in seconds) is reached.
+If a message is being handled, the worker will stop after the processing is finished:
+
+ php %command.full_name% --time-limit=3600
+
+Use the --bus option to specify the message bus to dispatch received messages
+to instead of trying to determine it automatically. This is required if the
+messages didn't originate from Messenger:
+
+ php %command.full_name% --bus=event_bus
+
+Use the --queues option to limit a receiver to only certain queues (only supported by some receivers):
+
+ php %command.full_name% --queues=fasttrack
+
+Use the --no-reset option to prevent services resetting after each message (may lead to leaking services' state between messages):
+
+ php %command.full_name% --no-reset
+EOF
+ )
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function interact(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
+
+ if ($this->receiverNames && 0 === \count($input->getArgument('receivers'))) {
+ $io->block('Which transports/receivers do you want to consume?', null, 'fg=white;bg=blue', ' ', true);
+
+ $io->writeln('Choose which receivers you want to consume messages from in order of priority.');
+ if (\count($this->receiverNames) > 1) {
+ $io->writeln(sprintf('Hint: to consume from multiple, use a list of their names, e.g. %s', implode(', ', $this->receiverNames)));
+ }
+
+ $question = new ChoiceQuestion('Select receivers to consume:', $this->receiverNames, 0);
+ $question->setMultiselect(true);
+
+ $input->setArgument('receivers', $io->askQuestion($question));
+ }
+
+ if (0 === \count($input->getArgument('receivers'))) {
+ throw new RuntimeException('Please pass at least one receiver.');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $receivers = [];
+ foreach ($receiverNames = $input->getArgument('receivers') as $receiverName) {
+ if (!$this->receiverLocator->has($receiverName)) {
+ $message = sprintf('The receiver "%s" does not exist.', $receiverName);
+ if ($this->receiverNames) {
+ $message .= sprintf(' Valid receivers are: %s.', implode(', ', $this->receiverNames));
+ }
+
+ throw new RuntimeException($message);
+ }
+
+ $receivers[$receiverName] = $this->receiverLocator->get($receiverName);
+ }
+
+ if (null !== $this->resetServicesListener && !$input->getOption('no-reset')) {
+ $this->eventDispatcher->addSubscriber($this->resetServicesListener);
+ }
+
+ $stopsWhen = [];
+ if ($limit = $input->getOption('limit')) {
+ $stopsWhen[] = "processed {$limit} messages";
+ $this->eventDispatcher->addSubscriber(new StopWorkerOnMessageLimitListener($limit, $this->logger));
+ }
+
+ if ($failureLimit = $input->getOption('failure-limit')) {
+ $stopsWhen[] = "reached {$failureLimit} failed messages";
+ $this->eventDispatcher->addSubscriber(new StopWorkerOnFailureLimitListener($failureLimit, $this->logger));
+ }
+
+ if ($memoryLimit = $input->getOption('memory-limit')) {
+ $stopsWhen[] = "exceeded {$memoryLimit} of memory";
+ $this->eventDispatcher->addSubscriber(new StopWorkerOnMemoryLimitListener($this->convertToBytes($memoryLimit), $this->logger));
+ }
+
+ if (null !== ($timeLimit = $input->getOption('time-limit'))) {
+ $stopsWhen[] = "been running for {$timeLimit}s";
+ $this->eventDispatcher->addSubscriber(new StopWorkerOnTimeLimitListener($timeLimit, $this->logger));
+ }
+
+ $stopsWhen[] = 'received a stop signal via the messenger:stop-workers command';
+
+ $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
+ $io->success(sprintf('Consuming messages from transport%s "%s".', \count($receivers) > 0 ? 's' : '', implode(', ', $receiverNames)));
+
+ if ($stopsWhen) {
+ $last = array_pop($stopsWhen);
+ $stopsWhen = ($stopsWhen ? implode(', ', $stopsWhen).' or ' : '').$last;
+ $io->comment("The worker will automatically exit once it has {$stopsWhen}.");
+ }
+
+ $io->comment('Quit the worker with CONTROL-C.');
+
+ if (OutputInterface::VERBOSITY_VERBOSE > $output->getVerbosity()) {
+ $io->comment('Re-run the command with a -vv option to see logs about consumed messages.');
+ }
+
+ $bus = $input->getOption('bus') ? $this->routableBus->getMessageBus($input->getOption('bus')) : $this->routableBus;
+
+ $worker = new Worker($receivers, $bus, $this->eventDispatcher, $this->logger);
+ $options = [
+ 'sleep' => $input->getOption('sleep') * 1000000,
+ ];
+ if ($queues = $input->getOption('queues')) {
+ $options['queues'] = $queues;
+ }
+ $worker->run($options);
+
+ return 0;
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('receivers')) {
+ $suggestions->suggestValues(array_diff($this->receiverNames, array_diff($input->getArgument('receivers'), [$input->getCompletionValue()])));
+
+ return;
+ }
+
+ if ($input->mustSuggestOptionValuesFor('bus')) {
+ $suggestions->suggestValues($this->busIds);
+ }
+ }
+
+ private function convertToBytes(string $memoryLimit): int
+ {
+ $memoryLimit = strtolower($memoryLimit);
+ $max = ltrim($memoryLimit, '+');
+ if (str_starts_with($max, '0x')) {
+ $max = \intval($max, 16);
+ } elseif (str_starts_with($max, '0')) {
+ $max = \intval($max, 8);
+ } else {
+ $max = (int) $max;
+ }
+
+ switch (substr(rtrim($memoryLimit, 'b'), -1)) {
+ case 't': $max *= 1024;
+ // no break
+ case 'g': $max *= 1024;
+ // no break
+ case 'm': $max *= 1024;
+ // no break
+ case 'k': $max *= 1024;
+ }
+
+ return $max;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Command/DebugCommand.php b/plugins/email/vendor/symfony/messenger/Command/DebugCommand.php
new file mode 100644
index 0000000..4320ad7
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Command/DebugCommand.php
@@ -0,0 +1,150 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * A console command to debug Messenger information.
+ *
+ * @author Roland Franssen
+ */
+class DebugCommand extends Command
+{
+ protected static $defaultName = 'debug:messenger';
+ protected static $defaultDescription = 'List messages you can dispatch using the message buses';
+
+ private $mapping;
+
+ public function __construct(array $mapping)
+ {
+ $this->mapping = $mapping;
+
+ parent::__construct();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this
+ ->addArgument('bus', InputArgument::OPTIONAL, sprintf('The bus id (one of "%s")', implode('", "', array_keys($this->mapping))))
+ ->setDescription(self::$defaultDescription)
+ ->setHelp(<<<'EOF'
+The %command.name% command displays all messages that can be
+dispatched using the message buses:
+
+ php %command.full_name%
+
+Or for a specific bus only:
+
+ php %command.full_name% command_bus
+
+EOF
+ )
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output);
+ $io->title('Messenger');
+
+ $mapping = $this->mapping;
+ if ($bus = $input->getArgument('bus')) {
+ if (!isset($mapping[$bus])) {
+ throw new RuntimeException(sprintf('Bus "%s" does not exist. Known buses are "%s".', $bus, implode('", "', array_keys($this->mapping))));
+ }
+ $mapping = [$bus => $mapping[$bus]];
+ }
+
+ foreach ($mapping as $bus => $handlersByMessage) {
+ $io->section($bus);
+
+ $tableRows = [];
+ foreach ($handlersByMessage as $message => $handlers) {
+ if ($description = self::getClassDescription($message)) {
+ $tableRows[] = [sprintf('%s>', $description)];
+ }
+
+ $tableRows[] = [sprintf('%s', $message)];
+ foreach ($handlers as $handler) {
+ $tableRows[] = [
+ sprintf(' handled by %s>', $handler[0]).$this->formatConditions($handler[1]),
+ ];
+ if ($handlerDescription = self::getClassDescription($handler[0])) {
+ $tableRows[] = [sprintf(' %s>', $handlerDescription)];
+ }
+ }
+ $tableRows[] = [''];
+ }
+
+ if ($tableRows) {
+ $io->text('The following messages can be dispatched:');
+ $io->newLine();
+ $io->table([], $tableRows);
+ } else {
+ $io->warning(sprintf('No handled message found in bus "%s".', $bus));
+ }
+ }
+
+ return 0;
+ }
+
+ private function formatConditions(array $options): string
+ {
+ if (!$options) {
+ return '';
+ }
+
+ $optionsMapping = [];
+ foreach ($options as $key => $value) {
+ $optionsMapping[] = $key.'='.$value;
+ }
+
+ return ' (when '.implode(', ', $optionsMapping).')';
+ }
+
+ private static function getClassDescription(string $class): string
+ {
+ try {
+ $r = new \ReflectionClass($class);
+
+ if ($docComment = $r->getDocComment()) {
+ $docComment = preg_split('#\n\s*\*\s*[\n@]#', substr($docComment, 3, -2), 2)[0];
+
+ return trim(preg_replace('#\s*\n\s*\*\s*#', ' ', $docComment));
+ }
+ } catch (\ReflectionException $e) {
+ }
+
+ return '';
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('bus')) {
+ $suggestions->suggestValues(array_keys($this->mapping));
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Command/FailedMessagesRemoveCommand.php b/plugins/email/vendor/symfony/messenger/Command/FailedMessagesRemoveCommand.php
new file mode 100644
index 0000000..cfdcba5
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Command/FailedMessagesRemoveCommand.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Command;
+
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
+use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+
+/**
+ * @author Ryan Weaver
+ */
+class FailedMessagesRemoveCommand extends AbstractFailedMessagesCommand
+{
+ protected static $defaultName = 'messenger:failed:remove';
+ protected static $defaultDescription = 'Remove given messages from the failure transport';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure(): void
+ {
+ $this
+ ->setDefinition([
+ new InputArgument('id', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'Specific message id(s) to remove'),
+ new InputOption('force', null, InputOption::VALUE_NONE, 'Force the operation without confirmation'),
+ new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
+ new InputOption('show-messages', null, InputOption::VALUE_NONE, 'Display messages before removing it (if multiple ids are given)'),
+ ])
+ ->setDescription(self::$defaultDescription)
+ ->setHelp(<<<'EOF'
+The %command.name% removes given messages that are pending in the failure transport.
+
+ php %command.full_name% {id1} [{id2} ...]
+
+The specific ids can be found via the messenger:failed:show command.
+EOF
+ )
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
+
+ $failureTransportName = $input->getOption('transport');
+ if (self::DEFAULT_TRANSPORT_OPTION === $failureTransportName) {
+ $failureTransportName = $this->getGlobalFailureReceiverName();
+ }
+
+ $receiver = $this->getReceiver($failureTransportName);
+
+ $shouldForce = $input->getOption('force');
+ $ids = (array) $input->getArgument('id');
+ $shouldDisplayMessages = $input->getOption('show-messages') || 1 === \count($ids);
+ $this->removeMessages($failureTransportName, $ids, $receiver, $io, $shouldForce, $shouldDisplayMessages);
+
+ return 0;
+ }
+
+ private function removeMessages(string $failureTransportName, array $ids, ReceiverInterface $receiver, SymfonyStyle $io, bool $shouldForce, bool $shouldDisplayMessages): void
+ {
+ if (!$receiver instanceof ListableReceiverInterface) {
+ throw new RuntimeException(sprintf('The "%s" receiver does not support removing specific messages.', $failureTransportName));
+ }
+
+ foreach ($ids as $id) {
+ $envelope = $receiver->find($id);
+ if (null === $envelope) {
+ $io->error(sprintf('The message with id "%s" was not found.', $id));
+ continue;
+ }
+
+ if ($shouldDisplayMessages) {
+ $this->displaySingleMessage($envelope, $io);
+ }
+
+ if ($shouldForce || $io->confirm('Do you want to permanently remove this message?', false)) {
+ $receiver->reject($envelope);
+
+ $io->success(sprintf('Message with id %s removed.', $id));
+ } else {
+ $io->note(sprintf('Message with id %s not removed.', $id));
+ }
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Command/FailedMessagesRetryCommand.php b/plugins/email/vendor/symfony/messenger/Command/FailedMessagesRetryCommand.php
new file mode 100644
index 0000000..0ef9984
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Command/FailedMessagesRetryCommand.php
@@ -0,0 +1,226 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Command;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
+use Symfony\Component\Messenger\EventListener\StopWorkerOnMessageLimitListener;
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
+use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+use Symfony\Component\Messenger\Transport\Receiver\SingleMessageReceiver;
+use Symfony\Component\Messenger\Worker;
+
+/**
+ * @author Ryan Weaver
+ */
+class FailedMessagesRetryCommand extends AbstractFailedMessagesCommand
+{
+ protected static $defaultName = 'messenger:failed:retry';
+ protected static $defaultDescription = 'Retry one or more messages from the failure transport';
+
+ private $eventDispatcher;
+ private $messageBus;
+ private $logger;
+
+ public function __construct(?string $globalReceiverName, $failureTransports, MessageBusInterface $messageBus, EventDispatcherInterface $eventDispatcher, LoggerInterface $logger = null)
+ {
+ $this->eventDispatcher = $eventDispatcher;
+ $this->messageBus = $messageBus;
+ $this->logger = $logger;
+
+ parent::__construct($globalReceiverName, $failureTransports);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure(): void
+ {
+ $this
+ ->setDefinition([
+ new InputArgument('id', InputArgument::IS_ARRAY, 'Specific message id(s) to retry'),
+ new InputOption('force', null, InputOption::VALUE_NONE, 'Force action without confirmation'),
+ new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
+ ])
+ ->setDescription(self::$defaultDescription)
+ ->setHelp(<<<'EOF'
+The %command.name% retries message in the failure transport.
+
+ php %command.full_name%
+
+The command will interactively ask if each message should be retried
+or discarded.
+
+Some transports support retrying a specific message id, which comes
+from the messenger:failed:show command.
+
+ php %command.full_name% {id}
+
+Or pass multiple ids at once to process multiple messages:
+
+php %command.full_name% {id1} {id2} {id3}
+
+EOF
+ )
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->eventDispatcher->addSubscriber(new StopWorkerOnMessageLimitListener(1));
+
+ $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
+ $io->comment('Quit this command with CONTROL-C.');
+ if (!$output->isVeryVerbose()) {
+ $io->comment('Re-run the command with a -vv option to see logs about consumed messages.');
+ }
+
+ $failureTransportName = $input->getOption('transport');
+ if (self::DEFAULT_TRANSPORT_OPTION === $failureTransportName) {
+ $this->printWarningAvailableFailureTransports($io, $this->getGlobalFailureReceiverName());
+ }
+ if ('' === $failureTransportName || null === $failureTransportName) {
+ $failureTransportName = $this->interactiveChooseFailureTransport($io);
+ }
+ $failureTransportName = self::DEFAULT_TRANSPORT_OPTION === $failureTransportName ? $this->getGlobalFailureReceiverName() : $failureTransportName;
+
+ $receiver = $this->getReceiver($failureTransportName);
+ $this->printPendingMessagesMessage($receiver, $io);
+
+ $io->writeln(sprintf('To retry all the messages, run messenger:consume %s', $failureTransportName));
+
+ $shouldForce = $input->getOption('force');
+ $ids = $input->getArgument('id');
+ if (0 === \count($ids)) {
+ if (!$input->isInteractive()) {
+ throw new RuntimeException('Message id must be passed when in non-interactive mode.');
+ }
+
+ $this->runInteractive($failureTransportName, $io, $shouldForce);
+
+ return 0;
+ }
+
+ $this->retrySpecificIds($failureTransportName, $ids, $io, $shouldForce);
+ $io->success('All done!');
+
+ return 0;
+ }
+
+ private function runInteractive(string $failureTransportName, SymfonyStyle $io, bool $shouldForce)
+ {
+ $receiver = $this->failureTransports->get($failureTransportName);
+ $count = 0;
+ if ($receiver instanceof ListableReceiverInterface) {
+ // for listable receivers, find the messages one-by-one
+ // this avoids using get(), which for some less-robust
+ // transports (like Doctrine), will cause the message
+ // to be temporarily "acked", even if the user aborts
+ // handling the message
+ while (true) {
+ $ids = [];
+ foreach ($receiver->all(1) as $envelope) {
+ ++$count;
+
+ $id = $this->getMessageId($envelope);
+ if (null === $id) {
+ throw new LogicException(sprintf('The "%s" receiver is able to list messages by id but the envelope is missing the TransportMessageIdStamp stamp.', $failureTransportName));
+ }
+ $ids[] = $id;
+ }
+
+ // break the loop if all messages are consumed
+ if (0 === \count($ids)) {
+ break;
+ }
+
+ $this->retrySpecificIds($failureTransportName, $ids, $io, $shouldForce);
+ }
+ } else {
+ // get() and ask messages one-by-one
+ $count = $this->runWorker($failureTransportName, $receiver, $io, $shouldForce);
+ }
+
+ // avoid success message if nothing was processed
+ if (1 <= $count) {
+ $io->success('All failed messages have been handled or removed!');
+ }
+ }
+
+ private function runWorker(string $failureTransportName, ReceiverInterface $receiver, SymfonyStyle $io, bool $shouldForce): int
+ {
+ $count = 0;
+ $listener = function (WorkerMessageReceivedEvent $messageReceivedEvent) use ($io, $receiver, $shouldForce, &$count) {
+ ++$count;
+ $envelope = $messageReceivedEvent->getEnvelope();
+
+ $this->displaySingleMessage($envelope, $io);
+
+ $shouldHandle = $shouldForce || $io->confirm('Do you want to retry (yes) or delete this message (no)?');
+
+ if ($shouldHandle) {
+ return;
+ }
+
+ $messageReceivedEvent->shouldHandle(false);
+ $receiver->reject($envelope);
+ };
+ $this->eventDispatcher->addListener(WorkerMessageReceivedEvent::class, $listener);
+
+ $worker = new Worker(
+ [$failureTransportName => $receiver],
+ $this->messageBus,
+ $this->eventDispatcher,
+ $this->logger
+ );
+
+ try {
+ $worker->run();
+ } finally {
+ $this->eventDispatcher->removeListener(WorkerMessageReceivedEvent::class, $listener);
+ }
+
+ return $count;
+ }
+
+ private function retrySpecificIds(string $failureTransportName, array $ids, SymfonyStyle $io, bool $shouldForce)
+ {
+ $receiver = $this->getReceiver($failureTransportName);
+
+ if (!$receiver instanceof ListableReceiverInterface) {
+ throw new RuntimeException(sprintf('The "%s" receiver does not support retrying messages by id.', $failureTransportName));
+ }
+
+ foreach ($ids as $id) {
+ $envelope = $receiver->find($id);
+ if (null === $envelope) {
+ throw new RuntimeException(sprintf('The message "%s" was not found.', $id));
+ }
+
+ $singleReceiver = new SingleMessageReceiver($receiver, $envelope);
+ $this->runWorker($failureTransportName, $singleReceiver, $io, $shouldForce);
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Command/FailedMessagesShowCommand.php b/plugins/email/vendor/symfony/messenger/Command/FailedMessagesShowCommand.php
new file mode 100644
index 0000000..6b78c8e
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Command/FailedMessagesShowCommand.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Command;
+
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
+use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
+use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
+
+/**
+ * @author Ryan Weaver
+ */
+class FailedMessagesShowCommand extends AbstractFailedMessagesCommand
+{
+ protected static $defaultName = 'messenger:failed:show';
+ protected static $defaultDescription = 'Show one or more messages from the failure transport';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure(): void
+ {
+ $this
+ ->setDefinition([
+ new InputArgument('id', InputArgument::OPTIONAL, 'Specific message id to show'),
+ new InputOption('max', null, InputOption::VALUE_REQUIRED, 'Maximum number of messages to list', 50),
+ new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
+ ])
+ ->setDescription(self::$defaultDescription)
+ ->setHelp(<<<'EOF'
+The %command.name% shows message that are pending in the failure transport.
+
+ php %command.full_name%
+
+Or look at a specific message by its id:
+
+ php %command.full_name% {id}
+EOF
+ )
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
+
+ $failureTransportName = $input->getOption('transport');
+ if (self::DEFAULT_TRANSPORT_OPTION === $failureTransportName) {
+ $this->printWarningAvailableFailureTransports($io, $this->getGlobalFailureReceiverName());
+ }
+ if ('' === $failureTransportName || null === $failureTransportName) {
+ $failureTransportName = $this->interactiveChooseFailureTransport($io);
+ }
+ $failureTransportName = self::DEFAULT_TRANSPORT_OPTION === $failureTransportName ? $this->getGlobalFailureReceiverName() : $failureTransportName;
+
+ $receiver = $this->getReceiver($failureTransportName);
+
+ $this->printPendingMessagesMessage($receiver, $io);
+
+ if (!$receiver instanceof ListableReceiverInterface) {
+ throw new RuntimeException(sprintf('The "%s" receiver does not support listing or showing specific messages.', $failureTransportName));
+ }
+
+ if (null === $id = $input->getArgument('id')) {
+ $this->listMessages($failureTransportName, $io, $input->getOption('max'));
+ } else {
+ $this->showMessage($failureTransportName, $id, $io);
+ }
+
+ return 0;
+ }
+
+ private function listMessages(?string $failedTransportName, SymfonyStyle $io, int $max)
+ {
+ /** @var ListableReceiverInterface $receiver */
+ $receiver = $this->getReceiver($failedTransportName);
+ $envelopes = $receiver->all($max);
+
+ $rows = [];
+ foreach ($envelopes as $envelope) {
+ /** @var RedeliveryStamp|null $lastRedeliveryStamp */
+ $lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
+ /** @var ErrorDetailsStamp|null $lastErrorDetailsStamp */
+ $lastErrorDetailsStamp = $envelope->last(ErrorDetailsStamp::class);
+ $lastRedeliveryStampWithException = $this->getLastRedeliveryStampWithException($envelope, true);
+
+ $errorMessage = '';
+ if (null !== $lastErrorDetailsStamp) {
+ $errorMessage = $lastErrorDetailsStamp->getExceptionMessage();
+ } elseif (null !== $lastRedeliveryStampWithException) {
+ // Try reading the errorMessage for messages that are still in the queue without the new ErrorDetailStamps.
+ $errorMessage = $lastRedeliveryStampWithException->getExceptionMessage();
+ }
+
+ $rows[] = [
+ $this->getMessageId($envelope),
+ \get_class($envelope->getMessage()),
+ null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s'),
+ $errorMessage,
+ ];
+ }
+
+ if (0 === \count($rows)) {
+ $io->success('No failed messages were found.');
+
+ return;
+ }
+
+ $io->table(['Id', 'Class', 'Failed at', 'Error'], $rows);
+
+ if (\count($rows) === $max) {
+ $io->comment(sprintf('Showing first %d messages.', $max));
+ }
+
+ $io->comment(sprintf('Run messenger:failed:show {id} --transport=%s -vv to see message details.', $failedTransportName));
+ }
+
+ private function showMessage(?string $failedTransportName, string $id, SymfonyStyle $io)
+ {
+ /** @var ListableReceiverInterface $receiver */
+ $receiver = $this->getReceiver($failedTransportName);
+ $envelope = $receiver->find($id);
+ if (null === $envelope) {
+ throw new RuntimeException(sprintf('The message "%s" was not found.', $id));
+ }
+
+ $this->displaySingleMessage($envelope, $io);
+
+ $io->writeln([
+ '',
+ sprintf(' Run messenger:failed:retry %s --transport=%s to retry this message.', $id, $failedTransportName),
+ sprintf(' Run messenger:failed:remove %s --transport=%s to delete it.', $id, $failedTransportName),
+ ]);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Command/SetupTransportsCommand.php b/plugins/email/vendor/symfony/messenger/Command/SetupTransportsCommand.php
new file mode 100644
index 0000000..a5feae3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Command/SetupTransportsCommand.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Command;
+
+use Psr\Container\ContainerInterface;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class SetupTransportsCommand extends Command
+{
+ protected static $defaultName = 'messenger:setup-transports';
+ protected static $defaultDescription = 'Prepare the required infrastructure for the transport';
+
+ private $transportLocator;
+ private $transportNames;
+
+ public function __construct(ContainerInterface $transportLocator, array $transportNames = [])
+ {
+ $this->transportLocator = $transportLocator;
+ $this->transportNames = $transportNames;
+
+ parent::__construct();
+ }
+
+ protected function configure()
+ {
+ $this
+ ->addArgument('transport', InputArgument::OPTIONAL, 'Name of the transport to setup', null)
+ ->setDescription(self::$defaultDescription)
+ ->setHelp(<<%command.name% command setups the transports:
+
+ php %command.full_name%
+
+Or a specific transport only:
+
+ php %command.full_name%
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $transportNames = $this->transportNames;
+ // do we want to set up only one transport?
+ if ($transport = $input->getArgument('transport')) {
+ if (!$this->transportLocator->has($transport)) {
+ throw new \RuntimeException(sprintf('The "%s" transport does not exist.', $transport));
+ }
+ $transportNames = [$transport];
+ }
+
+ foreach ($transportNames as $id => $transportName) {
+ $transport = $this->transportLocator->get($transportName);
+ if ($transport instanceof SetupableTransportInterface) {
+ $transport->setup();
+ $io->success(sprintf('The "%s" transport was set up successfully.', $transportName));
+ } else {
+ $io->note(sprintf('The "%s" transport does not support setup.', $transportName));
+ }
+ }
+
+ return 0;
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('transport')) {
+ $suggestions->suggestValues($this->transportNames);
+
+ return;
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Command/StopWorkersCommand.php b/plugins/email/vendor/symfony/messenger/Command/StopWorkersCommand.php
new file mode 100644
index 0000000..16fb1a4
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Command/StopWorkersCommand.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Command;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener;
+
+/**
+ * @author Ryan Weaver
+ */
+class StopWorkersCommand extends Command
+{
+ protected static $defaultName = 'messenger:stop-workers';
+ protected static $defaultDescription = 'Stop workers after their current message';
+
+ private $restartSignalCachePool;
+
+ public function __construct(CacheItemPoolInterface $restartSignalCachePool)
+ {
+ $this->restartSignalCachePool = $restartSignalCachePool;
+
+ parent::__construct();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure(): void
+ {
+ $this
+ ->setDefinition([])
+ ->setDescription(self::$defaultDescription)
+ ->setHelp(<<<'EOF'
+The %command.name% command sends a signal to stop any messenger:consume processes that are running.
+
+ php %command.full_name%
+
+Each worker command will finish the message they are currently processing
+and then exit. Worker commands are *not* automatically restarted: that
+should be handled by a process control system.
+EOF
+ )
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
+
+ $cacheItem = $this->restartSignalCachePool->getItem(StopWorkerOnRestartSignalListener::RESTART_REQUESTED_TIMESTAMP_KEY);
+ $cacheItem->set(microtime(true));
+ $this->restartSignalCachePool->save($cacheItem);
+
+ $io->success('Signal successfully sent to stop any running workers.');
+
+ return 0;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/DataCollector/MessengerDataCollector.php b/plugins/email/vendor/symfony/messenger/DataCollector/MessengerDataCollector.php
new file mode 100644
index 0000000..ef1ec4e
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/DataCollector/MessengerDataCollector.php
@@ -0,0 +1,149 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\DataCollector;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
+use Symfony\Component\Messenger\TraceableMessageBus;
+use Symfony\Component\VarDumper\Caster\ClassStub;
+
+/**
+ * @author Samuel Roze
+ *
+ * @final
+ */
+class MessengerDataCollector extends DataCollector implements LateDataCollectorInterface
+{
+ private $traceableBuses = [];
+
+ public function registerBus(string $name, TraceableMessageBus $bus)
+ {
+ $this->traceableBuses[$name] = $bus;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function collect(Request $request, Response $response, \Throwable $exception = null)
+ {
+ // Noop. Everything is collected live by the traceable buses & cloned as late as possible.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function lateCollect()
+ {
+ $this->data = ['messages' => [], 'buses' => array_keys($this->traceableBuses)];
+
+ $messages = [];
+ foreach ($this->traceableBuses as $busName => $bus) {
+ foreach ($bus->getDispatchedMessages() as $message) {
+ $debugRepresentation = $this->cloneVar($this->collectMessage($busName, $message));
+ $messages[] = [$debugRepresentation, $message['callTime']];
+ }
+ }
+
+ // Order by call time
+ usort($messages, function ($a, $b) { return $a[1] <=> $b[1]; });
+
+ // Keep the messages clones only
+ $this->data['messages'] = array_column($messages, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName(): string
+ {
+ return 'messenger';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ $this->data = [];
+ foreach ($this->traceableBuses as $traceableBus) {
+ $traceableBus->reset();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getCasters(): array
+ {
+ $casters = parent::getCasters();
+
+ // Unset the default caster truncating collectors data.
+ unset($casters['*']);
+
+ return $casters;
+ }
+
+ private function collectMessage(string $busName, array $tracedMessage)
+ {
+ $message = $tracedMessage['message'];
+
+ $debugRepresentation = [
+ 'bus' => $busName,
+ 'stamps' => $tracedMessage['stamps'] ?? null,
+ 'stamps_after_dispatch' => $tracedMessage['stamps_after_dispatch'] ?? null,
+ 'message' => [
+ 'type' => new ClassStub(\get_class($message)),
+ 'value' => $message,
+ ],
+ 'caller' => $tracedMessage['caller'],
+ ];
+
+ if (isset($tracedMessage['exception'])) {
+ $exception = $tracedMessage['exception'];
+
+ $debugRepresentation['exception'] = [
+ 'type' => \get_class($exception),
+ 'value' => $exception,
+ ];
+ }
+
+ return $debugRepresentation;
+ }
+
+ public function getExceptionsCount(string $bus = null): int
+ {
+ $count = 0;
+ foreach ($this->getMessages($bus) as $message) {
+ $count += (int) isset($message['exception']);
+ }
+
+ return $count;
+ }
+
+ public function getMessages(string $bus = null): array
+ {
+ if (null === $bus) {
+ return $this->data['messages'];
+ }
+
+ return array_filter($this->data['messages'], function ($message) use ($bus) {
+ return $bus === $message['bus'];
+ });
+ }
+
+ public function getBuses(): array
+ {
+ return $this->data['buses'];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/DependencyInjection/MessengerPass.php b/plugins/email/vendor/symfony/messenger/DependencyInjection/MessengerPass.php
new file mode 100644
index 0000000..f423889
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/DependencyInjection/MessengerPass.php
@@ -0,0 +1,402 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Messenger\Handler\HandlerDescriptor;
+use Symfony\Component\Messenger\Handler\HandlersLocator;
+use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
+use Symfony\Component\Messenger\TraceableMessageBus;
+use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+
+/**
+ * @author Samuel Roze
+ */
+class MessengerPass implements CompilerPassInterface
+{
+ private $handlerTag;
+ private $busTag;
+ private $receiverTag;
+
+ public function __construct(string $handlerTag = 'messenger.message_handler', string $busTag = 'messenger.bus', string $receiverTag = 'messenger.receiver')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/messenger', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->handlerTag = $handlerTag;
+ $this->busTag = $busTag;
+ $this->receiverTag = $receiverTag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ $busIds = [];
+ foreach ($container->findTaggedServiceIds($this->busTag) as $busId => $tags) {
+ $busIds[] = $busId;
+ if ($container->hasParameter($busMiddlewareParameter = $busId.'.middleware')) {
+ $this->registerBusMiddleware($container, $busId, $container->getParameter($busMiddlewareParameter));
+
+ $container->getParameterBag()->remove($busMiddlewareParameter);
+ }
+
+ if ($container->hasDefinition('data_collector.messenger')) {
+ $this->registerBusToCollector($container, $busId);
+ }
+ }
+
+ if ($container->hasDefinition('messenger.receiver_locator')) {
+ $this->registerReceivers($container, $busIds);
+ }
+ $this->registerHandlers($container, $busIds);
+ }
+
+ private function registerHandlers(ContainerBuilder $container, array $busIds)
+ {
+ $definitions = [];
+ $handlersByBusAndMessage = [];
+ $handlerToOriginalServiceIdMapping = [];
+
+ foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) {
+ foreach ($tags as $tag) {
+ if (isset($tag['bus']) && !\in_array($tag['bus'], $busIds, true)) {
+ throw new RuntimeException(sprintf('Invalid handler service "%s": bus "%s" specified on the tag "%s" does not exist (known ones are: "%s").', $serviceId, $tag['bus'], $this->handlerTag, implode('", "', $busIds)));
+ }
+
+ $className = $this->getServiceClass($container, $serviceId);
+ $r = $container->getReflectionClass($className);
+
+ if (null === $r) {
+ throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $serviceId, $className));
+ }
+
+ if (isset($tag['handles'])) {
+ $handles = isset($tag['method']) ? [$tag['handles'] => $tag['method']] : [$tag['handles']];
+ } else {
+ $handles = $this->guessHandledClasses($r, $serviceId);
+ }
+
+ $message = null;
+ $handlerBuses = (array) ($tag['bus'] ?? $busIds);
+
+ foreach ($handles as $message => $options) {
+ $buses = $handlerBuses;
+
+ if (\is_int($message)) {
+ if (\is_string($options)) {
+ $message = $options;
+ $options = [];
+ } else {
+ throw new RuntimeException(sprintf('The handler configuration needs to return an array of messages or an associated array of message and configuration. Found value of type "%s" at position "%d" for service "%s".', get_debug_type($options), $message, $serviceId));
+ }
+ }
+
+ if (\is_string($options)) {
+ $options = ['method' => $options];
+ }
+
+ if (!isset($options['from_transport']) && isset($tag['from_transport'])) {
+ $options['from_transport'] = $tag['from_transport'];
+ }
+
+ $priority = $tag['priority'] ?? $options['priority'] ?? 0;
+ $method = $options['method'] ?? '__invoke';
+
+ if (isset($options['bus'])) {
+ if (!\in_array($options['bus'], $busIds)) {
+ $messageLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : ($r->implementsInterface(MessageSubscriberInterface::class) ? sprintf('returned by method "%s::getHandledMessages()"', $r->getName()) : sprintf('used as argument type in method "%s::%s()"', $r->getName(), $method));
+
+ throw new RuntimeException(sprintf('Invalid configuration "%s" for message "%s": bus "%s" does not exist.', $messageLocation, $message, $options['bus']));
+ }
+
+ $buses = [$options['bus']];
+ }
+
+ if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
+ $messageLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : ($r->implementsInterface(MessageSubscriberInterface::class) ? sprintf('returned by method "%s::getHandledMessages()"', $r->getName()) : sprintf('used as argument type in method "%s::%s()"', $r->getName(), $method));
+
+ throw new RuntimeException(sprintf('Invalid handler service "%s": class or interface "%s" "%s" not found.', $serviceId, $message, $messageLocation));
+ }
+
+ if (!$r->hasMethod($method)) {
+ throw new RuntimeException(sprintf('Invalid handler service "%s": method "%s::%s()" does not exist.', $serviceId, $r->getName(), $method));
+ }
+
+ if ('__invoke' !== $method) {
+ $wrapperDefinition = (new Definition('callable'))->addArgument([new Reference($serviceId), $method])->setFactory('Closure::fromCallable');
+
+ $definitions[$definitionId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($message.':'.$priority.':'.$serviceId.':'.$method)] = $wrapperDefinition;
+ } else {
+ $definitionId = $serviceId;
+ }
+
+ $handlerToOriginalServiceIdMapping[$definitionId] = $serviceId;
+
+ foreach ($buses as $handlerBus) {
+ $handlersByBusAndMessage[$handlerBus][$message][$priority][] = [$definitionId, $options];
+ }
+ }
+
+ if (null === $message) {
+ throw new RuntimeException(sprintf('Invalid handler service "%s": method "%s::getHandledMessages()" must return one or more messages.', $serviceId, $r->getName()));
+ }
+ }
+ }
+
+ foreach ($handlersByBusAndMessage as $bus => $handlersByMessage) {
+ foreach ($handlersByMessage as $message => $handlersByPriority) {
+ krsort($handlersByPriority);
+ $handlersByBusAndMessage[$bus][$message] = array_merge(...$handlersByPriority);
+ }
+ }
+
+ $handlersLocatorMappingByBus = [];
+ foreach ($handlersByBusAndMessage as $bus => $handlersByMessage) {
+ foreach ($handlersByMessage as $message => $handlers) {
+ $handlerDescriptors = [];
+ foreach ($handlers as $handler) {
+ $definitions[$definitionId = '.messenger.handler_descriptor.'.ContainerBuilder::hash($bus.':'.$message.':'.$handler[0])] = (new Definition(HandlerDescriptor::class))->setArguments([new Reference($handler[0]), $handler[1]]);
+ $handlerDescriptors[] = new Reference($definitionId);
+ }
+
+ $handlersLocatorMappingByBus[$bus][$message] = new IteratorArgument($handlerDescriptors);
+ }
+ }
+ $container->addDefinitions($definitions);
+
+ foreach ($busIds as $bus) {
+ $container->register($locatorId = $bus.'.messenger.handlers_locator', HandlersLocator::class)
+ ->setArgument(0, $handlersLocatorMappingByBus[$bus] ?? [])
+ ;
+ if ($container->has($handleMessageId = $bus.'.middleware.handle_message')) {
+ $container->getDefinition($handleMessageId)
+ ->replaceArgument(0, new Reference($locatorId))
+ ;
+ }
+ }
+
+ if ($container->hasDefinition('console.command.messenger_debug')) {
+ $debugCommandMapping = $handlersByBusAndMessage;
+ foreach ($busIds as $bus) {
+ if (!isset($debugCommandMapping[$bus])) {
+ $debugCommandMapping[$bus] = [];
+ }
+
+ foreach ($debugCommandMapping[$bus] as $message => $handlers) {
+ foreach ($handlers as $key => $handler) {
+ $debugCommandMapping[$bus][$message][$key][0] = $handlerToOriginalServiceIdMapping[$handler[0]];
+ }
+ }
+ }
+ $container->getDefinition('console.command.messenger_debug')->replaceArgument(0, $debugCommandMapping);
+ }
+ }
+
+ private function guessHandledClasses(\ReflectionClass $handlerClass, string $serviceId): iterable
+ {
+ if ($handlerClass->implementsInterface(MessageSubscriberInterface::class)) {
+ return $handlerClass->getName()::getHandledMessages();
+ }
+
+ try {
+ $method = $handlerClass->getMethod('__invoke');
+ } catch (\ReflectionException $e) {
+ throw new RuntimeException(sprintf('Invalid handler service "%s": class "%s" must have an "__invoke()" method.', $serviceId, $handlerClass->getName()));
+ }
+
+ if (0 === $method->getNumberOfRequiredParameters()) {
+ throw new RuntimeException(sprintf('Invalid handler service "%s": method "%s::__invoke()" requires at least one argument, first one being the message it handles.', $serviceId, $handlerClass->getName()));
+ }
+
+ $parameters = $method->getParameters();
+ if (!$type = $parameters[0]->getType()) {
+ throw new RuntimeException(sprintf('Invalid handler service "%s": argument "$%s" of method "%s::__invoke()" must have a type-hint corresponding to the message class it handles.', $serviceId, $parameters[0]->getName(), $handlerClass->getName()));
+ }
+
+ if ($type instanceof \ReflectionUnionType) {
+ $types = [];
+ $invalidTypes = [];
+ foreach ($type->getTypes() as $type) {
+ if (!$type->isBuiltin()) {
+ $types[] = (string) $type;
+ } else {
+ $invalidTypes[] = (string) $type;
+ }
+ }
+
+ if ($types) {
+ return $types;
+ }
+
+ throw new RuntimeException(sprintf('Invalid handler service "%s": type-hint of argument "$%s" in method "%s::__invoke()" must be a class , "%s" given.', $serviceId, $parameters[0]->getName(), $handlerClass->getName(), implode('|', $invalidTypes)));
+ }
+
+ if ($type->isBuiltin()) {
+ throw new RuntimeException(sprintf('Invalid handler service "%s": type-hint of argument "$%s" in method "%s::__invoke()" must be a class , "%s" given.', $serviceId, $parameters[0]->getName(), $handlerClass->getName(), $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type));
+ }
+
+ return [$type->getName()];
+ }
+
+ private function registerReceivers(ContainerBuilder $container, array $busIds)
+ {
+ $receiverMapping = [];
+ $failureTransportsMap = [];
+ if ($container->hasDefinition('console.command.messenger_failed_messages_retry')) {
+ $commandDefinition = $container->getDefinition('console.command.messenger_failed_messages_retry');
+ $globalReceiverName = $commandDefinition->getArgument(0);
+ if (null !== $globalReceiverName) {
+ if ($container->hasAlias('messenger.failure_transports.default')) {
+ $failureTransportsMap[$globalReceiverName] = new Reference('messenger.failure_transports.default');
+ } else {
+ $failureTransportsMap[$globalReceiverName] = new Reference('messenger.transport.'.$globalReceiverName);
+ }
+ }
+ }
+
+ foreach ($container->findTaggedServiceIds($this->receiverTag) as $id => $tags) {
+ $receiverClass = $this->getServiceClass($container, $id);
+ if (!is_subclass_of($receiverClass, ReceiverInterface::class)) {
+ throw new RuntimeException(sprintf('Invalid receiver "%s": class "%s" must implement interface "%s".', $id, $receiverClass, ReceiverInterface::class));
+ }
+
+ $receiverMapping[$id] = new Reference($id);
+
+ foreach ($tags as $tag) {
+ if (isset($tag['alias'])) {
+ $receiverMapping[$tag['alias']] = $receiverMapping[$id];
+ if ($tag['is_failure_transport'] ?? false) {
+ $failureTransportsMap[$tag['alias']] = $receiverMapping[$id];
+ }
+ }
+ }
+ }
+
+ $receiverNames = [];
+ foreach ($receiverMapping as $name => $reference) {
+ $receiverNames[(string) $reference] = $name;
+ }
+
+ $buses = [];
+ foreach ($busIds as $busId) {
+ $buses[$busId] = new Reference($busId);
+ }
+
+ if ($hasRoutableMessageBus = $container->hasDefinition('messenger.routable_message_bus')) {
+ $container->getDefinition('messenger.routable_message_bus')
+ ->replaceArgument(0, ServiceLocatorTagPass::register($container, $buses));
+ }
+
+ if ($container->hasDefinition('console.command.messenger_consume_messages')) {
+ $consumeCommandDefinition = $container->getDefinition('console.command.messenger_consume_messages');
+
+ if ($hasRoutableMessageBus) {
+ $consumeCommandDefinition->replaceArgument(0, new Reference('messenger.routable_message_bus'));
+ }
+
+ $consumeCommandDefinition->replaceArgument(4, array_values($receiverNames));
+ try {
+ $consumeCommandDefinition->replaceArgument(6, $busIds);
+ } catch (OutOfBoundsException $e) {
+ // ignore to preserve compatibility with symfony/framework-bundle < 5.4
+ }
+ }
+
+ if ($container->hasDefinition('console.command.messenger_setup_transports')) {
+ $container->getDefinition('console.command.messenger_setup_transports')
+ ->replaceArgument(1, array_values($receiverNames));
+ }
+
+ $container->getDefinition('messenger.receiver_locator')->replaceArgument(0, $receiverMapping);
+
+ $failureTransportsLocator = ServiceLocatorTagPass::register($container, $failureTransportsMap);
+
+ $failedCommandIds = [
+ 'console.command.messenger_failed_messages_retry',
+ 'console.command.messenger_failed_messages_show',
+ 'console.command.messenger_failed_messages_remove',
+ ];
+ foreach ($failedCommandIds as $failedCommandId) {
+ if ($container->hasDefinition($failedCommandId)) {
+ $definition = $container->getDefinition($failedCommandId);
+ $definition->replaceArgument(1, $failureTransportsLocator);
+ }
+ }
+ }
+
+ private function registerBusToCollector(ContainerBuilder $container, string $busId)
+ {
+ $container->setDefinition(
+ $tracedBusId = 'debug.traced.'.$busId,
+ (new Definition(TraceableMessageBus::class, [new Reference($tracedBusId.'.inner')]))->setDecoratedService($busId)
+ );
+
+ $container->getDefinition('data_collector.messenger')->addMethodCall('registerBus', [$busId, new Reference($tracedBusId)]);
+ }
+
+ private function registerBusMiddleware(ContainerBuilder $container, string $busId, array $middlewareCollection)
+ {
+ $middlewareReferences = [];
+ foreach ($middlewareCollection as $middlewareItem) {
+ $id = $middlewareItem['id'];
+ $arguments = $middlewareItem['arguments'] ?? [];
+ if (!$container->has($messengerMiddlewareId = 'messenger.middleware.'.$id)) {
+ $messengerMiddlewareId = $id;
+ }
+
+ if (!$container->has($messengerMiddlewareId)) {
+ throw new RuntimeException(sprintf('Invalid middleware: service "%s" not found.', $id));
+ }
+
+ if ($container->findDefinition($messengerMiddlewareId)->isAbstract()) {
+ $childDefinition = new ChildDefinition($messengerMiddlewareId);
+ $childDefinition->setArguments($arguments);
+ if (isset($middlewareReferences[$messengerMiddlewareId = $busId.'.middleware.'.$id])) {
+ $messengerMiddlewareId .= '.'.ContainerBuilder::hash($arguments);
+ }
+ $container->setDefinition($messengerMiddlewareId, $childDefinition);
+ } elseif ($arguments) {
+ throw new RuntimeException(sprintf('Invalid middleware factory "%s": a middleware factory must be an abstract definition.', $id));
+ }
+
+ $middlewareReferences[$messengerMiddlewareId] = new Reference($messengerMiddlewareId);
+ }
+
+ $container->getDefinition($busId)->replaceArgument(0, new IteratorArgument(array_values($middlewareReferences)));
+ }
+
+ private function getServiceClass(ContainerBuilder $container, string $serviceId): string
+ {
+ while (true) {
+ $definition = $container->findDefinition($serviceId);
+
+ if (!$definition->getClass() && $definition instanceof ChildDefinition) {
+ $serviceId = $definition->getParent();
+
+ continue;
+ }
+
+ return $definition->getClass();
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Envelope.php b/plugins/email/vendor/symfony/messenger/Envelope.php
new file mode 100644
index 0000000..49476fe
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Envelope.php
@@ -0,0 +1,131 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger;
+
+use Symfony\Component\Messenger\Stamp\StampInterface;
+
+/**
+ * A message wrapped in an envelope with stamps (configurations, markers, ...).
+ *
+ * @author Maxime Steinhausser
+ */
+final class Envelope
+{
+ /**
+ * @var array>
+ */
+ private $stamps = [];
+ private $message;
+
+ /**
+ * @param StampInterface[] $stamps
+ */
+ public function __construct(object $message, array $stamps = [])
+ {
+ $this->message = $message;
+
+ foreach ($stamps as $stamp) {
+ $this->stamps[\get_class($stamp)][] = $stamp;
+ }
+ }
+
+ /**
+ * Makes sure the message is in an Envelope and adds the given stamps.
+ *
+ * @param object|Envelope $message
+ * @param StampInterface[] $stamps
+ */
+ public static function wrap(object $message, array $stamps = []): self
+ {
+ $envelope = $message instanceof self ? $message : new self($message);
+
+ return $envelope->with(...$stamps);
+ }
+
+ /**
+ * @return static A new Envelope instance with additional stamp
+ */
+ public function with(StampInterface ...$stamps): self
+ {
+ $cloned = clone $this;
+
+ foreach ($stamps as $stamp) {
+ $cloned->stamps[\get_class($stamp)][] = $stamp;
+ }
+
+ return $cloned;
+ }
+
+ /**
+ * @return static A new Envelope instance without any stamps of the given class
+ */
+ public function withoutAll(string $stampFqcn): self
+ {
+ $cloned = clone $this;
+
+ unset($cloned->stamps[$this->resolveAlias($stampFqcn)]);
+
+ return $cloned;
+ }
+
+ /**
+ * Removes all stamps that implement the given type.
+ */
+ public function withoutStampsOfType(string $type): self
+ {
+ $cloned = clone $this;
+ $type = $this->resolveAlias($type);
+
+ foreach ($cloned->stamps as $class => $stamps) {
+ if ($class === $type || is_subclass_of($class, $type)) {
+ unset($cloned->stamps[$class]);
+ }
+ }
+
+ return $cloned;
+ }
+
+ public function last(string $stampFqcn): ?StampInterface
+ {
+ return isset($this->stamps[$stampFqcn = $this->resolveAlias($stampFqcn)]) ? end($this->stamps[$stampFqcn]) : null;
+ }
+
+ /**
+ * @return StampInterface[]|StampInterface[][] The stamps for the specified FQCN, or all stamps by their class name
+ */
+ public function all(string $stampFqcn = null): array
+ {
+ if (null !== $stampFqcn) {
+ return $this->stamps[$this->resolveAlias($stampFqcn)] ?? [];
+ }
+
+ return $this->stamps;
+ }
+
+ /**
+ * @return object The original message contained in the envelope
+ */
+ public function getMessage(): object
+ {
+ return $this->message;
+ }
+
+ /**
+ * BC to be removed in 6.0.
+ */
+ private function resolveAlias(string $fqcn): string
+ {
+ static $resolved;
+
+ return $resolved[$fqcn] ?? ($resolved[$fqcn] = class_exists($fqcn) ? (new \ReflectionClass($fqcn))->getName() : $fqcn);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/AbstractWorkerMessageEvent.php b/plugins/email/vendor/symfony/messenger/Event/AbstractWorkerMessageEvent.php
new file mode 100644
index 0000000..e3ece81
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/AbstractWorkerMessageEvent.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Stamp\StampInterface;
+
+abstract class AbstractWorkerMessageEvent
+{
+ private $envelope;
+ private $receiverName;
+
+ public function __construct(Envelope $envelope, string $receiverName)
+ {
+ $this->envelope = $envelope;
+ $this->receiverName = $receiverName;
+ }
+
+ public function getEnvelope(): Envelope
+ {
+ return $this->envelope;
+ }
+
+ /**
+ * Returns a unique identifier for transport receiver this message was received from.
+ */
+ public function getReceiverName(): string
+ {
+ return $this->receiverName;
+ }
+
+ public function addStamps(StampInterface ...$stamps): void
+ {
+ $this->envelope = $this->envelope->with(...$stamps);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/SendMessageToTransportsEvent.php b/plugins/email/vendor/symfony/messenger/Event/SendMessageToTransportsEvent.php
new file mode 100644
index 0000000..5fd5fd8
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/SendMessageToTransportsEvent.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Event is dispatched before a message is sent to the transport.
+ *
+ * The event is *only* dispatched if the message will actually
+ * be sent to at least one transport. If the message is sent
+ * to multiple transports, the message is dispatched only one time.
+ * This message is only dispatched the first time a message
+ * is sent to a transport, not also if it is retried.
+ *
+ * @author Ryan Weaver
+ */
+final class SendMessageToTransportsEvent
+{
+ private $envelope;
+
+ public function __construct(Envelope $envelope)
+ {
+ $this->envelope = $envelope;
+ }
+
+ public function getEnvelope(): Envelope
+ {
+ return $this->envelope;
+ }
+
+ public function setEnvelope(Envelope $envelope)
+ {
+ $this->envelope = $envelope;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/WorkerMessageFailedEvent.php b/plugins/email/vendor/symfony/messenger/Event/WorkerMessageFailedEvent.php
new file mode 100644
index 0000000..7fb858c
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/WorkerMessageFailedEvent.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Dispatched when a message was received from a transport and handling failed.
+ *
+ * The event name is the class name.
+ */
+final class WorkerMessageFailedEvent extends AbstractWorkerMessageEvent
+{
+ private $throwable;
+ private $willRetry = false;
+
+ public function __construct(Envelope $envelope, string $receiverName, \Throwable $error)
+ {
+ $this->throwable = $error;
+
+ parent::__construct($envelope, $receiverName);
+ }
+
+ public function getThrowable(): \Throwable
+ {
+ return $this->throwable;
+ }
+
+ public function willRetry(): bool
+ {
+ return $this->willRetry;
+ }
+
+ public function setForRetry(): void
+ {
+ $this->willRetry = true;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/WorkerMessageHandledEvent.php b/plugins/email/vendor/symfony/messenger/Event/WorkerMessageHandledEvent.php
new file mode 100644
index 0000000..3c4a030
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/WorkerMessageHandledEvent.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+/**
+ * Dispatched after a message was received from a transport and successfully handled.
+ *
+ * The event name is the class name.
+ */
+final class WorkerMessageHandledEvent extends AbstractWorkerMessageEvent
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/WorkerMessageReceivedEvent.php b/plugins/email/vendor/symfony/messenger/Event/WorkerMessageReceivedEvent.php
new file mode 100644
index 0000000..5b99edc
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/WorkerMessageReceivedEvent.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+/**
+ * Dispatched when a message was received from a transport but before sent to the bus.
+ *
+ * The event name is the class name.
+ */
+final class WorkerMessageReceivedEvent extends AbstractWorkerMessageEvent
+{
+ private $shouldHandle = true;
+
+ public function shouldHandle(bool $shouldHandle = null): bool
+ {
+ if (null !== $shouldHandle) {
+ $this->shouldHandle = $shouldHandle;
+ }
+
+ return $this->shouldHandle;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/WorkerMessageRetriedEvent.php b/plugins/email/vendor/symfony/messenger/Event/WorkerMessageRetriedEvent.php
new file mode 100644
index 0000000..d348b44
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/WorkerMessageRetriedEvent.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+/**
+ * Dispatched after a message has been sent for retry.
+ *
+ * The event name is the class name.
+ */
+final class WorkerMessageRetriedEvent extends AbstractWorkerMessageEvent
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/WorkerRunningEvent.php b/plugins/email/vendor/symfony/messenger/Event/WorkerRunningEvent.php
new file mode 100644
index 0000000..ca32cb4
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/WorkerRunningEvent.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+use Symfony\Component\Messenger\Worker;
+
+/**
+ * Dispatched after the worker processed a message or didn't receive a message at all.
+ *
+ * @author Tobias Schultze
+ */
+final class WorkerRunningEvent
+{
+ private $worker;
+ private $isWorkerIdle;
+
+ public function __construct(Worker $worker, bool $isWorkerIdle)
+ {
+ $this->worker = $worker;
+ $this->isWorkerIdle = $isWorkerIdle;
+ }
+
+ public function getWorker(): Worker
+ {
+ return $this->worker;
+ }
+
+ /**
+ * Returns true when no message has been received by the worker.
+ */
+ public function isWorkerIdle(): bool
+ {
+ return $this->isWorkerIdle;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/WorkerStartedEvent.php b/plugins/email/vendor/symfony/messenger/Event/WorkerStartedEvent.php
new file mode 100644
index 0000000..9d37d8d
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/WorkerStartedEvent.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+use Symfony\Component\Messenger\Worker;
+
+/**
+ * Dispatched when a worker has been started.
+ *
+ * @author Tobias Schultze
+ */
+final class WorkerStartedEvent
+{
+ private $worker;
+
+ public function __construct(Worker $worker)
+ {
+ $this->worker = $worker;
+ }
+
+ public function getWorker(): Worker
+ {
+ return $this->worker;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Event/WorkerStoppedEvent.php b/plugins/email/vendor/symfony/messenger/Event/WorkerStoppedEvent.php
new file mode 100644
index 0000000..e0d4610
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Event/WorkerStoppedEvent.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Event;
+
+use Symfony\Component\Messenger\Worker;
+
+/**
+ * Dispatched when a worker has been stopped.
+ *
+ * @author Robin Chalas
+ */
+final class WorkerStoppedEvent
+{
+ private $worker;
+
+ public function __construct(Worker $worker)
+ {
+ $this->worker = $worker;
+ }
+
+ public function getWorker(): Worker
+ {
+ return $this->worker;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/AddErrorDetailsStampListener.php b/plugins/email/vendor/symfony/messenger/EventListener/AddErrorDetailsStampListener.php
new file mode 100644
index 0000000..fd5c9d6
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/AddErrorDetailsStampListener.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
+use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
+
+final class AddErrorDetailsStampListener implements EventSubscriberInterface
+{
+ public function onMessageFailed(WorkerMessageFailedEvent $event): void
+ {
+ $stamp = ErrorDetailsStamp::create($event->getThrowable());
+ $previousStamp = $event->getEnvelope()->last(ErrorDetailsStamp::class);
+
+ // Do not append duplicate information
+ if (null === $previousStamp || !$previousStamp->equals($stamp)) {
+ $event->addStamps($stamp);
+ }
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ // must have higher priority than SendFailedMessageForRetryListener
+ WorkerMessageFailedEvent::class => ['onMessageFailed', 200],
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/DispatchPcntlSignalListener.php b/plugins/email/vendor/symfony/messenger/EventListener/DispatchPcntlSignalListener.php
new file mode 100644
index 0000000..27182bd
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/DispatchPcntlSignalListener.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+
+/**
+ * @author Tobias Schultze
+ */
+class DispatchPcntlSignalListener implements EventSubscriberInterface
+{
+ public function onWorkerRunning(): void
+ {
+ pcntl_signal_dispatch();
+ }
+
+ public static function getSubscribedEvents()
+ {
+ if (!\function_exists('pcntl_signal_dispatch')) {
+ return [];
+ }
+
+ return [
+ WorkerRunningEvent::class => ['onWorkerRunning', 100],
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/ResetServicesListener.php b/plugins/email/vendor/symfony/messenger/EventListener/ResetServicesListener.php
new file mode 100644
index 0000000..d5266f3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/ResetServicesListener.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+use Symfony\Component\Messenger\Event\WorkerStoppedEvent;
+
+/**
+ * @author Grégoire Pineau
+ */
+class ResetServicesListener implements EventSubscriberInterface
+{
+ private $servicesResetter;
+
+ public function __construct(ServicesResetter $servicesResetter)
+ {
+ $this->servicesResetter = $servicesResetter;
+ }
+
+ public function resetServices(WorkerRunningEvent $event): void
+ {
+ if (!$event->isWorkerIdle()) {
+ $this->servicesResetter->reset();
+ }
+ }
+
+ public function resetServicesAtStop(WorkerStoppedEvent $event): void
+ {
+ $this->servicesResetter->reset();
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ WorkerRunningEvent::class => ['resetServices', -1024],
+ WorkerStoppedEvent::class => ['resetServicesAtStop', -1024],
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/SendFailedMessageForRetryListener.php b/plugins/email/vendor/symfony/messenger/EventListener/SendFailedMessageForRetryListener.php
new file mode 100644
index 0000000..dab74b2
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/SendFailedMessageForRetryListener.php
@@ -0,0 +1,173 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Psr\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
+use Symfony\Component\Messenger\Event\WorkerMessageRetriedEvent;
+use Symfony\Component\Messenger\Exception\HandlerFailedException;
+use Symfony\Component\Messenger\Exception\RecoverableExceptionInterface;
+use Symfony\Component\Messenger\Exception\RuntimeException;
+use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;
+use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
+use Symfony\Component\Messenger\Stamp\DelayStamp;
+use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
+use Symfony\Component\Messenger\Stamp\StampInterface;
+use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @author Tobias Schultze
+ */
+class SendFailedMessageForRetryListener implements EventSubscriberInterface
+{
+ private $sendersLocator;
+ private $retryStrategyLocator;
+ private $logger;
+ private $eventDispatcher;
+ private $historySize;
+
+ public function __construct(ContainerInterface $sendersLocator, ContainerInterface $retryStrategyLocator, LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, int $historySize = 10)
+ {
+ $this->sendersLocator = $sendersLocator;
+ $this->retryStrategyLocator = $retryStrategyLocator;
+ $this->logger = $logger;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->historySize = $historySize;
+ }
+
+ public function onMessageFailed(WorkerMessageFailedEvent $event)
+ {
+ $retryStrategy = $this->getRetryStrategyForTransport($event->getReceiverName());
+ $envelope = $event->getEnvelope();
+ $throwable = $event->getThrowable();
+
+ $message = $envelope->getMessage();
+ $context = [
+ 'class' => \get_class($message),
+ ];
+
+ $shouldRetry = $retryStrategy && $this->shouldRetry($throwable, $envelope, $retryStrategy);
+
+ $retryCount = RedeliveryStamp::getRetryCountFromEnvelope($envelope);
+ if ($shouldRetry) {
+ $event->setForRetry();
+
+ ++$retryCount;
+
+ $delay = $retryStrategy->getWaitingTime($envelope, $throwable);
+
+ if (null !== $this->logger) {
+ $this->logger->warning('Error thrown while handling message {class}. Sending for retry #{retryCount} using {delay} ms delay. Error: "{error}"', $context + ['retryCount' => $retryCount, 'delay' => $delay, 'error' => $throwable->getMessage(), 'exception' => $throwable]);
+ }
+
+ // add the delay and retry stamp info
+ $retryEnvelope = $this->withLimitedHistory($envelope, new DelayStamp($delay), new RedeliveryStamp($retryCount));
+
+ // re-send the message for retry
+ $this->getSenderForTransport($event->getReceiverName())->send($retryEnvelope);
+
+ if (null !== $this->eventDispatcher) {
+ $this->eventDispatcher->dispatch(new WorkerMessageRetriedEvent($retryEnvelope, $event->getReceiverName()));
+ }
+ } else {
+ if (null !== $this->logger) {
+ $this->logger->critical('Error thrown while handling message {class}. Removing from transport after {retryCount} retries. Error: "{error}"', $context + ['retryCount' => $retryCount, 'error' => $throwable->getMessage(), 'exception' => $throwable]);
+ }
+ }
+ }
+
+ /**
+ * Adds stamps to the envelope by keeping only the First + Last N stamps.
+ */
+ private function withLimitedHistory(Envelope $envelope, StampInterface ...$stamps): Envelope
+ {
+ foreach ($stamps as $stamp) {
+ $history = $envelope->all(\get_class($stamp));
+ if (\count($history) < $this->historySize) {
+ $envelope = $envelope->with($stamp);
+ continue;
+ }
+
+ $history = array_merge(
+ [$history[0]],
+ \array_slice($history, -$this->historySize + 2),
+ [$stamp]
+ );
+
+ $envelope = $envelope->withoutAll(\get_class($stamp))->with(...$history);
+ }
+
+ return $envelope;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ // must have higher priority than SendFailedMessageToFailureTransportListener
+ WorkerMessageFailedEvent::class => ['onMessageFailed', 100],
+ ];
+ }
+
+ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool
+ {
+ if ($e instanceof RecoverableExceptionInterface) {
+ return true;
+ }
+
+ // if one or more nested Exceptions is an instance of RecoverableExceptionInterface we should retry
+ // if ALL nested Exceptions are an instance of UnrecoverableExceptionInterface we should not retry
+ if ($e instanceof HandlerFailedException) {
+ $shouldNotRetry = true;
+ foreach ($e->getNestedExceptions() as $nestedException) {
+ if ($nestedException instanceof RecoverableExceptionInterface) {
+ return true;
+ }
+
+ if (!$nestedException instanceof UnrecoverableExceptionInterface) {
+ $shouldNotRetry = false;
+ break;
+ }
+ }
+ if ($shouldNotRetry) {
+ return false;
+ }
+ }
+
+ if ($e instanceof UnrecoverableExceptionInterface) {
+ return false;
+ }
+
+ return $retryStrategy->isRetryable($envelope, $e);
+ }
+
+ private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface
+ {
+ if ($this->retryStrategyLocator->has($alias)) {
+ return $this->retryStrategyLocator->get($alias);
+ }
+
+ return null;
+ }
+
+ private function getSenderForTransport(string $alias): SenderInterface
+ {
+ if ($this->sendersLocator->has($alias)) {
+ return $this->sendersLocator->get($alias);
+ }
+
+ throw new RuntimeException(sprintf('Could not find sender "%s" based on the same receiver to send the failed message to for retry.', $alias));
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/SendFailedMessageToFailureTransportListener.php b/plugins/email/vendor/symfony/messenger/EventListener/SendFailedMessageToFailureTransportListener.php
new file mode 100644
index 0000000..644f5a6
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/SendFailedMessageToFailureTransportListener.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Psr\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
+use Symfony\Component\Messenger\Stamp\DelayStamp;
+use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
+use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
+use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
+
+/**
+ * Sends a rejected message to a "failure transport".
+ *
+ * @author Ryan Weaver
+ */
+class SendFailedMessageToFailureTransportListener implements EventSubscriberInterface
+{
+ private $failureSenders;
+ private $logger;
+
+ /**
+ * @param ContainerInterface $failureSenders
+ */
+ public function __construct($failureSenders, LoggerInterface $logger = null)
+ {
+ if (!$failureSenders instanceof ContainerInterface) {
+ trigger_deprecation('symfony/messenger', '5.3', 'Passing a SenderInterface value as 1st argument to "%s()" is deprecated, pass a ServiceLocator instead.', __METHOD__);
+ }
+
+ $this->failureSenders = $failureSenders;
+ $this->logger = $logger;
+ }
+
+ public function onMessageFailed(WorkerMessageFailedEvent $event)
+ {
+ if ($event->willRetry()) {
+ return;
+ }
+
+ if (!$this->hasFailureTransports($event)) {
+ return;
+ }
+
+ $failureSender = $this->getFailureSender($event->getReceiverName());
+ if (null === $failureSender) {
+ return;
+ }
+
+ $envelope = $event->getEnvelope();
+
+ // avoid re-sending to the failed sender
+ if (null !== $envelope->last(SentToFailureTransportStamp::class)) {
+ return;
+ }
+
+ $envelope = $envelope->with(
+ new SentToFailureTransportStamp($event->getReceiverName()),
+ new DelayStamp(0),
+ new RedeliveryStamp(0)
+ );
+
+ if (null !== $this->logger) {
+ $this->logger->info('Rejected message {class} will be sent to the failure transport {transport}.', [
+ 'class' => \get_class($envelope->getMessage()),
+ 'transport' => \get_class($failureSender),
+ ]);
+ }
+
+ $failureSender->send($envelope);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ WorkerMessageFailedEvent::class => ['onMessageFailed', -100],
+ ];
+ }
+
+ private function getFailureSender(string $receiverName): SenderInterface
+ {
+ if ($this->failureSenders instanceof SenderInterface) {
+ return $this->failureSenders;
+ }
+
+ return $this->failureSenders->get($receiverName);
+ }
+
+ private function hasFailureTransports(WorkerMessageFailedEvent $event): bool
+ {
+ return ($this->failureSenders instanceof ContainerInterface && $this->failureSenders->has($event->getReceiverName())) || $this->failureSenders instanceof SenderInterface;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnCustomStopExceptionListener.php b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnCustomStopExceptionListener.php
new file mode 100644
index 0000000..ee8daec
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnCustomStopExceptionListener.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+use Symfony\Component\Messenger\Exception\HandlerFailedException;
+use Symfony\Component\Messenger\Exception\StopWorkerExceptionInterface;
+
+/**
+ * @author Grégoire Pineau
+ */
+class StopWorkerOnCustomStopExceptionListener implements EventSubscriberInterface
+{
+ private $stop = false;
+
+ public function onMessageFailed(WorkerMessageFailedEvent $event): void
+ {
+ $th = $event->getThrowable();
+ if ($th instanceof StopWorkerExceptionInterface) {
+ $this->stop = true;
+ }
+ if ($th instanceof HandlerFailedException) {
+ foreach ($th->getNestedExceptions() as $e) {
+ if ($e instanceof StopWorkerExceptionInterface) {
+ $this->stop = true;
+ break;
+ }
+ }
+ }
+ }
+
+ public function onWorkerRunning(WorkerRunningEvent $event): void
+ {
+ if ($this->stop) {
+ $event->getWorker()->stop();
+ }
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ WorkerMessageFailedEvent::class => 'onMessageFailed',
+ WorkerRunningEvent::class => 'onWorkerRunning',
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnFailureLimitListener.php b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnFailureLimitListener.php
new file mode 100644
index 0000000..29dc6aa
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnFailureLimitListener.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+
+/**
+ * @author Michel Hunziker
+ */
+class StopWorkerOnFailureLimitListener implements EventSubscriberInterface
+{
+ private $maximumNumberOfFailures;
+ private $logger;
+ private $failedMessages = 0;
+
+ public function __construct(int $maximumNumberOfFailures, LoggerInterface $logger = null)
+ {
+ $this->maximumNumberOfFailures = $maximumNumberOfFailures;
+ $this->logger = $logger;
+
+ if ($maximumNumberOfFailures <= 0) {
+ throw new InvalidArgumentException('Failure limit must be greater than zero.');
+ }
+ }
+
+ public function onMessageFailed(WorkerMessageFailedEvent $event): void
+ {
+ ++$this->failedMessages;
+ }
+
+ public function onWorkerRunning(WorkerRunningEvent $event): void
+ {
+ if (!$event->isWorkerIdle() && $this->failedMessages >= $this->maximumNumberOfFailures) {
+ $this->failedMessages = 0;
+ $event->getWorker()->stop();
+
+ if (null !== $this->logger) {
+ $this->logger->info('Worker stopped due to limit of {count} failed message(s) is reached', ['count' => $this->maximumNumberOfFailures]);
+ }
+ }
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ WorkerMessageFailedEvent::class => 'onMessageFailed',
+ WorkerRunningEvent::class => 'onWorkerRunning',
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnMemoryLimitListener.php b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnMemoryLimitListener.php
new file mode 100644
index 0000000..73350fd
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnMemoryLimitListener.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+
+/**
+ * @author Simon Delicata
+ * @author Tobias Schultze
+ */
+class StopWorkerOnMemoryLimitListener implements EventSubscriberInterface
+{
+ private $memoryLimit;
+ private $logger;
+ private $memoryResolver;
+
+ public function __construct(int $memoryLimit, LoggerInterface $logger = null, callable $memoryResolver = null)
+ {
+ $this->memoryLimit = $memoryLimit;
+ $this->logger = $logger;
+ $this->memoryResolver = $memoryResolver ?: static function () {
+ return memory_get_usage(true);
+ };
+ }
+
+ public function onWorkerRunning(WorkerRunningEvent $event): void
+ {
+ $memoryResolver = $this->memoryResolver;
+ $usedMemory = $memoryResolver();
+ if ($usedMemory > $this->memoryLimit) {
+ $event->getWorker()->stop();
+ if (null !== $this->logger) {
+ $this->logger->info('Worker stopped due to memory limit of {limit} bytes exceeded ({memory} bytes used)', ['limit' => $this->memoryLimit, 'memory' => $usedMemory]);
+ }
+ }
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ WorkerRunningEvent::class => 'onWorkerRunning',
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnMessageLimitListener.php b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnMessageLimitListener.php
new file mode 100644
index 0000000..ca71ff1
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnMessageLimitListener.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+
+/**
+ * @author Samuel Roze
+ * @author Tobias Schultze
+ */
+class StopWorkerOnMessageLimitListener implements EventSubscriberInterface
+{
+ private $maximumNumberOfMessages;
+ private $logger;
+ private $receivedMessages = 0;
+
+ public function __construct(int $maximumNumberOfMessages, LoggerInterface $logger = null)
+ {
+ $this->maximumNumberOfMessages = $maximumNumberOfMessages;
+ $this->logger = $logger;
+
+ if ($maximumNumberOfMessages <= 0) {
+ throw new InvalidArgumentException('Message limit must be greater than zero.');
+ }
+ }
+
+ public function onWorkerRunning(WorkerRunningEvent $event): void
+ {
+ if (!$event->isWorkerIdle() && ++$this->receivedMessages >= $this->maximumNumberOfMessages) {
+ $this->receivedMessages = 0;
+ $event->getWorker()->stop();
+
+ if (null !== $this->logger) {
+ $this->logger->info('Worker stopped due to maximum count of {count} messages processed', ['count' => $this->maximumNumberOfMessages]);
+ }
+ }
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ WorkerRunningEvent::class => 'onWorkerRunning',
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnRestartSignalListener.php b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnRestartSignalListener.php
new file mode 100644
index 0000000..0fb3d40
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnRestartSignalListener.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+use Symfony\Component\Messenger\Event\WorkerStartedEvent;
+
+/**
+ * @author Ryan Weaver
+ */
+class StopWorkerOnRestartSignalListener implements EventSubscriberInterface
+{
+ public const RESTART_REQUESTED_TIMESTAMP_KEY = 'workers.restart_requested_timestamp';
+
+ private $cachePool;
+ private $logger;
+ private $workerStartedAt;
+
+ public function __construct(CacheItemPoolInterface $cachePool, LoggerInterface $logger = null)
+ {
+ $this->cachePool = $cachePool;
+ $this->logger = $logger;
+ }
+
+ public function onWorkerStarted(): void
+ {
+ $this->workerStartedAt = microtime(true);
+ }
+
+ public function onWorkerRunning(WorkerRunningEvent $event): void
+ {
+ if ($this->shouldRestart()) {
+ $event->getWorker()->stop();
+ if (null !== $this->logger) {
+ $this->logger->info('Worker stopped because a restart was requested.');
+ }
+ }
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ WorkerStartedEvent::class => 'onWorkerStarted',
+ WorkerRunningEvent::class => 'onWorkerRunning',
+ ];
+ }
+
+ private function shouldRestart(): bool
+ {
+ $cacheItem = $this->cachePool->getItem(self::RESTART_REQUESTED_TIMESTAMP_KEY);
+
+ if (!$cacheItem->isHit()) {
+ // no restart has ever been scheduled
+ return false;
+ }
+
+ return $this->workerStartedAt < $cacheItem->get();
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnSigtermSignalListener.php b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnSigtermSignalListener.php
new file mode 100644
index 0000000..c865546
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnSigtermSignalListener.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerStartedEvent;
+
+/**
+ * @author Tobias Schultze
+ */
+class StopWorkerOnSigtermSignalListener implements EventSubscriberInterface
+{
+ private $logger;
+
+ public function __construct(LoggerInterface $logger = null)
+ {
+ $this->logger = $logger;
+ }
+
+ public function onWorkerStarted(WorkerStartedEvent $event): void
+ {
+ pcntl_signal(\SIGTERM, function () use ($event) {
+ if (null !== $this->logger) {
+ $this->logger->info('Received SIGTERM signal.', ['transport_names' => $event->getWorker()->getMetadata()->getTransportNames()]);
+ }
+
+ $event->getWorker()->stop();
+ });
+ }
+
+ public static function getSubscribedEvents()
+ {
+ if (!\function_exists('pcntl_signal')) {
+ return [];
+ }
+
+ return [
+ WorkerStartedEvent::class => ['onWorkerStarted', 100],
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnTimeLimitListener.php b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnTimeLimitListener.php
new file mode 100644
index 0000000..a3f982d
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/EventListener/StopWorkerOnTimeLimitListener.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\EventListener;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+use Symfony\Component\Messenger\Event\WorkerStartedEvent;
+
+/**
+ * @author Simon Delicata
+ * @author Tobias Schultze
+ */
+class StopWorkerOnTimeLimitListener implements EventSubscriberInterface
+{
+ private $timeLimitInSeconds;
+ private $logger;
+ private $endTime;
+
+ public function __construct(int $timeLimitInSeconds, LoggerInterface $logger = null)
+ {
+ $this->timeLimitInSeconds = $timeLimitInSeconds;
+ $this->logger = $logger;
+ }
+
+ public function onWorkerStarted(): void
+ {
+ $startTime = microtime(true);
+ $this->endTime = $startTime + $this->timeLimitInSeconds;
+ }
+
+ public function onWorkerRunning(WorkerRunningEvent $event): void
+ {
+ if ($this->endTime < microtime(true)) {
+ $event->getWorker()->stop();
+ if (null !== $this->logger) {
+ $this->logger->info('Worker stopped due to time limit of {timeLimit}s exceeded', ['timeLimit' => $this->timeLimitInSeconds]);
+ }
+ }
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ WorkerStartedEvent::class => 'onWorkerStarted',
+ WorkerRunningEvent::class => 'onWorkerRunning',
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/DelayedMessageHandlingException.php b/plugins/email/vendor/symfony/messenger/Exception/DelayedMessageHandlingException.php
new file mode 100644
index 0000000..4314a4f
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/DelayedMessageHandlingException.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * When handling queued messages from {@link DispatchAfterCurrentBusMiddleware},
+ * some handlers caused an exception. This exception contains all those handler exceptions.
+ *
+ * @author Tobias Nyholm
+ */
+class DelayedMessageHandlingException extends RuntimeException
+{
+ private $exceptions;
+
+ public function __construct(array $exceptions)
+ {
+ $exceptionMessages = implode(", \n", array_map(
+ function (\Throwable $e) {
+ return \get_class($e).': '.$e->getMessage();
+ },
+ $exceptions
+ ));
+
+ if (1 === \count($exceptions)) {
+ $message = sprintf("A delayed message handler threw an exception: \n\n%s", $exceptionMessages);
+ } else {
+ $message = sprintf("Some delayed message handlers threw an exception: \n\n%s", $exceptionMessages);
+ }
+
+ $this->exceptions = $exceptions;
+
+ parent::__construct($message, 0, $exceptions[0]);
+ }
+
+ public function getExceptions(): array
+ {
+ return $this->exceptions;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/ExceptionInterface.php b/plugins/email/vendor/symfony/messenger/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..02a72de
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * Base Messenger component's exception.
+ *
+ * @author Samuel Roze
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/HandlerFailedException.php b/plugins/email/vendor/symfony/messenger/Exception/HandlerFailedException.php
new file mode 100644
index 0000000..6ac3169
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/HandlerFailedException.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+use Symfony\Component\Messenger\Envelope;
+
+class HandlerFailedException extends RuntimeException
+{
+ private $exceptions;
+ private $envelope;
+
+ /**
+ * @param \Throwable[] $exceptions
+ */
+ public function __construct(Envelope $envelope, array $exceptions)
+ {
+ $firstFailure = current($exceptions);
+
+ $message = sprintf('Handling "%s" failed: ', \get_class($envelope->getMessage()));
+
+ parent::__construct(
+ $message.(1 === \count($exceptions)
+ ? $firstFailure->getMessage()
+ : sprintf('%d handlers failed. First failure is: %s', \count($exceptions), $firstFailure->getMessage())
+ ),
+ (int) $firstFailure->getCode(),
+ $firstFailure
+ );
+
+ $this->envelope = $envelope;
+ $this->exceptions = $exceptions;
+ }
+
+ public function getEnvelope(): Envelope
+ {
+ return $this->envelope;
+ }
+
+ /**
+ * @return \Throwable[]
+ */
+ public function getNestedExceptions(): array
+ {
+ return $this->exceptions;
+ }
+
+ public function getNestedExceptionOfClass(string $exceptionClassName): array
+ {
+ return array_values(
+ array_filter(
+ $this->exceptions,
+ function ($exception) use ($exceptionClassName) {
+ return is_a($exception, $exceptionClassName);
+ }
+ )
+ );
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/InvalidArgumentException.php b/plugins/email/vendor/symfony/messenger/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..a75c722
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/InvalidArgumentException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * @author Yonel Ceruto
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/LogicException.php b/plugins/email/vendor/symfony/messenger/Exception/LogicException.php
new file mode 100644
index 0000000..75f97d2
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/LogicException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * @author Roland Franssen
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/MessageDecodingFailedException.php b/plugins/email/vendor/symfony/messenger/Exception/MessageDecodingFailedException.php
new file mode 100644
index 0000000..bea4ac5
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/MessageDecodingFailedException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * Thrown when a message cannot be decoded in a serializer.
+ */
+class MessageDecodingFailedException extends InvalidArgumentException
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/NoHandlerForMessageException.php b/plugins/email/vendor/symfony/messenger/Exception/NoHandlerForMessageException.php
new file mode 100644
index 0000000..d5efb45
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/NoHandlerForMessageException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * @author Samuel Roze
+ */
+class NoHandlerForMessageException extends LogicException
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/RecoverableExceptionInterface.php b/plugins/email/vendor/symfony/messenger/Exception/RecoverableExceptionInterface.php
new file mode 100644
index 0000000..b49ddbf
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/RecoverableExceptionInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * Marker interface for exceptions to indicate that handling a message should have worked.
+ *
+ * If something goes wrong while handling a message that's received from a transport
+ * and the message should be retried, a handler can throw such an exception.
+ *
+ * @author Jérémy Derussé
+ */
+interface RecoverableExceptionInterface extends \Throwable
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/RecoverableMessageHandlingException.php b/plugins/email/vendor/symfony/messenger/Exception/RecoverableMessageHandlingException.php
new file mode 100644
index 0000000..6514573
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/RecoverableMessageHandlingException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * A concrete implementation of RecoverableExceptionInterface that can be used directly.
+ *
+ * @author Frederic Bouchery
+ */
+class RecoverableMessageHandlingException extends RuntimeException implements RecoverableExceptionInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/RejectRedeliveredMessageException.php b/plugins/email/vendor/symfony/messenger/Exception/RejectRedeliveredMessageException.php
new file mode 100644
index 0000000..0befccf
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/RejectRedeliveredMessageException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * @author Tobias Schultze
+ */
+class RejectRedeliveredMessageException extends RuntimeException
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/RuntimeException.php b/plugins/email/vendor/symfony/messenger/Exception/RuntimeException.php
new file mode 100644
index 0000000..2d6c7b3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/RuntimeException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * @author Fabien Potencier
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/StopWorkerException.php b/plugins/email/vendor/symfony/messenger/Exception/StopWorkerException.php
new file mode 100644
index 0000000..e53bd32
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/StopWorkerException.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * @author Grégoire Pineau
+ */
+class StopWorkerException extends RuntimeException implements StopWorkerExceptionInterface
+{
+ public function __construct(string $message = 'Worker should stop.', \Throwable $previous = null)
+ {
+ parent::__construct($message, 0, $previous);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/StopWorkerExceptionInterface.php b/plugins/email/vendor/symfony/messenger/Exception/StopWorkerExceptionInterface.php
new file mode 100644
index 0000000..16019e8
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/StopWorkerExceptionInterface.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * @author Grégoire Pineau
+ */
+interface StopWorkerExceptionInterface extends \Throwable
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/TransportException.php b/plugins/email/vendor/symfony/messenger/Exception/TransportException.php
new file mode 100644
index 0000000..79952b8
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/TransportException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * @author Eric Masoero
+ */
+class TransportException extends RuntimeException
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/UnrecoverableExceptionInterface.php b/plugins/email/vendor/symfony/messenger/Exception/UnrecoverableExceptionInterface.php
new file mode 100644
index 0000000..ba5addf
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/UnrecoverableExceptionInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * Marker interface for exceptions to indicate that handling a message will continue to fail.
+ *
+ * If something goes wrong while handling a message that's received from a transport
+ * and the message should not be retried, a handler can throw such an exception.
+ *
+ * @author Tobias Schultze
+ */
+interface UnrecoverableExceptionInterface extends \Throwable
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/UnrecoverableMessageHandlingException.php b/plugins/email/vendor/symfony/messenger/Exception/UnrecoverableMessageHandlingException.php
new file mode 100644
index 0000000..31616fb
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/UnrecoverableMessageHandlingException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+/**
+ * A concrete implementation of UnrecoverableExceptionInterface that can be used directly.
+ *
+ * @author Frederic Bouchery
+ */
+class UnrecoverableMessageHandlingException extends RuntimeException implements UnrecoverableExceptionInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Exception/ValidationFailedException.php b/plugins/email/vendor/symfony/messenger/Exception/ValidationFailedException.php
new file mode 100644
index 0000000..fe129cd
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Exception/ValidationFailedException.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Exception;
+
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+
+/**
+ * @author Tobias Nyholm
+ */
+class ValidationFailedException extends RuntimeException
+{
+ private $violations;
+ private $violatingMessage;
+
+ public function __construct(object $violatingMessage, ConstraintViolationListInterface $violations)
+ {
+ $this->violatingMessage = $violatingMessage;
+ $this->violations = $violations;
+
+ parent::__construct(sprintf('Message of type "%s" failed validation.', \get_class($this->violatingMessage)));
+ }
+
+ public function getViolatingMessage()
+ {
+ return $this->violatingMessage;
+ }
+
+ public function getViolations(): ConstraintViolationListInterface
+ {
+ return $this->violations;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/HandleTrait.php b/plugins/email/vendor/symfony/messenger/HandleTrait.php
new file mode 100644
index 0000000..4f77a93
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/HandleTrait.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger;
+
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Stamp\HandledStamp;
+
+/**
+ * Leverages a message bus to expect a single, synchronous message handling and return its result.
+ *
+ * @author Maxime Steinhausser
+ */
+trait HandleTrait
+{
+ /** @var MessageBusInterface */
+ private $messageBus;
+
+ /**
+ * Dispatches the given message, expecting to be handled by a single handler
+ * and returns the result from the handler returned value.
+ * This behavior is useful for both synchronous command & query buses,
+ * the last one usually returning the handler result.
+ *
+ * @param object|Envelope $message The message or the message pre-wrapped in an envelope
+ *
+ * @return mixed
+ */
+ private function handle(object $message)
+ {
+ if (!$this->messageBus instanceof MessageBusInterface) {
+ throw new LogicException(sprintf('You must provide a "%s" instance in the "%s::$messageBus" property, "%s" given.', MessageBusInterface::class, static::class, get_debug_type($this->messageBus)));
+ }
+
+ $envelope = $this->messageBus->dispatch($message);
+ /** @var HandledStamp[] $handledStamps */
+ $handledStamps = $envelope->all(HandledStamp::class);
+
+ if (!$handledStamps) {
+ throw new LogicException(sprintf('Message of type "%s" was handled zero times. Exactly one handler is expected when using "%s::%s()".', get_debug_type($envelope->getMessage()), static::class, __FUNCTION__));
+ }
+
+ if (\count($handledStamps) > 1) {
+ $handlers = implode(', ', array_map(function (HandledStamp $stamp): string {
+ return sprintf('"%s"', $stamp->getHandlerName());
+ }, $handledStamps));
+
+ throw new LogicException(sprintf('Message of type "%s" was handled multiple times. Only one handler is expected when using "%s::%s()", got %d: %s.', get_debug_type($envelope->getMessage()), static::class, __FUNCTION__, \count($handledStamps), $handlers));
+ }
+
+ return $handledStamps[0]->getResult();
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Handler/Acknowledger.php b/plugins/email/vendor/symfony/messenger/Handler/Acknowledger.php
new file mode 100644
index 0000000..a2317b7
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Handler/Acknowledger.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Handler;
+
+use Symfony\Component\Messenger\Exception\LogicException;
+
+/**
+ * @author Nicolas Grekas
+ */
+class Acknowledger
+{
+ private $handlerClass;
+ private $ack;
+ private $error = null;
+ private $result = null;
+
+ /**
+ * @param null|\Closure(\Throwable|null, mixed):void $ack
+ */
+ public function __construct(string $handlerClass, \Closure $ack = null)
+ {
+ $this->handlerClass = $handlerClass;
+ $this->ack = $ack ?? static function () {};
+ }
+
+ /**
+ * @param mixed $result
+ */
+ public function ack($result = null): void
+ {
+ $this->doAck(null, $result);
+ }
+
+ public function nack(\Throwable $error): void
+ {
+ $this->doAck($error);
+ }
+
+ public function getError(): ?\Throwable
+ {
+ return $this->error;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ public function isAcknowledged(): bool
+ {
+ return null === $this->ack;
+ }
+
+ public function __destruct()
+ {
+ if ($this->ack instanceof \Closure) {
+ throw new LogicException(sprintf('The acknowledger was not called by the "%s" batch handler.', $this->handlerClass));
+ }
+ }
+
+ private function doAck(\Throwable $e = null, $result = null): void
+ {
+ if (!$ack = $this->ack) {
+ throw new LogicException(sprintf('The acknowledger cannot be called twice by the "%s" batch handler.', $this->handlerClass));
+ }
+ $this->ack = null;
+ $this->error = $e;
+ $this->result = $result;
+ $ack($e, $result);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Handler/BatchHandlerInterface.php b/plugins/email/vendor/symfony/messenger/Handler/BatchHandlerInterface.php
new file mode 100644
index 0000000..a2fce4e
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Handler/BatchHandlerInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Handler;
+
+/**
+ * @author Nicolas Grekas
+ */
+interface BatchHandlerInterface
+{
+ /**
+ * @param Acknowledger|null $ack The function to call to ack/nack the $message.
+ * The message should be handled synchronously when null.
+ *
+ * @return mixed The number of pending messages in the batch if $ack is not null,
+ * the result from handling the message otherwise
+ */
+ // public function __invoke(object $message, Acknowledger $ack = null): mixed;
+
+ /**
+ * Flushes any pending buffers.
+ *
+ * @param bool $force Whether flushing is required; it can be skipped if not
+ */
+ public function flush(bool $force): void;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Handler/BatchHandlerTrait.php b/plugins/email/vendor/symfony/messenger/Handler/BatchHandlerTrait.php
new file mode 100644
index 0000000..be7124d
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Handler/BatchHandlerTrait.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Handler;
+
+use Symfony\Component\Messenger\Exception\LogicException;
+
+/**
+ * @author Nicolas Grekas
+ */
+trait BatchHandlerTrait
+{
+ private $jobs = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function flush(bool $force): void
+ {
+ if ($jobs = $this->jobs) {
+ $this->jobs = [];
+ $this->process($jobs);
+ }
+ }
+
+ /**
+ * @param Acknowledger|null $ack The function to call to ack/nack the $message.
+ * The message should be handled synchronously when null.
+ *
+ * @return mixed The number of pending messages in the batch if $ack is not null,
+ * the result from handling the message otherwise
+ */
+ private function handle(object $message, ?Acknowledger $ack)
+ {
+ if (null === $ack) {
+ $ack = new Acknowledger(get_debug_type($this));
+ $this->jobs[] = [$message, $ack];
+ $this->flush(true);
+
+ return $ack->getResult();
+ }
+
+ $this->jobs[] = [$message, $ack];
+ if (!$this->shouldFlush()) {
+ return \count($this->jobs);
+ }
+
+ $this->flush(true);
+
+ return 0;
+ }
+
+ private function shouldFlush(): bool
+ {
+ return 10 <= \count($this->jobs);
+ }
+
+ /**
+ * Completes the jobs in the list.
+ *
+ * @list $jobs A list of pairs of messages and their corresponding acknowledgers
+ */
+ private function process(array $jobs): void
+ {
+ throw new LogicException(sprintf('"%s" should implement abstract method "process()".', get_debug_type($this)));
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Handler/HandlerDescriptor.php b/plugins/email/vendor/symfony/messenger/Handler/HandlerDescriptor.php
new file mode 100644
index 0000000..6acb2c2
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Handler/HandlerDescriptor.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Handler;
+
+/**
+ * Describes a handler and the possible associated options, such as `from_transport`, `bus`, etc.
+ *
+ * @author Samuel Roze
+ */
+final class HandlerDescriptor
+{
+ private $handler;
+ private $name;
+ private $batchHandler;
+ private $options;
+
+ public function __construct(callable $handler, array $options = [])
+ {
+ if (!$handler instanceof \Closure) {
+ $handler = \Closure::fromCallable($handler);
+ }
+
+ $this->handler = $handler;
+ $this->options = $options;
+
+ $r = new \ReflectionFunction($handler);
+
+ if (str_contains($r->name, '{closure}')) {
+ $this->name = 'Closure';
+ } elseif (!$handler = $r->getClosureThis()) {
+ $class = $r->getClosureScopeClass();
+
+ $this->name = ($class ? $class->name.'::' : '').$r->name;
+ } else {
+ if ($handler instanceof BatchHandlerInterface) {
+ $this->batchHandler = $handler;
+ }
+
+ $this->name = \get_class($handler).'::'.$r->name;
+ }
+ }
+
+ public function getHandler(): callable
+ {
+ return $this->handler;
+ }
+
+ public function getName(): string
+ {
+ $name = $this->name;
+ $alias = $this->options['alias'] ?? null;
+
+ if (null !== $alias) {
+ $name .= '@'.$alias;
+ }
+
+ return $name;
+ }
+
+ public function getBatchHandler(): ?BatchHandlerInterface
+ {
+ return $this->batchHandler;
+ }
+
+ public function getOption(string $option)
+ {
+ return $this->options[$option] ?? null;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Handler/HandlersLocator.php b/plugins/email/vendor/symfony/messenger/Handler/HandlersLocator.php
new file mode 100644
index 0000000..6252bcc
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Handler/HandlersLocator.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Handler;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Stamp\ReceivedStamp;
+
+/**
+ * Maps a message to a list of handlers.
+ *
+ * @author Nicolas Grekas
+ * @author Samuel Roze
+ */
+class HandlersLocator implements HandlersLocatorInterface
+{
+ private $handlers;
+
+ /**
+ * @param HandlerDescriptor[][]|callable[][] $handlers
+ */
+ public function __construct(array $handlers)
+ {
+ $this->handlers = $handlers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHandlers(Envelope $envelope): iterable
+ {
+ $seen = [];
+
+ foreach (self::listTypes($envelope) as $type) {
+ foreach ($this->handlers[$type] ?? [] as $handlerDescriptor) {
+ if (\is_callable($handlerDescriptor)) {
+ $handlerDescriptor = new HandlerDescriptor($handlerDescriptor);
+ }
+
+ if (!$this->shouldHandle($envelope, $handlerDescriptor)) {
+ continue;
+ }
+
+ $name = $handlerDescriptor->getName();
+ if (\in_array($name, $seen)) {
+ continue;
+ }
+
+ $seen[] = $name;
+
+ yield $handlerDescriptor;
+ }
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public static function listTypes(Envelope $envelope): array
+ {
+ $class = \get_class($envelope->getMessage());
+
+ return [$class => $class]
+ + class_parents($class)
+ + class_implements($class)
+ + ['*' => '*'];
+ }
+
+ private function shouldHandle(Envelope $envelope, HandlerDescriptor $handlerDescriptor): bool
+ {
+ if (null === $received = $envelope->last(ReceivedStamp::class)) {
+ return true;
+ }
+
+ if (null === $expectedTransport = $handlerDescriptor->getOption('from_transport')) {
+ return true;
+ }
+
+ return $received->getTransportName() === $expectedTransport;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Handler/HandlersLocatorInterface.php b/plugins/email/vendor/symfony/messenger/Handler/HandlersLocatorInterface.php
new file mode 100644
index 0000000..c0ac07f
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Handler/HandlersLocatorInterface.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Handler;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Maps a message to a list of handlers.
+ *
+ * @author Nicolas Grekas
+ */
+interface HandlersLocatorInterface
+{
+ /**
+ * Returns the handlers for the given message name.
+ *
+ * @return iterable
+ */
+ public function getHandlers(Envelope $envelope): iterable;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Handler/MessageHandlerInterface.php b/plugins/email/vendor/symfony/messenger/Handler/MessageHandlerInterface.php
new file mode 100644
index 0000000..7b219a3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Handler/MessageHandlerInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Handler;
+
+/**
+ * Marker interface for message handlers.
+ *
+ * @author Samuel Roze
+ */
+interface MessageHandlerInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Handler/MessageSubscriberInterface.php b/plugins/email/vendor/symfony/messenger/Handler/MessageSubscriberInterface.php
new file mode 100644
index 0000000..2eca1a9
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Handler/MessageSubscriberInterface.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Handler;
+
+/**
+ * Handlers can implement this interface to handle multiple messages.
+ *
+ * @author Samuel Roze
+ */
+interface MessageSubscriberInterface extends MessageHandlerInterface
+{
+ /**
+ * Returns a list of messages to be handled.
+ *
+ * It returns a list of messages like in the following example:
+ *
+ * yield MyMessage::class;
+ *
+ * It can also change the priority per classes.
+ *
+ * yield FirstMessage::class => ['priority' => 0];
+ * yield SecondMessage::class => ['priority' => -10];
+ *
+ * It can also specify a method, a priority, a bus and/or a transport per message:
+ *
+ * yield FirstMessage::class => ['method' => 'firstMessageMethod'];
+ * yield SecondMessage::class => [
+ * 'method' => 'secondMessageMethod',
+ * 'priority' => 20,
+ * 'bus' => 'my_bus_name',
+ * 'from_transport' => 'your_transport_name',
+ * ];
+ *
+ * The benefit of using `yield` instead of returning an array is that you can `yield` multiple times the
+ * same key and therefore subscribe to the same message multiple times with different options.
+ *
+ * The `__invoke` method of the handler will be called as usual with the message to handle.
+ */
+ public static function getHandledMessages(): iterable;
+}
diff --git a/plugins/email/vendor/symfony/messenger/LICENSE b/plugins/email/vendor/symfony/messenger/LICENSE
new file mode 100644
index 0000000..74cdc2d
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2022 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugins/email/vendor/symfony/messenger/MessageBus.php b/plugins/email/vendor/symfony/messenger/MessageBus.php
new file mode 100644
index 0000000..3db9fab
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/MessageBus.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger;
+
+use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
+use Symfony\Component\Messenger\Middleware\StackMiddleware;
+
+/**
+ * @author Samuel Roze
+ * @author Matthias Noback
+ * @author Nicolas Grekas
+ */
+class MessageBus implements MessageBusInterface
+{
+ private $middlewareAggregate;
+
+ /**
+ * @param iterable $middlewareHandlers
+ */
+ public function __construct(iterable $middlewareHandlers = [])
+ {
+ if ($middlewareHandlers instanceof \IteratorAggregate) {
+ $this->middlewareAggregate = $middlewareHandlers;
+ } elseif (\is_array($middlewareHandlers)) {
+ $this->middlewareAggregate = new \ArrayObject($middlewareHandlers);
+ } else {
+ // $this->middlewareAggregate should be an instance of IteratorAggregate.
+ // When $middlewareHandlers is an Iterator, we wrap it to ensure it is lazy-loaded and can be rewound.
+ $this->middlewareAggregate = new class($middlewareHandlers) implements \IteratorAggregate {
+ private $middlewareHandlers;
+ private $cachedIterator;
+
+ public function __construct(\Traversable $middlewareHandlers)
+ {
+ $this->middlewareHandlers = $middlewareHandlers;
+ }
+
+ public function getIterator(): \Traversable
+ {
+ if (null === $this->cachedIterator) {
+ $this->cachedIterator = new \ArrayObject(iterator_to_array($this->middlewareHandlers, false));
+ }
+
+ return $this->cachedIterator;
+ }
+ };
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch(object $message, array $stamps = []): Envelope
+ {
+ $envelope = Envelope::wrap($message, $stamps);
+ $middlewareIterator = $this->middlewareAggregate->getIterator();
+
+ while ($middlewareIterator instanceof \IteratorAggregate) {
+ $middlewareIterator = $middlewareIterator->getIterator();
+ }
+ $middlewareIterator->rewind();
+
+ if (!$middlewareIterator->valid()) {
+ return $envelope;
+ }
+ $stack = new StackMiddleware($middlewareIterator);
+
+ return $middlewareIterator->current()->handle($envelope, $stack);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/MessageBusInterface.php b/plugins/email/vendor/symfony/messenger/MessageBusInterface.php
new file mode 100644
index 0000000..f1dbe0a
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/MessageBusInterface.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger;
+
+use Symfony\Component\Messenger\Stamp\StampInterface;
+
+/**
+ * @author Samuel Roze
+ */
+interface MessageBusInterface
+{
+ /**
+ * Dispatches the given message.
+ *
+ * @param object|Envelope $message The message or the message pre-wrapped in an envelope
+ * @param StampInterface[] $stamps
+ */
+ public function dispatch(object $message, array $stamps = []): Envelope;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/ActivationMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/ActivationMiddleware.php
new file mode 100644
index 0000000..8d101e4
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/ActivationMiddleware.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Execute the inner middleware according to an activation strategy.
+ *
+ * @author Maxime Steinhausser
+ */
+class ActivationMiddleware implements MiddlewareInterface
+{
+ private $inner;
+ private $activated;
+
+ /**
+ * @param bool|callable $activated
+ */
+ public function __construct(MiddlewareInterface $inner, $activated)
+ {
+ $this->inner = $inner;
+ $this->activated = $activated;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ if (\is_callable($this->activated) ? ($this->activated)($envelope) : $this->activated) {
+ return $this->inner->handle($envelope, $stack);
+ }
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php
new file mode 100644
index 0000000..925fd5f
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Stamp\BusNameStamp;
+
+/**
+ * Adds the BusNameStamp to the bus.
+ *
+ * @author Ryan Weaver
+ */
+class AddBusNameStampMiddleware implements MiddlewareInterface
+{
+ private $busName;
+
+ public function __construct(string $busName)
+ {
+ $this->busName = $busName;
+ }
+
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ if (null === $envelope->last(BusNameStamp::class)) {
+ $envelope = $envelope->with(new BusNameStamp($this->busName));
+ }
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php
new file mode 100644
index 0000000..a088140
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php
@@ -0,0 +1,133 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
+use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
+
+/**
+ * Allow to configure messages to be handled after the current bus is finished.
+ *
+ * I.e, messages dispatched from a handler with a DispatchAfterCurrentBus stamp
+ * will actually be handled once the current message being dispatched is fully
+ * handled.
+ *
+ * For instance, using this middleware before the DoctrineTransactionMiddleware
+ * means sub-dispatched messages with a DispatchAfterCurrentBus stamp would be
+ * handled after the Doctrine transaction has been committed.
+ *
+ * @author Tobias Nyholm
+ */
+class DispatchAfterCurrentBusMiddleware implements MiddlewareInterface
+{
+ /**
+ * @var QueuedEnvelope[] A queue of messages and next middleware
+ */
+ private $queue = [];
+
+ /**
+ * @var bool this property is used to signal if we are inside a the first/root call to
+ * MessageBusInterface::dispatch() or if dispatch has been called inside a message handler
+ */
+ private $isRootDispatchCallRunning = false;
+
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ if (null !== $envelope->last(DispatchAfterCurrentBusStamp::class)) {
+ if ($this->isRootDispatchCallRunning) {
+ $this->queue[] = new QueuedEnvelope($envelope, $stack);
+
+ return $envelope;
+ }
+
+ $envelope = $envelope->withoutAll(DispatchAfterCurrentBusStamp::class);
+ }
+
+ if ($this->isRootDispatchCallRunning) {
+ /*
+ * A call to MessageBusInterface::dispatch() was made from inside the main bus handling,
+ * but the message does not have the stamp. So, process it like normal.
+ */
+ return $stack->next()->handle($envelope, $stack);
+ }
+
+ // First time we get here, mark as inside a "root dispatch" call:
+ $this->isRootDispatchCallRunning = true;
+ try {
+ // Execute the whole middleware stack & message handling for main dispatch:
+ $returnedEnvelope = $stack->next()->handle($envelope, $stack);
+ } catch (\Throwable $exception) {
+ /*
+ * Whenever an exception occurs while handling a message that has
+ * queued other messages, we drop the queued ones.
+ * This is intentional since the queued commands were likely dependent
+ * on the preceding command.
+ */
+ $this->queue = [];
+ $this->isRootDispatchCallRunning = false;
+
+ throw $exception;
+ }
+
+ // "Root dispatch" call is finished, dispatch stored messages.
+ $exceptions = [];
+ while (null !== $queueItem = array_shift($this->queue)) {
+ // Save how many messages are left in queue before handling the message
+ $queueLengthBefore = \count($this->queue);
+ try {
+ // Execute the stored messages
+ $queueItem->getStack()->next()->handle($queueItem->getEnvelope(), $queueItem->getStack());
+ } catch (\Exception $exception) {
+ // Gather all exceptions
+ $exceptions[] = $exception;
+ // Restore queue to previous state
+ $this->queue = \array_slice($this->queue, 0, $queueLengthBefore);
+ }
+ }
+
+ $this->isRootDispatchCallRunning = false;
+ if (\count($exceptions) > 0) {
+ throw new DelayedMessageHandlingException($exceptions);
+ }
+
+ return $returnedEnvelope;
+ }
+}
+
+/**
+ * @internal
+ */
+final class QueuedEnvelope
+{
+ /** @var Envelope */
+ private $envelope;
+
+ /** @var StackInterface */
+ private $stack;
+
+ public function __construct(Envelope $envelope, StackInterface $stack)
+ {
+ $this->envelope = $envelope->withoutAll(DispatchAfterCurrentBusStamp::class);
+ $this->stack = $stack;
+ }
+
+ public function getEnvelope(): Envelope
+ {
+ return $this->envelope;
+ }
+
+ public function getStack(): StackInterface
+ {
+ return $this->stack;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php
new file mode 100644
index 0000000..6e40d11
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Stamp\ReceivedStamp;
+use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
+
+/**
+ * @author Ryan Weaver
+ */
+class FailedMessageProcessingMiddleware implements MiddlewareInterface
+{
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ // look for "received" messages decorated with the SentToFailureTransportStamp
+ /** @var SentToFailureTransportStamp|null $sentToFailureStamp */
+ $sentToFailureStamp = $envelope->last(SentToFailureTransportStamp::class);
+ if (null !== $sentToFailureStamp && null !== $envelope->last(ReceivedStamp::class)) {
+ // mark the message as "received" from the original transport
+ // this guarantees the same behavior as when originally received
+ $envelope = $envelope->with(new ReceivedStamp($sentToFailureStamp->getOriginalReceiverName()));
+ }
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php
new file mode 100644
index 0000000..149f304
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php
@@ -0,0 +1,145 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\NullLogger;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\HandlerFailedException;
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Exception\NoHandlerForMessageException;
+use Symfony\Component\Messenger\Handler\Acknowledger;
+use Symfony\Component\Messenger\Handler\HandlerDescriptor;
+use Symfony\Component\Messenger\Handler\HandlersLocatorInterface;
+use Symfony\Component\Messenger\Stamp\AckStamp;
+use Symfony\Component\Messenger\Stamp\FlushBatchHandlersStamp;
+use Symfony\Component\Messenger\Stamp\HandledStamp;
+use Symfony\Component\Messenger\Stamp\NoAutoAckStamp;
+
+/**
+ * @author Samuel Roze
+ */
+class HandleMessageMiddleware implements MiddlewareInterface
+{
+ use LoggerAwareTrait;
+
+ private $handlersLocator;
+ private $allowNoHandlers;
+
+ public function __construct(HandlersLocatorInterface $handlersLocator, bool $allowNoHandlers = false)
+ {
+ $this->handlersLocator = $handlersLocator;
+ $this->allowNoHandlers = $allowNoHandlers;
+ $this->logger = new NullLogger();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws NoHandlerForMessageException When no handler is found and $allowNoHandlers is false
+ */
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ $handler = null;
+ $message = $envelope->getMessage();
+
+ $context = [
+ 'class' => \get_class($message),
+ ];
+
+ $exceptions = [];
+ foreach ($this->handlersLocator->getHandlers($envelope) as $handlerDescriptor) {
+ if ($this->messageHasAlreadyBeenHandled($envelope, $handlerDescriptor)) {
+ continue;
+ }
+
+ try {
+ $handler = $handlerDescriptor->getHandler();
+ $batchHandler = $handlerDescriptor->getBatchHandler();
+
+ /** @var AckStamp $ackStamp */
+ if ($batchHandler && $ackStamp = $envelope->last(AckStamp::class)) {
+ $ack = new Acknowledger(get_debug_type($batchHandler), static function (\Throwable $e = null, $result = null) use ($envelope, $ackStamp, $handlerDescriptor) {
+ if (null !== $e) {
+ $e = new HandlerFailedException($envelope, [$e]);
+ } else {
+ $envelope = $envelope->with(HandledStamp::fromDescriptor($handlerDescriptor, $result));
+ }
+
+ $ackStamp->ack($envelope, $e);
+ });
+
+ $result = $handler($message, $ack);
+
+ if (!\is_int($result) || 0 > $result) {
+ throw new LogicException(sprintf('A handler implementing BatchHandlerInterface must return the size of the current batch as a positive integer, "%s" returned from "%s".', \is_int($result) ? $result : get_debug_type($result), get_debug_type($batchHandler)));
+ }
+
+ if (!$ack->isAcknowledged()) {
+ $envelope = $envelope->with(new NoAutoAckStamp($handlerDescriptor));
+ } elseif ($ack->getError()) {
+ throw $ack->getError();
+ } else {
+ $result = $ack->getResult();
+ }
+ } else {
+ $result = $handler($message);
+ }
+
+ $handledStamp = HandledStamp::fromDescriptor($handlerDescriptor, $result);
+ $envelope = $envelope->with($handledStamp);
+ $this->logger->info('Message {class} handled by {handler}', $context + ['handler' => $handledStamp->getHandlerName()]);
+ } catch (\Throwable $e) {
+ $exceptions[] = $e;
+ }
+ }
+
+ /** @var FlushBatchHandlersStamp $flushStamp */
+ if ($flushStamp = $envelope->last(FlushBatchHandlersStamp::class)) {
+ /** @var NoAutoAckStamp $stamp */
+ foreach ($envelope->all(NoAutoAckStamp::class) as $stamp) {
+ try {
+ $handler = $stamp->getHandlerDescriptor()->getBatchHandler();
+ $handler->flush($flushStamp->force());
+ } catch (\Throwable $e) {
+ $exceptions[] = $e;
+ }
+ }
+ }
+
+ if (null === $handler) {
+ if (!$this->allowNoHandlers) {
+ throw new NoHandlerForMessageException(sprintf('No handler for message "%s".', $context['class']));
+ }
+
+ $this->logger->info('No handler for message {class}', $context);
+ }
+
+ if (\count($exceptions)) {
+ throw new HandlerFailedException($envelope, $exceptions);
+ }
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+
+ private function messageHasAlreadyBeenHandled(Envelope $envelope, HandlerDescriptor $handlerDescriptor): bool
+ {
+ /** @var HandledStamp $stamp */
+ foreach ($envelope->all(HandledStamp::class) as $stamp) {
+ if ($stamp->getHandlerName() === $handlerDescriptor->getName()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/MiddlewareInterface.php b/plugins/email/vendor/symfony/messenger/Middleware/MiddlewareInterface.php
new file mode 100644
index 0000000..9826611
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/MiddlewareInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * @author Samuel Roze
+ */
+interface MiddlewareInterface
+{
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php
new file mode 100644
index 0000000..9e994dd
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpReceivedStamp;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\RejectRedeliveredMessageException;
+use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp as LegacyAmqpReceivedStamp;
+
+/**
+ * Middleware that throws a RejectRedeliveredMessageException when a message is detected that has been redelivered by AMQP.
+ *
+ * The middleware runs before the HandleMessageMiddleware and prevents redelivered messages from being handled directly.
+ * The thrown exception is caught by the worker and will trigger the retry logic according to the retry strategy.
+ *
+ * AMQP redelivers messages when they do not get acknowledged or rejected. This can happen when the connection times out
+ * or an exception is thrown before acknowledging or rejecting. When such errors happen again while handling the
+ * redelivered message, the message would get redelivered again and again. The purpose of this middleware is to prevent
+ * infinite redelivery loops and to unblock the queue by republishing the redelivered messages as retries with a retry
+ * limit and potential delay.
+ *
+ * @author Tobias Schultze
+ */
+class RejectRedeliveredMessageMiddleware implements MiddlewareInterface
+{
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ $amqpReceivedStamp = $envelope->last(AmqpReceivedStamp::class);
+ if ($amqpReceivedStamp instanceof AmqpReceivedStamp && $amqpReceivedStamp->getAmqpEnvelope()->isRedelivery()) {
+ throw new RejectRedeliveredMessageException('Redelivered message from AMQP detected that will be rejected and trigger the retry logic.');
+ }
+
+ // Legacy code to support symfony/messenger < 5.1
+ $amqpReceivedStamp = $envelope->last(LegacyAmqpReceivedStamp::class);
+ if ($amqpReceivedStamp instanceof LegacyAmqpReceivedStamp && $amqpReceivedStamp->getAmqpEnvelope()->isRedelivery()) {
+ throw new RejectRedeliveredMessageException('Redelivered message from AMQP detected that will be rejected and trigger the retry logic.');
+ }
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/RouterContextMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/RouterContextMiddleware.php
new file mode 100644
index 0000000..62bd1d7
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/RouterContextMiddleware.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp;
+use Symfony\Component\Messenger\Stamp\RouterContextStamp;
+use Symfony\Component\Routing\RequestContextAwareInterface;
+
+/**
+ * Restore the Router context when processing the message.
+ *
+ * @author Jérémy Derussé
+ */
+class RouterContextMiddleware implements MiddlewareInterface
+{
+ private $router;
+
+ public function __construct(RequestContextAwareInterface $router)
+ {
+ $this->router = $router;
+ }
+
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ if (!$envelope->last(ConsumedByWorkerStamp::class) || !$contextStamp = $envelope->last(RouterContextStamp::class)) {
+ $context = $this->router->getContext();
+ $envelope = $envelope->with(new RouterContextStamp(
+ $context->getBaseUrl(),
+ $context->getMethod(),
+ $context->getHost(),
+ $context->getScheme(),
+ $context->getHttpPort(),
+ $context->getHttpsPort(),
+ $context->getPathInfo(),
+ $context->getQueryString()
+ ));
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+
+ $context = $this->router->getContext();
+ $currentBaseUrl = $context->getBaseUrl();
+ $currentMethod = $context->getMethod();
+ $currentHost = $context->getHost();
+ $currentScheme = $context->getScheme();
+ $currentHttpPort = $context->getHttpPort();
+ $currentHttpsPort = $context->getHttpsPort();
+ $currentPathInfo = $context->getPathInfo();
+ $currentQueryString = $context->getQueryString();
+
+ /* @var RouterContextStamp $contextStamp */
+ $context
+ ->setBaseUrl($contextStamp->getBaseUrl())
+ ->setMethod($contextStamp->getMethod())
+ ->setHost($contextStamp->getHost())
+ ->setScheme($contextStamp->getScheme())
+ ->setHttpPort($contextStamp->getHttpPort())
+ ->setHttpsPort($contextStamp->getHttpsPort())
+ ->setPathInfo($contextStamp->getPathInfo())
+ ->setQueryString($contextStamp->getQueryString())
+ ;
+
+ try {
+ return $stack->next()->handle($envelope, $stack);
+ } finally {
+ $context
+ ->setBaseUrl($currentBaseUrl)
+ ->setMethod($currentMethod)
+ ->setHost($currentHost)
+ ->setScheme($currentScheme)
+ ->setHttpPort($currentHttpPort)
+ ->setHttpsPort($currentHttpsPort)
+ ->setPathInfo($currentPathInfo)
+ ->setQueryString($currentQueryString)
+ ;
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php
new file mode 100644
index 0000000..669fe76
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\NullLogger;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent;
+use Symfony\Component\Messenger\Stamp\ReceivedStamp;
+use Symfony\Component\Messenger\Stamp\SentStamp;
+use Symfony\Component\Messenger\Transport\Sender\SendersLocatorInterface;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @author Samuel Roze
+ * @author Tobias Schultze
+ */
+class SendMessageMiddleware implements MiddlewareInterface
+{
+ use LoggerAwareTrait;
+
+ private $sendersLocator;
+ private $eventDispatcher;
+
+ public function __construct(SendersLocatorInterface $sendersLocator, EventDispatcherInterface $eventDispatcher = null)
+ {
+ $this->sendersLocator = $sendersLocator;
+ $this->eventDispatcher = class_exists(Event::class) ? LegacyEventDispatcherProxy::decorate($eventDispatcher) : $eventDispatcher;
+ $this->logger = new NullLogger();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ $context = [
+ 'class' => \get_class($envelope->getMessage()),
+ ];
+
+ $sender = null;
+
+ if ($envelope->all(ReceivedStamp::class)) {
+ // it's a received message, do not send it back
+ $this->logger->info('Received message {class}', $context);
+ } else {
+ $shouldDispatchEvent = true;
+ foreach ($this->sendersLocator->getSenders($envelope) as $alias => $sender) {
+ if (null !== $this->eventDispatcher && $shouldDispatchEvent) {
+ $event = new SendMessageToTransportsEvent($envelope);
+ $this->eventDispatcher->dispatch($event);
+ $envelope = $event->getEnvelope();
+ $shouldDispatchEvent = false;
+ }
+
+ $this->logger->info('Sending message {class} with {alias} sender using {sender}', $context + ['alias' => $alias, 'sender' => \get_class($sender)]);
+ $envelope = $sender->send($envelope->with(new SentStamp(\get_class($sender), \is_string($alias) ? $alias : null)));
+ }
+ }
+
+ if (null === $sender) {
+ return $stack->next()->handle($envelope, $stack);
+ }
+
+ // message should only be sent and not be handled by the next middleware
+ return $envelope;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/StackInterface.php b/plugins/email/vendor/symfony/messenger/Middleware/StackInterface.php
new file mode 100644
index 0000000..6e922c5
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/StackInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * Implementations must be cloneable, and each clone must unstack the stack independently.
+ */
+interface StackInterface
+{
+ /**
+ * Returns the next middleware to process a message.
+ */
+ public function next(): MiddlewareInterface;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/StackMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/StackMiddleware.php
new file mode 100644
index 0000000..30c181f
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/StackMiddleware.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * @author Nicolas Grekas
+ */
+class StackMiddleware implements MiddlewareInterface, StackInterface
+{
+ private $stack;
+ private $offset = 0;
+
+ /**
+ * @param iterable|MiddlewareInterface|null $middlewareIterator
+ */
+ public function __construct($middlewareIterator = null)
+ {
+ $this->stack = new MiddlewareStack();
+
+ if (null === $middlewareIterator) {
+ return;
+ }
+
+ if ($middlewareIterator instanceof \Iterator) {
+ $this->stack->iterator = $middlewareIterator;
+ } elseif ($middlewareIterator instanceof MiddlewareInterface) {
+ $this->stack->stack[] = $middlewareIterator;
+ } elseif (!is_iterable($middlewareIterator)) {
+ throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be iterable of "%s", "%s" given.', __METHOD__, MiddlewareInterface::class, get_debug_type($middlewareIterator)));
+ } else {
+ $this->stack->iterator = (function () use ($middlewareIterator) {
+ yield from $middlewareIterator;
+ })();
+ }
+ }
+
+ public function next(): MiddlewareInterface
+ {
+ if (null === $next = $this->stack->next($this->offset)) {
+ return $this;
+ }
+
+ ++$this->offset;
+
+ return $next;
+ }
+
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ return $envelope;
+ }
+}
+
+/**
+ * @internal
+ */
+class MiddlewareStack
+{
+ /** @var \Iterator */
+ public $iterator;
+ public $stack = [];
+
+ public function next(int $offset): ?MiddlewareInterface
+ {
+ if (isset($this->stack[$offset])) {
+ return $this->stack[$offset];
+ }
+
+ if (null === $this->iterator) {
+ return null;
+ }
+
+ $this->iterator->next();
+
+ if (!$this->iterator->valid()) {
+ return $this->iterator = null;
+ }
+
+ return $this->stack[] = $this->iterator->current();
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/TraceableMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/TraceableMiddleware.php
new file mode 100644
index 0000000..f391cfe
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/TraceableMiddleware.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+/**
+ * Collects some data about a middleware.
+ *
+ * @author Maxime Steinhausser
+ */
+class TraceableMiddleware implements MiddlewareInterface
+{
+ private $stopwatch;
+ private $busName;
+ private $eventCategory;
+
+ public function __construct(Stopwatch $stopwatch, string $busName, string $eventCategory = 'messenger.middleware')
+ {
+ $this->stopwatch = $stopwatch;
+ $this->busName = $busName;
+ $this->eventCategory = $eventCategory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ $stack = new TraceableStack($stack, $this->stopwatch, $this->busName, $this->eventCategory);
+
+ try {
+ return $stack->next()->handle($envelope, $stack);
+ } finally {
+ $stack->stop();
+ }
+ }
+}
+
+/**
+ * @internal
+ */
+class TraceableStack implements StackInterface
+{
+ private $stack;
+ private $stopwatch;
+ private $busName;
+ private $eventCategory;
+ private $currentEvent;
+
+ public function __construct(StackInterface $stack, Stopwatch $stopwatch, string $busName, string $eventCategory)
+ {
+ $this->stack = $stack;
+ $this->stopwatch = $stopwatch;
+ $this->busName = $busName;
+ $this->eventCategory = $eventCategory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function next(): MiddlewareInterface
+ {
+ if (null !== $this->currentEvent && $this->stopwatch->isStarted($this->currentEvent)) {
+ $this->stopwatch->stop($this->currentEvent);
+ }
+
+ if ($this->stack === $nextMiddleware = $this->stack->next()) {
+ $this->currentEvent = 'Tail';
+ } else {
+ $this->currentEvent = sprintf('"%s"', get_debug_type($nextMiddleware));
+ }
+ $this->currentEvent .= sprintf(' on "%s"', $this->busName);
+
+ $this->stopwatch->start($this->currentEvent, $this->eventCategory);
+
+ return $nextMiddleware;
+ }
+
+ public function stop(): void
+ {
+ if (null !== $this->currentEvent && $this->stopwatch->isStarted($this->currentEvent)) {
+ $this->stopwatch->stop($this->currentEvent);
+ }
+ $this->currentEvent = null;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Middleware/ValidationMiddleware.php b/plugins/email/vendor/symfony/messenger/Middleware/ValidationMiddleware.php
new file mode 100644
index 0000000..fb199dd
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Middleware/ValidationMiddleware.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\ValidationFailedException;
+use Symfony\Component\Messenger\Stamp\ValidationStamp;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+
+/**
+ * @author Tobias Nyholm
+ */
+class ValidationMiddleware implements MiddlewareInterface
+{
+ private $validator;
+
+ public function __construct(ValidatorInterface $validator)
+ {
+ $this->validator = $validator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ $message = $envelope->getMessage();
+ $groups = null;
+ /** @var ValidationStamp|null $validationStamp */
+ if ($validationStamp = $envelope->last(ValidationStamp::class)) {
+ $groups = $validationStamp->getGroups();
+ }
+
+ $violations = $this->validator->validate($message, null, $groups);
+ if (\count($violations)) {
+ throw new ValidationFailedException($message, $violations);
+ }
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/README.md b/plugins/email/vendor/symfony/messenger/README.md
new file mode 100644
index 0000000..644269c
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/README.md
@@ -0,0 +1,29 @@
+Messenger Component
+===================
+
+The Messenger component helps applications send and receive messages to/from
+other applications or via message queues.
+
+Sponsor
+-------
+
+The Messenger component for Symfony 5.4/6.0 is [backed][1] by [SensioLabs][2].
+
+As the creator of Symfony, SensioLabs supports companies using Symfony, with an
+offering encompassing consultancy, expertise, services, training, and technical
+assistance to ensure the success of web application development projects.
+
+Help Symfony by [sponsoring][3] its development!
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/messenger.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
+
+[1]: https://symfony.com/backers
+[2]: https://sensiolabs.com
+[3]: https://symfony.com/sponsor
diff --git a/plugins/email/vendor/symfony/messenger/Retry/MultiplierRetryStrategy.php b/plugins/email/vendor/symfony/messenger/Retry/MultiplierRetryStrategy.php
new file mode 100644
index 0000000..c081830
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Retry/MultiplierRetryStrategy.php
@@ -0,0 +1,91 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Retry;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
+
+/**
+ * A retry strategy with a constant or exponential retry delay.
+ *
+ * For example, if $delayMilliseconds=10000 & $multiplier=1 (default),
+ * each retry will wait exactly 10 seconds.
+ *
+ * But if $delayMilliseconds=10000 & $multiplier=2:
+ * * Retry 1: 10 second delay
+ * * Retry 2: 20 second delay (10000 * 2 = 20000)
+ * * Retry 3: 40 second delay (20000 * 2 = 40000)
+ *
+ * @author Ryan Weaver
+ *
+ * @final
+ */
+class MultiplierRetryStrategy implements RetryStrategyInterface
+{
+ private $maxRetries;
+ private $delayMilliseconds;
+ private $multiplier;
+ private $maxDelayMilliseconds;
+
+ /**
+ * @param int $maxRetries The maximum number of times to retry
+ * @param int $delayMilliseconds Amount of time to delay (or the initial value when multiplier is used)
+ * @param float $multiplier Multiplier to apply to the delay each time a retry occurs
+ * @param int $maxDelayMilliseconds Maximum delay to allow (0 means no maximum)
+ */
+ public function __construct(int $maxRetries = 3, int $delayMilliseconds = 1000, float $multiplier = 1, int $maxDelayMilliseconds = 0)
+ {
+ $this->maxRetries = $maxRetries;
+
+ if ($delayMilliseconds < 0) {
+ throw new InvalidArgumentException(sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMilliseconds));
+ }
+ $this->delayMilliseconds = $delayMilliseconds;
+
+ if ($multiplier < 1) {
+ throw new InvalidArgumentException(sprintf('Multiplier must be greater than zero: "%s" given.', $multiplier));
+ }
+ $this->multiplier = $multiplier;
+
+ if ($maxDelayMilliseconds < 0) {
+ throw new InvalidArgumentException(sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMilliseconds));
+ }
+ $this->maxDelayMilliseconds = $maxDelayMilliseconds;
+ }
+
+ /**
+ * @param \Throwable|null $throwable The cause of the failed handling
+ */
+ public function isRetryable(Envelope $message, \Throwable $throwable = null): bool
+ {
+ $retries = RedeliveryStamp::getRetryCountFromEnvelope($message);
+
+ return $retries < $this->maxRetries;
+ }
+
+ /**
+ * @param \Throwable|null $throwable The cause of the failed handling
+ */
+ public function getWaitingTime(Envelope $message, \Throwable $throwable = null): int
+ {
+ $retries = RedeliveryStamp::getRetryCountFromEnvelope($message);
+
+ $delay = $this->delayMilliseconds * $this->multiplier ** $retries;
+
+ if ($delay > $this->maxDelayMilliseconds && 0 !== $this->maxDelayMilliseconds) {
+ return $this->maxDelayMilliseconds;
+ }
+
+ return (int) ceil($delay);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Retry/RetryStrategyInterface.php b/plugins/email/vendor/symfony/messenger/Retry/RetryStrategyInterface.php
new file mode 100644
index 0000000..52c294b
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Retry/RetryStrategyInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Retry;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * @author Fabien Potencier
+ * @author Grégoire Pineau
+ * @author Ryan Weaver
+ */
+interface RetryStrategyInterface
+{
+ /**
+ * @param \Throwable|null $throwable The cause of the failed handling
+ */
+ public function isRetryable(Envelope $message/* , \Throwable $throwable = null */): bool;
+
+ /**
+ * @param \Throwable|null $throwable The cause of the failed handling
+ *
+ * @return int The time to delay/wait in milliseconds
+ */
+ public function getWaitingTime(Envelope $message/* , \Throwable $throwable = null */): int;
+}
diff --git a/plugins/email/vendor/symfony/messenger/RoutableMessageBus.php b/plugins/email/vendor/symfony/messenger/RoutableMessageBus.php
new file mode 100644
index 0000000..ece1478
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/RoutableMessageBus.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger;
+
+use Psr\Container\ContainerInterface;
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Stamp\BusNameStamp;
+
+/**
+ * Bus of buses that is routable using a BusNameStamp.
+ *
+ * This is useful when passed to Worker: messages received
+ * from the transport can be sent to the correct bus.
+ *
+ * @author Ryan Weaver
+ */
+class RoutableMessageBus implements MessageBusInterface
+{
+ private $busLocator;
+ private $fallbackBus;
+
+ public function __construct(ContainerInterface $busLocator, MessageBusInterface $fallbackBus = null)
+ {
+ $this->busLocator = $busLocator;
+ $this->fallbackBus = $fallbackBus;
+ }
+
+ public function dispatch(object $envelope, array $stamps = []): Envelope
+ {
+ if (!$envelope instanceof Envelope) {
+ throw new InvalidArgumentException('Messages passed to RoutableMessageBus::dispatch() must be inside an Envelope.');
+ }
+
+ /** @var BusNameStamp|null $busNameStamp */
+ $busNameStamp = $envelope->last(BusNameStamp::class);
+
+ if (null === $busNameStamp) {
+ if (null === $this->fallbackBus) {
+ throw new InvalidArgumentException('Envelope is missing a BusNameStamp and no fallback message bus is configured on RoutableMessageBus.');
+ }
+
+ return $this->fallbackBus->dispatch($envelope, $stamps);
+ }
+
+ return $this->getMessageBus($busNameStamp->getBusName())->dispatch($envelope, $stamps);
+ }
+
+ /**
+ * @internal
+ */
+ public function getMessageBus(string $busName): MessageBusInterface
+ {
+ if (!$this->busLocator->has($busName)) {
+ throw new InvalidArgumentException(sprintf('Bus named "%s" does not exist.', $busName));
+ }
+
+ return $this->busLocator->get($busName);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/AckStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/AckStamp.php
new file mode 100644
index 0000000..b94c2c9
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/AckStamp.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Marker stamp for messages that can be ack/nack'ed.
+ */
+final class AckStamp implements NonSendableStampInterface
+{
+ private $ack;
+
+ /**
+ * @param \Closure(Envelope, \Throwable|null) $ack
+ */
+ public function __construct(\Closure $ack)
+ {
+ $this->ack = $ack;
+ }
+
+ public function ack(Envelope $envelope, \Throwable $e = null): void
+ {
+ ($this->ack)($envelope, $e);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/BusNameStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/BusNameStamp.php
new file mode 100644
index 0000000..e9765c0
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/BusNameStamp.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * Stamp used to identify which bus it was passed to.
+ *
+ * @author Ryan Weaver
+ */
+final class BusNameStamp implements StampInterface
+{
+ private $busName;
+
+ public function __construct(string $busName)
+ {
+ $this->busName = $busName;
+ }
+
+ public function getBusName(): string
+ {
+ return $this->busName;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/ConsumedByWorkerStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/ConsumedByWorkerStamp.php
new file mode 100644
index 0000000..3ae37ba
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/ConsumedByWorkerStamp.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * A marker that this message was consumed by a worker process.
+ */
+class ConsumedByWorkerStamp implements NonSendableStampInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/DelayStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/DelayStamp.php
new file mode 100644
index 0000000..92859e3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/DelayStamp.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * Apply this stamp to delay delivery of your message on a transport.
+ */
+final class DelayStamp implements StampInterface
+{
+ private $delay;
+
+ /**
+ * @param int $delay The delay in milliseconds
+ */
+ public function __construct(int $delay)
+ {
+ $this->delay = $delay;
+ }
+
+ public function getDelay(): int
+ {
+ return $this->delay;
+ }
+
+ public static function delayFor(\DateInterval $interval): self
+ {
+ $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
+ $end = $now->add($interval);
+
+ return new self(($end->getTimestamp() - $now->getTimestamp()) * 1000);
+ }
+
+ public static function delayUntil(\DateTimeInterface $dateTime): self
+ {
+ return new self(($dateTime->getTimestamp() - time()) * 1000);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/DispatchAfterCurrentBusStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/DispatchAfterCurrentBusStamp.php
new file mode 100644
index 0000000..0ee31f0
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/DispatchAfterCurrentBusStamp.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * Marker item to tell this message should be handled in after the current bus has finished.
+ *
+ * @see \Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware
+ *
+ * @author Tobias Nyholm
+ */
+final class DispatchAfterCurrentBusStamp implements NonSendableStampInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/ErrorDetailsStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/ErrorDetailsStamp.php
new file mode 100644
index 0000000..62abf23
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/ErrorDetailsStamp.php
@@ -0,0 +1,96 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+use Symfony\Component\ErrorHandler\Exception\FlattenException;
+use Symfony\Component\Messenger\Exception\HandlerFailedException;
+use Throwable;
+
+/**
+ * Stamp applied when a messages fails due to an exception in the handler.
+ */
+final class ErrorDetailsStamp implements StampInterface
+{
+ /** @var string */
+ private $exceptionClass;
+
+ /** @var int|string */
+ private $exceptionCode;
+
+ /** @var string */
+ private $exceptionMessage;
+
+ /** @var FlattenException|null */
+ private $flattenException;
+
+ /**
+ * @param int|string $exceptionCode
+ */
+ public function __construct(string $exceptionClass, $exceptionCode, string $exceptionMessage, FlattenException $flattenException = null)
+ {
+ $this->exceptionClass = $exceptionClass;
+ $this->exceptionCode = $exceptionCode;
+ $this->exceptionMessage = $exceptionMessage;
+ $this->flattenException = $flattenException;
+ }
+
+ public static function create(Throwable $throwable): self
+ {
+ if ($throwable instanceof HandlerFailedException) {
+ $throwable = $throwable->getPrevious();
+ }
+
+ $flattenException = null;
+ if (class_exists(FlattenException::class)) {
+ $flattenException = FlattenException::createFromThrowable($throwable);
+ }
+
+ return new self(\get_class($throwable), $throwable->getCode(), $throwable->getMessage(), $flattenException);
+ }
+
+ public function getExceptionClass(): string
+ {
+ return $this->exceptionClass;
+ }
+
+ public function getExceptionCode()
+ {
+ return $this->exceptionCode;
+ }
+
+ public function getExceptionMessage(): string
+ {
+ return $this->exceptionMessage;
+ }
+
+ public function getFlattenException(): ?FlattenException
+ {
+ return $this->flattenException;
+ }
+
+ public function equals(?self $that): bool
+ {
+ if (null === $that) {
+ return false;
+ }
+
+ if ($this->flattenException && $that->flattenException) {
+ return $this->flattenException->getClass() === $that->flattenException->getClass()
+ && $this->flattenException->getCode() === $that->flattenException->getCode()
+ && $this->flattenException->getMessage() === $that->flattenException->getMessage();
+ }
+
+ return $this->exceptionClass === $that->exceptionClass
+ && $this->exceptionCode === $that->exceptionCode
+ && $this->exceptionMessage === $that->exceptionMessage;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/FlushBatchHandlersStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/FlushBatchHandlersStamp.php
new file mode 100644
index 0000000..5dfbe22
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/FlushBatchHandlersStamp.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * Marker telling that any batch handlers bound to the envelope should be flushed.
+ */
+final class FlushBatchHandlersStamp implements NonSendableStampInterface
+{
+ private $force;
+
+ public function __construct(bool $force)
+ {
+ $this->force = $force;
+ }
+
+ public function force(): bool
+ {
+ return $this->force;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/HandledStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/HandledStamp.php
new file mode 100644
index 0000000..9d5a2c1
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/HandledStamp.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+use Symfony\Component\Messenger\Handler\HandlerDescriptor;
+
+/**
+ * Stamp identifying a message handled by the `HandleMessageMiddleware` middleware
+ * and storing the handler returned value.
+ *
+ * This is used by synchronous command buses expecting a return value and the retry logic
+ * to only execute handlers that didn't succeed.
+ *
+ * @see \Symfony\Component\Messenger\Middleware\HandleMessageMiddleware
+ * @see \Symfony\Component\Messenger\HandleTrait
+ *
+ * @author Maxime Steinhausser
+ */
+final class HandledStamp implements StampInterface
+{
+ private $result;
+ private $handlerName;
+
+ /**
+ * @param mixed $result The returned value of the message handler
+ */
+ public function __construct($result, string $handlerName)
+ {
+ $this->result = $result;
+ $this->handlerName = $handlerName;
+ }
+
+ /**
+ * @param mixed $result The returned value of the message handler
+ */
+ public static function fromDescriptor(HandlerDescriptor $handler, $result): self
+ {
+ return new self($result, $handler->getName());
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ public function getHandlerName(): string
+ {
+ return $this->handlerName;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/NoAutoAckStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/NoAutoAckStamp.php
new file mode 100644
index 0000000..15ba383
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/NoAutoAckStamp.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+use Symfony\Component\Messenger\Handler\HandlerDescriptor;
+
+/**
+ * Marker telling that ack should not be done automatically for this message.
+ */
+final class NoAutoAckStamp implements NonSendableStampInterface
+{
+ private $handlerDescriptor;
+
+ public function __construct(HandlerDescriptor $handlerDescriptor)
+ {
+ $this->handlerDescriptor = $handlerDescriptor;
+ }
+
+ public function getHandlerDescriptor(): HandlerDescriptor
+ {
+ return $this->handlerDescriptor;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/NonSendableStampInterface.php b/plugins/email/vendor/symfony/messenger/Stamp/NonSendableStampInterface.php
new file mode 100644
index 0000000..9ebd534
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/NonSendableStampInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * A stamp that should not be included with the Envelope if sent to a transport.
+ *
+ * @author Ryan Weaver
+ */
+interface NonSendableStampInterface extends StampInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/ReceivedStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/ReceivedStamp.php
new file mode 100644
index 0000000..7297b17
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/ReceivedStamp.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
+
+/**
+ * Marker stamp for a received message.
+ *
+ * This is mainly used by the `SendMessageMiddleware` middleware to identify
+ * a message should not be sent if it was just received.
+ *
+ * @see SendMessageMiddleware
+ *
+ * @author Samuel Roze
+ */
+final class ReceivedStamp implements NonSendableStampInterface
+{
+ private $transportName;
+
+ public function __construct(string $transportName)
+ {
+ $this->transportName = $transportName;
+ }
+
+ public function getTransportName(): string
+ {
+ return $this->transportName;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/RedeliveryStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/RedeliveryStamp.php
new file mode 100644
index 0000000..fef9b06
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/RedeliveryStamp.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+use Symfony\Component\ErrorHandler\Exception\FlattenException;
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Stamp applied when a messages needs to be redelivered.
+ */
+final class RedeliveryStamp implements StampInterface
+{
+ private $retryCount;
+ private $redeliveredAt;
+ private $exceptionMessage;
+ private $flattenException;
+
+ /**
+ * @param \DateTimeInterface|null $exceptionMessage
+ */
+ public function __construct(int $retryCount, $exceptionMessage = null, FlattenException $flattenException = null, \DateTimeInterface $redeliveredAt = null)
+ {
+ $this->retryCount = $retryCount;
+ $this->redeliveredAt = $redeliveredAt ?? new \DateTimeImmutable();
+ if (null !== $redeliveredAt) {
+ trigger_deprecation('symfony/messenger', '5.2', sprintf('Using the "$redeliveredAt" as 4th argument of the "%s::__construct()" is deprecated, pass "$redeliveredAt" as second argument instead.', self::class));
+ }
+
+ if ($exceptionMessage instanceof \DateTimeInterface) {
+ // In Symfony 6.0, the second argument will be $redeliveredAt
+ $this->redeliveredAt = $exceptionMessage;
+ if (null !== $redeliveredAt) {
+ throw new \LogicException('It is deprecated to specify a redeliveredAt as 4th argument. The correct way is to specify redeliveredAt as the second argument. Using both is not allowed.');
+ }
+ } elseif (null !== $exceptionMessage) {
+ trigger_deprecation('symfony/messenger', '5.2', sprintf('Using the "$exceptionMessage" parameter in the "%s" class is deprecated, use the "%s" class instead.', self::class, ErrorDetailsStamp::class));
+ $this->exceptionMessage = $exceptionMessage;
+ }
+
+ if (null !== $flattenException) {
+ trigger_deprecation('symfony/messenger', '5.2', sprintf('Using the "$flattenException" parameter in the "%s" class is deprecated, use the "%s" class instead.', self::class, ErrorDetailsStamp::class));
+ }
+ $this->flattenException = $flattenException;
+ }
+
+ public static function getRetryCountFromEnvelope(Envelope $envelope): int
+ {
+ /** @var self|null $retryMessageStamp */
+ $retryMessageStamp = $envelope->last(self::class);
+
+ return $retryMessageStamp ? $retryMessageStamp->getRetryCount() : 0;
+ }
+
+ public function getRetryCount(): int
+ {
+ return $this->retryCount;
+ }
+
+ /**
+ * @deprecated since Symfony 5.2, use ErrorDetailsStamp instead.
+ */
+ public function getExceptionMessage(): ?string
+ {
+ trigger_deprecation('symfony/messenger', '5.2', sprintf('Using the "getExceptionMessage()" method of the "%s" class is deprecated, use the "%s" class instead.', self::class, ErrorDetailsStamp::class));
+
+ return $this->exceptionMessage;
+ }
+
+ /**
+ * @deprecated since Symfony 5.2, use ErrorDetailsStamp instead.
+ */
+ public function getFlattenException(): ?FlattenException
+ {
+ trigger_deprecation('symfony/messenger', '5.2', sprintf('Using the "getFlattenException()" method of the "%s" class is deprecated, use the "%s" class instead.', self::class, ErrorDetailsStamp::class));
+
+ return $this->flattenException;
+ }
+
+ public function getRedeliveredAt(): \DateTimeInterface
+ {
+ return $this->redeliveredAt;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/RouterContextStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/RouterContextStamp.php
new file mode 100644
index 0000000..4906f50
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/RouterContextStamp.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * @author Jérémy Derussé
+ */
+class RouterContextStamp implements StampInterface
+{
+ private $baseUrl;
+ private $method;
+ private $host;
+ private $scheme;
+ private $httpPort;
+ private $httpsPort;
+ private $pathInfo;
+ private $queryString;
+
+ public function __construct(string $baseUrl, string $method, string $host, string $scheme, int $httpPort, int $httpsPort, string $pathInfo, string $queryString)
+ {
+ $this->baseUrl = $baseUrl;
+ $this->method = $method;
+ $this->host = $host;
+ $this->scheme = $scheme;
+ $this->httpPort = $httpPort;
+ $this->httpsPort = $httpsPort;
+ $this->pathInfo = $pathInfo;
+ $this->queryString = $queryString;
+ }
+
+ public function getBaseUrl(): string
+ {
+ return $this->baseUrl;
+ }
+
+ public function getMethod(): string
+ {
+ return $this->method;
+ }
+
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ public function getScheme(): string
+ {
+ return $this->scheme;
+ }
+
+ public function getHttpPort(): int
+ {
+ return $this->httpPort;
+ }
+
+ public function getHttpsPort(): int
+ {
+ return $this->httpsPort;
+ }
+
+ public function getPathInfo(): string
+ {
+ return $this->pathInfo;
+ }
+
+ public function getQueryString(): string
+ {
+ return $this->queryString;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/SentStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/SentStamp.php
new file mode 100644
index 0000000..eebbfc3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/SentStamp.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * Marker stamp identifying a message sent by the `SendMessageMiddleware`.
+ *
+ * @see \Symfony\Component\Messenger\Middleware\SendMessageMiddleware
+ *
+ * @author Maxime Steinhausser
+ */
+final class SentStamp implements NonSendableStampInterface
+{
+ private $senderClass;
+ private $senderAlias;
+
+ public function __construct(string $senderClass, string $senderAlias = null)
+ {
+ $this->senderAlias = $senderAlias;
+ $this->senderClass = $senderClass;
+ }
+
+ public function getSenderClass(): string
+ {
+ return $this->senderClass;
+ }
+
+ public function getSenderAlias(): ?string
+ {
+ return $this->senderAlias;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/SentToFailureTransportStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/SentToFailureTransportStamp.php
new file mode 100644
index 0000000..60810cf
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/SentToFailureTransportStamp.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * Stamp applied when a message is sent to the failure transport.
+ *
+ * @author Ryan Weaver
+ */
+final class SentToFailureTransportStamp implements StampInterface
+{
+ private $originalReceiverName;
+
+ public function __construct(string $originalReceiverName)
+ {
+ $this->originalReceiverName = $originalReceiverName;
+ }
+
+ public function getOriginalReceiverName(): string
+ {
+ return $this->originalReceiverName;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/SerializerStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/SerializerStamp.php
new file mode 100644
index 0000000..3df15ca
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/SerializerStamp.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * @author Maxime Steinhausser
+ */
+final class SerializerStamp implements StampInterface
+{
+ private $context;
+
+ public function __construct(array $context)
+ {
+ $this->context = $context;
+ }
+
+ public function getContext(): array
+ {
+ return $this->context;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/StampInterface.php b/plugins/email/vendor/symfony/messenger/Stamp/StampInterface.php
new file mode 100644
index 0000000..dc1fc0a
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/StampInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * An envelope stamp related to a message.
+ *
+ * Stamps must be serializable value objects for transport.
+ *
+ * @author Maxime Steinhausser
+ */
+interface StampInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/TransportMessageIdStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/TransportMessageIdStamp.php
new file mode 100644
index 0000000..2128b46
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/TransportMessageIdStamp.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+/**
+ * Added by a sender or receiver to indicate the id of this message in that transport.
+ *
+ * @author Ryan Weaver
+ */
+final class TransportMessageIdStamp implements StampInterface
+{
+ private $id;
+
+ /**
+ * @param mixed $id some "identifier" of the message in a transport
+ */
+ public function __construct($id)
+ {
+ $this->id = $id;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Stamp/ValidationStamp.php b/plugins/email/vendor/symfony/messenger/Stamp/ValidationStamp.php
new file mode 100644
index 0000000..2127187
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Stamp/ValidationStamp.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Stamp;
+
+use Symfony\Component\Validator\Constraints\GroupSequence;
+
+/**
+ * @author Maxime Steinhausser
+ */
+final class ValidationStamp implements StampInterface
+{
+ private $groups;
+
+ /**
+ * @param string[]|GroupSequence $groups
+ */
+ public function __construct($groups)
+ {
+ $this->groups = $groups;
+ }
+
+ public function getGroups()
+ {
+ return $this->groups;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Test/Middleware/MiddlewareTestCase.php b/plugins/email/vendor/symfony/messenger/Test/Middleware/MiddlewareTestCase.php
new file mode 100644
index 0000000..08c3d6a
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Test/Middleware/MiddlewareTestCase.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Test\Middleware;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
+use Symfony\Component\Messenger\Middleware\StackInterface;
+use Symfony\Component\Messenger\Middleware\StackMiddleware;
+
+/**
+ * @author Nicolas Grekas
+ */
+abstract class MiddlewareTestCase extends TestCase
+{
+ protected function getStackMock(bool $nextIsCalled = true)
+ {
+ if (!$nextIsCalled) {
+ $stack = $this->createMock(StackInterface::class);
+ $stack
+ ->expects($this->never())
+ ->method('next')
+ ;
+
+ return $stack;
+ }
+
+ $nextMiddleware = $this->createMock(MiddlewareInterface::class);
+ $nextMiddleware
+ ->expects($this->once())
+ ->method('handle')
+ ->willReturnCallback(function (Envelope $envelope, StackInterface $stack): Envelope {
+ return $envelope;
+ })
+ ;
+
+ return new StackMiddleware($nextMiddleware);
+ }
+
+ protected function getThrowingStackMock(\Throwable $throwable = null)
+ {
+ $nextMiddleware = $this->createMock(MiddlewareInterface::class);
+ $nextMiddleware
+ ->expects($this->once())
+ ->method('handle')
+ ->willThrowException($throwable ?? new \RuntimeException('Thrown from next middleware.'))
+ ;
+
+ return new StackMiddleware($nextMiddleware);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/TraceableMessageBus.php b/plugins/email/vendor/symfony/messenger/TraceableMessageBus.php
new file mode 100644
index 0000000..df595b0
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/TraceableMessageBus.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger;
+
+/**
+ * @author Samuel Roze
+ */
+class TraceableMessageBus implements MessageBusInterface
+{
+ private $decoratedBus;
+ private $dispatchedMessages = [];
+
+ public function __construct(MessageBusInterface $decoratedBus)
+ {
+ $this->decoratedBus = $decoratedBus;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch(object $message, array $stamps = []): Envelope
+ {
+ $envelope = Envelope::wrap($message, $stamps);
+ $context = [
+ 'stamps' => array_merge([], ...array_values($envelope->all())),
+ 'message' => $envelope->getMessage(),
+ 'caller' => $this->getCaller(),
+ 'callTime' => microtime(true),
+ ];
+
+ try {
+ return $envelope = $this->decoratedBus->dispatch($message, $stamps);
+ } catch (\Throwable $e) {
+ $context['exception'] = $e;
+
+ throw $e;
+ } finally {
+ $this->dispatchedMessages[] = $context + ['stamps_after_dispatch' => array_merge([], ...array_values($envelope->all()))];
+ }
+ }
+
+ public function getDispatchedMessages(): array
+ {
+ return $this->dispatchedMessages;
+ }
+
+ public function reset()
+ {
+ $this->dispatchedMessages = [];
+ }
+
+ private function getCaller(): array
+ {
+ $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 8);
+
+ $file = $trace[1]['file'] ?? null;
+ $line = $trace[1]['line'] ?? null;
+
+ $handleTraitFile = (new \ReflectionClass(HandleTrait::class))->getFileName();
+ $found = false;
+ for ($i = 1; $i < 8; ++$i) {
+ if (isset($trace[$i]['file'], $trace[$i + 1]['file'], $trace[$i + 1]['line']) && $trace[$i]['file'] === $handleTraitFile) {
+ $file = $trace[$i + 1]['file'];
+ $line = $trace[$i + 1]['line'];
+ $found = true;
+
+ break;
+ }
+ }
+
+ for ($i = 2; $i < 8 && !$found; ++$i) {
+ if (isset($trace[$i]['class'], $trace[$i]['function'])
+ && 'dispatch' === $trace[$i]['function']
+ && is_a($trace[$i]['class'], MessageBusInterface::class, true)
+ ) {
+ $file = $trace[$i]['file'];
+ $line = $trace[$i]['line'];
+
+ while (++$i < 8) {
+ if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) {
+ $file = $trace[$i]['file'];
+ $line = $trace[$i]['line'];
+
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ $name = str_replace('\\', '/', (string) $file);
+
+ return [
+ 'name' => substr($name, strrpos($name, '/') + 1),
+ 'file' => $file,
+ 'line' => $line,
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpFactory.php b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpFactory.php
new file mode 100644
index 0000000..27775b7
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpFactory.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\AmqpExt;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpFactory as BridgeAmqpFactory;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpFactory::class, BridgeAmqpFactory::class);
+
+class_exists(BridgeAmqpFactory::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpFactory
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpReceivedStamp.php b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpReceivedStamp.php
new file mode 100644
index 0000000..3ce9a1d
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpReceivedStamp.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\AmqpExt;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpReceivedStamp as BridgeAmqpReceivedStamp;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated,use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpReceivedStamp::class, BridgeAmqpReceivedStamp::class);
+
+class_exists(BridgeAmqpReceivedStamp::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpReceivedStamp
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpReceiver.php b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpReceiver.php
new file mode 100644
index 0000000..3b4b8f9
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpReceiver.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\AmqpExt;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpReceiver as BridgeAmqpReceiver;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpReceiver::class, BridgeAmqpReceiver::class);
+
+class_exists(BridgeAmqpReceiver::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpReceiver
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpSender.php b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpSender.php
new file mode 100644
index 0000000..f6e195d
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpSender.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\AmqpExt;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpSender as BridgeAmqpSender;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpSender::class, BridgeAmqpSender::class);
+
+class_exists(BridgeAmqpSender::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpSender
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpStamp.php b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpStamp.php
new file mode 100644
index 0000000..5c6f696
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpStamp.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\AmqpExt;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpStamp as BridgeAmqpStamp;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpStamp::class, BridgeAmqpStamp::class);
+
+class_exists(BridgeAmqpStamp::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpStamp
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpTransport.php b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpTransport.php
new file mode 100644
index 0000000..e14dbfc
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpTransport.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\AmqpExt;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransport as BridgeAmqpTransport;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpTransport::class, BridgeAmqpTransport::class);
+
+class_exists(BridgeAmqpTransport::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpTransport
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpTransportFactory.php b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpTransportFactory.php
new file mode 100644
index 0000000..2b48968
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/AmqpTransportFactory.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\AmqpExt;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory as BridgeAmqpTransportFactory;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpTransportFactory::class, BridgeAmqpTransportFactory::class);
+
+class_exists(BridgeAmqpTransportFactory::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpTransportFactory
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/Connection.php b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/Connection.php
new file mode 100644
index 0000000..1301ca9
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/AmqpExt/Connection.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\AmqpExt;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\Connection as BridgeConnection;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', Connection::class, BridgeConnection::class);
+
+class_exists(BridgeConnection::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class Connection
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Doctrine/Connection.php b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/Connection.php
new file mode 100644
index 0000000..6f02bcc
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/Connection.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Doctrine;
+
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection as BridgeConnection;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', Connection::class, BridgeConnection::class);
+
+class_exists(BridgeConnection::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
+ */
+ class Connection
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineReceivedStamp.php b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineReceivedStamp.php
new file mode 100644
index 0000000..6adb60a
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineReceivedStamp.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Doctrine;
+
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceivedStamp as BridgeDoctrineReceivedStamp;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineReceivedStamp::class, BridgeDoctrineReceivedStamp::class);
+
+class_exists(BridgeDoctrineReceivedStamp::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
+ */
+ class DoctrineReceivedStamp
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineReceiver.php b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineReceiver.php
new file mode 100644
index 0000000..ffe0d44
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineReceiver.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Doctrine;
+
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceiver as BridgeDoctrineReceiver;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineReceiver::class, BridgeDoctrineReceiver::class);
+
+class_exists(BridgeDoctrineReceiver::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
+ */
+ class DoctrineReceiver
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineSender.php b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineSender.php
new file mode 100644
index 0000000..17aa795
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineSender.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Doctrine;
+
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineSender as BridgeDoctrineSender;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineSender::class, BridgeDoctrineSender::class);
+
+class_exists(BridgeDoctrineSender::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
+ */
+ class DoctrineSender
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineTransport.php b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineTransport.php
new file mode 100644
index 0000000..6aa96ba
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineTransport.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Doctrine;
+
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport as BridgeDoctrineTransport;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineTransport::class, BridgeDoctrineTransport::class);
+
+class_exists(BridgeDoctrineTransport::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
+ */
+ class DoctrineTransport
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineTransportFactory.php b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineTransportFactory.php
new file mode 100644
index 0000000..fe277d6
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Doctrine/DoctrineTransportFactory.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Doctrine;
+
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransportFactory as BridgeDoctrineTransportFactory;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineTransportFactory::class, BridgeDoctrineTransportFactory::class);
+
+class_exists(BridgeDoctrineTransportFactory::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
+ */
+ class DoctrineTransportFactory
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/InMemoryTransport.php b/plugins/email/vendor/symfony/messenger/Transport/InMemoryTransport.php
new file mode 100644
index 0000000..eedbb9c
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/InMemoryTransport.php
@@ -0,0 +1,166 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Transport that stays in memory. Useful for testing purpose.
+ *
+ * @author Gary PEGEOT
+ */
+class InMemoryTransport implements TransportInterface, ResetInterface
+{
+ /**
+ * @var Envelope[]
+ */
+ private $sent = [];
+
+ /**
+ * @var Envelope[]
+ */
+ private $acknowledged = [];
+
+ /**
+ * @var Envelope[]
+ */
+ private $rejected = [];
+
+ /**
+ * @var Envelope[]
+ */
+ private $queue = [];
+
+ private $nextId = 1;
+
+ /**
+ * @var SerializerInterface|null
+ */
+ private $serializer;
+
+ public function __construct(SerializerInterface $serializer = null)
+ {
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ return array_values($this->decode($this->queue));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ $this->acknowledged[] = $this->encode($envelope);
+
+ if (!$transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class)) {
+ throw new LogicException('No TransportMessageIdStamp found on the Envelope.');
+ }
+
+ unset($this->queue[$transportMessageIdStamp->getId()]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ $this->rejected[] = $this->encode($envelope);
+
+ if (!$transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class)) {
+ throw new LogicException('No TransportMessageIdStamp found on the Envelope.');
+ }
+
+ unset($this->queue[$transportMessageIdStamp->getId()]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ $id = $this->nextId++;
+ $envelope = $envelope->with(new TransportMessageIdStamp($id));
+ $encodedEnvelope = $this->encode($envelope);
+ $this->sent[] = $encodedEnvelope;
+ $this->queue[$id] = $encodedEnvelope;
+
+ return $envelope;
+ }
+
+ public function reset()
+ {
+ $this->sent = $this->queue = $this->rejected = $this->acknowledged = [];
+ }
+
+ /**
+ * @return Envelope[]
+ */
+ public function getAcknowledged(): array
+ {
+ return $this->decode($this->acknowledged);
+ }
+
+ /**
+ * @return Envelope[]
+ */
+ public function getRejected(): array
+ {
+ return $this->decode($this->rejected);
+ }
+
+ /**
+ * @return Envelope[]
+ */
+ public function getSent(): array
+ {
+ return $this->decode($this->sent);
+ }
+
+ /**
+ * @return Envelope|array
+ */
+ private function encode(Envelope $envelope)
+ {
+ if (null === $this->serializer) {
+ return $envelope;
+ }
+
+ return $this->serializer->encode($envelope);
+ }
+
+ /**
+ * @param array $messagesEncoded
+ *
+ * @return Envelope[]
+ */
+ private function decode(array $messagesEncoded): array
+ {
+ if (null === $this->serializer) {
+ return $messagesEncoded;
+ }
+
+ return array_map(
+ [$this->serializer, 'decode'],
+ $messagesEncoded
+ );
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/InMemoryTransportFactory.php b/plugins/email/vendor/symfony/messenger/Transport/InMemoryTransportFactory.php
new file mode 100644
index 0000000..ee7d028
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/InMemoryTransportFactory.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport;
+
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @author Gary PEGEOT
+ */
+class InMemoryTransportFactory implements TransportFactoryInterface, ResetInterface
+{
+ /**
+ * @var InMemoryTransport[]
+ */
+ private $createdTransports = [];
+
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ ['serialize' => $serialize] = $this->parseDsn($dsn);
+
+ return $this->createdTransports[] = new InMemoryTransport($serialize ? $serializer : null);
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ return str_starts_with($dsn, 'in-memory://');
+ }
+
+ public function reset()
+ {
+ foreach ($this->createdTransports as $transport) {
+ $transport->reset();
+ }
+ }
+
+ private function parseDsn(string $dsn): array
+ {
+ $query = [];
+ if ($queryAsString = strstr($dsn, '?')) {
+ parse_str(ltrim($queryAsString, '?'), $query);
+ }
+
+ return [
+ 'serialize' => filter_var($query['serialize'] ?? false, \FILTER_VALIDATE_BOOLEAN),
+ ];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Receiver/ListableReceiverInterface.php b/plugins/email/vendor/symfony/messenger/Transport/Receiver/ListableReceiverInterface.php
new file mode 100644
index 0000000..897c7a5
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Receiver/ListableReceiverInterface.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Receiver;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Used when a receiver has the ability to list messages and find specific messages.
+ * A receiver that implements this should add the TransportMessageIdStamp
+ * to the Envelopes that it returns.
+ *
+ * @author Ryan Weaver
+ */
+interface ListableReceiverInterface extends ReceiverInterface
+{
+ /**
+ * Returns all the messages (up to the limit) in this receiver.
+ *
+ * Messages should be given the same stamps as when using ReceiverInterface::get().
+ *
+ * @return Envelope[]|iterable
+ */
+ public function all(int $limit = null): iterable;
+
+ /**
+ * Returns the Envelope by id or none.
+ *
+ * Message should be given the same stamps as when using ReceiverInterface::get().
+ */
+ public function find($id): ?Envelope;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Receiver/MessageCountAwareInterface.php b/plugins/email/vendor/symfony/messenger/Transport/Receiver/MessageCountAwareInterface.php
new file mode 100644
index 0000000..b680d8a
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Receiver/MessageCountAwareInterface.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Receiver;
+
+/**
+ * @author Samuel Roze
+ * @author Ryan Weaver
+ */
+interface MessageCountAwareInterface
+{
+ /**
+ * Returns the number of messages waiting to be handled.
+ *
+ * In some systems, this may be an approximate number.
+ */
+ public function getMessageCount(): int;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Receiver/QueueReceiverInterface.php b/plugins/email/vendor/symfony/messenger/Transport/Receiver/QueueReceiverInterface.php
new file mode 100644
index 0000000..1886afe
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Receiver/QueueReceiverInterface.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Receiver;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Some transports may have multiple queues. This interface is used to read from only some queues.
+ *
+ * @author David Buchmann
+ */
+interface QueueReceiverInterface extends ReceiverInterface
+{
+ /**
+ * Get messages from the specified queue names instead of consuming from all queues.
+ *
+ * @param string[] $queueNames
+ *
+ * @return Envelope[]
+ */
+ public function getFromQueues(array $queueNames): iterable;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Receiver/ReceiverInterface.php b/plugins/email/vendor/symfony/messenger/Transport/Receiver/ReceiverInterface.php
new file mode 100644
index 0000000..68f72c5
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Receiver/ReceiverInterface.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Receiver;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\TransportException;
+
+/**
+ * @author Samuel Roze
+ * @author Ryan Weaver
+ */
+interface ReceiverInterface
+{
+ /**
+ * Receives some messages.
+ *
+ * While this method could return an unlimited number of messages,
+ * the intention is that it returns only one, or a "small number"
+ * of messages each time. This gives the user more flexibility:
+ * they can finish processing the one (or "small number") of messages
+ * from this receiver and move on to check other receivers for messages.
+ * If this method returns too many messages, it could cause a
+ * blocking effect where handling the messages received from one
+ * call to get() takes a long time, blocking other receivers from
+ * being called.
+ *
+ * If applicable, the Envelope should contain a TransportMessageIdStamp.
+ *
+ * If a received message cannot be decoded, the message should not
+ * be retried again (e.g. if there's a queue, it should be removed)
+ * and a MessageDecodingFailedException should be thrown.
+ *
+ * @throws TransportException If there is an issue communicating with the transport
+ *
+ * @return Envelope[]
+ */
+ public function get(): iterable;
+
+ /**
+ * Acknowledges that the passed message was handled.
+ *
+ * @throws TransportException If there is an issue communicating with the transport
+ */
+ public function ack(Envelope $envelope): void;
+
+ /**
+ * Called when handling the message failed and it should not be retried.
+ *
+ * @throws TransportException If there is an issue communicating with the transport
+ */
+ public function reject(Envelope $envelope): void;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Receiver/SingleMessageReceiver.php b/plugins/email/vendor/symfony/messenger/Transport/Receiver/SingleMessageReceiver.php
new file mode 100644
index 0000000..1ad5f22
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Receiver/SingleMessageReceiver.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Receiver;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Receiver that decorates another, but receives only 1 specific message.
+ *
+ * @author Ryan Weaver
+ *
+ * @internal
+ */
+class SingleMessageReceiver implements ReceiverInterface
+{
+ private $receiver;
+ private $envelope;
+ private $hasReceived = false;
+
+ public function __construct(ReceiverInterface $receiver, Envelope $envelope)
+ {
+ $this->receiver = $receiver;
+ $this->envelope = $envelope;
+ }
+
+ public function get(): iterable
+ {
+ if ($this->hasReceived) {
+ return [];
+ }
+
+ $this->hasReceived = true;
+
+ return [$this->envelope];
+ }
+
+ public function ack(Envelope $envelope): void
+ {
+ $this->receiver->ack($envelope);
+ }
+
+ public function reject(Envelope $envelope): void
+ {
+ $this->receiver->reject($envelope);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/RedisExt/Connection.php b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/Connection.php
new file mode 100644
index 0000000..39679ab
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/Connection.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\RedisExt;
+
+use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection as BridgeConnection;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', Connection::class, BridgeConnection::class);
+
+class_exists(BridgeConnection::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class Connection
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisReceivedStamp.php b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisReceivedStamp.php
new file mode 100644
index 0000000..0a5866c
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisReceivedStamp.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\RedisExt;
+
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisReceivedStamp as BridgeRedisReceivedStamp;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated,use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisReceivedStamp::class, BridgeRedisReceivedStamp::class);
+
+class_exists(BridgeRedisReceivedStamp::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class RedisReceivedStamp
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisReceiver.php b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisReceiver.php
new file mode 100644
index 0000000..6aafbb1
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisReceiver.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\RedisExt;
+
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisReceiver as BridgeRedisReceiver;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisReceiver::class, BridgeRedisReceiver::class);
+
+class_exists(BridgeRedisReceiver::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class RedisReceiver
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisSender.php b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisSender.php
new file mode 100644
index 0000000..8928236
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisSender.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\RedisExt;
+
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisSender as BridgeRedisSender;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisSender::class, BridgeRedisSender::class);
+
+class_exists(BridgeRedisSender::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class RedisSender
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisTransport.php b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisTransport.php
new file mode 100644
index 0000000..939b748
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisTransport.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\RedisExt;
+
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransport as BridgeRedisTransport;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisTransport::class, BridgeRedisTransport::class);
+
+class_exists(BridgeRedisTransport::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class RedisTransport
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisTransportFactory.php b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisTransportFactory.php
new file mode 100644
index 0000000..a93822b
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/RedisExt/RedisTransportFactory.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\RedisExt;
+
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory as BridgeRedisTransportFactory;
+
+trigger_deprecation('symfony/messenger', '5.1', 'The "%s" class is deprecated, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisTransportFactory::class, BridgeRedisTransportFactory::class);
+
+class_exists(BridgeRedisTransportFactory::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class RedisTransportFactory
+ {
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Sender/SenderInterface.php b/plugins/email/vendor/symfony/messenger/Transport/Sender/SenderInterface.php
new file mode 100644
index 0000000..3414a40
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Sender/SenderInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Sender;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * @author Samuel Roze
+ */
+interface SenderInterface
+{
+ /**
+ * Sends the given envelope.
+ *
+ * The sender can read different stamps for transport configuration,
+ * like delivery delay.
+ *
+ * If applicable, the returned Envelope should contain a TransportMessageIdStamp.
+ */
+ public function send(Envelope $envelope): Envelope;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Sender/SendersLocator.php b/plugins/email/vendor/symfony/messenger/Transport/Sender/SendersLocator.php
new file mode 100644
index 0000000..9d0a1e3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Sender/SendersLocator.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Sender;
+
+use Psr\Container\ContainerInterface;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\RuntimeException;
+use Symfony\Component\Messenger\Handler\HandlersLocator;
+
+/**
+ * Maps a message to a list of senders.
+ *
+ * @author Fabien Potencier
+ */
+class SendersLocator implements SendersLocatorInterface
+{
+ private $sendersMap;
+ private $sendersLocator;
+
+ /**
+ * @param array> $sendersMap An array, keyed by "type", set to an array of sender aliases
+ * @param ContainerInterface $sendersLocator Locator of senders, keyed by sender alias
+ */
+ public function __construct(array $sendersMap, ContainerInterface $sendersLocator)
+ {
+ $this->sendersMap = $sendersMap;
+ $this->sendersLocator = $sendersLocator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSenders(Envelope $envelope): iterable
+ {
+ $seen = [];
+
+ foreach (HandlersLocator::listTypes($envelope) as $type) {
+ foreach ($this->sendersMap[$type] ?? [] as $senderAlias) {
+ if (!\in_array($senderAlias, $seen, true)) {
+ if (!$this->sendersLocator->has($senderAlias)) {
+ throw new RuntimeException(sprintf('Invalid senders configuration: sender "%s" is not in the senders locator.', $senderAlias));
+ }
+
+ $seen[] = $senderAlias;
+ $sender = $this->sendersLocator->get($senderAlias);
+ yield $senderAlias => $sender;
+ }
+ }
+ }
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Sender/SendersLocatorInterface.php b/plugins/email/vendor/symfony/messenger/Transport/Sender/SendersLocatorInterface.php
new file mode 100644
index 0000000..feab1f4
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Sender/SendersLocatorInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Sender;
+
+use Symfony\Component\Messenger\Envelope;
+
+/**
+ * Maps a message to a list of senders.
+ *
+ * @author Samuel Roze
+ * @author Tobias Schultze
+ */
+interface SendersLocatorInterface
+{
+ /**
+ * Gets the senders for the given message name.
+ *
+ * @return iterable Indexed by sender alias if available
+ */
+ public function getSenders(Envelope $envelope): iterable;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php b/plugins/email/vendor/symfony/messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php
new file mode 100644
index 0000000..5f335d0
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Serialization\Normalizer;
+
+use Symfony\Component\ErrorHandler\Exception\FlattenException;
+use Symfony\Component\Messenger\Transport\Serialization\Serializer;
+use Symfony\Component\Serializer\Exception\InvalidArgumentException;
+use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
+use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
+use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
+
+/**
+ * This normalizer is only used in Debug/Dev/Messenger contexts.
+ *
+ * @author Pascal Luna
+ */
+final class FlattenExceptionNormalizer implements DenormalizerInterface, ContextAwareNormalizerInterface
+{
+ use NormalizerAwareTrait;
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidArgumentException
+ */
+ public function normalize($object, string $format = null, array $context = []): array
+ {
+ $normalized = [
+ 'message' => $object->getMessage(),
+ 'code' => $object->getCode(),
+ 'headers' => $object->getHeaders(),
+ 'class' => $object->getClass(),
+ 'file' => $object->getFile(),
+ 'line' => $object->getLine(),
+ 'previous' => null === $object->getPrevious() ? null : $this->normalize($object->getPrevious(), $format, $context),
+ 'status' => $object->getStatusCode(),
+ 'status_text' => $object->getStatusText(),
+ 'trace' => $object->getTrace(),
+ 'trace_as_string' => $object->getTraceAsString(),
+ ];
+
+ return $normalized;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supportsNormalization($data, string $format = null, array $context = []): bool
+ {
+ return $data instanceof FlattenException && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function denormalize($data, string $type, string $format = null, array $context = []): FlattenException
+ {
+ $object = new FlattenException();
+
+ $object->setMessage($data['message']);
+ $object->setCode($data['code']);
+ $object->setStatusCode($data['status'] ?? 500);
+ $object->setClass($data['class']);
+ $object->setFile($data['file']);
+ $object->setLine($data['line']);
+ $object->setStatusText($data['status_text']);
+ $object->setHeaders((array) $data['headers']);
+
+ if (isset($data['previous'])) {
+ $object->setPrevious($this->denormalize($data['previous'], $type, $format, $context));
+ }
+
+ $property = new \ReflectionProperty(FlattenException::class, 'trace');
+ $property->setAccessible(true);
+ $property->setValue($object, (array) $data['trace']);
+
+ $property = new \ReflectionProperty(FlattenException::class, 'traceAsString');
+ $property->setAccessible(true);
+ $property->setValue($object, $data['trace_as_string']);
+
+ return $object;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
+ {
+ return FlattenException::class === $type && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Serialization/PhpSerializer.php b/plugins/email/vendor/symfony/messenger/Transport/Serialization/PhpSerializer.php
new file mode 100644
index 0000000..e6332b3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Serialization/PhpSerializer.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Serialization;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
+use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+
+/**
+ * @author Ryan Weaver
+ */
+class PhpSerializer implements SerializerInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function decode(array $encodedEnvelope): Envelope
+ {
+ if (empty($encodedEnvelope['body'])) {
+ throw new MessageDecodingFailedException('Encoded envelope should have at least a "body", or maybe you should implement your own serializer.');
+ }
+
+ if (!str_ends_with($encodedEnvelope['body'], '}')) {
+ $encodedEnvelope['body'] = base64_decode($encodedEnvelope['body']);
+ }
+
+ $serializeEnvelope = stripslashes($encodedEnvelope['body']);
+
+ return $this->safelyUnserialize($serializeEnvelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function encode(Envelope $envelope): array
+ {
+ $envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
+
+ $body = addslashes(serialize($envelope));
+
+ if (!preg_match('//u', $body)) {
+ $body = base64_encode($body);
+ }
+
+ return [
+ 'body' => $body,
+ ];
+ }
+
+ private function safelyUnserialize(string $contents)
+ {
+ if ('' === $contents) {
+ throw new MessageDecodingFailedException('Could not decode an empty message using PHP serialization.');
+ }
+
+ $signalingException = new MessageDecodingFailedException(sprintf('Could not decode message using PHP serialization: %s.', $contents));
+ $prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback');
+ $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) {
+ if (__FILE__ === $file) {
+ throw $signalingException;
+ }
+
+ return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
+ });
+
+ try {
+ $meta = unserialize($contents);
+ } finally {
+ restore_error_handler();
+ ini_set('unserialize_callback_func', $prevUnserializeHandler);
+ }
+
+ return $meta;
+ }
+
+ /**
+ * @internal
+ */
+ public static function handleUnserializeCallback(string $class)
+ {
+ throw new MessageDecodingFailedException(sprintf('Message class "%s" not found during decoding.', $class));
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Serialization/Serializer.php b/plugins/email/vendor/symfony/messenger/Transport/Serialization/Serializer.php
new file mode 100644
index 0000000..6142266
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Serialization/Serializer.php
@@ -0,0 +1,183 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Serialization;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
+use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+use Symfony\Component\Messenger\Stamp\SerializerStamp;
+use Symfony\Component\Messenger\Stamp\StampInterface;
+use Symfony\Component\Serializer\Encoder\JsonEncoder;
+use Symfony\Component\Serializer\Encoder\XmlEncoder;
+use Symfony\Component\Serializer\Exception\ExceptionInterface;
+use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
+use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+use Symfony\Component\Serializer\Serializer as SymfonySerializer;
+use Symfony\Component\Serializer\SerializerInterface as SymfonySerializerInterface;
+
+/**
+ * @author Samuel Roze
+ */
+class Serializer implements SerializerInterface
+{
+ public const MESSENGER_SERIALIZATION_CONTEXT = 'messenger_serialization';
+ private const STAMP_HEADER_PREFIX = 'X-Message-Stamp-';
+
+ private $serializer;
+ private $format;
+ private $context;
+
+ public function __construct(SymfonySerializerInterface $serializer = null, string $format = 'json', array $context = [])
+ {
+ $this->serializer = $serializer ?? self::create()->serializer;
+ $this->format = $format;
+ $this->context = $context + [self::MESSENGER_SERIALIZATION_CONTEXT => true];
+ }
+
+ public static function create(): self
+ {
+ if (!class_exists(SymfonySerializer::class)) {
+ throw new LogicException(sprintf('The "%s" class requires Symfony\'s Serializer component. Try running "composer require symfony/serializer" or use "%s" instead.', __CLASS__, PhpSerializer::class));
+ }
+
+ $encoders = [new XmlEncoder(), new JsonEncoder()];
+ $normalizers = [new ArrayDenormalizer(), new ObjectNormalizer()];
+ $serializer = new SymfonySerializer($normalizers, $encoders);
+
+ return new self($serializer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function decode(array $encodedEnvelope): Envelope
+ {
+ if (empty($encodedEnvelope['body']) || empty($encodedEnvelope['headers'])) {
+ throw new MessageDecodingFailedException('Encoded envelope should have at least a "body" and some "headers", or maybe you should implement your own serializer.');
+ }
+
+ if (empty($encodedEnvelope['headers']['type'])) {
+ throw new MessageDecodingFailedException('Encoded envelope does not have a "type" header.');
+ }
+
+ $stamps = $this->decodeStamps($encodedEnvelope);
+ $serializerStamp = $this->findFirstSerializerStamp($stamps);
+
+ $context = $this->context;
+ if (null !== $serializerStamp) {
+ $context = $serializerStamp->getContext() + $context;
+ }
+
+ try {
+ $message = $this->serializer->deserialize($encodedEnvelope['body'], $encodedEnvelope['headers']['type'], $this->format, $context);
+ } catch (ExceptionInterface $e) {
+ throw new MessageDecodingFailedException('Could not decode message: '.$e->getMessage(), $e->getCode(), $e);
+ }
+
+ return new Envelope($message, $stamps);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function encode(Envelope $envelope): array
+ {
+ $context = $this->context;
+ /** @var SerializerStamp|null $serializerStamp */
+ if ($serializerStamp = $envelope->last(SerializerStamp::class)) {
+ $context = $serializerStamp->getContext() + $context;
+ }
+
+ $envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
+
+ $headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope) + $this->getContentTypeHeader();
+
+ return [
+ 'body' => $this->serializer->serialize($envelope->getMessage(), $this->format, $context),
+ 'headers' => $headers,
+ ];
+ }
+
+ private function decodeStamps(array $encodedEnvelope): array
+ {
+ $stamps = [];
+ foreach ($encodedEnvelope['headers'] as $name => $value) {
+ if (!str_starts_with($name, self::STAMP_HEADER_PREFIX)) {
+ continue;
+ }
+
+ try {
+ $stamps[] = $this->serializer->deserialize($value, substr($name, \strlen(self::STAMP_HEADER_PREFIX)).'[]', $this->format, $this->context);
+ } catch (ExceptionInterface $e) {
+ throw new MessageDecodingFailedException('Could not decode stamp: '.$e->getMessage(), $e->getCode(), $e);
+ }
+ }
+ if ($stamps) {
+ $stamps = array_merge(...$stamps);
+ }
+
+ return $stamps;
+ }
+
+ private function encodeStamps(Envelope $envelope): array
+ {
+ if (!$allStamps = $envelope->all()) {
+ return [];
+ }
+
+ $headers = [];
+ foreach ($allStamps as $class => $stamps) {
+ $headers[self::STAMP_HEADER_PREFIX.$class] = $this->serializer->serialize($stamps, $this->format, $this->context);
+ }
+
+ return $headers;
+ }
+
+ /**
+ * @param StampInterface[] $stamps
+ */
+ private function findFirstSerializerStamp(array $stamps): ?SerializerStamp
+ {
+ foreach ($stamps as $stamp) {
+ if ($stamp instanceof SerializerStamp) {
+ return $stamp;
+ }
+ }
+
+ return null;
+ }
+
+ private function getContentTypeHeader(): array
+ {
+ $mimeType = $this->getMimeTypeForFormat();
+
+ return null === $mimeType ? [] : ['Content-Type' => $mimeType];
+ }
+
+ private function getMimeTypeForFormat(): ?string
+ {
+ switch ($this->format) {
+ case 'json':
+ return 'application/json';
+ case 'xml':
+ return 'application/xml';
+ case 'yml':
+ case 'yaml':
+ return 'application/x-yaml';
+ case 'csv':
+ return 'text/csv';
+ }
+
+ return null;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Serialization/SerializerInterface.php b/plugins/email/vendor/symfony/messenger/Transport/Serialization/SerializerInterface.php
new file mode 100644
index 0000000..fc133f7
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Serialization/SerializerInterface.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Serialization;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
+
+/**
+ * @author Samuel Roze
+ */
+interface SerializerInterface
+{
+ /**
+ * Decodes an envelope and its message from an encoded-form.
+ *
+ * The `$encodedEnvelope` parameter is a key-value array that
+ * describes the envelope and its content, that will be used by the different transports.
+ *
+ * The most common keys are:
+ * - `body` (string) - the message body
+ * - `headers` (string) - a key/value pair of headers
+ *
+ * @throws MessageDecodingFailedException
+ */
+ public function decode(array $encodedEnvelope): Envelope;
+
+ /**
+ * Encodes an envelope content (message & stamps) to a common format understandable by transports.
+ * The encoded array should only contain scalars and arrays.
+ *
+ * Stamps that implement NonSendableStampInterface should
+ * not be encoded.
+ *
+ * The most common keys of the encoded array are:
+ * - `body` (string) - the message body
+ * - `headers` (string) - a key/value pair of headers
+ */
+ public function encode(Envelope $envelope): array;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/SetupableTransportInterface.php b/plugins/email/vendor/symfony/messenger/Transport/SetupableTransportInterface.php
new file mode 100644
index 0000000..8b6a857
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/SetupableTransportInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport;
+
+/**
+ * @author Vincent Touzet
+ */
+interface SetupableTransportInterface
+{
+ /**
+ * Setup the transport.
+ */
+ public function setup(): void;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Sync/SyncTransport.php b/plugins/email/vendor/symfony/messenger/Transport/Sync/SyncTransport.php
new file mode 100644
index 0000000..67af903
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Sync/SyncTransport.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Sync;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Messenger\Stamp\ReceivedStamp;
+use Symfony\Component\Messenger\Stamp\SentStamp;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * Transport that immediately marks messages as received and dispatches for handling.
+ *
+ * @author Ryan Weaver
+ */
+class SyncTransport implements TransportInterface
+{
+ private $messageBus;
+
+ public function __construct(MessageBusInterface $messageBus)
+ {
+ $this->messageBus = $messageBus;
+ }
+
+ public function get(): iterable
+ {
+ throw new InvalidArgumentException('You cannot receive messages from the Messenger SyncTransport.');
+ }
+
+ public function stop(): void
+ {
+ throw new InvalidArgumentException('You cannot call stop() on the Messenger SyncTransport.');
+ }
+
+ public function ack(Envelope $envelope): void
+ {
+ throw new InvalidArgumentException('You cannot call ack() on the Messenger SyncTransport.');
+ }
+
+ public function reject(Envelope $envelope): void
+ {
+ throw new InvalidArgumentException('You cannot call reject() on the Messenger SyncTransport.');
+ }
+
+ public function send(Envelope $envelope): Envelope
+ {
+ /** @var SentStamp|null $sentStamp */
+ $sentStamp = $envelope->last(SentStamp::class);
+ $alias = null === $sentStamp ? 'sync' : ($sentStamp->getSenderAlias() ?: $sentStamp->getSenderClass());
+
+ $envelope = $envelope->with(new ReceivedStamp($alias));
+
+ return $this->messageBus->dispatch($envelope);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/Sync/SyncTransportFactory.php b/plugins/email/vendor/symfony/messenger/Transport/Sync/SyncTransportFactory.php
new file mode 100644
index 0000000..d30c49c
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/Sync/SyncTransportFactory.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport\Sync;
+
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Ryan Weaver
+ */
+class SyncTransportFactory implements TransportFactoryInterface
+{
+ private $messageBus;
+
+ public function __construct(MessageBusInterface $messageBus)
+ {
+ $this->messageBus = $messageBus;
+ }
+
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ return new SyncTransport($this->messageBus);
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ return str_starts_with($dsn, 'sync://');
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/TransportFactory.php b/plugins/email/vendor/symfony/messenger/Transport/TransportFactory.php
new file mode 100644
index 0000000..474dd6f
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/TransportFactory.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport;
+
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * @author Samuel Roze
+ */
+class TransportFactory implements TransportFactoryInterface
+{
+ private $factories;
+
+ /**
+ * @param iterable $factories
+ */
+ public function __construct(iterable $factories)
+ {
+ $this->factories = $factories;
+ }
+
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ foreach ($this->factories as $factory) {
+ if ($factory->supports($dsn, $options)) {
+ return $factory->createTransport($dsn, $options, $serializer);
+ }
+ }
+
+ // Help the user to select Symfony packages based on protocol.
+ $packageSuggestion = '';
+ if (0 === strpos($dsn, 'amqp://')) {
+ $packageSuggestion = ' Run "composer require symfony/amqp-messenger" to install AMQP transport.';
+ } elseif (0 === strpos($dsn, 'doctrine://')) {
+ $packageSuggestion = ' Run "composer require symfony/doctrine-messenger" to install Doctrine transport.';
+ } elseif (0 === strpos($dsn, 'redis://') || 0 === strpos($dsn, 'rediss://')) {
+ $packageSuggestion = ' Run "composer require symfony/redis-messenger" to install Redis transport.';
+ } elseif (0 === strpos($dsn, 'sqs://') || preg_match('#^https://sqs\.[\w\-]+\.amazonaws\.com/.+#', $dsn)) {
+ $packageSuggestion = ' Run "composer require symfony/amazon-sqs-messenger" to install Amazon SQS transport.';
+ } elseif (0 === strpos($dsn, 'beanstalkd://')) {
+ $packageSuggestion = ' Run "composer require symfony/beanstalkd-messenger" to install Beanstalkd transport.';
+ }
+
+ throw new InvalidArgumentException(sprintf('No transport supports the given Messenger DSN "%s".%s.', $dsn, $packageSuggestion));
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ foreach ($this->factories as $factory) {
+ if ($factory->supports($dsn, $options)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/TransportFactoryInterface.php b/plugins/email/vendor/symfony/messenger/Transport/TransportFactoryInterface.php
new file mode 100644
index 0000000..5741c30
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/TransportFactoryInterface.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport;
+
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * Creates a Messenger transport.
+ *
+ * @author Samuel Roze
+ */
+interface TransportFactoryInterface
+{
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface;
+
+ public function supports(string $dsn, array $options): bool;
+}
diff --git a/plugins/email/vendor/symfony/messenger/Transport/TransportInterface.php b/plugins/email/vendor/symfony/messenger/Transport/TransportInterface.php
new file mode 100644
index 0000000..18c1bb8
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Transport/TransportInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport;
+
+use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+interface TransportInterface extends ReceiverInterface, SenderInterface
+{
+}
diff --git a/plugins/email/vendor/symfony/messenger/Worker.php b/plugins/email/vendor/symfony/messenger/Worker.php
new file mode 100644
index 0000000..33358d3
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/Worker.php
@@ -0,0 +1,268 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
+use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
+use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
+use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
+use Symfony\Component\Messenger\Event\WorkerRunningEvent;
+use Symfony\Component\Messenger\Event\WorkerStartedEvent;
+use Symfony\Component\Messenger\Event\WorkerStoppedEvent;
+use Symfony\Component\Messenger\Exception\HandlerFailedException;
+use Symfony\Component\Messenger\Exception\RejectRedeliveredMessageException;
+use Symfony\Component\Messenger\Exception\RuntimeException;
+use Symfony\Component\Messenger\Stamp\AckStamp;
+use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp;
+use Symfony\Component\Messenger\Stamp\FlushBatchHandlersStamp;
+use Symfony\Component\Messenger\Stamp\NoAutoAckStamp;
+use Symfony\Component\Messenger\Stamp\ReceivedStamp;
+use Symfony\Component\Messenger\Transport\Receiver\QueueReceiverInterface;
+use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @author Samuel Roze
+ * @author Tobias Schultze
+ *
+ * @final
+ */
+class Worker
+{
+ private $receivers;
+ private $bus;
+ private $eventDispatcher;
+ private $logger;
+ private $shouldStop = false;
+ private $metadata;
+ private $acks = [];
+ private $unacks;
+
+ /**
+ * @param ReceiverInterface[] $receivers Where the key is the transport name
+ */
+ public function __construct(array $receivers, MessageBusInterface $bus, EventDispatcherInterface $eventDispatcher = null, LoggerInterface $logger = null)
+ {
+ $this->receivers = $receivers;
+ $this->bus = $bus;
+ $this->logger = $logger;
+ $this->eventDispatcher = class_exists(Event::class) ? LegacyEventDispatcherProxy::decorate($eventDispatcher) : $eventDispatcher;
+ $this->metadata = new WorkerMetadata([
+ 'transportNames' => array_keys($receivers),
+ ]);
+ $this->unacks = new \SplObjectStorage();
+ }
+
+ /**
+ * Receive the messages and dispatch them to the bus.
+ *
+ * Valid options are:
+ * * sleep (default: 1000000): Time in microseconds to sleep after no messages are found
+ * * queues: The queue names to consume from, instead of consuming from all queues. When this is used, all receivers must implement the QueueReceiverInterface
+ */
+ public function run(array $options = []): void
+ {
+ $options = array_merge([
+ 'sleep' => 1000000,
+ ], $options);
+ $queueNames = $options['queues'] ?? null;
+
+ $this->metadata->set(['queueNames' => $queueNames]);
+
+ $this->dispatchEvent(new WorkerStartedEvent($this));
+
+ if ($queueNames) {
+ // if queue names are specified, all receivers must implement the QueueReceiverInterface
+ foreach ($this->receivers as $transportName => $receiver) {
+ if (!$receiver instanceof QueueReceiverInterface) {
+ throw new RuntimeException(sprintf('Receiver for "%s" does not implement "%s".', $transportName, QueueReceiverInterface::class));
+ }
+ }
+ }
+
+ while (!$this->shouldStop) {
+ $envelopeHandled = false;
+ $envelopeHandledStart = microtime(true);
+ foreach ($this->receivers as $transportName => $receiver) {
+ if ($queueNames) {
+ $envelopes = $receiver->getFromQueues($queueNames);
+ } else {
+ $envelopes = $receiver->get();
+ }
+
+ foreach ($envelopes as $envelope) {
+ $envelopeHandled = true;
+
+ $this->handleMessage($envelope, $transportName);
+ $this->dispatchEvent(new WorkerRunningEvent($this, false));
+
+ if ($this->shouldStop) {
+ break 2;
+ }
+ }
+
+ // after handling a single receiver, quit and start the loop again
+ // this should prevent multiple lower priority receivers from
+ // blocking too long before the higher priority are checked
+ if ($envelopeHandled) {
+ break;
+ }
+ }
+
+ if (!$envelopeHandled && $this->flush(false)) {
+ continue;
+ }
+
+ if (!$envelopeHandled) {
+ $this->dispatchEvent(new WorkerRunningEvent($this, true));
+
+ if (0 < $sleep = (int) ($options['sleep'] - 1e6 * (microtime(true) - $envelopeHandledStart))) {
+ usleep($sleep);
+ }
+ }
+ }
+
+ $this->flush(true);
+ $this->dispatchEvent(new WorkerStoppedEvent($this));
+ }
+
+ private function handleMessage(Envelope $envelope, string $transportName): void
+ {
+ $event = new WorkerMessageReceivedEvent($envelope, $transportName);
+ $this->dispatchEvent($event);
+ $envelope = $event->getEnvelope();
+
+ if (!$event->shouldHandle()) {
+ return;
+ }
+
+ $acked = false;
+ $ack = function (Envelope $envelope, \Throwable $e = null) use ($transportName, &$acked) {
+ $acked = true;
+ $this->acks[] = [$transportName, $envelope, $e];
+ };
+
+ try {
+ $e = null;
+ $envelope = $this->bus->dispatch($envelope->with(new ReceivedStamp($transportName), new ConsumedByWorkerStamp(), new AckStamp($ack)));
+ } catch (\Throwable $e) {
+ }
+
+ $noAutoAckStamp = $envelope->last(NoAutoAckStamp::class);
+
+ if (!$acked && !$noAutoAckStamp) {
+ $this->acks[] = [$transportName, $envelope, $e];
+ } elseif ($noAutoAckStamp) {
+ $this->unacks[$noAutoAckStamp->getHandlerDescriptor()->getBatchHandler()] = [$envelope->withoutAll(AckStamp::class), $transportName];
+ }
+
+ $this->ack();
+ }
+
+ private function ack(): bool
+ {
+ $acks = $this->acks;
+ $this->acks = [];
+
+ foreach ($acks as [$transportName, $envelope, $e]) {
+ $receiver = $this->receivers[$transportName];
+
+ if (null !== $e) {
+ if ($rejectFirst = $e instanceof RejectRedeliveredMessageException) {
+ // redelivered messages are rejected first so that continuous failures in an event listener or while
+ // publishing for retry does not cause infinite redelivery loops
+ $receiver->reject($envelope);
+ }
+
+ if ($e instanceof HandlerFailedException) {
+ $envelope = $e->getEnvelope();
+ }
+
+ $failedEvent = new WorkerMessageFailedEvent($envelope, $transportName, $e);
+
+ $this->dispatchEvent($failedEvent);
+ $envelope = $failedEvent->getEnvelope();
+
+ if (!$rejectFirst) {
+ $receiver->reject($envelope);
+ }
+
+ continue;
+ }
+
+ $handledEvent = new WorkerMessageHandledEvent($envelope, $transportName);
+ $this->dispatchEvent($handledEvent);
+ $envelope = $handledEvent->getEnvelope();
+
+ if (null !== $this->logger) {
+ $message = $envelope->getMessage();
+ $context = [
+ 'class' => \get_class($message),
+ ];
+ $this->logger->info('{class} was handled successfully (acknowledging to transport).', $context);
+ }
+
+ $receiver->ack($envelope);
+ }
+
+ return (bool) $acks;
+ }
+
+ private function flush(bool $force): bool
+ {
+ $unacks = $this->unacks;
+
+ if (!$unacks->count()) {
+ return false;
+ }
+
+ $this->unacks = new \SplObjectStorage();
+
+ foreach ($unacks as $batchHandler) {
+ [$envelope, $transportName] = $unacks[$batchHandler];
+ try {
+ $this->bus->dispatch($envelope->with(new FlushBatchHandlersStamp($force)));
+ $envelope = $envelope->withoutAll(NoAutoAckStamp::class);
+ unset($unacks[$batchHandler], $batchHandler);
+ } catch (\Throwable $e) {
+ $this->acks[] = [$transportName, $envelope, $e];
+ }
+ }
+
+ return $this->ack();
+ }
+
+ public function stop(): void
+ {
+ if (null !== $this->logger) {
+ $this->logger->info('Stopping worker.', ['transport_names' => $this->metadata->getTransportNames()]);
+ }
+
+ $this->shouldStop = true;
+ }
+
+ public function getMetadata(): WorkerMetadata
+ {
+ return $this->metadata;
+ }
+
+ private function dispatchEvent(object $event): void
+ {
+ if (null === $this->eventDispatcher) {
+ return;
+ }
+
+ $this->eventDispatcher->dispatch($event);
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/WorkerMetadata.php b/plugins/email/vendor/symfony/messenger/WorkerMetadata.php
new file mode 100644
index 0000000..0eaab06
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/WorkerMetadata.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger;
+
+/**
+ * @author Oleg Krasavin
+ */
+final class WorkerMetadata
+{
+ private $metadata;
+
+ public function __construct(array $metadata)
+ {
+ $this->metadata = $metadata;
+ }
+
+ public function set(array $newMetadata): void
+ {
+ $this->metadata = array_merge($this->metadata, $newMetadata);
+ }
+
+ /**
+ * Returns the queue names the worker consumes from, if "--queues" option was used.
+ * Returns null otherwise.
+ */
+ public function getQueueNames(): ?array
+ {
+ return $this->metadata['queueNames'] ?? null;
+ }
+
+ /**
+ * Returns an array of unique identifiers for transport receivers the worker consumes from.
+ */
+ public function getTransportNames(): array
+ {
+ return $this->metadata['transportNames'] ?? [];
+ }
+}
diff --git a/plugins/email/vendor/symfony/messenger/composer.json b/plugins/email/vendor/symfony/messenger/composer.json
new file mode 100644
index 0000000..a62537c
--- /dev/null
+++ b/plugins/email/vendor/symfony/messenger/composer.json
@@ -0,0 +1,57 @@
+{
+ "name": "symfony/messenger",
+ "type": "library",
+ "description": "Helps applications send and receive messages to/from other applications or via message queues",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Samuel Roze",
+ "email": "samuel.roze@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "psr/log": "^1|^2|^3",
+ "symfony/amqp-messenger": "^5.1|^6.0",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/doctrine-messenger": "^5.1|^6.0",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/redis-messenger": "^5.1|^6.0"
+ },
+ "require-dev": {
+ "psr/cache": "^1.0|^2.0|^3.0",
+ "symfony/console": "^5.4|^6.0",
+ "symfony/dependency-injection": "^5.3|^6.0",
+ "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
+ "symfony/http-kernel": "^4.4|^5.0|^6.0",
+ "symfony/process": "^4.4|^5.0|^6.0",
+ "symfony/property-access": "^4.4|^5.0|^6.0",
+ "symfony/routing": "^4.4|^5.0|^6.0",
+ "symfony/serializer": "^5.0|^6.0",
+ "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/stopwatch": "^4.4|^5.0|^6.0",
+ "symfony/validator": "^4.4|^5.0|^6.0"
+ },
+ "conflict": {
+ "symfony/event-dispatcher": "<4.4",
+ "symfony/framework-bundle": "<4.4",
+ "symfony/http-kernel": "<4.4",
+ "symfony/serializer": "<5.0"
+ },
+ "suggest": {
+ "enqueue/messenger-adapter": "For using the php-enqueue library as a transport."
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Messenger\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/plugins/email/vendor/symfony/mime/Address.php b/plugins/email/vendor/symfony/mime/Address.php
new file mode 100644
index 0000000..ae83efd
--- /dev/null
+++ b/plugins/email/vendor/symfony/mime/Address.php
@@ -0,0 +1,149 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime;
+
+use Egulias\EmailValidator\EmailValidator;
+use Egulias\EmailValidator\Validation\MessageIDValidation;
+use Egulias\EmailValidator\Validation\RFCValidation;
+use Symfony\Component\Mime\Encoder\IdnAddressEncoder;
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Exception\LogicException;
+use Symfony\Component\Mime\Exception\RfcComplianceException;
+
+/**
+ * @author Fabien Potencier
+ */
+final class Address
+{
+ /**
+ * A regex that matches a structure like 'Name '.
+ * It matches anything between the first < and last > as email address.
+ * This allows to use a single string to construct an Address, which can be convenient to use in
+ * config, and allows to have more readable config.
+ * This does not try to cover all edge cases for address.
+ */
+ private const FROM_STRING_PATTERN = '~(?[^<]*)<(?.*)>[^>]*~';
+
+ private static $validator;
+ private static $encoder;
+
+ private $address;
+ private $name;
+
+ public function __construct(string $address, string $name = '')
+ {
+ if (!class_exists(EmailValidator::class)) {
+ throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s"; try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class));
+ }
+
+ if (null === self::$validator) {
+ self::$validator = new EmailValidator();
+ }
+
+ $this->address = trim($address);
+ $this->name = trim(str_replace(["\n", "\r"], '', $name));
+
+ if (!self::$validator->isValid($this->address, class_exists(MessageIDValidation::class) ? new MessageIDValidation() : new RFCValidation())) {
+ throw new RfcComplianceException(sprintf('Email "%s" does not comply with addr-spec of RFC 2822.', $address));
+ }
+ }
+
+ public function getAddress(): string
+ {
+ return $this->address;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getEncodedAddress(): string
+ {
+ if (null === self::$encoder) {
+ self::$encoder = new IdnAddressEncoder();
+ }
+
+ return self::$encoder->encodeString($this->address);
+ }
+
+ public function toString(): string
+ {
+ return ($n = $this->getEncodedName()) ? $n.' <'.$this->getEncodedAddress().'>' : $this->getEncodedAddress();
+ }
+
+ public function getEncodedName(): string
+ {
+ if ('' === $this->getName()) {
+ return '';
+ }
+
+ return sprintf('"%s"', preg_replace('/"/u', '\"', $this->getName()));
+ }
+
+ /**
+ * @param Address|string $address
+ */
+ public static function create($address): self
+ {
+ if ($address instanceof self) {
+ return $address;
+ }
+
+ if (!\is_string($address)) {
+ throw new InvalidArgumentException(sprintf('An address can be an instance of Address or a string ("%s" given).', get_debug_type($address)));
+ }
+
+ if (false === strpos($address, '<')) {
+ return new self($address);
+ }
+
+ if (!preg_match(self::FROM_STRING_PATTERN, $address, $matches)) {
+ throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $address, self::class));
+ }
+
+ return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"'));
+ }
+
+ /**
+ * @param array $addresses
+ *
+ * @return Address[]
+ */
+ public static function createArray(array $addresses): array
+ {
+ $addrs = [];
+ foreach ($addresses as $address) {
+ $addrs[] = self::create($address);
+ }
+
+ return $addrs;
+ }
+
+ /**
+ * @deprecated since Symfony 5.2, use "create()" instead.
+ */
+ public static function fromString(string $string): self
+ {
+ trigger_deprecation('symfony/mime', '5.2', '"%s()" is deprecated, use "%s::create()" instead.', __METHOD__, __CLASS__);
+
+ if (!str_contains($string, '<')) {
+ return new self($string, '');
+ }
+
+ if (!preg_match(self::FROM_STRING_PATTERN, $string, $matches)) {
+ throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $string, self::class));
+ }
+
+ return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"'));
+ }
+}
diff --git a/plugins/email/vendor/symfony/mime/BodyRendererInterface.php b/plugins/email/vendor/symfony/mime/BodyRendererInterface.php
new file mode 100644
index 0000000..d692172
--- /dev/null
+++ b/plugins/email/vendor/symfony/mime/BodyRendererInterface.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime;
+
+/**
+ * @author Fabien Potencier
+ */
+interface BodyRendererInterface
+{
+ public function render(Message $message): void;
+}
diff --git a/plugins/email/vendor/symfony/mime/CHANGELOG.md b/plugins/email/vendor/symfony/mime/CHANGELOG.md
new file mode 100644
index 0000000..f272346
--- /dev/null
+++ b/plugins/email/vendor/symfony/mime/CHANGELOG.md
@@ -0,0 +1,26 @@
+CHANGELOG
+=========
+
+5.2.0
+-----
+
+ * Add support for DKIM
+ * Deprecated `Address::fromString()`, use `Address::create()` instead
+
+4.4.0
+-----
+
+ * [BC BREAK] Removed `NamedAddress` (`Address` now supports a name)
+ * Added PHPUnit constraints
+ * Added `AbstractPart::asDebugString()`
+ * Added `Address::fromString()`
+
+4.3.3
+-----
+
+ * [BC BREAK] Renamed method `Headers::getAll()` to `Headers::all()`.
+
+4.3.0
+-----
+
+ * Introduced the component as experimental
diff --git a/plugins/email/vendor/symfony/mime/CharacterStream.php b/plugins/email/vendor/symfony/mime/CharacterStream.php
new file mode 100644
index 0000000..238debd
--- /dev/null
+++ b/plugins/email/vendor/symfony/mime/CharacterStream.php
@@ -0,0 +1,218 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime;
+
+/**
+ * @author Fabien Potencier
+ * @author Xavier De Cock
+ *
+ * @internal
+ */
+final class CharacterStream
+{
+ /** Pre-computed for optimization */
+ private const UTF8_LENGTH_MAP = [
+ "\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1,
+ "\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1,
+ "\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1,
+ "\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1,
+ "\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1,
+ "\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1,
+ "\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1,
+ "\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1,
+ "\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1,
+ "\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1,
+ "\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1,
+ "\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1,
+ "\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1,
+ "\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1,
+ "\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1,
+ "\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1,
+ "\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0,
+ "\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0,
+ "\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0,
+ "\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0,
+ "\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0,
+ "\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0,
+ "\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0,
+ "\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0,
+ "\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2,
+ "\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2,
+ "\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2,
+ "\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2,
+ "\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3,
+ "\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3,
+ "\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4,
+ "\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0,
+ ];
+
+ private $data = '';
+ private $dataSize = 0;
+ private $map = [];
+ private $charCount = 0;
+ private $currentPos = 0;
+ private $fixedWidth = 0;
+
+ /**
+ * @param resource|string $input
+ */
+ public function __construct($input, ?string $charset = 'utf-8')
+ {
+ $charset = strtolower(trim($charset)) ?: 'utf-8';
+ if ('utf-8' === $charset || 'utf8' === $charset) {
+ $this->fixedWidth = 0;
+ $this->map = ['p' => [], 'i' => []];
+ } else {
+ switch ($charset) {
+ // 16 bits
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16':
+ case 'utf-16':
+ $this->fixedWidth = 2;
+ break;
+
+ // 32 bits
+ case 'ucs4':
+ case 'ucs-4':
+ case 'utf32':
+ case 'utf-32':
+ $this->fixedWidth = 4;
+ break;
+
+ // 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh,
+ // koi-?7, koi-?8-?.+, mik, (cork|t1), v?iscii
+ // and fallback
+ default:
+ $this->fixedWidth = 1;
+ }
+ }
+ if (\is_resource($input)) {
+ $blocks = 16372;
+ while (false !== $read = fread($input, $blocks)) {
+ $this->write($read);
+ }
+ } else {
+ $this->write($input);
+ }
+ }
+
+ public function read(int $length): ?string
+ {
+ if ($this->currentPos >= $this->charCount) {
+ return null;
+ }
+ $length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length;
+ if ($this->fixedWidth > 0) {
+ $len = $length * $this->fixedWidth;
+ $ret = substr($this->data, $this->currentPos * $this->fixedWidth, $len);
+ $this->currentPos += $length;
+ } else {
+ $end = $this->currentPos + $length;
+ $end = $end > $this->charCount ? $this->charCount : $end;
+ $ret = '';
+ $start = 0;
+ if ($this->currentPos > 0) {
+ $start = $this->map['p'][$this->currentPos - 1];
+ }
+ $to = $start;
+ for (; $this->currentPos < $end; ++$this->currentPos) {
+ if (isset($this->map['i'][$this->currentPos])) {
+ $ret .= substr($this->data, $start, $to - $start).'?';
+ $start = $this->map['p'][$this->currentPos];
+ } else {
+ $to = $this->map['p'][$this->currentPos];
+ }
+ }
+ $ret .= substr($this->data, $start, $to - $start);
+ }
+
+ return $ret;
+ }
+
+ public function readBytes(int $length): ?array
+ {
+ if (null !== $read = $this->read($length)) {
+ return array_map('ord', str_split($read, 1));
+ }
+
+ return null;
+ }
+
+ public function setPointer(int $charOffset): void
+ {
+ if ($this->charCount < $charOffset) {
+ $charOffset = $this->charCount;
+ }
+ $this->currentPos = $charOffset;
+ }
+
+ public function write(string $chars): void
+ {
+ $ignored = '';
+ $this->data .= $chars;
+ if ($this->fixedWidth > 0) {
+ $strlen = \strlen($chars);
+ $ignoredL = $strlen % $this->fixedWidth;
+ $ignored = $ignoredL ? substr($chars, -$ignoredL) : '';
+ $this->charCount += ($strlen - $ignoredL) / $this->fixedWidth;
+ } else {
+ $this->charCount += $this->getUtf8CharPositions($chars, $this->dataSize, $ignored);
+ }
+ $this->dataSize = \strlen($this->data) - \strlen($ignored);
+ }
+
+ private function getUtf8CharPositions(string $string, int $startOffset, string &$ignoredChars): int
+ {
+ $strlen = \strlen($string);
+ $charPos = \count($this->map['p']);
+ $foundChars = 0;
+ $invalid = false;
+ for ($i = 0; $i < $strlen; ++$i) {
+ $char = $string[$i];
+ $size = self::UTF8_LENGTH_MAP[$char];
+ if (0 == $size) {
+ /* char is invalid, we must wait for a resync */
+ $invalid = true;
+ continue;
+ }
+
+ if ($invalid) {
+ /* We mark the chars as invalid and start a new char */
+ $this->map['p'][$charPos + $foundChars] = $startOffset + $i;
+ $this->map['i'][$charPos + $foundChars] = true;
+ ++$foundChars;
+ $invalid = false;
+ }
+ if (($i + $size) > $strlen) {
+ $ignoredChars = substr($string, $i);
+ break;
+ }
+ for ($j = 1; $j < $size; ++$j) {
+ $char = $string[$i + $j];
+ if ($char > "\x7F" && $char < "\xC0") {
+ // Valid - continue parsing
+ } else {
+ /* char is invalid, we must wait for a resync */
+ $invalid = true;
+ continue 2;
+ }
+ }
+ /* Ok we got a complete char here */
+ $this->map['p'][$charPos + $foundChars] = $startOffset + $i + $size;
+ $i += $j - 1;
+ ++$foundChars;
+ }
+
+ return $foundChars;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mime/Crypto/DkimOptions.php b/plugins/email/vendor/symfony/mime/Crypto/DkimOptions.php
new file mode 100644
index 0000000..171bb25
--- /dev/null
+++ b/plugins/email/vendor/symfony/mime/Crypto/DkimOptions.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime\Crypto;
+
+/**
+ * A helper providing autocompletion for available DkimSigner options.
+ *
+ * @author Fabien Potencier
+ */
+final class DkimOptions
+{
+ private $options = [];
+
+ public function toArray(): array
+ {
+ return $this->options;
+ }
+
+ /**
+ * @return $this
+ */
+ public function algorithm(string $algo): self
+ {
+ $this->options['algorithm'] = $algo;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function signatureExpirationDelay(int $show): self
+ {
+ $this->options['signature_expiration_delay'] = $show;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function bodyMaxLength(int $max): self
+ {
+ $this->options['body_max_length'] = $max;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function bodyShowLength(bool $show): self
+ {
+ $this->options['body_show_length'] = $show;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function headerCanon(string $canon): self
+ {
+ $this->options['header_canon'] = $canon;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function bodyCanon(string $canon): self
+ {
+ $this->options['body_canon'] = $canon;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function headersToIgnore(array $headers): self
+ {
+ $this->options['headers_to_ignore'] = $headers;
+
+ return $this;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mime/Crypto/DkimSigner.php b/plugins/email/vendor/symfony/mime/Crypto/DkimSigner.php
new file mode 100644
index 0000000..f0f7091
--- /dev/null
+++ b/plugins/email/vendor/symfony/mime/Crypto/DkimSigner.php
@@ -0,0 +1,220 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime\Crypto;
+
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Exception\RuntimeException;
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\Part\AbstractPart;
+
+/**
+ * @author Fabien Potencier
+ *
+ * RFC 6376 and 8301
+ */
+final class DkimSigner
+{
+ public const CANON_SIMPLE = 'simple';
+ public const CANON_RELAXED = 'relaxed';
+
+ public const ALGO_SHA256 = 'rsa-sha256';
+ public const ALGO_ED25519 = 'ed25519-sha256'; // RFC 8463
+
+ private $key;
+ private $domainName;
+ private $selector;
+ private $defaultOptions;
+
+ /**
+ * @param string $pk The private key as a string or the path to the file containing the private key, should be prefixed with file:// (in PEM format)
+ * @param string $passphrase A passphrase of the private key (if any)
+ */
+ public function __construct(string $pk, string $domainName, string $selector, array $defaultOptions = [], string $passphrase = '')
+ {
+ if (!\extension_loaded('openssl')) {
+ throw new \LogicException('PHP extension "openssl" is required to use DKIM.');
+ }
+ if (!$this->key = openssl_pkey_get_private($pk, $passphrase)) {
+ throw new InvalidArgumentException('Unable to load DKIM private key: '.openssl_error_string());
+ }
+
+ $this->domainName = $domainName;
+ $this->selector = $selector;
+ $this->defaultOptions = $defaultOptions + [
+ 'algorithm' => self::ALGO_SHA256,
+ 'signature_expiration_delay' => 0,
+ 'body_max_length' => \PHP_INT_MAX,
+ 'body_show_length' => false,
+ 'header_canon' => self::CANON_RELAXED,
+ 'body_canon' => self::CANON_RELAXED,
+ 'headers_to_ignore' => [],
+ ];
+ }
+
+ public function sign(Message $message, array $options = []): Message
+ {
+ $options += $this->defaultOptions;
+ if (!\in_array($options['algorithm'], [self::ALGO_SHA256, self::ALGO_ED25519], true)) {
+ throw new InvalidArgumentException(sprintf('Invalid DKIM signing algorithm "%s".', $options['algorithm']));
+ }
+ $headersToIgnore['return-path'] = true;
+ $headersToIgnore['x-transport'] = true;
+ foreach ($options['headers_to_ignore'] as $name) {
+ $headersToIgnore[strtolower($name)] = true;
+ }
+ unset($headersToIgnore['from']);
+ $signedHeaderNames = [];
+ $headerCanonData = '';
+ $headers = $message->getPreparedHeaders();
+ foreach ($headers->getNames() as $name) {
+ foreach ($headers->all($name) as $header) {
+ if (isset($headersToIgnore[strtolower($header->getName())])) {
+ continue;
+ }
+
+ if ('' !== $header->getBodyAsString()) {
+ $headerCanonData .= $this->canonicalizeHeader($header->toString(), $options['header_canon']);
+ $signedHeaderNames[] = $header->getName();
+ }
+ }
+ }
+
+ [$bodyHash, $bodyLength] = $this->hashBody($message->getBody(), $options['body_canon'], $options['body_max_length']);
+
+ $params = [
+ 'v' => '1',
+ 'q' => 'dns/txt',
+ 'a' => $options['algorithm'],
+ 'bh' => base64_encode($bodyHash),
+ 'd' => $this->domainName,
+ 'h' => implode(': ', $signedHeaderNames),
+ 'i' => '@'.$this->domainName,
+ 's' => $this->selector,
+ 't' => time(),
+ 'c' => $options['header_canon'].'/'.$options['body_canon'],
+ ];
+
+ if ($options['body_show_length']) {
+ $params['l'] = $bodyLength;
+ }
+ if ($options['signature_expiration_delay']) {
+ $params['x'] = $params['t'] + $options['signature_expiration_delay'];
+ }
+ $value = '';
+ foreach ($params as $k => $v) {
+ $value .= $k.'='.$v.'; ';
+ }
+ $value = trim($value);
+ $header = new UnstructuredHeader('DKIM-Signature', $value);
+ $headerCanonData .= rtrim($this->canonicalizeHeader($header->toString()."\r\n b=", $options['header_canon']));
+ if (self::ALGO_SHA256 === $options['algorithm']) {
+ if (!openssl_sign($headerCanonData, $signature, $this->key, \OPENSSL_ALGO_SHA256)) {
+ throw new RuntimeException('Unable to sign DKIM hash: '.openssl_error_string());
+ }
+ } else {
+ throw new \RuntimeException(sprintf('The "%s" DKIM signing algorithm is not supported yet.', self::ALGO_ED25519));
+ }
+ $header->setValue($value.' b='.trim(chunk_split(base64_encode($signature), 73, ' ')));
+ $headers->add($header);
+
+ return new Message($headers, $message->getBody());
+ }
+
+ private function canonicalizeHeader(string $header, string $headerCanon): string
+ {
+ if (self::CANON_RELAXED !== $headerCanon) {
+ return $header."\r\n";
+ }
+
+ $exploded = explode(':', $header, 2);
+ $name = strtolower(trim($exploded[0]));
+ $value = str_replace("\r\n", '', $exploded[1]);
+ $value = trim(preg_replace("/[ \t][ \t]+/", ' ', $value));
+
+ return $name.':'.$value."\r\n";
+ }
+
+ private function hashBody(AbstractPart $body, string $bodyCanon, int $maxLength): array
+ {
+ $hash = hash_init('sha256');
+ $relaxed = self::CANON_RELAXED === $bodyCanon;
+ $currentLine = '';
+ $emptyCounter = 0;
+ $isSpaceSequence = false;
+ $length = 0;
+ foreach ($body->bodyToIterable() as $chunk) {
+ $canon = '';
+ for ($i = 0, $len = \strlen($chunk); $i < $len; ++$i) {
+ switch ($chunk[$i]) {
+ case "\r":
+ break;
+ case "\n":
+ // previous char is always \r
+ if ($relaxed) {
+ $isSpaceSequence = false;
+ }
+ if ('' === $currentLine) {
+ ++$emptyCounter;
+ } else {
+ $currentLine = '';
+ $canon .= "\r\n";
+ }
+ break;
+ case ' ':
+ case "\t":
+ if ($relaxed) {
+ $isSpaceSequence = true;
+ break;
+ }
+ // no break
+ default:
+ if ($emptyCounter > 0) {
+ $canon .= str_repeat("\r\n", $emptyCounter);
+ $emptyCounter = 0;
+ }
+ if ($isSpaceSequence) {
+ $currentLine .= ' ';
+ $canon .= ' ';
+ $isSpaceSequence = false;
+ }
+ $currentLine .= $chunk[$i];
+ $canon .= $chunk[$i];
+ }
+ }
+
+ if ($length + \strlen($canon) >= $maxLength) {
+ $canon = substr($canon, 0, $maxLength - $length);
+ $length += \strlen($canon);
+ hash_update($hash, $canon);
+
+ break;
+ }
+
+ $length += \strlen($canon);
+ hash_update($hash, $canon);
+ }
+
+ // Add trailing Line return if last line is non empty
+ if ('' !== $currentLine) {
+ hash_update($hash, "\r\n");
+ $length += \strlen("\r\n");
+ }
+
+ if (!$relaxed && 0 === $length) {
+ hash_update($hash, "\r\n");
+ $length = 2;
+ }
+
+ return [hash_final($hash, true), $length];
+ }
+}
diff --git a/plugins/email/vendor/symfony/mime/Crypto/SMime.php b/plugins/email/vendor/symfony/mime/Crypto/SMime.php
new file mode 100644
index 0000000..cba95f2
--- /dev/null
+++ b/plugins/email/vendor/symfony/mime/Crypto/SMime.php
@@ -0,0 +1,111 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime\Crypto;
+
+use Symfony\Component\Mime\Exception\RuntimeException;
+use Symfony\Component\Mime\Part\SMimePart;
+
+/**
+ * @author Sebastiaan Stok
+ *
+ * @internal
+ */
+abstract class SMime
+{
+ protected function normalizeFilePath(string $path): string
+ {
+ if (!file_exists($path)) {
+ throw new RuntimeException(sprintf('File does not exist: "%s".', $path));
+ }
+
+ return 'file://'.str_replace('\\', '/', realpath($path));
+ }
+
+ protected function iteratorToFile(iterable $iterator, $stream): void
+ {
+ foreach ($iterator as $chunk) {
+ fwrite($stream, $chunk);
+ }
+ }
+
+ protected function convertMessageToSMimePart($stream, string $type, string $subtype): SMimePart
+ {
+ rewind($stream);
+
+ $headers = '';
+
+ while (!feof($stream)) {
+ $buffer = fread($stream, 78);
+ $headers .= $buffer;
+
+ // Detect ending of header list
+ if (preg_match('/(\r\n\r\n|\n\n)/', $headers, $match)) {
+ $headersPosEnd = strpos($headers, $headerBodySeparator = $match[0]);
+
+ break;
+ }
+ }
+
+ $headers = $this->getMessageHeaders(trim(substr($headers, 0, $headersPosEnd)));
+
+ fseek($stream, $headersPosEnd + \strlen($headerBodySeparator));
+
+ return new SMimePart($this->getStreamIterator($stream), $type, $subtype, $this->getParametersFromHeader($headers['content-type']));
+ }
+
+ protected function getStreamIterator($stream): iterable
+ {
+ while (!feof($stream)) {
+ yield str_replace("\n", "\r\n", str_replace("\r\n", "\n", fread($stream, 16372)));
+ }
+ }
+
+ private function getMessageHeaders(string $headerData): array
+ {
+ $headers = [];
+ $headerLines = explode("\r\n", str_replace("\n", "\r\n", str_replace("\r\n", "\n", $headerData)));
+ $currentHeaderName = '';
+
+ // Transform header lines into an associative array
+ foreach ($headerLines as $headerLine) {
+ // Empty lines between headers indicate a new mime-entity
+ if ('' === $headerLine) {
+ break;
+ }
+
+ // Handle headers that span multiple lines
+ if (!str_contains($headerLine, ':')) {
+ $headers[$currentHeaderName] .= ' '.trim($headerLine);
+ continue;
+ }
+
+ $header = explode(':', $headerLine, 2);
+ $currentHeaderName = strtolower($header[0]);
+ $headers[$currentHeaderName] = trim($header[1]);
+ }
+
+ return $headers;
+ }
+
+ private function getParametersFromHeader(string $header): array
+ {
+ $params = [];
+
+ preg_match_all('/(?P[a-z-0-9]+)=(?P"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches);
+
+ foreach ($matches['value'] as $pos => $paramValue) {
+ $params[$matches['name'][$pos]] = trim($paramValue, '"');
+ }
+
+ return $params;
+ }
+}
diff --git a/plugins/email/vendor/symfony/mime/Crypto/SMimeEncrypter.php b/plugins/email/vendor/symfony/mime/Crypto/SMimeEncrypter.php
new file mode 100644
index 0000000..9081860
--- /dev/null
+++ b/plugins/email/vendor/symfony/mime/Crypto/SMimeEncrypter.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime\Crypto;
+
+use Symfony\Component\Mime\Exception\RuntimeException;
+use Symfony\Component\Mime\Message;
+
+/**
+ * @author Sebastiaan Stok