Appearance
<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /changelog/Changelog.4.101.md for this page in Markdown format</div>
Version 4
version 4.101
[4.101.2] fix(meta-capi): silence FacebookAds CrashReporter stdout writes that flooded PHP-FPM stderr (Advisable-com/ecommercen#232)
- Symptom. wecare PHP-FPM containers shipped ~4GB/day of
FacebookAds\CrashReporter : Enabledlines to Loki/S3 — one per HTTP request — crowding out real signal in slow-log analysis. Discovered while building a PHP-FPM slow-log → JSON pipeline cluster-side. - Root cause.
Advisable\MetaConversionsApi\Service\FacebookConversionService::__construct(line 80) calledApi::init($pixelId, null, $pixelAccessToken)without the 4th$log_crashargument, which defaults totruein the FB Business SDK. That triggersFacebookAds\CrashReporter::enable(), whichfwrite()sFacebookAds\CrashReporter : Enableddirect tophp://stdoutper request — bypassing PHP'serror_logmachinery entirely, so the ini directive cannot silence it. The CrashReporter also attempts to upload crash data to Facebook on shutdown. - Fix. One-line change in
src/MetaConversionsApi/Service/FacebookConversionService.php:80— passesfalseas the 4th arg:Api::init($pixelId, null, $pixelAccessToken, false). Disables the SDK CrashReporter at init. No SDK upgrade required; the parameter has been part ofApi::initfor many SDK versions. Takes effect on the next deploy of any tenant using Meta CAPI. - Scope check. Only one production call site of
Api::initexists in the codebase (grep -rn 'Api::init('). Worth checking client repos (seajets etc.) on next upstream sync — same default would apply anywhere a tenant overridesFacebookConversionServiceor constructs the SDK directly. - Docs.
docs/flows/integration/IN-07-facebook-catalog-capi.mdupdated to document the explicit$log_crash=falseargument.
- Symptom. wecare PHP-FPM containers shipped ~4GB/day of
[4.101.2] fix(doc-ba): guardrail against orchestrator-supplied template lines that break VitePress (Advisable-com/ecommercen#246)
- Root cause. The SY-33 breadcrumb that broke the 4.101.0 docs deploy (resolved by hotfix 4.101.1) did not originate in
.claude/agents/doc-ba-document.mdor.claude/skills/doc-ba-workflow/SKILL.md— both templates correctly start at# {Title}. It came from the orchestrator's prompt when dispatching the agent for SY-33; the agent dutifully followed the malformed template it was given. No guardrail at the agent level prevented a bad template from reaching disk. - Fix. Strengthens the existing "Match the existing doc style" instruction (#5) in
.claude/agents/doc-ba-document.md. The agent now must Read the first ~20 lines of a sibling doc in the same category before writing a new flow doc and conform to the corpus head byte-for-byte. If the orchestrator-supplied template includes a line no sibling carries (breadcrumb, alternate frontmatter, speculative nav header), the agent drops it and follows the corpus. The instruction also explicitly notes that VitePress production builds run in strict-link mode and that dead relative links ((./index),(./index.md), etc.) abort the Cloudflare Pages deploy without affecting the Docker image step. - Reference docs cited as canonical examples:
docs/flows/admin/AD-13-settings.md,docs/flows/admin/AD-03-order-management-admin.md,docs/flows/system/SY-32-logging.md— all start at# Titlewith no breadcrumb. - Closes
Advisable-com/ecommercen#246.
- Root cause. The SY-33 breadcrumb that broke the 4.101.0 docs deploy (resolved by hotfix 4.101.1) did not originate in
[4.101.1] fix(docs): drop dead breadcrumb in SY-33 flow doc that broke the VitePress production build
- The breadcrumb header
[Home](../../Home.md) | [Flows](../index.md) | [System](index.md)at the top ofdocs/flows/system/SY-33-bot-detection-session-exclusion.mdreferenced a non-existentdocs/flows/system/index.md. VitePress catches dead links during production build and aborted with[vitepress] 1 dead link(s) found.The Bitbucket Pipelines*.*.*tag job built and pushed the Docker image successfully but the subsequent docs-deploy-to-Cloudflare-Pages step failed and the 4.101.0 docs never reachedupstream-docs-ecommercen.pages.dev. - Removed the breadcrumb line entirely — none of the other system flow docs (SY-01 cron-framework, SY-31 rest-api-versioning, SY-32 logging) carry a breadcrumb. SY-33 now matches the convention: file starts at the
# SY-33: …H1. - No content change. Bumps
application/config/version.phpto04.101.001.000so the tag triggers a fresh pipeline run that completes both Docker image push and docs deploy.
- The breadcrumb header
[4.101.0] feat(session): use
matomo/device-detectorfor bot detection inMY_Sessionto stop creating sessions for modern AI crawlers- Root cause.
MY_Session::isBotSession()(application/libraries/Session/MY_Session.php:21) delegated to CodeIgniter'sUser_agent::is_robot(), which matches against the hand-maintained list of ~88 substrings inapplication/config/user_agents.php. The list is years out of date and misses every modern AI crawler — GPTBot, ClaudeBot, OAI-SearchBot, PerplexityBot, CCBot, Amazonbot, Applebot, Bytespider, etc. — and many security scanners and uptime checkers. Those crawlers were getting full CI sessions: rows in the session store,Set-Cookieheaders, session regenerations, all of it. - Fix in
application/libraries/Session/MY_Session.php:21-39. Replaced the body ofisBotSession()with aDeviceDetector\DeviceDetectorparse against$_SERVER['HTTP_USER_AGENT']. Empty UA short-circuits totrue(no real browser omits it).discardBotInformation()is set beforeparse()so the detector runs the fast bot-only path (no OS/client/device parsing). On any DeviceDetector failure the method fails open (returnsfalse) and logs vialog_message('error', ...)(PSR-3 → Monolog) so a corrupted YAML or OOM never blocks real users from logging in. - What stays the same.
isRouteExcluded(), thesession_excludes.phpconfig, the constructor early-return ordering — all untouched. CodeIgniter'sUser_agentlibrary andapplication/config/user_agents.phpstay in place because other call sites (agent->referrer(),agent->getOwnRedirectUrl()inAdv_customer.phpandChannelTrack.php) still depend on them. - Smoke verified against
develop.local.GPTBot,ClaudeBot,Googlebot, and empty-UA requests now return noSet-Cookieheader; real Chrome and garbage-UA strings (asdf) get sessions as before. - Follow-ups (not in this PR). Three other
is_robot()consumers can migrate to the same detection in separate PRs:ecommercen/core/Adv_front_controller.php:321(shouldLoadAdvisableAI()— highest financial upside; AI calls currently fire on every GPTBot page hit),ecommercen/api/controllers/AdvApiCartController.php:269(destroy()cart guard), and the doc references indocs/changelog/Changelog.4.30.md:40+docs/flows/customer/CF-34-ai-recommendations.md.
- Root cause.
[4.101.0] feat(rest): port Product/PriceTracking write layer + graph service + job to modern domain layer (Advisable-com/ecommercen#128)
- Write layer. New
Advisable\Domains\Product\PriceTracking\{WriteData,Validator,WriteService,Repository\WriteRepository}. The WriteService is registered in DI and used internally by the modern TrackPrices job. POST/PUT/DELETE REST routes remain commented inapplication/config/rest_routes.php— write access stays system-only, exactly as before. - Graph service. Ported
AdvPriceTrackingGraphs→Advisable\Domains\Product\PriceTracking\GraphService. The Omnibus reference-price logic (hasPriceChanges,isSuccessivePriceReductions,getHigherPriceOfPeriod,getLowestPriceOfPeriod,getNextLowestPriceOfPeriod,hasLowerPriceThanFinalPrice) is preserved byte-for-byte to keep storefront output identical. - Options DTO. Ported
AdvPriceTrackingOptions→Advisable\Domains\Product\PriceTracking\Options(immutablereadonly, public properties preserved for view-template parity). Constructed viaOptions::fromRegistry()factory and registered as a DI service so views/helpers/controllers resolve a single shared instance viadi()->get(Options::class). - PriceCalculator. Extracted the final-price math (regular + special discount + VAT) into
Advisable\Domains\Product\PriceTracking\PriceCalculatoras a framework-free, unit-testable service. The CI helpersapplyVatWithoutFormat/formatNumberwere inlined asround(...)+sprintf(...)so the calculator no longer depends onshopmodule_helper. - Job port.
AdvTrackPricesis nowAdvisable\Domains\Product\PriceTracking\Jobs\TrackPrices, implementing\JobCommanddirectly. It injects the modern WriteService and PriceCalculator.application/config/jobs.phpnow references the FQCN (matches theRemoveExpiredRefreshTokenspattern). - New REST graph endpoints.
GET /rest/product/price-tracking/graph/{productId}andGET /rest/product/price-tracking/graph?productIds=1,2,3(replacing the legacy/api/priceTracking/...controller). Annotated with OpenAPI; bulk endpoint takes a comma-separatedproductIdsquery param. - Legacy files removed (10 files).
ecommercen/job/libraries/AdvTrackPrices.php,ecommercen/eshop/libraries/AdvPriceTrackingGraphs.php,ecommercen/eshop/libraries/AdvPriceTrackingOptions.php,ecommercen/eshop/models/AdvPriceTrackingModel.php,ecommercen/api/controllers/AdvApiPriceTrackingController.php, plus the 4application/modules/...client-overridable subclasses (TrackPrices,PriceTrackingOptions,Price_tracking_graphs,Price_tracking_model,Api_price_tracking) and the legacy unit testtests/Legacy/Eshop/AdvPriceTrackingGraphsTest.php. - Call sites rewired (8).
ecommercen/core/Adv_front_controller.php,ecommercen/helpers/theme_helper.php,ecommercen/eshop/models/Adv_product_parser_model.php(3 sites), and 5 views (application/views/main/components/footer/footer_json_adv_app_context.php,application/views/admin/footer_json_adv_app_context_admin.php,application/views/admin/products/list.php,application/views/admin/settings/settings.php,application/views/main/components/live_search/live_search.php,live_search_v2.php) now resolve the Options DTO and GraphService viadi()->get(...). - Legacy
/api/priceTracking/...routes removed.application/config/routes.phpcleaned up; the 2 dispatcher entries are gone. Existing storefront/frontend callers should be repointed at the new/rest/product/price-tracking/graph[/{productId}]endpoints. - Tests. 4 new unit-test classes (
tests/Unit/Domains/Product/PriceTracking/{GraphServiceTest,PriceCalculatorTest,ValidatorTest,WriteDataTest}.php, 38 tests, 41 assertions). Replaces the deletedAdvPriceTrackingGraphsTest. Post-merge with develop (which brought #11, #78, #230, plus the Database-suite-green fixes), the full grand-total is 5,866 tests passing across Unit (2,663) + Integration (89, 37 self-skipped on Redis/APCu) + Legacy (207, 6 self-skipped on POSIX-only branches) + Database (2,907) with 0 failures and 0 errors. - DI registration.
src/Domains/Product/container.phpregisters the 6 new services;src/Rest/Product/container.phpadds the$graphServicearg on the controller.
- Write layer. New
[4.101.0] feat(rest/transporter): live smart-point catalog endpoint for guest checkout (Advisable-com/ecommercen#237)
- Endpoint. New
GET /rest/transporter/{transporterId}/smart-point— guest-accessible, returns the live pickup-point catalog for a single transporter. Used by Velora'suseSmartPointscomposable for the checkout pickup picker. - Service. New
Advisable\Domains\Transporter\SmartPoint\SmartPointCatalogServiceaggregates an existingsrc/SmartPoints/Transporters/*provider per request. No caching — mirrors the legacyAdv_order::smartPointsInitializebehaviour. Org-wide REST caching policy is under separate discussion inAdvisable-com/ecommercen#241; this endpoint is the first adopter candidate once a policy lands. - Controller. Thin
Advisable\Rest\Transporter\Controllers\SmartPointmaps typed service exceptions to envelope codes:200on success,404on unknown transporter,409on disabled provider,502on upstream failure. - Auth.
auth=guest— matches legacyAdv_order(Front_c) exposure. The pre-existing/rest/order/smart-point(per-order saved row, backend-only) is unchanged; the two endpoints are distinct concepts and stay distinct.
- Endpoint. New
[4.101.0] fix(tests): Database suite green end-to-end — DI bootstrap + transaction-isolation + seed-id partitioning
Container::markBootReady()added totests/bootstrap_ci.php. Without it,Container::getContainer()returns the stub container in every CI-backed test because thepre_controllerhook never fires under the test bootstrap. Everydi()->get(...)from inside a test'ssetUp()or production code-under-test was throwingServiceNotFoundException— accounting for ~2900 of the ~2904 baseline errors in the Database suite.LoggerDiBindingTest's manual reflection-basedbootReadytoggle (added 4.99.15) is now redundant but harmless.CiDatabaseTestCasetransaction wrapper rewritten. The old version used$db->trans_begin()/trans_rollback(), but CI3's reference-counted trans system short-circuits at depth > 0 without issuing a realBEGIN. WhenBaseWriteRepository::transactional()inside the SUT calledtrans_start(), it would either no-op (if depth > 0) or — worse — fire its ownSTART TRANSACTIONwhen depth was 0, which MySQL semantics treat as an implicitCOMMITof any active transaction. Inner writes leaked permanently, defeating the per-test rollback. The fix opens the test transaction with a rawSTART TRANSACTIONquery and then primes CI3's private_trans_depth = 1via\Closure::bind()so nestedtrans_start()calls just increment the counter instead of starting a new MySQL transaction.tearDown()issuesROLLBACKand resets depth to 0. Documented in inline comments referencing the CI3 driver behaviour atsystem/database/DB_driver.php.TestSeedmoved from id=99001 to id=888001 across all 6 entities. The 99000-99999 range is the established convention for per-test-class fixtures (each Integration test inserts its own row at 99001 etc.). The seed overlapping at the same id caused silent PK collisions: test INSERTs against the existing seed row failed, then reads returned the seed's data, producing mysterious assertion failures. id=888001 is far above the test fixture range so no collision is possible. Tests that explicitly reference seed rows as FK targets (onlyProduct\Product\{Repository,Service}Test'svat_id) updated to point at 888001.- Two stale-test fixes carried over from the #230 investigation:
Product\Product\ServiceTest::createData()gained the now-requiredproductCodesfield (validator tightened by #125), andSeo\DefaultMetaTag\ServiceTesthadfor=3/for=5updated to valid1/2enum values (validator restricted by #45). These weren't tracked as bugs because the underlying tests had been silently failing inside the broader Database suite breakdown. - Result. Database suite went from
2904 errors + 208 failures(out of 2907 tests) to 2907 / 2907 passing. Full grand-total across Unit + Integration + Database + Legacy: 5,860 tests pass, 0 failures, 0 errors.
[4.101.0] refactor(repository): inject
CI_DB_query_builderintoBaseRepositoryandBaseWriteRepositoryvia DI (closes Advisable-com/ecommercen#230)- Antipattern. Both
Advisable\Domains\Support\Repository\BaseRepository::__construct()andBaseWriteRepository::__construct()reached into the CI3 superobject ($CI =& get_instance(); $this->db = $CI->db;) to obtain theirCI_DB_query_builder. Because every concrete domain repository insrc/Domains/(and every clientcustom/Domains/repo) inherits from these classes, the entire repository layer was untestable without booting the full CI stack.BaseWriteRepositoryTestpapered over it with aReflectionClass::newInstanceWithoutConstructor()hack plus reflection on a private property — comments in the test even documented why the constructor had to be bypassed. - Fix (Strategy A, optional +
di()fallback). Both base constructors now accept?CI_DB_query_builder $db = nulland resolve$this->db = $db ?? di()->get(CI_DB_query_builder::class). Direct callers (production via Symfony autowiring of the factory registered in #78, tests via explicit mock injection) get pure constructor injection; thedi()->get()fallback survives only for legacy callers that haven't been migrated to pass$dbthrough. Theget_instance()call is gone from the runtime path. Mirrors the optional-logger pattern shipped in 4.99.15. - Why this approach. ~80 concrete
Repository.phpsubclasses currently override__construct(RepositoryConfigurator $configurator) { parent::__construct($configurator); }purely to narrow the configurator type for Symfony autowiring. Making$dba mandatory parent arg would require editing every one of those subclasses (plus the same in every client fork on next upstream sync) to forward$dbthrough. Optional-with-fallback achieves the same runtime semantics in 2 files, leaves subclass signatures unchanged, and preserves backward compatibility for the dozens of direct-instantiation callers that haven't been audited yet. Strict mandatory-arg propagation is tracked separately for a quieter moment as Advisable-com/ecommercen#231. - Tests. New
tests/Unit/Domains/Support/Repository/BaseRepositoryTest.php(3 tests) pins the constructor contract: explicitCI_DB_query_builderinjection, configurator relations stored, subclass-declared$tableexposed.BaseWriteRepositoryTestdrops itsnewInstanceWithoutConstructor()reflection hack in favor ofnew ConcreteWriteRepository($this->db)— same 10 method tests now exercise the real constructor path. Full Unit suite: 2625/2625 (+3 vs prior baseline);LoggerDiBindingTest5/5 confirms the container compiles cleanly under the new fallback path.
- Antipattern. Both
[4.101.0] fix(product): null
shop_product.shelfcode_idreferences on shelf code delete (closes Advisable-com/ecommercen#11)- Root cause.
Advisable\Domains\Product\Shelfcode\WriteService::delete()delegated straight toBaseWriteRepository::delete(), which only ranDELETE FROM shop_product_shelfcodes WHERE id = ?. The legacyAdv_shelfcodes_model::delete_record()(ecommercen/eshop/models/Adv_shelfcodes_model.php:113-118) performs a soft-dereference first —UPDATE shop_product SET shelfcode_id = NULL WHERE shelfcode_id = ?— and only then deletes the shelf-code row. The modern path skipped step one, soDELETE /rest/product/shelfcode/{id}either 500'd on the FK (where the constraint exists) or orphanedshop_product.shelfcode_idreferences pointing at a no-longer-existent shelf code. - Fix. New
Shelfcode\Repository\WriteRepository::nullProductReferences(int|string $id)runs the sameUPDATE shop_product SET shelfcode_id = NULL WHERE shelfcode_id = ?.WriteService::delete()now wraps the pair insidewriteRepository->transactional()— null-out the references first, then delete the shelf code — matching theCms\Builder\WriteService::delete()cascade pattern. Returnsfalseon transaction rollback or repo failure; partial cascades can no longer leak. - Tests. New
tests/Unit/Domains/Product/Shelfcode/WriteServiceTest.php(5 tests) asserts call ordering (nullProductReferencesruns before thedeletecall),falsereturn ontransactional()returningnull,falsereturn onWriteRepository::delete()returningfalse, plus two smoke tests pinning thecreate()/update()happy paths so the delete rework cannot silently regress its neighbours. Full Unit suite: 2622/2622 (+5 vs prior baseline).
- Root cause.
[4.101.0] refactor(checkout): inject
CI_DB_query_builderintoStockService(closes Advisable-com/ecommercen#78)- Antipattern.
Advisable\Domains\Checkout\StockService::__construct()reached into the CI3 superobject ($CI =& get_instance(); $this->db = $CI->db;) instead of declaring itsCI_DB_query_builderdependency. The service bypassed the DI container, hid its dependency fromsrc/Domains/Checkout/container.php, and forcedtests/Unit/Domains/Checkout/StockServiceTest.phpto instantiate viaReflectionClass::newInstanceWithoutConstructor()and inject the DB mock through reflection on a private property. - Fix. Constructor now takes
private CI_DB_query_builder $dbas a typed parameter — the standard DI pattern documented in.claude/rules/domain-layer.md. The test drops the reflection hack in favor ofnew StockService($mock). - New factory-backed DI registration for
CI_DB_query_builder. A newsrc/CodeIgniter/QueryBuilderFactory::create()returnsget_instance()->db;application/config/container/container.phpregisters it as the factory forCI_DB_query_builder(->set(CI_DB_query_builder::class, CI_DB_query_builder::class)->factory([QueryBuilderFactory::class, 'create'])). Mirrors howAppLoggerFactory::createbacksLoggerInterfaceinapplication/config/container/logger.php. Any future service that wants the query builder via DI can now type-hint it in its constructor with zero per-module wiring. Also tidied a stale unusedReferenceimport fromcontainer.php. - Follow-up. Advisable-com/ecommercen#230 tracks the same migration for
BaseRepository::__construct()andBaseWriteRepository::__construct(), which still use theget_instance()antipattern and are inherited by the entire domain-repository layer. - Tests. Unit suite 2617/2617 (matches the pre-change baseline);
LoggerDiBindingTest5/5 confirms the container compiles cleanly with the new factory registration.
- Antipattern.
[4.101.0] fix(gifts): use canonical
filterApplicableGiftRules()in admin order gift validation (Advisable-com/ecommercen#205)- Root cause.
Adv_orders_admin::validateProductGiftSelections()atecommercen/eshop/controllers/Adv_orders_admin.php:2624reimplemented the canonicalAdv_gifts_model::filterApplicableGiftRules()(ecommercen/eshop/models/Adv_gifts_model.php:512-535) as an inlinearray_filter, but the inline copy lacked the rule 13 carve-out. Rule 13 ("Products required - get cheapest free") does not usegift_choices, so itsgift_choices[1]is normally empty — the inline filter dropped it unconditionally while the canonical method called earlier at line 1513 keeps it. The admin order edit screen then diverged for Rule 13 gifts (visible in one widget, missing from validation, or vice versa). Storefront checkout was unaffected because it only ever used the canonical method. - Fix in
ecommercen/eshop/controllers/Adv_orders_admin.php:2624. Replaced the 18-line inlinearray_filterclosure with$this->gifts_model->filterApplicableGiftRules($availableGiftRules, $applicableGiftIds). Both call sites (1513 and 2624) now share the single Rule-13-aware canonical method, eliminating the divergence.
- Root cause.
[4.101.0] fix(gifts): strip
dom_gift_IDon rule 13 save and purge orphangift_choicesrows (Advisable-com/ecommercen#206)- Root cause. The rule 13 admin form POSTed
dom_gift_IDbutpostDataRuleCleanupHelper()had noremoveFieldsentry for it, so the field was written as-is togift_choices. Rule 13 runtime (Adv_gifts_model.php:511-535,:1172-1227) never readsgift_choices— the rows were dead on arrival. - Fix in
ecommercen/eshop/models/Adv_gift_rules_model.php:133. Appended'dom_gift_ID' => 1to rule 13'sremoveFieldsarray.postDataRuleCleanupHelper()then strips the field on save, matching the pattern rules 6/7/8 already use fordom_req_ID. - Cleanup patcher
patches/CleanupRule13GiftChoices.php. One-shotDELETE gc FROM gift_choices gc INNER JOIN gifts g ON g.id = gc.gift_id WHERE g.rule_id = 13to remove rows written by previous saves. - Migration
database/migrations/20260511120000_cleanup_rule13_gift_choices.php. Phinx migration extending\Patches\AbstractPatcherthat invokes the patcher. - Follow-up. Advisable-com/ecommercen#229 tracks the broader UX bug (rules 6/7/8/13 form fields rendered unconditionally regardless of active rule).
- Tests.
tests/Legacy/Eshop/AdvGiftsModelTest.php60/60 pass; pre-existing integration test errors are unrelated.
- Root cause. The rule 13 admin form POSTed
Notes
[4.101.0] Breaking — legacy PriceTracking classes deleted (Advisable-com/ecommercen#128):
- Any client repo that overrode
AdvTrackPrices,AdvPriceTrackingGraphs,AdvPriceTrackingOptions,AdvPriceTrackingModel, orAdvApiPriceTrackingController(or theirapplication/modules/...thin-shim subclassesTrackPrices/PriceTrackingOptions/Price_tracking_graphs/Price_tracking_model/Api_price_tracking) must migrate to the modern domain services. Override targets:Custom\Domains\Product\PriceTracking\Jobs\TrackPrices(replaces the legacy job class — extend the modern one or compose around itsWriteServiceinjection)Custom\Domains\Product\PriceTracking\PriceCalculator(custom discount/VAT logic — wire via DI alias to override the upstream calculator)Custom\Domains\Product\PriceTracking\GraphService(custom Omnibus reference-price algorithm)Custom\Domains\Product\PriceTracking\Options(custom registry mapping)
- Frontend callers of
/api/priceTracking/...must be repointed at the modern REST endpoints:/rest/product/price-tracking/graph/{productId}(single) and/rest/product/price-tracking/graph?productIds=...(bulk). - Storefront/admin view templates that constructed
new PriceTrackingOptions()directly now resolve\Advisable\Domains\Product\PriceTracking\Optionsviadi()->get(...); the public property shape is preserved so view-side code reading$priceTrackingOptions->enabled,->days,->labelPriceTrackDiscount[...], etc. continues to work unchanged.
- Any client repo that overrode
[4.101.0] Check for overrides (admin order gift validation):
ecommercen/eshop/controllers/Adv_orders_admin.php—validateProductGiftSelections()now delegates the applicable-rules filtering toAdv_gifts_model::filterApplicableGiftRules()instead of an inlinearray_filter. Any client override of this controller method (or ofAdv_orders_admin) that retained the old inline closure will continue to drop Rule 13 gifts from the admin order edit screen. Re-point overrides at the canonical model method, or remove the override if it only existed to add the missing Rule 13 carve-out.
[4.101.0] REQUIRES
php migrator.php migrate:- One-time cleanup patcher
Patches\CleanupRule13GiftChoicesto delete orphan rows ingift_choicesfor rule 13 gifts (Advisable-com/ecommercen#206).
- One-time cleanup patcher