Developer notes
Files and data
Section titled “Files and data”| Item | Value |
|---|---|
| Add-on id | kv_b2b_quote_request (scheme 3.0, priority 4700) |
| Namespace | Tygh\Addons\KvB2bQuoteRequest in app/addons/kv_b2b_quote_request/Tygh/Addons/KvB2bQuoteRequest/ |
| Tables | ?:kv_rfq_quotes, ?:kv_rfq_quote_items, ?:kv_rfq_messages, ?:kv_rfq_attachments, ?:kv_rfq_rules (product/category display rules), ?:kv_rfq_drafts (saved quote lists of signed-in customers) |
| Privileges | view_kv_b2b_quote_request, manage_kv_b2b_quote_request (group kv_b2b_quote_request, section Orders) |
| Controllers | kv_b2b_quote_request (front end and back end) |
| Files | var/custom_files/kv_b2b_quote_request/<quote_id>/ (custom_files storage) |
| License | Tygh/Addons/KvB2bQuoteRequest/License/Client.php (license check client, generated from the shared Kavio Labs template), controllers/backend/kv_b2b_quote_request_license.php (“Check now” / cron license check), design/backend/templates/addons/kv_b2b_quote_request/license/info.tpl (License tab (points to the card)), design/backend/templates/addons/kv_b2b_quote_request/license/card.tpl (License card (add-on page) / banner), design/backend/templates/addons/kv_b2b_quote_request/license/status_bar.tpl (status chip/banner for own admin pages), design/backend/templates/addons/kv_b2b_quote_request/license/badge.tpl (badge in Add-ons > Manage add-ons), design/backend/templates/addons/kv_b2b_quote_request/hooks/addons/ (tabs_content.pre.tpl (card), manage.post.tpl (badge)), design/backend/templates/addons/kv_b2b_quote_request/hooks/index/styles.post.tpl (loads license.less), design/backend/css/addons/kv_b2b_quote_request/license.less (card, chip and badge styles (admin LESS variables)) |
Prices in quotes are stored in the primary currency, like product prices. Status codes: N New, R In review, O Offered, A Accepted, D Rejected, E Expired, C Ordered. Rule modes: B next to Add to cart, O quote only, D do not show (no row = inherit).
How quoted prices are enforced
Section titled “How quoted prices are enforced”Accepting an offer adds each item with extra.kv_rfq = {quote_id, item_id}; prices and quantities never come from the request.
| Hook | What happens |
|---|---|
pre_add_to_cart |
Removes kv_rfq markers sent by the browser; on cart updates restores the marker of existing quote lines from the server-side cart; refuses products that are quote only for the visitor. |
generate_cart_id |
Quote lines get their own cart id, so they never merge with regular lines. |
exclude_products_from_calculation |
Runs before core reads product prices. Each quote line is checked against the database: the visitor owns the quote (same account, or a guest session that opened the signed link), the quote is Accepted and not expired, item, product and options match. Valid lines get stored_price = Y, the quoted unit price (quantity break by amount) and the allowed quantity; anything else is removed from the cart. |
get_cart_product_data_post |
Undoes catalog promotions on quoted lines unless “Apply catalog promotions to quoted prices” is on. |
pre_place_order |
The complete quote must be in the cart with allowed quantities; then one atomic UPDATE … SET status = 'C' WHERE status = 'A' reserves it. A second session loses the race and the order is refused. |
place_order |
Stores the order id (the vendor’s child order in Multi-Vendor). |
Quoted prices apply on the storefront only; the back-end order editor keeps the prices stored in the order (stored_price).
Hidden prices and the button
Section titled “Hidden prices and the button”gather_additional_product_data_post (storefront) adds $product.kv_rfq = {mode: B|O, hide_price: bool} and, for hidden prices, zeroes every price field (price, list_price, discounts, quantity discounts, zero_price_action) so core templates print no price. The SEO add-on’s structured data gets no offer price (seo_get_schema_org_markup_items_post). The button is rendered in the buttons:add_to_cart hook: .override for quote only, .post for “next to”.
The result depends only on the product and the visitor’s user groups. Product blocks and the cached category page get the user group signature in their block cache key (callable handler fn_kv_b2b_quote_request_block_cache_key, only when the settings make the output group-dependent) and kv_rfq_rules as an update handler.
Full page cache (CS-Cart 4.21)
Section titled “Full page cache (CS-Cart 4.21)”- Product pages: the button depends on user groups only; guests all get the same output.
- Per-visitor output comes from the account menu (
my_accountis rendered per visitor) and the Quote list block (session_dependent,cache => false); after an AJAX add the counters are updated byfunc.jsfrom the response. - The add-on’s own pages are not cacheable dispatches. No
?:storage_datawrites: offer expiry is a cheap indexedUPDATEwhen quote pages load.
Notifications
Section titled “Notifications”Events (schemas/notifications/events.post.php, vendor receivers in events_multivendor.post.php): kv_b2b_quote_request.created, .offer_sent, .staff_message, .customer_update. Data provider: QuoteDataProvider (variables: quote_number, customer_name, customer_label, company_name, items[] with product, options_text, requested_qty, quantity, price, subtotal, tiers[], total, currency_code, valid_until, shipping_note, message, author_name, status_name, update_title, quote_url, is_guest). E-mail templates are Twig and valid in Twig 2 and 3; prices use the core |price(currency_code) filter.
Core hooks and schemas used for licensing
Section titled “Core hooks and schemas used for licensing”| Type | Hook / schema | Purpose |
|---|---|---|
| PHP | before_dispatch (admin area and console only; static handler License\Client::onBeforeDispatch) |
Runs the due license check and shows license notices (not on the add-on’s own pages, which show the status bar) |
| Schema | block_manager/blocks (products cache; main cache overrides of categories.view, companies.products, products.view) |
The existing callable handler fn_kv_b2b_quote_request_block_cache_key() returns off without a valid license, so cached product blocks and pages switch between the quote button / hidden prices and normal prices at once |
| 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 |
License check
Section titled “License check”Tygh\Addons\KvB2bQuoteRequest\License\Client verifies the license against the Kavio Labs license server.
fn_kv_b2b_quote_request_is_licensed()is the gate. It reads the stored state only (one storage read per request) and never contacts the server. When it returnsfalse:gather_additional_products_data_pre/gather_additional_product_data_postadd nokv_rfqdata and hide no prices: no “Request a quote” button, normal prices;pre_add_to_cart(CartBridge::filterAddToCart()) no longer refuses quote-only products;- the storefront controller redirects
add,update_cart,submitandcartto My quotes (guests: the home page) without a message, refusesnegotiate,rejectandmessage, and allowsacceptonly for quotes that are already Accepted (puts them into the cart again); the quote page is read-only (kv_rfq_readonly); fn_kv_b2b_quote_request_cart_count()andfn_kv_b2b_quote_request_offers_count()return 0 (account menu, Quote list block);- the REST API refuses customer
PUT(messages, rejections); reading and staff access are unchanged; - the admin quote list and quote page show the license status bar as a banner with what is switched off (root administrators only, never vendors).
- Not gated, so accepted quotes keep working:
CartBridge::validateCart()(quoted price on every cart calculation),keepQuotedPrice(),reserveForOrder(),linkOrder(), the back end, notifications, expiry and the REST API for staff. - 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_b2b_quote_request(?:storage_data), orvar/kv_storage/kv_b2b_quote_request/license.jsonwhile the full page cache is active, protected by an HMAC with the store’s crypt key. - Cron:
php admin.php --dispatch=kv_b2b_quote_request_license.check. - Admin UI:
License\Clientassigns the view data of the license card to$kv_lic_kv_b2b_quote_requestin 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_b2b_quote_request_license_ui($context). Every full page of the add-on’s own admin controllers (quote list, quote page) includeslicense/status_bar.tplas 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_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. - The license check endpoint (
kv_b2b_quote_request_license.check) accepts only CSRF-protected POST requests of administrators, and console requests. Vendors cannot use it.
Hooks for developers
Section titled “Hooks for developers”| Hook | Arguments | When |
|---|---|---|
kv_b2b_quote_request_submit_pre |
&$quote, $group_items, $form, $auth |
Before a quote row is saved (once per vendor quote). |
kv_b2b_quote_request_get_quotes |
$params, &$conditions |
Quote list query (add SQL conditions). |
kv_b2b_quote_request_notify_pre |
$event, &$data, &$rules |
Before an event is dispatched. |
REST API
Section titled “REST API”/api/kv_b2b_quotes (administrators; vendors with “vendor quotes” on; customers with a token see their own quotes).
| Method | Path | Description |
|---|---|---|
| GET | /api/kv_b2b_quotes |
List. Filters: status, email, company_id, group_id, order_id, q, page, items_per_page. |
| GET | /api/kv_b2b_quotes/:id |
Quote with items and messages (staff also get internal notes). |
| PUT | /api/kv_b2b_quotes/:id |
Staff: items ({item_id: {quantity, price, discount, tiers: [{qty, price}], delete}}), shipping_note, valid_until (timestamp), send_offer (bool, with message), status + notify_customer, message + is_internal. Customer: message, reject. |
| DELETE | /api/kv_b2b_quotes/:id |
Delete (administrators only). |
Quotes are created on the storefront; accepting needs the customer’s cart, so it is a storefront action. Example:
-d '{"items":{"12":{"price":24.5,"quantity":100,"tiers":[{"qty":500,"price":22}]}},"send_offer":true,"message":"Thank you"}' \ https://shop.example.com/api/kv_b2b_quotes/41Upgrades
Section titled “Upgrades”The add-on ships Upgrade\Synchronizer: after files are replaced in place, the first administrator request adds missing tables, columns, settings, language variables and e-mail templates, and updates the add-on version. Disable with define('KV_UPGRADE_SYNC_DISABLED', true);.
Third-party code
Section titled “Third-party code”Tygh/Addons/KvB2bQuoteRequest/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.