Skip to content

Developer notes

Table Contents
?:kv_mao_offers One row per offer: company (MV vendor of the product, ULT storefront company), storefront, product snapshot (name, code, options as JSON, options text), quantity, list_price (cart unit price at offer time, primary currency), offer_price, counter_price, final_price, customer (user id or e-mail and name), status, rounds, respond_until, accept_until, access_key (secret of the signed link), order id, auto (A/D), consent time, dates.
?:kv_mao_messages History: author (C customer, S staff, X store), action (offer, counter, accept, decline, withdraw, expire, order, reopen), price, message.
?:kv_mao_rules Rules per object (P product, C category, V vendor): mode (‘’/Y/N), min_price (products), accept_pct, floor_pct (NULL = inherit).

P waiting for the merchant · C counter-offer (waiting for the customer) · A accepted · O ordered · D declined · W withdrawn or counter declined by the customer · E expired.

Every change is a conditional UPDATE ... WHERE status IN (...), so two people answering at the same time cannot both win.

  • An offer line carries only extra.kv_mao = {offer_id}. Only CartBridge::addOfferToCart() creates it; pre_add_to_cart removes markers sent by the browser and keeps the server-side marker on cart updates. generate_cart_id gives the line its own cart id.
  • exclude_products_from_calculation runs on every calculation before the core reads prices: the visitor must own the offer (same account; guest offers: a session grant from the signed link or the submission), status A, accept_until not passed, same product and options. Valid lines get stored_price = Y, the agreed price and exactly the offered quantity; anything else is removed.
  • get_cart_product_data_post removes catalog promotions from offer lines (unless the policy is “All promotions”); calculate_cart_items sets $apply_cart_promotions = false while an offer line is in the cart (policy “Offer price only”).
  • pre_place_order: the quantity must equal the offer quantity, then UPDATE ... SET status = 'O' WHERE status = 'A' AND accept_until >= now (single use; all offers of the cart or none). place_order links the order (Multi-Vendor: the vendor’s child order). change_order_status to F, D or I and delete_order give the offer back while its deadline has not passed.
  • The list price is computed with fn_get_cart_product_data() on a temporary cart (quantity discounts, option modifiers, catalog promotions, stock check), never taken from the request.

gather_additional_products_data_post (button flag on the product page and quick view), pre_add_to_cart, generate_cart_id, exclude_products_from_calculation, calculate_cart_items, get_cart_product_data_post, pre_place_order, place_order, change_order_status, delete_order, update_product_post, update_category_post, clone_product, delete_product_post, delete_category_post, delete_company, post_delete_user. Signatures are identical in 4.17.2 and 4.21.2.

Template hooks: buttons:add_to_cart (post), checkout:product_options (post, cart badge), profiles:my_account_menu (post), index:scripts / index:styles; admin products:detailed_content, categories:detailed_content.

Own hook: kv_make_an_offer_notify_pre($event, &$data, &$rules) before every notification.

The product page prints only a static button (it depends on the product, the rules and the settings, never on the visitor; the products.view main block cache gets the rules table and the licence state as keys). The dialog body, the offer pages and all actions are AJAX or POST requests to kv_make_an_offer.*, which is not a cacheable dispatch. Storefront requests write only to the add-on’s tables.

Area Dispatch
Storefront kv_make_an_offer.form POST (AJAX): dialog body for the posted product form; GET: page version
kv_make_an_offer.submit POST: create an offer
kv_make_an_offer.manage, .view My offers, offer page (offer_id, key)
.accept_counter, .decline_counter, .new_offer, .withdraw, .add_to_cart POST actions (offer_id, key)
Admin / vendor kv_make_an_offer.manage, .update, .export, .vendor_rules List, offer page, CSV, vendor defaults
.accept, .counter, .decline, .delete, .m_delete POST actions (delete: administrators only)
Console admin.php --dispatch=kv_make_an_offer.cron Expires overdue offers and sends the e-mails

Storefront POSTs are CSRF-protected (schemas/security/csrf_validation.post.php); the form also has a honeypot field.

app/addons/kv_make_an_offer/
addon.xml init.php func.php
controllers/backend/kv_make_an_offer.php, products.post.php, categories.post.php, kv_make_an_offer_license.php
controllers/frontend/kv_make_an_offer.php
schemas/block_manager/blocks.post.php products.view cache keys
schemas/menu/menu.post.php, menu_vendor.post.php, actions.post.php
schemas/notifications/events.post.php, events_multivendor.post.php
schemas/permissions/admin.post.php, vendor_multivendor.post.php
schemas/security/csrf_validation.post.php
Tygh/Addons/KvMakeAnOffer/
Access.php ownership, signed links, session grants
CartBridge.php cart lines, price enforcement, single use
Config.php settings, statuses
Notifier.php events
OfferDataProvider.php e-mail data
OfferService.php offer life cycle, limits, automatic answers, expiry
Pricing.php options, list price, amounts
Repository.php offers, history, search, statistics
Rules.php rule resolution
License/ Upgrade/ license client, self-healing upgrades
design/backend/templates/addons/kv_make_an_offer/ admin pages, rule fields, hooks
var/themes_repository/responsive/templates/addons/kv_make_an_offer/ button, dialog body, My offers, offer page, cart badge
js/addons/kv_make_an_offer/func.js dialog
var/langs/en/addons/kv_make_an_offer.po