Developer guide
File map
Section titled “File map”app/addons/kv_free_shipping_progress_bar/├── addon.xml scheme 3.0, settings (ROOT,STOREFRONT), uninstall function├── init.php registers the dispatch_before_display hook├── func.php template API, hook handler, admin preview, uninstall├── controllers/frontend/kv_free_shipping_progress_bar.php AJAX refresh endpoint (mode "refresh")├── controllers/backend/kv_free_shipping_progress_bar_license.php "Check now" / cron license check├── schemas/block_manager/blocks.post.php block type "kv_free_shipping_progress_bar"└── Tygh/Addons/KvFreeShippingProgressBar/ PSR-0 classes ├── Config.php validated settings (colors, amounts, enums) ├── GoalCalculator.php session cart → view data ├── ShippingThresholdDetector.php free-shipping threshold from manual rates ├── PromotionGoalDetector.php unlockable cart promotions ├── License/Client.php license check client (generated from the shared Kavio Labs template) └── Upgrade/Synchronizer.php self-healing in-place upgradesdesign/backend/templates/addons/kv_free_shipping_progress_bar/settings/detected_goals.tpldesign/backend/templates/addons/kv_free_shipping_progress_bar/license/info.tpl License tab (points to the card)design/backend/templates/addons/kv_free_shipping_progress_bar/license/card.tpl License card (add-on page) / bannerdesign/backend/templates/addons/kv_free_shipping_progress_bar/license/status_bar.tpl status chip/banner for own admin pagesdesign/backend/templates/addons/kv_free_shipping_progress_bar/license/badge.tpl badge in Add-ons > Manage add-onsdesign/backend/templates/addons/kv_free_shipping_progress_bar/hooks/addons/ tabs_content.pre.tpl (card), manage.post.tpl (badge)design/backend/css/addons/kv_free_shipping_progress_bar/license.less card, chip and badge styles (admin LESS variables)var/themes_repository/responsive/templates/addons/kv_free_shipping_progress_bar/├── components/bar.tpl stable holder (AJAX target) + state├── components/bar_content.tpl message, progressbar, markers, unlocked list, vendor rows├── components/icon.tpl inline SVG icons├── blocks/progress_bar.tpl Block Manager template├── views/kv_free_shipping_progress_bar/refresh.tpl└── hooks/ ├── index/{styles.post,scripts.post,body.pre}.tpl ├── checkout/{minicart.pre,payment_options.pre,shipping_rates.pre}.tpl └── products/{notification_items.post,product_detail_bottom.post}.tplvar/themes_repository/responsive/css/addons/kv_free_shipping_progress_bar/styles.lessjs/addons/kv_free_shipping_progress_bar/func.jsvar/langs/en/addons/kv_free_shipping_progress_bar.poNo core files are modified, and no database tables are created.
Hooks used
Section titled “Hooks used”| Type | Hook | Purpose |
|---|---|---|
| PHP | dispatch_before_display |
Adds kv_fspb_state (cart fingerprint) to every storefront AJAX response |
| PHP | before_dispatch (admin area and console only; static handler License\Client::onBeforeDispatch) |
Runs the due license check and shows license notices |
| Schema | block_manager/blocks (cart_content cache) |
License state in the cart block’s cache key |
| 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 |
| Template | index:styles (post), index:scripts (post) |
Registers LESS and JS |
| Template | index:body (pre) |
Sticky bar |
| Template | checkout:minicart (pre) |
Mini-cart placement (core blocks/cart_content.tpl) |
| Template | checkout:payment_options (pre) |
Cart page placement (guarded by $location == "cart") |
| Template | checkout:shipping_rates (pre) |
Checkout placement (lite checkout shipping step) |
| Template | products:notification_items (post) |
“Added to cart” popup (guarded by checkout.add) |
| Template | products:product_detail_bottom (post) |
Product page (lazy) |
All hooks exist in CS-Cart 4.17.2 (Ultimate and Multi-Vendor) and in Multi-Vendor 4.18.3 and 4.21.2.
License check
Section titled “License check”Tygh\Addons\KvFreeShippingProgressBar\License\Client verifies the license against the Kavio Labs license server.
fn_kv_free_shipping_progress_bar_is_licensed()is the storefront gate. When it returnsfalse,fn_kv_free_shipping_progress_bar_is_enabled()returnsfalsefor every placement,fn_kv_free_shipping_progress_bar_get_bar_data()returns an empty bar and the Block Manager block renders nothing. It reads the stored state only (one storage read per request) and never contacts the server.- The core cart block (
cart_content) is cached for guests with an empty cart. Its cache key includes the license state (blocks.post.php,callable_handlers), so the mini-cart bar appears or disappears at once. 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_free_shipping_progress_bar(?:storage_data), orvar/kv_storage/kv_free_shipping_progress_bar/license.jsonwhile the full page cache is active, protected by an HMAC with the store’s crypt key. - Cron:
php admin.php --dispatch=kv_free_shipping_progress_bar_license.check. - Admin UI:
License\Clientassigns the view data of the license card to$kv_lic_kv_free_shipping_progress_barin itsbefore_dispatchhandler, 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 callfn_kv_free_shipping_progress_bar_license_ui($context). The add-on has no admin pages of its own, so it uses no status bar. - 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.
Data flow
Section titled “Data flow”- A placement template includes
components/bar.tplwithplacement,layoutandbar_id. bar.tplcallsfn_kv_free_shipping_progress_bar_get_bar_data(). The result is cached per request and per cart fingerprint, and errors are caught: on failure the bar is hidden and the page is never broken.GoalCalculator::calculate():- reads
$cart['product_groups'](calculated by the core) and filters out groups that need no shipping, groups shipped by the marketplace and download-only groups; - per group, runs
ShippingThresholdDetector::detectThreshold()on each shipping method of the group (rate_info= the customer’s rate area). The package is built likeShippings::_getPackageInfo(): free-shipping and download products are excluded for methods flagged Use for free shipping; - in Ultimate without calculated groups (empty or restored cart) it loads the storefront’s methods with
Shippings::getShippingsList()for the customer location; - adds promotion goals from
PromotionGoalDetector::detect()(corefn_get_promotions()+fn_check_promotion_conditions()on a copy of the cart with a simulated subtotal); - maps every goal onto the cart subtotal axis (
target = subtotal + remaining), sorts the goals and builds the markers.
- reads
bar_content.tplrenders the messages with__()and the amounts withcommon/price.tpl.
View data (fn_kv_free_shipping_progress_bar_get_bar_data())
Section titled “View data (fn_kv_free_shipping_progress_bar_get_bar_data())”[ 'show' => bool, // render the bar at all 'is_empty' => bool, // empty cart → intro texts 'subtotal' => float, // primary currency 'percent' => float, // 0–100 of the main bar 'show_main' => bool, // there are cart-level goals 'next_goal' => ?array, // nearest unreached goal 'last_reached' => ?array, 'reached_goals' => array, 'all_reached' => bool, 'markers' => array, // [position (%), reached, type, title, target] 'vendors' => array, // MV rows: company_id, name, remaining, threshold, reached, percent 'track_max' => float, 'state' => string, // 12-char cart fingerprint 'style' => string, // validated CSS custom properties]// goal: ['type' => free_shipping|gift|discount|reward, 'title', 'target', 'remaining', 'reached', 'source' => shipping|manual|promotion]AJAX model
Section titled “AJAX model”-
Every bar lives in a stable holder
<div class="kv-fspb-holder" … id="…">…<!--id--></div>. Following the core convention,idis the last attribute and the closing comment is required byTygh\Ajax. AJAX replaces only the holder’s inner HTML. -
Holder IDs start with
cart_status_kv_fspb_orcheckout_kv_fspb_, so the coreresult_idswildcards (cart_status*on add to cart and mini-cart delete,cart*,checkout*on the cart form,checkout*on lite checkout) re-render them with no extra request. -
dispatch_before_displayassignskv_fspb_stateto each storefront AJAX response.func.jslistens toce.ajaxdoneand compares it with thedata-ca-kv-fspb-stateof every bar. Out-of-date bars are re-rendered with one hidden request:GET index.php?dispatch=kv_free_shipping_progress_bar.refresh&result_ids=<ids>&bars[<id>][placement]=…&bars[<id>][layout]=…The endpoint accepts AJAX requests only (404 otherwise). It validates IDs (
^(cart_status_kv_fspb|checkout_kv_fspb|kv_fspb_lazy)_[a-z0-9_]{1,64}$), placements (whitelist, must be enabled) and layouts, with at most 20 bars per request. It only reads the session cart. -
Lazy bars (
lazy=true, ID prefixkv_fspb_lazy_) render an empty holder. Use them inside block-cached content: the product page main block is cached for guests (cache_overrides_by_dispatch['products.view']).func.jsloads them once.commoninit.
Template API
Section titled “Template API”{include file="addons/kv_free_shipping_progress_bar/components/bar.tpl" placement="block" {* minicart|cart|checkout|notification|product|sticky|block *} layout="compact" {* full|compact *} bar_id="cart_status_kv_fspb_my_header" {* unique, see prefixes above *}}Helpers callable from templates: fn_kv_free_shipping_progress_bar_get_bar_data(), fn_kv_free_shipping_progress_bar_is_enabled($placement), fn_kv_free_shipping_progress_bar_get_config().
Styling
Section titled “Styling”All selectors are scoped under .kv-fspb / .kv-fspb-holder (BEM-like: __message, __track, __fill, __marker, __unlocked, __vendors, modifiers --compact, --full, --reached, --celebrate, --animated, --<placement>, --sticky-top|bottom). Themes can override these custom properties:
.kv-fspb { --kv-fspb-fill: #1f8a4c; --kv-fspb-reached: #1f8a4c; --kv-fspb-track: rgba(0, 0, 0, .09); --kv-fspb-text: inherit; --kv-fspb-sticky-bg: #fff; --kv-fspb-radius: 999px; --kv-fspb-height: 8px;}Admin color settings are emitted as inline custom properties on .kv-fspb and win over theme CSS. RTL is supported ([dir="rtl"]), and animations are disabled under prefers-reduced-motion: reduce.
Accessibility
Section titled “Accessibility”- Bar:
role="progressbar",aria-valuemin="0",aria-valuemax="100",aria-valuenow,aria-valuetext(plain-text message) andaria-label. - Holder:
aria-live="polite", so updates after add to cart are announced. - Icons are decorative (
aria-hidden). The sticky close button has an accessible label and a visible focus ring.
Extending
Section titled “Extending”- Different texts per placement: override
components/bar_content.tplin your theme, or call__()with your own variables in a custom block template that includesbar.tpl. - Own placement: include
bar.tplin any template hook with a uniquebar_id(prefixcart_status_kv_fspb_). - Custom goal sources: the calculation is encapsulated in
GoalCalculator. An injectable promotions provider is available as the 4th constructor argument for tests.
Security notes
Section titled “Security notes”- All SQL goes through
db_*placeholders. Serialized data from the database is unserialized withallowed_classes => false. - Colors are validated against a hex pattern. Amounts are parsed strictly. Promotion and vendor names are HTML-escaped.
- The refresh endpoint never changes state, and its input is whitelisted.
- The license check endpoint (
kv_free_shipping_progress_bar_license.check) accepts only CSRF-protected POST requests of administrators, and console requests. Vendors cannot use it.
Uninstall
Section titled “Uninstall”fn_kv_free_shipping_progress_bar_uninstall() removes every block of type kv_free_shipping_progress_bar with Block::instance()->remove(), which also cleans snapping, descriptions and content. The core removes settings and language variables.
Third-party code
Section titled “Third-party code”Icons are adapted from Feather (MIT License).
Tygh/Addons/KvFreeShippingProgressBar/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.