From 313785b39f889bcb44503a672598e0f9bb8ead46 Mon Sep 17 00:00:00 2001 From: SebastianKrupinski Date: Tue, 22 Oct 2024 00:21:11 -0400 Subject: [PATCH] fix: perform bulk message actions Signed-off-by: SebastianKrupinski --- appinfo/routes.php | 5 ++ lib/Controller/MessagesController.php | 21 ++++- lib/Db/MailAccountMapper.php | 19 +++++ lib/Db/MessageMapper.php | 22 +++++ lib/IMAP/MessageMapper.php | 36 +++++++++ lib/Service/MessageOperationService.php | 103 ++++++++++++++++++++++++ 6 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 lib/Service/MessageOperationService.php diff --git a/appinfo/routes.php b/appinfo/routes.php index eddd1a5ee3..e582688d6a 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -240,6 +240,11 @@ 'url' => '/api/messages/{id}/flags', 'verb' => 'PUT' ], + [ + 'name' => 'messages#changeFlags', + 'url' => '/api/messages/flags', + 'verb' => 'PUT' + ], [ 'name' => 'messages#setTag', 'url' => '/api/messages/{id}/tags/{imapLabel}', diff --git a/lib/Controller/MessagesController.php b/lib/Controller/MessagesController.php index 88b6299e21..de24f2cdb2 100755 --- a/lib/Controller/MessagesController.php +++ b/lib/Controller/MessagesController.php @@ -30,6 +30,7 @@ use OCA\Mail\Service\AccountService; use OCA\Mail\Service\AiIntegrations\AiIntegrationsService; use OCA\Mail\Service\ItineraryService; +use OCA\Mail\Service\MessageOperationService; use OCA\Mail\Service\SmimeService; use OCA\Mail\Service\SnoozeService; use OCP\AppFramework\Controller; @@ -94,7 +95,8 @@ public function __construct(string $appName, IDkimService $dkimService, IUserPreferences $preferences, SnoozeService $snoozeService, - AiIntegrationsService $aiIntegrationService) { + AiIntegrationsService $aiIntegrationService, + private MessageOperationService $messageOperationService) { parent::__construct($appName, $request); $this->accountService = $accountService; $this->mailManager = $mailManager; @@ -782,6 +784,23 @@ public function setFlags(int $id, array $flags): JSONResponse { return new JSONResponse(); } + /** + * + * @NoAdminRequired + * + * @param array $identifiers + * @param array $flags + * + * @return JSONResponse + */ + #[TrapError] + public function changeFlags(array $identifiers, array $flags): JSONResponse { + + $this->messageOperationService->changeFlags($this->currentUserId, $identifiers, $flags); + // TODO: add proper responses + return new JSONResponse(); + } + /** * @NoAdminRequired * diff --git a/lib/Db/MailAccountMapper.php b/lib/Db/MailAccountMapper.php index 0e41bbe976..78821b5dfc 100644 --- a/lib/Db/MailAccountMapper.php +++ b/lib/Db/MailAccountMapper.php @@ -66,6 +66,25 @@ public function findById(int $id): MailAccount { return $this->findEntity($query); } + /** + * Finds all mail accounts by account ids + * + * @param array $identifiers + * + * @return array + */ + public function findByIds(array $identifiers): array { + + $cmd = $this->db->getQueryBuilder(); + $cmd->select('*') + ->from($this->getTableName()) + ->where( + $cmd->expr()->in('id', $cmd->createNamedParameter($identifiers, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY) + ); + + return $this->findEntities($cmd); + } + /** * Finds all Mail Accounts by user id existing for this user * diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php index b9b63af0b0..502dc947dd 100644 --- a/lib/Db/MessageMapper.php +++ b/lib/Db/MessageMapper.php @@ -178,6 +178,28 @@ public function findUidsForIds(Mailbox $mailbox, array $ids) { }, array_chunk($ids, 1000)); } + /** + * @param array $identifiers + * + * @return array + */ + public function findMailboxAndUid(array $identifiers): array { + + if ($identifiers === []) { + return []; + } + + $cmd = $this->db->getQueryBuilder(); + $cmd->select('mailbox_id', 'uid') + ->from($this->getTableName()) + ->where( + $cmd->expr()->in('id', $cmd->createNamedParameter($identifiers, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY) + ); + + return $cmd->executeQuery()->fetchAll(); + + } + /** * @param Account $account * diff --git a/lib/IMAP/MessageMapper.php b/lib/IMAP/MessageMapper.php index 20f1b6b48f..2089c0f24c 100644 --- a/lib/IMAP/MessageMapper.php +++ b/lib/IMAP/MessageMapper.php @@ -419,6 +419,24 @@ public function addFlag(Horde_Imap_Client_Socket $client, ); } + /** + * @throws Horde_Imap_Client_Exception + */ + public function addFlags( + Horde_Imap_Client_Socket $client, + Mailbox $mailbox, + array $uids, + array $flags + ): void { + $client->store( + $mailbox->getName(), + [ + 'ids' => new Horde_Imap_Client_Ids($uids), + 'add' => $flags, + ] + ); + } + /** * @throws Horde_Imap_Client_Exception */ @@ -435,6 +453,24 @@ public function removeFlag(Horde_Imap_Client_Socket $client, ); } + /** + * @throws Horde_Imap_Client_Exception + */ + public function removeFlags( + Horde_Imap_Client_Socket $client, + Mailbox $mailbox, + array $uids, + array $flags + ): void { + $client->store( + $mailbox->getName(), + [ + 'ids' => new Horde_Imap_Client_Ids($uids), + 'remove' => $flags, + ] + ); + } + /** * @param Horde_Imap_Client_Socket $client * @param Mailbox $mailbox diff --git a/lib/Service/MessageOperationService.php b/lib/Service/MessageOperationService.php new file mode 100644 index 0000000000..004d6c5d8e --- /dev/null +++ b/lib/Service/MessageOperationService.php @@ -0,0 +1,103 @@ + [message_id, message_id]] + protected function groupByMailbox(array $collection) { + return array_reduce($collection, function ($carry, $pair) { + if (!isset($carry[$pair['mailbox_id']])) { + $carry[$pair['mailbox_id']] = []; + } + $carry[$pair['mailbox_id']][] = $pair['uid']; + return $carry; + }, []); + } + + // group mailboxes by account ['account_id' => [mailbox object]] + protected function groupByAccount(array $collection) { + return array_reduce($collection, function ($carry, $entry) { + if (!isset($carry[$entry->getAccountId()])) { + $carry[$entry->getAccountId()] = []; + } + $carry[$entry->getAccountId()][] = $entry; + return $carry; + }, []); + } + + /** + * @param string $userId system user id + * @param array $identifiers message ids + * @param array $flags message flags + */ + public function changeFlags(string $userId, array $identifiers, array $flags): void { + + // retrieve meta data [uid, mailbox_id] for all messages + $messages = $this->groupByMailbox($this->messageMapper->findMailboxAndUid($identifiers)); + // retrieve all mailboxes + $mailboxes = $this->groupByAccount($this->mailboxMapper->findByIds(array_keys($messages))); + // retrieve all accounts + $accounts = $this->accountMapper->findByIds(array_keys($mailboxes)); + + foreach ($accounts as $account) { + $account = new Account($account); + // determine if account belongs to the user and skip if not + if ($account->getUserId() != $userId) { + continue; + } + + $client = $this->clientFactory->getClient($account); + + try { + foreach ($mailboxes[$account->getId()] as $mailbox) { + foreach ($flags as $flag => $value) { + $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); + // Only send system flags to the IMAP server as other flags might not be supported + $imapFlags = $this->mailManager->filterFlags($client, $account, $flag, $mailbox->getName()); + if (empty($imapFlags)) { + continue; + } + if ($value) { + $this->imapMessageMapper->addFlags($client, $mailbox, $messages[$mailbox->getId()], $imapFlags); + } else { + $this->imapMessageMapper->removeFlags($client, $mailbox, $messages[$mailbox->getId()], $imapFlags); + } + } + } + + } catch (Horde_Imap_Client_Exception $e) { + // TODO: Add proper error handling + } finally { + $client->logout(); + } + } + + } + +} \ No newline at end of file