fix: scope csrf fields and block past dates
This commit is contained in:
@@ -9,8 +9,8 @@ function render_rule_form(AppContext $ctx, string $mode, ?array $rule): string
|
||||
$replyFrequency = selected_reply_frequency($rule);
|
||||
$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');
|
||||
$startValue = (string)($rule['start_value'] ?? default_schedule_value($ctx, true));
|
||||
$endValue = (string)($rule['end_value'] ?? default_schedule_value($ctx, false));
|
||||
$action = $ctx->url($isUpdate ? 'update.html' : 'create.html');
|
||||
$intent = $isUpdate ? 'update:' . $id : 'create';
|
||||
$csrf = $ctx->csrfField($intent);
|
||||
@@ -27,6 +27,16 @@ function render_rule_form(AppContext $ctx, string $mode, ?array $rule): string
|
||||
'</form>';
|
||||
}
|
||||
|
||||
function default_schedule_value(AppContext $ctx, bool $start): string
|
||||
{
|
||||
$today = new DateTimeImmutable('today', new DateTimeZone($ctx->settings->timezone()));
|
||||
$date = $start ? $today : $today->modify('+14 days');
|
||||
if ($ctx->settings->scheduleMode() === 'directadmin_period') {
|
||||
return $date->format('Y-m-d') . '|' . ($start ? 'morning' : 'evening');
|
||||
}
|
||||
return $date->format('Y-m-d') . 'T' . ($start ? '09:00' : '17:00');
|
||||
}
|
||||
|
||||
/** @param array<string,mixed>|null $rule */
|
||||
function selected_reply_frequency(?array $rule): string
|
||||
{
|
||||
@@ -119,7 +129,8 @@ function render_calendar_picker(AppContext $ctx, string $name, string $title, st
|
||||
}
|
||||
$selectedDt = DateTimeImmutable::createFromFormat('!Y-m-d', $selectedDate) ?: new DateTimeImmutable('today');
|
||||
$monthStart = $selectedDt->modify('first day of this month');
|
||||
$days = render_calendar_days($name, $selectedDate, $monthStart);
|
||||
$minDate = (new DateTimeImmutable('today', new DateTimeZone($ctx->settings->timezone())))->format('Y-m-d');
|
||||
$days = render_calendar_days($name, $selectedDate, $monthStart, $minDate);
|
||||
$visibleMonth = $monthStart->format('Y-m');
|
||||
$monthLabel = calendar_month_label($ctx, $monthStart);
|
||||
if ($mode === 'exact_hour') {
|
||||
@@ -131,7 +142,7 @@ function render_calendar_picker(AppContext $ctx, string $name, string $title, st
|
||||
'<option value="evening"' . ($selectedPeriod === 'evening' ? ' selected' : '') . '>' . AppContext::e($ctx->t('Evening')) . '</option>' .
|
||||
'</select>';
|
||||
}
|
||||
return '<div class="panel"><h3>' . AppContext::e($title) . '</h3><div class="br-restore-layout"><div class="br-restore-calendar" data-calendar-picker data-schedule-mode="' . AppContext::e($mode) . '" data-calendar-name="' . AppContext::e($name) . '" data-visible-month="' . AppContext::e($visibleMonth) . '" data-selected-date="' . AppContext::e($selectedDate) . '"><h4>' . AppContext::e($title) . '</h4><div class="br-month-nav"><button type="button" class="br-month-btn" data-calendar-prev>‹</button><span class="br-month-label" data-calendar-month-label>' . AppContext::e($monthLabel) . '</span><button type="button" class="br-month-btn" data-calendar-next>›</button></div>' .
|
||||
return '<div class="panel"><h3>' . AppContext::e($title) . '</h3><div class="br-restore-layout"><div class="br-restore-calendar" data-calendar-picker data-schedule-mode="' . AppContext::e($mode) . '" data-calendar-name="' . AppContext::e($name) . '" data-visible-month="' . AppContext::e($visibleMonth) . '" data-selected-date="' . AppContext::e($selectedDate) . '" data-min-date="' . AppContext::e($minDate) . '"><h4>' . AppContext::e($title) . '</h4><div class="br-month-nav"><button type="button" class="br-month-btn" data-calendar-prev>‹</button><span class="br-month-label" data-calendar-month-label>' . AppContext::e($monthLabel) . '</span><button type="button" class="br-month-btn" data-calendar-next>›</button></div>' .
|
||||
'<table class="br-calendar-grid"><thead><tr>' . render_weekday_headers($ctx) . '</tr></thead><tbody data-calendar-days>' . $days . '</tbody></table>' .
|
||||
'<div class="br-date-label" id="' . AppContext::e($name . '_label') . '">' . AppContext::e($ctx->t('Selected')) . ': ' . 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>' . AppContext::e($ctx->t('Time')) . '</h4>' . $timeControl . '</div></div>' .
|
||||
@@ -153,7 +164,7 @@ function render_weekday_headers(AppContext $ctx): string
|
||||
return $html;
|
||||
}
|
||||
|
||||
function render_calendar_days(string $name, string $selectedDate, DateTimeImmutable $monthStart): string
|
||||
function render_calendar_days(string $name, string $selectedDate, DateTimeImmutable $monthStart, string $minDate): string
|
||||
{
|
||||
$offset = ((int)$monthStart->format('N')) - 1;
|
||||
$daysInMonth = (int)$monthStart->format('t');
|
||||
@@ -167,6 +178,11 @@ function render_calendar_days(string $name, string $selectedDate, DateTimeImmuta
|
||||
continue;
|
||||
}
|
||||
$date = $monthStart->setDate((int)$monthStart->format('Y'), (int)$monthStart->format('m'), $day)->format('Y-m-d');
|
||||
if ($date < $minDate) {
|
||||
$html .= '<td><span class="br-cal-day-disabled">' . $day . '</span></td>';
|
||||
$day++;
|
||||
continue;
|
||||
}
|
||||
$class = $date === $selectedDate ? ' is-selected' : '';
|
||||
$html .= '<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>';
|
||||
$day++;
|
||||
|
||||
+10
-4
@@ -58,14 +58,14 @@ final class AppContext
|
||||
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']) . '">';
|
||||
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('csrf_ts'), $this->post('csrf_token'), 3600, $this->post('csrf_sid'))) {
|
||||
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.');
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,7 @@ final class AppContext
|
||||
}
|
||||
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; }
|
||||
@@ -174,6 +175,11 @@ final class AppContext
|
||||
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++;
|
||||
|
||||
@@ -39,6 +39,9 @@ final class RuleValidator
|
||||
$tz = new DateTimeZone($settings->timezone());
|
||||
[$startTs, $startValue] = self::parseScheduleValue((string)($input['start_value'] ?? ''), $mode, $tz, true);
|
||||
[$endTs, $endValue] = self::parseScheduleValue((string)($input['end_value'] ?? ''), $mode, $tz, false);
|
||||
if (substr($startValue, 0, 10) < (new DateTimeImmutable('today', $tz))->format('Y-m-d')) {
|
||||
throw new InvalidArgumentException('Data rozpoczęcia nie może być wcześniejsza niż aktualna data.');
|
||||
}
|
||||
if ($endTs <= $startTs) {
|
||||
throw new InvalidArgumentException('Data zakończenia musi być późniejsza niż data rozpoczęcia.');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user