Search by

asconsulting / contao-zyppy-popup

asconsulting

A popup Module for Contao 5.3+

Package info

github.com/asconsulting/contao-zyppy-popup

Homepage

Type:contao-bundle

pkg:composer/asconsulting/contao-zyppy-popup

Statistics

Installs: 1

Dependents: 0

Suggesters: 1

Stars: 0

Open Issues: 0

5.0.1 2026-10-08 03:01 UTC

This package is auto-updated.

Last update: 2026-10-09 19:34:33 UTC


README

Turns any article or front end module into a pop-up for Contao 5. Tick Popup on the record, choose how and when it opens, and the bundle wraps it in an overlay frame at the end of <body>.

A pop-up can also carry an Accept/Reject step. In its decorative form that is a client-side nag box; in its enforced form it is a real server-side gate that withholds the content until the visitor has accepted, which is what an age check or a terms-of-service gate needs.

Requirements

  • PHP 8.1 or newer
  • Contao 5.3 or newer, with no upper bound. The Zyppy Suite supports the LTS releases and is tested against the current non-LTS release as well: the test suite passes on core-bundle 5.3.0, 5.7.13 and 6.0.2 (last run 2026-10-07).
  • No jQuery. The pop-up script is plain JavaScript.

Installation

composer require asconsulting/contao-zyppy-popup:^5.0

Then run the database migration. Contao Manager does this on install and on update; on the command line it is vendor/bin/contao-console contao:migrate. Besides adding the pop-up columns to tl_article and tl_module, the migration adds a custom Pop-up section, ID popup, to every existing page layout, and new layouts get it as their default. You do not have to create that section by hand, and you do not have to open each layout in the back end to make the bundle notice it.

If a site builder deliberately removes the Pop-up section from a layout, the migration puts it back the next time it runs. If a section with ID popup already exists under a different title, only its title is corrected; its template and position are left alone.

Usage

Setting up a pop-up

  1. Edit the article or front end module and tick Popup in its Popup Settings legend. The pop-up fields below appear.
  2. Put the record where the bundle looks for it. Where that is depends on the page layout type — see Page layout type.

Pop-up settings

Field Meaning
Unique ID (popupUuid) Generated automatically. It is the key of the consent cookie, so it is made with random_bytes(); do not hand-edit it to something guessable.
CSS Class A class added to the pop-up frame.
Delay Milliseconds to wait before the pop-up is shown.
Reshow Delay Minutes before the same visitor sees the pop-up again.
Scroll Trigger Show the pop-up once the visitor has scrolled this many pixels.
Fade Duration Length of the fade-out, in milliseconds.
Trigger A CSS selector; clicking a matching element opens the pop-up.
Add Close Button Adds a close button to the frame.
Accept Popup The visitor must accept the pop-up. Reveals Consent mode and Popup Reject — see Consent modes.
Popup Reject Where to send a visitor who rejects.
Clear on Popup (modules only) With asconsulting/contao-zyppy-search installed, clears a live search's results when the pop-up opens.

Page layout type

Pop-ups work on both Contao page layout types.

  • On a default (legacy) layout, put the article or module in the Pop-up layout section the migration created.
  • On a modern layout (composed from Twig {% slot %} templates), put the module in a column named popup. The pop-up frame is emitted at the end of <body>, which is where a fixed overlay belongs, so the layout template does not have to declare a popup slot - although declaring one is the only way the module wizard will offer popup as a column in the first place. A module row left over from a layout that was switched from default to modern keeps working for the same reason. Only modules with "Popup" ticked get a pop-up frame; anything else in that column - a plain module, a content element, an article - is rendered normally, just at the end of the body rather than where the slot sits.
  • Article pop-ups have always worked on both types and are unchanged.

The two layout types take completely different code paths inside Contao - a modern layout never fires the generatePage hook - but both go through the same consent gate here, so an "Enforced" pop-up withholds its content identically on either. If anything at all goes wrong while building a pop-up on a modern layout, the pop-up is dropped and an error is written to the application log; it is never published without its gate.

If a default layout ends up with no section whose ID is exactly popup, a module pop-up on that layout is generated and then dropped by Contao without being rendered. The bundle writes a clear error to the application log (var/logs/, the Monolog app channel) when that happens. It is not the back end "System log": that table is only fed by records carrying a Contao ContaoContext, which this bundle does not add.

Consent modes

