mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-03 02:27:01 +00:00
Merge pull request #3536 from espocrm/imap-migration
Migrate IMAP lib from Laminas to ImapEngine
This commit is contained in:
@@ -2,19 +2,11 @@
|
||||
|
||||
namespace Espo\Core\Mail\Account;
|
||||
|
||||
use DirectoryTree\ImapEngine\Mailbox;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Mail\Account\Storage\DirectoryTreeStorage;
|
||||
use Espo\Core\Mail\Account\Storage\Handler;
|
||||
use Espo\Core\Mail\Account\Storage\LaminasStorage;
|
||||
use Espo\Core\Mail\Account\Storage\Params;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Mail\Storage\Imap;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Laminas\Mail\Protocol\Exception\ExceptionInterface;
|
||||
use Laminas\Mail\Protocol\Exception\RuntimeException as ProtocolRuntimeException;
|
||||
use Laminas\Mail\Protocol\Imap as ImapProtocol;
|
||||
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
|
||||
use Laminas\Mail\Storage\Exception\RuntimeException as LaminasRuntimeException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
@@ -23,124 +15,40 @@ class CommonStorageFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function create(Params $params): LaminasStorage
|
||||
public function create(Params $params): DirectoryTreeStorage
|
||||
{
|
||||
$handlerClassName = $params->getImapHandlerClassName();
|
||||
$handler = null;
|
||||
$isHandled = false;
|
||||
|
||||
if ($handlerClassName && $params->getId()) {
|
||||
$handler = $this->injectableFactory->create($handlerClassName);
|
||||
|
||||
if ($handler instanceof Handler || method_exists($handler, 'handle')) {
|
||||
$params = $handler->handle($params, $params->getId());
|
||||
|
||||
$isHandled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($params->getAuthMechanism() === Params::AUTH_MECHANISM_XOAUTH) {
|
||||
$imapParams = $this->prepareXoauthProtocol($params);
|
||||
} else {
|
||||
$rawParams = [
|
||||
'host' => $params->getHost(),
|
||||
'port' => $params->getPort(),
|
||||
'username' => $params->getUsername(),
|
||||
'password' => $params->getPassword(),
|
||||
'id' => $params->getId(),
|
||||
];
|
||||
$encryption = match ($params->getSecurity()) {
|
||||
Params::SECURITY_SSL => 'ssl',
|
||||
Params::SECURITY_START_TLS => 'starttls',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($params->getSecurity()) {
|
||||
$rawParams['security'] = $params->getSecurity();
|
||||
}
|
||||
$authentication = $params->getAuthMechanism() === Params::AUTH_MECHANISM_XOAUTH ?
|
||||
'oauth' : 'plain';
|
||||
|
||||
$imapParams = null;
|
||||
$config = [
|
||||
'host' => $params->getHost(),
|
||||
'port' => $params->getPort(),
|
||||
'username' => $params->getUsername(),
|
||||
'password' => $params->getPassword(),
|
||||
'encryption' => $encryption,
|
||||
'authentication' => $authentication,
|
||||
];
|
||||
|
||||
// For bc.
|
||||
if (!$isHandled && $handler && $params->getId() && method_exists($handler, 'prepareProtocol')) {
|
||||
$imapParams = $handler->prepareProtocol($params->getId(), $rawParams);
|
||||
}
|
||||
$mailbox = new Mailbox($config);
|
||||
|
||||
if (!$imapParams) {
|
||||
$imapParams = [
|
||||
'host' => $rawParams['host'],
|
||||
'port' => $rawParams['port'],
|
||||
'user' => $rawParams['username'],
|
||||
'password' => $rawParams['password'],
|
||||
];
|
||||
|
||||
if (!empty($rawParams['security'])) {
|
||||
$imapParams['ssl'] = $rawParams['security'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$storage = new Imap($imapParams);
|
||||
} catch (LaminasRuntimeException|InvalidArgumentException|ProtocolRuntimeException $e) {
|
||||
throw new ImapError($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return new LaminasStorage($storage);
|
||||
}
|
||||
|
||||
private function prepareXoauthProtocol(Params $params): ?ImapProtocol
|
||||
{
|
||||
$username = $params->getUsername();
|
||||
$accessToken = $params->getPassword();
|
||||
$host = $params->getHost() ?? throw new RuntimeException("No IMAP host.");
|
||||
$port = $params->getPort();
|
||||
$ssl = $params->getSecurity() ?: false;
|
||||
|
||||
try {
|
||||
$protocol = new ImapProtocol($host, $port, $ssl);
|
||||
} catch (ExceptionInterface $e) {
|
||||
throw new RuntimeException($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
$authString = base64_encode("user=$username\1auth=Bearer $accessToken\1\1");
|
||||
|
||||
$authenticateParams = ['XOAUTH2', $authString];
|
||||
$protocol->sendRequest('AUTHENTICATE', $authenticateParams);
|
||||
|
||||
$i = 0;
|
||||
|
||||
while (true) {
|
||||
if ($i === 10) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$response = '';
|
||||
$isPlus = $protocol->readLine($response, '+', true);
|
||||
|
||||
if ($isPlus) {
|
||||
$this->log->warning("Imap XOauth: Extra server challenge: " . var_export($response, true));
|
||||
|
||||
$protocol->sendRequest('');
|
||||
} else {
|
||||
if (
|
||||
is_string($response) &&
|
||||
(preg_match('/^NO /i', $response) || preg_match('/^BAD /i', $response))
|
||||
) {
|
||||
$this->log->error("Imap XOauth: Failure: " . var_export($response, true));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_string($response) && preg_match("/^OK /i", $response)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
return $protocol;
|
||||
return new DirectoryTreeStorage($mailbox);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,9 +54,16 @@ class FetchData
|
||||
return ObjectUtil::clone($this->data);
|
||||
}
|
||||
|
||||
public function getLastUniqueId(string $folder): ?string
|
||||
public function getLastUid(string $folder): ?int
|
||||
{
|
||||
return $this->data->lastUID->$folder ?? null;
|
||||
$id = $this->data->lastUID->$folder ?? null;
|
||||
|
||||
if ($id === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// To int for bc. It used to be string.
|
||||
return (int) $id;
|
||||
}
|
||||
|
||||
public function getLastDate(string $folder): ?DateTime
|
||||
@@ -84,7 +91,7 @@ class FetchData
|
||||
return $this->data->byDate->$folder ?? false;
|
||||
}
|
||||
|
||||
public function setLastUniqueId(string $folder, ?string $uniqueId): void
|
||||
public function setLastUid(string $folder, ?int $uniqueId): void
|
||||
{
|
||||
if (!property_exists($this->data, 'lastUID')) {
|
||||
$this->data->lastUID = (object) [];
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
namespace Espo\Core\Mail\Account;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\Mail\Account\Storage\Flag;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Exceptions\NoImap;
|
||||
@@ -41,7 +40,6 @@ use Espo\Core\Mail\MessageWrapper;
|
||||
use Espo\Core\Mail\Account\Hook\BeforeFetch as BeforeFetchHook;
|
||||
use Espo\Core\Mail\Account\Hook\AfterFetch as AfterFetchHook;
|
||||
use Espo\Core\Mail\Account\Hook\BeforeFetchResult as BeforeFetchHookResult;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\Field\DateTime as DateTimeField;
|
||||
@@ -52,7 +50,6 @@ use Espo\ORM\Collection;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
|
||||
use Throwable;
|
||||
use DateTime;
|
||||
|
||||
@@ -115,51 +112,44 @@ class Fetcher
|
||||
try {
|
||||
$storage->selectFolder($folderOriginal);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error(
|
||||
"{$account->getEntityType()} {$account->getId()}, " .
|
||||
"could not select folder '$folder'; [{$e->getCode()}] {$e->getMessage()}"
|
||||
);
|
||||
$message = "{$account->getEntityType()} {$account->getId()}, " .
|
||||
"could not select folder '$folder'; {$e->getMessage()}";
|
||||
|
||||
$this->log->error($message, ['exception' => $e]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$lastUniqueId = $fetchData->getLastUniqueId($folder);
|
||||
$lastId = $fetchData->getLastUid($folder);
|
||||
$lastDate = $fetchData->getLastDate($folder);
|
||||
$forceByDate = $fetchData->getForceByDate($folder);
|
||||
|
||||
$portionLimit = $forceByDate ? 0 : $account->getPortionLimit();
|
||||
|
||||
$previousLastUniqueId = $lastUniqueId;
|
||||
$previousLastId = $lastId;
|
||||
|
||||
$idList = $this->getIdList(
|
||||
$ids = $this->fetchIds(
|
||||
account: $account,
|
||||
storage: $storage,
|
||||
lastUID: $lastUniqueId,
|
||||
lastUid: $lastId,
|
||||
lastDate: $lastDate,
|
||||
forceByDate: $forceByDate,
|
||||
);
|
||||
|
||||
if (count($idList) === 1 && $lastUniqueId) {
|
||||
if ($storage->getUniqueId($idList[0]) === $lastUniqueId) {
|
||||
return;
|
||||
}
|
||||
if (count($ids) === 1 && $ids[0] === $lastId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$counter = 0;
|
||||
|
||||
foreach ($idList as $id) {
|
||||
if ($counter == count($idList) - 1) {
|
||||
$lastUniqueId = $storage->getUniqueId($id);
|
||||
foreach ($ids as $id) {
|
||||
if ($counter === count($ids) - 1) {
|
||||
$lastId = $id;
|
||||
}
|
||||
|
||||
if ($forceByDate && $previousLastUniqueId) {
|
||||
$uid = $storage->getUniqueId($id);
|
||||
if ($forceByDate && $previousLastId && $id <= $previousLastId) {
|
||||
$counter++;
|
||||
|
||||
if ((int) $uid <= (int) $previousLastUniqueId) {
|
||||
$counter++;
|
||||
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$email = $this->fetchEmail(
|
||||
@@ -170,11 +160,11 @@ class Fetcher
|
||||
mappedEmailFolderId: $account->getMappedEmailFolder($folderOriginal)?->getId(),
|
||||
);
|
||||
|
||||
$isLast = $counter === count($idList) - 1;
|
||||
$isLast = $counter === count($ids) - 1;
|
||||
$isLastInPortion = $counter === $portionLimit - 1;
|
||||
|
||||
if ($isLast || $isLastInPortion) {
|
||||
$lastUniqueId = $storage->getUniqueId($id);
|
||||
$lastId = $id;
|
||||
|
||||
if ($email && $email->getDateSent()) {
|
||||
$lastDate = $email->getDateSent();
|
||||
@@ -187,7 +177,7 @@ class Fetcher
|
||||
break;
|
||||
}
|
||||
|
||||
$counter++;
|
||||
$counter ++;
|
||||
}
|
||||
|
||||
if ($forceByDate) {
|
||||
@@ -195,25 +185,21 @@ class Fetcher
|
||||
}
|
||||
|
||||
$fetchData->setLastDate($folder, $lastDate);
|
||||
$fetchData->setLastUniqueId($folder, $lastUniqueId);
|
||||
$fetchData->setLastUid($folder, $lastId);
|
||||
|
||||
if ($forceByDate && $previousLastUniqueId) {
|
||||
$idList = $storage->getIdsFromUniqueId($previousLastUniqueId);
|
||||
if ($forceByDate && $previousLastId) {
|
||||
$ids = $storage->getUidsFromUid($previousLastId);
|
||||
|
||||
if (count($idList)) {
|
||||
$uid1 = $storage->getUniqueId($idList[0]);
|
||||
|
||||
if ((int) $uid1 > (int) $previousLastUniqueId) {
|
||||
$fetchData->setForceByDate($folder, false);
|
||||
}
|
||||
if (count($ids) && $ids[0] > $previousLastId) {
|
||||
$fetchData->setForceByDate($folder, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!$forceByDate &&
|
||||
$previousLastUniqueId &&
|
||||
count($idList) &&
|
||||
(int) $previousLastUniqueId >= (int) $lastUniqueId
|
||||
count($ids) &&
|
||||
$previousLastId &&
|
||||
$previousLastId >= $lastId
|
||||
) {
|
||||
// Handling broken numbering. Next time fetch since the last date rather than the last UID.
|
||||
$fetchData->setForceByDate($folder, true);
|
||||
@@ -226,20 +212,20 @@ class Fetcher
|
||||
* @return int[]
|
||||
* @throws Error
|
||||
*/
|
||||
private function getIdList(
|
||||
private function fetchIds(
|
||||
Account $account,
|
||||
Storage $storage,
|
||||
?string $lastUID,
|
||||
?int $lastUid,
|
||||
?DateTimeField $lastDate,
|
||||
bool $forceByDate
|
||||
bool $forceByDate,
|
||||
): array {
|
||||
|
||||
if (!empty($lastUID) && !$forceByDate) {
|
||||
return $storage->getIdsFromUniqueId($lastUID);
|
||||
if ($lastUid !== null && !$forceByDate) {
|
||||
return $storage->getUidsFromUid($lastUid);
|
||||
}
|
||||
|
||||
if ($lastDate) {
|
||||
return $storage->getIdsSinceDate($lastDate);
|
||||
return $storage->getUidsSinceDate($lastDate);
|
||||
}
|
||||
|
||||
if (!$account->getFetchSince()) {
|
||||
@@ -248,7 +234,7 @@ class Fetcher
|
||||
|
||||
$fetchSince = $account->getFetchSince()->toDateTime();
|
||||
|
||||
return $storage->getIdsSinceDate(
|
||||
return $storage->getUidsSinceDate(
|
||||
DateTimeField::fromDateTime($fetchSince)
|
||||
);
|
||||
}
|
||||
@@ -316,7 +302,7 @@ class Fetcher
|
||||
$flags !== null &&
|
||||
!in_array(Flag::SEEN, $flags)
|
||||
) {
|
||||
$storage->setFlags($id, self::flagsWithoutRecent($flags));
|
||||
$storage->unmarkSeen($id);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error(
|
||||
@@ -436,10 +422,10 @@ class Fetcher
|
||||
try {
|
||||
return $this->importer->import($message, $data);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error(
|
||||
"{$account->getEntityType()} {$account->getId()}, import message; " .
|
||||
"{$e->getCode()} {$e->getMessage()}"
|
||||
);
|
||||
$message = "{$account->getEntityType()} {$account->getId()}, import message; " .
|
||||
"{$e->getCode()} {$e->getMessage()}";
|
||||
|
||||
$this->log->error($message, ['exception' => $e]);
|
||||
|
||||
if ($this->entityManager->getLocker()->isLocked()) {
|
||||
$this->entityManager->getLocker()->rollback();
|
||||
@@ -449,17 +435,6 @@ class Fetcher
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $flags
|
||||
* @return string[]
|
||||
*/
|
||||
private static function flagsWithoutRecent(array $flags): array
|
||||
{
|
||||
return array_values(
|
||||
array_diff($flags, [Flag::RECENT])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Mail\Account\GroupAccount;
|
||||
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Message;
|
||||
use Espo\Core\Mail\Message\Part;
|
||||
|
||||
@@ -62,7 +63,11 @@ class BouncedRecognizer
|
||||
return true;
|
||||
}
|
||||
|
||||
$content = $message->getRawContent();
|
||||
try {
|
||||
$content = $message->getRawContent();
|
||||
} catch (ImapError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
str_contains($content, 'message/delivery-status') &&
|
||||
|
||||
@@ -42,8 +42,6 @@ use Espo\Core\Mail\Sender\Message;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Exception;
|
||||
|
||||
use Laminas\Mail\Exception\ExceptionInterface;
|
||||
|
||||
class Service
|
||||
{
|
||||
public function __construct(
|
||||
@@ -117,10 +115,10 @@ class Service
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
$message = $e instanceof ExceptionInterface || $e instanceof ImapError ?
|
||||
$message = $e instanceof ImapError ?
|
||||
$e->getMessage() : '';
|
||||
|
||||
throw new ErrorSilent($message);
|
||||
throw new ErrorSilent($message, previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
namespace Espo\Core\Mail\Account\GroupAccount;
|
||||
|
||||
use Espo\Core\Mail\Account\CommonStorageFactory;
|
||||
use Espo\Core\Mail\Account\Storage;
|
||||
use Espo\Core\Mail\Account\Storage\Params;
|
||||
use Espo\Core\Mail\Account\Account;
|
||||
use Espo\Core\Mail\Account\StorageFactory as StorageFactoryInterface;
|
||||
use Espo\Core\Mail\Account\Storage\LaminasStorage;
|
||||
use Espo\Core\Mail\Exceptions\NoImap;
|
||||
|
||||
class StorageFactory implements StorageFactoryInterface
|
||||
@@ -42,7 +42,7 @@ class StorageFactory implements StorageFactoryInterface
|
||||
private CommonStorageFactory $commonStorageFactory,
|
||||
) {}
|
||||
|
||||
public function create(Account $account): LaminasStorage
|
||||
public function create(Account $account): Storage
|
||||
{
|
||||
$imapParams = $account->getImapParams();
|
||||
|
||||
@@ -63,7 +63,7 @@ class StorageFactory implements StorageFactoryInterface
|
||||
return $this->createWithParams($params);
|
||||
}
|
||||
|
||||
public function createWithParams(Params $params): LaminasStorage
|
||||
public function createWithParams(Params $params): Storage
|
||||
{
|
||||
return $this->commonStorageFactory->create($params);
|
||||
}
|
||||
|
||||
@@ -43,8 +43,6 @@ use Espo\Core\Mail\Account\StorageFactory;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Core\Mail\Sender\Message;
|
||||
|
||||
use Laminas\Mail\Exception\ExceptionInterface;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Service
|
||||
@@ -154,7 +152,7 @@ class Service
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
$message = $e instanceof ExceptionInterface || $e instanceof ImapError ?
|
||||
$message = $e instanceof ImapError ?
|
||||
$e->getMessage() : '';
|
||||
|
||||
throw new ErrorSilent($message);
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
namespace Espo\Core\Mail\Account\PersonalAccount;
|
||||
|
||||
use Espo\Core\Mail\Account\CommonStorageFactory;
|
||||
use Espo\Core\Mail\Account\Storage;
|
||||
use Espo\Core\Mail\Account\Storage\Params;
|
||||
use Espo\Core\Mail\Account\StorageFactory as StorageFactoryInterface;
|
||||
use Espo\Core\Mail\Account\Account;
|
||||
use Espo\Core\Mail\Exceptions\NoImap;
|
||||
use Espo\Core\Mail\Account\Storage\LaminasStorage;
|
||||
|
||||
use LogicException;
|
||||
|
||||
@@ -44,7 +44,7 @@ class StorageFactory implements StorageFactoryInterface
|
||||
private CommonStorageFactory $commonStorageFactory,
|
||||
) {}
|
||||
|
||||
public function create(Account $account): LaminasStorage
|
||||
public function create(Account $account): Storage
|
||||
{
|
||||
$userLink = $account->getUser();
|
||||
|
||||
@@ -75,7 +75,7 @@ class StorageFactory implements StorageFactoryInterface
|
||||
return $this->createWithParams($params);
|
||||
}
|
||||
|
||||
public function createWithParams(Params $params): LaminasStorage
|
||||
public function createWithParams(Params $params): Storage
|
||||
{
|
||||
return $this->commonStorageFactory->create($params);
|
||||
}
|
||||
|
||||
@@ -30,49 +30,55 @@
|
||||
namespace Espo\Core\Mail\Account;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
|
||||
interface Storage
|
||||
{
|
||||
/**
|
||||
* Set message flags.
|
||||
* Mark as unseen.
|
||||
*
|
||||
* @param string[] $flags
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function setFlags(int $id, array $flags): void;
|
||||
public function unmarkSeen(int $id): void;
|
||||
|
||||
/**
|
||||
* Get a message size.
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function getSize(int $id): int;
|
||||
|
||||
/**
|
||||
* Get message raw content.
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function getRawContent(int $id): string;
|
||||
|
||||
/**
|
||||
* Get a message unique ID.
|
||||
*/
|
||||
public function getUniqueId(int $id): string;
|
||||
|
||||
/**
|
||||
* Get IDs from unique ID.
|
||||
*
|
||||
* @return int[]
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function getIdsFromUniqueId(string $uniqueId): array;
|
||||
public function getUidsFromUid(int $id): array;
|
||||
|
||||
/**
|
||||
* Get IDs since a specific date.
|
||||
*
|
||||
* @return int[]
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function getIdsSinceDate(DateTime $since): array;
|
||||
public function getUidsSinceDate(DateTime $since): array;
|
||||
|
||||
/**
|
||||
* Get only header and flags. Won't fetch the whole email.
|
||||
*
|
||||
* @return array{header: string, flags: string[]}
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function getHeaderAndFlags(int $id): array;
|
||||
|
||||
@@ -83,16 +89,22 @@ interface Storage
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function getFolderNames(): array;
|
||||
|
||||
/**
|
||||
* Select a folder.
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function selectFolder(string $name): void;
|
||||
|
||||
/**
|
||||
* Store a message.
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function appendMessage(string $content, ?string $folder = null): void;
|
||||
public function appendMessage(string $content, string $folder): void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* 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\Account\Storage;
|
||||
|
||||
use DirectoryTree\ImapEngine\Exceptions\Exception as CommonException;
|
||||
use DirectoryTree\ImapEngine\FolderInterface;
|
||||
use DirectoryTree\ImapEngine\Mailbox;
|
||||
use DirectoryTree\ImapEngine\Message;
|
||||
use DirectoryTree\ImapEngine\MessageInterface;
|
||||
use DirectoryTree\ImapEngine\MessageQuery;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Mail\Account\Storage;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use LogicException;
|
||||
|
||||
class DirectoryTreeStorage implements Storage
|
||||
{
|
||||
private ?FolderInterface $selectedFolder = null;
|
||||
|
||||
public function __construct(
|
||||
private Mailbox $mailbox,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @todo Test.
|
||||
* @inheritDoc
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function unmarkSeen(int $id): void
|
||||
{
|
||||
$folder = $this->getSelectedFolder();
|
||||
|
||||
try {
|
||||
$message = $folder->messages()
|
||||
->withFlags()
|
||||
->find($id);
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
if (!$message) {
|
||||
throw new ImapError("Could not fetch message $id.");
|
||||
}
|
||||
|
||||
try {
|
||||
$message->unmarkSeen();
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function getSize(int $id): int
|
||||
{
|
||||
$folder = $this->getSelectedFolder();
|
||||
|
||||
try {
|
||||
$message = $folder->messages()
|
||||
->withSize()
|
||||
->find($id);
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
if (!$message) {
|
||||
throw new ImapError("Could not fetch message $id.");
|
||||
}
|
||||
|
||||
return $message->size() ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function getRawContent(int $id): string
|
||||
{
|
||||
$folder = $this->getSelectedFolder();
|
||||
|
||||
try {
|
||||
$message = $folder->messages()
|
||||
->withHeaders()
|
||||
->withFlags()
|
||||
->withBody()
|
||||
->find($id);
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
if (!$message) {
|
||||
throw new ImapError("Could not fetch message $id.");
|
||||
}
|
||||
|
||||
if (!$message instanceof Message) {
|
||||
throw new LogicException("Not supported message instance.");
|
||||
}
|
||||
|
||||
return $message->body();
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Test.
|
||||
* @inheritDoc
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function getUidsFromUid(int $id): array
|
||||
{
|
||||
$output = [];
|
||||
|
||||
$query = $this->getSelectedFolder()
|
||||
->messages()
|
||||
->withoutHeaders()
|
||||
->uid($id, INF);
|
||||
|
||||
/**
|
||||
* Magic methods are used.
|
||||
* @noinspection PhpConditionAlreadyCheckedInspection
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
assert($query instanceof MessageQuery);
|
||||
|
||||
try {
|
||||
$query->each(function (MessageInterface $message) use (&$output) {
|
||||
$output[] = $message->uid();
|
||||
});
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Test.
|
||||
* @inheritDoc
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function getUidsSinceDate(DateTime $since): array
|
||||
{
|
||||
$output = [];
|
||||
|
||||
$query = $this->getSelectedFolder()
|
||||
->messages()
|
||||
->withoutHeaders()
|
||||
->since($since->toDateTime());
|
||||
|
||||
/**
|
||||
* Magic methods are used.
|
||||
* @noinspection PhpConditionAlreadyCheckedInspection
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
assert($query instanceof MessageQuery);
|
||||
|
||||
try {
|
||||
$query->each(function (MessageInterface $message) use (&$output) {
|
||||
$output[] = $message->uid();
|
||||
});
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{header: string, flags: string[]}
|
||||
* @inheritDoc
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function getHeaderAndFlags(int $id): array
|
||||
{
|
||||
try {
|
||||
$folder = $this->getSelectedFolder();
|
||||
|
||||
$message = $folder->messages()
|
||||
->withHeaders()
|
||||
->withFlags()
|
||||
->find($id);
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
if (!$message) {
|
||||
throw new ImapError("Could not fetch message $id.");
|
||||
}
|
||||
|
||||
if (!$message instanceof Message) {
|
||||
// Whenever the library is upgraded, it's reasonable to check whether
|
||||
// the Message instance is still returned by the library.
|
||||
throw new LogicException("Not supported message instance.");
|
||||
}
|
||||
|
||||
return [
|
||||
'header' => $message->head(),
|
||||
'flags' => $folder->flags(),
|
||||
];
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
$this->mailbox->disconnect();
|
||||
$this->selectedFolder = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @inheritDoc
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function getFolderNames(): array
|
||||
{
|
||||
$output = [];
|
||||
|
||||
try {
|
||||
$folders = $this->mailbox->folders()->get();
|
||||
|
||||
foreach ($folders as $folder) {
|
||||
$output[] = $folder->name();
|
||||
}
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function selectFolder(string $name): void
|
||||
{
|
||||
try {
|
||||
$folder = $this->getFolder($name);
|
||||
$this->selectedFolder = $folder;
|
||||
$folder->select();
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function appendMessage(string $content, string $folder): void
|
||||
{
|
||||
try {
|
||||
$this->getFolder($folder)
|
||||
->messages()
|
||||
->append($content);
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
private function getSelectedFolder(): FolderInterface
|
||||
{
|
||||
return $this->selectedFolder ?: $this->mailbox->inbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ImapError
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
private function getFolder(string $name): FolderInterface
|
||||
{
|
||||
try {
|
||||
$folder = $this->mailbox->folders()->find($name);
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
if (!$folder) {
|
||||
throw new ImapError("Could not select folder '$name'.");
|
||||
}
|
||||
|
||||
return $folder;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* 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\Account\Storage;
|
||||
|
||||
use Espo\Core\Mail\Account\Storage;
|
||||
use Espo\Core\Mail\Mail\Storage\Imap;
|
||||
use Espo\Core\Field\DateTime;
|
||||
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
class LaminasStorage implements Storage
|
||||
{
|
||||
public function __construct(private Imap $imap)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param string[] $flags
|
||||
*/
|
||||
public function setFlags(int $id, array $flags): void
|
||||
{
|
||||
$this->imap->setFlags($id, $flags);
|
||||
}
|
||||
|
||||
public function getSize(int $id): int
|
||||
{
|
||||
/** @var int */
|
||||
return $this->imap->getSize($id);
|
||||
}
|
||||
|
||||
public function getRawContent(int $id): string
|
||||
{
|
||||
return $this->imap->getRawContent($id);
|
||||
}
|
||||
|
||||
public function getUniqueId(int $id): string
|
||||
{
|
||||
/** @var string */
|
||||
return $this->imap->getUniqueId($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getIdsFromUniqueId(string $uniqueId): array
|
||||
{
|
||||
return $this->imap->getIdsFromUniqueId($uniqueId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getIdsSinceDate(DateTime $since): array
|
||||
{
|
||||
return $this->imap->getIdsSinceDate(
|
||||
$since->toDateTime()->format('d-M-Y')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{header: string, flags: string[]}
|
||||
*/
|
||||
public function getHeaderAndFlags(int $id): array
|
||||
{
|
||||
return $this->imap->getHeaderAndFlags($id);
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
$this->imap->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFolderNames(): array
|
||||
{
|
||||
$folderIterator = new RecursiveIteratorIterator(
|
||||
$this->imap->getFolders(),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($folderIterator as $folder) {
|
||||
$list[] = mb_convert_encoding($folder->getGlobalName(), 'UTF-8', 'UTF7-IMAP');
|
||||
}
|
||||
|
||||
/** @var string[] */
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function selectFolder(string $name): void
|
||||
{
|
||||
$nameConverted = mb_convert_encoding($name, 'UTF7-IMAP', 'UTF-8');
|
||||
|
||||
$this->imap->selectFolder($nameConverted);
|
||||
}
|
||||
|
||||
public function appendMessage(string $content, ?string $folder = null): void
|
||||
{
|
||||
if ($folder !== null) {
|
||||
$folder = mb_convert_encoding($folder, 'UTF7-IMAP', 'UTF-8');
|
||||
}
|
||||
|
||||
$this->imap->appendMessage($content, $folder);
|
||||
}
|
||||
}
|
||||
@@ -36,17 +36,18 @@ use SensitiveParameter;
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
/** @since 9.3.0 */
|
||||
public const string SECURITY_SSL = 'SSL';
|
||||
/** @since 9.3.0 */
|
||||
public const string SECURITY_START_TLS = 'TLS';
|
||||
|
||||
/** @var ?class-string<object> */
|
||||
private ?string $imapHandlerClassName;
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
/** @since 9.3.0 */
|
||||
public const string AUTH_MECHANISM_PLAIN = 'plain';
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
/** @since 9.3.0 */
|
||||
public const string AUTH_MECHANISM_XOAUTH = 'xoauth';
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,8 +37,6 @@ use Espo\Core\Utils\Config;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Email;
|
||||
|
||||
use Laminas\Mail\Message;
|
||||
|
||||
/**
|
||||
* A service for email sending. Can send with SMTP parameters of the system email account or with specific parameters.
|
||||
* Uses a builder to send with specific parameters.
|
||||
@@ -124,17 +122,6 @@ class EmailSender
|
||||
return $this->createSender()->withEnvelopeOptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Mail;
|
||||
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Importer\Data;
|
||||
use Espo\Entities\Email;
|
||||
|
||||
@@ -37,5 +38,8 @@ use Espo\Entities\Email;
|
||||
*/
|
||||
interface Importer
|
||||
{
|
||||
/**
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function import(Message $message, Data $data): ?Email;
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* 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;
|
||||
|
||||
class Headers extends \Laminas\Mail\Headers
|
||||
{}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* 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\Storage;
|
||||
|
||||
class Imap extends \Laminas\Mail\Storage\Imap
|
||||
{
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getIdsFromUniqueId(string $uid): array
|
||||
{
|
||||
$nextUid = strval(intval($uid) + 1);
|
||||
|
||||
assert($this->protocol !== null);
|
||||
|
||||
return $this->protocol->search(['UID ' . $nextUid . ':*']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date A date in the `d-M-Y` format.
|
||||
* @return int[]
|
||||
*/
|
||||
public function getIdsSinceDate(string $date): array
|
||||
{
|
||||
assert($this->protocol !== null);
|
||||
|
||||
return $this->protocol->search(['SINCE ' . $date]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return array{header: string, flags: string[]}
|
||||
*/
|
||||
public function getHeaderAndFlags(int $id): array
|
||||
{
|
||||
assert($this->protocol !== null);
|
||||
|
||||
/** @var array{'RFC822.HEADER': string, 'FLAGS': string[]} $data */
|
||||
$data = $this->protocol->fetch(['FLAGS', 'RFC822.HEADER'], $id);
|
||||
|
||||
$header = $data['RFC822.HEADER'];
|
||||
|
||||
$flags = [];
|
||||
|
||||
foreach ($data['FLAGS'] as $flag) {
|
||||
$flags[] = static::$knownFlags[$flag] ?? $flag;
|
||||
}
|
||||
|
||||
return [
|
||||
'flags' => $flags,
|
||||
'header' => $header,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Mail;
|
||||
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Message\Part;
|
||||
|
||||
interface Message
|
||||
@@ -50,11 +51,15 @@ interface Message
|
||||
|
||||
/**
|
||||
* Get a raw content part.
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function getRawContent(): string;
|
||||
|
||||
/**
|
||||
* Get a full raw message.
|
||||
*
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function getFullRawContent(): string;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
namespace Espo\Core\Mail;
|
||||
|
||||
use Espo\Core\Mail\Account\Storage;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Message\Part;
|
||||
|
||||
use RuntimeException;
|
||||
@@ -42,6 +43,9 @@ class MessageWrapper implements Message
|
||||
/** @var ?string[] */
|
||||
private ?array $flagList = null;
|
||||
|
||||
/**
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function __construct(
|
||||
private int $id,
|
||||
private ?Storage $storage = null,
|
||||
|
||||
@@ -44,10 +44,6 @@ use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Laminas\Mail\Headers;
|
||||
use Laminas\Mail\Message as LaminasMessage;
|
||||
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Mailer\Envelope;
|
||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
use Symfony\Component\Mailer\Transport\TransportInterface;
|
||||
@@ -71,7 +67,6 @@ class Sender
|
||||
/** @var array<string, mixed> */
|
||||
private array $overrideParams = [];
|
||||
private ?string $envelopeFromAddress = null;
|
||||
private ?LaminasMessage $laminasMessage = null;
|
||||
/** @var ?iterable<Attachment> */
|
||||
private $attachmentList = null;
|
||||
/** @var array{string, string}[] */
|
||||
@@ -96,7 +91,6 @@ class Sender
|
||||
{
|
||||
$this->params = [];
|
||||
$this->envelopeFromAddress = null;
|
||||
$this->laminasMessage = null;
|
||||
$this->attachmentList = null;
|
||||
$this->overrideParams = [];
|
||||
$this->headers = [];
|
||||
@@ -197,19 +191,6 @@ class Sender
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a message instance.
|
||||
*
|
||||
* @deprecated As of v9.1. Use `withAddedHeader`.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function withMessage(LaminasMessage $message): self
|
||||
{
|
||||
$this->laminasMessage = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header.
|
||||
*
|
||||
@@ -313,8 +294,6 @@ class Sender
|
||||
$this->applyBody($email, $message);
|
||||
$this->applyMessageId($email, $message);
|
||||
|
||||
$this->applyLaminasMessageHeaders($message);
|
||||
|
||||
if (!$this->transport) {
|
||||
throw new LogicException();
|
||||
}
|
||||
@@ -595,17 +574,6 @@ class Sender
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
if ($email->isAutoReply() && !$message->getHeaders()->has('Auto-Submitted')) {
|
||||
$message->getHeaders()->addTextHeader('Auto-Submitted', 'auto-replied');
|
||||
}
|
||||
@@ -625,24 +593,4 @@ class Sender
|
||||
|
||||
return new Envelope(new Address($this->envelopeFromAddress), $recipients);
|
||||
}
|
||||
|
||||
private function applyLaminasMessageHeaders(Message $message): void
|
||||
{
|
||||
if (!$this->laminasMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parts = preg_split("/\R\R/", $message->toString(), 2);
|
||||
|
||||
if (!is_array($parts) || count($parts) < 2) {
|
||||
throw new RuntimeException("Could not split email.");
|
||||
}
|
||||
|
||||
/** @noinspection PhpMultipleClassDeclarationsInspection */
|
||||
$this->laminasMessage
|
||||
->setHeaders(
|
||||
Headers::fromString($parts[0])
|
||||
)
|
||||
->setBody($parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
namespace Espo\Modules\Crm\Tools\MassEmail;
|
||||
|
||||
use Espo\Modules\Crm\Tools\MassEmail\MessagePreparator\Headers;
|
||||
use Laminas\Mail\Message;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Mail\ConfigDataProvider;
|
||||
use Espo\ORM\EntityCollection;
|
||||
|
||||
@@ -33,6 +33,7 @@ use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\FileStorage\Manager;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Importer;
|
||||
use Espo\Core\Mail\Importer\Data;
|
||||
use Espo\Core\Mail\MessageWrapper;
|
||||
@@ -40,6 +41,7 @@ use Espo\Core\Mail\Parsers\MailMimeParser;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\EntityManager;
|
||||
use RuntimeException;
|
||||
|
||||
class ImportEmlService
|
||||
{
|
||||
@@ -66,7 +68,11 @@ class ImportEmlService
|
||||
$attachment = $this->getAttachment($fileId);
|
||||
$contents = $this->fileStorageManager->getContents($attachment);
|
||||
|
||||
$message = new MessageWrapper(1, null, $this->parser, $contents);
|
||||
try {
|
||||
$message = new MessageWrapper(1, null, $this->parser, $contents);
|
||||
} catch (ImapError $e) {
|
||||
throw new RuntimeException(previous: $e);
|
||||
}
|
||||
|
||||
$this->checkDuplicate($message);
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
"slim/slim": "^4.15",
|
||||
"slim/psr7": "^1",
|
||||
"dragonmantank/cron-expression": "^3.4",
|
||||
"laminas/laminas-mail": "dev-master#3befe2ed6193c10fb045369bd6473bd70fb9ceac",
|
||||
"laminas/laminas-mime": "dev-master#25659453400e0b2970e490dd0e4045f62acf5371",
|
||||
"laminas/laminas-ldap": "2.20.x-dev#e7b9fe0e295f3898fb97d08b320654c92849918a",
|
||||
"monolog/monolog": "^3.9",
|
||||
"zordius/lightncandy": "dev-espo#v1.2.5e",
|
||||
@@ -56,7 +54,8 @@
|
||||
"league/html-to-markdown": "^5.1",
|
||||
"psr/clock": "^1.0",
|
||||
"react/child-process": "^0.6.6",
|
||||
"lasserafn/php-initial-avatar-generator": "dev-update-image-lib#a46ab8f1427f93c5b37957e739205da7fcca0290"
|
||||
"lasserafn/php-initial-avatar-generator": "dev-update-image-lib#a46ab8f1427f93c5b37957e739205da7fcca0290",
|
||||
"directorytree/imapengine": "^1.19"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^11.5",
|
||||
@@ -96,14 +95,6 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/yurikuzn/lightncandy.git"
|
||||
},
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/yurikuzn/laminas-mail.git"
|
||||
},
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/yurikuzn/laminas-mime.git"
|
||||
},
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/yurikuzn/laminas-ldap.git"
|
||||
|
||||
890
composer.lock
generated
890
composer.lock
generated
@@ -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": "c120697edc5492876e53f288b9a41e13",
|
||||
"content-hash": "1d054b72274e6a650dc6dabcab11660f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -638,6 +638,68 @@
|
||||
],
|
||||
"time": "2024-09-19T14:15:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "directorytree/imapengine",
|
||||
"version": "v1.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DirectoryTree/ImapEngine.git",
|
||||
"reference": "fd8d25780f76cde4e716767d557ea0d4f9be37f2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/DirectoryTree/ImapEngine/zipball/fd8d25780f76cde4e716767d557ea0d4f9be37f2",
|
||||
"reference": "fd8d25780f76cde4e716767d557ea0d4f9be37f2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"egulias/email-validator": "^4.0",
|
||||
"illuminate/collections": ">=9.0",
|
||||
"nesbot/carbon": ">=2.0",
|
||||
"php": "^8.1",
|
||||
"symfony/mime": ">=6.0",
|
||||
"zbateson/mail-mime-parser": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"pestphp/pest": "^2.0|^3.0",
|
||||
"spatie/ray": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DirectoryTree\\ImapEngine\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Steve Bauman",
|
||||
"email": "steven_bauman@outlook.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A fully-featured IMAP library -- without the PHP extension",
|
||||
"homepage": "https://github.com/directorytree/imapengine",
|
||||
"keywords": [
|
||||
"engine",
|
||||
"imap",
|
||||
"mail"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/DirectoryTree/ImapEngine/issues",
|
||||
"source": "https://github.com/DirectoryTree/ImapEngine/tree/v1.19.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/stevebauman",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-19T19:26:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.10.4",
|
||||
@@ -1813,6 +1875,205 @@
|
||||
],
|
||||
"time": "2024-07-18T11:15:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/collections",
|
||||
"version": "v12.41.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/collections.git",
|
||||
"reference": "1cf6115f711ff775fcce547dee82d59cac33fedb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/collections/zipball/1cf6115f711ff775fcce547dee82d59cac33fedb",
|
||||
"reference": "1cf6115f711ff775fcce547dee82d59cac33fedb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/conditionable": "^12.0",
|
||||
"illuminate/contracts": "^12.0",
|
||||
"illuminate/macroable": "^12.0",
|
||||
"php": "^8.2",
|
||||
"symfony/polyfill-php84": "^1.33",
|
||||
"symfony/polyfill-php85": "^1.33"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/http": "Required to convert collections to API resources (^12.0).",
|
||||
"symfony/var-dumper": "Required to use the dump method (^7.2)."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "12.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"functions.php",
|
||||
"helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Illuminate\\Support\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Collections package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-11-29T13:55:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/conditionable",
|
||||
"version": "v12.41.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/conditionable.git",
|
||||
"reference": "ec677967c1f2faf90b8428919124d2184a4c9b49"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/conditionable/zipball/ec677967c1f2faf90b8428919124d2184a4c9b49",
|
||||
"reference": "ec677967c1f2faf90b8428919124d2184a4c9b49",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "12.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Support\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Conditionable package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-05-13T15:08:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/contracts",
|
||||
"version": "v12.41.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/contracts.git",
|
||||
"reference": "19e8938edb73047017cfbd443b96844b86da4a59"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/contracts/zipball/19e8938edb73047017cfbd443b96844b86da4a59",
|
||||
"reference": "19e8938edb73047017cfbd443b96844b86da4a59",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"psr/container": "^1.1.1|^2.0.1",
|
||||
"psr/simple-cache": "^1.0|^2.0|^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "12.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Contracts\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Contracts package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-11-26T21:36:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/macroable",
|
||||
"version": "v12.41.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/macroable.git",
|
||||
"reference": "e862e5648ee34004fa56046b746f490dfa86c613"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/macroable/zipball/e862e5648ee34004fa56046b746f490dfa86c613",
|
||||
"reference": "e862e5648ee34004fa56046b746f490dfa86c613",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "12.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Support\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Macroable package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2024-07-23T16:31:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/gif",
|
||||
"version": "4.2.2",
|
||||
@@ -2101,463 +2362,6 @@
|
||||
},
|
||||
"time": "2025-12-03T11:18:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-loader",
|
||||
"version": "2.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laminas/laminas-loader.git",
|
||||
"reference": "f2eedd3a6e774d965158fd11946bb1eba72e298c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laminas/laminas-loader/zipball/f2eedd3a6e774d965158fd11946bb1eba72e298c",
|
||||
"reference": "f2eedd3a6e774d965158fd11946bb1eba72e298c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||
},
|
||||
"conflict": {
|
||||
"zendframework/zend-loader": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laminas/laminas-coding-standard": "~2.4.0",
|
||||
"phpunit/phpunit": "~9.5.25"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laminas\\Loader\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Autoloading and plugin loading strategies",
|
||||
"homepage": "https://laminas.dev",
|
||||
"keywords": [
|
||||
"laminas",
|
||||
"loader"
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"docs": "https://docs.laminas.dev/laminas-loader/",
|
||||
"forum": "https://discourse.laminas.dev",
|
||||
"issues": "https://github.com/laminas/laminas-loader/issues",
|
||||
"rss": "https://github.com/laminas/laminas-loader/releases.atom",
|
||||
"source": "https://github.com/laminas/laminas-loader"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||
"type": "community_bridge"
|
||||
}
|
||||
],
|
||||
"abandoned": true,
|
||||
"time": "2024-10-16T09:06:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-mail",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yurikuzn/laminas-mail.git",
|
||||
"reference": "3befe2ed6193c10fb045369bd6473bd70fb9ceac"
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"laminas/laminas-loader": "^2.9.0",
|
||||
"laminas/laminas-mime": "dev-master#25659453400e0b2970e490dd0e4045f62acf5371",
|
||||
"laminas/laminas-stdlib": "^3.17.0",
|
||||
"laminas/laminas-validator": "^2.31.0",
|
||||
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
|
||||
"symfony/polyfill-intl-idn": "^1.27.0",
|
||||
"symfony/polyfill-mbstring": "^1.27.0",
|
||||
"webmozart/assert": "^1.11.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laminas/laminas-coding-standard": "~2.5.0",
|
||||
"laminas/laminas-db": "^2.18",
|
||||
"laminas/laminas-servicemanager": "^3.22.1",
|
||||
"phpunit/phpunit": "^10.4.2",
|
||||
"psalm/plugin-phpunit": "^0.18.4",
|
||||
"symfony/process": "^6.3.4 || ^7.0.0",
|
||||
"vimeo/psalm": "^5.15"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-servicemanager": "^3.21 when using SMTP to deliver messages"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laminas": {
|
||||
"component": "Laminas\\Mail",
|
||||
"config-provider": "Laminas\\Mail\\ConfigProvider"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laminas\\Mail\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"LaminasTest\\Mail\\": "test/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": [
|
||||
"@cs-check",
|
||||
"@static-analysis",
|
||||
"@test"
|
||||
],
|
||||
"cs-check": [
|
||||
"phpcs"
|
||||
],
|
||||
"cs-fix": [
|
||||
"phpcbf"
|
||||
],
|
||||
"static-analysis": [
|
||||
"psalm --shepherd --stats"
|
||||
],
|
||||
"test": [
|
||||
"phpunit --colors=always"
|
||||
],
|
||||
"test-coverage": [
|
||||
"phpunit --colors=always --coverage-clover clover.xml"
|
||||
]
|
||||
},
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages",
|
||||
"homepage": "https://laminas.dev",
|
||||
"keywords": [
|
||||
"laminas",
|
||||
"mail"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laminas/laminas-mail/issues",
|
||||
"forum": "https://discourse.laminas.dev",
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"source": "https://github.com/laminas/laminas-mail",
|
||||
"docs": "https://docs.laminas.dev/laminas-mail/",
|
||||
"rss": "https://github.com/laminas/laminas-mail/releases.atom"
|
||||
},
|
||||
"abandoned": "symfony/mailer",
|
||||
"time": "2024-12-05T11:07:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-mime",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yurikuzn/laminas-mime.git",
|
||||
"reference": "25659453400e0b2970e490dd0e4045f62acf5371"
|
||||
},
|
||||
"require": {
|
||||
"laminas/laminas-stdlib": "^2.7 || ^3.0",
|
||||
"php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||
},
|
||||
"conflict": {
|
||||
"zendframework/zend-mime": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laminas/laminas-coding-standard": "~2.4.0",
|
||||
"laminas/laminas-mail": "^2.19.0",
|
||||
"phpunit/phpunit": "~9.5.25"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-mail": "Laminas\\Mail component"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laminas\\Mime\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"files": [
|
||||
"test/TestAsset/Mail/Headers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"LaminasTest\\Mime\\": "test/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": [
|
||||
"@cs-check",
|
||||
"@test"
|
||||
],
|
||||
"cs-check": [
|
||||
"phpcs"
|
||||
],
|
||||
"cs-fix": [
|
||||
"phpcbf"
|
||||
],
|
||||
"test": [
|
||||
"phpunit --colors=always"
|
||||
],
|
||||
"test-coverage": [
|
||||
"phpunit --colors=always --coverage-clover clover.xml"
|
||||
]
|
||||
},
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Create and parse MIME messages and parts",
|
||||
"homepage": "https://laminas.dev",
|
||||
"keywords": [
|
||||
"laminas",
|
||||
"mime"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://docs.laminas.dev/laminas-mime/",
|
||||
"issues": "https://github.com/laminas/laminas-mime/issues",
|
||||
"source": "https://github.com/laminas/laminas-mime",
|
||||
"rss": "https://github.com/laminas/laminas-mime/releases.atom",
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"forum": "https://discourse.laminas.dev"
|
||||
},
|
||||
"abandoned": "symfony/mime",
|
||||
"time": "2024-12-05T10:39:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-servicemanager",
|
||||
"version": "3.23.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laminas/laminas-servicemanager.git",
|
||||
"reference": "a8640182b892b99767d54404d19c5c3b3699f79b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/a8640182b892b99767d54404d19c5c3b3699f79b",
|
||||
"reference": "a8640182b892b99767d54404d19c5c3b3699f79b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laminas/laminas-stdlib": "^3.19",
|
||||
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
|
||||
"psr/container": "^1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"ext-psr": "*",
|
||||
"laminas/laminas-code": "<4.10.0",
|
||||
"zendframework/zend-code": "<3.3.1",
|
||||
"zendframework/zend-servicemanager": "*"
|
||||
},
|
||||
"provide": {
|
||||
"psr/container-implementation": "^1.0"
|
||||
},
|
||||
"replace": {
|
||||
"container-interop/container-interop": "^1.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||
"friendsofphp/proxy-manager-lts": "^1.0.18",
|
||||
"laminas/laminas-code": "^4.14.0",
|
||||
"laminas/laminas-coding-standard": "~2.5.0",
|
||||
"laminas/laminas-container-config-test": "^0.8",
|
||||
"mikey179/vfsstream": "^1.6.12",
|
||||
"phpbench/phpbench": "^1.3.1",
|
||||
"phpunit/phpunit": "^10.5.36",
|
||||
"psalm/plugin-phpunit": "^0.18.4",
|
||||
"vimeo/psalm": "^5.26.1"
|
||||
},
|
||||
"suggest": {
|
||||
"friendsofphp/proxy-manager-lts": "ProxyManager ^2.1.1 to handle lazy initialization of services"
|
||||
},
|
||||
"bin": [
|
||||
"bin/generate-deps-for-config-factory",
|
||||
"bin/generate-factory-for-class"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/autoload.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Laminas\\ServiceManager\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Factory-Driven Dependency Injection Container",
|
||||
"homepage": "https://laminas.dev",
|
||||
"keywords": [
|
||||
"PSR-11",
|
||||
"dependency-injection",
|
||||
"di",
|
||||
"dic",
|
||||
"laminas",
|
||||
"service-manager",
|
||||
"servicemanager"
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"docs": "https://docs.laminas.dev/laminas-servicemanager/",
|
||||
"forum": "https://discourse.laminas.dev",
|
||||
"issues": "https://github.com/laminas/laminas-servicemanager/issues",
|
||||
"rss": "https://github.com/laminas/laminas-servicemanager/releases.atom",
|
||||
"source": "https://github.com/laminas/laminas-servicemanager"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||
"type": "community_bridge"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-28T21:32:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-stdlib",
|
||||
"version": "3.20.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laminas/laminas-stdlib.git",
|
||||
"reference": "8974a1213be42c3e2f70b2c27b17f910291ab2f4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/8974a1213be42c3e2f70b2c27b17f910291ab2f4",
|
||||
"reference": "8974a1213be42c3e2f70b2c27b17f910291ab2f4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||
},
|
||||
"conflict": {
|
||||
"zendframework/zend-stdlib": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laminas/laminas-coding-standard": "^3.0",
|
||||
"phpbench/phpbench": "^1.3.1",
|
||||
"phpunit/phpunit": "^10.5.38",
|
||||
"psalm/plugin-phpunit": "^0.19.0",
|
||||
"vimeo/psalm": "^5.26.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laminas\\Stdlib\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "SPL extensions, array utilities, error handlers, and more",
|
||||
"homepage": "https://laminas.dev",
|
||||
"keywords": [
|
||||
"laminas",
|
||||
"stdlib"
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"docs": "https://docs.laminas.dev/laminas-stdlib/",
|
||||
"forum": "https://discourse.laminas.dev",
|
||||
"issues": "https://github.com/laminas/laminas-stdlib/issues",
|
||||
"rss": "https://github.com/laminas/laminas-stdlib/releases.atom",
|
||||
"source": "https://github.com/laminas/laminas-stdlib"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||
"type": "community_bridge"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-29T13:46:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-validator",
|
||||
"version": "2.64.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laminas/laminas-validator.git",
|
||||
"reference": "771e504760448ac7af660710237ceb93be602e08"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laminas/laminas-validator/zipball/771e504760448ac7af660710237ceb93be602e08",
|
||||
"reference": "771e504760448ac7af660710237ceb93be602e08",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laminas/laminas-servicemanager": "^3.21.0",
|
||||
"laminas/laminas-stdlib": "^3.19",
|
||||
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
|
||||
"psr/http-message": "^1.0.1 || ^2.0.0"
|
||||
},
|
||||
"conflict": {
|
||||
"zendframework/zend-validator": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laminas/laminas-coding-standard": "^2.5",
|
||||
"laminas/laminas-db": "^2.20",
|
||||
"laminas/laminas-filter": "^2.35.2",
|
||||
"laminas/laminas-i18n": "^2.26.0",
|
||||
"laminas/laminas-session": "^2.20",
|
||||
"laminas/laminas-uri": "^2.11.0",
|
||||
"phpunit/phpunit": "^10.5.20",
|
||||
"psalm/plugin-phpunit": "^0.19.0",
|
||||
"psr/http-client": "^1.0.3",
|
||||
"psr/http-factory": "^1.1.0",
|
||||
"vimeo/psalm": "^5.24.0"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator",
|
||||
"laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator",
|
||||
"laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages",
|
||||
"laminas/laminas-i18n-resources": "Translations of validator messages",
|
||||
"laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains",
|
||||
"laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator",
|
||||
"laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators",
|
||||
"psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laminas": {
|
||||
"component": "Laminas\\Validator",
|
||||
"config-provider": "Laminas\\Validator\\ConfigProvider"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laminas\\Validator\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria",
|
||||
"homepage": "https://laminas.dev",
|
||||
"keywords": [
|
||||
"laminas",
|
||||
"validator"
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"docs": "https://docs.laminas.dev/laminas-validator/",
|
||||
"forum": "https://discourse.laminas.dev",
|
||||
"issues": "https://github.com/laminas/laminas-validator/issues",
|
||||
"rss": "https://github.com/laminas/laminas-validator/releases.atom",
|
||||
"source": "https://github.com/laminas/laminas-validator"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||
"type": "community_bridge"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-26T21:29:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v1.3.7",
|
||||
@@ -7361,6 +7165,166 @@
|
||||
],
|
||||
"time": "2025-07-08T02:45:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php84",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php84.git",
|
||||
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191",
|
||||
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php84\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"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": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-24T13:30:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php85",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php85.git",
|
||||
"reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
|
||||
"reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php85\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"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": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-23T16:12:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.4.0",
|
||||
@@ -9915,12 +9879,10 @@
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"laminas/laminas-mail": 20,
|
||||
"laminas/laminas-mime": 20,
|
||||
"laminas/laminas-ldap": 20,
|
||||
"zordius/lightncandy": 20,
|
||||
"cboden/ratchet": 20,
|
||||
"lasserafn/php-initial-avatar-generator": 20
|
||||
"laminas/laminas-ldap": 20,
|
||||
"lasserafn/php-initial-avatar-generator": 20,
|
||||
"zordius/lightncandy": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
@@ -9938,7 +9900,7 @@
|
||||
"ext-pdo": "*",
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-dev": {},
|
||||
"platform-overrides": {
|
||||
"php": "8.3.0"
|
||||
},
|
||||
|
||||
@@ -48,9 +48,9 @@ class FetchDataTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
$data = FetchData::fromRaw($raw);
|
||||
|
||||
$this->assertEquals('10', $data->getLastUniqueId('test'));
|
||||
$this->assertEquals(10, $data->getLastUid('test'));
|
||||
$this->assertEquals('2022-01-01 00:00:00', $data->getLastDate('test')->toString());
|
||||
$this->assertEquals(null, $data->getLastUniqueId('not-existing'));
|
||||
$this->assertEquals(null, $data->getLastUid('not-existing'));
|
||||
$this->assertEquals(null, $data->getLastDate('not-existing'));
|
||||
$this->assertEquals(false, $data->getForceByDate('test'));
|
||||
$this->assertEquals($raw, $data->getRaw());
|
||||
@@ -58,10 +58,10 @@ class FetchDataTest extends \PHPUnit\Framework\TestCase
|
||||
$now = DateTime::createNow();
|
||||
|
||||
$data->setForceByDate('test', true);
|
||||
$data->setLastUniqueId('test', '11');
|
||||
$data->setLastUid('test', 11);
|
||||
$data->setLastDate('test', $now);
|
||||
|
||||
$this->assertEquals('11', $data->getLastUniqueId('test'));
|
||||
$this->assertEquals(11, $data->getLastUid('test'));
|
||||
$this->assertEquals(true, $data->getForceByDate('test'));
|
||||
$this->assertEquals($now, $data->getLastDate('test'));
|
||||
}
|
||||
|
||||
3
upgrades/9.3/data.json
Normal file
3
upgrades/9.3/data.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"manifest": {}
|
||||
}
|
||||
95
upgrades/9.3/scripts/BeforeUpgrade.php
Normal file
95
upgrades/9.3/scripts/BeforeUpgrade.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* 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.
|
||||
************************************************************************/
|
||||
|
||||
use Espo\Core\Container;
|
||||
use Espo\Entities\Extension;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/** @noinspection PhpMultipleClassDeclarationsInspection */
|
||||
class BeforeUpgrade
|
||||
{
|
||||
private ?Container $container = null;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function run(Container $container): void
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
$this->processCheckExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function processCheckExtensions(): void
|
||||
{
|
||||
$errorMessageList = [];
|
||||
|
||||
$this->processCheckExtension('Google Integration', '1.8.3', $errorMessageList);
|
||||
$this->processCheckExtension('Outlook Integration', '1.6.12', $errorMessageList);
|
||||
|
||||
if (!count($errorMessageList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = implode("\n\n", $errorMessageList);
|
||||
|
||||
throw new Error($message);
|
||||
}
|
||||
|
||||
private function processCheckExtension(string $name, string $minVersion, array &$errorMessageList): void
|
||||
{
|
||||
$em = $this->container->getByClass(EntityManager::class);
|
||||
|
||||
$extension = $em->getRDBRepository(Extension::ENTITY_TYPE)
|
||||
->where([
|
||||
'name' => $name,
|
||||
'isInstalled' => true,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if (!$extension) {
|
||||
return;
|
||||
}
|
||||
|
||||
$version = $extension->get('version');
|
||||
|
||||
if (version_compare($version, $minVersion, '>=')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message =
|
||||
"EspoCRM 9.3 is not compatible with '$name' extension of versions lower than $minVersion. " .
|
||||
"You need to upgrade the extension.";
|
||||
|
||||
$errorMessageList[] = $message;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user