Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--appinfo/info.xml2
-rw-r--r--appinfo/routes.php10
-rw-r--r--lib/Account.php7
-rw-r--r--lib/Contracts/IMailManager.php29
-rwxr-xr-xlib/Controller/MessagesController.php67
-rw-r--r--lib/Db/MailAccountMapper.php9
-rw-r--r--lib/Db/Message.php46
-rw-r--r--lib/Db/MessageMapper.php143
-rw-r--r--lib/Db/Tag.php68
-rw-r--r--lib/Db/TagMapper.php219
-rw-r--r--lib/Migration/Version1100Date20210304143008.php120
-rw-r--r--lib/Model/IMAPMessage.php99
-rw-r--r--lib/Service/MailManager.php113
-rw-r--r--lib/Service/Search/Flag.php4
-rw-r--r--lib/Service/SetupService.php16
-rw-r--r--lib/Service/Sync/ImapToDbSynchronizer.php14
-rw-r--r--src/components/Envelope.vue4
-rw-r--r--src/components/MenuEnvelope.vue2
-rw-r--r--src/components/ThreadEnvelope.vue4
-rw-r--r--src/service/MessageService.js18
-rw-r--r--src/store/actions.js12
-rw-r--r--src/store/mutations.js4
-rw-r--r--tests/Unit/Controller/MessagesControllerTest.php253
-rw-r--r--tests/Unit/Listener/MessageCacheUpdaterListenerTest.php3
-rw-r--r--tests/Unit/Service/MailManagerTest.php109
-rw-r--r--tests/Unit/Service/SetupServiceTest.php13
-rw-r--r--tests/psalm-baseline.xml5
27 files changed, 1252 insertions, 141 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 16d00a06b..151a27ea5 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -12,7 +12,7 @@
- **🙈 We’re not reinventing the wheel!** Based on the great [Horde](http://horde.org) libraries.
- **📬 Want to host your own mail server?** We don’t have to reimplement this as you could set up [Mail-in-a-Box](https://mailinabox.email)!
]]></description>
- <version>1.10.0-alpha.1</version>
+ <version>1.10.0-alpha.2</version>
<licence>agpl</licence>
<author>Christoph Wurst</author>
<author>Greta Doçi</author>
diff --git a/appinfo/routes.php b/appinfo/routes.php
index d25b478db..3f8e4bc3e 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -175,6 +175,16 @@ return [
'verb' => 'PUT'
],
[
+ 'name' => 'messages#setTag',
+ 'url' => '/api/messages/{id}/tags/{imapLabel}',
+ 'verb' => 'PUT'
+ ],
+ [
+ 'name' => 'messages#removeTag',
+ 'url' => '/api/messages/{id}/tags/{imapLabel}',
+ 'verb' => 'DELETE'
+ ],
+ [
'name' => 'messages#move',
'url' => '/api/messages/{id}/move',
'verb' => 'POST'
diff --git a/lib/Account.php b/lib/Account.php
index 456fac531..c6c3bb2f4 100644
--- a/lib/Account.php
+++ b/lib/Account.php
@@ -218,6 +218,13 @@ class Account implements JsonSerializable {
}
/**
+ * @return string
+ */
+ public function getUserId() {
+ return $this->account->getUserId();
+ }
+
+ /**
* @deprecated
*
* @return void
diff --git a/lib/Contracts/IMailManager.php b/lib/Contracts/IMailManager.php
index 9b3f48e41..33fe6d970 100644
--- a/lib/Contracts/IMailManager.php
+++ b/lib/Contracts/IMailManager.php
@@ -23,15 +23,16 @@ declare(strict_types=1);
namespace OCA\Mail\Contracts;
+use OCA\Mail\Db\Tag;
+use OCA\Mail\Folder;
use OCA\Mail\Account;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\Message;
-use OCA\Mail\Exception\ClientException;
-use OCA\Mail\Exception\ServiceException;
-use OCA\Mail\Folder;
+use OCA\Mail\Service\Quota;
use OCA\Mail\IMAP\FolderStats;
use OCA\Mail\Model\IMAPMessage;
-use OCA\Mail\Service\Quota;
+use OCA\Mail\Exception\ClientException;
+use OCA\Mail\Exception\ServiceException;
use OCP\AppFramework\Db\DoesNotExistException;
interface IMailManager {
@@ -172,6 +173,18 @@ interface IMailManager {
/**
* @param Account $account
+ * @param string $mailbox
+ * @param int $uid
+ * @param Tag $tag
+ * @param bool $value
+ *
+ * @throws ClientException
+ * @throws ServiceException
+ */
+ public function tagMessage(Account $account, string $mailbox, Message $message, Tag $tag, bool $value): void;
+
+ /**
+ * @param Account $account
*
* @return Quota|null
*/
@@ -229,4 +242,12 @@ interface IMailManager {
* @return array
*/
public function getMailAttachments(Account $account, Mailbox $mailbox, Message $message) : array;
+
+ /**
+ * @param string $imapLabel
+ * @param string $userId
+ * @return Tag
+ * @throws DoesNotExistException
+ */
+ public function getTagByImapLabel(string $imapLabel, string $userId): Tag;
}
diff --git a/lib/Controller/MessagesController.php b/lib/Controller/MessagesController.php
index 6361d415e..0cfa3a8eb 100755
--- a/lib/Controller/MessagesController.php
+++ b/lib/Controller/MessagesController.php
@@ -41,7 +41,6 @@ use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Http\AttachmentDownloadResponse;
use OCA\Mail\Http\HtmlResponse;
-use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\ItineraryService;
use OCP\AppFramework\Controller;
@@ -653,6 +652,68 @@ class MessagesController extends Controller {
* @NoAdminRequired
* @TrapError
*
+ * @param int $id
+ * @param string $imapLabel
+ *
+ * @return JSONResponse
+ *
+ * @throws ClientException
+ * @throws ServiceException
+ */
+ public function setTag(int $id, string $imapLabel): JSONResponse {
+ try {
+ $message = $this->mailManager->getMessage($this->currentUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
+ $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+
+ try {
+ $tag = $this->mailManager->getTagByImapLabel($imapLabel, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+
+ $this->mailManager->tagMessage($account, $mailbox->getName(), $message, $tag, true);
+ return new JSONResponse();
+ }
+
+ /**
+ * @NoAdminRequired
+ * @TrapError
+ *
+ * @param int $id
+ * @param string $imapLabel
+ *
+ * @return JSONResponse
+ *
+ * @throws ClientException
+ * @throws ServiceException
+ */
+ public function removeTag(int $id, string $imapLabel): JSONResponse {
+ try {
+ $message = $this->mailManager->getMessage($this->currentUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
+ $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+
+ try {
+ $tag = $this->mailManager->getTagByImapLabel($imapLabel, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+
+ $this->mailManager->tagMessage($account, $mailbox->getName(), $message, $tag, false);
+ return new JSONResponse();
+ }
+
+ /**
+ * @NoAdminRequired
+ * @TrapError
+ *
* @param int $accountId
* @param string $folderId
* @param int $id
@@ -705,10 +766,10 @@ class MessagesController extends Controller {
}
/**
- * @param array $attachment
- *
* Determines if the content of this attachment is an image
*
+ * @param array $attachment
+ *
* @return boolean
*/
private function attachmentIsImage(array $attachment): bool {
diff --git a/lib/Db/MailAccountMapper.php b/lib/Db/MailAccountMapper.php
index 5b4fec756..1dccbb674 100644
--- a/lib/Db/MailAccountMapper.php
+++ b/lib/Db/MailAccountMapper.php
@@ -148,4 +148,13 @@ class MailAccountMapper extends QBMapper {
return $this->findEntities($query);
}
+
+ public function getAllUserIdsWithAccounts(): array {
+ $qb = $this->db->getQueryBuilder();
+ $query = $qb
+ ->selectDistinct('user_id')
+ ->from($this->getTableName());
+
+ return $this->findEntities($query);
+ }
}
diff --git a/lib/Db/Message.php b/lib/Db/Message.php
index 3c6a46743..ea27288b0 100644
--- a/lib/Db/Message.php
+++ b/lib/Db/Message.php
@@ -86,8 +86,9 @@ class Message extends Entity implements JsonSerializable {
'forwarded',
'junk',
'notjunk',
- 'important',
- 'mdnsent'
+ 'mdnsent',
+ Tag::LABEL_IMPORTANT,
+ '$important' // @todo remove this when we have removed all references on IMAP to $important @link https://github.com/nextcloud/mail/issues/25
];
protected $uid;
@@ -125,6 +126,9 @@ class Message extends Entity implements JsonSerializable {
/** @var AddressList */
private $bcc;
+ /** @var Tag[] */
+ private $tags = [];
+
public function __construct() {
$this->from = new AddressList([]);
$this->to = new AddressList([]);
@@ -200,6 +204,20 @@ class Message extends Entity implements JsonSerializable {
}
/**
+ * @return Tag[]
+ */
+ public function getTags(): array {
+ return $this->tags;
+ }
+
+ /**
+ * @param array $tags
+ */
+ public function setTags(array $tags): void {
+ $this->tags = $tags;
+ }
+
+ /**
* @return AddressList
*/
public function getCc(): AddressList {
@@ -232,14 +250,26 @@ class Message extends Entity implements JsonSerializable {
// Ignore
return;
}
-
- $this->setter(
- $this->columnToProperty("flag_$flag"),
- [$value]
- );
+ if ($flag === Tag::LABEL_IMPORTANT) {
+ $this->setFlagImportant($value);
+ } else {
+ $this->setter(
+ $this->columnToProperty("flag_$flag"),
+ [$value]
+ );
+ }
}
public function jsonSerialize() {
+ $tags = $this->getTags();
+ $indexed = array_combine(
+ array_map(
+ function (Tag $tag) {
+ return $tag->getImapLabel();
+ }, $tags),
+ $tags
+ );
+
return [
'databaseId' => $this->getId(),
'uid' => $this->getUid(),
@@ -253,10 +283,10 @@ class Message extends Entity implements JsonSerializable {
'draft' => $this->getFlagDraft(),
'forwarded' => $this->getFlagForwarded(),
'hasAttachments' => $this->getFlagAttachments() ?? false,
- 'important' => $this->getFlagImportant(),
'junk' => $this->getFlagJunk(),
'mdnsent' => $this->getFlagMdnsent(),
],
+ 'tags' => $indexed,
'from' => $this->getFrom()->jsonSerialize(),
'to' => $this->getTo()->jsonSerialize(),
'cc' => $this->getCc()->jsonSerialize(),
diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php
index 796c2b551..b51217e5c 100644
--- a/lib/Db/MessageMapper.php
+++ b/lib/Db/MessageMapper.php
@@ -25,26 +25,28 @@ declare(strict_types=1);
namespace OCA\Mail\Db;
+use OCP\IUser;
+use function ltrim;
use OCA\Mail\Account;
use OCA\Mail\Address;
-use OCA\Mail\AddressList;
-use OCA\Mail\IMAP\Threading\DatabaseMessage;
-use OCA\Mail\Service\Search\Flag;
-use OCA\Mail\Service\Search\FlagExpression;
-use OCA\Mail\Service\Search\SearchQuery;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\AppFramework\Db\QBMapper;
-use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\DB\QueryBuilder\IQueryBuilder;
-use OCP\IDBConnection;
-use OCP\IUser;
use RuntimeException;
-use function array_combine;
-use function array_keys;
+use OCP\IDBConnection;
use function array_map;
use function get_class;
-use function ltrim;
use function mb_substr;
+use function array_keys;
+use function array_combine;
+use function array_udiff;
+use OCA\Mail\AddressList;
+use OCA\Mail\Service\Search\Flag;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCA\Mail\Service\Search\SearchQuery;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCA\Mail\Service\Search\FlagExpression;
+use OCA\Mail\IMAP\Threading\DatabaseMessage;
+use OCA\Mail\Support\PerformanceLogger;
+use OCP\AppFramework\Db\DoesNotExistException;
/**
* @template-extends QBMapper<Message>
@@ -54,10 +56,21 @@ class MessageMapper extends QBMapper {
/** @var ITimeFactory */
private $timeFactory;
+ /** @var TagMapper */
+ private $tagMapper;
+
+ /** @var PerformanceLogger */
+ private $performanceLogger;
+
public function __construct(IDBConnection $db,
- ITimeFactory $timeFactory) {
+ ITimeFactory $timeFactory,
+ TagMapper $tagMapper,
+ PerformanceLogger $performanceLogger
+ ) {
parent::__construct($db, 'mail_messages');
$this->timeFactory = $timeFactory;
+ $this->tagMapper = $tagMapper;
+ $this->performanceLogger = $performanceLogger;
}
/**
@@ -119,7 +132,7 @@ class MessageMapper extends QBMapper {
$query->expr()->eq('m.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)
);
- $results = $this->findRecipients($this->findEntities($query));
+ $results = $this->findRelatedData($this->findEntities($query));
if (empty($results)) {
throw new DoesNotExistException("Message $id does not exist");
}
@@ -229,7 +242,11 @@ class MessageMapper extends QBMapper {
$this->db->commit();
}
- public function insertBulk(Message ...$messages): void {
+ /**
+ * @param Message ...$messages
+ * @return void
+ */
+ public function insertBulk(Account $account, Message ...$messages): void {
$this->db->beginTransaction();
$qb1 = $this->db->getQueryBuilder();
@@ -309,19 +326,26 @@ class MessageMapper extends QBMapper {
$qb2->execute();
}
}
+ foreach ($message->getTags() as $tag) {
+ $this->tagMapper->tagMessage($tag, $message->getMessageId(), $account->getUserId());
+ }
}
$this->db->commit();
}
/**
- * @param Message ...$messages
- *
+ * @param Account $account
+ * @param Message[] $messages
* @return Message[]
*/
- public function updateBulk(Message ...$messages): array {
+ public function updateBulk(Account $account, Message ...$messages): array {
$this->db->beginTransaction();
+ $perf = $this->performanceLogger->start(
+ 'partial sync ' . $account->getId() . ':' . $account->getName()
+ );
+
$query = $this->db->getQueryBuilder();
$query->update($this->getTableName())
->set('flag_answered', $query->createParameter('flag_answered'))
@@ -339,7 +363,9 @@ class MessageMapper extends QBMapper {
$query->expr()->eq('uid', $query->createParameter('uid')),
$query->expr()->eq('mailbox_id', $query->createParameter('mailbox_id'))
));
-
+ // get all tags before the loop and create a mapping [message_id => [tag,...]]
+ $tags = $this->tagMapper->getAllTagsForMessages($messages);
+ $perf->step("Selected Tags for all messages");
foreach ($messages as $message) {
if (empty($message->getUpdatedFields())) {
// Micro optimization
@@ -360,13 +386,59 @@ class MessageMapper extends QBMapper {
$query->setParameter('flag_important', $message->getFlagImportant(), IQueryBuilder::PARAM_BOOL);
$query->execute();
+ $perf->step('Updated message ' . $message->getId());
+
+
+ $imapTags = $message->getTags();
+ $dbTags = $tags[$message->getMessageId()] ?? [];
+
+ if (empty($imapTags) === true && empty($dbTags) === true) {
+ // neither old nor new tags
+ continue;
+ }
+
+ $toAdd = array_udiff($imapTags, $dbTags, function (Tag $a, Tag $b) {
+ return strcmp($a->getImapLabel(), $b->getImapLabel());
+ });
+ foreach ($toAdd as $tag) {
+ // add logging
+ $this->tagMapper->tagMessage($tag, $message->getMessageId(), $account->getUserId());
+ }
+ $perf->step("Tagged messages");
+
+ if (empty($dbTags) === true) {
+ // we have nothing to possibly remove
+ continue;
+ }
+
+ $toRemove = array_udiff($dbTags, $imapTags, function (Tag $a, Tag $b) {
+ return strcmp($a->getImapLabel(), $b->getImapLabel());
+ });
+ foreach ($toRemove as $tag) {
+ //add logging
+ $this->tagMapper->untagMessage($tag, $message->getMessageId());
+ }
+ $perf->step("Untagged messages");
}
$this->db->commit();
+ $perf->end();
return $messages;
}
+ public function getTags(Message $message) : array {
+ $mqb = $this->db->getQueryBuilder();
+ $mqb->select('tag_id')
+ ->from('mail_message_tags')
+ ->where($mqb->expr()->eq('imap_message_id', $mqb->createNamedParameter($message->getMessageId())));
+ $result = $mqb->execute();
+ $ids = array_map(function (array $row) {
+ return (int)$row['tag_id'];
+ }, $result->fetchAll());
+
+ return $ids;
+ }
/**
* @param Message ...$messages
*
@@ -480,7 +552,7 @@ class MessageMapper extends QBMapper {
$qb->expr()->isNotNull('m1.thread_root_id')
)
->orderBy('sent_at', 'desc');
- return $this->findRecipients($this->findEntities($selectMessages));
+ return $this->findRelatedData($this->findEntities($selectMessages));
}
/**
@@ -711,6 +783,10 @@ class MessageMapper extends QBMapper {
}
private function flagToColumnName(Flag $flag): string {
+ // workaround for @link https://github.com/nextcloud/mail/issues/25
+ if ($flag->getFlag() === Tag::LABEL_IMPORTANT) {
+ return "flag_important";
+ }
$key = ltrim($flag->getFlag(), '\\$');
return "flag_$key";
}
@@ -733,7 +809,7 @@ class MessageMapper extends QBMapper {
)
->orderBy('sent_at', 'desc');
- return $this->findRecipients($this->findEntities($select));
+ return $this->findRelatedData($this->findEntities($select));
}
/**
@@ -755,7 +831,7 @@ class MessageMapper extends QBMapper {
)
->orderBy('sent_at', 'desc');
- return $this->findRecipients($this->findEntities($select));
+ return $this->findRelatedData($this->findEntities($select));
}
/**
@@ -809,6 +885,21 @@ class MessageMapper extends QBMapper {
}
/**
+ * @param Message[] $messages
+ * @return Message[]
+ */
+ public function findRelatedData(array $messages): array {
+ $messages = $this->findRecipients($messages);
+ $tags = $this->tagMapper->getAllTagsForMessages($messages);
+ /** @var Message $message */
+ $messages = array_map(function ($message) use ($tags) {
+ $message->setTags($tags[$message->getMessageId()] ?? []);
+ return $message;
+ }, $messages);
+ return $messages;
+ }
+
+ /**
* @param Mailbox $mailbox
* @param int $highest
*
@@ -847,7 +938,7 @@ class MessageMapper extends QBMapper {
$qb->expr()->gt('updated_at', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT))
);
- return $this->findRecipients($this->findEntities($select));
+ return $this->findRelatedData($this->findEntities($select));
}
/**
@@ -870,7 +961,7 @@ class MessageMapper extends QBMapper {
->orderBy('sent_at', 'desc')
->setMaxResults($limit);
- return $this->findRecipients($this->findEntities($select));
+ return $this->findRelatedData($this->findEntities($select));
}
public function deleteOrphans(): void {
diff --git a/lib/Db/Tag.php b/lib/Db/Tag.php
new file mode 100644
index 000000000..261f9c393
--- /dev/null
+++ b/lib/Db/Tag.php
@@ -0,0 +1,68 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @author 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Mail\Db;
+
+use JsonSerializable;
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @method string getUserId()
+ * @method void setUserId(string $userId)
+ * @method string getDisplayName()
+ * @method void setDisplayName(string $displayName)
+ * @method string getImapLabel()
+ * @method void setImapLabel(string $imapLabel)
+ * @method string getColor()
+ * @method void setColor(string $color)
+ * @method bool getIsDefaultTag()
+ * @method void setIsDefaultTag(bool $flag)
+ */
+class Tag extends Entity implements JsonSerializable {
+ protected $userId;
+ protected $displayName;
+ protected $imapLabel;
+ protected $color;
+ protected $isDefaultTag;
+
+ public const LABEL_IMPORTANT = '$label1';
+
+ public function __construct() {
+ $this->addType('isDefaultTag', 'boolean');
+ }
+ /**
+ * @return array
+ */
+ public function jsonSerialize() {
+ return [
+ 'id' => $this->getId(),
+ 'userId' => $this->getUserId(),
+ 'displayName' => $this->getDisplayName(),
+ 'imapLabel' => $this->getImapLabel(),
+ 'color' => $this->getColor(),
+ 'isDefaultTag' => $this->getIsDefaultTag(),
+ ];
+ }
+}
diff --git a/lib/Db/TagMapper.php b/lib/Db/TagMapper.php
new file mode 100644
index 000000000..219590223
--- /dev/null
+++ b/lib/Db/TagMapper.php
@@ -0,0 +1,219 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @author 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Mail\Db;
+
+use function array_map;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\Entity;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\IL10N;
+
+/**
+ * @template-extends QBMapper<Tag>
+ */
+class TagMapper extends QBMapper {
+
+ /** @var IL10N */
+ private $l10n;
+
+ public function __construct(IDBConnection $db,
+ IL10N $l10n) {
+ parent::__construct($db, 'mail_tags');
+ $this->l10n = $l10n;
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function getTagByImapLabel(string $imapLabel, string $userId): Entity {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from($this->getTableName())
+ ->where(
+ $qb->expr()->eq('imap_label', $qb->createNamedParameter($imapLabel)),
+ $qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
+ );
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function getTagForUser(int $id, string $userId): Entity {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from($this->getTableName())
+ ->where(
+ $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)),
+ $qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
+ );
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * @return Tag[]
+ * @throws DoesNotExistException
+ */
+ public function getAllTagForUser(string $userId): array {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from($this->getTableName())
+ ->where(
+ $qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
+ );
+ return $this->findEntities($qb);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function getTag(int $id): Entity {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * Tag a message in the DB
+ *
+ * To tag (flag) a message on IMAP, @see \OCA\Mail\Service\MailManager::tagMessage
+ */
+ public function tagMessage(Tag $tag, string $messageId, string $userId): void {
+ /** @var Tag $exists */
+ try {
+ $exists = $this->getTagByImapLabel($tag->getImapLabel(), $userId);
+ $tag->setId($exists->getId());
+ } catch (DoesNotExistException $e) {
+ $tag = $this->insert($tag);
+ }
+
+ $qb = $this->db->getQueryBuilder();
+ $qb->insert('mail_message_tags');
+ $qb->setValue('imap_message_id', $qb->createNamedParameter($messageId));
+ $qb->setValue('tag_id', $qb->createNamedParameter($tag->getId(), IQueryBuilder::PARAM_INT));
+ $qb->execute();
+ }
+
+ /**
+ * Remove a tag from a DB message
+ *
+ * This does not(!) untag a message on IMAP
+ */
+ public function untagMessage(Tag $tag, string $messageId): void {
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('mail_message_tags')
+ ->where($qb->expr()->eq('imap_message_id', $qb->createNamedParameter($messageId)))
+ ->where($qb->expr()->eq('tag_id', $qb->createNamedParameter($tag->getId())));
+ $qb->execute();
+ }
+
+ /**
+ * @param Message[] $messages
+ * @return Tag[][]
+ */
+ public function getAllTagsForMessages(array $messages): array {
+ $ids = array_map(function (Message $message) {
+ return $message->getMessageId();
+ }, $messages);
+
+ $qb = $this->db->getQueryBuilder();
+ $idsQuery = $qb->select('mt.*')
+ ->from('mail_message_tags', 'mt')
+ ->where(
+ $qb->expr()->in('imap_message_id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_STR_ARRAY))
+ );
+ $idsQuery = $idsQuery->execute();
+ $queryResult = $idsQuery->fetchAll();
+ if (empty($queryResult)) {
+ return [];
+ }
+ $result = [];
+ foreach ($queryResult as $qr) {
+ $result[] = $qr['imap_message_id'];
+ $result[$qr['imap_message_id']][] = $this->getTag((int)$qr['tag_id']);
+ };
+ return $result;
+ }
+
+ /**
+ * Create some default system tags
+ *
+ * This is designed to be similar to Thunderbird's email tags
+ * $label1 to $label5 with the according states and colours
+ *
+ * <i>The array_udiff can be removed and the insert warpped in
+ * an exception as soon as NC20 is not supported any more</i>
+ *
+ * @link https://github.com/nextcloud/mail/issues/25
+ */
+ public function createDefaultTags(MailAccount $account): void {
+ $tags = [];
+ for ($i = 1; $i < 6; $i++) {
+ $tag = new Tag();
+ $tag->setImapLabel('$label' . $i);
+ $tag->setUserId($account->getUserId());
+ switch ($i) {
+ case 1:
+ $tag->setDisplayName($this->l10n->t('Important'));
+ $tag->setColor('#FF0000');
+ $tag->setIsDefaultTag(true);
+ break;
+ case 2:
+ $tag->setDisplayName($this->l10n->t('Work'));
+ $tag->setColor('#FFC300');
+ $tag->setIsDefaultTag(true);
+ break;
+ case 3:
+ $tag->setDisplayName($this->l10n->t('Personal'));
+ $tag->setColor('#008000');
+ $tag->setIsDefaultTag(true);
+ break;
+ case 4:
+ $tag->setDisplayName($this->l10n->t('To Do'));
+ $tag->setColor('#000080');
+ $tag->setIsDefaultTag(true);
+ break;
+ case 5:
+ $tag->setDisplayName($this->l10n->t('Later'));
+ $tag->setColor('#800080');
+ $tag->setIsDefaultTag(true);
+ break;
+ }
+ $tags[] = $tag;
+ }
+ $dbTags = $this->getAllTagForUser($account->getUserId());
+ $toInsert = array_udiff($tags, $dbTags, function (Tag $a, Tag $b) {
+ return strcmp($a->getImapLabel(), $b->getImapLabel());
+ });
+ foreach ($toInsert as $entity) {
+ $this->insert($entity);
+ }
+ }
+}
diff --git a/lib/Migration/Version1100Date20210304143008.php b/lib/Migration/Version1100Date20210304143008.php
new file mode 100644
index 000000000..06c6f6578
--- /dev/null
+++ b/lib/Migration/Version1100Date20210304143008.php
@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\Mail\Migration;
+
+use Closure;
+use OCA\Mail\Db\MailAccountMapper;
+use OCA\Mail\Db\TagMapper;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+/**
+ * @link https://github.com/nextcloud/mail/issues/25
+ */
+class Version1100Date20210304143008 extends SimpleMigrationStep {
+
+ /**
+ * @var TagMapper
+ */
+ protected $tagMapper;
+
+ /**
+ * @var MailAccountMapper
+ */
+ protected $mailAccountMapper;
+
+ public function __construct(TagMapper $tagMapper, MailAccountMapper $mailAccountMapper) {
+ $this->tagMapper = $tagMapper;
+ $this->mailAccountMapper = $mailAccountMapper;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ * @return null|ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ $schema = $schemaClosure();
+
+ if (!$schema->hasTable('mail_tags')) {
+ $tagsTable = $schema->createTable('mail_tags');
+ $tagsTable->addColumn('id', 'integer', [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ 'length' => 4,
+ ]);
+ $tagsTable->addColumn('user_id', 'string', [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $tagsTable->addColumn('imap_label', 'string', [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $tagsTable->addColumn('display_name', 'string', [
+ 'notnull' => true,
+ 'length' => 128,
+ ]);
+ $tagsTable->addColumn('color', 'string', [
+ 'notnull' => false,
+ 'length' => 9,
+ 'default' => "#fff"
+ ]);
+ $tagsTable->addColumn('is_default_tag', 'boolean', [
+ 'notnull' => false,
+ 'default' => false
+ ]);
+ $tagsTable->setPrimaryKey(['id']);
+ $tagsTable->addIndex(['user_id'], 'mail_msg_tags_usr_id_index');
+ $tagsTable->addUniqueIndex(
+ [
+ 'user_id',
+ 'imap_label',
+ ],
+ 'mail_msg_tags_usr_lbl_idx'
+ );
+ }
+
+ if (!$schema->hasTable('mail_message_tags')) {
+ $tagsMessageTable = $schema->createTable('mail_message_tags');
+ $tagsMessageTable->addColumn('id', 'integer', [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ 'length' => 4,
+ ]);
+ $tagsMessageTable->addColumn('imap_message_id', 'string', [
+ 'notnull' => true,
+ 'length' => 1023,
+ ]);
+ $tagsMessageTable->addColumn('tag_id', 'integer', [
+ 'notnull' => true,
+ 'length' => 4,
+ ]);
+ $tagsMessageTable->setPrimaryKey(['id']);
+ $tagsMessageTable->addUniqueIndex(
+ [
+ 'imap_message_id',
+ 'tag_id',
+ ],
+ 'mail_msg_tag_id_idx'
+ );
+ }
+ return $schema;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ */
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ $accounts = $this->mailAccountMapper->getAllUserIdsWithAccounts();
+ foreach ($accounts as $account) {
+ $this->tagMapper->createDefaultTags($account);
+ }
+ }
+}
diff --git a/lib/Model/IMAPMessage.php b/lib/Model/IMAPMessage.php
index 46005a0c3..24743ef44 100644
--- a/lib/Model/IMAPMessage.php
+++ b/lib/Model/IMAPMessage.php
@@ -29,29 +29,32 @@ declare(strict_types=1);
namespace OCA\Mail\Model;
+use OC;
use Exception;
-use Horde_Imap_Client;
-use Horde_Imap_Client_Data_Envelope;
-use Horde_Imap_Client_Data_Fetch;
-use Horde_Imap_Client_DateTime;
-use Horde_Imap_Client_Fetch_Query;
-use Horde_Imap_Client_Ids;
-use Horde_Imap_Client_Mailbox;
-use Horde_Imap_Client_Socket;
-use Horde_Mime_Headers;
-use Horde_Mime_Headers_MessageId;
+use function trim;
+use OCP\Files\File;
use Horde_Mime_Part;
+use OCA\Mail\Db\Tag;
use JsonSerializable;
-use OC;
+use function in_array;
+use Horde_Imap_Client;
+use Horde_Mime_Headers;
+use OCA\Mail\Db\Message;
use OCA\Mail\AddressList;
-use OCA\Mail\Db\LocalAttachment;
+use Horde_Imap_Client_Ids;
use OCA\Mail\Service\Html;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\Files\File;
-use OCP\Files\SimpleFS\ISimpleFile;
-use function in_array;
+use OCA\Mail\Db\MailAccount;
+use Horde_Imap_Client_Socket;
+use Horde_Imap_Client_Mailbox;
+use Horde_Imap_Client_DateTime;
+use OCA\Mail\Db\LocalAttachment;
use function mb_convert_encoding;
-use function trim;
+use Horde_Imap_Client_Data_Fetch;
+use Horde_Mime_Headers_MessageId;
+use Horde_Imap_Client_Fetch_Query;
+use OCP\Files\SimpleFS\ISimpleFile;
+use Horde_Imap_Client_Data_Envelope;
+use OCP\AppFramework\Db\DoesNotExistException;
class IMAPMessage implements IMessage, JsonSerializable {
use ConvertAddresses;
@@ -149,7 +152,7 @@ class IMAPMessage implements IMessage, JsonSerializable {
'forwarded' => in_array(Horde_Imap_Client::FLAG_FORWARDED, $flags),
'hasAttachments' => $this->hasAttachments($this->fetch->getStructure()),
'mdnsent' => in_array(Horde_Imap_Client::FLAG_MDNSENT, $flags, true),
- 'important' => in_array('$important', $flags, true)
+ 'important' => in_array(Tag::LABEL_IMPORTANT, $flags, true)
];
}
@@ -673,8 +676,14 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('not implemented');
}
- public function toDbMessage(int $mailboxId): \OCA\Mail\Db\Message {
- $msg = new \OCA\Mail\Db\Message();
+ /**
+ * Cast all values from an IMAP message into the correct DB format
+ *
+ * @param integer $mailboxId
+ * @return Message
+ */
+ public function toDbMessage(int $mailboxId, MailAccount $account): Message {
+ $msg = new Message();
$messageId = $this->getMessageId();
if (empty(trim($messageId))) {
@@ -707,10 +716,58 @@ class IMAPMessage implements IMessage, JsonSerializable {
in_array('junk', $flags, true)
);
$msg->setFlagNotjunk(in_array(Horde_Imap_Client::FLAG_NOTJUNK, $flags, true));
- $msg->setFlagImportant(in_array('$important', $flags, true));
+ // @todo remove this as soon as possible @link https://github.com/nextcloud/mail/issues/25
+ $msg->setFlagImportant(in_array('$important', $flags, true) || in_array(Tag::LABEL_IMPORTANT, $flags, true));
$msg->setFlagAttachments(false);
$msg->setFlagMdnsent(in_array(Horde_Imap_Client::FLAG_MDNSENT, $flags, true));
+ $allowed = [
+ Horde_Imap_Client::FLAG_SEEN,
+ Horde_Imap_Client::FLAG_ANSWERED,
+ Horde_Imap_Client::FLAG_FLAGGED,
+ Horde_Imap_Client::FLAG_DELETED,
+ Horde_Imap_Client::FLAG_DRAFT,
+ Horde_Imap_Client::FLAG_RECENT,
+ Horde_Imap_Client::FLAG_JUNK,
+ Horde_Imap_Client::FLAG_MDNSENT,
+ ];
+ // remove all standard IMAP flags from $filters
+ $tags = array_filter($flags, function ($flag) use ($allowed) {
+ return in_array($flag, $allowed, true) === false;
+ });
+
+ if (empty($tags) === true) {
+ return $msg;
+ }
+ // cast all leftover $flags to be used as tags
+ $msg->setTags($this->generateTagEntites($tags, $account->getUserId()));
return $msg;
}
+
+ /**
+ * Build tag entities from keywords sent by IMAP
+ *
+ * Will use IMAP keyword '$xxx' to create a value for
+ * display_name like 'xxx'
+ *
+ * @link https://github.com/nextcloud/mail/issues/25
+ *
+ * @param string[] $tags
+ * @return Tag[]
+ */
+ private function generateTagEntites(array $tags, string $userId): array {
+ $t = [];
+ foreach ($tags as $keyword) {
+ // Map the old $important to $label1 until we have caught them all
+ if ($keyword === '$important') {
+ $keyword = Tag::LABEL_IMPORTANT;
+ }
+ $tag = new Tag();
+ $tag->setImapLabel($keyword);
+ $tag->setDisplayName(str_replace('$', '', $keyword));
+ $tag->setUserId($userId);
+ $t[] = $tag;
+ }
+ return $t;
+ }
}
diff --git a/lib/Service/MailManager.php b/lib/Service/MailManager.php
index d37278477..782946e6a 100644
--- a/lib/Service/MailManager.php
+++ b/lib/Service/MailManager.php
@@ -23,34 +23,36 @@ declare(strict_types=1);
namespace OCA\Mail\Service;
-use Horde_Imap_Client;
-use Horde_Imap_Client_Exception;
-use Horde_Imap_Client_Exception_NoSupportExtension;
-use Horde_Imap_Client_Socket;
+use OCA\Mail\Db\Tag;
+use OCA\Mail\Folder;
use OCA\Mail\Account;
-use OCA\Mail\Contracts\IMailManager;
+use Horde_Imap_Client;
+use function array_map;
use OCA\Mail\Db\Mailbox;
-use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Db\Message;
-use OCA\Mail\Db\MessageMapper as DbMessageMapper;
-use OCA\Mail\Events\BeforeMessageDeletedEvent;
+use function array_values;
+use OCA\Mail\Db\TagMapper;
+use Psr\Log\LoggerInterface;
+use Horde_Imap_Client_Socket;
+use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\IMAP\FolderStats;
+use OCA\Mail\IMAP\MailboxSync;
+use OCA\Mail\IMAP\FolderMapper;
+use OCA\Mail\Model\IMAPMessage;
+use Horde_Imap_Client_Exception;
+use OCA\Mail\Contracts\IMailManager;
+use OCA\Mail\IMAP\IMAPClientFactory;
+use OCA\Mail\Exception\ClientException;
use OCA\Mail\Events\MessageDeletedEvent;
use OCA\Mail\Events\MessageFlaggedEvent;
-use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCA\Mail\Events\BeforeMessageDeletedEvent;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCA\Mail\Db\MessageMapper as DbMessageMapper;
+use Horde_Imap_Client_Exception_NoSupportExtension;
use OCA\Mail\Exception\TrashMailboxNotSetException;
-use OCA\Mail\Folder;
-use OCA\Mail\IMAP\FolderMapper;
-use OCA\Mail\IMAP\FolderStats;
-use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MailboxSync;
use OCA\Mail\IMAP\MessageMapper as ImapMessageMapper;
-use OCA\Mail\Model\IMAPMessage;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\EventDispatcher\IEventDispatcher;
-use Psr\Log\LoggerInterface;
-use function array_map;
-use function array_values;
class MailManager implements IMailManager {
@@ -86,8 +88,12 @@ class MailManager implements IMailManager {
/** @var DbMessageMapper */
private $dbMessageMapper;
+ /** @var TagMapper */
+ private $tagMapper;
+
/** @var IEventDispatcher */
private $eventDispatcher;
+
/**
* @var LoggerInterface
*/
@@ -100,7 +106,8 @@ class MailManager implements IMailManager {
ImapMessageMapper $messageMapper,
DbMessageMapper $dbMessageMapper,
IEventDispatcher $eventDispatcher,
- LoggerInterface $logger) {
+ LoggerInterface $logger,
+ TagMapper $tagMapper) {
$this->imapClientFactory = $imapClientFactory;
$this->mailboxMapper = $mailboxMapper;
$this->mailboxSync = $mailboxSync;
@@ -109,6 +116,7 @@ class MailManager implements IMailManager {
$this->dbMessageMapper = $dbMessageMapper;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
+ $this->tagMapper = $tagMapper;
}
public function getMailbox(string $uid, int $id): Mailbox {
@@ -393,7 +401,7 @@ class MailManager implements IMailManager {
if (empty($imapFlag) === true) {
continue;
}
- if ($value === true) {
+ if ($value) {
$this->imapMessageMapper->addFlag($client, $mb, $uid, $imapFlag);
} else {
$this->imapMessageMapper->removeFlag($client, $mb, $uid, $imapFlag);
@@ -420,6 +428,50 @@ class MailManager implements IMailManager {
}
/**
+ * Tag (flag) a message on IMAP
+ *
+ * @param Account $account
+ * @param string $mailbox
+ * @param integer $uid
+ * @param Tag $tag
+ * @param boolean $value
+ * @return void
+ *
+ * @uses
+ *
+ * @link https://github.com/nextcloud/mail/issues/25
+ */
+ public function tagMessage(Account $account, string $mailbox, Message $message, Tag $tag, bool $value): void {
+ $client = $this->imapClientFactory->getClient($account);
+ try {
+ $mb = $this->mailboxMapper->find($account, $mailbox);
+ } catch (DoesNotExistException $e) {
+ throw new ClientException("Mailbox $mailbox does not exist", 0, $e);
+ }
+ if ($this->isPermflagsEnabled($account, $mailbox) === true) {
+ try {
+ if ($value) {
+ // imap keywords and flags work the same way
+ $this->imapMessageMapper->addFlag($client, $mb, $message->getUid(), $tag->getImapLabel());
+ } else {
+ $this->imapMessageMapper->removeFlag($client, $mb, $message->getUid(), $tag->getImapLabel());
+ }
+ } catch (Horde_Imap_Client_Exception $e) {
+ throw new ServiceException(
+ "Could not set message keyword on IMAP: " . $e->getMessage(),
+ (int) $e->getCode(),
+ $e
+ );
+ }
+ }
+ if ($value) {
+ $this->tagMapper->tagMessage($tag, $message->getMessageId(), $account->getUserId());
+ } else {
+ $this->tagMapper->untagMessage($tag, $message->getMessageId());
+ }
+ }
+
+ /**
* @param Account $account
*
* @return Quota|null
@@ -518,6 +570,21 @@ class MailManager implements IMailManager {
}
/**
+ * @param string $imapLabel
+ * @param string $userId
+ * @return Tag
+ * @throws DoesNotExistException
+ */
+ public function getTagByImapLabel(string $imapLabel, string $userId): Tag {
+ try {
+ return $this->tagMapper->getTagByImapLabel($imapLabel, $userId);
+ } catch (DoesNotExistException $e) {
+ throw new ClientException('Unknow Tag', (int)$e->getCode(), $e);
+ }
+ }
+
+
+ /**
* Filter out IMAP flags that aren't supported by the client server
*
* @param Horde_Imap_Client_Socket $client
@@ -534,7 +601,7 @@ class MailManager implements IMailManager {
// Only allow flag setting if IMAP supports Permaflags
// @TODO check if there are length & char limits on permflags
if ($this->isPermflagsEnabled($account, $mailbox) === true) {
- return ["$" . $flag];
+ return [$flag];
}
return [];
}
diff --git a/lib/Service/Search/Flag.php b/lib/Service/Search/Flag.php
index 79294813a..491a63aca 100644
--- a/lib/Service/Search/Flag.php
+++ b/lib/Service/Search/Flag.php
@@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OCA\Mail\Service\Search;
use Horde_Imap_Client;
+use OCA\Mail\Db\Tag;
/**
* @psalm-immutable
@@ -34,7 +35,8 @@ class Flag {
public const ANSWERED = Horde_Imap_Client::FLAG_ANSWERED;
public const SEEN = Horde_Imap_Client::FLAG_SEEN;
public const FLAGGED = Horde_Imap_Client::FLAG_FLAGGED;
- public const IMPORTANT = '\\important';
+ /** @deprecated */
+ public const IMPORTANT = Tag::LABEL_IMPORTANT;
public const DELETED = Horde_Imap_Client::FLAG_DELETED;
/** @var string */
diff --git a/lib/Service/SetupService.php b/lib/Service/SetupService.php
index ffd6146ac..78b423425 100644
--- a/lib/Service/SetupService.php
+++ b/lib/Service/SetupService.php
@@ -31,6 +31,7 @@ use Horde_Mail_Exception;
use Horde_Mail_Transport_Smtphorde;
use OCA\Mail\Account;
use OCA\Mail\Db\MailAccount;
+use OCA\Mail\Db\TagMapper;
use OCA\Mail\Exception\CouldNotConnectException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\IMAPClientFactory;
@@ -56,21 +57,26 @@ class SetupService {
/** @var IMAPClientFactory */
private $imapClientFactory;
- /** var LoggerInterface */
+ /** @var LoggerInterface */
private $logger;
+ /** @var TagMapper */
+ private $tagMapper;
+
public function __construct(AutoConfig $autoConfig,
AccountService $accountService,
ICrypto $crypto,
SmtpClientFactory $smtpClientFactory,
IMAPClientFactory $imapClientFactory,
- LoggerInterface $logger) {
+ LoggerInterface $logger,
+ TagMapper $tagMapper) {
$this->autoConfig = $autoConfig;
$this->accountService = $accountService;
$this->crypto = $crypto;
$this->smtpClientFactory = $smtpClientFactory;
$this->imapClientFactory = $imapClientFactory;
$this->logger = $logger;
+ $this->tagMapper = $tagMapper;
}
/**
@@ -78,6 +84,8 @@ class SetupService {
* @param string $emailAddress
* @param string $password
* @return Account|null
+ *
+ * @link https://github.com/nextcloud/mail/issues/25
*/
public function createNewAutoConfiguredAccount($accountName, $emailAddress, $password) {
$this->logger->info('setting up auto detected account');
@@ -88,6 +96,8 @@ class SetupService {
$this->accountService->save($mailAccount);
+ $this->tagMapper->createDefaultTags($mailAccount);
+
return new Account($mailAccount);
}
@@ -139,6 +149,8 @@ class SetupService {
$this->accountService->save($newAccount);
$this->logger->debug("account created " . $newAccount->getId());
+ $this->tagMapper->createDefaultTags($newAccount);
+
return $account;
}
diff --git a/lib/Service/Sync/ImapToDbSynchronizer.php b/lib/Service/Sync/ImapToDbSynchronizer.php
index 3132aad06..2adba1ee2 100644
--- a/lib/Service/Sync/ImapToDbSynchronizer.php
+++ b/lib/Service/Sync/ImapToDbSynchronizer.php
@@ -292,8 +292,8 @@ class ImapToDbSynchronizer {
}
foreach (array_chunk($imapMessages['messages'], 500) as $chunk) {
- $this->dbMapper->insertBulk(...array_map(function (IMAPMessage $imapMessage) use ($mailbox) {
- return $imapMessage->toDbMessage($mailbox->getId());
+ $this->dbMapper->insertBulk($account, ...array_map(function (IMAPMessage $imapMessage) use ($mailbox, $account) {
+ return $imapMessage->toDbMessage($mailbox->getId(), $account->getMailAccount());
}, $chunk));
}
$perf->step('persist messages in database');
@@ -349,8 +349,8 @@ class ImapToDbSynchronizer {
$perf->step('get new messages via Horde');
foreach (array_chunk($response->getNewMessages(), 500) as $chunk) {
- $dbMessages = array_map(function (IMAPMessage $imapMessage) use ($mailbox) {
- return $imapMessage->toDbMessage($mailbox->getId());
+ $dbMessages = array_map(function (IMAPMessage $imapMessage) use ($mailbox, $account) {
+ return $imapMessage->toDbMessage($mailbox->getId(), $account->getMailAccount());
}, $chunk);
$this->dispatcher->dispatch(
@@ -359,7 +359,7 @@ class ImapToDbSynchronizer {
);
$perf->step('classified a chunk of new messages');
- $this->dbMapper->insertBulk(...$dbMessages);
+ $this->dbMapper->insertBulk($account, ...$dbMessages);
}
$perf->step('persist new messages');
@@ -378,8 +378,8 @@ class ImapToDbSynchronizer {
$perf->step('get changed messages via Horde');
foreach (array_chunk($response->getChangedMessages(), 500) as $chunk) {
- $this->dbMapper->updateBulk(...array_map(function (IMAPMessage $imapMessage) use ($mailbox) {
- return $imapMessage->toDbMessage($mailbox->getId());
+ $this->dbMapper->updateBulk($account, ...array_map(function (IMAPMessage $imapMessage) use ($mailbox, $account) {
+ return $imapMessage->toDbMessage($mailbox->getId(), $account->getMailAccount());
}, $chunk));
}
$perf->step('persist changed messages');
diff --git a/src/components/Envelope.vue b/src/components/Envelope.vue
index 5b19a4ea7..228518c77 100644
--- a/src/components/Envelope.vue
+++ b/src/components/Envelope.vue
@@ -21,9 +21,9 @@
:data-starred="data.flags.flagged ? 'true' : 'false'"
@click.prevent="onToggleFlagged" />
<div
- v-if="data.flags.important"
+ v-if="data.tags.$label1"
class="app-content-list-item-star icon-important"
- :data-starred="data.flags.important ? 'true' : 'false'"
+ :data-starred="data.tags.$label1 ? 'true' : 'false'"
@click.prevent="onToggleImportant"
v-html="importantSvg" />
<div
diff --git a/src/components/MenuEnvelope.vue b/src/components/MenuEnvelope.vue
index 3a182fee9..b9c0f4a67 100644
--- a/src/components/MenuEnvelope.vue
+++ b/src/components/MenuEnvelope.vue
@@ -40,7 +40,7 @@
:close-after-click="true"
@click.prevent="onToggleImportant">
{{
- envelope.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important')
+ envelope.tags.$label1 ? t('mail', 'Mark unimportant') : t('mail', 'Mark important')
}}
</ActionButton>
<ActionButton :icon="iconFavorite"
diff --git a/src/components/ThreadEnvelope.vue b/src/components/ThreadEnvelope.vue
index 9a49fd299..1c5de9992 100644
--- a/src/components/ThreadEnvelope.vue
+++ b/src/components/ThreadEnvelope.vue
@@ -28,9 +28,9 @@
:disable-tooltip="true"
:size="40" />
<div
- v-if="envelope.flags.important"
+ v-if="envelope.tags.$label1"
class="app-content-list-item-star icon-important"
- :data-starred="envelope.flags.important ? 'true' : 'false'"
+ :data-starred="envelope.tags.$label1 ? 'true' : 'false'"
@click.prevent="onToggleImportant"
v-html="importantSvg" />
<div
diff --git a/src/service/MessageService.js b/src/service/MessageService.js
index a1b49138d..c24402571 100644
--- a/src/service/MessageService.js
+++ b/src/service/MessageService.js
@@ -116,6 +116,24 @@ export function setEnvelopeFlag(id, flag, value) {
})
}
+export function setEnvelopeTag(id, tagId) {
+ const url = generateUrl('/apps/mail/api/messages/{id}/tags/{tagId}', {
+ id, tagId,
+ })
+
+ return axios
+ .put(url)
+}
+
+export function removeEnvelopeTag(id, tagId) {
+ const url = generateUrl('/apps/mail/api/messages/{id}/tags/{tagId}', {
+ id, tagId,
+ })
+
+ return axios
+ .delete(url)
+}
+
export async function fetchMessage(id) {
const url = generateUrl('/apps/mail/api/messages/{id}/body', {
id,
diff --git a/src/store/actions.js b/src/store/actions.js
index dcdba38a5..4ae91144d 100644
--- a/src/store/actions.js
+++ b/src/store/actions.js
@@ -595,20 +595,20 @@ export default {
},
toggleEnvelopeImportant({ commit, getters }, envelope) {
// Change immediately and switch back on error
- const oldState = envelope.flags.important
- commit('flagEnvelope', {
+ const oldState = envelope.tags.$label1
+ commit('tagEnvelope', {
envelope,
- flag: 'important',
+ tag: '$label1',
value: !oldState,
})
- setEnvelopeFlag(envelope.databaseId, 'important', !oldState).catch((e) => {
+ setEnvelopeFlag(envelope.databaseId, '$label1', !oldState).catch((e) => {
console.error('could not toggle message important state', e)
// Revert change
- commit('flagEnvelope', {
+ commit('tagEnvelope', {
envelope,
- flag: 'important',
+ tag: '$label1',
value: oldState,
})
})
diff --git a/src/store/mutations.js b/src/store/mutations.js
index 4bfa5b4eb..b0c13e02b 100644
--- a/src/store/mutations.js
+++ b/src/store/mutations.js
@@ -173,10 +173,14 @@ export default {
return
}
Vue.set(existing, 'flags', envelope.flags)
+ Vue.set(existing, 'tags', envelope.tags)
},
flagEnvelope(state, { envelope, flag, value }) {
envelope.flags[flag] = value
},
+ tagEnvelope(state, { envelope, tag, value }) {
+ envelope.tags[tag] = value
+ },
removeEnvelope(state, { id }) {
const envelope = state.envelopes[id]
if (!envelope) {
diff --git a/tests/Unit/Controller/MessagesControllerTest.php b/tests/Unit/Controller/MessagesControllerTest.php
index b795948b1..8943aa331 100644
--- a/tests/Unit/Controller/MessagesControllerTest.php
+++ b/tests/Unit/Controller/MessagesControllerTest.php
@@ -24,40 +24,41 @@ declare(strict_types=1);
namespace OCA\Mail\Tests\Unit\Controller;
-use ChristophWurst\Nextcloud\Testing\TestCase;
-use OC\AppFramework\Http\Request;
-use OC\Security\CSP\ContentSecurityPolicyNonceManager;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCA\Mail\Db\Tag;
use OCA\Mail\Account;
+use OCA\Mail\Mailbox;
+use OCP\Files\Folder;
+use ReflectionObject;
+use OCP\IURLGenerator;
use OCA\Mail\Attachment;
-use OCA\Mail\Contracts\IMailManager;
-use OCA\Mail\Contracts\IMailSearch;
-use OCA\Mail\Contracts\IMailTransmission;
-use OCA\Mail\Contracts\ITrustedSenderService;
-use OCA\Mail\Controller\MessagesController;
-use OCA\Mail\Exception\ClientException;
-use OCA\Mail\Exception\ServiceException;
-use OCA\Mail\Http\AttachmentDownloadResponse;
+use OCP\AppFramework\Http;
+use OCA\Mail\Model\Message;
+use Psr\Log\LoggerInterface;
use OCA\Mail\Http\HtmlResponse;
-use OCA\Mail\Mailbox;
use OCA\Mail\Model\IMAPMessage;
-use OCA\Mail\Model\Message;
+use OCP\Files\IMimeTypeDetector;
+use OC\AppFramework\Http\Request;
+use OCA\Mail\Service\MailManager;
+use OCA\Mail\Contracts\IMailSearch;
+use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\ItineraryService;
-use OCA\Mail\Service\MailManager;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\AppFramework\Http;
-use OCP\AppFramework\Http\ContentSecurityPolicy;
-use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\ZipResponse;
+use OCA\Mail\Exception\ClientException;
+use OCP\AppFramework\Http\JSONResponse;
+use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\Contracts\IMailTransmission;
use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\Files\Folder;
-use OCP\Files\IMimeTypeDetector;
-use OCP\IL10N;
-use OCP\IRequest;
-use OCP\IURLGenerator;
+use OCA\Mail\Controller\MessagesController;
use PHPUnit\Framework\MockObject\MockObject;
-use Psr\Log\LoggerInterface;
-use ReflectionObject;
+use OCA\Mail\Contracts\ITrustedSenderService;
+use OCA\Mail\Http\AttachmentDownloadResponse;
+use ChristophWurst\Nextcloud\Testing\TestCase;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OC\Security\CSP\ContentSecurityPolicyNonceManager;
class MessagesControllerTest extends TestCase {
@@ -647,6 +648,208 @@ class MessagesControllerTest extends TestCase {
$this->assertEquals($expected, $response);
}
+ public function testSetTagFailing() {
+ $accountId = 17;
+ $mailboxId = 987;
+ $id = 1;
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(444);
+ $message->setMailboxId($mailboxId);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $mailbox = new \OCA\Mail\Db\Mailbox();
+ $mailbox->setName('INBOX');
+ $mailbox->setAccountId($accountId);
+ $this->mailManager->expects($this->once())
+ ->method('getMessage')
+ ->with($this->userId, $id)
+ ->willReturn($message);
+ $this->mailManager->expects($this->once())
+ ->method('getMailbox')
+ ->with($this->userId, $mailboxId)
+ ->willReturn($mailbox);
+ $this->accountService->expects($this->once())
+ ->method('find')
+ ->with($this->equalTo($this->userId), $this->equalTo($accountId))
+ ->willThrowException(new DoesNotExistException(''));
+ $this->mailManager->expects($this->never())
+ ->method('getTagByImapLabel');
+ $this->mailManager->expects($this->never())
+ ->method('tagMessage');
+
+ $this->controller->setTag($id, Tag::LABEL_IMPORTANT);
+ }
+
+ public function testSetTagNotFound() {
+ $accountId = 17;
+ $mailboxId = 987;
+ $id = 1;
+ $imapLabel = '$label6';
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(444);
+ $message->setMailboxId($mailboxId);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $mailbox = new \OCA\Mail\Db\Mailbox();
+ $mailbox->setName('INBOX');
+ $mailbox->setAccountId($accountId);
+ $this->mailManager->expects($this->once())
+ ->method('getMessage')
+ ->with($this->userId, $id)
+ ->willReturn($message);
+ $this->mailManager->expects($this->once())
+ ->method('getMailbox')
+ ->with($this->userId, $mailboxId)
+ ->willReturn($mailbox);
+ $this->accountService->expects($this->once())
+ ->method('find')
+ ->with($this->equalTo($this->userId), $this->equalTo($accountId))
+ ->will($this->returnValue($this->account));
+ $this->mailManager->expects($this->once())
+ ->method('getTagByImapLabel')
+ ->with($imapLabel,$this->userId)
+ ->willThrowException(new DoesNotExistException(''));
+ $this->mailManager->expects($this->never())
+ ->method('tagMessage');
+
+ $this->controller->setTag($id, $imapLabel);
+ }
+
+ public function testSetTag() {
+ $accountId = 17;
+ $mailboxId = 987;
+ $id = 1;
+ $tag = new Tag();
+ $tag->setImapLabel(Tag::LABEL_IMPORTANT);
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(444);
+ $message->setMailboxId($mailboxId);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $mailbox = new \OCA\Mail\Db\Mailbox();
+ $mailbox->setName('INBOX');
+ $mailbox->setAccountId($accountId);
+ $this->mailManager->expects($this->once())
+ ->method('getMessage')
+ ->with($this->userId, $id)
+ ->willReturn($message);
+ $this->mailManager->expects($this->once())
+ ->method('getMailbox')
+ ->with($this->userId, $mailboxId)
+ ->willReturn($mailbox);
+ $this->accountService->expects($this->once())
+ ->method('find')
+ ->with($this->equalTo($this->userId), $this->equalTo($accountId))
+ ->will($this->returnValue($this->account));
+ $this->mailManager->expects($this->once())
+ ->method('getTagByImapLabel')
+ ->with($tag->getImapLabel(),$this->userId)
+ ->willReturn($tag);
+ $this->mailManager->expects($this->once())
+ ->method('tagMessage')
+ ->with($this->account, $mailbox->getName(), $message, $tag, true);
+
+ $this->controller->setTag($id, $tag->getImapLabel());
+ }
+
+ public function testRemoveTagFailing() {
+ $accountId = 17;
+ $mailboxId = 987;
+ $id = 1;
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(444);
+ $message->setMailboxId($mailboxId);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $mailbox = new \OCA\Mail\Db\Mailbox();
+ $mailbox->setName('INBOX');
+ $mailbox->setAccountId($accountId);
+ $this->mailManager->expects($this->once())
+ ->method('getMessage')
+ ->with($this->userId, $id)
+ ->willReturn($message);
+ $this->mailManager->expects($this->once())
+ ->method('getMailbox')
+ ->with($this->userId, $mailboxId)
+ ->willReturn($mailbox);
+ $this->accountService->expects($this->once())
+ ->method('find')
+ ->with($this->equalTo($this->userId), $this->equalTo($accountId))
+ ->willThrowException(new DoesNotExistException(''));
+ $this->mailManager->expects($this->never())
+ ->method('getTagByImapLabel');
+ $this->mailManager->expects($this->never())
+ ->method('tagMessage');
+
+ $this->controller->removeTag($id, Tag::LABEL_IMPORTANT);
+ }
+
+ public function testRemoveTagNotFound() {
+ $accountId = 17;
+ $mailboxId = 987;
+ $id = 1;
+ $imapLabel = '$label6';
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(444);
+ $message->setMailboxId($mailboxId);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $mailbox = new \OCA\Mail\Db\Mailbox();
+ $mailbox->setName('INBOX');
+ $mailbox->setAccountId($accountId);
+ $this->mailManager->expects($this->once())
+ ->method('getMessage')
+ ->with($this->userId, $id)
+ ->willReturn($message);
+ $this->mailManager->expects($this->once())
+ ->method('getMailbox')
+ ->with($this->userId, $mailboxId)
+ ->willReturn($mailbox);
+ $this->accountService->expects($this->once())
+ ->method('find')
+ ->with($this->equalTo($this->userId), $this->equalTo($accountId))
+ ->will($this->returnValue($this->account));
+ $this->mailManager->expects($this->once())
+ ->method('getTagByImapLabel')
+ ->with($imapLabel,$this->userId)
+ ->willThrowException(new DoesNotExistException(''));
+ $this->mailManager->expects($this->never())
+ ->method('tagMessage');
+
+ $this->controller->removeTag($id, $imapLabel);
+ }
+
+ public function testRemoveTag() {
+ $accountId = 17;
+ $mailboxId = 987;
+ $id = 1;
+ $tag = new Tag();
+ $tag->setImapLabel(Tag::LABEL_IMPORTANT);
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(444);
+ $message->setMailboxId($mailboxId);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $mailbox = new \OCA\Mail\Db\Mailbox();
+ $mailbox->setName('INBOX');
+ $mailbox->setAccountId($accountId);
+ $this->mailManager->expects($this->once())
+ ->method('getMessage')
+ ->with($this->userId, $id)
+ ->willReturn($message);
+ $this->mailManager->expects($this->once())
+ ->method('getMailbox')
+ ->with($this->userId, $mailboxId)
+ ->willReturn($mailbox);
+ $this->accountService->expects($this->once())
+ ->method('find')
+ ->with($this->equalTo($this->userId), $this->equalTo($accountId))
+ ->will($this->returnValue($this->account));
+ $this->mailManager->expects($this->once())
+ ->method('getTagByImapLabel')
+ ->with($tag->getImapLabel(),$this->userId)
+ ->willReturn($tag);
+ $this->mailManager->expects($this->once())
+ ->method('tagMessage')
+ ->with($this->account, $mailbox->getName(), $message, $tag, false);
+
+ $this->controller->removeTag($id, $tag->getImapLabel());
+ }
+
public function testSetFlagsFlagged() {
$accountId = 17;
$mailboxId = 987;
diff --git a/tests/Unit/Listener/MessageCacheUpdaterListenerTest.php b/tests/Unit/Listener/MessageCacheUpdaterListenerTest.php
index 5fac913cf..3981b3744 100644
--- a/tests/Unit/Listener/MessageCacheUpdaterListenerTest.php
+++ b/tests/Unit/Listener/MessageCacheUpdaterListenerTest.php
@@ -30,6 +30,7 @@ use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Account;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\Message;
+use OCA\Mail\Db\Tag;
use OCA\Mail\Events\MessageFlaggedEvent;
use OCA\Mail\Listener\MessageCacheUpdaterListener;
use OCP\EventDispatcher\Event;
@@ -65,7 +66,7 @@ class MessageCacheUpdaterListenerTest extends TestCase {
$account,
$mailbox,
123,
- 'important',
+ Tag::LABEL_IMPORTANT,
true
);
$this->serviceMock->getParameter('mapper')
diff --git a/tests/Unit/Service/MailManagerTest.php b/tests/Unit/Service/MailManagerTest.php
index d46e96cbf..8406205dc 100644
--- a/tests/Unit/Service/MailManagerTest.php
+++ b/tests/Unit/Service/MailManagerTest.php
@@ -31,6 +31,8 @@ use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Db\Message;
use OCA\Mail\Db\MessageMapper as DbMessageMapper;
+use OCA\Mail\Db\Tag;
+use OCA\Mail\Db\TagMapper;
use OCA\Mail\Events\BeforeMessageDeletedEvent;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Folder;
@@ -74,6 +76,9 @@ class MailManagerTest extends TestCase {
/** @var MockObject|LoggerInterface */
private $logger;
+ /** @var MockObject|TagMapper */
+ private $tagMapper;
+
protected function setUp(): void {
parent::setUp();
@@ -85,6 +90,7 @@ class MailManagerTest extends TestCase {
$this->mailboxSync = $this->createMock(MailboxSync::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->logger = $this->createMock(LoggerInterface::class);
+ $this->tagMapper = $this->createMock(TagMapper::class);
$this->manager = new MailManager(
$this->imapClientFactory,
@@ -94,7 +100,8 @@ class MailManagerTest extends TestCase {
$this->imapMessageMapper,
$this->dbMessageMapper,
$this->eventDispatcher,
- $this->logger
+ $this->logger,
+ $this->tagMapper
);
}
@@ -310,8 +317,8 @@ class MailManagerTest extends TestCase {
$this->imapMessageMapper->expects($this->never())
->method('removeFlag');
- $this->manager->flagMessage($account, 'INBOX', 123, 'important', true);
- $this->manager->flagMessage($account, 'INBOX', 123, 'important', false);
+ $this->manager->flagMessage($account, 'INBOX', 123, Tag::LABEL_IMPORTANT, true);
+ $this->manager->flagMessage($account, 'INBOX', 123, Tag::LABEL_IMPORTANT, false);
}
public function testSetCustomFlagWithIMAPCapabilities(): void {
@@ -327,7 +334,7 @@ class MailManagerTest extends TestCase {
$this->imapMessageMapper->expects($this->once())
->method('addFlag');
- $this->manager->flagMessage($account, 'INBOX', 123, 'important', true);
+ $this->manager->flagMessage($account, 'INBOX', 123, Tag::LABEL_IMPORTANT, true);
}
public function testUnsetCustomFlagWithIMAPCapabilities(): void {
@@ -343,7 +350,7 @@ class MailManagerTest extends TestCase {
$this->imapMessageMapper->expects($this->once())
->method('removeFlag');
- $this->manager->flagMessage($account, 'INBOX', 123, 'important', false);
+ $this->manager->flagMessage($account, 'INBOX', 123, Tag::LABEL_IMPORTANT, false);
}
public function testFilterFlagStandard(): void {
@@ -378,7 +385,7 @@ class MailManagerTest extends TestCase {
->method('getClient')
->willReturn($client);
- $this->assertEquals([], $this->manager->filterFlags($account, '$important' , 'INBOX'));
+ $this->assertEquals([], $this->manager->filterFlags($account, Tag::LABEL_IMPORTANT , 'INBOX'));
}
public function testSetFilterFlagsImportant() {
@@ -392,7 +399,7 @@ class MailManagerTest extends TestCase {
->method('status')
->willReturn(['permflags' => [ "11" => "\*" ]]);
- $this->assertEquals(['$important'], $this->manager->filterFlags($account, 'important' , 'INBOX'));
+ $this->assertEquals([Tag::LABEL_IMPORTANT], $this->manager->filterFlags($account, Tag::LABEL_IMPORTANT , 'INBOX'));
}
public function testIsPermflagsEnabledTrue(): void {
@@ -443,6 +450,94 @@ class MailManagerTest extends TestCase {
$this->manager->flagMessage($account, 'INBOX', 123, 'seen', false);
}
+ public function testTagMessage(): void {
+ $client = $this->createMock(Horde_Imap_Client_Socket::class);
+ $account = $this->createMock(Account::class);
+ $tag = new Tag();
+ $tag->setImapLabel(Tag::LABEL_IMPORTANT);
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(123);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $this->imapClientFactory->expects($this->any())
+ ->method('getClient')
+ ->willReturn($client);
+ $mb = $this->createMock(Mailbox::class);
+ $this->mailboxMapper->expects($this->once())
+ ->method('find')
+ ->with($account, 'INBOX')
+ ->willReturn($mb);
+ $client->expects($this->once())
+ ->method('status')
+ ->willReturn(['permflags' => [ "11" => "\*"] ]);
+ $this->imapMessageMapper->expects($this->once())
+ ->method('addFlag')
+ ->with($client, $mb, 123, Tag::LABEL_IMPORTANT);
+ $account->expects($this->once())
+ ->method('getUserId')
+ ->willReturn('test');
+ $this->manager->tagMessage($account, 'INBOX', $message, $tag, true);
+ }
+
+ public function testUntagMessage(): void {
+ $client = $this->createMock(Horde_Imap_Client_Socket::class);
+ $account = $this->createMock(Account::class);
+ $tag = new Tag();
+ $tag->setImapLabel(Tag::LABEL_IMPORTANT);
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(123);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $this->imapClientFactory->expects($this->any())
+ ->method('getClient')
+ ->willReturn($client);
+ $mb = $this->createMock(Mailbox::class);
+ $this->mailboxMapper->expects($this->once())
+ ->method('find')
+ ->with($account, 'INBOX')
+ ->willReturn($mb);
+ $client->expects($this->once())
+ ->method('status')
+ ->willReturn(['permflags' => [ "11" => "\*"] ]);
+ $this->imapMessageMapper->expects($this->once())
+ ->method('removeFlag')
+ ->with($client, $mb, 123, Tag::LABEL_IMPORTANT);
+ $this->imapMessageMapper->expects($this->never())
+ ->method('addFlag');
+ $account->expects($this->never())
+ ->method('getUserId')
+ ->willReturn('test');
+ $this->manager->tagMessage($account, 'INBOX', $message, $tag, false);
+ }
+
+ public function testTagNoIMAPCapabilities(): void {
+ $client = $this->createMock(Horde_Imap_Client_Socket::class);
+ $account = $this->createMock(Account::class);
+ $message = new \OCA\Mail\Db\Message();
+ $message->setUid(123);
+ $message->setMessageId('<jhfjkhdsjkfhdsjkhfjkdsh@test.com>');
+ $tag = new Tag();
+ $tag->setImapLabel(Tag::LABEL_IMPORTANT);
+
+ $this->imapClientFactory->expects($this->any())
+ ->method('getClient')
+ ->willReturn($client);
+ $mb = $this->createMock(Mailbox::class);
+ $this->mailboxMapper->expects($this->once())
+ ->method('find')
+ ->with($account, 'INBOX')
+ ->willReturn($mb);
+ $client->expects($this->once())
+ ->method('status')
+ ->willReturn([]);
+ $this->imapMessageMapper->expects($this->never())
+ ->method('removeFlag');
+ $this->imapMessageMapper->expects($this->never())
+ ->method('addFlag');
+ $account->expects($this->once())
+ ->method('getUserId')
+ ->willReturn('test');
+ $this->manager->tagMessage($account, 'INBOX', $message, $tag, true);
+ }
+
public function testGetThread(): void {
$account = $this->createMock(Account::class);
$messageId = 123;
diff --git a/tests/Unit/Service/SetupServiceTest.php b/tests/Unit/Service/SetupServiceTest.php
index 70288db2a..1d5ff27bc 100644
--- a/tests/Unit/Service/SetupServiceTest.php
+++ b/tests/Unit/Service/SetupServiceTest.php
@@ -34,6 +34,7 @@ use OCA\Mail\Service\AutoConfig\AutoConfig;
use OCA\Mail\Service\SetupService;
use OCA\Mail\SMTP\SmtpClientFactory;
use ChristophWurst\Nextcloud\Testing\TestCase;
+use OCA\Mail\Db\TagMapper;
use OCP\Security\ICrypto;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
@@ -61,6 +62,9 @@ class SetupServiceTest extends TestCase {
/** @var SetupService */
private $service;
+ /** @var TagMapper|MockObject */
+ private $tagMapper;
+
protected function setUp(): void {
parent::setUp();
@@ -70,6 +74,7 @@ class SetupServiceTest extends TestCase {
$this->smtpClientFactory = $this->createMock(SmtpClientFactory::class);
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
$this->logger = $this->createMock(LoggerInterface::class);
+ $this->tagMapper = $this->createMock(TagMapper::class);
$this->service = new SetupService(
$this->autoConfig,
@@ -77,7 +82,8 @@ class SetupServiceTest extends TestCase {
$this->crypto,
$this->smtpClientFactory,
$this->imapClientFactory,
- $this->logger
+ $this->logger,
+ $this->tagMapper
);
}
@@ -109,6 +115,11 @@ class SetupServiceTest extends TestCase {
$this->accountService->expects($this->once())
->method('save')
->with($account);
+
+ $this->tagMapper->expects($this->once())
+ ->method('createDefaultTags')
+ ->with($account);
+
$expected = new Account($account);
$actual = $this->service->createNewAutoConfiguredAccount($name, $email, $password);
diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml
index 1cb8ae6f3..f05676999 100644
--- a/tests/psalm-baseline.xml
+++ b/tests/psalm-baseline.xml
@@ -93,6 +93,11 @@
<code>$qb2-&gt;createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)</code>
</ImplicitToStringCast>
</file>
+ <file src="lib/Db/TagMapper.php">
+ <ImplicitToStringCast occurrences="1">
+ <code>$qb-&gt;createNamedParameter($ids, IQueryBuilder::PARAM_STR_ARRAY)</code>
+ </ImplicitToStringCast>
+ </file>
<file src="lib/Db/MessageMapper.php">
<ImplicitToStringCast occurrences="26">
<code>$deleteRecipientsQuery-&gt;createFunction($messageIdQuery-&gt;getSQL())</code>