Skip to content

Add twig support - #1584

Open
zonky2 wants to merge 15 commits into
release/2.5from
hotfix/fix_twig_support
Open

Add twig support#1584
zonky2 wants to merge 15 commits into
release/2.5from
hotfix/fix_twig_support

Conversation

@zonky2

@zonky2 zonky2 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

No description provided.

zonky2 added 10 commits July 24, 2026 14:45
A matching Twig template "@Contao/metamodels/<group>/<leaf>.html.twig" now takes
precedence over the legacy ".html5" template, mirroring Contao core. When no Twig
template exists the legacy template is used unchanged (full BC fallback).

The new MetaModels\Render\TwigTemplateSurrogate resolves the candidate via Contao's
managed "@contao" filesystem loader (so the templates are Template Studio ready and
support theme and project overrides) and renders it through Twig with a context built
by Contao's ContextFactory. MetaModels\Render\Template consults it before including
the PHP template; the render group (attribute, filter, item) is passed by the callers
via TemplateFactory::createTemplate().

Only the visual "html5" format is a Twig candidate - the plain text format used for
the search index and sorting stays on the PHP engine. Precedence applies in the front
end and the back end.

See docs/twig-templates.md for the naming scheme and how packages ship templates
(twig/ namespace root with a .twig-root marker).
…through the surrogate

Ship the first core Twig templates under the namespace root
Resources/contao/templates/twig/.twig-root:
- metamodels/item/prerendered.html.twig (from metamodel_prerendered.html5)
- metamodels/filter/default.html.twig (from mm_filteritem_default.html5)

FrontendFilter::renderWidget() now renders the filter widget through
MetaModels\Render\Template (via TemplateFactory, group "filter") instead of a
raw Contao FrontendTemplate, so filter widgets use the same
@Contao/metamodels/filter/<leaf> scheme as attributes and items.

Verified in the front end: item list and filter widgets render through Twig
with correct output (labels, values, actions) and fall back to the legacy
.html5 templates where no Twig template exists.
Ship the remaining core item and filter Twig templates:
- item/unrendered, item/prerendered_debug
- filter/checkbox, filter/radiobuttons, filter/linklist, filter/datepicker
  (each extends filter/default via Twig block inheritance)

Transitional back-compat (deprecated, to be removed in 3.0 with the .html5
templates): a legacy override of the flat template name in the project
"templates/" directory (or a theme folder) keeps precedence over a bundle
provided Twig template, so existing customisations keep working after the
upgrade. Template::hasLegacyTemplateOverride() detects such overrides using the
kernel project directory, which is now injected via TemplateFactory instead of
being fetched from the container.

Verified in the front end across several list pages (HTTP 200).
Provide Twig versions of the Contao content element / module wrapper templates
(flat @contao identifiers): ce_metamodel_list, mm_filter_default,
mm_clearall_default and the pagination template mm_pagination.

The item templates now include the separate @Contao/mm_actionbutton.html.twig
instead of inlining the markup, so existing custom action button templates keep
overriding it.
Cover MetaModels\Render\TwigTemplateSurrogate: the html5-only format gate, the
empty group and missing template short-circuits, precedence of a higher priority
legacy .html5 template, rendering the Twig template when it exists, and the
identifier mapping (metamodels/<group>/<leaf>) for the attribute, filter and item
groups including the prefix stripping.
Contao 5.7 renders be_wildcard through an auto-escaping Twig template, so the
HTML info text built for the MetaModels content element / module preview
("MM: …", "Fi: …") showed up as escaped source in the back end list. Wrap the
info text in a Twig Markup instance so it is treated as safe HTML by the Twig
be_wildcard template while still rendering correctly via the legacy .html5 one.
Contao 5.7 marks Backend.getScrollOffset() as deprecated and will drop it in Contao 6.
The global and row operations of the MetaModels tables carried it as an inline onclick
handler, so the back end kept emitting the deprecated call on every list view.

