Skip to content

Commit

Permalink
feat: apply personal out-of-office data to the auto responder
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
  • Loading branch information
st3iny authored and ChristophWurst committed Nov 24, 2023
1 parent 12b8097 commit 3cbdf1f
Show file tree
Hide file tree
Showing 37 changed files with 1,968 additions and 615 deletions.
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,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>3.6.0-alpha.1</version>
<version>3.6.0-alpha.2</version>
<licence>agpl</licence>
<author>Christoph Wurst</author>
<author homepage="https://github.com/nextcloud/groupware">Nextcloud Groupware Team</author>
Expand Down
16 changes: 15 additions & 1 deletion appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,21 @@
'url' => '/api/drafts/move/{id}',
'verb' => 'POST',
],

[
'name' => 'outOfOffice#getState',
'url' => '/api/out-of-office/{accountId}',
'verb' => 'GET',
],
[
'name' => 'outOfOffice#update',
'url' => '/api/out-of-office/{accountId}',
'verb' => 'POST',
],
[
'name' => 'outOfOffice#followSystem',
'url' => '/api/out-of-office/{accountId}/follow-system',
'verb' => 'POST',
],
],
'resources' => [
'accounts' => ['url' => '/api/accounts'],
Expand Down
10 changes: 10 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
use OCA\Mail\Listener\MoveJunkListener;
use OCA\Mail\Listener\NewMessageClassificationListener;
use OCA\Mail\Listener\OauthTokenRefreshListener;
use OCA\Mail\Listener\OutOfOfficeListener;
use OCA\Mail\Listener\SaveSentMessageListener;
use OCA\Mail\Listener\SpamReportListener;
use OCA\Mail\Listener\UserDeletedListener;
Expand All @@ -88,6 +89,8 @@
use OCP\Dashboard\IAPIWidgetV2;
use OCP\IServerContainer;
use OCP\Search\IFilteringProvider;
use OCP\User\Events\OutOfOfficeEndedEvent;
use OCP\User\Events\OutOfOfficeStartedEvent;
use OCP\User\Events\UserDeletedEvent;
use OCP\Util;
use Psr\Container\ContainerInterface;
Expand Down Expand Up @@ -142,6 +145,13 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(SynchronizationEvent::class, AccountSynchronizedThreadUpdaterListener::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);

// TODO: drop condition if nextcloud < 28 is not supported anymore
if (class_exists(OutOfOfficeStartedEvent::class)
&& class_exists(OutOfOfficeEndedEvent::class)) {
$context->registerEventListener(OutOfOfficeStartedEvent::class, OutOfOfficeListener::class);
$context->registerEventListener(OutOfOfficeEndedEvent::class, OutOfOfficeListener::class);
}

$context->registerMiddleWare(ErrorMiddleware::class);
$context->registerMiddleWare(ProvisioningMiddleware::class);

Expand Down
178 changes: 178 additions & 0 deletions lib/Controller/OutOfOfficeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Mail\Controller;

use DateTimeImmutable;
use OCA\Mail\AppInfo\Application;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Http\JsonResponse;
use OCA\Mail\Http\TrapError;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\OutOfOffice\OutOfOfficeState;
use OCA\Mail\Service\OutOfOfficeService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\User\IAvailabilityCoordinator;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;

class OutOfOfficeController extends Controller {
private ?IAvailabilityCoordinator $availabilityCoordinator;

public function __construct(
IRequest $request,
ContainerInterface $container,
private IUserSession $userSession,
private AccountService $accountService,
private OutOfOfficeService $outOfOfficeService,
private ITimeFactory $timeFactory,
) {
parent::__construct(Application::APP_ID, $request);

try {
$this->availabilityCoordinator = $container->get(IAvailabilityCoordinator::class);
} catch (ContainerExceptionInterface) {
$this->availabilityCoordinator = null;
}
}

/**
* @NoAdminRequired
* @NoCSRFRequired
*/
#[TrapError]
public function getState(int $accountId): JsonResponse {
$user = $this->userSession->getUser();
if ($user === null) {
return JsonResponse::fail([], Http::STATUS_FORBIDDEN);
}

$account = $this->accountService->findById($accountId);
if ($account->getUserId() !== $user->getUID()) {
return JsonResponse::fail([], Http::STATUS_NOT_FOUND);
}

$state = $this->outOfOfficeService->parseState($account->getMailAccount());
return JsonResponse::success($state);
}

/**
* @NoAdminRequired
*/
#[TrapError]
public function followSystem(int $accountId) {
if ($this->availabilityCoordinator === null) {
return JsonResponse::fail([], Http::STATUS_NOT_IMPLEMENTED);
}

$user = $this->userSession->getUser();
if ($user === null) {
return JsonResponse::fail([], Http::STATUS_FORBIDDEN);
}

$account = $this->accountService->findById($accountId);
if ($account->getUserId() !== $user->getUID()) {
return JsonResponse::fail([], Http::STATUS_NOT_FOUND);
}

$mailAccount = $account->getMailAccount();
if (!$mailAccount->getOutOfOfficeFollowsSystem()) {
$mailAccount->setOutOfOfficeFollowsSystem(true);
$this->accountService->update($mailAccount);
}

$state = null;
$now = $this->timeFactory->getTime();
$currentOutOfOfficeData = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
if ($currentOutOfOfficeData !== null
&& $currentOutOfOfficeData->getStartDate() <= $now
&& $currentOutOfOfficeData->getEndDate() > $now) {
// In the middle of a running absence => enable auto responder
$state = new OutOfOfficeState(
true,
new DateTimeImmutable("@" . $currentOutOfOfficeData->getStartDate()),
new DateTimeImmutable("@" . $currentOutOfOfficeData->getEndDate()),
$currentOutOfOfficeData->getShortMessage(),
$currentOutOfOfficeData->getMessage(),
);
$this->outOfOfficeService->update($mailAccount, $state);
} else {
// Absence has not yet started or has already ended => disable auto responder
$this->outOfOfficeService->disable($mailAccount);
}

return JsonResponse::success($state);
}

/**
* @NoAdminRequired
*/
#[TrapError]
public function update(
int $accountId,
bool $enabled,
?string $start,
?string $end,
string $subject,
string $message,
): JsonResponse {
$user = $this->userSession->getUser();
if ($user === null) {
return JsonResponse::fail([], Http::STATUS_FORBIDDEN);
}

$account = $this->accountService->findById($accountId);
if ($account->getUserId() !== $user->getUID()) {
return JsonResponse::fail([], Http::STATUS_NOT_FOUND);
}

if ($enabled && $start === null) {
throw new ServiceException("Missing start date");
}

$mailAccount = $account->getMailAccount();
if ($mailAccount->getOutOfOfficeFollowsSystem()) {
$mailAccount->setOutOfOfficeFollowsSystem(false);
$this->accountService->update($mailAccount);
}

$state = new OutOfOfficeState(
$enabled,
$start ? new DateTimeImmutable($start) : null,
$end ? new DateTimeImmutable($end) : null,
$subject,
$message,
);
$this->outOfOfficeService->update($mailAccount, $state);

$newState = $this->outOfOfficeService->parseState($mailAccount);
return JsonResponse::success($newState);
}
}
22 changes: 20 additions & 2 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\User\IAvailabilityCoordinator;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Throwable;
use function class_exists;
Expand All @@ -77,6 +80,7 @@ class PageController extends Controller {
private SmimeService $smimeService;
private AiIntegrationsService $aiIntegrationsService;
private IUserManager $userManager;
private ?IAvailabilityCoordinator $availabilityCoordinator;

public function __construct(string $appName,
IRequest $request,
Expand All @@ -96,7 +100,8 @@ public function __construct(string $appName,
ICredentialStore $credentialStore,
SmimeService $smimeService,
AiIntegrationsService $aiIntegrationsService,
IUserManager $userManager, ) {
IUserManager $userManager,
ContainerInterface $container) {
parent::__construct($appName, $request);

$this->urlGenerator = $urlGenerator;
Expand All @@ -116,6 +121,12 @@ public function __construct(string $appName,
$this->smimeService = $smimeService;
$this->aiIntegrationsService = $aiIntegrationsService;
$this->userManager = $userManager;

try {
$this->availabilityCoordinator = $container->get(IAvailabilityCoordinator::class);
} catch (ContainerExceptionInterface $e) {
$this->availabilityCoordinator = null;
}
}

/**
Expand Down Expand Up @@ -174,7 +185,7 @@ public function index(): TemplateResponse {
'sort-order',
$this->preferences->getPreference($this->currentUserId, 'sort-order', 'newest')
);

try {
$password = $this->credentialStore->getLoginCredentials()->getPassword();
$passwordIsUnavailable = $password === null || $password === '';
Expand Down Expand Up @@ -277,6 +288,13 @@ function (SmimeCertificate $certificate) {
),
);

if ($this->availabilityCoordinator !== null) {
$this->initialStateService->provideInitialState(
'enable-system-out-of-office',
$this->availabilityCoordinator->isEnabled(),
);
}

$csp = new ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
$response->setContentSecurityPolicy($csp);
Expand Down
Loading

0 comments on commit 3cbdf1f

Please sign in to comment.