Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The rating depends on the installed text processing backend. See [the rating ove

Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]></description>
<version>5.7.0-alpha.2</version>
<version>5.7.0-alpha.3</version>
<licence>agpl</licence>
<author homepage="https://github.com/ChristophWurst">Christoph Wurst</author>
<author homepage="https://github.com/GretaD">GretaD</author>
Expand Down
4 changes: 4 additions & 0 deletions lib/Account.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public function getUserId() {
public function getDebug(): bool {
return $this->account->getDebug();
}

public function getImipCreate(): bool {
return $this->account->getImipCreate();
}

/**
* Set the quota percentage
Expand Down
7 changes: 6 additions & 1 deletion lib/Controller/AccountsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,9 @@ public function patchAccount(int $id,
?int $trashRetentionDays = null,
?int $junkMailboxId = null,
?bool $searchBody = null,
?bool $classificationEnabled = null): JSONResponse {
?bool $classificationEnabled = null,
?bool $imipCreate = null,
): JSONResponse {
$account = $this->accountService->find($this->currentUserId, $id);

$dbAccount = $account->getMailAccount();
Expand Down Expand Up @@ -282,6 +284,9 @@ public function patchAccount(int $id,
if ($classificationEnabled !== null) {
$dbAccount->setClassificationEnabled($classificationEnabled);
}
if ($imipCreate !== null) {
$dbAccount->setImipCreate($imipCreate);
}
return new JSONResponse(
new Account($this->accountService->save($dbAccount))
);
Expand Down
9 changes: 9 additions & 0 deletions lib/Db/MailAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
* @method void setDebug(bool $debug)
* @method bool getClassificationEnabled()
* @method void setClassificationEnabled(bool $classificationEnabled)
* @method bool getImipCreate()
* @method void setImipCreate(bool $value)
*/
class MailAccount extends Entity {
public const SIGNATURE_MODE_PLAIN = 0;
Expand Down Expand Up @@ -190,6 +192,8 @@ class MailAccount extends Entity {
protected bool $debug = false;
protected bool $classificationEnabled = true;

protected bool $imipCreate = false;

/**
* @param array $params
*/
Expand Down Expand Up @@ -253,6 +257,9 @@ public function __construct(array $params = []) {
if (isset($params['classificationEnabled'])) {
$this->setClassificationEnabled($params['classificationEnabled']);
}
if (isset($params['imipCreate'])) {
$this->setImipCreate($params['imipCreate']);
}

$this->addType('inboundPort', 'integer');
$this->addType('outboundPort', 'integer');
Expand All @@ -278,6 +285,7 @@ public function __construct(array $params = []) {
$this->addType('oooFollowsSystem', 'boolean');
$this->addType('debug', 'boolean');
$this->addType('classificationEnabled', 'boolean');
$this->addType('imipCreate', 'boolean');
}

public function getOutOfOfficeFollowsSystem(): bool {
Expand Down Expand Up @@ -327,6 +335,7 @@ public function toJson() {
'outOfOfficeFollowsSystem' => $this->getOutOfOfficeFollowsSystem(),
'debug' => $this->getDebug(),
'classificationEnabled' => $this->getClassificationEnabled(),
'imipCreate' => $this->getImipCreate(),
];

if (!is_null($this->getOutboundHost())) {
Expand Down
37 changes: 37 additions & 0 deletions lib/Migration/Version5007Date20251208000000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version5007Date20251208000000 extends SimpleMigrationStep {

/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();
$accountsTable = $schema->getTable('mail_accounts');
if (!$accountsTable->hasColumn('imip_create')) {
$accountsTable->addColumn('imip_create', Types::BOOLEAN, [
'default' => '0',
'notNull' => false,
]);
}
return $schema;
}
}
28 changes: 13 additions & 15 deletions lib/Service/IMipService.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@
continue;
}

$principalUri = 'principals/users/' . $account->getUserId();
$userId = $account->getUserId();
$recipient = $account->getEmail();
$imipCreate = $account->getImipCreate();

foreach ($filteredMessages as $message) {
/** @var IMAPMessage $imapMessage */
Expand All @@ -138,20 +139,17 @@
try {
// an IMAP message could contain more than one iMIP object
foreach ($imapMessage->scheduling as $schedulingInfo) {
if ($schedulingInfo['method'] === 'REQUEST') {
$processed = $this->calendarManager->handleIMipRequest($principalUri, $sender, $recipient, $schedulingInfo['contents']);
$message->setImipProcessed($processed);
$message->setImipError(!$processed);
} elseif ($schedulingInfo['method'] === 'REPLY') {
$processed = $this->calendarManager->handleIMipReply($principalUri, $sender, $recipient, $schedulingInfo['contents']);
$message->setImipProcessed($processed);
$message->setImipError(!$processed);
} elseif ($schedulingInfo['method'] === 'CANCEL') {
$replyTo = $imapMessage->getReplyTo()->first()?->getEmail();
$processed = $this->calendarManager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $schedulingInfo['contents']);
$message->setImipProcessed($processed);
$message->setImipError(!$processed);
}
$processed = $this->calendarManager->handleIMip(

Check failure on line 142 in lib/Service/IMipService.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable31

UndefinedInterfaceMethod

lib/Service/IMipService.php:142:44: UndefinedInterfaceMethod: Method OCP\Calendar\IManager::handleIMip does not exist (see https://psalm.dev/181)
$userId,
$schedulingInfo['contents'],
[

Check failure on line 145 in lib/Service/IMipService.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable32

InvalidArgument

lib/Service/IMipService.php:145:8: InvalidArgument: Argument 3 of OCP\Calendar\IManager::handleIMip expects array{absent?: 'create', recipient?: string}, but array{absent: 'create'|'ignore', absentCreateStatus: 'tentative', recipient: string} with additional array shape fields (absentCreateStatus) was provided (see https://psalm.dev/004)

Check failure on line 145 in lib/Service/IMipService.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidArgument

lib/Service/IMipService.php:145:8: InvalidArgument: Argument 3 of OCP\Calendar\IManager::handleIMip expects array{absent?: 'create', recipient?: string}, but array{absent: 'create'|'ignore', absentCreateStatus: 'tentative', recipient: string} with additional array shape fields (absentCreateStatus) was provided (see https://psalm.dev/004)
'recipient' => $recipient,
'absent' => $imipCreate ? 'create' : 'ignore',
'absentCreateStatus' => 'tentative',
],
);
$message->setImipProcessed($processed);
$message->setImipError(!$processed);
}
} catch (Throwable $e) {
$this->logger->error('iMIP message processing failed', [
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nextcloud-mail",
"version": "5.7.0-alpha.1",
"version": "5.7.0-alpha.3",
"private": true,
"description": "Nextcloud Mail",
"license": "AGPL-3.0-only",
Expand Down
8 changes: 8 additions & 0 deletions src/components/AccountSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
</NcButton>
</div>
</AppSettingsSection>
<AppSettingsSection
v-if="account"
id="calendar-settings"
:name="t('mail', 'Calendar settings')">
<CalendarSettings :account="account" />
</AppSettingsSection>
<AppSettingsSection
v-if="account"
id="classification"
Expand All @@ -79,14 +85,14 @@
id="mail-filters"
:name="t('mail', 'Filters')">
<div id="mail-filters">
<MailFilters :key="account.accountId" ref="mailFilters" :account="account" />

Check warning on line 88 in src/components/AccountSettings.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'mailFilters' is defined as ref, but never used
</div>
</AppSettingsSection>
<AppSettingsSection
v-if="account"
id="quick-actions-settings"
:name="t('mail', 'Quick actions')">
<Settings :key="account.accountId" ref="quickActions" :account="account" />

Check warning on line 95 in src/components/AccountSettings.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'quickActions' is defined as ref, but never used
</AppSettingsSection>
<AppSettingsSection
v-if="account && account.sieveEnabled"
Expand All @@ -95,7 +101,7 @@
<div id="sieve-filter">
<SieveFilterForm
:key="account.accountId"
ref="sieveFilterForm"

Check warning on line 104 in src/components/AccountSettings.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'sieveFilterForm' is defined as ref, but never used
:account="account" />
</div>
</AppSettingsSection>
Expand All @@ -119,7 +125,7 @@
<div id="sieve-settings">
<SieveAccountForm
:key="account.accountId"
ref="sieveAccountForm"

Check warning on line 128 in src/components/AccountSettings.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'sieveAccountForm' is defined as ref, but never used
:account="account" />
</div>
</AppSettingsSection>
Expand All @@ -138,6 +144,7 @@
import AliasSettings from '../components/AliasSettings.vue'
import EditorSettings from '../components/EditorSettings.vue'
import SignatureSettings from '../components/SignatureSettings.vue'
import CalendarSettings from './CalendarSettings.vue'
import CertificateSettings from './CertificateSettings.vue'
import MailFilters from './mailFilter/MailFilters.vue'
import OutOfOfficeForm from './OutOfOfficeForm.vue'
Expand All @@ -161,6 +168,7 @@
AppSettingsDialog,
AppSettingsSection,
AccountDefaultsSettings,
CalendarSettings,
OutOfOfficeForm,
CertificateSettings,
TrashRetentionSettings,
Expand Down Expand Up @@ -203,7 +211,7 @@
},

watch: {
open(newState, oldState) {

Check warning on line 214 in src/components/AccountSettings.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'oldState' is defined but never used
if (newState === true && this.fetchActiveSieveScript === true) {
logger.debug(`Load active sieve script for account ${this.account.accountId}`)
this.fetchActiveSieveScript = false
Expand Down
69 changes: 69 additions & 0 deletions src/components/CalendarSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<div>
<NcCheckboxRadioSwitch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing loading state, which is problematic for multiple clicks in a short time

id="imip-create"
:checked="imipCreate"
@update:checked="onToggleImipCreate">
{{ t('mail', 'Automatically create tentative appointments in calendar') }}
</NcCheckboxRadioSwitch>
</div>
</template>

<script>
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
import { mapStores } from 'pinia'
import Logger from '../logger.js'
import useMainStore from '../store/mainStore.js'

export default {
name: 'CalendarSettings',
components: {
NcCheckboxRadioSwitch,
},

Check failure on line 27 in src/components/CalendarSettings.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Enforce new lines between multi-line properties in Vue components
props: {
account: {
type: Object,
required: true,
},
},

data() {
return {
imipCreate: this.account.imipCreate,
}
},

computed: {
...mapStores(useMainStore),
},

methods: {
async onToggleImipCreate(val) {
const oldVal = this.imipCreate
this.imipCreate = val

try {
await this.mainStore.patchAccount({
account: this.account,
data: {
imipCreate: val,
},
})
Logger.info(`Automatic calendar appointment creation ${val ? 'enabled' : 'disabled'}`)
} catch (error) {
Logger.error(`could not ${val ? 'enable' : 'disable'} automatic calendar appointment creation`, { error })
this.imipCreate = oldVal
throw error
}
},
},
}
</script>

<style lang="scss" scoped>

Check failure on line 68 in src/components/CalendarSettings.vue

View workflow job for this annotation

GitHub Actions / NPM lint

`<style>` is empty. Empty block is not allowed
</style>
Loading
Loading