Ticking "User must accept popup" adds a "Consent mode" field with two options. These are two different features with two different guarantees - pick deliberately, not by habit:

  • Decorative (the default). A promo/nag box: it shows Accept and Reject buttons, and closes or navigates away on click, but this is entirely client-side and enforces nothing. The popup content is present in the HTML response from the moment the page is generated, before any interaction. Anyone can see it with view-source, curl, a screen reader, or by turning JavaScript off - accepting or rejecting is cosmetic only. Use this for "please sign up for our newsletter" style boxes, never for anything with legal weight.

  • Enforced. A real server-side gate. The popup's content is not present in the HTTP response at all until the visitor has a valid, signed consent cookie - not hidden with CSS, not present-but-collapsed, genuinely absent from the markup the server sends. Accept/Reject is a plain HTML <form method="post"> to a dedicated route, so it works even with JavaScript disabled. Accepting sets a cookie (Secure, HttpOnly, SameSite=Lax) whose value is an HMAC signature of the popup's unique ID, keyed on the site's kernel.secret - it cannot be forged or copied from one popup to another without the server secret. Use this for age verification, terms-of-service gates, or anything else where "the visitor saw and accepted this" needs to be a fact about the server's behaviour, not just the browser's.

    A page carrying an enforced-mode popup is always sent with Cache-Control: private, no-store and Vary: Cookie, regardless of the page's own caching settings - the response genuinely differs by the visitor's consent cookie, so it must never be served from a shared cache or reverse proxy to a different visitor.

    Rejecting redirects to the configured "Popup Reject" URL. In enforced mode that URL is restricted to the current site (a relative path, or an absolute URL on the same host) - an off-site target falls back to the site root, since the redirect happens through a real server round trip and an unrestricted external target would be an open redirect. Decorative mode's Reject button navigates there directly from the visitor's own browser with no server involved, so that restriction does not apply there.

The template

To edit the pop-up markup (close button, Accept/Reject controls, etc), see the fe_popup_wrapper template. It is used for both module and article pop-ups. It is a Twig template (contao/templates/frontend/fe_popup_wrapper.html.twig); override it the way you override any Contao template, under the same flat fe_popup_wrapper identifier. Twig autoescapes, so a value you add to it is escaped by default; only body is deliberately raw.

  • The template renders the Accept/Reject controls itself whenever "User must accept popup" is ticked - you do not need to hand-author .accept / .reject elements inside the article or module content.
  • The bundle ships no mod_article override: articles are rendered by Contao core's own current template and the pop-up frame is wrapped around the result, so an article keeps wrapperAttributes, sanitize_html and everything else core adds in future releases.
  • Only a full article body is wrapped. A popup-flagged article that appears as a teaser in an article list, or through the "article" include element, renders normally and is not turned into a pop-up.

Migration from legacy version

This package replaces the legacy package asconsulting/zyppy_popup (namespace ZyppyPopup\). The old name does not resolve any more.

The four legacy Zyppy packages — page, classes, popup and search — have to be upgraded together, in one composer update: old and new packages register the same DCA fields and module types, so a site that briefly has both installed double-registers. The walkthrough for the whole suite, including the order of operations and a verification checklist, is UPGRADING.md in the contao-zyppy-page repository. The part specific to this package:

  • Columns. tl_article and tl_module keep every pop-up column they had. Exactly one column is added on each: popupConsentMode, which defaults to decorative. Existing pop-ups therefore keep behaving exactly as they do today; enforced is opt-in, per pop-up. No data migration.
  • The layout section is now added by a migration. contao:migrate adds the Pop-up section to every page layout. Verify that it is present on every tl_layout row, not just the one you looked at.
  • The one thing that bites. The legacy package shipped contao/templates/modules/mod_article.html5, an override of Contao's own article template. 5.0.0 drops it entirely. If a site copied that file into its project templates/ folder, it is still there, still overriding core's mod_article for every article on the site, and it references pop-up internals that no longer exist. Inspect and remove templates/mod_article.html5.
  • fe_popup_wrapper keeps its name and moved from .html5 to .html.twig. A site override needs porting to Twig, and the .html5 copy deleted — on Contao 5 the .html5 copy silently keeps winning; on Contao 6 it breaks.
  • popupClear on tl_module is a pop-up field that only means anything to the search module. It is left where it was.

Development

composer install
vendor/bin/phpunit --no-coverage

Licence

Licensed under the GNU Affero General Public License v3.0 only (AGPL-3.0-only). See LICENSE. Copyright Andrew Stevens.