8 min readOct 16, 2026by jakub

Changelog

Current version: 3.0.0

Combined history for qoliber/omnibus-directive and its two optional companions. qoliber/mysql-utils and qoliber/hyva-module-registration are shared dependencies with their own release cycles and are not tracked here.

3.0.0 — 2026-09-27

Omnibus Directive

Added

  • A -X% discount badge next to the lowest 30-day price, under Stores > Configuration > Qoliber > Omnibus Directive > Discount Badge. X is the reduction from the prior price, rounded down so it never overstates the discount; the badge shows only when Show Discount Badge is on and X is at least Minimum Discount (%) (default 10). It renders on product pages, category and search listings, and CatalogWidget listings. Dynamic product prices only: the Static attribute is not the price in effect before the current one, so a badge calculated from it could claim a bigger reduction than the directive allows.
  • qoliber_omnibus_directive_price_log, which records every change of a product's effective price per website and customer group.
  • A daily cron job, qoliber_omnibus_directive_expire_stale_reductions (00:15), that ends reductions once they have lasted 30 days.
  • Model\HistoricalPriceDisplay, the single rule for when the figure and the badge are displayed, shared by the Luma price box, the Luma listing renderer and the Hyvä templates.

Changed

  • BREAKING In Dynamic mode the index now stores the Omnibus prior price: the lowest price in effect during the 30 days BEFORE the current price took effect. It used to include the current price, so a reduction was its own "lowest price" and the figure was hidden exactly when the directive requires it. The prior price stays fixed while a reduction lasts and becomes the current price once that has lasted 30 days. When no earlier price is known, the current price is used, so no reduction is claimed without evidence. The index column is still min_price, so the registry, bulk loader and ElasticSuite read it unchanged. Price history starts when this version is first indexed.
  • BREAKING With Always Show Historical Price off, the figure now shows whenever the current price differs from it - after a reduction, and after a rise from a lower 30-day price - and hides only when they are equal. It used to show only when the current price was higher.
  • The full reindex pages through products by id instead of LIMIT/OFFSET without an ORDER BY, which did not guarantee every product was visited.
  • composer.json declares support.security.

Removed

  • The 30-day date prune of the index table. The index is derived from the price log on every run, and the log is pruned of entries that can no longer reach any window.

Omnibus Directive for Hyvä

Added

  • The -X% discount badge on Hyvä product pages and listings, styled with Tailwind utilities (added to tailwind-classes.phtml so they survive the purge). ViewModel\HyvaHistoricalPrice::getDiscountPercent() exposes it, and getHistoricalPriceData() carries it as discount_percent.

Changed

  • BREAKING Requires qoliber/omnibus-directive ^3.0: the templates and HyvaHistoricalPrice use Model\HistoricalPriceDisplay, which 3.0.0 adds.
  • The product-page template and HyvaHistoricalPrice::shouldShowHistoricalPrice() now use the shared Model\HistoricalPriceDisplay rule from qoliber/omnibus-directive, so Hyvä and Luma decide the same way. With Always Show Historical Price off, the figure shows whenever the current price differs from it, including after a reduction.
  • composer.json declares support.security.

Omnibus Directive for ElasticSuite

Changed

  • BREAKING Requires qoliber/omnibus-directive ^3.0, released in lockstep with the core module. No code changes: the index this module reads, qoliber_omnibus_directive_min_price_index, keeps its schema, and its min_price now holds the core module's Omnibus prior price.
  • composer.json declares support.security.

2.0.0 — 2026-08-13

Omnibus Directive

Findings F1–F24 refer to the repository's code-review audit, which documents each one in full: the defect, the file and line as originally imported, the failure it caused, the fix, and the test (where one exists) that proves it.

Removed

  • BREAKING The Hyvä presentation layer — layouts, templates, and ViewModel\HyvaHistoricalPrice — moved to the new qoliber/omnibus-directive-hyva package. This module no longer renders anything on a Hyvä storefront by itself; stores running Hyvä must also install and enable Qoliber_OmnibusDirectiveHyva. The pricing pool wiring, the registry, GetDynamicPrice, and Model\Configuration stay here and are shared by both storefronts.
  • qoliber/core dependency, replaced by a <tab id="qoliber"> declaration in this module's own system.xml (see F23 below).

