Sync global-autoresponder current state
This commit is contained in:
+244
-23
@@ -5,6 +5,7 @@ function render_rule_form(AppContext $ctx, string $mode, ?array $rule): string
|
||||
{
|
||||
$isUpdate = $mode === 'update';
|
||||
$id = $isUpdate ? (string)$rule['id'] : '';
|
||||
$name = (string)($rule['name'] ?? '');
|
||||
$subjectPrefix = $rule['subject_prefix'] ?? $rule['subject'] ?? '';
|
||||
$replyFrequency = selected_reply_frequency($ctx, $rule);
|
||||
$body = $rule['body'] ?? '';
|
||||
@@ -17,16 +18,25 @@ function render_rule_form(AppContext $ctx, string $mode, ?array $rule): string
|
||||
$hiddenId = $isUpdate ? '<input type="hidden" name="id" value="' . AppContext::e($id) . '">' : '';
|
||||
|
||||
return '<form method="post" action="' . AppContext::e($action) . '">' . $csrf . $hiddenId .
|
||||
'<div class="panel"><h3>' . AppContext::e($ctx->t('Response content')) . '</h3><div class="row"><div><label>' . AppContext::e($ctx->t('Subject prefix')) . '</label><div class="subject-prefix-control"><input type="text" name="subject_prefix" value="' . AppContext::e((string)$subjectPrefix) . '"><span>' . AppContext::e($ctx->t('Original subject suffix')) . '</span></div></div>' .
|
||||
'<div class="panel"><h3>' . AppContext::e($ctx->t('Response content')) . '</h3><div class="row"><div><label>' . AppContext::e($ctx->t('Autoresponder name')) . '</label><input type="text" name="name" value="' . AppContext::e($name) . '"><div class="field-help">' . AppContext::e($ctx->t('Autoresponder name help')) . '</div></div>' .
|
||||
'<div><label>' . AppContext::e($ctx->t('Subject prefix')) . '</label><div class="subject-prefix-control"><input type="text" name="subject_prefix" value="' . AppContext::e((string)$subjectPrefix) . '"><span>' . AppContext::e($ctx->t('Original subject suffix')) . '</span></div></div>' .
|
||||
'<div><label>' . AppContext::e($ctx->t('Response frequency')) . '</label>' . render_reply_frequency_select($ctx, $replyFrequency) . '</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>' .
|
||||
render_calendar_picker($ctx, 'start_value', $ctx->t('Start date'), $startValue) .
|
||||
render_calendar_picker($ctx, 'end_value', $ctx->t('End date'), $endValue) .
|
||||
render_schedule_panel($ctx, $startValue, $endValue, !$isUpdate) .
|
||||
'<div class="panel"><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>' .
|
||||
'</form>';
|
||||
}
|
||||
|
||||
function render_schedule_panel(AppContext $ctx, string $startValue, string $endValue, bool $enforceMinDate = true): string
|
||||
{
|
||||
return '<div class="panel schedule-panel"><h3>' . AppContext::e($ctx->t('Sending period')) . '</h3>' .
|
||||
'<div class="schedule-scroll"><div class="schedule-grid">' .
|
||||
render_calendar_picker($ctx, 'start_value', $ctx->t('Start date'), $startValue, $enforceMinDate) .
|
||||
render_calendar_picker($ctx, 'end_value', $ctx->t('End date'), $endValue, $enforceMinDate) .
|
||||
'</div></div></div>';
|
||||
}
|
||||
|
||||
function default_schedule_value(AppContext $ctx, bool $start): string
|
||||
{
|
||||
$today = new DateTimeImmutable('today', new DateTimeZone($ctx->settings->timezone()));
|
||||
@@ -39,9 +49,6 @@ function selected_reply_frequency(AppContext $ctx, ?array $rule): string
|
||||
if ($rule === null) {
|
||||
return '1440';
|
||||
}
|
||||
if (!empty($rule['reply_every_message'])) {
|
||||
return $ctx->settings->backendMode() === 'da' ? '1' : 'every';
|
||||
}
|
||||
$minutes = (string)(int)($rule['repeat_minutes'] ?? 1440);
|
||||
return array_key_exists($minutes, reply_frequency_options($ctx)) ? $minutes : '1440';
|
||||
}
|
||||
@@ -60,11 +67,7 @@ function render_reply_frequency_select(AppContext $ctx, string $selected): strin
|
||||
/** @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 + [
|
||||
return [
|
||||
'1' => '1 minute',
|
||||
'10' => '10 minutes',
|
||||
'30' => '30 minutes',
|
||||
@@ -114,12 +117,9 @@ function attach_rule_scope(AppContext $ctx, array $rule): array
|
||||
return $rule;
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $rule */
|
||||
function enforce_rule_backend_constraints(AppContext $ctx, ?string $currentId, array $rule): void
|
||||
/** @return array<string,mixed>|null */
|
||||
function find_active_rule_conflict(AppContext $ctx, ?string $currentId): ?array
|
||||
{
|
||||
if ($ctx->settings->backendMode() !== 'da' || empty($rule['enabled'])) {
|
||||
return;
|
||||
}
|
||||
foreach ($ctx->rules->all($ctx->daUser->username()) as $existing) {
|
||||
if (empty($existing['enabled'])) {
|
||||
continue;
|
||||
@@ -127,13 +127,234 @@ function enforce_rule_backend_constraints(AppContext $ctx, ?string $currentId, a
|
||||
if ($currentId !== null && (string)($existing['id'] ?? '') === $currentId) {
|
||||
continue;
|
||||
}
|
||||
throw new RuntimeException($ctx->t('DirectAdmin backend supports only one enabled global rule.'));
|
||||
return $existing;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $rule */
|
||||
function enforce_rule_directadmin_constraints(AppContext $ctx, ?string $currentId, array $rule): void
|
||||
{
|
||||
if (empty($rule['enabled'])) {
|
||||
return;
|
||||
}
|
||||
if (find_active_rule_conflict($ctx, $currentId) !== null) {
|
||||
throw new RuntimeException($ctx->t('Only one global autoresponder can be active per user.'));
|
||||
}
|
||||
}
|
||||
|
||||
function render_calendar_picker(AppContext $ctx, string $name, string $title, string $value): string
|
||||
/** @param array<string,mixed> $rule */
|
||||
function display_rule_name(AppContext $ctx, array $rule): string
|
||||
{
|
||||
$name = trim((string)($rule['name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
return $name;
|
||||
}
|
||||
$subjectPrefix = trim((string)($rule['subject_prefix'] ?? $rule['subject'] ?? ''));
|
||||
return $subjectPrefix !== '' ? $subjectPrefix : $ctx->t('Unnamed autoresponder');
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $rule */
|
||||
function format_rule_created_at(AppContext $ctx, array $rule): string
|
||||
{
|
||||
$timestamp = (int)($rule['created_at'] ?? $rule['updated_at'] ?? time());
|
||||
return format_rule_timestamp($ctx, $timestamp);
|
||||
}
|
||||
|
||||
function format_rule_timestamp(AppContext $ctx, int $timestamp): string
|
||||
{
|
||||
return (new DateTimeImmutable('@' . $timestamp))
|
||||
->setTimezone(new DateTimeZone($ctx->settings->timezone()))
|
||||
->format('Y-m-d H:i');
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $rule */
|
||||
/** @param array<string,string> $extra */
|
||||
function render_hidden_rule_inputs(AppContext $ctx, array $rule, bool $enabled, array $extra = []): string
|
||||
{
|
||||
$fields = [
|
||||
'name' => display_rule_name_for_input($rule),
|
||||
'subject_prefix' => (string)($rule['subject_prefix'] ?? $rule['subject'] ?? ''),
|
||||
'reply_frequency' => selected_reply_frequency($ctx, $rule),
|
||||
'body' => (string)($rule['body'] ?? ''),
|
||||
'start_value' => (string)($rule['start_value'] ?? ''),
|
||||
'end_value' => (string)($rule['end_value'] ?? ''),
|
||||
];
|
||||
$html = '';
|
||||
foreach ($fields as $name => $value) {
|
||||
$html .= '<input type="hidden" name="' . AppContext::e($name) . '" value="' . AppContext::e($value) . '">';
|
||||
}
|
||||
if ($enabled) {
|
||||
$html .= '<input type="hidden" name="enabled" value="1">';
|
||||
}
|
||||
foreach ($extra as $name => $value) {
|
||||
$html .= '<input type="hidden" name="' . AppContext::e($name) . '" value="' . AppContext::e($value) . '">';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $rule */
|
||||
function display_rule_name_for_input(array $rule): string
|
||||
{
|
||||
return trim((string)($rule['name'] ?? ''));
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $pending @param array<string,mixed> $active */
|
||||
function render_create_conflict_modal(AppContext $ctx, array $pending, array $active): string
|
||||
{
|
||||
$newName = display_rule_name($ctx, $pending);
|
||||
$oldName = display_rule_name($ctx, $active);
|
||||
$created = format_rule_created_at($ctx, $active);
|
||||
$action = $ctx->url('create.html');
|
||||
|
||||
$html = '<div class="br-overlay" role="dialog" aria-modal="true"><div class="br-modal">';
|
||||
$html .= '<h3>' . AppContext::e($ctx->t('Only one active autoresponder')) . '</h3>';
|
||||
$html .= '<p>' . AppContext::e($ctx->t('Active autoresponder exists for user.', [
|
||||
'user' => $ctx->daUser->username(),
|
||||
'name' => $oldName,
|
||||
'created' => $created,
|
||||
])) . '</p>';
|
||||
$html .= '<p>' . AppContext::e($ctx->t('Only one global autoresponder can be active per user.')) . '</p>';
|
||||
$html .= '<div class="br-modal-actions">';
|
||||
$html .= '<form method="post" action="' . AppContext::e($action) . '">' . $ctx->csrfField('create') . render_hidden_rule_inputs($ctx, $pending, true) .
|
||||
'<input type="hidden" name="conflict_action" value="replace"><input type="hidden" name="conflict_rule_id" value="' . AppContext::e((string)($active['id'] ?? '')) . '">' .
|
||||
'<button class="success" type="submit">' . AppContext::e($ctx->t('Enable new and disable previous', ['new' => $newName, 'old' => $oldName])) . '</button></form>';
|
||||
$html .= '<form method="post" action="' . AppContext::e($action) . '">' . $ctx->csrfField('create') . render_hidden_rule_inputs($ctx, $pending, false) .
|
||||
'<input type="hidden" name="conflict_action" value="create_disabled"><input type="hidden" name="conflict_rule_id" value="' . AppContext::e((string)($active['id'] ?? '')) . '">' .
|
||||
'<button type="submit">' . AppContext::e($ctx->t('Add as disabled', ['name' => $newName])) . '</button></form>';
|
||||
$html .= '<button class="danger-fill" type="button" data-modal-close>' . AppContext::e($ctx->t('Cancel')) . '</button>';
|
||||
$html .= '</div></div></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $pending @param array<int,array<string,mixed>> $nativeEntries @param array<string,string> $extra */
|
||||
function render_native_vacation_conflict_modal(AppContext $ctx, array $pending, array $nativeEntries, array $extra = []): string
|
||||
{
|
||||
$rows = '';
|
||||
foreach ($nativeEntries as $entry) {
|
||||
$email = (string)($entry['email'] ?? '');
|
||||
$domain = (string)($entry['domain'] ?? '');
|
||||
$mailbox = (string)($entry['mailbox'] ?? '');
|
||||
$rows .= '<tr><td>' . AppContext::e($email) . '</td><td>' . AppContext::e($domain) . '</td><td>' . AppContext::e($mailbox) . '</td></tr>';
|
||||
}
|
||||
|
||||
$html = '<div class="br-overlay" role="dialog" aria-modal="true"><div class="br-modal">';
|
||||
$html .= '<h3>' . AppContext::e($ctx->t('Native autoresponders already exist')) . '</h3>';
|
||||
$html .= '<p>' . AppContext::e($ctx->t('Native autoresponders exist for user.', ['user' => $ctx->daUser->username()])) . '</p>';
|
||||
$html .= '<p><strong>' . AppContext::e($ctx->t('Native autoresponders removal warning')) . '</strong></p>';
|
||||
$html .= '<table><thead><tr><th>' . AppContext::e($ctx->t('Email address')) . '</th><th>' . AppContext::e($ctx->t('Domain')) . '</th><th>' . AppContext::e($ctx->t('Mailbox')) . '</th></tr></thead><tbody>' . $rows . '</tbody></table>';
|
||||
$html .= '<div class="br-modal-actions">';
|
||||
$html .= '<form method="post" action="' . AppContext::e($ctx->url('create.html')) . '">' . $ctx->csrfField('create') . render_hidden_rule_inputs($ctx, $pending, !empty($pending['enabled']), $extra + ['native_conflict_action' => 'disable_native']) .
|
||||
'<button class="danger-fill" type="submit">' . AppContext::e($ctx->t('Remove existing autoresponders')) . '</button></form>';
|
||||
$html .= '<button type="button" data-modal-close>' . AppContext::e($ctx->t('Cancel')) . '</button>';
|
||||
$html .= '</div></div></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
function directadmin_vacation_sync_service(AppContext $ctx): DirectAdminVacationSyncService
|
||||
{
|
||||
return new DirectAdminVacationSyncService(
|
||||
$ctx->rules,
|
||||
new DirectAdminSyncRepository(),
|
||||
new MailboxDirectory(),
|
||||
new DirectAdminVacationApi()
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<int,array{domain:string,mailbox:string,email:string}> */
|
||||
function list_untracked_native_da_vacations(AppContext $ctx): array
|
||||
{
|
||||
return directadmin_vacation_sync_service($ctx)->untrackedNativeVacations($ctx->daUser->username());
|
||||
}
|
||||
|
||||
function delete_untracked_native_da_vacations(AppContext $ctx): void
|
||||
{
|
||||
$service = directadmin_vacation_sync_service($ctx);
|
||||
$service->deleteNativeVacations($ctx->daUser->username(), $service->untrackedNativeVacations($ctx->daUser->username()));
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $target @param array<string,mixed> $active */
|
||||
function render_toggle_conflict_modal(AppContext $ctx, array $target, array $active): string
|
||||
{
|
||||
$targetName = display_rule_name($ctx, $target);
|
||||
$oldName = display_rule_name($ctx, $active);
|
||||
$created = format_rule_created_at($ctx, $active);
|
||||
$id = (string)($target['id'] ?? '');
|
||||
|
||||
$html = '<div class="br-overlay" role="dialog" aria-modal="true"><div class="br-modal">';
|
||||
$html .= '<h3>' . AppContext::e($ctx->t('Only one active autoresponder')) . '</h3>';
|
||||
$html .= '<p>' . AppContext::e($ctx->t('Active autoresponder exists for user.', [
|
||||
'user' => $ctx->daUser->username(),
|
||||
'name' => $oldName,
|
||||
'created' => $created,
|
||||
])) . '</p>';
|
||||
$html .= '<p>' . AppContext::e($ctx->t('Only one global autoresponder can be active per user.')) . '</p>';
|
||||
$html .= '<div class="br-modal-actions">';
|
||||
$html .= '<form method="post" action="' . AppContext::e($ctx->url('toggle.html')) . '">' . $ctx->csrfField('toggle:' . $id) .
|
||||
'<input type="hidden" name="id" value="' . AppContext::e($id) . '">' .
|
||||
'<input type="hidden" name="conflict_action" value="replace"><input type="hidden" name="conflict_rule_id" value="' . AppContext::e((string)($active['id'] ?? '')) . '">' .
|
||||
'<button class="success" type="submit">' . AppContext::e($ctx->t('Enable autoresponder', ['name' => $targetName])) . '</button></form>';
|
||||
$html .= '<button class="danger-fill" type="button" data-modal-close>' . AppContext::e($ctx->t('Cancel')) . '</button>';
|
||||
$html .= '</div></div></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
function render_delete_confirmation_body(AppContext $ctx, array $rule): string
|
||||
{
|
||||
$id = (string)($rule['id'] ?? '');
|
||||
$name = display_rule_name($ctx, $rule);
|
||||
$created = format_rule_created_at($ctx, $rule);
|
||||
$message = $ctx->t('Delete confirmation question', [
|
||||
'name' => $name,
|
||||
'created' => $created,
|
||||
]);
|
||||
|
||||
return '<div class="panel"><p class="confirm-question">' . AppContext::e($message) . '</p>' .
|
||||
'<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('Yes')) . '</button>' .
|
||||
'<a class="btn" href="' . AppContext::e($ctx->url('index.html')) . '">' . AppContext::e($ctx->t('No')) . '</a></div></form></div>';
|
||||
}
|
||||
|
||||
function render_rules_index_body(AppContext $ctx, string $extraHtml = ''): string
|
||||
{
|
||||
$rules = $ctx->rules->all($ctx->daUser->username());
|
||||
if ($rules === []) {
|
||||
return '<div class="panel"><div class="empty-state"><div><h3>' . AppContext::e($ctx->t('No global autoresponders')) . '</h3></div></div></div>' . $extraHtml;
|
||||
}
|
||||
|
||||
$rows = '';
|
||||
foreach ($rules as $rule) {
|
||||
$id = (string)$rule['id'];
|
||||
$enabled = !empty($rule['enabled']);
|
||||
$subjectPrefix = (string)($rule['subject_prefix'] ?? $rule['subject'] ?? '');
|
||||
$rows .= '<tr><td><span class="badge ' . ($enabled ? 'ok' : 'off') . '">' . AppContext::e($ctx->t($enabled ? 'Enabled' : 'Disabled')) . '</span></td>';
|
||||
$rows .= '<td>' . AppContext::e(display_rule_name($ctx, $rule)) . '</td>';
|
||||
$rows .= '<td>' . AppContext::e($subjectPrefix) . '</td>';
|
||||
$rows .= '<td>' . AppContext::e(format_rule_created_at($ctx, $rule)) . '</td>';
|
||||
$rows .= '<td>' . AppContext::e(format_rule_timestamp($ctx, (int)$rule['start_ts'])) . '<br><span class="muted">' . AppContext::e($ctx->t('To')) . ' ' . AppContext::e(format_rule_timestamp($ctx, (int)$rule['end_ts'])) . '</span></td>';
|
||||
$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>';
|
||||
$rows .= '<a class="btn danger" href="' . AppContext::e($ctx->url('delete.html', ['id' => $id])) . '">' . AppContext::e($ctx->t('Delete')) . '</a>';
|
||||
$rows .= '</span></td></tr>';
|
||||
}
|
||||
|
||||
return '<div class="panel"><div class="panel-head"><div><h3>' . AppContext::e($ctx->t('Global autoresponders')) . '</h3></div>' .
|
||||
'</div><table><thead><tr><th>' . AppContext::e($ctx->t('Status')) . '</th><th>' . AppContext::e($ctx->t('Autoresponder name')) . '</th><th>' . AppContext::e($ctx->t('Subject prefix')) . '</th><th>' . AppContext::e($ctx->t('Created')) . '</th><th>' . AppContext::e($ctx->t('Sending period')) . '</th><th>' . AppContext::e($ctx->t('Actions')) . '</th></tr></thead><tbody>' . $rows . '</tbody></table></div>' . $extraHtml;
|
||||
}
|
||||
|
||||
function render_rules_index(AppContext $ctx, array $ok = [], array $errors = [], string $extraHtml = ''): void
|
||||
{
|
||||
$ctx->render('Global autoresponders', render_rules_index_body($ctx, $extraHtml), $ok, $errors);
|
||||
}
|
||||
|
||||
function render_calendar_picker(AppContext $ctx, string $name, string $title, string $value, bool $enforceMinDate = true): string
|
||||
{
|
||||
$mode = $ctx->settings->scheduleMode();
|
||||
$columnClass = $name === 'start_value' ? 'schedule-start' : 'schedule-end';
|
||||
$timeLabel = $ctx->t($name === 'start_value' ? 'Start hour' : 'End hour');
|
||||
$selectedDate = substr($value, 0, 10);
|
||||
if (!preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $selectedDate)) {
|
||||
$selectedDate = date('Y-m-d');
|
||||
@@ -147,12 +368,12 @@ 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');
|
||||
$minDate = (new DateTimeImmutable('today', new DateTimeZone($ctx->settings->timezone())))->format('Y-m-d');
|
||||
$minDate = $enforceMinDate ? (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') {
|
||||
$timeControl = '<label>' . AppContext::e($ctx->t('Time')) . '</label><input type="time" name="' . AppContext::e($name . '_time') . '" value="' . AppContext::e($selectedTime) . '" step="60" data-time-for="' . AppContext::e($name) . '">';
|
||||
$timeControl = '<label>' . AppContext::e($timeLabel) . '</label><input type="time" name="' . AppContext::e($name . '_time') . '" value="' . AppContext::e($selectedTime) . '" step="60" data-time-for="' . AppContext::e($name) . '">';
|
||||
} else {
|
||||
$timeControl = '<label>' . AppContext::e($ctx->t('Time of day')) . '</label><select name="' . AppContext::e($name . '_period') . '" data-period-for="' . AppContext::e($name) . '">' .
|
||||
'<option value="morning"' . ($selectedPeriod === 'morning' ? ' selected' : '') . '>' . AppContext::e($ctx->t('Morning')) . '</option>' .
|
||||
@@ -160,10 +381,10 @@ 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) . '" 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>' .
|
||||
return '<div class="schedule-column ' . AppContext::e($columnClass) . '"><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>' .
|
||||
'<div class="br-time-picker">' . $timeControl . '</div>' .
|
||||
'<input type="hidden" id="' . AppContext::e($name) . '" name="' . AppContext::e($name) . '" value="' . AppContext::e($value) . '" data-canonical-value="' . AppContext::e($name) . '"></div>';
|
||||
}
|
||||
|
||||
@@ -196,7 +417,7 @@ 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) {
|
||||
if ($minDate !== '' && $date < $minDate) {
|
||||
$html .= '<td><span class="br-cal-day-disabled">' . $day . '</span></td>';
|
||||
$day++;
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user