feat: move messages to junk folder
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
This commit is contained in:
Родитель
2507be59e9
Коммит
45c5ab9277
|
@ -22,7 +22,7 @@ Positive:
|
|||
|
||||
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
|
||||
]]></description>
|
||||
<version>3.4.0-alpha.2</version>
|
||||
<version>3.4.0-alpha.3</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Greta Doçi</author>
|
||||
<author homepage="https://github.com/nextcloud/groupware">Nextcloud Groupware Team</author>
|
||||
|
|
|
@ -60,6 +60,7 @@ use OCA\Mail\Listener\InteractionListener;
|
|||
use OCA\Mail\Listener\MailboxesSynchronizedSpecialMailboxesUpdater;
|
||||
use OCA\Mail\Listener\MessageCacheUpdaterListener;
|
||||
use OCA\Mail\Listener\MessageKnownSinceListener;
|
||||
use OCA\Mail\Listener\MoveJunkListener;
|
||||
use OCA\Mail\Listener\NewMessageClassificationListener;
|
||||
use OCA\Mail\Listener\OauthTokenRefreshListener;
|
||||
use OCA\Mail\Listener\SaveSentMessageListener;
|
||||
|
@ -123,6 +124,7 @@ class Application extends App implements IBootstrap {
|
|||
$context->registerEventListener(MessageFlaggedEvent::class, MessageCacheUpdaterListener::class);
|
||||
$context->registerEventListener(MessageFlaggedEvent::class, SpamReportListener::class);
|
||||
$context->registerEventListener(MessageFlaggedEvent::class, HamReportListener::class);
|
||||
$context->registerEventListener(MessageFlaggedEvent::class, MoveJunkListener::class);
|
||||
$context->registerEventListener(MessageDeletedEvent::class, MessageCacheUpdaterListener::class);
|
||||
$context->registerEventListener(MessageSentEvent::class, AddressCollectionListener::class);
|
||||
$context->registerEventListener(MessageSentEvent::class, FlagRepliedMessageListener::class);
|
||||
|
|
|
@ -242,7 +242,9 @@ class AccountsController extends Controller {
|
|||
int $trashMailboxId = null,
|
||||
int $archiveMailboxId = null,
|
||||
bool $signatureAboveQuote = null,
|
||||
int $trashRetentionDays = null): JSONResponse {
|
||||
int $trashRetentionDays = null,
|
||||
int $junkMailboxId = null,
|
||||
bool $moveJunk = null): JSONResponse {
|
||||
$account = $this->accountService->find($this->currentUserId, $id);
|
||||
|
||||
$dbAccount = $account->getMailAccount();
|
||||
|
@ -279,6 +281,13 @@ class AccountsController extends Controller {
|
|||
// Passing 0 (or lower) disables retention
|
||||
$dbAccount->setTrashRetentionDays($trashRetentionDays <= 0 ? null : $trashRetentionDays);
|
||||
}
|
||||
if ($junkMailboxId !== null) {
|
||||
$this->mailManager->getMailbox($this->currentUserId, $junkMailboxId);
|
||||
$dbAccount->setJunkMailboxId($junkMailboxId);
|
||||
}
|
||||
if ($moveJunk !== null) {
|
||||
$dbAccount->setMoveJunk($moveJunk);
|
||||
}
|
||||
return new JSONResponse(
|
||||
$this->accountService->save($dbAccount)
|
||||
);
|
||||
|
|
|
@ -110,6 +110,10 @@ use OCP\AppFramework\Db\Entity;
|
|||
* @method void setQuotaPercentage(int $quota);
|
||||
* @method int|null getTrashRetentionDays()
|
||||
* @method void setTrashRetentionDays(int|null $trashRetentionDays)
|
||||
* @method int|null getJunkMailboxId()
|
||||
* @method void setJunkMailboxId(?int $id)
|
||||
* @method bool isMoveJunk()
|
||||
* @method void setMoveJunk(bool $moveJunk)
|
||||
*/
|
||||
class MailAccount extends Entity {
|
||||
public const SIGNATURE_MODE_PLAIN = 0;
|
||||
|
@ -181,6 +185,9 @@ class MailAccount extends Entity {
|
|||
/** @var int|null */
|
||||
protected $trashRetentionDays;
|
||||
|
||||
protected ?int $junkMailboxId = null;
|
||||
protected bool $moveJunk = false;
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
*/
|
||||
|
@ -254,6 +261,8 @@ class MailAccount extends Entity {
|
|||
$this->addType('smimeCertificateId', 'integer');
|
||||
$this->addType('quotaPercentage', 'integer');
|
||||
$this->addType('trashRetentionDays', 'integer');
|
||||
$this->addType('junkMailboxId', 'integer');
|
||||
$this->addType('moveJunk', 'boolean');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -285,6 +294,8 @@ class MailAccount extends Entity {
|
|||
'smimeCertificateId' => $this->getSmimeCertificateId(),
|
||||
'quotaPercentage' => $this->getQuotaPercentage(),
|
||||
'trashRetentionDays' => $this->getTrashRetentionDays(),
|
||||
'junkMailboxId' => $this->getJunkMailboxId(),
|
||||
'moveJunk' => ($this->isMoveJunk() === true),
|
||||
];
|
||||
|
||||
if (!is_null($this->getOutboundHost())) {
|
||||
|
|
|
@ -112,6 +112,15 @@ class MailboxesSynchronizedSpecialMailboxesUpdater implements IEventListener {
|
|||
$mailAccount->setArchiveMailboxId(null);
|
||||
}
|
||||
}
|
||||
if ($mailAccount->getJunkMailboxId() === null || !array_key_exists($mailAccount->getJunkMailboxId(), $mailboxes)) {
|
||||
try {
|
||||
$junkMailbox = $this->findSpecial($mailboxes, 'junk');
|
||||
$mailAccount->setJunkMailboxId($junkMailbox->getId());
|
||||
} catch (DoesNotExistException) {
|
||||
$this->logger->info("Account " . $account->getId() . " does not have an junk mailbox");
|
||||
$mailAccount->setJunkMailboxId(null);
|
||||
}
|
||||
}
|
||||
|
||||
$this->mailAccountMapper->update($mailAccount);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
*
|
||||
* @author Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Listener;
|
||||
|
||||
use OCA\Mail\Contracts\IMailManager;
|
||||
use OCA\Mail\Events\MessageFlaggedEvent;
|
||||
use OCA\Mail\Exception\ClientException;
|
||||
use OCA\Mail\Exception\ServiceException;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @template-implements IEventListener<Event|MessageFlaggedEvent>
|
||||
*/
|
||||
class MoveJunkListener implements IEventListener {
|
||||
public function __construct(
|
||||
private IMailManager $mailManager,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
if (!$event instanceof MessageFlaggedEvent || $event->getFlag() !== '$junk') {
|
||||
return;
|
||||
}
|
||||
|
||||
$account = $event->getAccount();
|
||||
$mailAccount = $account->getMailAccount();
|
||||
|
||||
if (!$mailAccount->isMoveJunk()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mailbox = $event->getMailbox();
|
||||
|
||||
if ($event->isSet() && $mailAccount->getJunkMailboxId() !== $mailbox->getId()) {
|
||||
try {
|
||||
$junkMailbox = $this->mailManager->getMailbox($account->getUserId(), $mailAccount->getJunkMailboxId());
|
||||
} catch (ClientException) {
|
||||
$this->logger->debug('move to junk enabled, but junk mailbox does not exist. account_id: {account_id}, junk_mailbox_id: {junk_mailbox_id}', [
|
||||
'account_id' => $account->getId(),
|
||||
'junk_mailbox_id' => $mailAccount->getJunkMailboxId(),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->mailManager->moveMessage(
|
||||
$account,
|
||||
$mailbox->getName(),
|
||||
$event->getUid(),
|
||||
$account,
|
||||
$junkMailbox->getName(),
|
||||
);
|
||||
} catch (ServiceException $e) {
|
||||
$this->logger->error('move message to junk mailbox failed. account_id: {account_id}', [
|
||||
'exception' => $e,
|
||||
'account_id' => $account->getId(),
|
||||
]);
|
||||
}
|
||||
} elseif (!$event->isSet() && 'INBOX' !== $mailbox->getName()) {
|
||||
try {
|
||||
$this->mailManager->moveMessage(
|
||||
$account,
|
||||
$mailbox->getName(),
|
||||
$event->getUid(),
|
||||
$account,
|
||||
'INBOX',
|
||||
);
|
||||
} catch (ServiceException $e) {
|
||||
$this->logger->error('move message to inbox failed. account_id: {account_id}', [
|
||||
'exception' => $e,
|
||||
'account_id' => $account->getId(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
*
|
||||
* @author Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
*
|
||||
* @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\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
class Version3001Date20230307113544 extends SimpleMigrationStep {
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
$accountsTable = $schema->getTable('mail_accounts');
|
||||
$accountsTable->addColumn('junk_mailbox_id', 'integer', [
|
||||
'notnull' => false,
|
||||
'default' => null,
|
||||
'length' => 20,
|
||||
]);
|
||||
$accountsTable->addColumn('move_junk', 'boolean', [
|
||||
'notnull' => false,
|
||||
'default' => false,
|
||||
]);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
|
@ -41,6 +41,11 @@
|
|||
</p>
|
||||
|
||||
<MailboxInlinePicker v-model="archiveMailbox" :account="account" :disabled="saving" />
|
||||
|
||||
<p>
|
||||
{{ t('mail', 'Junk messages are saved in:') }}
|
||||
</p>
|
||||
<MailboxInlinePicker v-model="junkMailbox" :account="account" :disabled="saving" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -173,6 +178,33 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
junkMailbox: {
|
||||
get() {
|
||||
const mb = this.$store.getters.getMailbox(this.account.junkMailboxId)
|
||||
if (!mb) {
|
||||
return
|
||||
}
|
||||
return mb.databaseId
|
||||
},
|
||||
async set(junkMailboxId) {
|
||||
logger.debug('setting junk mailbox to ' + junkMailboxId)
|
||||
this.saving = true
|
||||
try {
|
||||
await this.$store.dispatch('patchAccount', {
|
||||
account: this.account,
|
||||
data: {
|
||||
junkMailboxId,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('could not set junk mailbox', {
|
||||
error,
|
||||
})
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<AppSettingsSection id="default-folders" :title=" t('mail', 'Default folders')">
|
||||
<p class="settings-hint">
|
||||
{{
|
||||
t('mail', 'The folders to use for drafts, sent messages, deleted messages and archived messages.')
|
||||
t('mail', 'The folders to use for drafts, sent messages, deleted messages, archived messages and junk messages.')
|
||||
}}
|
||||
</p>
|
||||
<AccountDefaultsSettings :account="account" />
|
||||
|
@ -65,6 +65,9 @@
|
|||
</p>
|
||||
<TrashRetentionSettings :account="account" />
|
||||
</AppSettingsSection>
|
||||
<AppSettingsSection id="junk-settings" :title="t('mail', 'Junk settings')">
|
||||
<JunkSettings :account="account" />
|
||||
</AppSettingsSection>
|
||||
<AppSettingsSection
|
||||
v-if="account"
|
||||
id="out-of-office-replies"
|
||||
|
@ -128,6 +131,7 @@ import SieveFilterForm from './SieveFilterForm'
|
|||
import OutOfOfficeForm from './OutOfOfficeForm'
|
||||
import CertificateSettings from './CertificateSettings'
|
||||
import TrashRetentionSettings from './TrashRetentionSettings'
|
||||
import JunkSettings from './JunkSettings'
|
||||
|
||||
export default {
|
||||
name: 'AccountSettings',
|
||||
|
@ -145,6 +149,7 @@ export default {
|
|||
OutOfOfficeForm,
|
||||
CertificateSettings,
|
||||
TrashRetentionSettings,
|
||||
JunkSettings,
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
|
|
|
@ -586,8 +586,30 @@ export default {
|
|||
onToggleSeen() {
|
||||
this.$store.dispatch('toggleEnvelopeSeen', { envelope: this.data })
|
||||
},
|
||||
onToggleJunk() {
|
||||
this.$store.dispatch('toggleEnvelopeJunk', this.data)
|
||||
async onToggleJunk() {
|
||||
const removeEnvelope = await this.$store.dispatch('moveEnvelopeToJunk', this.data)
|
||||
|
||||
/**
|
||||
* moveEnvelopeToJunk returns true if the envelope should be moved to a different mailbox.
|
||||
*
|
||||
* Our backend (MessageMapper.move) implemented move as copy and delete.
|
||||
* The message is copied to another mailbox and gets a new UID; the message in the current folder is deleted.
|
||||
*
|
||||
* Trigger the delete event here to open the next envelope and remove the current envelope from the list.
|
||||
* The delete event bubbles up to Mailbox.onDelete to the actual implementation.
|
||||
*
|
||||
* In Mailbox.onDelete, fetchNextEnvelopes requires the current envelope to find the next envelope.
|
||||
* Therefore, it must run before removing the envelope.
|
||||
*/
|
||||
|
||||
if (removeEnvelope) {
|
||||
await this.$emit('delete', this.data.databaseId)
|
||||
}
|
||||
|
||||
await this.$store.dispatch('toggleEnvelopeJunk', {
|
||||
envelope: this.data,
|
||||
removeEnvelope,
|
||||
})
|
||||
},
|
||||
async onDelete() {
|
||||
// Remove from selection first
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<!--
|
||||
- @copyright 2023 Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
-
|
||||
- @author 2023 Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
-
|
||||
- @license AGPL-3.0-or-later
|
||||
-
|
||||
- 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/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NcCheckboxRadioSwitch
|
||||
:checked="account.moveJunk"
|
||||
:disabled="saving"
|
||||
type="switch"
|
||||
@update:checked="saveMoveJunk">
|
||||
{{ t('mail', 'Move messages to Junk folder') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import logger from '../logger'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch'
|
||||
|
||||
export default {
|
||||
name: 'JunkSettings',
|
||||
components: {
|
||||
NcCheckboxRadioSwitch,
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
saving: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveMoveJunk(moveJunk) {
|
||||
logger.debug('setting move junk to mailbox to ' + moveJunk)
|
||||
this.saving = true
|
||||
try {
|
||||
await this.$store.dispatch('patchAccount', {
|
||||
account: this.account,
|
||||
data: {
|
||||
moveJunk,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('could not set move junk to mailbox', {
|
||||
error,
|
||||
})
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -381,8 +381,30 @@ export default {
|
|||
onToggleSeen() {
|
||||
this.$store.dispatch('toggleEnvelopeSeen', { envelope: this.envelope })
|
||||
},
|
||||
onToggleJunk() {
|
||||
this.$store.dispatch('toggleEnvelopeJunk', this.envelope)
|
||||
async onToggleJunk() {
|
||||
const removeEnvelope = await this.$store.dispatch('moveEnvelopeToJunk', this.envelope)
|
||||
|
||||
/**
|
||||
* moveEnvelopeToJunk returns true if the envelope should be moved to a different mailbox.
|
||||
*
|
||||
* Our backend (MessageMapper.move) implemented move as copy and delete.
|
||||
* The message is copied to another mailbox and gets a new UID; the message in the current folder is deleted.
|
||||
*
|
||||
* Trigger the delete event here to open the next envelope and remove the current envelope from the list.
|
||||
* The delete event bubbles up to MailboxThread.deleteMessage and is forwarded to Mailbox.onDelete to the actual implementation.
|
||||
*
|
||||
* In Mailbox.onDelete, fetchNextEnvelopes requires the current envelope to find the next envelope.
|
||||
* Therefore, it must run before removing the envelope.
|
||||
*/
|
||||
|
||||
if (removeEnvelope) {
|
||||
await this.$emit('delete', this.envelope.databaseId)
|
||||
}
|
||||
|
||||
await this.$store.dispatch('toggleEnvelopeJunk', {
|
||||
envelope: this.envelope,
|
||||
removeEnvelope,
|
||||
})
|
||||
},
|
||||
toggleSelected() {
|
||||
this.$emit('update:selected')
|
||||
|
|
|
@ -56,6 +56,8 @@
|
|||
:size="20" />
|
||||
<IconDelete v-else-if="mailbox.databaseId === account.trashMailboxId"
|
||||
:size="20" />
|
||||
<IconJunk v-else-if="mailbox.databaseId === account.junkMailboxId"
|
||||
:size="20" />
|
||||
<IconFolderShared v-else-if="mailbox.shared"
|
||||
:size="20" />
|
||||
<IconFolder v-else
|
||||
|
@ -224,6 +226,7 @@ import IconInfo from 'vue-material-design-icons/Information'
|
|||
import IconDraft from 'vue-material-design-icons/Pencil'
|
||||
import IconArchive from 'vue-material-design-icons/PackageDown'
|
||||
import IconInbox from 'vue-material-design-icons/Home'
|
||||
import IconJunk from 'vue-material-design-icons/Fire'
|
||||
import IconAllInboxes from 'vue-material-design-icons/InboxMultiple'
|
||||
import EraserVariant from 'vue-material-design-icons/EraserVariant'
|
||||
import ImportantIcon from './icons/ImportantIcon'
|
||||
|
@ -263,6 +266,7 @@ export default {
|
|||
IconFolderShared,
|
||||
IconDraft,
|
||||
IconArchive,
|
||||
IconJunk,
|
||||
IconInbox,
|
||||
EraserVariant,
|
||||
ImportantIcon,
|
||||
|
|
|
@ -961,7 +961,7 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
async toggleEnvelopeJunk({ commit, getters }, envelope) {
|
||||
async toggleEnvelopeJunk({ commit, getters }, { envelope, removeEnvelope }) {
|
||||
return handleHttpAuthErrors(commit, async () => {
|
||||
// Change immediately and switch back on error
|
||||
const oldState = envelope.flags.$junk
|
||||
|
@ -976,6 +976,10 @@ export default {
|
|||
value: oldState,
|
||||
})
|
||||
|
||||
if (removeEnvelope) {
|
||||
commit('removeEnvelope', { id: envelope.databaseId })
|
||||
}
|
||||
|
||||
try {
|
||||
await setEnvelopeFlags(envelope.databaseId, {
|
||||
$junk: !oldState,
|
||||
|
@ -984,6 +988,10 @@ export default {
|
|||
} catch (error) {
|
||||
console.error('could not toggle message junk state', error)
|
||||
|
||||
if (removeEnvelope) {
|
||||
commit('addEnvelope', envelope)
|
||||
}
|
||||
|
||||
// Revert change
|
||||
commit('flagEnvelope', {
|
||||
envelope,
|
||||
|
@ -1361,4 +1369,28 @@ export default {
|
|||
commit('patchAccount', { account, data: { smimeCertificateId } })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Should the envelope moved to the junk (or back to inbox)
|
||||
*
|
||||
* @param {object} context Vuex store context
|
||||
* @param {object} context.getters Vuex store getters
|
||||
* @param {object} envelope envelope object@
|
||||
* @return {boolean}
|
||||
*/
|
||||
async moveEnvelopeToJunk({ getters }, envelope) {
|
||||
const account = getters.getAccount(envelope.accountId)
|
||||
if (account.moveJunk === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!envelope.flags.$junk) {
|
||||
// move message to junk
|
||||
return account.junkMailboxId && envelope.mailboxId !== account.junkMailboxId
|
||||
}
|
||||
|
||||
const inbox = getters.getInbox(account.id)
|
||||
// move message to inbox
|
||||
return inbox && envelope.mailboxId !== inbox.databaseId
|
||||
},
|
||||
}
|
||||
|
|
|
@ -144,4 +144,10 @@ export const getters = {
|
|||
},
|
||||
getNcVersion: (state) => state.preferences?.ncVersion,
|
||||
getAppVersion: (state) => state.preferences?.version,
|
||||
findMailboxBySpecialRole: (state, getters) => (accountId, specialRole) => {
|
||||
return getters.getMailboxes(accountId).find(mailbox => mailbox.specialRole === specialRole)
|
||||
},
|
||||
getInbox: (state, getters) => (accountId) => {
|
||||
return getters.findMailboxBySpecialRole(accountId, 'inbox')
|
||||
},
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ describe('Vuex store actions', () => {
|
|||
dispatch: jest.fn(),
|
||||
getters: {
|
||||
accounts: [],
|
||||
getAccount: jest.fn(),
|
||||
getInbox: jest.fn(),
|
||||
getMailbox: jest.fn(),
|
||||
getMailboxes: jest.fn(),
|
||||
getEnvelope: jest.fn(),
|
||||
|
@ -513,4 +515,87 @@ describe('Vuex store actions', () => {
|
|||
expect(NotificationService.showNewMessagesNotification).toHaveBeenCalled
|
||||
})
|
||||
})
|
||||
|
||||
it('should move message to junk', async() => {
|
||||
context.getters.getAccount.mockReturnValueOnce({
|
||||
moveJunk: true,
|
||||
junkMailboxId: 10
|
||||
})
|
||||
|
||||
const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
|
||||
flags: {
|
||||
$junk: false
|
||||
},
|
||||
mailboxId: 1
|
||||
})
|
||||
|
||||
expect(removeEnvelope).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should move message to junk, no mailbox configured', async() => {
|
||||
context.getters.getAccount.mockReturnValueOnce({
|
||||
moveJunk: true,
|
||||
junkMailboxId: null
|
||||
})
|
||||
|
||||
const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
|
||||
flags: {
|
||||
$junk: false
|
||||
},
|
||||
mailboxId: 1
|
||||
})
|
||||
|
||||
expect(removeEnvelope).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should move message to inbox', async() => {
|
||||
context.getters.getAccount.mockReturnValueOnce({
|
||||
moveJunk: true,
|
||||
junkMailboxId: 10
|
||||
})
|
||||
context.getters.getInbox.mockReturnValueOnce({
|
||||
databaseId: 1
|
||||
})
|
||||
|
||||
const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
|
||||
flags: {
|
||||
$junk: true
|
||||
},
|
||||
mailboxId: 10
|
||||
})
|
||||
|
||||
expect(removeEnvelope).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should move message to inbox, inbox not found', async() => {
|
||||
context.getters.getAccount.mockReturnValueOnce({
|
||||
moveJunk: true,
|
||||
junkMailboxId: 10
|
||||
})
|
||||
context.getters.getInbox.mockReturnValueOnce(undefined)
|
||||
|
||||
const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
|
||||
flags: {
|
||||
$junk: true
|
||||
},
|
||||
mailboxId: 10
|
||||
})
|
||||
|
||||
expect(removeEnvelope).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should not move messages', async() => {
|
||||
context.getters.getAccount.mockReturnValueOnce({
|
||||
moveJunk: false
|
||||
})
|
||||
|
||||
const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
|
||||
flags: {
|
||||
$junk: true
|
||||
},
|
||||
mailboxId: 10
|
||||
})
|
||||
|
||||
expect(removeEnvelope).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -199,4 +199,53 @@ describe('Vuex store getters', () => {
|
|||
const envelopesB = getters.getEnvelopesByThreadRootId('345-678-901')
|
||||
expect(envelopesB.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('find mailbox by special role: inbox', () => {
|
||||
const mockedGetters = {
|
||||
getMailboxes: () => [
|
||||
{
|
||||
name: 'Test',
|
||||
specialRole: 0,
|
||||
},
|
||||
{
|
||||
name: 'INBOX',
|
||||
specialRole: 'inbox',
|
||||
},
|
||||
{
|
||||
name: 'Trash',
|
||||
specialRole: 'trash',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = getters.findMailboxBySpecialRole(state, mockedGetters)('100', 'inbox')
|
||||
|
||||
expect(result).toEqual({
|
||||
name: 'INBOX',
|
||||
specialRole: 'inbox'
|
||||
});
|
||||
})
|
||||
|
||||
it('find mailbox by special role: undefined', () => {
|
||||
const mockedGetters = {
|
||||
getMailboxes: () => [
|
||||
{
|
||||
name: 'Test',
|
||||
specialRole: 0,
|
||||
},
|
||||
{
|
||||
name: 'INBOX',
|
||||
specialRole: 'inbox',
|
||||
},
|
||||
{
|
||||
name: 'Trash',
|
||||
specialRole: 'trash',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = getters.findMailboxBySpecialRole(state, mockedGetters)('100', 'drafts')
|
||||
|
||||
expect(result).toEqual(undefined);
|
||||
})
|
||||
})
|
||||
|
|
|
@ -78,6 +78,8 @@ class MailAccountTest extends TestCase {
|
|||
'smimeCertificateId' => null,
|
||||
'quotaPercentage' => 10,
|
||||
'trashRetentionDays' => 60,
|
||||
'junkMailboxId' => null,
|
||||
'moveJunk' => false
|
||||
], $a->toJson());
|
||||
}
|
||||
|
||||
|
@ -111,6 +113,8 @@ class MailAccountTest extends TestCase {
|
|||
'smimeCertificateId' => null,
|
||||
'quotaPercentage' => null,
|
||||
'trashRetentionDays' => 60,
|
||||
'junkMailboxId' => null,
|
||||
'moveJunk' => false,
|
||||
];
|
||||
$a = new MailAccount($expected);
|
||||
// TODO: fix inconsistency
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
*
|
||||
* @author Daniel Kesselberg <mail@danielkesselberg.de>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace Unit\Listener;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
use OCA\Mail\Account;
|
||||
use OCA\Mail\Contracts\IMailManager;
|
||||
use OCA\Mail\Db\MailAccount;
|
||||
use OCA\Mail\Db\Mailbox;
|
||||
use OCA\Mail\Events\MessageFlaggedEvent;
|
||||
use OCA\Mail\Exception\ClientException;
|
||||
use OCA\Mail\Exception\ServiceException;
|
||||
use OCA\Mail\Listener\MoveJunkListener;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\Test\TestLogger;
|
||||
|
||||
class MoveJunkListenerTest extends TestCase {
|
||||
private IMailManager $mailManager;
|
||||
private LoggerInterface $logger;
|
||||
private MoveJunkListener $listener;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->mailManager = $this->createMock(IMailManager::class);
|
||||
$this->logger = new TestLogger();
|
||||
|
||||
$this->listener = new MoveJunkListener(
|
||||
$this->mailManager,
|
||||
$this->logger
|
||||
);
|
||||
}
|
||||
|
||||
public function testIgnoreOtherFlags(): void {
|
||||
$event = $this->createMock(MessageFlaggedEvent::class);
|
||||
$event->method('getFlag')
|
||||
->willReturn('test');
|
||||
|
||||
$event->expects($this->never())
|
||||
->method('getAccount');
|
||||
|
||||
$this->listener->handle($event);
|
||||
}
|
||||
|
||||
public function testMoveJunkDisabled(): void {
|
||||
$mailAccount = new MailAccount();
|
||||
$mailAccount->setMoveJunk(false);
|
||||
$account = new Account($mailAccount);
|
||||
|
||||
$event = $this->createMock(MessageFlaggedEvent::class);
|
||||
$event->method('getFlag')
|
||||
->willReturn('$junk');
|
||||
$event->method('getAccount')
|
||||
->willReturn($account);
|
||||
|
||||
$event->expects($this->never())
|
||||
->method('getMailbox');
|
||||
|
||||
$this->listener->handle($event);
|
||||
}
|
||||
|
||||
public function testMoveJunkMailboxNotFound(): void {
|
||||
$mailAccount = new MailAccount();
|
||||
$mailAccount->setJunkMailboxId(200);
|
||||
$mailAccount->setMoveJunk(true);
|
||||
$mailAccount->setUserId('bob');
|
||||
$account = new Account($mailAccount);
|
||||
|
||||
$mailbox = new Mailbox();
|
||||
$mailbox->setId(100);
|
||||
|
||||
$this->mailManager->method('getMailbox')
|
||||
->willThrowException(new ClientException('Computer says no'));
|
||||
|
||||
$event = new MessageFlaggedEvent(
|
||||
$account,
|
||||
$mailbox,
|
||||
100,
|
||||
'$junk',
|
||||
true
|
||||
);
|
||||
|
||||
$this->listener->handle($event);
|
||||
|
||||
$this->assertCount(1, $this->logger->records);
|
||||
}
|
||||
|
||||
public function testMoveJunkAlreadyInJunk(): void {
|
||||
$mailAccount = new MailAccount();
|
||||
$mailAccount->setJunkMailboxId(200);
|
||||
$mailAccount->setMoveJunk(true);
|
||||
$mailAccount->setUserId('bob');
|
||||
$account = new Account($mailAccount);
|
||||
|
||||
$mailbox = new Mailbox();
|
||||
$mailbox->setId(200);
|
||||
|
||||
$this->mailManager->expects($this->never())
|
||||
->method('moveMessage');
|
||||
|
||||
$event = new MessageFlaggedEvent(
|
||||
$account,
|
||||
$mailbox,
|
||||
100,
|
||||
'$junk',
|
||||
true
|
||||
);
|
||||
|
||||
$this->listener->handle($event);
|
||||
}
|
||||
|
||||
public function testMoveJunkFailed(): void {
|
||||
$mailAccount = new MailAccount();
|
||||
$mailAccount->setJunkMailboxId(200);
|
||||
$mailAccount->setMoveJunk(true);
|
||||
$mailAccount->setUserId('bob');
|
||||
$account = new Account($mailAccount);
|
||||
|
||||
$mailbox = new Mailbox();
|
||||
$mailbox->setId(100);
|
||||
$mailbox->setName('INBOX');
|
||||
|
||||
$junkMailbox = new Mailbox();
|
||||
$junkMailbox->setId(200);
|
||||
$junkMailbox->setName('Junk');
|
||||
|
||||
$this->mailManager->method('getMailbox')
|
||||
->willReturn($junkMailbox);
|
||||
|
||||
$this->mailManager->method('moveMessage')
|
||||
->willThrowException(new ServiceException('Computer says no'));
|
||||
|
||||
$event = new MessageFlaggedEvent(
|
||||
$account,
|
||||
$mailbox,
|
||||
100,
|
||||
'$junk',
|
||||
true
|
||||
);
|
||||
|
||||
$this->listener->handle($event);
|
||||
|
||||
$this->assertCount(1, $this->logger->records);
|
||||
}
|
||||
|
||||
public function testMoveJunkAlreadyInInbox(): void {
|
||||
$mailAccount = new MailAccount();
|
||||
$mailAccount->setJunkMailboxId(200);
|
||||
$mailAccount->setMoveJunk(true);
|
||||
$mailAccount->setUserId('bob');
|
||||
$account = new Account($mailAccount);
|
||||
|
||||
$mailbox = new Mailbox();
|
||||
$mailbox->setId(100);
|
||||
$mailbox->setName('INBOX');
|
||||
|
||||
$this->mailManager->expects($this->never())
|
||||
->method('moveMessage');
|
||||
|
||||
$event = new MessageFlaggedEvent(
|
||||
$account,
|
||||
$mailbox,
|
||||
100,
|
||||
'$junk',
|
||||
false
|
||||
);
|
||||
|
||||
$this->listener->handle($event);
|
||||
}
|
||||
|
||||
public function testMoveJunkToInboxFailed(): void {
|
||||
$mailAccount = new MailAccount();
|
||||
$mailAccount->setJunkMailboxId(200);
|
||||
$mailAccount->setMoveJunk(true);
|
||||
$mailAccount->setUserId('bob');
|
||||
$account = new Account($mailAccount);
|
||||
|
||||
$mailbox = new Mailbox();
|
||||
$mailbox->setId(200);
|
||||
$mailbox->setName('Junk');
|
||||
|
||||
$junkMailbox = new Mailbox();
|
||||
$junkMailbox->setId(200);
|
||||
$junkMailbox->setName('Junk');
|
||||
|
||||
$this->mailManager->method('getMailbox')
|
||||
->willReturn($junkMailbox);
|
||||
|
||||
$this->mailManager->method('moveMessage')
|
||||
->willThrowException(new ServiceException('Computer says no'));
|
||||
|
||||
$event = new MessageFlaggedEvent(
|
||||
$account,
|
||||
$mailbox,
|
||||
100,
|
||||
'$junk',
|
||||
false
|
||||
);
|
||||
|
||||
$this->listener->handle($event);
|
||||
|
||||
$this->assertCount(1, $this->logger->records);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче