diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a30547..906d12d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,9 @@ ## Навигация -- **Текущий месяц:** [Июнь 2026](#июнь-2026) (ниже) -- **Предыдущий месяц:** [Май 2026](#май-2026) (ниже) -- **Ещё раньше:** [Апрель 2026](#апрель-2026), [Март 2026](#март-2026), [Февраль 2026](#февраль-2026), [Январь 2026](#январь-2026) (ниже) +- **Текущий месяц:** [Август 2026](#август-2026) (ниже) +- **Предыдущий месяц:** [Июнь 2026](#июнь-2026) (ниже) +- **Ещё раньше:** [Май 2026](#май-2026), [Апрель 2026](#апрель-2026), [Март 2026](#март-2026), [Февраль 2026](#февраль-2026), [Январь 2026](#январь-2026) (ниже) - **Архив по месяцам:** - [Декабрь 2025](changelogs/2025-12.md) - [Ноябрь 2025](changelogs/2025-11.md) @@ -15,6 +15,85 @@ --- +## Август 2026 + +### [2026-08-13] 🚀 Версия 1.13.0-beta1 + +**Тип релиза:** MINOR (beta) — публичный Web API для headless-витрины и кабинета клиента, миграция менеджера на Ext-less Vue, масштабная security-закалка API и декомпозиция сервисов. + +#### ✨ Добавлено + +**Публичный Web API каталога товаров (#333):** headless-эндпойнты `GET /api/v1/product/list` и `GET /api/v1/product/get/{id}` работают **без авторизации** (без `TokenMiddleware`) — витрина на любом фронте может тянуть каталог напрямую. Новый `ProductCatalogService` + `Web\ProductController`, покрыт `ProductCatalogServiceTest`. + +**Кабинет клиента через Web API — история заказов, logout и восстановление пароля (#425, #442, #475):** + +- **Заказы клиента.** `GET /api/v1/customer/orders`, `GET /api/v1/customer/orders/{id}` и `POST /api/v1/customer/orders/{id}/cancel` — список и карточка заказа, отфильтрованные по `customer_id`; из публичных DTO вырезаны `token`/`properties`. +- **Аутентификация.** Новый `CustomerAuthController` (login / register / logout / forgotPassword / resetPassword) с отдельными роутами. `logout` проходит через `TokenMiddleware` (гидрация сессии из `MS3TOKEN`/cookie перед отзывом токенов). `forgotPassword` — rate-limit по IP и email, enumeration-safe (одинаковый ответ независимо от существования email), выпускает `password_reset`-токен с TTL. `resetPassword` — обязательный токен, rate-limit по IP и токену, `HTTP 429` при превышении. +- **Единый публичный контракт клиента.** `CustomerPublicDto` — allowlist полей ответа (`PUBLIC_FIELDS` + активные `msExtraField`), никогда не отдаёт `token`/`password`; `editableFieldKeys()` = базовые редактируемые поля + динамические extra-колонки. `POST /customer/add` и `PUT /customer/profile` принимают только allowlist (fail-closed `field_not_allowed`). **Breaking:** ответы Web API по клиенту теперь в форме DTO, add/update профиля закрыты allowlist'ом. + +**Программное создание заказов без сессии (#508):** сервис `ProgrammaticOrderService` (`ms3_programmatic_order`) позволяет дополнениям и cron создавать финализированный `msOrder` **без корзины и сессии** — через тот же pipeline расчёта стоимости / номера / статуса, с уникальным `idempotency_key` и событиями с `origin=integration`. + +**Миграция менеджера на Ext-less Vue (#533, #534, #536, #537):** страницы «Заказы»/«Заказ» (#533), «Клиенты»/«Уведомления» (#534), «Настройки» (#536) и «Утилиты» (#537) переведены на Help-style tpl + чистый `ms3.config` — убраны Ext-обёртки и `waitForElement`, каждая страница монтируется одним Vite-entry. Deep-link'и сохранены (`order_id` через GET, `#tab-*` хэши, localStorage-состояние вкладок). + +**Vue-вкладки в карточке товара — «Категории» и «Связи» (#479/#113, #518):** + +- **Категории (#113).** Замена ExtJS `ms3-tree-categories` на Vue-вкладку поверх переиспользуемого виджета `ResourceCategoryTree` и REST tree-эндпойнта. Новый `ProductCategoryTreeService` (`ms3_product_category_tree`), общие утилиты `IntArrayDecoder` и `ResourceCategoryTreeQueryTrait`. Чекбоксы следуют выбору клиента, а не устаревшим `msCategoryMember`-строкам. +- **Связи (#518).** CRUD связей товара перенесён в `ProductLinkService` + Manager REST, ExtJS-грид/окно связей удалены. `ProductTabs` перешёл с `v-else-if`-цепочки на map компонентов. + +**Тип extra field «ключ-значение» — `ms3-key-value` (#323):** настраиваемые key-value поля с режимами fixed и free, бэкенд-валидация через `KeyValueFieldService`, Vue-редакторы для форм товара/заказа и для определения extra field. Гидрация и обработка выровнены с паттерном repeater (#301). + +**Галерея — выбор главного превью без смены порядка (#512):** новое поле `preview_file_id` на `msProductData` позволяет назначить любое фото галереи главным превью, сохранив порядок сортировки самой галереи. + +**Импорт CSV — колонки `msExtraField` (#510):** активные extra fields подмешиваются в список полей импорта и сохраняются через `Product\Create|Update` после `loadMap()` — Object Extension-колонки больше не теряются при маппинге и сохранении. + +**События EmptyOrder / GetOrderCost и агрегация ошибок `Order::set` (#393):** на очистке черновика вызываются зарегистрированные `msOnBeforeEmptyOrder`/`msOnEmptyOrder`, на `getTotalCost` — `msOnBeforeGetOrderCost`/`msOnGetOrderCost`. `Order::set()` агрегирует пофайловые ошибки `add()`. + +**Rate limiting — подключаемые хранилища счётчиков (#487):** `RateLimitMiddleware` больше не пишет напрямую в `sys_get_temp_dir()`. Хранилище за `RateLimitStoreInterface`: файловый драйвер по умолчанию (поведение одного узла не меняется), опциональные Redis/Memcached через настройку/env (нужно только PHP-расширение, без обязательной composer-зависимости). Graceful fallback на файловый store при отсутствии расширения или сбое подключения (лог WARN). + +**Document-level ACL для грида товаров категории (#473):** `checkPolicy` view/save/publish/delete на эндпойнтах `CategoryProductsController` в дополнение к глобальным `msproduct_*` правам; корректный подсчёт ACL-видимого total для пагинации и `403` при запрете bulk-действий политикой документа. + +**Инфраструктура тестов и CI-гейты (#394, #433, #495, #496):** PHP lint/smoke + ESLint-гейт на PR (#394), PHPStan-гейт + PHPUnit-скаффолд для чистых хелперов (#433), интеграционные тесты (MySQL CI + жизненный цикл `AuthManager` #496, finalize required fields + cart SQLite draft #495). Лексикон для сообщений `CategoryProductsController` (#467); паритет EN/RU и удаление дублей ключей (#360). + +#### 🔄 Изменено + +**Декомпозиция `OrdersController` + фабричная карта `ServiceRegistry` (#492):** list/mutation/products/presenter вынесены в отдельные сервисы (контроллер < 1k строк), менеджерские order-сервисы зарегистрированы в DI. Switch/`in_array`-разводка `ServiceRegistry` заменена явной картой `ServiceRegistryFactories`. Проверки пары доставка/оплата (#459) перенесены в `ManagerOrderMutationService`. + +**Разрез крупных сервисов — фаза #365 (#513, #514, #515):** `GridConfigService` (Phase A), `OptionLoaderService` (Phase B) и `ProductDataService` (Phase C) разбиты на узкие сервисы. Дополнительно вынесены `ExtraFieldsController` из CRUD extra fields (#491) и `ModelField`-сервисы из `ModelFieldsController` (#517). + +**Единый расчёт стоимости заказа checkout ↔ manager (#476):** общий `OrderCostEngine` для математики доставки/оплаты, база процентной комиссии оплаты выровнена по cart-only для паритета с MS2 (#460). + +**Единый Response envelope на HTTP-границе (#505):** унифицированная обёртка ответа API. Декомпозиция `ImportCSV` на `EventGate` + `ProductImportService` (#499); `EventGate` как контракт `returnedValues` (#456). + +**Тонкие фасады и Vue-композаблы:** фасады `Cart`/`Customer` поверх Services-менеджеров (#406), разрез `OrderView.vue` на композаблы и диалоги (#478), вынос тонких list/config/crud/sort-композаблов (#397). + +**Общий reference CRUD и консолидация combo-списков (#509, #490):** shared reference CRUD для доставок и оплат (#509); `SettingsComboListService` шарится между ExtJS combo GetList и Manager REST, удалены дублирующие Delivery↔Payment membership-процессоры (#490). + +**Замена заброшенного `rakit/validation` (#486)** и удаление мёртвого кода: deprecated pass-through `OrderService`/`Customer` (#489), мёртвые field-config override-эндпойнты (#383), `ms3_config_manager` (#485). + +**Производительность:** раздельный stats-эндпойнт с условным Address JOIN (#469), параллельная загрузка values опций в `OrderView` (#447), request-scoped мемоизация `getGridConfig` (#488). + +#### 🐛 Исправлено + +**Security/ACL-закалка Web API — сессии и токены:** ротация API-токена против token fixation (#516); закрытие обхода сессии после отзыва токена + харденинг web-logout (#462); безопасный CORS — без wildcard-with-credentials, same-origin по умолчанию (#463); корректные `HTTP 401/429` для auth и rate-limit (#434); авто-выпуск гостевого токена для `cart/get` + синхронизация `publicRoutes` (#440); привязка API-сессии на верификации email (#441); удаление false-success token/refresh-заглушки (#351); отказ по истёкшим API-токенам вместо тихого продления (#396); закрытие захвата чужого checkout-токена через email (#369/#391); восстановление storefront-логина и сессионной авторизации (#321). + +**Security/ACL-закалка Manager API:** права `msorder_save` на write-роуты заказов (#402), `mssetting_save` на запись config/grid-config (#404, #436), ужесточение ACL клиентов и товаров категории (#400), `view_document` на `/references` (PII клиента, #438); скоупинг inline-edit/мутаций товаров категории по категории — IDOR (#443, #454); whitelist `filter_*` на list-эндпойнтах (#457), полей deliveries-active dropdown (#427), критериев `msProductOption` (#452); запрет `privacy_accepted_at` в quick-update (#439); прекращение утечки секретного токена в ответах заказа (#426) и секретов клиента в ответах profile/add (#428); экранирование амперсандов в href ссылки на заказ в письме (#465); прекращение загрузки storefront-роутов через менеджерский коннектор (#401). + +**Расчёт и оформление заказа:** отклонение несовместимых пар доставка/оплата на submit/checkout/manager/finalize (#459); блокировка сохранения во время пересчёта стоимости (#461); ревалидация фиксированного статуса после мутации плагином (#464); выравнивание finalize-стоимости с `ManagerOrderCostRecalculator` (#448); cart-only база процентной комиссии оплаты (#460); `payment_link` в письмах о заказе/смене статуса (#458); возврат ошибки при неудачном сохранении статуса (#392); вес черновика = вес единицы × количество (#403); защита от дублей номеров при конкурентном submit (#399); локальный календарный день в фильтрах по дате (#405). + +**Товары, каталог, корзина:** учёт дополнительных категорий (`msCategoryMember`) в `msProducts` и гриде категории (#482); резолв relation-полей в гриде товаров категории (#330); сохранение очистки color/size/tags при сохранении (#325); смена опций в корзине через Web API и storefront (#502); конверсия типа ресурса Документ → Товар (#331). + +**Vue-регрессии позднего цикла:** двойной confirm в гриде товаров категории (#538/#540); гонка `HTTP_MODAUTH` на Ext-less Vue-страницах — токен в `ms3.config` (#544/#545); бесконечный рекурсивный цикл в `ResourceCategoryTree`, замораживавший Настройки→Опции (#546/#547); дублирующиеся confirm-диалоги на общей шине PrimeVue между вкладками Настроек (#548/#549) и лишний confirm удаления в `VendorsGrid` (#551/#552); устаревшие ответы грида при быстрой смене фильтра/страницы (#468); распаковка пустого object/data в коннекторе `request.js` (#466); прекращение загрузки отсутствующего `vue-dist/main.min.css` на обновлении товара (#504). + +**Валюта, установка, импорт:** ремонт mojibake-дефолта «?» для `ms3_currency_symbol` (#498) и inline-символ рубля в repair-миграции (#520); тосты и пошаговые ошибки при сбоях полей/превью импорта (#453); чтение start-ответа из object-envelope (#390). + +**DI:** подключение `OptionCategoryService` в `ms3_option_service` (#535); резолв платёжной ссылки через `PaymentService` (#485). + +#### 📦 Зависимости + +Security-бампы транзитивных пакетов `vueManager` (js-yaml, fast-uri, happy-dom, vite 6.4.3) — #483, #506. + +--- + ## Июнь 2026 ### [2026-06-22] 🚀 Версия 1.12.0-beta1 diff --git a/_build/config.inc.php b/_build/config.inc.php index 917c4c33..dfd12cb4 100644 --- a/_build/config.inc.php +++ b/_build/config.inc.php @@ -12,7 +12,7 @@ 'name' => 'MiniShop3', 'name_lower' => 'minishop3', 'name_short' => 'ms3', - 'version' => '1.12.0', + 'version' => '1.13.0', 'release' => 'beta1', // Install package to site right after build 'install' => false, diff --git a/core/components/minishop3/docs/changelog.txt b/core/components/minishop3/docs/changelog.txt index 9d2dbdd8..f2cf55b4 100644 --- a/core/components/minishop3/docs/changelog.txt +++ b/core/components/minishop3/docs/changelog.txt @@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.13.0-beta1] + +### Added +- Public product catalog Web API (#333): `GET /api/v1/product/list` and `GET /api/v1/product/get/{id}`, no authentication (headless storefront). New `ProductCatalogService` + `Web\ProductController`. +- Customer cabinet Web API: order history `GET /api/v1/customer/orders`, `GET /api/v1/customer/orders/{id}`, `POST /api/v1/customer/orders/{id}/cancel`, scoped by `customer_id` with `token`/`properties` stripped from public DTOs (#425). New `CustomerAuthController` with logout, forgot-password and reset-password routes — enumeration-safe, rate-limited per IP/email/token, HTTP 429 on limit (#442). +- Unified public customer contract `CustomerPublicDto` with an editable-field allowlist (never exposes `token`/`password`); `POST /customer/add` and `PUT /customer/profile` are fail-closed (`field_not_allowed`). Breaking: Web API customer responses now use the DTO shape (#475). +- Sessionless programmatic order creation: `ProgrammaticOrderService` (`ms3_programmatic_order`) lets add-ons and cron create a finalized `msOrder` without cart/session, with `idempotency_key` and `origin=integration` events through the existing finalize pipeline (#508). +- Ext-less Vue manager pages: Orders/Order (#533), Customers/Notifications (#534), Settings (#536), Utilities (#537) now mount from a single Vite entry via a Help-style controller — no Ext wrappers or `waitForElement`; deep-links and tab state preserved. +- Product Vue tabs: Categories tab replaces the ExtJS category tree with a reusable `ResourceCategoryTree` widget + REST tree endpoint (#479, closes #113); Links tab moves link CRUD to `ProductLinkService` + Manager REST (#518). +- New extra field type `ms3-key-value` with fixed and free modes, `KeyValueFieldService` validation and Vue editors for manager forms and field definitions (#323). +- Gallery: choose any image as the main preview without reordering the gallery — `preview_file_id` on `msProductData` (#512). +- CSV import now maps and persists `msExtraField` (Object Extension) columns via `Product\Create|Update` after `loadMap()` (#510). +- Order events `msOnBeforeEmptyOrder` / `msOnEmptyOrder` and `msOnBeforeGetOrderCost` / `msOnGetOrderCost`; `Order::set()` aggregates per-field validation errors (#393). +- Rate limiting: pluggable storage behind `RateLimitStoreInterface` — file driver by default, optional Redis/Memcached via setting/env, graceful fallback to file on missing extension or connection failure (#487). +- Document-level ACL (view/save/publish/delete `checkPolicy`) on category-products endpoints, plus ACL-visible pagination totals and 403 on denied bulk actions (#473). +- Test infrastructure and CI gates: PHP lint/smoke + ESLint gate on PR (#394), PHPStan gate + PHPUnit scaffold (#433), MySQL integration CI and `AuthManager` lifecycle / finalize / cart draft tests (#495, #496). Lexicon for `CategoryProductsController` messages (#467); EN/RU parity and duplicate-key cleanup (#360). + +### Changed +- Decomposed manager `OrdersController` into list/mutation/products/presenter services (controller < 1k LOC) and replaced the `ServiceRegistry` switch/`in_array` wiring with an explicit `ServiceRegistryFactories` map (#492). +- Split large services (issue #365): `GridConfigService` (Phase A #513), `OptionLoaderService` (Phase B #514), `ProductDataService` (Phase C #515). Extra-fields CRUD extracted into `ExtraFieldsController` (#491); `ModelFieldsController` split into `ModelField` services (#517). +- Unified checkout ↔ manager order-cost math in a shared `OrderCostEngine`; payment percent-commission base aligned to cart-only for MS2 parity (#476, #460). +- Unified Response envelope on the HTTP boundary (#505). `ImportCSV` decomposed into `EventGate` + `ProductImportService` (#499); `EventGate` formalizes the `returnedValues` contract (#456). +- Thin `Cart`/`Customer` facades over Services managers (#406); `OrderView.vue` split into composables and dialogs (#478); thin list/config/crud/sort Vue composables extracted (#397). +- Shared reference CRUD for deliveries and payments (#509); `SettingsComboListService` shared between ExtJS combo GetList and Manager REST, duplicate membership processors removed (#490). +- Replaced abandoned `rakit/validation` (#486); removed dead code — deprecated pass-through `OrderService`/`Customer` (#489), field-config override endpoints (#383), `ms3_config_manager` (#485). +- Performance: separate order stats endpoint with a conditional Address JOIN (#469); parallel loading of option field values in `OrderView` (#447); request-scoped memoization of `getGridConfig` (#488). + +### Fixed +- Web API session/token hardening: API-token rotation against token fixation (#516); session-bypass closed after token revoke + web-logout hardening (#462); safe CORS — no wildcard-with-credentials, same-origin by default (#463); correct HTTP 401/429 for auth and rate limit (#434); guest token auto-minted for `cart/get` (#440); API session bound on email verify (#441); false-success token/refresh stub removed (#351); expired API tokens rejected instead of silently renewed (#396); checkout token takeover via email closed (#369/#391); storefront login and session auth restored (#321). +- Manager API ACL hardening: `msorder_save` on order writes (#402), `mssetting_save` on config/grid-config writes (#404, #436), tightened customer/category-product ACL (#400), `view_document` on `/references` PII (#438); category-product inline-edit/mutations scoped to the category (IDOR #443, #454); `filter_*` and dropdown/criteria whitelists (#457, #427, #452); `privacy_accepted_at` rejected in quick-update (#439); secret token no longer leaked in order responses (#426) nor customer secrets in profile/add responses (#428); ampersands escaped in the manager order-link email href (#465); storefront routes no longer loaded via the manager connector (#401). +- Order cost and submit: incompatible delivery/payment pairs rejected on submit/checkout/manager/finalize (#459); save blocked while cost recalculation is in flight (#461); fixed status revalidated after plugin mutation (#464); finalize cost aligned with `ManagerOrderCostRecalculator` (#448); `payment_link` filled in order/status emails (#458); error returned on failed status save (#392); draft weight = unit weight × count (#403); duplicate order numbers prevented under concurrent submit (#399); local calendar day used for date filters (#405). +- Products / catalog / cart: additional (`msCategoryMember`) categories included in `msProducts` and the category grid (#482); relation-type grid columns resolved via JOIN (#330); clearing color/size/tags now persists on save (#325); cart option change wired end-to-end via Web API and storefront (#502); Document → Product resource-type conversion fixed (#331). +- Late-cycle Vue regressions: double confirm on the category-products grid (#538/#540); `HTTP_MODAUTH` token race on Ext-less pages — token now in `ms3.config` (#544/#545); infinite recursive loop in `ResourceCategoryTree` that froze Settings→Options (#546/#547); duplicate confirm dialogs across Settings tab grids (#548/#549) and a redundant `VendorsGrid` delete confirm (#551/#552); stale grid responses on fast filter/page changes (#468); empty object/data unwrapped in the `request.js` connector (#466); missing `vue-dist/main.min.css` no longer requested on product update (#504). +- Currency/install/import: mojibake `?` default repaired for `ms3_currency_symbol` (#498) with an inline ruble symbol in the repair migration (#520); import shows toasts and per-step errors on field/preview failures (#453) and reads the start response from the object envelope (#390). +- DI: `OptionCategoryService` wired into `ms3_option_service` (#535); payment link resolved via `PaymentService` (#485). + +### Dependencies +- vueManager transitive security bumps: js-yaml, fast-uri, happy-dom, vite 6.4.3 (#483, #506). + ## [1.12.0-beta1] ### Added diff --git a/core/components/minishop3/src/MiniShop3.php b/core/components/minishop3/src/MiniShop3.php index 156c21d9..ce8c66d3 100644 --- a/core/components/minishop3/src/MiniShop3.php +++ b/core/components/minishop3/src/MiniShop3.php @@ -20,7 +20,7 @@ class MiniShop3 { - public $version = '1.12.0-beta1'; + public $version = '1.13.0-beta1'; /** @var modX $modx */ public $modx;