Changed

  • BREAKING Model\Configuration's five getters (isEnabled, getProductPricesType, isShowOnPlp, isShowOnPdp, isAlwaysShow) now accept an optional store code and resolve at ScopeInterface::SCOPE_STORE; etc/adminhtml/system.xml opens the section, its group, and all five fields to showInWebsite="1" showInStore="1" (F7). A multi-store install can now set the Omnibus configuration per store view, which was not possible before.
  • BREAKING The price-recording observer was renamed Observer\UpdateHistoricalPriceAdminhtmlObserver → Observer\UpdateHistoricalPriceObserver and rebound from catalog_product_save_after (global, but a no-op — see F2) to catalog_product_save_before (global). etc/adminhtml/events.xml, which duplicated the binding for the admin area only, was deleted.
  • composer.json: "license" corrected to "proprietary"; explicit php (~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0) and magento/framework constraints added; the qoliber.dev "source" block dropped (F19, F20, F21).

Fixed

  • F1 Service\GetDynamicPrice::execute() treated a registry miss as a real price of 0.0 instead of falling back to a per-product query, because HistoricalPriceRegistry::getPrice() (F10) always returned a float, never null. Any product outside a bulk-loaded registry silently lost its historical price. Now branches on isBulkLoaded() && hasPrice().
  • F2 The price-history observer was bound to catalog_product_save_after globally and to catalog_product_save_before only under adminhtml. On save_after its setData() calls are never persisted, so REST, SOAP, bin/magento import, and ERP-driven price changes — most real-world price changes — recorded no history at all. Now bound once, to catalog_product_save_before, globally.
  • F3 Model\Indexer\MinPrice::executeList() issued one SELECT per price row to read the existing minimum (measured: 8278 queries over an 8156-row index). Replaced with one batched SELECT ... WHERE product_id IN (?) per call (1687 queries after).
  • F4 getAllProductIdsBatch() hardcoded entity_type_id = 4 for the status attribute. Now resolved via Magento\Eav\Model\Config.
  • F5 deleteOldPrices() ran once per 1000-product batch inside executeList() instead of once per reindex. Hoisted to execute(), executeFull(), and executeRow().
  • F6 The product-status join filtered attribute_id but not store_id, so a product with a store-scoped status value was yielded once per scope row (12 duplicates measured on this catalogue). Added cpev.store_id = 0.
  • F7 See "Changed" above.
  • F8 Plugin\Block\Product\AbstractProductPlugin was an around plugin that called $subject->getDetailsRenderer('historical_price') with no null guard. getDetailsRenderer() returns null whenever a layout has no renderer-list block at all, which fatals the unguarded call. Converted to after, added a null/instanceof guard, and an explicit isShowOnPlp() check.
  • F9 Pricing\Render\HistoricalPriceBox::__construct() used implicit nullable parameters, deprecated as of PHP 8.4. Made the ?Type nullability explicit.
  • F10 HistoricalPriceRegistry::getPrice() cast a missing entry to 0.0. Now returns ?float; hasPrice() remains the presence check.
  • F11 view/frontend/templates/hyva/tailwind-classes.phtml (now in qoliber/omnibus-directive-hyva) built a $classes safelist array that was never output — Tailwind's regex file scanner picked it up regardless, so the mechanism worked by accident. Now rendered into a hidden element so intent and mechanism agree.
  • F12 system.xml gave three fields the same sortOrder="10". PHP 8's stable sort meant declaration order was preserved regardless, so this was a determinism/hygiene fix, not a live rendering bug. Renumbered 10/20/30/40/50.
  • F22 Categories with Display Mode "Static block only" render their product grid through Magento\CatalogWidget\Block\Product\ProductsList, whose renderer list (category.product.type.widget.details.renderers) this module never targeted — only the native category grid's list was wired. Every such category (7 of this install's: gear, collections, training, men, women, promotions, sale) showed no historical price at all. Added view/frontend/layout/catalog_widget_product_list.xml registering the same renderer on the widget's list.
  • F23 etc/adminhtml/system.xml referenced <tab>qoliber</tab> with no matching <tab id="qoliber"> declared anywhere — previously supplied by the now-dropped qoliber/core package. Magento\Config\Model\Config\ Structure throws while assembling the tab tree, which broke Stores > Configuration for every admin user, not only this section. The module now declares its own <tab id="qoliber"> and no longer depends on another Qoliber package to be installable.
  • F24 MinPrice::executeList() computed min($price['price'], $price['final_price'], $price['min_price']). Composite types (grouped, bundle, downloadable) carry NULL in price/ final_price in catalog_product_index_price; PHP's min() ranks NULL below any real number, so it was chosen over a genuine min_price, and MySQL coerced that NULL to 0 in the NOT NULL index column — a permanent, silent "price has never dropped" for every composite product. NULL candidates are now filtered out before min(), and a row is skipped entirely rather than written as a fabricated 0 when no price data exists.

