38 min readOct 16, 2026by jakub

Changelog

Current version: 1.5.0

1.5.0 — 2026-09-11

Product Labels

Added

  • New Api/LabelUpdateInterface::update() — the PUT route now binds to this instead of the shared save(), so the URL id is always authoritative (see Fixed)
  • The label indexer's insert chunk size (INSERT ... ON DUPLICATE KEY UPDATE batching, default 1000) is now a constructor argument on Model/Indexer/ProductLabels, matching the existing configurable batchSize
  • dev/tools/qoliber/validate.sh — a local, repeatable release-validation script (PHP 8.1 lint, a Rector downgrade floor guard, composer validate, the unit and integration suites, base-module and add-on install combinations — plain ProductLabels, +Creator, +Hyvä, all — production di:compile/static-content deploy per combination, and the full Playwright suite including the cache-required projects below) and dev/tools/qoliber/cache-mode.sh (switches the dev store between the built-in full page cache, Varnish and its normal disabled state for those proofs); documented in docs/validation.md
  • A Playwright end-to-end suite in tests/playwright proving that label edits reach cached storefronts with no manual flush, and that REST enforces its ACL and its id binding over real HTTP. The cache specs (specs/cache/**, projects cache-builtin and cache-varnish) require the built-in FPC or Varnish and fail rather than skip when it isn't active. They cover: a text edit on Luma and Hyvä product and category pages and on Luma and Hyvä CMS pages with a Catalog Products List widget (also when such a page was rebuilt from the cached widget block); stop_processing, disabling a label and moving active_from into the past on Luma and Hyvä product and category pages; a priority change on Luma and Hyvä category pages; and, built-in FPC only, the schedule-boundary cron reaching Luma and Hyvä category pages cached before a label started or ended. The REST specs (specs/api/**) exercise restricted ACL roles and the PUT id binding, including the query-string requests the old route got wrong
  • New integration suites proving the indexer, the scheduler and the admin Save through Magento's own machinery: cron execution via crontab.xml/Magento\Cron\Model\Config (Test/Integration/Cron/FlushScheduledLabelCacheCronTest), mview consumption of product/category/stock changes in "Update by Schedule" mode (Test/Integration/Mview/LabelMviewTest), insert/page batch-boundary and mid-batch-failure rollback behaviour (Test/Integration/Edge/IndexerBatchBoundaryTest), multi-currency/composite-pricing label text variables (Test/Integration/Edge/PriceVariablesTest), and the admin label Save through real routing and DI, a failing reindex included (Test/Integration/Controller/Adminhtml/Label/SaveTest)
  • Label priority and "Stop Further Label Processing" (client request) — labels can now be reordered on the storefront instead of always sorting by id. A new priority field (grid-sortable, admin form field, priority ASC, label_id ASC processing order) and a stop_processing checkbox let a merchant say "once this label is shown, render nothing lower in the stack." The flag only takes effect when its own label actually displays (a suppressed-by-trigger label never stops anything), and a higher-priority label always paints visually on top of lower-priority ones (Label::getStackOffset() drives the wrapper z-index). Both fields are exposed on the label grid (priority, stop_processing columns) and over REST (LabelInterface::STOP_PROCESSING + getter/setter on LabelInterface)
  • Api/LabelManagementInterface::create() — POST /V1/qoliber-productlabels/label now routes to a dedicated create-only service contract instead of the shared save(), so a POST can no longer silently overwrite an existing label by id
  • New Api/ProductLabelIdentitiesInterface::getIdentitiesByProduct(), implemented by RendererPool — returns the cache identities of every label indexed for a product, built through the normal label-block pipeline (so any afterGetIdentities plugin still applies) without an extra query for already-preloaded products
  • A schedule-boundary cron (qoliber_productlabels_flush_scheduled_label_cache, every minute) purges both the page cache and the app cache for products whose enabled label just started or stopped being active, so a label's active_from/active_to boundary no longer waits for the next full reindex or product save to be reflected on cached pages. Products are purged one tag each up to 1000 products; above that the generic product tag is purged instead (a whole-catalogue label would otherwise send hundreds of PURGE requests to every Varnish node)
  • Storefront {PRICE}/{SAVE_PERCENT}/{SAVE_AMOUNT} variables now read from the product's price info (tax/display aware) instead of the raw price attributes

Security

  • SVG label sanitizer rewritten as an element/attribute allowlist (Model/Label/SvgSanitizer) — the previous denylist missed real bypasses found this cycle: undeclared-namespace xlink:href, case-obfuscated attribute/element names (HREF, <set ATTRIBUTENAME="href">), HTML-parser integration points inside <desc>/<title> that let attacker markup re-parent the surrounding page, CDATA/comment breakout, and CSS url()/image-set() escapes — including CRLF-terminated escapes that survived naive decoding. The sanitizer now case-folds ASCII-only (never strtolower//i, both locale-sensitive and reopen the SMIL href vector under locales such as tr_TR), rebuilds a fresh document from an explicit element and attribute-name allowlist, constrains id/class to safe identifier syntax, and checks CSS in linear time (the first behavior-property check was quadratic and could take 8+ seconds on a crafted 100 KB stylesheet). <style> blocks are kept (Illustrator exports rely on them for .cls-* styling) but their CSS text is rejected wholesale if it contains @import, a non-local url(), image-set, expression(, behavior/-moz-binding or a stray backslash
  • Fixed stored XSS in the admin label grid — the preview and position columns rendered label_text, label_text_color, label_base64 and label_position straight from stored data with no escaping; a crafted value (or a row with malformed label_serialized) could inject markup into the grid or blank the whole grid response. Both columns now decode through the shared normalizer and escape every output (escapeHtml/escapeUrl/escapeHtmlAttr), and a bad row is skipped rather than breaking the page
  • Fixed storefront XSS via the base64, image and shape label templates — a crafted data: URI, image URL or shape reference could break out of its HTML attribute. All three templates now escape through escapeHtmlAttr/escapeUrl and only render values the normalizer judged safe
  • Label data (label_serialized, qty_trigger) is now normalized and validated on every write path — the admin Save controller, the resource model (so REST and any other writer gets the same guarantees), and again on every read — instead of trusting whatever was previously persisted
  • The admin form's custom-SVG preview is now rendered inside an <img data-bind="attr: {src: ...}"> instead of a knockout html: binding, so even a value that slipped past server-side sanitization can only ever be rasterized, never executed, in the preview pane
  • The admin label grid previews custom SVG as an <img src="data:image/svg+xml;base64,..."> too, instead of printing the sanitized SVG inline: inline, the <style> sheets and element ids the allowlist keeps applied to the whole admin page
  • Fixed CSRF-able label and design deletes — Delete controllers accepted plain GET requests; they are now POST-only (HttpPostActionInterface), and the grid's delete action is submitted as POST
  • Tightened ACL — Edit/NewAction now declare their own resource instead of inheriting the parent controller's, the label listing's aclResource is Labels_grid, and new condition rows are built by the module's own NewConditionHtml controller (Qoliber_ProductLabels::Label_save, POST) instead of the Cart Price Rules controller, so editing label conditions no longer needs the Cart Price Rules permission (Magento_SalesRule::quote). The SKU and category choosers inside a condition are still core Catalog Price Rule widgets and still need Magento_CatalogRule::promo_catalog
  • Hardened label image uploads — server-side image validation (validateUploadFile), a 2 MB size cap enforced before the file is written, and an upload response that returns only name/file/url/size/type (no server paths)

Fixed

  • A failed realtime label reindex is now recoverable, and Save reports it truthfully. A realtime ("Update on Save") reindex failure was logged and the label's own cache tag purged, but the indexer was never invalidated and nothing arranged a retry — a stale or missing index could only be fixed by a manual reindex, and the admin Save controller still showed "You saved the Product Label." (or, for Save and Apply, "The rule has been applied to products.") as if nothing had gone wrong. The observer now invalidates the indexer on a realtime failure (indexer_reindex_all_invalid, so the next indexer cron run rebuilds it) and records the outcome; Save shows a warning instead — "The label was saved, but it could not be applied to products. The label index was marked for a rebuild, which runs with the next indexer cron." — for a plain Save and for Save and Apply alike, and Save and Apply no longer claims the label was applied. When the index could not be marked for a rebuild either — the recovery invalidate failed as well, or a scheduled-mode invalidate failed (a deadlock on the indexer state table, for example) — the failure is logged and the warning says so and asks for a manual reindex (bin/magento indexer:reindex qoliber_productlabels_indexer, or Save and Apply in "Update by Schedule" mode). A scheduled-mode Save and Apply in that case skips the warning and applies the label itself (which leaves nothing for the missed rebuild), reporting only the apply's own result; an observer failure before the indexer mode is known gets the manual-reindex warning and no apply. Otherwise Save and Apply works as in 1.4.x: in "Update by Schedule" mode it reindexes the label, also after an edit that could not change matching
  • Related, upsell and cross-sell product images keep their own labels. The image plugin suppressed label rendering for every product image on a page once the PDP's main gallery had rendered its own label, regardless of which product the image actually showed — a related or upsell item shown after the main gallery got no label at all. Suppression is now scoped to the same product only (the gallery's own product id vs. the image's product id); an image with no known product renders unmodified instead of risking a fatal
  • Scheduled-mode label saves now purge pages after commit too. In "Update by Schedule" mode, a save that affects the index (status, conditions, store views) invalidated the indexer and returned without purging anything beyond core's own in-transaction purge, which runs before the commit — a request landing between that purge and the commit could re-cache the old label state, and nothing purged it again until the next full reindex. Disabling a label, for instance, could keep showing it on an already-cached page. The scheduled path now falls through to the same post-commit purge every other save path already runs
  • Catalog Products List widget blocks are now tagged with their listed products' labels (qlbl_<id> plus the generic qlbl), so a label edit that needs no reindex (text, priority, stop_processing, triggers) purges the widget instead of waiting for its block cache (up to 86400 s) to expire on its own. This also covers a page rebuilt while the widget's own block_html entry is still cached — the widget's complete identities are stored alongside that cache entry and merged back in on a hit, so a later label or date-window edit still purges it correctly
  • The schedule-boundary cron now ends its window on the database clock the storefront uses to decide whether a label is active, instead of PHP's clock: with PHP running ahead of the database, a label's start could be purged before the storefront actually started showing it, and the page cached in between would keep it hidden with nothing left to purge it later. Label expiry is likewise purged only once the label has actually disappeared from the storefront (in [from, to) rather than treating active_to itself as already expired), so a page cached in that same second doesn't get purged while the label still shows
  • PUT /V1/qoliber-productlabels/label/:labelId now always updates the label named in the URL. Magento's own body-id override only applies to a single-key body with no query string, so with a query string (?x=1) or several top-level body fields the body's label_id decided which label was written: a different id updated that other label, and no id created a new one. The route now targets a dedicated Api/LabelUpdateInterface::update(), which binds the URL id authoritatively; an unknown id now returns 404 instead of creating a new label
  • Full-page cache tags were dropped whenever a label rendered. RendererPool wrote the page's X-Magento-Tags header directly, discarding whatever core's own cache-tag preprocessor had already collected (cat_p/cat_c and friends). Any page that rendered a label was effectively never purged by ordinary product/category saves. Label tags are now merged into core's PageCacheTagsPreprocessorComposite output through a plugin instead of being written independently
  • The indexer now resolves its table names through ResourceConnection::getTableName(), which applies the configured table prefix (it used the database adapter's getTableName(), which does not, so on installs with a table prefix the indexer addressed a table that does not exist), rebuilds transaction-safely instead of truncating, and isolates one label's failure from the rest — a single label with an unsupported condition operator no longer empties the whole index; its previous rows are removed and every other label still indexes correctly
  • Per-product/per-store validation replaces a SQL pre-filter that over-matched: "SKU is X" no longer also matches "X-2" (the pre-filter used a LIKE), and store-view-scoped attribute values are now respected instead of ignored. Negated conditions (!=, "ALL FALSE" groups) are validated correctly by skipping the pre-filter entirely when a condition tree can't be safely narrowed by SQL — merchants may see fewer labelled products where the old matching over-matched
  • Scheduled and expired labels are now indexed (previously excluded by the indexer's own date filter, duplicating RendererPool's date check and hiding labels that should reappear once their window opens)
  • Store-view normalisation is now consistent everywhere — [0] (All Store Views) expands to every real store view id, and ["0","1"]-style mixed string/int lists are normalised the same way on write, read, and in the indexer
  • Added catalog_category_product and catalog_product_website mview subscriptions, so category and website reassignment now triggers a reindex the way product saves already did
  • Saving a label now keeps the index fresh — realtime mode ("Update on Save") reindexes that label immediately; scheduled mode ("Update by Schedule") invalidates the indexer, so the next cron run does a full reindex (as core Catalog Price Rules do), and "Save and Apply" additionally reindexes that label right away — instead of waiting for a manual reindex. Only saves that can change matching (a new label, its conditions, status or store views) touch the index
  • Label saves that need no reindex (text, colour, image, priority, "Stop Further Label Processing", triggers) now purge the label's cached pages after the save is committed. Before, only core's purge inside the save transaction ran: a request between that purge and the commit could cache the old label again, and nothing purged it later; block-cached HTML tagged with the label (Hyvä list items) was never cleaned
  • Moving a label's date window (active_from into the past, clearing it, extending an expired active_to) now purges the pages of the products the label is indexed for (per product up to 1000, the generic product tag above). Pages cached while the label was outside its window never showed it and carried no label tag, so these edits were never reflected until the pages expired
  • Partial REST updates (PUT with a subset of fields) no longer wipe conditions_serialized; the repository now merges a detached payload onto the stored label instead of overwriting it wholesale
  • An unresolvable {ATTR:typo} text variable no longer fatals the page; it renders as empty text
  • Composite product prices in label text variables — {PRICE} no longer shows 0.00 for configurable, bundle and grouped products; grouped products (no regular-price model of their own) show the minimal final price of the group, and their {SAVE_*} values are 0 (see Changed)
  • Countdown labels ({SPDL}/{SPHL}) now use the store timezone consistently, actually keep ticking after the page loads (previously froze at the first render), and initialize without CSP violations on a strict-CSP Hyvä store
  • Labels now render for products that were never preloaded into the current request (for example, a CMS widget product list), not only for products loaded through the main collection
  • The stock-quantity join used for the qty trigger and {STOCK} now filters to the default stock, instead of potentially joining an unrelated stock row
  • The admin grid no longer breaks entirely when one row has malformed label_serialized; that row is skipped and the rest of the grid still renders
  • Text-only labels (visual type "Text", or a label whose image, shape or design is missing) no longer render half outside the product image on Luma and Hyvä. With no artwork to centre over, the label box collapsed to 0x0 on its corner and the text was centred on that point (one word per line); the text is now laid out in normal flow and the label box is anchored at its position. Labels with artwork render as before
  • priority and stop_processing are validated on every write path: a priority that is not a whole number of 0 or more is refused with "Priority must be a whole number of 0 or more." (REST clients used to get the raw SQL error, or a silently stored 0), and stop_processing is stored as 0/1 (a REST "5" was stored as 5 and shown blank)
  • Listing pages decode and sanitize each label's data once per request instead of once per product showing it (a 20 KB custom SVG label cost about 90 ms extra on a 36-product listing)
  • Fixed several admin form-widget bugs: the quantity-trigger "enabled" checkbox now round-trips correctly regardless of whether the stored value is a boolean, 1 or "1"; the text-size range slider and rotate rotary switch now stay in sync when the underlying observable changes programmatically (not only on user drag)

Changed

  • Custom SVG is re-sanitized on read. Existing labels lose whatever falls outside the new allowlist: <a>, <switch>, <foreignObject>, <feImage>, <mpath>, <metadata>, editor-namespace attributes, markup inside <desc>/<title>, and <image> hrefs that are http:// or relative. <style> sheets are kept where their CSS passes the new safety checks. No data migration runs; this happens automatically the next time each label is rendered, saved, or shown in the grid
  • Labels now require at least one store view — REST clients (and the admin form) that previously saved a label with an empty stores array now get a validation error instead of a silently store-view-less label
  • Matching is stricter in general (see Fixed above): SKU "is" is exact, store-view attribute values are respected. Review your labels' conditions if you were relying on the old, looser matching
  • data-countdown-end replaces data-special-to-date on the label wrapper markup. Any theme override of label/wrapper.phtml (Luma or Hyvä) that reads the old attribute must be updated, or its countdown will stop working
  • {PRICE}/{SAVE_PERCENT}/{SAVE_AMOUNT} now come from price info rather than raw attributes, so they reflect tax display settings and (for grouped products) the minimal final price; grouped-product savings are always 0 since a grouped product has no own regular price to compare against
  • Constructor signature changes (not @api, but affects any subclass or DI override that calls parent::__construct() positionally): Model\Label (the Combine $conditionFactory first parameter is removed), Renderer\RendererPool (ResponseInterface/SvgSanitizer removed; LabelDataNormalizer and LabelTagCollector added), Block\Renderer\Label (new required LabelDataNormalizer parameter), Controller\Adminhtml\Label\Save, Delete and Upload, Model\Indexer\ProductLabels (new LoggerInterface, LabelDataNormalizer, CacheContext, Model\Indexer\PageCacheCleaner and Model\Label\Variable\Resolver\Stock arguments before $batchSize, plus a new trailing $cachePurgeThreshold), Model\ResourceModel\Label (now has a constructor: LabelDataNormalizer and Json after the resource context), Block\Adminhtml\Label\Edit\Tab\Conditions (new LabelInterfaceFactory before $data), Ui\Component\Listing\Column\LabelPreview (SvgSanitizer replaced by LabelDataNormalizer and Escaper), Ui\Component\Listing\Column\LabelPosition (new LabelDataNormalizer, Magento\Framework\View\Asset\Repository and Escaper) and Ui\DataProvider\Label\Form\Modifier\ProductLabelHydrator (new LabelDataNormalizer). The unused Block\Adminhtml\Label\Edit\DuplicateButton was removed
  • Custom implementations of Api\Data\LabelInterface must add the new getStopProcessing()/setStopProcessing() methods
  • REST: a PUT /V1/qoliber-productlabels/label/:id for an id that does not exist now returns 404 instead of answering with success without saving anything, and an invalid priority returns 400 with a readable message
  • The label wrapper templates (label/wrapper.phtml, Luma and Hyvä) render the visual-type template before the wrapper and mark text-only labels (Luma: _text-only class on .product-label; Hyvä: the text drops absolute inset-0). Stores that styled around the old text-only layout will see those labels move inside the image
  • Known limitations:
    • A full reindex still runs inside a single database transaction. This is strictly safer than the previous truncate-then-rebuild (readers never see a half-built index), but a very large catalog produces a longer-lived transaction during a full reindex. A chunked/replica-swap approach is a candidate follow-up
    • Labels rendered inside Varnish ESI blocks do not add their label tags to the ESI response, so label edits that need no reindex do not purge those blocks
    • In "Update on Save" mode nothing reindexes labels or purges their pages when products or stock change; only label saves do
    • A failed Varnish PURGE is logged by Magento core and not retried (the schedule-boundary cron included) — a purge that a Varnish node drops for any reason is not resent until the next event that purges the same tag
  • The qoliber/mysql-utils dependency floor is raised to ^1.1, magento/module-page-cache to ^100.4.1 (needed for the cache-tag preprocessor plugin), magento/module-catalog-widget (the widget label-tags plugin — Magento_CatalogWidget must stay installed and enabled), and requires on magento/module-catalog-inventory, magento/module-config, magento/module-cron, magento/module-eav and magento/module-webapi were added to reflect code the module already used but did not declare

Upgrade notes

  • Run setup:upgrade for the new stop_processing column and the composite (product_id, store_id) index on the label-to-product table, then reindex (or let the scheduled indexer catch up) so priority ordering and the new store-scoped matching take effect
  • Web API contract changes: regenerate SOAP clients and clients generated from the REST schema. POST /V1/qoliber-productlabels/label and PUT /V1/qoliber-productlabels/label/:labelId no longer call LabelRepositoryInterface::save() but LabelManagementInterface::create() and LabelUpdateInterface::update() (see Added and Fixed); URLs and request and response bodies are unchanged. SOAP: the operation qoliberProductLabelsLabelRepositoryV1Save is gone, because no route references save() any more; creating is qoliberProductLabelsLabelManagementV1Create and updating is qoliberProductLabelsLabelUpdateV1Update, on the new services qoliberProductLabelsLabelManagementV1 and qoliberProductLabelsLabelUpdateV1. REST schema (/rest/<store>/schema): both operations are now tagged with their new service. Magento 2.4.4 builds operationIds (and body parameter names) from the service and method, so there qoliberProductLabelsLabelRepositoryV1SavePost/qoliberProductLabelsLabelRepositoryV1SavePut become qoliberProductLabelsLabelManagementV1CreatePost/qoliberProductLabelsLabelUpdateV1UpdatePut; later releases build them from the HTTP method and path, which are unchanged
  • Flush the full page cache (built-in or Varnish) after upgrading. Pages cached under 1.4.0 kept only the qlbl_* label tags in the X-Magento-Tags header the page cache reads (see Fixed), so product and category saves would never purge them
  • If any theme overrides label/wrapper.phtml, update it for data-countdown-end and for the text-only label layout (see Changed)
  • Use "Update by Schedule" for the label indexer, in particular for stock-driven labels (stock or qty triggers, {STOCK}): in "Update on Save" mode the label indexer receives no product or stock change events, so their pages are not purged when stock changes
  • The Qoliber_ProductLabelsMsi add-on is not part of this release's fixes. Its stock provider joins inventory_stock_<id>.product_id, a column only the default-stock view (inventory_stock_1) has; the index tables of custom MSI stocks are keyed by SKU, so a website assigned to a custom stock gets a database error. Keep the add-on disabled until it gets its own fix pass
  • Environments running the creatuity/magento2-interceptors compiled interceptor generator: only one around plugin per method is honoured, and adding a plugin can require clearing generated/staticcache for it to take effect. This release added afterGetIdentities/afterProcess-style plugins only (no new around plugins), but see that package's known limitations if you add your own
  • Magento_CatalogWidget must remain installed and enabled — the module now hard-requires magento/module-catalog-widget for the Catalog Products List label-tags plugin (see Changed)
  • rector/rector is only a require-dev of this repository's own development install (the floor guard used by dev/tools/qoliber/validate.sh); it is not, and should not be, a dependency of any shipped module

Label Designer (Creator)

Added

  • New Test/Integration/Model/CreatorVisualTypeTest — asserts visual type 5 ("Creator Design") is offered after the core types and that the Label block maps it to Qoliber_ProductLabelsCreator::label/type/creator.phtml, moved out of ProductLabels' own VisualTypesDiTest so ProductLabels' integration suite passes on an install without this module (a base-only install previously failed on assertions that only hold with Creator present)
  • Storefront and admin-preview label images from a Creator design now carry width/height attributes, sized from the design's own preview_width/preview_height (recorded at save time as half the canvas size) instead of rendering at native canvas resolution. The admin label-form preview also shows the design at this storefront size instead of at 2x. Pre-1.5.0 designs keep NULL sizes and render exactly as before
  • Api/DesignSearchResultsInterface is now actually implemented (Model/DesignSearchResults) — DesignRepository::getList() previously threw a TypeError on every call because it returned a plain SearchResults where the interface required the typed one
  • A dedicated grid collection (Model/ResourceModel/Design/Grid/Collection) selects only the columns the design grid needs, instead of loading full canvas_data per row

Security

  • Design delete is now POST-only (Controller/Adminhtml/Design/Delete implements HttpPostActionInterface), closing a CSRF-able GET delete
  • A design still referenced by a label (visual type 5 only — a label switched away from Creator no longer blocks the design it used to point at) can no longer be deleted; the admin sees which labels use it and must change those first
  • Design preview uploads are now validated: must decode as a data:image/…;base64, URI, be a real PNG or JPEG (checked by content, not the claimed MIME type), at most 5 MB decoded and 4096px per side — rejected before any file or database row is written
  • Preview files are saved atomically with a content-addressed, cache-busting filename (design_<hash>.png/.jpg) written before the database row is committed; a failed save releases the new file instead of leaving an orphan. The preview a design had before an update is kept: file names are content hashes, so an old URL keeps showing the old preview, and block-cached storefront HTML (Hyvä product list items, catalog widgets) can still reference it until it is rebuilt. Deleting a design removes its current preview file, unless another design has the identical preview
  • The storefront Creator template (label/type/creator.phtml) only passes int or string design IDs to the preview resolver: any other stored creator_design_id (an array, a boolean) renders nothing instead of throwing a TypeError that turned the product page into a 500
  • The editor's generated config JSON is now emitted through JsonHexTag instead of plain json_encode, closing a </script> breakout in the admin editor page

Fixed

  • Design::getList()/search — see Added; this also fixes the admin design grid, which relied on the same broken search-results contract
  • Fabric.js 7 compatibility: the editor's legacy type checks ('FabricText', string-based type comparisons) no longer match fabric 7's lower-cased instance types ('rect', 'i-text', etc.), which had silently broken the image-filter panel, group recolouring, text re-measurement after font load, and loading any pre-1.5.0 saved design (loadFromJSON rejected it outright). All type checks are now class-based (instanceof) or use fabric 7's actual lower-case type strings, and custom per-object controls (resize/rotate handles) are re-registered for fabric 7's createControls() API
  • A design whose stored canvas data fails to load (legacy incompatible shape, or corrupted JSON) no longer lets the admin save over it with a blank canvas — Save is disabled until the stored design finishes loading, and stays disabled with an explicit error message if loading fails
  • Save is also disabled while a stored design's web fonts are still loading (capped at 8 seconds), so a save can no longer bake in a fallback font instead of the intended one
  • Fixed the blank-canvas overwrite in general: the editor previously allowed Save while a design was still being parsed
  • Design name search / references to a stale Model\Design when the requested design id doesn't exist now show "This design no longer exists." and redirect to the grid, instead of a fatal
  • Admin preview size for Creator designs on the label form now matches storefront size (was previously 2x the storefront rendering)
  • Saving or deleting a design now refreshes the pages and cached blocks that show it. A plugin (Plugin\Renderer\AddDesignIdentityPlugin) adds qlblc_design_<id> (what a design save purges from the full page cache) and the generic qlblc_design (what it cleans from the block cache) to the cache tags of every Creator label; before, cached pages and Hyvä product list items kept showing the old design
  • Images uploaded into the editor are scaled down so their longest side is at most twice the canvas's longest side (a 2000 px photo on a 300 px canvas is embedded at 600 px), which keeps the stored canvas data and the previews small

Changed

  • Preview upload cap raised from an initial 2 MB to 5 MB decoded (a full-resolution export of a photo-heavy 1200px canvas can exceed 2 MB); a base64-encoded 5 MB preview is roughly 7 MB over the wire, so installs with a low post_max_size/client_max_body_size may need to raise it (Magento's own recommendation of post_max_size >= 64M already covers this)
  • The editor now exports previews at 1x canvas size (was exporting the display multiplier); design dimensions elsewhere are derived from the recorded preview_width/preview_height
  • Removed 110 unreferenced images from view/adminhtml/web/images/creator/ and two dead JS modules (js/editor/drawing.js, js/editor/custom-selects.js) that no template or script referenced
  • Added a Creator-specific admin CSP whitelist (etc/adminhtml/csp_whitelist.xml) for the Google Fonts hosts the editor loads
  • Constructor signature changes (not @api): DesignRepository (new PreviewImageStorage), Controller\Adminhtml\Design\Save (Filesystem replaced by PreviewImageStorage and LoggerInterface), Delete (new DesignUsage), Edit (new DesignRepositoryInterface), Plugin\Listing\LabelPreviewCreator (new Escaper) and Block\Adminhtml\Editor (new JsonHexTag before $data). Any subclass calling parent::__construct() positionally needs updating
  • Replaced design preview files are no longer deleted when a design is updated (see Security), so earlier previews of edited designs stay in pub/media/qoliber/productlabelscreator
  • qoliber/product-labels dependency raised to ^1.5.0
  • Version bump to align with ProductLabels 1.5.0

Upgrade notes

  • Run setup:upgrade — this module previously shipped without a db_schema_whitelist.json; one is now generated so its declarative schema is enforced the same way as every other Qoliber module
  • If you deep-link to any of the 110 removed images/creator/* assets from custom code or CMS content, re-add them before upgrading; grep confirmed no reference from this suite's own code

Hyvä Compatibility

Added

  • Registered ViewModel\ProductListItemLabelTags as an additional_item_renderer_processors entry on Hyvä's product_list_item block. Without it, a Hyvä product-list item served from Hyvä's own block_html cache never re-ran the label renderer, so the page's X-Magento-Tags (and the cached item's own cache_tags) lacked the product's qlbl_* tags — a label edit that didn't trigger a reindex (text, colour, image, priority, stop_processing, a qty trigger) would leave stale cached listing items on Hyvä storefronts. The processor now runs on every list-item render, cache hit or miss, and adds the item's label identities plus the generic qlbl tag to both the page's tag collector and the item's own cache_tags
  • New js/label-countdown.phtml, registered in before.body.end: an Alpine component (qoliberLabelCountdown) that replaces the old inline countdown markup and reads the shared data-countdown-end attribute (see ProductLabels changelog); it keeps ticking after the initial render and registers with HyvaCsp so it runs cleanly under a strict Content-Security-Policy

Fixed

  • The Hyvä label wrapper's z-index is now driven by the label's processing order (stack_index/stack_size from ProductLabels), so a higher-priority label reliably paints on top of a lower-priority one on the storefront; the wrapper's fixed z-10 class is dropped in favour of an inline value, capped below the fullscreen gallery overlay's own z-index
  • Label countdown text ({SPDL}/{SPHL}) previously rendered once and never updated; it now ticks every 60 seconds via the new Alpine component
  • Text-only labels (visual type "Text", or a label whose image, shape or design is missing) no longer render half outside the product image. The wrapper renders the visual-type template first; when there is no artwork the label text drops absolute inset-0 and stays in normal flow, so it gives the label box its size and the position classes anchor it. No new Tailwind classes (only classes the template already used), and the inline z-index is unchanged

Changed

  • Escaping in the label wrapper template moved from escapeHtml() (the label and position class names were escaped twice) to escapeHtmlAttr() for every attribute value (the class names, the style attribute and data-countdown-end); the label text is still printed through escapeHtml(). Escaping only — no behaviour change for well-formed label data
  • If you have overridden label/wrapper.phtml in a child theme, also port the text-only handling (render the type template first, drop absolute inset-0 from the text when it outputs nothing), or text-only labels keep the old layout
  • data-countdown-end (epoch milliseconds) replaces data-special-to-date on the label wrapper. If you have overridden label/wrapper.phtml in a child theme, update it — the old attribute is no longer emitted and the new Alpine countdown component only reads the new one
  • hyva-themes/magento2-theme-module dependency raised to ^1.3.11 (the floor that ships HyvaCsp/strict-CSP support, needed by the countdown component, and confirmed to already include the additional_item_renderer_processors block argument used above, which itself shipped in 1.1.15)
  • qoliber/hyva-module-registration constraint tightened from an unbounded >=1.0.0 to ^1.0
  • qoliber/product-labels dependency raised to ^1.5.0

Upgrade notes

  • Requires ProductLabels 1.5.0 or later — the countdown attribute rename and the label cache-tag collector this module plugs into both ship there
  • Known dependency mismatch (unresolved, flagged for the hyva-module-registration package owner): the installed qoliber/hyva-module-registration 1.0.1 caps PHP at ~8.4.0, while this suite's modules declare ~8.5.0. Not fixed here — no PHP constraint was touched in this release

1.4.0 — 2026-07-31

Product Labels

Security

  • Stored XSS via Custom SVG label — label_custom_svg was taken straight from the admin POST body, stored unmodified, and rendered unescaped on the storefront (label/type/svg.phtml) and in the admin grid preview (Ui/Component/Listing/Column/LabelPreview.php). SVG is an active document format: <script>, on* event handlers and <foreignObject> all execute. Any admin holding Qoliber_ProductLabels::Label_save could put persistent JavaScript on every product page the label matched, and on the label grid seen by other admins
  • Added Model/Label/SvgSanitizer — parses SVG as XML and removes <script>, <foreignObject>, <iframe>, <embed>, <object>, <handler>, <listener>, <audio> and <video> elements, every on* event-handler attribute, and any attribute carrying a javascript:, vbscript: or data:text/html URI (whitespace- and case-obfuscated variants included). Markup with a doctype, markup that is not well-formed XML, and markup whose root is not <svg> are rejected outright
  • SVG is sanitized on save and on read — rows written before this release can still contain active markup, so RendererPool::getLabelDataByProductIds() re-sanitizes on the way out. No data migration is required
  • Removed svg from the label image uploader's allowed extensions (Controller/Adminhtml/Label/Upload.php). Uploads are served from media/ on the storefront origin, where an SVG carrying script executes with same-origin privileges. Use the Custom SVG field, which is sanitized
  • Label grid was reachable without the Product Labels permission — Controller/Adminhtml/Label/Index.php extended Magento\Backend\App\Action directly instead of the module's abstract Label controller, so it inherited Magento_Backend::admin, which every authenticated admin satisfies. The menu entry was gated on Qoliber_ProductLabels::Labels_grid, but that resource was enforced nowhere — an admin role without Product Labels access could open qoliber_productlabels/label/index directly and read every label. The controller now declares Labels_grid

Fixed

  • Three Web API routes rejected every caller, including full-admin tokens — etc/webapi.xml guarded the two GET routes and the PUT route on Qoliber_ProductLabels::Label_view and Qoliber_ProductLabels::Label_update, neither of which existed in etc/acl.xml. An undefined ACL resource makes isAllowed() return false unconditionally. Both resources are now declared
  • Deleting a non-existent label reported success — Controller/Adminhtml/Label/Delete.php loaded and deleted without checking the model had loaded, so a stale label_id produced WHERE label_id = NULL, removed nothing, and still showed "You deleted the Label."
  • Removed ReflectionMethod::setAccessible() from Test/Unit/Renderer/RendererPoolTriggerTest.php — a no-op since PHP 8.1 and deprecated in 8.5, where the Magento unit bootstrap promotes deprecations to errors. It failed 36 tests on PHP 8.5

Added

  • Unit test suite for SvgSanitizer — 21 tests covering script elements, event handlers at root and nested depth, <foreignObject>, javascript:/vbscript: URIs, obfuscated schemes, javascript: inside style, doctype entity payloads, malformed and non-SVG input, preservation of legitimate artwork (including currentColor, which the module's CSS-variable theming depends on), and idempotency

Changed

  • Verified compatibility across the declared PHP range: the Rector downgrade floor guard reports no syntax above PHP 8.1, and the unit suite passes on 8.1–8.5
  • Declared PHP 8.5 support (~8.5.0 added to the php constraint). Note this is unit-level verification: magento/framework still constrains to ~8.2.0||~8.3.0||~8.4.0, so a full Magento install on 8.5 is not yet possible and the integration suite has not been exercised there

Removed

  • Dropped the qoliber/core dependency. Nothing in this module referenced it: the admin menu is parented on Magento_Backend::marketing, the ACL tree on Magento_Backend::admin, there is no system.xml using Core's qoliber config tab, no PHP references the Qoliber\Core namespace, and etc/module.xml never sequenced Qoliber_Core. The only remaining link is a <sequence> entry in Qoliber_MySqlUtils, which Magento tolerates when the module is absent (Loader::expandSequence() resolves a missing entry to an empty list)

Label Designer (Creator)

Changed

  • Version bump to align with ProductLabels 1.4.0
  • Updated dependency: requires qoliber/product-labels >= 1.4.0
  • Declared PHP 8.5 support (~8.5.0 added to the php constraint). Note this is unit-level verification: magento/framework still constrains to ~8.2.0||~8.3.0||~8.4.0, so a full Magento install on 8.5 is not yet possible and the integration suite has not been exercised there

Removed

  • Deleted the unreferenced duplicate asset view/adminhtml/web/images/creator/animate-arrow - Copy.png. The space in the filename is hostile to static-content:deploy and to web servers serving pub/static

Hyvä Compatibility

Fixed

  • Labels rendered unpositioned on Hyvä 1.3 (Tailwind 3) — the module shipped no view/frontend/tailwind/ directory. On Tailwind 4 (Hyvä 1.4+) that is harmless, because hyva-sources emits an automatic @source for any module without a module.css. On Tailwind 3 there is no automatic scanning: a module contributes content globs only through its own tailwind.config.js, and the Hyvä theme config ships with its app/code glob commented out. Every utility used solely in label/wrapper.phtml was therefore purged — silently, with no build error — and since the wrapper does all of its positioning with utilities (absolute, top-0, left-1/2, inset-0, z-10/z-20, flex), labels fell into normal document flow below the product image
  • Added view/frontend/tailwind/tailwind.config.js declaring content globs for templates/, layout/ and Plugin/. The Plugin/ glob covers the relative wrapper class emitted from GalleryPlugin.php, which the *.phtml/*.xml globs do not reach

Changed

  • Version bump to align with ProductLabels 1.4.0
  • Updated dependency: requires qoliber/product-labels >= 1.4.0
  • Declared PHP 8.5 support (~8.5.0 added to the php constraint). Note this is unit-level verification: magento/framework still constrains to ~8.2.0||~8.3.0||~8.4.0, so a full Magento install on 8.5 is not yet possible and the integration suite has not been exercised there

Notes

  • No module.css is shipped, deliberately. The module has no CSS of its own, and adding a module.css would suppress the automatic @source that Tailwind 4 relies on to scan this module's templates
  • Label content elements (.label-image, .label-svg, .label-shape-img, .label-base64-img, .label-creator-image) still have no Hyvä styling — their Luma rules live in LESS, which Hyvä does not compile. Sizing is currently bounded only by Tailwind's preflight img { max-width: 100% }, which resolves against the gallery rather than the label box

MSI Stock Support

Changed

  • Version bump to align with ProductLabels 1.4.0
  • Updated dependency: requires qoliber/product-labels >= 1.4.0
  • Declared PHP 8.5 support (~8.5.0 added to the php constraint). Verified by the Rector downgrade floor guard (no syntax above the 8.1 floor) and by running the unit suite on PHP 8.5. Note this is unit-level verification: magento/framework still constrains to ~8.2.0||~8.3.0||~8.4.0, so a full Magento install on 8.5 is not yet possible and the integration suite has not been exercised there

1.3.0 — 2026-03-22

Product Labels

Changed

  • Removed MSI (Multi-Source Inventory) hard dependency — module now works on any Magento 2.4.6+ installation regardless of MSI presence
  • Qty trigger evaluation moved from per-product MSI API calls to SQL JOIN on cataloginventory_stock_item in RendererPool::getLabelDataByProductIds() — category pages now use only 2 SQL queries total regardless of product count
  • {STOCK} variable now works on all page types (category, product, widgets) — previously limited to product detail pages only. Uses pre-loaded stock qty from the RendererPool JOIN with zero additional queries
  • {STOCK} variable resolver simplified to zero dependencies — reads label_stock_qty set on product by RendererPool, no DB connection or request object needed
  • Stock qty data source extracted to StockQtyProviderInterface — pluggable via DI preference, defaults to cataloginventory_stock_item
  • Labels with qty trigger enabled are now hidden on composite product types (configurable, bundle, grouped) — uses Magento's isComposite() from product type config, not hardcoded types. Composite products have qty=0 in cataloginventory, making qty-based evaluation impossible
  • Label Designer moved from Catalog menu to Marketing > Product Labels menu

Fixed

  • Qty trigger now works on Magento installations without MSI modules
  • Fixed qty_trigger JS component not syncing default operator field on initial save — operator was missing from DB, causing trigger to be silently skipped on frontend
  • Added PHP fallback: missing operator defaults to <= for backward compatibility with existing data
  • Fixed stale index data — full reindex now truncates the index table before rebuilding; per-label reindex deletes old rows before checking if label is still active, so disabled/expired labels are properly cleaned up
  • Added WHERE label.status = 1 safety check to storefront label query — prevents disabled labels from rendering even if stale index rows exist
  • Fixed ACL resources — Save and Upload controllers now declare Qoliber_ProductLabels::Label_save, Delete declares Label_delete; base controller corrected from non-existent top_level to Qoliber_ProductLabels::Label
  • Labels now render in priority order — added ORDER BY label.priority ASC to storefront query
  • Fixed base64 template XSS — validates data:image/ prefix before rendering
  • Fixed N+1 query in Creator admin grid — designs now batch-loaded via single collection query instead of per-ID repository calls
  • Fixed N+1 in Creator storefront DesignPreviewResolver — refactored to support batch preloading with single query
  • Fixed VisualTypesDiTest integration test — added @magentoAppArea frontend annotation after typeTemplates moved to frontend DI scope

Added

  • StockQtyProviderInterface — extensible stock qty source, allowing MSI or custom inventory modules to override stock data via DI preference
  • StockQtyProvider — default implementation using cataloginventory_stock_item (works without MSI)
  • Admin notice on qty trigger field explaining that labels are hidden on composite product types
  • Creator ACL resource for Label Designer access control
  • Integration test suite for qty_trigger save/load cycle (12 tests covering all operators, edge cases, update/disable flows)
  • Unit tests for composite product hiding, missing operator default, boolean enabled, zero/negative qty, null stock, {STOCK} variable on all page types, batch design loading

Label Designer (Creator)

Changed

  • Version bump to align with ProductLabels 1.3.0
  • Updated dependency: requires qoliber/product-labels >= 1.3.0
  • Label Designer moved from Catalog menu to Marketing > Product Labels menu

Fixed

  • Fixed N+1 query in admin grid — design preview images now batch-loaded via single collection query instead of per-ID repository calls
  • Refactored DesignPreviewResolver to support batch preloading with single query and per-request caching

Hyvä Compatibility

Changed

  • Version bump to align with ProductLabels 1.3.0
  • Updated dependency: requires qoliber/product-labels >= 1.3.0
  • Removed unnecessary qoliber/core and qoliber/mysql-utils dependencies

MSI Stock Support

Added

  • Initial release of Product Labels MSI compatibility module
  • MsiStockQtyProvider — implements StockQtyProviderInterface using MSI inventory_stock_{X} views for accurate multi-source salable quantity
  • Automatic website → stock ID resolution via inventory_stock_sales_channel table
  • Falls back to stock ID 1 when no sales channel mapping exists
  • Stock ID cached per request to avoid repeated lookups
  • DI preference overrides core StockQtyProvider when module is installed
  • Unit tests for stock ID resolution, qty retrieval, JOIN generation, and fallback behavior
  • Integration tests verifying DI wiring, qty retrieval from inventory views, and SELECT JOIN correctness

1.2.0 — 2026-03-02

Product Labels

Added

  • DI-extensible visual type system — VisualTypes now accepts $additionalTypes via constructor injection, allowing other modules to register new label types without modifying core code
  • DI-extensible template mapping — Label block getTypeTemplate() replaced hardcoded match with injected $typeTemplates array, enabling modules to register custom render templates via di.xml
  • Creator Design support (visual type 5) — LabelPreview grid column and Save controller now handle creator_design_id field
  • Unit and integration test suite (159 tests):
    • Unit tests for TextProcessor, all 7 variable resolvers, RendererPool trigger logic, ProductLabelHydrator, StockStatus, VisualTypes, Label block, LabelPreview, ProductLabelsProxy
    • Integration tests for LabelRepository CRUD, indexer conditions (SKU, price, special price, dates, boolean new, categories, combined AND/OR, attribute sets), TextProcessor variable resolution with real products, DI wiring verification

Fixed

  • Stock trigger logic in RendererPool::shouldDisplayLabel() — stock_trigger=1 (In Stock Only) and stock_trigger=2 (Out of Stock Only) were both hiding labels for salable products; now correctly distinguished

1.1.0 — 2026-02-22

Product Labels

Added

  • active_to field on LabelInterface with getter/setter for label end-date scheduling
  • Dynamic label text system with template variable support:
    • TextProcessor service with pluggable variable resolvers via DI
    • Built-in resolvers: {PRICE}, {SPECIAL_PRICE}, {SAVE_PERCENT}, {SAVE_AMOUNT}, {SKU}, {STOCK}, {NEW_FOR}, {ATTR:code}, {BR} (line break)
    • Countdown placeholders {SPDL} (days left) and {SPHL} (hours left) for special price expiry
  • Cache identity support — Label model and Label block now implement IdentityInterface with qlbl cache tag
  • _eventPrefix and _cacheTag properties on Label model for proper Magento cache invalidation
  • CSS custom properties (--label-rotate, --label-text-color, --label-text-size) on label blocks replacing inline style generation
  • Admin help template (variables-help.html) for label text variable reference in the form UI
  • Indexer INSERT_CHUNK_SIZE constant (1000) — batch inserts are now chunked to avoid oversized SQL statements
  • Date-range filtering in indexer — only labels within their active_from/active_to window are indexed
  • Date-range filtering in frontend RendererPool query — expired/future labels excluded at render time
  • type column now fetched in RendererPool query and passed to label blocks
  • Product is now set on label renderer block, enabling dynamic text resolution
  • label_id data set on renderer block for cache identity
  • RequireJS config and labelCountdown widget for Luma countdown labels
  • Luma _module.less now uses CSS custom properties for rotation, text color and font size

Fixed

  • Admin conditions tab fieldset_id key renamed to field_set_id to match Magento core expectations
  • setConditionFormName now propagates jsFormObject to conditions, fixing broken condition rule UI in admin
  • Variable shadowing bug in indexer where $productIds was overwritten inside batch loop
  • Incorrect assignment in Label::getConditionsInstance() that overwrote $conditionFactory property
  • Inverted stock trigger logic in RendererPool::shouldDisplayLabel() — labels with stock trigger now correctly hide for salable products
  • Unserialization error in RendererPool when qty_trigger is null/empty
  • Potential null access on label_position key in LabelPosition and LabelType UI listing columns
  • Foreign key constraint definitions in db_schema.xml referencing wrong table name (qoliber_productlabels_to_entity -> qoliber_productlabels_label_to_entity)
  • Combine condition type now set to parent \Magento\CatalogRule\Model\Rule\Condition\Combine::class instead of self-referencing
  • preview.js — isImage() now safely checks for array and length before accessing image[0].url
  • Removed debug placeholder text from default.phtml template
  • Duplicate validate-no-html-tags validation rule in label form XML
  • Null-safe defaults for all serialized label data fields in Save controller
  • Indexer now uses getColumnValues('entity_id') instead of getAllIds() for reliable product ID extraction

Changed

  • Replaced ObjectManager usage with LabelFactory DI in Delete, Edit, and Save admin controllers
  • Constructor property promotion applied across Index, NewAction, and Edit controllers
  • Return type declarations added to execute() methods across all admin controllers
  • Observer event changed from catalog_block_product_list_collection to catalog_product_collection_load_after for more reliable label data fetching
  • Admin menu moved from Qoliber_Core::menu to Magento_Backend::marketing with updated sort orders
  • Removed Qoliber_Core module dependency from module.xml sequence
  • Label type field notice updated with clearer static vs dynamic explanation
  • Label text variables help moved from inline notice to dedicated HTML template component
  • Luma wrapper template refactored — replaced inline <style> blocks with CSS custom properties and consolidated positioning logic
  • Code style: added blank line after <?php opening tag across all PHP files, removed unnecessary blank lines inside classes

MySQL Utils — 2026-09-11

Added

  • Sql\BatchInsertStatementGenerator::createInsertOnDuplicateStatement() now accepts an optional ?AdapterInterface $connection parameter, so callers on a non-default connection get their insert-on-duplicate statement prepared against that connection instead of always the default one. Backward compatible: omitting it keeps the old behaviour
  • flatten() now accepts an optional $columns parameter to emit row values in a given column order; without it, behaviour is unchanged. When $columns is given, a row missing one of those columns now throws InvalidArgumentException instead of silently shifting values into the wrong bind position

Fixed

  • Cached insert statements are now re-prepared after a database reconnect. createInsertOnDuplicateStatement() cached prepared statements keyed by the adapter object's identity, but the adapter survives a reconnect (closeConnection(), an application server's _resetState(), or the adapter's own retry after "MySQL server has gone away") while its underlying PDO handle does not — the cached statement kept the old, closed session alive by reference and, if reused, executed against it instead of the caller's current session and transaction. In an isolation-enabled test this surfaced as the cached insert running under the previous test's session. The cache key now tracks the live PDO handle's identity, so a reconnect always re-prepares the statement instead of reusing a stale one
  • Stale statements from a previous connection generation are now evicted from the cache instead of accumulating: every reconnect on a given adapter drops that adapter's earlier cache entries (other adapters' entries are untouched), so a long-lived process that reconnects repeatedly no longer leaks PDOStatement/PDO objects and orphaned server-side connections
  • The prepared-statement cache key for createInsertOnDuplicateStatement() now includes the connection identity (spl_object_id) alongside table, columns, row count and update columns, so two different connections used through the same generator instance no longer share (and corrupt) each other's cached statement
  • Table and column identifiers are now quoted (with embedded backticks escaped) when building the generated SQL, instead of being interpolated raw
  • createInsertOnDuplicateStatement() no longer requires Zend_Db's getConnection(), which AdapterInterface does not declare: an adapter without it (another AdapterInterface implementation, or a test double of the interface) is cached on its own identity instead of failing with "Call to undefined method". The adapter still has to provide prepare(), as every Magento adapter does

1.0.7 — 2025-05-27

Qoliber Core

Changed

  • Updated author information from individual developer email to generic Qoliber team email across all XML files
  • Standardized author attribution in module.xml, acl.xml, adminhtml menu.xml, and system.xml files
  • Updated registration.php author information for consistency

1.0.6 — 2025-04-16

Qoliber Core

Added

  • Added new "Manage" submenu item to Qoliber admin menu with sortOrder 10
  • Submenu provides dedicated section for managing Qoliber extensions under the main Qoliber menu

1.0.5 — 2024-10-10

Qoliber Core

Added

  • Added adminhtml LESS module file (_module.less) with Qoliber logo as base64 encoded PNG image for menu branding
  • Custom styling for Qoliber menu items in admin panel

Changed

  • Changed Qoliber admin menu sortOrder from 10 to 60 to reposition menu in admin sidebar
  • Updated Qoliber configuration tab sortOrder from 10 to 950 for better positioning in system configuration
  • Added CSS class "qoliber-tab" to system configuration tab for custom styling

1.0.4 — 2024-10-10

Qoliber Core

Changed

  • Migrated repository from GitLab to GitHub ([email protected]:qoliber/core.git)
  • Changed license from proprietary Qoliber license to MIT open source license
  • Updated LICENSE.md from custom Qoliber EULA to standard OSL 3.0 (Open Software License)
  • Updated composer.json license field to reflect MIT license

1.0.3 — 2024-08-01

Qoliber Core

Added

  • Added "Configuration" submenu item to Qoliber admin menu
  • Submenu positioned with sortOrder 100 under the main Qoliber menu item

1.0.2 — 2024-08-01

Qoliber Core

Added

  • Added source repository information to composer.json (GitLab)
  • Added git reference field set to "master" branch

Changed

  • Updated license URL from store.qoliber.com to qoliber.com domain

1.0.1 — 2025-05-11

Qoliber Core — 2024-08-01

Added

  • Added comprehensive LICENSE.md file with Qoliber Extensions User License Agreement
  • License defines usage terms, ownership, refund policy, and breach conditions
  • Includes terms for yearly support subscriptions and multi-year support packages

Changed

MySQL Utils

Added

  • Added BatchProvider service class (Sql/BatchProvider.php) for efficient batch processing of large datasets
  • Implemented fetchEntityBatches() method to retrieve entity ID ranges in configurable batch sizes (default 1000)
  • Uses SQL FLOOR() function with grouping to create optimized batches based on MIN/MAX entity IDs
  • Utilizes Zend_Db_Expr for database-level calculations to minimize memory usage
  • Returns array of batch ranges with 'from' and 'to' boundaries for chunked processing
  • Useful for processing large tables without loading all records into memory

Changed

  • Cleaned up composer.json formatting and structure
  • Updated module version from initial release to 1.0.1

1.0.0 — 2025-05-16

Product Labels

Added

  • Initial release of Product Labels module
  • Complete product label management system with advanced admin UI
  • Multiple label visualization types:
    • Text labels with customizable styling
    • Shape-based labels with 20+ predefined shapes
    • Custom image upload labels
    • SVG label support
    • Base64 encoded image labels
  • Advanced label designer in admin panel:
    • Live preview functionality showing label on product images
    • Interactive position selector with 9 placement options (top-left, top-center, top-right, middle-left, center, middle-right, bottom-left, bottom-center, bottom-right)
    • Visual shape selector with thumbnail previews
    • Rotary color picker switch for label styling
    • Font size range slider with visual feedback
    • Real-time label preview on light/dark backgrounds
    • Desktop and mobile preview modes
  • Label condition system:
    • Rule-based product selection using Magento's condition builder
    • Stock status triggers (In Stock, Out of Stock)
    • Quantity-based triggers with configurable thresholds
    • Date-based activation (active_from and active_to)
    • Priority system for multiple label handling
  • Label type configuration:
    • Category labels
    • Product labels
    • Custom label types
  • Admin UI components:
    • Label listing grid with live preview thumbnails
    • Label form with multiple fieldsets
    • Custom UI components:
      • Position selector with SVG icons
      • Shape selector with image gallery
      • Preview component with product image overlay
      • Quantity trigger component
      • Range slider for numeric values
      • Rotary switch for color selection
      • Visual type switcher
    • Save and Apply button for immediate indexing
    • Save and Continue button for iterative editing
    • Duplicate label functionality
  • Indexer system:
    • Product label indexer for performance optimization
    • MView integration for real-time updates
    • Label-to-product relationship index table
    • Store-specific label assignment
    • Efficient product-label mapping
  • Frontend label rendering:
    • Automatic label display on product images
    • Product listing page (PLP) label integration
    • Product detail page (PDP) label display
    • Image factory plugin for label injection
    • Gallery plugin for product image labels
    • Renderer pool pattern for label type handling
  • Label shapes library:
    • 20+ custom Qoliber shapes (ribbons, badges, banners, flags)
    • Circle, hexagon, triangle shapes
    • Bestseller and New Arrival badges
    • Seasonal and promotional shapes
    • Quarter-circle corner labels
    • List and note style shapes
  • Custom JavaScript components:
    • position.js - Interactive position selector
    • preview.js - Live label preview
    • qty-trigger.js - Quantity trigger configuration
    • range-slider.js - Numeric range input
    • rotary-switch.js - Color picker rotary
    • shape-selector.js - Shape gallery selector
    • visual_type.js - Label type switcher
    • save-and-apply.js - Custom save action with indexing
  • Admin styling:
    • Custom Less modules for all UI components
    • Label preview styling
    • Shape selector grid
    • Slider controls
    • Rotary switch animations
    • Position selector grid
  • Database schema:
    • Label configuration table
    • Label-to-entity index table with store support
    • Foreign key constraints for data integrity
    • Serialized condition storage
    • Serialized label configuration storage
  • Observer system:
    • Product collection after-load observer for label injection
  • Plugin system:
    • Product image block plugins for label rendering
    • Image factory plugin for label overlay
    • Gallery plugin for multiple images
  • Repository pattern:
    • LabelRepository for CRUD operations
    • Search result interface for label queries
  • Renderer system:
    • RendererPool for extensible label rendering
    • Type-specific renderers (text, shape, image, SVG, base64)
    • Default fallback renderer
  • UI columns:
    • Label actions column
    • Label position indicator
    • Label preview thumbnail
    • Label type badge
    • Status badge with color coding
  • API interfaces:
    • LabelInterface for label data
    • LabelRepositoryInterface for operations
    • RendererPoolInterface for rendering
    • LabelSearchResultsInterface for search
  • Admin templates:
    • Column templates for grid display
    • Form element templates for custom components
    • Preview templates with product image simulation
  • Frontend templates:
    • Label wrapper template
    • Type-specific rendering templates
  • Module styling:
    • Frontend Less module for label display
    • Admin Less modules for form components
  • Web API integration for label management
  • ACL permissions for label administration
  • Condition combine model for rule building
  • Data provider with product label hydration
  • Shape modifier for UI component
  • Dependent on Qoliber MySQL Utils module

Label Designer (Creator) — 2026-03-02

Added

  • Initial release of Product Labels Creator module
  • Canvas-based label designer with drag-and-drop support
  • Design templates library for quick label creation
  • Visual type 5 (Creator Design) — renders labels from saved canvas designs
  • Design preview resolver for frontend label rendering
  • Admin grid for managing saved designs with image previews
  • Integration with ProductLabels core via DI-extensible type system
  • Unit test coverage for design repository, save controller, and preview resolver

Hyvä Compatibility

Added

  • Initial release of Product Labels Hyva compatibility module
  • Hyva theme support for Product Labels module
  • Alpine.js integration for reactive label rendering
  • Hyva-specific label wrapper template with stock quantity handling
  • Block renderer plugin for Hyva theme compatibility:
    • Custom label rendering for Hyva product images
    • Integration with Hyva's image rendering system
    • Product data extraction for label conditions
  • Reactive label display based on:
    • Product stock quantity changes
    • Stock status updates
    • Dynamic quantity triggers
  • Frontend DI configuration for Hyva-specific plugins
  • Template overrides for Hyva theme label display
  • Seamless integration with Product Labels module core functionality
  • Dependency management:
    • Requires Qoliber Core module
    • Requires Qoliber MySQL Utils module
    • Requires Qoliber Product Labels module

Qoliber Core — 2024-08-01

Added

  • Initial release of Qoliber Core module
  • Created base module structure with module.xml, registration.php, and composer.json
  • Added ACL configuration (etc/acl.xml) with "Qoliber_Core::menu" resource
  • Created admin menu structure with main "Qoliber" menu item positioned at sortOrder 10
  • Added system configuration tab "qoliber" for all Qoliber modules
  • Added English translations (i18n/en_US.csv)
  • Set module dependency on magento/module-backend

MySQL Utils — 2025-05-11

Added

  • Initial release of MySQL Utils module (QM-117)
  • Created utility module for MySQL/MariaDB database operations
  • Set up module structure with module.xml and registration.php
  • Configured PSR-4 autoloading for Qoliber\MySqlUtils namespace
  • Added composer.json with module metadata
  • Set source repository to [email protected]:qoliber/mysqlu-tils
  • Provides foundation for MySQL utility functions and helpers
Changelog — Product Labels — Marketing — Extensions | qoliber Docs