Skip to content

Developer notes

This page is the integration contract for other add-ons and custom code. Everything below is stable within the 1.x versions.

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.011

Treat the format as internal: use the APIs below instead of parsing the cookie.

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, after DOMContentLoaded, with the current state. If you subscribe later, the callback still runs (asynchronously).
  • 'change': after every saved choice, with the new state plus changed (categories whose value changed), revoked (categories switched off), action and previous (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.
}
})();

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 */ }
});

The core creates window.dataLayer and a gtag() function if none exist, then pushes:

  1. ['consent', 'default', {...}]: one command for the listed regions (all denied except security_storage) and one for everywhere else, both with wait_for_update;
  2. ['set', 'ads_data_redaction', true] / ['set', 'url_passthrough', true] if enabled;
  3. ['consent', 'update', {...}] immediately when a valid choice is stored;
  4. {event: 'kv_consent_default', kv_consent: {...}}.

After every choice: ['consent', 'update', {...}] and {event: 'kv_consent_update', kv_consent: {...}}.

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”
init.php
fn_register_hooks('kv_cookie_consent_mode_get_services');
// func.php
function 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).

  • Output filter (Smarty, root template only): injects the core after <head>, rewrites matching <script> tags to type="text/plain" data-kv-consent="<category>" data-kv-service="<id>" and matching iframes to data-kv-src plus a placeholder div.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 mention kvConsent, 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 is js/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.

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.

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 of hooks/index/styles.post.tpl). The status bar is the first line inside the mainbox capture of views/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 variable kv_cookie_consent_mode.license_off_effect. License\Client assigns the view data to $kv_lic_kv_cookie_consent_mode in its before_dispatch handler (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 call fn_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 returns false the add-on runs in the deny-all safe mode:
  • dispatch_before_display: kv_ccm is empty (no banner, preference center or floating button), kv_ccm_locked hides 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_redaction on), 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 the kv_ga4_consent cookie of Ecommerce Tracking Pro is reset to dd before its head script reads it.
  • Consent::current() returns null, so fn_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.log stores nothing; kv_cookie_consent_mode.gdpr_notice refuses notice_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 a before_dispatch handler 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.check with JSON {"v":1, "license", "addon", "version", "hosts":[…], "platform":{"version", "edition"}, "nonce"}, sent through Tygh\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), or var/kv_storage/kv_cookie_consent_mode/license.json while 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_host and https_host are development hosts): the constants KV_LICENSE_SERVER_URL, KV_LICENSE_TEST_HOST (act as if the development storefronts ran on that host) and KV_LICENSE_TEST_TIME_OFFSET (seconds). They are ignored on production hosts.

?: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).

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
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)

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.