237 lines
6.2 KiB
PHP
237 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace KTXM\ProviderMailSystem\Providers;
|
|
|
|
use KTXF\Mail\Provider\ProviderBaseInterface;
|
|
use KTXF\Mail\Selector\ServiceSelector;
|
|
use KTXF\Mail\Service\IServiceBase;
|
|
use KTXF\Mail\Service\ServiceScope;
|
|
use KTXM\ProviderMailSystem\Stores\ServiceStore;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* SMTP Mail Provider
|
|
*
|
|
* Provider for SMTP-based mail services. Supports multiple configured
|
|
* services per tenant with system and user scopes.
|
|
*
|
|
* @since 2025.05.01
|
|
*/
|
|
class Provider implements ProviderBaseInterface {
|
|
|
|
private const PROVIDER_ID = 'system';
|
|
private const PROVIDER_LABEL = 'System Mail Provider';
|
|
private const PROVIDER_DESCRIPTION = 'Outbound-only System mail provider for system notifications';
|
|
private const PROVIDER_ICON = 'fa-envelope';
|
|
|
|
private array $capabilities = [
|
|
self::CAPABILITY_SERVICE_LIST => true,
|
|
self::CAPABILITY_SERVICE_FETCH => true,
|
|
self::CAPABILITY_SERVICE_EXTANT => true,
|
|
];
|
|
|
|
public function __construct(
|
|
private LoggerInterface $logger,
|
|
private ServiceStore $serviceStore,
|
|
) {}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function capable(string $value): bool {
|
|
return $this->capabilities[$value] ?? false;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function capabilities(): array {
|
|
return $this->capabilities;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function id(): string {
|
|
return self::PROVIDER_ID;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function label(): string {
|
|
return self::PROVIDER_LABEL;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function type(): string {
|
|
return self::TYPE_MAIL;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function identifier(): string {
|
|
return self::PROVIDER_ID;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function description(): string {
|
|
return self::PROVIDER_DESCRIPTION;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function icon(): string {
|
|
return self::PROVIDER_ICON;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function serviceList(string $tenantId, string $userId, ?ServiceSelector $selector = null): array {
|
|
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function serviceExtant(string $tenantId, ?string $userId, string|int ...$identifiers): array {
|
|
$result = [];
|
|
|
|
foreach ($identifiers as $id) {
|
|
$result[$id] = $this->serviceFetch($tenantId, $userId, $id) !== null;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function serviceFetch(string $tenantId, ?string $userId, string|int $identifier): ?IServiceBase {
|
|
$identifier = (string)$identifier;
|
|
if ($identifier === '') {
|
|
return null;
|
|
}
|
|
|
|
// Only handle @system addresses for this provider
|
|
if (!str_ends_with(strtolower($identifier), '@system')) {
|
|
return null;
|
|
}
|
|
|
|
// Fetch by primary address (which is the sid)
|
|
$service = $this->serviceStore->getService($tenantId, $identifier);
|
|
if ($service === null) {
|
|
return null;
|
|
}
|
|
|
|
// Enforce scope visibility
|
|
if ($service->getScope() === ServiceScope::System) {
|
|
return $service;
|
|
}
|
|
|
|
if ($service->getScope() === ServiceScope::User) {
|
|
if ($userId !== null && $service->getOwner() === $userId) {
|
|
return $service;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function serviceFindByAddress(string $tenantId, ?string $userId, string $address): ?IServiceBase {
|
|
$address = strtolower(trim($address));
|
|
if ($address === '') {
|
|
return null;
|
|
}
|
|
|
|
// Only handle @system addresses for this provider
|
|
if (!str_ends_with($address, '@system')) {
|
|
return null;
|
|
}
|
|
|
|
// Use store's findServiceByAddress which checks primary, secondary, and catch-all patterns
|
|
$service = $this->serviceStore->findServiceByAddress($tenantId, $address);
|
|
if ($service === null) {
|
|
return null;
|
|
}
|
|
|
|
// Enforce scope visibility
|
|
if ($service->getScope() === ServiceScope::System) {
|
|
return $service;
|
|
}
|
|
|
|
if ($service->getScope() === ServiceScope::User) {
|
|
if ($userId !== null && $service->getOwner() === $userId) {
|
|
return $service;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function jsonSerialize(): array {
|
|
return [
|
|
self::JSON_PROPERTY_TYPE => self::JSON_TYPE,
|
|
self::JSON_PROPERTY_ID => $this->id(),
|
|
self::JSON_PROPERTY_LABEL => $this->label(),
|
|
self::JSON_PROPERTY_CAPABILITIES => $this->capabilities(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Apply selector filters to services
|
|
*/
|
|
private function applySelector(array $services, ServiceSelector $selector): array {
|
|
return array_filter($services, function(IServiceBase $service) use ($selector) {
|
|
if ($selector->getScope() !== null && $service->getScope() !== $selector->getScope()) {
|
|
return false;
|
|
}
|
|
|
|
if ($selector->getOwner() !== null && $service->getOwner() !== $selector->getOwner()) {
|
|
return false;
|
|
}
|
|
|
|
if ($selector->getAddress() !== null && !$service->handlesAddress($selector->getAddress())) {
|
|
return false;
|
|
}
|
|
|
|
if ($selector->getCapabilities() !== null) {
|
|
foreach ($selector->getCapabilities() as $cap) {
|
|
if (!$service->capable($cap)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($selector->getEnabled() !== null && $service->getEnabled() !== $selector->getEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
}
|