diff --git a/application/Espo/Core/Mail/Account/GroupAccount/Hooks/BeforeFetch.php b/application/Espo/Core/Mail/Account/GroupAccount/Hooks/BeforeFetch.php index a21a00a6e7..3217530129 100644 --- a/application/Espo/Core/Mail/Account/GroupAccount/Hooks/BeforeFetch.php +++ b/application/Espo/Core/Mail/Account/GroupAccount/Hooks/BeforeFetch.php @@ -68,7 +68,7 @@ class BeforeFetch implements BeforeFetchInterface { if ( $message->hasHeader('from') && - preg_match('/MAILER-DAEMON|POSTMASTER/i', $message->getHeader('from')) + preg_match('/MAILER-DAEMON|POSTMASTER/i', $message->getHeader('from') ?? '') ) { try { $toSkip = $this->processBounced($message); @@ -113,7 +113,7 @@ class BeforeFetch implements BeforeFetchInterface else { $to = $message->getHeader('to'); - if (preg_match('/\+bounce-qid-[a-z0-9\-]*/', $to, $m)) { + if (preg_match('/\+bounce-qid-[a-z0-9\-]*/', $to ?? '', $m)) { /** @var array{string} */ $arr = preg_split('/\+bounce-qid-/', $m[0], -1, \PREG_SPLIT_NO_EMPTY); diff --git a/application/Espo/Core/Mail/Account/GroupAccount/Service.php b/application/Espo/Core/Mail/Account/GroupAccount/Service.php index 08410abd3b..f05629a6e1 100644 --- a/application/Espo/Core/Mail/Account/GroupAccount/Service.php +++ b/application/Espo/Core/Mail/Account/GroupAccount/Service.php @@ -81,7 +81,8 @@ class Service $params = $params ->withPassword( - $params->getPassword() ?? $this->crypt->decrypt($account->getPassword()) + $params->getPassword() ?? + $this->crypt->decrypt($account->getPassword() ?? '') ) ->withImapHandlerClassName($account->getImapHandlerClassName()); } @@ -98,7 +99,8 @@ class Service $params = $params ->withPassword( - $params->getPassword() ?? $this->crypt->decrypt($account->getPassword()) + $params->getPassword() ?? + $this->crypt->decrypt($account->getPassword() ?? '') ) ->withImapHandlerClassName($account->getImapHandlerClassName()); } diff --git a/application/Espo/Core/Mail/Account/GroupAccount/StorageFactory.php b/application/Espo/Core/Mail/Account/GroupAccount/StorageFactory.php index ff788e08a8..292e274a39 100644 --- a/application/Espo/Core/Mail/Account/GroupAccount/StorageFactory.php +++ b/application/Espo/Core/Mail/Account/GroupAccount/StorageFactory.php @@ -64,7 +64,7 @@ class StorageFactory implements StorageFactoryInterface ->setSecurity($account->getSecurity()) ->setUsername($account->getUsername()) ->setPassword( - $this->crypt->decrypt($account->getPassword()) + $this->crypt->decrypt($account->getPassword() ?? '') ) ->setId($account->getId()) ->setImapHandlerClassName($account->getImapHandlerClassName()) @@ -104,7 +104,7 @@ class StorageFactory implements StorageFactoryInterface ); } - if (method_exists($handler, 'prepareProtocol')) { + if ($handler && method_exists($handler, 'prepareProtocol')) { // for backward compatibility $rawParams['ssl'] = $rawParams['security'] ?? null; diff --git a/application/Espo/Core/Mail/Account/PersonalAccount/Account.php b/application/Espo/Core/Mail/Account/PersonalAccount/Account.php index a3fb0ee578..d4ac03c29a 100644 --- a/application/Espo/Core/Mail/Account/PersonalAccount/Account.php +++ b/application/Espo/Core/Mail/Account/PersonalAccount/Account.php @@ -68,11 +68,15 @@ class Account implements AccountInterface throw new Error("No assigned user."); } - $this->user = $this->entityManager->getEntity(User::ENTITY_TYPE, $this->entity->getAssignedUser()->getId()); + $userId = $this->entity->getAssignedUser()->getId(); - if (!$this->user) { + $user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $userId); + + if (!$user) { throw new Error("Assigned user not found."); } + + $this->user = $user; } public function updateFetchData(FetchData $fetchData): void @@ -114,10 +118,12 @@ class Account implements AccountInterface { $linkMultiple = LinkMultiple::create(); + $userLink = $this->getUser(); + return $linkMultiple->withAdded( LinkMultipleItem - ::create($this->getUser()->getId()) - ->withName($this->getUser()->getName()) + ::create($userLink->getId()) + ->withName($userLink->getName() ?? '') ); } @@ -128,7 +134,13 @@ class Account implements AccountInterface public function getUser(): Link { - return $this->entity->getAssignedUser(); + $userLink = $this->entity->getAssignedUser(); + + if (!$userLink) { + throw new Error("No assigned user."); + } + + return $userLink; } public function getTeams(): LinkMultiple @@ -144,7 +156,7 @@ class Account implements AccountInterface return $linkMultiple->withAdded( LinkMultipleItem ::create($team->getId()) - ->withName($team->getName()) + ->withName($team->getName() ?? '') ); } diff --git a/application/Espo/Core/Mail/Account/PersonalAccount/Service.php b/application/Espo/Core/Mail/Account/PersonalAccount/Service.php index cb3d6b3ced..c1e5036f93 100644 --- a/application/Espo/Core/Mail/Account/PersonalAccount/Service.php +++ b/application/Espo/Core/Mail/Account/PersonalAccount/Service.php @@ -98,7 +98,8 @@ class Service $params = $params ->withPassword( - $params->getPassword() ?? $this->crypt->decrypt($account->getPassword()) + $params->getPassword() ?? + $this->crypt->decrypt($account->getPassword() ?? '') ) ->withImapHandlerClassName($account->getImapHandlerClassName()); } @@ -136,7 +137,8 @@ class Service $params = $params ->withPassword( - $params->getPassword() ?? $this->crypt->decrypt($account->getPassword()) + $params->getPassword() ?? + $this->crypt->decrypt($account->getPassword() ?? '') ) ->withImapHandlerClassName($account->getImapHandlerClassName()); } diff --git a/application/Espo/Core/Mail/Account/PersonalAccount/StorageFactory.php b/application/Espo/Core/Mail/Account/PersonalAccount/StorageFactory.php index dc67013e98..41f2788305 100644 --- a/application/Espo/Core/Mail/Account/PersonalAccount/StorageFactory.php +++ b/application/Espo/Core/Mail/Account/PersonalAccount/StorageFactory.php @@ -45,6 +45,7 @@ use Espo\Repositories\UserData as UserDataRepository; use Espo\ORM\EntityManager; +use LogicException; use Throwable; class StorageFactory implements StorageFactoryInterface @@ -71,16 +72,24 @@ class StorageFactory implements StorageFactoryInterface public function create(Account $account): LaminasStorage { + $userLink = $account->getUser(); + + if (!$userLink) { + throw new LogicException("No user for mail account."); + } + + $userId = $userLink->getId(); + $params = Params::createBuilder() ->setHost($account->getHost()) ->setPort($account->getPort()) ->setSecurity($account->getSecurity()) ->setUsername($account->getUsername()) ->setPassword( - $this->crypt->decrypt($account->getPassword()) + $this->crypt->decrypt($account->getPassword() ?? '') ) ->setEmailAddress($account->getEmailAddress()) - ->setUserId($account->getUser()->getId()) + ->setUserId($userId) ->setId($account->getId()) ->setImapHandlerClassName($account->getImapHandlerClassName()) ->build(); @@ -123,7 +132,7 @@ class StorageFactory implements StorageFactoryInterface ); } - if (method_exists($handler, 'prepareProtocol')) { + if ($handler && method_exists($handler, 'prepareProtocol')) { // for backward compatibility $rawParams['ssl'] = $rawParams['security'] ?? null; @@ -140,7 +149,7 @@ class StorageFactory implements StorageFactoryInterface $imapHandlers = $userData->get('imapHandlers') ?? (object) []; if (isset($imapHandlers->$emailAddress)) { - /** @var ?class-string */ + /** @var class-string */ $handlerClassName = $imapHandlers->$emailAddress; try { @@ -153,7 +162,7 @@ class StorageFactory implements StorageFactoryInterface ); } - if (method_exists($handler, 'prepareProtocol')) { + if ($handler && method_exists($handler, 'prepareProtocol')) { $imapParams = $handler->prepareProtocol($userId, $emailAddress, $rawParams); } } diff --git a/application/Espo/Core/Mail/FiltersMatcher.php b/application/Espo/Core/Mail/FiltersMatcher.php index 4c7dd75165..8d5fb95aee 100644 --- a/application/Espo/Core/Mail/FiltersMatcher.php +++ b/application/Espo/Core/Mail/FiltersMatcher.php @@ -49,13 +49,16 @@ class FiltersMatcher foreach ($filterList as $filter) { $filterCount = 0; - if ($filter->getFrom()) { + $from = $filter->getFrom(); + $subject = $filter->getSubject(); + + if ($from) { $filterCount++; if ( !$this->matchString( - strtolower($filter->getFrom()), - strtolower($email->getFromAddress()) + strtolower($from), + strtolower($email->getFromAddress() ?? '') ) ) { continue; @@ -70,10 +73,12 @@ class FiltersMatcher } } - if ($filter->getSubject()) { + if ($subject) { $filterCount++; - if (!$this->matchString($filter->getSubject(), $email->getSubject())) { + if ( + !$this->matchString($subject, $email->getSubject() ?? '') + ) { continue; } } @@ -100,9 +105,20 @@ class FiltersMatcher private function matchTo(Email $email, EmailFilter $filter): bool { + $filterTo = $filter->getTo(); + + if ($filterTo === null) { + return false; + } + if (count($email->getToAddressList())) { foreach ($email->getToAddressList() as $to) { - if ($this->matchString(strtolower($filter->getTo()), strtolower($to))) { + if ( + $this->matchString( + strtolower($filterTo), + strtolower($to) + ) + ) { return true; } } @@ -122,11 +138,11 @@ class FiltersMatcher continue; } - if (stripos($bodyPlain, $phrase) !== false) { + if ($bodyPlain && stripos($bodyPlain, $phrase) !== false) { return true; } - if (stripos($body, $phrase) !== false) { + if ($body && stripos($body, $phrase) !== false) { return true; } } diff --git a/application/Espo/Core/Mail/Importer.php b/application/Espo/Core/Mail/Importer.php index 60cd843711..5fd74080b5 100644 --- a/application/Espo/Core/Mail/Importer.php +++ b/application/Espo/Core/Mail/Importer.php @@ -34,6 +34,8 @@ use Espo\Repositories\Email as EmailRepository; use Espo\ORM\EntityManager; +use Espo\Core\Utils\DateTime as DateTimeUtil; + use Espo\Core\Notification\AssignmentNotificator; use Espo\Core\Notification\AssignmentNotificatorFactory; use Espo\Core\Notification\AssignmentNotificator\Params as AssignmentNotificatorParams; @@ -182,6 +184,7 @@ class Importer $parser->hasHeader($message, 'message-Id') && $parser->getHeader($message, 'message-Id') ) { + /** @var string */ $messageId = $parser->getMessageId($message); $email->set('messageId', $messageId); @@ -217,25 +220,35 @@ class Importer if ($parser->hasHeader($message, 'date')) { try { - $dt = new DateTime($parser->getHeader($message, 'date')); + /** @var string */ + $dateHeaderValue = $parser->getHeader($message, 'date'); - $dateSent = $dt->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s'); + $dt = new DateTime($dateHeaderValue); + + $dateSent = $dt + ->setTimezone(new DateTimeZone('UTC')) + ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); $email->set('dateSent', $dateSent); } catch (Exception $e) {} } else { - $email->set('dateSent', date('Y-m-d H:i:s')); + $email->set('dateSent', date(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT)); } if ($parser->hasHeader($message, 'delivery-Date')) { try { - $dt = new DateTime($parser->getHeader($message, 'delivery-Date')); + /** @var string */ + $deliveryDateHeaderValue = $parser->getHeader($message, 'delivery-Date'); - $deliveryDate = $dt->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s'); + $dt = new DateTime($deliveryDateHeaderValue); - $email->set('delivery-Date', $deliveryDate); + $deliveryDate = $dt + ->setTimezone(new DateTimeZone('UTC')) + ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); + + $email->set('deliveryDate', $deliveryDate); } catch (Exception $e) {} } @@ -280,6 +293,7 @@ class Importer if ($replied) { $email->set('repliedId', $replied->getId()); + /** @var string[] */ $repliedTeamIdList = $replied->getLinkMultipleIdList('teams'); foreach ($repliedTeamIdList as $repliedTeamId) { @@ -449,6 +463,7 @@ class Importer return; } + /** @var string[] */ $parentTeamIdList = $parent->getLinkMultipleIdList('teams'); foreach ($parentTeamIdList as $parentTeamId) { @@ -653,6 +668,7 @@ class Importer $duplicate->loadLinkMultipleField('users'); + /** @var string[] */ $fetchedUserIdList = $duplicate->getLinkMultipleIdList('users'); $duplicate->setLinkMultipleIdList('users', []); @@ -710,6 +726,7 @@ class Importer ); } + /** @var string[] */ $fetchedTeamIdList = $duplicate->getLinkMultipleIdList('teams'); foreach ($teamIdList as $teamId) { @@ -731,9 +748,9 @@ class Importer $dt->modify('+5 seconds'); - $executeAt = $dt->format('Y-m-d H:i:s'); + $executeAt = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); - $job = $this->entityManager->getEntity('Job'); + $job = $this->entityManager->getNewEntity('Job'); $job->set([ 'serviceName' => 'Stream', diff --git a/application/Espo/Core/Mail/Mail/Header/XQueueItemId.php b/application/Espo/Core/Mail/Mail/Header/XQueueItemId.php index bbe2b55863..4d6337db02 100644 --- a/application/Espo/Core/Mail/Mail/Header/XQueueItemId.php +++ b/application/Espo/Core/Mail/Mail/Header/XQueueItemId.php @@ -44,7 +44,7 @@ class XQueueItemId implements Header\HeaderInterface private ?string $id = null; /** - * @param ?string $headerLine + * @param string $headerLine * @return self */ public static function fromString($headerLine) diff --git a/application/Espo/Core/Mail/Mail/Storage/Imap.php b/application/Espo/Core/Mail/Mail/Storage/Imap.php index 94b01e56e6..2dd04d59fd 100644 --- a/application/Espo/Core/Mail/Mail/Storage/Imap.php +++ b/application/Espo/Core/Mail/Mail/Storage/Imap.php @@ -38,6 +38,8 @@ class Imap extends \Laminas\Mail\Storage\Imap { $nextUid = strval(intval($uid) + 1); + assert($this->protocol !== null); + return $this->protocol->search(['UID ' . $nextUid . ':*']); } @@ -46,6 +48,8 @@ class Imap extends \Laminas\Mail\Storage\Imap */ public function getIdsSinceDate(string $date): array { + assert($this->protocol !== null); + return $this->protocol->search(['SINCE "' . $date . '"']); } @@ -55,6 +59,8 @@ class Imap extends \Laminas\Mail\Storage\Imap */ public function getHeaderAndFlags(int $id): array { + assert($this->protocol !== null); + /** @var array{'RFC822.HEADER': string, FLAGS: string[]} */ $data = $this->protocol->fetch(['FLAGS', 'RFC822.HEADER'], $id); diff --git a/application/Espo/Core/Mail/MessageWrapper.php b/application/Espo/Core/Mail/MessageWrapper.php index c5f9e48f6d..98439aadb9 100644 --- a/application/Espo/Core/Mail/MessageWrapper.php +++ b/application/Espo/Core/Mail/MessageWrapper.php @@ -31,6 +31,8 @@ namespace Espo\Core\Mail; use Espo\Core\Mail\Account\Storage; +use RuntimeException; + class MessageWrapper implements Message { private int $id; @@ -81,17 +83,29 @@ class MessageWrapper implements Message public function hasHeader(string $name): bool { - return $this->getParser()->hasHeader($this, $name); + if (!$this->parser) { + throw new RuntimeException(); + } + + return $this->parser->hasHeader($this, $name); } public function getHeader(string $attribute): ?string { - return $this->getParser()->getHeader($this, $attribute); + if (!$this->parser) { + throw new RuntimeException(); + } + + return $this->parser->getHeader($this, $attribute); } public function getRawContent(): string { if (is_null($this->rawContent)) { + if (!$this->storage) { + throw new RuntimeException(); + } + $this->rawContent = $this->storage->getRawContent($this->id); } diff --git a/application/Espo/Core/Mail/Sender.php b/application/Espo/Core/Mail/Sender.php index e7d1d475df..b347759777 100644 --- a/application/Espo/Core/Mail/Sender.php +++ b/application/Espo/Core/Mail/Sender.php @@ -602,7 +602,9 @@ class Sender $htmlPart = null; - if ($email->get('isHtml')) { + $isHtml = $email->get('isHtml'); + + if ($isHtml) { $htmlPart = new MimePart($email->getBodyForSending()); $htmlPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE; @@ -613,7 +615,7 @@ class Sender if (!empty($attachmentPartList)) { $messageType = 'multipart/related'; - if ($email->get('isHtml')) { + if ($isHtml) { $content = new MimeMessage(); $content->addPart($textPart); @@ -637,7 +639,7 @@ class Sender } } else { - if ($email->get('isHtml')) { + if ($isHtml) { $body->setParts([$textPart, $htmlPart]); $messageType = 'multipart/alternative'; @@ -698,6 +700,8 @@ class Sender $message->getHeaders()->addHeader($messageIdHeader); + assert($this->transport !== null); + $this->transport->send($message); $email->set('status', 'Sent');