chore: Update copyright year from 2025 to 2026 across core files
- Updated copyright headers in 3,055 core application files - Changed 'Copyright (C) 2014-2025' to 'Copyright (C) 2014-2026' - Added 123 new files from EspoCRM core updates - Removed 4 deprecated files - Total changes: 61,637 insertions, 54,283 deletions This is a routine maintenance update for the new year 2026.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -164,4 +164,12 @@ interface Account
|
||||
* Get the last connection time;
|
||||
*/
|
||||
public function getConnectedAt(): ?DateTime;
|
||||
|
||||
|
||||
/**
|
||||
* Get an email folder mapped to the email folder.
|
||||
*
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function getMappedEmailFolder(string $folder): ?Link;
|
||||
}
|
||||
|
||||
81
application/Espo/Core/Mail/Account/CommonStorageFactory.php
Normal file
81
application/Espo/Core/Mail/Account/CommonStorageFactory.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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;
|
||||
|
||||
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\Params;
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
class CommonStorageFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
) {}
|
||||
|
||||
public function create(Params $params): DirectoryTreeStorage
|
||||
{
|
||||
$handlerClassName = $params->getImapHandlerClassName();
|
||||
|
||||
if ($handlerClassName && $params->getId()) {
|
||||
$handler = $this->injectableFactory->create($handlerClassName);
|
||||
|
||||
if ($handler instanceof Handler || method_exists($handler, 'handle')) {
|
||||
$params = $handler->handle($params, $params->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$encryption = match ($params->getSecurity()) {
|
||||
Params::SECURITY_SSL => 'ssl',
|
||||
Params::SECURITY_START_TLS => 'starttls',
|
||||
default => null,
|
||||
};
|
||||
|
||||
$authentication = $params->getAuthMechanism() === Params::AUTH_MECHANISM_XOAUTH ?
|
||||
'oauth' : 'plain';
|
||||
|
||||
$config = [
|
||||
'host' => $params->getHost(),
|
||||
'port' => $params->getPort(),
|
||||
'username' => $params->getUsername(),
|
||||
'password' => $params->getPassword(),
|
||||
'encryption' => $encryption,
|
||||
'authentication' => $authentication,
|
||||
];
|
||||
|
||||
$mailbox = new Mailbox($config);
|
||||
|
||||
return new DirectoryTreeStorage($mailbox);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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) [];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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;
|
||||
|
||||
@@ -100,12 +97,13 @@ class Fetcher
|
||||
/**
|
||||
* @param Collection<EmailFilter> $filterList
|
||||
* @throws Error
|
||||
* @throws ImapError
|
||||
*/
|
||||
private function fetchFolder(
|
||||
Account $account,
|
||||
string $folderOriginal,
|
||||
Storage $storage,
|
||||
Collection $filterList
|
||||
Collection $filterList,
|
||||
): void {
|
||||
|
||||
$fetchData = $account->getFetchData();
|
||||
@@ -115,60 +113,59 @@ 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(
|
||||
$account,
|
||||
$storage,
|
||||
$lastUniqueId,
|
||||
$lastDate,
|
||||
$forceByDate
|
||||
$ids = $this->fetchIds(
|
||||
account: $account,
|
||||
storage: $storage,
|
||||
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($account, $storage, $id, $filterList);
|
||||
$email = $this->fetchEmail(
|
||||
account: $account,
|
||||
storage: $storage,
|
||||
id: $id,
|
||||
filterList: $filterList,
|
||||
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();
|
||||
@@ -181,7 +178,7 @@ class Fetcher
|
||||
break;
|
||||
}
|
||||
|
||||
$counter++;
|
||||
$counter ++;
|
||||
}
|
||||
|
||||
if ($forceByDate) {
|
||||
@@ -189,25 +186,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);
|
||||
@@ -219,21 +212,22 @@ class Fetcher
|
||||
/**
|
||||
* @return int[]
|
||||
* @throws Error
|
||||
* @throws ImapError
|
||||
*/
|
||||
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()) {
|
||||
@@ -242,7 +236,7 @@ class Fetcher
|
||||
|
||||
$fetchSince = $account->getFetchSince()->toDateTime();
|
||||
|
||||
return $storage->getIdsSinceDate(
|
||||
return $storage->getUidsSinceDate(
|
||||
DateTimeField::fromDateTime($fetchSince)
|
||||
);
|
||||
}
|
||||
@@ -254,7 +248,8 @@ class Fetcher
|
||||
Account $account,
|
||||
Storage $storage,
|
||||
int $id,
|
||||
Collection $filterList
|
||||
Collection $filterList,
|
||||
?string $mappedEmailFolderId,
|
||||
): ?Email {
|
||||
|
||||
$teamIdList = $account->getTeams()->getIdList();
|
||||
@@ -265,11 +260,7 @@ class Fetcher
|
||||
|
||||
$fetchOnlyHeader = $this->checkFetchOnlyHeader($storage, $id);
|
||||
|
||||
$folderData = [];
|
||||
|
||||
if ($userId && $account->getEmailFolder()) {
|
||||
$folderData[$userId] = $account->getEmailFolder()->getId();
|
||||
}
|
||||
$folderData = $this->prepareFolderData($userId, $mappedEmailFolderId, $account);
|
||||
|
||||
$flags = null;
|
||||
|
||||
@@ -286,7 +277,12 @@ class Fetcher
|
||||
->withGroupEmailFolderId($groupEmailFolderId);
|
||||
|
||||
try {
|
||||
$message = new MessageWrapper($id, $storage, $parser);
|
||||
$message = new MessageWrapper(
|
||||
id: $id,
|
||||
storage: $storage,
|
||||
parser: $parser,
|
||||
peek: $account->keepFetchedEmailsUnread(),
|
||||
);
|
||||
|
||||
$hookResult = null;
|
||||
|
||||
@@ -313,7 +309,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(
|
||||
@@ -433,10 +429,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();
|
||||
@@ -447,13 +443,22 @@ class Fetcher
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $flags
|
||||
* @return string[]
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private static function flagsWithoutRecent(array $flags): array
|
||||
private function prepareFolderData(?string $userId, ?string $mappedEmailFolderId, Account $account): array
|
||||
{
|
||||
return array_values(
|
||||
array_diff($flags, [Flag::RECENT])
|
||||
);
|
||||
if (!$userId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$folderData = [];
|
||||
|
||||
if ($mappedEmailFolderId) {
|
||||
$folderData[$userId] = $mappedEmailFolderId;
|
||||
} else if ($account->getEmailFolder()) {
|
||||
$folderData[$userId] = $account->getEmailFolder()->getId();
|
||||
}
|
||||
|
||||
return $folderData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -381,4 +381,9 @@ class Account implements AccountInterface
|
||||
/** @var DateTime */
|
||||
return $this->entity->getValueObject('connectedAt');
|
||||
}
|
||||
|
||||
public function getMappedEmailFolder(string $folder): ?Link
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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') &&
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -29,31 +29,20 @@
|
||||
|
||||
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\ImapError;
|
||||
use Espo\Core\Mail\Exceptions\NoImap;
|
||||
use Espo\Core\Mail\Mail\Storage\Imap;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\InjectableFactory;
|
||||
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Laminas\Mail\Storage\Exception\RuntimeException;
|
||||
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
|
||||
use Laminas\Mail\Protocol\Exception\RuntimeException as ProtocolRuntimeException;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class StorageFactory implements StorageFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Log $log,
|
||||
private InjectableFactory $injectableFactory
|
||||
private CommonStorageFactory $commonStorageFactory,
|
||||
) {}
|
||||
|
||||
public function create(Account $account): LaminasStorage
|
||||
public function create(Account $account): Storage
|
||||
{
|
||||
$imapParams = $account->getImapParams();
|
||||
|
||||
@@ -74,62 +63,8 @@ class StorageFactory implements StorageFactoryInterface
|
||||
return $this->createWithParams($params);
|
||||
}
|
||||
|
||||
public function createWithParams(Params $params): LaminasStorage
|
||||
public function createWithParams(Params $params): Storage
|
||||
{
|
||||
$rawParams = [
|
||||
'host' => $params->getHost(),
|
||||
'port' => $params->getPort(),
|
||||
'username' => $params->getUsername(),
|
||||
'password' => $params->getPassword(),
|
||||
'imapHandler' => $params->getImapHandlerClassName(),
|
||||
Attribute::ID => $params->getId(),
|
||||
];
|
||||
|
||||
if ($params->getSecurity()) {
|
||||
$rawParams['security'] = $params->getSecurity();
|
||||
}
|
||||
|
||||
$imapParams = null;
|
||||
|
||||
$handlerClassName = $rawParams['imapHandler'] ?? null;
|
||||
|
||||
$handler = null;
|
||||
|
||||
if ($handlerClassName && !empty($rawParams['id'])) {
|
||||
try {
|
||||
$handler = $this->injectableFactory->create($handlerClassName);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error("InboundEmail: Could not create Imap Handler. Error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($handler && method_exists($handler, 'prepareProtocol')) {
|
||||
// for backward compatibility
|
||||
$rawParams['ssl'] = $rawParams['security'] ?? null;
|
||||
|
||||
// @todo Incorporate an interface `LaminasProtocolPreparator`.
|
||||
$imapParams = $handler->prepareProtocol($rawParams['id'], $rawParams);
|
||||
}
|
||||
}
|
||||
|
||||
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 (RuntimeException|InvalidArgumentException|ProtocolRuntimeException $e) {
|
||||
throw new ImapError($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return new LaminasStorage($storage);
|
||||
return $this->commonStorageFactory->create($params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -201,6 +201,11 @@ class Account implements AccountInterface
|
||||
return $this->entity->getEmailFolder();
|
||||
}
|
||||
|
||||
public function getMappedEmailFolder(string $folder): ?Link
|
||||
{
|
||||
return $this->entity->getMappedEmailFolder($folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -29,32 +29,22 @@
|
||||
|
||||
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\ImapError;
|
||||
use Espo\Core\Mail\Exceptions\NoImap;
|
||||
use Espo\Core\Mail\Mail\Storage\Imap;
|
||||
use Espo\Core\Mail\Account\Storage\LaminasStorage;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
|
||||
use Laminas\Mail\Protocol\Exception\RuntimeException as ProtocolRuntimeException;
|
||||
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
|
||||
use Laminas\Mail\Storage\Exception\RuntimeException;
|
||||
|
||||
use LogicException;
|
||||
use Throwable;
|
||||
|
||||
class StorageFactory implements StorageFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Log $log,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private CommonStorageFactory $commonStorageFactory,
|
||||
) {}
|
||||
|
||||
public function create(Account $account): LaminasStorage
|
||||
public function create(Account $account): Storage
|
||||
{
|
||||
$userLink = $account->getUser();
|
||||
|
||||
@@ -85,65 +75,8 @@ class StorageFactory implements StorageFactoryInterface
|
||||
return $this->createWithParams($params);
|
||||
}
|
||||
|
||||
public function createWithParams(Params $params): LaminasStorage
|
||||
public function createWithParams(Params $params): Storage
|
||||
{
|
||||
$rawParams = [
|
||||
'host' => $params->getHost(),
|
||||
'port' => $params->getPort(),
|
||||
'username' => $params->getUsername(),
|
||||
'password' => $params->getPassword(),
|
||||
'emailAddress' => $params->getEmailAddress(),
|
||||
'userId' => $params->getUserId(),
|
||||
'imapHandler' => $params->getImapHandlerClassName(),
|
||||
Attribute::ID => $params->getId(),
|
||||
];
|
||||
|
||||
if ($params->getSecurity()) {
|
||||
$rawParams['security'] = $params->getSecurity();
|
||||
}
|
||||
|
||||
/** @var ?class-string $handlerClassName */
|
||||
$handlerClassName = $rawParams['imapHandler'] ?? null;
|
||||
|
||||
$handler = null;
|
||||
$imapParams = null;
|
||||
|
||||
if ($handlerClassName && !empty($rawParams['id'])) {
|
||||
try {
|
||||
$handler = $this->injectableFactory->create($handlerClassName);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error(
|
||||
"EmailAccount: Could not create Imap Handler. Error: " . $e->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
if ($handler && method_exists($handler, 'prepareProtocol')) {
|
||||
// for backward compatibility
|
||||
$rawParams['ssl'] = $rawParams['security'] ?? null;
|
||||
|
||||
$imapParams = $handler->prepareProtocol($rawParams['id'], $rawParams);
|
||||
}
|
||||
}
|
||||
|
||||
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 (RuntimeException|InvalidArgumentException|ProtocolRuntimeException $e) {
|
||||
throw new ImapError($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return new LaminasStorage($storage);
|
||||
return $this->commonStorageFactory->create($params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -30,49 +30,57 @@
|
||||
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
|
||||
* @todo Move to the Message interface. Call from the MessageWrapper.
|
||||
*
|
||||
* @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;
|
||||
public function getRawContent(int $id, bool $peek): 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 +91,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,326 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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, bool $peek): string
|
||||
{
|
||||
$folder = $this->getSelectedFolder();
|
||||
|
||||
try {
|
||||
$message = $folder->messages()
|
||||
->withHeaders()
|
||||
->withFlags()
|
||||
->withBody()
|
||||
->find($id);
|
||||
|
||||
if ($message instanceof Message && !$peek) {
|
||||
$message->markSeen();
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
$query->setFetchOrderAsc();
|
||||
|
||||
try {
|
||||
$query->each(function (MessageInterface $message) use (&$output) {
|
||||
$output[] = $message->uid();
|
||||
});
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
// May return wrong items. Do not return one with matching id.
|
||||
$output = array_filter($output, fn ($it) => $it > $id);
|
||||
$output = array_values($output);
|
||||
|
||||
// Otherwise, it's in reverse order.
|
||||
sort($output);
|
||||
|
||||
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);
|
||||
|
||||
$query->setFetchOrderAsc();
|
||||
|
||||
try {
|
||||
$query->each(function (MessageInterface $message) use (&$output) {
|
||||
$output[] = $message->uid();
|
||||
});
|
||||
} catch (CommonException $e) {
|
||||
throw new ImapError($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
// Otherwise, it's in reverse order.
|
||||
sort($output);
|
||||
|
||||
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[] = mb_convert_encoding($folder->path(), 'UTF-8', 'UTF7-IMAP');
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -27,7 +27,15 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Mail;
|
||||
namespace Espo\Core\Mail\Account\Storage;
|
||||
|
||||
class Headers extends \Laminas\Mail\Headers
|
||||
{}
|
||||
/**
|
||||
* Handle storage parameters.
|
||||
* To be used by extensions.
|
||||
*
|
||||
* @since 9.3.0
|
||||
*/
|
||||
interface Handler
|
||||
{
|
||||
public function handle(Params $params, string $id): Params;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -36,11 +36,23 @@ 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 */
|
||||
public const string AUTH_MECHANISM_PLAIN = 'plain';
|
||||
|
||||
/** @since 9.3.0 */
|
||||
public const string AUTH_MECHANISM_XOAUTH = 'xoauth';
|
||||
|
||||
/**
|
||||
* @param ?class-string<object> $imapHandlerClassName
|
||||
* @param self::AUTH_MECHANISM_* $authMechanism
|
||||
*/
|
||||
public function __construct(
|
||||
private ?string $host,
|
||||
@@ -51,7 +63,8 @@ class Params
|
||||
?string $imapHandlerClassName,
|
||||
private ?string $id,
|
||||
private ?string $userId,
|
||||
private ?string $emailAddress
|
||||
private ?string $emailAddress,
|
||||
private string $authMechanism = self::AUTH_MECHANISM_PLAIN,
|
||||
) {
|
||||
$this->imapHandlerClassName = $imapHandlerClassName;
|
||||
}
|
||||
@@ -109,6 +122,38 @@ class Params
|
||||
return $this->emailAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self::AUTH_MECHANISM_*
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function getAuthMechanism(): string
|
||||
{
|
||||
return $this->authMechanism;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self::AUTH_MECHANISM_* $authMechanism
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function withAuthMechanism(string $authMechanism): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->authMechanism = $authMechanism;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function withUsername(?string $username): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->username = $username;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withPassword(#[SensitiveParameter] ?string $password): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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,11 +43,15 @@ class MessageWrapper implements Message
|
||||
/** @var ?string[] */
|
||||
private ?array $flagList = null;
|
||||
|
||||
/**
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function __construct(
|
||||
private int $id,
|
||||
private ?Storage $storage = null,
|
||||
private ?Parser $parser = null,
|
||||
private ?string $fullRawContent = null
|
||||
private ?string $fullRawContent = null,
|
||||
private bool $peek = false,
|
||||
) {
|
||||
if ($storage) {
|
||||
$data = $storage->getHeaderAndFlags($id);
|
||||
@@ -108,7 +113,7 @@ class MessageWrapper implements Message
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$this->rawContent = $this->storage->getRawContent($this->id);
|
||||
$this->rawContent = $this->storage->getRawContent($this->id, $this->peek);
|
||||
}
|
||||
|
||||
return $this->rawContent ?? '';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -53,15 +53,12 @@ class DefaultTransportPreparator implements TransportPreparator
|
||||
|
||||
// 'SSL' is treated as implicit SSL/TLS. 'TLS' is treated as STARTTLS.
|
||||
// STARTTLS is the most common method.
|
||||
$scheme = $smtpParams->getSecurity() === 'SSL' ? 'smtps' : 'smtp';
|
||||
$scheme = $smtpParams->getSecurity() === SmtpParams::SECURITY_SSL_TLS ? 'smtps' : 'smtp';
|
||||
|
||||
if ($smtpParams->getSecurity() === 'TLS' && !defined('OPENSSL_VERSION_NUMBER')) {
|
||||
if ($smtpParams->getSecurity() === SmtpParams::SECURITY_START_TLS && !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(
|
||||
@@ -71,6 +68,10 @@ class DefaultTransportPreparator implements TransportPreparator
|
||||
)
|
||||
);
|
||||
|
||||
if (!$smtpParams->getSecurity() && $transport instanceof EsmtpTransport) {
|
||||
$transport->setAutoTls(false);
|
||||
}
|
||||
|
||||
if (!$transport instanceof EsmtpTransport) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -57,6 +57,9 @@ class SmtpParams
|
||||
public const AUTH_MECHANISM_PLAIN = 'plain';
|
||||
public const AUTH_MECHANISM_XOAUTH = 'xoauth';
|
||||
|
||||
public const string SECURITY_SSL_TLS = 'SSL';
|
||||
public const string SECURITY_START_TLS = 'TLS';
|
||||
|
||||
/** @var string[] */
|
||||
private array $paramList = [
|
||||
'server',
|
||||
|
||||
Reference in New Issue
Block a user