Developer notes
File map
Section titled “File map”app/addons/kv_vendor_sponsored_products/ addon.xml settings, 9 tables, 2 privileges init.php / func.php hooks, block content function, cache key, visitor data, licence helpers controllers/backend/kv_sponsored.php campaigns, moderation, statistics, CSV, posted charges, vendors, cron controllers/backend/kv_vendor_sponsored_products_license.php "Check now" and licence cron controllers/frontend/kv_sponsored.php tracking endpoint (event) controllers/frontend/categories.post.php category strip data controllers/frontend/products.post.php search strip and product page row data schemas/block_manager/blocks.post.php block type + cache keys of the cached main blocks schemas/menu/*.post.php Marketing > Sponsored products (admin, vendor panel) schemas/permissions/*.post.php privileges, vendor modes, cron as trusted controller schemas/security/csrf_validation.post.php tracking endpoint without form token Tygh/Addons/KvVendorSponsoredProducts/ Config.php settings (amounts in cents), placements, days, rotation bucket Campaigns.php storage, validation, approval rules, vendor flags Selector.php slot selection (cacheable), ranking, visibility through fn_get_products Token.php signed slot tokens, hashed visitor data, signed click cookie Tracker.php impressions, clicks, click-fraud guard Billing.php charges under a vendor lock, budgets, available balance, funded flag FreshPayouts.php core VendorPayouts without the per-request balance cache Settlement.php posting to core vendor accounting, crash repair Stats.php daily statistics, order attribution, reports, CSV Maintenance.php scheduled tasks (cron and pseudo-cron) Money.php cents StorageData.php run marks (files instead of storage_data while the full page cache is on) License/, Upgrade/ licence client, self-healing upgradesdesign/backend/templates/addons/kv_vendor_sponsored_products/ pages, components, settings info, licence UIvar/themes_repository/responsive/templates/addons/kv_vendor_sponsored_products/ components/strip.tpl the sponsored slot markup hooks/categories/view_description.post.tpl, hooks/wrapper/mainbox_general_title_wrapper.post.tpl, hooks/products/product_tabs.post.tpl, blocks/sponsored/{grid,row}.tplvar/themes_repository/responsive/css/addons/kv_vendor_sponsored_products/styles.lessjs/addons/kv_vendor_sponsored_products/func.js (storefront tracking), backend.js (campaign form)var/langs/en/addons/kv_vendor_sponsored_products.poMoney model
Section titled “Money model”- A charge is a row in
kv_spads_charges(click or fixed day). It is recorded under a MySQL named lock per vendor (GET_LOCK), after checking the campaign (status, approval, dates, vendor active and not blocked), the daily budget (kv_spads_spend), the total budget and the vendor’s available amount:core balance − unposted charges − minimum balance. All comparisons use integer cents. A charge never exceeds a budget or the available amount, also with concurrent clicks. - The core balance is read through
Tygh\VendorPayouts::getBalance()(subclassFreshPayoutsonly skips the per-request Registry cache of the sums), so Vendor plans commission and every add-on that hooksvendor_payouts_get_income/_get_balanceare applied. - Posting (
Settlement) groups unposted charges per vendor and day (or hour) into one core?:vendor_payoutsrow:payout_type = payout, positivepayout_amount,approval_status = C, comment with the referenceSP-<settlement id>. Core counts such a row against the vendor income and as admin income (the Vendor plans plan fee uses the same row type). Vendor debt payout only completes pending payout rows when a debt is paid, so posted ad charges are never touched by it. - Posting order: settlement row (status N) → charges bound to it → core row → settlement status P. A run finds settlements left in status N (crash) and finishes them when the core row with the reference exists, otherwise unbinds the charges and deletes the row. One posting run at a time (
GET_LOCK), and each vendor is posted under the vendor lock. - Direct payments: such vendors receive order money directly and usually have no positive balance, so their campaigns pause for not enough balance until they top up. Ads never charge into a balance below the minimum you keep.
- Uninstall keeps every posted core row; unposted charges are dropped with the tables.
Tables
Section titled “Tables”| Table | Purpose |
|---|---|
kv_spads_campaigns |
Campaign: vendor, name, pricing (C cost per click, F fixed), bid, daily and total budget, placements, dates, status (A, H paused, E ended), approval (P, Y, R, S), reason, exhausted day + reason |
kv_spads_campaign_products |
Products of a campaign |
kv_spads_spend |
Spend and clicks per campaign and day; fixed-price day charged flag |
kv_spads_stats |
Daily statistics per campaign, product and placement: impressions, clicks, invalid clicks, spend |
kv_spads_clicks |
Click log (charged and filtered with a reason); IP, user agent and visitor id are stored only as keyed hashes |
kv_spads_charges |
Money ledger of recorded charges, settlement id once posted |
kv_spads_settlements |
Postings: vendor, period, amount, number of charges, core payout id, status |
kv_spads_attributions |
Order lines that followed a click: order, product, campaign, amount |
kv_spads_vendors |
Per vendor: blocked, automatic approval, funded flag |
Identical arguments in Multi-Vendor 4.17.2 and 4.21.2.
| Hook | Use |
|---|---|
place_order |
Attribute order lines to clicks (storefront orders only) |
delete_order |
Remove attribution rows |
vendor_payouts_update_post |
A vendor balance changed (order, refund, withdrawal, top-up): re-check whether the vendor can pay for its campaigns; resumes campaigns paused for balance |
delete_company |
Remove the vendor’s campaigns and ad data |
delete_product_post |
Remove a deleted product from campaigns |
before_dispatch |
Pseudo-cron in the admin and vendor panel (every 15 minutes, GET pages only) |
init_templater_post |
Self-healing upgrade (only while pending) |
Template hooks: categories:view_description (post), wrapper:mainbox_general_title_wrapper (post, main content block of the search page only), products:product_tabs (post), index:styles, index:scripts.
Full page cache (4.21) and block cache
Section titled “Full page cache (4.21) and block cache”- The slot selection depends only on the context (category, search query, product), the live campaigns and a 15-minute rotation bucket, never on the visitor. Pages stay cacheable for all guests.
- The live campaign list is read through
Registry::registerCachetagged withkv_spads_campaigns,kv_spads_campaign_products,kv_spads_vendorsandcompanies: approving, pausing or a budget running out purges the cached pages. - The cached main blocks of
categories.viewandproducts.viewget these tables as update handlers and a callable handler (licence state, rotation bucket, settings). - No storefront GET writes anything (no
storage_data, no session). Impressions and clicks go toindex.php?dispatch=kv_sponsored.eventbynavigator.sendBeacon(POST, never cached, no session write).
Tracking endpoint and click protection
Section titled “Tracking endpoint and click protection”kv_sponsored.event accepts POST only and answers 204. Tokens printed in the slots are <campaign>-<product>-<placement>-<bucket>-<HMAC> (key derived from the store’s crypt key) and are accepted for 48 hours. Clicks: bot user agents → invalid; administrators and the campaign vendor’s users → ignored; same visitor (cookie kv_spads_vid, else hash of IP + user agent), campaign and product within the dedupe window → invalid duplicate; per-IP limits per campaign and day and per hour → invalid. Behind a local reverse proxy the first public X-Forwarded-For address is used. Charged clicks set the signed cookie kv_spads_clk (last 10 clicks, attribution window).
Cookies: kv_spads_vid (random visitor id for click deduplication, 1 year) and kv_spads_clk (signed list of recent sponsored clicks for order attribution; not set when the window is 0).
License check
Section titled “License check”fn_kv_vendor_sponsored_products_is_licensed() (License\Client::isActive()) gates the storefront slots and the block, the tracking endpoint (impressions, clicks, charges, attribution) and posting. Campaign editing, moderation, statistics and CSV are never gated. The licence check itself runs only in the admin panel or from cron (php admin.php --dispatch=kv_vendor_sponsored_products_license.check).
Extension points
Section titled “Extension points”- Change the markup: copy
components/strip.tplinto your theme (design/themes/<theme>/templates/addons/kv_vendor_sponsored_products/components/strip.tpl). - Other positions: place the Sponsored products block, or include
components/strip.tplwith a slot array from\Tygh\Addons\KvVendorSponsoredProducts\Selector::forCategory(),forSearch(),forProduct()orforBlock(). - Read statistics:
\Tygh\Addons\KvVendorSponsoredProducts\Stats::totals($filter, 'campaign_id'|'company_id'|'day').