clicktrail / twig
Render-only Twig 3 helpers for ClickTrail attribution: head loader tag, hidden form inputs, consent state attributes.
Requires
- php: >=8.1
- clicktrail/php-sdk: dev-main
- twig/twig: ^3.0
README
English | Português | Deutsch | 中文
clicktrail/twig
Render-only Twig 3 helpers for ClickTrail attribution — your adapter precomputes the values, these functions only render and escape the markup.
Index
- Why
- Installation
- Quick start
- Loader script tag
- Hidden attribution inputs
- Consent state attributes
- How it differs
- Testing
- License
Why
Attribution markup usually grows by hand inside templates: copy-pasted loader tags, hidden inputs nobody keeps in sync with the GTM variable list, consent attributes that drift. clicktrail/twig gives October components, Craft modules and Symfony bundles one canonical way to render ClickTrail markup — and none of the logic.
Installation
composer require clicktrail/twig
Requires PHP >= 8.1, Twig ^3.0, and clicktrail/php-sdk.
Quick start
{{ clicktrail_head({script_src: '/ct/loader.js', site_id: site_id}) }}
{# renders <script src="/ct/loader.js" data-ct-site-id="acme-store" async></script> #}
That is the whole contract: values in, escaped markup out. No HTTP calls, no storage, no consent judgment — ever.
Loader script tag
clicktrail_head(array $config): string
Renders the first-party loader <script> tag plus data-ct-* configuration attributes. $config keys:
script_src(required): URL of the first-party loader script.- Every other scalar key becomes a
data-ct-<key>attribute (underscores become hyphens), e.g.site_id→data-ct-site-id.
{{ clicktrail_head({script_src: '/ct/loader.js', site_id: site_id}) }}
{# every scalar key lands as data-ct-<key>; missing script_src renders '' #}
Hidden attribution inputs
clicktrail_hidden_attribution_inputs(array $attribution): string
Renders hidden <input> fields carrying the full attribution context into forms — same field list as the ClickTrail GTM attribution variables and the October AttributionHidden component: visitor/session/event/site IDs, utm_* values, all 10 ad click IDs, landing page, initial referrer, and consent state.
$attribution is a flat precomputed map, e.g.
['visitor_id' => ..., 'session_id' => ..., 'event_id' => ..., 'site_id' => ..., 'utm_source' => ..., 'gclid' => ..., 'landing_page' => ..., 'initial_referrer' => ..., 'consent_state' => 'granted']. Empty values are skipped.
<form method="post"> {{ clicktrail_hidden_attribution_inputs(attribution)|raw }} </form> <!-- one <input type="hidden" name="ct_<field>"> per non-empty field, in canonical order; empty fields produce no input at all -->
Consent state attributes
clicktrail_consent_state(array $snapshot): string
Renders normalized consent state as data-ct-consent-* attributes from a ClickTrailConsentSnapshot-shaped array (functional, analytics_storage, advertising_storage, ad_user_data, ad_personalization). Intended for use inside an opening tag:
<body{{ clicktrail_consent_state(consent_snapshot)|raw }}> <!-- <body data-ct-consent-functional="granted" data-ct-consent-analytics_storage="granted" ...> -->
Unknown or missing keys render nothing — the function makes no consent judgment of its own.
How it differs
| Typical template helpers | clicktrail/twig |
|---|---|
| Fetch, track or persist inside the template | Render only: never HTTP, never storage, never side effects |
| Evaluate whether consent was legally valid | Renders whatever snapshot the caller passes in |
| Ad-hoc escaping per helper | Every dynamic value through htmlspecialchars(..., ENT_QUOTES) |
Positioning: layer-1 framework package (ADR-0001 polyrepo). Platform adapters own the effects (clock, storage, HTTP, consent resolution); this package owns none of them.
Testing
php tests/_runner.php # standalone assert runner, no composer install needed
Or inside a container:
podman run --rm -v "$PWD":/app:Z wordpress:php8.3-apache php tests/_runner.php
License
MIT © 2026 Vizuh OÜ.