f7cloud_client/apps/mail/lib/Service/AccountService.php
root 8b6a0139db f7cloud_client
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 22:59:26 +00:00

260 lines
7.1 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2016-2024 F7cloud GmbH and F7cloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Mail\Service;
use OCA\Mail\Account;
use OCA\Mail\AppInfo\Application;
use OCA\Mail\BackgroundJob\PreviewEnhancementProcessingJob;
use OCA\Mail\BackgroundJob\QuotaJob;
use OCA\Mail\BackgroundJob\RepairSyncJob;
use OCA\Mail\BackgroundJob\SyncJob;
use OCA\Mail\BackgroundJob\TrainImportanceClassifierJob;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\IJobList;
use OCP\IConfig;
use function array_map;
class AccountService {
/** @var MailAccountMapper */
private $mapper;
/**
* Cache accounts for multiple calls to 'findByUserId'
*
* @var array<string, list<Account>>
*/
private array $accounts = [];
/** @var AliasesService */
private $aliasesService;
/** @var IJobList */
private $jobList;
/** @var IMAPClientFactory */
private $imapClientFactory;
public function __construct(
MailAccountMapper $mapper,
AliasesService $aliasesService,
IJobList $jobList,
IMAPClientFactory $imapClientFactory,
private readonly IConfig $config,
private readonly ITimeFactory $timeFactory,
) {
$this->mapper = $mapper;
$this->aliasesService = $aliasesService;
$this->jobList = $jobList;
$this->imapClientFactory = $imapClientFactory;
}
/**
* @param string $currentUserId
* @return list<Account>
*/
public function findByUserId(string $currentUserId): array {
if (!isset($this->accounts[$currentUserId])) {
$this->accounts[$currentUserId] = array_map(static fn ($a) => new Account($a), $this->mapper->findByUserId($currentUserId));
}
return $this->accounts[$currentUserId];
}
/**
* @param int $id
*
* @return Account
* @throws DoesNotExistException
*/
public function findById(int $id): Account {
return new Account($this->mapper->findById($id));
}
/**
* Finds a mail account by user id and mail address
*
* @since 4.0.0
*
* @param string $userId system user id
* @param string $address mail address (e.g. test@example.com)
*
* @return Account[]
*/
public function findByUserIdAndAddress(string $userId, string $address): array {
// evaluate if cached accounts collection already exists
if (isset($this->accounts[$userId])) {
// initialize temporary collection
$list = [];
// iterate through accounts and find accounts matching mail address
foreach ($this->accounts[$userId] as $account) {
if ($account->getEmail() === $address) {
$list[] = $account;
}
}
return $list;
}
// if cached accounts collection did not exist retrieve account details directly from the data store
return array_map(static fn ($a) => new Account($a), $this->mapper->findByUserIdAndAddress($userId, $address));
}
/**
* @param string $userId
* @param int $id
*
* @return Account
* @throws ClientException
*/
public function find(string $userId, int $id): Account {
if (isset($this->accounts[$userId])) {
foreach ($this->accounts[$userId] as $account) {
if ($account->getId() === $id) {
return $account;
}
}
throw new ClientException("Account $id does not exist or you don\'t have permission to access it");
}
try {
return new Account($this->mapper->find($userId, $id));
} catch (DoesNotExistException $e) {
throw new ClientException("Account $id does not exist or you don\'t have permission to access it");
}
}
/**
* @param int $accountId
*
* @throws ClientException
*/
public function delete(string $currentUserId, int $accountId): void {
try {
$mailAccount = $this->mapper->find($currentUserId, $accountId);
} catch (DoesNotExistException $e) {
throw new ClientException("Account $accountId does not exist", 0, $e);
}
$this->aliasesService->deleteAll($accountId);
$this->mapper->delete($mailAccount);
}
/**
* @param int $accountId
*
* @throws ClientException
*/
public function deleteByAccountId(int $accountId): void {
try {
$mailAccount = $this->mapper->findById($accountId);
} catch (DoesNotExistException $e) {
throw new ClientException("Account $accountId does not exist", 0, $e);
}
$this->aliasesService->deleteAll($accountId);
$this->mapper->delete($mailAccount);
}
/**
* @param MailAccount $newAccount
* @return MailAccount
*/
public function save(MailAccount $newAccount): MailAccount {
$newAccount = $this->mapper->save($newAccount);
// Insert background jobs for this account
$this->scheduleBackgroundJobs($newAccount->getId());
// Set initial heartbeat
$this->config->setUserValue(
$newAccount->getUserId(),
Application::APP_ID,
'ui-heartbeat',
(string)$this->timeFactory->getTime(),
);
return $newAccount;
}
public function update(MailAccount $account): MailAccount {
return $this->mapper->update($account);
}
/**
* @param int $id
* @param string $uid
* @param string|null $signature
*
* @throws ClientException
* @throws ServiceException
*/
public function updateSignature(int $id, string $uid, ?string $signature = null): void {
$account = $this->find($uid, $id);
$mailAccount = $account->getMailAccount();
$mailAccount->setSignature($signature);
$this->mapper->save($mailAccount);
}
/**
* @return MailAccount[]
*/
public function getAllAcounts(): array {
return $this->mapper->getAllAccounts();
}
/**
* @param string $currentUserId
* @param int $accountId
* @return bool
*/
public function testAccountConnection(string $currentUserId, int $accountId) :bool {
$account = $this->find($currentUserId, $accountId);
try {
$client = $this->imapClientFactory->getClient($account);
$client->close();
return true;
} catch (\Throwable $e) {
return false;
}
}
public function scheduleBackgroundJobs(int $accountId): void {
$arguments = ['accountId' => $accountId];
$now = $this->timeFactory->getTime();
$this->scheduleBackgroundJob(SyncJob::class, $now, $arguments);
$this->scheduleBackgroundJob(TrainImportanceClassifierJob::class, $now, $arguments);
$this->scheduleBackgroundJob(PreviewEnhancementProcessingJob::class, $now, $arguments);
$this->scheduleBackgroundJob(QuotaJob::class, $now, $arguments);
$inThreeDays = $now + (3 * 86400);
$this->scheduleBackgroundJob(RepairSyncJob::class, $inThreeDays, $arguments);
}
/**
* IJobList::add() / IJobList::scheduleAfter() resets last_run, last_check, and reserved_at if the job exists.
* To avoid unwanted resets (e.g. when enabling debug mode), we check first if the job is already present.
*
* @param class-string<IJob> $job
* @param mixed $argument The serializable argument to be passed to $job->run() when the job is executed
*/
private function scheduleBackgroundJob(string $job, int $runAfter, mixed $argument = null): void {
if (!$this->jobList->has($job, $argument)) {
$this->jobList->scheduleAfter($job, $runAfter, $argument);
}
}
}