Known limitations

  • F4, F9, F11, F21 have no dedicated falsifying test — each is a portability, hygiene, or packaging fix with no observable behavioural difference on this install. See the audit for the reasoning specific to each. (F13–F18 are documented in app/code/Qoliber/OmnibusDirectiveElasticSuite/CHANGELOG.md.)

[Unreleased changes prior to 2.0.0 were not tracked in this file.]

Omnibus Directive for Hyvä

First release of this package. It starts at 2.0.0 rather than 1.0.0 so that every module in the Omnibus Directive set carries the same version, which is the convention across Qoliber suites — the Hyvä companion is not versioned independently of the core module it renders for.

Added

  • Initial release. The Hyvä presentation layer moves here out of qoliber/omnibus-directive, which shipped it up to and including 1.0.6.
  • ViewModel\HyvaHistoricalPrice, exposing the historical price to Hyvä templates without pulling Hyva\Theme classes into the core module.
  • Hyvä layout handles for the product page, category page, search results page, and the Tailwind safelist template (hyva_catalog_product_view.xml, hyva_catalog_category_view.xml, hyva_catalogsearch_result_index.xml, hyva_default.xml).
  • Product list renderer templates (historical-price.phtml and the Alpine.js variant historical-price-alpine.phtml) rendered through the core module's theme-agnostic Block\Product\List\Renderer\HistoricalPrice.

Notes

  • The pricing pool wiring, the registry, the dynamic price service and the configuration model stay in qoliber/omnibus-directive and are shared with the Luma templates; only the Hyvä view layer and its HyvaHistoricalPrice view model live here.

Omnibus Directive for ElasticSuite

Fixed

  • ElasticSearch lookups never executed: the service called Layer\Resolver::create() at view time, which throws once the catalog controller has created the layer, and a bare catch (\Exception) silently fell back to MySQL. The service now consumes the resolved layer.
  • The database fallback queried website_id = 0 because a null website id was cast to int.
  • Fallbacks are logged instead of discarded.

Removed

  • BREAKING GetDynamicPricePlugin, which walked the whole layer collection once per product on a listing page while bypassing the registry.
  • BREAKING ElasticSearchHistoricalPriceService::getHistoricalPriceForProduct(), whose only caller was that plugin.

Changed

  • Depends on RequestInterface rather than the concrete Http request.
  • Requires qoliber/omnibus-directive ^2.0 and PHP 8.1-8.5.

1.0.2 — 2025-10-16

Omnibus Directive for ElasticSuite

Released 2025-10-16.

Fixed

  • Fixed ElasticSearchHistoricalPriceService for better compatibility with ElasticSuite

1.0.1 — 2025-09-17

Omnibus Directive for ElasticSuite

Released 2025-09-17.

Changed

  • Updated HistoricalPriceData datasource to align with changes in main OmnibusDirective module
  • Improved integration with OmnibusDirective configuration changes

1.0.0 — 2025-08-28

Omnibus Directive for ElasticSuite

Released 2025-08-28.

Added

  • Initial release of Omnibus Directive ElasticSuite integration module
  • Implemented ElasticSearch index integration for historical prices
  • Added HistoricalPriceData datasource for Smile ElasticSuite fulltext indexing
  • Implemented ElasticSearchHistoricalPriceService for retrieving prices from ElasticSearch
  • Added plugin for BulkLoadHistoricalPrices to use ElasticSearch when available
  • Added plugin for GetDynamicPrice to leverage ElasticSearch for price retrieval
  • Implemented elasticsuite_indices.xml configuration for historical price indexing
  • Added historical price fields to ElasticSearch product index:
    • historical_price - indexed as decimal
    • historical_price_date - indexed as date
  • Enabled high-performance price retrieval from ElasticSearch instead of MySQL
  • Added automatic fallback to database when ElasticSearch is unavailable
  • Implemented dependency on smile/elasticsuite and qoliber/omnibus-directive modules

Performance

  • Significantly improved historical price loading performance on category pages using ElasticSearch
  • Reduced database queries by leveraging ElasticSearch for bulk price operations
  • Optimized price data retrieval for large product catalogs

Integration

  • Seamless integration with Smile ElasticSuite search engine
  • Compatible with OmnibusDirective module's registry and service layer
  • Automatic detection and usage of ElasticSearch when module is enabled
Changelog — Omnibus Directive — Compliance & Legal — Extensions | qoliber Docs