Помощник $shop: списки магазина
Категории, страницы, подборки товаров, бренды, метки, отзывы и корзина — прямо из шаблона, без подготовки данных.
На этой странице
$shop — помощник, который есть в каждом шаблоне. Через него тема берёт то, что
ей нужно, не дожидаясь, пока данные передадут: категории для меню, подборки
товаров на главную, бренды, метки, отзывы, состояние корзины.
Помощник живёт один на запрос и запоминает результаты — повторный вызов с теми
же параметрами в базу не ходит. Звать {$shop->categories()} в шапке, в меню
каталога и в плитках на главной не накладно.
Сводка
| Метод | Что отдаёт |
|---|---|
contacts() |
контакты магазина |
locales() |
языки витрины со ссылками на текущую страницу |
categories($parentId, $depth, $media) |
дерево категорий |
pages($parentId) |
меню страниц, созданных владельцем |
products($code, $preset, $limit, $sort) |
карточки товаров подборки |
catalogProducts($categoryId, $depth, $limit) |
товары в порядке каталога — для прайс-листа |
tags($limit) |
метки каталога |
brands($limit) |
бренды с логотипами |
reviews($limit) |
последние одобренные отзывы |
blogs($full) |
блоги витрины со всеми полями; true — и с описаниями |
blogCategories($blog, $full) |
рубрики блога деревом — для меню блога |
posts($options) |
записи блогов по условиям |
accessories($productId) |
группы аксессуаров товара |
cartCount() |
число позиций в корзине |
cartTotal() |
сумма корзины |
cartPath() |
куда ведёт значок корзины |
isOnepageCheckout() |
включено ли оформление в один шаг |
favoritesCount() |
число товаров в избранном |
compareCount() |
число товаров в сравнении |
inCart() |
что уже лежит в корзине |
cartItem($productId, $skuId) |
позиция корзины с этим товаром |
inCartQty($productId, $skuId) |
сколько этого товара в корзине |
cardVariantsEnabled() |
показывает ли тема варианты покупки на карточках |
Категории
{$shop->categories()} верхний уровень
{$shop->categories(null, 2)} два уровня
{$shop->categories(null, 2, true)} два уровня, с картинками
{$shop->categories($category.id, 1)} подкатегории текущей
| Параметр | По умолчанию | Что задаёт |
|---|---|---|
parentId |
пусто | корень поддерева; пусто — верхний уровень |
depth |
1 |
на сколько уровней вглубь |
media |
нет | добавить ли картинки |
Поля узла: id, name, initial (первая буква — для плитки без картинки),
slug, url, children. С media добавляются logo, icon, cover — либо
адрес файла, либо пусто.
{foreach $shop->categories(null, 2, true) as $cat}
<a href="{$cat.url}">
{if $cat.icon}<img src="{$cat.icon}" alt="">{/if}
{$cat.name}
</a>
{foreach $cat.children as $sub}
<a href="{$sub.url}">{$sub.name}</a>
{/foreach}
{/foreach}
Выключенные категории не приходят.
Информационные страницы
{foreach $shop->pages() as $page}
<a href="{$page.url}">{$page.title}</a>
{/foreach}
Поля: id, slug, title, url. Выключенные не приходят, порядок — как задан
в панели. Страниц нет — пустой список.
Подборки товаров
Два способа получить список товаров на главную.
По коду подборки, заведённой владельцем:
{$shop->products('new-arrivals', null, 12)}
Код задаётся в панели, в разделе подборок. Подборки с таким кодом нет или она выключена — пустой список. Это основной способ: владелец сам решает, что показывать, не трогая шаблон.
По готовому набору правил:
{$shop->products(null, 'discount', 8)}
| Набор | Что отбирает |
|---|---|
new |
добавленные за последние 30 дней |
discount |
только со скидкой |
low_stock |
остаток от 1 до 5 |
random |
случайные |
bestsellers |
по продажам |
popular |
по просмотрам |
top_rated |
по оценкам |
Неизвестное имя молча превращается в new.
⚠️ Честно про три последних: bestsellers, popular и top_rated сейчас
сортируют по новизне. Товары придут правильные, порядок — как у новинок.
Сортировка по продажам, просмотрам и оценкам объявлена, но пока не сделана.
Возвращаются полные карточки товара — тот же набор полей, что у товаров категории. Он описан на странице Данные товара.
Товары в порядке каталога
Для прайс-листа и похожих страниц: товары идут раздел за разделом, как разделы стоят в меню, а внутри раздела — в порядке, который вы расставили в панели. Новые товары попадают в список сами.
{$shop->catalogProducts()} верхний уровень, до 100 товаров
{$shop->catalogProducts(null, 2, 500)} с подразделами, до 500 товаров
{$shop->catalogProducts($category.id, 3)} ветка одного раздела на три уровня
| Параметр | По умолчанию | Что задаёт |
|---|---|---|
categoryId |
пусто | с какого раздела начать; пусто — весь каталог |
depth |
1 |
сколько уровней раскрыть: 1 — только сам раздел, 2 — с подразделами |
limit |
100 |
сколько товаров отдать, не больше 1000 |
Товар из нескольких разделов приходит один раз — там, где встретился первым. Скрытые разделы и разделы «по условиям» не участвуют. Отсутствующие товары витрина прячет или опускает в конец раздела — по своей настройке, как в остальных списках.
Строки лёгкие, без фото и характеристик, — их хватает на таблицу «название,
цена, Купить» даже на большом каталоге. Поля: id, name, url, sku,
sku_id (основной вариант), price, compare_price, unit, in_stock,
is_available, preorder.
{foreach $shop->catalogProducts(null, 2, 500) as $p}
<div>
{$p.name|escape} — {money amount=$p.price currency=$storefront.currency}
{if $p.is_available}
<button type="button" data-add="{$p.id}" data-sku="{$p.sku_id}">Купить</button>
{/if}
</div>
{/foreach}
Метки и бренды
{foreach $shop->tags(20) as $tag}<a href="{$tag.url}">{$tag.name}</a>{/foreach}
{foreach $shop->brands(30) as $brand}
<a href="{$brand.url}">
{if $brand.logo}<img src="{$brand.logo}" alt="{$brand.name}">{else}{$brand.name}{/if}
</a>
{/foreach}
Метки: id, slug, name, url, по алфавиту. Бренды: то же плюс logo.
Логотип берётся уменьшенным, при отсутствии уменьшенной копии — оригинал. По
умолчанию — 50 штук.
Отзывы
{foreach $shop->reviews(6) as $r}
<blockquote>
<p>{$r.text}</p>
<cite>{$r.name}, <a href="{$r.product_url}">{$r.product}</a>, {$r.date}</cite>
</blockquote>
{/foreach}
Последние одобренные отзывы о товарах — для блока на главной. Поля: name,
initial, product, product_url (ведёт на #, если товар удалён), rating,
text, date в формате дд.мм.
Отзывы о магазине
{foreach $shop->storeReviews(20) as $r}
<blockquote>
<p>{$r.text}</p>
<cite>{$r.name}{if $r.product}, {$r.product}{/if}, {$r.date}</cite>
</blockquote>
{/foreach}
Отзывы о покупке в целом, а не о товаре: о доставке, упаковке, работе менеджера.
Список отдаёт одобренные отзывы этой витрины и общие для всех витрин, новые
первыми, не больше 200. Поля: id, name, initial, rating, title, text,
date в формате дд.мм.гггг, date_long — дата словами на языке страницы
(«1 апреля 2025»), date_iso — та же дата для разметки поисковиков
(datePublished), и product — подпись покупателя «что брал». Это текст, а не
ссылка: такого товара в каталоге может уже не быть.
В replies лежат одобренные ответы под отзывом: name, is_admin (ответил
магазин), text, date_long. Текст отзыва и ответа выводите с экранированием:
{$r.text|escape|nl2br nofilter}.
Итог для шапки блока даёт $shop->storeReviewsSummary(): count — сколько всего
одобренных отзывов о магазине, average — средняя оценка с одним знаком после
запятой. Считается по всем отзывам, а не только по показанным:
{$s = $shop->storeReviewsSummary()}
{if $s.count}{$s.average} на основе {$s.count} отзывов{/if}
Чтобы покупатель оставил отзыв о магазине, форма отправляет на
CartiX.urls.reviewSubmit поле kind=store вместо product_id и, по желанию,
product_label с подписью товара. Остальные поля — как у отзыва о товаре.
Статьи
{foreach $shop->blogs() as $blog}
<a href="{$blog.url}">{$blog.name}</a>
{/foreach}
blogs() отдаёт блоги этой витрины с теми же полями, что у blog на странице
блога (id, slug, name, summary, url, icon, cover, cover_thumb,
banner), и rss — адрес ленты подписки. Полное описание description приходит
по $shop->blogs(true). blogCategories('news') отдаёт рубрики блога деревом, с
теми же полями, что categories на странице блога; блог называют номером или
адресом, blogCategories('news', true) добавляет описания. Почему описания
приходят по запросу — на странице Данные страниц. Ссылку на статьи ставят в шапку и в подвал; блогов
может быть несколько — например «Новости» и «Гайды».
{$shop->posts(4)} четыре свежие записи
{$shop->posts(['blog' => 'news', 'limit' => 3])} только из блога «Новости»
{$shop->posts(['category' => 'guides', 'sort' => 'random', 'limit' => 3])}
posts() отдаёт карточки записей — те же, что приходят в ленту блога. Условия
подборки перечисляются набором: blog, category, with_children, tag,
ids, exclude, limit, offset, sort, pinned, full. Блог, рубрика и метка
называются номером или адресом. Что каждое из условий
означает, написано на странице Теги магазина — помощник и
тег {posts} понимают их одинаково.
Чаще всего проще поставить тег: {posts limit=3} сразу нарисует карточки
шаблоном темы, а {posts limit=3 assign=fresh} положит записи в переменную.
Помощник нужен там, где записи уходят в другой шаблон параметром:
{include file="blocks/news.html" posts=$shop->posts(3) title='storefront.news'}
Аксессуары
{foreach $shop->accessories($product.id) as $group}
<h3>{$group.title}</h3>
{foreach $group.items as $item}
<label>
<input type="checkbox" value="{$item.sku_id}">
<img src="{$item.image}" alt="">
{$item.name}
{money amount=$item.final_price currency=$storefront.currency}
</label>
{/foreach}
{/foreach}
Группа: id, title, multiple (можно выбрать несколько), items. Аксессуар:
product_id, sku_id, name, url, image, in_stock, is_available,
discount (процент группы), price, final_price (со скидкой группы),
compare_price, default_quantity, unit, qty_step, qty_min, is_default,
count (сколько штук предложенного варианта есть в наличии, с ядра 2.53.4).
Остаток аксессуара count считается так же, как остаток на странице товара:
без товара, отложенного под чужие заказы, а минус показывается нулём. Если
остаток у товара не ведётся, count пустой — товар есть всегда, и число лучше не
показывать вовсе:
{if $item.count !== null}{$item.count} {$item.unit}{/if}
Аксессуары выключены в настройках — пустой список. Группа, в которой не осталось ни одного доступного товара, не приходит вовсе.
На странице товара то же самое уже лежит в $product.accessories — отдельно
звать не нужно. И помните: живой пересчёт комплекта делает сам магазин, тема
только размечает блок — см. Атрибуты data-*.
Языки
{foreach $shop->locales() as $l}
<a href="{$l.url}"{if $l.active} class="is-active"{/if}>{$l.short}</a>
{/foreach}
Поля: code (ru), prefix (uk или ua — задаётся в справочнике языков),
name (название на родном языке), short (префикс заглавными), active, url.
url — путь текущей страницы на этом языке, а не полный адрес:
переключатель обязан остаться на том домене, по которому покупатель зашёл.
Корзина
<a href="{$shop->cartPath()}">
Корзина
<span data-badge="cart">{$shop->cartCount()}</span>
<span data-cart-total-header>{money amount=$shop->cartTotal() currency=$storefront.currency precision=0}</span>
</a>
cartPath() отдаёт /checkout, если у витрины включено оформление в один шаг,
иначе /cart — значок корзины должен вести туда, куда владелец настроил.
cartTotal() берёт готовый итог корзины, а не складывает позиции: своя
формула в шапке разъедется со страницей корзины и с оформлением. Корзины ещё
нет — ноль.
favoritesCount() и compareCount() — то же самое для избранного и сравнения.
Все четыре дёшевы, зовите откуда угодно.
Что уже в корзине
inCart(), cartItem(), inCartQty() отвечают, лежит ли товар в корзине.
Позиция: item_id, product_id, sku_id, qty, qty_text, unit, packs,
lines. Товар лежит несколькими вариантами — количества складываются, а
item_id, sku_id и packs приходят пустыми: позиция уже не одна.
⚠️ Обычный путь другой. Эти методы помечают страницу как личную и выключают для неё общий кэш витрины: страница перестаёт отдаваться из кэша и собирается заново каждому покупателю.
Штатный способ показать «уже в корзине» — атрибут на карточке:
<article class="card" data-in-cart="{$product.id}">
…
<span class="card__in-cart">
В корзине <b data-in-cart-qty></b> <span data-in-cart-unit></span>
</span>
</article>
Количество проставит магазин уже в браузере, и страница останется кэшируемой. Подробности — на странице Атрибуты data-*.
Варианты покупки на карточках
cardVariantsEnabled() читает настройку темы card_variants — показывать ли
выбор цвета и размера прямо в списке товаров.
⚠️ Настройка обязана быть объявлена в theme.json. У базовой темы её нет,
поэтому базовая тема варианты на карточках не показывает. Пока поля нет в схеме,
магазин не тратит запросы на подготовку вариантов — и правка шаблона ничего не
изменит. Как объявить поле, написано на странице
Файл theme.json.
Чего у $shop нет
Помощник отдаёт списки и состояние, а не всё подряд. Заказов покупателя, произвольной выборки товаров по своим условиям, чужих сущностей магазина в нём нет — и обойти это, обратившись из шаблона к базе, нельзя: шаблон работает в песочнице.
Если теме нужны данные, которых у $shop нет, — это задача дополнения. Оно
считает своё и выводит через точку встраивания или
собственный тег.