#15 - maintainer email template
This commit was merged in pull request #20.
This commit is contained in:
@@ -6,6 +6,10 @@ The form definition is an array of element it contains. Each element has a *type
|
||||
|
||||
Keep the form structure and content separate -> when working with the form, take time to figure, if you are working with structure, or existence of something within.
|
||||
|
||||
## Rendering
|
||||
|
||||
The rendering currently happens on the backend, but that might be wrong. It renders out structure, but interactive components gets rendered on the frontend anyway.
|
||||
|
||||
## Submitting
|
||||
|
||||
When user submits the form, the `RsvFormSubmitter.js` is called. It collects the values, which we describe in more detail, and then sends it using `fetch` as POST to `reservations/forms/{form_id}`.
|
||||
|
||||
@@ -8,10 +8,12 @@ use Reservair\Templating\RsvTemplateEngine;
|
||||
*
|
||||
* Two responsibilities:
|
||||
* 1. Maintainer approval request — fires per timetable item that needs
|
||||
* confirmation; template is hardcoded (not admin-configurable).
|
||||
* confirmation, carrying the accept/refuse links the maintainer acts on.
|
||||
* 2. User notification on form close — fires once per form submission when
|
||||
* every reservation item reaches a terminal state. Subject/body come from
|
||||
* the form definition's reservation element `email_templates` attr.
|
||||
* every reservation item reaches a terminal state.
|
||||
*
|
||||
* Both subjects/bodies come from the reservation element's `email_templates`
|
||||
* attr, falling back to the hardcoded defaults when the admin left them blank.
|
||||
*
|
||||
* Exceptions are swallowed so a mail failure never rolls back a transaction.
|
||||
*/
|
||||
@@ -52,7 +54,15 @@ class RsvEmailListener {
|
||||
$start_dt = (clone $event->reservation->start_utc)->setTimezone($tz);
|
||||
$end_dt = (clone $event->reservation->end_utc)->setTimezone($tz);
|
||||
|
||||
$body = $engine->render(self::DEFAULT_PENDING_BODY, [
|
||||
[$tpl, $form_values] = self::resolve_maintainer_template($event);
|
||||
|
||||
// Treat blank (admin cleared the field) the same as "use the default".
|
||||
$subject_tpl = trim((string) ($tpl['subject'] ?? '')) !== '' ? $tpl['subject'] : self::DEFAULT_PENDING_SUBJECT;
|
||||
$body_tpl = trim((string) ($tpl['body'] ?? '')) !== '' ? $tpl['body'] : self::DEFAULT_PENDING_BODY;
|
||||
|
||||
// Reservation context wins over form values on key collisions, so the
|
||||
// accept/refuse links can never be shadowed by a submitted field.
|
||||
$symbols = array_merge($form_values, [
|
||||
'reservation_id' => (string) $event->reservation_id,
|
||||
'date' => $start_dt->format('Y-m-d'),
|
||||
'start' => $start_dt->format('H:i'),
|
||||
@@ -61,12 +71,59 @@ class RsvEmailListener {
|
||||
'refuse_url' => get_rest_url(null, 'reservations/v1/timetable-reservation/refuse/' . $event->code),
|
||||
]);
|
||||
|
||||
(new RsvEmailSender())->send($event->maintainer_email, self::DEFAULT_PENDING_SUBJECT, $body);
|
||||
$subject = sanitize_text_field($engine->render_plain($subject_tpl, $symbols));
|
||||
$body = $engine->render($body_tpl, $symbols);
|
||||
|
||||
(new RsvEmailSender())->send($event->maintainer_email, $subject, $body);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the admin-authored maintainer template for the timetable this
|
||||
* reservation belongs to, together with the submission's field values so the
|
||||
* template can reference the submitter's answers.
|
||||
*
|
||||
* @return array{0: array<string, mixed>, 1: array<string, mixed>} The
|
||||
* maintainer template (empty when none is configured) and form values.
|
||||
*/
|
||||
private static function resolve_maintainer_template(RsvTimetableReservationPendingEvent $event): array {
|
||||
$form_submit_id = (new RsvReservationRepository())->get_form_submit_id($event->reservation_id);
|
||||
if ($form_submit_id === null) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
$form_submit = (new RsvFormSubmitRepository())->get($form_submit_id);
|
||||
if ($form_submit === null) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
$form_values = is_array($form_submit['values'] ?? null) ? $form_submit['values'] : [];
|
||||
|
||||
$definition = (new RsvFormDefinitionRepository())->get((int) $form_submit['form_id']);
|
||||
if ($definition === null) {
|
||||
return [[], $form_values];
|
||||
}
|
||||
|
||||
$elements = $definition['definition']['elements'] ?? [];
|
||||
$reservation_element = null;
|
||||
foreach ($elements as $el) {
|
||||
if (!is_array($el) || ($el['type'] ?? '') !== 'reservation') {
|
||||
continue;
|
||||
}
|
||||
$reservation_element ??= $el; // fall back to the first reservation element
|
||||
if ((int) ($el['timetable_id'] ?? 0) === $event->reservation->timetable_id) {
|
||||
$reservation_element = $el;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$template = $reservation_element['email_templates']['maintainer'] ?? [];
|
||||
|
||||
return [is_array($template) ? $template : [], $form_values];
|
||||
}
|
||||
|
||||
public static function on_form_submit_closed(RsvFormSubmitClosedEvent $event): void {
|
||||
try {
|
||||
$form_submit = (new RsvFormSubmitRepository())->get($event->form_submit_id);
|
||||
|
||||
@@ -167,6 +167,8 @@ class RsvFormsPage extends RsvAdminPage {
|
||||
accepted_body: <?= json_encode("<h1>Vaše rezervace byla přijata</h1>\n<p>Vaše rezervace byla schválena. Těšíme se na vás!</p>") ?>,
|
||||
refused_subject: <?= json_encode('Rezervace zamítnuta') ?>,
|
||||
refused_body: <?= json_encode("<h1>Vaše rezervace byla zamítnuta</h1>\n<p>Vaše rezervace bohužel nebyla schválena. Zkuste prosím jiný termín.</p>") ?>,
|
||||
maintainer_subject: <?= json_encode('Nová rezervace čeká na schválení') ?>,
|
||||
maintainer_body: <?= json_encode("<h1>Nový požadavek o rezervaci</h1>\n<p>Rezervace č. {{reservation_id}} čeká na vaše schválení.</p>\n<p><strong>Datum:</strong> {{date}}</p>\n<p><strong>Čas:</strong> {{start}} – {{end}}</p>\n<p><reservation-actions></reservation-actions></p>") ?>,
|
||||
};
|
||||
|
||||
const rsv_elements_source = (function(initial_items, next_id_start) {
|
||||
@@ -205,6 +207,10 @@ class RsvFormsPage extends RsvAdminPage {
|
||||
timetable_id: data.timetable_id ? parseInt(data.timetable_id) : null,
|
||||
price_per_block: parseFloat(data.price_per_block ?? '0') || 0,
|
||||
email_templates: {
|
||||
maintainer: {
|
||||
subject: data.email_maintainer_subject ?? RSV_EMAIL_DEFAULTS.maintainer_subject,
|
||||
body: data.email_maintainer_body ?? RSV_EMAIL_DEFAULTS.maintainer_body,
|
||||
},
|
||||
on_accepted: {
|
||||
enabled: !!data.email_accepted_enabled,
|
||||
subject: data.email_accepted_subject ?? RSV_EMAIL_DEFAULTS.accepted_subject,
|
||||
@@ -298,8 +304,9 @@ class RsvFormsPage extends RsvAdminPage {
|
||||
.input_checkbox('required', 'Required', data?.required ?? false);
|
||||
|
||||
if ((data?.type ?? rsv_element_types[0]) === 'reservation') {
|
||||
const accepted = data?.email_templates?.on_accepted ?? {};
|
||||
const refused = data?.email_templates?.on_refused ?? {};
|
||||
const maintainer = data?.email_templates?.maintainer ?? {};
|
||||
const accepted = data?.email_templates?.on_accepted ?? {};
|
||||
const refused = data?.email_templates?.on_refused ?? {};
|
||||
const timetable_options = [
|
||||
{ value: '', label: '— none —' },
|
||||
...rsv_timetables.map(t => ({ value: t.id, label: t.name })),
|
||||
@@ -307,6 +314,9 @@ class RsvFormsPage extends RsvAdminPage {
|
||||
builder
|
||||
.input_select('timetable_id', 'Timetable', timetable_options, data?.timetable_id ?? '')
|
||||
.input_number('price_per_block', 'Price per block', data?.price_per_block ?? 0)
|
||||
.fieldset('Email - for maintainer', '100%')
|
||||
.input_text('email_maintainer_subject', 'Subject', maintainer.subject ?? RSV_EMAIL_DEFAULTS.maintainer_subject)
|
||||
.input_textarea('email_maintainer_body', 'Body', maintainer.body ?? RSV_EMAIL_DEFAULTS.maintainer_body)
|
||||
.fieldset('Email — accepted', '100%')
|
||||
.input_checkbox('email_accepted_enabled', 'Send email when accepted', accepted.enabled ?? true)
|
||||
.input_text('email_accepted_subject', 'Subject', accepted.subject ?? RSV_EMAIL_DEFAULTS.accepted_subject)
|
||||
|
||||
Reference in New Issue
Block a user