Skip to content

fix(web): локаль toast корзины через ctx страницы (#541) - #542

Open
Ibochkarev wants to merge 3 commits into
betafrom
fix/541-web-api-page-context
Open

fix(web): локаль toast корзины через ctx страницы (#541)#542
Ibochkarev wants to merge 3 commits into
betafrom
fix/541-web-api-page-context

Conversation

@Ibochkarev

Copy link
Copy Markdown
Member

Описание

Cart toast (response.message → iziToast) брал cultureKey контекста web, потому что api.php всегда делал initialize('web'). Страница на Babel/другом контексте уже показывала правильные лейблы, а toast — нет.

Фикс: ms3Config.ctx = контекст страницы → ?ctx= на каждый Web API запрос → WebApiContextResolver + switchContext$ms3->initialize($ctx) до загрузки lexicon корзины.

Тип изменений

  • Исправление бага (non-breaking change)
  • Новая функциональность (non-breaking change)
  • Breaking change (изменение, ломающее обратную совместимость)
  • Рефакторинг (без изменения функциональности)
  • Документация
  • Другое (опишите):

Связанные Issues

Closes #541

Как это было протестировано?

php -l core/components/minishop3/src/Services/Api/WebApiContextResolver.php
# exit 0
cd core/components/minishop3
./vendor/bin/phpunit tests/Unit/Services/Api/WebApiContextResolverTest.php
# OK (11 tests, 16 assertions), exit 0
php tests/WebApiPageContextSmokeTest.php
# OK, exit 0
php tests/RouterRoutePlansTest.php
# OK, exit 0
php tests/TokenSessionRevokePolicyTest.php
# OK, exit 0
  • Ручное тестирование
  • Автоматические тесты (unit + smoke)
  • Тестирование на разных версиях PHP/MODX

Конфигурация тестирования:

  • MiniShop3: branch fix/541-web-api-page-context
  • MODX: n/a (unit/smoke без полной установки)
  • PHP: 8.4.17

Скриншоты (если применимо)

До После
toast на языке web toast на cultureKey контекста страницы

Чеклист

  • Код соответствует стилю проекта
  • Добавлены/обновлены комментарии в сложных местах
  • Изменения не ломают существующую функциональность
  • Лексиконы добавлены на двух языках (ru/en) — n/a, ключи те же
  • PHPStan проходит без новых ошибок (composer stan / CI job PHPStan)
  • ESLint проходит без ошибок — n/a (ванильный JS, не vueManager)
  • Обновлён CHANGELOG.md — нет (по политике репо)

Дополнительные заметки

Что изменено

  • Plugin OnLoadWebDocument: initialize / registerFrontend с $modx->context->key (раньше ms3Config.ctx всегда был web).
  • ApiClient.buildUrl + TokenManager: query ctx.
  • WebApiContextResolver: sanitize, reject mgr*, switchContext; при fail остаётся текущий контекст.
  • AuthorizedCustomerTrait / token/get: initialize с активным context key.

ms3_cart_context

  • При ms3_cart_context=1 draft по-прежнему в web (Cart::initialize).
  • При 0 (default) draft context = page ctx — как у сниппетов. Старые draft’ы, созданные API только в web при просмотре non-web витрины, могут не находиться, пока не включите ms3_cart_context или не пересоздадите корзину.

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.
@Ibochkarev Ibochkarev added priority: medium Средний приоритет javascript Pull requests that update javascript code bug Something isn't working labels Aug 12, 2026
@Ibochkarev
Ibochkarev requested a review from biz87 August 12, 2026 15:22

@biz87 biz87 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Разобрал 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 — нет. Дальше nullCart::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.
@Ibochkarev

Copy link
Copy Markdown
Member Author

Addressed review blockers from #542 (review) in 6b6571e2:

  1. Unknown ctx / null contextWebApiContextResolver checks getContext() before switchContext; on failed switch restores a live context. CartController / AuthorizedCustomerTrait use liveContextKey(). api.php catches \Throwable and reads ctx from $_REQUEST.
  2. ms3_cart_context=1 Cart/Order sync — shared CartDraftContext::resolve() in Cart::initialize and Order construct/initialize.
  3. Tests — unit double nulls context on failed switch; covers unknown-ctx skip + restore. Smoke checks the new guards/wiring.
  4. Nits — blank line in ApiClient; ctx source aligned with route.

@Ibochkarev
Ibochkarev requested a review from biz87 August 12, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working javascript Pull requests that update javascript code priority: medium Средний приоритет

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Cart toast-нотификации игнорируют локаль страницы: api.php всегда initialize('web')

2 participants