Skip to content

Developer guide

Path Purpose
app/addons/kv_abandoned_cart_recovery/addon.xml Settings, tables, e-mail templates
app/addons/kv_abandoned_cart_recovery/func.php Hook handlers, install/uninstall, settings info handlers
app/addons/kv_abandoned_cart_recovery/Tygh/Addons/KvAbandonedCartRecovery/ CartCapture, Sender, MessageBuilder, Coupons, Restorer, Attribution, Consent, Links, Stats, Repository, Config, StorageData, Logger, Upgrade\Synchronizer
controllers/backend/kv_abandoned_cart_recovery.php Dashboard, cart list, cart view, preview, run now, console cron
controllers/backend/kv_abandoned_cart_recovery_license.php “Check now” / cron license check
app/addons/kv_abandoned_cart_recovery/Tygh/Addons/KvAbandonedCartRecovery/License/Client.php License check client (generated from the shared Kavio Labs template)
design/backend/templates/addons/kv_abandoned_cart_recovery/license/info.tpl License tab (points to the card)
design/backend/templates/addons/kv_abandoned_cart_recovery/license/card.tpl License card (add-on page) / banner
design/backend/templates/addons/kv_abandoned_cart_recovery/license/status_bar.tpl Status chip/banner for own admin pages
design/backend/templates/addons/kv_abandoned_cart_recovery/license/badge.tpl Badge in Add-ons > Manage add-ons
design/backend/templates/addons/kv_abandoned_cart_recovery/hooks/addons/ Tabs_content.pre.tpl (card), manage.post.tpl (badge)
design/backend/templates/addons/kv_abandoned_cart_recovery/hooks/index/styles.post.tpl Loads license.less
design/backend/css/addons/kv_abandoned_cart_recovery/license.less Card, chip and badge styles (admin LESS variables)
controllers/frontend/kv_abandoned_cart_recovery.php Restore, unsubscribe/resubscribe, capture endpoint, cron URL
controllers/frontend/checkout.post.php Checkbox state for the checkout page
schemas/ Admin menu, permissions (admin, vendor), settings variants, GDPR
var/themes_repository/responsive/templates/addons/kv_abandoned_cart_recovery/ hooks/index/scripts.post.tpl (checkout only), unsubscribe page
js/addons/kv_abandoned_cart_recovery/func.js Checkout checkbox and e-mail capture
Hook Since Use
save_cart_content_post 4.x Capture storefront carts (AREA === 'C', type C). No write when the cart hash and e-mail are unchanged.
place_order_post 4.x Attribution (parent orders only), coupon single use
delete_user 4.x Delete the customer’s captured carts
before_dispatch 4.x Optional admin fallback runner (shutdown function, every 10 minutes at most)
before_dispatch (admin area and console only; static handler License\Client::onBeforeDispatch) 4.x Runs the due license check and shows license notices (not on the add-on’s own pages, which show the status bar)
init_templater_post 4.x Upgrade synchronizer (only while an upgrade is pending)

Template hooks: index:scripts (storefront, only when the checkout controller assigned $kv_acr_checkout); admin addons:tabs_content (pre, license card at the top of the add-on page), addons:manage (post, license badge, moved into the add-on’s row) and index:styles (post, license.less).

Table Content
?:kv_acr_carts One row per captured cart: identity (user_type R/U, user_id = customer id or guest cart id), storefront, company (Ultimate), e-mail, name, language, currency, subtotal, cart_hash, status (P, R, C, O, U, X), steps_sent, last_sent_at, clicks, order_id, order_total, recovered_step, token, timestamps
?:kv_acr_items Cart lines: product, vendor (company_id), amount, unit price, product_options (JSON), name
?:kv_acr_messages One row per reminder: step, status (S sent, F failed), error, sent/clicked time, coupon code, promotion, coupon status (A active, U used, E expired/withdrawn), expiry
?:kv_acr_contacts Per e-mail consent: Y (ticked the box or resubscribed), N (unticked or unsubscribed)

All tables are InnoDB, created with CREATE TABLE IF NOT EXISTS and dropped on uninstall.

