* 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; }); } }