Developer notes
This page is the integration contract for other add-ons and custom code. Everything below is stable within the 1.x versions.
Consent model
Section titled “Consent model”| Category | Meaning | Default |
|---|---|---|
necessary |
Required for the store to work | always true |
preferences |
Remembered settings, personalization | false until granted |
analytics |
Measurement and statistics | false until granted |
marketing |
Advertising, pixels, video embeds | false until granted |
A choice is valid when it was made for the current policy version and is not older than the configured lifetime. Without a valid choice every optional category is false.
The choice is stored in the first-party cookie kv_consent (path /, SameSite=Lax, Secure on HTTPS):
1.<consent id: 16 hex chars>.<policy version>.<unix time>.<preferences><analytics><marketing>e.g. 1.3f9c0a7e5b2d4c61.2.1760000000.011Treat the format as internal: use the APIs below instead of parsing the cookie.
JavaScript API: window.kvConsent
Section titled “JavaScript API: window.kvConsent”The consent core is inlined right after <head>, before any other script, so window.kvConsent is available to every script of the page, including inline scripts in <head>.
| Member | Returns | Description |
|---|---|---|
get(category) |
boolean |
Whether the category is granted. get('necessary') is always true. Unknown categories return false. |
has(category) |
boolean |
Alias of get(). |
getAll() |
object |
{necessary, preferences, analytics, marketing, decided, id, version, time} |
hasDecision() |
boolean |
Whether the visitor made a valid choice for the current policy. |
on(event, callback) |
function |
Subscribes and returns an unsubscribe function. Events: 'ready' and 'change'. |
set(choices, action, options) |
object |
Saves a choice, e.g. set({analytics: true}). Missing categories keep their value. action is logged (api by default). When a category is withdrawn the page reloads; pass {reload: false} as options to prevent that. |
acceptAll() / rejectAll() |
object |
Same as the banner buttons. |
open() |
Opens the preference center. | |
categories |
string[] |
['necessary', 'preferences', 'analytics', 'marketing'] |
version |
string |
API version, 1.0.0. |
Events:
'ready': once, afterDOMContentLoaded, with the current state. If you subscribe later, the callback still runs (asynchronously).'change': after every saved choice, with the new state pluschanged(categories whose value changed),revoked(categories switched off),actionandprevious(the state before).
Example:
(function () { function apply(state) { if (state.marketing) { /* start the pixel */ } else { /* stop sending */ } } if (window.kvConsent) { apply(window.kvConsent.getAll()); // current state, right away window.kvConsent.on('change', apply); // later changes } else { // The add-on is disabled or not installed: no consent banner on this store. }})();DOM events
Section titled “DOM events”Dispatched on document (they bubble to window) with the same detail as the callbacks:
| Event | When |
|---|---|
kv:consent-ready |
Once, after DOMContentLoaded |
kv:consent-changed |
After every saved choice |
kv_consent_changed |
Legacy alias of kv:consent-changed; new code should use kv:consent-changed |
document.addEventListener('kv:consent-changed', function (e) { if (e.detail.revoked.indexOf('analytics') !== -1) { /* clean up */ }});dataLayer
Section titled “dataLayer”The core creates window.dataLayer and a gtag() function if none exist, then pushes:
['consent', 'default', {...}]: one command for the listed regions (all denied exceptsecurity_storage) and one for everywhere else, both withwait_for_update;['set', 'ads_data_redaction', true]/['set', 'url_passthrough', true]if enabled;['consent', 'update', {...}]immediately when a valid choice is stored;{event: 'kv_consent_default', kv_consent: {...}}.
After every choice: ['consent', 'update', {...}] and {event: 'kv_consent_update', kv_consent: {...}}.
PHP API
Section titled “PHP API”The functions are defined only while the add-on is active, so always check with function_exists():
if (function_exists('fn_kv_cookie_consent_mode_has_consent')) { $allowed = fn_kv_cookie_consent_mode_has_consent('marketing');} else { $allowed = true; // no consent manager on this store: apply your own default}| Function | Returns |
|---|---|
fn_kv_cookie_consent_mode_has_consent(string $category): bool |
Same rules as kvConsent.get(), based on the request’s kv_consent cookie. Use it in storefront requests (the cookie is not available in cron jobs or the admin panel). |
fn_kv_cookie_consent_mode_get_consent(): ?array |
['id', 'version', 'time', 'preferences', 'analytics', 'marketing'] or null without a valid choice. |
For server-side events that are sent later (cron, payment callbacks), store the decision with the order when the shopper is on the storefront.
Hook: register services for automatic blocking
Section titled “Hook: register services for automatic blocking”fn_register_hooks('kv_cookie_consent_mode_get_services');
// func.phpfunction fn_my_addon_kv_cookie_consent_mode_get_services(&$services){ $services['my_chat'] = [ 'category' => 'preferences', // preferences|analytics|marketing 'name' => 'My chat widget', // shown in the preference center 'src' => ['widget.mychat.example/'], 'inline' => ['MyChat.init('], 'iframe' => [], 'cookies' => ['mychat_'], // removed when the category is withdrawn ];}Services added this way are always blocked (they have no checkbox in the settings).
What the add-on changes in the page
Section titled “What the add-on changes in the page”- Output filter (Smarty, root template only): injects the core after
<head>, rewrites matching<script>tags totype="text/plain" data-kv-consent="<category>" data-kv-service="<id>"and matching iframes todata-kv-srcplus a placeholderdiv.kv-ccm-placeholder. Categories already granted by the cookie are not rewritten. - Skipped scripts: non-JavaScript types, scripts already tagged, scripts with
data-kv-consent-skip, inline scripts that mentionkvConsent, and inline scripts that define a Kavio Labs configuration object (kv<Name>Config). Consent-aware add-ons are therefore never blocked. - Template hook
index:footer(post) renders the banner, the preference center and the floating button. The banner script isjs/addons/kv_cookie_consent_mode/ui.js. - Block
kv_cookie_consent_mode(Cookie settings links):addons/kv_cookie_consent_mode/blocks/cookie_links.tpl. - CSS in
css/addons/kv_cookie_consent_mode/styles.less, scoped under.kv-cookie-consent-mode, with CSS custom properties (--kv-ccm-accent,--kv-ccm-text,--kv-ccm-bg,--kv-ccm-font, …) you can override in your theme.
Ecommerce Tracking Pro (kv_ga4_ecommerce)
Section titled “Ecommerce Tracking Pro (kv_ga4_ecommerce)”Both add-ons work together without changing Ecommerce Tracking Pro. In dispatch_before_display (priority 9000, after its handler) this add-on adjusts the page configuration of Ecommerce Tracking Pro:
- its own Consent Mode defaults are removed, because our core already set them first;
- its consent mode follows Google tags before consent (basic or advanced);
- its GDPR (Klaro) bridge is switched off.
The core then calls window.kvGa4.consent({analytics, ads}) on every page and after every choice. Ecommerce Tracking Pro stores that state in its own cookie for the server-side purchase fallback.
License check
Section titled “License check”Tygh\Addons\KvCookieConsentMode\License\Client verifies the license against the Kavio Labs license server. The client, the “Check now”/cron controller controllers/backend/kv_cookie_consent_mode_license.php and the License tab template design/backend/templates/addons/kv_cookie_consent_mode/license/info.tpl are generated from the shared Kavio Labs template; don’t edit them by hand.
- Admin license UI (generated, REVISION 3; don’t edit by hand):
design/backend/templates/addons/kv_cookie_consent_mode/license/card.tpl(License card at the top of the add-on page, every tab;hooks/addons/tabs_content.pre.tpl),license/badge.tpl(badge in Add-ons > Manage add-ons;hooks/addons/manage.post.tpl),license/status_bar.tpl(chip/banner for the add-on’s own admin pages),design/backend/css/addons/kv_cookie_consent_mode/license.less(loaded by the kv-license block ofhooks/index/styles.post.tpl). The status bar is the first line inside the mainbox capture ofviews/kv_cookie_consent_mode/logs.tpl(consent log). All of them render nothing for vendors and for administrators without the “Manage settings” privilege. The “what is switched off” sentence is the language variablekv_cookie_consent_mode.license_off_effect.License\Clientassigns the view data to$kv_lic_kv_cookie_consent_modein itsbefore_dispatchhandler (only for administrators with the Manage settings privilege); the templates only read this variable, so they render nothing (and no error) while an older version of the PHP code is loaded, for example in the request that uploads a new zip over the installed add-on. Templates never call add-on functions; PHP code can callfn_kv_cookie_consent_mode_license_ui($context). fn_kv_cookie_consent_mode_is_licensed()(func.php) is the gate. It reads the stored state only (one storage read per request) and never contacts the server. When it returnsfalsethe add-on runs in the deny-all safe mode:dispatch_before_display:kv_ccmis empty (no banner, preference center or floating button),kv_ccm_lockedhides the Cookie settings links block, the output filter still blocks every configured service, and blocked iframes get a placeholder without Load/Allow buttons.fn_kv_cookie_consent_mode_get_client_config($config, true):locked: true, one global Consent Mode default with every type “denied” (ads_data_redactionon), no Meta/TikTok signals, no log URL. The consent core ignores the stored cookie,kvConsent.set()/acceptAll()save nothing and send nothing, the placeholder buttons do nothing, and thekv_ga4_consentcookie of Ecommerce Tracking Pro is reset toddbefore its head script reads it.Consent::current()returnsnull, sofn_kv_cookie_consent_mode_has_consent()/fn_kv_cookie_consent_mode_get_consent()report no consent to other add-ons (server side).fn_kv_cookie_consent_mode_align_ga4(): Ecommerce Tracking Pro runs in “basic” mode (its tag waits for analytics consent) without its own defaults.kv_cookie_consent_mode.logstores nothing;kv_cookie_consent_mode.gdpr_noticerefusesnotice_action=disable(restore still works).fn_kv_cookie_consent_mode_before_dispatch()(admin page views of the root administrator) shows the safe mode notice once per session and license status; the Status block has a License row.Client::boot()(init.php) registers abefore_dispatchhandler for the admin area and the console only. On admin page views and console requests it runs the check when it is due (key changed, or 24 hours after the last check; 1 hour after a failure) and shows the admin notice once per session.- Request:
POST https://cs-cart.kaviolabs.com/index.php?dispatch=kv_licenses.checkwith JSON{"v":1, "license", "addon", "version", "hosts":[…], "platform":{"version", "edition"}, "nonce"}, sent throughTygh\Http(3 s connect, 5 s total; kept out of the CS-Cart request log). The answer is accepted only when its Ed25519 signature matches a public key embedded in the client and it echoes the nonce. - State: storage key
kv_license_kv_cookie_consent_mode(?:storage_data), orvar/kv_storage/kv_cookie_consent_mode/license.jsonwhile the full page cache is active, protected by an HMAC with the store’s crypt key. - Cron:
php admin.php --dispatch=kv_cookie_consent_mode_license.check. - Development stores only (
http_hostandhttps_hostare development hosts): the constantsKV_LICENSE_SERVER_URL,KV_LICENSE_TEST_HOST(act as if the development storefronts ran on that host) andKV_LICENSE_TEST_TIME_OFFSET(seconds). They are ignored on production hosts.
Database
Section titled “Database”?:kv_cookie_consent_logs: log_id, consent_id, company_id, storefront_id, created_at, policy_version, preferences, analytics, marketing (Y/N), action, ip (truncated: IPv4 /24, IPv6 /48), lang_code, page (path only). It is dropped on uninstall.
Storage keys: kv_cookie_consent_mode_gdpr_prev (GDPR notice mode to restore) and kv_cookie_consent_mode_cleanup (last retention run).
Controllers
Section titled “Controllers”| Dispatch | Area | Method | Purpose |
|---|---|---|---|
kv_cookie_consent_mode.log |
storefront | POST (navigator.sendBeacon) |
Logs a choice; the cookie is the source of truth |
kv_cookie_consent_mode.logs |
admin | GET | Consent log page (view_settings) |
kv_cookie_consent_mode.export |
admin | GET | CSV export (view_settings) |
kv_cookie_consent_mode.cleanup |
admin | POST | Apply the retention period now (update_settings) |
kv_cookie_consent_mode.gdpr_notice |
admin | POST | `notice_action=disable |
kv_cookie_consent_mode_license.check |
admin, console | POST / console | License check (“Check now”, cron); generated |
Hooks used
Section titled “Hooks used”| Type | Hook | Purpose |
|---|---|---|
| PHP | dispatch_before_display (priority 9000) |
Banner data, Ecommerce Tracking Pro alignment, output filter (bootstrap + blocking) |
| PHP | before_dispatch |
Safe mode notice in the admin panel (no valid license) |
| PHP | before_dispatch (admin area and console only; static handler License\Client::onBeforeDispatch) |
Runs the due license check and shows license notices |
| Template (admin) | addons:tabs_content (pre) |
License card at the top of the add-on page |
| Template (admin) | addons:manage (post) |
License badge, moved into the add-on’s row (4.17–4.21 have no per-row hook) |
| Template (admin) | index:styles (post) |
license.less (kv-license block) |
| PHP | init_templater_post (only while an upgrade is pending) |
Self-healing upgrade (Upgrade\Synchronizer) |
Third-party code
Section titled “Third-party code”Tygh/Addons/KvCookieConsentMode/License/Ed25519.php (checks the signature of the license server’s answers on servers whose PHP has no sodium extension) is derived from paragonie/sodium_compat, ISC License, Copyright (c) 2016-2023 Paragon Initiative Enterprises and Copyright (c) 2013-2019 Frank Denis. The full license notice is in the file header.