Теги шаблонов: справочник
Деньги, количества, ссылки, переводы, эскизы фотографий, подключение стилей и скриптов — все теги с примерами.
На этой странице
- Деньги
- Количества
- Адреса разделов
- Надписи темы
- Эскизы фотографий
- Записи блогов
- Блок «Новости» на главной
- Что есть у записи
- Частые ошибки
- Товары карточками
- Блоки
- Формы из CRM
- Шаблон partials/crm-form.html
- Формы
- Печатные формы заказа
- Печать по клику
- Документ на другом языке
- Стили и скрипты
- Место для дополнений
- Посмотреть, что пришло в шаблон
- Теги дополнений
- Обработчики значений
- Экранирование
- Чего в теме нет
Кроме обычных подстановок, условий и циклов Smarty, теме доступны теги самого магазина. Ниже — все они.
| Тег | Для чего |
|---|---|
{money} |
сумма с валютой |
{qty} |
дробное количество |
{url} |
адрес раздела витрины |
{trans} |
надпись темы, в том числе со склонением |
{thumb} |
эскиз фотографии нужного размера |
{posts} |
записи блогов: на главной, в подвале, где угодно |
{products} |
выбранные товары карточками |
{content_block} |
блок, который владелец пишет один раз для многих страниц |
{crm_form} |
форма на сайте из раздела «CRM → Формы» |
{csrf_field} {csrf_token} |
защита формы |
{captcha} |
проверка «я не робот» |
{asset_css} {asset_js} |
подключить свой стиль или скрипт |
{assets_head} {assets_footer} |
вывести собранный список файлов |
{hook} |
место для дополнений |
{dump} {dumpc} |
посмотреть, что пришло в шаблон |
Деньги
{money amount=$product.price currency=$storefront.currency}
{money amount=$order.total currency=$order.currency}
{money amount=$shop->cartTotal() currency=$storefront.currency precision=0}
| Параметр | По умолчанию | Что задаёт |
|---|---|---|
amount |
0 |
сумма |
currency |
USD |
валюта |
locale |
текущий язык | как разделять разряды и дробную часть |
precision |
как в валюте | сколько знаков после запятой |
⚠️ Валюту передавайте всегда. По умолчанию тег форматирует сумму долларами, и на тестовом магазине с валютой по умолчанию ошибку не видно — а в гривневом магазине цена уедет с долларовым знаком.
precision=0 пригодится там, где копейки ломают вёрстку: сумма корзины в шапке,
подпись на карточке.
Тег только форматирует число. Считать в шаблоне не нужно и нельзя — почему, написано на странице Цены, количества и валюта.
Количества
{qty value=$item.qty} {$item.unit} → 30,8 м²
Товары бывают дробными: метры, килограммы, площадь. Тег выводит число по-местному («30,8», а не «30.8»), отбрасывает хвостовые нули («2», а не «2,000») и не ставит разделитель разрядов — это же число подставляется в поле ввода количества, а «1 200» браузер прочитал бы как «1».
Адреса разделов
<a href="{url path='/search'}">Поиск</a>
<a href="{url path='/'}">На главную</a>
<a href="{url path='/cart' locale='uk'}">Кошик</a>
| Параметр | По умолчанию | Что задаёт |
|---|---|---|
path |
/ |
путь раздела |
locale |
текущий язык | язык, на котором открыть раздел |
absolute |
нет | полный адрес с доменом вместо пути |
Тег сам добавляет языковой префикс и приводит адрес к виду, принятому в магазине. Полный адрес нужен только поисковику — для ссылок внутри сайта берите обычный.
Адреса товаров, категорий, брендов, меток и страниц уже приходят готовыми в
данных ($product.url, $cat.url). Склеивать их руками нельзя: схему адресов
выбирает владелец магазина. Подробности — на странице
Адреса разделов и языки.
Надписи темы
{trans key="shop.cart"}
{trans key="shop.products_count" count=$total}
{trans key="shop.hello" name=$customer.first_name}
Строка берётся из файла темы locale/<язык>.json, а если ключа там нет — из
надписей самого магазина. Не нашлось нигде — выведется сам ключ: страница не
падает, но в вёрстке будет висеть shop.cart.
Всё, кроме key, считается подстановкой: {trans key="shop.hello" name="Иван"}
подставит имя вместо :name в строке.
Параметр count включает выбор формы по числу — «1 товар», «2 товара»,
«5 товаров». Как записать такую строку, написано на странице
Тексты темы и языки.
Эскизы фотографий
<img src="{thumb src=$product.image size=320}" alt="{$product.name}">
<img src="{thumb src=$product.image size=120 type='crop'}" alt="">
type |
Что делает |
|---|---|
max (по умолчанию) |
вписывает картинку целиком в квадрат со стороной size |
crop |
обрезает до квадрата size × size |
width |
задаёт ширину, высота считается сама |
height |
задаёт высоту, ширина считается сама |
Эскизы нарезаются по требованию: первый покупатель, открывший страницу, ждёт нарезку, дальше файл отдаёт веб-сервер напрямую. Если возможность выключена в настройках или файл лежит не на публичном диске, тег вернёт исходный адрес — картинка покажется, просто в полном размере.
Записи блогов
Тег {posts} ставит статьи блога в любое место оформления: на главную, в
подвал, на страницу товара, в текст информационной страницы. Работает он одним
из двух способов, и способ выбирает параметр assign. Перепутать их — самая
частая ошибка с этим тегом, поэтому сначала о них.
Способ первый: тег рисует сам. Без assign тег сразу выводит карточки
статей там, где стоит:
{posts limit=3}
Каждую запись тег рисует шаблоном partials/blog-card.html — тем же, которым
тема рисует ленту блога, — и оборачивает список в <div class="posts-embed">.
Если такого шаблона в вашей теме нет, карточка берётся из базовой темы
default. Переменную тег при этом не создаёт, и перебирать после него в
{foreach} нечего.
Способ второй: тег отдаёт данные. С assign тег ничего не выводит, а
кладёт список записей в переменную с указанным именем. Разметку вы пишете сами:
{posts limit=3 assign=freshPosts}
{foreach $freshPosts as $post}
<a href="{$post.url}">{$post.title}</a>
{/foreach}
Тот же список отдаёт помощник $shop->posts():
{assign var=freshPosts value=$shop->posts(3)}
{assign var=freshPosts value=$shop->posts(3, 'news')}
{assign var=freshPosts value=$shop->posts(['blog' => 'news', 'sort' => 'random', 'limit' => 3])}
Помощнику аргументы передаются по порядку: первым — сколько записей нужно,
вторым — адрес блога. Остальные условия передаются набором в квадратных
скобках, с теми же именами, что у тега. Именованных параметров вида
$shop->posts(limit=3) Smarty в вызове метода не понимает и сообщает об
ошибке в шаблоне. Если записи нужны только в этом же шаблоне, проще
поставить тег с assign.
Условия подборки у тега и помощника одни и те же:
{posts limit=3} три свежие записи карточками
{posts blog="news" limit=4} только из блога «Новости»
{posts category="guides" sort="random" limit=3} вперемешку из рубрики
{posts limit=4 assign=fresh} отдать записи в переменную
| Параметр | По умолчанию | Что задаёт |
|---|---|---|
blog |
все блоги витрины | блог: номер или адрес; несколько — через запятую |
category |
все рубрики | рубрика: номер или адрес; несколько — через запятую |
with_children |
да | брать ли записи вложенных рубрик |
full |
нет | отдать рубрики записей с полным описанием |
tag |
все метки | метка: номер или адрес; несколько — через запятую |
ids |
— | номера записей; при sort по умолчанию показываются в этом же порядке |
exclude |
— | номера записей, которые показывать не нужно |
limit |
4 |
сколько записей показать |
offset |
0 |
сколько первых записей пропустить |
sort |
feed |
в каком порядке (ниже) |
pinned |
— | true — только закреплённые записи |
assign |
— | имя переменной: тег отдаёт записи, а не разметку |
template |
partials/blog-card.html |
свой шаблон карточки; запись приходит в него переменной $post |
class |
posts-embed |
класс обёртки |
sort |
Порядок |
|---|---|
feed |
как в ленте блога: закреплённые сверху, дальше от свежих к старым |
new |
самые свежие |
old |
самые старые |
popular |
которые чаще читают |
random |
вперемешку: при каждом заходе свой набор |
title |
по алфавиту |
Магазин показывает только то, что видит покупатель: черновики, записи из архива,
записи с будущей датой публикации и записи выключенного блога в подборку не
попадают. Берутся записи блогов этой витрины — блог соседней витрины не
покажется, даже если написать его адрес. Выключенная рубрика в условии
category не найдётся, а если выключена главная рубрика самой записи, запись
в подборку попадёт, но без рубрики: поле category у неё будет пустым.
Записей по условиям не нашлось — тег без assign не выводит ничего, и {if}
вокруг него не нужен. С assign в переменную попадёт пустой список, поэтому
свою разметку оборачивайте в {if $freshPosts}, чтобы на странице не остался
пустой заголовок блока.
Блог, рубрику и метку называют как удобнее — адресом или номером:
{posts blog="news" limit=3} адрес виден в ссылке: /news/
{posts blog=3 limit=3} номер написан в панели, рядом с готовым кодом
Адрес нагляднее в шаблоне, номер надёжнее: ссылку на блог владелец может поменять, а номер останется прежним. Номер блога написан на его странице в разделе «Статьи», номер рубрики — на странице рубрики, там же стоит и готовая строка с кнопкой «Копировать».
Если на витрине нет ни одного включённого блога, тег без assign ничего не
выводит, а с assign отдаёт пустой список.
Блок «Новости» на главной
Так устроен блок свежих статей в базовой теме, файл blocks/news.html. Блок
берёт три последние записи из блогов витрины, ставит рядом ссылку на все
новости и не показывается вовсе, пока статей нет. Его можно взять за основу
своего блока: классы и разметку замените своими, а обращения к записям
оставьте как есть.
{posts limit=3 assign=freshPosts}
{assign var=blogs value=$shop->blogs()}
{if $freshPosts}
<section class="news">
<div class="section-head">
<h2 class="section-title">{trans key="storefront.news"}</h2>
{if $blogs}
<a class="section-more" href="{$blogs[0].url}">
{trans key="storefront.all_news"}
<i class="fa-solid fa-arrow-right"></i>
</a>
{/if}
</div>
<div class="news__grid">
{foreach $freshPosts as $p}
<article class="news-card">
<a class="news-card__cover" href="{$p.url}">
{if $p.category}<span class="news-card__cat">{$p.category.name}</span>{/if}
{if $p.cover_thumb}
<img class="news-card__img" src="{$p.cover_thumb}" alt="{$p.title|escape}" loading="lazy">
{else}
<i class="fa-regular fa-newspaper news-card__ico"></i>
{/if}
</a>
<div class="news-card__body">
{if $p.date_label}
<span class="news-card__date"><i class="fa-regular fa-calendar"></i> {$p.date_label}</span>
{/if}
<a class="news-card__title" href="{$p.url}">{$p.title}</a>
{if $p.summary}<p class="news-card__excerpt">{$p.summary}</p>{/if}
<a class="news-card__more" href="{$p.url}">
{trans key="storefront.read_more"}
<i class="fa-solid fa-arrow-right"></i>
</a>
</div>
</article>
{/foreach}
</div>
</section>
{/if}
$shop->blogs() отдаёт блоги витрины в том порядке, в котором они стоят в
панели, поэтому $blogs[0].url — адрес первого блога. Если блогов несколько и
ссылка нужна на определённый, найдите его по адресу:
{foreach $shop->blogs() as $b}
{if $b.slug == 'news'}<a href="{$b.url}">{trans key="storefront.all_news"}</a>{/if}
{/foreach}
Надписи storefront.news, storefront.all_news и storefront.read_more взяты
из файлов надписей базовой темы. В своей теме заведите такие же или свои — как
это делается, написано выше, в разделе «Надписи темы».
Что есть у записи
Каждая запись из {posts … assign=…} и $shop->posts() — это карточка того же
вида, что получает лента блога и шаблон partials/blog-card.html:
| Поле | Что внутри |
|---|---|
url |
адрес статьи на витрине — его ставят в href |
title |
заголовок |
summary |
анонс, короткий текст для карточки; пусто, если владелец его не написал |
cover |
обложка в полном размере; пусто, если не загружена |
cover_thumb |
эскиз обложки для карточки; пусто, если обложки нет |
category.name |
название главной рубрики записи |
category.url |
адрес страницы этой рубрики |
date_label |
дата подписью на языке витрины: «17 сентября 2026» |
date_iso |
полная дата со временем — для <time datetime="…"> |
date |
дата в виде 2026-09-17 — если нужен свой формат |
author |
автор; пусто, если не указан |
reading_time |
сколько минут займёт чтение |
views |
сколько раз статью прочитали |
is_pinned |
запись закреплена вверху ленты |
id, slug |
номер и адрес записи |
Поле category целиком пустое, если у записи нет рубрики или рубрика
выключена. Поэтому {$p.category.name} всегда оборачивают в
{if $p.category}, как в примере выше: без проверки запись без рубрики
сломает страницу. Даты считаются в часовом поясе магазина. Остальные данные
страниц блога описаны на странице Данные страниц.
Частые ошибки
Тег без assign и {foreach} по своей переменной. Это самая частая
причина жалобы «limit не работает». Шаблон написан так:
{$posts = [[ 'title' => '…', 'date' => '07 липня', 'cat' => 'Огляди' ]]}
{posts limit=3}
<div class="news__grid">
{foreach $posts as $p}
<a href="#">{$p.title}</a> {$p.cat} {$p.date}
{/foreach}
</div>
Тег {posts limit=3} без assign честно нарисовал три карточки — шаблоном
partials/blog-card.html, над сеткой новостей, а если стили темы этих
карточек не знают, их легко не заметить. В переменную тег ничего не положил,
поэтому {foreach} перебирает массив, написанный строкой выше, и сколько бы вы
ни меняли limit, на странице ничего не меняется. Правильно — попросить
данные и перебирать именно их:
{posts limit=3 assign=freshPosts}
{if $freshPosts}
<div class="news__grid">
{foreach $freshPosts as $p}
<a href="{$p.url}">{$p.title}</a>
{if $p.category}{$p.category.name}{/if}
<time datetime="{$p.date_iso}">{$p.date_label}</time>
{/foreach}
</div>
{/if}
Заглушка из макета осталась в шаблоне. Массив вида
{$posts = [[ 'title' => … ]]} или {assign var=posts value=[…]} нужен
только верстальщику, пока у магазина нет статей. В готовой теме его нужно
удалить: если он останется, покупатели будут видеть выдуманные новости, а
настоящие статьи не появятся, сколько бы их ни написали.
Переменная $posts. На страницах ленты блога, рубрики и метки
(blog.html, blog-category.html, blog-tag.html) в $posts уже лежат
записи текущей страницы ленты. Если блок новостей стоит в шапке или подвале и
перебирает $posts, на страницах блога он покажет ленту, а не свою подборку.
А {posts … assign=posts}, поставленный прямо в шаблон страницы блога,
подменит саму ленту. Берите для подборки своё имя, например freshPosts.
Имена полей из макета. В макете поля обычно называются по-своему, а у записи магазина имена такие:
| В макете | У записи магазина |
|---|---|
cat |
category.name, проверять через {if $p.category} |
date с готовой подписью вроде «07 липня» |
date_label; в date лежит дата вида 2026-07-07 |
href="#" |
href="{$p.url}" |
img, image |
cover_thumb для карточки, cover в полном размере |
text, excerpt |
summary |
Поле, которого у записи нет, — это ошибка в шаблоне, и страница с таким
блоком не откроется. Если после перехода на настоящие статьи главная
перестала открываться, первым делом сверьте имена полей с таблицей: чаще
всего в разметке остался {$p.cat} или {$p.category.name} без проверки
{if $p.category}.
Записей меньше, чем limit. limit — это верхняя граница, а не
обещание. Если в блогах витрины опубликовано две статьи, при limit=3 их
будет две. Черновики, записи из архива, записи с датой публикации в будущем и
записи блога другой витрины в подборку не попадают. Проверьте, что статья
опубликована и дата её публикации уже наступила.
Товары карточками
{products ids="12,7,3"}
{products ids="12,7,3" template="partials/product-card-small.html"}
Тег показывает выбранные товары карточками темы. Чаще всего его ставит не верстальщик, а владелец магазина: в редакторе текста он отмечает товары, и в описание категории или в статью попадает такая вставка. Товары выводятся в том порядке, в котором их выбрали.
| Параметр | По умолчанию | Что задаёт |
|---|---|---|
ids |
— | номера товаров через запятую |
limit |
24 |
сколько товаров показать |
template |
partials/product-card.html |
свой шаблон карточки |
class |
products-embed |
класс обёртки |
Подборки товаров для главной берутся иначе — у помощника $shop->products(),
чтобы владелец менял их состав в панели, не трогая шаблон. Об этом написано на
странице Помощник $shop.
Блоки
Блок — кусок текста или вёрстки, который владелец пишет один раз и показывает на многих страницах: преимущества магазина, условия доставки, баннер акции. Заводится он в панели, в разделе «Витрины и контент → Блоки», у каждого блока есть код. Правка блока сразу меняется везде, где он стоит.
{content_block code="ship-info"}
{content_block code="custom_products" list="viagra" limit=12}
{content_block code="category-`$category.slug`-info"}
В шаблон темы блок ставится тегом. В текст страницы, описание товара или категории владелец вставляет его кнопкой «Блок» в редакторе — параметры, если нужны, пишутся там же, под «Дополнительно».
Какой текст показать, магазин решает сам: сначала свой текст текущей витрины на языке страницы, затем общий текст блока на этом языке, затем свой текст витрины на основном языке магазина и в конце общий текст на основном языке. Если текста нет нигде, блок выключен или блока с таким кодом нет, тег ничего не выводит — проверять заранее не нужно. Поэтому удобно заводить блоки по шаблону кода, как в третьей строке: блок появится у того раздела, для которого его завели.
Параметры. Всё, кроме code, становится переменными внутри блока: при
list="viagra" limit=12 в тексте блока доступны {$list} и {$limit}, а все
параметры вместе — {$params}. Так один блок показывает на разных страницах
разные данные. Пример блока «Товары списка» (текст блока — вставка Smarty):
{$found = $shop->productList($list)}
{if $found}
<ul>
{foreach $found.products as $p}
{if $p@iteration > $limit|default:12}{break}{/if}
<li><a href="{$p.url}">{$p.name|escape}</a></li>
{/foreach}
</ul>
{/if}
Параметры видны только самому блоку: на страницу и во вложенные блоки они не
попадают. Имена params, shop, storefront, theme, seo, customer,
contacts, cookies, get, request, action и smarty заняты — такой
параметр пропускается.
Внутри блока работают вставки Smarty: теги дополнений, {products}, {posts},
помощник $shop. Когда блок вызван из шаблона темы, ему видны переменные этой
страницы и работает {include} шаблонов темы. Блок не может вставить сам себя —
такая вставка просто пропускается. Смена темы блоки не трогает: они хранятся в
магазине, а не в теме.
Формы из CRM
Формы «Задать вопрос», «Обратная связь» и другие владелец собирает в панели, в разделе «CRM → Формы»: поля, тексты на каждом языке и что делать с заявкой. Как это устроено для владельца, написано на странице Формы на сайте. В тему форма ставится тегом:
{crm_form code="question"}
{crm_form code="question" class="form-wide"}
{crm_form code="wholesale" template="partials/crm-form-compact.html"}
| Параметр | По умолчанию | Что задаёт |
|---|---|---|
code |
— | код формы, его видно на странице формы под кнопкой «Код для темы» |
class |
пусто | свой класс, приходит в шаблон как $form.class |
template |
partials/crm-form.html |
свой шаблон формы в теме |
В текст страницы, описание товара или блок владелец вставляет форму кнопкой
«Форма» в редакторе, и туда уходит тот же тег. Тег ничего не выводит, если
формы с таким кодом нет, она выключена или не работает на этой витрине, а
также если путь в template не похож на путь к шаблону: допустимы латинские
буквы, цифры, _, - и /, в конце .html. Если такого шаблона нет ни в
теме, ни в стандартной теме, тег тоже ничего не выводит. Сбой формы страницу
не роняет.
Отправку без перезагрузки, ошибки у полей и «спасибо» даёт сам магазин: вместе с
формой он сам подключает /storefront/crm-form.js и
/storefront/crm-form.css. Поэтому форма работает в любой теме, а тема отвечает только за разметку и
оформление.
Шаблон partials/crm-form.html
Форму рисует шаблон темы partials/crm-form.html или тот, что задан в
template. Если в теме такого шаблона нет, магазин берёт его из стандартной
темы default: форма появится и в теме, написанной до форм. Кроме $form,
шаблону видны $storefront, $theme, $auth, $min_order, $below_min,
$free_shipping_from, $free_shipping_left, $cookies, $get, $request
и переменные, которые добавляют дополнения. Переменных страницы ($shop, $customer, $product и
других) в шаблоне формы нет. Все ключи $form есть всегда.
| Ключ | Что в нём |
|---|---|
$form.code |
код формы |
$form.id |
id для разметки: crm-form-question; вторая копия той же формы на странице — crm-form-question--2 |
$form.class |
класс из параметра class |
$form.title |
«Заголовок над формой», может быть пустым |
$form.submit |
надпись на кнопке |
$form.action |
адрес отправки, уже с языком страницы |
$form.fields |
поля списком (скрытых полей в нём нет) |
$form.consent |
галочка согласия на обработку данных: name, id, text, checked, error; null, если форма согласие не спрашивает |
$form.sent |
форму только что отправили без скрипта, пора показать «спасибо» |
$form.again_url |
эта же страница с пустой формой — для ссылки «отправить ещё одно» без скрипта |
$form.success |
текст «спасибо» |
$form.error |
общая ошибка после отправки без скрипта, иначе null |
$form.protection |
служебные поля формы одной строкой |
$form.i18n |
надписи на языке страницы: required, sending, failed, choose, again |
Каждое поле в $form.fields:
| Ключ | Что в нём |
|---|---|
key |
ключ поля, постоянный |
name |
имя для <input name>: fields[ключ], у флажков fields[ключ][] |
id |
id поля: id формы, дефис и ключ |
type |
text, textarea, phone, email, number, date, select, radio, checkboxes или consent |
input |
вид <input type>: tel, email, number, date или text |
autocomplete |
name, tel, email или off — по тому, куда поле записывается в карточке |
label, placeholder |
подпись и подсказка на языке страницы |
required |
обязательное ли поле |
width |
full или half |
maxlength |
предел длины, null у видов без предела |
value |
значение: после неудачной отправки без скрипта — то, что ввёл покупатель, а вошедшему покупателю — его имя, телефон и почта |
checked |
отмечен ли флажок-согласие |
options |
варианты списка, переключателей и флажков: value, label, selected |
error |
ошибка поля после отправки без скрипта, иначе null |
Что обязательно в разметке. Оформление меняйте свободно, но оставьте:
{$form.protection nofilter}внутри<form>. Там ключ защиты формы, метка времени, адрес страницы, невидимая ловушка для роботов и капча, если она включена. Без этой строки отправка не пройдёт или уйдёт в спам. Печатать её нужно сnofilter, иначе вместо полей на странице окажется их текст.- Имена полей ввода — ровно
{$f.name}, у галочки согласия —{$form.consent.name}со значением1. - Опоры
data-crm-*, по которым скрипт и стили магазина находят части формы:
| Опора | Где стоит |
|---|---|
data-crm-form="{$form.code}" |
на самой <form>; там же data-sending (надпись кнопки на время отправки) и data-failed (текст на случай сбоя отправки) |
data-crm-body |
обёртка всего, что прячется после успешной отправки |
data-crm-field="{$f.key}" |
обёртка поля, получает класс is-invalid; у согласия ключ consent |
data-crm-error="{$f.key}" |
место для текста ошибки поля |
data-crm-message |
место для общей ошибки формы |
data-crm-success |
сообщение «спасибо» |
data-crm-submit |
кнопка отправки |
data-crm-again |
«отправить ещё одно», необязательно |
data-crm-success-text, data-crm-submit-label |
необязательно: куда внутри «спасибо» и кнопки ставить текст, если там есть значок |
data-crm-grid |
необязательно, только для стилей: сетка полей, где поле с data-width="half" занимает полстроки |
data-crm-choice |
необязательно, только для стилей: подпись вокруг флажка или переключателя |
Пока форма отправляется, на ней стоит класс is-sending, а кнопка выключена.
Подсказки браузера об ошибках скрипт отключает: ошибки приходят от магазина
на языке витрины и встают у своих полей.
Стили магазина в crm-form.css отвечают за раскладку и запасной вид полей,
кнопки и сообщений. Они написаны через :where(), и правило темы их
перебивает. Исключений два: элемент с атрибутом hidden внутри формы скрыт
всегда, а ловушку для роботов .crm-form__trap тема не переопределяет. Цвет
акцента (рамка поля в фокусе, кнопка, флажки) тема задаёт переменной
--crm-form-accent.
Без скрипта форма уходит обычной отправкой и возвращает покупателя на ту
же страницу, к форме. Результат приходит в тот же $form: sent, error,
ошибки полей и введённые значения. Поэтому шаблон должен показывать их сам,
как в стандартной теме, где data-crm-success скрыт атрибутом hidden, пока
$form.sent ложно. Такая страница в общий кеш не попадает.
Событие после отправки. Когда магазин принял отправку, на форме
срабатывает событие cartix:form-sent. Оно всплывает до document, в
detail.code — код формы. При ошибке события нет. Отправку робота магазин
тоже «принимает», чтобы не подсказывать ему, поэтому событие считает
отправки, дошедшие до магазина, а не настоящие заявки. Его удобно отдавать
счётчику целей:
document.addEventListener('cartix:form-sent', (event) => {
window.dataLayer?.push({ event: 'form_sent', form: event.detail.code });
});
Если форма появляется на странице позже, например в окне, которое тема
подгружает по клику, оживите её вызовом CartiX.initForms(контейнер), где
контейнер — элемент, внутри которого лежит форма (не сама <form>). Форму,
которая уже работает, вызов не трогает. Функция есть, только когда на
странице уже загружен /storefront/crm-form.js, то есть когда на ней с
самого начала стоит хотя бы одна форма: скрипты внутри подгруженного куска
браузер при вставке не выполняет.
Свой шаблон проще всего начать с копии
themes/default/templates/partials/crm-form.html: перенесите её в свою тему и
меняйте оформление, не трогая опоры.
Формы
<form method="post" action="/reviews/submit">
{csrf_field}
…
{captcha form="reviews"}
<button type="submit">Отправить</button>
</form>
{csrf_field} ставит скрытое поле — без него форма не пройдёт. {csrf_token}
отдаёт тот же ключ строкой; в каркас его вставлять не надо — мета-тег
csrf-token печатает магазин, а скриптам ключ приходит в CartiX.csrf.
{captcha form="…"} сам спрашивает, включена ли проверка «я не робот» для этой
формы, и рисует нужный виджет. Проверка выключена — тег не выводит ничего, и
ветку {if} вокруг него писать не надо.
Подробнее — на странице Формы витрины.
Печатные формы заказа
{assign var="invoice" value={order_document order=$order code="order.invoice"}}
{if $invoice}
<a href="{$invoice}" target="_blank" rel="noopener">Счёт на оплату</a>
{/if}
Тег отдаёт ссылку на печатную форму заказа: счёт, накладную, гарантийный талон — любой бланк из «Настройки» → «Печатные формы». По ссылке открывается готовая к печати страница, та же, что печатает менеджер: номер и дата документа выдаются один раз, поэтому покупатель и бухгалтерия видят одну и ту же бумагу.
order — заказ кабинета ($order на странице account/order.html), его номер
или заказ, пришедший в шаблон. code — код бланка: он написан в самой форме,
в «Настройки» → «Печатные формы», строкой «Код бланка» рядом с названием, и
копируется кнопкой. pdf=true — та же бумага файлом:
{assign var="pdf" value={order_document order=$order code="order.delivery_note" pdf=true}}
{if $pdf}<a href="{$pdf}">Скачать накладную</a>{/if}
Печать по клику
Обычная ссылка открывает бумагу и оставляет покупателя один на один с вопросом,
чем её напечатать: на телефоне печать спрятана в меню «Поделиться». Скажите
print=true — страница откроет окно печати сама, как только загрузится:
{assign var="doc" value={order_document order=$order code="order.invoice" print=true}}
{if $doc}
<a href="{$doc}" target="_blank" rel="noopener">Распечатать счёт</a>
{/if}
Печать вызывается после полной загрузки, а не сразу: иначе лист уходит в принтер
без логотипа и печати, которые не успели загрузиться. С pdf=true параметр не
нужен и не действует — там окно печати открывает программа просмотра файла.
Если не хочется уводить покупателя на другую вкладку, печатайте в скрытой рамке — это те же несколько строк, библиотека для этого не нужна:
<a class="js-print" href="{order_document order=$order code="order.invoice"}">Распечатать счёт</a>
{literal}
<script>
document.addEventListener('click', function (e) {
var link = e.target.closest('.js-print');
if (!link) return;
e.preventDefault();
var frame = document.createElement('iframe');
frame.style.cssText = 'position:fixed;right:0;bottom:0;width:0;height:0;border:0';
frame.src = link.getAttribute('href');
frame.onload = function () { frame.contentWindow.focus(); frame.contentWindow.print(); };
document.body.appendChild(frame);
});
</script>
{/literal}
Рамку не убирайте сразу после print() — пока открыто окно печати, документ
обязан оставаться на странице. Один и тот же приём работает для любого бланка:
достаточно поменять код формы в ссылке.
Документ на другом языке
По умолчанию бумага выходит на языке заказа: покупатель оформлял по-украински — и счёт будет украинский, даже если сейчас он смотрит магазин по-русски. Нужен другой язык — скажите об этом прямо:
{assign var="en" value={order_document order=$order code="order.invoice" locale="en"}}
{if $en}<a href="{$en}">Invoice in English</a>{/if}
Языком можно распорядиться и по-своему — например, дать бумагу на том языке, на котором покупатель сейчас смотрит магазин:
{assign var="doc" value={order_document order=$order code="order.invoice" locale=$storefront.locale}}
{if $doc}<a href="{$doc}">Счёт на оплату</a>{/if}
Вместе с pdf это тоже работает:
{order_document order=$order code="order.invoice" locale="en" pdf=true}
Просить можно только язык, на котором работает сам магазин («Основные настройки» → «Региональные настройки»). Незнакомый код тег молча пропускает мимо ушей — бумага выйдет на языке заказа, а не пустой. Если бланк на нужном языке не написан, магазин возьмёт его на своём основном: счёт на чужом языке лучше пустого листа.
Где показывать документы и какие — решает тема: в кабинете, на странице «Заказ оформлен», в письме. Магазин отвечает за две вещи, в которых тема ошибиться не может:
- ссылку получает только тот, чей это заказ — вошедший покупатель со своим заказом либо гость, оформивший его в этой же сессии;
- выключенный или пустой бланк ссылки не даёт вовсе, иначе кнопка вела бы на «страница не найдена».
В обоих случаях тег возвращает пустую строку — поэтому ссылку прячут обычным
{if}, как в примерах выше.
Адрес построен на личном ключе заказа — том же, по которому покупатель попадает на оплату. Своего секрета у документа нет, и такую ссылку можно положить в письмо. Помните: кто ссылку получил, тот документ и откроет.
Стили и скрипты
{block name="page_assets"}
{asset_css file="css/product.css"}
{asset_js file="js/product.js" defer=true}
{/block}
| Параметр | Что делает |
|---|---|
file |
путь внутри assets/ вашей темы |
inline |
вставить содержимое прямо в страницу, а не ссылкой |
key |
имя, по которому файл не подключится дважды |
head / footer |
куда выводить |
| остальные | уезжают атрибутами в <link> или <script> |
{assets_head} и {assets_footer} выводят собранный список — последними, когда
страница собрана целиком, поэтому файл можно объявить в любом месте шаблона.
Соседние файлы магазин склеивает в один сжатый. Что попадает в склейку и как
это проверить — на странице Стили, скрипты и картинки темы.
Место для дополнений
{hook name="product.buybox.end" product=$product}
Сюда дополнения выводят свою разметку. Подписчиков нет — тег не выводит ничего. Каждое дополнение вызывается отдельно и в защите от ошибок: упавшее не выводит ничего, страница остаётся живой.
Всё, что передано параметрами, уезжает дополнению — поэтому полезно передавать контекст: товар, позицию корзины. Полный список мест — на странице Точки для дополнений.
Посмотреть, что пришло в шаблон
{dump} всё, что доступно этому шаблону
{dump var=$product} одно значение
{dumpc var=$product} то же самое в консоль браузера
{$product|dump nofilter} привычной записью
Видит только сотрудник магазина. Покупателю блок не покажется, даже если тег забыли в теме. Пароли, ключи и подписи в выводе заменяются звёздочками, глубина ограничена шестью уровнями, длинные списки и строки обрезаются.
Теги дополнений
Дополнение может завести собственный тег — он появится во всех темах сразу. Установленные сейчас:
| Тег | Дополнение |
|---|---|
{slider id="home-hero"} |
Слайдеры и баннеры |
{slider_data id="home-hero" assign=hero} |
то же, отдаёт данные в переменную |
{oneclick_button product=$product} |
Купить в один клик |
{back_in_stock product=$product} |
Сообщить о поступлении |
{callback_button} |
Обратный звонок |
{shorturl url=$product.url} |
Короткие ссылки |
Тег выключенного дополнения, оставленный в шаблоне, не ломает страницу — он просто ничего не выводит. Поэтому тема может ссылаться на теги дополнений, не проверяя, стоят ли они.
Что приносит установленное дополнение, видно двумя способами: в его описании в
разделе «Дополнения» и по папке templates/plugins/<дополнение>/ в вашей теме —
дополнение с собственной разметкой кладёт шаблоны прямо туда.
Обработчики значений
В Smarty 5 в шаблон нельзя писать любую функцию языка. Магазин возвращает безопасный набор:
Текст. ucfirst, lcfirst, ucwords, trim, ltrim, rtrim, str_pad,
str_replace, str_contains, str_starts_with, str_ends_with, sprintf,
strrev, mb_strlen, mb_substr, mb_strtolower, mb_strtoupper, urlencode,
rawurlencode, htmlspecialchars_decode.
Числа. abs, ceil, floor, intval, floatval, max, min, intdiv.
Списки. array_keys, array_values, array_slice, array_reverse,
array_sum, array_unique, array_flip.
Оформление. tel превращает телефон из настроек в ссылку:
<a href="tel:{$phone|tel}">. color_tone отвечает light или dark для
цвета из настроек — по нему тема выбирает светлый или тёмный логотип
(подробнее).
Отладка. dump, dumpc.
Сверх этого работает всё, что Smarty умеет сам: count, round, implode,
substr, json_encode, date_format, escape, default, upper, nl2br,
truncate, strip_tags.
{$product.name|mb_strtoupper}
{$product.summary|truncate:120:'…'}
{$product.price|default:0}
{$order.created_at|date_format:'%d.%m.%Y'}
Незнакомый обработчик страницу не ломает: значение проходит насквозь, а имя уходит в журнал. Так опечатка не превращается в белый экран у покупателя — но и не даёт о себе знать, поэтому перед выкладкой в журнал стоит заглянуть.
Экранирование
Всё, что выводится, экранируется само. {$product.name} безопасен всегда, даже
если в названии товара есть < или кавычки.
Там, где данные содержат готовую разметку, нужен nofilter:
{$page.content nofilter} содержимое информационной страницы
{$product.description nofilter} описание товара
{$product.json nofilter} карта вариантов покупки для браузера
Забыли nofilter — покупатель увидит текст с <p> вместо абзацев.
Чего в теме нет
Теги {sum}, {in_words}, {number} и {profile} относятся к печатным
бланкам — счетам, накладным, чекам, — а не к витрине. В шаблоне темы их нет.
Про них написано в разделе
Печатные бланки.