Быстрый старт
Эта страница — для работы только в шаблонах MODX. Ни IDE, ни git, ни контроллеров: установил, вызвал сниппет, подменил чанк.
Вывести товары
Поставьте пакет и напишите в шаблоне:
[[!pbProducts]]Всё. На странице появятся выложенные товары в том порядке, в котором их расставил менеджер, — карточка с картинкой, названием, ценой, кнопкой «В корзину» и сердечком избранного.
Восклицательный знак обязателен: без него MODX закеширует вывод, и первый же покупатель, положивший товар в корзину, покажет своё состояние остальным.
Что нужно один раз настроить
Строго говоря — ничего. Свежая установка открывает витрину на /shop, кладёт в неё раздел «Демонстрация» с тремя товарами и включает один способ оплаты — офлайновый, «Наличными при получении». Заказ проходит целиком, оплату подтверждает менеджер в гриде платежей.
Дальше это меняют под себя:
| Настройка | Зачем менять |
|---|---|
pbshop_route_prefix | Префикс каталога, по умолчанию shop. Пустое значение выключает витрину целиком. Если на сайте уже есть страница /shop, витрина её перекроет — смените префикс |
pbshop_currency | Валюта по умолчанию. Товар без цены в этой валюте карточка печатает без цены — это не то же самое, что «ноль» |
pbshop_catalog_limit | Сколько товаров на странице каталога и раздела, по умолчанию двенадцать. Ноль — все разом. На сниппеты не влияет: у них свой &limit |
| Способы оплаты | Остальные четырнадцать приезжают выключенными, и это не недоделка: включённый эквайер с чужими ключами уводит деньги на чужой shop_id. Включайте тот, по которому есть договор |
| Раздел «Демонстрация» | Удалите его вместе с товарами, когда заведёте свои. Все псевдонимы начинаются с demo- |
Товары, категории, опции и варианты заводятся в разделе pbShop в менеджере — конструктор полей приходит из PageBlocks, поэтому своё поле добавляется мышкой и никаких колонок под него не нужно.
Пять сниппетов
[[!pbProducts]] товары каталога
[[!pbProduct? &id=`mug-logo`]] один товар карточкой
[[!pbCategories]] разделы каталога
[[!pbCart]] корзина: состав, скидка, итог
[[!pbOrder? &uuid=`…`]] заказ карточкойpbProducts, pbCategories и pbCart работают без параметров: первый печатает выложенные товары, второй — корни каталога, то есть сам каталог (&parent спускает на уровень ниже), третий — корзину того, кто пришёл.
pbProduct и pbOrder без параметра обойтись не могут и не делают вид, что могут: угадать, какой товар или чей заказ вы имели в виду, нечем. Вызов без &id и без &uuid печатает, чего не хватает, — «Укажите товар: &id=псевдоним или &id=12».
Корзина и заказ — только некешированным вызовом
И не работают при включённом pageblocks_http_cache: кеш ответов ключуется адресом без cookie, поэтому напечатанный в разметку состав одного покупателя достался бы остальным. Сниппет в этом случае отказывается вслух и объясняет, что делать, — молчаливая пустая корзина выглядела бы пустой корзиной.
Поменять разметку
Два способа, и первый обычно достаточен.
Править дефолтный чанк
При установке компонент заводит свои чанки обычными записями в дереве менеджера, в категории pbShop:
| Чанк | Что печатает |
|---|---|
pbShop.card | карточка товара в списке — её же берёт pbProduct |
pbShop.list | обёртка списка товаров и категорий |
pbShop.categoryCard | раздел в списке |
pbShop.cartRow | строка корзины |
pbShop.cartRows | обёртка корзины с итогом и скидкой |
pbShop.orderCard | карточка заказа |
Откройте, поправьте, сохраните — изменения видны на витрине сразу. Обновление компонента не затрёт вашу правку: при установке сохраняется хеш поставленного содержимого, и перезаписывается только то, чего никто не касался. Если правили — в лог уйдёт строка «дефолтный чанк обновлён, ваш изменён», а ваш останется как есть.
Отдельно лежат чанки страниц витрины: pbShop.catalog, pbShop.category, pbShop.product, pbShop.cart, pbShop.order, pbShop.layout и другие. Они нужны, если вы правите не карточку, а страницу целиком.
Галочка «статичный» требует системной настройки MODX
Поле «Статичный файл» у наших чанков заполнено сразу — поставив галочку, вы забираете чанк себе файлом под git. Но MODX запишет файл только если в static_elements_allowed_extensions разрешено расширение tpl. Пустая настройка означает «ни одного статичного элемента»: источник объявляется немутабельным, и менеджер ответит ошибкой поля. Проверено на стенде 01.10.2026 — настройка там пуста, и файл не создался ни из менеджера, ни из API.
Указать свой чанк в вызове
[[!pbProducts? &tpl=`myCard`]]Значение &tpl понимает четыре формы — те же, к которым аудитория MODX привыкла:
card чанк из базы
file:chunks/card файл из pageblocks_elements_path
@FILE chunks/card то же самое, привычка пользователей pdoTools
@INLINE <b>{$title}</b> инлайнОтката между источниками нет
Указали file: — ищется только файл. Не нашли — в разметке появится понятная надпись, а не тихий поиск того же имени в базе: неявный откат делает поведение недетерминированным и неотлаживаемым.
Чего в вызове нет и не будет: отдельного параметра пути (customPath и подобных). Источник указывается внутри значения — иначе &tpl и &tplWrapper в одном вызове не смогли бы брать шаблоны из разных мест.
Что доступно в карточке
pbShop.card получает плоский набор — модель в чанк не приезжает намеренно, иначе верстальщику пришлось бы знать про валюты и про то, что у составного товара своя цена служебная:
| Плейсхолдер | Что внутри |
|---|---|
{$title} | название |
{$url} | адрес карточки; пусто, если витрина выключена |
{$image} | путь к картинке как его сохранил менеджер. Уменьшает её блок image.tpl через Glide: {include 'file:image' src=$image alt=$title w=400 h=400} |
{$formatted} | цена строкой, у составного товара — «от 1990 ₽». Пусто значит «в этой валюте не продаётся» |
{$old_formatted} | старая цена для зачёркивания. Пусто, если скидки нет: сравнение с текущей ценой делает товар, шаблону остаётся напечатать непустое |
{$alias} | псевдоним, он же ключ для кнопок «в корзину» и «в избранное» |
{$product_id} | идентификатор |
{$in_stock} | есть ли что отгружать |
{$variable} | составной ли товар: у такого выбирают вариант на карточке, а не кладут в корзину из списка |
{$idx}, {$first}, {$last} | номер строки и её края — для «каждый третий в ряд» |
Картинки
Товар, раздел и вариант хранят путь к картинке (image), а уменьшает её Glide из PageBlocks — своей обвязки компонент не держит. В шаблонах это общий блок:
{include 'file:image' src=$image alt=$title w=400 h=400}
{include 'file:image' src=$product->image alt=$product->title w=800 h=800 crop=0}Он отдаёт webp запрошенного размера (q=82) с width/height в атрибутах и loading="lazy". Размеры в атрибутах не украшение: без них браузер не знает высоту до загрузки, и список прыгает под курсором ровно тогда, когда покупатель целится в карточку. crop=0 — вписать целиком вместо обрезки по центру.
У товара без картинки печатается рамка той же пропорции с надписью «Нет фото». Не пустота: карточка без рамки встаёт выше соседей, и ряд в сетке разъезжается.
Поставочные демо-товары идут без фотографий
Три товара раздела «Демонстрация» показывают заглушку — стоковых картинок компонент не везёт: это лишний вес пакета и чужая лицензия в вашем магазине. Добавьте свои, и уменьшение заработает само.
Битый файл не ломает страницу, но и не чинится сам
Нет файла по пути, нулевой файл, HEIC под именем .jpg — Glide пишет одну строку в лог и возвращает исходный адрес. Страница цела, картинка не уменьшена: смотрите журнал MODX, там сказано, что именно с файлом.
Параметры
Одни и те же у всех списков. Выучив их здесь, вы напишете вызов любого другого сниппета линейки, не открывая документацию.
| Параметр | Смысл |
|---|---|
&tpl | чанк элемента; не указан — дефолтный чанк компонента |
&tplWrapper | чанк обёртки; 0 — без обёртки |
&tplEmpty | чанк для пустого списка; не указан — пустой вывод |
&limit | сколько выводить; 0 — все |
&offset | сколько пропустить |
&page | номер страницы вместо смещения |
&sort | именованный набор витрины: cheap, expensive, new, name. Незнакомое значение — обычный порядок, а не отказ |
&sortby | по какому полю сортировать; несколько — через запятую |
&sortdir | asc или desc |
&where | дополнительные условия, JSON |
&parent | раздел: псевдоним или id |
&outputSeparator | разделитель между строками |
&toPlaceholder | положить вывод в плейсхолдер вместо печати на месте |
&showLog | отладка; печатается только вошедшему в менеджер |
У pbProduct вместо раздела — &id (псевдоним или число), у pbOrder — &uuid.
Порядок на самих страницах каталога и раздела — те же наборы в адресе: ?sort=cheap. Ряд ссылок печатает блок sorting.tpl, данные приходят в $sorting. Отсортированная страница закрыта <meta robots="noindex,follow"> — это те же товары, переставленные местами, то есть дубль раздела; постраничность при этом открыта, вторая страница несёт другие товары.
Сортировать можно только по колонкам таблицы
Поле, добавленное конструктором, живёт в JSON-колонке, и сортировка по нему не сортирует, а валится ошибкой SQL. Поэтому &sortby с таким полем отвечает отказом до запроса и перечисляет, по чему можно: id, alias, title, price, old_price, stock, weight, menuindex, published_at, created_at, updated_at.
Когда что-то не вышло
Сниппет не молчит. Опечатались в имени чанка — на месте вызова появится «Чанк «myCard» не найден»; в имени раздела — «Раздел «mugs» не найден»; в &where — что именно не разобралось в JSON. Пустой вывод означает ровно одно: выводить нечего.
Имя настройки в надпись не попадает — оно уходит в лог MODX: покупателю знать внутреннее устройство магазина незачем.
Дальше
- Разработчику — если понадобится своя страница с данными из магазина мимо сниппетов. Один и тот же проект спокойно живёт и так, и так.