mail/lib/Controller/PageController.php

441 строка
14 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2014-2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Mail\Controller;
use OCA\Mail\AppInfo\Application;
use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Contracts\IUserPreferences;
use OCA\Mail\Db\SmimeCertificate;
use OCA\Mail\Db\TagMapper;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AiIntegrations\AiIntegrationsService;
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\Classification\ClassificationSettingsService;
use OCA\Mail\Service\InternalAddressService;
use OCA\Mail\Service\OutboxService;
use OCA\Mail\Service\SmimeService;
use OCA\Viewer\Event\LoadViewer;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\Authentication\Exceptions\CredentialsUnavailableException;
use OCP\Authentication\Exceptions\PasswordUnavailableException;
use OCP\Authentication\LoginCredentials\IStore as ICredentialStore;
use OCP\Collaboration\Reference\RenderReferenceEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\TextProcessing\FreePromptTaskType;
use OCP\TextProcessing\SummaryTaskType;
use OCP\User\IAvailabilityCoordinator;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Throwable;
use function class_exists;
use function http_build_query;
use function json_decode;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class PageController extends Controller {
private IURLGenerator $urlGenerator;
private IConfig $config;
private AccountService $accountService;
private AliasesService $aliasesService;
private ?string $currentUserId;
private IUserSession $userSession;
private IUserPreferences $preferences;
private IMailManager $mailManager;
private TagMapper $tagMapper;
private IInitialState $initialStateService;
private LoggerInterface $logger;
private OutboxService $outboxService;
private IEventDispatcher $dispatcher;
private ICredentialstore $credentialStore;
private SmimeService $smimeService;
private AiIntegrationsService $aiIntegrationsService;
private IUserManager $userManager;
private ?IAvailabilityCoordinator $availabilityCoordinator;
private ClassificationSettingsService $classificationSettingsService;
private InternalAddressService $internalAddressService;
public function __construct(string $appName,
IRequest $request,
IURLGenerator $urlGenerator,
IConfig $config,
AccountService $accountService,
AliasesService $aliasesService,
?string $UserId,
IUserSession $userSession,
IUserPreferences $preferences,
IMailManager $mailManager,
TagMapper $tagMapper,
IInitialState $initialStateService,
LoggerInterface $logger,
OutboxService $outboxService,
IEventDispatcher $dispatcher,
ICredentialStore $credentialStore,
SmimeService $smimeService,
AiIntegrationsService $aiIntegrationsService,
IUserManager $userManager,
ContainerInterface $container,
ClassificationSettingsService $classificationSettingsService,
InternalAddressService $internalAddressService, ) {
parent::__construct($appName, $request);
$this->urlGenerator = $urlGenerator;
$this->config = $config;
$this->accountService = $accountService;
$this->aliasesService = $aliasesService;
$this->currentUserId = $UserId;
$this->userSession = $userSession;
$this->preferences = $preferences;
$this->mailManager = $mailManager;
$this->tagMapper = $tagMapper;
$this->initialStateService = $initialStateService;
$this->logger = $logger;
$this->outboxService = $outboxService;
$this->dispatcher = $dispatcher;
$this->credentialStore = $credentialStore;
$this->smimeService = $smimeService;
$this->aiIntegrationsService = $aiIntegrationsService;
$this->userManager = $userManager;
$this->classificationSettingsService = $classificationSettingsService;
$this->internalAddressService = $internalAddressService;
// TODO: inject directly if support for nextcloud < 28 is dropped
try {
$this->availabilityCoordinator = $container->get(IAvailabilityCoordinator::class);
} catch (ContainerExceptionInterface) {
$this->availabilityCoordinator = null;
}
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse renders the index page
*/
public function index(): TemplateResponse {
if (class_exists(LoadViewer::class)) {
$this->dispatcher->dispatchTyped(new LoadViewer());
}
$this->initialStateService->provideInitialState(
'debug',
$this->config->getSystemValue('debug', false)
);
$this->initialStateService->provideInitialState(
'ncVersion',
$this->config->getSystemValue('version', '0.0.0')
);
$mailAccounts = $this->accountService->findByUserId($this->currentUserId);
$accountsJson = [];
foreach ($mailAccounts as $mailAccount) {
$json = $mailAccount->jsonSerialize();
$json['aliases'] = $this->aliasesService->findAll($mailAccount->getId(),
$this->currentUserId);
try {
$mailboxes = $this->mailManager->getMailboxes($mailAccount);
$json['mailboxes'] = $mailboxes;
} catch (Throwable $ex) {
$this->logger->critical('Could not load account mailboxes: ' . $ex->getMessage(), [
'exception' => $ex,
]);
$json['mailboxes'] = [];
$json['error'] = true;
}
$accountsJson[] = $json;
}
$this->initialStateService->provideInitialState(
'accounts',
$accountsJson
);
$this->initialStateService->provideInitialState(
'account-settings',
json_decode($this->preferences->getPreference($this->currentUserId, 'account-settings', '[]'), true, 512, JSON_THROW_ON_ERROR) ?? []
);
$this->initialStateService->provideInitialState(
'tags',
$this->tagMapper->getAllTagsForUser($this->currentUserId)
);
$this->initialStateService->provideInitialState(
'internal-addresses-list',
$this->internalAddressService->getInternalAddresses($this->currentUserId)
);
$this->initialStateService->provideInitialState(
'internal-addresses',
$this->preferences->getPreference($this->currentUserId, 'internal-addresses', false)
);
$this->initialStateService->provideInitialState(
'sort-order',
$this->preferences->getPreference($this->currentUserId, 'sort-order', 'newest')
);
try {
$password = $this->credentialStore->getLoginCredentials()->getPassword();
$passwordIsUnavailable = $password === null || $password === '';
} catch (CredentialsUnavailableException|PasswordUnavailableException $e) {
$passwordIsUnavailable = true;
}
$this->initialStateService->provideInitialState(
'password-is-unavailable',
$passwordIsUnavailable,
);
$user = $this->userSession->getUser();
$response = new TemplateResponse($this->appName, 'index',
[
'attachment-size-limit' => $this->config->getSystemValue('app.mail.attachment-size-limit', 0),
'app-version' => $this->config->getAppValue('mail', 'installed_version'),
'external-avatars' => $this->preferences->getPreference($this->currentUserId, 'external-avatars', 'true'),
'layout-mode' => $this->preferences->getPreference($this->currentUserId, 'layout-mode', 'vertical-split'),
'reply-mode' => $this->preferences->getPreference($this->currentUserId, 'reply-mode', 'top'),
'collect-data' => $this->preferences->getPreference($this->currentUserId, 'collect-data', 'true'),
'search-priority-body' => $this->preferences->getPreference($this->currentUserId, 'search-priority-body', 'false'),
'start-mailbox-id' => $this->preferences->getPreference($this->currentUserId, 'start-mailbox-id'),
'tag-classified-messages' => $this->classificationSettingsService->isClassificationEnabled($this->currentUserId) ? 'true' : 'false',
'follow-up-reminders' => $this->preferences->getPreference($this->currentUserId, 'follow-up-reminders', 'true'),
]);
$this->initialStateService->provideInitialState(
'prefill_displayName',
$this->userManager->getDisplayName($this->currentUserId),
);
$this->initialStateService->provideInitialState(
'prefill_email',
$this->config->getUserValue($user->getUID(), 'settings', 'email', '')
);
$this->initialStateService->provideInitialState(
'outbox-messages',
$this->outboxService->getMessages($user->getUID())
);
$googleOauthclientId = $this->config->getAppValue(Application::APP_ID, 'google_oauth_client_id');
if (!empty($googleOauthclientId)) {
$this->initialStateService->provideInitialState(
'google-oauth-url',
'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query([
'client_id' => $googleOauthclientId,
'redirect_uri' => $this->urlGenerator->linkToRouteAbsolute('mail.googleIntegration.oauthRedirect'),
'response_type' => 'code',
'prompt' => 'consent',
'state' => '_accountId_', // Replaced by frontend
'scope' => 'https://mail.google.com/',
'access_type' => 'offline',
'login_hint' => '_email_', // Replaced by frontend
]),
);
}
$microsoftOauthClientId = $this->config->getAppValue(Application::APP_ID, 'microsoft_oauth_client_id');
$microsoftOauthTenantId = $this->config->getAppValue(Application::APP_ID, 'microsoft_oauth_tenant_id', 'common');
if (!empty($microsoftOauthClientId) && !empty($microsoftOauthTenantId)) {
$this->initialStateService->provideInitialState(
'microsoft-oauth-url',
"https://login.microsoftonline.com/$microsoftOauthTenantId/oauth2/v2.0/authorize?" . http_build_query([
'client_id' => $microsoftOauthClientId,
'redirect_uri' => $this->urlGenerator->linkToRouteAbsolute('mail.microsoftIntegration.oauthRedirect'),
'response_type' => 'code',
'response_mode' => 'query',
'prompt' => 'consent',
'state' => '_accountId_', // Replaced by frontend
'scope' => 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send',
'access_type' => 'offline',
'login_hint' => '_email_', // Replaced by frontend
]),
);
}
// Disable snooze and scheduled send in frontend if ajax cron is used because it is unreliable
$cronMode = $this->config->getAppValue('core', 'backgroundjobs_mode', 'ajax');
$this->initialStateService->provideInitialState(
'disable-scheduled-send',
$cronMode === 'ajax',
);
$this->initialStateService->provideInitialState(
'disable-snooze',
$cronMode === 'ajax',
);
$this->initialStateService->provideInitialState(
'allow-new-accounts',
$this->config->getAppValue('mail', 'allow_new_mail_accounts', 'yes') === 'yes'
);
$this->initialStateService->provideInitialState(
'llm_summaries_available',
$this->aiIntegrationsService->isLlmProcessingEnabled() && $this->aiIntegrationsService->isLlmAvailable(SummaryTaskType::class)
);
$this->initialStateService->provideInitialState(
'llm_freeprompt_available',
$this->aiIntegrationsService->isLlmProcessingEnabled() && $this->aiIntegrationsService->isLlmAvailable(FreePromptTaskType::class)
);
$this->initialStateService->provideInitialState(
'llm_followup_available',
$this->aiIntegrationsService->isLlmProcessingEnabled()
&& $this->aiIntegrationsService->isLlmAvailable(FreePromptTaskType::class)
);
$this->initialStateService->provideInitialState(
'smime-certificates',
array_map(
function (SmimeCertificate $certificate) {
return $this->smimeService->enrichCertificate($certificate);
},
$this->smimeService->findAllCertificates($user->getUID()),
),
);
if ($this->availabilityCoordinator !== null) {
$this->initialStateService->provideInitialState(
'enable-system-out-of-office',
$this->availabilityCoordinator->isEnabled(),
);
}
$csp = new ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
$response->setContentSecurityPolicy($csp);
$this->dispatcher->dispatchTyped(new RenderReferenceEvent());
return $response;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function setup(): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function mailbox(int $id): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function thread(int $mailboxId, int $id): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function filteredThread(string $filter, int $mailboxId, int $id): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function outbox(): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function outboxMessage(int $messageId): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function draft(int $mailboxId, int $draftId): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function filteredDraft(string $filter, int $mailboxId, int $draftId): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @param string $uri
*
* @return RedirectResponse
*/
public function compose(string $uri): RedirectResponse {
$parts = parse_url($uri);
$params = ['to' => $parts['path']];
if (isset($parts['query'])) {
$parts = explode('&', $parts['query']);
foreach ($parts as $part) {
$pair = explode('=', $part, 2);
$params[strtolower($pair[0])] = urldecode($pair[1]);
}
}
array_walk($params,
static function (&$value, $key) {
$value = "$key=" . urlencode($value);
});
$name = '?' . implode('&', $params);
$baseUrl = $this->urlGenerator->linkToRoute('mail.page.mailto');
return new RedirectResponse($baseUrl . $name);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function mailto(): TemplateResponse {
return $this->index();
}
}