filterStringParser = $filterStringParser; $this->imapSearchProvider = $imapSearchProvider; $this->messageMapper = $messageMapper; $this->previewEnhancer = $previewEnhancer; $this->timeFactory = $timeFactory; } #[\Override] public function findMessage(Account $account, Mailbox $mailbox, Message $message): Message { $processed = $this->previewEnhancer->process( $account, $mailbox, [$message] ); if ($processed === []) { throw new DoesNotExistException('Message does not exist'); } return $processed[0]; } /** * @param Account $account * @param Mailbox $mailbox * @param string $sortOrder * @param string|null $filter * @param int|null $cursor * @param int|null $limit * @param string|null $view * * @return Message[] * * @throws ClientException * @throws ServiceException */ #[\Override] public function findMessages(Account $account, Mailbox $mailbox, string $sortOrder, ?string $filter, ?int $cursor, ?int $limit, ?string $userId, ?string $view): array { if ($mailbox->hasLocks($this->timeFactory->getTime())) { throw MailboxLockedException::from($mailbox); } if (!$mailbox->isCached()) { throw MailboxNotCachedException::from($mailbox); } $query = $this->filterStringParser->parse($filter); if ($cursor !== null) { $query->setCursor($cursor); } if ($view !== null) { $query->setThreaded($view === self::VIEW_THREADED); } // In flagged we don't want anything but flagged messages if ($mailbox->isSpecialUse(Horde_Imap_Client::SPECIALUSE_FLAGGED)) { $query->addFlag(Flag::is(Flag::FLAGGED)); } // Don't show deleted messages except for trash folders if (!$mailbox->isSpecialUse(Horde_Imap_Client::SPECIALUSE_TRASH)) { $query->addFlag(Flag::not(Flag::DELETED)); } return $this->previewEnhancer->process( $account, $mailbox, $this->messageMapper->findByIds($account->getUserId(), $this->getIdsLocally($account, $mailbox, $query, $sortOrder, $limit), $sortOrder, ), true, $userId ); } /** * Find messages across all mailboxes for a user * * @return Message[] * * @throws ServiceException */ #[\Override] public function findMessagesGlobally( IUser $user, SearchQuery $query, ?int $limit): array { return $this->messageMapper->findByIds($user->getUID(), $this->getIdsGlobally($user, $query, $limit), 'DESC' ); } /** * We combine local flag and headers merge with UIDs that match the body search if necessary * * @throws ServiceException */ private function getIdsLocally(Account $account, Mailbox $mailbox, SearchQuery $query, string $sortOrder, ?int $limit): array { if (empty($query->getBodies())) { return $this->messageMapper->findIdsByQuery($mailbox, $query, $sortOrder, $limit); } try { $fromImap = $this->imapSearchProvider->findMatches( $account, $mailbox, $query ); } catch (\Throwable $e) { // IMAP body search can fail (e.g. server charset, Cyrillic). Fall back to DB-only. $fromImap = []; } $hasHeaderCriteria = $query->getSubjects() !== [] || $query->getFrom() !== [] || $query->getTo() !== [] || $query->getCc() !== [] || $query->getBcc() !== []; // Messages matching body (IMAP) and other criteria $idsFromImap = $this->messageMapper->findIdsByQuery($mailbox, $query, $sortOrder, $limit, $fromImap); // When we have subject/from/to, also get messages matching by sender/subject (partial match in DB) // so that e.g. "Дми" finds "Дмитрий Мельник" even if "Дми" is not in the body if (!$hasHeaderCriteria) { return $idsFromImap; } $idsFromDb = $this->messageMapper->findIdsByQuery($mailbox, $query, $sortOrder, $limit, null); $mergedIds = array_values(array_unique(array_merge($idsFromImap, $idsFromDb))); return $this->messageMapper->findIdsSortedBySentAt($mailbox, $mergedIds, $sortOrder, $limit); } /** * We combine local flag and headers merge with UIDs that match the body search if necessary * * @todo find a way to search across all mailboxes efficiently without iterating over each of them and include IMAP results * * @throws ServiceException */ private function getIdsGlobally(IUser $user, SearchQuery $query, ?int $limit): array { return $this->messageMapper->findIdsGloballyByQuery($user, $query, $limit); } }