-
Notifications
You must be signed in to change notification settings - Fork 259
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9651 from nextcloud/feature/request-803
feature: mail provider backend
- Loading branch information
Showing
9 changed files
with
1,183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,6 +83,27 @@ public function findByUserId(string $userId): array { | |
return $this->findEntities($query); | ||
} | ||
|
||
/** | ||
* Finds a mail account(s) by user id and mail address | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param string $userId system user id | ||
* @param string $address mail address (e.g. [email protected]) | ||
* | ||
* @return MailAccount[] | ||
*/ | ||
public function findByUserIdAndAddress(string $userId, string $address): array { | ||
$qb = $this->db->getQueryBuilder(); | ||
$query = $qb | ||
->select('*') | ||
->from($this->getTableName()) | ||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))) | ||
->andWhere($qb->expr()->eq('email', $qb->createNamedParameter($address))); | ||
|
||
return $this->findEntities($query); | ||
} | ||
|
||
/** | ||
* @throws DoesNotExistException | ||
* @throws MultipleObjectsReturnedException | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
*/ | ||
namespace OCA\Mail\Provider\Command; | ||
|
||
use OCA\Mail\Db\LocalAttachment; | ||
use OCA\Mail\Db\LocalMessage; | ||
use OCA\Mail\Exception\ClientException; | ||
use OCA\Mail\Exception\UploadException; | ||
use OCA\Mail\Service\AccountService; | ||
use OCA\Mail\Service\Attachment\AttachmentService; | ||
use OCA\Mail\Service\OutboxService; | ||
use OCP\AppFramework\Utility\ITimeFactory; | ||
use OCP\Mail\Provider\Exception\SendException; | ||
use OCP\Mail\Provider\IAddress; | ||
use OCP\Mail\Provider\IMessage; | ||
|
||
class MessageSend { | ||
|
||
public function __construct( | ||
protected ITimeFactory $time, | ||
protected AccountService $accountService, | ||
protected OutboxService $outboxService, | ||
protected AttachmentService $attachmentService | ||
) { | ||
} | ||
|
||
/** | ||
* Performs send operation | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param string $userId system user id | ||
* @param string $serviceId mail account id | ||
* @param IMessage $message mail message object with all required parameters to send a message | ||
* @param array $options array of options reserved for future use | ||
* | ||
* @return LocalMessage | ||
* | ||
* @throws SendException on failure, check message for reason | ||
* | ||
*/ | ||
public function perform(string $userId, string $serviceId, IMessage $message, array $options = []): LocalMessage { | ||
// validate that at least one To address is present | ||
if (count($message->getTo()) === 0) { | ||
throw new SendException('Invalid Message Parameter: MUST contain at least one TO address with a valid address'); | ||
} | ||
// validate that all To, CC and BCC have email address | ||
$entries = array_merge($message->getTo(), $message->getCc(), $message->getBcc()); | ||
array_walk($entries, function ($entry) { | ||
if (empty($entry->getAddress())) { | ||
throw new SendException('Invalid Message Parameter: All TO, CC and BCC addresses MUST contain at least an email address'); | ||
} | ||
}); | ||
// validate that all attachments have a name, type, and contents | ||
$entries = $message->getAttachments(); | ||
array_walk($entries, function ($entry) { | ||
if (empty($entry->getName()) || empty($entry->getType()) || empty($entry->getContents())) { | ||
throw new SendException('Invalid Attachment Parameter: MUST contain values for Name, Type and Contents'); | ||
} | ||
}); | ||
// retrieve user mail account details | ||
try { | ||
$account = $this->accountService->find($userId, (int)$serviceId); | ||
} catch (ClientException $e) { | ||
throw new SendException('Error: occurred while retrieving mail account details', 0, $e); | ||
} | ||
// convert mail provider message to mail app message | ||
$localMessage = new LocalMessage(); | ||
$localMessage->setType($localMessage::TYPE_OUTGOING); | ||
$localMessage->setAccountId($account->getId()); | ||
$localMessage->setSubject((string)$message->getSubject()); | ||
$localMessage->setBody((string)$message->getBody()); | ||
// disabled due to issues caused by opening these messages in gui | ||
//$localMessage->setEditorBody($message->getBody()); | ||
$localMessage->setHtml(true); | ||
$localMessage->setSendAt($this->time->getTime()); | ||
// convert mail provider addresses to recipient addresses | ||
$to = $this->convertAddressArray($message->getTo()); | ||
$cc = $this->convertAddressArray($message->getCc()); | ||
$bcc = $this->convertAddressArray($message->getBcc()); | ||
// save attachments | ||
$attachments = []; | ||
try { | ||
foreach ($message->getAttachments() as $entry) { | ||
$attachments[] = $this->attachmentService->addFileFromString( | ||
$userId, | ||
(string)$entry->getName(), | ||
(string)$entry->getType(), | ||
(string)$entry->getContents() | ||
); | ||
} | ||
} catch (UploadException $e) { | ||
$this->purgeSavedAttachments($attachments); | ||
throw new SendException('Error: occurred while saving mail message attachment', 0, $e); | ||
} | ||
// save message | ||
$localMessage = $this->outboxService->saveMessage( | ||
$account, | ||
$localMessage, | ||
$to, | ||
$cc, | ||
$bcc, | ||
array_map(static fn (LocalAttachment $attachment) => $attachment->jsonSerialize(), $attachments) | ||
); | ||
// send message | ||
try { | ||
$localMessage = $this->outboxService->sendMessage($localMessage, $account); | ||
} catch (\Throwable $e) { | ||
throw new SendException('Error: occurred while sending mail message', 0, $e); | ||
} | ||
|
||
return $localMessage; | ||
} | ||
|
||
/** | ||
* Converts IAddress objects collection to plain array | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param array<int,IAddress> $addresses collection of IAddress objects | ||
* | ||
* @return array<int, array{email: string, label?: string}> collection of addresses and labels | ||
*/ | ||
protected function convertAddressArray(array $addresses): array { | ||
return array_map(static function (IAddress $address) { | ||
return !empty($address->getLabel()) | ||
? ['email' => (string)$address->getAddress(), 'label' => (string)$address->getLabel()] | ||
: ['email' => (string)$address->getAddress()]; | ||
}, $addresses); | ||
} | ||
|
||
/** | ||
* Removes attachments from data store | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param array<int, LocalAttachment> $attachments collection of local attachment objects | ||
*/ | ||
protected function purgeSavedAttachments(array $attachments): void { | ||
foreach ($attachments as $attachment) { | ||
$this->attachmentService->deleteAttachment($attachment->getUserId(), $attachment->getId()); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
*/ | ||
namespace OCA\Mail\Provider; | ||
|
||
use OCA\Mail\Account; | ||
use OCA\Mail\Exception\ClientException; | ||
use OCA\Mail\Service\AccountService; | ||
use OCP\IL10N; | ||
use OCP\Mail\Provider\Address as MailAddress; | ||
use OCP\Mail\Provider\IProvider; | ||
use OCP\Mail\Provider\IService; | ||
use Psr\Container\ContainerInterface; | ||
use Psr\Log\LoggerInterface; | ||
|
||
class MailProvider implements IProvider { | ||
|
||
public function __construct( | ||
protected ContainerInterface $container, | ||
protected AccountService $accountService, | ||
protected LoggerInterface $logger, | ||
protected IL10N $l10n | ||
) { | ||
} | ||
|
||
/** | ||
* Arbitrary unique text string identifying this provider | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @return string id of this provider (e.g. UUID or 'IMAP/SMTP' or anything else) | ||
*/ | ||
public function id(): string { | ||
return 'mail-application'; | ||
} | ||
|
||
/** | ||
* Localized human friendly name of this provider | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @return string label/name of this provider (e.g. Plain Old IMAP/SMTP) | ||
*/ | ||
public function label(): string { | ||
return $this->l10n->t('Mail Application'); | ||
} | ||
|
||
/** | ||
* Determine if any services are configured for a specific user | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param string $userId system user id | ||
* | ||
* @return bool true if any services are configure for the user | ||
*/ | ||
public function hasServices(string $userId): bool { | ||
return (count($this->listServices($userId)) > 0); | ||
} | ||
|
||
/** | ||
* Retrieve collection of services for a specific user | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param string $userId system user id | ||
* | ||
* @return array<string,IService> collection of service id and object ['1' => IServiceObject] | ||
*/ | ||
public function listServices(string $userId): array { | ||
// retrieve service(s) details from data store | ||
$accounts = $this->accountService->findByUserId($userId); | ||
// construct temporary collection | ||
$services = []; | ||
// add services to collection | ||
foreach ($accounts as $entry) { | ||
$services[(string)$entry->getId()] = $this->serviceFromAccount($userId, $entry); | ||
} | ||
// return list of services for user | ||
return $services; | ||
} | ||
|
||
/** | ||
* Retrieve a service with a specific id | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param string $userId system user id | ||
* @param string $serviceId mail account id | ||
* | ||
* @return IService|null returns service object or null if none found | ||
* | ||
*/ | ||
public function findServiceById(string $userId, string $serviceId): IService|null { | ||
// determine if a valid user and service id was submitted | ||
if (empty($userId) && !ctype_digit($serviceId)) { | ||
return null; | ||
} | ||
// retrieve service details from data store | ||
try { | ||
$account = $this->accountService->find($userId, (int)$serviceId); | ||
} catch(ClientException $e) { | ||
$this->logger->error('Error occurred while retrieving mail account details', [ 'exception' => $e ]); | ||
return null; | ||
} | ||
// return mail service object | ||
return $this->serviceFromAccount($userId, $account); | ||
} | ||
|
||
/** | ||
* Retrieve a service for a specific mail address | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param string $userId system user id | ||
* @param string $address mail address (e.g. [email protected]) | ||
* | ||
* @return IService|null returns service object or null if none found | ||
*/ | ||
public function findServiceByAddress(string $userId, string $address): IService|null { | ||
// retrieve service details from data store | ||
$accounts = $this->accountService->findByUserIdAndAddress($userId, $address); | ||
// evaluate if service details where found | ||
if (count($accounts) > 0) { | ||
// return mail service object | ||
return $this->serviceFromAccount($userId, $accounts[0]); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Construct a new fresh service object | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @return IService fresh service object | ||
*/ | ||
public function initiateService(): IService { | ||
return new MailService($this->container); | ||
} | ||
|
||
/** | ||
* Construct a service object from a mail account | ||
* | ||
* @since 4.0.0 | ||
* | ||
* @param string $userId system user id | ||
* @param Account $account mail account | ||
* | ||
* @return IService service object | ||
*/ | ||
protected function serviceFromAccount(string $userId, Account $account): IService { | ||
// extract values | ||
$serviceId = (string)$account->getId(); | ||
$serviceLabel = $account->getName(); | ||
$serviceAddress = new MailAddress($account->getEmail(), $account->getName()); | ||
// return mail service object | ||
return new MailService($this->container, $userId, $serviceId, $serviceLabel, $serviceAddress); | ||
} | ||
|
||
} |
Oops, something went wrong.