6 min readAug 8, 2026by jakub

Changelog

Current version: 1.1.1

[1.1.1] - 2026-08-08

Compatibility

  • Version bump to align with the suite's 1.1.1 release, which fixes a fatal in the Qoliber_GuestWishlistAnalytics admin grid. No source changes.

[1.1.0] - 2026-08-01

Fixes

  • Time and date-time custom options lost their AM/PM. processOptions() filtered composite option values with array_filter($value, 'is_numeric'), which silently discarded day_part because "pm" is not numeric. A customer choosing 10:30 pm had 10:30 stored — a twelve-hour error, with no warning at any layer. Composite options (date, time, date_time) now keep every part.
  • Required custom options were never enforced. Controller/Wishlist/Item/ Add.php validated composite product types only (default => true), and SimpleRenderer::hasFullConfiguration() returned true unconditionally, so a simple product with a required option was accepted, displayed as fully configured, and only failed later at add-to-cart with a generic message. The controller now refuses it up front with the same needs_configuration + product_url response already used for unconfigured composites, naming the options that are missing.

Architecture

  • New Model/Product/Option/CustomOptionProcessor is the single owner of custom-option parsing, sanitization and required-option checking. The logic previously existed as five byte-identical private copies — in ConfigurableProcessor, BundleProcessor, GroupedProcessor, DownloadableProcessor and ProcessorPool — which is why one bug in it affected every product type at once. All five copies are gone; each processor now receives it by constructor injection.
  • It distinguishes a composite option (keyed by name: month, day, year, hour, minute, day_part) from a value list (a positional array of option value IDs). Composite parts are kept as sanitized strings; value lists keep being coerced to int, as before.

Compatibility

  • PHP 8.1 – 8.5. The declared constraint gains ~8.5.0. Verified two ways: a Rector downgrade-set floor guard proving no syntax newer than 8.1 is present, and the unit suite executed on a real php:8.5-cli runtime with error_reporting=E_ALL and no deprecation notices.

Removed

  • Model/Resolver/ (10 classes). All were unreachable: this module's schema.graphqls is a stub declaring no @resolver, the module does not sequence Magento_GraphQl, and nothing referenced the namespace. The live resolvers are in Qoliber_GuestWishlistGraphQl. Nine of the ten also used implicit-nullable parameters (array $value = null), deprecated in PHP 8.4. Three operations they named have no live equivalent and were never reachable: GenerateGuestWishlistToken, MoveCartItemToGuestWishlist, GuestWishlistItemProduct.
  • magento/module-catalog-graph-ql from require — unused.

Tests

  • Unit suite 292 → 313 tests / 478 assertions. New coverage for composite date/time options, required-option detection, and the custom-option cases GroupedProcessorTest had been missing — it was the only processor test without any.
  • Integration suite fixed and green at 103 tests / 200 assertions. Two tests were asserting behaviour that no longer holds: testAddProductToWishlist supplied none of the simple fixture's four required options and expected success, and testGetByShareToken stored a plaintext share token where production stores a SHA-256 hash — broken since 1.0.0 and never noticed, because the suite could not bootstrap.

Documentation

  • LICENSE.md added; README.md now documents the PHP 8.1–8.5 gates and states the correct PHPStan invocation (the documented --phpVersion flag was removed in PHPStan 2.x, so the command as published could not run).
  • Qoliber file headers added to every PHP, XML, JS and PHTML file.

[1.0.1] - 2026-04-14

