feat: implement global autoresponder plugin
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function render_rule_form(AppContext $ctx, string $mode, ?array $rule): string
|
||||
{
|
||||
$isUpdate = $mode === 'update';
|
||||
$id = $isUpdate ? (string)$rule['id'] : '';
|
||||
$subject = $rule['subject'] ?? '';
|
||||
$body = $rule['body'] ?? '';
|
||||
$enabled = $rule === null || !empty($rule['enabled']);
|
||||
$startValue = (string)($rule['start_value'] ?? '2026-06-10T09:00');
|
||||
$endValue = (string)($rule['end_value'] ?? '2026-06-24T17:00');
|
||||
$action = $ctx->url($isUpdate ? 'update.html' : 'create.html');
|
||||
$intent = $isUpdate ? 'update:' . $id : 'create';
|
||||
$csrf = $ctx->csrfField($intent);
|
||||
$hiddenId = $isUpdate ? '<input type="hidden" name="id" value="' . AppContext::e($id) . '">' : '';
|
||||
|
||||
return '<form method="post" action="' . AppContext::e($action) . '">' . $csrf . $hiddenId .
|
||||
'<div class="grid grid-2"><div class="panel"><h3>Treść odpowiedzi</h3><div class="row"><div><label>' . AppContext::e($ctx->t('Subject')) . '</label><input type="text" name="subject" value="' . AppContext::e((string)$subject) . '"></div>' .
|
||||
'<div><label>' . AppContext::e($ctx->t('Status')) . '</label><label class="br-time-item"><input type="checkbox" name="enabled" value="1" ' . ($enabled ? 'checked' : '') . '> ' . AppContext::e($ctx->t('Enabled')) . '</label></div></div>' .
|
||||
'<label>' . AppContext::e($ctx->t('Message body')) . '</label><textarea name="body">' . AppContext::e((string)$body) . '</textarea></div>' .
|
||||
'<div class="panel"><h3>Podsumowanie</h3><div class="summary-card"><div class="kv"><strong>Zakres</strong><span>' . AppContext::e($ctx->t('All valid POP/IMAP mailboxes in this account')) . '</span></div>' .
|
||||
'<div class="kv"><strong>Start</strong><span>' . AppContext::e($startValue) . '</span></div><div class="kv"><strong>Koniec</strong><span>' . AppContext::e($endValue) . '</span></div></div>' .
|
||||
'<div class="actions"><button class="primary" type="submit">' . AppContext::e($ctx->t($isUpdate ? 'Save changes' : 'Save autoresponder')) . '</button><a class="btn" href="' . AppContext::e($ctx->url('index.html')) . '">' . AppContext::e($ctx->t('Cancel')) . '</a></div></div></div>' .
|
||||
render_calendar_picker('start_value', 'Data rozpoczęcia', $startValue) .
|
||||
render_calendar_picker('end_value', 'Data zakończenia', $endValue) .
|
||||
'</form>';
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $post @return array<string,mixed> */
|
||||
function normalize_rule_post(array $post): array
|
||||
{
|
||||
foreach (['start_value', 'end_value'] as $key) {
|
||||
$date = trim((string)($post[$key . '_date'] ?? ''));
|
||||
$hour = trim((string)($post[$key . '_hour'] ?? ''));
|
||||
if ($date !== '' && preg_match('/^[0-9]{2}$/', $hour)) {
|
||||
$post[$key] = $date . 'T' . $hour . ':00';
|
||||
}
|
||||
}
|
||||
return $post;
|
||||
}
|
||||
|
||||
function render_calendar_picker(string $name, string $title, string $value): string
|
||||
{
|
||||
$selectedDate = substr($value, 0, 10);
|
||||
$selectedHour = substr($value, 11, 2) ?: '09';
|
||||
$days = '';
|
||||
for ($week = 0, $day = 1; $week < 4; $week++) {
|
||||
$days .= '<tr>';
|
||||
for ($i = 0; $i < 7; $i++, $day++) {
|
||||
$date = sprintf('2026-06-%02d', $day);
|
||||
$class = $date === $selectedDate ? ' is-selected' : '';
|
||||
$days .= '<td><button type="button" class="br-cal-day' . $class . '" data-date-target="' . AppContext::e($name . '_date') . '" data-date-label="' . AppContext::e($name . '_label') . '" data-date-value="' . AppContext::e($date) . '">' . $day . '</button></td>';
|
||||
}
|
||||
$days .= '</tr>';
|
||||
}
|
||||
$hours = '';
|
||||
foreach (['08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18'] as $hour) {
|
||||
$hours .= '<label class="br-time-item"><input type="radio" name="' . AppContext::e($name . '_hour') . '" value="' . $hour . '" ' . ($hour === $selectedHour ? 'checked' : '') . '> ' . $hour . ':00</label>';
|
||||
}
|
||||
return '<div class="panel"><h3>' . AppContext::e($title) . '</h3><div class="br-restore-layout"><div class="br-restore-calendar"><h4>' . AppContext::e($title) . '</h4><div class="br-month-nav"><button type="button" class="br-month-btn">‹</button><span class="br-month-label">Czerwiec 2026</span><button type="button" class="br-month-btn">›</button></div>' .
|
||||
'<table class="br-calendar-grid"><thead><tr><th>Pn</th><th>Wt</th><th>Śr</th><th>Cz</th><th>Pt</th><th>Sb</th><th>Nd</th></tr></thead><tbody>' . $days . '</tbody></table>' .
|
||||
'<div class="br-date-label" id="' . AppContext::e($name . '_label') . '">Wybrano: ' . AppContext::e($selectedDate) . '</div><input type="hidden" id="' . AppContext::e($name . '_date') . '" name="' . AppContext::e($name . '_date') . '" value="' . AppContext::e($selectedDate) . '"></div>' .
|
||||
'<div class="br-restore-calendar"><h4>Godzina</h4><div class="br-time-picker"><div class="br-time-title">Wybierz godzinę</div><div class="br-time-list">' . $hours . '</div></div></div></div>' .
|
||||
'<input type="hidden" name="' . AppContext::e($name) . '" value="' . AppContext::e($value) . '"></div>';
|
||||
}
|
||||
|
||||
function render_preview_box(array $rule): string
|
||||
{
|
||||
return '<div class="preview-box"><div><strong>Temat:</strong> ' . AppContext::e((string)$rule['subject']) . '</div><br><div>' . nl2br(AppContext::e((string)$rule['body'])) . '</div></div>';
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return static function (AppContext $ctx): void {
|
||||
$errors = [];
|
||||
if (Http::isPost()) {
|
||||
try {
|
||||
$ctx->requireCsrf('create');
|
||||
$input = normalize_rule_post($_POST);
|
||||
$rule = RuleValidator::validate($input, $ctx->settings);
|
||||
$ctx->rules->create($ctx->daUser->username(), $rule);
|
||||
Http::redirect($ctx->url('index.html', ['status' => 'created']));
|
||||
} catch (Throwable $e) {
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
$ctx->render('Add autoresponder', render_rule_form($ctx, 'create', null), [], $errors);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return static function (AppContext $ctx): void {
|
||||
$id = Http::isPost() ? $ctx->post('id') : $ctx->query('id');
|
||||
$rule = $ctx->rules->find($ctx->daUser->username(), $id);
|
||||
if ($rule === null) {
|
||||
$ctx->render('Confirm deletion', '<div class="alert alert-err">Rule not found</div>');
|
||||
return;
|
||||
}
|
||||
if (Http::isPost()) {
|
||||
try {
|
||||
$ctx->requireCsrf('delete:' . $id);
|
||||
$ctx->rules->delete($ctx->daUser->username(), $id);
|
||||
$ctx->audit->record('delete', $ctx->daUser->username(), ['rule_id' => $id, 'subject' => (string)$rule['subject']]);
|
||||
Http::redirect($ctx->url('index.html', ['status' => 'deleted']));
|
||||
} catch (Throwable $e) {
|
||||
$ctx->render('Confirm deletion', '<div class="alert alert-err">' . AppContext::e($e->getMessage()) . '</div>');
|
||||
return;
|
||||
}
|
||||
}
|
||||
$body = '<div class="grid grid-2"><div class="panel"><h3>' . AppContext::e($ctx->t('Preview autoresponder')) . '</h3>' . render_preview_box($rule) . '</div>' .
|
||||
'<div class="panel"><h3>' . AppContext::e($ctx->t('Confirm deletion')) . '</h3><div class="alert alert-err">Usunięcie reguły wyłączy odpowiedź automatyczną utworzoną przez tę regułę dla objętych skrzynek.</div>' .
|
||||
'<form method="post" action="' . AppContext::e($ctx->url('delete.html')) . '">' . $ctx->csrfField('delete:' . $id) . '<input type="hidden" name="id" value="' . AppContext::e($id) . '">' .
|
||||
'<div class="actions"><button class="danger-fill" type="submit">' . AppContext::e($ctx->t('Delete autoresponder')) . '</button><a class="btn" href="' . AppContext::e($ctx->url('index.html')) . '">' . AppContext::e($ctx->t('Do not delete')) . '</a></div></form></div></div>';
|
||||
$ctx->render('Confirm deletion', $body);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return static function (AppContext $ctx): void {
|
||||
$rules = $ctx->rules->all($ctx->daUser->username());
|
||||
$ok = [];
|
||||
$status = $ctx->query('status');
|
||||
if ($status === 'created') { $ok[] = $ctx->t('Rule created.'); }
|
||||
if ($status === 'updated') { $ok[] = $ctx->t('Rule updated.'); }
|
||||
if ($status === 'deleted') { $ok[] = $ctx->t('Rule deleted.'); }
|
||||
if ($status === 'toggled') { $ok[] = $ctx->t('Rule status changed.'); }
|
||||
|
||||
if ($rules === []) {
|
||||
$body = '<div class="panel"><div class="empty-state"><div><h3>' . AppContext::e($ctx->t('No global autoresponders')) . '</h3>' .
|
||||
'<p class="muted">' . AppContext::e($ctx->t('All valid POP/IMAP mailboxes in this account')) . '</p>' .
|
||||
'<a class="btn amber" href="' . AppContext::e($ctx->url('create.html')) . '">' . AppContext::e($ctx->t('Add autoresponder')) . '</a></div></div></div>';
|
||||
$ctx->render('Global autoresponders', $body, $ok);
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = '';
|
||||
foreach ($rules as $rule) {
|
||||
$id = (string)$rule['id'];
|
||||
$enabled = !empty($rule['enabled']);
|
||||
$rows .= '<tr><td><span class="badge ' . ($enabled ? 'ok' : 'off') . '">' . AppContext::e($ctx->t($enabled ? 'Enabled' : 'Disabled')) . '</span></td>';
|
||||
$rows .= '<td>' . AppContext::e((string)$rule['subject']) . '<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">do ' . AppContext::e(date('Y-m-d H:i', (int)$rule['end_ts'])) . '</span></td>';
|
||||
$rows .= '<td>' . AppContext::e($rule['dynamic_scope'] ? 'Dynamiczny' : 'Snapshot') . '</td><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>';
|
||||
$rows .= '<a class="btn danger" href="' . AppContext::e($ctx->url('delete.html', ['id' => $id])) . '">' . AppContext::e($ctx->t('Delete')) . '</a>';
|
||||
$rows .= '</span></td></tr>';
|
||||
}
|
||||
|
||||
$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>Status</th><th>' . AppContext::e($ctx->t('Subject')) . '</th><th>Okres wysyłki</th><th>Zakres</th><th>Akcje</th></tr></thead><tbody>' . $rows . '</tbody></table></div>';
|
||||
$ctx->render('Global autoresponders', $body, $ok);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return static function (AppContext $ctx): void {
|
||||
$id = $ctx->query('id');
|
||||
$rule = $ctx->rules->find($ctx->daUser->username(), $id);
|
||||
if ($rule === null) {
|
||||
$ctx->render('Preview autoresponder', '<div class="alert alert-err">Rule not found</div>');
|
||||
return;
|
||||
}
|
||||
$body = '<div class="grid grid-2"><div class="panel"><h3>' . AppContext::e($ctx->t('Preview autoresponder')) . '</h3>' . render_preview_box($rule) . '</div>' .
|
||||
'<div class="panel"><h3>Szczegóły reguły</h3><div class="summary-card"><div class="kv"><strong>Status</strong><span>' . AppContext::e($ctx->t(!empty($rule['enabled']) ? 'Enabled' : 'Disabled')) . '</span></div>' .
|
||||
'<div class="kv"><strong>Okres</strong><span>' . AppContext::e(date('Y-m-d H:i', (int)$rule['start_ts']) . ' - ' . date('Y-m-d H:i', (int)$rule['end_ts'])) . '</span></div></div>' .
|
||||
'<div class="actions"><a class="btn" href="' . AppContext::e($ctx->url('update.html', ['id' => $id])) . '">' . AppContext::e($ctx->t('Edit')) . '</a><a class="btn danger" href="' . AppContext::e($ctx->url('delete.html', ['id' => $id])) . '">' . AppContext::e($ctx->t('Delete')) . '</a><a class="btn" href="' . AppContext::e($ctx->url('index.html')) . '">Powrót</a></div></div></div>';
|
||||
$ctx->render('Preview autoresponder', $body);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return static function (AppContext $ctx): void {
|
||||
$ctx->requirePost();
|
||||
$id = $ctx->post('id');
|
||||
$ctx->requireCsrf('toggle:' . $id);
|
||||
$rule = $ctx->rules->find($ctx->daUser->username(), $id);
|
||||
if ($rule === null) {
|
||||
throw new RuntimeException('Rule not found');
|
||||
}
|
||||
$ctx->rules->update($ctx->daUser->username(), $id, ['enabled' => empty($rule['enabled'])]);
|
||||
Http::redirect($ctx->url('index.html', ['status' => 'toggled']));
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return static function (AppContext $ctx): void {
|
||||
$id = Http::isPost() ? $ctx->post('id') : $ctx->query('id');
|
||||
$rule = $ctx->rules->find($ctx->daUser->username(), $id);
|
||||
if ($rule === null) {
|
||||
$ctx->render('Edit autoresponder', '<div class="alert alert-err">Rule not found</div>');
|
||||
return;
|
||||
}
|
||||
$errors = [];
|
||||
if (Http::isPost()) {
|
||||
try {
|
||||
$ctx->requireCsrf('update:' . $id);
|
||||
$input = normalize_rule_post($_POST);
|
||||
$patch = RuleValidator::validate($input, $ctx->settings);
|
||||
$ctx->rules->update($ctx->daUser->username(), $id, $patch);
|
||||
Http::redirect($ctx->url('index.html', ['status' => 'updated']));
|
||||
} catch (Throwable $e) {
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
$ctx->render('Edit autoresponder', render_rule_form($ctx, 'update', $rule), [], $errors);
|
||||
};
|
||||
Reference in New Issue
Block a user