All 27 occurrences across the eleven DCA files now use the Stimulus controller instead:
data-action="contao--scroll-offset#store". On the delete operations the confirmation
stays in place as a plain onclick - it is no MooTools API and Contao itself combines
both attributes the same way in tl_files.php.
The previous pass only covered the static DCA files. The commands of the item tables are
built at runtime, so every MetaModel kept emitting the deprecated Backend.getScrollOffset()
on its copy, cut and delete buttons - visible for instance in the hierarchy and variant
tree views.

CommandBuilder now hands out data-action="contao--scroll-offset#store" for those, with the
confirmation of the delete command kept as a plain onclick, and the edit operation that
LoadDataContainer adds to a parent table pointing at a MetaModel follows the same scheme.
Backend.toggleCheckboxes() is deprecated in Contao 5.7 and will be dropped in Contao 6.
The "select all" switch of the add-all dialog now uses the contao--check-all Stimulus
controller, which adds shift range selection on top.

Only the attribute checkboxes carry the target attribute, so the "activate" option next
to the switch is no longer toggled along with them.
The template hooked its setup into the MooTools "domready" event. Contao only keeps that
event alive as a compatibility bridge - backend.js re-fires it on "turbo:render" - and it
goes away with Contao 6.

The setup now runs directly and registers for "turbo:render" itself, the same way the core
Stimulus controllers do. The listener drops itself as soon as its field is gone, so it does
not pile up while navigating, and a new guard bails out when the editor div already exists,
which prevents a second editor when Turbo restores a cached page.

Contao's own contao--ace controller is deliberately not used: it recomputes maxLines from
the window height after the config callback and would override the number of rows that can
be configured per attribute in MetaModels.
@zonky2
zonky2 force-pushed the hotfix/fix_twig_support branch from c4d0eab to 6aec428 Compare July 27, 2026 16:27
zonky2 added 5 commits July 27, 2026 22:27
So far only the visual "html5" format could be surrogated; the plain text format (search
index, sorting, group headers, alias generation) always stayed on the PHP engine.

Its templates carry an additional ".text" in the name: "<leaf>.text.html.twig" surrogates
the legacy "<leaf>.text". The doubled extension is not cosmetic. Contao derives the
template identifier by stripping the trailing ".html.twig" resp. ".twig" and refuses to mix
types under one identifier, so a "<leaf>.text.twig" beside a "<leaf>.html.twig" would share
the identifier and make ContaoFilesystemLoader abort the whole hierarchy with an
OutOfBoundsException - back end and front end would answer with HTTP 500. The naming is
already established by the notelist mail template.

A project side "templates/<leaf>.text" keeps its precedence, the same way it does for the
visual format.

The test for the previously unsupported format now uses a format that really is
unsupported, and two new ones cover the text variant.
The two lines were kept apart while both were in flight: the Twig template
support on this branch, the removal of the deprecated MooTools back end
calls on hotfix/mootools-removal. They never touched the same file, so
this merge is clean.

Brings in the scroll offset replacement in the static DCAs and in the
commands built at runtime, the check box toggle of the add-all dialog and
the ACE editor initialisation.
The table has no list view: its data provider aggregates all rows of a
MetaModel into a single record and refuses to fetch a collection. It
never said so, so anything routing to its list ran into that refusal -
visible as a 500 after "save and close", and on the list url itself.

dc-general already acts on config/forceEdit: the list handler forwards to
the edit action, and the back url leaves the table behind. Setting the
flag is all that was missing.
The attribute disabled Turbo Drive for every MetaModels back end page and
carried no reason beyond "for the time being". The reason has meanwhile
been found and addressed where it belongs: dc-general excludes its own
forms, because Turbo discards the answer to an auto submit, and that is
what display conditions ride on.

Everything else keeps working with Turbo - verified across the whole
harness, see docs/turbo.md in the dc-general for the measurement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant