Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: apply personal out-of-office data to the auto responder #9077

Merged
merged 1 commit into from
Nov 24, 2023
Merged
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 @@ -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);
st3iny marked this conversation as resolved.
Show resolved Hide resolved
} 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
Loading