Skip to content

Commit

Permalink
feat: mail provider backend
Browse files Browse the repository at this point in the history
Signed-off-by: SebastianKrupinski <[email protected]>
  • Loading branch information
SebastianKrupinski committed Aug 1, 2024
1 parent c0f8c2a commit 9b4486a
Show file tree
Hide file tree
Showing 9 changed files with 1,077 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
use OCA\Mail\Listener\SpamReportListener;
use OCA\Mail\Listener\UserDeletedListener;
use OCA\Mail\Notification\Notifier;
use OCA\Mail\Provider\MailProvider;
use OCA\Mail\Search\FilteringProvider;
use OCA\Mail\Search\Provider;
use OCA\Mail\Service\Attachment\AttachmentService;
Expand Down Expand Up @@ -164,6 +165,10 @@ public function register(IRegistrationContext $context): void {
$context->registerSearchProvider(Provider::class);
}

// Added in version 4.0.0
// register mail provider
$context->registerMailProvider(MailProvider::class);

$context->registerNotifierService(Notifier::class);

// bypass Horde Translation system
Expand Down
23 changes: 23 additions & 0 deletions lib/Db/MailAccountMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,29 @@ 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[]
*
* @throws DoesNotExistException
*/
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
Expand Down
100 changes: 100 additions & 0 deletions lib/Provider/Command/MessageSend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?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\LocalMessage;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCA\Mail\Service\OutboxService;
use OCA\Mail\Service\SmimeService;
use OCP\IConfig;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Mail\Provider\Exception\SendException;
use OCP\Mail\Provider\IMessage;

class MessageSend {

public function __construct(
protected IConfig $config,
protected ITimeFactory $time,
protected AccountService $accountService,
protected OutboxService $outboxService,
protected AttachmentService $attachmentService
) {
}

public function perform(string $userId, string $serviceId, IMessage $message, array $option = []): LocalMessage {
// find user mail account details
$account = $this->accountService->find($userId, (int) $serviceId);
// convert mail provider message to mail app message
$localMessage = new LocalMessage();
$localMessage->setType($localMessage::TYPE_OUTGOING);
$localMessage->setAccountId($account->getId());
$localMessage->setSubject($message->getSubject());
$localMessage->setBody($message->getBody());
$localMessage->setHtml(true);
$localMessage->setSendAt($this->time->getTime());

// convert all mail provider attachments to local attachments
$attachments = [];
if (count($message->getAttachments()) > 0) {
// iterate attachments and save them
foreach ($message->getAttachments() as $entry) {
// determine if required parameters are set
if (empty($entry->getName()) || empty($entry->getType()) || empty($entry->getContents())) {
throw new SendException("Invalid Attachment Parameter: MUST contain values for Name, Type and Contents");
}
// convert mail provider attachment to mail app attachment
$attachments[] = $this->attachmentService->addFileFromString(
$userId,
$entry->getName(),
$entry->getType(),
$entry->getContents()
)->jsonSerialize();
}
}
// determine if required To address is set
if (empty($message->getTo()) || empty($message->getTo()[0]->getAddress())) {
throw new SendException("Invalid Message Parameter: MUST contain at least one TO address with a valid address");
}
// convert recipiant addresses
$to = $this->convertAddressArray($message->getTo());
$cc = $this->convertAddressArray($message->getCc());
$bcc = $this->convertAddressArray($message->getBcc());
// save message for sending
$localMessage = $this->outboxService->saveMessage(
$account,
$localMessage,
$to,
$cc,
$bcc,
$attachments
);

// evaluate if job scheduler is NOT cron, send message right away otherwise let cron job handle it
if ($this->config->getAppValue('core', 'backgroundjobs_mode', 'ajax') !== 'cron') {
$localMessage = $this->outboxService->sendMessage($localMessage, $account);
}

return $localMessage;

}

protected function convertAddressArray(array|null $in) {
// construct place holder
$out = [];
// convert format
foreach ($in as $entry) {
$out[] = (!empty($entry->getLabel())) ? ['email' => $entry->getAddress(), 'label' => $entry->getLabel()] : ['email' => $entry->getAddress()];
}
// return converted addressess
return $out;
}

}
176 changes: 176 additions & 0 deletions lib/Provider/MailProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<?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\Service\AccountService;
use OCP\Mail\Provider\Address as MailAddress;
use OCP\Mail\Provider\IProvider;
use OCP\Mail\Provider\IService;
use Psr\Container\ContainerInterface;

class MailProvider implements IProvider {

private ?array $ServiceCollection = [];

public function __construct(
protected ContainerInterface $container,
protected AccountService $accountService
) {
}

/**
* 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 frendly 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 'Mail Application';
}

/**
* determain if any services are configured for a specific user
*
* @since 4.0.0
*
* @param string $userId 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 user id
*
* @return array<string,IService> collection of service id and object ['1' => IServiceObject]
*/
public function listServices(string $userId): array {

try {
// retrieve service(s) details from data store
$accounts = $this->accountService->findByUserId($userId);
} catch (\Throwable $th) {
return [];
}
// construct temporary collection
$services = [];
// add services to collection
foreach ($accounts as $entry) {
// extract values
$serviceId = (string) $entry->getId();
$label = $entry->getName();
$address = new MailAddress($entry->getEmail(), $entry->getName());
// add service to collection
$services[$serviceId] = new MailService($this->container, $userId, $serviceId, $label, $address);
}
// return list of services for user
return $services;

}

/**
* retrieve a service with a specific id
*
* @since 4.0.0
*
* @param string $userId user id
* @param string $serviceId service id
*
* @return IService|null returns service object or null if none found
*/
public function findServiceById(string $userId, string $serviceId): IService | null {

// evaluate if id is a number
if (is_numeric($serviceId)) {
try {
// retrieve service details from data store
$account = $this->accountService->find($userId, (int) $serviceId);
} catch(\Throwable $th) {
return null;
}
}
// evaluate if service details where found
if ($account instanceof Account) {
// extract values
$serviceId = (string) $account->getId();
$label = $account->getName();
$address = new MailAddress($account->getEmail(), $account->getName());
// return mail service object
return new MailService($this->container, $userId, $serviceId, $label, $address);
}

return null;

}

/**
* retrieve a service for a specific mail address
*
* @since 4.0.0
*
* @param string $userId user id
* @param string $address mail address (e.g. [email protected])
*
* @return IService returns service object or null if none found
*/
public function findServiceByAddress(string $userId, string $address): IService | null {

try {
// retrieve service details from data store
$accounts = $this->accountService->findByUserIdAndAddress($userId, $address);
} catch(\Throwable $th) {
return null;
}
// evaliate if service details where found
if (is_array($accounts) && count($accounts) > 0 && $accounts[0] instanceof Account) {
// extract values
$serviceId = (string) $accounts[0]->getId();
$label = $accounts[0]->getName();
$address = new MailAddress($accounts[0]->getEmail(), $accounts[0]->getName());
// return mail service object
return new MailService($this->container, $userId, $serviceId, $label, $address);
}

return null;

}

/**
* construct a new empty service object
*
* @since 30.0.0
*
* @return IService blank service object
*/
public function initiateService(): IService {

return new MailService($this->container);

}

}
Loading

0 comments on commit 9b4486a

Please sign in to comment.