Developer notes
Overview · Installation · Configuration · Usage · FAQ
How values are saved
Section titled “How values are saved”Every change is written with core fn_update_product($data, $product_id, $lang_code), one product at a time, with only the changed fields in $data:
- built-in fields as on the product page (
price,list_price,amount,status,weight,free_shipping,shipping_freight,product,product_code,page_title,meta_description,meta_keywords,search_words,short_description,full_description,seo_name); - categories as
category_ids+main_category; - features as
product_features[<feature_id>](variant id for select features,''removes the value).
So every hook of core and other add-ons runs (update_product_pre, update_product_post, update_product_categories_post, update_product_features_value_pre, …): SEO names and redirects, product variations, master products, vendor premoderation, stock notifications, block cache table tags and full page cache bans behave as after a save on the product page. Core notifications raised during a save are collected and shown in the grid instead of as pop-ups.
After the saves of a request (or a step), the values are read back from the database. The change log stores the real old and new values; a requested change that core or an add-on refused is reported as not accepted and not logged.
Price: the base price (quantity 1, all user groups) in ?:product_prices; in Ultimate with a storefront selected, that storefront’s ?:ult_product_prices row when it exists. Texts: ?:product_descriptions in the content language, ?:ult_product_descriptions of the selected storefront when it exists.
Data model
Section titled “Data model”| Table | Purpose |
|---|---|
?:kv_bpe_batches |
One row per save, mass action or undo: user, company (vendor or storefront scope), language, type (I inline, M mass, U undo), status (R running, A applied, X undone, F failed), summary (JSON: fields or the action), product ids of the scope (JSON), cursor, counters, messages, times |
?:kv_bpe_changes |
One row per changed cell: batch, product, field (price, categories, f_<feature_id>, …), old value, new value |
?:kv_bpe_views |
Saved views: user, company, name, shared flag, parameters (JSON: filters, columns, page size) |
Values are stored as canonical strings: prices 12.50, weight 1.250, stock -3, status A/D/H, flags Y/N, categories main:12,15,18, select features the variant id.
Controllers and endpoints
Section titled “Controllers and endpoints”controllers/backend/kv_bulk_product_editor.php (admin and vendor panel):
| Mode | Method | Purpose |
|---|---|---|
manage |
GET | The page |
rows |
GET | One page of products: filters (JSON), columns (JSON), page, per_page |
categories, variants |
GET | Category search, variants of a select feature |
log, changes |
GET | Change log, cell changes of an entry |
save |
POST | Inline changes: changes = JSON [{id, field, value, orig}] (orig = the value the grid loaded, for the conflict check) |
preview, apply_start |
POST | Mass action: spec (JSON: field, op, value, mode, rounding, find, case, categories), scope (selected with ids or filter with filters) |
step |
POST | Runs the next step of a running action or undo (batch_id) |
undo_start |
POST | Starts the undo of an entry |
view_save, view_delete |
POST | Saved views |
POST requests need the admin security_hash (core CSRF check). Permissions: schemas/permissions/admin.post.php (GET view_catalog, POST manage_catalog) and vendor_multivendor.post.php (the vendor setting). Every product id is checked against what core fn_get_products() shows the current user; entries and views are scoped to the user’s company or storefront.
Large catalogs
Section titled “Large catalogs”- The grid pages through
fn_get_products()with the filters, so core and add-on conditions (vendor scope, storefront scope, variations) apply; the visible columns are then read with a few batch queries per page. - A mass action resolves its scope once (product ids in pages of 1,000, at most 100,000) and stores it in the batch. Each
stepclaims the next slice atomically (a second tab or a double click never applies a slice twice) and saves the products of the slice. - Measured on Multi-Vendor 4.21.2 / PHP 8.3 with 10,000 products: a page of 250 rows with filter and sorting in about 0.15 s; resolving 10,000 ids in about 1 s; a price change on 10,000 products in 200 steps of 50 in about 80 s (slowest step under 0.5 s); its undo in about the same time. Times depend on the server and on the add-ons that act on product saves.
Full page cache (4.21)
Section titled “Full page cache (4.21)”The add-on has no storefront output and writes nothing from storefront requests. Saved products are refreshed in the page cache by core (update_product_post).
License check
Section titled “License check”Tygh\Addons\KvBulkProductEditor\License\Client (generated) verifies the license. Gate points (fn_kv_bulk_product_editor_is_licensed(), Access::licensed() in class code): the save and apply_start modes. Not gated: the grid and all GET endpoints, saved views, the change log, undo_start and step (so an action that was already running can finish and earlier changes can always be undone).
Not in 1.0.0
Section titled “Not in 1.0.0”Product options and option combinations, images, multiple-checkbox and date features, creating and deleting products, and file import/export (use core Export/Import). Short and full descriptions are changed with find and replace only.
File map
Section titled “File map”| Path | Purpose |
|---|---|
app/addons/kv_bulk_product_editor/addon.xml |
Settings, tables |
init.php, func.php |
License and upgrade blocks, settings info handler, fn_kv_bulk_product_editor_is_licensed() |
Tygh/Addons/KvBulkProductEditor/Fields.php |
Editable fields, validation, canonical values, features |
Reader.php |
Filters, paging, reading grid values (Ultimate overrides, SEO names, features) |
Writer.php |
Saving through fn_update_product(), conflict check, read-back, change log rows |
MassAction.php |
Mass-action rules, rounding, preview |
ChangeLog.php |
Batches, the step runner, undo, log list, retention |
Views.php, Access.php, Ui.php |
Saved views, scope and permissions, configuration of the grid script |
License/Client.php, License/Ed25519.php, controllers/backend/kv_bulk_product_editor_license.php |
License check (generated) |
Upgrade/Synchronizer.php |
Self-healing upgrades (generated) |
controllers/backend/kv_bulk_product_editor.php |
Page and JSON endpoints |
schemas/ |
Menu (admin, vendor panel configurator), permissions (admin, vendor) |
design/backend/templates/addons/kv_bulk_product_editor/ |
Page template, license card/bar/badge (license/info.tpl for the License tab) |
design/backend/css/addons/kv_bulk_product_editor/styles.less |
Grid styles (admin LESS variables, light and dark) |
js/addons/kv_bulk_product_editor/grid.js |
The grid (ES5 + jQuery, no external libraries) |