Files
provider_imap/lib/Providers/Provider.php
2026-05-23 20:18:58 -04:00

215 lines
6.9 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXM\ProviderImap\Providers;
use KTXF\Mail\Provider\ProviderBaseInterface;
use KTXF\Mail\Provider\ProviderServiceDiscoverInterface;
use KTXF\Mail\Provider\ProviderServiceMutateInterface;
use KTXF\Mail\Provider\ProviderServiceTestInterface;
use KTXF\Mail\Service\ServiceBaseInterface;
use KTXF\Mail\Service\ServiceMutableInterface;
use KTXF\Resource\Provider\ResourceServiceLocationInterface;
use KTXF\Resource\Provider\ResourceServiceMutateInterface;
use KTXM\ProviderImap\Service\Discovery;
use KTXM\ProviderImap\Service\Remote\RemoteService;
use KTXM\ProviderImap\Stores\ServiceStore;
/**
* IMAP Mail Provider
*/
class Provider implements ProviderBaseInterface, ProviderServiceMutateInterface, ProviderServiceDiscoverInterface, ProviderServiceTestInterface
{
protected const PROVIDER_IDENTIFIER = 'imap';
protected const PROVIDER_LABEL = 'IMAP Mail Provider';
protected const PROVIDER_DESCRIPTION = 'Provides mail services via the IMAP protocol';
protected const PROVIDER_ICON = 'mdi-email';
protected array $providerAbilities = [
self::CAPABILITY_SERVICE_LIST => true,
self::CAPABILITY_SERVICE_FETCH => true,
self::CAPABILITY_SERVICE_EXTANT => true,
self::CAPABILITY_SERVICE_CREATE => true,
self::CAPABILITY_SERVICE_MODIFY => true,
self::CAPABILITY_SERVICE_DESTROY => true,
self::CAPABILITY_SERVICE_TEST => true,
];
public function __construct(
private readonly ServiceStore $serviceStore,
) {}
public function jsonSerialize(): array
{
return [
self::JSON_PROPERTY_TYPE => self::JSON_TYPE,
self::JSON_PROPERTY_IDENTIFIER => self::PROVIDER_IDENTIFIER,
self::JSON_PROPERTY_LABEL => self::PROVIDER_LABEL,
self::JSON_PROPERTY_CAPABILITIES => $this->providerAbilities,
];
}
public function jsonDeserialize(array|string $data): static
{
return $this;
}
public function type(): string
{
return self::TYPE_MAIL;
}
public function identifier(): string
{
return self::PROVIDER_IDENTIFIER;
}
public function label(): string
{
return self::PROVIDER_LABEL;
}
public function description(): string
{
return self::PROVIDER_DESCRIPTION;
}
public function icon(): string
{
return self::PROVIDER_ICON;
}
public function capable(string $value): bool
{
return !empty($this->providerAbilities[$value]);
}
public function capabilities(): array
{
return $this->providerAbilities;
}
public function serviceList(string $tenantId, string $userId, array $filter = []): array
{
$list = $this->serviceStore->list($tenantId, $userId, $filter);
foreach ($list as $serviceData) {
$serviceInstance = $this->serviceFresh()->fromStore($serviceData);
$list[$serviceInstance->identifier()] = $serviceInstance;
}
return $list;
}
public function serviceFetch(string $tenantId, string $userId, string|int $identifier): ?Service
{
$serviceData = $this->serviceStore->fetch($tenantId, $userId, $identifier);
if ($serviceData === null) {
return null;
}
$serviceInstance = $this->serviceFresh()->fromStore($serviceData);
return $serviceInstance;
}
public function serviceFindByAddress(string $tenantId, string $userId, string $address): ?Service
{
/** @var Service[] $services */
$services = $this->serviceList($tenantId, $userId);
foreach ($services as $service) {
if ($service->hasAddress($address)) {
return $service;
}
}
return null;
}
public function serviceExtant(string $tenantId, string $userId, string|int ...$identifiers): array
{
return $this->serviceStore->extant($tenantId, $userId, $identifiers);
}
public function serviceFresh(): Service
{
return new Service();
}
public function serviceCreate(string $tenantId, string $userId, ResourceServiceMutateInterface $service): string
{
if (!($service instanceof Service)) {
throw new \InvalidArgumentException('Service must be instance of IMAP Service');
}
$created = $this->serviceStore->create($tenantId, $userId, $service);
return (string) $created['sid'];
}
public function serviceModify(string $tenantId, string $userId, ResourceServiceMutateInterface $service): string
{
if (!($service instanceof Service)) {
throw new \InvalidArgumentException('Service must be instance of IMAP Service');
}
$updated = $this->serviceStore->modify($tenantId, $userId, $service);
return (string) $updated['sid'];
}
public function serviceDestroy(string $tenantId, string $userId, ResourceServiceMutateInterface $service): bool
{
if (!($service instanceof Service)) {
return false;
}
return $this->serviceStore->delete($tenantId, $userId, $service->identifier());
}
public function serviceDiscover(
string $tenantId,
string $userId,
string $identity,
?string $location = null,
?string $secret = null
): ResourceServiceLocationInterface|null {
$discovery = new Discovery();
$verifySSL = true;
return $discovery->discover($identity, $location, $secret, $verifySSL);
}
public function serviceTest(ServiceBaseInterface|ServiceMutableInterface $service, array $options = []): array
{
$startTime = microtime(true);
try {
if (!($service instanceof Service)) {
throw new \InvalidArgumentException('Service must be an instance of IMAP Service');
}
// augment the service with any provided test options (e.g. override location or credentials)
$service->fromStore(['sid' => 'test']);
// Attempt to authenticate and list mailboxes as a connectivity check
$client = RemoteService::freshClient($service);
$service = RemoteService::mailService($service, $client);
$mailboxes = iterator_to_array($service->collectionList());
$latency = (int) round((microtime(true) - $startTime) * 1000);
return [
'success' => true,
'message' => 'IMAP connection successful'
. ' (Mailboxes: ' . count($mailboxes) . ')'
. ' (Latency: ' . $latency . ' ms)',
];
} catch (\Throwable $e) {
return [
'success' => false,
'message' => 'Test failed: ' . $e->getMessage(),
];
}
}
}