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.109.md for this page in Markdown format</div>

Home | Changelog

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 the NamedLoggerInterface DI pattern, inserting ?NamedLoggerInterface $logger as argument #2 and pushing ?CircuitBreaker $circuitBreaker to argument #3. The class and the modern DispatchManagoOrderListener were updated, but the legacy new Manago(...) call sites were not — they still passed the circuit breaker as arg #2, so every reached call threw a fatal TypeError (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'))null as the logger (the constructor self-resolves it via di() and ->withName('manago')) and the circuit breaker correctly as arg #3. Six legacy ecommercen/ controllers (Adv_products, Adv_checkout, Adv_customer, Adv_front_controller, Adv_order, AdvApiCartController) plus the upstream-tracked application/controllers/Webrun.php (a 7th site the issue flagged as fork-only but which is present in the main repo) were corrected. A repo-wide new Manago( grep confirms no other 2-arg caller remains; the only other site is the already-correct modern listener.
  • [4.109.5] fix(customer): use di()->get() for the password-reset service in recoverPasswordPost (Advisable-com/ecommercen#357)

    • The bug. The storefront recover-password handler called di(PasswordResetService::class)->requestReset($email), but the di() helper takes no parameters and returns the container itself — the class argument was silently discarded and requestReset() was invoked on AppCachedContainer, fatalling with Call 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 shared PasswordResetService, and is the only di(...)-called-with-an-argument site in the codebase.
    • The fix. di()->get(\Advisable\Domains\Customer\Customer\PasswordResetService::class)->requestReset($email) in ecommercen/eshop/controllers/Adv_customer.php:444 — the same di()->get(...) form every other caller uses. A repo-wide grep confirms no other di(&lt;arg>) call remains.
  • [4.109.5] fix(eshop): null-safe session access in isCustomerLoggedIn() (Advisable-com/ecommercen#358)

    • The bug. isCustomerLoggedIn() dereferenced get_instance()->session->userdata('customer_logged_in') with no guard. It is called from the shared storefront header/footer (and other main-layout partials), so it also runs on controllers that never loaded the session library — e.g. a feed controller that does not extend the front controller and falls through to error_404() while still rendering the storefront layout. On those requests $CI->session is null and the page fatals with Call to a member function userdata() on null. Newly exposed in 4.109.2, which moved session loading into Adv_front_controller::__construct() (so only front-controller paths get a session).
    • The fix. get_instance()->session?->userdata('customer_logged_in') ?? false — the nullsafe operator returns false ("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.
  • [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 finish so master and develop re-sync with the canonical merge structure (Merge branch 'release/X' on master, Merge tag 'X' into develop on 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.
  • [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 &lt;msg>/&lt;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 &lt;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.
  • [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 thin settings.json that registers the marketplace (pinned to the stable channel) 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/, or application/ 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).
  • [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; in application/config/app.php is the one source of truth. A new productsListMaxLimit() 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 &lt;= products_list_max (and always includes the ceiling itself); the 1000 => ΟΛΑ option is gone.
    • Server-side validation. maxPageLimitsPost() casts the posted max_limit and stores it only if it is one of the offered options, rejecting tampered or legacy 1000 values.
    • Defensive clamp. Adv_product_categories (1 site) and Adv_vendors (2 sites) clamp $this->limit to the ceiling after reading the session — this neutralizes any visitor session already holding 1000 immediately 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_get and products_batch_update are registered in McpServerFactory alongside the existing per-product tools. The customer-facing guide docs/guides/mcp-connector.md was updated in the same branch.
    • products_batch_get(ids[]). Fetches up to 100 products by id in a single query, using the existing id filter (WHERE IN) in Product\Service. Returns data — the get_product shape in the requested order — and missing — 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 same updateProduct path used by update_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 as update_product. Cannot create products.
    • Tests. 14 new ProductToolsTest unit tests cover the three feature entries in this batch. Full Unit suite green (3411 tests). A namespaced config_item() Unit-bootstrap shim was added so the domain ListRequest (via HasLocales::getLocales()) resolves locales under the pure-Unit bootstrap, without shadowing CodeIgniter's global config_item().
  • [4.109.0] feat(mcp): expose read-only price, is_active, updated_at and stock on product reads (Advisable-com/ecommercen#346 #347)

    • What it is. Both get_product and search_products now return four additional read-only fields on every product (sourced from the master shop_product row and its product_codes relation), 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 when active=1 AND soft_delete=0), updated_at (the date_changed column). 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 the productCodes relation (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_exists check so calling either tool on an entity that was not loaded with these fields returns null rather than triggering BaseEntity's DI-backed lazy-relation loader.
  • [4.109.0] feat(mcp): search_products server-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_products motivated 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_page cap raised from 20 to 200 (#342). The limit was an arbitrary tool constant; the domain Pagination spec has no ceiling.
    • has_meta filter. New FilterByMetaPresence subquery spec, locale-scoped, matching the tool projection's has_meta rule: meta_title OR meta_description non-empty in shop_product_mui for the given locale (#343). has_meta=false surfaces products still missing meta, including products with no translation row at all for the locale (NOT IN matches them). The filter is routed from Product\Service::buildSpecifications via a metaPresence relation sentinel on the FilterRequest. This is the key SEO-backlog enabler.
    • New filters is_active, vendor_id, updated_since. vendor_id is an alias of the existing brand_id field (both map to shop_product.vendor_id; brand_id wins when both are supplied). updated_since uses the new FilterByDateChangedSince spec (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_products now defaults to active, non-deleted products (#345). include_inactive=true opts back in for drafts. The default is applied in the MCP tool layer, not in the shared ListRequest, 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 survive GenerateListRequest's 0 == null value check.
  • [4.109.0] feat(builder): page-builder Code Editor gated behind enableBuilderCodeEditor config 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; in application/config/app.php (placed alongside the other builder/product flags enableProductVariations / enableCloneProduct / enableModalCloneProduct). It defaults to false — the feature is OFF on all shops unless explicitly enabled. The flag is bridged server-side into window.builderEditor.enableCodeEditor in application/views/main/components/footer/footer_js.php and read in assets/admin/js/builder/editor/buttons.js before registering the command and panel button.
  • [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_description tool 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, while ToolResult only fired a warning above 70 / 170 respectively. A value the schema called "over the recommended length" produced no warnings[] entry. The two numbers lived in separate classes (McpServerFactory schema strings vs ToolResult integer constants) with no shared source of truth, so they could drift independently.
    • Fix. Made the thresholds a single source of truth: ToolResult::META_TITLE_SOFT and META_DESCRIPTION_SOFT are now public const aligned with the platform's own SEO convention in Advisable\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_title of 66–70 chars (or meta_description of 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).

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.json pointing at the Advisable-com/ecommercen-claude marketplace. 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 thin settings.json. To get the upstream-merge agent in the client, enable the client-only ecommercen-client-merge slice there (commit it into the client's .claude/settings.json, or enable it per-developer).
  • [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; in application/config/app.php. Clients that leave the flag absent or false will not see the Code Editor button — no other behaviour changes.
    • Seajets, forbetterskin, Evripidis clients have already using this feature