Appearance
<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>
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 misnamedbuilder_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 anexclusive_builder_block_id_{lang}value at all.Adv_vendors_admin::getAddVendorDataMuiPost()compounded this by readingexclusive_builder_block_idfrom that samebuilder_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 postsexclusive_builder_block_id_{lang}(matching the normal dropdown'sbuilder_block_id_{lang}staying untouched, and matchingupdate.php's two distinct field names).Adv_vendors_admin::getAddVendorDataMuiPost()now readsexclusive_builder_block_idfrom the dedicatedexclusive_builder_block_id_{lang}POST key, matchinggetEditVendorDataMuiPost(). The stale docblock note claiming the wrong-key read was intentional has been removed. - Tests.
AdvVendorsAdminHooksTest's add-side MUI test now assertsexclusive_builder_block_idis read from its own dedicated POST key and can hold a value independent ofbuilder_block_id(previously it asserted the buggy mirroring behavior). - No REST API or language-key changes.
- Why. On the vendor add form, the exclusive builder-block
[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, andAdv_product_categories_adminwith template-method hooks and, in doing so, widened the basevalidation()from the original untypedvalidation($isUpdate = false)tovalidation(bool $isUpdate = false, ?int $id = null): void. That is a breaking change for client forks whose subclasses overridevalidation()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 (itsVendors_adminandProduct_categories_adminoverridevalidation($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 signatureprotected function validation($isUpdate = false)(dropping thebool/?int $id/: void), and its body now calls the apply-hooks with$isUpdateonly.$isUpdate/$idare unused by validation in these three controllers (none has anis_unique/uniqueness rule), so dropping$idis a functional no-op — the same rules register in the same order (Lines/Vendors: master→MUI; Categories: MUI→master). Theapply{Entity}Master/MuiValidationRules()hooks deliberately keep the wider(bool $isUpdate = false, ?int $id = null): voidsignature (new methods, no legacy fork overrides;$idstays available to any future uniqueness rule).Adv_products_adminis unaffected — itsvalidation()signature predates #417 and its$idgenuinely drives theis_unique_mui[...slug...{$id}...]rule. - Tests. The Phase 2/3/4 parity suites (
AdvLinesAdminHooksTest,AdvVendorsAdminHooksTest,AdvProductCategoriesAdminHooksTest) now assertvalidation()'s reverted contract (one untyped$isUpdateparam, defaultfalse, no declared return type) via a dedicated test; theapply*ValidationRuleshook-contract and rule-set-parity assertions are unchanged. Full Legacy suite green. - No REST API or language-key changes.
- Why. #417 Problem 2 (Phases 2–4) retrofitted
[4.114.1] refactor(admin): retrofit
Adv_product_categories_adminto the full template-method hook set (Advisable-com/ecommercen#417)- Why. The
Adv_*_adminbase controllers are the client extension point, butAdv_product_categories_admin'sadd(),edit(), andvalidation()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 keepsparent_idraw and writes both images unconditionally, edit castsparent_idtointand merges images inline conditional on a fresh upload),getAddCategoryDataMuiPost()/getEditCategoryDataMuiPost()(edit preserves the stored slug),beforeAddEntityRecord()/beforeEditEntityRecord(),setNewEntityRelations()/setUpdateEntityRelations(),afterAddRender()/afterEditRender(), andapplyCategoryMuiValidationRules()/applyCategoryMasterValidationRules()(validation()keeps its original MUI-before-master registration order). TheupdateProductCategory()product-list sync is folded intosetUpdateEntityRelations()to run afterupdate_record()instead of before it — behavior-equivalent, since it only rewrites theshop_product_category_listspivot, whichupdate_record()never reads (no read-after-write coupling). Therel_catrelative-category handling and thedel_image/del_smallImagepre-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. theparent_idraw-vs-int and images-included-vs-excluded differences), MUI keysets incl. edit slug preservation, the fold (setUpdateEntityRelationsrunsupdateProductCategorythenmanageCategoryTags, gated by config), the asymmetric relations (manageBlogArticlesadd-only), and validation rule-name parity + MUI-before-master order. - No REST API or language-key changes.
- Why. The
[4.114.1] refactor(admin): retrofit
Adv_vendors_adminto the full template-method hook set (Advisable-com/ecommercen#417)- Why. The
Adv_*_adminbase controllers are the client extension point, butAdv_vendors_admin'sadd(),edit(), andvalidation()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_adminfollows. - 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 doesdel_imageN→null then conditional replace, and the two paths castis_promo/is_exclusivedifferently),getAddVendorDataMuiPost()/getEditVendorDataMuiPost(),beforeAddEntityRecord()/beforeEditEntityRecord(),setNewEntityRelations()/setUpdateEntityRelations()(which wrap the vendor→videos assignment — NOT no-ops here),afterAddRender()/afterEditRender(), andapplyVendorMasterValidationRules()/applyVendorMuiValidationRules().validation()widens to(bool $isUpdate = false, ?int $id = null). The inline advuploader/upload-error handling and thesetupAiContentGenerationJsonState()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-existingexclusive_builder_block_idadd/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.
- Why. The
[4.114.1] refactor(admin): retrofit
Adv_lines_adminto the full template-method hook set (Advisable-com/ecommercen#417)- Why. The
Adv_*_adminbase controllers are the client extension point, butAdv_lines_admin'sadd(),edit(), andvalidation()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_adminandAdv_product_categories_adminfollow. - 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(), andapplyLineMasterValidationRules()/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.
- Why. The
[4.114.1] refactor(admin): extract master/MUI validation seams from
Adv_products_admin::validation()(Advisable-com/ecommercen#417)- Why. The
Adv_*_adminbase controllers are the client extension point, andAdv_products_adminalready exposes fine-grainedprotectedtemplate-method hooks foradd()/edit()(the same conventionAdv_maps_adminuses) — but itsvalidation()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 requiredcategory_ids[]rule and the entire per-language MUI loop). - The change.
validation()becomes a thin orchestrator delegating to two newprotectedseams —applyProductMasterValidationRules(bool $isUpdate, ?int $id)(the master, non-MUIset_rules()chain) andapplyProductMuiValidationRules(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_adminvalidation 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.
- Why. The
[4.114.1] refactor(admin): widen
Adv_product_categories_admin::updateProductCategory()fromprivatetoprotected(Advisable-com/ecommercen#417)- Why. The
Adv_*_adminbase controllers are the client extension point — each client ships a concrete subclass that overrides individual public action methods.updateProductCategory()wasprivateyet is invoked from the overridable publicedit()action, so a client overridingedit()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 ofProduct_categories_adminthat overrideedit()can now call$this->updateProductCategory($id)directly. Client-sync (breaking on stale forks): any fork that still carries its ownprivate updateProductCategory()copy MUST delete it when syncing past this release — aprotectedparent method plus aprivatesame-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.
- Why. The
[4.114.1] fix(gift-cards): guard NULL
tran_ticketin theCancelPendingGiftCardsPayPal Advanced flow (Advisable-com/ecommercen#416)- Why.
AdvCancelPendingGiftCards::cancelPendingPaypalAdvancedOrders()passed$order->tran_ticketstraight intoPayPalRestApi::getOrderDetails(string $tran_ticket), whose parameter is a non-nullablestring. A pending PayPal Advanced gift-card order left with aNULLtran_ticket(checkout abandoned before a ticket was persisted) therefore threw an unhandledTypeError, 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 tocancelGiftCard()— mirroring the siblingAdvCancelIncompleteOrdersjob's "empty ticket ⇒ cancel" rule. An abandoned, ticket-less order is cancelled instead of crashing the job. - Tests.
AdvCancelPendingGiftCardsTestcovers the NULL/empty-tran_ticketcancel branch (the regression) and the no-pending-orders no-op. - No REST API or language-key changes.
- Why.
[4.114.1] chore(docker): empty the integration
.env.placeholderfile (drop strayFILES_S3_PREFIX)- Why.
.docker/integration/.env.placeholderhad a strayFILES_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.
- Why.
[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 blockingserver-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 ofVueLiveDataTrait::cartResourceContents()) — a per-merge conflict risk. - The change. A new
CART/NON_BLOCKING_ERROR_CODESregistry value (pipe-delimited list of numeric codes, e.g.9or7|9) lets a store demote listedserver-class cart-error codes to the inline, non-blockingvalidationclass 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 newsetStockError()helper, so it applies consistently to the cart page, the minicart live-data feed, and the sharedCartErrorsingleton — not only the live-data path the client fork it replaces cleaned. stockErrorClass()demotes only codes whose default class isserver;validation-class codes (e.g. code 8) are unaffected, so listing them is a harmless no-op.
- The fix lives at the source:
- Tests.
AdvCartResourceStockErrorTestcovers the registry-list parsing and the demotion decision, including the empty-list backwards-compatibility case. - No REST API or language-key changes.
- Why.
[4.114.0] feat(product-bundles): make the bundle-builder list default page size configurable (Advisable-com/ecommercen#402)
- Why.
AdvApiProductBundlesAdminhardcoded a default page size of10in both bundle-builder list endpoints (listBundles()andsearchReferences()), 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_SIZEregistry value sets the default page size for both endpoints. Unset, non-positive, or non-numeric values fall back to10(the previous default, fully backwards-compatible), and the configured value is capped at200to bound per-request payload and query cost. Only the no-?limit=default path changes; explicitly supplied?limit=values are untouched. - Tests.
AdvApiProductBundlesAdminPageSizeTestcovers the fallback (unset/empty/zero/negative/non-numeric), the configured value, and the cap. - No REST API or language-key changes.
- Why.