Appearance
<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /changelog/Changelog.4.109.md for this page in Markdown format</div>
Version 4
version 4.109
[4.109.5] fix(manago): pass logger as constructor arg #2 at legacy
new Manago()call sites (Advisable-com/ecommercen#356)- The bug. 4.109.2 migrated
Manago::__construct()to theNamedLoggerInterfaceDI pattern, inserting?NamedLoggerInterface $loggeras argument #2 and pushing?CircuitBreaker $circuitBreakerto argument #3. The class and the modernDispatchManagoOrderListenerwere updated, but the legacynew Manago(...)call sites were not — they still passed the circuit breaker as arg #2, so every reached call threw a fatalTypeError(Argument #2 ($logger) must be of type ?NamedLoggerInterface, CircuitBreaker given). A hard 500 on product page, checkout, customer login, recommendations, order events and the cart API; one client production log showed 812 fatals in a single day. - The fix. Updated all call sites to the 3-arg form
new Manago($registry, null, di()->get('circuit_breaker.manago'))—nullas the logger (the constructor self-resolves it viadi()and->withName('manago')) and the circuit breaker correctly as arg #3. Six legacyecommercen/controllers (Adv_products,Adv_checkout,Adv_customer,Adv_front_controller,Adv_order,AdvApiCartController) plus the upstream-trackedapplication/controllers/Webrun.php(a 7th site the issue flagged as fork-only but which is present in the main repo) were corrected. A repo-widenew Manago(grep confirms no other 2-arg caller remains; the only other site is the already-correct modern listener.
- The bug. 4.109.2 migrated
[4.109.5] fix(customer): use
di()->get()for the password-reset service inrecoverPasswordPost(Advisable-com/ecommercen#357)- The bug. The storefront recover-password handler called
di(PasswordResetService::class)->requestReset($email), but thedi()helper takes no parameters and returns the container itself — the class argument was silently discarded andrequestReset()was invoked onAppCachedContainer, fatalling withCall to undefined method AppCachedContainer::requestReset(). Every "forgot password" submission returned a 500. The defect was introduced by the #323 fix (4.107.5) that routed the legacy form through the sharedPasswordResetService, and is the onlydi(...)-called-with-an-argument site in the codebase. - The fix.
di()->get(\Advisable\Domains\Customer\Customer\PasswordResetService::class)->requestReset($email)inecommercen/eshop/controllers/Adv_customer.php:444— the samedi()->get(...)form every other caller uses. A repo-wide grep confirms no otherdi(<arg>)call remains.
- The bug. The storefront recover-password handler called
[4.109.5] fix(eshop): null-safe session access in
isCustomerLoggedIn()(Advisable-com/ecommercen#358)- The bug.
isCustomerLoggedIn()dereferencedget_instance()->session->userdata('customer_logged_in')with no guard. It is called from the shared storefront header/footer (and othermain-layout partials), so it also runs on controllers that never loaded thesessionlibrary — e.g. a feed controller that does not extend the front controller and falls through toerror_404()while still rendering the storefront layout. On those requests$CI->sessionisnulland the page fatals withCall to a member function userdata() on null. Newly exposed in 4.109.2, which moved session loading intoAdv_front_controller::__construct()(so only front-controller paths get a session). - The fix.
get_instance()->session?->userdata('customer_logged_in') ?? false— the nullsafe operator returnsfalse("not logged in") when the session library was never loaded, protecting the shared layout render path for all ~34 callers. Targeted helper-level guard rather than force-loading the session on every path.
- The bug.
[4.109.4] chore(release): git-flow sync release — re-establish canonical branch structure (no functional changes)
- Why. A maintenance/no-op release whose only purpose is to run a clean git-flow
release finishsomasteranddevelopre-sync with the canonical merge structure (Merge branch 'release/X'on master,Merge tag 'X' into developon develop). The 4.107–4.109.2 releases had been finished manually and drifted the branch histories; 4.109.3 re-aligned the content and this cut restores the proper merge shape going forward. No application code, config, schema, or REST changes.
- Why. A maintenance/no-op release whose only purpose is to run a clean git-flow
[4.109.3] fix(docs): escape pipe chars in flow-doc table code spans — unbreaks the VitePress docs deploy (failed on 4.109.0/.1/.2)
- The problem. An unescaped
|inside an inline code span within a Markdown table cell is parsed as a column delimiter, which splits the code span open and leaks<msg>/<Class>-style placeholders into the page as raw HTML. VitePress's Vue-SFC compiler then aborted the production docs build with "Element is missing end tag" (docs/flows/integration/IN-24-mcp-connector.md:222). A failed build does not error loudly — it silently skips the Cloudflare Pages deploy. The defect was added with IN-24 in 4.109.0 and, because each release rebuilds the docs from the tag, it failed the docs deploy on every release since. - The fix. Escaped the offending pipes as
\|so the code span stays intact and the placeholders remain HTML-escaped inside<code>. Verified with a full local VitePress build. Four same-class render-only cases (AD-13, AD-19, CF-23, IN-08) were corrected in the same pass. A guard against re-introducing the pattern shipped separately in the doc-ba agent toolchain.
- The problem. An unescaped
[4.109.2] chore(claude): agent system extracted to the ecommercen-claude plugin marketplace
- What changed. The Claude Code agent system (18 agents, 14 skills, 7 rules, 2 hooks) that previously lived inline in
.claude/is now distributed as the ecommercen-claude plugin marketplace (ecommercen-platform, hosted on GitHub). This repo's.claude/is reduced to a thinsettings.jsonthat registers the marketplace (pinned to thestablechannel) and enables the default-on slices —core,generators,qa,docs,legacy; the 128 inline agent/skill/rule/hook files are removed. The agent system is now versioned in one place instead of drifting between the main repo and the client forks. - No runtime impact. Developer-tooling only — no PHP, JavaScript,
src/,ecommercen/, orapplication/code changed (pre-flight unchanged at 6844 no-DB tests). The SessionStart repo-detector and the REST-API-changelog merge-guard hook now ship from the plugin (ecommercen-core/ecommercen-qa).
- What changed. The Claude Code agent system (18 agents, 14 skills, 7 rules, 2 hooks) that previously lived inline in
[4.109.1] fix(listing): cap storefront per-page selector to prevent 1000-product listings (Advisable-com/ecommercen#298)
- The problem. The category and vendor per-page selector ("show N per page") offered an "ΟΛΑ/ALL" option worth 1000, and the chosen value was stored in the session and used as the listing limit with no allow-list and no cap. When 1000 was the active selection (sticky session or a default override), category/vendor pages queried and rendered up to 1000 products per request — ~1.85 MB HTML responses and high PHP time, amplified by crawlers (SkroutzBot, Googlebot) walking category pages.
- Single config-driven ceiling. New
$config['products_list_max'] = 72;inapplication/config/app.phpis the one source of truth. A newproductsListMaxLimit()helper reads it (falling back to 72 if absent) and drives all three enforcement points below, so tuning the cap — per client or theme — is a one-line config change. - Selector options.
pageLimitsArray()now builds its options from the base steps{9, 18, 36, 72}filtered to<= products_list_max(and always includes the ceiling itself); the1000 => ΟΛΑoption is gone. - Server-side validation.
maxPageLimitsPost()casts the postedmax_limitand stores it only if it is one of the offered options, rejecting tampered or legacy1000values. - Defensive clamp.
Adv_product_categories(1 site) andAdv_vendors(2 sites) clamp$this->limitto the ceiling after reading the session — this neutralizes any visitor session already holding1000immediately on deploy, without waiting for it to expire.
[4.109.0] feat(mcp): bulk product tools —
products_batch_get&products_batch_update(Advisable-com/ecommercen#348 #349)- What it is. Two new bulk product tools bring the MCP tool surface to 13 (up from 11).
products_batch_getandproducts_batch_updateare registered inMcpServerFactoryalongside the existing per-product tools. The customer-facing guidedocs/guides/mcp-connector.mdwas updated in the same branch. products_batch_get(ids[]). Fetches up to 100 products by id in a single query, using the existingidfilter (WHERE IN) inProduct\Service. Returnsdata— theget_productshape in the requested order — andmissing— a list of ids that were not found. Ignores active/visibility status because the ids are explicit.products_batch_update(updates[]). Updates title + meta for up to 50 existing products, routing each row through the sameupdateProductpath used byupdate_product. Best-effort: a failing or missing id is collected as{id, error}and does not abort the batch; each successfully updated row is audited asupdate_product. Cannot create products.- Tests. 14 new
ProductToolsTestunit tests cover the three feature entries in this batch. Full Unit suite green (3411 tests). A namespacedconfig_item()Unit-bootstrap shim was added so the domainListRequest(viaHasLocales::getLocales()) resolves locales under the pure-Unit bootstrap, without shadowing CodeIgniter's globalconfig_item().
- What it is. Two new bulk product tools bring the MCP tool surface to 13 (up from 11).
[4.109.0] feat(mcp): expose read-only
price,is_active,updated_atandstockon product reads (Advisable-com/ecommercen#346 #347)- What it is. Both
get_productandsearch_productsnow return four additional read-only fields on every product (sourced from the mastershop_productrow and itsproduct_codesrelation), giving the LLM context it needs to prioritise which products to work on. price,is_active,updated_at. Sourced directly from the master row:price(cast to float),is_active(true whenactive=1ANDsoft_delete=0),updated_at(thedate_changedcolumn). Delivers the read half of the Wecare Dev Brief #1 pricing request.stock. Product-level total quantity summed over active, non-soft-deleted SKUs loaded via theproductCodesrelation (batched per page). Summed in PHP after the relation load so it works within the existing Specification-based query.- Attribute guard. Field access uses a
property_existscheck so calling either tool on an entity that was not loaded with these fields returnsnullrather than triggeringBaseEntity's DI-backed lazy-relation loader.
- What it is. Both
[4.109.0] feat(mcp):
search_productsserver-side filtering, larger pages & active-by-default scope (Advisable-com/ecommercen#342 #343 #344 #345)- What it is. A batch of filtering and usability improvements to
search_productsmotivated by the Wecare MCP Dev Brief #1: the tool can now surface the SEO backlog directly, filter by status and recency, and page through larger result sets without chaining calls. - Larger pages.
per_pagecap raised from 20 to 200 (#342). The limit was an arbitrary tool constant; the domainPaginationspec has no ceiling. has_metafilter. NewFilterByMetaPresencesubquery spec, locale-scoped, matching the tool projection'shas_metarule:meta_title OR meta_descriptionnon-empty inshop_product_muifor the given locale (#343).has_meta=falsesurfaces products still missing meta, including products with no translation row at all for the locale (NOT IN matches them). The filter is routed fromProduct\Service::buildSpecificationsvia ametaPresencerelation sentinel on theFilterRequest. This is the key SEO-backlog enabler.- New filters
is_active,vendor_id,updated_since.vendor_idis an alias of the existingbrand_idfield (both map toshop_product.vendor_id;brand_idwins when both are supplied).updated_sinceuses the newFilterByDateChangedSincespec (shop_product.date_changed >=), intended for incremental processing (#344). The date/datetime value is validated and normalised in the tool layer before being passed to the spec. - Active-by-default scope.
search_productsnow defaults to active, non-deleted products (#345).include_inactive=trueopts back in for drafts. The default is applied in the MCP tool layer, not in the sharedListRequest, so REST consumers are unaffected. - Falsy-value pass-through. Filter values that are falsy (
is_active=false,has_meta=false,softDelete=0) are passed as strings ('0','1') so they surviveGenerateListRequest's0 == nullvalue check.
- What it is. A batch of filtering and usability improvements to
[4.109.0] feat(builder): page-builder Code Editor gated behind
enableBuilderCodeEditorconfig flag- What it is. The GrapesJS admin page-builder now ships a Code Editor modal — a panel button that opens a modal where admins can edit a selected block's raw HTML and CSS directly, with HTML+CSS / HTML / CSS tabs, copy-to-clipboard, format, line numbers, and Ctrl+S to apply. Locale keys (
code.editor.*,copy,format,cancel,characters) were added to all six locale files (en, el, es, it, ru, zh). - Feature flag. The Code Editor is gated behind a new boolean config:
$config['enableBuilderCodeEditor'] = false;inapplication/config/app.php(placed alongside the other builder/product flagsenableProductVariations/enableCloneProduct/enableModalCloneProduct). It defaults tofalse— the feature is OFF on all shops unless explicitly enabled. The flag is bridged server-side intowindow.builderEditor.enableCodeEditorinapplication/views/main/components/footer/footer_js.phpand read inassets/admin/js/builder/editor/buttons.jsbefore registering the command and panel button.
- What it is. The GrapesJS admin page-builder now ships a Code Editor modal — a panel button that opens a modal where admins can edit a selected block's raw HTML and CSS directly, with HTML+CSS / HTML / CSS tabs, copy-to-clipboard, format, line numbers, and Ctrl+S to apply. Locale keys (
[4.109.0] fix(mcp): reconcile meta-length tool-schema advice with soft-warning thresholds (Advisable-com/ecommercen#340)
- The bug. The MCP connector's
meta_title/meta_descriptiontool input schemas advised different length bounds from the server-side soft-warning thresholds: the schema text told the LLM ≤ 60 chars for title and 120–160 for description, whileToolResultonly fired a warning above 70 / 170 respectively. A value the schema called "over the recommended length" produced nowarnings[]entry. The two numbers lived in separate classes (McpServerFactoryschema strings vsToolResultinteger constants) with no shared source of truth, so they could drift independently. - Fix. Made the thresholds a single source of truth:
ToolResult::META_TITLE_SOFTandMETA_DESCRIPTION_SOFTare nowpublic constaligned with the platform's own SEO convention inAdvisable\Domains\Seo\CustomMetaTag\Validator(title 65 "to fit the Google SERP", description 150).McpServerFactory::metaTitleProp()/metaDescriptionProp()now build their schema descriptions from those constants, so the advice the LLM sees and the threshold the server enforces can no longer drift. - Behaviour. Cosmetic — warnings are non-blocking and all writes always succeeded. The advised bound now matches the enforced threshold; a
meta_titleof 66–70 chars (ormeta_descriptionof 151–170) now correctly produces a warning where it previously produced none. - Tests. New
ToolResultTest(7 tests): threshold boundary checks (no warning at/below, warns above with the recommended-length number), independent per-field warnings, multibyte character counting, and a constant-pinning guard against the SEO convention (65/150). Full Unit suite green (3397 tests).
- The bug. The MCP connector's
Notes
[4.109.2] Agent system is now a plugin — one-time developer action; no runtime/deploy step
- After merging this release, the inline
.claude/files are gone and replaced by a thin.claude/settings.jsonpointing at theAdvisable-com/ecommercen-claudemarketplace. Developers need GitHub read access to that (private) repo; Claude Code then auto-enables the default-on slices on next session. Nothing to run, deploy, or migrate. - Clients: this is the upstream half of the agent-system migration. When a client merges this release, the merge removes the client's stale inline
.claude/and brings in the thinsettings.json. To get the upstream-merge agent in the client, enable the client-onlyecommercen-client-mergeslice there (commit it into the client's.claude/settings.json, or enable it per-developer).
- After merging this release, the inline
[4.109.0] Requires
npm run admin-production- Upgrade note for existing clients. Because the default is
false, any client that was already relying on or expecting this Code Editor must explicitly opt in by setting$config['enableBuilderCodeEditor'] = true;inapplication/config/app.php. Clients that leave the flag absent orfalsewill not see the Code Editor button — no other behaviour changes. - Seajets, forbetterskin, Evripidis clients have already using this feature
- Upgrade note for existing clients. Because the default is