Skip to content

<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /changelog/Changelog.4.114.md for this page in Markdown format</div>

Home | Changelog

Version 4

version 4.114

  • [4.114.2] fix(admin): vendor add form couldn't set the exclusive builder block independently of the normal one (Advisable-com/ecommercen#421)

    • Why. On the vendor add form, the exclusive builder-block form_dropdown() was misnamed builder_block_id_{lang} — the same field name as the normal builder-block dropdown just above it — so the two selectors collided and the browser never posted an exclusive_builder_block_id_{lang} value at all. Adv_vendors_admin::getAddVendorDataMuiPost() compounded this by reading exclusive_builder_block_id from that same builder_block_id_{lang} POST key instead of the dedicated one. The net effect: a vendor's two builder blocks could not be set independently on create — the exclusive selection was silently clobbered by the normal one and only took effect once the vendor was saved and then edited, since the edit path (update.php + getEditVendorDataMuiPost()) already used the correct dedicated key. This is a pre-existing bug: the controller half was preserved verbatim through the #417 Phase 3 template-method retrofit, whose docblock incorrectly documented the wrong-key read as an intentional add/edit divergence.
    • The change. application/views/admin/vendors/create.php's exclusive builder-block dropdown now posts exclusive_builder_block_id_{lang} (matching the normal dropdown's builder_block_id_{lang} staying untouched, and matching update.php's two distinct field names). Adv_vendors_admin::getAddVendorDataMuiPost() now reads exclusive_builder_block_id from the dedicated exclusive_builder_block_id_{lang} POST key, matching getEditVendorDataMuiPost(). The stale docblock note claiming the wrong-key read was intentional has been removed.
    • Tests. AdvVendorsAdminHooksTest's add-side MUI test now asserts exclusive_builder_block_id is read from its own dedicated POST key and can hold a value independent of builder_block_id (previously it asserted the buggy mirroring behavior).
    • No REST API or language-key changes.
  • [4.114.1] fix(admin): revert the validation() signature widening on the Lines/Vendors/Categories admin controllers to restore client-fork BC (Advisable-com/ecommercen#417)

    • Why. #417 Problem 2 (Phases 2–4) retrofitted Adv_lines_admin, Adv_vendors_admin, and Adv_product_categories_admin with template-method hooks and, in doing so, widened the base validation() from the original untyped validation($isUpdate = false) to validation(bool $isUpdate = false, ?int $id = null): void. That is a breaking change for client forks whose subclasses override validation() with the original untyped signature: PHP fatals at class-load with "Declaration of {Fork}::validation(...) must be compatible with {Base}::validation(...)". Confirmed against the joypharmacy fork (its Vendors_admin and Product_categories_admin override validation($isUpdate = false)) — it would fatal on the next upstream sync, contradicting Problem 2's "backward-compatible extension point" promise. Caught before any release shipped the widened signature.
    • The change. validation() on all three controllers is reverted to its exact original signature protected function validation($isUpdate = false) (dropping the bool/?int $id/: void), and its body now calls the apply-hooks with $isUpdate only. $isUpdate/$id are unused by validation in these three controllers (none has an is_unique/uniqueness rule), so dropping $id is a functional no-op — the same rules register in the same order (Lines/Vendors: master→MUI; Categories: MUI→master). The apply{Entity}Master/MuiValidationRules() hooks deliberately keep the wider (bool $isUpdate = false, ?int $id = null): void signature (new methods, no legacy fork overrides; $id stays available to any future uniqueness rule). Adv_products_admin is unaffected — its validation() signature predates #417 and its $id genuinely drives the is_unique_mui[...slug...{$id}...] rule.
    • Tests. The Phase 2/3/4 parity suites (AdvLinesAdminHooksTest, AdvVendorsAdminHooksTest, AdvProductCategoriesAdminHooksTest) now assert validation()'s reverted contract (one untyped $isUpdate param, default false, no declared return type) via a dedicated test; the apply*ValidationRules hook-contract and rule-set-parity assertions are unchanged. Full Legacy suite green.
    • No REST API or language-key changes.
  • [4.114.1] refactor(admin): retrofit Adv_product_categories_admin to the full template-method hook set (Advisable-com/ecommercen#417)

    • Why. The Adv_*_admin base controllers are the client extension point, but Adv_product_categories_admin's add(), edit(), and validation() were monoliths: a client that needed to vary the master field set, the per-language MUI payload, the post-save relations, or one half of the validation rules had to copy an entire action, which then drifts from upstream. This is Phase 4 (final) of #417 Problem 2 — the template-method / OCP retrofit of the monolithic admin actions — following Products, Lines, and Vendors; it completes Problem 2.
    • The change. add()/edit()/validation() become thin orchestrators delegating to products-style protected seams: getAddCategoryMasterPostData() / getEditCategoryMasterPostData() (a deliberate add/edit split — add keeps parent_id raw and writes both images unconditionally, edit casts parent_id to int and merges images inline conditional on a fresh upload), getAddCategoryDataMuiPost() / getEditCategoryDataMuiPost() (edit preserves the stored slug), beforeAddEntityRecord() / beforeEditEntityRecord(), setNewEntityRelations() / setUpdateEntityRelations(), afterAddRender() / afterEditRender(), and applyCategoryMuiValidationRules() / applyCategoryMasterValidationRules() (validation() keeps its original MUI-before-master registration order). The updateProductCategory() product-list sync is folded into setUpdateEntityRelations() to run after update_record() instead of before it — behavior-equivalent, since it only rewrites the shop_product_category_lists pivot, which update_record() never reads (no read-after-write coupling). The rel_cat relative-category handling and the del_image/del_smallImage pre-submit clearing stay inline. validation() widens to (bool $isUpdate = false, ?int $id = null). Pure extract-method (bar the deliberate, verified fold): the un-overridden base class builds an identical save payload and registers an identical rule set in the same order. Strictly additive and backward-compatible — a whole-method override is unaffected, and a fork can now override any single seam.
    • Tests. AdvProductCategoriesAdminHooksTest — no-DB reflection parity guard (32 tests): hook contract, the add/edit master field-set split (incl. the parent_id raw-vs-int and images-included-vs-excluded differences), MUI keysets incl. edit slug preservation, the fold (setUpdateEntityRelations runs updateProductCategory then manageCategoryTags, gated by config), the asymmetric relations (manageBlogArticles add-only), and validation rule-name parity + MUI-before-master order.
    • No REST API or language-key changes.
  • [4.114.1] refactor(admin): retrofit Adv_vendors_admin to the full template-method hook set (Advisable-com/ecommercen#417)

    • Why. The Adv_*_admin base controllers are the client extension point, but Adv_vendors_admin's add(), edit(), and validation() were monoliths: a client that needed to vary the master field set, the per-language MUI payload, the post-save relations, or one half of the validation rules had to copy an entire action, which then drifts from upstream (a client fork had already reduced the edit-side image handling). This is Phase 3 of #417 Problem 2 — the template-method / OCP retrofit of the monolithic admin actions — following Phase 1 (Adv_products_admin) and Phase 2 (Adv_lines_admin); Adv_product_categories_admin follows.
    • The change. add()/edit()/validation() become thin orchestrators delegating to products-style protected seams: getAddVendorMasterPostData() / getEditVendorMasterPostData() (a deliberate add/edit split, unlike Lines' shared master hook — add writes all four images unconditionally while edit does del_imageN→null then conditional replace, and the two paths cast is_promo/is_exclusive differently), getAddVendorDataMuiPost() / getEditVendorDataMuiPost(), beforeAddEntityRecord() / beforeEditEntityRecord(), setNewEntityRelations() / setUpdateEntityRelations() (which wrap the vendor→videos assignment — NOT no-ops here), afterAddRender() / afterEditRender(), and applyVendorMasterValidationRules() / applyVendorMuiValidationRules(). validation() widens to (bool $isUpdate = false, ?int $id = null). The inline advuploader/upload-error handling and the setupAiContentGenerationJsonState() call are left in place. Pure extract-method: the un-overridden base class builds an identical save payload and registers an identical rule set in the same order. Strictly additive and backward-compatible — a whole-method override is unaffected, and a fork can now override any single seam.
    • Tests. AdvVendorsAdminHooksTest — no-DB reflection parity guard: hook contract (existence/visibility/return type/signature), the add/edit master field sets (incl. the edit-side image conditional matrix — the exact regression guard against the fork's drift), add/edit MUI field-key sets (incl. the pre-existing exclusive_builder_block_id add/edit source-key divergence, preserved verbatim), validation rule-name parity + order, video-relations gating, and independent overridability of the master vs MUI validation seams.
    • No REST API or language-key changes.
  • [4.114.1] refactor(admin): retrofit Adv_lines_admin to the full template-method hook set (Advisable-com/ecommercen#417)

    • Why. The Adv_*_admin base controllers are the client extension point, but Adv_lines_admin's add(), edit(), and validation() were monoliths: a client that needed to vary the master field set, the per-language MUI payload, or one half of the validation rules had to copy an entire action, which then drifts from upstream (a client fork had already reduced the master save to images-only). This is Phase 2 of #417 Problem 2 — the template-method / OCP retrofit of the monolithic admin actions — following Phase 1 (Adv_products_admin); Adv_vendors_admin and Adv_product_categories_admin follow.
    • The change. add()/edit()/validation() become thin orchestrators delegating to products-style protected seams: getLineMasterPostData() (shared master scalars; the two image fields stay inline because add/edit handle them differently), getAddLineDataMuiPost() / getEditLineDataMuiPost(), beforeAddEntityRecord() / beforeEditEntityRecord(), setNewEntityRelations() / setUpdateEntityRelations() (no-ops for Lines), afterAddRender() / afterEditRender(), and applyLineMasterValidationRules() / applyLineMuiValidationRules(). validation() widens to (bool $isUpdate = false, ?int $id = null). Pure extract-method: the un-overridden base class builds an identical save payload and registers an identical rule set in the same order. Strictly additive and backward-compatible — a whole-method override is unaffected, and a fork can now override any single seam.
    • Tests. AdvLinesAdminHooksTest — no-DB reflection parity guard: hook contract (existence/visibility/return type/signature), master post-data field set (regression guard against the images-only drift), add/edit MUI field-key sets incl. role gating and slug preservation, validation rule-name parity + order, and independent overridability of the master vs MUI validation seams.
    • No REST API or language-key changes.
  • [4.114.1] refactor(admin): extract master/MUI validation seams from Adv_products_admin::validation() (Advisable-com/ecommercen#417)

    • Why. The Adv_*_admin base controllers are the client extension point, and Adv_products_admin already exposes fine-grained protected template-method hooks for add()/edit() (the same convention Adv_maps_admin uses) — but its validation() was still a monolith. A client that needed to vary just the master rule set or just the per-language MUI rule set had to copy the whole ~80-line method, which then drifts from upstream (the joypharmacy fork's copy had already dropped the required category_ids[] rule and the entire per-language MUI loop).
    • The change. validation() becomes a thin orchestrator delegating to two new protected seams — applyProductMasterValidationRules(bool $isUpdate, ?int $id) (the master, non-MUI set_rules() chain) and applyProductMuiValidationRules(bool $isUpdate, ?int $id) (the per-language MUI loop, including the role-gated meta/url/slug rules). The four feature-flag-gated blocks (ENABLE_SPECIAL_DISCOUNTS, useProductAvailabilityDateRange, SHOW_PRODUCT_HITS, POINT_SYSTEM) stay inline. A client fork can now override the master- or MUI-rule set independently instead of copying the whole method. Pure extract-method — the un-overridden base class registers an identical rule set in the same order; strictly additive and backward-compatible. This is Phase 1 of #417 Problem 2 (Adv_products_admin validation seams); the sibling controller retrofits (Lines/Vendors/Categories) ship in follow-up PRs.
    • Tests. AdvProductsAdminValidationSeamsTest (no-DB reflection) guards the hook contract (visibility/signature), the exact master rule-set field order (regression guard against a dropped or reordered rule), and the independent overridability of the two seams.
    • No REST API or language-key changes.
  • [4.114.1] refactor(admin): widen Adv_product_categories_admin::updateProductCategory() from private to protected (Advisable-com/ecommercen#417)

    • Why. The Adv_*_admin base controllers are the client extension point — each client ships a concrete subclass that overrides individual public action methods. updateProductCategory() was private yet is invoked from the overridable public edit() action, so a client overriding edit() could not call $this->updateProductCategory() and was forced to copy the helper verbatim (observed as a byte-identical duplicate in the joypharmacy fork, kept solely to satisfy the visibility rule).
    • The change. The method is now protected (body and signature unchanged). Client subclasses of Product_categories_admin that override edit() can now call $this->updateProductCategory($id) directly. Client-sync (breaking on stale forks): any fork that still carries its own private updateProductCategory() copy MUST delete it when syncing past this release — a protected parent method plus a private same-named child method is a fatal error in PHP (a subclass cannot narrow visibility), so the admin controller will fail to load until the duplicate is removed. The change is strictly additive and backward-compatible — the base class's own $this-> call resolves identically under both visibilities. Scope is deliberately limited to this one helper (issue #417 Problem 1); the broader template-method hook extraction (Problem 2) is deferred.
    • Tests. None — a pure visibility widening on an unchanged method body with an unchanged existing caller; there is no behavior change to exercise.
    • No REST API or language-key changes.
  • [4.114.1] fix(gift-cards): guard NULL tran_ticket in the CancelPendingGiftCards PayPal Advanced flow (Advisable-com/ecommercen#416)

    • Why. AdvCancelPendingGiftCards::cancelPendingPaypalAdvancedOrders() passed $order->tran_ticket straight into PayPalRestApi::getOrderDetails(string $tran_ticket), whose parameter is a non-nullable string. A pending PayPal Advanced gift-card order left with a NULL tran_ticket (checkout abandoned before a ticket was persisted) therefore threw an unhandled TypeError, aborting the whole 15-minute job on the first ticket-less order — so no stale pending gift-card orders were resolved on that run, and it kept failing every run until the row was cleaned up by hand.
    • The change. The gateway call is now guarded with !empty($order->tran_ticket) (the client is also built lazily, only when a ticket is present), and a missing ticket falls through to cancelGiftCard() — mirroring the sibling AdvCancelIncompleteOrders job's "empty ticket ⇒ cancel" rule. An abandoned, ticket-less order is cancelled instead of crashing the job.
    • Tests. AdvCancelPendingGiftCardsTest covers the NULL/empty-tran_ticket cancel branch (the regression) and the no-pending-orders no-op.
    • No REST API or language-key changes.
  • [4.114.1] chore(docker): empty the integration .env.placeholder file (drop stray FILES_S3_PREFIX)

    • Why. .docker/integration/.env.placeholder had a stray FILES_S3_PREFIX=… value committed to it. The file is meant to be an empty, tracked placeholder that keeps the integration env directory present; the leftover key risked bleeding a bogus S3 prefix into integration runs.
    • The change. Reset the file back to a zero-byte placeholder. Dev/integration tooling only — no application runtime, REST API, or language-key changes.
  • [4.114.0] feat(cart): make blocking cart-error codes demotable to non-blocking via a registry key (Advisable-com/ecommercen#401)

    • Why. AdvCartResource::getStockErrors() raised stock-error code 9 ("Product is not immediately available") as a blocking server-class error, which the storefront renders as a 10-second toast even for backorderable items. Stores selling such items had to fork upstream core (filtering the code out of VueLiveDataTrait::cartResourceContents()) — a per-merge conflict risk.
    • The change. A new CART/NON_BLOCKING_ERROR_CODES registry value (pipe-delimited list of numeric codes, e.g. 9 or 7|9) lets a store demote listed server-class cart-error codes to the inline, non-blocking validation class the storefront already renders per line item. An empty or unset value (the default) preserves the previous behavior exactly.
      • The fix lives at the source: getStockErrors() now routes all four stock-error raises through a new setStockError() helper, so it applies consistently to the cart page, the minicart live-data feed, and the shared CartError singleton — not only the live-data path the client fork it replaces cleaned.
      • stockErrorClass() demotes only codes whose default class is server; validation-class codes (e.g. code 8) are unaffected, so listing them is a harmless no-op.
    • Tests. AdvCartResourceStockErrorTest covers the registry-list parsing and the demotion decision, including the empty-list backwards-compatibility case.
    • No REST API or language-key changes.
  • [4.114.0] feat(product-bundles): make the bundle-builder list default page size configurable (Advisable-com/ecommercen#402)

    • Why. AdvApiProductBundlesAdmin hardcoded a default page size of 10 in both bundle-builder list endpoints (listBundles() and searchReferences()), applied whenever the request omits ?limit=. Stores with many bundles found this truncating, and clients had to subclass the controller purely to widen the default.
    • The change. A new PRODUCT_BUNDLES/DEFAULT_PAGE_SIZE registry value sets the default page size for both endpoints. Unset, non-positive, or non-numeric values fall back to 10 (the previous default, fully backwards-compatible), and the configured value is capped at 200 to bound per-request payload and query cost. Only the no-?limit= default path changes; explicitly supplied ?limit= values are untouched.
    • Tests. AdvApiProductBundlesAdminPageSizeTest covers the fallback (unset/empty/zero/negative/non-numeric), the configured value, and the cap.
    • No REST API or language-key changes.