feat: implement global autoresponder plugin

This commit is contained in:
Marek Miklewicz
2026-06-02 19:19:00 +02:00
commit 6f989af278
62 changed files with 2018 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
final class AppContext
{
public function __construct(
public DirectAdminUser $daUser,
public Settings $settings,
public RuleRepository $rules,
public CsrfGuard $csrf,
public Lang $lang,
public AuditLog $audit,
) {
}
public function action(): string
{
return (string)PLUGIN_ACTION;
}
public function baseUrl(): string
{
return '/CMD_PLUGINS/global-autoresponder';
}
/** @param array<string,string|int> $query */
public function url(string $page, array $query = []): string
{
$url = $this->baseUrl() . '/' . ltrim($page, '/');
if ($query !== []) {
$url .= '?' . http_build_query($query);
}
return $url;
}
public function t(string $key): string
{
return $this->lang->t($key);
}
public function post(string $key, string $default = ''): string
{
return Http::getString($_POST, $key, $default);
}
public function query(string $key, string $default = ''): string
{
return Http::getString($_GET, $key, $default);
}
public function requirePost(): void
{
if (!Http::isPost()) {
throw new RuntimeException('Method not allowed');
}
}
public function csrfField(string $intent): string
{
$issued = $this->csrf->issue($intent);
return '<input type="hidden" name="csrf_ts" value="' . self::e((string)$issued['ts']) . '">' .
'<input type="hidden" name="csrf_sid" value="' . self::e($issued['sid']) . '">' .
'<input type="hidden" name="csrf_token" value="' . self::e($issued['token']) . '">';
}
public function requireCsrf(string $intent): void
{
if (!$this->csrf->validate($intent, $this->post('csrf_ts'), $this->post('csrf_token'), 3600, $this->post('csrf_sid'))) {
throw new RuntimeException('Nieprawidłowy lub wygasły token CSRF.');
}
}
public function render(string $title, string $body, array $ok = [], array $errors = []): void
{
header('Content-Type: text/html; charset=UTF-8');
$alerts = '';
foreach ($ok as $msg) {
$alerts .= '<div class="alert alert-ok">' . self::e((string)$msg) . '</div>';
}
foreach ($errors as $msg) {
$alerts .= '<div class="alert alert-err">' . self::e((string)$msg) . '</div>';
}
echo '<!doctype html><html lang="' . self::e($this->lang->code()) . '"><head><meta charset="utf-8">';
echo '<meta name="viewport" content="width=device-width, initial-scale=1">';
echo '<title>' . self::e($this->t($title)) . '</title><style>' . $this->styles() . '</style></head><body>';
echo '<div class="wrap"><div class="breadcrumbs">' . self::e($this->t('Dashboard')) . ' <span>&gt;</span> ' . self::e($this->t('Global autoresponders')) . '</div>';
echo '<header><h1>' . self::e($this->t($title)) . '</h1><p>' . self::e($this->t('Manage automatic replies for account mailboxes')) . '</p></header>';
echo '<div class="top-actions"><a class="btn amber' . ($this->action() === 'index' ? ' active' : '') . '" href="' . self::e($this->url('index.html')) . '">' . self::e($this->t('AUTORESPONDERS')) . '</a>';
echo '<a class="btn amber' . ($this->action() === 'create' ? ' active' : '') . '" href="' . self::e($this->url('create.html')) . '">' . self::e($this->t('ADD AUTORESPONDER')) . '</a></div>';
echo '<section class="main-section">' . $alerts . $this->lang->translateHtml($body) . '</section></div>';
echo '<script>' . $this->scripts() . '</script></body></html>';
}
public static function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
private function styles(): string
{
$skin = $this->daUser->skin();
$path = PLUGIN_ROOT . '/user/' . $skin . '.css';
if (!is_file($path)) {
$path = PLUGIN_ROOT . '/user/enhanced.css';
}
return is_file($path) ? (string)file_get_contents($path) : '';
}
private function scripts(): string
{
return <<<'JS'
(function () {
document.querySelectorAll('[data-date-value]').forEach(function (btn) {
btn.addEventListener('click', function () {
var target = document.getElementById(btn.getAttribute('data-date-target'));
var label = document.getElementById(btn.getAttribute('data-date-label'));
if (target) { target.value = btn.getAttribute('data-date-value') || ''; }
if (label) { label.textContent = 'Wybrano: ' + (btn.getAttribute('data-date-value') || ''); }
btn.closest('.br-restore-calendar').querySelectorAll('.br-cal-day').forEach(function (d) { d.classList.remove('is-selected'); });
btn.classList.add('is-selected');
});
});
})();
JS;
}
}