Fixes

  • Critical: unblock Web API & Swagger. Every /rest/* route was returning HTTP 500 with "The \Magento\Framework\DataObject parameter type is invalid" in developer mode. Root cause: WishlistItemInterface::getBuyRequest() declared a return type of \Magento\Framework\DataObject, which Magento's Web API type processor refuses to register — one bad method on one interface aborted the entire ServiceMetadata walk and took down every REST endpoint on the store, not just ours. Removed getBuyRequest() from the API data interface; the concrete \Qoliber\GuestWishlist\Model\Wishlist\Item class still exposes it, and every internal caller already works with the concrete model via the factory / repository, so runtime behaviour is unchanged.

Verification

  • /rest/all/schema?services=all now returns a valid Swagger document (47 paths, all 8 guest-wishlist routes present).
  • POST /V1/integration/admin/token mints a valid bearer token.
  • POST /V1/guest-wishlist/items responds correctly.
  • 292/292 unit tests still passing.

[1.0.0] - 2026-04-12

Security

  • Breaking: share tokens are now stored as SHA-256 hashes in qoliber_guest_wishlist_share.share_token. Plaintext is returned to the caller at creation time and embedded in the share URL but never persisted, matching the pattern already used by the main wishlist token_hash column. Any pre-existing share links generated against 0.9.0 will stop resolving.
  • Breaking: cross-device restore tokens are now stored as SHA-256 hashes in qoliber_guest_wishlist_cross_device_link.link_token. Pending restore links from 0.9.0 will no longer resolve.
  • Breaking: guest wishlist tokens have been removed from REST API URL paths. All private-token endpoints are POST with the token in the JSON body. URL paths leak into web server logs, reverse proxies, APM traces, browser history, and Referer headers — unacceptable for a bearer credential. The only GET route remaining is /V1/guest-wishlist/shared/:shareToken (share tokens are public by design). See etc/webapi.xml for the new routes.

Fixes

  • Fix release-blocker typed class constant in Api/Data/ WishlistItemOptionInterface.php (PHP 8.3-only syntax; crashed parse on the advertised PHP 8.1 / 8.2 targets).
  • CleanOrphanedWishlists cron now also deletes abandoned non-empty wishlists (customer_id IS NULL and updated_at older than the configurable threshold). Default: 365 days. Without this, item rows from guests who never return accumulated indefinitely.
  • MergeStrategy now checks for existing native-wishlist items with the same product + info_buyRequest before inserting, so customers who had the same product on both wishlists no longer get duplicate rows after login.
  • StockValidatorInterface::getStockStatus() accepts an optional requestedQty argument (default 1.0). Legacy Validator and the MSI override both propagate it to the StockStatus DTO so isQtyAvailable is now meaningful instead of always comparing against > 0.
  • Fix latent readonly-property bug in skipped processor tests — private readonly properties assigned in setUp() crash on PHP 8.1+ if the tests are ever unskipped.

Tests

  • Replaced four obsolete markTestSkipped() processor test files with fresh unit suites covering the current ProcessorInterface (simple / configurable / bundle / grouped / downloadable). Unit suite is now 292 tests / 453 assertions / 0 skipped / 0 risky.
  • Fix the previously-broken GetSharedWishlistTest (missing WishlistShareRepositoryInterface mock).
  • Rewrote Test/Api/GuestWishlistApiTest.php to match the new body-token REST contract.

Documentation

  • Schema comments on secondary token columns now explicitly say "Token Hash (SHA-256)" with a block comment describing why the plaintext is never persisted, so source-verification reviewers can see the hashing at a glance.
  • Made the full-native-wishlist-replacement scope explicit in the observer docblock, layout comment, and README blockquote. This is an intentional design decision (the module provides a unified wishlist for both guest and logged-in customers), not a side effect.
  • Added README.md covering the six-module suite, installation, admin configuration paths, and commands for running PHPUnit, PHPStan (PHP 8.1 target), and Playwright e2e.
  • Updated CHECKLIST.md section 8.1 to the new body-token REST contract with security rationale.

Removed

  • Stale @phpstan-ignore annotations in SaveForLater/CartItemMover and Observer/RedirectToGuestWishlist that referenced errors PHPStan no longer reports.

[0.9.0] - 2026-03-23

Features

  • Full guest wishlist for Magento Open Source / Mage-OS — no login required
  • Token-based UUID identification with SHA-256 hashing and timing-safe comparison
  • Cookie security: HttpOnly, Secure, SameSite=Lax
  • Support for all product types: simple, configurable, bundle, grouped, downloadable
  • Product option storage: super_attribute, bundle_option, super_group, custom options, downloadable links
  • Swatch validation on PLP and PDP — blocks adding without all required options selected
  • Server-side composite product validation with redirect to product page for unconfigured products
  • Item quantity management and per-item notes (up to 1000 chars) with auto-save
  • Note save with full-card loader overlay, 300ms delay to avoid flash, green/red border feedback
  • Wishlist sharing with public tokens, optional name, expiration dates
  • Share modal with intro text, item count, social buttons (Facebook, X, WhatsApp, Email)
  • Shared wishlist page: add-to-cart via AJAX, collapsible options, stock status, share name display
  • Out-of-stock products greyed out and sorted to end of wishlist
  • Collapsible product options toggle ("X option(s) selected")
  • Equal-height wishlist cards with CSS flexbox, button pinned to bottom
  • Guest-to-customer wishlist linking on login/registration (sets customer_id, no data migration)
  • Replaces native Magento wishlist UI entirely — native module stays active for API/dependencies
  • Customer account integration: sidebar navigation link, 2-column layout for logged-in users
  • "Stored locally in browser" notice for guests, hidden for logged-in customers
  • FPC compatible: customer-data section for header counter via localStorage
  • Save for Later from cart page
  • Cross-device sync via email link
  • Add All to Cart functionality
  • noindex/nofollow on wishlist and shared wishlist pages
  • Max quantity cap (10,000) to prevent abuse
  • Whitelist-based filter field and condition type validation in repository
  • Share name sanitization with htmlspecialchars + strip_tags

Architecture

  • Product Type Renderer system: RendererInterface + RendererPool registered via DI
  • PHP renderers: SimpleRenderer, ConfigurableRenderer, BundleRenderer, GroupedRenderer, DownloadableRenderer
  • JS validators per product type: configurable.js, bundle.js, grouped.js (loaded dynamically)
  • StockValidatorInterface for pluggable stock checking (legacy default, MSI via GuestWishlistMsi)
  • CustomerData section (guest-wishlist) for FPC-compatible header counter
  • Clean separation: no MSI dependencies in core module

Code Quality

  • PHPStan level 8: 0 errors
  • PHPCS PSR-12: 0 non-Magento errors
  • 228 unit tests passing
  • PHP 8.1+ with constructor property promotion, strict types, readonly properties
Changelog — Guest Wishlist — Marketing — Extensions | qoliber Docs