Repository navigation
Conversation
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-opus-4-8
Add TwigEnvironmentFactory (PSR-4, src/) that creates a Twig\Environment with html autoescaping, and a TwigRenderer global class (legacy tree) implementing TemplateRenderer. Widen PageValidators constructor hint to accept SmartyPage|TwigRenderer. Register TwigRenderer in namespace.php and add tpl_c/twig/ to .gitignore. Assisted-by: Claude:claude-sonnet-4-6
Add an empty Twig AbstractExtension wired into TwigRenderer, exposing getFunctions() and getFilters() stubs ready for Phase 1 tasks. Assisted-by: Claude:claude-sonnet-4-6
Add protected $renderer (TemplateRenderer) to Page; wire it to a SmartyRenderer in the constructor and keep $this->smarty pointing at the inner SmartyPage for full BC. Zero behaviour change. Assisted-by: Claude:claude-sonnet-4-6
Wire SmartyRenderer into EmailMessage so $this->renderer holds the TemplateRenderer contract; $this->email kept as a BC alias to the underlying SmartyPage. Zero behaviour change. Assisted-by: Claude:claude-sonnet-4-6
Control.__construct now accepts TemplateRenderer|SmartyPage; raw SmartyPage is wrapped via SmartyRenderer::wrap() (preserves the existing page instance). $this->data changes from \Smarty\Data to a plain array; Set/Get/Display updated. All concrete controls' constructors updated to match the widened type. Assisted-by: Claude:claude-sonnet-4-6
Strengthen ControlRendererTest beyond smoke-test construction: verify Set/Get round-trip (including null for missing keys), Display output via a fake TemplateRenderer recording call args and returning a sentinel, and that a raw SmartyPage is wrapped into a SmartyRenderer on construction. Assisted-by: Claude:claude-sonnet-4-6
Replace every new SmartyPage() constructor call inside DashboardPresenter::Initialize() with new SmartyRenderer(). SmartyRenderer wraps SmartyPage and implements TemplateRenderer, so behaviour is identical — this routes all dashboard controls through the renderer abstraction layer. Adds DashboardPresenterRendererTest as a guard test verifying each control accepts and stores a SmartyRenderer instance. Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Twig's CoreExtension already provides a constant() function that
resolves
PHP constants including class constants (e.g.
CustomAttributeTypes::CHECKBOX).
Our custom TwigFunction was a redundant YAGNI override with a weaker
implementation (no object-form, no sandbox awareness).
Remove the custom constant() TwigFunction from LibreBookingExtension and
update the test to prove Twig's native constant() suffices — rendering
{{ constant('CustomAttributeTypes::CHECKBOX') }} through an environment
with only LibreBookingExtension added passes without any custom
function.
Assisted-by: Claude:claude-sonnet-4-6
Register translate() in LibreBookingExtension.getFunctions(), matching
SmartyPage::SmartyTranslate semantics: no args → GetString(key, '');
string args → GetString(key, explode(',', args)); array args passed
directly. Marked is_safe=>html so callers get unescaped HTML strings,
consistent with the Smarty equivalent.
Assisted-by: Claude:claude-sonnet-4-6
Extract linkify logic from SmartyPage::CreateUrl into a PSR-4 static helper (LibreBooking\Common\Text\LinkifyText) so both engines share one implementation. Add custom Twig filters sanitize_rich_text, url2link, escapequotes, html_entity_decode, intval to LibreBookingExtension. Confirm Twig natives lower/url_encode/length cover the strtolower, urlencode, count Smarty modifiers — no custom duplicates added. Assisted-by: Claude:claude-sonnet-4-6
Add a custom |urlencode Twig filter backed by PHP urlencode() (space → '+'), matching SmartyPage::UrlEncode exactly. Twig's native |url_encode uses rawurlencode (space → '%20') and is a different filter name, so both coexist without conflict. Tests confirm the space → '+' distinction and byte-identical output vs SmartyPage::UrlEncode. Assisted-by: Claude:claude-sonnet-4-6
5beb615 to
885b4ca
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved template issues omit Thai reminder descriptions, corrupt subscription URLs through missing HTML escaping, and double-escape stored terms text.
4 open issues
What changed in this PR
Migrates LibreBooking’s templates to Twig while retaining Smarty fallback when no Twig counterpart exists. The migration covers page, control, Ajax, administrative, CSV, and localized email rendering.
Changes:
- Adds a shared renderer abstraction, automatic engine selection, and Twig equivalents of Smarty functions and filters.
- Ports templates and adapts page, validation, and control integration points.
- Adds rendering-parity tests, golden fixtures, Twig linting, and editor/cache configuration.
This pull request requires changes. The 16 unresolved moderate findings concern double-escaping in ten resource-status email templates, terms, resource names, and group names; incorrect calendar subscription URL escaping; and a missing description in Thai start reminders.
| File | Description |
|---|---|
tpl/wait-box.twig |
Twig loading dialog. |
tpl/tos.twig |
Terms display; unresolved double-escaping at line 3. |
tpl/ResourceDisplay/resource-display-not-enabled.twig |
Disabled resource-display message. |
tpl/ResourceDisplay/resource-display-instructions.twig |
Resource-display setup guidance. |
tpl/Reservation/private-participation.twig |
Private-participation placeholder. |
tpl/Reservation/pdf.twig |
Reservation PDF configuration script. |
tpl/Reservation/pdf_libraries.twig |
PDF library includes. |
tpl/Reservation/attachment-error.twig |
Attachment error page. |
tpl/Reports/error.twig |
Report-generation error message. |
tpl/Reports/chart.twig |
Report chart container and assets. |
tpl/maintenance.twig |
Maintenance page. |
tpl/json_data.twig |
JSON response output. |
tpl/guest-participation.twig |
Guest invitation response page. |
tpl/ExternalAuth/external-login-error.twig |
External authentication error page. |
tpl/Export/embedded-calendar-container.twig |
Embedded calendar wrapper. |
tpl/error.twig |
General error page. |
tpl/Email/emailheader.twig |
Shared email header. |
tpl/Email/emailfooter.twig |
Shared email footer. |
tpl/Controls/Checkbox.twig |
Checkbox control markup. |
tpl/Calendar/mycalendar.twig |
Personal calendar entry template. |
tpl/Calendar/mycalendar.subscription.twig |
Personal subscription controls; missing outer HTML escaping at line 9. |
tpl/Calendar/calendar.twig |
Resource calendar entry template. |
tpl/Calendar/calendar.subscription.twig |
Calendar subscription controls; missing outer HTML escaping at line 5. |
tpl/Ajax/reservation/waitlist_added.twig |
Waitlist confirmation. |
tpl/Ajax/reservation/update_successful.twig |
Reservation update confirmation. |
tpl/Ajax/reservation/reservation_error.twig |
Critical reservation error response. |
tpl/Ajax/reservation/reservation_attributes.twig |
Reservation attribute controls. |
tpl/Ajax/reservation/reservation_attributes_print.twig |
Printable attribute JSON output. |
tpl/Ajax/reservation/delete_successful.twig |
Reservation deletion confirmation. |
tpl/Ajax/reservation/delete_failed.twig |
Reservation deletion errors. |
tpl/Ajax/reservation/checkin_successful.twig |
Check-in/out confirmation. |
tpl/Ajax/reservation/checkin_failed.twig |
Check-in/out errors. |
tpl/Admin/Users/import_user_template_csv.twig |
User-import CSV headers. |
tpl/Admin/Schedules/manage_availability.twig |
Schedule availability display. |
tpl/Admin/Resources/show_resource_qr.twig |
Resource QR page; resource-name double-escaping at line 5. |
tpl/Admin/Resources/manage_resources_groups.twig |
Resource-group display; group-name double-escaping at line 6. |
tpl/Admin/Resources/manage_resources_credits.twig |
Resource credit usage display. |
tpl/Admin/Resources/manage_resources_capacity.twig |
Resource capacity display. |
tpl/Admin/Resources/import_resource_template_csv.twig |
Resource-import CSV headers. |
tpl/Admin/Reservations/import_reservations_template_csv.twig |
Reservation-import CSV headers. |
tpl/Admin/Groups/groups_csv.twig |
Group CSV export. |
tpl/Activation/activation-sent.twig |
Activation-email confirmation page. |
tpl/Activation/activation-error.twig |
Account activation error page. |
tpl/_render_fallback_probe.tpl |
Smarty-only fallback test fixture. |
tests/Web/InstallEntryPointTest.php |
Accepts either HTML attribute quote style. |
tests/src/Common/Templating/TwigEnvironmentFactoryTest.php |
Tests Twig autoescaping. |
tests/src/Common/Templating/TemplateRendererTest.php |
Tests renderer interface methods. |
tests/Pages/Reports/CustomCsvTemplateTest.php |
Tests CSV exports through Twig. |
tests/Pages/fixtures/schedule-block-only/Schedule/schedule.twig |
Twig schedule test parent. |
tests/Pages/fixtures/schedule-block-only/Schedule/schedule.tpl |
Removes Smarty schedule test parent. |
tests/Infrastructure/Common/SmartyRendererTest.php |
Tests Smarty adapter compatibility. |
tests/Golden/fixtures/wait-box.php |
Loading-dialog parity fixture. |
tests/Golden/fixtures/tos.php |
Terms-page parity fixture. |
tests/Golden/fixtures/support-and-credits.php |
Support-page parity fixture. |
tests/Golden/fixtures/search-reservations.php |
Reservation-search parity fixture. |
tests/Golden/fixtures/password.php |
Password-page parity fixture. |
tests/Golden/fixtures/participation.php |
Participation-page parity fixture. |
tests/Golden/fixtures/notification-preferences.php |
Notification-preferences parity fixture. |
tests/Golden/fixtures/myaccount-base.php |
Shared account-page fixture variables. |
tests/Golden/fixtures/javascript-includes.php |
Basic JavaScript include fixture. |
tests/Golden/fixtures/javascript-includes_full.php |
Full JavaScript include fixture. |
tests/Golden/fixtures/guest-participation.php |
Guest-participation parity fixture. |
tests/Golden/fixtures/globalfooter.php |
Basic footer parity fixture. |
tests/Golden/fixtures/globalfooter_full.php |
Full footer parity fixture. |
tests/Golden/fixtures/forgot_pwd.php |
Password-recovery parity fixture. |
tests/Golden/fixtures/dashboard.php |
Dashboard parity fixture. |
tests/Golden/fixtures/.gitkeep |
Fixture-directory placeholder. |
tests/Golden/baselines/wait-box.html |
Normalized loading-dialog baseline. |
tests/Golden/baselines/.gitkeep |
Baseline-directory placeholder. |
phpunit.xml.dist |
Registers the golden test suite. |
Pages/Ajax/ResourceDetailsPage.php |
Uses engine-selecting page display. |
Pages/Admin/ManageUsersPage.php |
Renames the page-specific rendering hook. |
Pages/Admin/GroupAdminManageUsersPage.php |
Updates the overridden rendering hook. |
lib/Common/Validators/PageValidators.php |
Accepts Twig-backed validation owners. |
lib/Common/namespace.php |
Loads renderer integration classes. |
lang/zh_cn/StartReminderEmail.twig |
Localized start-reminder email. |
lang/zh_cn/ResetPassword.twig |
Localized password-reset email. |
lang/zh_cn/ReservationAvailable.twig |
Localized availability email. |
lang/zh_cn/ReportEmail.twig |
Localized report email. |
lang/zh_cn/InviteUser.twig |
Localized user invitation email. |
lang/zh_cn/GuestAccountCreation.twig |
Localized guest-account email. |
lang/zh_cn/EndReminderEmail.twig |
Localized end-reminder email. |
lang/zh_cn/AnnouncementEmail.twig |
Localized announcement email. |
lang/zh_cn/AccountCreationForUser.twig |
Localized account-details email. |
lang/zh_cn/AccountCreation.twig |
Localized registration notification. |
lang/vn_vn/StartReminderEmail.twig |
Localized start-reminder email. |
lang/vn_vn/ResetPassword.twig |
Localized password-reset email. |
lang/vn_vn/ReservationAvailable.twig |
Localized availability email. |
lang/vn_vn/ReportEmail.twig |
Localized report email. |
lang/vn_vn/GuestAccountCreation.twig |
Localized guest-account email. |
lang/vn_vn/EndReminderEmail.twig |
Localized end-reminder email. |
lang/vn_vn/AnnouncementEmail.twig |
Localized announcement email. |
lang/vn_vn/AccountCreation.twig |
Localized registration notification. |
lang/vn_vn/AccountActivation.twig |
Localized account activation email. |
lang/th_th/StartReminderEmail.twig |
Thai start reminder; incorrect description variable at line 9. |
lang/th_th/ResetPassword.twig |
Localized password-reset email. |
lang/th_th/ReservationAvailable.twig |
Localized availability email. |
lang/th_th/ReportEmail.twig |
Localized report email. |
lang/th_th/MissedCheckinEmail.twig |
Localized missed-check-in email. |
lang/th_th/InviteUser.twig |
Localized user invitation email. |
lang/th_th/GuestAccountCreation.twig |
Localized guest-account email. |
lang/th_th/EndReminderEmail.twig |
Localized end-reminder email. |
lang/th_th/AnnouncementEmail.twig |
Localized announcement email. |
lang/th_th/AccountCreationForUser.twig |
Localized account-details email. |
lang/th_th/AccountCreation.twig |
Localized registration notification. |
lang/th_th/AccountActivation.twig |
Localized account activation email. |
lang/sv_sv/ResetPassword.twig |
Localized password-reset email. |
lang/sv_sv/AccountActivation.twig |
Localized account activation email. |
lang/sr_sr/StartReminderEmail.twig |
Localized start-reminder email. |
lang/sr_sr/ResetPassword.twig |
Localized password-reset email. |
lang/sr_sr/ReportEmail.twig |
Localized report email. |
lang/sr_sr/EndReminderEmail.twig |
Localized end-reminder email. |
lang/sr_sr/AccountCreation.twig |
Localized registration notification. |
lang/sr_sr/AccountActivation.twig |
Localized account activation email. |
lang/sk/StartReminderEmail.twig |
Localized start-reminder email. |
lang/sk/ResetPassword.twig |
Localized password-reset email. |
lang/sk/ReportEmail.twig |
Localized report email. |
lang/sk/EndReminderEmail.twig |
Localized end-reminder email. |
lang/sk/AccountActivation.twig |
Localized account activation email. |
lang/si_si/StartReminderEmail.twig |
Localized start-reminder email. |
lang/si_si/ResetPassword.twig |
Localized password-reset email. |
lang/si_si/ReportEmail.twig |
Localized report email. |
lang/si_si/EndReminderEmail.twig |
Localized end-reminder email. |
lang/si_si/AccountCreation.twig |
Localized registration notification. |
lang/si_si/AccountActivation.twig |
Localized account activation email. |
lang/pt_pt/StartReminderEmail.twig |
Localized start-reminder email. |
lang/pt_pt/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 3. |
lang/pt_pt/ResetPassword.twig |
Localized password-reset email. |
lang/pt_pt/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/pt_pt/ReservationAvailable.twig |
Localized availability email. |
lang/pt_pt/ReportEmail.twig |
Localized report email. |
lang/pt_pt/InviteUser.twig |
Localized user invitation email. |
lang/pt_pt/GuestAccountCreation.twig |
Localized guest-account email. |
lang/pt_pt/EndReminderEmail.twig |
Localized end-reminder email. |
lang/pt_pt/AnnouncementEmail.twig |
Localized announcement email. |
lang/pt_pt/AccountDeleted.twig |
Localized account-deletion email. |
lang/pt_pt/AccountCreationForUser.twig |
Localized account-details email. |
lang/pt_pt/AccountCreation.twig |
Localized registration notification. |
lang/pt_pt/AccountActivation.twig |
Localized account activation email. |
lang/pt_br/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 6. |
lang/pt_br/ResetPassword.twig |
Localized password-reset email. |
lang/pt_br/ReservationAvailable.twig |
Localized availability email. |
lang/pt_br/ReportEmail.twig |
Localized report email. |
lang/pt_br/InviteUser.twig |
Localized user invitation email. |
lang/pt_br/GuestAccountCreation.twig |
Localized guest-account email. |
lang/pt_br/AnnouncementEmail.twig |
Localized announcement email. |
lang/pt_br/AccountDeleted.twig |
Localized account-deletion email. |
lang/pt_br/AccountCreationForUser.twig |
Localized account-details email. |
lang/pt_br/AccountCreation.twig |
Localized registration notification. |
lang/pt_br/AccountActivation.twig |
Localized account activation email. |
lang/pl/StartReminderEmail.twig |
Localized start-reminder email. |
lang/pl/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 3. |
lang/pl/ResetPassword.twig |
Localized password-reset email. |
lang/pl/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/pl/ReservationAvailable.twig |
Localized availability email. |
lang/pl/ReportEmail.twig |
Localized report email. |
lang/pl/InviteUser.twig |
Localized user invitation email. |
lang/pl/GuestAccountCreation.twig |
Localized guest-account email. |
lang/pl/EndReminderEmail.twig |
Localized end-reminder email. |
lang/pl/AnnouncementEmail.twig |
Localized announcement email. |
lang/pl/AccountDeleted.twig |
Localized account-deletion email. |
lang/pl/AccountCreationForUser.twig |
Localized account-details email. |
lang/pl/AccountCreation.twig |
Localized registration notification. |
lang/pl/AccountActivation.twig |
Localized account activation email. |
lang/ja_jp/StartReminderEmail.twig |
Localized start-reminder email. |
lang/ja_jp/ResetPassword.twig |
Localized password-reset email. |
lang/ja_jp/ReportEmail.twig |
Localized report email. |
lang/ja_jp/EndReminderEmail.twig |
Localized end-reminder email. |
lang/ja_jp/AccountCreation.twig |
Localized registration notification. |
lang/ja_jp/AccountActivation.twig |
Localized account activation email. |
lang/it_it/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 2. |
lang/it_it/ResetPassword.twig |
Localized password-reset email. |
lang/it_it/ReservationAvailable.twig |
Localized availability email. |
lang/it_it/ReportEmail.twig |
Localized report email. |
lang/it_it/InviteUser.twig |
Localized user invitation email. |
lang/it_it/GuestAccountCreation.twig |
Localized guest-account email. |
lang/it_it/AnnouncementEmail.twig |
Localized announcement email. |
lang/it_it/AccountDeleted.twig |
Localized account-deletion email. |
lang/it_it/AccountCreationForUser.twig |
Localized account-details email. |
lang/it_it/AccountCreation.twig |
Localized registration notification. |
lang/it_it/AccountActivation.twig |
Localized account activation email. |
lang/id_id/StartReminderEmail.twig |
Localized start-reminder email. |
lang/id_id/ResetPassword.twig |
Localized password-reset email. |
lang/id_id/ReportEmail.twig |
Localized report email. |
lang/id_id/EndReminderEmail.twig |
Localized end-reminder email. |
lang/id_id/AccountActivation.twig |
Localized account activation email. |
lang/hu_hu/StartReminderEmail.twig |
Localized start-reminder email. |
lang/hu_hu/ResetPassword.twig |
Localized password-reset email. |
lang/hu_hu/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/hu_hu/ReservationAvailable.twig |
Localized availability email. |
lang/hu_hu/ReportEmail.twig |
Localized report email. |
lang/hu_hu/InviteUser.twig |
Localized user invitation email. |
lang/hu_hu/GuestAccountCreation.twig |
Localized guest-account email. |
lang/hu_hu/EndReminderEmail.twig |
Localized end-reminder email. |
lang/hu_hu/AnnouncementEmail.twig |
Localized announcement email. |
lang/hu_hu/AccountCreationForUser.twig |
Localized account-details email. |
lang/hu_hu/AccountCreation.twig |
Localized registration notification. |
lang/hu_hu/AccountActivation.twig |
Localized account activation email. |
lang/hr_hr/StartReminderEmail.twig |
Localized start-reminder email. |
lang/hr_hr/ResetPassword.twig |
Localized password-reset email. |
lang/hr_hr/ReportEmail.twig |
Localized report email. |
lang/hr_hr/EndReminderEmail.twig |
Localized end-reminder email. |
lang/hr_hr/AccountCreation.twig |
Localized registration notification. |
lang/hr_hr/AccountActivation.twig |
Localized account activation email. |
lang/he/ReportEmail.twig |
Localized report email. |
lang/he/AccountActivation.twig |
Localized account activation email. |
lang/fr_fr/StartReminderEmail.twig |
Localized start-reminder email. |
lang/fr_fr/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 3. |
lang/fr_fr/ResetPassword.twig |
Localized password-reset email. |
lang/fr_fr/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/fr_fr/ReservationAvailable.twig |
Localized availability email. |
lang/fr_fr/ReportEmail.twig |
Localized report email. |
lang/fr_fr/InviteUser.twig |
Localized user invitation email. |
lang/fr_fr/GuestAccountCreation.twig |
Localized guest-account email. |
lang/fr_fr/EndReminderEmail.twig |
Localized end-reminder email. |
lang/fr_fr/AnnouncementEmail.twig |
Localized announcement email. |
lang/fr_fr/AccountDeleted.twig |
Localized account-deletion email. |
lang/fr_fr/AccountCreationForUser.twig |
Localized account-details email. |
lang/fr_fr/AccountCreation.twig |
Localized registration notification. |
lang/fr_fr/AccountActivation.twig |
Localized account activation email. |
lang/fi_fi/StartReminderEmail.twig |
Localized start-reminder email. |
lang/fi_fi/ResourceStatusChanged.twig |
Localized resource-status email. |
lang/fi_fi/ResetPassword.twig |
Localized password-reset email. |
lang/fi_fi/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/fi_fi/ReservationAvailable.twig |
Localized availability email. |
lang/fi_fi/ReportEmail.twig |
Localized report email. |
lang/fi_fi/InviteUser.twig |
Localized user invitation email. |
lang/fi_fi/GuestAccountCreation.twig |
Localized guest-account email. |
lang/fi_fi/AnnouncementEmail.twig |
Localized announcement email. |
lang/fi_fi/AccountDeleted.twig |
Localized account-deletion email. |
lang/fi_fi/AccountCreationForUser.twig |
Localized account-details email. |
lang/fi_fi/AccountCreation.twig |
Localized registration notification. |
lang/fi_fi/AccountActivation.twig |
Localized account activation email. |
lang/eu_es/StartReminderEmail.twig |
Localized start-reminder email. |
lang/eu_es/ResetPassword.twig |
Localized password-reset email. |
lang/eu_es/ReportEmail.twig |
Localized report email. |
lang/eu_es/EndReminderEmail.twig |
Localized end-reminder email. |
lang/eu_es/AccountCreation.twig |
Localized registration notification. |
lang/eu_es/AccountActivation.twig |
Localized account activation email. |
lang/es/StartReminderEmail.twig |
Localized start-reminder email. |
lang/es/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 3. |
lang/es/ResetPassword.twig |
Localized password-reset email. |
lang/es/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/es/ReservationAvailable.twig |
Localized availability email. |
lang/es/ReportEmail.twig |
Localized report email. |
lang/es/InviteUser.twig |
Localized user invitation email. |
lang/es/GuestAccountCreation.twig |
Localized guest-account email. |
lang/es/EndReminderEmail.twig |
Localized end-reminder email. |
lang/es/AnnouncementEmail.twig |
Localized announcement email. |
lang/es/AccountDeleted.twig |
Localized account-deletion email. |
lang/es/AccountCreationForUser.twig |
Localized account-details email. |
lang/es/AccountCreation.twig |
Localized registration notification. |
lang/es/AccountActivation.twig |
Localized account activation email. |
lang/en_us/StartReminderEmail.twig |
English start-reminder email. |
lang/en_us/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 3. |
lang/en_us/ResetPassword.twig |
English password-reset email. |
lang/en_us/ReservationSeriesEnding.twig |
English recurring-series reminder. |
lang/en_us/ReservationAvailable.twig |
English availability email. |
lang/en_us/ReportEmail.twig |
English report email. |
lang/en_us/InviteUser.twig |
English user invitation email. |
lang/en_us/GuestAccountCreation.twig |
English guest-account email. |
lang/en_us/EndReminderEmail.twig |
English end-reminder email. |
lang/en_us/AnnouncementEmail.twig |
English announcement email. |
lang/en_us/AccountDeleted.twig |
English account-deletion email. |
lang/en_us/AccountCreationForUser.twig |
English account-details email. |
lang/en_us/AccountCreation.twig |
English registration notification. |
lang/en_us/AccountActivation.twig |
English account activation email. |
lang/el_gr/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 3. |
lang/el_gr/ResetPassword.twig |
Localized password-reset email. |
lang/el_gr/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/el_gr/ReservationAvailable.twig |
Localized availability email. |
lang/el_gr/ReportEmail.twig |
Localized report email. |
lang/el_gr/InviteUser.twig |
Localized user invitation email. |
lang/el_gr/GuestAccountCreation.twig |
Localized guest-account email. |
lang/el_gr/AnnouncementEmail.twig |
Localized announcement email. |
lang/el_gr/AccountDeleted.twig |
Localized account-deletion email. |
lang/el_gr/AccountCreationForUser.twig |
Localized account-details email. |
lang/el_gr/AccountCreation.twig |
Localized registration notification. |
lang/el_gr/AccountActivation.twig |
Localized account activation email. |
lang/ee_ee/StartReminderEmail.twig |
Localized start-reminder email. |
lang/ee_ee/ResetPassword.twig |
Localized password-reset email. |
lang/ee_ee/ReservationAvailable.twig |
Localized availability email. |
lang/ee_ee/ReportEmail.twig |
Localized report email. |
lang/ee_ee/InviteUser.twig |
Localized user invitation email. |
lang/ee_ee/GuestAccountCreation.twig |
Localized guest-account email. |
lang/ee_ee/EndReminderEmail.twig |
Localized end-reminder email. |
lang/ee_ee/AnnouncementEmail.twig |
Localized announcement email. |
lang/ee_ee/AccountCreation.twig |
Localized registration notification. |
lang/ee_ee/AccountActivation.twig |
Localized account activation email. |
lang/du_nl/StartReminderEmail.twig |
Localized start-reminder email. |
lang/du_nl/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 3. |
lang/du_nl/ResetPassword.twig |
Localized password-reset email. |
lang/du_nl/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/du_nl/ReservationAvailable.twig |
Localized availability email. |
lang/du_nl/ReportEmail.twig |
Localized report email. |
lang/du_nl/InviteUser.twig |
Localized user invitation email. |
lang/du_nl/GuestAccountCreation.twig |
Localized guest-account email. |
lang/du_nl/EndReminderEmail.twig |
Localized end-reminder email. |
lang/du_nl/AccountDeleted.twig |
Localized account-deletion email. |
lang/du_nl/AccountCreationForUser.twig |
Localized account-details email. |
lang/du_nl/AccountCreation.twig |
Localized registration notification. |
lang/du_nl/AccountActivation.twig |
Localized account activation email. |
lang/de_de/ResourceStatusChanged.twig |
Resource-status email; message double-escaping at line 7. |
lang/de_de/ResetPassword.twig |
Localized password-reset email. |
lang/de_de/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/de_de/ReservationAvailable.twig |
Localized availability email. |
lang/de_de/ReportEmail.twig |
Localized report email. |
lang/de_de/InviteUser.twig |
Localized user invitation email. |
lang/de_de/GuestAccountCreation.twig |
Localized guest-account email. |
lang/de_de/EndReminderEmail.twig |
Localized end-reminder email. |
lang/de_de/AccountDeleted.twig |
Localized account-deletion email. |
lang/de_de/AccountCreationForUser.twig |
Localized account-details email. |
lang/de_de/AccountCreation.twig |
Localized registration notification. |
lang/de_de/AccountActivation.twig |
Localized account activation email. |
lang/da_da/StartReminderEmail.twig |
Localized start-reminder email. |
lang/da_da/ResetPassword.twig |
Localized password-reset email. |
lang/da_da/ReservationSeriesEnding.twig |
Localized recurring-series reminder. |
lang/da_da/ReservationAvailable.twig |
Localized availability email. |
lang/da_da/ReportEmail.twig |
Localized report email. |
lang/da_da/MissedCheckinEmail.twig |
Localized missed-check-in email. |
lang/da_da/InviteUser.twig |
Localized user invitation email. |
lang/da_da/GuestAccountCreation.twig |
Localized guest-account email. |
lang/da_da/EndReminderEmail.twig |
Localized end-reminder email. |
lang/da_da/AnnouncementEmail.twig |
Localized announcement email. |
lang/da_da/AccountDeleted.twig |
Localized account-deletion email. |
lang/da_da/AccountCreationForUser.twig |
Localized account-details email. |
lang/da_da/AccountCreation.twig |
Localized registration notification. |
lang/da_da/AccountActivation.twig |
Localized account activation email. |
lang/cz/StartReminderEmail.twig |
Localized start-reminder email. |
lang/cz/ResetPassword.twig |
Localized password-reset email. |
lang/cz/ReportEmail.twig |
Localized report email. |
lang/cz/EndReminderEmail.twig |
Localized end-reminder email. |
lang/cz/AccountCreation.twig |
Localized registration notification. |
lang/cz/AccountActivation.twig |
Localized account activation email. |
lang/bg_bg/ResetPassword.twig |
Localized password-reset email. |
lang/bg_bg/ReportEmail.twig |
Localized report email. |
lang/bg_bg/AccountActivation.twig |
Localized account activation email. |
Controls/DatePickerSetupControl.php |
Accepts shared renderer instances. |
Controls/Dashboard/ResourceAvailabilityControl.php |
Accepts renderer-backed dashboard rendering. |
Controls/Dashboard/PastReservations.php |
Updates reservation dashboard constructor types. |
Controls/Dashboard/DashboardItem.php |
Broadens the dashboard renderer contract. |
Controls/Dashboard/AnnouncementsControl.php |
Accepts renderer-backed announcement rendering. |
Controls/AttributeControl.php |
Accepts renderer-backed attribute rendering. |
.vscode/extensions.json |
Recommends Twig editor support. |
.gitignore |
Ignores Twig and formatter caches. |
Review effort: Balanced
885b4ca to
b3b6043
Compare
Add Group A HTML-emitting Twig functions to LibreBookingExtension: cancel_button, update_button, add_button, delete_button, reset_button, filter_button, ok_button, showhide_icon, indicator. Each function accepts an explicit `attributes` array for pass-through HTML attributes, reproducing SmartyPage::AppendAttributes output (insertion order, key="value" format). Parity tests compare Twig output to live SmartyPage method output via output-buffer capture. Assisted-by: Claude:claude-sonnet-4-6
Add Group B Twig functions to LibreBookingExtension: validator, async_validator, validation_group, textbox, object_html_options, setfocus, formname, csrf_token, read_only_attribute. Wire LibreBookingExtension to receive the TwigRenderer as an optional third constructor param so validator/textbox can access live PageValidators and template vars. TwigRenderer passes $this when constructing the extension. Assisted-by: Claude:claude-sonnet-4-6
Add Group C to LibreBookingExtension: html_link, add_querystring, sort_column, resource_image, fullname, formatdate/format_date, formatcurrency, js_array, linebreak, flush. All functions produce byte-identical output to the corresponding SmartyPage methods. Echo-only SmartyPage methods (sort_column, formatcurrency, flush) now return their string instead. Group B ServiceLocator tests extracted to their own file so PHPUnit discovers them correctly. Assisted-by: Claude:claude-sonnet-4-6
Adds jsfile, cssfile, vendor_js, vendor_css, datatable and datatablefilter to LibreBookingExtension (Group D). Reproduces GetDefaultDataTablePageSize and BuildDataTableLengthMenu as private helpers on the extension so output is byte-identical to SmartyPage. 23 parity tests in LibreBookingExtensionGroupDTest confirm this. Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Add EngineSelector (src/Common/Templating/EngineSelector.php) with a static twigNameFor() that maps a .tpl name to .twig and probes the given search dirs. Wire it into Page::Display(): when a .twig exists the page constructs a TwigRenderer, transfers all assigned vars and the live PageValidators, and renders via Twig; otherwise falls back to Smarty unchanged. A protected getTemplateSearchDirs() hook makes the logic testable without touching real template dirs. Assisted-by: Claude:claude-sonnet-4-6
Wire the email pipeline to TwigRenderer with Smarty fallback for not-yet-migrated templates: - TwigRenderer::fetch() now does engine-select: if a .twig counterpart of the requested .tpl exists in the Twig loader, render via Twig; otherwise delegate to the smartyFallback. - TwigRenderer::assign() now propagates variables to smartyFallback so Smarty fallback paths see the same data as the Twig path. - TwigRenderer::fetchLocalized() implements the full SmartyPage::FetchLocalized resolution logic (language code resolution, -custom override, enforce-custom default-language fallback, en_us fallback), then engine-selects: .twig at the resolved path uses Twig; otherwise delegates to smartyFallback. - EmailMessage::__construct() builds a TwigRenderer as the primary renderer instead of SmartyRenderer. The $this->email BC alias is preserved (a separate SmartyPage backed by the same Resources) for subclasses that call it directly (e.g. ReportEmailMessage::__construct). - Set(), FetchTemplate(), Translate(), Charset() now route through $this->renderer (TwigRenderer). Translate() uses Resources::GetString() directly, removing the SmartyTranslate dependency. - Migrate tpl/Email/emailheader.tpl and emailfooter.tpl to .twig counterparts (original .tpl files retained). The header/footer are now rendered via Twig while email body templates (lang/xx/*.tpl) continue to fall through to Smarty until individually migrated. Tests: - TwigRendererTest: add fetch() engine-select and fetchLocalized() fallback tests - EmailMessageRendererTest: verify TwigRenderer is primary, BC alias is SmartyPage, variable propagation and Charset() delegation - tests/Golden/EmailGoldenTest.php: parity tests for header/footer and smoke test for unmigrated body fallback Assisted-by: Claude:claude-sonnet-4-6
Add .twig counterparts for all 20 lang/en_us/*.tpl email bodies so that
TwigRenderer::fetchLocalized now selects the Twig engine for en_us.
Original .tpl files are kept; the 27 other language directories continue
using the Smarty fallback (Phase-5 backlog).
Key conversion decisions:
- User-data fields emitted raw in Smarty kept |raw for 1:1 parity
(security-backlog: review XSS hardening post-migration)
- Password (was |escape:'html' in Smarty) uses Twig autoescape naturally
- Rich-text fields (AnnouncementText, resource.description,
resource.notes,
Message) go through |sanitize_rich_text|url2link|nl2br|raw pipeline
- constant('InvitationAction::Decline') replaces bare PHP constant refs
- loop.last replaces $smarty.foreach.*.last
- control('AttributeControl', {...}) replaces {control type="..."} tag
Replace vacuous testFetchLocalizedSelectsTwigWhenTwigFileExists in
TwigRendererTest with a genuine test that calls fetchLocalized on a real
on-disk .twig file and asserts Twig output matches Smarty output.
Add EmailBodiesGoldenTest: live same-process Smarty-vs-Twig comparison
for all 20 bodies covering every conditional branch (39 test methods).
323 golden tests pass; 2243 full-suite tests pass; phpstan +
phpstan_next
both clean.
Assisted-by: Claude:claude-sonnet-4-6
Create attribute-list.twig and manage_attributes.twig alongside their
kept .tpl counterparts. Add AdminAttributesGoldenTest.php covering both
templates with 11 tests (attribute-list: 8 branches; manage_attributes:
full-page parity + 2 structural checks). Divergences noted:
- JS array joins (entityIds, entityDescriptions) use |raw in script
context to match Smarty's unescaped join separator behaviour.
- adminOnly rendered via {{ attribute.AdminOnly() }} (returns int 0/1)
to match Smarty's { $attribute->AdminOnly() } output.
- datatable() called with tableId conditional to reproduce Smarty's
unset-variable empty-string behaviour when Attributes list is empty.
Assisted-by: Claude:claude-sonnet-4-6
Add Twig counterparts for all three Admin/Import templates: - import.twig: hub page with ICS and Quartzy import links (full parity) - ics_import.twig: ICS upload form with CSRF, async validators, JS handler - quartzy_import.twig: Quartzy ZIP upload form with bookings checkbox; adds rel="noopener noreferrer" to target="_blank" external link per security policy (structural divergence from Smarty, intentional) Add AdminImportGoldenTest covering all three templates with FakeServer CSRF pinning; import/ics_import assert full parity; quartzy_import uses structural assertions for the noopener divergence. Assisted-by: Claude:claude-sonnet-4-6
Translate manage_payments.tpl and transaction_log.tpl to Twig, keeping
the original Smarty templates in place.
Accepted structural divergence (documented in golden tests):
- {update_button submit=true} in Smarty emits a stray `submit="1"`
HTML attribute via GetButtonAttributes() → AppendAttributes(); the
Twig update_button() function treats `submit` as a named boolean and
does not forward it as an HTML attribute. Twig output is
authoritative.
Golden test strategy:
- transaction_log: assertParity (Smarty ≡ Twig, no buttons → no
divergence)
- manage_payments: assertTwigBaseline (Twig output stored as
authoritative
baseline, divergence documented in each test's docblock)
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Replace stored-baseline and structural-only tests with LIVE Smarty-vs-Twig assertSame comparisons for the Admin Import, Payments, and Blackouts templates. ch2 Import (quartzy_import): - Remove stray rel="noopener noreferrer" from quartzy_import.twig (Smarty .tpl does not have it; rel-hardening is a BACKLOG item, not applied inline). - Replace 4 structural assertTwigContains tests with a single assertParity. ch3 Payments (manage_payments): - Replace all 6 assertTwigBaseline tests with live assertPaymentsParity. - Strip stray submit="1" (Smarty AppendAttributes leak) from Smarty output before comparison; strip DataTable <script> block from both outputs to handle the Smarty undefined-$tableId path when PaymentsEnabled=false. - Delete the 6 stored baseline HTML files. ch4 Blackouts: - manage_blackouts_response (5 tests) and manage_blackouts_edit (2 tests): full assertParity, no accepted divergences. - manage_blackouts main page (3 tests): assertParity after stripping data-default="H:00" from both outputs (clock-based, not honoured by Date::_SetNow() in either engine). - Fix manage_blackouts_edit.twig checked='checked' quote style to match Smarty .tpl output (both are spec-identical HTML). Assisted-by: Claude:claude-sonnet-4-6
Add groups_csv.twig and manage_groups.twig alongside their Smarty
originals. Covers all template variables, control flow branches,
formname/button/datatable/html_link helpers, and PHP class constants.
Add AdminGroupsGoldenTest: 17 live Smarty-vs-Twig parity tests using
identical fixtures, HtmlNormalizer normalization, and assertSame.
Accepted divergence (a): Smarty {update_button submit="true"} emits
a stray submit="..." HTML attribute; Twig omits it. Stripped from
both outputs before comparison (3 occurrences: permissionsForm,
resourceAdminForm, scheduleAdminForm) — rest of each button is
Smarty-parity-verified.
Assisted-by: Claude:claude-sonnet-4-6
Adds 4 Twig templates alongside the existing .tpl files plus a
golden test confirming Smarty/Twig output parity:
- import_reservations_template_csv.twig: full parity
- reservations_csv.twig: full parity
- manage_reservation_colors.twig: full parity (duplicates the
harmless duplicate method="post" attribute from the .tpl source)
- manage_reservations.twig: parity after stripping the spurious
submit="1" attribute that Smarty emits via AppendAttributes when
{update_button submit=true} is used
Accepted divergences documented in test:
- update_button(submit=true): Twig does not forward the `submit`
param as an HTML attribute; Smarty AppendAttributes does.
- escapequotes vs escape:'quotes': Twig escapes both " (")
and ' (&LibreBooking#39;); Smarty escapes only ' with backslash. Tests use
quote-free labels to stay in the parity zone.
Assisted-by: Claude:claude-sonnet-4-6
Migrate credit_log, import_user_template_csv, manage_users, user-update, and users_csv templates to Twig alongside existing Smarty .tpl files. Add AdminUsersGoldenTest with live Smarty-vs-Twig parity assertions. Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Migrate four Admin root-level Smarty templates to Twig (additive only;
original .tpl files kept intact):
- tpl/Admin/InlineAttributeEdit.twig — reusable partial for inline
editable custom attributes (text/textarea/select/checkbox/datetime).
Includes full inline JS flatpickr integration for datetime type.
- tpl/Admin/manage_accessories.twig — CRUD page with add/edit/delete
modals and resource-assignment modal with checkbox table.
- tpl/Admin/manage_announcements.twig — Announcements CRUD with
Trumbowyg rich-text editor and Select2 multi-select for groups.
Pages::NameFromId() static call replaced with an inline lookup hash
(produces identical output for all valid DisplayPage values 1 and 5).
- tpl/Admin/manage_quotas.twig — Quota CRUD with {capture} → {% set %}
HTML-snippet args passed to translate('QuotaConfiguration', [...]).
Golden test (tests/Golden/AdminRootGoldenTest.php):
- 18 live Smarty-vs-Twig parity assertions via
HtmlNormalizer::normalize.
- InlineAttributeEdit: all 5 attribute types + not-applicable branch.
- Accessories: empty + rows (null quantity / unlimited symbol ∞).
- Announcements: empty + with/without canEmail rows.
- Quotas: empty + all-day/everyday + timed/specific-days variants.
- Structural assertTwigContains checks for key elements.
- Accepted divergences documented:
(a) update_button submit="true" stray attr — stripped from both;
form="accessoryResourcesForm" attr added to Twig to match Smarty.
(b) InlineAttributeEdit CHECKBOX trailing space before > — matched.
(c) data-default clock values stripped from both in quotas parity.
Security gaps noted (not fixed — additive only):
- manage_accessories.twig: resource checkbox values use raw
user-supplied
IDs in HTML attributes; no rel=noopener on any external link.
- manage_announcements.twig: rich-text via sanitize_rich_text (existing
policy); no additional hardening added.
Assisted-by: Claude:claude-sonnet-4-6
Add Twig counterparts for all 4 Admin/Schedules templates
(manage_availability, manage_peak_times, manage_schedules,
view_schedules) and validate them with 25 live Smarty-vs-Twig
golden tests.
Bug fixes found during testing:
- manage_peak_times.twig: boolean data attributes used `? 1 : 0`
instead of `? 1 : ''`, causing false to render as "0" vs ""
- manage_schedules.twig: `{capture}` vs `{assign}` scope —
daysVisible modal title input rendered wrong var reference
- manage_schedules.twig: `indicator()` missing the id argument
- manage_schedules.twig: `.row` div ids in changeLayout modal
must reference `{{ id }}` (not a literal day index) to match
Smarty {function} scope inheritance behaviour
- manage_schedules.twig: `|e('js')` replaced with `|escape_js`
throughout (14 occurrences) — built-in escaper Unicode-escapes
spaces, custom filter matches Smarty's output
Accepted divergences handled in the golden strip function:
- submit="1" stray attribute from {update_button}
- data-default clock-based timepicker values
- flatpickr random id/for attributes
- .row div ids inside changeLayout modal
- Whitespace before comma in LayoutDescription translate args
(Smarty {capture} carries trailing whitespace that normalises
to a space before the comma separator)
Assisted-by: Claude:claude-sonnet-4-6
Add Twig counterparts alongside the kept Smarty originals for the
Admin/Resources sub-panel partials (manage_resources_{access,capacity,
credits,duration,group_permissions,groups,public,user_permissions}) and
for view_resources, manage_resource_{groups,menu,status,types},
show_resource_qr, resources_csv and import_resource_template_csv.
Key conversion patterns:
- CSV templates use {% autoescape false %} + |replace({("'"): "\\'"})
to replicate Smarty's |escape:'quotes' backslash escaping
- manage_resource_status: {function}/{call} -> {% macro %}/{{ _self.* }}
- view_resources: Twig whitespace control ({%- -%}) for the
descriptionValue div to match Smarty's {strip} block
Golden tests: AdminResourcesSubPanelsGoldenTest and
AdminResourcesListGoldenTest. Translate args with integer 0 values are
passed as array [value] to bypass PHP empty() short-circuit. The public
panel href & vs & is an accepted divergence stripped in
assertPublicParity; assertParityHrefAmp also normalises &LibreBooking#39; and
&LibreBooking#39;.
Closes: LibreBooking#58
Assisted-by: Claude:claude-sonnet-4-6
Migrate tpl/Admin/Resources/manage_resources.tpl (2432 lines) to Twig.
Add 21-test golden parity suite covering empty list, filter form, single
and multiple resources, admin groups, custom attributes, status reasons,
credits, resource types, contact-is-user, bulk dialogs, and JS block
structural coverage.
Bugs fixed in .twig:
- update_button submit='1' was inside attributes dict instead of as the
named $submit parameter; fixed for bulk-update and resource-group
dialogs.
- Description/notes editable divs collapsed to single line to match
Smarty
{strip} whitespace removal between tag attributes.
Test fixture makeAdminGroup updated to expose public $Id/$Name
properties
mirroring GroupItemView, so Smarty property access matches Twig method
calls.
Assisted-by: Claude:claude-sonnet-4-6
Add a .twig sibling for every translated email .tpl (391 files in all non-en_us languages), using the same conversions as the existing en_us templates. TwigRenderer::fetchLocalized() already prefers a .twig next to the resolved .tpl, so the Smarty files stay as the anchor for language resolution and custom-template lookup. Assisted-by: Claude:claude-sonnet-5-5
The rebase onto develop brought .tpl changes that the Twig templates did not have, because Twig is selected whenever a .twig exists. - add the escape_csv filter and the fullname csv option to the Twig extension, matching SmartyPage - escape CSV values with escape_csv in the users, groups, reservations, resources and report CSV templates - drop the trailing comma from report CSV rows - move the comma outside the quotes for user CSV attribute headers - add the max concurrent reservations column to the resources CSV - skip days without periods in the wide schedule view - render the subscribe-to-calendar control as a button - use the SubscribeToCalendar title on the public resource links Render the CSV and wide schedule template tests through Twig, as production does, and accept either quote style for the install page marker. Assisted-by: Claude:claude-sonnet-5-5
EmailLocalizedGoldenTest renders every lang/<code>/<Name>.tpl that has a .twig next to it through Smarty and Twig with six fixture variants (populated, empty and each invitation action) and compares the normalized output. The fixtures contain <, & and " so escaping is exercised. Twig HTML-escapes user-typed text that Smarty prints raw. That is the only accepted difference and it is an explicit allowlist: Description, DeleteReason and Message where the template uses |nl2br, and participant/invitee names. Any other difference fails. The Thai StartReminderEmail template cannot compile in Smarty (its variable name is not a valid Smarty identifier), so only the Twig render is asserted for it, and a second test fails once Smarty can compile it. GoldenCoverageTest fails when a .tpl with a .twig counterpart is not mentioned in any tests/Golden test. Assisted-by: Claude:claude-sonnet-5-5
28e077e to
74fb92b
Compare
In debug mode, ?template_engine=smarty renders pages, AJAX fragments,
controls, partials and emails with the Smarty templates, and
?template_engine=twig returns to Twig. The choice is stored in a
cookie so follow-up and AJAX requests use the same engine. Request
data is only read, never modified, so URLs rebuilt from the current
query string and the route parameter validation keep working.
The footer of each engine names itself ("Template engine: Smarty" in
globalfooter.tpl, "Twig" in globalfooter.twig), and a
X-Template-Engine response header names the engine that rendered the
page. All of this is ignored unless app.debug is enabled.
Assisted-by: Claude:claude-opus-5-5
There was a problem hiding this comment.
🔵 Needs a closer look
The cross-engine rendering and escaping migration needs representative browser and email validation, plus end-to-end checks of interactive flows and customized-template fallback, beyond golden-fixture parity to assess compatibility.
No open issues 🎉
4 resolved since last review
Review effort: Balanced
|
I addressed the earlier copilot review and added the engine switch option |

Summary
Migrates the Smarty templates to Twig, with Smarty kept as a fallback:
twig/twigand aTemplateRendererabstraction (SmartyRenderer,TwigRenderer).Page,EmailMessageandControlrender through it..twigfile existsnext to the
.tpl, otherwise with Smarty. Pages still callDisplay('….tpl'); the.tplfiles stay in place.LibreBookingExtensionports the Smarty plugins (translate, form/validation,buttons, links, formatting, assets, datatable, pagination, controls) and
modifiers to Twig functions and filters.
(including the localized email bodies) to Twig.
twig-cs-fixer(composer twigcsfixer:lint/:fix).Upstream template changes up to current
develop(no-double-encode escaping,restored server-side pagination, server settings versions, Google Calendar
URL, profile-data escaping in emails) are ported into the Twig templates.
Switching between Smarty and Twig (debug mode)
For comparing the engines on a running instance, with
app.debugenabled:?template_engine=smartyrenders with the Smarty templates;?template_engine=twigswitches back. The choice is stored in atemplate_enginecookie, so following pages and AJAX requests (e.g. theschedule's reservation load) use the same engine.
Template engine: Smarty(fromglobalfooter.tpl) orTemplate engine: Twig(fromglobalfooter.twig), and every page sends anX-Template-Engine: smarty|twigheader.query string (login redirects, pagination) and the route parameter
validation on schedule/calendar pages keep working.
app.debug, the parameter and cookie are ignored and nothing isshown.
Escaping
Stored text is mixed:
Server::GetForm()HTML-encodes web-form input, whilereservation title/description, the REST API, SSO/LDAP logins and some imports
store raw values. Smarty printed most values raw; Twig's default escaping
would double-encode the encoded ones (
&on screen).escape_oncefilter (escape without re-encoding existing entities),equivalent to Smarty's
|escape:'html':'UTF-8':false. It is used for usertext in pages and emails, so both stored forms display correctly and raw
markup is escaped.
example reservation titles, resource and accessory names in emails, slot
labels in the mobile/condensed/monitor views).
is_safenow escape what they embed (buildAttributes,object_html_options,read_only_attribute);json_encodeuses theJSON_HEX_*flags.|raware app-built URLs, config values, sanitized rich text andemail values already escaped in PHP. Moving to raw storage plus plain
autoescaping (with a data migration) is a follow-up.
*-custom.tpl) still render through Smarty.Testing
composer phpunit -- --testsuite golden, 1114 tests) rendereach migrated template with Smarty and Twig using the same fixture variables
and compare the results after normalization: whitespace collapsed, entity
and attribute-quote spelling canonicalized, plus documented per-test
exceptions (mainly the escaping changes above). The output is equivalent,
not byte-identical (Twig files are reformatted by twig-cs-fixer).
if/elsebranches their fixtures reach;several tests run multiple variants, but not every branch is covered.
TwigStoredEntityEscapingTestrenders real page and email templates withstored-encoded and raw values;
TemplateEngineOverrideTestandPageEngineSelectionTestcover the engine switch.composer phpunit: all pass exceptConfigTest::testHistoricalLegacyConfigDevelParsesCorrectlyandPluginConfigLoadingTest::testPluginConfigValidation, which fail locallyonly (environment-specific).
composer phpcsfixer:lint,twig-cs-fixer lint tpl langand commitlint are clean.🤖 Generated with Claude Code