Документація

Теги шаблонів: довідник

Гроші, кількості, посилання, переклади, ескізи фотографій, підключення стилів і скриптів — усі теги з прикладами.

На цій сторінці

Крім звичайних підстановок, умов і циклів 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 — покупець побачить текст із &lt;p&gt; замість абзаців.

Чого в темі немає

Теги {sum}, {in_words}, {number} і {profile} стосуються друкованих бланків — рахунків, накладних, чеків, — а не вітрини. У шаблоні теми їх немає. Про них написано в розділі Друковані бланки.

Оновлено 13 жовтня 2026