Appearance
<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>
Version 4
version 4.99
[4.99.17] fix(docker): bump Node base image to 24.13.1-alpine3.22 to match
package.jsonengines- Hotfix for the 4.99.16 image build failure. The 4.99.16 release stamped
package.jsonenginesto~24.13.0/~11.8.0(commitc3f17cf38e, #216) but left the Docker base image pinned atnode:20.16-alpine3.20in three places. Combined with.npmrc'sengine-strict=true, thenpm run all-productionstep inside the Docker builder failed before producingmetadata.json, breaking the tag-triggered Bitbucket pipeline. - Three pinned places aligned to Node 24.13.1.
.docker/images/node.dockerfile—NODE_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.sh—export NODE_VERSION=24.13.1. - Why
24.13.1-alpine3.22. Docker Hub does not publishnode:24.13-alpine3.20or-alpine3.21tags — the available alpine variants for Node 24.13 are3.22and3.23. Picked3.22(released June 2025, contemporaneous with Node 24.13) over the newer3.23for stability. Verified the bundled npm version:vendor v24.13.1/deps/npm/package.jsondeclares"version": "11.8.0"— exact match forengines.npm: "~11.8.0", no separatenpm install -gstep needed. - Out of scope. Local
compose.shintegration stack (.docker/integration/.envNGINX_IMAGE_TAG=1.27-alpine3.20-slim) is unrelated and untouched. Only Node images bumped.
- Hotfix for the 4.99.16 image build failure. The 4.99.16 release stamped
[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()callederror_log()for every pre-DIlog_message()call regardless of level. CI3's bootstrap (Config Class Initialized,Hooks Class Initialized,mbstring.internal_encodingdeprecations, etc.) fires several INFO/DEBUG records beforedi()is wired, and on hosts where theerror_logini is unset (e.g. Plesk) PHP CLI sends those to STDERR — which cron mails. Every* * * * * php cli.php job_managerminute produced one email per enabled queue × every CI bootstrap line. Docker images masked it because.docker/images/php.dockerfilesetserror_log = /dev/stderrdeliberately. - Threshold filter applied before
error_log(). New_logger_fallback_below_threshold($level)helper maps the requested level andAPP_LOG_THRESHOLDenv to Monolog's numeric ranks and drops records below the threshold — matching the filter Monolog itself applies once the DI logger is up. Default isNOTICE(mirrorsapplication/config/monolog.php); unparseable env values fall back toNOTICE; unrecognized log levels pass through (better noise than a swallowed signal during early bootstrap). - Reads
$_SERVERand$_ENVdirectly, notenv(). Two globalenv()definitions live in this codebase:public/index.phpreads$_ENVonly, while the cakephp/core override (loaded via composer files autoload) reads$_SERVERfirst. Bypassingenv()and matching dotenv's storage in both globals keeps the fallback predictable across web, CLI, and test contexts. - Tests. New
testFallbackHonorsAppLogThresholddata provider covers ERROR/WARNING/NOTICE/DEBUG/unparseable thresholds across all five PSR-3 levels emitted with the DI container deliberately empty, plustestFallbackUnknownLevelPassesThroughfor arbitrary level strings. 13 tests intests/Unit/Logger/LogHelperHandlersTest.php(was 8); full Logger suite green.
- Cron mailbox flooded on bare-metal hosts. After the PSR-3 migration in
[4.99.16] build(node): bump engines to Node 24.13 / npm 11.8
package.jsonenginesdeclaration only — no source changes. Verified locally on Windows:npm ciclean (926 packages, only Sass forward-compat deprecation noise — no native build failures, no node-gyp), and bothnpm run productionandnpm run admin-productioncomplete 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()hadif (!is_cli()) { $this->load->library(['session', 'cart', 'advauth']); }sitting at the root of every HTTP controller's inheritance chain. Every REST request instantiated aHandlesRestfulActionsdescendant, the constructor chain bubbled all the way up toController, and CI3's session library calledsession_start()which wrote a file tosess_save_path(default../sessions/). REST never reads$this->session/cart/advauth(auth is JWT viaAuthenticationMiddleware/Tokens), so this was pure overhead — one session file per request, plus per-requestSet-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_controllerandAdv_front_controller— both inserted right afterparent::__construct()and before any code that reads session userdata (admin'sisLoggedIn()check; front'sgdprSessionCookies()andinitializeCustomerSessionData()). - Two outliers patched explicitly.
Adv_login(advisable internal login) andAdvSso(SSO callback) extendBase_cdirectly 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/pageproduces zero new session files (200 OK, noSet-Cookieheader,Api-Version: 1). 3× anonymousGET /(storefront) produces +3 session files withSet-Cookie: dev1ssck=...per response, confirming the storefront pipeline is unaffected. - Tests.
--testsuite=Unit2611/2611,--testsuite=Integration89/89,--testsuite=Legacy234/234.
- Root cause.
[4.99.16] fix(logger): default
APP_LOG_THRESHOLDtoNOTICEso 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'whenAPP_LOG_THRESHOLDis 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=STDOUTwas believed broken because Loki was empty for the backend container. The format pipeline was correct end-to-end — every record was being gated upstream byChannelThresholdHandleragainst theERRORdefault and discarded before ever reachingJsonFormatter. Same gate would have produced the same empty output in file mode; STDOUT just made the silence visible. - Why
NOTICEand notWARNINGorINFO.NOTICEadmits operational notices (deprecations, slow-query thresholds, JWT validation anomalies) without the per-request noise ofINFO, while still leaving "healthy steady-state" pods near-silent in Loki — typically a few records per pod-hour vs the zero ofERROR. Per-channel runtime overrides (APP_LOG_CHANNEL_OVERRIDES) and per-channel static map inmonolog.phpstill work for surgical DEBUG sessions. - Flow doc
docs/flows/system/SY-32-logging.mdupdated to match (the example referencing the platform default).
- Two-line code change in
[4.99.16] feat(seo): port CustomMetaTag CRUD to Domain/REST (partial #119)
- New
Seo/CustomMetaTagDomain module — Entity, Repository, RepositoryConfigurator (no relations), Service, ListRequest, WriteData, Validator, WriteRepository (withexistsByUrl(?int $excludeId)lookup), WriteService. Mirrors the legacyAdv_custom_metatags_model/Adv_custom_metatagscontroller pair which is the primary SEO management tool for shop owners. - New REST controller at
/rest/seo/custom-meta-tagwithindex/show/item/store/update/destroy(and the(\w{2})/...localized variants — 12 routes total). Wired insrc/Rest/Seo/container.php. Domain services registered insrc/Domains/Seo/container.php. - Validation matches legacy form_validation rules.
urlrequired + unique (case-sensitive lookup against the samecustom_meta_tags.urlUNIQUE index), max 255 chars (DB hard cap). On update, the uniqueness check excludes the current row id (existsByUrl($url, $id)mirrorsis_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-stringurlon 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_atset todate('Y-m-d H:i:s')on create (single instant for both, matching legacyAdv_custom_metatags_model::addRecord).updated_atbumped 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_libdoes not touchcustom_meta_tags— it only resolves defaults and language metatags. The per-URL runtime resolution isAdv_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,CustomMetaTagCollectionschemas.public/openapi.jsonandpublic/openapi-v1.jsonregenerated. - 39 unit tests across 3 files:
Seo\CustomMetaTag\WriteServiceTest(11),ValidatorTest(19),WriteDataTest(9). Full Unit suite: 2611 tests, 7751 assertions, all green.
- New
[4.99.16] feat(cms): implement Cms/Builder write layer + standalone REST CRUD (closes #122)
- New
Advisable\Rest\Cms\Controllers\Buildercontroller at/rest/cms/builderwithindex,show,item,store,update,destroy(and the(\w{2})/...localized variants — 12 routes total). Backed by a freshCms\Builder\WriteService,WriteData,Validator,WriteRepository. Read-side already existed but was only reachable as a nested relation underPage/MuiResource; this commit gives it a top-level surface. - Cascade-nullify on delete.
WriteRepository::delete()mirrors the legacyAdvBlockBuilderModel::deleteWithRelations()— before deletingbuilder_blocks.id = N, it nullifies 7 FK columns across 6*_muitables: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, andshop_vendor_mui.exclusive_builder_block_id. The cascade list lives in aprivate const FK_REFERENCESarray so future linkages are one-line additions. The whole sequence is wrapped in atransactional()block inWriteService::delete()— a partial cascade can never leave dangling FKs. - Validation.
titlerequired 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\Entitywas missing thehtml@propertydeclaration — the existingResourceexposed$resource->htmland was relying onBaseEntity::__getfalling back to the raw DB row. Added the docblock so static analysis and IDE autocomplete see the field. - DI wiring.
Cms\Builder\WriteRepository,Validator,WriteServiceregistered insrc/Domains/Cms/container.php. Controller wired insrc/Rest/Cms/container.phpwithservice,resourceClass,collectionClass,listRequestClass,writeServiceargs. - OpenAPI. New
BuilderBlockschema (write payload),BuilderResource/BuilderCollectionalready existed.public/openapi.jsonandpublic/openapi-v1.jsonregenerated. - 30 unit tests across 3 files:
Cms\Builder\WriteServiceTest(11),ValidatorTest(12),WriteDataTest(7). Full Unit suite: 2572 tests, 7692 assertions, all green.
- New
[4.99.16] feat(product): implement ProductCode write layer and nested product-code creation (closes #125)
- Standalone REST endpoints for
product_codes. NewAdvisable\Rest\Product\Controllers\ProductCodeexposesGET/POST/PUT/DELETE /rest/product/product-code(and the/itemand/{id}variants) for direct SKU management. Backed by a freshProductCode\WriteService,WriteData,Validator, andWriteRepository. Read-side already existed; this commit only adds the write surface. - Auto-generated
product_codeon create. Ports the legacygenerateProductCode()helper toWriteService::generateUniqueCode():<prefix><productId>(-<attrValueIds>)(-<2-digit random>). Prefix loaded lazily from registryOTHER.GENERATE_PRODUCT_CODE_PREFIXto keep the constructor DI-safe. Collision retry capped at 50 attempts, then throwsRuntimeExceptionso we never spin forever on a saturated keyspace. Caller-providedproductCodeshort-circuits both the lookup and the retry loop. - Nested
productCodeson the Product create/update payload.Product\WriteService::create()now requiresproductCodes: [...](≥1 entry) and orchestrates: parentshop_productinsert → for each entry,ProductCode\WriteService::create()(with parent's freshly-insertedproduct_idinjected) → for eachattribute_value_idin the entry,ProductCodeAttribute\WriteService::create(). All inside the single parent transaction. Update path accepts an optionalproductCodesarray — entries withidroute toProductCode\WriteService::update(), entries withoutidinsert new codes; codes not listed are left untouched (no implicit deletes). NewNestedProductCodeDataDTO 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), forbidsidon create entries, and rejects blank-stringproductCodeand negativestock.validateProductCodesForUpdate()only enforcesstock >= 0—idis allowed; blank-stringproductCodeis delegated to the innerProductCode\Validatorso single-source-of-truth validation stays in the inner write service.ProductCode\Validator::validateForCreate()requiresproductId;validateForUpdate()rejects blank-stringproductCodeand negativestock. - DI wiring. New service definitions in
src/Domains/Product/container.php(Validator, WriteData factories, WriteRepository, WriteService) andsrc/Rest/Product/container.php(ProductCode controller). Routes added toapplication/config/rest_routes.php. - OpenAPI. Two new schemas (
ProductCode,ProductCodeNested) registered, attached to the Product schema via$ref.public/openapi.jsonandpublic/openapi-v1.jsonregenerated. - 95 new unit tests across 6 files (8
ProductCode\WriteServiceTest, 15ProductCode\ValidatorTest, 13ProductCode\WriteDataTest, 16Product\NestedProductCodeDataTest, 24Product\ValidatorTest, 27Product\WriteServiceTest). Code-gen tests use aWriteServiceWithStubbedPrefixsubclass to bypass the registry call so unit tests never touch CI'sget_instance(). Full Unit suite: 2542 tests, 7643 assertions, all green.
- Standalone REST endpoints for
[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::__constructrequired[ADVISABLE, ADMIN, MARKETING]; the RESTProduct\Controllers\Reviewpolicy atapplication/config/rest_policies.php:312required[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. Therest_policies.phpfile header (lines 39–40) explicitly states "Roles arrays are aligned with legacy admin controller allowRole() checks", and the siblingCustomerReviewpolicy already had this exact alignment with a "(not PRODUCTS)" provenance comment. - Fix.
Reviewpolicy defaults switched to[AUTH_ROLE_ADMIN, AUTH_ROLE_MARKETING]with a provenance comment matching the siblingCustomerReviewentry. 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.phppin the resolved auth + roles for every public Review method (index/show/item read paths, store/update/destroy write paths). Future drift inrest_policies.phpwill 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:583lists 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.
- Disjoint role sets enforcing the same business operation. Legacy
[4.99.16] fix(seo): add validation rules to DefaultMetaTag write validator (closes #45)
- Empty validator silently passed every payload.
DefaultMetaTag\Validator::validateForCreate()andvalidateForUpdate()previously built an empty$errorsarray and never threw. Payloads missing required fields, with invalidfordiscriminators, or with strings exceeding the underlying column limits all sailed through. The OpenAPI schema declaredfor/orderas required and gave nullable string fields, but nothing enforced the contract end-to-end. - Rules now enforced.
forrequired on create, must be1(front) or2(other) — the only values rendered by the legacy admin dropdown and the only values stored indefaultmetatags.forhistorically.orderrequired on create, must be ≥ 0.langoptional but exactly 2 chars when present (column isVARCHAR(2)— longer values would be silently truncated).metaTitle≤ 255 chars,metaKeywords≤ 1000,metaDescription≤ 500.validateForUpdateallows 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.phpcover required-field omissions (single + combined),forenum enforcement,ordernegative rejection, exact-lengthlangcontract (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.
- Empty validator silently passed every payload.
[4.99.16] fix(upload): normalise basename in collision path when
fileExtToLower(closes #214)- Mixed-case extension hybridised final filename.
AdvUploadFileNameGenerator::__invoke()used case-sensitivestr_replaceto strip the file extension from the basename when iterating to find a free filename slot. WithfileExtToLower=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_replacewithstr_ireplace. The non-collision path was unaffected (it never used the stripped basename). Updated the regression test intests/Unit/Upload/AdvUploadFileNameGeneratorTest.phpto assert the corrected contract ('photo1.jpg'for thephoto.JPG+fileExtToLower=truecollision case) and dropped the_BUGsuffix. Surfaced while writing the test coverage tracked under #39.
- Mixed-case extension hybridised final filename.
[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, andAdvUploadFileNameGeneratorhad no unit tests despite sitting on the request path of every multi-image admin entity (vendors, badges, sliders, banners, etc.). Two known bugs inAdvUploadValidatoralone (isImagealways 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_typesparsing variants,initializequirks, 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 —mapUploadKeyToDataKeyidentity default,parseInputcontent-type branching,processFileUploadsmerge semantics,applyFieldProtectionslug/URL stripping with and withoutAUTH_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.
- Zero coverage on the upload primitives.
[4.99.16] refactor(meta-capi): reuse injected logger in MetaClient instantiation
- Service-locator inside the constructor when the dependency was already injected.
FacebookConversionService::__constructalready received aNamedLoggerInterface; theMetaClientconstruction at line 99 was callingdi()->get(NamedLoggerInterface::class)again to fetch the same singleton. Replacedlogger: di()->get(...)withlogger: $this->logger. Runtime channel string is unchanged:MetaClient::__constructcalls->withName('meta-conversions-api')on whatever logger it receives, andwithNameoverwrites rather than accumulating. IN-07 Known Issues entry cleared.
- Service-locator inside the constructor when the dependency was already injected.
[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\LoggerInterfaceis now autowire-discoverable viaapplication/config/container/logger.php.Advisable\Logger\NamedLoggerInterface(a PSR-3 sub-interface that addswithName(string): static) is aliased to the same instance for type-safe channel-tagging at consumer call sites. Both resolve to aMonolog\Loggersubclass —Advisable\Logger\AppLogger— built byAdvisable\Logger\AppLoggerFactory::create(). - CI3 error handlers replaced via PHP function precedence.
application/helpers/log_helper.phpdefineslog_message,_error_handler,_exception_handler, and_shutdown_handlerBEFOREsystem/core/CodeIgniter.phpboots (require_once at the top ofpublic/index.php). CI3'sfunction_exists()guards inCommon.phpskip its own definitions;set_error_handler('_error_handler')etc. now register OUR functions. Pre-DI calls fall back toerror_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%carriesopenai,transporter-elta,manago, etc. instead of routing into separate log files. AppIntrospectionProcessorsubclasses 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 ofCoreLogger.php:93.ChannelThresholdHandlerdecorator wraps every handler in the DI-built logger. Per-channel thresholds inmonolog.php'schannelThresholdsmap raise or lower the effective threshold per channel; runtime override viaAPP_LOG_CHANNEL_OVERRIDESJSON env var lets you bump verbosity for a debug session without a redeploy.- All 44
new CodeIgniterLogger()callers migrated. 38 modernsrc/classes use constructor injection ofNamedLoggerInterface(now optional with adi()fallback for legacy direct-instantiation callers — see the bullet below); 6 legacy CI3 callers use a lazydi()accessor pattern.application/libraries/CoreLogger.phpandapplication/libraries/CodeIgniterLogger.phpdeleted;application/core/Log.phpis 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-modeLineFormatter—allowInlineLineBreaks=falsecollapses embedded newlines (including stack traces) to a single space. K8s mode usesJsonFormatterwithappendNewline=trueandincludeStacktraces=trueso each record is exactly one physical JSON-encoded line. - Three worked migrations (
Adv_checkout23 calls,PublicOrders15 calls,Moosend3 calls) document the lazy-accessor and constructor-injection patterns for the remaining ~180log_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.
- Single PSR-3 logger registered in DI.
[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 isFilterHandler(level range) → ChannelThresholdHandler(channel gate) → StreamHandlerso 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\JsonFormatterso Loki/Grafana/Datadog/CloudWatch can indexchannel,level,message,contextas structured fields instead of regex-parsing line shapes. Stack traces incontext.exceptionare 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_CONFIGURATIONunset) keepsLineFormatterfor human-readabletail -fduring 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.phpcover the level-split routing per pipeline, the JsonFormatter exception serialization, file-mode preservation, and the wrapping order.
- stdout/stderr level split (12-factor). In STDOUT mode each record routes to exactly one stream:
[4.99.15] fix(logger): DI compile re-entrance + runtime hardening
Container::$bootReadygate. During CI3's controller-class autoload cascade,application/third_party/MX/Base.phprunsnew CI;at file load →CI_Controller::__construct→Loader::initialize()→ autoloadsdatabaselibrary → driver constructor callslog_message()→ routes todi()→ would triggersetUpContainer()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 surfacesInvalid service "Country": class "Base_c" not found. Fix:Container::getContainer()returns an empty stubContainerInterfaceuntilContainer::markBootReady()is called from the newpre_controllerhook inapplication/config/hooks.php. CI3 fires that hook after the user controller is fully autoloaded but before line 291's first explicitdi()call, so the real compile runs only after every parent class is declared.$buildingremains as a secondary guard for in-compile re-entrance from service constructors.%-escape inapplication/config/container/logger.php. Symfony's parameter bag treats%foo%tokens in service arguments as parameter references; ourlineFormatstrings 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.AppLoggerfor nominalNamedLoggerInterfacetyping.Monolog\Loggerhas a matchingwithName()method but doesn't declareimplements NamedLoggerInterface, so passing the DI-resolved instance to a consumer constructor type-hinted asNamedLoggerInterfacefailed PHP's nominal type check (FacebookConversionService::__construct(), etc.). AddedAdvisable\Logger\AppLogger— a thinMonolog\Loggersubclass that declares the interface and overrideswithName(): staticfor the covariant return type, with no behaviour change.- Optional-logger fallback in 38
src/classes. Commitbefd8d27d(already on the branch) had migrated 38 classes from self-resolvednew CodeIgniterLogger()to constructor-injectedNamedLoggerInterfacebut missed dozens of legacy callers inapplication/andecommercen/(vouchers, jobs,Adv_orders_admin, smart-points,AdvTransporters,ChannelFactory, etc.) that instantiate these classes with their old 1-arg signature — surfaced asArgumentCountErrorat 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 deletedCodeIgniterLoggershim, 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/Loggerenrolled in the Integration suite inphpunit.xml.dist(was silently skipped).tests/bootstrap_ci.phpnow requireslog_helper.phpBEFORE CI3'sCommon.phpso ourlog_messageoverride wins thefunction_existsguard race — without this, CI3's ownlog_messagewould callload_class('Log', 'core')and hit the throwingCI_Logtripwire on every CI bootstrap.LoggerDiBindingTest::setUpBeforeClasssnapshotsContainer::$bootReadyand flips it totruefor the test class (pre_controllerhook 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, ortests/bootstrap_ci.php, re-apply the equivalent changes — particularly thepre_controllerhook callingContainer::markBootReady(), otherwisedi()will return the empty stub for the entire request lifecycle and every log line falls back toerror_log().
[4.99.15] feat(rest): per-relation language scoping for translations (closes Advisable-com/ecommercen#213)
?with=translationsalways loaded every language stored in*_muitables. 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=translationsor?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 declarescopableColumn: 'lang'in theirRepositoryConfigurator. The OneToMany loader (and the BelongsTo / HasOne / ManyToMany variants) emitWHERE 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
relationParamsdirectly intoWithRelationsorBaseRepository::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 flatrelationParamsmap keyed by full dot-path.WithRelationsspec gains a third constructor arg;RelationLoaderInterface::load()gains two optional trailing args ($relationParams,$params);BaseRepository::loadRelations()andloadRelation()thread the params through to the loaders.ListRequestcarries$relationParams;GenerateListRequestpopulates it;HandlesRestfulActions::show()threads it intoService::get(). - Interface signature shift:
ReadService::get()gains a fourtharray $relationParams = []arg. All 110 service implementations updated mechanically.Product\Variation\Repository\Repositoryships 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 forOneToManyLoaderscope application path. 5 DB-backed integration tests onCms\Page\ServiceTest(single locale, multi locale, unknown locale → empty, no-params baseline, end-to-end viaListRequest::$relationParams). Full suite: 5,588 unit/integration + 2,907 database = 8,495 tests, 0 failures. - Requires
cache/container.phpdeletion on first deploy after merge — the compiled DI container references the oldReadService::get()3-arg signature; first request rebuilds it cleanly. No DB migration. No asset rebuild. - Verify on merge:
- On any storefront with
*_muidata, hit a translation-bearing endpoint with?with=translations[el]and confirm the response has exactly one translation per master row withlang: el. Drop the bracket and confirm all locales return. - Pagination
totalshould be identical between?with=translationsand?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
childrenorparentwith 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 atranslationskey. This is by design: parser does not recognise:and the configurator has no relation literally namedtranslations: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/RelationLoadingruns 46/46 green;vendor/bin/phpunit --testsuite Database tests/Integration/Domains/Cms/Page/ServiceTest.phpruns 19/19 green.
- On any storefront with
[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 MySQLwait_timeout, the socket is killed before Phinx writes thephinxlogbookkeeping row, surfacing asPDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone awayat 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.AdapterWrapperpropagates both calls down to the underlyingMysqlAdapter, guaranteeing a fresh socket for the immediately-followingmigrated()write. - Manual recovery for environments that already hit this bug (data applied but
phinxlogrow missing —php migrator.php statuskeeps showing the migration as down):INSERT INTO phinxlog (version, migration_name, start_time, end_time, breakpoint) VALUES (<version>, '<MigrationName>', NOW(), NOW(), 0);
[4.99.15] fix(jobs): scope session_gc to the configured save_path on files driver (#210)
AdvClearExpiredSessions::executeCommand()previously calledsession_gc()after only settingsession.gc_maxlifetime— that routes to PHP's native file save_handler, which hardcodes thesess_*filename prefix. CI3 writes session files as<cookie_name><sid>(e.g.ci_session1a2b...), sosession_gc()matched zero files in production and the cron silently did nothing.- Replaced with
find -maxdepth 1 -type f -name '<cookie_name>*' -mmin +N -deleteinvoked viaexec(). Native filesystem traversal, prefix-aware, runs in the configuredsess_save_pathonly. realpath()canonicalization onsess_save_pathbefore invoking find: collapses the..from the defaultFCPATH . '../sessions'config and resolves symlinks so log lines are clean and behavior is predictable across deploy tooling.- Driver short-circuit: non-
filesdrivers (redis, database) still skip — they manage their own TTL. Windows hosts skip with an info log line (nofindfallback). 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 inecommercen/core/Mailer.php(lines 288, 332, 336, 340) passed a legacyboolas the third argument. The currentlog_message()signature declares that parameter asarray $context = [], andCoreLogger::error()is strictly typed — passingfalsecaused aTypeErrorthat aborted the request. The symptom was most visible during the checkout order-complete flow when an SMTP send failed. - Fix: removed the trailing
, falsefrom all four call sites. No other callers in the codebase pass a non-array third argument.
- Four
[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.distpair with one config defining four named testsuites: Unit, Integration, Database, Legacy.composer testnow runs the full ~5,400-test suite;composer test:fastis the no-DB inner loop. Deletesphpunit-database.xml.dist. - New
composershortcuts:test:database,test:legacy,test:fast. docs/guides/Testing.mdrewritten to document the unified layout and per-testsuite placement rules.- No production code change. No REST API contract change.
- Replaces the historical
[4.99.15] fix(gifts,orders,reviews): fix three bugs exposed by the unified test suite (#207, #204, #174)
- #207 — Gifts: null
final_pricetreated as 0.0 in Rule 13 cheapest-selector.getCheapestRequirementProductInCartcast 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 whenfinal_price === null. - #204 — Orders: Rule 13 cart-trim skipped when gift and cart use different product codes.
adjustCartForRule13Giftsmatched cart rows byproduct_code_idalone. 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 (newAdv_gifts_model::getProductIdsByCodeIds) and falls back to product-level matching; the exact-code path still wins when both share the underlying product. - #174 — Reviews:
RatingAggregatornever fired fromWriteService.Product\Review\WriteServiceread$entity->productId(camelCase) but the Entity column isproduct_id(snake_case).BaseEntity::__getreturned null, soshop_product.average_ratingandshop_product.review_countwere never updated after REST-layer review writes. - Also un-skips the non-Rule-13 gift_choices counterpart test (adds a stocked
product_codesfixture) and removes a false-alarm skip onCartServiceTest::testClaimCartMergesIntoExisting(no CI bootstrap required — all dependencies are mocked). - Closes #207, closes #204, closes #174.
- #207 — Gifts: null
[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.phpcoveringruleProductsCheapestFreeValidator,getCheapestRequirementProductInCart(greedy allocation, tie-break, cart-qty cap, orphan handling,giftsToGive=0early return), andfilterApplicableGiftRulesRule 13 exemption. - Adds 12 unit tests in new file
tests/Legacy/Eshop/AdvOrderModelRule13Test.phpforgetRule13GiftAdjustmentsForProductsandadjustCartForRule13Gifts— usesinvokeArgsto handle the by-reference&$cartargument. - Adds the DB integration test for
getActiveGiftRulesRule 13 bypass in new filetests/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_strinsideescape, double-runningreal_escape_stringover user input. Harmless for plain text but values containing\were over-escaped —foo\barsearched forfoo\\barin 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.
- The fix shape introduced in #199/#201 wrapped
[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*_muitable. - Fix: wrap user input in
$db->escape_like_str()and appendESCAPE '!'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
FilterByBarcodefor #199. - Closes #201.
- 17
[4.99.15] fix(product): route barcode filter through FilterByBarcode subquery (#199)
FilterByBarcodespecification: subquery onshop_product_barcodes, OR-joined LIKE for multiple values, sentinelWHERE id = -1on empty inputProduct\Serviceinterceptsbarcodekey before the defaultFilterbranch- 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 withESCAPE '!'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-levelgetCatProductsBasefor modern callers)Product\ListRequest: new filterscategoryId(direct M2M) andcategoryDescendantId(recursive)Product\ServiceinjectsCategory\Serviceand intercepts both keys via the newFilterByCategorysubquery specification (avoids JOIN-by-relation limitation in the existing infrastructure)FilterRequestgains an optionalkeyfield 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_DEVELOPERto controller RBAC gate inAdvPlusAlgoPrioritySettings::__construct() - Update flow doc AD-56 to reflect aligned permissions (remove Known Issue #1, update RBAC table and code example)
- Closes #75
- Add
[4.99.15] fix(api-docs): move changelog panel inside version bar so it renders visibly (#197)
- Append
#changelog-panelto#version-barinrenderVersionBar()after the panel reference is acquired - Toggle logic, content rendering, and click-outside-to-close behaviour unchanged
- Closes #197
- Append
[4.99.15] fix(cart): show correct toast when decreasing cart quantity
assets/vue/store/actions.jsreportCartEventskeyed the add/remove flag onitem.quantity === 0only, so any non-zero new quantity — including a decrease (e.g. 3 → 2) — fell into theelsebranch and setresponse.addToCart = true.setCartDatathen rendered the greencart.product_addedtoast 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 flagsresponse.addToCart. - Previous-cart lookup uses
Number()coercion onproductCodeIdto tolerate the string-vs-number mismatch between server-rendered initial cart state and user-action payloads — same convention already used ingetters.cartProductCodeQuantity. - Analytics tag dispatch (
tag.removeFromCart/tag.addToCartand the GA4 variants) is left untouched — still keyed onquantity === 0. Toast styling is left untouched (success/green for both add and remove); only the message text was wrong. - Reported by client
v4-evripidis(commite85839058). - Requires
npm run production(ornpm run all-production) — the file is bundled intopublic/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, notcart.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
dataLayerin DevTools:removeFromCart/add_to_cart_GA4events still fire as before — only the toast message changed.
- On a cart with quantity 3 of any product, click the minus button to drop to 2 → toast reads
[4.99.14] fix(smartpoint): stop leaking
messagelisteners on every BoxNow / Skroutz widget openassets/main/vue/SmartPoint/Widgets/SmartPointWidget.jswas re-injecting the BoxNow / Skroutz CDN<script>on everyinitializeWidgetBoxNow/initializeWidgetSkroutzcall. Removing the<script>element does NOT undowindow.addEventListener('message', …)registered by the CDN code while it ran — those listeners stayed alive onwindow. Each open accumulated one more listener, so a single locker pick firedafterSelectonce per accumulated listener: 1st open → 1 toast, 2nd → 2 toasts, …, Nth → N toasts (plus NsetSelectedSmartPointdispatches and NWidgetCloseemits).- 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}_WidgetOpenemit inSmartPointMixin.initializeWidgetForTransporter, whichBoxNowWidget.vue/SkroutzLastMileWidget.vueroute throughclickOpenWidgetElement()(clicks the hidden.boxnow-map-widget-button/.skroutz-map-widget-buttonelement).window._bn_map_widget_config/window._skroutzPointsMapWidgetOptionsare still overwritten on every call soafterSelectalways closes over the latest widget instance. - Requires
npm run production(ornpm run all-production) — the file is bundled intopublic/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">srcends inboxnow.cy). - Mixed flow: open BoxNow → close → open Skroutz → close → open BoxNow again → only one of each script tag in
<head>, no listener accumulation. - DevTools spot-check: in the Memory or Performance panel, count message listeners on
windowbefore and after multiple opens — should stay flat.
[4.99.14] feat(product): expose
averageRatingandreviewCounton 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]=Xround trips. - Two new columns on
shop_product:average_rating(DECIMAL(3,2)) andreview_count(INT(10) UNSIGNED), both with default 0 and indexed for sort/filter. - New
Advisable\Domains\Product\Review\RatingAggregator::recomputeForProduct(int $productId)readsAVG(star_points) + COUNT(*)fromshop_product_reviews WHERE active=1and writes the result back toshop_product. Average is rounded to 2 decimals. - Recompute hooks: modern
Product\Review\WriteServicecalls the aggregator on create / update / delete (handling product-id reassignment by recomputing both the old and the new product). LegacyAdv_product_reviews_modelcalls it fromaddReview,updateReview,setReviewStatus, andsetReviewsStatusBatch— covering customer review submission and admin moderation. - One-time backfill migration
20260428120100_backfill_product_review_aggregates(Phinx wrapper) +Patches\BackfillProductReviewAggregatespopulates the new columns for existing reviews via a singleUPDATE ... LEFT JOINquery. Advisable\Domains\Product\Product\ListRequestexposesaverageRatingandreviewCountas both filterable and sortable; storefront can request?sort=-averageRatingfor "highest-rated first" cards.- OpenAPI
ProductResourceschema updated;averageRatingandreviewCountadded to the required fields list. - 12 new integration tests in
tests/Integration/Domains/Product/Review/RatingAggregatorTest.phpcovering 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 migratefor 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_countmatchesSELECT COUNT(*) FROM shop_product_reviews WHERE product_id = X AND active = 1andaverage_ratingmatches the equivalentAVG(star_points). - Modern write path:
POST /rest/product/reviewwithactive=1thenGET /rest/product/product/{id}—reviewCountincrements,averageRatingshifts. - Legacy admin moderation: flip a pending review to approved via
Adv_product_reviews_adminUI; confirm shop_product cached values change. - Storefront response shape: list endpoint includes
averageRatingandreviewCounteven for products with zero reviews (returned as0and0, never null). - Sort works:
GET /rest/product/product?sort=-averageRatingreturns highest-rated products first. - Run
vendor/bin/phpunit tests/Integration/Domains/Product/Review/RatingAggregatorTest.phpagainst the migrated test DB — all 12 tests green.
- Backfill landed: pick a few products with active reviews and confirm
- Storefront product list responses now include cached aggregate fields, so frontends can render star ratings on product cards without per-product
[4.99.14] fix(cart): read cart subtotal from shop_product.price (fixes #27)
calculate()now prefers the already-hydratedproductCode.productgetItemPrice()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()requiresvalue, enforces the range[0, 100],validateForUpdate()now takes anint|string|null $idso theWriteRepository::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::classdefaults nowAUTH_ROLE_ADVISABLEcontinues 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
quantityfield exclusively (qtyalias removed, closes #108) - Cart:
cart_contentssnapshot 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
$_FILESguard inHandlesUploadActions(closes #110) - Collections: all REST collection responses are now wrapped in the pagination envelope (closes #167)
- Product:
negativeStockfield exposed on the public product API (closes #171) - ProductCategory:
parentandchildrenrelations 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
- Cart: cart endpoints now use
[4.99.14] feat(rest): audit logging for authenticated REST writes (closes #188)
- New
Advisable\Rest\Middleware\AuditMiddlewareruns last in the REST middleware pipeline (RouterDispatcher). On authenticated POST/PUT/DELETE/PATCH it logs touser_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 (numericuserId→user_idcolumn, stringuserId→usernamecolumn forconfig_authusers). Roles are resolved from the JWT to a CSV of role names viaauth/roles_model, matching the admin audit format.providercolumn gains two new values:REST_BACKEND(admin JWT) andREST_CUSTOMER(customer JWT) — alongside the existingDATABASE/CONFIGfor legacy admin.- Request bodies are captured even for
application/jsonwrites via a newgetRestStates()that falls back to$ci->input->inputStream()when$_POSTis 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.
- New
[4.99.14] fix(views): move favicon HTML snippet to
storage()abstractionapplication/views/main.phppreviously 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 itsstorage/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 throughFront_c::main.phpfor live-preview parity).- The snippet now lives at
files/favicons/view.htmlunderstorage(), colocated with the favicon images (favicon-*.png,manifest.json) that are already stored there.main.phpreads it viastorage()->exists()+storage()->get(), so missing files never throw. AdvFavicons::save()now writes the generated HTML directly viastorage()->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:FaviconImageGeneratorwas writing it via rawfopen()/fwrite(), also bypassingstorage(). Replaced withstorage()->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 legacystorage/views/favicons.phpcontent intofiles/favicons/view.html. Idempotent — skips when source is missing/empty or destination is already populated. New installs need no action. - The legacy
CreateFaviconHtmlpatcher is now obsolete (its sole purpose was to suppress theload->view()crash) but is left intact — Phinx history is immutable. - Requires
php migrator.php migratefor clients with an existing local favicon snippet.
[4.99.14] fix(rest): drop nonexistent
newsletterfield from customer registerPOST /rest/auth/customer/registeraccepted an optionalnewsletterboolean and pushed'newsletter' => 0|1intoshop_customerviaadd_customer_front(). Theshop_customertable has nonewslettercolumn (verified indatabase/initial/initial.sqland across all migrations), so every call hitUnknown 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
newsletterfrom the OpenAPI request schema, the controller body, and regeneratedopenapi.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 viaProcess::start(). It was callingstart()without a callback, so child stdout/stderr was captured in memory and silently discarded — theAdvJobQueueManagerforwarding fix from 4.99.12 was effectively nullified at the outer dispatch layer.- Fix: instantiate
StreamingOutputForwarderonce 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 inkubectl logs. - Audit confirmed
AdvJobManagerandAdvJobQueueManagerare the onlyProcess::*()call sites inecommercen/job/— no remaining forwarding gaps in the job-dispatch path. - Two new tests added to
tests/Unit/Jobs/Process/StreamingOutputForwarderTest.php: asyncstart()/wait()path and multiple parallel processes sharing one forwarder, matchingAdvJobManager's real runtime shape. - The temporary
AdvJobManager.phpruntime 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)AdvJobQueueManagercalledProcess::run()andProcess::restart()without a callback, so all child-process stdout/stderr was captured internally and discarded when theProcessobject 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 routesProcess::OUTlines to the parent's STDOUT andProcess::ERRlines to STDERR in real time, so container log aggregators receive the output immediately without buffering. StreamingOutputForwarderaccepts injectable stream resources ($stdout,$stderr) so it can be tested without spawning a real process or relying onSTDOUT/STDERRsuperglobals.- 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 viasymfony/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 resetAdv_customer_model::isValidToken()used strictnum_rows() == 1, butshop_customer.active_tokenhas no UNIQUE constraint — if two customers ever shared a token value, the predicate returned false for both, invalidating otherwise-valid reset tokens. Changed to>= 1to match the method's intent (pure existence check).- Gates
POST /rest/auth/customer/reset-password(viaPasswordResetService) 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 buildapp.dockerfilenow copiescustom/(PSR-4Custom\namespace) via a build-arg stage-selection trick: two alpine stages (custom_src_false/custom_src_true) are picked byINCLUDE_CUSTOM. BuildKit skips the unselected stage, so theCOPY custom /customnever evaluates on the upstream repo where the directory is absent.build-app.shauto-detects whethercustom/exists in the build context and forwards--build-arg INCLUDE_CUSTOM=true|false. Both the PR-preview and tag-release pipeline steps already invokebuild-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 andconfig_authusers (e.g.advisable); for config users,idis null,isConfigUser: true, and role names are resolved viaAdmin\Role\Service - Refactored
POST /rest/customer/me— the inlineunset()blocklist is replaced with an explicitSELF_SERVICE_FIELDSclass constant allowlist.mailis now blocked — this matches legacy storefront behavior (the account form renders the mail input asdisabledandAdv_customer::update_info()never saved it).password,salt, admin-only, and system fields remain blocked. - New
Advisable\Domains\Customer\Customer\PasswordResetService— thin wrapper around legacyAdv_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 triggerAdv_base_model::__construct()before CI helpers (get_languages()etc.) are available. - New
Advisable\Rest\Admin\Controllers\Me— thin GET-only controller.POST /meis 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 toapplication/config/rest_policies.phpfor 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)
- New
[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. Whenfloor(reqCount / gift_per_count) > 1and 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). getCheapestRequirementProductInCartnow accepts a 4th arg$giftsToGiveand uses a greedy loop: iterates candidates cheapest-first (price ties break by lowest product id), takesmin(remaining_gifts, product_stock)from each, continues until all owed gifts are allocated. Returns[1 => [pid, pid, ...]]as a flat per-slot list.getRule13GiftAdjustmentsForProductsinAdv_order_modelreplacedreset() × giftCountToGivewith a foreach that counts occurrences in the flat list, producing correct[pid => count]per product.AdvCartResource::getGifts(),adjustCartContentsForRule13(), andgetGiftRules()aggregate the flat slot list viaarray_count_valuesso each unique pid gets its own entry with the correct per-pid count. Deduplicateschoices[1]for display.Adv_orders_admin::gifts_for_products()receives the same grouping treatment for the admin order page.applyRule13CartAdjustments()inorder-gifts-block.js: relaxedif (newQty <= 0) returntoif (newQty < 0) returnso 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 theorder-gifts-block.jschange)
- Rule 13 previously returned a single cheapest product and multiplied it by
[4.99.11] refactor(jobs): rename
retriescolumn tomax_retriesin job_schedule (fixes #109)- The
retriescolumn stored the configured retry limit, not a running count — the actual counter is the separateretry_countcolumn. The misleading name risked confusion. - Added Phinx migration
20260416093953_rename_retries_to_max_retries.phpto 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 readrow.job_retry_times(undefined); now readsrow.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)
- The
[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_GROUPSonly listed upstreamRest/*patterns - Add
Administrationgroup forRest/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
- Client REST endpoints (Certus, Wallbid, Ship, Seajets, etc.) were found by the scanner but silently dropped from the sidebar because
[4.99.11] fix(i18n): add missing English translation for Mailchimp admin menu entry
- Add
admin.menu.general.settings.mailchimp.iconandadmin.menu.general.settings.mailchimpto 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
- Add
[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 byAdv_seo_liblong 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/seoand/{lang}/seo) that pointed to the emptySeo::index() - Updated
docs/flows/admin/AD-14-seo-management.md— removed staleAdv_seo.phpfrom Key Files table - Added 36 unit tests for the DefaultMetaTag domain layer:
WriteDataTest(11 tests),ValidatorTest(4 tests),WriteServiceTest(13 tests)
- Deleted
[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!== nullguards instead of truthy checks (prevents0 → nullbugs) - Add
requiredarrays andnullable: trueto OA\Schema attributes for client codegen - Add
descriptionannotations for admin-only / context-gated fields - Fix dangling
$refschema names (OrderOrderResource→OrderResource, ShelfcodeResource→ProductShelfcodeResource, etc.) - Fix Product Resource fatal: remove conflicting
Vendor\Resourceimport - 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
isoCodeOA type:'int64'→type: 'integer', format: 'int64' - Move
pointFactorto always-emitted (storefront loyalty display);totalPointsto authenticated context - Updated all affected tests (910 REST tests pass)
- Cast all id/numeric fields to explicit
[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
- Config value was
[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()queriesshop_productfor VAT referencesValidator::validateForDelete()throwsValidationExceptionif referencedWriteService::delete()calls validator before repository (mirrors create/update pattern)HandlesWriteActions::doDestroy()catchesValidationExceptionas 409 Conflict for all controllers- 7 unit tests added (WriteRepositoryTest, ValidatorTest, WriteServiceTest)
- Prevent deletion of VAT rates referenced by products via the REST API — matching legacy admin's
[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 fromapplication/config/rest_policies.php - The Keyword REST controller was deleted in b501baac8; these two lines were the only remaining references
- Removed dangling
[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=InnoDBwithout explicitROW_FORMAT=DYNAMICcan inherit COMPACT row format, which stores 768-byte BLOB/TEXT prefixes inline and exceeds the 8126-byte row size limit on wide tables likeshop_product_mui- Fixed original migration (
20260408100114) to includeROW_FORMAT=DYNAMIC - Added follow-up migration (
20260415102932) for clients that already ran the old version: queriesinformation_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_Routerextracts major version from URI (/rest/v1/...) before route matching — zero changes torest_routes.php; unversioned/rest/...requests fall back to latestRouterDispatcherresolves version, swaps override controllers, returns 410 Gone for obsolete versions, sets RFC 9745/8594 response headers (Api-Version,Deprecation,Sunset,Link)VersionResolverservice +VersionResultDTO insrc/Rest/Versioning/PolicyResolverparent-class fallback so version-override controllers inherit base controller policies automaticallyRequestContextcarriesapiVersionthrough the middleware pipelineGenerateOpenApiJsonproduces per-version specs (openapi-v{N}.json) with versioned path prefixes andapi-versions.jsonmanifestapi-docs.htmlversion selector dropdown with status badges (active/deprecated); graceful fallback toopenapi.jsonwhen manifest is missing- 16 unit tests covering resolution, status checks, RFC-compliant header generation, controller overrides
- Version registry config (
[4.99.10] feat(admin): migrate roles from config to database table (closes #166, #170, #172, #175)
- Created
rolesDB table with 9 seeded roles matchingAUTH_ROLE_*constants; added FK constraint onuser_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
HandlesRestfulActionswithshow()route and hidden-role guard for non-advisable callers - Fixed DI
container.php: removeduse_sections=trueconfig hack that poisoned CI3 namespace and broke admin login on first request after cache clear - Fixed
Adv_roles_modelto read from DB instead of config (prevents null crash during container compilation); also fixedgetRolesByName()bug and removed deaduser_role_modelload - Batched role validation into single
match()query with type-safearray_diff(eliminates N+1) - Removed
auth_rolesfromapplication/config/auth.php; deprecatedRoleProviderclass - Updated
x-relationstype on User controller fromone_to_manytomany_to_many - Breaking:
auth_rolesconfig key no longer exists — roles are now managed via therolesDB table.AUTH_ROLE_*constants remain unchanged. - Requires
php migrator.php migrateto create therolestable and add the FK constraint
- Created
[4.99.10] fix(tests): resolve 50 test errors across unit and database suites (#177, #178, #179, #180, #181, #182)
- Page WriteServiceTest: add missing
SlugGeneratormock (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
bannerImagetocreateData()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
- Page WriteServiceTest: add missing
[4.99.9] fix(tests): remove stale Seo/Keyword REST test files (closes #168)
- Deleted
CollectionTest.phpandResourceTest.phpfor 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)
- Deleted
[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 fatalReflectionExceptionduring DI container compilation ApiEndpointTraitis a legacy classmap-loaded trait with no namespace; corrected to bareuse ApiEndpointTrait
- The Role controller declared
[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_muifrom MyISAM to InnoDB - InnoDB provides row-level locking (critical for
ci_sessionsunder 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_dataincluded because its original migration omitted ENGINE, leaving it as MyISAM on servers with MyISAM as the default- Requires
php migrator.php migrate
- Converted
[4.99.8] fix(security): remove vulnerable order export feature (VULN-05)
- Deleted
generate_xsl()andgenerate_xsl_for_geniki()fromapplication/controllers/Webrun.php— both usedunserialize()on data fromshop_export(unsafe deserialization, VULN-05); removed unused PhpSpreadsheet imports - Deleted
create_export()fromecommercen/eshop/controllers/Adv_orders_admin.php— sole caller ofset_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()andget_export_data()helper functions fromecommercen/helpers/shopmodule_helper.php(already marked@deprecated) - Removed
eshop.admin.order.create_exportandeshop.admin.order.download.resultstranslation keys from all 8 language files - Added Phinx migration to drop the
shop_exporttable (reversible) - Requires
php migrator.php migrateto drop the shop_export table
- Deleted
[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
masterbranch (production), PR builds deploy topr-{id}branch (preview) - Docker and docs builds run in parallel after tag parsing step
- PR cleanup:
pullrequest-fulfilled/pullrequest-rejectedtriggers delete PR Docker tags - CF credentials (
CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID) are workspace-level; docs step skips gracefully if not set
- Docs site hosted on CF Pages with Zero Trust access control (
[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) andllms-full.txt(all content) in build output - OpenAPI operations excluded (hash-based operationIds produce useless entries)
- Available at
/llms.txton the docs site
- Generates
[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
- Client flow docs live in
[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-workflowskill; 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 withspa-botuser identity and advisable role - Uses the REST Auth Config (same key/algorithm as regular JWT auth)
- Run via
php cli.php utils/generateSpaToken— outputs anSPA_TOKEN=...line ready for.envfiles - 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 fromsrc/Rest/Seo/container.php - Deleted entire
src/Domains/Seo/Keyword/directory (Entity, Repository, WriteRepository, RepositoryConfigurator, Service, WriteService, Validator, WriteData, ListRequest); removed DI registration fromsrc/Domains/Seo/container.php - Removed
admin.menu.keywords.iconandadmin.menu.keywords.labellanguage keys from all 8 language files - Deleted legacy HMVC layer:
Adv_keywordscontroller,Adv_keywords_model, application wrappers (Keywords,Keywords_model), and 3 admin views (list, create, update) - Removed
_defkeywords()and_makelinks()methods fromAdv_seocontroller; removeddefkeywords()call fromindex() - Removed "Internal Linking" button from seo/metatags admin view; removed
eshop.admin.seo.internal.linking.labelfrom all 8 language files - Added Phinx migration to drop the
keywordstable (reversible) - Requires
php migrator.php migrateto drop the keywords table - Requires
composer installfor autoloader cleanup
- The keywords module allowed defining keyword-to-URL mappings that were auto-linked in page content via
[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_ENABLEDenv var (defaultfalse); whentrue,rest_routes.phpis included and all/rest/*endpoints become active - Documented in
docs/guides/RestApiModules.mdunder "Enabling the REST API"
[4.99.6] feat(job): add ClearExpiredSessions job with deterministic session GC
- PHP's probability-based
gc_probability/gc_divisorsession cleanup is unreliable under high traffic — expired sessions accumulate - New
AdvClearExpiredSessionsbase job inecommercen/job/libraries/callssession_gc()deterministically; client-overridableClearExpiredSessionsinapplication/modules/job/libraries/ - In CLI context the Session library is not loaded, so
gc_maxlifetimestays at the php.ini default (1440s) instead of the configuredsess_expiration; the job readssess_expirationviaconfig_item()and applies it withini_set()before invokingsession_gc() - Uses
CodeIgniterLoggerfor logging - Scheduled daily at 3 AM (
0 3 * * *) in thecorequeue (application/config/jobs.php), 300s grace time, 0 retries - Requires
composer installafter deployment so the autoloader picks up the new classes
- PHP's probability-based
[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
PaymentConfirmationServicewith idempotentconfirmPayment()/cancelPayment()— handles status update,is_paidflag, and stock reduction for online payments - New
src/Rest/Webhooks/module with per-gateway webhook handlers: Stripe (HMAC signature verified viaSTRIPE.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_SECRETRegistry key for Stripe webhook signature verification
- Orders placed via online gateways (Stripe, VivaWallet, PayPal) remained stuck in
[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_usednot incremented, allowing unlimited reuse - Product stock never decremented —
product_codes.stockunchanged, enabling overselling - Add
OrderBasketwrite layer (WriteRepository,WriteData,WriteServicewithcreateForOrder()bulk insert) - Add
OrderBasketBuilder— snapshots pricing, VAT percentage, and active discounts from product data at checkout time - Add
StockServicewithreduceStockForOrder()/restoreStockForOrder()using raw SQL stock arithmetic (matches legacyremoveOrderStock) - Add
CouponCode\WriteRepository::incrementUsed()for atomic counter increment (matches legacymarkCouponUsed) - Offline payment adapters (delivery, bank_transfer, paid_at_store) now return
PENDING_ACCEPTEDinstead ofPENDING, 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
- Cart items were never written to
[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
customDatetimefield to Video WriteData- The
custom_datetimecolumn 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, andtoArray
- The
[4.99.5] feat(openapi): make OpenAPI spec fully machine-readable for AI agents
- Added
requiredfields to 120 write + translation schemas (284 total required fields) based on DB NOT NULL constraints, migration analysis, and admin validation - Added
descriptionto all OA\Property annotations on WriteData and MuiWriteData files for field-level documentation - Added structured
x-relations,x-sorts,x-filtersvendor 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.phpglobal 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
- Added
[4.99.5] fix(auth): add
SetEnvIf Authorizationto.htaccess.examplefor JWT on Apache CGI/FastCGI- Apache CGI/FastCGI strips the
Authorizationheader before it reaches PHP, breaking JWT Bearer token authentication - Added
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1topublic/.htaccess.example - Docker (nginx + PHP-FPM) is unaffected —
fastcgi_paramspasses the header through by default
- Apache CGI/FastCGI strips the
[4.99.5] fix(openapi): align OA annotations with routes across 7 controllers
- Audited all REST controllers against
rest_routes.phpandopenapi.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/category→event/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.jsonspec
- Audited all REST controllers against
[4.99.5] refactor(middleware): make site mode allowed namespaces config-driven
AdvSiteModeMiddleware::siteModeInstanceOf()previously had a hardcodedAdvisable\Rest\namespace string; it now loops over thesiteModeAllowedNamespacesconfig array- Added
$allowedNamespacesproperty 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 subclassingSiteModeMiddleware
[4.99.4] feat(rest): add file upload support to REST API via multipart/form-data
- New
HandlesUploadActionstrait replacesHandlesWriteActionson controllers with file fields — handlesmultipart/form-datafor store/update and deletes physical files on destroy - New
UploadService(src/Domains/Support/Upload/) wraps legacyUploadValidator,UploadFileNameGenerator, andStorage(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-datacontent types on all upload endpoints
- New
[4.99.4] fix(rest): replace PUT with POST for all write endpoints
- PHP does not populate
$_POST/$_FILESfor PUT requests withmultipart/form-data— file uploads were silently failing on update - All 204 PUT routes changed to POST in
rest_routes.php; all 86OA\Putannotations changed toOA\Post - Update endpoints now use
POST /rest/{context}/{entity}/{id}instead ofPUT - Breaking change for API consumers — update client code to use POST for updates
- PHP does not populate
[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, causingCall to undefined methodfatal errors - Both
HandlesWriteActionsandHandlesUploadActionsrefactored: logic moved todoStore()/doUpdate()/doDestroy()protected methods; public methods delegate to them - All controller overrides updated to call
$this->doStore()etc. instead ofparent::store()
- 86 controllers called
[4.99.4] fix(rest): embed config user roles in JWT tokens
AdminAuth::getUserRoles()only checked DB roles — config-based users (fromconfig_authinauth.php) always got empty roles, failing RBAC checks- Now checks
config_authroles 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 theAdvisable\Rest\namespace- Client repos can extend
SiteModeMiddlewareto addCustom\Rest\etc.
[4.99.4] fix(deps): update phpseclib and flysystem-sftp-v3 to patch AES-CBC padding oracle vulnerability
phpseclib/phpseclibupgraded from 3.0.49 to 3.0.50 — fixes CVE-2026-32935 (AES-CBC unpadding susceptible to padding oracle timing attack)league/flysystem-sftp-v3upgraded 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 loadingauth/Adv_user_role_modeldirectly; in client deployments the MX_Loader searchesapplication/modules/first and fails to find the ecommercen base model- Changed to load
auth/user_role_model(application-level wrapper) which resolves correctly and inheritsgetUserRolesIds()from the base
[4.99.4] fix(deps): block malicious axios versions to protect against supply chain attack
axiosdependency range changed from^1.12.0to>=1.12.0 <1.14.1 || >=1.14.2— blocks1.14.1(known malicious) while allowing future safe releasesoverridesblock added inpackage.jsonwith the same range to enforce the constraint on any transitive dependency that pulls inaxios- 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 annotatesfreeQuantityon cart items alongside reducingquantity, so the Vue layer knows how many units are free when dispatching cart update requestsAdvCartProductRow.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 readscartProduct.giftCountToGive || cartProduct.quantityfor both thev-ifguard 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.phpandproduct_combo_list.phpwere still passing"vendors/{$productData->vendor_slug}"in the$is_canonicalbranch- The 4.99.3
transformProductForJsonrefactor auto-resolves vendor URLs viavendors_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
ResourceContextscope (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
- Resources now conditionally include backend-only fields based on
[4.99.3] feat(rest): align RBAC policies with legacy admin controller roles
- Every
rest_policies.phproles array verified against corresponding legacy admin controllerallowRole()checks AUTH_ROLE_ADMINexplicitly 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=anyfor customer self-service browsing - 45 legacy admin controllers without REST equivalents documented in
.claude/tmp/missing-rest-coverage.md
- Every
[4.99.3] fix(test): resolve DB connection and global state issues in test suite
- Fixed dotenv overwriting PHPUnit
ENVIRONMENT=testingwith.env'sENVIRONMENT=production— save/restore PHPUnit env vars around dotenv loading - Fixed CI3
config/testing/database.phpnot including base config — CI3 loads environment-specific config INSTEAD OF (not in addition to) base config - Fixed
CI::$APPcreated with null router when Unit tests trigger CI class autoloading before full boot —bootstrap_ci.phpnow detects and repairs corrupted instance - Replaced global
get_instance()stubs in middleware tests with swappableGetInstanceRegistry - All 5316 tests (Unit + Integration + Legacy) now pass in a single
vendor/bin/phpunitrun
- Fixed dotenv overwriting PHPUnit
[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:AuthenticationMiddleware→AuthorizationMiddleware→ResourceContextMiddleware→RelationFilterMiddleware AuthenticationMiddleware: extracts JWT Bearer token and populatesRequestContext; never halts the requestAuthorizationMiddleware: resolves effective policy (method → controller → global) and halts with 401/403 when auth type or role requirements are not satisfiedResourceContextMiddleware: setsResourceContexton the controller before action execution; Resource transformers can checkisPublic()/isCustomer()/isBackend()to conditionally include/exclude fieldsRelationFilterMiddleware: silently strips?with=relations not in the allowed list for the resolved scope- 9 RBAC roles matching the legacy admin role system (
AUTH_ROLE_ADVISABLEthroughAUTH_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.php—defaultsfor global fallback,controllersfor per-controller overrides withdefaults,methods, andrelationskeys - Auth types:
none,guest,any,customer,backend,legacy_guard - Default behavior for unlisted controllers is
backendauth (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
- New four-middleware pipeline in
[4.99.3] fix(tests): fix BaseWriteRepositoryTest failing in full unit suite
BaseWriteRepositoryTest::buildRepository()usednew ConcreteWriteRepository()which callsget_instance()->dbin 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$dbvia 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
--updatenow 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),
usestatement 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
--reportflag 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$argvparser - Flag position is now flexible — flags can appear before or after the subcommand
- PHP's
[4.99.3] fix(smartpoint): switch BoxNow widget from native popup to custom iframe overlay to fix iPhone/Chrome compatibility
SmartPointWidget.js: changed BoxNow widgettypefrompopuptoiframeandautoselectfromfalsetotrueBoxNowWidget.vue: replaced inline map div with a custom popup overlay — backdrop (#boxnow_shadow), close button (#boxnow_close_all), andwidgetStateOpenreactive 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
<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
CiDatabaseTestCasebase class — CI3-aware, transaction-per-test rollback,db()helper - New
phpunit-database.xml.distconfig 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.phpfix: 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 — replacedget(insert_id())withmatchOne(Filter...) - fix(Cookie): DI container passed
NullRelationConfiguratorto typedRepositoryConfiguratorconstructor - fix(Map/WriteData): column name
is_active→active(didn't match DB schema) - fix(Map/MuiWriteData):
title/slugtyped as?int→?string - fix(Attribute/WriteData): removed non-existent
old_idcolumn mapping - fix(Attribute/MuiWriteData): removed non-existent
SoftIdcolumn mapping - fix(MX/Controller.php):
require→require_onceto prevent class redeclaration
- fix(SortByTranslation): add
- New
[4.99.3] refactor(product): remove 16 dead methods from Adv_product_model
- Deleted 16 methods confirmed to have zero callers across
ecommercen/,application/, andsrc/: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
- Deleted 16 methods confirmed to have zero callers across
[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 13implode()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
- Replaced 39 old-style
[4.99.3] refactor(product): normalise return types to
arrayfor 18 query methods in Adv_product_model- Refactored 18 methods that previously returned
array|falseto returnarrayconsistently —return falsereplaced withreturn []in all 18 - Return type hints updated from
array|falsetoarrayfor 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 —
falsewas semantically equivalent to[]in all call sites
- Refactored 18 methods that previously returned
[4.99.3] refactor(product): visibility, type hints, query builder cleanup, and early returns in Adv_product_model
- Added explicit
publicvisibility 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
ifconditions: inprocessProductCodes()(json_decode result),insertProductCode()(insert_id result), andfixAdminSearch()(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: mixedfor complex return paths (D) - Refactored 3 methods with early returns / guard clauses:
getProductAdmin()(guard after query check, flattened body),delete()(two guards replacing$returnaccumulator, directreturn $this->db->affected_rows() > 0),get_record()(removed$tmpinit, two early returns, simplified$detailswith ternary) (L) - All changes are readability and type-safety refactors — no behaviour change
- Added explicit
[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()ontableProductCodeAttributes, deadforeach ($enabledFeeds ...)block, orphaned->group_by(), dead$select .= image_2/image_3/image_4lines and their now-emptyif ($includeAllImages)guard, and three stale Greek comments inget_products_with_paths(),getVendorProductsHits(), andrender_vendors() - Fixed missing spaces around
=increateSlug()andcategoryWithProductExists()per code style guide - Renamed single-letter and abbreviated variables to descriptive camelCase:
$q→$queryinget_master_record();$ret→$resultingetProductCodes()andgetProductCodeImages();$tmp→$resultinprocessProductCodes();$get→$queryinfindSlugsByUrl()andget_cat_products();$ind→$index,$master_attr_id→$masterAttrId,$where_attrs→$whereAttrsinget_cat_products();$iCount→$indexingetCatProducts()andgetCatProductsHits();$c→$attemptincreateSlug() - Replaced all PHP logical
andoperators with&&throughout the file (~50 occurrences); SQL string literals left untouched - All changes are readability and style refactors — no behaviour change
- Removed 7 commented-out code blocks: orphaned
[4.99.3] refactor(product): type-safety and visibility cleanup in Adv_product_model — feed methods, setProductsVendor(), getProductsWithDiscountGreaterThan()
updateFeedPriceModifier(): added: voidreturn typegetFeedProducts(): added?inttype hint to$feedIdparameter, added: arrayreturn type- Ten feed proxy methods (
getFeedProductsForGoogle(),getFeedProductsForFacebook(),getFeedProductsForBestPrice(),getFeedProductsForContactPigeon(),getFeedProductsForLinkwise(),getFeedProductsForWellcomm(),getFeedProductsForManago(),getFeedProductsForEmag(),getFeedProductsForPublicMarketplace(),getFeedProductsForAdvisableAI()): added: arrayreturn type get_public_2p_records(): added: arrayreturn typesetProductsVendor(): addedarray/inttype hints on parameters, added: voidreturn type, replacedset()->update()chain withupdate($this->table, [...])— behaviour identicalgetProductsWithDiscountGreaterThan(): addedpublicvisibility modifier (was missing), addedinttype hints on$discountand$limit, removed unused$custom_selectparameter and itsif/elsebranch, 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→$canonicalMatchfor clarity; extracted$canonicalMatchassignment out of theifcondition; addedstringtype hint; added$categorySlug !== ''guard to skip redundant cache lookup when no category is present in the URLbaseProduct(): extracted canonical redirect block into newhandleProductCanonicalRedirect(object $canonicalMatch): voidprotected helper — loadseshop/productsmodule and callscanonicalProduct()with the resolved vendor URL;baseProduct()now delegates to this helper when a canonical match is foundbaseCheckout(): replacedif/if/fallthroughstructure with amatchexpression; extracted default branch to newhandleCheckoutPage(): voidprotected helper; now always returnstrueafter the match (removes a redundantreturn truefrom each branch)handleGetResponse()andhandleCheckoutHandle(): corrected return type frombooltovoid— neither method ever returnedfalsehandleCheckoutHandle(): replaced bareexit('Could not serve your request...')with propererror_401()dispatch viaeshop/homemodule- 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_ttlchanged frompublictoprotected— only used internally and by subclasses_remap()gainsstring/arrayparam types and: voidreturn typecustomRemap()gainsstringparam type and: boolreturn type- Fix alignment spacing on
$productSlugassignment inbaseProduct()per code style guide
[4.99.3] refactor(eshop): simplify baseCheckout() in Adv_eshop — extract handleGetResponse() and handleCheckoutHandle()
- Replaced
if/elseif/elsechain with early-return dispatch and two dedicated protected helpers handleGetResponse(array $params): bool— isolates payment gateway callback routing; uses strictin_array()for the simple-gateway list (paybybank,stripe,iris)handleCheckoutHandle(array $params): bool— isolates the handle action with a guard for missing paramsbaseCheckout()now reads as a plain dispatch table: get_response → handle → checkout page- Both helpers are
protectedso client repos can override individual actions without duplicating the full method - Added
arraytype hint and: boolreturn type tobaseCheckout(); removed unreachablereturn false
- Replaced
[4.99.3] refactor(eshop): simplify baseCategory() in Adv_eshop — extract parseCategorySlugAndOffset()
- Extracted
parseCategorySlugAndOffset(string $method): arrayprotected helper to isolate the pagination offset detection logic; client repos can override it independently - Collapsed the duplicated
if/elsebranches inbaseCategory()into a single code path — one cache query, one dispatch - Added
stringtype hint and: boolreturn type tobaseCategory(), consistent withbaseProduct() - No functional change — behaviour is identical
- Extracted
[4.99.3] feat(cart): add Cart domain with DB persistence and REST endpoints
- New
shop_cartandshop_cart_itemtables (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)
- New
[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-streamfrom 3.0.0 to 4.0.0- Controller renamed:
GenericMediaController→MediaController(route updated inroutes.yaml) services.yamlreduced from ~270 lines to 7 — library now exports all DI config via@vendor/services-library.yaml; host keeps only parameter overrides
- Controller renamed:
[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.jsonenginesnow requires Node 24.13+ / npm 11.8+. Local dev environments and CI runners must upgrade beforenpm ciwill pass strict-engines mode.[4.99.16] Check for overrides (REST product/review RBAC alignment — #56):
application/config/rest_policies.php—Reviewcontroller defaults changed from[ADMIN, PRODUCTS]to[ADMIN, MARKETING]. Any clientrest_policies.phpoverride that pinned the old role set will reapply the divergence.
[4.99.16] Check for overrides (REST stateless session loading fix):
ecommercen/core/Controller.phpecommercen/core/Adv_admin_controller.phpecommercen/core/Adv_front_controller.phpecommercen/advisable/controllers/Adv_login.phpecommercen/sso/controllers/AdvSso.php- Behavior change for any client controller that extends
Base_cdirectly (i.e. not viaFront_corAdmin_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_idreference corrected (wasproductId; aggregator hook was silently no-op)ecommercen/eshop/models/Adv_gifts_model.php—getCheapestRequirementProductInCartnow skips nullfinal_price; new public methodgetProductIdsByCodeIds(array $codeIds): arrayecommercen/eshop/models/Adv_order_model.php—adjustCartForRule13Giftsnow resolvesproduct_code_id → product_idfor fallback cart-trim matching
[4.99.15] Check for overrides (FilterByTranslation LIKE wildcard fix):
src/Domains/Ai/Position/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Blog/Article/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Blog/Author/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Blog/Category/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Blog/Tag/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Document/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Page/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Subcontent/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Video/Repository/Specification/FilterByTranslation.phpsrc/Domains/Country/Country/Repository/Specification/FilterByTranslation.phpsrc/Domains/Country/County/Repository/Specification/FilterByTranslation.phpsrc/Domains/Map/Location/Repository/Specification/FilterByTranslation.phpsrc/Domains/Map/Map/Repository/Specification/FilterByTranslation.phpsrc/Domains/Product/Product/Repository/Specification/FilterByTranslation.phpsrc/Domains/Product/ProductList/Repository/Specification/FilterByTranslation.phpsrc/Domains/Slider/Slide/Repository/Specification/FilterByTranslation.phpsrc/Domains/Slider/Slider/Repository/Specification/FilterByTranslation.php
[4.99.15] Check for overrides:
ecommercen/plus/controllers/AdvPlusAlgoPrioritySettings.phppublic/api-docs.htmlsrc/Domains/Support/Request/QueryListBuilder/FilterRequest.phpsrc/Domains/Support/Request/QueryListBuilder/GenerateListRequest.phpsrc/Domains/Product/Category/Service.phpsrc/Domains/Product/Product/Service.phpsrc/Domains/Product/Product/ListRequest.phpsrc/Domains/Product/Product/Repository/Specification/FilterByBarcode.phpsrc/Domains/Product/Product/Repository/Specification/FilterByCategory.phpsrc/Rest/Product/Controllers/Product.phpapplication/config/rest_api_versions.php
[4.99.14] Check for overrides (VAT write layer):
src/Domains/Order/Vat/Repository/WriteRepository.phpsrc/Domains/Order/Vat/Validator.phpsrc/Domains/Order/Vat/WriteService.php
[4.99.14] Check for overrides (REST batch — cart, collections, product, checkout):
src/Domains/Order/Order/Repository/Entity.phpsrc/Domains/Order/Order/WriteData.phpsrc/Rest/Cart/Resources/Cart/Collection.phpsrc/Rest/Cart/Resources/CartItem/Collection.phpsrc/Rest/Cms/Resources/Blog/Article/Collection.phpsrc/Rest/Cms/Resources/Blog/Author/Collection.phpsrc/Rest/Cms/Resources/Blog/Category/Collection.phpsrc/Rest/Cms/Resources/Blog/Comment/Collection.phpsrc/Rest/Cms/Resources/Blog/Tag/Collection.phpsrc/Rest/Cms/Resources/Document/Collection.phpsrc/Rest/Cms/Resources/Page/Collection.phpsrc/Rest/Cms/Resources/Subcontent/Collection.phpsrc/Rest/Country/Resources/Country/Collection.phpsrc/Rest/Country/Resources/County/Collection.phpsrc/Rest/Customer/Resources/Customer/Collection.phpsrc/Rest/Map/Resources/Location/Collection.phpsrc/Rest/Map/Resources/Map/Collection.phpsrc/Rest/Order/Resources/Basket/Collection.phpsrc/Rest/Order/Resources/Order/Collection.phpsrc/Rest/Order/Resources/Store/Collection.phpsrc/Rest/Order/Resources/Vat/Collection.phpsrc/Rest/Product/Resources/Attribute/Collection.phpsrc/Rest/Product/Resources/AttributeGroup/Collection.phpsrc/Rest/Product/Resources/Badge/Collection.phpsrc/Rest/Product/Resources/Barcode/Collection.phpsrc/Rest/Product/Resources/Category/Collection.phpsrc/Rest/Product/Resources/Line/Collection.phpsrc/Rest/Product/Resources/Media/Collection.phpsrc/Rest/Product/Resources/Product/Collection.phpsrc/Rest/Product/Resources/ProductCode/Collection.phpsrc/Rest/Product/Resources/ProductCodeAttribute/Collection.phpsrc/Rest/Product/Resources/Promo/Collection.phpsrc/Rest/Product/Resources/Supplier/Collection.phpsrc/Rest/Product/Resources/Tag/Collection.phpsrc/Rest/Product/Resources/TagCategory/Collection.phpsrc/Rest/Product/Resources/Vendor/Collection.phpsrc/Rest/Slider/Resources/Group/Collection.phpsrc/Rest/Slider/Resources/Slide/Collection.phpsrc/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 <= 0→newQty < 0); without rebuild, admin order total will not reflect the deduction in that scenarioassets/admin/js/jobs/jobs.vue— readsmax_retriesinstead of undefinedjob_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
!== nullguards 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.phpsrc/Rest/Cart/Resources/CartItem/Resource.phpsrc/Rest/Cart/Resources/CartTotals/Resource.phpsrc/Rest/Cms/Resources/Blog/Article/MuiResource.phpsrc/Rest/Cms/Resources/Blog/Author/MuiResource.phpsrc/Rest/Cms/Resources/Blog/Author/Resource.phpsrc/Rest/Cms/Resources/Blog/Category/MuiResource.phpsrc/Rest/Cms/Resources/Blog/Category/Resource.phpsrc/Rest/Cms/Resources/Blog/Tag/MuiResource.phpsrc/Rest/Cms/Resources/Document/MuiResource.phpsrc/Rest/Cms/Resources/Page/MuiResource.phpsrc/Rest/Cms/Resources/Page/Resource.phpsrc/Rest/Cms/Resources/Subcontent/MuiResource.phpsrc/Rest/Country/Resources/Country/Resource.phpsrc/Rest/Customer/Resources/Customer/Resource.php(scope changes: pointFactor → public, totalPoints → authenticated)src/Rest/Map/Resources/Location/MuiResource.phpsrc/Rest/Map/Resources/Map/MuiResource.phpsrc/Rest/Order/Controllers/Order.php(tracking() now formats dates)src/Rest/Order/Resources/Basket/Resource.phpsrc/Rest/Order/Resources/Store/MuiResource.phpsrc/Rest/Order/Resources/Tracking/Resource.phpsrc/Rest/Product/Resources/Attribute/MuiResource.phpsrc/Rest/Product/Resources/AttributeGroup/MuiResource.phpsrc/Rest/Product/Resources/Badge/MuiResource.phpsrc/Rest/Product/Resources/Badge/Resource.phpsrc/Rest/Product/Resources/Barcode/Resource.phpsrc/Rest/Product/Resources/Category/MuiResource.phpsrc/Rest/Product/Resources/Line/MuiResource.phpsrc/Rest/Product/Resources/Media/Resource.phpsrc/Rest/Product/Resources/Product/MuiResource.phpsrc/Rest/Product/Resources/ProductCode/Resource.phpsrc/Rest/Product/Resources/Promo/MuiResource.phpsrc/Rest/Product/Resources/Supplier/MuiResource.phpsrc/Rest/Product/Resources/Tag/MuiResource.phpsrc/Rest/Product/Resources/TagCategory/MuiResource.phpsrc/Rest/Product/Resources/Vendor/MuiResource.phpsrc/Rest/Slider/Resources/Slide/MuiResource.phpsrc/Rest/Slider/Resources/Slider/MuiResource.phpsrc/Rest/Slider/Resources/Slider/Resource.phpsrc/Rest/Support/Resources/PaginationLinks.phpsrc/Rest/Support/Resources/PaginationMeta.php
[4.99.11] Client repos with custom
max_upload_sizeinapplication/config/app.php: The default changed from'19000000'to19000. 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
MuiWriteRepositorymust updateinsertForEntity()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.phpsrc/Rest/Middleware/PolicyResolver.phpsrc/Rest/Middleware/RequestContext.phpsrc/Rest/OpenApi.phpsrc/Rest/Versioning/VersionResolver.phpsrc/Rest/Versioning/VersionResult.php
[4.99.10] Check for overrides:
ecommercen/auth/models/Adv_roles_model.phpsrc/Domains/Admin/Role/ListRequest.phpsrc/Domains/Admin/Role/Repository/Entity.phpsrc/Domains/Admin/Role/Repository/Repository.phpsrc/Domains/Admin/Role/Repository/RepositoryConfigurator.phpsrc/Domains/Admin/Role/Service.phpsrc/Rest/Admin/Resources/Role/Collection.phpsrc/Rest/Admin/Resources/Role/Resource.php
[4.99.10] REQUIRES
php migrator.php migrate— roles table creation (migration20260409131959_create_roles_table.php): Creates therolestable, seeds 9 canonical roles, cleans orphanuser_rolerows, and adds an FK constraint onuser_role.role_id. Must run before deploying the new code — the legacyAdv_roles_modeland modernRole\Repositoryboth read from this table.[4.99.10] Breaking:
auth_rolesconfig removed fromapplication/config/auth.php. Any client code that readsconfig_item('auth_roles')must be updated to query therolesDB table instead. TheAUTH_ROLE_*constants inconstants.phpare unchanged.[4.99.10] Client repos with a
RoleProvideroverride:RoleProvideris deprecated and no longer registered in the DI container. Client repos that override or extend it must migrate toRole\ServiceorRole\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. Migration20260415102932detects 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 (migration20260408100114_convert_myisam_tables_to_innodb.php): The migration runsALTER TABLE ... ENGINE=InnoDB ROW_FORMAT=DYNAMICon 10 tables. On large deployments theshop_product_muitable 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.phpsrc/Domains/Admin/User/ListRequest.phpsrc/Domains/Admin/User/Repository/Entity.phpsrc/Domains/Admin/User/Repository/Repository.phpsrc/Domains/Admin/User/Repository/RepositoryConfigurator.phpsrc/Domains/Admin/User/Repository/WriteRepository.phpsrc/Domains/Admin/User/Service.phpsrc/Domains/Admin/User/Validator.phpsrc/Domains/Admin/User/WriteData.phpsrc/Domains/Admin/User/WriteService.phpsrc/Domains/Admin/UserRole/Repository/Entity.phpsrc/Domains/Admin/UserRole/Repository/Repository.phpsrc/Domains/Admin/container.phpsrc/Rest/Admin/Controllers/Role.phpsrc/Rest/Admin/Controllers/User.phpsrc/Rest/Admin/Resources/User/Collection.phpsrc/Rest/Admin/Resources/User/Resource.phpsrc/Rest/Admin/container.php
[4.99.8] Check for overrides:
src/Domains/Support/Auth/AuthHashService.phpsrc/Domains/Support/Auth/container.php
[4.99.8] REQUIRES
php migrator.php migrate— Order export feature removed (VULN-05): Theshop_exporttable is dropped by migration. Runphp migrator.php migrateon 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 callsset_export_data(),get_export_data(),Webrun::generate_xsl(), orWebrun::generate_xsl_for_geniki()— these methods are gone.[4.99.7] REQUIRES
php migrator.php migrate+composer install— Keywords module removed: Thekeywordstable is dropped by migration. Runphp migrator.php migrateon all environments after deploying. Runcomposer installto clean up the autoloader. Any client repo that overridesapplication/modules/keywords/orapplication/modules/keywords_model/should remove those overrides — they will be dead code.[4.99.6] REQUIRES
composer install— NewClearExpiredSessionsjob added. After merging to a client repo, runcomposer installso the autoloader picks upAdvClearExpiredSessionsandClearExpiredSessions. The job is scheduled daily at 3 AM in thecorequeue. Clients can overrideClearExpiredSessionsinapplication/modules/job/libraries/if needed.[4.99.6] Payment webhooks require
STRIPE.WEBHOOK_SECRETRegistry 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 returnPENDING_ACCEPTEDstatus instead ofPENDING. This aligns with legacy checkout behavior where stock is reduced immediately for offline payments. API consumers pollingpayment-statuswill see this change.[4.99.6] Check for overrides:
src/Domains/Checkout/PlaceOrderService.phpsrc/Domains/Checkout/PaymentConfirmationService.phpsrc/Domains/Checkout/StockService.phpsrc/Domains/Checkout/OrderBasketBuilder.phpsrc/Domains/Checkout/container.phpsrc/Domains/Order/OrderBasket/WriteService.phpsrc/Domains/Order/OrderBasket/WriteData.phpsrc/Domains/Order/OrderBasket/Repository/WriteRepository.phpsrc/Domains/Order/container.phpsrc/Domains/Promotion/CouponCode/Repository/WriteRepository.phpsrc/Domains/Checkout/Payment/Adapters/DeliveryAdapter.phpsrc/Domains/Checkout/Payment/Adapters/BankTransferAdapter.phpsrc/Domains/Checkout/Payment/Adapters/PaidAtStoreAdapter.phpsrc/Rest/Webhooks/Controllers/Webhook.phpsrc/Rest/Webhooks/container.phpsrc/Rest/Checkout/Controllers/Checkout.phpapplication/config/rest_routes.phpapplication/config/container/modules.php
[4.99.6] Check for overrides:
src/Domains/Cms/Blog/Article/WriteService.phpsrc/Domains/Cms/Blog/Author/WriteService.phpsrc/Domains/Cms/Blog/Category/WriteService.phpsrc/Domains/Cms/Blog/Tag/WriteService.phpsrc/Domains/Cms/OfferCategory/WriteService.phpsrc/Domains/Cms/Video/WriteService.phpsrc/Domains/Event/Category/WriteService.phpsrc/Domains/Event/Event/WriteService.phpsrc/Domains/Map/Map/WriteService.phpsrc/Domains/Product/Category/WriteService.phpsrc/Domains/Product/Line/WriteService.phpsrc/Domains/Product/Product/WriteService.phpsrc/Domains/Product/Promo/WriteService.phpsrc/Domains/Product/Tag/Category/WriteService.phpsrc/Domains/Product/Tag/Tag/WriteService.phpsrc/Domains/Product/Vendor/WriteService.phpsrc/Domains/Support/Slug/GeneratesSlugs.phpsrc/Domains/Support/Slug/SlugGenerator.phpsrc/Domains/Support/Slug/container.phpsrc/Rest/Support/Controllers/HandlesUploadActions.php
[4.99.5] REST controllers bypass site mode guard automatically via the new
siteModeAllowedNamespacesconfig key inapplication/config/app.php. Client repos add extra namespaces (e.g.Custom\Rest\,Custom\Certus\) directly in their config — noSiteModeMiddlewaresubclass needed.[4.99.5] Client Apache deployments using CGI/FastCGI must add
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1to theirpublic/.htaccessfor 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
POSTinstead ofPUT. 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
UploadServicereuses the sameUploadValidator+UploadFileNameGenerator+Storage(Flysystem) pipeline as legacy admin controllers.[4.99.4] Controllers with file fields use
HandlesUploadActionsinstead ofHandlesWriteActions— do NOT use both traits together. See.claude/rules/rest-layer.mdfor 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.mdreordered accordinglyupdate-docsSKILL.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->productTagLpin tag join query'shop_product'→$this->tablein product FROM clause'shop_prices_view'→$this->viewPricesin 6 join calls across multiple query methods'product_code_attributes'→$this->tableProductCodeAttributesin 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
$tableShelfCodesproperty ('shop_product_shelfcodes') — never referenced in this class; the table is used independently via its own property inAdv_order_basket_model - Remove unused
$tableSupplierproperty ('shop_supplier') — unreferenced anywhere in the codebase - Add explicit
stringtype declarations to all 31 previously untyped table/view name properties, consistent with the 3 properties that were already typed - Change
$tableBarcodesvisibility frompublictoprotected— never accessed externally; all other models with atableBarcodesproperty declare itprotectedindependently - No functional change — internal, non-API-facing cleanup only
- Remove unused
[4.99.3] refactor(product): remove 5 dead methods from Adv_product_model
get_random_mui_records()— thin wrapper overget_mui_records()with no callerswhereBarCodesIn()— single-line helper with no callerscreate_slug()— carried a@TODO deprecatecomment since 2017; zero callers (other models maintain independent implementations)getMaxDiscountPerCategory()— empty stub returning[], zero callerssetOrderBy()— 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 formalizebatchMasterUpdate()signature- Remove
batch_master_update($products, $change_row, $with_value)fromAdv_product_model— was a 3-argument snake_case wrapper that simply delegated tobatchMasterUpdate() - Add
publicvisibility and: voidreturn type tobatchMasterUpdate(array $productIds, array $updateData)— it was already used publicly but had no explicit declaration - Update all 5 callers in
Adv_products_admin.phpto callbatchMasterUpdate()directly with the correct array parameter signature
- Remove
[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
HandlesWriteActionstrait (store,update,destroyactions) - 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 ofinsert_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 (
BlogArticleController→BlogArticle) - Add
protectBackendauth guard to Order write endpoints (store/update/destroy) - Resolve OpenAPI schema name collisions by prefixing with context (e.g.,
Category→EventCategoryWriteData) - Move Order and Product/Category write service DI registrations to correct container sections
- Fix BlogArticle route class reference (
[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 usingSELECT 1+get_where()ontableMuiwithLIMIT 1; no JOIN; returnsboolinstead ofobject|falseget_content_url($slugAsUrl)→findSlugsByUrl(string $slugAsUrl): ?object— joinstableVendorsMuito also returnvendor_slug; appliesTRIM(BOTH '/' FROM url)in WHERE clause; returns?objectinstead ofobject|false
[4.99.3] feat(product): add existsBySlug() to Adv_product_category_model
- New method
existsBySlug(string $slug): bool— optimised existence check usingSELECT 1+get_where()ontableMuiwithLIMIT 1 - Does NOT replace
get_content(), which is still used where the full row is needed
- New method
[4.99.3] refactor(eshop): rewrite baseProduct() and setCanonicalUrl() in Adv_eshop.php / Adv_products.php
baseProduct(): cleaner slug extraction viastrrpos; short-circuits category lookup (only queries if product found first);query2branch now callscanonicalProduct()with vendor URL (was incorrectly callingindex()); updated callers useexistsBySlug()on bothproduct_modelandproduct_category_modelbaseCategory(): updated caller to useproduct_category_model::existsBySlug()setCanonicalUrl()(Adv_products.php): always sets canonical URL — removedisCanonicalflag dependency; prefers product's ownurlfield; falls back to{vendorsBaseUrl}/{vendor_slug}/{slug}; usessite_url()instead ofcurrent_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 usedexistsBySlug(); both branches now use the optimised existence check for consistency
- The paginated branch was still calling
[4.99.3] fix(admin): trim leading/trailing slashes from product URL field on insert and update
getCreateProductDataMuiPost()andgetEditProductDataMuiPost()inAdv_products_admin.phpnow strip leading and trailing slashes from the URL field before persisting
[4.99.3] feat(product-url): honour
shop_product_mui.urlfield everywhere — product cards, feeds, sitemaptransformProductForJson()intheme_helper.phpusesmatch(true)priority:$product->url→$urlparam →vendors_base_url_{lang}/{vendor_slug}/{slug}→category_slug/slug; replaces hardcoded"vendors/"prefix withvendors_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) andorder_item.phpupdated to same priority logic ProductVariationsTrait.phpupdated to respecturlfield and usevendors_base_url_{lang}configAdvXml.phpbase class: newresolveProductUrl(object $product): stringmethod with identical priority logic; called inparseItem(); 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 inlineOutputGoogleProductReviewXml.php: inline ternary forurlfield (not inAdvXmlhierarchy)Adv_product_model::getFeedProducts()andgetProductsForSiteMap():{$this->tableMui}.urladded to SELECTAdvGenerateSitemaps.php: usesurlfield when set; falls back tovendors_base_url_{lang}/{vendor_slug}/{slug}
[4.99.3] refactor(theme-helper): remove redundant vendor
$urlparam from 20 callers oftransformProductForJson()transformProductForJson()now resolves vendor URLs natively viamatch(true)priority logic usingvendor_slug+vendors_base_url_{lang}config- Removed
"vendors/{$vendor_slug}"as the$urlargument from 20 call sites across views and one controller - The
$urlparam 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
ReadServiceInterfaceandWriteServiceInterface— the old combinedServiceInterfaceis deleted; 130+ domain services migrated toReadServiceInterface, dead write stubs removed - New
BaseWriteRepositorybase class for write-side persistence (insert, update, delete) - New
ValidationExceptionfor structured domain validation errors - New
HandlesWriteActionstrait for REST controllers — provides standardstore(),update(),destroy()actions with OpenAPI-annotated responses - Cms/Page proof-of-concept:
WriteData/MuiWriteDataDTOs,Validator,WriteRepository,MuiWriteRepository,WriteServicewith transactional create/update/delete; REST controller updated withHandlesWriteActions - 67 new unit tests covering the write layer infrastructure and Page write service
- New
[4.99.2] feat(domains): CQRS write layer — Phase 3 & 4: Product Badge and Cms Subcontent
- Product/Badge write layer:
WriteDataDTO,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
- Product/Badge write layer:
[4.99.2] fix(openapi): fix SecurityScheme param name and regenerate spec
- Fixed pre-existing bug:
securitySchemeId→securityScheme(invalid param in swagger-php 5.8) - Regenerated
openapi.jsonwith all write endpoints included
- Fixed pre-existing bug:
[4.99.2] docs(openapi): add write request body schemas with consistent field names
- Added
OA\Schemaannotations toWriteData/MuiWriteDataDTOs 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
- Added
[4.99.2] docs(openapi): add missing fields to PageMuiResource read schema
- Added 6 missing fields to
PageMuiResourceOA schema:pageId,metaKeywords,metaDescription,metaTitle,url,redirectUrl
- Added 6 missing fields to
[4.99.1] fix(tests): reset PHP time limit after DeferredTaskRunner tests
DeferredTaskRunner::run()callsset_time_limit()which persists globally across the test suite- Tests
testSkipsTaskThatExceedsBudget(budget=2 s) andtestAllTasksSkippedWhenBudgetTooSmall(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()toDeferredTaskRunnerTestthat resets the limit viaset_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-jwtv7.0.3 (pinned incomposer.locksince 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, causingDomainException: Provided key is too shorton all 5 tests inTokensTestthat callgenerateAccessToken() - 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_livedtier 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-taskchannel config to monolog.php (threshold WARNING) - Create
deferred_task.loggerDI service and inject into the runner - Add env vars to .env.example
- Add
[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(...) * 2state 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.distwith 3 test suites: Unit, Integration, Legacy - Add
tests/bootstrap.php(minimal autoload) andtests/bootstrap_ci.php(full CI3 bootstrap for integration/legacy tests) - Add
tests/CiTestCase.phpbase 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
.gitignoreentries for.phpunit.cacheandphpunit.xml(developer overrides)
- Add
[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, includingCustom\PSR-4 layer status - Add
.claude/settings.jsonto register the SessionStart hook - Update
CLAUDE.mdwith 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 usecustom/when extending upstream or working with existing custom domains
- Add 12 subagent definitions in
[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
couponcolumn tocodein 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.
- Changes covered: docblock removed, public visibility and typed signature added,
[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.
- The changes are: docblock removed, public visibility added, parameter
[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::$datawith validatorruleProductsCheapestFreeValidatorandsetDefaultsforcinggift_user_choice_countto 0 - Add
ruleProductsCheapestFreeValidatortoAdv_gifts_model: counts total quantity of requirement products present in cart - Add
getCheapestRequirementProductInCarthelper: finds cheapest in-cart requirement product using live price data; deterministic tie-break by lowest product ID - Wire case 13 into
getGiftForProductsInCartswitch and use helper for dynamic gift pool instead ofgift_choicestable - Add
rule13.nameandrule13.helptranslation keys in all 8 languages (English, Greek, German, French, Spanish, Italian, Russian, Chinese)
- Add rule 13 entry to
[4.99.0] fix(gifts): fix getActiveGiftRules excluding rule 13 from isLive
- Select
rule_idin query so it is available in the filter callback - Remove early-exit on empty choices so rule 13 (no
gift_choicesrows) is not excluded - Bypass choices check in
array_filterfor rule 13 — pool is computed dynamically
- Select
[4.99.0] fix(gifts): correct cart display and checkout total for rule 13 (cheapest free)
- Add
rule_idtocartGiftsentries ingetGiftForProductsInCart()so callers can identify rule 13 gifts - Add
AdvCartResource::adjustCartContentsForRule13()to subtract the gift qty from the matching cart row before sendingcartContentsto the frontend - Add
Adv_order_model::getRule13GiftAdjustments()to resolve cheapest free product and deduction count - Apply adjustment in
baseParseCartContents()via local$paidQtyso subtotals/totals reflect only paid items - Add
adjustCartForRule13Gifts()and call it inprocessOrder()to remove the free item from stored basket rows at order placement
- Add
[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 ingifts_for_productsAJAX endpoint - Skip rule 13 in
validateProductGiftSelectionsserver-side callback (auto-assigned, no user selection needed) - Force
ruleSatisfiesChoiceSelection=truefor rule 13 in Vue Vuex getter - Extract
getRule13GiftAdjustmentsForProducts()for reuse between session cart and admin fake cart - Apply rule 13 price deduction in
createAdminFakeCartso admin order totals price correctly - Cache
rule13Productson the Vue app and exposereapplyRule13Deductions()called after everyupdateMarkUpPricesrebuild
- Override
[4.99.4] REQUIRES
composer install— phpseclib security patch:phpseclib/phpseclibupdated to 3.0.50 (CVE-2026-32935) andleague/flysystem-sftp-v3updated to 3.33.0. After merging to a client repo, runcomposer installto pull the updated packages.[4.99.4] axios supply chain attack — no build required:
axios@1.14.1andaxios@0.30.4were published on March 30, 2026 by a compromised npm maintainer account (jasonsaayman) and deliver a cross-platform Remote Access Trojan (RAT).package.jsonhas been updated to exclude these versions. Safe versions areaxios@1.14.0and below (for 1.x) andaxios@0.30.3and 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.vueandAdvCartGiftRow.vuewere updated. After merging this fix, runnpm run production(ornpm run all-production) to rebuild the frontend assets.[4.99.4] Client repos with a CartResource override: Any client repo that overrides
AdvCartResourceand re-implementsadjustCartContentsForRule13()must ensure their override also annotatesfreeQuantityon 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.phpthat overridesadjustCartContentsForRule13()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, runnpm run productionto rebuild the frontend assets.[4.99.3] Check for overrides:
src/Rest/Cms/Controllers/BlogArticle.phpsrc/Rest/Cms/Controllers/BlogAuthor.phpsrc/Rest/Cms/Controllers/BlogCategory.phpsrc/Rest/Cms/Controllers/BlogComment.phpsrc/Rest/Cms/Controllers/BlogTag.phpsrc/Rest/Cms/Controllers/Document.phpsrc/Rest/Country/Controllers/Country.phpsrc/Rest/Country/Controllers/County.phpsrc/Rest/Map/Controllers/Location.phpsrc/Rest/Map/Controllers/Map.phpsrc/Rest/Order/Controllers/Store.phpsrc/Rest/Order/Controllers/Vat.phpsrc/Rest/Product/Controllers/Attribute.phpsrc/Rest/Product/Controllers/AttributeGroup.phpsrc/Rest/Product/Controllers/Barcode.phpsrc/Rest/Product/Controllers/Line.phpsrc/Rest/Product/Controllers/Media.phpsrc/Rest/Product/Controllers/ProductCodeAttribute.phpsrc/Rest/Product/Controllers/Promo.phpsrc/Rest/Product/Controllers/Supplier.phpsrc/Rest/Product/Controllers/Tag.phpsrc/Rest/Product/Controllers/TagCategory.phpsrc/Rest/Slider/Controllers/Group.phpsrc/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 viagit showand add it as an override in the client'sapplication/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|falsetoarrayfor 18 methods inecommercen/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 fromarray|falsetoarrayand replacereturn falsewithreturn []to stay compatible.[4.99.3] REMOVED PARAMETER — action required for client repos:
getProductsWithDiscountGreaterThan()inecommercen/eshop/models/Adv_product_model.phpno longer accepts a third$custom_selectparameter. If a client repo calls this method with three arguments, remove the third argument. If a client repo overrides this method, remove the$custom_selectparameter from the override signature.[4.99.3] Check for overrides:
ecommercen/eshop/models/Adv_product_model.php—setProductsVendor(): signature nowpublic function setProductsVendor(array $productIds, int $vendorId): void— update any override to match the added type hints and: voidreturn typeecommercen/eshop/models/Adv_product_model.php—getFeedProducts():$feedIdis now?int— update any override to matchecommercen/eshop/models/Adv_product_model.php— the tengetFeedProductsFor*()proxy methods now declare: arrayreturn 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 toprotected function customRemap(string $method): bool. Update the override to match — add thestringtype hint on$methodand the: boolreturn type declaration. The method must return abool.[4.99.3] Check for overrides:
ecommercen/eshop/controllers/Adv_eshop.php—baseProduct()refactored; the canonical redirect logic is now inhandleProductCanonicalRedirect(object $canonicalMatch): void; override this helper in a client repo to customise canonical redirect behaviour (e.g. different URL resolution, additional tracking) without duplicatingbaseProduct()ecommercen/eshop/controllers/Adv_eshop.php—baseCheckout()refactored; if a client repo overrides this method, verify compatibility with the new signaturebaseCheckout(array $params): booland the three protected helpershandleGetResponse(): void,handleCheckoutHandle(): void, andhandleCheckoutPage(): void; notehandleCheckoutHandle()now dispatcheserror_401()viaeshop/homeinstead of callingexit()directly — override if different error handling is requiredecommercen/eshop/controllers/Adv_eshop.php—baseCategory()refactored; if a client repo overrides this method, verify it is compatible with the new signaturebaseCategory(string $method): booland the extractedparseCategorySlugAndOffset()protected helper (also overridable independently)ecommercen/eshop/controllers/Adv_eshop.php—$pscache_ttlchanged toprotected; 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 migrateto createshop_cartandshop_cart_itemtables - 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
- Run
[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 calculatorsrc/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 endpointssrc/Rest/Checkout/Controllers/Checkout.php— 5 checkout endpointssrc/Rest/Auth/CustomerAuth.php— added register() methodsrc/Rest/Customer/Controllers/Customer.php— added me/password endpointssrc/Rest/Order/Controllers/Order.php— added cancel() and tracking() endpointsapplication/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: renameGenericMediaController→MediaController. - If referencing media-stream classes by FQCN in DI overrides: check namespace moves (
Domain\...\Implementation\→Infrastructure\...). Seevendor/ecommercen/media-stream/docs/Guides/Migration-3.x-to-4.x.md.
- If overriding
[4.99.3] DO NOT pass
"vendors/{$vendor_slug}"as the$urlparam totransformProductForJson()— this is now redundant. The helper resolves vendor URLs natively viamatch(true)priority logic (vendor_slug+vendors_base_url_{lang}config). Passing a vendor path as$urloverrides the config-driven resolution unnecessarily. The$urlparam 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 yourapplication/config/or registry settings. If the item is absent,config_item()returnsnulland the fallbackcategory_slug/slugpath is used.[4.99.3] Check for overrides:
ecommercen/helpers/theme_helper.php—transformProductForJson()ecommercen/feeds/core/AdvXml.php— newresolveProductUrl()methodecommercen/eshop/traits/ProductVariationsTrait.phpecommercen/job/libraries/AdvGenerateSitemaps.phpecommercen/eshop/models/Adv_product_model.php—getFeedProducts(),getProductsForSiteMap()application/views/main/components/product_card/product_card.phpapplication/views/main/components/product_card/product_card_fashion.phpapplication/views/main/components/product_card/nav_product_card.phpapplication/views/main/components/product_card/product_combo_card.phpapplication/views/main/components/product_bundles/product_bundle_combo_card.phpapplication/views/main/components/customer/order_item.phpsrc/Feeds/Output/OutputGoogleProductReviewXml.php
[4.99.3] REMOVED METHOD — action required for client repos:
batch_master_update()has been permanently removed fromAdv_product_model. If a client repo calls this method, replace it withbatchMasterUpdate()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])
- Before:
[4.99.3] REMOVED METHODS — action required for client repos: The following 5 methods have been permanently removed from
Adv_product_modelinecommercen/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'sapplication/modules/eshop/models/Product_model.php(which extendsAdv_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/orderrequire 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.,
Category→EventCategoryWriteData/ProductCategoryWriteData). If client tooling generates code frompublic/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 withHandlesWriteActionstrait 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_modelhave been renamed and their signatures changed:get_content($slug)→existsBySlug(string $slug): bool— return type changed fromobject|falsetoboolget_content_url($slugAsUrl)→findSlugsByUrl(string $slugAsUrl): ?object— now also returnsvendor_slug; return type changed fromobject|falseto?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.phpoverride.
[4.99.3] Check for overrides:
ecommercen/eshop/models/Adv_product_model.php—existsBySlug(),findSlugsByUrl()ecommercen/eshop/models/Adv_product_category_model.php— newexistsBySlug()ecommercen/eshop/controllers/Adv_eshop.php—baseProduct(),baseCategory()ecommercen/eshop/controllers/Adv_products.php—setCanonicalUrl()ecommercen/eshop/controllers/Adv_products_admin.php—getCreateProductDataMuiPost(),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:
ServiceInterfacedeleted — onlyReadServiceInterfaceandWriteServiceInterfaceexist- 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
ServiceInterfacemust be updated to the appropriate interface
- All domain services now implement
[4.99.2] Check for overrides:
src/Domains/Cms/Page/WriteRepository.php— new (write-side persistence for Page)src/Domains/Cms/Page/MuiWriteRepository.php— newsrc/Domains/Cms/Page/WriteService.php— newsrc/Domains/Cms/Page/WriteData.php/MuiWriteData.php— new (DTOs)src/Domains/Cms/Page/Validator.php— newsrc/Domains/Cms/Subcontent/WriteRepository.php— newsrc/Domains/Cms/Subcontent/WriteService.php— newsrc/Domains/Product/Badge/WriteRepository.php— newsrc/Domains/Product/Badge/MuiWriteRepository.php— newsrc/Domains/Product/Badge/WriteService.php— newsrc/Domains/Support/Repository/BaseWriteRepository.php— newsrc/Domains/Support/ValidationException.php— newsrc/Rest/Support/Controllers/HandlesWriteActions.php— new (store/update/destroy for REST)src/Rest/Cms/Controllers/Page.php— updated (write actions added)- All 130+
src/Domains/*/Service.phpfiles — interface changed fromServiceInterfacetoReadServiceInterface
[4.99.0] Check for overrides:
ecommercen/sliders/models/Adv_sliders_model.php
[4.99.0] Check for overrides:
ecommercen/audience/models/AdvCustomerTagModel.phpecommercen/coupons/controllers/Adv_coupons.phpecommercen/eshop/controllers/Adv_reporting.phpecommercen/eshop/models/Adv_order_basket_model.phpecommercen/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.phpsrc/Cache/Adapter/InMemoryAdapter.phpsrc/Cache/Adapter/RedisAdapter.phpsrc/Cache/Adapter/RedisClusterAdapter.phpsrc/Cache/Adapter/ShmAdapter.php
[4.99.0] Check for overrides:
src/CircuitBreaker/CircuitBreaker.php
[4.99.0] Check for overrides:
src/DeferredTask/DeferredTask.phpsrc/DeferredTask/DeferredTaskRunner.php
[4.99.0] Check for overrides:
src/MetaConversionsApi/Client/MetaHttpClient.phpsrc/MetaConversionsApi/Service/FacebookConversionService.php
[4.99.0] Check for overrides:
application/controllers/Webrun.phpecommercen/eshop/controllers/Adv_vendors.phpsrc/Analytics/MatomoTracking.phpsrc/Manago/Manago.php
[4.99.0] Check for overrides:
src/Cache/Adapter/CacheAdapterInterface.phpsrc/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.phpsrc/Cache/Adapter/WithKeyPartsExtractionTrait.phpsrc/Cache/Adapter/WithRedisKeyParsingTrait.phpsrc/Cache/Adapter/WithRedisOptionsTrait.php
[4.99.0] Check for overrides:
application/controllers/Testing.php
[4.99.0] BREAKING:
CacheAdapterInterfaceDI 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)
- Client repos using
[4.99.0] New .env vars (copy from .env.example):
APP_CACHE_L2_*(replaces oldAPP_CACHE_*)APP_CACHE_L1_*(new tier)APP_DEFERRED_TASK_ENABLED,APP_DEFERRED_TASK_BUDGET_SECONDSAPP_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.phpecommercen/core/Adv_front_controller.phpecommercen/core/Container.phpecommercen/core/models/Adv_base_model.phpecommercen/eshop/models/Adv_gifts_model.phpecommercen/eshop/models/Adv_product_category_model.phpecommercen/eshop/models/Adv_products_in_cart_model.phpecommercen/eshop/models/Adv_sliders_model.phpecommercen/libraries/AdvCartResource.phpecommercen/libraries/AdvCountriesCounties.phpecommercen/eshop/controllers/Adv_eshop.phpecommercen/eshop/controllers/Adv_home.phpecommercen/eshop/controllers/Adv_products.phpecommercen/eshop/controllers/Adv_product_categories.phpecommercen/eshop/controllers/Adv_product_categories_admin.phpecommercen/eshop/controllers/Adv_products_admin.phpecommercen/eshop/controllers/Adv_gifts_admin.phpecommercen/eshop/controllers/Adv_order.phpecommercen/eshop/controllers/Adv_customer.phpecommercen/checkout/controllers/Adv_checkout.phpecommercen/ai/libraries/AdvAdvisableAI.phpecommercen/seo/libraries/Adv_seo_lib.phpecommercen/job/libraries/AdvClearCacheOnLimit.phpecommercen/search/controllers/Adv_search.phpecommercen/settings/controllers/Adv_settings.phpecommercen/eshop/libraries/AdvTransportersRegistry.phpecommercen/api/controllers/AdvApiCartController.phpecommercen/helpers/debug_helper.phpecommercen/helpers/shopmodule_helper.phpapplication/libraries/Pscache.phpapplication/libraries/Advauth.phpapplication/libraries/ProjectAgoraFactory.phpapplication/config/hooks.phpapplication/config/cache.phpapplication/config/monolog.phpapplication/controllers/Healthz.php
[4.99.0] Check for overrides:
Gifts_admin::add
[4.99.0] Check for overrides:
src/Domains/Support/Repository/BaseRepository.phpsrc/Domains/Support/Repository/RelationLoader/AbstractRelationLoader.phpsrc/Domains/Support/Repository/RelationLoader/BelongsToLoader.phpsrc/Domains/Support/Repository/RelationLoader/HasOneLoader.phpsrc/Domains/Support/Repository/RelationLoader/ManyToManyLoader.phpsrc/Domains/Support/Repository/RelationLoader/OneToManyLoader.phpsrc/Domains/Support/Repository/RelationLoader/RelationContext.phpsrc/Domains/Support/Repository/RelationLoader/RelationLoaderInterface.phpsrc/Domains/Support/Repository/RelationLoader/RelationLoaderRegistry.php
[4.99.0] Check for overrides (REST auth separation):
src/Rest/Auth/Auth.php— deleted (replaced by AdminAuth + CustomerAuth)src/Rest/Auth/RestApiUser.php— deletedsrc/Rest/Auth/AdminAuth.php— newsrc/Rest/Auth/CustomerAuth.php— newsrc/Rest/Auth/BackendGuard.php— newsrc/Rest/Auth/FrontendGuard.php— newsrc/Rest/Auth/AnyGuard.php— newsrc/Rest/Support/Resources/ResourceContext.php— newsrc/Rest/Auth/Tokens.php— removeduidfrom JWT payloadsrc/Rest/Auth/RefreshTokenModel.php— addeduser_typeparametersrc/Rest/Auth/container.php— replaced Auth with AdminAuth + CustomerAuthsrc/Rest/Support/Controllers/HandlesRestfulActions.php— added ResourceContext wiringsrc/Rest/Support/Resources/BaseResource.php— added context propagationsrc/Rest/Support/Resources/BaseCollection.php— added context propagationapplication/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.phpfor tests needing CodeIgniter — extend this instead ofTestCasefor integration/legacy tests phpunit.xml.distis committed (shared defaults); createphpunit.xmllocally 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
- New base class
[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.phpsrc/Domains/Product/ProductList/Group/Repository/Entity.phpsrc/Domains/Product/ProductList/Group/Repository/Repository.phpsrc/Domains/Product/ProductList/Group/Repository/RepositoryConfigurator.phpsrc/Domains/Product/ProductList/Group/Service.phpsrc/Domains/Product/ProductList/ListRequest.phpsrc/Domains/Product/ProductList/ProductLp/ListRequest.phpsrc/Domains/Product/ProductList/ProductLp/Repository/Entity.phpsrc/Domains/Product/ProductList/ProductLp/Repository/Repository.phpsrc/Domains/Product/ProductList/ProductLp/Repository/RepositoryConfigurator.phpsrc/Domains/Product/ProductList/ProductLp/Service.phpsrc/Domains/Product/ProductList/Repository/Entity.phpsrc/Domains/Product/ProductList/Repository/MuiEntity.phpsrc/Domains/Product/ProductList/Repository/MuiRepository.phpsrc/Domains/Product/ProductList/Repository/Repository.phpsrc/Domains/Product/ProductList/Repository/RepositoryConfigurator.phpsrc/Domains/Product/ProductList/Service.phpsrc/Domains/Product/container.phpsrc/Rest/Product/Controllers/ProductList.phpsrc/Rest/Product/Resources/ProductList/Collection.phpsrc/Rest/Product/Resources/ProductList/Group/Collection.phpsrc/Rest/Product/Resources/ProductList/Group/Resource.phpsrc/Rest/Product/Resources/ProductList/MuiCollection.phpsrc/Rest/Product/Resources/ProductList/MuiResource.phpsrc/Rest/Product/Resources/ProductList/ProductLp/Collection.phpsrc/Rest/Product/Resources/ProductList/ProductLp/Resource.phpsrc/Rest/Product/Resources/ProductList/Resource.phpsrc/Rest/Product/container.php
[4.99.0] Check for overrides:
src/Domains/Product/ProductList/Repository/Specification/FilterByTranslation.phpsrc/Domains/Product/ProductList/Repository/Specification/SortByTranslation.phpsrc/Rest/Product/Controllers/ProductListGroup.phpsrc/Rest/Product/Controllers/ProductListProductLp.php
[4.99.0] Check for overrides:
src/Domains/Product/Variation/Group/ListRequest.phpsrc/Domains/Product/Variation/Group/Repository/Entity.phpsrc/Domains/Product/Variation/Group/Repository/MuiEntity.phpsrc/Domains/Product/Variation/Group/Repository/MuiRepository.phpsrc/Domains/Product/Variation/Group/Repository/Repository.phpsrc/Domains/Product/Variation/Group/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Variation/Group/Service.phpsrc/Domains/Product/Variation/ListRequest.phpsrc/Domains/Product/Variation/Repository/Entity.phpsrc/Domains/Product/Variation/Repository/MuiEntity.phpsrc/Domains/Product/Variation/Repository/MuiRepository.phpsrc/Domains/Product/Variation/Repository/Repository.phpsrc/Domains/Product/Variation/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Variation/Service.phpsrc/Domains/Product/Variation/Value/ListRequest.phpsrc/Domains/Product/Variation/Value/Repository/Entity.phpsrc/Domains/Product/Variation/Value/Repository/MuiEntity.phpsrc/Domains/Product/Variation/Value/Repository/MuiRepository.phpsrc/Domains/Product/Variation/Value/Repository/Repository.phpsrc/Domains/Product/Variation/Value/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Variation/Value/Service.phpsrc/Rest/Product/Controllers/Variation.phpsrc/Rest/Product/Controllers/VariationGroup.phpsrc/Rest/Product/Controllers/VariationValue.phpsrc/Rest/Product/Resources/Variation/Collection.phpsrc/Rest/Product/Resources/Variation/MuiCollection.phpsrc/Rest/Product/Resources/Variation/MuiResource.phpsrc/Rest/Product/Resources/Variation/Resource.phpsrc/Rest/Product/Resources/VariationGroup/Collection.phpsrc/Rest/Product/Resources/VariationGroup/MuiCollection.phpsrc/Rest/Product/Resources/VariationGroup/MuiResource.phpsrc/Rest/Product/Resources/VariationGroup/Resource.phpsrc/Rest/Product/Resources/VariationValue/Collection.phpsrc/Rest/Product/Resources/VariationValue/MuiCollection.phpsrc/Rest/Product/Resources/VariationValue/MuiResource.phpsrc/Rest/Product/Resources/VariationValue/Resource.php
[4.99.0] Check for overrides:
src/Domains/Product/Download/ListRequest.phpsrc/Domains/Product/Download/Repository/Entity.phpsrc/Domains/Product/Download/Repository/Repository.phpsrc/Domains/Product/Download/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Download/Service.phpsrc/Domains/Product/Review/ListRequest.phpsrc/Domains/Product/Review/Repository/Entity.phpsrc/Domains/Product/Review/Repository/Repository.phpsrc/Domains/Product/Review/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Review/Service.phpsrc/Rest/Product/Controllers/Download.phpsrc/Rest/Product/Controllers/Review.phpsrc/Rest/Product/Resources/Download/Collection.phpsrc/Rest/Product/Resources/Download/Resource.phpsrc/Rest/Product/Resources/Review/Collection.phpsrc/Rest/Product/Resources/Review/Resource.php
[4.99.0] Check for overrides:
src/Domains/Product/Related/Group/ListRequest.phpsrc/Domains/Product/Related/Group/Repository/Entity.phpsrc/Domains/Product/Related/Group/Repository/MuiEntity.phpsrc/Domains/Product/Related/Group/Repository/MuiRepository.phpsrc/Domains/Product/Related/Group/Repository/Repository.phpsrc/Domains/Product/Related/Group/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Related/Group/Service.phpsrc/Domains/Product/Related/ListRequest.phpsrc/Domains/Product/Related/Repository/Entity.phpsrc/Domains/Product/Related/Repository/Repository.phpsrc/Domains/Product/Related/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Related/Service.phpsrc/Rest/Product/Controllers/Related.phpsrc/Rest/Product/Controllers/RelatedGroup.phpsrc/Rest/Product/Resources/Related/Collection.phpsrc/Rest/Product/Resources/Related/Resource.phpsrc/Rest/Product/Resources/RelatedGroup/Collection.phpsrc/Rest/Product/Resources/RelatedGroup/MuiCollection.phpsrc/Rest/Product/Resources/RelatedGroup/MuiResource.phpsrc/Rest/Product/Resources/RelatedGroup/Resource.php
[4.99.0] Check for overrides:
src/Domains/Product/Bundle/BuilderReference/ListRequest.phpsrc/Domains/Product/Bundle/BuilderReference/Repository/Entity.phpsrc/Domains/Product/Bundle/BuilderReference/Repository/Repository.phpsrc/Domains/Product/Bundle/BuilderReference/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Bundle/BuilderReference/Service.phpsrc/Domains/Product/Bundle/Criteria/ListRequest.phpsrc/Domains/Product/Bundle/Criteria/Repository/Entity.phpsrc/Domains/Product/Bundle/Criteria/Repository/Repository.phpsrc/Domains/Product/Bundle/Criteria/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Bundle/Criteria/Service.phpsrc/Domains/Product/Bundle/CriteriaReference/ListRequest.phpsrc/Domains/Product/Bundle/CriteriaReference/Repository/Entity.phpsrc/Domains/Product/Bundle/CriteriaReference/Repository/Repository.phpsrc/Domains/Product/Bundle/CriteriaReference/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Bundle/CriteriaReference/Service.phpsrc/Domains/Product/Bundle/Display/ListRequest.phpsrc/Domains/Product/Bundle/Display/Repository/Entity.phpsrc/Domains/Product/Bundle/Display/Repository/Repository.phpsrc/Domains/Product/Bundle/Display/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Bundle/Display/Service.phpsrc/Domains/Product/Bundle/ListRequest.phpsrc/Domains/Product/Bundle/Pricing/ListRequest.phpsrc/Domains/Product/Bundle/Pricing/Repository/Entity.phpsrc/Domains/Product/Bundle/Pricing/Repository/Repository.phpsrc/Domains/Product/Bundle/Pricing/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Bundle/Pricing/Service.phpsrc/Domains/Product/Bundle/Repository/Entity.phpsrc/Domains/Product/Bundle/Repository/Repository.phpsrc/Domains/Product/Bundle/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Bundle/Service.phpsrc/Rest/Product/Controllers/Bundle.phpsrc/Rest/Product/Controllers/BundleBuilderReference.phpsrc/Rest/Product/Controllers/BundleCriteria.phpsrc/Rest/Product/Controllers/BundleCriteriaReference.phpsrc/Rest/Product/Controllers/BundleDisplay.phpsrc/Rest/Product/Controllers/BundlePricing.phpsrc/Rest/Product/Resources/Bundle/Collection.phpsrc/Rest/Product/Resources/Bundle/Resource.phpsrc/Rest/Product/Resources/BundleBuilderReference/Collection.phpsrc/Rest/Product/Resources/BundleBuilderReference/Resource.phpsrc/Rest/Product/Resources/BundleCriteria/Collection.phpsrc/Rest/Product/Resources/BundleCriteria/Resource.phpsrc/Rest/Product/Resources/BundleCriteriaReference/Collection.phpsrc/Rest/Product/Resources/BundleCriteriaReference/Resource.phpsrc/Rest/Product/Resources/BundleDisplay/Collection.phpsrc/Rest/Product/Resources/BundleDisplay/Resource.phpsrc/Rest/Product/Resources/BundlePricing/Collection.phpsrc/Rest/Product/Resources/BundlePricing/Resource.php
[4.99.0] Check for overrides:
src/Domains/Product/ProductMeta/ListRequest.phpsrc/Domains/Product/ProductMeta/Repository/Entity.phpsrc/Domains/Product/ProductMeta/Repository/Repository.phpsrc/Domains/Product/ProductMeta/Repository/RepositoryConfigurator.phpsrc/Domains/Product/ProductMeta/Service.phpsrc/Rest/Product/Controllers/ProductMeta.phpsrc/Rest/Product/Resources/ProductMeta/Collection.phpsrc/Rest/Product/Resources/ProductMeta/Resource.php
[4.99.0] Check for overrides:
src/Domains/Product/Product/Repository/RepositoryConfigurator.phpsrc/Rest/Product/Resources/Product/Resource.php
[4.99.0] Check for overrides:
src/Domains/Currency/Currency/ListRequest.phpsrc/Domains/Currency/Currency/Repository/Entity.phpsrc/Domains/Currency/Currency/Repository/Repository.phpsrc/Domains/Currency/Currency/Repository/RepositoryConfigurator.phpsrc/Domains/Currency/Currency/Service.phpsrc/Domains/Currency/container.phpsrc/Rest/Currency/Controllers/Currency.phpsrc/Rest/Currency/Resources/Currency/Collection.phpsrc/Rest/Currency/Resources/Currency/Resource.phpsrc/Rest/Currency/container.php
[4.99.0] Check for overrides:
src/Domains/Product/WaitingList/ListRequest.phpsrc/Domains/Product/WaitingList/Repository/Entity.phpsrc/Domains/Product/WaitingList/Repository/Repository.phpsrc/Domains/Product/WaitingList/Repository/RepositoryConfigurator.phpsrc/Domains/Product/WaitingList/Service.phpsrc/Domains/Product/Wishlist/ListRequest.phpsrc/Domains/Product/Wishlist/Repository/Entity.phpsrc/Domains/Product/Wishlist/Repository/Repository.phpsrc/Domains/Product/Wishlist/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Wishlist/Service.phpsrc/Rest/Product/Controllers/WaitingList.phpsrc/Rest/Product/Controllers/Wishlist.phpsrc/Rest/Product/Resources/WaitingList/Collection.phpsrc/Rest/Product/Resources/WaitingList/Resource.phpsrc/Rest/Product/Resources/Wishlist/Collection.phpsrc/Rest/Product/Resources/Wishlist/Resource.php
[4.99.0] Check for overrides:
src/Domains/Product/Shelfcode/ListRequest.phpsrc/Domains/Product/Shelfcode/Repository/Entity.phpsrc/Domains/Product/Shelfcode/Repository/Repository.phpsrc/Domains/Product/Shelfcode/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Shelfcode/Service.phpsrc/Rest/Product/Controllers/Product.phpsrc/Rest/Product/Controllers/Shelfcode.phpsrc/Rest/Product/Resources/Shelfcode/Collection.phpsrc/Rest/Product/Resources/Shelfcode/Resource.php
[4.99.0] Check for overrides:
src/Domains/Seo/DefaultMetaTag/ListRequest.phpsrc/Domains/Seo/DefaultMetaTag/Repository/Entity.phpsrc/Domains/Seo/DefaultMetaTag/Repository/Repository.phpsrc/Domains/Seo/DefaultMetaTag/Repository/RepositoryConfigurator.phpsrc/Domains/Seo/DefaultMetaTag/Service.phpsrc/Domains/Seo/Keyword/ListRequest.phpsrc/Domains/Seo/Keyword/Repository/Entity.phpsrc/Domains/Seo/Keyword/Repository/Repository.phpsrc/Domains/Seo/Keyword/Repository/RepositoryConfigurator.phpsrc/Domains/Seo/Keyword/Service.phpsrc/Domains/Seo/container.phpsrc/Rest/Seo/Controllers/DefaultMetaTag.phpsrc/Rest/Seo/Controllers/Keyword.phpsrc/Rest/Seo/Resources/DefaultMetaTag/Collection.phpsrc/Rest/Seo/Resources/DefaultMetaTag/Resource.phpsrc/Rest/Seo/Resources/Keyword/Collection.phpsrc/Rest/Seo/Resources/Keyword/Resource.phpsrc/Rest/Seo/container.php
[4.99.0] Check for overrides:
src/Domains/Event/Category/ListRequest.phpsrc/Domains/Event/Category/Repository/Entity.phpsrc/Domains/Event/Category/Repository/MuiEntity.phpsrc/Domains/Event/Category/Repository/MuiRepository.phpsrc/Domains/Event/Category/Repository/Repository.phpsrc/Domains/Event/Category/Repository/RepositoryConfigurator.phpsrc/Domains/Event/Category/Service.phpsrc/Domains/Event/Event/ListRequest.phpsrc/Domains/Event/Event/Repository/Entity.phpsrc/Domains/Event/Event/Repository/MuiEntity.phpsrc/Domains/Event/Event/Repository/MuiRepository.phpsrc/Domains/Event/Event/Repository/Repository.phpsrc/Domains/Event/Event/Repository/RepositoryConfigurator.phpsrc/Domains/Event/Event/Service.phpsrc/Domains/Event/container.phpsrc/Rest/Event/Controllers/Event.phpsrc/Rest/Event/Controllers/EventCategory.phpsrc/Rest/Event/Resources/Event/Collection.phpsrc/Rest/Event/Resources/Event/MuiCollection.phpsrc/Rest/Event/Resources/Event/MuiResource.phpsrc/Rest/Event/Resources/Event/Resource.phpsrc/Rest/Event/Resources/EventCategory/Collection.phpsrc/Rest/Event/Resources/EventCategory/MuiCollection.phpsrc/Rest/Event/Resources/EventCategory/MuiResource.phpsrc/Rest/Event/Resources/EventCategory/Resource.phpsrc/Rest/Event/container.php
[4.99.0] Check for overrides:
src/Domains/Cms/Offer/ListRequest.phpsrc/Domains/Cms/Offer/Repository/Entity.phpsrc/Domains/Cms/Offer/Repository/MuiEntity.phpsrc/Domains/Cms/Offer/Repository/MuiRepository.phpsrc/Domains/Cms/Offer/Repository/Repository.phpsrc/Domains/Cms/Offer/Repository/RepositoryConfigurator.phpsrc/Domains/Cms/Offer/Service.phpsrc/Domains/Cms/OfferCategory/ListRequest.phpsrc/Domains/Cms/OfferCategory/Repository/Entity.phpsrc/Domains/Cms/OfferCategory/Repository/MuiEntity.phpsrc/Domains/Cms/OfferCategory/Repository/MuiRepository.phpsrc/Domains/Cms/OfferCategory/Repository/Repository.phpsrc/Domains/Cms/OfferCategory/Repository/RepositoryConfigurator.phpsrc/Domains/Cms/OfferCategory/Service.phpsrc/Domains/Cms/container.phpsrc/Rest/Cms/Controllers/Offer.phpsrc/Rest/Cms/Controllers/OfferCategory.phpsrc/Rest/Cms/Resources/Offer/Collection.phpsrc/Rest/Cms/Resources/Offer/MuiCollection.phpsrc/Rest/Cms/Resources/Offer/MuiResource.phpsrc/Rest/Cms/Resources/Offer/Resource.phpsrc/Rest/Cms/Resources/OfferCategory/Collection.phpsrc/Rest/Cms/Resources/OfferCategory/MuiCollection.phpsrc/Rest/Cms/Resources/OfferCategory/MuiResource.phpsrc/Rest/Cms/Resources/OfferCategory/Resource.phpsrc/Rest/Cms/container.php
[4.99.0] Check for overrides:
src/Domains/Promotion/Gift/ListRequest.phpsrc/Domains/Promotion/Gift/Repository/Entity.phpsrc/Domains/Promotion/Gift/Repository/MuiEntity.phpsrc/Domains/Promotion/Gift/Repository/MuiRepository.phpsrc/Domains/Promotion/Gift/Repository/Repository.phpsrc/Domains/Promotion/Gift/Repository/RepositoryConfigurator.phpsrc/Domains/Promotion/Gift/Service.phpsrc/Domains/Promotion/GiftChoice/ListRequest.phpsrc/Domains/Promotion/GiftChoice/Repository/Entity.phpsrc/Domains/Promotion/GiftChoice/Repository/Repository.phpsrc/Domains/Promotion/GiftChoice/Repository/RepositoryConfigurator.phpsrc/Domains/Promotion/GiftChoice/Service.phpsrc/Domains/Promotion/GiftRequirement/ListRequest.phpsrc/Domains/Promotion/GiftRequirement/Repository/Entity.phpsrc/Domains/Promotion/GiftRequirement/Repository/Repository.phpsrc/Domains/Promotion/GiftRequirement/Repository/RepositoryConfigurator.phpsrc/Domains/Promotion/GiftRequirement/Service.phpsrc/Domains/Promotion/container.phpsrc/Rest/Promotion/Controllers/Gift.phpsrc/Rest/Promotion/Controllers/GiftChoice.phpsrc/Rest/Promotion/Controllers/GiftRequirement.phpsrc/Rest/Promotion/Resources/Gift/Collection.phpsrc/Rest/Promotion/Resources/Gift/MuiCollection.phpsrc/Rest/Promotion/Resources/Gift/MuiResource.phpsrc/Rest/Promotion/Resources/Gift/Resource.phpsrc/Rest/Promotion/Resources/GiftChoice/Collection.phpsrc/Rest/Promotion/Resources/GiftChoice/Resource.phpsrc/Rest/Promotion/Resources/GiftRequirement/Collection.phpsrc/Rest/Promotion/Resources/GiftRequirement/Resource.phpsrc/Rest/Promotion/container.php
[4.99.0] Check for overrides:
src/Domains/Promotion/Coupon/ListRequest.phpsrc/Domains/Promotion/Coupon/Repository/Entity.phpsrc/Domains/Promotion/Coupon/Repository/Repository.phpsrc/Domains/Promotion/Coupon/Repository/RepositoryConfigurator.phpsrc/Domains/Promotion/Coupon/Service.phpsrc/Domains/Promotion/CouponCode/ListRequest.phpsrc/Domains/Promotion/CouponCode/Repository/Entity.phpsrc/Domains/Promotion/CouponCode/Repository/Repository.phpsrc/Domains/Promotion/CouponCode/Repository/RepositoryConfigurator.phpsrc/Domains/Promotion/CouponCode/Service.phpsrc/Domains/Promotion/CouponProduct/ListRequest.phpsrc/Domains/Promotion/CouponProduct/Repository/Entity.phpsrc/Domains/Promotion/CouponProduct/Repository/Repository.phpsrc/Domains/Promotion/CouponProduct/Repository/RepositoryConfigurator.phpsrc/Domains/Promotion/CouponProduct/Service.phpsrc/Domains/Promotion/CouponRule/ListRequest.phpsrc/Domains/Promotion/CouponRule/Repository/Entity.phpsrc/Domains/Promotion/CouponRule/Repository/Repository.phpsrc/Domains/Promotion/CouponRule/Repository/RepositoryConfigurator.phpsrc/Domains/Promotion/CouponRule/Service.phpsrc/Domains/Promotion/CouponVendor/ListRequest.phpsrc/Domains/Promotion/CouponVendor/Repository/Entity.phpsrc/Domains/Promotion/CouponVendor/Repository/Repository.phpsrc/Domains/Promotion/CouponVendor/Repository/RepositoryConfigurator.phpsrc/Domains/Promotion/CouponVendor/Service.phpsrc/Rest/Promotion/Controllers/Coupon.phpsrc/Rest/Promotion/Controllers/CouponCode.phpsrc/Rest/Promotion/Controllers/CouponProduct.phpsrc/Rest/Promotion/Controllers/CouponRule.phpsrc/Rest/Promotion/Controllers/CouponVendor.phpsrc/Rest/Promotion/Resources/Coupon/Collection.phpsrc/Rest/Promotion/Resources/Coupon/Resource.phpsrc/Rest/Promotion/Resources/CouponCode/Collection.phpsrc/Rest/Promotion/Resources/CouponCode/Resource.phpsrc/Rest/Promotion/Resources/CouponProduct/Collection.phpsrc/Rest/Promotion/Resources/CouponProduct/Resource.phpsrc/Rest/Promotion/Resources/CouponRule/Collection.phpsrc/Rest/Promotion/Resources/CouponRule/Resource.phpsrc/Rest/Promotion/Resources/CouponVendor/Collection.phpsrc/Rest/Promotion/Resources/CouponVendor/Resource.php
[4.99.0] Check for overrides:
src/Domains/Order/GiftCardOrder/ListRequest.phpsrc/Domains/Order/GiftCardOrder/Repository/Entity.phpsrc/Domains/Order/GiftCardOrder/Repository/Repository.phpsrc/Domains/Order/GiftCardOrder/Repository/RepositoryConfigurator.phpsrc/Domains/Order/GiftCardOrder/Service.phpsrc/Domains/Order/container.phpsrc/Rest/Order/Controllers/GiftCardOrder.phpsrc/Rest/Order/Resources/GiftCardOrder/Collection.phpsrc/Rest/Order/Resources/GiftCardOrder/Resource.phpsrc/Rest/Order/container.php
[4.99.0] Check for overrides:
src/Domains/Transporter/CountyAvailability/ListRequest.phpsrc/Domains/Transporter/CountyAvailability/Repository/Entity.phpsrc/Domains/Transporter/CountyAvailability/Repository/Repository.phpsrc/Domains/Transporter/CountyAvailability/Repository/RepositoryConfigurator.phpsrc/Domains/Transporter/CountyAvailability/Service.phpsrc/Domains/Transporter/OptionPricing/ListRequest.phpsrc/Domains/Transporter/OptionPricing/Repository/Entity.phpsrc/Domains/Transporter/OptionPricing/Repository/Repository.phpsrc/Domains/Transporter/OptionPricing/Repository/RepositoryConfigurator.phpsrc/Domains/Transporter/OptionPricing/Service.phpsrc/Domains/Transporter/PostAvailability/ListRequest.phpsrc/Domains/Transporter/PostAvailability/Repository/Entity.phpsrc/Domains/Transporter/PostAvailability/Repository/Repository.phpsrc/Domains/Transporter/PostAvailability/Repository/RepositoryConfigurator.phpsrc/Domains/Transporter/PostAvailability/Service.phpsrc/Domains/Transporter/PostPricing/ListRequest.phpsrc/Domains/Transporter/PostPricing/Repository/Entity.phpsrc/Domains/Transporter/PostPricing/Repository/Repository.phpsrc/Domains/Transporter/PostPricing/Repository/RepositoryConfigurator.phpsrc/Domains/Transporter/PostPricing/Service.phpsrc/Domains/Transporter/Pricing/ListRequest.phpsrc/Domains/Transporter/Pricing/Repository/Entity.phpsrc/Domains/Transporter/Pricing/Repository/Repository.phpsrc/Domains/Transporter/Pricing/Repository/RepositoryConfigurator.phpsrc/Domains/Transporter/Pricing/Service.phpsrc/Domains/Transporter/PublicMapping/ListRequest.phpsrc/Domains/Transporter/PublicMapping/Repository/Entity.phpsrc/Domains/Transporter/PublicMapping/Repository/Repository.phpsrc/Domains/Transporter/PublicMapping/Repository/RepositoryConfigurator.phpsrc/Domains/Transporter/PublicMapping/Service.phpsrc/Domains/Transporter/Setting/ListRequest.phpsrc/Domains/Transporter/Setting/Repository/Entity.phpsrc/Domains/Transporter/Setting/Repository/Repository.phpsrc/Domains/Transporter/Setting/Repository/RepositoryConfigurator.phpsrc/Domains/Transporter/Setting/Service.phpsrc/Domains/Transporter/Transporter/ListRequest.phpsrc/Domains/Transporter/Transporter/Repository/Entity.phpsrc/Domains/Transporter/Transporter/Repository/MuiEntity.phpsrc/Domains/Transporter/Transporter/Repository/MuiRepository.phpsrc/Domains/Transporter/Transporter/Repository/Repository.phpsrc/Domains/Transporter/Transporter/Repository/RepositoryConfigurator.phpsrc/Domains/Transporter/Transporter/Service.phpsrc/Domains/Transporter/container.phpsrc/Rest/Transporter/Controllers/CountyAvailability.phpsrc/Rest/Transporter/Controllers/OptionPricing.phpsrc/Rest/Transporter/Controllers/PostAvailability.phpsrc/Rest/Transporter/Controllers/PostPricing.phpsrc/Rest/Transporter/Controllers/Pricing.phpsrc/Rest/Transporter/Controllers/PublicMapping.phpsrc/Rest/Transporter/Controllers/Setting.phpsrc/Rest/Transporter/Controllers/Transporter.phpsrc/Rest/Transporter/Resources/CountyAvailability/Collection.phpsrc/Rest/Transporter/Resources/CountyAvailability/Resource.phpsrc/Rest/Transporter/Resources/OptionPricing/Collection.phpsrc/Rest/Transporter/Resources/OptionPricing/Resource.phpsrc/Rest/Transporter/Resources/PostAvailability/Collection.phpsrc/Rest/Transporter/Resources/PostAvailability/Resource.phpsrc/Rest/Transporter/Resources/PostPricing/Collection.phpsrc/Rest/Transporter/Resources/PostPricing/Resource.phpsrc/Rest/Transporter/Resources/Pricing/Collection.phpsrc/Rest/Transporter/Resources/Pricing/Resource.phpsrc/Rest/Transporter/Resources/PublicMapping/Collection.phpsrc/Rest/Transporter/Resources/PublicMapping/Resource.phpsrc/Rest/Transporter/Resources/Setting/Collection.phpsrc/Rest/Transporter/Resources/Setting/Resource.phpsrc/Rest/Transporter/Resources/Transporter/Collection.phpsrc/Rest/Transporter/Resources/Transporter/MuiCollection.phpsrc/Rest/Transporter/Resources/Transporter/MuiResource.phpsrc/Rest/Transporter/Resources/Transporter/Resource.phpsrc/Rest/Transporter/container.php
[4.99.0] Check for overrides:
src/Domains/Product/CustomizationSchema/ListRequest.phpsrc/Domains/Product/CustomizationSchema/Repository/Entity.phpsrc/Domains/Product/CustomizationSchema/Repository/Repository.phpsrc/Domains/Product/CustomizationSchema/Repository/RepositoryConfigurator.phpsrc/Domains/Product/CustomizationSchema/Service.phpsrc/Rest/Product/Controllers/CustomizationSchema.phpsrc/Rest/Product/Resources/CustomizationSchema/Collection.phpsrc/Rest/Product/Resources/CustomizationSchema/Resource.php
[4.99.0] Check for overrides:
src/Domains/Order/Order/Repository/RepositoryConfigurator.phpsrc/Domains/Order/OrderTag/ListRequest.phpsrc/Domains/Order/OrderTag/Repository/Entity.phpsrc/Domains/Order/OrderTag/Repository/Repository.phpsrc/Domains/Order/OrderTag/Repository/RepositoryConfigurator.phpsrc/Domains/Order/OrderTag/Service.phpsrc/Rest/Order/Controllers/Order.phpsrc/Rest/Order/Controllers/OrderTag.phpsrc/Rest/Order/Resources/Order/Resource.phpsrc/Rest/Order/Resources/OrderTag/Collection.phpsrc/Rest/Order/Resources/OrderTag/Resource.php
[4.99.0] Check for overrides:
src/Domains/Cms/Video/ListRequest.phpsrc/Domains/Cms/Video/Repository/Entity.phpsrc/Domains/Cms/Video/Repository/MuiEntity.phpsrc/Domains/Cms/Video/Repository/MuiRepository.phpsrc/Domains/Cms/Video/Repository/Repository.phpsrc/Domains/Cms/Video/Repository/RepositoryConfigurator.phpsrc/Domains/Cms/Video/Repository/Specification/FilterByTranslation.phpsrc/Domains/Cms/Video/Repository/Specification/SortByTranslation.phpsrc/Domains/Cms/Video/Service.phpsrc/Domains/Product/Vendor/Repository/RepositoryConfigurator.phpsrc/Rest/Cms/Controllers/Video.phpsrc/Rest/Cms/Resources/Video/Collection.phpsrc/Rest/Cms/Resources/Video/MuiCollection.phpsrc/Rest/Cms/Resources/Video/MuiResource.phpsrc/Rest/Cms/Resources/Video/Resource.phpsrc/Rest/Product/Controllers/Vendor.phpsrc/Rest/Product/Resources/Vendor/Resource.php
[4.99.0] Check for overrides:
src/Domains/Plus/Audience/ListRequest.phpsrc/Domains/Plus/Audience/Repository/Entity.phpsrc/Domains/Plus/Audience/Repository/Repository.phpsrc/Domains/Plus/Audience/Repository/RepositoryConfigurator.phpsrc/Domains/Plus/Audience/Service.phpsrc/Domains/Plus/AudienceCriteria/ListRequest.phpsrc/Domains/Plus/AudienceCriteria/Repository/Entity.phpsrc/Domains/Plus/AudienceCriteria/Repository/Repository.phpsrc/Domains/Plus/AudienceCriteria/Repository/RepositoryConfigurator.phpsrc/Domains/Plus/AudienceCriteria/Service.phpsrc/Domains/Plus/container.phpsrc/Domains/Slider/Slide/Repository/RepositoryConfigurator.phpsrc/Rest/Plus/Controllers/Audience.phpsrc/Rest/Plus/Controllers/AudienceCriteria.phpsrc/Rest/Plus/Resources/Audience/Collection.phpsrc/Rest/Plus/Resources/Audience/Resource.phpsrc/Rest/Plus/Resources/AudienceCriteria/Collection.phpsrc/Rest/Plus/Resources/AudienceCriteria/Resource.phpsrc/Rest/Plus/container.phpsrc/Rest/Slider/Controllers/Slide.phpsrc/Rest/Slider/Resources/Slide/Resource.php
[4.99.0] Check for overrides:
src/Domains/Order/Customer/ListRequest.php src/Domains/Customer/Customer/ListRequest.phpsrc/Domains/Order/Customer/Repository/Entity.php src/Domains/Customer/Customer/Repository/Entity.phpsrc/Domains/Order/Customer/Repository/Repository.php src/Domains/Customer/Customer/Repository/Repository.phpsrc/Domains/Customer/Customer/Repository/RepositoryConfigurator.phpsrc/Domains/Order/Customer/Service.php src/Domains/Customer/Customer/Service.phpsrc/Domains/Customer/CustomerCampaign/ListRequest.phpsrc/Domains/Customer/CustomerCampaign/Repository/Entity.phpsrc/Domains/Customer/CustomerCampaign/Repository/Repository.phpsrc/Domains/Customer/CustomerCampaign/Repository/RepositoryConfigurator.phpsrc/Domains/Customer/CustomerCampaign/Service.phpsrc/Domains/Customer/CustomerMessageHistory/ListRequest.phpsrc/Domains/Customer/CustomerMessageHistory/Repository/Entity.phpsrc/Domains/Customer/CustomerMessageHistory/Repository/Repository.phpsrc/Domains/Customer/CustomerMessageHistory/Repository/RepositoryConfigurator.phpsrc/Domains/Customer/CustomerMessageHistory/Service.phpsrc/Domains/Customer/CustomerReview/ListRequest.phpsrc/Domains/Customer/CustomerReview/Repository/Entity.phpsrc/Domains/Customer/CustomerReview/Repository/Repository.phpsrc/Domains/Customer/CustomerReview/Repository/RepositoryConfigurator.phpsrc/Domains/Customer/CustomerReview/Service.phpsrc/Domains/Customer/CustomerSmsMarketing/ListRequest.phpsrc/Domains/Customer/CustomerSmsMarketing/Repository/Entity.phpsrc/Domains/Customer/CustomerSmsMarketing/Repository/Repository.phpsrc/Domains/Customer/CustomerSmsMarketing/Repository/RepositoryConfigurator.phpsrc/Domains/Customer/CustomerSmsMarketing/Service.phpsrc/Domains/Customer/CustomerTag/ListRequest.phpsrc/Domains/Customer/CustomerTag/Repository/Entity.phpsrc/Domains/Customer/CustomerTag/Repository/Repository.phpsrc/Domains/Order/Customer/Repository/RepositoryConfigurator.php src/Domains/Customer/CustomerTag/Repository/RepositoryConfigurator.phpsrc/Domains/Customer/CustomerTag/Service.phpsrc/Domains/Customer/container.phpsrc/Rest/Order/Controllers/Customer.php src/Rest/Customer/Controllers/Customer.phpsrc/Rest/Customer/Controllers/CustomerCampaign.phpsrc/Rest/Customer/Controllers/CustomerMessageHistory.phpsrc/Rest/Customer/Controllers/CustomerReview.phpsrc/Rest/Customer/Controllers/CustomerSmsMarketing.phpsrc/Rest/Customer/Controllers/CustomerTag.phpsrc/Rest/Order/Resources/Customer/Collection.php src/Rest/Customer/Resources/Customer/Collection.phpsrc/Rest/Order/Resources/Customer/Resource.php src/Rest/Customer/Resources/Customer/Resource.phpsrc/Rest/Customer/Resources/CustomerCampaign/Collection.phpsrc/Rest/Customer/Resources/CustomerCampaign/Resource.phpsrc/Rest/Customer/Resources/CustomerMessageHistory/Collection.phpsrc/Rest/Customer/Resources/CustomerMessageHistory/Resource.phpsrc/Rest/Customer/Resources/CustomerReview/Collection.phpsrc/Rest/Customer/Resources/CustomerReview/Resource.phpsrc/Rest/Customer/Resources/CustomerSmsMarketing/Collection.phpsrc/Rest/Customer/Resources/CustomerSmsMarketing/Resource.phpsrc/Rest/Customer/Resources/CustomerTag/Collection.phpsrc/Rest/Customer/Resources/CustomerTag/Resource.phpsrc/Rest/Customer/container.php
[4.99.0] Check for overrides:
src/Domains/Cms/Blog/Article/Repository/RepositoryConfigurator.phpsrc/Domains/Product/Category/Repository/RepositoryConfigurator.phpsrc/Rest/Cms/Resources/Blog/Article/Resource.phpsrc/Rest/Product/Controllers/Category.phpsrc/Rest/Product/Resources/Category/Resource.php
[4.99.0] Check for overrides:
src/Domains/Cms/ContactEmail/ListRequest.phpsrc/Domains/Cms/ContactEmail/Repository/Entity.phpsrc/Domains/Cms/ContactEmail/Repository/Repository.phpsrc/Domains/Cms/ContactEmail/Repository/RepositoryConfigurator.phpsrc/Domains/Cms/ContactEmail/Service.phpsrc/Rest/Cms/Controllers/ContactEmail.phpsrc/Rest/Cms/Resources/ContactEmail/Collection.phpsrc/Rest/Cms/Resources/ContactEmail/Resource.php
[4.99.0] Check for overrides:
src/Domains/Cms/Cookie/ListRequest.phpsrc/Domains/Cms/Cookie/Repository/Entity.phpsrc/Domains/Cms/Cookie/Repository/Repository.phpsrc/Domains/Cms/Cookie/Repository/RepositoryConfigurator.phpsrc/Domains/Cms/Cookie/Service.phpsrc/Rest/Cms/Controllers/Cookie.phpsrc/Rest/Cms/Resources/Cookie/Collection.phpsrc/Rest/Cms/Resources/Cookie/Resource.php
[4.99.0] Check for overrides:
src/Domains/Product/PriceTracking/ListRequest.phpsrc/Domains/Product/PriceTracking/Repository/Entity.phpsrc/Domains/Product/PriceTracking/Repository/Repository.phpsrc/Domains/Product/PriceTracking/Repository/RepositoryConfigurator.phpsrc/Domains/Product/PriceTracking/Service.phpsrc/Rest/Product/Controllers/PriceTracking.phpsrc/Rest/Product/Resources/PriceTracking/Collection.phpsrc/Rest/Product/Resources/PriceTracking/Resource.php
[4.99.0] Check for overrides:
src/Domains/Order/Note/ListRequest.phpsrc/Domains/Order/Note/Repository/Entity.phpsrc/Domains/Order/Note/Repository/Repository.phpsrc/Domains/Order/Note/Service.phpsrc/Rest/Order/Controllers/Note.phpsrc/Rest/Order/Resources/Note/Collection.phpsrc/Rest/Order/Resources/Note/Resource.php
[4.99.0] Check for overrides:
src/Domains/Marketplace/Iris/Order/ListRequest.phpsrc/Domains/Marketplace/Iris/Order/Repository/Entity.phpsrc/Domains/Marketplace/Iris/Order/Repository/Repository.phpsrc/Domains/Marketplace/Iris/Order/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Iris/Order/Service.phpsrc/Domains/Marketplace/Jcc/Order/ListRequest.phpsrc/Domains/Marketplace/Jcc/Order/Repository/Entity.phpsrc/Domains/Marketplace/Jcc/Order/Repository/Repository.phpsrc/Domains/Marketplace/Jcc/Order/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Jcc/Order/Service.phpsrc/Domains/Marketplace/Public/LineItem/ListRequest.phpsrc/Domains/Marketplace/Public/LineItem/Repository/Entity.phpsrc/Domains/Marketplace/Public/LineItem/Repository/Repository.phpsrc/Domains/Marketplace/Public/LineItem/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Public/LineItem/Service.phpsrc/Domains/Marketplace/Public/Order/ListRequest.phpsrc/Domains/Marketplace/Public/Order/Repository/Entity.phpsrc/Domains/Marketplace/Public/Order/Repository/Repository.phpsrc/Domains/Marketplace/Public/Order/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Public/Order/Service.phpsrc/Domains/Marketplace/Shopflix/LineItem/ListRequest.phpsrc/Domains/Marketplace/Shopflix/LineItem/Repository/Entity.phpsrc/Domains/Marketplace/Shopflix/LineItem/Repository/Repository.phpsrc/Domains/Marketplace/Shopflix/LineItem/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Shopflix/LineItem/Service.phpsrc/Domains/Marketplace/Shopflix/Order/ListRequest.phpsrc/Domains/Marketplace/Shopflix/Order/Repository/Entity.phpsrc/Domains/Marketplace/Shopflix/Order/Repository/Repository.phpsrc/Domains/Marketplace/Shopflix/Order/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Shopflix/Order/Service.phpsrc/Domains/Marketplace/Skroutz/InvoiceDetail/ListRequest.phpsrc/Domains/Marketplace/Skroutz/InvoiceDetail/Repository/Entity.phpsrc/Domains/Marketplace/Skroutz/InvoiceDetail/Repository/Repository.phpsrc/Domains/Marketplace/Skroutz/InvoiceDetail/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Skroutz/InvoiceDetail/Service.phpsrc/Domains/Marketplace/Skroutz/LineItem/ListRequest.phpsrc/Domains/Marketplace/Skroutz/LineItem/Repository/Entity.phpsrc/Domains/Marketplace/Skroutz/LineItem/Repository/Repository.phpsrc/Domains/Marketplace/Skroutz/LineItem/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Skroutz/LineItem/Service.phpsrc/Domains/Marketplace/Skroutz/Order/ListRequest.phpsrc/Domains/Marketplace/Skroutz/Order/Repository/Entity.phpsrc/Domains/Marketplace/Skroutz/Order/Repository/Repository.phpsrc/Domains/Marketplace/Skroutz/Order/Repository/RepositoryConfigurator.phpsrc/Domains/Marketplace/Skroutz/Order/Service.phpsrc/Domains/Marketplace/container.phpsrc/Rest/Marketplace/Controllers/IrisOrder.phpsrc/Rest/Marketplace/Controllers/JccOrder.phpsrc/Rest/Marketplace/Controllers/PublicLineItem.phpsrc/Rest/Marketplace/Controllers/PublicOrder.phpsrc/Rest/Marketplace/Controllers/ShopflixLineItem.phpsrc/Rest/Marketplace/Controllers/ShopflixOrder.phpsrc/Rest/Marketplace/Controllers/SkroutzInvoiceDetail.phpsrc/Rest/Marketplace/Controllers/SkroutzLineItem.phpsrc/Rest/Marketplace/Controllers/SkroutzOrder.phpsrc/Rest/Marketplace/Resources/IrisOrder/Collection.phpsrc/Rest/Marketplace/Resources/IrisOrder/Resource.phpsrc/Rest/Marketplace/Resources/JccOrder/Collection.phpsrc/Rest/Marketplace/Resources/JccOrder/Resource.phpsrc/Rest/Marketplace/Resources/PublicLineItem/Collection.phpsrc/Rest/Marketplace/Resources/PublicLineItem/Resource.phpsrc/Rest/Marketplace/Resources/PublicOrder/Collection.phpsrc/Rest/Marketplace/Resources/PublicOrder/Resource.phpsrc/Rest/Marketplace/Resources/ShopflixLineItem/Collection.phpsrc/Rest/Marketplace/Resources/ShopflixLineItem/Resource.phpsrc/Rest/Marketplace/Resources/ShopflixOrder/Collection.phpsrc/Rest/Marketplace/Resources/ShopflixOrder/Resource.phpsrc/Rest/Marketplace/Resources/SkroutzInvoiceDetail/Collection.phpsrc/Rest/Marketplace/Resources/SkroutzInvoiceDetail/Resource.phpsrc/Rest/Marketplace/Resources/SkroutzLineItem/Collection.phpsrc/Rest/Marketplace/Resources/SkroutzLineItem/Resource.phpsrc/Rest/Marketplace/Resources/SkroutzOrder/Collection.phpsrc/Rest/Marketplace/Resources/SkroutzOrder/Resource.phpsrc/Rest/Marketplace/container.php
[4.99.0] Check for overrides:
src/Domains/Order/DhlVoucher/ListRequest.phpsrc/Domains/Order/DhlVoucher/Repository/Entity.phpsrc/Domains/Order/DhlVoucher/Repository/Repository.phpsrc/Domains/Order/DhlVoucher/Repository/RepositoryConfigurator.phpsrc/Domains/Order/DhlVoucher/Service.phpsrc/Domains/Order/SmartPoint/ListRequest.phpsrc/Domains/Order/SmartPoint/Repository/Entity.phpsrc/Domains/Order/SmartPoint/Repository/Repository.phpsrc/Domains/Order/SmartPoint/Repository/RepositoryConfigurator.phpsrc/Domains/Order/SmartPoint/Service.phpsrc/Rest/Order/Controllers/DhlVoucher.phpsrc/Rest/Order/Controllers/SmartPoint.phpsrc/Rest/Order/Resources/DhlVoucher/Collection.phpsrc/Rest/Order/Resources/DhlVoucher/Resource.phpsrc/Rest/Order/Resources/SmartPoint/Collection.phpsrc/Rest/Order/Resources/SmartPoint/Resource.php
[4.99.0] Check for overrides:
src/Domains/Ai/ContentGeneration/ListRequest.phpsrc/Domains/Ai/ContentGeneration/Repository/Entity.phpsrc/Domains/Ai/ContentGeneration/Repository/Repository.phpsrc/Domains/Ai/ContentGeneration/Repository/RepositoryConfigurator.phpsrc/Domains/Ai/ContentGeneration/Service.phpsrc/Domains/Ai/Position/ListRequest.phpsrc/Domains/Ai/Position/Repository/Entity.phpsrc/Domains/Ai/Position/Repository/MuiEntity.phpsrc/Domains/Ai/Position/Repository/MuiRepository.phpsrc/Domains/Ai/Position/Repository/Repository.phpsrc/Domains/Ai/Position/Repository/RepositoryConfigurator.phpsrc/Domains/Ai/Position/Repository/Specification/FilterByTranslation.phpsrc/Domains/Ai/Position/Repository/Specification/SortByTranslation.phpsrc/Domains/Ai/Position/Service.phpsrc/Domains/Ai/container.phpsrc/Rest/Ai/Controllers/ContentGeneration.phpsrc/Rest/Ai/Controllers/Position.phpsrc/Rest/Ai/Resources/ContentGeneration/Collection.phpsrc/Rest/Ai/Resources/ContentGeneration/Resource.phpsrc/Rest/Ai/Resources/Position/Collection.phpsrc/Rest/Ai/Resources/Position/MuiCollection.phpsrc/Rest/Ai/Resources/Position/MuiResource.phpsrc/Rest/Ai/Resources/Position/Resource.phpsrc/Rest/Ai/container.php
[4.99.0] Check for overrides:
src/Rest/Product/Resources/Line/Resource.phpsrc/Rest/Product/Resources/Promo/Resource.phpsrc/Rest/Product/Resources/Supplier/Resource.phpsrc/Rest/Product/Resources/Tag/Resource.phpsrc/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::getFirstLevelChildrenCategoriesProduct_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_sluginstead ofProduct_model::get_parent_slug[4.99.0] Check for overrides:
Gift_choices_model::getOptionsWithActiveStockForGiftIdsVendors::vendors
[4.99.0] Gift rule 13 (cheapest free) — check for overrides:
ecommercen/eshop/models/Adv_gifts_model.php— new methodsruleProductsCheapestFreeValidator,getCheapestRequirementProductInCart; updatedgetGiftForProductsInCartswitch andgetActiveGiftRulesfilterecommercen/eshop/models/Adv_gift_rules_model.php— new rule 13 entry in$dataecommercen/eshop/models/Adv_order_model.php— new methodsgetRule13GiftAdjustments,getRule13GiftAdjustmentsForProducts,adjustCartForRule13Gifts; updatedbaseParseCartContentsandprocessOrderecommercen/eshop/models/Adv_order_model.php— in methodcreate_order_adminwe change theif ($otherPostElements['transport_id'])withif (!empty($otherPostElements['transport_id']))for the case that we do not have transportersecommercen/eshop/controllers/Adv_orders_admin.php— newgetRule13GiftAdjustmentsForProductsusage ingifts_for_productsandcreateAdminFakeCartecommercen/libraries/AdvCartResource.php— newadjustCartContentsForRule13methodassets/admin/js/order-gifts-block.js— rule 13 auto-assign handling in Vue Vuex getter andreapplyRule13Deductions- See Gifts Module Guide for full documentation
[4.99.0] Need
npm run admin-production