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

Home | Changelog

Version 4

version 4.99

  • [4.99.17] fix(docker): bump Node base image to 24.13.1-alpine3.22 to match package.json engines

    • Hotfix for the 4.99.16 image build failure. The 4.99.16 release stamped package.json engines to ~24.13.0 / ~11.8.0 (commit c3f17cf38e, #216) but left the Docker base image pinned at node:20.16-alpine3.20 in three places. Combined with .npmrc's engine-strict=true, the npm run all-production step inside the Docker builder failed before producing metadata.json, breaking the tag-triggered Bitbucket pipeline.
    • Three pinned places aligned to Node 24.13.1. .docker/images/node.dockerfileNODE_VERSION=24.13.1, NODE_ALPINE_VERSION=3.22. .docker/images/app.dockerfile — same args (the alpine arg here was always dead weight; updated for consistency). .docker/scripts/build/build-common.shexport NODE_VERSION=24.13.1.
    • Why 24.13.1-alpine3.22. Docker Hub does not publish node:24.13-alpine3.20 or -alpine3.21 tags — the available alpine variants for Node 24.13 are 3.22 and 3.23. Picked 3.22 (released June 2025, contemporaneous with Node 24.13) over the newer 3.23 for stability. Verified the bundled npm version: vendor v24.13.1/deps/npm/package.json declares "version": "11.8.0" — exact match for engines.npm: "~11.8.0", no separate npm install -g step needed.
    • Out of scope. Local compose.sh integration stack (.docker/integration/.env NGINX_IMAGE_TAG=1.27-alpine3.20-slim) is unrelated and untouched. Only Node images bumped.
  • [4.99.16] fix(logger): pre-DI fallback now honors APP_LOG_THRESHOLD (closes #222)

    • Cron mailbox flooded on bare-metal hosts. After the PSR-3 migration in b8f30e995, application/helpers/log_helper.php::_logger_dispatch_or_fallback() called error_log() for every pre-DI log_message() call regardless of level. CI3's bootstrap (Config Class Initialized, Hooks Class Initialized, mbstring.internal_encoding deprecations, etc.) fires several INFO/DEBUG records before di() is wired, and on hosts where the error_log ini is unset (e.g. Plesk) PHP CLI sends those to STDERR — which cron mails. Every * * * * * php cli.php job_manager minute produced one email per enabled queue × every CI bootstrap line. Docker images masked it because .docker/images/php.dockerfile sets error_log = /dev/stderr deliberately.
    • Threshold filter applied before error_log(). New _logger_fallback_below_threshold($level) helper maps the requested level and APP_LOG_THRESHOLD env to Monolog's numeric ranks and drops records below the threshold — matching the filter Monolog itself applies once the DI logger is up. Default is NOTICE (mirrors application/config/monolog.php); unparseable env values fall back to NOTICE; unrecognized log levels pass through (better noise than a swallowed signal during early bootstrap).
    • Reads $_SERVER and $_ENV directly, not env(). Two global env() definitions live in this codebase: public/index.php reads $_ENV only, while the cakephp/core override (loaded via composer files autoload) reads $_SERVER first. Bypassing env() and matching dotenv's storage in both globals keeps the fallback predictable across web, CLI, and test contexts.
    • Tests. New testFallbackHonorsAppLogThreshold data provider covers ERROR/WARNING/NOTICE/DEBUG/unparseable thresholds across all five PSR-3 levels emitted with the DI container deliberately empty, plus testFallbackUnknownLevelPassesThrough for arbitrary level strings. 13 tests in tests/Unit/Logger/LogHelperHandlersTest.php (was 8); full Logger suite green.
  • [4.99.16] build(node): bump engines to Node 24.13 / npm 11.8

    • package.json engines declaration only — no source changes. Verified locally on Windows: npm ci clean (926 packages, only Sass forward-compat deprecation noise — no native build failures, no node-gyp), and both npm run production and npm run admin-production complete with exit code 0.
    • No engines.node ceiling in the dep tree would block Node 24, and no package has Node-24-specific runtime issues. Laravel Mix 6 (the unmaintained piece) builds clean under Node 24 without the OpenSSL legacy-provider escape hatch.
    • Out of scope here. Security floor bumps for axios, lodash-es, grapesjs (and a few hygiene bumps) are tracked separately under #216 and sub-issues #217–#221 — those land independently of the engine version.
  • [4.99.16] fix(rest): stop loading session/cart/advauth for stateless REST controllers

    • Root cause. ecommercen/core/Controller.php::__construct() had if (!is_cli()) { $this->load->library(['session', 'cart', 'advauth']); } sitting at the root of every HTTP controller's inheritance chain. Every REST request instantiated a HandlesRestfulActions descendant, the constructor chain bubbled all the way up to Controller, and CI3's session library called session_start() which wrote a file to sess_save_path (default ../sessions/). REST never reads $this->session/cart/advauth (auth is JWT via AuthenticationMiddleware/Tokens), so this was pure overhead — one session file per request, plus per-request Set-Cookie: dev1ssck=... sprawl on otherwise stateless endpoints.
    • Fix. Removed the eager load from Controller. Pushed it down into the only branches that actually consume those libraries — Adv_admin_controller and Adv_front_controller — both inserted right after parent::__construct() and before any code that reads session userdata (admin's isLoggedIn() check; front's gdprSessionCookies() and initializeCustomerSessionData()).
    • Two outliers patched explicitly. Adv_login (advisable internal login) and AdvSso (SSO callback) extend Base_c directly but actively use $this->session/$this->advauth. Both now load those libraries in their own constructors. Verified clean by enumerating every controller class and grepping for word-boundary \$this->session\b|\$this->cart\b|\$this->advauth\b — all other Base_c-direct controllers (REST CRUD ×~110, payment webhooks AdvViva/AdvJcc/AdvKlarnaPayments/AdvSmartCart, feed XML via AdvXml, job runners AdvJob/AdvJobManager/AdvJobQueueManager, Cronjob, Editor, Patcher, Redirect, AdvModeController, AdvApiPriceTrackingController, AdvApiServices, AdvProtectedApi, Adv_farmakon_erp, Adv_solr) and AppController-direct controllers (Blockaccess, Dbutils, Adv_eshop) make no use of those libraries.
    • End-to-end verified. With the patch applied, 5× anonymous GET /rest/cms/page produces zero new session files (200 OK, no Set-Cookie header, Api-Version: 1). 3× anonymous GET / (storefront) produces +3 session files with Set-Cookie: dev1ssck=... per response, confirming the storefront pipeline is unaffected.
    • Tests. --testsuite=Unit 2611/2611, --testsuite=Integration 89/89, --testsuite=Legacy 234/234.
  • [4.99.16] fix(logger): default APP_LOG_THRESHOLD to NOTICE so a healthy pod has signal-of-life

    • Two-line code change in application/config/monolog.php — both the STDOUT-mode and file-mode branches now fall back to 'NOTICE' instead of 'ERROR' when APP_LOG_THRESHOLD is unset. Configmaps that explicitly set the env var continue to override the fallback exactly as before; this only affects deployments that don't set it.
    • Surfaced live as a misdiagnosis on seajets-admin. JSON output via MONOLOG_CHANNEL_CONFIGURATION=STDOUT was believed broken because Loki was empty for the backend container. The format pipeline was correct end-to-end — every record was being gated upstream by ChannelThresholdHandler against the ERROR default and discarded before ever reaching JsonFormatter. Same gate would have produced the same empty output in file mode; STDOUT just made the silence visible.
    • Why NOTICE and not WARNING or INFO. NOTICE admits operational notices (deprecations, slow-query thresholds, JWT validation anomalies) without the per-request noise of INFO, while still leaving "healthy steady-state" pods near-silent in Loki — typically a few records per pod-hour vs the zero of ERROR. Per-channel runtime overrides (APP_LOG_CHANNEL_OVERRIDES) and per-channel static map in monolog.php still work for surgical DEBUG sessions.
    • Flow doc docs/flows/system/SY-32-logging.md updated to match (the example referencing the platform default).
  • [4.99.16] feat(seo): port CustomMetaTag CRUD to Domain/REST (partial #119)

    • New Seo/CustomMetaTag Domain module — Entity, Repository, RepositoryConfigurator (no relations), Service, ListRequest, WriteData, Validator, WriteRepository (with existsByUrl(?int $excludeId) lookup), WriteService. Mirrors the legacy Adv_custom_metatags_model/Adv_custom_metatags controller pair which is the primary SEO management tool for shop owners.
    • New REST controller at /rest/seo/custom-meta-tag with index/show/item/store/update/destroy (and the (\w{2})/... localized variants — 12 routes total). Wired in src/Rest/Seo/container.php. Domain services registered in src/Domains/Seo/container.php.
    • Validation matches legacy form_validation rules. url required + unique (case-sensitive lookup against the same custom_meta_tags.url UNIQUE index), max 255 chars (DB hard cap). On update, the uniqueness check excludes the current row id (existsByUrl($url, $id) mirrors is_unique[custom_meta_tags.url.id.$id]). Soft length limits: pageTitle ≤ 65, metaDescription ≤ 150, metaKeywords ≤ 1000 — generous ceilings inside the SERP-friendly band so obviously-malformed payloads are rejected before storage. Empty-string url on update returns 422 (cannot be empty); omitting the field entirely is allowed and skips the uniqueness check.
    • Timestamps managed by the WriteService. created_at + updated_at set to date('Y-m-d H:i:s') on create (single instant for both, matching legacy Adv_custom_metatags_model::addRecord). updated_at bumped on update only when there's actual data to write — a no-op update (POST /{id} with empty body) does NOT bump the timestamp. Created_at is never touched on update.
    • Out of scope (deliberately split for follow-up). Adv_seo_lib does not touch custom_meta_tags — it only resolves defaults and language metatags. The per-URL runtime resolution is Adv_front_controller::customMetaTags() (31 lines, simple URL lookup with pscache TTL); leaving it on the legacy side until a separate "port Seo_lib runtime" issue is filed. The CRUD half closes the immediately-actionable scope of #119.
    • OpenAPI. New CustomMetaTag (write payload), CustomMetaTagResource, CustomMetaTagCollection schemas. public/openapi.json and public/openapi-v1.json regenerated.
    • 39 unit tests across 3 files: Seo\CustomMetaTag\WriteServiceTest (11), ValidatorTest (19), WriteDataTest (9). Full Unit suite: 2611 tests, 7751 assertions, all green.
  • [4.99.16] feat(cms): implement Cms/Builder write layer + standalone REST CRUD (closes #122)

    • New Advisable\Rest\Cms\Controllers\Builder controller at /rest/cms/builder with index, show, item, store, update, destroy (and the (\w{2})/... localized variants — 12 routes total). Backed by a fresh Cms\Builder\WriteService, WriteData, Validator, WriteRepository. Read-side already existed but was only reachable as a nested relation under Page/MuiResource; this commit gives it a top-level surface.
    • Cascade-nullify on delete. WriteRepository::delete() mirrors the legacy AdvBlockBuilderModel::deleteWithRelations() — before deleting builder_blocks.id = N, it nullifies 7 FK columns across 6 *_mui tables: blog_mui.builder_block_id, categories_mui.builder_block_id, shop_product_category_mui.builder_block_id, shop_product_mui.builder_block_id, shop_promo_mui.builder_block_id, shop_vendor_mui.builder_block_id, and shop_vendor_mui.exclusive_builder_block_id. The cascade list lives in a private const FK_REFERENCES array so future linkages are one-line additions. The whole sequence is wrapped in a transactional() block in WriteService::delete() — a partial cascade can never leave dangling FKs.
    • Validation. title required on create (rejects null/empty/whitespace), rejected as blank on update (omitting the field is allowed). Other fields (content, html, css, js) are optional on both paths.
    • Read-layer bug-fix in passing. Cms\Builder\Repository\Entity was missing the html @property declaration — the existing Resource exposed $resource->html and was relying on BaseEntity::__get falling back to the raw DB row. Added the docblock so static analysis and IDE autocomplete see the field.
    • DI wiring. Cms\Builder\WriteRepository, Validator, WriteService registered in src/Domains/Cms/container.php. Controller wired in src/Rest/Cms/container.php with service, resourceClass, collectionClass, listRequestClass, writeService args.
    • OpenAPI. New BuilderBlock schema (write payload), BuilderResource/BuilderCollection already existed. public/openapi.json and public/openapi-v1.json regenerated.
    • 30 unit tests across 3 files: Cms\Builder\WriteServiceTest (11), ValidatorTest (12), WriteDataTest (7). Full Unit suite: 2572 tests, 7692 assertions, all green.
  • [4.99.16] feat(product): implement ProductCode write layer and nested product-code creation (closes #125)

    • Standalone REST endpoints for product_codes. New Advisable\Rest\Product\Controllers\ProductCode exposes GET/POST/PUT/DELETE /rest/product/product-code (and the /item and /{id} variants) for direct SKU management. Backed by a fresh ProductCode\WriteService, WriteData, Validator, and WriteRepository. Read-side already existed; this commit only adds the write surface.
    • Auto-generated product_code on create. Ports the legacy generateProductCode() helper to WriteService::generateUniqueCode(): &lt;prefix>&lt;productId>(-&lt;attrValueIds>)(-&lt;2-digit random>). Prefix loaded lazily from registry OTHER.GENERATE_PRODUCT_CODE_PREFIX to keep the constructor DI-safe. Collision retry capped at 50 attempts, then throws RuntimeException so we never spin forever on a saturated keyspace. Caller-provided productCode short-circuits both the lookup and the retry loop.
    • Nested productCodes on the Product create/update payload. Product\WriteService::create() now requires productCodes: [...] (≥1 entry) and orchestrates: parent shop_product insert → for each entry, ProductCode\WriteService::create() (with parent's freshly-inserted product_id injected) → for each attribute_value_id in the entry, ProductCodeAttribute\WriteService::create(). All inside the single parent transaction. Update path accepts an optional productCodes array — entries with id route to ProductCode\WriteService::update(), entries without id insert new codes; codes not listed are left untouched (no implicit deletes). New NestedProductCodeData DTO accepts attributes as either a flat int array or [{attribute_value_id: N}] objects in either snake_case or camelCase.
    • Validation. Product\Validator::validateProductCodesForCreate() rejects empty arrays (clean 422 instead of TypeError when the payload omits the key), forbids id on create entries, and rejects blank-string productCode and negative stock. validateProductCodesForUpdate() only enforces stock >= 0id is allowed; blank-string productCode is delegated to the inner ProductCode\Validator so single-source-of-truth validation stays in the inner write service. ProductCode\Validator::validateForCreate() requires productId; validateForUpdate() rejects blank-string productCode and negative stock.
    • DI wiring. New service definitions in src/Domains/Product/container.php (Validator, WriteData factories, WriteRepository, WriteService) and src/Rest/Product/container.php (ProductCode controller). Routes added to application/config/rest_routes.php.
    • OpenAPI. Two new schemas (ProductCode, ProductCodeNested) registered, attached to the Product schema via $ref. public/openapi.json and public/openapi-v1.json regenerated.
    • 95 new unit tests across 6 files (8 ProductCode\WriteServiceTest, 15 ProductCode\ValidatorTest, 13 ProductCode\WriteDataTest, 16 Product\NestedProductCodeDataTest, 24 Product\ValidatorTest, 27 Product\WriteServiceTest). Code-gen tests use a WriteServiceWithStubbedPrefix subclass to bypass the registry call so unit tests never touch CI's get_instance(). Full Unit suite: 2542 tests, 7643 assertions, all green.
  • [4.99.16] fix(rbac): align REST product/review policy with legacy admin (closes #56)

    • Disjoint role sets enforcing the same business operation. Legacy Adv_product_reviews_admin::__construct required [ADVISABLE, ADMIN, MARKETING]; the REST Product\Controllers\Review policy at application/config/rest_policies.php:312 required [ADMIN, PRODUCTS]. A MARKETING-only operator could moderate via legacy admin but got 403 from REST writes; a PRODUCTS-only operator could moderate via REST but got 401 from legacy. The rest_policies.php file header (lines 39–40) explicitly states "Roles arrays are aligned with legacy admin controller allowRole() checks", and the sibling CustomerReview policy already had this exact alignment with a "(not PRODUCTS)" provenance comment.
    • Fix. Review policy defaults switched to [AUTH_ROLE_ADMIN, AUTH_ROLE_MARKETING] with a provenance comment matching the sibling CustomerReview entry. ADVISABLE auto-bypasses per the role-hierarchy comment at the top of the file and is never listed in roles arrays.
    • Regression guard. 6 new data-provider rows in tests/Unit/Rest/Middleware/PolicyResolverIntegrationTest.php pin the resolved auth + roles for every public Review method (index/show/item read paths, store/update/destroy write paths). Future drift in rest_policies.php will fail CI.
    • Flow doc AD-52 updated — API Reference role columns and Required Roles paragraph reflect the new alignment; Business Rule #5 rewritten from "REST RBAC divergence" to a "RBAC alignment" note pointing at the regression guard.
    • Pre-existing legacy oddity NOT addressed here (separate concern): application/config/admin_menu.php:583 lists PRODUCTS in the parent menu group's roles, so PRODUCTS-only operators see the menu link, click, and get 401. Pre-dates this issue and is flagged in AD-52 Business Rule #11; removing PRODUCTS changes admin sidebar visibility and merits its own deliberate review.
  • [4.99.16] fix(seo): add validation rules to DefaultMetaTag write validator (closes #45)

    • Empty validator silently passed every payload. DefaultMetaTag\Validator::validateForCreate() and validateForUpdate() previously built an empty $errors array and never threw. Payloads missing required fields, with invalid for discriminators, or with strings exceeding the underlying column limits all sailed through. The OpenAPI schema declared for/order as required and gave nullable string fields, but nothing enforced the contract end-to-end.
    • Rules now enforced. for required on create, must be 1 (front) or 2 (other) — the only values rendered by the legacy admin dropdown and the only values stored in defaultmetatags.for historically. order required on create, must be ≥ 0. lang optional but exactly 2 chars when present (column is VARCHAR(2) — longer values would be silently truncated). metaTitle ≤ 255 chars, metaKeywords ≤ 1000, metaDescription ≤ 500. validateForUpdate allows partial payloads but applies the same shape rules to whatever fields are present. All errors are accumulated and thrown together so the client gets every problem in one response.
    • Tests. 23 tests in tests/Unit/Domains/Seo/DefaultMetaTag/ValidatorTest.php cover required-field omissions (single + combined), for enum enforcement, order negative rejection, exact-length lang contract (rejecting 0/1/3/7 chars), per-field length caps with off-by-one boundary tests, multi-error accumulation, valid minimal/full payloads, and the parallel update-mode contract (partial-allowed, shape-checked-when-present). The 4 stale "does not throw" tests from before this commit are replaced.
    • AD-14 Known Issues updated to reflect that the previously-empty validator is no longer a known issue.
  • [4.99.16] fix(upload): normalise basename in collision path when fileExtToLower (closes #214)

    • Mixed-case extension hybridised final filename. AdvUploadFileNameGenerator::__invoke() used case-sensitive str_replace to strip the file extension from the basename when iterating to find a free filename slot. With fileExtToLower=true, getExtension() returned the lowercased extension (e.g. .jpg) but the source filename retained its original casing (e.g. photo.JPG). The case-sensitive miss left the basename unchanged, so the collision loop probed 'photo.JPG1.jpg' etc. — final filenames hybridised the original mixed-case extension in the basename with the lowercased extension at the tail.
    • One-line fix. Replaced str_replace with str_ireplace. The non-collision path was unaffected (it never used the stripped basename). Updated the regression test in tests/Unit/Upload/AdvUploadFileNameGeneratorTest.php to assert the corrected contract ('photo1.jpg' for the photo.JPG + fileExtToLower=true collision case) and dropped the _BUG suffix. Surfaced while writing the test coverage tracked under #39.
  • [4.99.16] test(upload): add 43 unit tests for the cross-cutting upload pipeline (closes #39)

    • Zero coverage on the upload primitives. HandlesUploadActions, UploadService, AdvUploadValidator, and AdvUploadFileNameGenerator had no unit tests despite sitting on the request path of every multi-image admin entity (vendors, badges, sliders, banners, etc.). Two known bugs in AdvUploadValidator alone (isImage always returning false, documented in SY-25 Known Issues #3) went undetected for that exact reason.
    • New tests/Unit/Upload/AdvUploadFileNameGeneratorTest (8 tests: overwrite, encrypt, increment-on-collision, budget exhaustion, missing extension, plus a _BUG-suffixed test pinning the collision-path mixed-case quirk fixed in #214); AdvUploadValidatorTest (18 tests including a _BUG-suffixed test pinning SY-25 Known Issue #3, MIME alias normalisation, setter clamping, set_allowed_types parsing variants, initialize quirks, error rendering, validate no-file paths); UploadServiceTest (3 tests for early-skip paths only — is_uploaded_file() blocks the happy path, covered indirectly by per-domain controller tests); HandlesUploadActionsTest (14 tests exercising trait pure-logic methods via an anonymous test harness — mapUploadKeyToDataKey identity default, parseInput content-type branching, processFileUploads merge semantics, applyFieldProtection slug/URL stripping with and without AUTH_ROLE_ADVISABLE, translation-row protection).
    • _BUG-suffixed tests pin existing buggy behaviour rather than the desired contract — when those bugs are fixed, the tests will fail and need to be rewritten. This is intentional: the tests should regress with the bug, not silently mask it. SY-25 Tests section enumerates the new coverage.
  • [4.99.16] refactor(meta-capi): reuse injected logger in MetaClient instantiation

    • Service-locator inside the constructor when the dependency was already injected. FacebookConversionService::__construct already received a NamedLoggerInterface; the MetaClient construction at line 99 was calling di()->get(NamedLoggerInterface::class) again to fetch the same singleton. Replaced logger: di()->get(...) with logger: $this->logger. Runtime channel string is unchanged: MetaClient::__construct calls ->withName('meta-conversions-api') on whatever logger it receives, and withName overwrites rather than accumulating. IN-07 Known Issues entry cleared.
  • [4.99.15] feat(logger): full DI migration to PSR-3, single-line emission, channel-tagging via withName() (closes #200)

    • Single PSR-3 logger registered in DI. Psr\Log\LoggerInterface is now autowire-discoverable via application/config/container/logger.php. Advisable\Logger\NamedLoggerInterface (a PSR-3 sub-interface that adds withName(string): static) is aliased to the same instance for type-safe channel-tagging at consumer call sites. Both resolve to a Monolog\Logger subclass — Advisable\Logger\AppLogger — built by Advisable\Logger\AppLoggerFactory::create().
    • CI3 error handlers replaced via PHP function precedence. application/helpers/log_helper.php defines log_message, _error_handler, _exception_handler, and _shutdown_handler BEFORE system/core/CodeIgniter.php boots (require_once at the top of public/index.php). CI3's function_exists() guards in Common.php skip its own definitions; set_error_handler('_error_handler') etc. now register OUR functions. Pre-DI calls fall back to error_log() so very-early bootstrap log messages are not silently dropped.
    • Single-channel monolog.php. Five legacy channels (codeigniter, advisable-ai, project-agora, deferred-task) collapsed into one 'app' channel; per-component tagging is now via $logger->withName('component') so %channel% carries openai, transporter-elta, manago, etc. instead of routing into separate log files.
    • AppIntrospectionProcessor subclasses Monolog's processor and adds the four CI3-override helper functions plus the private dispatcher to its skip set — every record now reports the real caller (controller / service / model) instead of CoreLogger.php:93.
    • ChannelThresholdHandler decorator wraps every handler in the DI-built logger. Per-channel thresholds in monolog.php's channelThresholds map raise or lower the effective threshold per channel; runtime override via APP_LOG_CHANNEL_OVERRIDES JSON env var lets you bump verbosity for a debug session without a redeploy.
    • All 44 new CodeIgniterLogger() callers migrated. 38 modern src/ classes use constructor injection of NamedLoggerInterface (now optional with a di() fallback for legacy direct-instantiation callers — see the bullet below); 6 legacy CI3 callers use a lazy di() accessor pattern. application/libraries/CoreLogger.php and application/libraries/CodeIgniterLogger.php deleted; application/core/Log.php is a tripwire stub that throws on construction so any code reaching for the legacy class fails loudly.
    • Single-line guarantee. _log_helper_build_formatter() is the single source of truth for the file-mode LineFormatterallowInlineLineBreaks=false collapses embedded newlines (including stack traces) to a single space. K8s mode uses JsonFormatter with appendNewline=true and includeStacktraces=true so each record is exactly one physical JSON-encoded line.
    • Three worked migrations (Adv_checkout 23 calls, PublicOrders 15 calls, Moosend 3 calls) document the lazy-accessor and constructor-injection patterns for the remaining ~180 log_message() callers, which continue to route through the global helper override and tag with the default channel.
    • Flow doc: docs/flows/system/SY-32-logging.md.
    • Migration of remaining log_message() callers is incremental and opportunistic — touch them when you're already in the file. application/helpers/farmakon_helper.php (11 calls inside global helper functions, no class to inject into) is left as-is.
  • [4.99.15] feat(logger): K8s observability — split stdout/stderr by level + JSON formatter (closes #200 follow-ups Options 1+2)

    • stdout/stderr level split (12-factor). In STDOUT mode each record routes to exactly one stream: Debug..Warning → stdout, Error..Emergency → stderr. Pipeline is FilterHandler(level range) → ChannelThresholdHandler(channel gate) → StreamHandler so a record outside the level range bubbles to the next pipeline without paying the per-channel-gate cost. Eliminates the previous behaviour where errors were duplicated across both streams.
    • JSON formatter for K8s ingestion. STDOUT mode now uses Monolog\Formatter\JsonFormatter so Loki/Grafana/Datadog/CloudWatch can index channel, level, message, context as structured fields instead of regex-parsing line shapes. Stack traces in context.exception are flattened into the same JSON object — single-line guarantee preserved structurally (newlines inside string values are JSON-escaped to \n).
    • File mode unchanged. STANDARD mode (MONOLOG_CHANNEL_CONFIGURATION unset) keeps LineFormatter for human-readable tail -f during development.
    • monolog.php's handlers list became a data-driven array of dicts (type, format, minLevel, maxLevel, lineFormat) so the factory can build any combination of streams/files with any formatter.
    • 6 new unit tests in tests/Unit/Logger/AppLoggerFactoryTest.php cover the level-split routing per pipeline, the JsonFormatter exception serialization, file-mode preservation, and the wrapping order.
  • [4.99.15] fix(logger): DI compile re-entrance + runtime hardening

    • Container::$bootReady gate. During CI3's controller-class autoload cascade, application/third_party/MX/Base.php runs new CI; at file load → CI_Controller::__constructLoader::initialize() → autoloads database library → driver constructor calls log_message() → routes to di() → would trigger setUpContainer() for the FIRST time WHILE the outer cascade is still mid-include. The compile pass then reflects on REST controllers sharing the same parent chain (HandlesRestfulActions → Base_c → ...), PHP autoloads them, hits the same files mid-include, and Symfony surfaces Invalid service "Country": class "Base_c" not found. Fix: Container::getContainer() returns an empty stub ContainerInterface until Container::markBootReady() is called from the new pre_controller hook in application/config/hooks.php. CI3 fires that hook after the user controller is fully autoloaded but before line 291's first explicit di() call, so the real compile runs only after every parent class is declared. $building remains as a secondary guard for in-compile re-entrance from service constructors.
    • %-escape in application/config/container/logger.php. Symfony's parameter bag treats %foo% tokens in service arguments as parameter references; our lineFormat strings contain %level_name%, %datetime%, %channel%. Recursively double the % characters before passing the config as a service argument; Symfony reduces %% back to % at runtime before the factory sees it.
    • AppLogger for nominal NamedLoggerInterface typing. Monolog\Logger has a matching withName() method but doesn't declare implements NamedLoggerInterface, so passing the DI-resolved instance to a consumer constructor type-hinted as NamedLoggerInterface failed PHP's nominal type check (FacebookConversionService::__construct(), etc.). Added Advisable\Logger\AppLogger — a thin Monolog\Logger subclass that declares the interface and overrides withName(): static for the covariant return type, with no behaviour change.
    • Optional-logger fallback in 38 src/ classes. Commit befd8d27d (already on the branch) had migrated 38 classes from self-resolved new CodeIgniterLogger() to constructor-injected NamedLoggerInterface but missed dozens of legacy callers in application/ and ecommercen/ (vouchers, jobs, Adv_orders_admin, smart-points, AdvTransporters, ChannelFactory, etc.) that instantiate these classes with their old 1-arg signature — surfaced as ArgumentCountError at runtime. These classes can't be moved to autowired DI services because their config is per-call runtime data (transporter settings fetched per order, payment-gateway config per checkout, etc.). Pattern applied uniformly: NamedLoggerInterface $logger?NamedLoggerInterface $logger = null; $logger->withName(...)($logger ?? di()->get(NamedLoggerInterface::class))->withName(...). Same semantic effect as the deleted CodeIgniterLogger shim, just sourcing the logger from the DI container instead of a wrapper class. Affected: 16 transporters (Acs, AcsSoap, Asap, BoxNow, CyprusPost, DailyCourier, Dhl, EasyMail, Elta, Fis, Geniki, GenikiV2, QualcoProvider, Skroutz, Speedex, Taxydema); 7 payment gateways (Iris, Jcc, Klarna, NBGPayHelper, PayPalRestApi, ApcoPay, VivaWallet/Base); 5 AI/cloud/data (OpenAI, AdvCloudflare, Marketdata, Manago, Apifon); 4 marketing/messaging (MetaClient, BulkerSms, OmniMessaging, YubotoOmni); 3 public/marketplace (Public2P, ShopflixOrders, SkroutzOrders); 2 video (BunnyStream, VideoManager); 1 pharmacy (Europharmacy/Base).
    • Test infrastructure. tests/Integration/Logger enrolled in the Integration suite in phpunit.xml.dist (was silently skipped). tests/bootstrap_ci.php now requires log_helper.php BEFORE CI3's Common.php so our log_message override wins the function_exists guard race — without this, CI3's own log_message would call load_class('Log', 'core') and hit the throwing CI_Log tripwire on every CI bootstrap. LoggerDiBindingTest::setUpBeforeClass snapshots Container::$bootReady and flips it to true for the test class (pre_controller hook never fires under the test bootstrap), then restores it on teardown.
    • Check for overrides: if any client repo overrides application/config/monolog.php, application/config/hooks.php, application/config/container/logger.php, application/core/Log.php, or tests/bootstrap_ci.php, re-apply the equivalent changes — particularly the pre_controller hook calling Container::markBootReady(), otherwise di() will return the empty stub for the entire request lifecycle and every log line falls back to error_log().
  • [4.99.15] feat(rest): per-relation language scoping for translations (closes Advisable-com/ecommercen#213)

    • ?with=translations always loaded every language stored in *_mui tables. New bracketed-CSV grammar lets clients scope the translation collection per request: ?with=translations[el] (single locale), ?with=translations[el,en] (multi), ?with=translations[el],parent (mixed — , outside brackets stays the relation separator), ?with=children.translations[el] (nested, leaf scoped). Empty params (?with=translations or ?with=translations[]) preserves the existing all-locales behaviour. [, ], and , all pass through curl, browsers, and PHP CGI parsing without URL encoding — chosen over a colon+pipe form for consistency with the existing filter syntax (?filter[id]=1,2,3).
    • Generic mechanism: Relation::$scopableColumn: ?string — opt-in declaration on the Relation DTO. All 38 translation relations declare scopableColumn: 'lang' in their RepositoryConfigurator. The OneToMany loader (and the BelongsTo / HasOne / ManyToMany variants) emit WHERE col IN (...) only when both column and request params are present. Scalar by design (singular) — if multi-axis scoping ever lands, switch to plural array via mechanical 38-file sweep.
    • Internal callers (jobs, services, legacy CI controllers) get the same scoping by passing relationParams directly into WithRelations or BaseRepository::loadRelations() — no HTTP round-trip needed.
    • Plumbing: new WithParser::parse() static helper with bracket-aware top-level splitter — , only separates relations at depth 0, so commas inside [...] are preserved as value separators without ambiguity. Produces both the existing nested relations tree AND a flat relationParams map keyed by full dot-path. WithRelations spec gains a third constructor arg; RelationLoaderInterface::load() gains two optional trailing args ($relationParams, $params); BaseRepository::loadRelations() and loadRelation() thread the params through to the loaders. ListRequest carries $relationParams; GenerateListRequest populates it; HandlesRestfulActions::show() threads it into Service::get().
    • Interface signature shift: ReadService::get() gains a fourth array $relationParams = [] arg. All 110 service implementations updated mechanically. Product\Variation\Repository\Repository ships an anonymous OneToManyLoader override — also updated to match the new interface signature.
    • Resource layer unchanged — collections render whatever the loader returned. Single-locale scoping arrives as a one-element array (no shape change for consistency).
    • 18 unit tests for WithParser (parser grammar — single value, multi value, mixed with no-param siblings, deep nesting, whitespace trim, empty brackets, lone brackets, dot-path key convention). 5 unit tests for OneToManyLoader scope application path. 5 DB-backed integration tests on Cms\Page\ServiceTest (single locale, multi locale, unknown locale → empty, no-params baseline, end-to-end via ListRequest::$relationParams). Full suite: 5,588 unit/integration + 2,907 database = 8,495 tests, 0 failures.
    • Requires cache/container.php deletion on first deploy after merge — the compiled DI container references the old ReadService::get() 3-arg signature; first request rebuilds it cleanly. No DB migration. No asset rebuild.
    • Verify on merge:
      • On any storefront with *_mui data, hit a translation-bearing endpoint with ?with=translations[el] and confirm the response has exactly one translation per master row with lang: el. Drop the bracket and confirm all locales return.
      • Pagination total should be identical between ?with=translations and ?with=translations[el] for the same filter — only the translation collection is scoped, master rows are untouched.
      • Spot-check nested case: any endpoint that exposes children or parent with translations — ?with=children.translations[el] should return scoped child translations, not all locales of children.
      • Cross-check that ?with=translations:el (the never-shipped colon syntax) silently drops the translations relation — the response should NOT contain a translations key. This is by design: parser does not recognise : and the configurator has no relation literally named translations:el.
      • Client repo audit: in each active client repo run grep -r "function get(int|string \$id, array \$relations" custom/Domains/ — no overrides should still be using the old 3-arg signature. Any that do will fatal on autoload until updated to 4 args.
      • vendor/bin/phpunit tests/Unit/Domains/Support/Request tests/Unit/Domains/Support/Repository/RelationLoading runs 46/46 green; vendor/bin/phpunit --testsuite Database tests/Integration/Domains/Cms/Page/ServiceTest.php runs 19/19 green.
  • [4.99.15] fix(patches): reconnect Phinx PDO after long-running patcher subprocesses

    • patches/AbstractPatcher::up() shells out to a subprocess for the patcher body. Phinx holds its own PDO connection (separate from CodeIgniter's $this->ci->db) which sits idle for the entire subprocess. On servers with a short MySQL wait_timeout, the socket is killed before Phinx writes the phinxlog bookkeeping row, surfacing as PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away at the end of an otherwise successful patcher.
    • Fix: AbstractPatcher::up() now calls $adapter->disconnect() then $adapter->connect() on $this->getAdapter() after the subprocess returns successfully. AdapterWrapper propagates both calls down to the underlying MysqlAdapter, guaranteeing a fresh socket for the immediately-following migrated() write.
    • Manual recovery for environments that already hit this bug (data applied but phinxlog row missing — php migrator.php status keeps showing the migration as down): INSERT INTO phinxlog (version, migration_name, start_time, end_time, breakpoint) VALUES (&lt;version>, '&lt;MigrationName>', NOW(), NOW(), 0);
  • [4.99.15] fix(jobs): scope session_gc to the configured save_path on files driver (#210)

    • AdvClearExpiredSessions::executeCommand() previously called session_gc() after only setting session.gc_maxlifetime — that routes to PHP's native file save_handler, which hardcodes the sess_* filename prefix. CI3 writes session files as &lt;cookie_name>&lt;sid> (e.g. ci_session1a2b...), so session_gc() matched zero files in production and the cron silently did nothing.
    • Replaced with find -maxdepth 1 -type f -name '&lt;cookie_name>*' -mmin +N -delete invoked via exec(). Native filesystem traversal, prefix-aware, runs in the configured sess_save_path only.
    • realpath() canonicalization on sess_save_path before invoking find: collapses the .. from the default FCPATH . '../sessions' config and resolves symlinks so log lines are clean and behavior is predictable across deploy tooling.
    • Driver short-circuit: non-files drivers (redis, database) still skip — they manage their own TTL. Windows hosts skip with an info log line (no find fallback). Empty path / missing dir / empty cookie_name / non-positive expiration each log an actionable error and abort.
    • 7 unit + integration tests cover driver short-circuit, all four config validation paths, the prefix-aware happy path (asserts expired prefix-matching files are deleted, prefix-mismatched files are retained), and Windows skip.
    • Check for overrides: ecommercen/job/libraries/AdvClearExpiredSessions.php.
  • [4.99.15] fix(mailer): correct log_message() context argument type in Mailer (#fix/mailer-log-message-context-type)

    • Four log_message('error', $msg, false) calls in ecommercen/core/Mailer.php (lines 288, 332, 336, 340) passed a legacy bool as the third argument. The current log_message() signature declares that parameter as array $context = [], and CoreLogger::error() is strictly typed — passing false caused a TypeError that aborted the request. The symptom was most visible during the checkout order-complete flow when an SMTP send failed.
    • Fix: removed the trailing , false from all four call sites. No other callers in the codebase pass a non-array third argument.
  • [4.99.15] refactor(test): unify PHPUnit config into single phpunit.xml.dist

    • Replaces the historical phpunit.xml.dist (excludes database group) + phpunit-database.xml.dist pair with one config defining four named testsuites: Unit, Integration, Database, Legacy. composer test now runs the full ~5,400-test suite; composer test:fast is the no-DB inner loop. Deletes phpunit-database.xml.dist.
    • New composer shortcuts: test:database, test:legacy, test:fast.
    • docs/guides/Testing.md rewritten to document the unified layout and per-testsuite placement rules.
    • No production code change. No REST API contract change.
  • [4.99.15] fix(gifts,orders,reviews): fix three bugs exposed by the unified test suite (#207, #204, #174)

    • #207 — Gifts: null final_price treated as 0.0 in Rule 13 cheapest-selector. getCheapestRequirementProductInCart cast null to 0.0 via (float), causing a product with missing pricing data to sort as the cheapest and silently get picked as the free gift. The product is now excluded when final_price === null.
    • #204 — Orders: Rule 13 cart-trim skipped when gift and cart use different product codes. adjustCartForRule13Gifts matched cart rows by product_code_id alone. When the same product was allocated via a different variant code (common with configurable products), the row was never found and the customer received both the paid unit and the free gift. Now resolves all involved codes to product IDs in one query (new Adv_gifts_model::getProductIdsByCodeIds) and falls back to product-level matching; the exact-code path still wins when both share the underlying product.
    • #174 — Reviews: RatingAggregator never fired from WriteService. Product\Review\WriteService read $entity->productId (camelCase) but the Entity column is product_id (snake_case). BaseEntity::__get returned null, so shop_product.average_rating and shop_product.review_count were never updated after REST-layer review writes.
    • Also un-skips the non-Rule-13 gift_choices counterpart test (adds a stocked product_codes fixture) and removes a false-alarm skip on CartServiceTest::testClaimCartMergesIntoExisting (no CI bootstrap required — all dependencies are mocked).
    • Closes #207, closes #204, closes #174.
  • [4.99.15] test(gifts): add Rule 13 (cheapest-required-free) test coverage (#208, #209)

    • Establishes the legacy contract for Rule 13 gift logic so the queued bug fixes (#203–#207) and the future REST port (#85) have a known-correct reference.
    • Adds 17 unit tests in tests/Legacy/Eshop/AdvGiftsModelTest.php covering ruleProductsCheapestFreeValidator, getCheapestRequirementProductInCart (greedy allocation, tie-break, cart-qty cap, orphan handling, giftsToGive=0 early return), and filterApplicableGiftRules Rule 13 exemption.
    • Adds 12 unit tests in new file tests/Legacy/Eshop/AdvOrderModelRule13Test.php for getRule13GiftAdjustmentsForProducts and adjustCartForRule13Gifts — uses invokeArgs to handle the by-reference &$cart argument.
    • Adds the DB integration test for getActiveGiftRules Rule 13 bypass in new file tests/Integration/Legacy/Eshop/AdvGiftsModelTest.php. New directory added to the Database testsuite.
    • Test-only — no production code change. No REST API contract change.
    • Closes #208, closes #209.
  • [4.99.15] fix(specs): single-pass escape pipeline in LIKE-clause specs (#202)

    • The fix shape introduced in #199/#201 wrapped escape_like_str inside escape, double-running real_escape_string over user input. Harmless for plain text but values containing \ were over-escaped — foo\bar searched for foo\\bar in the DB.
    • Replaced with manual single-quote wrapping: "'%" . escape_like_str($value) . "%'" — single pass, correct backslash handling.
    • Applied uniformly to FilterByBarcode and all 17 FilterByTranslation clones.
    • New backslash regression test on Product translations.
    • Closes #202.
  • [4.99.15] fix(security): escape LIKE wildcards in FilterByTranslation across all domains (#201)

    • 17 FilterByTranslation::apply() clones (Product, ProductList, Cms*, Country*, Map*, Slider*, Ai\Position) used $db->escape('%' . $value . '%') which protected against SQL injection but did NOT escape LIKE metacharacters (% and _). A request like ?filter[name.en]=% matched every row in the corresponding *_mui table.
    • Fix: wrap user input in $db->escape_like_str() and append ESCAPE '!' to the LIKE clause so % and _ are treated as literal characters.
    • 3 integration tests (ServiceTranslationFilterEscapeTest) verify normal partial match still works AND that %/_ user input no longer matches every row.
    • Same fix shape that landed in FilterByBarcode for #199.
    • Closes #201.
  • [4.99.15] fix(product): route barcode filter through FilterByBarcode subquery (#199)

    • FilterByBarcode specification: subquery on shop_product_barcodes, OR-joined LIKE for multiple values, sentinel WHERE id = -1 on empty input
    • Product\Service intercepts barcode key before the default Filter branch
    • Closes #199 (latent bug — filter never worked because the existing Filter spec emitted SQL against an unjoined table)
    • Note: LIKE wildcards (%, _) in user input are now escaped with ESCAPE '!' to prevent unintended match-everything behavior
  • [4.99.15] feat(product): truly recursive category-products filter in Domain/REST

    • Category\Service::getDescendantIds(int $rootId, bool $includeRoot = true): array — cycle-safe BFS, prunes unpublished branches, no depth cap (replaces the legacy 5-level getCatProductsBase for modern callers)
    • Product\ListRequest: new filters categoryId (direct M2M) and categoryDescendantId (recursive)
    • Product\Service injects Category\Service and intercepts both keys via the new FilterByCategory subquery specification (avoids JOIN-by-relation limitation in the existing infrastructure)
    • FilterRequest gains an optional key field carrying the URL filter name (enables Service-level discrimination of two filter keys mapping to the same SQL field)
    • REST API v1.3 changelog updated; OpenAPI x-filters extended on the products controller
  • [4.99.15] fix(rbac): align AdvPlusAlgoPrioritySettings controller to menu permissions (#75)

    • Add AUTH_ROLE_DEVELOPER to controller RBAC gate in AdvPlusAlgoPrioritySettings::__construct()
    • Update flow doc AD-56 to reflect aligned permissions (remove Known Issue #1, update RBAC table and code example)
    • Closes #75
  • [4.99.15] fix(api-docs): move changelog panel inside version bar so it renders visibly (#197)

    • Append #changelog-panel to #version-bar in renderVersionBar() after the panel reference is acquired
    • Toggle logic, content rendering, and click-outside-to-close behaviour unchanged
    • Closes #197
  • [4.99.15] fix(cart): show correct toast when decreasing cart quantity

    • assets/vue/store/actions.js reportCartEvents keyed the add/remove flag on item.quantity === 0 only, so any non-zero new quantity — including a decrease (e.g. 3 → 2) — fell into the else branch and set response.addToCart = true. setCartData then rendered the green cart.product_added toast on a quantity decrease.
    • Fix: detect direction by comparing the new quantity to the previous cart quantity. Any decrease (including a full removal to 0) flags response.removeFromCart; everything else flags response.addToCart.
    • Previous-cart lookup uses Number() coercion on productCodeId to tolerate the string-vs-number mismatch between server-rendered initial cart state and user-action payloads — same convention already used in getters.cartProductCodeQuantity.
    • Analytics tag dispatch (tag.removeFromCart / tag.addToCart and the GA4 variants) is left untouched — still keyed on quantity === 0. Toast styling is left untouched (success/green for both add and remove); only the message text was wrong.
    • Reported by client v4-evripidis (commit e85839058).
    • Requires npm run production (or npm run all-production) — the file is bundled into public/ui/main/. Asset rebuild + deploy needed for the fix to reach storefronts.
    • Verify on merge:
      • On a cart with quantity 3 of any product, click the minus button to drop to 2 → toast reads cart.product_removed, not cart.product_added.
      • Drop quantity from 1 to 0 (full remove) → toast still reads cart.product_removed (unchanged behavior).
      • Increase quantity from 2 to 3 → toast reads cart.product_added (unchanged behavior).
      • Add a brand-new product to cart (no previous entry) → toast reads cart.product_added.
      • Spot-check dataLayer in DevTools: removeFromCart / add_to_cart_GA4 events still fire as before — only the toast message changed.
  • [4.99.14] fix(smartpoint): stop leaking message listeners on every BoxNow / Skroutz widget open

    • assets/main/vue/SmartPoint/Widgets/SmartPointWidget.js was re-injecting the BoxNow / Skroutz CDN <script> on every initializeWidgetBoxNow / initializeWidgetSkroutz call. Removing the <script> element does NOT undo window.addEventListener('message', …) registered by the CDN code while it ran — those listeners stayed alive on window. Each open accumulated one more listener, so a single locker pick fired afterSelect once per accumulated listener: 1st open → 1 toast, 2nd → 2 toasts, …, Nth → N toasts (plus N setSelectedSmartPoint dispatches and N WidgetClose emits).
    • Bug pattern was symmetric in BoxNow (live, since 4.99.3, commit a8529a1ae) and Skroutz (latent — only triggers if the user opens Skroutz multiple times in one session).
    • Fix: skip the script re-injection when the CDN is already loaded. BoxNow gates by country (window.currentCountry === this.selectedCountry) since the URL differs per country (GR / CY / BG); country switch still triggers a reload. Skroutz gates by mere presence since it has only one URL. Re-shows are handled by the existing ${class_name}_WidgetOpen emit in SmartPointMixin.initializeWidgetForTransporter, which BoxNowWidget.vue / SkroutzLastMileWidget.vue route through clickOpenWidgetElement() (clicks the hidden .boxnow-map-widget-button / .skroutz-map-widget-button element). window._bn_map_widget_config / window._skroutzPointsMapWidgetOptions are still overwritten on every call so afterSelect always closes over the latest widget instance.
    • Requires npm run production (or npm run all-production) — the file is bundled into public/ui/main/. Asset rebuild + deploy needed for the fix to reach storefronts.
    • Verify on merge:
      • Open BoxNow widget 5 times in a row, pick a locker on the last open → exactly 1 toast (was: 5 toasts before fix).
      • Same flow for Skroutz on a deployment that has Skroutz lockers → exactly 1 toast.
      • Country switch flow: load checkout with country=GR, open BoxNow, switch to country=CY, open BoxNow → script reloads and widget still works (verify in DevTools that <script id="boxnow-widget-script"> src ends in boxnow.cy).
      • Mixed flow: open BoxNow → close → open Skroutz → close → open BoxNow again → only one of each script tag in &lt;head>, no listener accumulation.
      • DevTools spot-check: in the Memory or Performance panel, count message listeners on window before and after multiple opens — should stay flat.
  • [4.99.14] feat(product): expose averageRating and reviewCount on ProductResource (closes #174)

    • Storefront product list responses now include cached aggregate fields, so frontends can render star ratings on product cards without per-product GET /rest/product/review?filter[productId]=X round trips.
    • Two new columns on shop_product: average_rating (DECIMAL(3,2)) and review_count (INT(10) UNSIGNED), both with default 0 and indexed for sort/filter.
    • New Advisable\Domains\Product\Review\RatingAggregator::recomputeForProduct(int $productId) reads AVG(star_points) + COUNT(*) from shop_product_reviews WHERE active=1 and writes the result back to shop_product. Average is rounded to 2 decimals.
    • Recompute hooks: modern Product\Review\WriteService calls the aggregator on create / update / delete (handling product-id reassignment by recomputing both the old and the new product). Legacy Adv_product_reviews_model calls it from addReview, updateReview, setReviewStatus, and setReviewsStatusBatch — covering customer review submission and admin moderation.
    • One-time backfill migration 20260428120100_backfill_product_review_aggregates (Phinx wrapper) + Patches\BackfillProductReviewAggregates populates the new columns for existing reviews via a single UPDATE ... LEFT JOIN query.
    • Advisable\Domains\Product\Product\ListRequest exposes averageRating and reviewCount as both filterable and sortable; storefront can request ?sort=-averageRating for "highest-rated first" cards.
    • OpenAPI ProductResource schema updated; averageRating and reviewCount added to the required fields list.
    • 12 new integration tests in tests/Integration/Domains/Product/Review/RatingAggregatorTest.php covering recompute correctness (zero, one, many, exclude inactive, two-decimal rounding, scope-isolated) plus the create / update / activate / delete / product-id-change WriteService hooks.
    • Requires php migrator.php migrate for both the schema change (column add) and the backfill patcher.
    • Verify on merge:
      • Backfill landed: pick a few products with active reviews and confirm shop_product.review_count matches SELECT COUNT(*) FROM shop_product_reviews WHERE product_id = X AND active = 1 and average_rating matches the equivalent AVG(star_points).
      • Modern write path: POST /rest/product/review with active=1 then GET /rest/product/product/{id}reviewCount increments, averageRating shifts.
      • Legacy admin moderation: flip a pending review to approved via Adv_product_reviews_admin UI; confirm shop_product cached values change.
      • Storefront response shape: list endpoint includes averageRating and reviewCount even for products with zero reviews (returned as 0 and 0, never null).
      • Sort works: GET /rest/product/product?sort=-averageRating returns highest-rated products first.
      • Run vendor/bin/phpunit tests/Integration/Domains/Product/Review/RatingAggregatorTest.php against the migrated test DB — all 12 tests green.
  • [4.99.14] fix(cart): read cart subtotal from shop_product.price (fixes #27)

    • calculate() now prefers the already-hydrated productCode.product
    • getItemPrice() eager-loads ['product' => []] and reads the nested
    • Unit tests updated to reflect the real relation graph and cover the
    • CF-05 Live Testing Notes rewritten — the previous wording framed a
  • [4.99.14] fix(order): populate VAT REST Validator with real rules (fixes #7)

    • validateForCreate() requires value, enforces the range [0, 100],
    • validateForUpdate() now takes an int|string|null $id so the
    • WriteRepository::existsWithValue(float $value, ?int $excludeId)
    • WriteService::update() passes the id through to the validator.
    • Unit test suite expanded from 3 to 14 cases covering boundaries,
  • [4.99.14] fix(rest): require ADMIN/PRODUCTS role for VAT write endpoints (fixes #10)

    • application/config/rest_policies.php: Vat::class defaults now
    • AUTH_ROLE_ADVISABLE continues to bypass role checks via the
  • [4.99.14] fix(rest): batch of REST + Domain fixes (cart, webhook, uploads, currency, schemas)

    • Cart: cart endpoints now use quantity field exclusively (qty alias removed, closes #108)
    • Cart: cart_contents snapshot now persisted on REST order placement (closes #111)
    • Currency: currency id and rate are snapshotted at order placement so historical orders preserve their original FX context (closes #169)
    • Webhook: remove dead OrderService wiring from REST Webhook controller (closes #114, #81)
    • Uploads: remove duplicate $_FILES guard in HandlesUploadActions (closes #110)
    • Collections: all REST collection responses are now wrapped in the pagination envelope (closes #167)
    • Product: negativeStock field exposed on the public product API (closes #171)
    • ProductCategory: parent and children relations now available (closes #173)
    • Domain: BaseWriteRepository::insert() now handles the all-null edge case — rows with only nullable columns can be created without supplying any field values; MySQL applies column DEFAULTs correctly
  • [4.99.14] feat(rest): audit logging for authenticated REST writes (closes #188)

    • New Advisable\Rest\Middleware\AuditMiddleware runs last in the REST middleware pipeline (RouterDispatcher). On authenticated POST/PUT/DELETE/PATCH it logs to user_audit_logs — the same table the legacy admin audit trail uses. Reads (GET/HEAD) and unauthenticated requests are skipped to avoid table bloat.
    • AdvAuditModel::storeRestLog($userData) accepts pre-built user data from the middleware (numeric userIduser_id column, string userIdusername column for config_auth users). Roles are resolved from the JWT to a CSV of role names via auth/roles_model, matching the admin audit format.
    • provider column gains two new values: REST_BACKEND (admin JWT) and REST_CUSTOMER (customer JWT) — alongside the existing DATABASE / CONFIG for legacy admin.
    • Request bodies are captured even for application/json writes via a new getRestStates() that falls back to $ci->input->inputStream() when $_POST is empty.
    • Logging failures are swallowed and logged via log_message('error', ...) — audit issues never break a production write.
    • 17 new unit tests in tests/Unit/Rest/Middleware/AuditMiddlewareTest.php.
    • Flow doc AD-32 updated with the REST entry path.
  • [4.99.14] fix(views): move favicon HTML snippet to storage() abstraction

    • application/views/main.php previously read the generated favicon meta+link snippet from local disk via $this->load->view('../../storage/views/favicons', '', true). On S3/SFTP-backed deployments and on any pod that lost its storage/ volume between deploys, the file was missing and CI3 raised "Unable to load the requested file", crashing every page rendered through the storefront layout (most visibly the admin block builder, which renders through Front_c::main.php for live-preview parity).
    • The snippet now lives at files/favicons/view.html under storage(), colocated with the favicon images (favicon-*.png, manifest.json) that are already stored there. main.php reads it via storage()->exists() + storage()->get(), so missing files never throw.
    • AdvFavicons::save() now writes the generated HTML directly via storage()->put(). FaviconHtmlGenerator::saveView() was the only filesystem coupling left in the class and has been removed — the class is now a pure HTML producer.
    • Same-bucket fix for manifest.json: FaviconImageGenerator was writing it via raw fopen()/fwrite(), also bypassing storage(). Replaced with storage()->put() so the manifest survives on S3 alongside the PNG icons.
    • One-time data migration (patches/MigrateFaviconViewToStorage.php + database/migrations/20260428100000_migrate_favicon_view_to_storage.php) copies any existing legacy storage/views/favicons.php content into files/favicons/view.html. Idempotent — skips when source is missing/empty or destination is already populated. New installs need no action.
    • The legacy CreateFaviconHtml patcher is now obsolete (its sole purpose was to suppress the load->view() crash) but is left intact — Phinx history is immutable.
    • Requires php migrator.php migrate for clients with an existing local favicon snippet.
  • [4.99.14] fix(rest): drop nonexistent newsletter field from customer register

    • POST /rest/auth/customer/register accepted an optional newsletter boolean and pushed 'newsletter' => 0|1 into shop_customer via add_customer_front(). The shop_customer table has no newsletter column (verified in database/initial/initial.sql and across all migrations), so every call hit Unknown column 'newsletter' in 'field list' and registration silently failed.
    • Legacy Adv_customer::register() never persisted newsletter consent on the customer row either — that flow lives in checkout (Adv_order::register_newsletter) and dedicated provider endpoints (Manago/Moosend/Mailchimp). Aligns REST with legacy semantics: marketing-list subscription is the frontend's responsibility, not the auth endpoint's.
    • Removed newsletter from the OpenAPI request schema, the controller body, and regenerated openapi.json / openapi-v1.json.
  • [4.99.13] fix(jobs): forward child-process stdio through AdvJobManager (#195 follow-up)

    • AdvJobManager::index() is the outer K8s-CronJob dispatcher that spawns per-queue children via Process::start(). It was calling start() without a callback, so child stdout/stderr was captured in memory and silently discarded — the AdvJobQueueManager forwarding fix from 4.99.12 was effectively nullified at the outer dispatch layer.
    • Fix: instantiate StreamingOutputForwarder once before the start-loop and pass it to each $process->start($forwardStdio). All queue-child output now flows through to the parent process in real time and is visible in kubectl logs.
    • Audit confirmed AdvJobManager and AdvJobQueueManager are the only Process::*() call sites in ecommercen/job/ — no remaining forwarding gaps in the job-dispatch path.
    • Two new tests added to tests/Unit/Jobs/Process/StreamingOutputForwarderTest.php: async start()/wait() path and multiple parallel processes sharing one forwarder, matching AdvJobManager's real runtime shape.
    • The temporary AdvJobManager.php runtime overlay on seajets cron pods (mounted via ConfigMap + init container) can be removed once this image is deployed.
  • [4.99.12] fix(jobs): forward child-process stdio through AdvJobQueueManager (fixes #195)

    • AdvJobQueueManager called Process::run() and Process::restart() without a callback, so all child-process stdout/stderr was captured internally and discarded when the Process object was garbage-collected. Failed jobs appeared as "Error" in Job Monitor but left no trace in container logs (kubectl logs), making production triage impossible on container runtimes.
    • New invokable class Advisable\Jobs\Process\StreamingOutputForwarder (src/Jobs/Process/StreamingOutputForwarder.php) is passed as the callback. It routes Process::OUT lines to the parent's STDOUT and Process::ERR lines to STDERR in real time, so container log aggregators receive the output immediately without buffering.
    • StreamingOutputForwarder accepts injectable stream resources ($stdout, $stderr) so it can be tested without spawning a real process or relying on STDOUT/STDERR superglobals.
    • 5 PHPUnit 10.5 tests in tests/Unit/Jobs/Process/StreamingOutputForwarderTest.php: per-stream routing, chunk ordering (multiple chunks on the same stream), and a real child-process integration case via symfony/process.
    • Confirmed on seajets-admin in production: previously silent "Error" jobs are now fully visible in kubectl logs.
  • [4.99.12] fix(auth): correct isValidToken() existence check in customer password reset

    • Adv_customer_model::isValidToken() used strict num_rows() == 1, but shop_customer.active_token has no UNIQUE constraint — if two customers ever shared a token value, the predicate returned false for both, invalidating otherwise-valid reset tokens. Changed to >= 1 to match the method's intent (pure existence check).
    • Gates POST /rest/auth/customer/reset-password (via PasswordResetService) and the legacy storefront reset form. No behavioral change for the common path; fixes the duplicate-token edge case.
    • Resolved Known Issue #6 in docs/flows/customer/CF-10-customer-auth.md. Follow-up hardening (UNIQUE index, token TTL, entropy upgrade) still tracked in #193.
  • [4.99.11] chore(docker): include client custom/ directory in app image build

    • app.dockerfile now copies custom/ (PSR-4 Custom\ namespace) via a build-arg stage-selection trick: two alpine stages (custom_src_false / custom_src_true) are picked by INCLUDE_CUSTOM. BuildKit skips the unselected stage, so the COPY custom /custom never evaluates on the upstream repo where the directory is absent.
    • build-app.sh auto-detects whether custom/ exists in the build context and forwards --build-arg INCLUDE_CUSTOM=true|false. Both the PR-preview and tag-release pipeline steps already invoke build-app.sh — no pipeline changes needed.
  • [4.99.11] feat(rest): customer password reset + /me self-service endpoints (closes #191, #192)

    • New POST /rest/auth/customer/forgot-password — customer requests password reset email (legacy-parity; security hardening tracked in #193)
    • New POST /rest/auth/customer/reset-password — consume reset token and set new password (6-char minimum, matches existing REST customer password policies)
    • New GET /rest/admin/me — authenticated admin's own profile. Handles both DB users and config_auth users (e.g. advisable); for config users, id is null, isConfigUser: true, and role names are resolved via Admin\Role\Service
    • Refactored POST /rest/customer/me — the inline unset() blocklist is replaced with an explicit SELF_SERVICE_FIELDS class constant allowlist. mail is now blocked — this matches legacy storefront behavior (the account form renders the mail input as disabled and Adv_customer::update_info() never saved it). password, salt, admin-only, and system fields remain blocked.
    • New Advisable\Domains\Customer\Customer\PasswordResetService — thin wrapper around legacy Adv_customer_model + Adv_mailer. Constructor is side-effect-free; legacy CI model loading is deferred to private lazy accessors so Symfony DI container compilation does not trigger Adv_base_model::__construct() before CI helpers (get_languages() etc.) are available.
    • New Advisable\Rest\Admin\Controllers\Me — thin GET-only controller. POST /me is intentionally skipped because the current admin entity has no self-service writable fields (username changes still go through /rest/admin/users/{id}; password changes through /rest/admin/users/change-password). Added to application/config/rest_policies.php for auditability.
    • 9 unit tests + 3 integration tests (real-DB round-trip via CiDatabaseTestCase)
    • Follow-ups: #193 (harden reset flow — token entropy, TTL, email enumeration, rate limiting), #194 (introduce storefront_url() helper + migrate customer-facing email templates for headless deployments)
  • [4.99.11] fix(gifts): stock-aware greedy allocation for rule 13 multi-gift scenarios

    • Rule 13 previously returned a single cheapest product and multiplied it by giftCountToGive. When floor(reqCount / gift_per_count) > 1 and the cheapest product had less stock than the gift count, this produced impossible allocations (e.g., 2×C when only 1×C was in cart).
    • getCheapestRequirementProductInCart now accepts a 4th arg $giftsToGive and uses a greedy loop: iterates candidates cheapest-first (price ties break by lowest product id), takes min(remaining_gifts, product_stock) from each, continues until all owed gifts are allocated. Returns [1 => [pid, pid, ...]] as a flat per-slot list.
    • getRule13GiftAdjustmentsForProducts in Adv_order_model replaced reset() × giftCountToGive with a foreach that counts occurrences in the flat list, producing correct [pid => count] per product.
    • AdvCartResource::getGifts(), adjustCartContentsForRule13(), and getGiftRules() aggregate the flat slot list via array_count_values so each unique pid gets its own entry with the correct per-pid count. Deduplicates choices[1] for display.
    • Adv_orders_admin::gifts_for_products() receives the same grouping treatment for the admin order page.
    • applyRule13CartAdjustments() in order-gifts-block.js: relaxed if (newQty &lt;= 0) return to if (newQty &lt; 0) return so price deductions are applied even when a gift consumes 100% of a cart row (newQty === 0).
    • Requires npm run admin-production (admin Vue rebuild for the order-gifts-block.js change)
  • [4.99.11] refactor(jobs): rename retries column to max_retries in job_schedule (fixes #109)

    • The retries column stored the configured retry limit, not a running count — the actual counter is the separate retry_count column. The misleading name risked confusion.
    • Added Phinx migration 20260416093953_rename_retries_to_max_retries.php to rename the column
    • Updated all 15 PHP insert/read references across 10 files: AdvJobQueueManager, AdvJobAdmin, AdvCreateJobSchedule, AdvUploadVideoToStream, AdvCheckVideoStatusStream, AdvAddCustomersToAudience, Adv_audience_admin, AdvGiftCardAdminListing, AdvVideoShowcaseAdmin, Adv_settings
    • Fixed pre-existing display bug in assets/admin/js/jobs/jobs.vue — the retry count column showed blank because it read row.job_retry_times (undefined); now reads row.max_retries
    • Updated flow docs SY-01 and SY-22 with new column name; marked SY-01 Known Issue #2 as resolved
    • Requires php migrator.php migrate
    • Requires npm run admin-production (admin Vue rebuild for the display fix)
  • [4.99.11] fix(openapi): auto-group client-specific tags in api-docs sidebar

    • Client REST endpoints (Certus, Wallbid, Ship, Seajets, etc.) were found by the scanner but silently dropped from the sidebar because TAG_GROUPS only listed upstream Rest/* patterns
    • Add Administration group for Rest/Admin/* (upstream gap — User/Role endpoints were ungrouped)
    • Ungrouped tags are now auto-grouped by prefix: Rest/X/Y → group "X", non-Rest tags by first word (e.g. "Certus Checkout" → "Certus")
    • No client-side config needed — any new endpoints appear in the sidebar automatically
  • [4.99.11] fix(i18n): add missing English translation for Mailchimp admin menu entry

    • Add admin.menu.general.settings.mailchimp.icon and admin.menu.general.settings.mailchimp to the English admin menu language file — keys existed in Greek, Italian, and Spanish but were missing from English, causing the admin menu to show a raw translation key
  • [4.99.11] refactor(seo): remove dead Adv_seo controller and add DefaultMetaTag tests

    • Deleted ecommercen/seo/controllers/Adv_seo.php — deprecated front controller with zero callers; its methods (_create_metatags, _create_metatags_front) were replaced by Adv_seo_lib long ago
    • Deleted application/modules/seo/controllers/Seo.php — empty HMVC override of the dead parent
    • Removed 2 dead routes from application/config/routes.php (bare /seo and /{lang}/seo) that pointed to the empty Seo::index()
    • Updated docs/flows/admin/AD-14-seo-management.md — removed stale Adv_seo.php from Key Files table
    • Added 36 unit tests for the DefaultMetaTag domain layer: WriteDataTest (11 tests), ValidatorTest (4 tests), WriteServiceTest (13 tests)
  • [4.99.11] fix(rest): harden API responses with strict types, cast guards, and OpenAPI accuracy (PR #31, closes #92)

    • Cast all id/numeric fields to explicit (int)/(float) with !== null guards instead of truthy checks (prevents 0 → null bugs)
    • Add required arrays and nullable: true to OA\Schema attributes for client codegen
    • Add description annotations for admin-only / context-gated fields
    • Fix dangling $ref schema names (OrderOrderResource→OrderResource, ShelfcodeResource→ProductShelfcodeResource, etc.)
    • Fix Product Resource fatal: remove conflicting Vendor\Resource import
    • Fix Basket options: decode JSON string to object for wire transport
    • Add schema-only resources: CartTotalsResource, OrderTrackingResource, PaginationMeta, PaginationLinks
    • Refactor Cart controller to use CartResource + eager-loaded relations
    • Fix tracking() endpoint: wrap raw date columns in formatDate()
    • Fix Country isoCode OA type: 'int64'type: 'integer', format: 'int64'
    • Move pointFactor to always-emitted (storefront loyalty display); totalPoints to authenticated context
    • Updated all affected tests (910 REST tests pass)
  • [4.99.11] fix(security): correct max_upload_size from ~18.6GB to ~19MB (#6)

    • Config value was 19000000, treated as KB by all upload paths (AdvUploadValidator, CI3 Upload, UploadService) — effective limit was 19,000,000 KB ≈ 18.6 GB
    • Changed to 19000 (KB ≈ 19 MB), matching the intended limit
  • [4.99.11] fix(admin): remove dead webrun/generate_xsl link from old dashboard (#189)

    • The order export feature was removed in 4.99.8 (VULN-05) but the download button was left behind, rendering a link that 404s
  • [4.99.10] fix(vat): add canDelete guard to REST VAT delete endpoint (fixes #1)

    • Prevent deletion of VAT rates referenced by products via the REST API — matching legacy admin's canDelete() behavior
    • Without this guard, deleting a referenced VAT orphaned products: shop_prices_view's INNER JOIN silently dropped them from storefront listings and the product parser threw an undefined index fatal
    • WriteRepository::hasReferences() queries shop_product for VAT references
    • Validator::validateForDelete() throws ValidationException if referenced
    • WriteService::delete() calls validator before repository (mirrors create/update pattern)
    • HandlesWriteActions::doDestroy() catches ValidationException as 409 Conflict for all controllers
    • 7 unit tests added (WriteRepositoryTest, ValidatorTest, WriteServiceTest)
  • [4.99.10] fix(rest): remove stale Seo/Keyword policy reference (#168)

    • Removed dangling use Advisable\Rest\Seo\Controllers\Keyword; import and its RBAC policy entry from application/config/rest_policies.php
    • The Keyword REST controller was deleted in b501baac8; these two lines were the only remaining references
  • [4.99.10] docs(resync): update SY-23 for null-filtering insert behavior

    • Document BaseWriteRepository::insert() null filtering (array_filter before DB insert)
    • Document MuiWriteRepository::insertForEntity() routing through $this->insert()
    • Document Category B null→'' coercion in 9 MuiWriteData files for NOT NULL no-DEFAULT columns
    • Fix wrong table names in prose (sliders_slide_mui→slide_mui, cms_content_mui→categories_mui)
    • Add Known Issues: fromArray() duplicate-key lookups in 7 MuiWriteData files (camelCase fallback silently dead)
  • [4.99.10] chore(openapi): regenerate specs after versioning and null-fix merges

  • [4.99.10] docs(flows): add SY-31 REST API Versioning flow doc

    • Add docs/flows/system/SY-31-rest-api-versioning.md with 12 standard template sections
    • Document 7 known issues including missing integration tests and stale middleware guide
    • Catalog 16 unit tests across VersionResolverTest and VersionResultTest with 5 coverage gaps
  • [4.99.10] fix(rest): add config validation to VersionResolver and update middleware docs (#185, #186)

  • [4.99.10] feat(rest): add structured API changelog with UI panel and merge guard

  • [4.99.10] fix(docs): rewrite SY-30 client features as pattern reference

    • Remove ~/dev/advisable/adveshop4-clients/ local path reference
    • Remove fixed client counts and names (14 clients, 10 pharmacies, etc.)
    • Remove exhaustive feature enumeration (15 numbered features)
    • Remove feature tier comparison table
    • Restructure as pattern categories with representative examples
    • Add explicit note that this is not an exhaustive inventory
  • [4.99.10] fix(migration): add ROW_FORMAT=DYNAMIC to MyISAM→InnoDB conversion (#187)

    • ALTER TABLE ... ENGINE=InnoDB without explicit ROW_FORMAT=DYNAMIC can inherit COMPACT row format, which stores 768-byte BLOB/TEXT prefixes inline and exceeds the 8126-byte row size limit on wide tables like shop_product_mui
    • Fixed original migration (20260408100114) to include ROW_FORMAT=DYNAMIC
    • Added follow-up migration (20260415102932) for clients that already ran the old version: queries information_schema.TABLES, skips tables already InnoDB DYNAMIC, fixes anything still COMPACT or MyISAM
    • Requires php migrator.php migrate
  • [4.99.10] fix(rest): filter null values on insert to prevent NOT NULL constraint failures

    • BaseWriteRepository::insert() now filters null values before DB insert, letting MySQL apply column DEFAULT values for omitted fields
    • All 38 MuiWriteRepositories route insertForEntity() through the filtered insert() instead of calling db->insert() directly
    • 9 MuiWriteData files convert null→'' for NOT NULL columns without DB defaults (url, image, descr, name, fulltext, title, description, file) matching legacy admin behavior
    • Product WriteService sets date_changed=now() on create/update matching legacy admin behavior
  • [4.99.10] fix(security): suppress version number from API version error response (#183)

    • 400 error for unknown API versions now returns generic "Invalid API version" instead of echoing the requested version number, preventing version enumeration probes
  • [4.99.10] feat(rest): add URL-based API versioning with deprecation lifecycle

    • Version registry config (rest_api_versions.php) defines versions, lifecycle status (active/deprecated/obsolete), and per-version controller override maps
    • MY_Router extracts major version from URI (/rest/v1/...) before route matching — zero changes to rest_routes.php; unversioned /rest/... requests fall back to latest
    • RouterDispatcher resolves version, swaps override controllers, returns 410 Gone for obsolete versions, sets RFC 9745/8594 response headers (Api-Version, Deprecation, Sunset, Link)
    • VersionResolver service + VersionResult DTO in src/Rest/Versioning/
    • PolicyResolver parent-class fallback so version-override controllers inherit base controller policies automatically
    • RequestContext carries apiVersion through the middleware pipeline
    • GenerateOpenApiJson produces per-version specs (openapi-v{N}.json) with versioned path prefixes and api-versions.json manifest
    • api-docs.html version selector dropdown with status badges (active/deprecated); graceful fallback to openapi.json when manifest is missing
    • 16 unit tests covering resolution, status checks, RFC-compliant header generation, controller overrides
  • [4.99.10] feat(admin): migrate roles from config to database table (closes #166, #170, #172, #175)

    • Created roles DB table with 9 seeded roles matching AUTH_ROLE_* constants; added FK constraint on user_role.role_id
    • Added Role domain layer: Entity, Repository, Service, ListRequest in src/Domains/Admin/Role/
    • Converted User→Role relation from ONE_TO_MANY (UserRole pivot) to MANY_TO_MANY (Role through user_role) — enrichRoles() workaround removed
    • Rewrote REST Role controller to HandlesRestfulActions with show() route and hidden-role guard for non-advisable callers
    • Fixed DI container.php: removed use_sections=true config hack that poisoned CI3 namespace and broke admin login on first request after cache clear
    • Fixed Adv_roles_model to read from DB instead of config (prevents null crash during container compilation); also fixed getRolesByName() bug and removed dead user_role_model load
    • Batched role validation into single match() query with type-safe array_diff (eliminates N+1)
    • Removed auth_roles from application/config/auth.php; deprecated RoleProvider class
    • Updated x-relations type on User controller from one_to_many to many_to_many
    • Breaking: auth_roles config key no longer exists — roles are now managed via the roles DB table. AUTH_ROLE_* constants remain unchanged.
    • Requires php migrator.php migrate to create the roles table and add the FK constraint
  • [4.99.10] fix(tests): resolve 50 test errors across unit and database suites (#177, #178, #179, #180, #181, #182)

    • Page WriteServiceTest: add missing SlugGenerator mock (5th constructor param added in slug feature)
    • Seo Keyword tests: delete 3 orphaned test files left behind when Keyword domain was removed
    • Blog Category ServiceTest: add missing bannerImage to createData() fixture (NOT NULL column crash)
    • TestSeed: rewrite with correct table and column names (shop_product_vats, name/surname, etc.)
    • PlaceOrderServiceTest / GuestCheckoutTest: add 4 missing mock dependencies; pass all 13 constructor arguments in correct order
    • PaymentInitializerTest: update 6 assertions from PENDING to PENDING_ACCEPTED
  • [4.99.9] fix(tests): remove stale Seo/Keyword REST test files (closes #168)

    • Deleted CollectionTest.php and ResourceTest.php for the Keyword REST resource — the controller, Resource, and Collection were removed in b501baac8 but the test files were left behind, causing 4 class-not-found errors on every test run
    • DefaultMetaTag tests are unaffected and continue to pass (5 tests, 13 assertions)
  • [4.99.9] fix(admin): correct ApiEndpointTrait import in Role controller

    • The Role controller declared use Advisable\Rest\Support\Controllers\ApiEndpointTrait — a FQCN that does not exist, causing a fatal ReflectionException during DI container compilation
    • ApiEndpointTrait is a legacy classmap-loaded trait with no namespace; corrected to bare use ApiEndpointTrait
  • [4.99.8] feat(admin): port admin users, roles & permissions to Domain/REST (closes #115)

    • Admin/User domain: Entity, Repository, Service, WriteService, Validator
    • Admin/UserRole sub-entity for user_role pivot table
    • Admin/Role/RoleProvider reads roles from auth.php config
    • AuthHashService in src/Domains/Support/Auth/ decouples password hashing from CI framework
    • REST User controller: CRUD + soft delete/undelete + password reset + change own password
    • REST Role controller: read-only filtered role list
    • RBAC policies via middleware: AdminUser and AdminRole policies in rest_policies.php
    • Role-based guards: only Advisable (1) and Admin (3) can manage users; non-advisable admins cannot assign Advisable/Developer roles
    • Self-service change-password available to any backend user (no role restriction)
    • Unit tests for Validator (12), WriteService (4), RoleProvider (7)
    • DI container registration + route registration (26 routes)
  • [4.99.8] chore(db): convert 10 remaining MyISAM tables to InnoDB

    • Converted blog_comments, categories_mui, ci_sessions, public_line_items, public_orders, shop_customer_sms_marketing, shop_customer_tag, shop_line_mui, shop_order_asap_extra_data, shop_product_mui from MyISAM to InnoDB
    • InnoDB provides row-level locking (critical for ci_sessions under concurrent requests), full transaction support, crash recovery, and foreign key capability
    • Tables with FULLTEXT indexes (categories_mui, shop_line_mui, shop_product_mui) are safe — InnoDB FULLTEXT is supported since MySQL 5.6 / MariaDB 10.0.5
    • shop_order_asap_extra_data included because its original migration omitted ENGINE, leaving it as MyISAM on servers with MyISAM as the default
    • Requires php migrator.php migrate
  • [4.99.8] fix(security): remove vulnerable order export feature (VULN-05)

    • Deleted generate_xsl() and generate_xsl_for_geniki() from application/controllers/Webrun.php — both used unserialize() on data from shop_export (unsafe deserialization, VULN-05); removed unused PhpSpreadsheet imports
    • Deleted create_export() from ecommercen/eshop/controllers/Adv_orders_admin.php — sole caller of set_export_data(); also eliminated SQL injection where POST date params were concatenated directly into the query
    • Deleted admin view application/views/admin/orders/create_export.php (date picker + download buttons UI)
    • Removed deprecated set_export_data() and get_export_data() helper functions from ecommercen/helpers/shopmodule_helper.php (already marked @deprecated)
    • Removed eshop.admin.order.create_export and eshop.admin.order.download.results translation keys from all 8 language files
    • Added Phinx migration to drop the shop_export table (reversible)
    • Requires php migrator.php migrate to drop the shop_export table
  • [4.99.7] feat(ci): add docs deployment to Cloudflare Pages with PR previews

    • Docs site hosted on CF Pages with Zero Trust access control (docs-*-internal.ecommercen.com)
    • One permanent CF Pages project per client, branch deploys for PR previews
    • Pipeline triggers: tag builds deploy to master branch (production), PR builds deploy to pr-{id} branch (preview)
    • Docker and docs builds run in parallel after tag parsing step
    • PR cleanup: pullrequest-fulfilled / pullrequest-rejected triggers delete PR Docker tags
    • CF credentials (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID) are workspace-level; docs step skips gracefully if not set
  • [4.99.7] feat(docs): multi-spec OpenAPI infrastructure for VitePress

    • Support rendering multiple OpenAPI specs per client (platform API + vendor integrations like Wallbid)
    • Spec registry at docs-gen/specs.ts — add entries to register new specs
    • Route generator at docs-gen/create-spec-routes.ts — creates VitePress dynamic route dirs per spec
    • Per-page useOpenapi() solves multi-spec rendering conflict
    • Sidebar and nav entries generated automatically from registry with correct linkPrefix
    • Client repos append entries on upstream rebase without conflicts
  • [4.99.7] feat(docs): add vitepress-plugin-llms for AI tool consumption

    • Generates llms.txt (index) and llms-full.txt (all content) in build output
    • OpenAPI operations excluded (hash-based operationIds produce useless entries)
    • Available at /llms.txt on the docs site
  • [4.99.7] feat(docs): client-specific flow docs structure (CCF/CAD/CSY/CIN prefixes)

    • Client flow docs live in docs/client/flows/{customer,admin,system,integration}/
    • Category-specific prefixes: CCF-NN (customer), CAD-NN (admin), CSY-NN (system), CIN-NN (integration)
    • All doc-ba agents updated with client repo awareness: code classification (new feature vs override vs inherited), doc routing, citation rules, dedup boundaries
    • Main repo excludes docs/client/ from VitePress build; client repos remove the exclusion
  • [4.99.7] refactor(agents): move doc-ba orchestration from subagent to main session

    • Subagents cannot spawn other subagents in Claude Code — the orchestrator agent was falling back to shell commands
    • Orchestration logic merged into doc-ba-workflow skill; the 5 phase agents remain as subagents
    • Delete .claude/agents/doc-ba-orchestrator.md
  • [4.99.7] docs(flows): rename AdvEshop → Ecommercen across all flow docs and doc-ba agents

  • [4.99.7] feat(auth): add CLI utility to generate long-lived SPA tokens

    • SPAs that cannot perform interactive JWT authentication (e.g., headless frontends, kiosk apps) need a pre-generated bearer token
    • New Utils::generateSpaToken() CLI method generates a 10-year JWT with spa-bot user identity and advisable role
    • Uses the REST Auth Config (same key/algorithm as regular JWT auth)
    • Run via php cli.php utils/generateSpaToken — outputs an SPA_TOKEN=... line ready for .env files
    • CLI-only (protected by is_cli() guard in Utils constructor)
  • [4.99.7] refactor(seo): remove keywords module across all layers

    • The keywords module allowed defining keyword-to-URL mappings that were auto-linked in page content via _makelinks() — the feature was unused and has been fully removed
    • Removed 12 REST routes (6 read + 6 write) from application/config/rest_routes.php
    • Deleted src/Rest/Seo/Controllers/Keyword.php, Resource.php, Collection.php; removed DI registration from src/Rest/Seo/container.php
    • Deleted entire src/Domains/Seo/Keyword/ directory (Entity, Repository, WriteRepository, RepositoryConfigurator, Service, WriteService, Validator, WriteData, ListRequest); removed DI registration from src/Domains/Seo/container.php
    • Removed admin.menu.keywords.icon and admin.menu.keywords.label language keys from all 8 language files
    • Deleted legacy HMVC layer: Adv_keywords controller, Adv_keywords_model, application wrappers (Keywords, Keywords_model), and 3 admin views (list, create, update)
    • Removed _defkeywords() and _makelinks() methods from Adv_seo controller; removed defkeywords() call from index()
    • Removed "Internal Linking" button from seo/metatags admin view; removed eshop.admin.seo.internal.linking.label from all 8 language files
    • Added Phinx migration to drop the keywords table (reversible)
    • Requires php migrator.php migrate to drop the keywords table
    • Requires composer install for autoloader cleanup
  • [4.99.6] feat(rest): toggle REST API routes via APP_REST_API_ENABLED env var

    • REST routes were always loaded unconditionally — no way to disable them without code changes
    • Add APP_REST_API_ENABLED env var (default false); when true, rest_routes.php is included and all /rest/* endpoints become active
    • Documented in docs/guides/RestApiModules.md under "Enabling the REST API"
  • [4.99.6] feat(job): add ClearExpiredSessions job with deterministic session GC

    • PHP's probability-based gc_probability/gc_divisor session cleanup is unreliable under high traffic — expired sessions accumulate
    • New AdvClearExpiredSessions base job in ecommercen/job/libraries/ calls session_gc() deterministically; client-overridable ClearExpiredSessions in application/modules/job/libraries/
    • In CLI context the Session library is not loaded, so gc_maxlifetime stays at the php.ini default (1440s) instead of the configured sess_expiration; the job reads sess_expiration via config_item() and applies it with ini_set() before invoking session_gc()
    • Uses CodeIgniterLogger for logging
    • Scheduled daily at 3 AM (0 3 * * *) in the core queue (application/config/jobs.php), 300s grace time, 0 retries
    • Requires composer install after deployment so the autoloader picks up the new classes
  • [4.99.6] feat(checkout): add payment webhook handlers and client-initiated confirmation

    • Orders placed via online gateways (Stripe, VivaWallet, PayPal) remained stuck in pending_payment — no callback endpoints existed
    • New PaymentConfirmationService with idempotent confirmPayment() / cancelPayment() — handles status update, is_paid flag, and stock reduction for online payments
    • New src/Rest/Webhooks/ module with per-gateway webhook handlers: Stripe (HMAC signature verified via STRIPE.WEBHOOK_SECRET), VivaWallet (verification key exchange + event processing), PayPal (TODO: signature verification)
    • New POST /rest/checkout/confirm-payment/{orderId} client-initiated endpoint — verifies payment via gateway API (Stripe session, VivaWallet transaction, PayPal capture) for dual confirmation alongside webhooks
    • Webhook routes: POST /rest/webhooks/stripe, /rest/webhooks/vivawallet, /rest/webhooks/paypal
    • Requires STRIPE.WEBHOOK_SECRET Registry key for Stripe webhook signature verification
  • [4.99.6] fix(checkout): add basket items, coupon marking, and stock reduction to REST checkout

    • Cart items were never written to shop_order_basket — line items lost when cart cleared after order creation
    • Coupons validated but never marked as used — coupons.is_used not incremented, allowing unlimited reuse
    • Product stock never decremented — product_codes.stock unchanged, enabling overselling
    • Add OrderBasket write layer (WriteRepository, WriteData, WriteService with createForOrder() bulk insert)
    • Add OrderBasketBuilder — snapshots pricing, VAT percentage, and active discounts from product data at checkout time
    • Add StockService with reduceStockForOrder() / restoreStockForOrder() using raw SQL stock arithmetic (matches legacy removeOrderStock)
    • Add CouponCode\WriteRepository::incrementUsed() for atomic counter increment (matches legacy markCouponUsed)
    • Offline payment adapters (delivery, bank_transfer, paid_at_store) now return PENDING_ACCEPTED instead of PENDING, aligning with legacy checkout controller behavior
    • Stock reduction for offline payments happens immediately; for online payments, deferred to webhook/confirmation
    • 18 new unit tests covering basket builder, stock service, write service, and payment confirmation
  • [4.99.6] fix(rest-policies): enable public read access for promo and vendor endpoints

  • [4.99.6] feat(slug): add slug generation and field protection to domain/REST layer

    • Auto-generate slugs from name/title on create when not provided
    • Restrict slug/URL field changes to AUTH_ROLE_ADVISABLE users on update
    • Add SlugGenerator service with lazy boot and Registry integration
    • Add GeneratesSlugs trait for WriteService slug auto-generation
    • Add applyFieldProtection() to both write traits
    • 9 unit tests + 5 database integration tests
    • Load language helper in CiDatabaseTestCase for Registry compatibility
  • [4.99.5] fix(video): add missing customDatetime field to Video WriteData

    • The custom_datetime column is NOT NULL without DEFAULT but was not mapped in WriteData — REST create would fail at DB level
    • Added field to OA schema (required), constructor, fromArray, and toArray
  • [4.99.5] feat(openapi): make OpenAPI spec fully machine-readable for AI agents

    • Added required fields to 120 write + translation schemas (284 total required fields) based on DB NOT NULL constraints, migration analysis, and admin validation
    • Added description to all OA\Property annotations on WriteData and MuiWriteData files for field-level documentation
    • Added structured x-relations, x-sorts, x-filters vendor extensions to 105 controller OA\Tag annotations, replacing HTML-only descriptions
    • Relations include type info (belongs_to, one_to_many, many_to_many); filters include match type (exact, partial); locale fields use {locale} placeholder
    • Updated src/Rest/OpenApi.php global info with conventions documentation for vendor extensions, required fields, sorting, filtering, and relations
    • MuiWriteData schemas now require translation-essential fields (e.g., ProductTranslation requires lang, name, slug)
    • Regenerated openapi.json — 239 files changed, +7909/-1188 lines
    • AI agents can now programmatically discover required fields, available relations, valid sorts, and filter types from the spec alone
  • [4.99.5] fix(auth): add SetEnvIf Authorization to .htaccess.example for JWT on Apache CGI/FastCGI

    • Apache CGI/FastCGI strips the Authorization header before it reaches PHP, breaking JWT Bearer token authentication
    • Added SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 to public/.htaccess.example
    • Docker (nginx + PHP-FPM) is unaffected — fastcgi_params passes the header through by default
  • [4.99.5] fix(openapi): align OA annotations with routes across 7 controllers

    • Audited all REST controllers against rest_routes.php and openapi.json, fixing path mismatches, missing routes, and undocumented endpoints
    • Fix Slide item() OA path missing /rest/ prefix
    • Add missing Barcode GET read routes (index, show, item)
    • Fix EventCategory write routes using wrong path (event/categoryevent/event-category)
    • Fix Store item() OA path duplicating index path instead of /rest/order/store/item
    • Add Wishlist update (POST /{id}) OA annotation
    • Add Customer store/update/destroy OA annotations (admin endpoints)
    • Add Order store/update/destroy OA annotations (admin endpoints)
    • Regenerated openapi.json spec
  • [4.99.5] refactor(middleware): make site mode allowed namespaces config-driven

    • AdvSiteModeMiddleware::siteModeInstanceOf() previously had a hardcoded Advisable\Rest\ namespace string; it now loops over the siteModeAllowedNamespaces config array
    • Added $allowedNamespaces property read from config in the constructor
    • New config key in application/config/app.php: $config['siteModeAllowedNamespaces'] = ['Advisable\Rest\\']
    • Client repos can now add extra namespaces (e.g. Custom\Rest\, Custom\Certus\) by overriding the config array instead of subclassing SiteModeMiddleware
  • [4.99.4] feat(rest): add file upload support to REST API via multipart/form-data

    • New HandlesUploadActions trait replaces HandlesWriteActions on controllers with file fields — handles multipart/form-data for store/update and deletes physical files on destroy
    • New UploadService (src/Domains/Support/Upload/) wraps legacy UploadValidator, UploadFileNameGenerator, and Storage (Flysystem) in a clean DI-injectable service
    • 18 controllers wired: Vendor, Badge, Tag, TagCategory, Line, Supplier, Promo, Category, ProductList, Download, OfferCategory, Video, BlogArticle, BlogAuthor, BlogCategory, Event, EventCategory, Map
    • Added missing file fields to WriteData: ProductList (image, smallBanner), Blog/Category (bannerImage), Video (thumb)
    • OpenAPI annotations updated with dual application/json + multipart/form-data content types on all upload endpoints
  • [4.99.4] fix(rest): replace PUT with POST for all write endpoints

    • PHP does not populate $_POST / $_FILES for PUT requests with multipart/form-data — file uploads were silently failing on update
    • All 204 PUT routes changed to POST in rest_routes.php; all 86 OA\Put annotations changed to OA\Post
    • Update endpoints now use POST /rest/{context}/{entity}/{id} instead of PUT
    • Breaking change for API consumers — update client code to use POST for updates
  • [4.99.4] fix(rest): resolve trait method resolution bug in all write controllers

    • 86 controllers called parent::store() / parent::update() / parent::destroy() — but these methods come from traits, not the parent class, causing Call to undefined method fatal errors
    • Both HandlesWriteActions and HandlesUploadActions refactored: logic moved to doStore() / doUpdate() / doDestroy() protected methods; public methods delegate to them
    • All controller overrides updated to call $this->doStore() etc. instead of parent::store()
  • [4.99.4] fix(rest): embed config user roles in JWT tokens

    • AdminAuth::getUserRoles() only checked DB roles — config-based users (from config_auth in auth.php) always got empty roles, failing RBAC checks
    • Now checks config_auth roles first, falls back to DB roles
  • [4.99.4] fix(rest): allow REST controllers through site mode guard

    • AdvSiteModeMiddleware::siteModeInstanceOf() now bypasses the redirect for any controller in the Advisable\Rest\ namespace
    • Client repos can extend SiteModeMiddleware to add Custom\Rest\ etc.
  • [4.99.4] fix(deps): update phpseclib and flysystem-sftp-v3 to patch AES-CBC padding oracle vulnerability

    • phpseclib/phpseclib upgraded from 3.0.49 to 3.0.50 — fixes CVE-2026-32935 (AES-CBC unpadding susceptible to padding oracle timing attack)
    • league/flysystem-sftp-v3 upgraded from 3.31.0 to 3.33.0
    • No code changes required — both are transitive dependencies used by the SFTP storage adapter
  • [4.99.4] fix(rest): load application-level user role model instead of ecommercen base

    • AdminAuth::getUserRoles() was loading auth/Adv_user_role_model directly; in client deployments the MX_Loader searches application/modules/ first and fails to find the ecommercen base model
    • Changed to load auth/user_role_model (application-level wrapper) which resolves correctly and inherits getUserRolesIds() from the base
  • [4.99.4] fix(deps): block malicious axios versions to protect against supply chain attack

    • axios dependency range changed from ^1.12.0 to >=1.12.0 &lt;1.14.1 || >=1.14.2 — blocks 1.14.1 (known malicious) while allowing future safe releases
    • overrides block added in package.json with the same range to enforce the constraint on any transitive dependency that pulls in axios
    • No build required — this change only affects dependency resolution
  • [4.99.4] fix(cart): fix quantity spinner reset and gift badge display for gift rule 13 (cheapest product free)

    • AdvCartResource::adjustCartContentsForRule13() now annotates freeQuantity on cart items alongside reducing quantity, so the Vue layer knows how many units are free when dispatching cart update requests
    • AdvCartProductRow.vue: both quantity spinners now use :key="cartProduct.quantity + (cartProduct.freeQuantity || 0)" to force a re-render when total quantity (paid + free) changes; updateProductQuantity() restores free quantity before dispatching: quantity: quantity + (this.cartProduct.freeQuantity || 0)
    • AdvCartGiftRow.vue: gift badge now reads cartProduct.giftCountToGive || cartProduct.quantity for both the v-if guard and the displayed count, fixing an empty badge for rule 13 gifts
    • Requires frontend build (npm run production / npm run all-production)
  • [4.99.4] fix(views): remove redundant vendor slug path from transformProductForJson in combo list views

    • bundle_product_combo_list.php and product_combo_list.php were still passing "vendors/{$productData->vendor_slug}" in the $is_canonical branch
    • The 4.99.3 transformProductForJson refactor auto-resolves vendor URLs via vendors_base_url_{lang} config, making the explicit path redundant
  • [4.99.3] feat(rest): add field-level scope filtering to REST resources

    • Resources now conditionally include backend-only fields based on ResourceContext scope (public/customer/backend)
    • Guest and customer callers receive only storefront-safe fields; backend users see all fields
    • Product: hides 16 fields (wholesalePrice, acquisitionValue, softDelete, hits, vendorCode, hsCode, skroutzName, shelfcodeId, active, cartProductCustomization*, negativeStock, pointFactor, productView, dateChanged)
    • Category: hides 8 fields (published, isSensitive, order, menuColor, asProductsDetails, sliderId, menuSliderId, extraSliderId)
    • Customer: hides 7 fields (isInsecure, adminComments, totalPoints, hasAccess, isGuest, isSanitized, erpId) + 5 backend-only relations (campaigns, messageHistory, smsMarketing, tags, audiences)
    • CMS: hides active/published flags, priority/position, hits across BlogArticle, BlogCategory, BlogTag, BlogAuthor, Document, Video, Page, Offer
    • Event: hides active, dateChanged; Slider: hides groupPriority, slidesLimit; Slide: hides dateStart, dateEnd, audienceId, themeClass, priority
    • 21 new scope-filtering tests verify correct field visibility per scope
    • See REST Middleware & RBAC guide for full field filtering reference
  • [4.99.3] feat(rest): align RBAC policies with legacy admin controller roles

    • Every rest_policies.php roles array verified against corresponding legacy admin controller allowRole() checks
    • AUTH_ROLE_ADMIN explicitly listed where needed — does NOT auto-bypass (some routes are ADVISABLE-only)
    • Fixed wrong domain roles: Promo (PRODUCTS→MARKETING+MEDIA), CustomerReview (PRODUCTS→MARKETING), Offer/OfferCategory (CMS→MARKETING), Event/EventCategory (CMS→MARKETING+MEDIA), Video (CMS→MEDIA)
    • Fixed over-permissive entries: Store/Cookie/Transporter settings (→ADMIN-only), Customer CRUD (any→ADMIN+ORDERS), Map/Location (any→ADMIN+MARKETING+MEDIA)
    • Added missing ADMIN role to all entries where legacy includes it
    • Legacy provenance comments on every policy entry
    • Wishlist index/show opened to auth=any for customer self-service browsing
    • 45 legacy admin controllers without REST equivalents documented in .claude/tmp/missing-rest-coverage.md
  • [4.99.3] fix(test): resolve DB connection and global state issues in test suite

    • Fixed dotenv overwriting PHPUnit ENVIRONMENT=testing with .env's ENVIRONMENT=production — save/restore PHPUnit env vars around dotenv loading
    • Fixed CI3 config/testing/database.php not including base config — CI3 loads environment-specific config INSTEAD OF (not in addition to) base config
    • Fixed CI::$APP created with null router when Unit tests trigger CI class autoloading before full boot — bootstrap_ci.php now detects and repairs corrupted instance
    • Replaced global get_instance() stubs in middleware tests with swappable GetInstanceRegistry
    • All 5316 tests (Unit + Integration + Legacy) now pass in a single vendor/bin/phpunit run
  • [4.99.3] feat(rest): add middleware pipeline for REST API authentication, authorization, and RBAC

    • New four-middleware pipeline in RouterDispatcher::dispatch() runs before every controller action: AuthenticationMiddlewareAuthorizationMiddlewareResourceContextMiddlewareRelationFilterMiddleware
    • AuthenticationMiddleware: extracts JWT Bearer token and populates RequestContext; never halts the request
    • AuthorizationMiddleware: resolves effective policy (method → controller → global) and halts with 401/403 when auth type or role requirements are not satisfied
    • ResourceContextMiddleware: sets ResourceContext on the controller before action execution; Resource transformers can check isPublic() / isCustomer() / isBackend() to conditionally include/exclude fields
    • RelationFilterMiddleware: silently strips ?with= relations not in the allowed list for the resolved scope
    • 9 RBAC roles matching the legacy admin role system (AUTH_ROLE_ADVISABLE through AUTH_ROLE_MEDIA); roles embedded in JWT at login; AUTH_ROLE_ADVISABLE (value 1) bypasses all role checks
    • Centralized policy configuration in application/config/rest_policies.phpdefaults for global fallback, controllers for per-controller overrides with defaults, methods, and relations keys
    • Auth types: none, guest, any, customer, backend, legacy_guard
    • Default behavior for unlisted controllers is backend auth (safe default)
    • 65 new unit tests covering middleware logic, policy resolution, role checks, and relation filtering
    • See REST Middleware & RBAC guide for full configuration reference
  • [4.99.3] fix(tests): fix BaseWriteRepositoryTest failing in full unit suite

    • BaseWriteRepositoryTest::buildRepository() used new ConcreteWriteRepository() which calls get_instance()->db in the constructor — crashes with TypeError when CI3 is booted by other tests (db is null) or Notice when not booted (eval'd stub)
    • Fix: use ReflectionClass::newInstanceWithoutConstructor() to bypass the constructor entirely and inject the mock $db via reflection
    • Eliminates 10 errors and 1 notice from full unit suite run (--testsuite Unit)
  • [4.99.3] feat(docs): redesign check-client-docs with classification, intent capture, and batch operations

    • --update now produces table-based Client.md with Custom/Modified sub-sections, composite descriptions, and diff stats — replaces the old bare file-path bullet lists
    • Composite descriptions built from multiple signals: class metadata, method names (from diff for modified files), use statement analysis (Uses:), constructor DI dependencies (Deps:), docblocks, and developer intent extracted from commit bodies (ranked by commit type: feat > fix > other)
    • Batch git operations (4 calls per section instead of 5 per file) for fast execution even on large repos
    • Auto-classify files as Custom (new) or Modified (upstream override) via git diff --diff-filter
    • Stub detection: empty classes extending upstream are automatically skipped
    • New --report flag produces structured JSON with full metadata per file for agent consumption
    • Upstream ref failure now errors loudly with setup instructions instead of silently falling back to useless directory scan
    • Updated SKILL.md and docs-updater agent with simplified three-step workflow: scan → review → refine
  • [4.99.3] fix(docs): replace getopt() with manual $argv parser in process_changelog.php

    • PHP's getopt() stops parsing at the first non-option argument (the subcommand), silently ignoring all flags (--update, --upstream, --repo, --docs)
    • Replaced three separate getopt() calls with a single manual $argv parser
    • Flag position is now flexible — flags can appear before or after the subcommand
  • [4.99.3] fix(smartpoint): switch BoxNow widget from native popup to custom iframe overlay to fix iPhone/Chrome compatibility

    • SmartPointWidget.js: changed BoxNow widget type from popup to iframe and autoselect from false to true
    • BoxNowWidget.vue: replaced inline map div with a custom popup overlay — backdrop (#boxnow_shadow), close button (#boxnow_close_all), and widgetStateOpen reactive state that controls open/close lifecycle
    • Fixes locker map not opening on iPhone with Chrome and locker selection not working on the same page
    • Requires frontend build when merged to client (npm run production)
  • [4.99.3] chore: regenerate OpenAPI spec with headless commerce endpoints

  • [4.99.3] chore: hide Authorize button and add no-cache to API docs

    • Hide Swagger UI Authorize button via plugin (read-only docs, no Try It Out)
    • Add Cache-Control: no-cache meta tag to ensure fresh spec on each visit
  • [4.99.3] docs(openapi): add write annotations to 86 REST controllers

    • Product context: 33 controllers (Attribute, Category, Product, Vendor, etc.)
    • CMS context: 11 controllers (Blog*, Cookie, Document, Offer, Video, etc.)
    • Promotion context: 8 controllers (Coupon*, Gift*, etc.)
    • Order context: 5 controllers (GiftCardOrder, Note, OrderTag, Store, Vat)
    • Customer context: 5 controllers (CustomerCampaign, CustomerReview, etc.)
    • Transporter context: 8 controllers (store+destroy only for composite PK subdomains)
    • Slider, Country, Plus, Seo, Event, Ai, Map, Currency: 16 controllers
    • Country/County use string PK type in path parameters
    • Transporter subdomains have POST+DELETE only (no PUT) due to composite PKs
  • [4.99.3] docs: expand docs-updater agent and skill with comprehensive client repo verification rules

  • [4.99.3] fix(docs-updater): restore entries ordering, auto-detect upstream ref, and deduplicate agent

    • Restore the deleted Entries Ordering section in the skill
    • Add scope note to Changelog Workflow section clarifying it applies to all repos
    • Add detectUpstreamRef() to process_changelog.php for auto-detecting upstream ref
    • Replace hardcoded upstream/master references in skill docs with generic &lt;upstream-ref>
    • Deduplicate verification rules: trim agent's 40-line copy to a 2-line cross-reference
  • [4.99.3] test: add database integration test infrastructure and 2883 tests

    • New CiDatabaseTestCase base class — CI3-aware, transaction-per-test rollback, db() helper
    • New phpunit-database.xml.dist config for running database tests separately
    • Docker test DB on RAM disk (.docker/integration/backend-db-test.compose.yml)
    • Phinx testing environment with TestSeed (IDs 99001+)
    • bootstrap_ci.php fix: pre-load MX/Base.php to prevent class conflict during DI container compilation
    • Complete coverage: Repository tests (match/matchOne/count/get, filters, sorts, pagination, relation loading) and Service tests (CRUD lifecycle, validation) for all domain entities
    • Domains covered: Cms (13), Product (37), Order (9), Marketplace (9), Customer (6), Promotion (8), Transporter (8), Currency (1), Seo (2), Ai (2), Plus (2), Cart (2), Country (2), Slider (3), Event (2), Map (2)
    • See Testing Guide for setup instructions and full coverage table
    • Bug fixes discovered during testing:
      • fix(SortByTranslation): add select('table.*') to all 17 MUI specs — JOIN caused entity columns to be overridden by MUI columns
      • fix(Transporter): 7 child WriteServices crash on create() for composite PK tables — replaced get(insert_id()) with matchOne(Filter...)
      • fix(Cookie): DI container passed NullRelationConfigurator to typed RepositoryConfigurator constructor
      • fix(Map/WriteData): column name is_activeactive (didn't match DB schema)
      • fix(Map/MuiWriteData): title/slug typed as ?int?string
      • fix(Attribute/WriteData): removed non-existent old_id column mapping
      • fix(Attribute/MuiWriteData): removed non-existent SoftId column mapping
      • fix(MX/Controller.php): requirerequire_once to prevent class redeclaration
  • [4.99.3] refactor(product): remove 16 dead methods from Adv_product_model

    • Deleted 16 methods confirmed to have zero callers across ecommercen/, application/, and src/: categoryWithProductExists, get_cat_products, get_product_ids, get_product_isactive, get_vendor, get_vendor_mui, getCategoryAttributes, getCategoryMinMaxPrice, getDiscountPricesAndPercent, getMaxDiscountOfProducts, getProductCodesByIds, getProductsForBatch, getProductsToIndex, getRelatedProducts, updateMuiProductRecord, updateVat
    • No behaviour change for any active caller
  • [4.99.3] refactor(product): array syntax modernisation, SQL spacing fixes, and SQL injection hardening in Adv_product_model

    • Replaced 39 old-style = array() default parameter values and variable initialisations with short = [] syntax throughout the file (I)
    • Fixed missing spaces around = in 7 SQL JOIN ON strings (e.g. category_id ={$this->tableCatMui}category_id = {$this->tableCatMui}) (E)
    • Added array_map('intval', ...) around 13 implode() calls that place integer IDs directly into raw SQL strings (IN(...), FIELD(...), WHERE ... IN) — prevents SQL injection from non-integer input (A)
    • Affected methods for injection hardening: getProductAdmin, getCatProductsBase (both variants), getBarcodeByProductIdsAsArray, getProductCategoryProjectAgora, getProductCategoryGoogleRecommendations, getProductsByIdsOrderedFromInput, getProductBundleDataByProductIds, getProductCodesByIds, getFeedProducts (topSellers/topStock)
    • No impact on method signatures or return types — style and safety changes only
  • [4.99.3] refactor(product): normalise return types to array for 18 query methods in Adv_product_model

    • Refactored 18 methods that previously returned array|false to return array consistently — return false replaced with return [] in all 18
    • Return type hints updated from array|false to array for all 18 methods
    • Methods affected: getMasterRecords, getMasterRecordsWhereIn, get_records, get_products_with_paths, get_skroutz_records, getMuiProductRecords, getPromoProducts, getTagProducts, getVendorProductsHits, getCategoryAttributes, get_product_ids, getDiscountPricesAndPercent, get_record, get_mui_slug, getBarcodesByProductId, getCatProductsPriceRange, getRenderVendorsPrices, getProductsWhereIn
    • No behaviour change for callers that already check for empty arrays — false was semantically equivalent to [] in all call sites
  • [4.99.3] refactor(product): visibility, type hints, query builder cleanup, and early returns in Adv_product_model

    • Added explicit public visibility modifier to 63 methods that were previously relying on the implicit default — no behaviour change (B)
    • Replaced 13 verbose query builder chains with cleaner CI equivalents: ->from()->get()->get($table) (3×), ->from()->where()->get()->get_where() (8×), ->from()->where_in()->get()->where_in()->get($table) (2×) (F)
    • Extracted 3 assignments out of if conditions: in processProductCodes() (json_decode result), insertProductCode() (insert_id result), and fixAdminSearch() (array_search result) (G)
    • Added return type hints to ~97 methods: : void, : array, : bool, : string, : int, union types (array|false, object|false, string|false, object|null), and : mixed for complex return paths (D)
    • Refactored 3 methods with early returns / guard clauses: getProductAdmin() (guard after query check, flattened body), delete() (two guards replacing $return accumulator, direct return $this->db->affected_rows() > 0), get_record() (removed $tmp init, two early returns, simplified $details with ternary) (L)
    • All changes are readability and type-safety refactors — no behaviour change
  • [4.99.3] refactor(product): dead code removal, spacing fixes, variable renaming, and and&& in Adv_product_model

    • Removed 7 commented-out code blocks: orphaned ->join() on tableProductCodeAttributes, dead foreach ($enabledFeeds ...) block, orphaned ->group_by(), dead $select .= image_2/image_3/image_4 lines and their now-empty if ($includeAllImages) guard, and three stale Greek comments in get_products_with_paths(), getVendorProductsHits(), and render_vendors()
    • Fixed missing spaces around = in createSlug() and categoryWithProductExists() per code style guide
    • Renamed single-letter and abbreviated variables to descriptive camelCase: $q$query in get_master_record(); $ret$result in getProductCodes() and getProductCodeImages(); $tmp$result in processProductCodes(); $get$query in findSlugsByUrl() and get_cat_products(); $ind$index, $master_attr_id$masterAttrId, $where_attrs$whereAttrs in get_cat_products(); $iCount$index in getCatProducts() and getCatProductsHits(); $c$attempt in createSlug()
    • Replaced all PHP logical and operators with && throughout the file (~50 occurrences); SQL string literals left untouched
    • All changes are readability and style refactors — no behaviour change
  • [4.99.3] refactor(product): type-safety and visibility cleanup in Adv_product_model — feed methods, setProductsVendor(), getProductsWithDiscountGreaterThan()

    • updateFeedPriceModifier(): added : void return type
    • getFeedProducts(): added ?int type hint to $feedId parameter, added : array return type
    • Ten feed proxy methods (getFeedProductsForGoogle(), getFeedProductsForFacebook(), getFeedProductsForBestPrice(), getFeedProductsForContactPigeon(), getFeedProductsForLinkwise(), getFeedProductsForWellcomm(), getFeedProductsForManago(), getFeedProductsForEmag(), getFeedProductsForPublicMarketplace(), getFeedProductsForAdvisableAI()): added : array return type
    • get_public_2p_records(): added : array return type
    • setProductsVendor(): added array / int type hints on parameters, added : void return type, replaced set()->update() chain with update($this->table, [...]) — behaviour identical
    • getProductsWithDiscountGreaterThan(): added public visibility modifier (was missing), added int type hints on $discount and $limit, removed unused $custom_select parameter and its if/else branch, replaced ->from()->where()->...->get() builder chain with ->get_where() — no behaviour change
    • All changes are readability and type-safety refactors — no behaviour change
  • [4.99.3] refactor(eshop): further cleanup in Adv_eshop — baseProduct() guards, match expression in baseCheckout(), void helpers, canonical redirect extraction

    • baseProduct(): renamed $query$productExists, $queryCat$categoryExists, $query2$canonicalMatch for clarity; extracted $canonicalMatch assignment out of the if condition; added string type hint; added $categorySlug !== '' guard to skip redundant cache lookup when no category is present in the URL
    • baseProduct(): extracted canonical redirect block into new handleProductCanonicalRedirect(object $canonicalMatch): void protected helper — loads eshop/products module and calls canonicalProduct() with the resolved vendor URL; baseProduct() now delegates to this helper when a canonical match is found
    • baseCheckout(): replaced if/if/fallthrough structure with a match expression; extracted default branch to new handleCheckoutPage(): void protected helper; now always returns true after the match (removes a redundant return true from each branch)
    • handleGetResponse() and handleCheckoutHandle(): corrected return type from bool to void — neither method ever returned false
    • handleCheckoutHandle(): replaced bare exit('Could not serve your request...') with proper error_401() dispatch via eshop/home module
    • No behaviour change — readability and minor performance refactor only
  • [4.99.3] refactor(eshop): minor cleanup in Adv_eshop — visibility, type hints, and style

    • $pscache_ttl changed from public to protected — only used internally and by subclasses
    • _remap() gains string / array param types and : void return type
    • customRemap() gains string param type and : bool return type
    • Fix alignment spacing on $productSlug assignment in baseProduct() per code style guide
  • [4.99.3] refactor(eshop): simplify baseCheckout() in Adv_eshop — extract handleGetResponse() and handleCheckoutHandle()

    • Replaced if/elseif/else chain with early-return dispatch and two dedicated protected helpers
    • handleGetResponse(array $params): bool — isolates payment gateway callback routing; uses strict in_array() for the simple-gateway list (paybybank, stripe, iris)
    • handleCheckoutHandle(array $params): bool — isolates the handle action with a guard for missing params
    • baseCheckout() now reads as a plain dispatch table: get_response → handle → checkout page
    • Both helpers are protected so client repos can override individual actions without duplicating the full method
    • Added array type hint and : bool return type to baseCheckout(); removed unreachable return false
  • [4.99.3] refactor(eshop): simplify baseCategory() in Adv_eshop — extract parseCategorySlugAndOffset()

    • Extracted parseCategorySlugAndOffset(string $method): array protected helper to isolate the pagination offset detection logic; client repos can override it independently
    • Collapsed the duplicated if/else branches in baseCategory() into a single code path — one cache query, one dispatch
    • Added string type hint and : bool return type to baseCategory(), consistent with baseProduct()
    • No functional change — behaviour is identical
  • [4.99.3] feat(cart): add Cart domain with DB persistence and REST endpoints

    • New shop_cart and shop_cart_item tables (Phinx migration)
    • Cart + CartItem domain layers (Entity, Repository, Service, WriteService, WriteData, Validator)
    • CartService orchestrator: resolveCart, getOrCreateCart, addItem, updateItemQty, removeItem, clearCart, applyCoupon, removeCoupon, claimCart (guest→customer merge)
    • CartTotalsCalculator: computes item-level subtotals from product prices
    • REST Cart controller with 8 endpoints: GET /rest/cart, POST/PUT/DELETE items, POST/DELETE coupon, POST claim
    • Guest cart support via server-generated cart_token (X-Cart-Token header)
    • DI registration (domain + REST containers) and 16 routes (8 plain + 8 locale-prefixed)
  • [4.99.3] feat(auth): add customer registration, profile, and wishlist write endpoints

    • POST /rest/auth/customer/register — email, password, firstName, lastName, phone, newsletter
    • GET /rest/customer/me, PUT /rest/customer/me, PUT /rest/customer/me/password
    • Wishlist write endpoints with customer scope enforcement
    • Order cancel endpoint: PUT /rest/order/order/{id}/cancel (customer-scoped, cancellable statuses only)
  • [4.99.3] feat(checkout): add stateless checkout orchestration with shipping, coupon, and order placement

    • ShippingCalculator: queries Transporter domain for available carriers and costs
    • CouponValidator: wraps legacy Adv_coupons_model for coupon validation
    • PlaceOrderService: orchestrates cart→order conversion (validate cart, coupon, shipping, create order, init payment, clear cart)
    • PlaceOrderData/PlaceOrderResult/Address/PaymentRedirect value objects
    • REST endpoints: POST /rest/checkout/shipping, /coupon/validate, /totals, /place-order
    • 36 tests covering ShippingCalculator, CouponValidator, PlaceOrderService
  • [4.99.3] feat(payment): add payment adapter pattern for headless checkout

    • PaymentAdapterInterface + PaymentInitializer registry + PaymentInitializerFactory
    • Offline adapters: DeliveryAdapter, BankTransferAdapter, PaidAtStoreAdapter
    • Gateway adapters: StripeAdapter (Checkout session), VivaWalletAdapter, PayPalAdapter
    • GET /rest/checkout/payment-status/{orderId} endpoint
    • PaymentInitializerFactory conditionally registers gateway adapters from Registry config
    • 25 PaymentInitializer tests
  • [4.99.3] feat(checkout): add guest checkout and order tracking

    • Guest checkout: nullable customerId in PlaceOrderData, guest customer creation via Customer WriteService
    • Guest fields: customerEmail, guestFirstName, guestLastName, guestPhone
    • Order tracking endpoint: GET /rest/order/order/{id}/tracking (customer-scoped)
    • 8 GuestCheckout tests
  • [4.99.3] fix(domains): address code review findings for headless commerce

    • Fix shop_customers→shop_customer table name in registration (E1)
    • Add 8 missing language-prefix cart routes (E3)
    • Generic 500 error messages instead of leaking exceptions (E4)
    • Guest email validation with filter_var (E6)
    • Consistent sendError() usage for all error responses (E7)
    • Server-side cart token generation via bin2hex/random_bytes (W5)
    • Newsletter field in customer registration (W9)
    • CartItemWriteRepository.deleteByCartId() replaces direct DB in clearCart (W3)
    • PlaceOrderService uses CustomerRepository + CustomerWriteService instead of direct DB (W4)
    • CartTotalsCalculator returns {subtotal, itemCount} only — coupon placeholder removed (W11)
  • [4.99.3] deps(composer): bump ecommercen/media-stream from 3.0.0 to 4.0.0

    • Controller renamed: GenericMediaControllerMediaController (route updated in routes.yaml)
    • services.yaml reduced from ~270 lines to 7 — library now exports all DI config via @vendor/services-library.yaml; host keeps only parameter overrides
  • [4.99.3] docs(changelog): standardise entries ordering to descending by version (newest first)

    • Both the main entries list and the `

Notes

  • [4.99.16] Toolchain bump: package.json engines now requires Node 24.13+ / npm 11.8+. Local dev environments and CI runners must upgrade before npm ci will pass strict-engines mode.

  • [4.99.16] Check for overrides (REST product/review RBAC alignment — #56):

    • application/config/rest_policies.phpReview controller defaults changed from [ADMIN, PRODUCTS] to [ADMIN, MARKETING]. Any client rest_policies.php override that pinned the old role set will reapply the divergence.
  • [4.99.16] Check for overrides (REST stateless session loading fix):

    • ecommercen/core/Controller.php
    • ecommercen/core/Adv_admin_controller.php
    • ecommercen/core/Adv_front_controller.php
    • ecommercen/advisable/controllers/Adv_login.php
    • ecommercen/sso/controllers/AdvSso.php
    • Behavior change for any client controller that extends Base_c directly (i.e. not via Front_c or Admin_c) AND reads $this->session/$this->cart/$this->advauth — must add $this->load->library(['session', ...]) in its own constructor. The two upstream outliers (Adv_login, AdvSso) already include the explicit load.
  • [4.99.15] Check for overrides (Review WriteService + Rule 13 model fixes — #174, #207, #204):

    • src/Domains/Product/Review/WriteService.php$entity->product_id reference corrected (was productId; aggregator hook was silently no-op)
    • ecommercen/eshop/models/Adv_gifts_model.phpgetCheapestRequirementProductInCart now skips null final_price; new public method getProductIdsByCodeIds(array $codeIds): array
    • ecommercen/eshop/models/Adv_order_model.phpadjustCartForRule13Gifts now resolves product_code_id → product_id for fallback cart-trim matching
  • [4.99.15] Check for overrides (FilterByTranslation LIKE wildcard fix):

    • src/Domains/Ai/Position/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Blog/Article/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Blog/Author/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Blog/Category/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Blog/Tag/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Document/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Page/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Subcontent/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Video/Repository/Specification/FilterByTranslation.php
    • src/Domains/Country/Country/Repository/Specification/FilterByTranslation.php
    • src/Domains/Country/County/Repository/Specification/FilterByTranslation.php
    • src/Domains/Map/Location/Repository/Specification/FilterByTranslation.php
    • src/Domains/Map/Map/Repository/Specification/FilterByTranslation.php
    • src/Domains/Product/Product/Repository/Specification/FilterByTranslation.php
    • src/Domains/Product/ProductList/Repository/Specification/FilterByTranslation.php
    • src/Domains/Slider/Slide/Repository/Specification/FilterByTranslation.php
    • src/Domains/Slider/Slider/Repository/Specification/FilterByTranslation.php
  • [4.99.15] Check for overrides:

    • ecommercen/plus/controllers/AdvPlusAlgoPrioritySettings.php
    • public/api-docs.html
    • src/Domains/Support/Request/QueryListBuilder/FilterRequest.php
    • src/Domains/Support/Request/QueryListBuilder/GenerateListRequest.php
    • src/Domains/Product/Category/Service.php
    • src/Domains/Product/Product/Service.php
    • src/Domains/Product/Product/ListRequest.php
    • src/Domains/Product/Product/Repository/Specification/FilterByBarcode.php
    • src/Domains/Product/Product/Repository/Specification/FilterByCategory.php
    • src/Rest/Product/Controllers/Product.php
    • application/config/rest_api_versions.php
  • [4.99.14] Check for overrides (VAT write layer):

    • src/Domains/Order/Vat/Repository/WriteRepository.php
    • src/Domains/Order/Vat/Validator.php
    • src/Domains/Order/Vat/WriteService.php
  • [4.99.14] Check for overrides (REST batch — cart, collections, product, checkout):

    • src/Domains/Order/Order/Repository/Entity.php
    • src/Domains/Order/Order/WriteData.php
    • src/Rest/Cart/Resources/Cart/Collection.php
    • src/Rest/Cart/Resources/CartItem/Collection.php
    • src/Rest/Cms/Resources/Blog/Article/Collection.php
    • src/Rest/Cms/Resources/Blog/Author/Collection.php
    • src/Rest/Cms/Resources/Blog/Category/Collection.php
    • src/Rest/Cms/Resources/Blog/Comment/Collection.php
    • src/Rest/Cms/Resources/Blog/Tag/Collection.php
    • src/Rest/Cms/Resources/Document/Collection.php
    • src/Rest/Cms/Resources/Page/Collection.php
    • src/Rest/Cms/Resources/Subcontent/Collection.php
    • src/Rest/Country/Resources/Country/Collection.php
    • src/Rest/Country/Resources/County/Collection.php
    • src/Rest/Customer/Resources/Customer/Collection.php
    • src/Rest/Map/Resources/Location/Collection.php
    • src/Rest/Map/Resources/Map/Collection.php
    • src/Rest/Order/Resources/Basket/Collection.php
    • src/Rest/Order/Resources/Order/Collection.php
    • src/Rest/Order/Resources/Store/Collection.php
    • src/Rest/Order/Resources/Vat/Collection.php
    • src/Rest/Product/Resources/Attribute/Collection.php
    • src/Rest/Product/Resources/AttributeGroup/Collection.php
    • src/Rest/Product/Resources/Badge/Collection.php
    • src/Rest/Product/Resources/Barcode/Collection.php
    • src/Rest/Product/Resources/Category/Collection.php
    • src/Rest/Product/Resources/Line/Collection.php
    • src/Rest/Product/Resources/Media/Collection.php
    • src/Rest/Product/Resources/Product/Collection.php
    • src/Rest/Product/Resources/ProductCode/Collection.php
    • src/Rest/Product/Resources/ProductCodeAttribute/Collection.php
    • src/Rest/Product/Resources/Promo/Collection.php
    • src/Rest/Product/Resources/Supplier/Collection.php
    • src/Rest/Product/Resources/Tag/Collection.php
    • src/Rest/Product/Resources/TagCategory/Collection.php
    • src/Rest/Product/Resources/Vendor/Collection.php
    • src/Rest/Slider/Resources/Group/Collection.php
    • src/Rest/Slider/Resources/Slide/Collection.php
    • src/Rest/Slider/Resources/Slider/Collection.php
  • [4.99.11] REQUIRES npm run admin-production:

    • assets/admin/js/order-gifts-block.js — fix rule 13 price deduction when gift consumes 100% of a cart row (newQty &lt;= 0newQty &lt; 0); without rebuild, admin order total will not reflect the deduction in that scenario
    • assets/admin/js/jobs/jobs.vue — reads max_retries instead of undefined job_retry_times; without rebuild, "Retries" column in the job panel shows blank
  • [4.99.11] Check for overrides: Client repos that override any REST Resource class should verify the type casts and !== null guards match. 74 Resource files changed. Key patterns: (int) casts on all ID fields, (float) on price/numeric fields, (bool) on flag fields, formatDate() on all date columns. Also check overrides of:

    • src/Rest/Support/Resources/BaseResource.php (formatDate integer timestamp support)
    • src/Rest/Cart/Controllers/Cart.php (refactored to use CartResource)
    • src/Rest/Cart/Resources/Cart/Resource.php
    • src/Rest/Cart/Resources/CartItem/Resource.php
    • src/Rest/Cart/Resources/CartTotals/Resource.php
    • src/Rest/Cms/Resources/Blog/Article/MuiResource.php
    • src/Rest/Cms/Resources/Blog/Author/MuiResource.php
    • src/Rest/Cms/Resources/Blog/Author/Resource.php
    • src/Rest/Cms/Resources/Blog/Category/MuiResource.php
    • src/Rest/Cms/Resources/Blog/Category/Resource.php
    • src/Rest/Cms/Resources/Blog/Tag/MuiResource.php
    • src/Rest/Cms/Resources/Document/MuiResource.php
    • src/Rest/Cms/Resources/Page/MuiResource.php
    • src/Rest/Cms/Resources/Page/Resource.php
    • src/Rest/Cms/Resources/Subcontent/MuiResource.php
    • src/Rest/Country/Resources/Country/Resource.php
    • src/Rest/Customer/Resources/Customer/Resource.php (scope changes: pointFactor → public, totalPoints → authenticated)
    • src/Rest/Map/Resources/Location/MuiResource.php
    • src/Rest/Map/Resources/Map/MuiResource.php
    • src/Rest/Order/Controllers/Order.php (tracking() now formats dates)
    • src/Rest/Order/Resources/Basket/Resource.php
    • src/Rest/Order/Resources/Store/MuiResource.php
    • src/Rest/Order/Resources/Tracking/Resource.php
    • src/Rest/Product/Resources/Attribute/MuiResource.php
    • src/Rest/Product/Resources/AttributeGroup/MuiResource.php
    • src/Rest/Product/Resources/Badge/MuiResource.php
    • src/Rest/Product/Resources/Badge/Resource.php
    • src/Rest/Product/Resources/Barcode/Resource.php
    • src/Rest/Product/Resources/Category/MuiResource.php
    • src/Rest/Product/Resources/Line/MuiResource.php
    • src/Rest/Product/Resources/Media/Resource.php
    • src/Rest/Product/Resources/Product/MuiResource.php
    • src/Rest/Product/Resources/ProductCode/Resource.php
    • src/Rest/Product/Resources/Promo/MuiResource.php
    • src/Rest/Product/Resources/Supplier/MuiResource.php
    • src/Rest/Product/Resources/Tag/MuiResource.php
    • src/Rest/Product/Resources/TagCategory/MuiResource.php
    • src/Rest/Product/Resources/Vendor/MuiResource.php
    • src/Rest/Slider/Resources/Slide/MuiResource.php
    • src/Rest/Slider/Resources/Slider/MuiResource.php
    • src/Rest/Slider/Resources/Slider/Resource.php
    • src/Rest/Support/Resources/PaginationLinks.php
    • src/Rest/Support/Resources/PaginationMeta.php
  • [4.99.11] Client repos with custom max_upload_size in application/config/app.php: The default changed from '19000000' to 19000. The value is in KB (all upload paths multiply by 1024). If your client overrides this value, ensure it's in KB, not bytes.

  • [4.99.10] No action required. Cleanup only — removes two dead lines from application/config/rest_policies.php (stale import and policy entry for the already-deleted Keyword REST controller). No runtime behaviour changes.

  • [4.99.10] Check for overrides: Client repos that override any MuiWriteRepository must update insertForEntity() to call $this->insert($translationData) instead of $this->db->insert($this->table, $translationData). Affects all 38 MuiWriteRepository files. Also check overrides of:

    • src/Domains/Support/Repository/BaseWriteRepository.php (null filtering on insert)
    • src/Domains/Product/Product/WriteService.php (date_changed auto-set)
    • 9 MuiWriteData files: Slide, Page, Document, Subcontent, Location, Map, ProductList, Line, Promo (null→'' for NOT NULL columns)
  • [4.99.10] Check for overrides:

    • application/controllers/RouterDispatcher.php
    • src/Rest/Middleware/PolicyResolver.php
    • src/Rest/Middleware/RequestContext.php
    • src/Rest/OpenApi.php
    • src/Rest/Versioning/VersionResolver.php
    • src/Rest/Versioning/VersionResult.php
  • [4.99.10] Check for overrides:

    • ecommercen/auth/models/Adv_roles_model.php
    • src/Domains/Admin/Role/ListRequest.php
    • src/Domains/Admin/Role/Repository/Entity.php
    • src/Domains/Admin/Role/Repository/Repository.php
    • src/Domains/Admin/Role/Repository/RepositoryConfigurator.php
    • src/Domains/Admin/Role/Service.php
    • src/Rest/Admin/Resources/Role/Collection.php
    • src/Rest/Admin/Resources/Role/Resource.php
  • [4.99.10] REQUIRES php migrator.php migrate — roles table creation (migration 20260409131959_create_roles_table.php): Creates the roles table, seeds 9 canonical roles, cleans orphan user_role rows, and adds an FK constraint on user_role.role_id. Must run before deploying the new code — the legacy Adv_roles_model and modern Role\Repository both read from this table.

  • [4.99.10] Breaking: auth_roles config removed from application/config/auth.php. Any client code that reads config_item('auth_roles') must be updated to query the roles DB table instead. The AUTH_ROLE_* constants in constants.php are unchanged.

  • [4.99.10] Client repos with a RoleProvider override: RoleProvider is deprecated and no longer registered in the DI container. Client repos that override or extend it must migrate to Role\Service or Role\Repository.

  • [4.99.10] Actions: php migrator.php migrate

    • 20260409131959_create_roles_table.php
  • [4.99.10] REQUIRES php migrator.php migrate — ROW_FORMAT=DYNAMIC follow-up (#187): Clients that already ran the MyISAM→InnoDB migration before this fix may have tables with COMPACT row format. Migration 20260415102932 detects and fixes this automatically — no-op if tables are already InnoDB DYNAMIC.

  • [4.99.10] Actions: php migrator.php migrate

    • 20260415102932_ensure_innodb_dynamic_row_format.php
  • [4.99.8] REQUIRES php migrator.php migrate — MyISAM → InnoDB conversion (migration 20260408100114_convert_myisam_tables_to_innodb.php): The migration runs ALTER TABLE ... ENGINE=InnoDB ROW_FORMAT=DYNAMIC on 10 tables. On large deployments the shop_product_mui table may be very large and the conversion will cause a full table rebuild — plan to run this during low-traffic hours. The migration is reversible (reverts to MyISAM). No code changes required.

  • [4.99.8] Actions: php migrator.php migrate

    • 20260406150133_drop_shop_export_table.php
  • [4.99.8] Check for overrides:

    • src/Domains/Admin/Role/RoleProvider.php
    • src/Domains/Admin/User/ListRequest.php
    • src/Domains/Admin/User/Repository/Entity.php
    • src/Domains/Admin/User/Repository/Repository.php
    • src/Domains/Admin/User/Repository/RepositoryConfigurator.php
    • src/Domains/Admin/User/Repository/WriteRepository.php
    • src/Domains/Admin/User/Service.php
    • src/Domains/Admin/User/Validator.php
    • src/Domains/Admin/User/WriteData.php
    • src/Domains/Admin/User/WriteService.php
    • src/Domains/Admin/UserRole/Repository/Entity.php
    • src/Domains/Admin/UserRole/Repository/Repository.php
    • src/Domains/Admin/container.php
    • src/Rest/Admin/Controllers/Role.php
    • src/Rest/Admin/Controllers/User.php
    • src/Rest/Admin/Resources/User/Collection.php
    • src/Rest/Admin/Resources/User/Resource.php
    • src/Rest/Admin/container.php
  • [4.99.8] Check for overrides:

    • src/Domains/Support/Auth/AuthHashService.php
    • src/Domains/Support/Auth/container.php
  • [4.99.8] REQUIRES php migrator.php migrate — Order export feature removed (VULN-05): The shop_export table is dropped by migration. Run php migrator.php migrate on all environments after deploying. The entire order export feature has been removed — the admin "Create Export" route (orders_admin/create_export) no longer exists. Client repos should check for any custom code that calls set_export_data(), get_export_data(), Webrun::generate_xsl(), or Webrun::generate_xsl_for_geniki() — these methods are gone.

  • [4.99.7] REQUIRES php migrator.php migrate + composer install — Keywords module removed: The keywords table is dropped by migration. Run php migrator.php migrate on all environments after deploying. Run composer install to clean up the autoloader. Any client repo that overrides application/modules/keywords/ or application/modules/keywords_model/ should remove those overrides — they will be dead code.

  • [4.99.6] REQUIRES composer install — New ClearExpiredSessions job added. After merging to a client repo, run composer install so the autoloader picks up AdvClearExpiredSessions and ClearExpiredSessions. The job is scheduled daily at 3 AM in the core queue. Clients can override ClearExpiredSessions in application/modules/job/libraries/ if needed.

  • [4.99.6] Payment webhooks require STRIPE.WEBHOOK_SECRET Registry key for Stripe signature verification. Configure in the admin Registry panel under STRIPE. VivaWallet verification uses existing credentials. PayPal webhook signature verification is stubbed (TODO).

  • [4.99.6] Offline payment adapters (delivery, bank_transfer, paid_at_store) now return PENDING_ACCEPTED status instead of PENDING. This aligns with legacy checkout behavior where stock is reduced immediately for offline payments. API consumers polling payment-status will see this change.

  • [4.99.6] Check for overrides:

    • src/Domains/Checkout/PlaceOrderService.php
    • src/Domains/Checkout/PaymentConfirmationService.php
    • src/Domains/Checkout/StockService.php
    • src/Domains/Checkout/OrderBasketBuilder.php
    • src/Domains/Checkout/container.php
    • src/Domains/Order/OrderBasket/WriteService.php
    • src/Domains/Order/OrderBasket/WriteData.php
    • src/Domains/Order/OrderBasket/Repository/WriteRepository.php
    • src/Domains/Order/container.php
    • src/Domains/Promotion/CouponCode/Repository/WriteRepository.php
    • src/Domains/Checkout/Payment/Adapters/DeliveryAdapter.php
    • src/Domains/Checkout/Payment/Adapters/BankTransferAdapter.php
    • src/Domains/Checkout/Payment/Adapters/PaidAtStoreAdapter.php
    • src/Rest/Webhooks/Controllers/Webhook.php
    • src/Rest/Webhooks/container.php
    • src/Rest/Checkout/Controllers/Checkout.php
    • application/config/rest_routes.php
    • application/config/container/modules.php
  • [4.99.6] Check for overrides:

    • src/Domains/Cms/Blog/Article/WriteService.php
    • src/Domains/Cms/Blog/Author/WriteService.php
    • src/Domains/Cms/Blog/Category/WriteService.php
    • src/Domains/Cms/Blog/Tag/WriteService.php
    • src/Domains/Cms/OfferCategory/WriteService.php
    • src/Domains/Cms/Video/WriteService.php
    • src/Domains/Event/Category/WriteService.php
    • src/Domains/Event/Event/WriteService.php
    • src/Domains/Map/Map/WriteService.php
    • src/Domains/Product/Category/WriteService.php
    • src/Domains/Product/Line/WriteService.php
    • src/Domains/Product/Product/WriteService.php
    • src/Domains/Product/Promo/WriteService.php
    • src/Domains/Product/Tag/Category/WriteService.php
    • src/Domains/Product/Tag/Tag/WriteService.php
    • src/Domains/Product/Vendor/WriteService.php
    • src/Domains/Support/Slug/GeneratesSlugs.php
    • src/Domains/Support/Slug/SlugGenerator.php
    • src/Domains/Support/Slug/container.php
    • src/Rest/Support/Controllers/HandlesUploadActions.php
  • [4.99.5] REST controllers bypass site mode guard automatically via the new siteModeAllowedNamespaces config key in application/config/app.php. Client repos add extra namespaces (e.g. Custom\Rest\, Custom\Certus\) directly in their config — no SiteModeMiddleware subclass needed.

  • [4.99.5] Client Apache deployments using CGI/FastCGI must add SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 to their public/.htaccess for REST JWT authentication to work. The line is now included in .htaccess.example. Docker deployments are unaffected.

  • [4.99.4] Breaking change: All REST update endpoints now use POST instead of PUT. API consumers must update their HTTP method for update calls. The URL pattern remains the same (/rest/{context}/{entity}/{id}).

  • [4.99.4] File upload support requires no database changes. Existing file columns already store bare filenames. The UploadService reuses the same UploadValidator + UploadFileNameGenerator + Storage (Flysystem) pipeline as legacy admin controllers.

  • [4.99.4] Controllers with file fields use HandlesUploadActions instead of HandlesWriteActions — do NOT use both traits together. See .claude/rules/rest-layer.md for the full pattern.

  • [4.99.4] Needs npm installblock now follow the same descending order:[4.99.3][4.99.2][4.99.1][4.99.0]`

    • Changelog.4.99.md reordered accordingly
    • update-docs SKILL.md updated with new Entries Ordering section documenting the rule (prepend new entries at the top, never append)
  • [4.99.3] refactor(product): replace hardcoded table name strings with property references in Adv_product_model

    • 'shop_product_product_tags'$this->productTagLp in tag join query
    • 'shop_product'$this->table in product FROM clause
    • 'shop_prices_view'$this->viewPrices in 6 join calls across multiple query methods
    • 'product_code_attributes'$this->tableProductCodeAttributes in code attribute query
    • No functional change — improves internal consistency and reduces risk of typo-driven table name drift
  • [4.99.3] refactor(product): clean up property declarations in Adv_product_model

    • Remove unused $tableShelfCodes property ('shop_product_shelfcodes') — never referenced in this class; the table is used independently via its own property in Adv_order_basket_model
    • Remove unused $tableSupplier property ('shop_supplier') — unreferenced anywhere in the codebase
    • Add explicit string type declarations to all 31 previously untyped table/view name properties, consistent with the 3 properties that were already typed
    • Change $tableBarcodes visibility from public to protected — never accessed externally; all other models with a tableBarcodes property declare it protected independently
    • No functional change — internal, non-API-facing cleanup only
  • [4.99.3] refactor(product): remove 5 dead methods from Adv_product_model

    • get_random_mui_records() — thin wrapper over get_mui_records() with no callers
    • whereBarCodesIn() — single-line helper with no callers
    • create_slug() — carried a @TODO deprecate comment since 2017; zero callers (other models maintain independent implementations)
    • getMaxDiscountPerCategory() — empty stub returning [], zero callers
    • setOrderBy() — pass-through to $this->db->order_by() with no callers on this model
  • [4.99.3] refactor(product): remove batch_master_update() snake_case wrapper and formalize batchMasterUpdate() signature

    • Remove batch_master_update($products, $change_row, $with_value) from Adv_product_model — was a 3-argument snake_case wrapper that simply delegated to batchMasterUpdate()
    • Add public visibility and : void return type to batchMasterUpdate(array $productIds, array $updateData) — it was already used publicly but had no explicit declaration
    • Update all 5 callers in Adv_products_admin.php to call batchMasterUpdate() directly with the correct array parameter signature
  • [4.99.3] feat(domains): CQRS write layer rollout — POST/PUT/DELETE endpoints for 89 writable domains

    • Adds WriteData DTO, MuiWriteData DTO (MUI domains), Validator, WriteRepository, MuiWriteRepository, and WriteService to all writable domain entities
    • REST controllers gain HandlesWriteActions trait (store, update, destroy actions)
    • 426 new files across src/Domains/*/, 118 modified files (containers, controllers, routes)
    • application/config/rest_routes.php — 262 routes uncommented + 352 new POST/PUT/DELETE routes added
    • Country and County use string PKs (alpha_2, county_alpha) — WriteData includes PK field, WriteService uses PK from data instead of insert_id()
    • Excluded 15 read-only domains: all Marketplace/* (9), PriceTracking, OrderBasket, DhlVoucher, SmartPoint, Builder, ProductCode
  • [4.99.3] fix(domains): code review corrections for WriteService rollout

    • Fix BlogArticle route class reference (BlogArticleControllerBlogArticle)
    • Add protectBackend auth guard to Order write endpoints (store/update/destroy)
    • Resolve OpenAPI schema name collisions by prefixing with context (e.g., CategoryEventCategoryWriteData)
    • Move Order and Product/Category write service DI registrations to correct container sections
  • [4.99.3] refactor(product): rename and optimise slug lookup methods in Adv_product_model

    • get_content($slug)existsBySlug(string $slug): bool — optimised existence check using SELECT 1 + get_where() on tableMui with LIMIT 1; no JOIN; returns bool instead of object|false
    • get_content_url($slugAsUrl)findSlugsByUrl(string $slugAsUrl): ?object — joins tableVendorsMui to also return vendor_slug; applies TRIM(BOTH '/' FROM url) in WHERE clause; returns ?object instead of object|false
  • [4.99.3] feat(product): add existsBySlug() to Adv_product_category_model

    • New method existsBySlug(string $slug): bool — optimised existence check using SELECT 1 + get_where() on tableMui with LIMIT 1
    • Does NOT replace get_content(), which is still used where the full row is needed
  • [4.99.3] refactor(eshop): rewrite baseProduct() and setCanonicalUrl() in Adv_eshop.php / Adv_products.php

    • baseProduct(): cleaner slug extraction via strrpos; short-circuits category lookup (only queries if product found first); query2 branch now calls canonicalProduct() with vendor URL (was incorrectly calling index()); updated callers use existsBySlug() on both product_model and product_category_model
    • baseCategory(): updated caller to use product_category_model::existsBySlug()
    • setCanonicalUrl() (Adv_products.php): always sets canonical URL — removed isCanonical flag dependency; prefers product's own url field; falls back to {vendorsBaseUrl}/{vendor_slug}/{slug}; uses site_url() instead of current_url()
  • [4.99.3] fix(eshop): use existsBySlug() in the paginated branch of baseCategory() in Adv_eshop.php

    • The paginated branch was still calling product_category_model::get_content() (full row fetch) while the non-paginated branch already used existsBySlug(); both branches now use the optimised existence check for consistency
  • [4.99.3] fix(admin): trim leading/trailing slashes from product URL field on insert and update

    • getCreateProductDataMuiPost() and getEditProductDataMuiPost() in Adv_products_admin.php now strip leading and trailing slashes from the URL field before persisting
  • [4.99.3] feat(product-url): honour shop_product_mui.url field everywhere — product cards, feeds, sitemap

    • transformProductForJson() in theme_helper.php uses match(true) priority: $product->url$url param → vendors_base_url_{lang}/{vendor_slug}/{slug}category_slug/slug; replaces hardcoded "vendors/" prefix with vendors_base_url_{lang} config item
    • All six product card views (product_card.php, product_card_fashion.php, nav_product_card.php, product_combo_card.php, product_bundle_combo_card.php) and order_item.php updated to same priority logic
    • ProductVariationsTrait.php updated to respect url field and use vendors_base_url_{lang} config
    • AdvXml.php base class: new resolveProductUrl(object $product): string method with identical priority logic; called in parseItem(); all 11 XML feed controllers (AdvAgoraCatalog, AdvApiSearchXml, AdvBestPriceXml, AdvContactpigeonXml, AdvCriteoCatalog, AdvEmagXml, AdvGoogleCatalog, AdvLinkWiseXml, AdvManagoXml, AdvSkroutzXml, AdvWellcommXml) now call $this->resolveProductUrl($product) instead of building URLs inline
    • OutputGoogleProductReviewXml.php: inline ternary for url field (not in AdvXml hierarchy)
    • Adv_product_model::getFeedProducts() and getProductsForSiteMap(): {$this->tableMui}.url added to SELECT
    • AdvGenerateSitemaps.php: uses url field when set; falls back to vendors_base_url_{lang}/{vendor_slug}/{slug}
  • [4.99.3] refactor(theme-helper): remove redundant vendor $url param from 20 callers of transformProductForJson()

    • transformProductForJson() now resolves vendor URLs natively via match(true) priority logic using vendor_slug + vendors_base_url_{lang} config
    • Removed "vendors/{$vendor_slug}" as the $url argument from 20 call sites across views and one controller
    • The $url param remains valid where it carries genuine information (e.g. category path override via $catPathsDb[$category_id])
    • Files updated: reels/reels.php, promo/promo.php (×2), buy_the_look_list.php, product_fashion.php, product.php, reviews.php, Adv_reels.php, blog_post.php, stream_slider.php, products_slider.php, nav_products_slider.php, home_tabs_products_slider.php, products_list.php, content_embeddings/product_list.php, components/library/products_list/products_list.php, components/library/sliders/products_slider.php, layouts/library/customer/reviews.php, layouts/library/product_page/product.php, main/components/content_embeddings/product.php
  • [4.99.2] feat(domains): introduce CQRS write layer — Phase 1: infrastructure and mass migration

    • New ReadServiceInterface and WriteServiceInterface — the old combined ServiceInterface is deleted; 130+ domain services migrated to ReadServiceInterface, dead write stubs removed
    • New BaseWriteRepository base class for write-side persistence (insert, update, delete)
    • New ValidationException for structured domain validation errors
    • New HandlesWriteActions trait for REST controllers — provides standard store(), update(), destroy() actions with OpenAPI-annotated responses
    • Cms/Page proof-of-concept: WriteData / MuiWriteData DTOs, Validator, WriteRepository, MuiWriteRepository, WriteService with transactional create/update/delete; REST controller updated with HandlesWriteActions
    • 67 new unit tests covering the write layer infrastructure and Page write service
  • [4.99.2] feat(domains): CQRS write layer — Phase 3 & 4: Product Badge and Cms Subcontent

    • Product/Badge write layer: WriteData DTO, Validator, WriteRepository, MuiWriteRepository, WriteService; DI container and write routes registered
    • Cms/Subcontent write layer: extracted existing write logic from read Repository/Service into clean CQRS pattern; slug uniqueness validation preserved
    • 129 new tests across both domains
  • [4.99.2] fix(openapi): fix SecurityScheme param name and regenerate spec

    • Fixed pre-existing bug: securitySchemeIdsecurityScheme (invalid param in swagger-php 5.8)
    • Regenerated openapi.json with all write endpoints included
  • [4.99.2] docs(openapi): add write request body schemas with consistent field names

    • Added OA\Schema annotations to WriteData / MuiWriteData DTOs for POST/PUT request body documentation
    • Aligned write property names with read response field names (parentId, published, content, builderId)
    • fromArray() accepts both documented API names and legacy DB column names for backward compatibility
  • [4.99.2] docs(openapi): add missing fields to PageMuiResource read schema

    • Added 6 missing fields to PageMuiResource OA schema: pageId, metaKeywords, metaDescription, metaTitle, url, redirectUrl
  • [4.99.1] fix(tests): reset PHP time limit after DeferredTaskRunner tests

    • DeferredTaskRunner::run() calls set_time_limit() which persists globally across the test suite
    • Tests testSkipsTaskThatExceedsBudget (budget=2 s) and testAllTasksSkippedWhenBudgetTooSmall (budget=1 s) left PHP's execution time limit at 1–2 seconds, killing all subsequent tests with "Fatal error: Maximum execution time exceeded"
    • Add tearDown() to DeferredTaskRunnerTest that resets the limit via set_time_limit(0)
    • No production code changed — test file only
  • [4.99.1] fix(tests): pad JWT test key to meet HS256 minimum length

    • firebase/php-jwt v7.0.3 (pinned in composer.lock since 4.99.0) enforces RFC minimum key lengths — HS256 requires >= 256 bits (32 bytes)
    • Test key 'test-secret-key-for-unit-tests' was 30 bytes, causing DomainException: Provided key is too short on all 5 tests in TokensTest that call generateAccessToken()
    • Fix: padded key to exactly 32 bytes — 'test-secret-key-for-unit-tests!!'
    • No production code changed — test file only
  • [4.99.0] feat: Add pd() pretty dump debug helper with collapsible HTML tree

  • [4.99.0] perf: Eliminate redundant DB round-trips in gifts model

  • [4.99.0] fix(sliders): use strict false check for pscache miss in sliders model

  • [4.99.0] perf: Cache checkIfTableExists result in products_in_cart_model

  • [4.99.0] perf: Cache countries/counties queries via pscache

  • [4.99.0] perf: Cache AdvCartResource::getData() and hoist shared liveData

  • [4.99.0] chore: Remove debug profiler from Base_c

  • [4.99.0] perf: Cache Symfony DI container in all non-development environments

  • [4.99.0] refactor: Optimize product category get_depth method

    • Reduce cached data overhead in depth calculation
    • Minimize memory usage for category hierarchy traversal
    • Improve method efficiency without affecting functionality
  • [4.99.0] perf: Optimize and fix bugs in product category depth and combo methods

    • Fix operator precedence bug (!x == y) in get_depth and get_childs_combo
    • Replace recursive get_depth with iterative loop using a keyed parent map
    • Add getCategoryParentMap() for lightweight tree traversal (id+parent_id only)
    • Fix $cat->id → $cat->category_id bug in edit() after getCategoryParentMap refactor
    • Refactor get_childs_combo with typed params and simplified filter logic
    • Update all callers to use named arguments with new typed signature
  • [4.99.0] refactor: Rename cache config items and env vars with long_lived tier prefix

    • cache_adapter → cache_long_lived_adapter
    • cache_dir → cache_long_lived_connection
    • cache_default_expires → cache_long_lived_default_expires
    • cache_search_expires → cache_long_lived_search_expires
    • cache_delete_all_use_script → cache_long_lived_delete_all_use_script
    • cache_size_limit → cache_long_lived_size_limit
  • [4.99.0] feat: Three-tier cache architecture (long-lived, cross-request, in-memory)

    • cache.long_lived: pscache/query results (FileAdapter, RedisAdapter, RedisClusterAdapter)
    • cache.cross_request: circuit breakers, rate limiting (ApcuAdapter, ShmAdapter, Redis)
    • cache.in_memory: per-request memoization (InMemoryAdapter)
  • [4.99.0] perf: Add tenant prefix isolation for all cache adapters

  • [4.99.0] perf: Add dedicated SEO cache TTL config (cache_long_lived_seo_expires)

  • [4.99.0] feat: Add circuit breaker with cross-request cache backing

  • [4.99.0] refactor: Remove APP_CACHE_CROSS_REQUEST_SHM_PATH, reuse CONNECTION for ShmAdapter

    • Remove dedicated SHM_PATH env var; ShmAdapter uses CONNECTION as basePath
    • ShmAdapter now requires tenant prefix — throws if empty
    • Auto-detection: ShmAdapter only picked when explicit connection path is
  • [4.99.0] refactor: Add keyPrefix and app namespace to ShmAdapter

    • Default basePath now /dev/shm/ecommercen (app-namespaced)
    • Add $keyPrefix param for cache tier separation (cross_request, etc.)
    • Path structure: /dev/shm/ecommercen/<tenant>/<tier>/
  • [4.99.0] feat: Add DeferredTaskRunner, centralized circuit breakers, and post_system hook

    • DeferredTaskRunner: SplPriorityQueue with time budget, fastcgi_finish_request()
    • Centralized circuit breaker DI (Meta, Matomo, Manago, ProjectAgora) with
    • Moved ProjectAgora CB from project_agora.php to circuit_breakers.php
    • post_system hook wiring for deferred task execution
    • CircuitBreaker::getStatus() for admin observability
    • Unit tests for DeferredTaskRunner (8 tests)
  • [4.99.0] feat: Add configurable timeout overrides for Meta, Matomo, and Manago

  • [4.99.0] feat: Add circuit breakers inside services and defer all tracking calls

  • [4.99.0] perf: Enable Matomo bulk tracking — single deferred HTTP call per request

  • [4.99.0] Merge branch 'develop' into feature/db-roundtrip-optimizations

  • [4.99.0] feat(docker): Add APCu extension to PHP Docker images

  • [4.99.0] refactor(cache): rename cache tiers to L0/L1/L2 hierarchy

    • L0: in-memory, single-request lifetime (InMemoryAdapter)
    • L1: pod-local, cross-request (APCu/SHM) — no network dependency
    • L2: persistent, long-lived (file or Redis) — shareable across pods
    • cache_long_lived_* → cache_l2_*
    • cache_cross_request_* → cache_l1_*
    • cache.long_lived → cache.l2
    • cache.cross_request → cache.l1
    • cache.in_memory → cache.l0
    • APP_CACHE_LONG_LIVED_* → APP_CACHE_L2_*
    • APP_CACHE_CROSS_REQUEST_* → APP_CACHE_L1_*
  • [4.99.0] perf(circuit-breaker): move circuit breakers from L1 to L2 cache

  • [4.99.0] fix(cache): fall back L1 to L2 when pod-local cache is unavailable

  • [4.99.0] feat(cache): add isHealthy() probe to cache adapters and wire into readiness check

    • Add isHealthy(): bool to CacheAdapterInterface
    • RedisAdapter: ping with RedisException catch
    • RedisClusterAdapter: ping first master with RedisClusterException catch
    • FileAdapter: verify dir writable, or parent writable if dir missing
    • ShmAdapter: same writable check as FileAdapter
    • ApcuAdapter: check apcu_enabled()
    • InMemoryAdapter: always healthy
    • Healthz::ready() now checks cache.l1 and cache.l2 via DI container
    • Fix Solr check missing return after error response
  • [4.99.0] perf(opcache): add Composer-driven OPcache preload script

    • Classmap: preloads all app + vendor classes (~2400 files)
    • PSR-4: preloads app namespaces only (detects by path outside vendor/)
    • Compiled DI container: cache/container.php
    • Requires opcache.preload + opcache.preload_user in php.ini (manifest change)
  • [4.99.0] fix(docker): move opcache preload script to deploy dir

  • [4.99.0] fix(opcache): fix basePath resolution after script move to deploy dir

  • [4.99.0] fix(opcache): exclude CI3 app classes from preload and gate cache health check

    • Restrict classmap preloading to vendor/ classes only (no CI3 side effects)
    • Skip compiled DI container to avoid class redeclaration on require_once
    • PSR-4 app classes (src/) are safe — loaded by Composer autoloader, no
    • Gate readiness cache health check behind APP_HEALTHZ_CACHE_CHECK env var
  • [4.99.0] fix(opcache): clean up preloader warnings and skip dev-only directories

  • [4.99.0] fix(healthz): remove temporary cache check feature flag

  • [4.99.0] feat(tests): add k6 TTFB load testing scripts

    • k6 test script with custom TTFB metrics and p95 thresholds
    • .env.example with documented config options
  • [4.99.0] feat(tests): add k6 browser web vitals test and runner script

    • web-vitals.js: browser test for full page load metrics
    • run.sh: dotenv-aware runner (usage: tests/k6/run.sh ttfb.js)
    • Rename load profile vars to avoid k6 reserved K6_VUS/K6_DURATION
  • [4.99.0] feat(docker): add libheif for AVIF image support in libvips

  • [4.99.0] Merge remote-tracking branch 'origin/develop' into feature/db-roundtrip-optimizations

  • [4.99.0] chore(agents): set claude-sonnet-4-6 model for template-driven subagents

  • [4.99.0] docs(changelog): update changelog with wiki-to-docs migration entry

  • [4.99.0] refactor(config): split circuit breaker and deferred task config out of cache.php

    • Extract circuit breaker settings to application/config/circuit_breakers.php
    • Extract deferred task settings to application/config/deferred_tasks.php
    • Update container/circuit_breakers.php to load 'circuit_breakers' config
    • Update container/deferred_task.php to load 'deferred_tasks' config
    • cache.php now contains only L1/L2 cache configuration
  • [4.99.0] docs: add Claude Agent System link to Home and cross-link claude docs

  • [4.99.0] refactor(deferred-task): derive maxCostSeconds from service timeout configs

    • Add getMaxCostSeconds() to FacebookConversionService, MatomoTracking, Manago, ProjectAgoraHttpTrait
    • Store timeout/connectTimeout as properties in FacebookConversionService
    • Replace all hardcoded maxCostSeconds values at 11 call sites
    • Rename config key/env var to deferred_task_budget_seconds for clarity
  • [4.99.0] refactor(circuit-breaker): add seconds suffix to cooldown config names

    • Rename $cooldown → $cooldownSeconds, $maxCooldown → $maxCooldownSeconds in CircuitBreaker
    • Rename config keys: cb_{name}cooldown → cb_cooldown_seconds (and max)
    • Rename env vars: APP_CB_{NAME}COOLDOWN → APP_CB_COOLDOWN_SECONDS (and max)
    • Update DI container args and test named arguments to match
  • [4.99.0] feat(deferred-task): wire Monolog logger into DeferredTaskRunner

    • Add deferred-task channel config to monolog.php (threshold WARNING)
    • Create deferred_task.logger DI service and inject into the runner
    • Add env vars to .env.example
  • [4.99.0] chore(env): add commented-out Project Agora and Matomo log env vars

  • [4.99.0] feat(deferred-task): defer Advisable AI event calls after response flush

    • Defer bookmark, removeBookmark, rating, view, sessionToCustomerSwitch,
    • Defer addToCart, removeFromCart in AdvApiCartController
    • Add getAdvisableAIMaxCostSeconds() helper (TIMEOUT + CONNECT_TIMEOUT)
    • Purchase event uses PRIORITY_CRITICAL; all others PRIORITY_NORMAL
    • Recommendation (read) calls remain synchronous — not deferred
  • [4.99.0] build(docker): add poppler for PDF rendering support in libvips

  • [4.99.0] Merge remote-tracking branch 'origin/develop' into feature/db-roundtrip-optimizations

  • [4.99.0] fix(checkout): fix external transporter rates format in getExternalTransporterCost

  • [4.99.0] fix(cache): scope ApcuAdapter::clear() to tenant prefix

    • Use same APCUIterator + apcu_delete pattern already used by deleteByPattern()
    • Prevents cross-tenant cache invalidation in multi-tenant deployments
  • [4.99.0] fix(cache): fix ShmAdapter deleteByPattern/clearNamespace with MD5 filenames

    • Store the original key as 'k' field in the JSON payload alongside 'v' and 'e'
    • deleteByPattern() now iterates all .shm files and matches the stored
  • [4.99.0] docs(circuit-breaker): document half-open race as best-effort behavior

  • [4.99.0] Revert "docs(circuit-breaker): document half-open race as best-effort behavior"

  • [4.99.0] feat(cache): add storeIfAbsent() to CacheAdapterInterface

    • RedisAdapter/RedisClusterAdapter: SET NX / setnx()
    • ApcuAdapter: apcu_add()
    • ShmAdapter/FileAdapter: fopen('c+') + flock(LOCK_EX) + existence check
    • InMemoryAdapter: isset() guard (single-process, no race)
  • [4.99.0] fix(circuit-breaker): eliminate half-open race with atomic probe lock

    • Probe key (cb_{name}_probe) has TTL matching cooldown as safety net
    • recordFailure/recordSuccess clean up the probe key
    • Replace magic max(...) * 2 state TTL with injectable stateTtlBuffer
    • Simplify getStatus() nested ternary into if/elseif/else blocks
    • Updated tests with storeIfAbsent/exists mock wiring
  • [4.99.0] refactor(cache): unify adapter config via connection string with prefix precedence

    • FileAdapter: $basePath → $connectionString, parsed path defaults to ../cache/,
    • ShmAdapter: $basePath → $connectionString, parsed path defaults to
    • ApcuAdapter: added $connectionString param, ?prefix= overrides $tenantPrefix
    • Container config: unified DI wiring so all adapters receive $connectionString
    • .env.example: documented connection string format per adapter and prefix
  • [4.99.0] refactor(cache): extract L1 auto-detect into AutoDetectAdapter

    • Add AutoDetectAdapter: delegates to first healthy candidate (APCu → SHM),
    • Simplify container match to: 'auto' → AutoDetectAdapter, FQCN → direct,
  • [4.99.0] refactor(cache): replace AutoDetectAdapter with generic ChainAdapter

    • ChainAdapter: iterates candidates, picks first where isHealthy() is true,
    • ShmAdapter: no longer throws on empty prefix — sets empty directory and
    • Container: wires ChainAdapter with [APCu, SHM] candidates and L2 fallback;
  • [4.99.0] fix(cache): prevent fread ValueError in storeIfAbsent on empty files

  • [4.99.0] test(cache): add comprehensive tests for all cache adapters

    • Unit tests: InMemoryAdapter (25 tests), ChainAdapter (22 tests with mocks)
    • Integration tests: FileAdapter (24), ShmAdapter (21), ApcuAdapter (17), RedisAdapter (20)
    • Add TEST_REDIS_HOST/PORT to .env.example and .docker/integration/.env.example
  • [4.99.0] docs(cache): add comprehensive cache system documentation

    • Three-tier architecture overview with ASCII diagram
    • Per-adapter reference: constructor, storage, key mapping, serialization
    • ChainAdapter health-aware selection for L1 auto-detect
    • CachePool PSR-6 envelope format and key validation
    • Pscache method caching, dependency tracking, and helper functions
    • Environment variables and example configurations
    • Testing section: unit vs integration, Docker stack usage, extension gating, isolation
  • [4.99.0] feat(circuit-breaker): expose stateTtlBuffer as configurable env var per service

  • [4.99.0] feat(circuit-breaker): protect Advisable AI fire-and-forget calls

    • Lazy-load CB from DI container via circuitBreaker() helper
    • Register circuit_breaker.advisable_ai service in container config
    • Add APP_CB_ADVISABLE_AI_* env vars to config and .env.example
  • [4.99.0] docs(circuit-breaker): add circuit breaker system documentation

  • [4.99.0] feat(deferred-task): support unlimited budget with timeBudgetSeconds=0

    • Skip budget checks and remaining-time tracking when timeBudgetSeconds is 0
    • Add two tests: unlimited runs all tasks, unlimited isolates exceptions
  • [4.99.0] refactor(deferred-task): extract SAPI-aware flushResponse() method

  • [4.99.0] docs(deferred-task): add deferred task runner system documentation

  • [4.99.0] fix(deferred-task): respect PHP ini max_execution_time as hard ceiling

    • Extract applyTimeLimit() that only tightens, never extends the time limit
    • Unlimited budget (timeBudgetSeconds=0) no longer touches the timer
    • Use wall-clock elapsed (REQUEST_TIME_FLOAT) to estimate ini remainder
    • Add debug logging for all time limit decision paths
    • Update docs with Time Limit Behavior section and decision table
  • [4.99.0] fix(cache): replace json_encode with serialize in ShmAdapter

  • [4.99.0] refactor(cache,circuit-breaker,deferred-task): add declare(strict_types=1)

  • [4.99.0] docs(changelog): add db-roundtrip-optimizations branch entries to 4.98.X

  • [4.99.0] chore: remove deferredTaskProof temporary test method

  • [4.99.0] Merge branch 'develop' into feature/db-roundtrip-optimizations

  • [4.99.0] Merged in feature/db-roundtrip-optimizations (pull request #21)

  • [4.99.0] docs: add per-developer override strategy and path-specific rules (#claude-developer-overrides)

    • Add 5 path-specific rules in .claude/rules/ (domain, REST, legacy, migrations, client)
    • Create docs/guides/claude/developer-overrides.md with override hierarchy and recipes
    • Add .mcp.json empty placeholder for team-shared MCP servers
    • Add "Per-Developer Customization" section to CLAUDE.md
    • Update system-overview.md with Rules, MCP Servers, and Customization sections
    • Fix repo-flexibility.md: replace incorrect CLAUDE.local.md with actual override mechanisms
    • Add consistent cross-links ("See also") across all 5 claude guide documents
  • [4.99.0] fix(rest): auth JSON parsing, order scoping, BelongsToLoader safety

    • Order controller: deny unauthenticated access (401), force customerId
    • AdminAuth/CustomerAuth: use jsonDecodeInputStream() with fallback to
    • BelongsToLoader: null-check get() before chaining custom_result_object()
    • BelongsToLoader: apply $relation->scope via call_user_func(), matching
    • Add APP_DEFERRED_TASK_ENABLED=false and APP_DEFERRED_TASK_BUDGET_SECONDS=0
  • [4.99.0] fix: Fix the missing product image in the batch action of create gift rule

  • [4.99.0] refactor(repository): extract relation loading into strategy classes (#refactor-relations)

    • Add RelationLoaderInterface, AbstractRelationLoader, RelationContext, RelationLoaderRegistry
    • Extract OneToManyLoader, BelongsToLoader, HasOneLoader, ManyToManyLoader
    • Eliminate 6x duplicated sort-application logic via AbstractRelationLoader::applySorts()
    • Add public getters (getDb, getTable, getPrimaryKey, getEntityClass) to BaseRepository
    • Adapt Variation Repository to override createLoaderRegistry() instead of loadOneToMany()
    • Fix hardcoded 'id' references in recursive/M2M paths to use dynamic primaryKey
    • Fix risky tests: remove redundant header() in ApiEndpointTrait::sendError()
    • Consolidate test bootstrap: shared setup in bootstrap.php, CI-core in bootstrap_ci.php
    • Remove inline constant defines from test files (now loaded via bootstrap.php)
    • Add 36 new tests (102 assertions): 7 characterization + 5 unit test classes
  • [4.99.0] chore(agents): set claude-sonnet-4-6 model for template-driven sub agents

  • [4.99.0] chore: gitignore Claude Code per-developer local files

    • Untrack .claude/settings.local.json (was previously committed)
    • Add three Claude Code local file patterns to .gitignore
  • [4.99.0] feat(tests): comprehensive PHPUnit test coverage (2063 tests, 5907 assertions)

    • phpunit.xml.dist with Unit/Integration/Legacy suites
    • bootstrap.php (minimal) and bootstrap_ci.php (full CI3 bootstrap)
    • CiTestCase base class for integration/legacy tests
    • Composer test scripts
    • ~94 Domain Service tests across all 15 contexts
    • ~274 REST Resource/Collection transformation tests
    • HandlesRestfulActions integration tests (index/show/item)
    • CI bootstrap smoke tests
    • VAT computation and Greek VAT validation (AdvVatForOrder, AdvVatValidate)
    • Payment registry type conversion (AdvPaymentsRegistry)
    • Gift rule validators (Adv_gifts_model)
    • Coupon discount calculations (Adv_coupons_model)
    • Price tracking timeline analysis (AdvPriceTrackingGraphs)
    • Webhook checksum validation (AdvJcc, AdvKlarnaPayments)
    • Auth helper functions (allowRole, mergeUsersRoles, allowedUsers)
  • [4.99.0] feat(rest-auth): separate admin and customer auth with guards and resource scoping

    • Add AdminAuth and CustomerAuth controllers with OpenAPI annotations
    • Add BackendGuard, FrontendGuard, AnyGuard traits for access control
    • Add ResourceContext with SCOPE_PUBLIC/BACKEND/CUSTOMER for audience-aware
    • Add migration to add user_type column to refresh_tokens table
    • Update Order controller/resource as example of guard + context usage
    • Add bearerAuth security scheme to OpenAPI spec
    • Change REST_BACKEND_USER/REST_FRONTEND_USER constants from 'B'/'F' to
    • Add 34 unit tests: TokensTest, GuardTraitsTest, ResourceContextTest
    • Deprecate Advauth::statelessLogin() in favor of split methods
  • [4.99.0] feat(tests): comprehensive PHPUnit test coverage with 2063 tests and 5907 assertions

    • Add phpunit.xml.dist with 3 test suites: Unit, Integration, Legacy
    • Add tests/bootstrap.php (minimal autoload) and tests/bootstrap_ci.php (full CI3 bootstrap for integration/legacy tests)
    • Add tests/CiTestCase.php base class for integration/legacy tests with lazy CI boot, output capture, request helpers
    • Add ~94 Domain Service tests across all 15 contexts (Product, Order, Customer, Promotion, Transporter, Cms, Marketplace, etc.)
    • Add ~274 REST Resource/Collection transformation tests
    • Add HandlesRestfulActions integration tests (index/show/item with mocked Service)
    • Add CI bootstrap smoke tests (constants, core functions, config, singletons)
    • Add 197 Legacy Integration tests: VAT computation, Greek AFM validation, payments registry, gifts model, coupons discounts, price tracking, JCC checksum, Klarna payloads, auth helpers
    • Add Composer scripts: test, test:unit, test:coverage
    • Add .gitignore entries for .phpunit.cache and phpunit.xml (developer overrides)
  • [4.99.0] feat(agents): add agent-first orchestration system with 12 specialized subagents

    • Add 12 subagent definitions in .claude/agents/ (domain-generator, rest-crud-writer, code-reviewer, migration-writer, test-writer, container-writer, job-writer, patcher-writer, feed-writer, schema-analyzer, legacy-developer, wiki-updater)
    • Add SessionStart hook (.claude/hooks/detect-repo.sh) for auto-detecting main vs client repo, including Custom\ PSR-4 layer status
    • Add .claude/settings.json to register the SessionStart hook
    • Update CLAUDE.md with Agent Orchestration section: delegation rules, decision tree, client repo custom code pattern, context preservation, override support
    • Replace skill invocation guide with unified Agents & Skills table
    • All agents except wiki-updater work in both main and client repos
    • Client repo agents use custom/ (Custom\ namespace) for modern code, application/ for legacy
    • Agents scaffold custom/ on demand (not preemptively) for fresh client forks
    • job-writer and feed-writer default to application/ for simple tasks, only use custom/ when extending upstream or working with existing custom domains
  • [4.99.0] feat(product-list): implement ProductList REST API and domain logic

    • Create Domain entities, repositories, and services for ProductList, Groups, and ProductLp
    • Implement REST controllers and resources with support for multi-language (MUI) translations
    • Register REST routes and update the DI container for the ProductList service
  • [4.99.0] Merge branch 'develop' into feature/rest-product-list

  • [4.99.0] Merge branch 'develop' into feature/rest-product-list

  • [4.99.0] Merge branch 'develop' into feature/rest-product-list

  • [4.99.0] Merge branch 'develop' into feature/rest-product-list

  • [4.99.0] fix(rest): align ProductList domain and REST layers with standard module patterns

    • Add FilterByTranslation specification with proper subquery against product_list_mui
    • Add SortByTranslation specification with LEFT JOIN for locale-scoped sorting
    • Fix Service to route translation filters/sorts to custom specs instead of generic Filter/Sort
    • Add HandlesNotEmptyFilters trait to Service for consistency with other modules
    • Remove redundant constructor override from ProductList REST controller
    • Add ProductListGroup REST controller with OpenAPI annotations and routes
    • Add ProductListProductLp REST controller with OpenAPI annotations and routes
    • Add OA\Schema annotations to Group and ProductLp Collection classes
    • Register new controllers in DI container and rest_routes.php
  • [4.99.0] feat(rest): add Domain and REST layers for Variation, VariationGroup, and VariationValue

    • Variation (product_variations + product_variations_mui): product-level variation
    • VariationGroup (variation_groups + variation_groups_mui): global variation type
    • VariationValue (variation_values + variation_values_mui): global variation option
    • 21 Domain files: entities, repositories, MUI repos, configurators, services, list requests
    • 18 REST files: controllers with OpenAPI annotations, resources, collections, MUI variants
    • DI container registrations for all 3 entities in both Domain and REST containers
    • 18 REST routes (standard + locale-prefixed) for variation, variation-group, variation-value
  • [4.99.0] feat(rest): add Domain and REST layers for Review and Download

    • Review (shop_product_reviews): product reviews with star ratings, moderation
    • Download (product_downloads): downloadable files attached to products with
    • 10 Domain files: entities, repositories, configurators, services, list requests
    • 6 REST files: controllers with OpenAPI annotations, resources, collections
    • DI container registrations for both entities in Domain and REST containers
    • 12 REST routes (standard + locale-prefixed) for review and download endpoints
  • [4.99.0] feat(rest): add Domain and REST layers for Related and RelatedGroup

    • RelatedGroup (related_groups + related_groups_mui): MUI entity defining
    • Related (shop_related_products): junction entity linking products via groups.
    • 12 Domain files: entities, repositories, MUI repo, configurators, services, list requests
    • 8 REST files: controllers with OpenAPI annotations, resources, collections, MUI variants
    • DI container registrations for both entities in Domain and REST containers
    • 12 REST routes (standard + locale-prefixed) for related and related-group endpoints
  • [4.99.0] feat(rest): add Domain and REST layers for Bundle system (6 entities)

    • Bundle (shop_product_bundles): main entity with behavior, pricing strategy,
    • BundleDisplay (shop_product_bundles_display): key-value multi-language messages
    • BundleCriteria (shop_product_bundles_criteria): bundle matching rules with
    • BundleCriteriaReference (shop_product_bundle_criteria_references_lp): criteria-
    • BundleBuilderReference (shop_product_bundles_builder_references_lp): bundle-to-
    • BundlePricing (shop_product_bundle_pricing): pricing rules with discount and
    • 30 Domain files: entities, repositories, configurators, services, list requests
    • 18 REST files: controllers with OpenAPI annotations, resources, collections
    • DI container registrations (18 domain services, 6 REST controllers)
    • 36 REST routes (standard + locale-prefixed) for all 6 bundle endpoints
  • [4.99.0] feat(rest): add Domain and REST layers for ProductMeta

    • Add Entity, Repository, RepositoryConfigurator, Service, ListRequest
    • Add REST Controller with OpenAPI annotations, Resource, Collection
    • Register DI services in domain and REST containers
    • Register 6 REST routes (standard + locale-prefixed)
    • Filters: id, productId, metaType, category, lang (Exact), value (Partial)
    • Sorts: id, productId, metaType, category
  • [4.99.0] feat(rest): add vat BELONGS_TO relation to Product entity

    • Add vat BELONGS_TO relation in Product RepositoryConfigurator
    • Add vat resource mapping in Product REST Resource
  • [4.99.0] feat(rest): add variationValues MANY_TO_MANY relation via product_variation_values

    • Add variationValues relation to Product RepositoryConfigurator and Resource
    • Add products relation to VariationValue RepositoryConfigurator and Resource
  • [4.99.0] feat(rest): add Domain and REST layers for Currency

    • Add Entity, Repository, RepositoryConfigurator, Service, ListRequest
    • Add REST Controller with OpenAPI annotations, Resource, Collection
    • Create domain and REST container.php files
    • Register both containers in modules.php
    • Register 6 REST routes at rest/currency/currency
    • Filters: id, code, active (Exact), symbol (Partial)
    • Sorts: id, code, rate, active
  • [4.99.0] feat(rest): add Domain and REST layers for Wishlist and WaitingList

    • BELONGS_TO customer and product relations
    • Filters/sorts: id, customerId, productId
    • REST routes at rest/product/wishlist
    • Back-in-stock notification entries with status tracking
    • BELONGS_TO product relation
    • Filters: id, email, lang, productId, waitingStatus
    • Sorts: id, productId, creationDate, waitingStatus, emailSentDate
    • REST routes at rest/product/waiting-list
  • [4.99.0] feat(rest): add Domain and REST layers for Shelfcode with Product relation

    • New Shelfcode domain: table shop_product_shelfcodes, fields id + code
    • New REST endpoint: rest/product/shelfcode (index, show, item)
    • Product RepositoryConfigurator: added shelfcode BELONGS_TO relation
    • Product Resource: added shelfcode to addRelationToData + OA Schema properties
    • Product Controller: updated OA Tag description and with parameter examples
    • DI containers and routes registered
  • [4.99.0] feat(rest): add Domain and REST layers for Seo context (Keyword, DefaultMetaTag)

    • New Keyword domain: table keywords, fields id, keyword, alt_text, link_url,
    • New DefaultMetaTag domain: table defaultmetatags, fields id, meta_keywords,
    • REST endpoints: rest/seo/keyword and rest/seo/default-meta-tag with
    • New Seo domain and REST containers registered in modules.php
    • 12 routes added (6 per entity, standard + locale-prefixed)
  • [4.99.0] feat(rest): add Domain and REST layers for Event context (Event, EventCategory)

    • New Event domain: table events + events_mui, with translations (ONE_TO_MANY)
    • New EventCategory domain: table event_categories + event_categories_mui,
    • REST endpoints: rest/event/event and rest/event/event-category with
    • New Event domain and REST containers registered in modules.php
    • 12 routes added (6 per entity, standard + locale-prefixed)
  • [4.99.0] feat(rest): add Domain and REST layers for Offer and OfferCategory under Cms context

    • Offer: MUI entity with translations (ONE_TO_MANY) and category (BELONGS_TO) relations
    • OfferCategory: MUI entity with translations (ONE_TO_MANY) relation
    • REST controllers with OpenAPI annotations for both entities
    • Resources with proper relation handling (addCollectionToData, addResourceToData)
    • DI container registrations in existing Cms domain and REST containers
    • 12 read routes + 12 commented-out write route stubs in rest_routes.php
  • [4.99.0] feat(rest): add Domain and REST layers for Gift, GiftChoice, GiftRequirement under new Promotion context

    • Gift: MUI entity from gifts/gifts_mui tables with amount thresholds, date ranges, priority
    • GiftChoice: non-MUI entity from gift_choices table (products selectable as gift rewards)
    • GiftRequirement: non-MUI entity from gift_requirements table (eligibility conditions)
    • New Promotion domain and REST containers registered in modules.php
    • 18 read routes + 18 commented-out write route stubs in rest_routes.php
    • OpenAPI annotations on all controllers and resources
  • [4.99.0] feat(rest): add Domain and REST layers for Coupon system under Promotion context

    • Coupon has ONE_TO_MANY relations to codes, products, vendors, rules
    • Child entities have BELONGS_TO relation back to Coupon
    • CouponCode Resource renames scalar coupon column to code in API output
    • CouponCode Service includes gift card linkage notes in TODO comments
    • DI container registers children before parent for proper dependency resolution
    • 30 read routes + 30 commented-out write route stubs registered
  • [4.99.0] feat(rest): add Domain and REST layers for GiftCardOrder under Order context

    • Non-MUI, no relations (coupon_id FK to CouponCode noted for future implementation)
    • No declare(strict_types=1) to avoid type coercion issues with DB fields
    • 11 filters (customerId, giftCardStatus, couponId, payway, sendToEmail, etc.)
    • 6 sorts (id, customerId, amount, giftCardStatus, createdAt, completedAt)
    • 6 read routes + 6 commented-out write route stubs
  • [4.99.0] feat(rest): add Domain and REST layers for Transporter context with 8 entities

    • Transporter: MUI entity with FilterTranslation, HasLocales in ListRequest,
    • 7 child entities: composite PK tables with BELONGS_TO transporter relation,
    • REST controllers with OpenAPI annotations, 34 read routes + 28 write stubs
    • DI container with children before parent ordering
    • Registered in modules.php (domain + REST)
  • [4.99.0] feat(rest): add Domain and REST layers for Product CustomizationSchema

    • Domain: Entity, Repository, RepositoryConfigurator, Service, ListRequest
    • REST: Controller (index/show/item), Resource, Collection with OpenAPI annotations
    • Filters: id (exact), name (partial), isActive (exact)
    • Sorts: id, name, isActive
    • DI container registration in both domain and REST containers
    • Route registration with lang-prefix variants and write stubs
  • [4.99.0] feat(rest): add Domain and REST layers for OrderTag with M2M Order relation

    • Add OrderTag Entity, Repository, RepositoryConfigurator, Service, ListRequest
    • Add REST controller with index/show/item endpoints and OpenAPI annotations
    • Add Resource (id, slug, title + orders relation) and Collection
    • Wire M2M on Order side: tags relation in RepositoryConfigurator
    • Update Order Resource to include tags collection in addRelationToData
    • Update Order Controller OA\Tag to list tags as available relation
    • Register OrderTag in both Domain and REST container.php
    • Add 6 read routes + 6 write stubs in rest_routes.php
  • [4.99.0] feat(cms): add Video domain + REST API with MUI and M2M relations

    • Add Video domain layer (Entity, MuiEntity, Repository, MuiRepository,
    • Add FilterByTranslation and SortByTranslation specifications for
    • Add REST controller with index/show/item endpoints and OA annotations
    • Add Resource, Collection, MuiResource, MuiCollection transformers
    • Wire M2M relation Product ↔ Video via shop_product_video_lp
    • Wire M2M relation Vendor ↔ Video via shop_vendor_video_lp
    • Register Video in Cms domain and REST DI containers
    • Add 6 read routes + 6 write stubs for rest/cms/video
  • [4.99.0] feat(rest): add Domain and REST layers for Audience in new Plus context

    • Add Audience Entity, Repository, RepositoryConfigurator (ONE_TO_MANY
    • Add AudienceCriteria Entity, Repository, RepositoryConfigurator
    • Add REST controllers with OpenAPI annotations, Resources, and Collections
    • Add DI container configs for Plus domain and REST layers
    • Wire Coupon RepositoryConfigurator + Resource with audience BELONGS_TO
    • Wire Slide RepositoryConfigurator + Resource with audience BELONGS_TO
    • Register Plus modules in container/modules.php
    • Add 24 routes (6 read + 6 write stubs per entity) in rest_routes.php
  • [4.99.0] feat(rest): move Customer to own context and add 5 new customer entities

    • Move Customer domain from Order\Customer to Customer\Customer namespace
    • Move Customer REST from Rest\Order to Rest\Customer namespace
    • Add CustomerCampaign (shop_customer_campaign) with customer BELONGS_TO
    • Add CustomerMessageHistory (shop_customer_message_history) with customer BELONGS_TO via user_id
    • Add CustomerReview (shop_customer_reviews) with NullRelationConfigurator (uses mail, not customer_id)
    • Add CustomerSmsMarketing (shop_customer_sms_marketing) with customer BELONGS_TO
    • Add CustomerTag (shop_customer_tag) with customer BELONGS_TO
    • Wire ONE_TO_MANY relations on Customer RepositoryConfigurator for campaigns, messageHistory, smsMarketing, tags
    • Wire MANY_TO_MANY audiences relation on Customer via shop_customer_audience pivot
    • Add addRelationToData on Customer Resource for all child collections
    • Update all cross-context references (Order, Plus/Audience, Product/Wishlist)
    • Register Customer domain+REST containers in modules.php (before Order for dependency ordering)
    • Replace rest/order/customer routes with rest/customer/customer + 5 new entity route blocks
  • [4.99.0] feat(rest): wire cross-context M2M pivots for Category, Product, Event, and Article

    • Category: tagGroups (shop_product_category_group_tags_lp), relativeCategories
    • Product: events (shop_product_event_lp), articles (product_blog)
    • Event: products (shop_product_event_lp, reverse side)
    • Blog/Article: products (blog_product)
    • Update REST Resources with addRelationToData for new relations
    • Update Controller OA Tags to list new available relations
    • Update domain-coverage.md with all M2M pivot wiring
  • [4.99.0] feat(cms): add Domain and REST layers for ContactEmail

    • Entity with 7 fields: id, email, customer_id, email_id, email_datetime, meta_data, generated_email
    • BELONGS_TO customer relation via nullable customer_id
    • Filters: id (exact), email (partial), customerId (exact), emailId (exact)
    • Sorts: id, emailDatetime, email, emailId
    • REST endpoints at /rest/cms/contact-email with OpenAPI annotations
    • Register in Cms domain + REST containers and rest_routes.php
  • [4.99.0] feat(cms): add Domain and REST layers for Cookie

    • Entity with 9 fields, all NOT NULL (no relations — NullRelationConfigurator)
    • Filters: id (exact), categories (partial), dataControllers (partial), dataKeyNames (partial)
    • Sorts: id, categories
    • REST endpoints at /rest/cms/cookie with OpenAPI annotations
    • Register in Cms domain + REST containers and rest_routes.php
  • [4.99.0] feat(product): add Domain and REST layers for PriceTracking

    • Entity with 4 fields: id, product_id, price_date, price (decimal 11,2)
    • BELONGS_TO product relation via product_id
    • Filters: id (exact), productId (exact)
    • Sorts: id, priceDate, price
    • REST endpoints at /rest/product/price-tracking with OpenAPI annotations
    • Register in Product domain + REST containers and rest_routes.php
  • [4.99.0] feat(order): add Domain and REST layers for Note

    • Entity with 6 fields: id, entity_type, entity_id, user_id, note_text, entry_date
    • NullRelationConfigurator (polymorphic — no typed FK relations)
    • Filters: id (exact), entityType (exact), entityId (exact), userId (exact), noteText (partial)
    • Sorts: id, entryDate, entityType
    • REST endpoints at /rest/order/note with OpenAPI annotations
    • Register in Order domain + REST containers and rest_routes.php
  • [4.99.0] docs: update domain-coverage with ContactEmail, Cookie, PriceTracking, Note

  • [4.99.0] feat(marketplace): add Domain and REST layers for Marketplace context (9 entities)

    • Add Iris/Order entity (iris_orders) with BELONGS_TO Order + GiftCardOrder
    • Add Jcc/Order entity (jcc_order_ids) with BELONGS_TO Order
    • Add Public/Order entity (public_orders, varchar PK) with ONE_TO_MANY LineItem
    • Add Public/LineItem entity (public_line_items) with BELONGS_TO PublicOrder
    • Add Shopflix/Order entity (shopflix_orders, int PK no AI) with ONE_TO_MANY LineItem
    • Add Shopflix/LineItem entity (shopflix_line_items) with BELONGS_TO ShopflixOrder
    • Add Skroutz/Order entity (skroutz_orders, varchar PK) with ONE_TO_MANY LineItem + InvoiceDetail
    • Add Skroutz/LineItem entity (skroutz_line_items) with BELONGS_TO SkroutzOrder
    • Add Skroutz/InvoiceDetail entity (skroutz_invoice_details) with BELONGS_TO SkroutzOrder
    • Register Marketplace Domain + REST containers in modules.php
    • Add 9 route blocks with (.+) patterns for string PK entities (PublicOrder, SkroutzOrder)
  • [4.99.0] feat(order): add Domain and REST layers for DhlVoucher and SmartPoint

    • Add DhlVoucher entity (shop_order_dhl_vouchers) — DHL shipping vouchers
    • Add SmartPoint entity (shop_order_smart_point) — pickup location data
    • Register both in Order domain and REST containers
    • Add read routes + write stubs for both entities
  • [4.99.0] feat(ai): add Domain and REST layers for ContentGeneration and Position

    • Add Ai/ContentGeneration Domain layer (Entity, Repository, Service, ListRequest)
    • Add Ai/Position Domain layer with MUI support (Entity, MuiEntity, Repository, MuiRepository, RepositoryConfigurator, Service, ListRequest)
    • Add Ai/Position FilterByTranslation and SortByTranslation specifications
    • Add REST controllers, resources, and collections for both entities
    • Add Position MuiResource and MuiCollection for translation output
    • Register Ai Domain and REST containers in modules.php
    • Add read routes and write stubs for both entities in rest_routes.php
  • [4.99.0] chore: remove patch_routes.php and its include from routes.php

  • [4.99.0] Merge develop into feature/rest-product-list

    • products (MANY_TO_MANY) from feature branch
    • slider (BELONGS_TO) from develop branch
  • [4.99.0] fix: resolve type hints and DI container issues in Marketplace, Ai, and ListRequest classes

    • Fix Marketplace Public/Order and Skroutz/Order Service update/delete signatures to use int|string for string PKs
    • Fix Ai/container.php to explicitly pass RepositoryConfigurator to Repository constructors (resolve DI autowire ambiguity)
    • Add setAllowedRelationSorts() method to all ListRequest classes without relations (ContentGeneration, ContactEmail, Cookie, Note, PriceTracking)
    • Add string type hint to all Collection $collects properties to match BaseCollection interface
    • Regenerate openapi.json with all fixes applied
  • [4.99.0] Merged in chore/cleanup-obsolete-schema (pull request #22)

  • [4.99.0] fix(rest): wrap file fields with formatFile() in resource transformers

    • Wrap image/file fields with formatFile() in 13 resource classes
    • Add correct storage paths for each entity type (e.g., /files/vendors/, /files/products/)
    • Ensure API responses return full paths (e.g., /files/vendors/logo.png) instead of bare names
    • Follows pattern already established in Map, Badge, and Blog resources
  • [4.99.0] chore: upgrade dependencies and fix security vulnerability

    • Upgrade firebase/php-jwt from 6.11.1 to 7.0.3 (fixes CVE-2025-45769)
    • Update 13 Symfony components to v6.4.34
    • Update AWS SDK, Flysystem, and other utilities
    • All 23 package updates completed successfully
  • [4.99.0] refactor: add typed signature and remove docblock from update_record in Adv_product_category_model

  • [4.99.0] Merged in feature/rest-product-list (pull request #23)

  • [4.99.0] refactor: modernize add_record in Adv_product_category_model

    • Changes covered: docblock removed, public visibility and typed signature added,
      parameters renamed to camelCase, ?null default replaced with [],
    • order calculation simplified with null-coalescing,
    • early return on failed insert,
    • array_merge used to safely inject category_id into mui rows,
    • and the caller in Adv_product_categories_admin simplified to a single line.
  • [4.99.0] refactor: modernize get_mui_slug in Adv_product_category_model

    • The changes are: docblock removed, public visibility added, parameter
      typed as int, return changed from false to [] on empty result, and the query
      condensed to two lines.
  • [4.99.0] refactor: remove duplicate get_parent_slug from Adv_product_model and consolidate callers to product_category_model

  • [4.99.0] refactor: add typed signatures and remove docblocks from create_slug and get_parent_slug in Adv_product_category_model

  • [4.99.0] refactor: add typed properties and remove redundant languageAbbr declaration in Adv_product_category_model

  • [4.99.0] refactor: Remove unused num_cat_products method from product category model

  • [4.99.0] refactor: Remove unused getFirstLevelChildrenCategories method from product category model

  • [4.99.0] perf: Optimize getOptionsWithActiveStockForGiftIds method

    • Consolidate gift options merging logic to reduce iterations
    • Check active gift rules against merged options dataset
    • Improve query efficiency and reduce processing overhead
  • [4.99.0] feat(gifts): add rule 13 — products required, get cheapest free

    • Add rule 13 entry to Adv_gift_rules_model::$data with validator ruleProductsCheapestFreeValidator and setDefaults forcing gift_user_choice_count to 0
    • Add ruleProductsCheapestFreeValidator to Adv_gifts_model: counts total quantity of requirement products present in cart
    • Add getCheapestRequirementProductInCart helper: finds cheapest in-cart requirement product using live price data; deterministic tie-break by lowest product ID
    • Wire case 13 into getGiftForProductsInCart switch and use helper for dynamic gift pool instead of gift_choices table
    • Add rule13.name and rule13.help translation keys in all 8 languages (English, Greek, German, French, Spanish, Italian, Russian, Chinese)
  • [4.99.0] fix(gifts): fix getActiveGiftRules excluding rule 13 from isLive

    • Select rule_id in query so it is available in the filter callback
    • Remove early-exit on empty choices so rule 13 (no gift_choices rows) is not excluded
    • Bypass choices check in array_filter for rule 13 — pool is computed dynamically
  • [4.99.0] fix(gifts): correct cart display and checkout total for rule 13 (cheapest free)

    • Add rule_id to cartGifts entries in getGiftForProductsInCart() so callers can identify rule 13 gifts
    • Add AdvCartResource::adjustCartContentsForRule13() to subtract the gift qty from the matching cart row before sending cartContents to the frontend
    • Add Adv_order_model::getRule13GiftAdjustments() to resolve cheapest free product and deduction count
    • Apply adjustment in baseParseCartContents() via local $paidQty so subtotals/totals reflect only paid items
    • Add adjustCartForRule13Gifts() and call it in processOrder() to remove the free item from stored basket rows at order placement
  • [4.99.0] fix(gifts): fix admin order page for rule 13 (cheapest free) gift validation and totals

    • Override choices[1] with cheapest product for rule 13 in gifts_for_products AJAX endpoint
    • Skip rule 13 in validateProductGiftSelections server-side callback (auto-assigned, no user selection needed)
    • Force ruleSatisfiesChoiceSelection=true for rule 13 in Vue Vuex getter
    • Extract getRule13GiftAdjustmentsForProducts() for reuse between session cart and admin fake cart
    • Apply rule 13 price deduction in createAdminFakeCart so admin order totals price correctly
    • Cache rule13Products on the Vue app and expose reapplyRule13Deductions() called after every updateMarkUpPrices rebuild
  • [4.99.4] REQUIRES composer install — phpseclib security patch: phpseclib/phpseclib updated to 3.0.50 (CVE-2026-32935) and league/flysystem-sftp-v3 updated to 3.33.0. After merging to a client repo, run composer install to pull the updated packages.

  • [4.99.4] axios supply chain attack — no build required: axios@1.14.1 and axios@0.30.4 were published on March 30, 2026 by a compromised npm maintainer account (jasonsaayman) and deliver a cross-platform Remote Access Trojan (RAT). package.json has been updated to exclude these versions. Safe versions are axios@1.14.0 and below (for 1.x) and axios@0.30.3 and below (for 0.x). No frontend rebuild is needed — this change only affects dependency resolution. Reference: https://www.elastic.co/security-labs/axios-one-rat-to-rule-them-all

  • [4.99.4] REQUIRES FRONTEND BUILD — cart rule 13 fix: AdvCartProductRow.vue and AdvCartGiftRow.vue were updated. After merging this fix, run npm run production (or npm run all-production) to rebuild the frontend assets.

  • [4.99.4] Client repos with a CartResource override: Any client repo that overrides AdvCartResource and re-implements adjustCartContentsForRule13() must ensure their override also annotates freeQuantity on the cart item ($cartItem['freeQuantity'] = ($cartItem['freeQuantity'] ?? 0) + $reduce;). Without this annotation the Vue spinner fix will not function correctly for that client.

  • [4.99.4] Evripidis client — override now obsolete: The Evripidis client has an application/libraries/CartResource.php that overrides adjustCartContentsForRule13() specifically for this fix. Now that the fix is in the base class (ecommercen/libraries/AdvCartResource.php), that client override is no longer needed and can be removed from the Evripidis repo.

  • [4.99.3] REQUIRES FRONTEND BUILD — BoxNow widget: The BoxNow smart point widget was refactored to use a custom iframe overlay (BoxNowWidget.vue, SmartPointWidget.js). After merging to a client repo, run npm run production to rebuild the frontend assets.

  • [4.99.3] Check for overrides:

    • src/Rest/Cms/Controllers/BlogArticle.php
    • src/Rest/Cms/Controllers/BlogAuthor.php
    • src/Rest/Cms/Controllers/BlogCategory.php
    • src/Rest/Cms/Controllers/BlogComment.php
    • src/Rest/Cms/Controllers/BlogTag.php
    • src/Rest/Cms/Controllers/Document.php
    • src/Rest/Country/Controllers/Country.php
    • src/Rest/Country/Controllers/County.php
    • src/Rest/Map/Controllers/Location.php
    • src/Rest/Map/Controllers/Map.php
    • src/Rest/Order/Controllers/Store.php
    • src/Rest/Order/Controllers/Vat.php
    • src/Rest/Product/Controllers/Attribute.php
    • src/Rest/Product/Controllers/AttributeGroup.php
    • src/Rest/Product/Controllers/Barcode.php
    • src/Rest/Product/Controllers/Line.php
    • src/Rest/Product/Controllers/Media.php
    • src/Rest/Product/Controllers/ProductCodeAttribute.php
    • src/Rest/Product/Controllers/Promo.php
    • src/Rest/Product/Controllers/Supplier.php
    • src/Rest/Product/Controllers/Tag.php
    • src/Rest/Product/Controllers/TagCategory.php
    • src/Rest/Slider/Controllers/Group.php
    • src/Rest/Slider/Controllers/Slider.php
  • [4.99.3] REMOVED METHODS — action required for client repos: 16 methods were removed from ecommercen/eshop/models/Adv_product_model.php (categoryWithProductExists, get_cat_products, get_product_ids, get_product_isactive, get_vendor, get_vendor_mui, getCategoryAttributes, getCategoryMinMaxPrice, getDiscountPricesAndPercent, getMaxDiscountOfProducts, getProductCodesByIds, getProductsForBatch, getProductsToIndex, getRelatedProducts, updateMuiProductRecord, updateVat). If any of these are used in a client repo, retrieve the method body from the previous commit in the main repo via git show and add it as an override in the client's application/models/Product_model.php (or equivalent override class), rather than reverting the main repo change.

  • [4.99.3] Check for overrides — return type changed from array|false to array for 18 methods in ecommercen/eshop/models/Adv_product_model.php: getMasterRecords, getMasterRecordsWhereIn, get_records, get_products_with_paths, get_skroutz_records, getMuiProductRecords, getPromoProducts, getTagProducts, getVendorProductsHits, getCategoryAttributes, get_product_ids, getDiscountPricesAndPercent, get_record, get_mui_slug, getBarcodesByProductId, getCatProductsPriceRange, getRenderVendorsPrices, getProductsWhereIn. Client repos that override any of these methods must update the return type hint from array|false to array and replace return false with return [] to stay compatible.

  • [4.99.3] REMOVED PARAMETER — action required for client repos: getProductsWithDiscountGreaterThan() in ecommercen/eshop/models/Adv_product_model.php no longer accepts a third $custom_select parameter. If a client repo calls this method with three arguments, remove the third argument. If a client repo overrides this method, remove the $custom_select parameter from the override signature.

  • [4.99.3] Check for overrides:

    • ecommercen/eshop/models/Adv_product_model.phpsetProductsVendor(): signature now public function setProductsVendor(array $productIds, int $vendorId): void — update any override to match the added type hints and : void return type
    • ecommercen/eshop/models/Adv_product_model.phpgetFeedProducts(): $feedId is now ?int — update any override to match
    • ecommercen/eshop/models/Adv_product_model.php — the ten getFeedProductsFor*() proxy methods now declare : array return type — update any override signatures to match
  • [4.99.3] ACTION REQUIRED — if customRemap() is overridden in a client repo: The method signature has changed to protected function customRemap(string $method): bool. Update the override to match — add the string type hint on $method and the : bool return type declaration. The method must return a bool.

  • [4.99.3] Check for overrides:

    • ecommercen/eshop/controllers/Adv_eshop.phpbaseProduct() refactored; the canonical redirect logic is now in handleProductCanonicalRedirect(object $canonicalMatch): void; override this helper in a client repo to customise canonical redirect behaviour (e.g. different URL resolution, additional tracking) without duplicating baseProduct()
    • ecommercen/eshop/controllers/Adv_eshop.phpbaseCheckout() refactored; if a client repo overrides this method, verify compatibility with the new signature baseCheckout(array $params): bool and the three protected helpers handleGetResponse(): void, handleCheckoutHandle(): void, and handleCheckoutPage(): void; note handleCheckoutHandle() now dispatches error_401() via eshop/home instead of calling exit() directly — override if different error handling is required
    • ecommercen/eshop/controllers/Adv_eshop.phpbaseCategory() refactored; if a client repo overrides this method, verify it is compatible with the new signature baseCategory(string $method): bool and the extracted parseCategorySlugAndOffset() protected helper (also overridable independently)
    • ecommercen/eshop/controllers/Adv_eshop.php$pscache_ttl changed to protected; client code accessing this property directly from outside the class must be updated
  • [4.99.3] Headless Commerce API — new database tables required:

    • Run php migrator.php migrate to create shop_cart and shop_cart_item tables
    • New REST endpoints require JWT auth (existing customer auth) or X-Cart-Token header for guest carts
    • Payment gateway adapters (Stripe, VivaWallet, PayPal) are conditionally registered — only active if gateway config exists in Registry
  • [4.99.3] Check for overrides (headless commerce):

    • src/Domains/Cart/ — new Cart + CartItem domain (Entity, Repository, Service, WriteService)
    • src/Domains/Cart/CartService.php — cart orchestrator (resolve, add, update, remove, claim, coupon)
    • src/Domains/Cart/CartTotalsCalculator.php — item subtotal calculator
    • src/Domains/Checkout/ — new Checkout domain (PlaceOrderService, ShippingCalculator, CouponValidator)
    • src/Domains/Checkout/Payment/ — payment adapter pattern (interface, initializer, factory, 6 adapters)
    • src/Rest/Cart/Controllers/Cart.php — 8 cart endpoints
    • src/Rest/Checkout/Controllers/Checkout.php — 5 checkout endpoints
    • src/Rest/Auth/CustomerAuth.php — added register() method
    • src/Rest/Customer/Controllers/Customer.php — added me/password endpoints
    • src/Rest/Order/Controllers/Order.php — added cancel() and tracking() endpoints
    • application/config/rest_routes.php — 26 new routes (13 plain + 13 locale-prefixed)
  • [4.99.3] media-stream 4.0.0 — action required for client repos with custom mediastream config:

    • If overriding services.yaml: delete all manual service definitions, import @vendor/services-library.yaml, keep only parameter overrides.
    • If overriding routes.yaml: rename GenericMediaControllerMediaController.
    • If referencing media-stream classes by FQCN in DI overrides: check namespace moves (Domain\...\Implementation\Infrastructure\...). See vendor/ecommercen/media-stream/docs/Guides/Migration-3.x-to-4.x.md.
  • [4.99.3] DO NOT pass "vendors/{$vendor_slug}" as the $url param to transformProductForJson() — this is now redundant. The helper resolves vendor URLs natively via match(true) priority logic (vendor_slug + vendors_base_url_{lang} config). Passing a vendor path as $url overrides the config-driven resolution unnecessarily. The $url param should only be used where it carries genuine override information, such as a category path ($catPathsDb[$category_id]). This applies to both new development and client repo view/controller overrides.

  • [4.99.3] NEW CONFIG ITEM — required for vendor URL resolution: All product URL builders now read vendors_base_url_{lang} (e.g. vendors_base_url_el) from CI config instead of the previously hardcoded "vendors/" prefix. Ensure this config item is set correctly for each active language in your application/config/ or registry settings. If the item is absent, config_item() returns null and the fallback category_slug/slug path is used.

  • [4.99.3] Check for overrides:

    • ecommercen/helpers/theme_helper.phptransformProductForJson()
    • ecommercen/feeds/core/AdvXml.php — new resolveProductUrl() method
    • ecommercen/eshop/traits/ProductVariationsTrait.php
    • ecommercen/job/libraries/AdvGenerateSitemaps.php
    • ecommercen/eshop/models/Adv_product_model.phpgetFeedProducts(), getProductsForSiteMap()
    • application/views/main/components/product_card/product_card.php
    • application/views/main/components/product_card/product_card_fashion.php
    • application/views/main/components/product_card/nav_product_card.php
    • application/views/main/components/product_card/product_combo_card.php
    • application/views/main/components/product_bundles/product_bundle_combo_card.php
    • application/views/main/components/customer/order_item.php
    • src/Feeds/Output/OutputGoogleProductReviewXml.php
  • [4.99.3] REMOVED METHOD — action required for client repos: batch_master_update() has been permanently removed from Adv_product_model. If a client repo calls this method, replace it with batchMasterUpdate() using an associative array for the last two arguments:

    • Before: $this->product_model->batch_master_update($products, 'active', 1)
    • After: $this->product_model->batchMasterUpdate($products, ['active' => 1])
  • [4.99.3] REMOVED METHODS — action required for client repos: The following 5 methods have been permanently removed from Adv_product_model in ecommercen/eshop/models/Adv_product_model.php:

    • get_random_mui_records()
    • whereBarCodesIn()
    • create_slug()
    • getMaxDiscountPerCategory()
    • setOrderBy()
    • If any client repo's application/ code calls one of these methods, retrieve the original implementation from the last safe commit on develop before this removal (e8f95e30d) and add it as an override in the client repo's application/modules/eshop/models/Product_model.php (which extends Adv_product_model).
  • [4.99.3] NEW WRITE ENDPOINTS — check for client route conflicts: POST/PUT/DELETE write endpoints are now live for 89 domain entities. If a client repo has custom route definitions in application/config/rest_routes.php, verify there are no conflicts with the newly added routes.

  • [4.99.3] Order write endpoints are backend-only. POST/PUT/DELETE /rest/order/order require a valid backend JWT (protectBackend). Customer-token and unauthenticated requests are rejected with 401.

  • [4.99.3] OpenAPI schema name change: Several WriteData schemas were renamed to avoid collisions (e.g., CategoryEventCategoryWriteData / ProductCategoryWriteData). If client tooling generates code from public/openapi.json, regenerate after upgrading.

  • [4.99.3] Check for overrides — write-layer files added to 89 domains (per-domain: WriteData.php, MuiWriteData.php, Validator.php, WriteRepository.php, MuiWriteRepository.php, WriteService.php):

    • All contexts: Ai, Cms, Country, Currency, Customer, Event, Map, Order, Plus, Product, Promotion, Seo, Slider, Transporter
    • 89 REST controllers in src/Rest/*/Controllers/ — updated with HandlesWriteActions trait
    • application/config/rest_routes.php — 614 write routes added/uncommented
  • [4.99.3] RENAMED METHODS — action required for client repos: Two methods in Adv_product_model have been renamed and their signatures changed:

    • get_content($slug)existsBySlug(string $slug): bool — return type changed from object|false to bool
    • get_content_url($slugAsUrl)findSlugsByUrl(string $slugAsUrl): ?object — now also returns vendor_slug; return type changed from object|false to ?object
    • If a client repo calls either of these methods, update the call sites to use the new names and handle the new return types. If the old behaviour is needed, copy the original implementations into the client repo's Product_model.php override.
  • [4.99.3] Check for overrides:

    • ecommercen/eshop/models/Adv_product_model.phpexistsBySlug(), findSlugsByUrl()
    • ecommercen/eshop/models/Adv_product_category_model.php — new existsBySlug()
    • ecommercen/eshop/controllers/Adv_eshop.phpbaseProduct(), baseCategory()
    • ecommercen/eshop/controllers/Adv_products.phpsetCanonicalUrl()
    • ecommercen/eshop/controllers/Adv_products_admin.phpgetCreateProductDataMuiPost(), getEditProductDataMuiPost()
  • [4.99.1] No production code changed. Both fixes are in tests/Unit/ only — safe to apply to any client repo running the test suite after upgrading to 4.99.0.

  • [4.99.2] BREAKING: ServiceInterface deleted — only ReadServiceInterface and WriteServiceInterface exist

    • All domain services now implement ReadServiceInterface (read-only: list(), get(), item())
    • Write-capable services additionally implement WriteServiceInterface (create/update/delete)
    • Any client code type-hinting ServiceInterface must be updated to the appropriate interface
  • [4.99.2] Check for overrides:

    • src/Domains/Cms/Page/WriteRepository.phpnew (write-side persistence for Page)
    • src/Domains/Cms/Page/MuiWriteRepository.phpnew
    • src/Domains/Cms/Page/WriteService.phpnew
    • src/Domains/Cms/Page/WriteData.php / MuiWriteData.phpnew (DTOs)
    • src/Domains/Cms/Page/Validator.phpnew
    • src/Domains/Cms/Subcontent/WriteRepository.phpnew
    • src/Domains/Cms/Subcontent/WriteService.phpnew
    • src/Domains/Product/Badge/WriteRepository.phpnew
    • src/Domains/Product/Badge/MuiWriteRepository.phpnew
    • src/Domains/Product/Badge/WriteService.phpnew
    • src/Domains/Support/Repository/BaseWriteRepository.phpnew
    • src/Domains/Support/ValidationException.phpnew
    • src/Rest/Support/Controllers/HandlesWriteActions.phpnew (store/update/destroy for REST)
    • src/Rest/Cms/Controllers/Page.php — updated (write actions added)
    • All 130+ src/Domains/*/Service.php files — interface changed from ServiceInterface to ReadServiceInterface
  • [4.99.0] Check for overrides:

    • ecommercen/sliders/models/Adv_sliders_model.php
  • [4.99.0] Check for overrides:

    • ecommercen/audience/models/AdvCustomerTagModel.php
    • ecommercen/coupons/controllers/Adv_coupons.php
    • ecommercen/eshop/controllers/Adv_reporting.php
    • ecommercen/eshop/models/Adv_order_basket_model.php
    • ecommercen/eshop/models/Adv_reporting_model.php
  • [4.99.0] Check for overrides:

    • ecommercen/doofinder/models/AdvDoofinderModel.php
  • [4.99.0] Check for overrides:

    • src/Cache/Adapter/ApcuAdapter.php
    • src/Cache/Adapter/InMemoryAdapter.php
    • src/Cache/Adapter/RedisAdapter.php
    • src/Cache/Adapter/RedisClusterAdapter.php
    • src/Cache/Adapter/ShmAdapter.php
  • [4.99.0] Check for overrides:

    • src/CircuitBreaker/CircuitBreaker.php
  • [4.99.0] Check for overrides:

    • src/DeferredTask/DeferredTask.php
    • src/DeferredTask/DeferredTaskRunner.php
  • [4.99.0] Check for overrides:

    • src/MetaConversionsApi/Client/MetaHttpClient.php
    • src/MetaConversionsApi/Service/FacebookConversionService.php
  • [4.99.0] Check for overrides:

    • application/controllers/Webrun.php
    • ecommercen/eshop/controllers/Adv_vendors.php
    • src/Analytics/MatomoTracking.php
    • src/Manago/Manago.php
  • [4.99.0] Check for overrides:

    • src/Cache/Adapter/CacheAdapterInterface.php
    • src/Cache/Adapter/FileAdapter.php
  • [4.99.0] Check for overrides:

    • src/ProjectAgora/ProjectAgoraHttpTrait.php
  • [4.99.0] Check for overrides:

    • src/Cache/Adapter/AutoDetectAdapter.php
  • [4.99.0] Check for overrides:

    • src/Cache/Adapter/AutoDetectAdapter.php src/Cache/Adapter/ChainAdapter.php
  • [4.99.0] Check for overrides:

    • src/Cache/Adapter/ChainAdapter.php
    • src/Cache/Adapter/WithKeyPartsExtractionTrait.php
    • src/Cache/Adapter/WithRedisKeyParsingTrait.php
    • src/Cache/Adapter/WithRedisOptionsTrait.php
  • [4.99.0] Check for overrides:

    • application/controllers/Testing.php
  • [4.99.0] BREAKING: CacheAdapterInterface DI alias removed

    • Client repos using di()->get(CacheAdapterInterface::class) must switch to the named service 'cache.l2'
    • Old: di()->get(Advisable\Cache\Adapter\CacheAdapterInterface::class)
    • New: di()->get('cache.l2') (or 'cache.l1' / 'cache.l0' for other tiers)
  • [4.99.0] New .env vars (copy from .env.example):

    • APP_CACHE_L2_* (replaces old APP_CACHE_*)
    • APP_CACHE_L1_* (new tier)
    • APP_DEFERRED_TASK_ENABLED, APP_DEFERRED_TASK_BUDGET_SECONDS
    • APP_CB_{META,MATOMO,MANAGO,PROJECT_AGORA,ADVISABLE_AI}_* (circuit breaker per-service)
    • DEFERRED_TASK_LOG_THRESHOLD (Monolog channel)
  • [4.99.0] Check for overrides (db-roundtrip-optimizations):

    • ecommercen/core/Adv_base_controller.php
    • ecommercen/core/Adv_front_controller.php
    • ecommercen/core/Container.php
    • ecommercen/core/models/Adv_base_model.php
    • ecommercen/eshop/models/Adv_gifts_model.php
    • ecommercen/eshop/models/Adv_product_category_model.php
    • ecommercen/eshop/models/Adv_products_in_cart_model.php
    • ecommercen/eshop/models/Adv_sliders_model.php
    • ecommercen/libraries/AdvCartResource.php
    • ecommercen/libraries/AdvCountriesCounties.php
    • ecommercen/eshop/controllers/Adv_eshop.php
    • ecommercen/eshop/controllers/Adv_home.php
    • ecommercen/eshop/controllers/Adv_products.php
    • ecommercen/eshop/controllers/Adv_product_categories.php
    • ecommercen/eshop/controllers/Adv_product_categories_admin.php
    • ecommercen/eshop/controllers/Adv_products_admin.php
    • ecommercen/eshop/controllers/Adv_gifts_admin.php
    • ecommercen/eshop/controllers/Adv_order.php
    • ecommercen/eshop/controllers/Adv_customer.php
    • ecommercen/checkout/controllers/Adv_checkout.php
    • ecommercen/ai/libraries/AdvAdvisableAI.php
    • ecommercen/seo/libraries/Adv_seo_lib.php
    • ecommercen/job/libraries/AdvClearCacheOnLimit.php
    • ecommercen/search/controllers/Adv_search.php
    • ecommercen/settings/controllers/Adv_settings.php
    • ecommercen/eshop/libraries/AdvTransportersRegistry.php
    • ecommercen/api/controllers/AdvApiCartController.php
    • ecommercen/helpers/debug_helper.php
    • ecommercen/helpers/shopmodule_helper.php
    • application/libraries/Pscache.php
    • application/libraries/Advauth.php
    • application/libraries/ProjectAgoraFactory.php
    • application/config/hooks.php
    • application/config/cache.php
    • application/config/monolog.php
    • application/controllers/Healthz.php
  • [4.99.0] Check for overrides:

    • Gifts_admin::add
  • [4.99.0] Check for overrides:

    • src/Domains/Support/Repository/BaseRepository.php
    • src/Domains/Support/Repository/RelationLoader/AbstractRelationLoader.php
    • src/Domains/Support/Repository/RelationLoader/BelongsToLoader.php
    • src/Domains/Support/Repository/RelationLoader/HasOneLoader.php
    • src/Domains/Support/Repository/RelationLoader/ManyToManyLoader.php
    • src/Domains/Support/Repository/RelationLoader/OneToManyLoader.php
    • src/Domains/Support/Repository/RelationLoader/RelationContext.php
    • src/Domains/Support/Repository/RelationLoader/RelationLoaderInterface.php
    • src/Domains/Support/Repository/RelationLoader/RelationLoaderRegistry.php
  • [4.99.0] Check for overrides (REST auth separation):

    • src/Rest/Auth/Auth.phpdeleted (replaced by AdminAuth + CustomerAuth)
    • src/Rest/Auth/RestApiUser.phpdeleted
    • src/Rest/Auth/AdminAuth.phpnew
    • src/Rest/Auth/CustomerAuth.phpnew
    • src/Rest/Auth/BackendGuard.phpnew
    • src/Rest/Auth/FrontendGuard.phpnew
    • src/Rest/Auth/AnyGuard.phpnew
    • src/Rest/Support/Resources/ResourceContext.phpnew
    • src/Rest/Auth/Tokens.php — removed uid from JWT payload
    • src/Rest/Auth/RefreshTokenModel.php — added user_type parameter
    • src/Rest/Auth/container.php — replaced Auth with AdminAuth + CustomerAuth
    • src/Rest/Support/Controllers/HandlesRestfulActions.php — added ResourceContext wiring
    • src/Rest/Support/Resources/BaseResource.php — added context propagation
    • src/Rest/Support/Resources/BaseCollection.php — added context propagation
    • application/config/constants.php — REST_BACKEND_USER='backend', REST_FRONTEND_USER='customer'
    • application/config/rest_routes.php — new admin/customer auth routes
  • [4.99.0] Actions: php migrator.php migrate

    • 20260309131750_add_user_type_to_refresh_tokens.php
  • [4.99.0] PHPUnit Test Infrastructure:

    • New base class tests/CiTestCase.php for tests needing CodeIgniter — extend this instead of TestCase for integration/legacy tests
    • phpunit.xml.dist is committed (shared defaults); create phpunit.xml locally for developer overrides (gitignored)
    • Legacy test patterns: use ReflectionClass::newInstanceWithoutConstructor() to bypass CI-dependent constructors, ReflectionMethod::setAccessible(true) for protected/private methods
    • Pipeline CI test steps were removed from this branch and will be addressed in a separate issue
    • See Testing Guide for full documentation
  • [4.99.0] Agent-First Orchestration System documentation:

    • System Overview — instruction-based architecture, bypass mechanism, infrastructure components
    • Agent Catalog — all 12 agents with purpose, skills, repo scope, and smart behaviors
    • Repo Flexibility — main vs client file placement, custom/ layer, fresh fork handling
    • Usage Guide — day-to-day usage, workflow chains, troubleshooting
  • [4.99.0] Check for overrides:

    • src/Domains/Product/ProductList/Group/ListRequest.php
    • src/Domains/Product/ProductList/Group/Repository/Entity.php
    • src/Domains/Product/ProductList/Group/Repository/Repository.php
    • src/Domains/Product/ProductList/Group/Repository/RepositoryConfigurator.php
    • src/Domains/Product/ProductList/Group/Service.php
    • src/Domains/Product/ProductList/ListRequest.php
    • src/Domains/Product/ProductList/ProductLp/ListRequest.php
    • src/Domains/Product/ProductList/ProductLp/Repository/Entity.php
    • src/Domains/Product/ProductList/ProductLp/Repository/Repository.php
    • src/Domains/Product/ProductList/ProductLp/Repository/RepositoryConfigurator.php
    • src/Domains/Product/ProductList/ProductLp/Service.php
    • src/Domains/Product/ProductList/Repository/Entity.php
    • src/Domains/Product/ProductList/Repository/MuiEntity.php
    • src/Domains/Product/ProductList/Repository/MuiRepository.php
    • src/Domains/Product/ProductList/Repository/Repository.php
    • src/Domains/Product/ProductList/Repository/RepositoryConfigurator.php
    • src/Domains/Product/ProductList/Service.php
    • src/Domains/Product/container.php
    • src/Rest/Product/Controllers/ProductList.php
    • src/Rest/Product/Resources/ProductList/Collection.php
    • src/Rest/Product/Resources/ProductList/Group/Collection.php
    • src/Rest/Product/Resources/ProductList/Group/Resource.php
    • src/Rest/Product/Resources/ProductList/MuiCollection.php
    • src/Rest/Product/Resources/ProductList/MuiResource.php
    • src/Rest/Product/Resources/ProductList/ProductLp/Collection.php
    • src/Rest/Product/Resources/ProductList/ProductLp/Resource.php
    • src/Rest/Product/Resources/ProductList/Resource.php
    • src/Rest/Product/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/ProductList/Repository/Specification/FilterByTranslation.php
    • src/Domains/Product/ProductList/Repository/Specification/SortByTranslation.php
    • src/Rest/Product/Controllers/ProductListGroup.php
    • src/Rest/Product/Controllers/ProductListProductLp.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/Variation/Group/ListRequest.php
    • src/Domains/Product/Variation/Group/Repository/Entity.php
    • src/Domains/Product/Variation/Group/Repository/MuiEntity.php
    • src/Domains/Product/Variation/Group/Repository/MuiRepository.php
    • src/Domains/Product/Variation/Group/Repository/Repository.php
    • src/Domains/Product/Variation/Group/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Variation/Group/Service.php
    • src/Domains/Product/Variation/ListRequest.php
    • src/Domains/Product/Variation/Repository/Entity.php
    • src/Domains/Product/Variation/Repository/MuiEntity.php
    • src/Domains/Product/Variation/Repository/MuiRepository.php
    • src/Domains/Product/Variation/Repository/Repository.php
    • src/Domains/Product/Variation/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Variation/Service.php
    • src/Domains/Product/Variation/Value/ListRequest.php
    • src/Domains/Product/Variation/Value/Repository/Entity.php
    • src/Domains/Product/Variation/Value/Repository/MuiEntity.php
    • src/Domains/Product/Variation/Value/Repository/MuiRepository.php
    • src/Domains/Product/Variation/Value/Repository/Repository.php
    • src/Domains/Product/Variation/Value/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Variation/Value/Service.php
    • src/Rest/Product/Controllers/Variation.php
    • src/Rest/Product/Controllers/VariationGroup.php
    • src/Rest/Product/Controllers/VariationValue.php
    • src/Rest/Product/Resources/Variation/Collection.php
    • src/Rest/Product/Resources/Variation/MuiCollection.php
    • src/Rest/Product/Resources/Variation/MuiResource.php
    • src/Rest/Product/Resources/Variation/Resource.php
    • src/Rest/Product/Resources/VariationGroup/Collection.php
    • src/Rest/Product/Resources/VariationGroup/MuiCollection.php
    • src/Rest/Product/Resources/VariationGroup/MuiResource.php
    • src/Rest/Product/Resources/VariationGroup/Resource.php
    • src/Rest/Product/Resources/VariationValue/Collection.php
    • src/Rest/Product/Resources/VariationValue/MuiCollection.php
    • src/Rest/Product/Resources/VariationValue/MuiResource.php
    • src/Rest/Product/Resources/VariationValue/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/Download/ListRequest.php
    • src/Domains/Product/Download/Repository/Entity.php
    • src/Domains/Product/Download/Repository/Repository.php
    • src/Domains/Product/Download/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Download/Service.php
    • src/Domains/Product/Review/ListRequest.php
    • src/Domains/Product/Review/Repository/Entity.php
    • src/Domains/Product/Review/Repository/Repository.php
    • src/Domains/Product/Review/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Review/Service.php
    • src/Rest/Product/Controllers/Download.php
    • src/Rest/Product/Controllers/Review.php
    • src/Rest/Product/Resources/Download/Collection.php
    • src/Rest/Product/Resources/Download/Resource.php
    • src/Rest/Product/Resources/Review/Collection.php
    • src/Rest/Product/Resources/Review/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/Related/Group/ListRequest.php
    • src/Domains/Product/Related/Group/Repository/Entity.php
    • src/Domains/Product/Related/Group/Repository/MuiEntity.php
    • src/Domains/Product/Related/Group/Repository/MuiRepository.php
    • src/Domains/Product/Related/Group/Repository/Repository.php
    • src/Domains/Product/Related/Group/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Related/Group/Service.php
    • src/Domains/Product/Related/ListRequest.php
    • src/Domains/Product/Related/Repository/Entity.php
    • src/Domains/Product/Related/Repository/Repository.php
    • src/Domains/Product/Related/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Related/Service.php
    • src/Rest/Product/Controllers/Related.php
    • src/Rest/Product/Controllers/RelatedGroup.php
    • src/Rest/Product/Resources/Related/Collection.php
    • src/Rest/Product/Resources/Related/Resource.php
    • src/Rest/Product/Resources/RelatedGroup/Collection.php
    • src/Rest/Product/Resources/RelatedGroup/MuiCollection.php
    • src/Rest/Product/Resources/RelatedGroup/MuiResource.php
    • src/Rest/Product/Resources/RelatedGroup/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/Bundle/BuilderReference/ListRequest.php
    • src/Domains/Product/Bundle/BuilderReference/Repository/Entity.php
    • src/Domains/Product/Bundle/BuilderReference/Repository/Repository.php
    • src/Domains/Product/Bundle/BuilderReference/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Bundle/BuilderReference/Service.php
    • src/Domains/Product/Bundle/Criteria/ListRequest.php
    • src/Domains/Product/Bundle/Criteria/Repository/Entity.php
    • src/Domains/Product/Bundle/Criteria/Repository/Repository.php
    • src/Domains/Product/Bundle/Criteria/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Bundle/Criteria/Service.php
    • src/Domains/Product/Bundle/CriteriaReference/ListRequest.php
    • src/Domains/Product/Bundle/CriteriaReference/Repository/Entity.php
    • src/Domains/Product/Bundle/CriteriaReference/Repository/Repository.php
    • src/Domains/Product/Bundle/CriteriaReference/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Bundle/CriteriaReference/Service.php
    • src/Domains/Product/Bundle/Display/ListRequest.php
    • src/Domains/Product/Bundle/Display/Repository/Entity.php
    • src/Domains/Product/Bundle/Display/Repository/Repository.php
    • src/Domains/Product/Bundle/Display/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Bundle/Display/Service.php
    • src/Domains/Product/Bundle/ListRequest.php
    • src/Domains/Product/Bundle/Pricing/ListRequest.php
    • src/Domains/Product/Bundle/Pricing/Repository/Entity.php
    • src/Domains/Product/Bundle/Pricing/Repository/Repository.php
    • src/Domains/Product/Bundle/Pricing/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Bundle/Pricing/Service.php
    • src/Domains/Product/Bundle/Repository/Entity.php
    • src/Domains/Product/Bundle/Repository/Repository.php
    • src/Domains/Product/Bundle/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Bundle/Service.php
    • src/Rest/Product/Controllers/Bundle.php
    • src/Rest/Product/Controllers/BundleBuilderReference.php
    • src/Rest/Product/Controllers/BundleCriteria.php
    • src/Rest/Product/Controllers/BundleCriteriaReference.php
    • src/Rest/Product/Controllers/BundleDisplay.php
    • src/Rest/Product/Controllers/BundlePricing.php
    • src/Rest/Product/Resources/Bundle/Collection.php
    • src/Rest/Product/Resources/Bundle/Resource.php
    • src/Rest/Product/Resources/BundleBuilderReference/Collection.php
    • src/Rest/Product/Resources/BundleBuilderReference/Resource.php
    • src/Rest/Product/Resources/BundleCriteria/Collection.php
    • src/Rest/Product/Resources/BundleCriteria/Resource.php
    • src/Rest/Product/Resources/BundleCriteriaReference/Collection.php
    • src/Rest/Product/Resources/BundleCriteriaReference/Resource.php
    • src/Rest/Product/Resources/BundleDisplay/Collection.php
    • src/Rest/Product/Resources/BundleDisplay/Resource.php
    • src/Rest/Product/Resources/BundlePricing/Collection.php
    • src/Rest/Product/Resources/BundlePricing/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/ProductMeta/ListRequest.php
    • src/Domains/Product/ProductMeta/Repository/Entity.php
    • src/Domains/Product/ProductMeta/Repository/Repository.php
    • src/Domains/Product/ProductMeta/Repository/RepositoryConfigurator.php
    • src/Domains/Product/ProductMeta/Service.php
    • src/Rest/Product/Controllers/ProductMeta.php
    • src/Rest/Product/Resources/ProductMeta/Collection.php
    • src/Rest/Product/Resources/ProductMeta/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/Product/Repository/RepositoryConfigurator.php
    • src/Rest/Product/Resources/Product/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Currency/Currency/ListRequest.php
    • src/Domains/Currency/Currency/Repository/Entity.php
    • src/Domains/Currency/Currency/Repository/Repository.php
    • src/Domains/Currency/Currency/Repository/RepositoryConfigurator.php
    • src/Domains/Currency/Currency/Service.php
    • src/Domains/Currency/container.php
    • src/Rest/Currency/Controllers/Currency.php
    • src/Rest/Currency/Resources/Currency/Collection.php
    • src/Rest/Currency/Resources/Currency/Resource.php
    • src/Rest/Currency/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/WaitingList/ListRequest.php
    • src/Domains/Product/WaitingList/Repository/Entity.php
    • src/Domains/Product/WaitingList/Repository/Repository.php
    • src/Domains/Product/WaitingList/Repository/RepositoryConfigurator.php
    • src/Domains/Product/WaitingList/Service.php
    • src/Domains/Product/Wishlist/ListRequest.php
    • src/Domains/Product/Wishlist/Repository/Entity.php
    • src/Domains/Product/Wishlist/Repository/Repository.php
    • src/Domains/Product/Wishlist/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Wishlist/Service.php
    • src/Rest/Product/Controllers/WaitingList.php
    • src/Rest/Product/Controllers/Wishlist.php
    • src/Rest/Product/Resources/WaitingList/Collection.php
    • src/Rest/Product/Resources/WaitingList/Resource.php
    • src/Rest/Product/Resources/Wishlist/Collection.php
    • src/Rest/Product/Resources/Wishlist/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/Shelfcode/ListRequest.php
    • src/Domains/Product/Shelfcode/Repository/Entity.php
    • src/Domains/Product/Shelfcode/Repository/Repository.php
    • src/Domains/Product/Shelfcode/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Shelfcode/Service.php
    • src/Rest/Product/Controllers/Product.php
    • src/Rest/Product/Controllers/Shelfcode.php
    • src/Rest/Product/Resources/Shelfcode/Collection.php
    • src/Rest/Product/Resources/Shelfcode/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Seo/DefaultMetaTag/ListRequest.php
    • src/Domains/Seo/DefaultMetaTag/Repository/Entity.php
    • src/Domains/Seo/DefaultMetaTag/Repository/Repository.php
    • src/Domains/Seo/DefaultMetaTag/Repository/RepositoryConfigurator.php
    • src/Domains/Seo/DefaultMetaTag/Service.php
    • src/Domains/Seo/Keyword/ListRequest.php
    • src/Domains/Seo/Keyword/Repository/Entity.php
    • src/Domains/Seo/Keyword/Repository/Repository.php
    • src/Domains/Seo/Keyword/Repository/RepositoryConfigurator.php
    • src/Domains/Seo/Keyword/Service.php
    • src/Domains/Seo/container.php
    • src/Rest/Seo/Controllers/DefaultMetaTag.php
    • src/Rest/Seo/Controllers/Keyword.php
    • src/Rest/Seo/Resources/DefaultMetaTag/Collection.php
    • src/Rest/Seo/Resources/DefaultMetaTag/Resource.php
    • src/Rest/Seo/Resources/Keyword/Collection.php
    • src/Rest/Seo/Resources/Keyword/Resource.php
    • src/Rest/Seo/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Event/Category/ListRequest.php
    • src/Domains/Event/Category/Repository/Entity.php
    • src/Domains/Event/Category/Repository/MuiEntity.php
    • src/Domains/Event/Category/Repository/MuiRepository.php
    • src/Domains/Event/Category/Repository/Repository.php
    • src/Domains/Event/Category/Repository/RepositoryConfigurator.php
    • src/Domains/Event/Category/Service.php
    • src/Domains/Event/Event/ListRequest.php
    • src/Domains/Event/Event/Repository/Entity.php
    • src/Domains/Event/Event/Repository/MuiEntity.php
    • src/Domains/Event/Event/Repository/MuiRepository.php
    • src/Domains/Event/Event/Repository/Repository.php
    • src/Domains/Event/Event/Repository/RepositoryConfigurator.php
    • src/Domains/Event/Event/Service.php
    • src/Domains/Event/container.php
    • src/Rest/Event/Controllers/Event.php
    • src/Rest/Event/Controllers/EventCategory.php
    • src/Rest/Event/Resources/Event/Collection.php
    • src/Rest/Event/Resources/Event/MuiCollection.php
    • src/Rest/Event/Resources/Event/MuiResource.php
    • src/Rest/Event/Resources/Event/Resource.php
    • src/Rest/Event/Resources/EventCategory/Collection.php
    • src/Rest/Event/Resources/EventCategory/MuiCollection.php
    • src/Rest/Event/Resources/EventCategory/MuiResource.php
    • src/Rest/Event/Resources/EventCategory/Resource.php
    • src/Rest/Event/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Cms/Offer/ListRequest.php
    • src/Domains/Cms/Offer/Repository/Entity.php
    • src/Domains/Cms/Offer/Repository/MuiEntity.php
    • src/Domains/Cms/Offer/Repository/MuiRepository.php
    • src/Domains/Cms/Offer/Repository/Repository.php
    • src/Domains/Cms/Offer/Repository/RepositoryConfigurator.php
    • src/Domains/Cms/Offer/Service.php
    • src/Domains/Cms/OfferCategory/ListRequest.php
    • src/Domains/Cms/OfferCategory/Repository/Entity.php
    • src/Domains/Cms/OfferCategory/Repository/MuiEntity.php
    • src/Domains/Cms/OfferCategory/Repository/MuiRepository.php
    • src/Domains/Cms/OfferCategory/Repository/Repository.php
    • src/Domains/Cms/OfferCategory/Repository/RepositoryConfigurator.php
    • src/Domains/Cms/OfferCategory/Service.php
    • src/Domains/Cms/container.php
    • src/Rest/Cms/Controllers/Offer.php
    • src/Rest/Cms/Controllers/OfferCategory.php
    • src/Rest/Cms/Resources/Offer/Collection.php
    • src/Rest/Cms/Resources/Offer/MuiCollection.php
    • src/Rest/Cms/Resources/Offer/MuiResource.php
    • src/Rest/Cms/Resources/Offer/Resource.php
    • src/Rest/Cms/Resources/OfferCategory/Collection.php
    • src/Rest/Cms/Resources/OfferCategory/MuiCollection.php
    • src/Rest/Cms/Resources/OfferCategory/MuiResource.php
    • src/Rest/Cms/Resources/OfferCategory/Resource.php
    • src/Rest/Cms/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Promotion/Gift/ListRequest.php
    • src/Domains/Promotion/Gift/Repository/Entity.php
    • src/Domains/Promotion/Gift/Repository/MuiEntity.php
    • src/Domains/Promotion/Gift/Repository/MuiRepository.php
    • src/Domains/Promotion/Gift/Repository/Repository.php
    • src/Domains/Promotion/Gift/Repository/RepositoryConfigurator.php
    • src/Domains/Promotion/Gift/Service.php
    • src/Domains/Promotion/GiftChoice/ListRequest.php
    • src/Domains/Promotion/GiftChoice/Repository/Entity.php
    • src/Domains/Promotion/GiftChoice/Repository/Repository.php
    • src/Domains/Promotion/GiftChoice/Repository/RepositoryConfigurator.php
    • src/Domains/Promotion/GiftChoice/Service.php
    • src/Domains/Promotion/GiftRequirement/ListRequest.php
    • src/Domains/Promotion/GiftRequirement/Repository/Entity.php
    • src/Domains/Promotion/GiftRequirement/Repository/Repository.php
    • src/Domains/Promotion/GiftRequirement/Repository/RepositoryConfigurator.php
    • src/Domains/Promotion/GiftRequirement/Service.php
    • src/Domains/Promotion/container.php
    • src/Rest/Promotion/Controllers/Gift.php
    • src/Rest/Promotion/Controllers/GiftChoice.php
    • src/Rest/Promotion/Controllers/GiftRequirement.php
    • src/Rest/Promotion/Resources/Gift/Collection.php
    • src/Rest/Promotion/Resources/Gift/MuiCollection.php
    • src/Rest/Promotion/Resources/Gift/MuiResource.php
    • src/Rest/Promotion/Resources/Gift/Resource.php
    • src/Rest/Promotion/Resources/GiftChoice/Collection.php
    • src/Rest/Promotion/Resources/GiftChoice/Resource.php
    • src/Rest/Promotion/Resources/GiftRequirement/Collection.php
    • src/Rest/Promotion/Resources/GiftRequirement/Resource.php
    • src/Rest/Promotion/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Promotion/Coupon/ListRequest.php
    • src/Domains/Promotion/Coupon/Repository/Entity.php
    • src/Domains/Promotion/Coupon/Repository/Repository.php
    • src/Domains/Promotion/Coupon/Repository/RepositoryConfigurator.php
    • src/Domains/Promotion/Coupon/Service.php
    • src/Domains/Promotion/CouponCode/ListRequest.php
    • src/Domains/Promotion/CouponCode/Repository/Entity.php
    • src/Domains/Promotion/CouponCode/Repository/Repository.php
    • src/Domains/Promotion/CouponCode/Repository/RepositoryConfigurator.php
    • src/Domains/Promotion/CouponCode/Service.php
    • src/Domains/Promotion/CouponProduct/ListRequest.php
    • src/Domains/Promotion/CouponProduct/Repository/Entity.php
    • src/Domains/Promotion/CouponProduct/Repository/Repository.php
    • src/Domains/Promotion/CouponProduct/Repository/RepositoryConfigurator.php
    • src/Domains/Promotion/CouponProduct/Service.php
    • src/Domains/Promotion/CouponRule/ListRequest.php
    • src/Domains/Promotion/CouponRule/Repository/Entity.php
    • src/Domains/Promotion/CouponRule/Repository/Repository.php
    • src/Domains/Promotion/CouponRule/Repository/RepositoryConfigurator.php
    • src/Domains/Promotion/CouponRule/Service.php
    • src/Domains/Promotion/CouponVendor/ListRequest.php
    • src/Domains/Promotion/CouponVendor/Repository/Entity.php
    • src/Domains/Promotion/CouponVendor/Repository/Repository.php
    • src/Domains/Promotion/CouponVendor/Repository/RepositoryConfigurator.php
    • src/Domains/Promotion/CouponVendor/Service.php
    • src/Rest/Promotion/Controllers/Coupon.php
    • src/Rest/Promotion/Controllers/CouponCode.php
    • src/Rest/Promotion/Controllers/CouponProduct.php
    • src/Rest/Promotion/Controllers/CouponRule.php
    • src/Rest/Promotion/Controllers/CouponVendor.php
    • src/Rest/Promotion/Resources/Coupon/Collection.php
    • src/Rest/Promotion/Resources/Coupon/Resource.php
    • src/Rest/Promotion/Resources/CouponCode/Collection.php
    • src/Rest/Promotion/Resources/CouponCode/Resource.php
    • src/Rest/Promotion/Resources/CouponProduct/Collection.php
    • src/Rest/Promotion/Resources/CouponProduct/Resource.php
    • src/Rest/Promotion/Resources/CouponRule/Collection.php
    • src/Rest/Promotion/Resources/CouponRule/Resource.php
    • src/Rest/Promotion/Resources/CouponVendor/Collection.php
    • src/Rest/Promotion/Resources/CouponVendor/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Order/GiftCardOrder/ListRequest.php
    • src/Domains/Order/GiftCardOrder/Repository/Entity.php
    • src/Domains/Order/GiftCardOrder/Repository/Repository.php
    • src/Domains/Order/GiftCardOrder/Repository/RepositoryConfigurator.php
    • src/Domains/Order/GiftCardOrder/Service.php
    • src/Domains/Order/container.php
    • src/Rest/Order/Controllers/GiftCardOrder.php
    • src/Rest/Order/Resources/GiftCardOrder/Collection.php
    • src/Rest/Order/Resources/GiftCardOrder/Resource.php
    • src/Rest/Order/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Transporter/CountyAvailability/ListRequest.php
    • src/Domains/Transporter/CountyAvailability/Repository/Entity.php
    • src/Domains/Transporter/CountyAvailability/Repository/Repository.php
    • src/Domains/Transporter/CountyAvailability/Repository/RepositoryConfigurator.php
    • src/Domains/Transporter/CountyAvailability/Service.php
    • src/Domains/Transporter/OptionPricing/ListRequest.php
    • src/Domains/Transporter/OptionPricing/Repository/Entity.php
    • src/Domains/Transporter/OptionPricing/Repository/Repository.php
    • src/Domains/Transporter/OptionPricing/Repository/RepositoryConfigurator.php
    • src/Domains/Transporter/OptionPricing/Service.php
    • src/Domains/Transporter/PostAvailability/ListRequest.php
    • src/Domains/Transporter/PostAvailability/Repository/Entity.php
    • src/Domains/Transporter/PostAvailability/Repository/Repository.php
    • src/Domains/Transporter/PostAvailability/Repository/RepositoryConfigurator.php
    • src/Domains/Transporter/PostAvailability/Service.php
    • src/Domains/Transporter/PostPricing/ListRequest.php
    • src/Domains/Transporter/PostPricing/Repository/Entity.php
    • src/Domains/Transporter/PostPricing/Repository/Repository.php
    • src/Domains/Transporter/PostPricing/Repository/RepositoryConfigurator.php
    • src/Domains/Transporter/PostPricing/Service.php
    • src/Domains/Transporter/Pricing/ListRequest.php
    • src/Domains/Transporter/Pricing/Repository/Entity.php
    • src/Domains/Transporter/Pricing/Repository/Repository.php
    • src/Domains/Transporter/Pricing/Repository/RepositoryConfigurator.php
    • src/Domains/Transporter/Pricing/Service.php
    • src/Domains/Transporter/PublicMapping/ListRequest.php
    • src/Domains/Transporter/PublicMapping/Repository/Entity.php
    • src/Domains/Transporter/PublicMapping/Repository/Repository.php
    • src/Domains/Transporter/PublicMapping/Repository/RepositoryConfigurator.php
    • src/Domains/Transporter/PublicMapping/Service.php
    • src/Domains/Transporter/Setting/ListRequest.php
    • src/Domains/Transporter/Setting/Repository/Entity.php
    • src/Domains/Transporter/Setting/Repository/Repository.php
    • src/Domains/Transporter/Setting/Repository/RepositoryConfigurator.php
    • src/Domains/Transporter/Setting/Service.php
    • src/Domains/Transporter/Transporter/ListRequest.php
    • src/Domains/Transporter/Transporter/Repository/Entity.php
    • src/Domains/Transporter/Transporter/Repository/MuiEntity.php
    • src/Domains/Transporter/Transporter/Repository/MuiRepository.php
    • src/Domains/Transporter/Transporter/Repository/Repository.php
    • src/Domains/Transporter/Transporter/Repository/RepositoryConfigurator.php
    • src/Domains/Transporter/Transporter/Service.php
    • src/Domains/Transporter/container.php
    • src/Rest/Transporter/Controllers/CountyAvailability.php
    • src/Rest/Transporter/Controllers/OptionPricing.php
    • src/Rest/Transporter/Controllers/PostAvailability.php
    • src/Rest/Transporter/Controllers/PostPricing.php
    • src/Rest/Transporter/Controllers/Pricing.php
    • src/Rest/Transporter/Controllers/PublicMapping.php
    • src/Rest/Transporter/Controllers/Setting.php
    • src/Rest/Transporter/Controllers/Transporter.php
    • src/Rest/Transporter/Resources/CountyAvailability/Collection.php
    • src/Rest/Transporter/Resources/CountyAvailability/Resource.php
    • src/Rest/Transporter/Resources/OptionPricing/Collection.php
    • src/Rest/Transporter/Resources/OptionPricing/Resource.php
    • src/Rest/Transporter/Resources/PostAvailability/Collection.php
    • src/Rest/Transporter/Resources/PostAvailability/Resource.php
    • src/Rest/Transporter/Resources/PostPricing/Collection.php
    • src/Rest/Transporter/Resources/PostPricing/Resource.php
    • src/Rest/Transporter/Resources/Pricing/Collection.php
    • src/Rest/Transporter/Resources/Pricing/Resource.php
    • src/Rest/Transporter/Resources/PublicMapping/Collection.php
    • src/Rest/Transporter/Resources/PublicMapping/Resource.php
    • src/Rest/Transporter/Resources/Setting/Collection.php
    • src/Rest/Transporter/Resources/Setting/Resource.php
    • src/Rest/Transporter/Resources/Transporter/Collection.php
    • src/Rest/Transporter/Resources/Transporter/MuiCollection.php
    • src/Rest/Transporter/Resources/Transporter/MuiResource.php
    • src/Rest/Transporter/Resources/Transporter/Resource.php
    • src/Rest/Transporter/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/CustomizationSchema/ListRequest.php
    • src/Domains/Product/CustomizationSchema/Repository/Entity.php
    • src/Domains/Product/CustomizationSchema/Repository/Repository.php
    • src/Domains/Product/CustomizationSchema/Repository/RepositoryConfigurator.php
    • src/Domains/Product/CustomizationSchema/Service.php
    • src/Rest/Product/Controllers/CustomizationSchema.php
    • src/Rest/Product/Resources/CustomizationSchema/Collection.php
    • src/Rest/Product/Resources/CustomizationSchema/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Order/Order/Repository/RepositoryConfigurator.php
    • src/Domains/Order/OrderTag/ListRequest.php
    • src/Domains/Order/OrderTag/Repository/Entity.php
    • src/Domains/Order/OrderTag/Repository/Repository.php
    • src/Domains/Order/OrderTag/Repository/RepositoryConfigurator.php
    • src/Domains/Order/OrderTag/Service.php
    • src/Rest/Order/Controllers/Order.php
    • src/Rest/Order/Controllers/OrderTag.php
    • src/Rest/Order/Resources/Order/Resource.php
    • src/Rest/Order/Resources/OrderTag/Collection.php
    • src/Rest/Order/Resources/OrderTag/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Cms/Video/ListRequest.php
    • src/Domains/Cms/Video/Repository/Entity.php
    • src/Domains/Cms/Video/Repository/MuiEntity.php
    • src/Domains/Cms/Video/Repository/MuiRepository.php
    • src/Domains/Cms/Video/Repository/Repository.php
    • src/Domains/Cms/Video/Repository/RepositoryConfigurator.php
    • src/Domains/Cms/Video/Repository/Specification/FilterByTranslation.php
    • src/Domains/Cms/Video/Repository/Specification/SortByTranslation.php
    • src/Domains/Cms/Video/Service.php
    • src/Domains/Product/Vendor/Repository/RepositoryConfigurator.php
    • src/Rest/Cms/Controllers/Video.php
    • src/Rest/Cms/Resources/Video/Collection.php
    • src/Rest/Cms/Resources/Video/MuiCollection.php
    • src/Rest/Cms/Resources/Video/MuiResource.php
    • src/Rest/Cms/Resources/Video/Resource.php
    • src/Rest/Product/Controllers/Vendor.php
    • src/Rest/Product/Resources/Vendor/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Plus/Audience/ListRequest.php
    • src/Domains/Plus/Audience/Repository/Entity.php
    • src/Domains/Plus/Audience/Repository/Repository.php
    • src/Domains/Plus/Audience/Repository/RepositoryConfigurator.php
    • src/Domains/Plus/Audience/Service.php
    • src/Domains/Plus/AudienceCriteria/ListRequest.php
    • src/Domains/Plus/AudienceCriteria/Repository/Entity.php
    • src/Domains/Plus/AudienceCriteria/Repository/Repository.php
    • src/Domains/Plus/AudienceCriteria/Repository/RepositoryConfigurator.php
    • src/Domains/Plus/AudienceCriteria/Service.php
    • src/Domains/Plus/container.php
    • src/Domains/Slider/Slide/Repository/RepositoryConfigurator.php
    • src/Rest/Plus/Controllers/Audience.php
    • src/Rest/Plus/Controllers/AudienceCriteria.php
    • src/Rest/Plus/Resources/Audience/Collection.php
    • src/Rest/Plus/Resources/Audience/Resource.php
    • src/Rest/Plus/Resources/AudienceCriteria/Collection.php
    • src/Rest/Plus/Resources/AudienceCriteria/Resource.php
    • src/Rest/Plus/container.php
    • src/Rest/Slider/Controllers/Slide.php
    • src/Rest/Slider/Resources/Slide/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Order/Customer/ListRequest.php src/Domains/Customer/Customer/ListRequest.php
    • src/Domains/Order/Customer/Repository/Entity.php src/Domains/Customer/Customer/Repository/Entity.php
    • src/Domains/Order/Customer/Repository/Repository.php src/Domains/Customer/Customer/Repository/Repository.php
    • src/Domains/Customer/Customer/Repository/RepositoryConfigurator.php
    • src/Domains/Order/Customer/Service.php src/Domains/Customer/Customer/Service.php
    • src/Domains/Customer/CustomerCampaign/ListRequest.php
    • src/Domains/Customer/CustomerCampaign/Repository/Entity.php
    • src/Domains/Customer/CustomerCampaign/Repository/Repository.php
    • src/Domains/Customer/CustomerCampaign/Repository/RepositoryConfigurator.php
    • src/Domains/Customer/CustomerCampaign/Service.php
    • src/Domains/Customer/CustomerMessageHistory/ListRequest.php
    • src/Domains/Customer/CustomerMessageHistory/Repository/Entity.php
    • src/Domains/Customer/CustomerMessageHistory/Repository/Repository.php
    • src/Domains/Customer/CustomerMessageHistory/Repository/RepositoryConfigurator.php
    • src/Domains/Customer/CustomerMessageHistory/Service.php
    • src/Domains/Customer/CustomerReview/ListRequest.php
    • src/Domains/Customer/CustomerReview/Repository/Entity.php
    • src/Domains/Customer/CustomerReview/Repository/Repository.php
    • src/Domains/Customer/CustomerReview/Repository/RepositoryConfigurator.php
    • src/Domains/Customer/CustomerReview/Service.php
    • src/Domains/Customer/CustomerSmsMarketing/ListRequest.php
    • src/Domains/Customer/CustomerSmsMarketing/Repository/Entity.php
    • src/Domains/Customer/CustomerSmsMarketing/Repository/Repository.php
    • src/Domains/Customer/CustomerSmsMarketing/Repository/RepositoryConfigurator.php
    • src/Domains/Customer/CustomerSmsMarketing/Service.php
    • src/Domains/Customer/CustomerTag/ListRequest.php
    • src/Domains/Customer/CustomerTag/Repository/Entity.php
    • src/Domains/Customer/CustomerTag/Repository/Repository.php
    • src/Domains/Order/Customer/Repository/RepositoryConfigurator.php src/Domains/Customer/CustomerTag/Repository/RepositoryConfigurator.php
    • src/Domains/Customer/CustomerTag/Service.php
    • src/Domains/Customer/container.php
    • src/Rest/Order/Controllers/Customer.php src/Rest/Customer/Controllers/Customer.php
    • src/Rest/Customer/Controllers/CustomerCampaign.php
    • src/Rest/Customer/Controllers/CustomerMessageHistory.php
    • src/Rest/Customer/Controllers/CustomerReview.php
    • src/Rest/Customer/Controllers/CustomerSmsMarketing.php
    • src/Rest/Customer/Controllers/CustomerTag.php
    • src/Rest/Order/Resources/Customer/Collection.php src/Rest/Customer/Resources/Customer/Collection.php
    • src/Rest/Order/Resources/Customer/Resource.php src/Rest/Customer/Resources/Customer/Resource.php
    • src/Rest/Customer/Resources/CustomerCampaign/Collection.php
    • src/Rest/Customer/Resources/CustomerCampaign/Resource.php
    • src/Rest/Customer/Resources/CustomerMessageHistory/Collection.php
    • src/Rest/Customer/Resources/CustomerMessageHistory/Resource.php
    • src/Rest/Customer/Resources/CustomerReview/Collection.php
    • src/Rest/Customer/Resources/CustomerReview/Resource.php
    • src/Rest/Customer/Resources/CustomerSmsMarketing/Collection.php
    • src/Rest/Customer/Resources/CustomerSmsMarketing/Resource.php
    • src/Rest/Customer/Resources/CustomerTag/Collection.php
    • src/Rest/Customer/Resources/CustomerTag/Resource.php
    • src/Rest/Customer/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Cms/Blog/Article/Repository/RepositoryConfigurator.php
    • src/Domains/Product/Category/Repository/RepositoryConfigurator.php
    • src/Rest/Cms/Resources/Blog/Article/Resource.php
    • src/Rest/Product/Controllers/Category.php
    • src/Rest/Product/Resources/Category/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Cms/ContactEmail/ListRequest.php
    • src/Domains/Cms/ContactEmail/Repository/Entity.php
    • src/Domains/Cms/ContactEmail/Repository/Repository.php
    • src/Domains/Cms/ContactEmail/Repository/RepositoryConfigurator.php
    • src/Domains/Cms/ContactEmail/Service.php
    • src/Rest/Cms/Controllers/ContactEmail.php
    • src/Rest/Cms/Resources/ContactEmail/Collection.php
    • src/Rest/Cms/Resources/ContactEmail/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Cms/Cookie/ListRequest.php
    • src/Domains/Cms/Cookie/Repository/Entity.php
    • src/Domains/Cms/Cookie/Repository/Repository.php
    • src/Domains/Cms/Cookie/Repository/RepositoryConfigurator.php
    • src/Domains/Cms/Cookie/Service.php
    • src/Rest/Cms/Controllers/Cookie.php
    • src/Rest/Cms/Resources/Cookie/Collection.php
    • src/Rest/Cms/Resources/Cookie/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Product/PriceTracking/ListRequest.php
    • src/Domains/Product/PriceTracking/Repository/Entity.php
    • src/Domains/Product/PriceTracking/Repository/Repository.php
    • src/Domains/Product/PriceTracking/Repository/RepositoryConfigurator.php
    • src/Domains/Product/PriceTracking/Service.php
    • src/Rest/Product/Controllers/PriceTracking.php
    • src/Rest/Product/Resources/PriceTracking/Collection.php
    • src/Rest/Product/Resources/PriceTracking/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Order/Note/ListRequest.php
    • src/Domains/Order/Note/Repository/Entity.php
    • src/Domains/Order/Note/Repository/Repository.php
    • src/Domains/Order/Note/Service.php
    • src/Rest/Order/Controllers/Note.php
    • src/Rest/Order/Resources/Note/Collection.php
    • src/Rest/Order/Resources/Note/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Marketplace/Iris/Order/ListRequest.php
    • src/Domains/Marketplace/Iris/Order/Repository/Entity.php
    • src/Domains/Marketplace/Iris/Order/Repository/Repository.php
    • src/Domains/Marketplace/Iris/Order/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Iris/Order/Service.php
    • src/Domains/Marketplace/Jcc/Order/ListRequest.php
    • src/Domains/Marketplace/Jcc/Order/Repository/Entity.php
    • src/Domains/Marketplace/Jcc/Order/Repository/Repository.php
    • src/Domains/Marketplace/Jcc/Order/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Jcc/Order/Service.php
    • src/Domains/Marketplace/Public/LineItem/ListRequest.php
    • src/Domains/Marketplace/Public/LineItem/Repository/Entity.php
    • src/Domains/Marketplace/Public/LineItem/Repository/Repository.php
    • src/Domains/Marketplace/Public/LineItem/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Public/LineItem/Service.php
    • src/Domains/Marketplace/Public/Order/ListRequest.php
    • src/Domains/Marketplace/Public/Order/Repository/Entity.php
    • src/Domains/Marketplace/Public/Order/Repository/Repository.php
    • src/Domains/Marketplace/Public/Order/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Public/Order/Service.php
    • src/Domains/Marketplace/Shopflix/LineItem/ListRequest.php
    • src/Domains/Marketplace/Shopflix/LineItem/Repository/Entity.php
    • src/Domains/Marketplace/Shopflix/LineItem/Repository/Repository.php
    • src/Domains/Marketplace/Shopflix/LineItem/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Shopflix/LineItem/Service.php
    • src/Domains/Marketplace/Shopflix/Order/ListRequest.php
    • src/Domains/Marketplace/Shopflix/Order/Repository/Entity.php
    • src/Domains/Marketplace/Shopflix/Order/Repository/Repository.php
    • src/Domains/Marketplace/Shopflix/Order/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Shopflix/Order/Service.php
    • src/Domains/Marketplace/Skroutz/InvoiceDetail/ListRequest.php
    • src/Domains/Marketplace/Skroutz/InvoiceDetail/Repository/Entity.php
    • src/Domains/Marketplace/Skroutz/InvoiceDetail/Repository/Repository.php
    • src/Domains/Marketplace/Skroutz/InvoiceDetail/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Skroutz/InvoiceDetail/Service.php
    • src/Domains/Marketplace/Skroutz/LineItem/ListRequest.php
    • src/Domains/Marketplace/Skroutz/LineItem/Repository/Entity.php
    • src/Domains/Marketplace/Skroutz/LineItem/Repository/Repository.php
    • src/Domains/Marketplace/Skroutz/LineItem/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Skroutz/LineItem/Service.php
    • src/Domains/Marketplace/Skroutz/Order/ListRequest.php
    • src/Domains/Marketplace/Skroutz/Order/Repository/Entity.php
    • src/Domains/Marketplace/Skroutz/Order/Repository/Repository.php
    • src/Domains/Marketplace/Skroutz/Order/Repository/RepositoryConfigurator.php
    • src/Domains/Marketplace/Skroutz/Order/Service.php
    • src/Domains/Marketplace/container.php
    • src/Rest/Marketplace/Controllers/IrisOrder.php
    • src/Rest/Marketplace/Controllers/JccOrder.php
    • src/Rest/Marketplace/Controllers/PublicLineItem.php
    • src/Rest/Marketplace/Controllers/PublicOrder.php
    • src/Rest/Marketplace/Controllers/ShopflixLineItem.php
    • src/Rest/Marketplace/Controllers/ShopflixOrder.php
    • src/Rest/Marketplace/Controllers/SkroutzInvoiceDetail.php
    • src/Rest/Marketplace/Controllers/SkroutzLineItem.php
    • src/Rest/Marketplace/Controllers/SkroutzOrder.php
    • src/Rest/Marketplace/Resources/IrisOrder/Collection.php
    • src/Rest/Marketplace/Resources/IrisOrder/Resource.php
    • src/Rest/Marketplace/Resources/JccOrder/Collection.php
    • src/Rest/Marketplace/Resources/JccOrder/Resource.php
    • src/Rest/Marketplace/Resources/PublicLineItem/Collection.php
    • src/Rest/Marketplace/Resources/PublicLineItem/Resource.php
    • src/Rest/Marketplace/Resources/PublicOrder/Collection.php
    • src/Rest/Marketplace/Resources/PublicOrder/Resource.php
    • src/Rest/Marketplace/Resources/ShopflixLineItem/Collection.php
    • src/Rest/Marketplace/Resources/ShopflixLineItem/Resource.php
    • src/Rest/Marketplace/Resources/ShopflixOrder/Collection.php
    • src/Rest/Marketplace/Resources/ShopflixOrder/Resource.php
    • src/Rest/Marketplace/Resources/SkroutzInvoiceDetail/Collection.php
    • src/Rest/Marketplace/Resources/SkroutzInvoiceDetail/Resource.php
    • src/Rest/Marketplace/Resources/SkroutzLineItem/Collection.php
    • src/Rest/Marketplace/Resources/SkroutzLineItem/Resource.php
    • src/Rest/Marketplace/Resources/SkroutzOrder/Collection.php
    • src/Rest/Marketplace/Resources/SkroutzOrder/Resource.php
    • src/Rest/Marketplace/container.php
  • [4.99.0] Check for overrides:

    • src/Domains/Order/DhlVoucher/ListRequest.php
    • src/Domains/Order/DhlVoucher/Repository/Entity.php
    • src/Domains/Order/DhlVoucher/Repository/Repository.php
    • src/Domains/Order/DhlVoucher/Repository/RepositoryConfigurator.php
    • src/Domains/Order/DhlVoucher/Service.php
    • src/Domains/Order/SmartPoint/ListRequest.php
    • src/Domains/Order/SmartPoint/Repository/Entity.php
    • src/Domains/Order/SmartPoint/Repository/Repository.php
    • src/Domains/Order/SmartPoint/Repository/RepositoryConfigurator.php
    • src/Domains/Order/SmartPoint/Service.php
    • src/Rest/Order/Controllers/DhlVoucher.php
    • src/Rest/Order/Controllers/SmartPoint.php
    • src/Rest/Order/Resources/DhlVoucher/Collection.php
    • src/Rest/Order/Resources/DhlVoucher/Resource.php
    • src/Rest/Order/Resources/SmartPoint/Collection.php
    • src/Rest/Order/Resources/SmartPoint/Resource.php
  • [4.99.0] Check for overrides:

    • src/Domains/Ai/ContentGeneration/ListRequest.php
    • src/Domains/Ai/ContentGeneration/Repository/Entity.php
    • src/Domains/Ai/ContentGeneration/Repository/Repository.php
    • src/Domains/Ai/ContentGeneration/Repository/RepositoryConfigurator.php
    • src/Domains/Ai/ContentGeneration/Service.php
    • src/Domains/Ai/Position/ListRequest.php
    • src/Domains/Ai/Position/Repository/Entity.php
    • src/Domains/Ai/Position/Repository/MuiEntity.php
    • src/Domains/Ai/Position/Repository/MuiRepository.php
    • src/Domains/Ai/Position/Repository/Repository.php
    • src/Domains/Ai/Position/Repository/RepositoryConfigurator.php
    • src/Domains/Ai/Position/Repository/Specification/FilterByTranslation.php
    • src/Domains/Ai/Position/Repository/Specification/SortByTranslation.php
    • src/Domains/Ai/Position/Service.php
    • src/Domains/Ai/container.php
    • src/Rest/Ai/Controllers/ContentGeneration.php
    • src/Rest/Ai/Controllers/Position.php
    • src/Rest/Ai/Resources/ContentGeneration/Collection.php
    • src/Rest/Ai/Resources/ContentGeneration/Resource.php
    • src/Rest/Ai/Resources/Position/Collection.php
    • src/Rest/Ai/Resources/Position/MuiCollection.php
    • src/Rest/Ai/Resources/Position/MuiResource.php
    • src/Rest/Ai/Resources/Position/Resource.php
    • src/Rest/Ai/container.php
  • [4.99.0] Check for overrides:

    • src/Rest/Product/Resources/Line/Resource.php
    • src/Rest/Product/Resources/Promo/Resource.php
    • src/Rest/Product/Resources/Supplier/Resource.php
    • src/Rest/Product/Resources/Tag/Resource.php
    • src/Rest/Product/Resources/TagCategory/Resource.php
  • [4.99.0] Needs composer install

  • [4.99.0] Check for usage the following methods in the client and fetch them from the previous version of master if needed:

    • Product_category_model::getFirstLevelChildrenCategories
    • Product_category_model::num_cat_products
  • [4.99.0] Check for usage the following methods in the client and replace them with the equivalent:

  • Product_category_model::get_parent_slug instead of Product_model::get_parent_slug

  • [4.99.0] Check for overrides:

    • Gift_choices_model::getOptionsWithActiveStockForGiftIds
    • Vendors::vendors
  • [4.99.0] Gift rule 13 (cheapest free) — check for overrides:

    • ecommercen/eshop/models/Adv_gifts_model.php — new methods ruleProductsCheapestFreeValidator, getCheapestRequirementProductInCart; updated getGiftForProductsInCart switch and getActiveGiftRules filter
    • ecommercen/eshop/models/Adv_gift_rules_model.php — new rule 13 entry in $data
    • ecommercen/eshop/models/Adv_order_model.php — new methods getRule13GiftAdjustments, getRule13GiftAdjustmentsForProducts, adjustCartForRule13Gifts; updated baseParseCartContents and processOrder
    • ecommercen/eshop/models/Adv_order_model.php — in method create_order_admin we change the if ($otherPostElements['transport_id']) with if (!empty($otherPostElements['transport_id'])) for the case that we do not have transporters
    • ecommercen/eshop/controllers/Adv_orders_admin.php — new getRule13GiftAdjustmentsForProducts usage in gifts_for_products and createAdminFakeCart
    • ecommercen/libraries/AdvCartResource.php — new adjustCartContentsForRule13 method
    • assets/admin/js/order-gifts-block.js — rule 13 auto-assign handling in Vue Vuex getter and reapplyRule13Deductions
    • See Gifts Module Guide for full documentation
  • [4.99.0] Need npm run admin-production