Codes are added to the core Coupon code (auto-generated) condition (auto_coupons) of the chosen cart promotion: the same mechanism the core Give coupon bonus uses. Coupons::issue() appends a code under a MySQL named lock, rewrites conditions and conditions_hash with core fn_promotion_serialize(). markUsed() (order placed), expire() (run) and withdrawForCarts() (delete/uninstall) remove codes again; resync() re-adds active codes that are missing (e.g. after the promotion was saved from an admin page opened before the code was added). The code list stays small because only valid codes are in it; a promotion whose conditions grow beyond 50 KB gets no new codes.

  • No per-visitor output on cacheable pages: the checkout script and its configuration are only rendered on the checkout (never cached). The restore, unsubscribe, capture and cron dispatches are not cacheable.
  • Capture writes only to the add-on tables, and only when the cart changed.
  • Run information (last run, last result, error log) uses StorageData: files in var/kv_storage/kv_abandoned_cart_recovery/ while the full_page_cache add-on is active, ?:storage_data otherwise. Writing ?:storage_data would purge the whole page cache.
php admin.php --dispatch=kv_abandoned_cart_recovery.cron
GET index.php?dispatch=kv_abandoned_cart_recovery.cron&key=<cron key>

Both print the JSON summary of the run: sent, failed, skipped, ordered, closed, expired, purged, locked, disabled, unlicensed. A MySQL named lock prevents parallel runs.

index.php?dispatch=kv_abandoned_cart_recovery.restore&t=<token>&s=<step>&h=<signature> and ...unsubscribe&t=<token>&h=<signature>. The token is a random 128-bit value per cart; the signature is an HMAC-SHA256 (truncated) of the action, token and step with the add-on’s link secret (hidden setting generated on install).

Tygh\Addons\KvAbandonedCartRecovery\License\Client verifies the license against the Kavio Labs license server.

  • fn_kv_abandoned_cart_recovery_is_licensed() is the gate. It reads the stored state only (one storage read per request) and never contacts the server. In the storefront it uses the current storefront’s host; in the admin panel, console and cron it means “licensed for at least one storefront”. When it returns false:
    • fn_kv_abandoned_cart_recovery_save_cart_content_post() captures no carts;
    • controllers/frontend/checkout.post.php assigns no $kv_acr_checkout, so the checkout checkbox and its script are not rendered;
    • the capture endpoint (kv_abandoned_cart_recovery.capture) answers {"ok":false};
    • Sender::run() (console cron, cron URL, admin fallback, Run now) still expires and re-syncs the codes of e-mails already sent, closes stale carts and purges old records, but sends no reminders and issues no coupons; its summary has "unlicensed": true;
    • Sender::sendNext() (Send reminder N now on the cart page) returns unlicensed.
  • fn_kv_abandoned_cart_recovery_is_licensed_for_storefront($storefront_id) checks the host of a cart’s storefront (?:storefronts.url): on CS-Cart (Ultimate) with storefronts on several domains, Sender::run() skips the carts of storefronts the license does not cover (Repository::getDue() excludes them).
  • Not gated: restore and unsubscribe/resubscribe links, order attribution of restored carts, the e-mail preview and Send test (to an admin address), the dashboard, cart list and cart pages, GDPR export/anonymization, delete_user cleanup, uninstall.
  • No output inside core-cached blocks, so no callable_handlers entry is needed. Full page cache (4.21): the gate only reads stored state.
  • 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_abandoned_cart_recovery (?:storage_data), or var/kv_storage/kv_abandoned_cart_recovery/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_abandoned_cart_recovery_license.check.
  • Admin UI: License\Client assigns the view data of the license card to $kv_lic_kv_abandoned_cart_recovery in its before_dispatch handler, only for administrators with the Manage settings privilege (update_settings); vendors never get it. 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. PHP code can call fn_kv_abandoned_cart_recovery_license_ui($context). Every full page of the add-on’s own admin controllers (dashboard, cart list, cart page) includes license/status_bar.tpl as the first line of its mainbox: a green chip when licensed, the card as a compact banner otherwise. The add-on’s license notice is not shown on these pages (the bar shows the status); it appears on the next other admin page.
  • 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.
  • The license check endpoint (kv_abandoned_cart_recovery_license.check) accepts only CSRF-protected POST requests of administrators, and console requests. Vendors cannot use it.

With kv_cookie_consent_mode active and the setting on, the as-you-type capture runs only when window.kvConsent.has('marketing') is true in the browser, and the capture endpoint re-checks fn_kv_cookie_consent_mode_has_consent('marketing'). An explicit tick of the reminder checkbox is always accepted. The add-on sets no cookies of its own.

  • Edit the e-mails in Design → E-mail templates; all variables are listed in Configuration.
  • The add-on uses core hooks only; it does not override core templates or controllers.
  • Kill switch for the upgrade synchronizer: define('KV_UPGRADE_SYNC_DISABLED', true); in config.local.php.

Tygh/Addons/KvAbandonedCartRecovery/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.