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 witharray_filter($value, 'is_numeric'), which silently discardedday_partbecause"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.phpvalidated composite product types only (default => true), andSimpleRenderer::hasFullConfiguration()returnedtrueunconditionally, 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 sameneeds_configuration+product_urlresponse already used for unconfigured composites, naming the options that are missing.
Architecture
- New
Model/Product/Option/CustomOptionProcessoris the single owner of custom-option parsing, sanitization and required-option checking. The logic previously existed as five byte-identical private copies — inConfigurableProcessor,BundleProcessor,GroupedProcessor,DownloadableProcessorandProcessorPool— 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 toint, 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 realphp:8.5-cliruntime witherror_reporting=E_ALLand no deprecation notices.
Removed
Model/Resolver/(10 classes). All were unreachable: this module'sschema.graphqlsis a stub declaring no@resolver, the module does not sequenceMagento_GraphQl, and nothing referenced the namespace. The live resolvers are inQoliber_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-qlfromrequire— unused.
Tests
- Unit suite 292 → 313 tests / 478 assertions. New coverage for composite
date/time options, required-option detection, and the custom-option cases
GroupedProcessorTesthad 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:
testAddProductToWishlistsupplied none of thesimplefixture's four required options and expected success, andtestGetByShareTokenstored 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.mdadded;README.mdnow documents the PHP 8.1–8.5 gates and states the correct PHPStan invocation (the documented--phpVersionflag 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\DataObjectparameter 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. RemovedgetBuyRequest()from the API data interface; the concrete\Qoliber\GuestWishlist\Model\Wishlist\Itemclass 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=allnow returns a valid Swagger document (47 paths, all 8 guest-wishlist routes present).POST /V1/integration/admin/tokenmints a valid bearer token.POST /V1/guest-wishlist/itemsresponds 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 wishlisttoken_hashcolumn. 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). Seeetc/webapi.xmlfor 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). CleanOrphanedWishlistscron 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.MergeStrategynow 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 optionalrequestedQtyargument (default 1.0). LegacyValidatorand the MSI override both propagate it to theStockStatusDTO soisQtyAvailableis now meaningful instead of always comparing against> 0.- Fix latent readonly-property bug in skipped processor tests —
private readonlyproperties assigned insetUp()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 currentProcessorInterface(simple / configurable / bundle / grouped / downloadable). Unit suite is now 292 tests / 453 assertions / 0 skipped / 0 risky. - Fix the previously-broken
GetSharedWishlistTest(missingWishlistShareRepositoryInterfacemock). - Rewrote
Test/Api/GuestWishlistApiTest.phpto 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.mdcovering the six-module suite, installation, admin configuration paths, and commands for running PHPUnit, PHPStan (PHP 8.1 target), and Playwright e2e. - Updated
CHECKLIST.mdsection 8.1 to the new body-token REST contract with security rationale.
Removed
- Stale
@phpstan-ignoreannotations inSaveForLater/CartItemMoverandObserver/RedirectToGuestWishlistthat 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