-
Notifications
You must be signed in to change notification settings - Fork 296
feature: mail provider backend #9651
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
|
|
||
| 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 | ||
SebastianKrupinski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * | ||
| * @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( | ||
SebastianKrupinski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| $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); | ||
kesselb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } 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()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getUserId and getId only work when you pass an LocalAttachment object. You are calling jsonSerialize in Line 95, and therefore we have a plain array here. I prefer working with real objects rather than plain arrays and therefore recommend removing jsonSerialize in Line 95, add the phpdoc back for purgeSavedAttachmends, and use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. I fixed the oversight with the LocalAttachment. But i left he phpdoc as paslm is still not happy and I don't want miss another deadline because of paslm.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
|
|
||
| } | ||
| 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); | ||
| } | ||
|
|
||
| } |
Uh oh!
There was an error while loading. Please reload this page.