Files
global-autoresponder/exec/lib/DirectAdminVacationSyncService.php
2026-07-04 15:16:50 +02:00

168 lines
6.1 KiB
PHP

<?php
declare(strict_types=1);
final class DirectAdminVacationSyncService
{
public function __construct(
private RuleRepository $rules,
private DirectAdminSyncRepository $sync,
private MailboxDirectory $mailboxes,
private DirectAdminVacationApi $api
) {
}
public function syncUser(string $username): void
{
$rules = $this->rulesById($username);
foreach ($this->sync->allRuleEntries($username) as $ruleId => $entries) {
if (!isset($rules[$ruleId]) || empty($rules[$ruleId]['enabled'])) {
$this->deleteEntries($username, $ruleId, $entries);
}
}
foreach ($rules as $ruleId => $rule) {
if (empty($rule['enabled'])) {
continue;
}
$this->syncRule($username, $ruleId, $rule);
}
}
/** @return array<int,array{domain:string,mailbox:string,email:string}> */
public function untrackedNativeVacations(string $username): array
{
$tracked = $this->trackedEmails($username);
$out = [];
foreach ($this->mailboxes->domainsForUser($username) as $domain) {
foreach ($this->api->listVacations($username, $domain) as $local) {
$email = strtolower($local . '@' . $domain);
if (isset($tracked[$email])) {
continue;
}
$out[] = [
'domain' => $domain,
'mailbox' => $local,
'email' => $email,
];
}
}
usort($out, static fn (array $a, array $b): int => strcmp($a['email'], $b['email']));
return $out;
}
/** @param array<int,array<string,mixed>> $entries */
public function deleteNativeVacations(string $username, array $entries): void
{
foreach ($entries as $entry) {
$email = strtolower((string)($entry['email'] ?? ''));
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
continue;
}
$this->api->deleteVacation($username, $email);
}
}
/** @return array<string,array<string,mixed>> */
private function rulesById(string $username): array
{
$out = [];
foreach ($this->rules->all($username) as $rule) {
$id = (string)($rule['id'] ?? '');
if ($id !== '') {
$out[$id] = $rule;
}
}
return $out;
}
/** @param array<string,mixed> $rule */
private function syncRule(string $username, string $ruleId, array $rule): void
{
$existing = $this->indexedEntries($this->sync->entriesForRule($username, $ruleId));
$targets = $this->targetMailboxes($username, $rule);
$next = [];
$conflicts = [];
foreach ($targets as $mailbox) {
$email = strtolower((string)$mailbox['email']);
$isTracked = isset($existing[$email]);
if (!$isTracked && $this->api->vacationExists($username, $email)) {
$conflicts[] = $email;
}
}
if ($conflicts !== []) {
throw new RuntimeException('Untracked DirectAdmin vacation already exists for ' . implode(', ', $conflicts));
}
foreach ($targets as $mailbox) {
$email = strtolower((string)$mailbox['email']);
$isTracked = isset($existing[$email]);
$this->api->saveVacation($username, $email, $rule, $isTracked);
$next[] = [
'rule_id' => $ruleId,
'domain' => (string)$mailbox['domain'],
'mailbox' => (string)$mailbox['mailbox'],
'email' => $email,
'api_key' => $email,
'last_sync_at' => time(),
];
unset($existing[$email]);
}
foreach ($existing as $entry) {
$this->api->deleteVacation($username, (string)$entry['email']);
}
$this->sync->replaceRuleEntries($username, $ruleId, $next);
}
/** @param array<int,array<string,mixed>> $entries */
private function deleteEntries(string $username, string $ruleId, array $entries): void
{
foreach ($entries as $entry) {
$email = (string)($entry['email'] ?? $entry['api_key'] ?? '');
if ($email !== '') {
$this->api->deleteVacation($username, $email);
}
}
$this->sync->deleteRuleEntries($username, $ruleId);
}
/** @param array<int,array<string,mixed>> $entries @return array<string,array<string,mixed>> */
private function indexedEntries(array $entries): array
{
$out = [];
foreach ($entries as $entry) {
$email = strtolower((string)($entry['email'] ?? $entry['api_key'] ?? ''));
if ($email !== '') {
$out[$email] = $entry + ['email' => $email];
}
}
return $out;
}
/** @return array<string,bool> */
private function trackedEmails(string $username): array
{
$tracked = [];
foreach ($this->sync->allRuleEntries($username) as $entries) {
foreach ($entries as $entry) {
$email = strtolower((string)($entry['email'] ?? $entry['api_key'] ?? ''));
if ($email !== '') {
$tracked[$email] = true;
}
}
}
return $tracked;
}
/** @param array<string,mixed> $rule @return array<int,array{domain:string,mailbox:string,email:string}> */
private function targetMailboxes(string $username, array $rule): array
{
if (empty($rule['dynamic_scope']) && isset($rule['mailbox_snapshot']) && is_array($rule['mailbox_snapshot'])) {
return array_values(array_filter($rule['mailbox_snapshot'], static function (mixed $mailbox): bool {
return is_array($mailbox)
&& isset($mailbox['domain'], $mailbox['mailbox'], $mailbox['email'])
&& filter_var((string)$mailbox['email'], FILTER_VALIDATE_EMAIL);
}));
}
return $this->mailboxes->mailboxesForUser($username);
}
}