Skip to content

Commit 499bb78

Browse files
feat: automated appointment creation
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
1 parent 1243f84 commit 499bb78

File tree

13 files changed

+435
-28
lines changed

13 files changed

+435
-28
lines changed

appinfo/info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The rating depends on the installed text processing backend. See [the rating ove
3434
3535
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
3636
]]></description>
37-
<version>5.7.0-beta.2</version>
37+
<version>5.7.0-beta.3</version>
3838
<licence>agpl</licence>
3939
<author homepage="https://github.com/ChristophWurst">Christoph Wurst</author>
4040
<author homepage="https://github.com/GretaD">GretaD</author>

lib/Account.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public function getDebug(): bool {
6969
return $this->account->getDebug();
7070
}
7171

72+
public function getImipCreate(): bool {
73+
return $this->account->getImipCreate();
74+
}
75+
7276
/**
7377
* Set the quota percentage
7478
* @param Quota $quota

lib/Controller/AccountsController.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,9 @@ public function patchAccount(int $id,
234234
?int $trashRetentionDays = null,
235235
?int $junkMailboxId = null,
236236
?bool $searchBody = null,
237-
?bool $classificationEnabled = null): JSONResponse {
237+
?bool $classificationEnabled = null,
238+
?bool $imipCreate = null,
239+
): JSONResponse {
238240
$account = $this->accountService->find($this->currentUserId, $id);
239241

240242
$dbAccount = $account->getMailAccount();
@@ -285,6 +287,9 @@ public function patchAccount(int $id,
285287
if ($classificationEnabled !== null) {
286288
$dbAccount->setClassificationEnabled($classificationEnabled);
287289
}
290+
if ($imipCreate !== null) {
291+
$dbAccount->setImipCreate($imipCreate);
292+
}
288293
return new JSONResponse(
289294
new Account($this->accountService->save($dbAccount))
290295
);

lib/Db/MailAccount.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@
105105
* @method void setDebug(bool $debug)
106106
* @method bool getClassificationEnabled()
107107
* @method void setClassificationEnabled(bool $classificationEnabled)
108+
* @method bool getImipCreate()
109+
* @method void setImipCreate(bool $value)
108110
*/
109111
class MailAccount extends Entity {
110112
public const SIGNATURE_MODE_PLAIN = 0;
@@ -190,6 +192,8 @@ class MailAccount extends Entity {
190192
protected bool $debug = false;
191193
protected bool $classificationEnabled = true;
192194

195+
protected bool $imipCreate = false;
196+
193197
/**
194198
* @param array $params
195199
*/
@@ -253,6 +257,9 @@ public function __construct(array $params = []) {
253257
if (isset($params['classificationEnabled'])) {
254258
$this->setClassificationEnabled($params['classificationEnabled']);
255259
}
260+
if (isset($params['imipCreate'])) {
261+
$this->setImipCreate($params['imipCreate']);
262+
}
256263

257264
$this->addType('inboundPort', 'integer');
258265
$this->addType('outboundPort', 'integer');
@@ -278,6 +285,7 @@ public function __construct(array $params = []) {
278285
$this->addType('oooFollowsSystem', 'boolean');
279286
$this->addType('debug', 'boolean');
280287
$this->addType('classificationEnabled', 'boolean');
288+
$this->addType('imipCreate', 'boolean');
281289
}
282290

283291
public function getOutOfOfficeFollowsSystem(): bool {
@@ -328,6 +336,7 @@ public function toJson() {
328336
'outOfOfficeFollowsSystem' => $this->getOutOfOfficeFollowsSystem(),
329337
'debug' => $this->getDebug(),
330338
'classificationEnabled' => $this->getClassificationEnabled(),
339+
'imipCreate' => $this->getImipCreate(),
331340
];
332341

333342
if (!is_null($this->getOutboundHost())) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Mail\Migration;
11+
12+
use Closure;
13+
use OCP\DB\ISchemaWrapper;
14+
use OCP\DB\Types;
15+
use OCP\Migration\IOutput;
16+
use OCP\Migration\SimpleMigrationStep;
17+
18+
class Version5007Date20251208000000 extends SimpleMigrationStep {
19+
20+
/**
21+
* @param IOutput $output
22+
* @param Closure(): ISchemaWrapper $schemaClosure
23+
* @param array $options
24+
* @return null|ISchemaWrapper
25+
*/
26+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
27+
$schema = $schemaClosure();
28+
$accountsTable = $schema->getTable('mail_accounts');
29+
if (!$accountsTable->hasColumn('imip_create')) {
30+
$accountsTable->addColumn('imip_create', Types::BOOLEAN, [
31+
'default' => false,
32+
'notnull' => false,
33+
]);
34+
}
35+
return $schema;
36+
}
37+
}

lib/Service/IMipService.php

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use OCA\Mail\Db\MessageMapper;
1717
use OCA\Mail\Exception\ServiceException;
1818
use OCA\Mail\Model\IMAPMessage;
19+
use OCA\Mail\Util\ServerVersion;
1920
use OCP\AppFramework\Db\DoesNotExistException;
2021
use OCP\Calendar\IManager;
2122
use Psr\Log\LoggerInterface;
@@ -30,6 +31,7 @@ class IMipService {
3031
private MailboxMapper $mailboxMapper;
3132
private MailManager $mailManager;
3233
private MessageMapper $messageMapper;
34+
private ServerVersion $serverVersion;
3335

3436
public function __construct(
3537
AccountService $accountService,
@@ -38,13 +40,15 @@ public function __construct(
3840
MailboxMapper $mailboxMapper,
3941
MailManager $mailManager,
4042
MessageMapper $messageMapper,
43+
ServerVersion $serverVersion,
4144
) {
4245
$this->accountService = $accountService;
4346
$this->calendarManager = $manager;
4447
$this->logger = $logger;
4548
$this->mailboxMapper = $mailboxMapper;
4649
$this->mailManager = $mailManager;
4750
$this->messageMapper = $messageMapper;
51+
$this->serverVersion = $serverVersion;
4852
}
4953

5054
public function process(): void {
@@ -115,8 +119,10 @@ public function process(): void {
115119
continue;
116120
}
117121

118-
$principalUri = 'principals/users/' . $account->getUserId();
122+
$userId = $account->getUserId();
119123
$recipient = $account->getEmail();
124+
$imipCreate = $account->getImipCreate();
125+
$systemVersion = $this->serverVersion->getMajorVersion();
120126

121127
foreach ($filteredMessages as $message) {
122128
/** @var IMAPMessage $imapMessage */
@@ -138,20 +144,35 @@ public function process(): void {
138144
try {
139145
// an IMAP message could contain more than one iMIP object
140146
foreach ($imapMessage->scheduling as $schedulingInfo) {
141-
if ($schedulingInfo['method'] === 'REQUEST') {
142-
$processed = $this->calendarManager->handleIMipRequest($principalUri, $sender, $recipient, $schedulingInfo['contents']);
143-
$message->setImipProcessed($processed);
144-
$message->setImipError(!$processed);
145-
} elseif ($schedulingInfo['method'] === 'REPLY') {
146-
$processed = $this->calendarManager->handleIMipReply($principalUri, $sender, $recipient, $schedulingInfo['contents']);
147-
$message->setImipProcessed($processed);
148-
$message->setImipError(!$processed);
149-
} elseif ($schedulingInfo['method'] === 'CANCEL') {
150-
$replyTo = $imapMessage->getReplyTo()->first()?->getEmail();
151-
$processed = $this->calendarManager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $schedulingInfo['contents']);
152-
$message->setImipProcessed($processed);
153-
$message->setImipError(!$processed);
147+
$processed = false;
148+
if ($systemVersion < 33) {
149+
$principalUri = 'principals/users/' . $userId;
150+
if ($schedulingInfo['method'] === 'REQUEST') {
151+
$processed = $this->calendarManager->handleIMipRequest($principalUri, $sender, $recipient, $schedulingInfo['contents']);
152+
} elseif ($schedulingInfo['method'] === 'REPLY') {
153+
$processed = $this->calendarManager->handleIMipReply($principalUri, $sender, $recipient, $schedulingInfo['contents']);
154+
} elseif ($schedulingInfo['method'] === 'CANCEL') {
155+
$replyTo = $imapMessage->getReplyTo()->first()?->getEmail();
156+
$processed = $this->calendarManager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $schedulingInfo['contents']);
157+
}
158+
} else {
159+
if (!method_exists($this->calendarManager, 'handleIMip')) {
160+
$this->logger->error('iMIP handling is not supported by server version installed.');
161+
continue;
162+
}
163+
$processed = $this->calendarManager->handleIMip(
164+
$userId,
165+
$schedulingInfo['contents'],
166+
[
167+
'recipient' => $recipient,
168+
'absent' => $imipCreate ? 'create' : 'ignore',
169+
'absentCreateStatus' => 'tentative',
170+
],
171+
);
154172
}
173+
174+
$message->setImipProcessed($processed);
175+
$message->setImipError(!$processed);
155176
}
156177
} catch (Throwable $e) {
157178
$this->logger->error('iMIP message processing failed', [

lib/Util/ServerVersion.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Mail\Util;
11+
12+
use OCP\ServerVersion as OCPServerVersion;
13+
14+
class ServerVersion {
15+
16+
public function __construct(
17+
private OCPServerVersion $serverVersion,
18+
) {
19+
}
20+
21+
public function getMajorVersion(): int {
22+
return $this->serverVersion->getMajorVersion();
23+
}
24+
25+
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nextcloud-mail",
3-
"version": "5.7.0-beta.2",
3+
"version": "5.7.0-beta.3",
44
"private": true,
55
"description": "Nextcloud Mail",
66
"license": "AGPL-3.0-only",

src/components/AccountSettings.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@
6262
</NcButton>
6363
</div>
6464
</AppSettingsSection>
65+
<AppSettingsSection
66+
v-if="account && systemVersion >= 33"
67+
id="calendar-settings"
68+
:name="t('mail', 'Calendar settings')">
69+
<CalendarSettings :account="account" />
70+
</AppSettingsSection>
6571
<AppSettingsSection
6672
v-if="account"
6773
id="classification"
@@ -138,6 +144,7 @@ import AccountForm from '../components/AccountForm.vue'
138144
import AliasSettings from '../components/AliasSettings.vue'
139145
import EditorSettings from '../components/EditorSettings.vue'
140146
import SignatureSettings from '../components/SignatureSettings.vue'
147+
import CalendarSettings from './CalendarSettings.vue'
141148
import CertificateSettings from './CertificateSettings.vue'
142149
import MailFilters from './mailFilter/MailFilters.vue'
143150
import OutOfOfficeForm from './OutOfOfficeForm.vue'
@@ -161,6 +168,7 @@ export default {
161168
AppSettingsDialog,
162169
AppSettingsSection,
163170
AccountDefaultsSettings,
171+
CalendarSettings,
164172
OutOfOfficeForm,
165173
CertificateSettings,
166174
TrashRetentionSettings,
@@ -188,6 +196,7 @@ export default {
188196
trapElements: [],
189197
fetchActiveSieveScript: this.account.sieveEnabled,
190198
loadingClassificationToggle: false,
199+
systemVersion: parseInt(OC.config.version.split('.')[0], 10),
191200
}
192201
},
193202

0 commit comments

Comments
 (0)