441 строка
14 KiB
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();
|
|
}
|
|
}
|