Skip to content

Developer notes

Overview · Installation · Configuration · Usage · FAQ

One series per product and storefront (storefront_id; Multi-Vendor usually has one, Ultimate one per storefront whose company owns or shares the product). Each row is a period with unchanged values:

Column Meaning
price Base price for quantity 1 and the All usergroup (fn_get_product_price(); Ultimate: the storefront’s shared price)
list_price The product’s list price
final_price The price a guest sees on that storefront after catalog promotions (fn_get_product_data() + fn_gather_additional_product_data() with discounts, without options, guest auth)
valid_from, valid_to The period; valid_to = 0 is the current row
source I initial, E product edit, S scheduled check, P promotion change, M import or mass update

A row is written only when one of the three values changes; the previous row gets valid_to. Several changes within the same second update the row in place. Amounts are stored in the primary currency, like CS-Cart stores prices.

  • update_product_post, update_product_prices, tools_change_status (products): the product is queued and captured at the end of the same request (shutdown) and by the next run.
  • update_promotion_post, delete_promotions_pre, tools_change_status (catalog promotions), import_post (product patterns), global_update_products: a full check is requested.
  • A catalog promotion’s start or end date passed since the last run: a full check starts (scheduled promotions).
  • Every Full check every (hours): a full check (changes made outside CS-Cart functions).
  • Runs: the console cron admin.php --dispatch=kv_omnibus_price_history.cron, the storefront cron URL index.php?dispatch=kv_omnibus_price_history.cron&key=…, the admin fallback (shutdown of admin page views, at most once a minute per administrator session, 8 seconds; the check only reads and its throttle mark is kept in the session, so an admin page view without work writes nothing), Check prices now, and the installation. A run processes the queue, continues the full check from a cursor (batches of Products per batch), and prunes once a day. Run marks and the cursor are kept in ?:storage_data, or in files under var/kv_storage/kv_omnibus_price_history/ while the full page cache is on (a write to ?:storage_data would purge the whole page cache). Storefront page views never write: StorageData::set() and Recorder::queue() return without writing on storefront GET/HEAD requests (only the cron URL records), and catalog promotions that start or end are picked up by the next cron run or admin page view; until then the label of an affected product is hidden, never wrong (the visitor’s price no longer matches the recorded final price).
  • During a capture the hook promotion_apply_before_get_promotions gives the core the promotions of the storefront being recorded (the core caches the request storefront’s promotions in a static variable) and restores the core’s list afterwards.
  • Before the reduction: start = the latest valid_to of a row whose final_price differs from the current one (the moment the current final price took effect). The value is MIN(final_price) of the rows that overlap [start − N days, start). Without such a row, the current final price (no_history).
  • Rolling: MIN(final_price) of the rows that overlap [now − N days, now].

History::getLowest($product_ids, $storefront_id, $days, $calculation) returns current, lowest, start, since and no_history per product.

gather_additional_products_data_post (storefront area only) adds $product.kv_oph = amount, days, in_lists, text, reduced in one batch (three indexed queries per product list plus one per product with an earlier price). The label is added only when the visitor’s base price equals the recorded base price and, without option modifiers, the visitor’s final price equals the recorded final price. A products:product_data post hook appends the label to the core price captures of common/product_data.tpl: on the product page to list_discount_<id> (printed after the price, the old price and “You save” by the Responsive, Nova and Bright product templates), in product lists to clean_price_<id> (printed after the price and the old price by the grid, list and compact list templates). The label has its own cm-reload-<id> container, so CS-Cart refreshes it when options change.

Full page cache (4.21). The full page cache serves cached pages only to guests; logged-in customers get the fpc_disable_cache cookie. The label depends only on the guest price and the history, so it is identical for all guests; currency and language are part of the cache key. No session is read and nothing is written on storefront page views.

Block cache. The main block of products.view and categories.view and the products block get kv_oph_prices and kv_oph_excluded as update handlers and the label settings plus the license state as a callable cache key.

Table Purpose
?:kv_oph_prices The history (row_id, product_id, storefront_id, price, list_price, final_price, valid_from, valid_to, source); keys (product_id, storefront_id, valid_to), (product_id, storefront_id, valid_from), (valid_to), (valid_from)
?:kv_oph_queue Edited products waiting for a capture
?:kv_oph_excluded Products excluded from the label

Deleting a product deletes its history. Uninstalling drops the three tables.

Dispatch Area Purpose
kv_omnibus_price_history.manage Admin The history page
kv_omnibus_price_history.export Admin CSV of the filtered rows
kv_omnibus_price_history.export_product?product_id= Admin, vendor (own products) CSV of one product
kv_omnibus_price_history.run (POST) Admin Check prices now
kv_omnibus_price_history.cron Console / storefront with key One run (JSON summary)
products.update (post controller) Admin, vendor The Price history tab

Tygh\Addons\KvOmnibusPriceHistory\License\Client (generated) verifies the license. Gate point: the storefront label (Config::isLabelActive() in the gather hook; the license state is part of the block cache key). Not gated, by design: recording, the Price history tab, the history page and the CSV export, so the history has no gaps while a license problem is being fixed.

Separate series per usergroup (the label is hidden for visitors with a different price), quantity discount tiers, progressive reductions as one reduction period, per-category exclusions.

Path Purpose
app/addons/kv_omnibus_price_history/addon.xml Settings, tables
init.php, func.php Hooks, install/uninstall, admin fallback, settings info handlers, fn_kv_omnibus_price_history_is_licensed()
Tygh/Addons/KvOmnibusPriceHistory/Recorder.php Capture, queue, runs, full check, prune
History.php Lowest price, searches, chart data, CSV export, exclusions
Label.php Storefront label data
Config.php, StorageData.php Settings with limits, run marks (FPC-safe)
License/, controllers/backend/kv_omnibus_price_history_license.php License check (generated)
Upgrade/Synchronizer.php Self-healing upgrades (generated)
controllers/backend/, controllers/frontend/ History page, exports, Price history tab, cron
schemas/ Menu, permissions (vendor CSV of own products), block cache
var/themes_repository/responsive/templates/addons/kv_omnibus_price_history/ The label (components/label.tpl, hooks/products/product_data.post.tpl)
design/backend/templates/addons/kv_omnibus_price_history/ Tab, page, settings info templates