email sending library change
This commit is contained in:
@@ -192,17 +192,16 @@ class AfterFetch implements AfterFetchInterface
|
||||
}
|
||||
}
|
||||
|
||||
$message = new Message();
|
||||
$sender = $this->emailSender->create();
|
||||
|
||||
$messageId = $email->getMessageId();
|
||||
|
||||
if ($messageId) {
|
||||
$message->getHeaders()->addHeaderLine('In-Reply-To', $messageId);
|
||||
if ($email->getMessageId()) {
|
||||
$sender->withAddedHeader('In-Reply-To', $email->getMessageId());
|
||||
}
|
||||
|
||||
$message->getHeaders()->addHeaderLine('Auto-Submitted', 'auto-replied');
|
||||
$message->getHeaders()->addHeaderLine('X-Auto-Response-Suppress', 'All');
|
||||
$message->getHeaders()->addHeaderLine('Precedence', 'auto_reply');
|
||||
$sender
|
||||
->withAddedHeader('Auto-Submitted', 'auto-replied')
|
||||
->withAddedHeader('X-Auto-Response-Suppress', 'All')
|
||||
->withAddedHeader('Precedence', 'auto_reply');
|
||||
|
||||
try {
|
||||
$entityHash = [];
|
||||
@@ -268,8 +267,6 @@ class AfterFetch implements AfterFetchInterface
|
||||
|
||||
$this->entityManager->saveEntity($reply);
|
||||
|
||||
$sender = $this->emailSender->create();
|
||||
|
||||
$senderParams = SenderParams::create();
|
||||
|
||||
if ($inboundEmail->isAvailableForSending()) {
|
||||
@@ -304,13 +301,12 @@ class AfterFetch implements AfterFetchInterface
|
||||
|
||||
$sender
|
||||
->withParams($senderParams)
|
||||
->withMessage($message)
|
||||
->withAttachments($replyData->getAttachmentList())
|
||||
->send($reply);
|
||||
|
||||
$this->entityManager->saveEntity($reply);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error("Inbound Email: Auto-reply error: " . $e->getMessage());
|
||||
$this->log->error("Inbound Email: Auto-reply error: " . $e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,11 +101,23 @@ class EmailSender
|
||||
return $this->createSender()->withAttachments($attachmentList);
|
||||
}
|
||||
|
||||
/**
|
||||
* With an envelope from address.
|
||||
*
|
||||
* @since 9.1.0
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function withEnvelopeFromAddress(string $fromAddress): void
|
||||
{
|
||||
$this->createSender()->withEnvelopeFromAddress($fromAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* With envelope options.
|
||||
*
|
||||
* @param array<string, mixed> $options
|
||||
* @noinspection PhpUnused
|
||||
* @param array{from: string} $options
|
||||
* @deprecated As of v9.1.
|
||||
* @todo Remove in v10.0. Use `withEnvelopeFromAddress`.
|
||||
*/
|
||||
public function withEnvelopeOptions(array $options): Sender
|
||||
{
|
||||
@@ -114,12 +126,27 @@ class EmailSender
|
||||
|
||||
/**
|
||||
* Set a message instance.
|
||||
*
|
||||
* @deprecated As of v9.1. Use `withAddedHeader`.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function withMessage(Message $message): Sender
|
||||
{
|
||||
return $this->createSender()->withMessage($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header.
|
||||
*
|
||||
* @param string $name A header name.
|
||||
* @param string $value A header value.
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function withAddedHeader(string $name, string $value): Sender
|
||||
{
|
||||
return $this->createSender()->withAddedHeader($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether system SMTP is configured.
|
||||
*/
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Mail\Header;
|
||||
|
||||
use Laminas\Mail\Header;
|
||||
|
||||
class XQueueItemId implements Header\HeaderInterface
|
||||
{
|
||||
private string $fieldName = 'X-Queue-Item-Id';
|
||||
|
||||
private ?string $id = null;
|
||||
|
||||
/**
|
||||
* @param string $headerLine
|
||||
* @return self
|
||||
*/
|
||||
public static function fromString($headerLine)
|
||||
{
|
||||
list($name, $value) = Header\GenericHeader::splitHeaderLine($headerLine);
|
||||
|
||||
$valueDecoded = Header\HeaderWrap::mimeDecodeValue($value);
|
||||
|
||||
if (strtolower($name) !== 'x-queue-item-id') {
|
||||
throw new Header\Exception\InvalidArgumentException('Invalid header line for x-queue-item-id string');
|
||||
}
|
||||
|
||||
$header = new self();
|
||||
|
||||
$header->setId($valueDecoded);
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFieldName()
|
||||
{
|
||||
return $this->fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return void
|
||||
*/
|
||||
public function setFieldName($value)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $encoding
|
||||
* @return self
|
||||
*/
|
||||
public function setEncoding($encoding)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setId(?string $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEncoding()
|
||||
{
|
||||
return 'ASCII';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toString()
|
||||
{
|
||||
return $this->fieldName . ': ' . $this->getFieldValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $format
|
||||
* @return ?string
|
||||
*/
|
||||
public function getFieldValue($format = Header\HeaderInterface::FORMAT_RAW)
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ namespace Espo\Core\Mail;
|
||||
|
||||
use Espo\Core\FileStorage\Manager as FileStorageManager;
|
||||
use Espo\Core\Mail\Exceptions\NoSmtp;
|
||||
use Espo\Core\Mail\Smtp\TransportFactory;
|
||||
use Espo\Core\ORM\Repository\Option\SaveOption;
|
||||
use Espo\ORM\EntityCollection;
|
||||
use Espo\Core\Field\DateTime;
|
||||
@@ -43,17 +42,23 @@ use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Laminas\Mail\Header\ContentType as ContentTypeHeader;
|
||||
use Laminas\Mail\Header\MessageId as MessageIdHeader;
|
||||
use Laminas\Mail\Header\Sender as SenderHeader;
|
||||
use Laminas\Mail\Message;
|
||||
use Laminas\Mail\Protocol\Exception\RuntimeException as ProtocolRuntimeException;
|
||||
use Laminas\Mail\Transport\Envelope;
|
||||
use Laminas\Mail\Transport\Smtp as SmtpTransport;
|
||||
use Laminas\Mail\Transport\SmtpOptions;
|
||||
use Laminas\Mime\Message as MimeMessage;
|
||||
use Laminas\Mime\Mime as Mime;
|
||||
use Laminas\Mime\Part as MimePart;
|
||||
use Laminas\Mail\Headers;
|
||||
use Laminas\Mail\Message as LaminasMessage;
|
||||
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Mailer\Envelope;
|
||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
use Symfony\Component\Mailer\Transport\Dsn;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\Auth\CramMd5Authenticator;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\Auth\LoginAuthenticator;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\Auth\PlainAuthenticator;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\Auth\XOAuth2Authenticator;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Email as Message;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
@@ -63,30 +68,25 @@ use InvalidArgumentException;
|
||||
*/
|
||||
class Sender
|
||||
{
|
||||
private ?SmtpTransport $transport = null;
|
||||
private ?EsmtpTransport $transport = null;
|
||||
private bool $isGlobal = false;
|
||||
/** @var array<string, mixed> */
|
||||
private array $params = [];
|
||||
/** @var array<string, mixed> */
|
||||
private array $overrideParams = [];
|
||||
private ?Envelope $envelope = null;
|
||||
private ?Message $message = null;
|
||||
private ?string $envelopeFromAddress = null;
|
||||
private ?LaminasMessage $laminasMessage = null;
|
||||
/** @var ?iterable<Attachment> */
|
||||
private $attachmentList = null;
|
||||
/** @var array{string, string}[] */
|
||||
private array $headers = [];
|
||||
|
||||
private const ATTACHMENT_ATTR_CONTENTS = 'contents';
|
||||
|
||||
private const MESSAGE_TYPE_MULTIPART_RELATED = 'multipart/related';
|
||||
private const MESSAGE_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative';
|
||||
private const MESSAGE_TYPE_MULTIPART_MIXED = 'multipart/mixed';
|
||||
private const MESSAGE_TYPE_TEXT_PLAIN = 'text/plain';
|
||||
private const MESSAGE_TYPE_TEXT_HTML = 'text/html';
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private EntityManager $entityManager,
|
||||
private Log $log,
|
||||
private TransportFactory $transportFactory,
|
||||
private SendingAccountProvider $accountProvider,
|
||||
private FileStorageManager $fileStorageManager,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
@@ -97,10 +97,11 @@ class Sender
|
||||
private function resetParams(): void
|
||||
{
|
||||
$this->params = [];
|
||||
$this->envelope = null;
|
||||
$this->message = null;
|
||||
$this->envelopeFromAddress = null;
|
||||
$this->laminasMessage = null;
|
||||
$this->attachmentList = null;
|
||||
$this->overrideParams = [];
|
||||
$this->headers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,10 +164,22 @@ class Sender
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* With an envelope from address.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function withEnvelopeFromAddress(string $fromAddress): void
|
||||
{
|
||||
$this->envelopeFromAddress = $fromAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* With envelope options.
|
||||
*
|
||||
* @param array<string, mixed> $options
|
||||
* @param array{from: string} $options
|
||||
* @deprecated As of v9.1.
|
||||
* @todo Remove in v10.0. Use `withEnvelopeFromAddress`.
|
||||
*/
|
||||
public function withEnvelopeOptions(array $options): self
|
||||
{
|
||||
@@ -176,10 +189,27 @@ class Sender
|
||||
|
||||
/**
|
||||
* Set a message instance.
|
||||
*
|
||||
* @deprecated As of v9.1. Use `withAddedHeader`.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function withMessage(Message $message): self
|
||||
public function withMessage(LaminasMessage $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->laminasMessage = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header.
|
||||
*
|
||||
* @param string $name A header name.
|
||||
* @param string $value A header value.
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function withAddedHeader(string $name, string $value): self
|
||||
{
|
||||
$this->headers[] = [$name, $value];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -196,7 +226,6 @@ class Sender
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated As of 6.0. Use withSmtpParams.
|
||||
* @param array<string, mixed> $params
|
||||
@@ -224,53 +253,73 @@ class Sender
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
$this->transport = $this->transportFactory->create();
|
||||
|
||||
$config = $this->config;
|
||||
|
||||
$localHostName = $config->get('smtpLocalHostName', gethostname());
|
||||
|
||||
$options = [
|
||||
'name' => $localHostName,
|
||||
'host' => $params['server'],
|
||||
'port' => $params['port'],
|
||||
'connectionConfig' => [],
|
||||
];
|
||||
$security = strtolower($params['security'] ?? '');
|
||||
|
||||
// SSL is treated as implicit SSL/TLS. TLS is treated as STARTTLS.
|
||||
// STARTTLS is the most common method.
|
||||
$scheme = $security == 'ssl' ? 'smtps' : 'smtp';
|
||||
|
||||
if ($security === 'starttls' && !defined('OPENSSL_VERSION_NUMBER')) {
|
||||
throw new RuntimeException("OpenSSL is not available.");
|
||||
}
|
||||
|
||||
// @todo Use `auto_tls=false` if no security when Symfony v7.1 is installed.
|
||||
// @todo If starttls, it should be enforced.
|
||||
|
||||
$transport = (new EsmtpTransportFactory())
|
||||
->create(
|
||||
new Dsn(
|
||||
scheme: $scheme,
|
||||
host: $params['server'],
|
||||
port: $params['port'],
|
||||
)
|
||||
);
|
||||
|
||||
if (!$transport instanceof EsmtpTransport) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$this->transport = $transport;
|
||||
|
||||
$this->transport->setLocalDomain($localHostName);
|
||||
|
||||
$authMechanism = null;
|
||||
|
||||
$connectionOptions = $params['connectionOptions'] ?? [];
|
||||
$authString = $connectionOptions['authString'] ?? null;
|
||||
|
||||
foreach ($connectionOptions as $key => $value) {
|
||||
$options['connectionConfig'][$key] = $value;
|
||||
}
|
||||
if ($authString) {
|
||||
$decodedAuthString = base64_decode($authString);
|
||||
|
||||
if ($params['auth'] ?? false) {
|
||||
$authMechanism = $params['authMechanism'] ?? $params['smtpAuthMechanism'] ?? null;
|
||||
/** @noinspection RegExpRedundantEscape */
|
||||
if (preg_match("/user=(.*?)\\\1auth=Bearer (.*?)\\\1\\\1/", $decodedAuthString, $matches) !== false) {
|
||||
$username = $matches[1];
|
||||
$token = $matches[2];
|
||||
|
||||
if ($authMechanism) {
|
||||
$authMechanism = preg_replace("([.]{2,})", '', $authMechanism);
|
||||
|
||||
/** @noinspection SpellCheckingInspection */
|
||||
if (in_array($authMechanism, ['login', 'crammd5', 'plain'])) {
|
||||
$options['connectionClass'] = $authMechanism;
|
||||
} else {
|
||||
$options['connectionClass'] = 'login';
|
||||
}
|
||||
} else {
|
||||
$options['connectionClass'] = 'login';
|
||||
$this->transport->setUsername($username);
|
||||
$this->transport->setPassword($token);
|
||||
}
|
||||
|
||||
$options['connectionConfig']['username'] = $params['username'];
|
||||
$options['connectionConfig']['password'] = $params['password'] ?? null;
|
||||
$authMechanism = 'xoauth';
|
||||
} else if ($params['auth'] ?? false) {
|
||||
$authMechanism = ($params['authMechanism'] ?? $params['smtpAuthMechanism']) ?: 'login';
|
||||
|
||||
$this->transport->setUsername($params['username'] ?? '');
|
||||
$this->transport->setPassword($params['password'] ?? '');
|
||||
}
|
||||
|
||||
$authClassName = $params['authClassName'] ?? $params['smtpAuthClassName'] ?? null;
|
||||
|
||||
if ($authClassName) {
|
||||
$options['connectionClass'] = $authClassName;
|
||||
}
|
||||
|
||||
if ($params['security'] ?? null) {
|
||||
$options['connectionConfig']['ssl'] = strtolower($params['security']);
|
||||
if ($authMechanism === 'login') {
|
||||
$this->transport->setAuthenticators([new LoginAuthenticator()]);
|
||||
} else if ($authMechanism === 'crammd5') {
|
||||
$this->transport->setAuthenticators([new CramMd5Authenticator()]);
|
||||
} else if ($authMechanism === 'plain') {
|
||||
$this->transport->setAuthenticators([new PlainAuthenticator()]);
|
||||
} else if ($authMechanism === 'xoauth') {
|
||||
$this->transport->setAuthenticators([new XOAuth2Authenticator()]);
|
||||
}
|
||||
|
||||
if (array_key_exists('fromName', $params)) {
|
||||
@@ -280,14 +329,6 @@ class Sender
|
||||
if (array_key_exists('fromAddress', $params)) {
|
||||
$this->params['fromAddress'] = $params['fromAddress'];
|
||||
}
|
||||
|
||||
$this->transport->setOptions(
|
||||
new SmtpOptions($options)
|
||||
);
|
||||
|
||||
if ($this->envelope) {
|
||||
$this->transport->setEnvelope($this->envelope);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,10 +362,11 @@ class Sender
|
||||
$this->applyGlobal();
|
||||
}
|
||||
|
||||
$message = $this->message ?? new Message();
|
||||
$message = new Message();
|
||||
|
||||
$params = array_merge($this->params, $this->overrideParams);
|
||||
|
||||
$this->applyHeaders($message);
|
||||
$this->applyFrom($email, $message, $params);
|
||||
$this->applyReplyTo($email, $message, $params);
|
||||
$this->addRecipientAddresses($email, $message);
|
||||
@@ -332,13 +374,17 @@ class Sender
|
||||
$this->applyBody($email, $message);
|
||||
$this->applyMessageId($email, $message);
|
||||
|
||||
$message->setEncoding('UTF-8');
|
||||
$this->applyLaminasMessageHeaders($message);
|
||||
|
||||
assert($this->transport !== null);
|
||||
if (!$this->transport) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
$envelope = $this->prepareEnvelope($message);
|
||||
|
||||
try {
|
||||
$this->transport->send($message);
|
||||
} catch (Exception $e) {
|
||||
$this->transport->send($message, $envelope);
|
||||
} catch (Exception|TransportExceptionInterface $e) {
|
||||
$this->resetParams();
|
||||
$this->useGlobal();
|
||||
|
||||
@@ -355,9 +401,50 @@ class Sender
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MimePart[]
|
||||
* @return DataPart[]
|
||||
*/
|
||||
private function getInlineAttachmentPartList(Email $email): array
|
||||
private function getAttachmentParts(Email $email): array
|
||||
{
|
||||
/** @var EntityCollection<Attachment> $collection */
|
||||
$collection = $this->entityManager
|
||||
->getCollectionFactory()
|
||||
->create(Attachment::ENTITY_TYPE);
|
||||
|
||||
if (!$email->isNew()) {
|
||||
foreach ($email->getAttachments() as $attachment) {
|
||||
$collection[] = $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->attachmentList !== null) {
|
||||
foreach ($this->attachmentList as $attachment) {
|
||||
$collection[] = $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($collection as $attachment) {
|
||||
$contents = $attachment->has(self::ATTACHMENT_ATTR_CONTENTS) ?
|
||||
$attachment->get(self::ATTACHMENT_ATTR_CONTENTS) :
|
||||
$this->fileStorageManager->getContents($attachment);
|
||||
|
||||
$part = new DataPart(
|
||||
body: $contents,
|
||||
filename: $this->prepareAttachmentFileName($attachment),
|
||||
contentType: $attachment->getType(),
|
||||
);
|
||||
|
||||
$list[] = $part;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataPart[]
|
||||
*/
|
||||
private function getInlineAttachmentParts(Email $email): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
@@ -366,17 +453,11 @@ class Sender
|
||||
$attachment->get(self::ATTACHMENT_ATTR_CONTENTS) :
|
||||
$this->fileStorageManager->getContents($attachment);
|
||||
|
||||
$mimePart = new MimePart($contents);
|
||||
$part = (new DataPart($contents, null, $attachment->getType()))
|
||||
->asInline()
|
||||
->setContentId($attachment->getId() . '@espo');
|
||||
|
||||
$mimePart->disposition = Mime::DISPOSITION_INLINE;
|
||||
$mimePart->encoding = Mime::ENCODING_BASE64;
|
||||
$mimePart->id = $attachment->getId();
|
||||
|
||||
if ($attachment->getType()) {
|
||||
$mimePart->type = $attachment->getType();
|
||||
}
|
||||
|
||||
$list[] = $mimePart;
|
||||
$list[] = $part;
|
||||
}
|
||||
|
||||
return $list;
|
||||
@@ -385,20 +466,21 @@ class Sender
|
||||
/**
|
||||
* @throws SendingError
|
||||
*/
|
||||
private function handleException(Exception $e): never
|
||||
private function handleException(Exception|TransportExceptionInterface $e): never
|
||||
{
|
||||
if ($e instanceof ProtocolRuntimeException) {
|
||||
if ($e instanceof TransportExceptionInterface) {
|
||||
$message = "unknownError";
|
||||
|
||||
if (
|
||||
stripos($e->getMessage(), 'password') !== false ||
|
||||
stripos($e->getMessage(), 'credentials') !== false ||
|
||||
stripos($e->getMessage(), '5.7.8') !== false
|
||||
stripos($e->getMessage(), '5.7.8') !== false ||
|
||||
stripos($e->getMessage(), '5.7.3') !== false
|
||||
) {
|
||||
$message = 'invalidCredentials';
|
||||
}
|
||||
|
||||
$this->log->error("Email sending error: " . $e->getMessage());
|
||||
$this->log->error("Email sending error: " . $e->getMessage(), ['exception' => $e]);
|
||||
|
||||
throw new SendingError($message);
|
||||
}
|
||||
@@ -417,14 +499,14 @@ class Sender
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v6.0. Use withEnvelopeOptions.
|
||||
* @deprecated As of v6.0.
|
||||
*
|
||||
* @param array<string, mixed> $options
|
||||
* @todo Remove in v10.0.
|
||||
* @param array{from: string} $options
|
||||
* @todo Make private in v10.0. Use `withEnvelopeFromAddress`.
|
||||
*/
|
||||
public function setEnvelopeOptions(array $options): self
|
||||
{
|
||||
$this->envelope = new Envelope($options);
|
||||
$this->envelopeFromAddress = $options['from'];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -464,50 +546,7 @@ class Sender
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MimePart[]
|
||||
*/
|
||||
private function getAttachmentParts(Email $email): array
|
||||
{
|
||||
/** @var EntityCollection<Attachment> $collection */
|
||||
$collection = $this->entityManager
|
||||
->getCollectionFactory()
|
||||
->create(Attachment::ENTITY_TYPE);
|
||||
|
||||
if (!$email->isNew()) {
|
||||
foreach ($email->getAttachments() as $attachment) {
|
||||
$collection[] = $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->attachmentList !== null) {
|
||||
foreach ($this->attachmentList as $attachment) {
|
||||
$collection[] = $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
$attachmentPartList = [];
|
||||
|
||||
foreach ($collection as $attachment) {
|
||||
$contents = $attachment->has(self::ATTACHMENT_ATTR_CONTENTS) ?
|
||||
$attachment->get(self::ATTACHMENT_ATTR_CONTENTS) :
|
||||
$this->fileStorageManager->getContents($attachment);
|
||||
|
||||
$mimePart = new MimePart($contents);
|
||||
|
||||
$mimePart->disposition = Mime::DISPOSITION_ATTACHMENT;
|
||||
$mimePart->encoding = Mime::ENCODING_BASE64;
|
||||
$mimePart->filename = $this->prepareAttachmentFileName($attachment);
|
||||
|
||||
if ($attachment->getType()) {
|
||||
$mimePart->type = $attachment->getType();
|
||||
}
|
||||
|
||||
$attachmentPartList[] = $mimePart;
|
||||
}
|
||||
|
||||
return $attachmentPartList;
|
||||
}
|
||||
|
||||
private function prepareAttachmentFileName(mixed $attachment): string
|
||||
{
|
||||
@@ -541,7 +580,7 @@ class Sender
|
||||
$email->setFromAddress($fromAddress);
|
||||
}
|
||||
|
||||
$message->addFrom($fromAddress, $fromName);
|
||||
$message->addFrom(new Address($fromAddress, $fromName ?? ''));
|
||||
|
||||
$fromString = '<' . $fromAddress . '>';
|
||||
|
||||
@@ -551,11 +590,7 @@ class Sender
|
||||
|
||||
$email->set('fromString', $fromString);
|
||||
|
||||
$senderHeader = new SenderHeader();
|
||||
|
||||
$senderHeader->setAddress($fromAddress);
|
||||
|
||||
$message->getHeaders()->addHeader($senderHeader);
|
||||
$message->sender($fromAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -570,7 +605,7 @@ class Sender
|
||||
return;
|
||||
}
|
||||
|
||||
$message->setReplyTo($address, $name);
|
||||
$message->replyTo(new Address($address, $name ?? ''));
|
||||
|
||||
$email->setReplyToAddressList([$address]);
|
||||
}
|
||||
@@ -595,128 +630,75 @@ class Sender
|
||||
$messageId = substr($messageId, 1, strlen($messageId) - 2);
|
||||
}
|
||||
|
||||
$header = (new MessageIdHeader())->setId($messageId);
|
||||
|
||||
$message->getHeaders()->addHeader($header);
|
||||
$message->getHeaders()->addIdHeader('Message-ID', $messageId);
|
||||
}
|
||||
|
||||
private function applyBody(Email $email, Message $message): void
|
||||
{
|
||||
$attachmentPartList = $this->getAttachmentParts($email);
|
||||
$inlineAttachmentPartList = $this->getInlineAttachmentPartList($email);
|
||||
$message->text($email->getBodyPlainForSending());
|
||||
|
||||
$body = new MimeMessage();
|
||||
|
||||
$textPart = (new MimePart($email->getBodyPlainForSending()))
|
||||
->setType(self::MESSAGE_TYPE_TEXT_PLAIN)
|
||||
->setEncoding(Mime::ENCODING_QUOTEDPRINTABLE)
|
||||
->setCharset('utf-8');
|
||||
|
||||
$htmlPart = $email->isHtml() ?
|
||||
(new MimePart($email->getBodyForSending()))
|
||||
->setEncoding(Mime::ENCODING_QUOTEDPRINTABLE)
|
||||
->setType(self::MESSAGE_TYPE_TEXT_HTML)
|
||||
->setCharset('utf-8') :
|
||||
null;
|
||||
|
||||
$messageType = null;
|
||||
|
||||
$hasAttachments = count($attachmentPartList) !== 0;
|
||||
$hasInlineAttachments = count($inlineAttachmentPartList) !== 0;
|
||||
|
||||
if ($hasAttachments || $hasInlineAttachments) {
|
||||
if ($htmlPart) {
|
||||
$messageType = self::MESSAGE_TYPE_MULTIPART_MIXED;
|
||||
|
||||
$alternative = (new MimeMessage())
|
||||
->addPart($textPart)
|
||||
->addPart($htmlPart);
|
||||
|
||||
$alternativePart = (new MimePart($alternative->generateMessage()))
|
||||
->setType(self::MESSAGE_TYPE_MULTIPART_ALTERNATIVE)
|
||||
->setBoundary($alternative->getMime()->boundary());
|
||||
|
||||
if ($hasInlineAttachments && $hasAttachments) {
|
||||
$related = (new MimeMessage())->addPart($alternativePart);
|
||||
|
||||
foreach ($inlineAttachmentPartList as $attachmentPart) {
|
||||
$related->addPart($attachmentPart);
|
||||
}
|
||||
|
||||
$body->addPart(
|
||||
(new MimePart($related->generateMessage()))
|
||||
->setType(self::MESSAGE_TYPE_MULTIPART_RELATED)
|
||||
->setBoundary($related->getMime()->boundary())
|
||||
);
|
||||
}
|
||||
|
||||
if ($hasInlineAttachments && !$hasAttachments) {
|
||||
$messageType = self::MESSAGE_TYPE_MULTIPART_RELATED;
|
||||
|
||||
$body->addPart($alternativePart);
|
||||
|
||||
foreach ($inlineAttachmentPartList as $attachmentPart) {
|
||||
$body->addPart($attachmentPart);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasInlineAttachments) {
|
||||
$body->addPart($alternativePart);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$htmlPart) {
|
||||
$messageType = self::MESSAGE_TYPE_MULTIPART_RELATED;
|
||||
|
||||
$body->addPart($textPart);
|
||||
|
||||
foreach ($inlineAttachmentPartList as $attachmentPart) {
|
||||
$body->addPart($attachmentPart);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($attachmentPartList as $attachmentPart) {
|
||||
$body->addPart($attachmentPart);
|
||||
}
|
||||
} else {
|
||||
if ($email->isHtml()) {
|
||||
$body->setParts([$textPart, $htmlPart]);
|
||||
|
||||
$messageType = self::MESSAGE_TYPE_MULTIPART_ALTERNATIVE;
|
||||
} else {
|
||||
$body = $email->getBodyPlainForSending();
|
||||
|
||||
$messageType = self::MESSAGE_TYPE_TEXT_PLAIN;
|
||||
}
|
||||
if ($email->isHtml()) {
|
||||
$message->html($email->getBodyForSending());
|
||||
}
|
||||
|
||||
$message->setBody($body);
|
||||
|
||||
if ($messageType === self::MESSAGE_TYPE_TEXT_PLAIN) {
|
||||
if ($message->getHeaders()->has('content-type')) {
|
||||
$message->getHeaders()->removeHeader('content-type');
|
||||
}
|
||||
|
||||
$message->getHeaders()->addHeaderLine('Content-Type', 'text/plain; charset=UTF-8');
|
||||
|
||||
return;
|
||||
foreach ($this->getAttachmentParts($email) as $part) {
|
||||
$message->addPart($part);
|
||||
}
|
||||
|
||||
if (!$message->getHeaders()->has('content-type')) {
|
||||
$contentTypeHeader = new ContentTypeHeader();
|
||||
|
||||
$message->getHeaders()->addHeader($contentTypeHeader);
|
||||
foreach ($this->getInlineAttachmentParts($email) as $part) {
|
||||
$message->addPart($part);
|
||||
}
|
||||
|
||||
$contentTypeHeader = $message->getHeaders()->get('content-type');
|
||||
|
||||
assert($contentTypeHeader instanceof ContentTypeHeader);
|
||||
|
||||
$contentTypeHeader->setType($messageType);
|
||||
}
|
||||
|
||||
private function applySubject(Email $email, Message $message): void
|
||||
{
|
||||
$message->setSubject($email->getSubject() ?? '');
|
||||
$message->subject($email->getSubject() ?? '');
|
||||
}
|
||||
|
||||
private function applyHeaders(Message $message): void
|
||||
{
|
||||
foreach ($this->headers as $item) {
|
||||
$message->getHeaders()->addTextHeader($item[0], $item[1]);
|
||||
}
|
||||
|
||||
if ($this->laminasMessage) {
|
||||
// For bc.
|
||||
foreach ($this->laminasMessage->getHeaders() as $it) {
|
||||
if ($it->getFieldName() === 'Date') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$message->getHeaders()->addTextHeader($it->getFieldName(), $it->getFieldValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function prepareEnvelope(Message $message): ?Envelope
|
||||
{
|
||||
if (!$this->envelopeFromAddress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$recipients = [
|
||||
...$message->getTo(),
|
||||
...$message->getCc(),
|
||||
...$message->getBcc(),
|
||||
];
|
||||
|
||||
return new Envelope(new Address($this->envelopeFromAddress), $recipients);
|
||||
}
|
||||
|
||||
private function applyLaminasMessageHeaders(Message $message): void
|
||||
{
|
||||
if (!$this->laminasMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @noinspection PhpMultipleClassDeclarationsInspection */
|
||||
$this->laminasMessage
|
||||
->setHeaders(
|
||||
Headers::fromString($message->getPreparedHeaders()->toString())
|
||||
)
|
||||
->setBody($message->getBody()->toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,8 @@ class SmtpParams
|
||||
|
||||
/**
|
||||
* @return ?array<string, mixed>
|
||||
* @deprecated As of v9.1.0.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function getConnectionOptions(): ?array
|
||||
{
|
||||
@@ -209,6 +211,8 @@ class SmtpParams
|
||||
|
||||
/**
|
||||
* @param ?array<string, mixed> $connectionOptions
|
||||
* @deprecated As of v9.1.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function withConnectionOptions(?array $connectionOptions): self
|
||||
{
|
||||
|
||||
@@ -253,10 +253,11 @@ class Email extends Entity
|
||||
|
||||
foreach ($attachmentList as $attachment) {
|
||||
$id = $attachment->getId();
|
||||
$partId = $id . '@espo';
|
||||
|
||||
$body = str_replace(
|
||||
"\"?entryPoint=attachment&id=$id\"",
|
||||
"\"cid:$id\"",
|
||||
"\"cid:$partId\"",
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,11 +29,10 @@
|
||||
|
||||
namespace Espo\Modules\Crm\Tools\MassEmail;
|
||||
|
||||
use Espo\Core\Mail\Mail\Header\XQueueItemId;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Modules\Crm\Entities\Campaign;
|
||||
use Espo\Modules\Crm\Tools\MassEmail\MessagePreparator\Data;
|
||||
use Laminas\Mail\Headers;
|
||||
use Espo\Modules\Crm\Tools\MassEmail\MessagePreparator\Headers;
|
||||
|
||||
class DefaultMessageHeadersPreparator implements MessageHeadersPreparator
|
||||
{
|
||||
@@ -42,13 +41,8 @@ class DefaultMessageHeadersPreparator implements MessageHeadersPreparator
|
||||
|
||||
public function prepare(Headers $headers, Data $data): void
|
||||
{
|
||||
$id = $data->getId();
|
||||
|
||||
$header = new XQueueItemId();
|
||||
$header->setId($id);
|
||||
|
||||
$headers->addHeader($header);
|
||||
$headers->addHeaderLine('Precedence', 'bulk');
|
||||
$headers->addTextHeader('X-Queue-Item-Id', $data->getId());
|
||||
$headers->addTextHeader('Precedence', 'bulk');
|
||||
|
||||
$this->addMandatoryOptOut($headers, $data);
|
||||
}
|
||||
@@ -76,7 +70,7 @@ class DefaultMessageHeadersPreparator implements MessageHeadersPreparator
|
||||
|
||||
$url = "{$this->getSiteUrl()}/api/v1/Campaign/unsubscribe/$id";
|
||||
|
||||
$headers->addHeaderLine('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click');
|
||||
$headers->addHeaderLine('List-Unsubscribe', "<$url>");
|
||||
$headers->addTextHeader('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click');
|
||||
$headers->addTextHeader('List-Unsubscribe', "<$url>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
namespace Espo\Modules\Crm\Tools\MassEmail;
|
||||
|
||||
use Espo\Modules\Crm\Tools\MassEmail\MessagePreparator\Data;
|
||||
use Laminas\Mail\Headers;
|
||||
use Espo\Modules\Crm\Tools\MassEmail\MessagePreparator\Headers;
|
||||
|
||||
/**
|
||||
* Applies additional headers to a mass email message.
|
||||
|
||||
+9
-5
@@ -27,14 +27,18 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Smtp;
|
||||
namespace Espo\Modules\Crm\Tools\MassEmail\MessagePreparator;
|
||||
|
||||
use Laminas\Mail\Transport\Smtp as SmtpTransport;
|
||||
use Espo\Core\Mail\Sender;
|
||||
|
||||
class TransportFactory
|
||||
class Headers
|
||||
{
|
||||
public function create(): SmtpTransport
|
||||
public function __construct(
|
||||
private Sender $sender,
|
||||
) {}
|
||||
|
||||
public function addTextHeader(string $name, string $value): void
|
||||
{
|
||||
return new SmtpTransport();
|
||||
$this->sender->withAddedHeader($name, $value);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Modules\Crm\Tools\MassEmail;
|
||||
|
||||
use Espo\Modules\Crm\Tools\MassEmail\MessagePreparator\Headers;
|
||||
use Laminas\Mail\Message;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
@@ -193,13 +194,14 @@ class SendingProcessor
|
||||
private function prepareQueueItemMessage(
|
||||
EmailQueueItem $queueItem,
|
||||
Sender $sender,
|
||||
Message $message,
|
||||
SenderParams $senderParams
|
||||
SenderParams $senderParams,
|
||||
): void {
|
||||
|
||||
$id = $queueItem->getId();
|
||||
|
||||
$this->headersPreparator->prepare($message->getHeaders(), new Data($id, $senderParams, $queueItem));
|
||||
$headers = new Headers($sender);
|
||||
|
||||
$this->headersPreparator->prepare($headers, new Data($id, $senderParams, $queueItem));
|
||||
|
||||
$fromAddress = $senderParams->getFromAddress();
|
||||
|
||||
@@ -211,7 +213,7 @@ class SendingProcessor
|
||||
$bounceAddress = explode('@', $fromAddress)[0] . '+bounce-qid-' . $id .
|
||||
'@' . explode('@', $fromAddress)[1];
|
||||
|
||||
$sender->withEnvelopeOptions(['from' => $bounceAddress]);
|
||||
$sender->withEnvelopeFromAddress($bounceAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,14 +302,11 @@ class SendingProcessor
|
||||
$sender->withSmtpParams($smtpParams);
|
||||
}
|
||||
|
||||
$message = new Message();
|
||||
|
||||
try {
|
||||
$this->prepareQueueItemMessage($queueItem, $sender, $message, $senderParams);
|
||||
$this->prepareQueueItemMessage($queueItem, $sender, $senderParams);
|
||||
|
||||
$sender
|
||||
->withParams($senderParams)
|
||||
->withMessage($message)
|
||||
->withAttachments($attachmentList)
|
||||
->send($email);
|
||||
} catch (Exception $e) {
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
"plain": "PLAIN",
|
||||
"login": "LOGIN",
|
||||
"crammd5": "CRAM-MD5"
|
||||
},
|
||||
"smtpSecurity": {
|
||||
"SSL": "SSL/TLS",
|
||||
"TLS": "STARTTLS"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
|
||||
@@ -83,6 +83,10 @@
|
||||
"plain": "PLAIN",
|
||||
"login": "LOGIN",
|
||||
"crammd5": "CRAM-MD5"
|
||||
},
|
||||
"smtpSecurity": {
|
||||
"SSL": "SSL/TLS",
|
||||
"TLS": "STARTTLS"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
|
||||
@@ -202,6 +202,10 @@
|
||||
"Totp": "TOTP",
|
||||
"Email": "Email",
|
||||
"Sms": "SMS"
|
||||
},
|
||||
"smtpSecurity": {
|
||||
"SSL": "SSL/TLS",
|
||||
"TLS": "STARTTLS"
|
||||
}
|
||||
},
|
||||
"tooltips": {
|
||||
|
||||
@@ -48,6 +48,7 @@ use Espo\Core\Mail\ConfigDataProvider;
|
||||
use Espo\Core\Mail\EmailSender;
|
||||
use Espo\Core\Mail\Exceptions\NoSmtp;
|
||||
use Espo\Core\Mail\Exceptions\SendingError;
|
||||
use Espo\Core\Mail\Sender;
|
||||
use Espo\Core\Mail\SenderParams;
|
||||
use Espo\Core\Mail\Smtp\HandlerProcessor;
|
||||
use Espo\Core\Mail\SmtpParams;
|
||||
@@ -130,7 +131,7 @@ class SendService
|
||||
$systemFromName = $this->config->get('outboundEmailFromName');
|
||||
$systemFromAddress = $this->configDataProvider->getSystemOutboundAddress();
|
||||
|
||||
$emailSender = $this->emailSender->create();
|
||||
$sender = $this->emailSender->create();
|
||||
|
||||
$userAddressList = [];
|
||||
|
||||
@@ -167,14 +168,14 @@ class SendService
|
||||
}
|
||||
|
||||
if ($user && $smtpParams) {
|
||||
$emailSender->withSmtpParams($smtpParams);
|
||||
$sender->withSmtpParams($smtpParams);
|
||||
}
|
||||
|
||||
if (!$smtpParams) {
|
||||
[$smtpParams, $groupAccount] = $this->getGroupAccount($user, $originalFromAddress);
|
||||
|
||||
if ($smtpParams) {
|
||||
$emailSender->withSmtpParams($smtpParams);
|
||||
$sender->withSmtpParams($smtpParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,19 +214,25 @@ class SendService
|
||||
|
||||
$message = new Message();
|
||||
|
||||
$this->applyReplied($entity, $message);
|
||||
if (
|
||||
$groupAccount instanceof GroupAccount && $groupAccount->storeSentEmails() ||
|
||||
$personalAccount instanceof PersonalAccount && $personalAccount->storeSentEmails()
|
||||
) {
|
||||
$sender->withMessage($message);
|
||||
}
|
||||
|
||||
$this->applyReplied($entity, $sender);
|
||||
|
||||
$sender->withParams($params);
|
||||
|
||||
try {
|
||||
$emailSender
|
||||
->withParams($params)
|
||||
->withMessage($message)
|
||||
->send($entity);
|
||||
$sender->send($entity);
|
||||
} catch (Exception $e) {
|
||||
$entity->setStatus(Email::STATUS_DRAFT);
|
||||
|
||||
$this->entityManager->saveEntity($entity, [SaveOption::SILENT => true]);
|
||||
|
||||
$this->log->error("Email sending:" . $e->getMessage() . "; " . $e->getCode());
|
||||
$this->log->error("Email sending: " . $e->getMessage(), ['exception' => $e]);
|
||||
|
||||
$errorData = [
|
||||
'id' => $entity->getId(),
|
||||
@@ -569,13 +576,13 @@ class SendService
|
||||
return $this->config->get('smtpPassword');
|
||||
}
|
||||
|
||||
private function applyReplied(Email $entity, Message $message): void
|
||||
private function applyReplied(Email $entity, Sender $sender): void
|
||||
{
|
||||
$replied = $entity->getReplied();
|
||||
|
||||
if ($replied && $replied->getMessageId()) {
|
||||
$message->getHeaders()->addHeaderLine('In-Reply-To', $replied->getMessageId());
|
||||
$message->getHeaders()->addHeaderLine('References', $replied->getMessageId());
|
||||
$sender->withAddedHeader('In-Reply-To', $replied->getMessageId());
|
||||
$sender->withAddedHeader('References', $replied->getMessageId());
|
||||
}
|
||||
|
||||
if ($replied && $replied->getGroupFolder()) {
|
||||
|
||||
+2
-1
@@ -52,7 +52,8 @@
|
||||
"ext-ctype": "*",
|
||||
"lasserafn/php-initial-avatar-generator": "^4.4",
|
||||
"tholu/php-cidr-match": "^0.4",
|
||||
"league/oauth2-client": "^2.8"
|
||||
"league/oauth2-client": "^2.8",
|
||||
"symfony/mailer": "^6"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5",
|
||||
|
||||
Generated
+515
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6403890abb22db2e1629883a09a3c6c7",
|
||||
"content-hash": "cca59898a92b6c9f0723465a59dd8c9f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -964,6 +964,83 @@
|
||||
],
|
||||
"time": "2024-05-22T20:47:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/lexer.git",
|
||||
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
|
||||
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"psalm/plugin-phpunit": "^0.18.3",
|
||||
"vimeo/psalm": "^5.21"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\Lexer\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
|
||||
"keywords": [
|
||||
"annotations",
|
||||
"docblock",
|
||||
"lexer",
|
||||
"parser",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/lexer/issues",
|
||||
"source": "https://github.com/doctrine/lexer/tree/3.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-05T11:56:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/dompdf",
|
||||
"version": "v3.0.0",
|
||||
@@ -1184,6 +1261,73 @@
|
||||
],
|
||||
"time": "2024-10-09T13:47:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
"version": "4.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/egulias/EmailValidator.git",
|
||||
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
|
||||
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/lexer": "^2.0 || ^3.0",
|
||||
"php": ">=8.1",
|
||||
"symfony/polyfill-intl-idn": "^1.26"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.2",
|
||||
"vimeo/psalm": "^5.12"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Egulias\\EmailValidator\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Eduardo Gulias Davis"
|
||||
}
|
||||
],
|
||||
"description": "A library for validating emails against several RFCs",
|
||||
"homepage": "https://github.com/egulias/EmailValidator",
|
||||
"keywords": [
|
||||
"email",
|
||||
"emailvalidation",
|
||||
"emailvalidator",
|
||||
"validation",
|
||||
"validator"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/egulias/EmailValidator/issues",
|
||||
"source": "https://github.com/egulias/EmailValidator/tree/4.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/egulias",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-06T22:45:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "evenement/evenement",
|
||||
"version": "v3.0.2",
|
||||
@@ -4498,6 +4642,56 @@
|
||||
},
|
||||
"time": "2021-11-05T16:50:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/event-dispatcher",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/event-dispatcher.git",
|
||||
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
|
||||
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\EventDispatcher\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Standard interfaces for event handling.",
|
||||
"keywords": [
|
||||
"events",
|
||||
"psr",
|
||||
"psr-14"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/event-dispatcher/issues",
|
||||
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
|
||||
},
|
||||
"time": "2019-01-08T18:20:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.3",
|
||||
@@ -5936,6 +6130,162 @@
|
||||
],
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v7.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1",
|
||||
"reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/event-dispatcher-contracts": "^2.5|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/dependency-injection": "<6.4",
|
||||
"symfony/service-contracts": "<2.5"
|
||||
},
|
||||
"provide": {
|
||||
"psr/event-dispatcher-implementation": "1.0",
|
||||
"symfony/event-dispatcher-implementation": "2.0|3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/config": "^6.4|^7.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0",
|
||||
"symfony/error-handler": "^6.4|^7.0",
|
||||
"symfony/expression-language": "^6.4|^7.0",
|
||||
"symfony/http-foundation": "^6.4|^7.0",
|
||||
"symfony/service-contracts": "^2.5|^3",
|
||||
"symfony/stopwatch": "^6.4|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\EventDispatcher\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-25T14:21:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
"version": "v3.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
|
||||
"reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f",
|
||||
"reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"psr/event-dispatcher": "^1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/contracts",
|
||||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.5-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Contracts\\EventDispatcher\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Generic abstractions related to dispatching event",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"abstractions",
|
||||
"contracts",
|
||||
"decoupling",
|
||||
"interfaces",
|
||||
"interoperability",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v7.2.0",
|
||||
@@ -6186,6 +6536,170 @@
|
||||
],
|
||||
"time": "2025-01-09T15:48:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mailer",
|
||||
"version": "v6.4.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mailer.git",
|
||||
"reference": "e93a6ae2767d7f7578c2b7961d9d8e27580b2b11"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mailer/zipball/e93a6ae2767d7f7578c2b7961d9d8e27580b2b11",
|
||||
"reference": "e93a6ae2767d7f7578c2b7961d9d8e27580b2b11",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"egulias/email-validator": "^2.1.10|^3|^4",
|
||||
"php": ">=8.1",
|
||||
"psr/event-dispatcher": "^1",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
|
||||
"symfony/mime": "^6.2|^7.0",
|
||||
"symfony/service-contracts": "^2.5|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-client-contracts": "<2.5",
|
||||
"symfony/http-kernel": "<5.4",
|
||||
"symfony/messenger": "<6.2",
|
||||
"symfony/mime": "<6.2",
|
||||
"symfony/twig-bridge": "<6.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-client": "^5.4|^6.0|^7.0",
|
||||
"symfony/messenger": "^6.2|^7.0",
|
||||
"symfony/twig-bridge": "^6.2|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Mailer\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Helps sending emails",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mailer/tree/v6.4.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-24T15:27:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"version": "v7.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mime.git",
|
||||
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b",
|
||||
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/polyfill-intl-idn": "^1.10",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"egulias/email-validator": "~3.0.0",
|
||||
"phpdocumentor/reflection-docblock": "<3.2.2",
|
||||
"phpdocumentor/type-resolver": "<1.4.0",
|
||||
"symfony/mailer": "<6.4",
|
||||
"symfony/serializer": "<6.4.3|>7.0,<7.0.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"egulias/email-validator": "^2.1.10|^3.1|^4",
|
||||
"league/html-to-markdown": "^5.0",
|
||||
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0",
|
||||
"symfony/process": "^6.4|^7.0",
|
||||
"symfony/property-access": "^6.4|^7.0",
|
||||
"symfony/property-info": "^6.4|^7.0",
|
||||
"symfony/serializer": "^6.4.3|^7.0.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Mime\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Allows manipulating MIME messages",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"mime",
|
||||
"mime-type"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mime/tree/v7.2.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-19T08:51:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-iconv",
|
||||
"version": "v1.31.0",
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\Core\Mail;
|
||||
|
||||
use Laminas\Mail\Transport\Smtp as SmtpTransport;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Core\FileStorage\Manager;
|
||||
use Espo\Core\Mail\Account\Account;
|
||||
use Espo\Core\Mail\Account\SendingAccountProvider;
|
||||
use Espo\Core\Mail\ConfigDataProvider;
|
||||
use Espo\Core\Mail\EmailSender;
|
||||
use Espo\Core\Mail\Sender;
|
||||
use Espo\Core\Mail\Smtp\TransportFactory;
|
||||
use Espo\Core\Mail\SmtpParams;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Log;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class EmailSenderTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->config = $this->createMock(Config::class);
|
||||
$entityManager = $this->createMock(EntityManager::class);
|
||||
$injectableFactory = $this->createMock(InjectableFactory::class);
|
||||
$transportFactory = $this->createMock(TransportFactory::class);
|
||||
$this->transport = $this->createMock(SmtpTransport::class);
|
||||
|
||||
$accountProvider = $this->createMock(SendingAccountProvider::class);
|
||||
$log = $this->createMock(Log::class);
|
||||
|
||||
$emailSender = new EmailSender(
|
||||
$this->config,
|
||||
$accountProvider,
|
||||
$injectableFactory
|
||||
);
|
||||
|
||||
$configDataProvider = $this->createMock(ConfigDataProvider::class);
|
||||
|
||||
$sender = new Sender(
|
||||
$this->config,
|
||||
$entityManager,
|
||||
$log,
|
||||
$transportFactory,
|
||||
$accountProvider,
|
||||
$this->createMock(Manager::class),
|
||||
$configDataProvider
|
||||
);
|
||||
|
||||
$this->emailSender = $emailSender;
|
||||
|
||||
$injectableFactory
|
||||
->expects($this->any())
|
||||
->method('createWithBinding')
|
||||
->willReturn($sender);
|
||||
|
||||
$transportFactory
|
||||
->expects($this->any())
|
||||
->method('create')
|
||||
->willReturn($this->transport);
|
||||
|
||||
|
||||
$account = $this->createMock(Account::class);
|
||||
|
||||
$account
|
||||
->expects($this->once())
|
||||
->method('getSmtpParams')
|
||||
->willReturn(
|
||||
SmtpParams::create('test-server', 85)
|
||||
);
|
||||
|
||||
$accountProvider
|
||||
->expects($this->once())
|
||||
->method('getSystem')
|
||||
->willReturn($account);
|
||||
|
||||
$configDataProvider
|
||||
->expects($this->any())
|
||||
->method('getSystemOutboundAddress')
|
||||
->willReturn(null);
|
||||
}
|
||||
|
||||
protected function createEmail(array $data) : Email
|
||||
{
|
||||
$email = $this->getMockBuilder(Email::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$email
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->will(
|
||||
$this->returnCallback(
|
||||
function ($name) use ($data) {
|
||||
return $data[$name] ?? null;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
$email
|
||||
->expects($this->any())
|
||||
->method('getBodyPlainForSending')
|
||||
->willReturn('test');
|
||||
|
||||
$email
|
||||
->expects($this->any())
|
||||
->method('isNew')
|
||||
->willReturn(true);
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
public function testSend1()
|
||||
{
|
||||
$email = $this->createEmail([
|
||||
'name' => 'test',
|
||||
'from' => 'test@tester.com',
|
||||
]);
|
||||
|
||||
$this->transport
|
||||
->expects($this->once())
|
||||
->method('send');
|
||||
|
||||
$this->emailSender->send($email);
|
||||
}
|
||||
}
|
||||
@@ -473,7 +473,7 @@ class EmailTest extends \PHPUnit\Framework\TestCase
|
||||
->will($this->returnValue($attachment));
|
||||
|
||||
$body = $this->email->getBodyForSending();
|
||||
$this->assertEquals('test <img src="cid:Id01">', $body);
|
||||
$this->assertEquals('test <img src="cid:Id01@espo">', $body);
|
||||
}
|
||||
|
||||
public function testBodyPlain(): void
|
||||
|
||||
Reference in New Issue
Block a user