fix: match directadmin vacation controls

This commit is contained in:
Marek Miklewicz
2026-06-03 09:21:51 +02:00
parent 0617798821
commit 9174d62706
12 changed files with 176 additions and 53 deletions
+35 -16
View File
@@ -6,7 +6,7 @@ function render_rule_form(AppContext $ctx, string $mode, ?array $rule): string
$isUpdate = $mode === 'update';
$id = $isUpdate ? (string)$rule['id'] : '';
$subjectPrefix = $rule['subject_prefix'] ?? $rule['subject'] ?? '';
$replyFrequency = selected_reply_frequency($rule);
$replyFrequency = selected_reply_frequency($ctx, $rule);
$body = $rule['body'] ?? '';
$enabled = $rule === null || !empty($rule['enabled']);
$startValue = (string)($rule['start_value'] ?? default_schedule_value($ctx, true));
@@ -30,29 +30,43 @@ function render_rule_form(AppContext $ctx, string $mode, ?array $rule): string
function default_schedule_value(AppContext $ctx, bool $start): string
{
$today = new DateTimeImmutable('today', new DateTimeZone($ctx->settings->timezone()));
if ($ctx->settings->scheduleMode() === 'directadmin_period') {
return $today->format('Y-m-d') . '|' . ($start ? 'morning' : 'evening');
}
return $today->format('Y-m-d') . 'T' . ($start ? '09:00' : '17:00');
return $today->format('Y-m-d') . 'T' . ($start ? '00:00' : '23:59');
}
/** @param array<string,mixed>|null $rule */
function selected_reply_frequency(?array $rule): string
function selected_reply_frequency(AppContext $ctx, ?array $rule): string
{
if ($rule === null) {
return '1440';
}
if (!empty($rule['reply_every_message'])) {
return 'every';
return $ctx->settings->backendMode() === 'da' ? '1' : 'every';
}
$minutes = (string)(int)($rule['repeat_minutes'] ?? 1440);
return in_array($minutes, ['30', '60', '120', '360', '720', '1440', '2880', '10080'], true) ? $minutes : '1440';
return array_key_exists($minutes, reply_frequency_options($ctx)) ? $minutes : '1440';
}
function render_reply_frequency_select(AppContext $ctx, string $selected): string
{
$options = [
'every' => 'Send automatic reply for every message',
$options = reply_frequency_options($ctx);
$html = '<select name="reply_frequency">';
foreach ($options as $value => $label) {
$value = (string)$value;
$html .= '<option value="' . AppContext::e($value) . '"' . ($selected === $value ? ' selected' : '') . '>' . AppContext::e($ctx->t($label)) . '</option>';
}
return $html . '</select>';
}
/** @return array<string,string> */
function reply_frequency_options(AppContext $ctx): array
{
$options = [];
if ($ctx->settings->backendMode() !== 'da') {
$options['every'] = 'Send automatic reply for every message';
}
return $options + [
'1' => '1 minute',
'10' => '10 minutes',
'30' => '30 minutes',
'60' => '1 hour',
'120' => '2 hours',
@@ -60,14 +74,19 @@ function render_reply_frequency_select(AppContext $ctx, string $selected): strin
'720' => '12 hours',
'1440' => '1 day',
'2880' => '2 days',
'4320' => '3 days',
'5760' => '4 days',
'7200' => '5 days',
'8640' => '6 days',
'10080' => '7 days',
'11520' => '8 days',
'12960' => '9 days',
'14400' => '10 days',
'15840' => '11 days',
'17280' => '12 days',
'18720' => '13 days',
'20160' => '14 days',
];
$html = '<select name="reply_frequency">';
foreach ($options as $value => $label) {
$value = (string)$value;
$html .= '<option value="' . AppContext::e($value) . '"' . ($selected === $value ? ' selected' : '') . '>' . AppContext::e($ctx->t($label)) . '</option>';
}
return $html . '</select>';
}
/** @param array<string,mixed> $post @return array<string,mixed> */
+2 -2
View File
@@ -26,7 +26,7 @@ return static function (AppContext $ctx): void {
$rows .= '<tr><td><span class="badge ' . ($enabled ? 'ok' : 'off') . '">' . AppContext::e($ctx->t($enabled ? 'Enabled' : 'Disabled')) . '</span></td>';
$rows .= '<td>' . AppContext::e($subjectPrefix) . '<br><span class="muted">' . date('Y-m-d H:i', (int)($rule['updated_at'] ?? time())) . '</span></td>';
$rows .= '<td>' . AppContext::e(date('Y-m-d H:i', (int)$rule['start_ts'])) . '<br><span class="muted">' . AppContext::e($ctx->t('To')) . ' ' . AppContext::e(date('Y-m-d H:i', (int)$rule['end_ts'])) . '</span></td>';
$rows .= '<td>' . AppContext::e($ctx->t($rule['dynamic_scope'] ? 'Dynamic' : 'Snapshot')) . '</td><td><span class="table-actions">';
$rows .= '<td><span class="table-actions">';
$rows .= '<a class="btn" href="' . AppContext::e($ctx->url('update.html', ['id' => $id])) . '">' . AppContext::e($ctx->t('Edit')) . '</a>';
$rows .= '<a class="btn" href="' . AppContext::e($ctx->url('preview.html', ['id' => $id])) . '">' . AppContext::e($ctx->t('Preview')) . '</a>';
$rows .= '<form method="post" action="' . AppContext::e($ctx->url('toggle.html')) . '">' . $ctx->csrfField('toggle:' . $id) . '<input type="hidden" name="id" value="' . AppContext::e($id) . '"><button>' . AppContext::e($ctx->t($enabled ? 'Disable' : 'Enable')) . '</button></form>';
@@ -36,6 +36,6 @@ return static function (AppContext $ctx): void {
$body = '<div class="panel"><div class="panel-head"><div><h3>' . AppContext::e($ctx->t('Global autoresponders')) . '</h3><p class="muted">' . AppContext::e($ctx->t('All valid POP/IMAP mailboxes in this account')) . '</p></div>' .
'<a class="btn amber" href="' . AppContext::e($ctx->url('create.html')) . '">' . AppContext::e($ctx->t('Add autoresponder')) . '</a></div>' .
'<table><thead><tr><th>' . AppContext::e($ctx->t('Status')) . '</th><th>' . AppContext::e($ctx->t('Subject prefix')) . '</th><th>' . AppContext::e($ctx->t('Sending period')) . '</th><th>' . AppContext::e($ctx->t('Scope')) . '</th><th>' . AppContext::e($ctx->t('Actions')) . '</th></tr></thead><tbody>' . $rows . '</tbody></table></div>';
'<table><thead><tr><th>' . AppContext::e($ctx->t('Status')) . '</th><th>' . AppContext::e($ctx->t('Subject prefix')) . '</th><th>' . AppContext::e($ctx->t('Sending period')) . '</th><th>' . AppContext::e($ctx->t('Actions')) . '</th></tr></thead><tbody>' . $rows . '</tbody></table></div>';
$ctx->render('Global autoresponders', $body, $ok);
};
+24 -7
View File
@@ -28,8 +28,8 @@ class DirectAdminVacationApi
throw new InvalidArgumentException('Invalid mailbox email');
}
[$local, $domain] = explode('@', strtolower($email), 2);
[$startDate, $startTime] = $this->splitPeriod((string)($rule['start_value'] ?? ''));
[$endDate, $endTime] = $this->splitPeriod((string)($rule['end_value'] ?? ''));
[$startDate, $startTime] = $this->splitScheduleValue((string)($rule['start_value'] ?? ''), true);
[$endDate, $endTime] = $this->splitScheduleValue((string)($rule['end_value'] ?? ''), false);
[$startYear, $startMonth, $startDay] = explode('-', $startDate);
[$endYear, $endMonth, $endDay] = explode('-', $endDate);
$subjectPrefix = (string)($rule['subject_prefix'] ?? $rule['subject'] ?? '');
@@ -108,12 +108,16 @@ class DirectAdminVacationApi
}
/** @return array{0:string,1:string} */
private function splitPeriod(string $value): array
private function splitScheduleValue(string $value, bool $start): array
{
if (!preg_match('/^([0-9]{4}-[0-9]{2}-[0-9]{2})\|(morning|afternoon|evening)$/', $value, $m)) {
throw new InvalidArgumentException('DirectAdmin API backend requires directadmin_period values');
if (preg_match('/^([0-9]{4}-[0-9]{2}-[0-9]{2})T([0-9]{2}:[0-9]{2})$/', $value, $m)) {
return [$m[1], $m[2]];
}
return [$m[1], $m[2]];
if (!preg_match('/^([0-9]{4}-[0-9]{2}-[0-9]{2})\|(morning|afternoon|evening)$/', $value, $m)) {
throw new InvalidArgumentException('DirectAdmin API backend requires exact date-time values');
}
$time = ['morning' => '08:00', 'afternoon' => '13:00', 'evening' => $start ? '18:00' : '23:59'][$m[2]];
return [$m[1], $time];
}
/** @param array<string,string> $payload */
@@ -144,9 +148,11 @@ class DirectAdminVacationApi
private function replyOnceTime(array $rule): string
{
if (!empty($rule['reply_every_message'])) {
return '0';
return '1m';
}
return match ((int)($rule['repeat_minutes'] ?? 1440)) {
1 => '1m',
10 => '10m',
30 => '30m',
60 => '1h',
120 => '2h',
@@ -154,7 +160,18 @@ class DirectAdminVacationApi
720 => '12h',
1440 => '1d',
2880 => '2d',
4320 => '3d',
5760 => '4d',
7200 => '5d',
8640 => '6d',
10080 => '1w',
11520 => '8d',
12960 => '9d',
14400 => '10d',
15840 => '11d',
17280 => '12d',
18720 => '13d',
20160 => '14d',
default => '1d',
};
}
+18 -2
View File
@@ -5,6 +5,8 @@ final class RuleValidator
{
/** @var array<string,int> */
private const REPLY_FREQUENCIES = [
'1' => 1,
'10' => 10,
'30' => 30,
'60' => 60,
'120' => 120,
@@ -12,7 +14,18 @@ final class RuleValidator
'720' => 720,
'1440' => 1440,
'2880' => 2880,
'4320' => 4320,
'5760' => 5760,
'7200' => 7200,
'8640' => 8640,
'10080' => 10080,
'11520' => 11520,
'12960' => 12960,
'14400' => 14400,
'15840' => 15840,
'17280' => 17280,
'18720' => 18720,
'20160' => 20160,
];
/** @param array<string,mixed> $input @return array<string,mixed> */
@@ -45,7 +58,7 @@ final class RuleValidator
if ($endTs <= $startTs) {
throw new InvalidArgumentException('Data zakończenia musi być późniejsza niż data rozpoczęcia.');
}
[$replyEveryMessage, $repeatMinutes] = self::parseReplyFrequency((string)($input['reply_frequency'] ?? '1440'));
[$replyEveryMessage, $repeatMinutes] = self::parseReplyFrequency((string)($input['reply_frequency'] ?? '1440'), $settings);
return [
'subject_prefix' => $subjectPrefix,
@@ -64,10 +77,13 @@ final class RuleValidator
}
/** @return array{0:bool,1:int} */
private static function parseReplyFrequency(string $value): array
private static function parseReplyFrequency(string $value, Settings $settings): array
{
$value = trim(strtolower($value));
if ($value === 'every') {
if ($settings->backendMode() === 'da') {
throw new InvalidArgumentException('Częstość odpowiedzi jest nieprawidłowa.');
}
return [true, 0];
}
if (isset(self::REPLY_FREQUENCIES[$value])) {
+1 -1
View File
@@ -98,7 +98,7 @@ final class Settings
public function scheduleMode(): string
{
return $this->backendMode() === 'da' ? 'directadmin_period' : 'exact_hour';
return 'exact_hour';
}
public function dynamicMailboxScope(): bool