fix(web): локаль toast корзины через ctx страницы (#541) - #542
fix(web): локаль toast корзины через ctx страницы (#541)#542Ibochkarev wants to merge 3 commits into
Conversation
api.php always initialized MODX as web, so cart success messages used web cultureKey on Babel/multi-context pages. Send ms3Config.ctx on requests, switchContext safely, and wire plugin/registerFrontend to the real page context.
biz87
left a comment
There was a problem hiding this comment.
Разобрал PR. Направление и место фикса верные: modLexicon::load() резолвит cultureKey в момент вызова, а все ms3-топики (minishop3:cart в Cart::__construct, minishop3:default/customer в конструкторе CartController) грузятся уже после switchContext — механизм рабочий.
Но есть два блокера.
1. Несуществующий ctx обнуляет $modx->context → 500 на всех cart-эндпоинтах
sanitize() проверяет charset/длину/mgr*, а существование контекста докблок явно делегирует switchContext. У MODX там разрушительный failure mode. Проверил на живом MODX 3:
switchContext('zzz_nonexistent_ctx') → false
$modx->context → NULL
В modX::_initContext() при неудачном prepare() контекст зануляется ($this->context = null), а восстанавливается только в ветке провала checkPolicy('load') — то есть для несуществующего ключа не восстанавливается вообще.
apply() при этом честно вернёт 'web', и $ms3->initialize('web') отработает. Но $modx->context остаётся null до конца запроса, а CartController дёргает его 6 раз без guard:
// CartController.php:49, 84, 126, 160, 184, 208
$cart->initialize($this->modx->context->key, $token);AuthorizedCustomerTrait и config/routes/web.php в этом PR guard ?? 'web' получили, CartController — нет. Дальше null → Cart::initialize(string $ctx = 'web', …) → TypeError (для userland-функций null в non-nullable string это не deprecation, а ошибка). api.php ловит только \Exception, а TypeError — это \Error, так что до catch-блока не дойдёт: неперехваченный фатал, 500 без JSON-тела.
Репро — публично, без авторизации:
GET /assets/components/minishop3/api.php?route=/api/v1/cart/get&ctx=zzz
Органический сценарий тот же: закэшированная страница или бандл с ключом переименованного/удалённого контекста.
Фикс минимальный — проверять существование до switch:
if ($modx->getContext($ctx) === null) {
return $current;
}getContext() для несуществующего ключа возвращает null, не трогает $modx->context и не отравляет кэш $modx->contexts (isset() на null → false, повторный вызов перезапрашивает). Проверил оба случая. Плюс имеет смысл добавить ?? 'web' в шести местах CartController — раз уж соседям guard добавляли.
2. Рассинхрон Cart/Order при ms3_cart_context = 1
Cart настройку уважает:
// Cart.php:128
$this->ctx = $ms3CartContext ? 'web' : $ctx;Order — нет, он берёт ctx из $ms3->config['ctx'] в конструкторе (Order.php:59) и про ms3_cart_context не знает. Draft при этом ищется строго по context = $ctx (OrderDraftManager::getDraft()).
До PR API-путь был самосогласован: обе стороны 'web'. После PR на не-web странице с включённой «единой корзиной для всех контекстов» получается: Cart пишет draft в web, Order ищет в en, не находит, создаёт пустой → пустая корзина на расчёте и оформлении.
В сниппет-пути этот рассинхрон существует и сейчас (ms3_order.php: $ms3->initialize($modx->context->key) + $ms3->cart->initialize($modx->context->key, …)) — PR его не изобретает, но распространяет на API. Правильнее резолвить ctx корзины в одном месте, а не дублировать условие в двух контроллерах.
3. Тесты проверяют не то
WebApiPageContextSmokeTest — это grep по исходникам (str_contains($apiPhp, 'ms3->initialize($ctx)')). Он покраснеет от переименования локальной переменной и ничего не говорит о поведении.
Юнит-тест резолвера содержательнее, но его дубль modX при switchContext() === false оставляет context->key нетронутым — моделирует поведение, которого у MODX нет. Именно поэтому проблема №1 прошла мимо тестов. Если привести дубль к реальному поведению (занулять context при неудаче), тест testApplyKeepsCurrentWhenSwitchFails сразу покажет, что «current» после провала уже недоступен вызывающему коду.
4. Мелочи
- Лишние двойные пустые строки:
api.php(послеinitialize('web')и после ctx-блока),ApiClient.js(послеbuildUrl). Этот путь ESLint не покрывает, так что руками. routeчитается из$_REQUEST,ctx— из$_GET. Не баг (buildUrlкладёт ctx в query и для POST), но источники лучше согласовать.
По поводу заметки о старых draft'ах
В описании PR это подано как возможная потеря корзин. Уточню: сниппеты уже работают в контексте страницы (ms3_cart.php:50), а API писал в web — то есть на мультиконтекстном сайте с дефолтным ms3_cart_context=0 было split-brain, и набранное через API в ms3_order всё равно не отображалось. PR это чинит, а «потерянные draft'ы» — уже сломанное состояние, а не работающее. В релизных заметках упомянуть стоит, но как миграцию, а не как регрессию.
Итого: пункты 1 и 2 — блокеры (публично достижимый 500 и сломанный checkout на сайтах с единой корзиной), остальное на усмотрение.
Guard unknown ctx before switchContext (MODX nulls context), share CartDraftContext for Cart/Order when ms3_cart_context=1, and align tests with real failure modes from PR review.
|
Addressed review blockers from #542 (review) in
|
Описание
Cart toast (
response.message→ iziToast) бралcultureKeyконтекстаweb, потому чтоapi.phpвсегда делалinitialize('web'). Страница на Babel/другом контексте уже показывала правильные лейблы, а toast — нет.Фикс:
ms3Config.ctx= контекст страницы →?ctx=на каждый Web API запрос →WebApiContextResolver+switchContext→$ms3->initialize($ctx)до загрузки lexicon корзины.Тип изменений
Связанные Issues
Closes #541
Как это было протестировано?
Конфигурация тестирования:
fix/541-web-api-page-contextСкриншоты (если применимо)
webcultureKeyконтекста страницыЧеклист
composer stan/ CI jobPHPStan)Дополнительные заметки
Что изменено
OnLoadWebDocument:initialize/registerFrontendс$modx->context->key(раньшеms3Config.ctxвсегда былweb).ApiClient.buildUrl+TokenManager: queryctx.WebApiContextResolver: sanitize, rejectmgr*,switchContext; при fail остаётся текущий контекст.AuthorizedCustomerTrait/token/get:initializeс активным context key.ms3_cart_contextms3_cart_context=1draft по-прежнему вweb(Cart::initialize).0(default) draft context = page ctx — как у сниппетов. Старые draft’ы, созданные API только вwebпри просмотре non-web витрины, могут не находиться, пока не включитеms3_cart_contextили не пересоздадите корзину.