274 lines
12 KiB
PHP
274 lines
12 KiB
PHP
<?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;
|
|
}
|
|
|
|
/** @param array<string,string|int|float> $vars */
|
|
public function t(string $key, array $vars = []): string
|
|
{
|
|
return $this->lang->t($key, $vars);
|
|
}
|
|
|
|
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="gar_csrf_ts" value="' . self::e((string)$issued['ts']) . '">' .
|
|
'<input type="hidden" name="gar_csrf_sid" value="' . self::e($issued['sid']) . '">' .
|
|
'<input type="hidden" name="gar_csrf_token" value="' . self::e($issued['token']) . '">';
|
|
}
|
|
|
|
public function requireCsrf(string $intent): void
|
|
{
|
|
if (!$this->csrf->validate($intent, $this->post('gar_csrf_ts'), $this->post('gar_csrf_token'), 3600, $this->post('gar_csrf_sid'))) {
|
|
throw new RuntimeException('Nieprawidłowy lub wygasły token CSRF.');
|
|
}
|
|
}
|
|
|
|
public function syncDirectAdminVacations(): void
|
|
{
|
|
$service = new DirectAdminVacationSyncService(
|
|
$this->rules,
|
|
new DirectAdminSyncRepository(),
|
|
new MailboxDirectory(),
|
|
new DirectAdminVacationApi()
|
|
);
|
|
$service->syncUser($this->daUser->username());
|
|
}
|
|
|
|
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>></span> ' . self::e($this->t('Global autoresponders')) . '</div>';
|
|
echo '<header><h1>' . self::e($this->t($title)) . '</h1></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 . $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();
|
|
if ($skin === 'evolution') {
|
|
$base = PLUGIN_ROOT . '/user/enhanced.css';
|
|
$override = PLUGIN_ROOT . '/user/evolution.css';
|
|
return (is_file($base) ? (string)file_get_contents($base) : '') . "\n" .
|
|
(is_file($override) ? (string)file_get_contents($override) : '');
|
|
}
|
|
$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
|
|
{
|
|
$skin = json_encode($this->daUser->skin(), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
|
|
$selectedLabel = json_encode($this->t('Selected'), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
|
|
$monthNames = json_encode($this->lang->code() === 'pl'
|
|
? ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień']
|
|
: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
|
|
return <<<JS
|
|
(function () {
|
|
var pluginSkin = {$skin};
|
|
var selectedLabel = {$selectedLabel};
|
|
var monthNames = {$monthNames};
|
|
function expandDirectAdminPluginContainers() {
|
|
if (pluginSkin !== 'evolution') { return; }
|
|
var wrap = document.querySelector('.wrap');
|
|
if (!wrap) { return; }
|
|
wrap.style.width = '100%';
|
|
wrap.style.maxWidth = 'none';
|
|
wrap.style.marginLeft = '0';
|
|
wrap.style.marginRight = '0';
|
|
var node = wrap.parentElement;
|
|
var guard = 0;
|
|
while (node && node !== document.body && guard < 12) {
|
|
var id = node.id || '';
|
|
var className = typeof node.className === 'string' ? node.className : '';
|
|
var matchesDirectAdminContainer = id === 'content' || /(^|\\s)(container|container-fluid|main-content|page-content|content|content-wrapper|main-content-wrapper)(\\s|$)/.test(className);
|
|
var computed = window.getComputedStyle ? window.getComputedStyle(node) : null;
|
|
if (matchesDirectAdminContainer || (computed && computed.maxWidth && computed.maxWidth !== 'none')) {
|
|
node.style.maxWidth = 'none';
|
|
node.style.width = '100%';
|
|
}
|
|
node = node.parentElement;
|
|
guard++;
|
|
}
|
|
}
|
|
function pad(value) { return value < 10 ? '0' + value : String(value); }
|
|
function updateCanonical(name) {
|
|
var date = document.getElementById(name + '_date');
|
|
var canonical = document.getElementById(name);
|
|
if (!date || !canonical) { return; }
|
|
var time = document.querySelector('[data-time-for="' + name + '"]');
|
|
var period = document.querySelector('[data-period-for="' + name + '"]');
|
|
if (time) {
|
|
canonical.value = date.value + 'T' + (time.value || '09:00');
|
|
} else if (period) {
|
|
canonical.value = date.value + '|' + (period.value || 'morning');
|
|
}
|
|
}
|
|
function bindDateButton(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 = selectedLabel + ': ' + (btn.getAttribute('data-date-value') || ''); }
|
|
var calendar = btn.closest('[data-calendar-picker]');
|
|
if (calendar) { calendar.setAttribute('data-selected-date', 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');
|
|
if (calendar) { updateCanonical(calendar.getAttribute('data-calendar-name')); }
|
|
});
|
|
}
|
|
function renderMonth(calendar, year, month) {
|
|
var selected = calendar.getAttribute('data-selected-date') || '';
|
|
var minDate = calendar.getAttribute('data-min-date') || '';
|
|
var tbody = calendar.querySelector('[data-calendar-days]');
|
|
var label = calendar.querySelector('[data-calendar-month-label]');
|
|
if (!tbody || !label) { return; }
|
|
var first = new Date(Date.UTC(year, month - 1, 1));
|
|
var offset = (first.getUTCDay() + 6) % 7;
|
|
var days = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
var day = 1;
|
|
var html = '';
|
|
for (var week = 0; week < 6; week++) {
|
|
html += '<tr>';
|
|
for (var i = 0; i < 7; i++) {
|
|
if ((week === 0 && i < offset) || day > days) {
|
|
html += '<td class="br-cal-empty"></td>';
|
|
continue;
|
|
}
|
|
var value = year + '-' + pad(month) + '-' + pad(day);
|
|
if (minDate && value < minDate) {
|
|
html += '<td><span class="br-cal-day-disabled">' + day + '</span></td>';
|
|
day++;
|
|
continue;
|
|
}
|
|
var cls = value === selected ? ' is-selected' : '';
|
|
html += '<td><button type="button" class="br-cal-day' + cls + '" data-date-target="' + calendar.getAttribute('data-calendar-name') + '_date" data-date-label="' + calendar.getAttribute('data-calendar-name') + '_label" data-date-value="' + value + '">' + day + '</button></td>';
|
|
day++;
|
|
}
|
|
html += '</tr>';
|
|
if (day > days) { break; }
|
|
}
|
|
tbody.innerHTML = html;
|
|
label.textContent = monthNames[month - 1] + ' ' + year;
|
|
calendar.setAttribute('data-visible-month', year + '-' + pad(month));
|
|
tbody.querySelectorAll('[data-date-value]').forEach(bindDateButton);
|
|
}
|
|
document.querySelectorAll('[data-date-value]').forEach(function (btn) {
|
|
bindDateButton(btn);
|
|
});
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', expandDirectAdminPluginContainers);
|
|
} else {
|
|
expandDirectAdminPluginContainers();
|
|
}
|
|
document.querySelectorAll('[data-calendar-picker]').forEach(function (calendar) {
|
|
var name = calendar.getAttribute('data-calendar-name');
|
|
calendar.querySelectorAll('[data-calendar-prev],[data-calendar-next]').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var parts = (calendar.getAttribute('data-visible-month') || '').split('-');
|
|
var year = parseInt(parts[0], 10);
|
|
var month = parseInt(parts[1], 10);
|
|
if (!year || !month) { return; }
|
|
month += btn.hasAttribute('data-calendar-next') ? 1 : -1;
|
|
if (month < 1) { month = 12; year--; }
|
|
if (month > 12) { month = 1; year++; }
|
|
renderMonth(calendar, year, month);
|
|
});
|
|
});
|
|
document.querySelectorAll('[data-time-for="' + name + '"],[data-period-for="' + name + '"]').forEach(function (control) {
|
|
control.addEventListener('change', function () { updateCanonical(name); });
|
|
control.addEventListener('input', function () { updateCanonical(name); });
|
|
});
|
|
});
|
|
document.querySelectorAll('[data-modal-close]').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var overlay = btn.closest('.br-overlay');
|
|
if (overlay) {
|
|
overlay.style.display = 'none';
|
|
}
|
|
document.body.classList.remove('modal-open');
|
|
});
|
|
});
|
|
if (document.querySelector('.br-overlay')) {
|
|
document.body.classList.add('modal-open');
|
|
}
|
|
document.addEventListener('keydown', function (event) {
|
|
if (event.key !== 'Escape') { return; }
|
|
document.querySelectorAll('.br-overlay').forEach(function (overlay) {
|
|
overlay.style.display = 'none';
|
|
});
|
|
document.body.classList.remove('modal-open');
|
|
});
|
|
})();
|
|
JS;
|
|
}
|
|
}
|