Перенесення теми з 2.3 на 3.0
Що змінилося#
Головна відмінність тем 3.0 від 2.x — шаблонізатор. У 2.x шаблони були PHP-файлами з розширенням .tpl, а в 3.0 це файли Twig з розширенням .twig. Підтримки .tpl в ocStore 3.0.4.1 немає: у ядрі є лише адаптер Twig, тому тему з 2.3 неможливо просто скопіювати — кожен шаблон потрібно конвертувати.
| ocStore 2.3 | ocStore 3.0.4.1 | |
|---|---|---|
| Формат шаблонів | .tpl (PHP) |
.twig (Twig 3) |
| Шлях шаблонів вітрини | catalog/view/theme/<тема>/template/ |
без змін |
| Шлях шаблонів адмінки | admin/view/template/ |
без змін |
| Виведення змінної | <?php echo $heading_title; ?> |
{{ heading_title }} |
| PHP-код у шаблоні | дозволений | неможливий |
| Мовні рядки в шаблоні | лише передані контролером через $data |
усі завантажені мовні рядки доступні автоматично |
| Токен адмінки в URL | token |
user_token |
Решта логіки тем — контролери, мовні файли, стилі, макети — лишилася майже такою самою.
Перед оновленням магазину з 2.x до 3.0 перемкніть магазин на стандартну тему й вимкніть сторонні доповнення (див. Оновлення з 2.x до 3.0). Свою тему переносьте окремо, на тестовій копії.
Структура теми в 3.0#
catalog/view/theme/mytheme/
├── template/ ← шаблони .twig (структура як у default/template/)
│ ├── common/header.twig
│ ├── common/footer.twig
│ ├── product/product.twig
│ └── extension/module/…
├── stylesheet/
│ └── stylesheet.css
└── image/
Тема не мусить містити всі шаблони. Якщо файлу немає в теці вашої теми, ocStore автоматично бере його з catalog/view/theme/default/template/. Тож у власну тему достатньо покласти лише ті шаблони, які ви змінюєте.
Посилання на CSS і зображення теми прописуються в шаблонах шляхом від кореня сайту, наприклад у common/header.twig:
<link href="catalog/view/theme/mytheme/stylesheet/stylesheet.css" rel="stylesheet">
Налаштування теми в адмінці#
Як і в 2.3, тема в 3.0 підключається через доповнення типу Шаблони.
- Скопіюйте теку теми в
catalog/view/theme/mytheme/. - Відкрийте Доповнення → Доповнення, у списку «Оберіть тип доповнення» виберіть Шаблони.
- Відредагуйте «Шаблон за замовчуванням» (активуйте його кнопкою Активація, якщо він не активний).
- У полі Директорія шаблона виберіть
mytheme, встановіть Статус «Включено» і збережіть. - Відкрийте Система → Налаштування, відредагуйте магазин і на вкладці Загальні переконайтеся, що в полі Шаблон вибрано «Шаблон за замовчуванням». Збережіть.
У налаштуваннях доповнення-шаблону (а не в загальних налаштуваннях магазину) у 3.0 зберігаються:
- кількість товарів на сторінці категорії та довжина короткого опису;
- розміри всіх зображень: категорії, мініатюри й спливаючого зображення товару, додаткових і супутніх товарів, порівняння, списку бажань, кошика, магазину на мапі, виробника.
Ці параметри зберігаються з ключами theme_default_* (наприклад, theme_default_image_thumb_width), а контролери читають їх як $this->config->get('theme_' . $this->config->get('config_theme') . '_image_thumb_width').
Якщо ви створюєте окреме доповнення типу «Шаблони» (admin/controller/extension/theme/<код>.php), враховуйте, як 3.0.4.1 визначає теку теми: для коду default береться значення Директорія шаблона (theme_default_directory), а для будь-якого іншого коду назва теки теми має збігатися з кодом доповнення. Доповнення має зберігати налаштування theme_<код>_status та theme_<код>_image_*, інакше вітрина покаже помилку «A theme has not been assigned to this store!» або зображення без розмірів.
Конвертація синтаксису#
Основні відповідності#
| PHP (.tpl) | Twig (.twig) |
|---|---|
<?php echo $title; ?> |
{{ title }} |
<?php echo $product['name']; ?> |
{{ product.name }} |
<?php echo $error_name[$language['language_id']]; ?> |
{{ error_name[language.language_id] }} |
<?php if ($error_warning) { ?> |
{% if error_warning %} |
<?php if (!$products) { ?> |
{% if not products %} |
<?php if ($sort == 'p.price') { ?> |
{% if sort == 'p.price' %} |
<?php if ($a && $b) { ?> / \|\| |
{% if a and b %} / or |
<?php } elseif ($x) { ?> |
{% elseif x %} |
<?php } else { ?> |
{% else %} |
<?php } ?> (кінець if) |
{% endif %} |
<?php foreach ($products as $product) { ?> |
{% for product in products %} |
<?php foreach ($items as $key => $item) { ?> |
{% for key, item in items %} |
<?php } ?> (кінець foreach) |
{% endfor %} |
<?php $row = 0; ?> |
{% set row = 0 %} |
<?php $row++; ?> |
{% set row = row + 1 %} |
<?php if (isset($error[$id])) { ?> |
{% if error[id] is defined %} або {% if error[id] %} |
<?php if (in_array($id, $selected)) { ?> |
{% if id in selected %} |
<?php echo count($products); ?> |
{{ products\|length }} |
<?php echo $a . ' ' . $b; ?> |
{{ a ~ ' ' ~ b }} |
<?php echo isset($x) ? $x : ''; ?> |
{{ x ?? '' }} або {{ x\|default('') }} |
<?php echo $flag ? 'checked' : ''; ?> |
{{ flag ? 'checked' }} |
<?php echo htmlspecialchars($value); ?> |
{{ value\|e }} |
<?php echo nl2br($text); ?> |
{{ text\|nl2br }} |
<?php echo date('Y'); ?> |
{{ 'now'\|date('Y') }} |
<?php // коментар ?> |
{# коментар #} |
Корисні можливості Twig у циклах: loop.index (номер з 1), loop.index0 (з 0), loop.first, loop.last, loop.length. Цикл з блоком «порожньо»:
{% for product in products %}
<div class="product">{{ product.name }}</div>
{% else %}
<p>{{ text_empty }}</p>
{% endfor %}
Приклад: фрагмент шаблону до і після#
Було (product/category.tpl, 2.3):
<?php if ($products) { ?>
<div class="row">
<?php foreach ($products as $product) { ?>
<div class="product-layout col-sm-4">
<h4><a href="<?php echo $product['href']; ?>"><?php echo $product['name']; ?></a></h4>
<?php if ($product['special']) { ?>
<span class="price-new"><?php echo $product['special']; ?></span>
<span class="price-old"><?php echo $product['price']; ?></span>
<?php } else { ?>
<?php echo $product['price']; ?>
<?php } ?>
</div>
<?php } ?>
</div>
<?php } else { ?>
<p><?php echo $text_empty; ?></p>
<?php } ?>
Стало (product/category.twig, 3.0):
{% if products %}
<div class="row">
{% for product in products %}
<div class="product-layout col-sm-4">
<h4><a href="{{ product.href }}">{{ product.name }}</a></h4>
{% if product.special %}
<span class="price-new">{{ product.special }}</span>
<span class="price-old">{{ product.price }}</span>
{% else %}
{{ product.price }}
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<p>{{ text_empty }}</p>
{% endif %}
Масова заміна регулярними виразами#
Більшу частину рутинної роботи можна зробити пошуком і заміною з регулярними виразами в редакторі коду (VS Code, PhpStorm, Notepad++). Виконуйте заміни по черзі й обов'язково переглядайте результат — складні конструкції все одно доведеться правити вручну.
Набір регулярних виразів для заміни .tpl → .twig
Синтаксис груп $1 підходить для VS Code і PhpStorm; у Notepad++ використовуйте \1.
1. Виведення змінної
Знайти: <\?php echo \$(\w+); \?>
Замінити: {{ $1 }}
2. Виведення елемента масиву
Знайти: <\?php echo \$(\w+)\['(\w+)'\]; \?>
Замінити: {{ $1.$2 }}
3. Виведення за мовою
Знайти: <\?php echo \$(\w+)\[\$(\w+)\['(\w+)'\]\]; \?>
Замінити: {{ $1[$2.$3] }}
4. Умова за змінною
Знайти: <\?php if \(\$(\w+)\) \{ \?>
Замінити: {% if $1 %}
5. Заперечення
Знайти: <\?php if \(!\$(\w+)\) \{ \?>
Замінити: {% if not $1 %}
6. Умова за елементом масиву
Знайти: <\?php if \(\$(\w+)\['(\w+)'\]\) \{ \?>
Замінити: {% if $1.$2 %}
7. Порівняння з рядком
Знайти: <\?php if \(\$(\w+) == '([^']*)'\) \{ \?>
Замінити: {% if $1 == '$2' %}
8. elseif
Знайти: <\?php \} elseif \(\$(\w+)\) \{ \?>
Замінити: {% elseif $1 %}
9. else
Знайти: <\?php \} else \{ \?>
Замінити: {% else %}
10. foreach
Знайти: <\?php foreach \(\$(\w+) as \$(\w+)\) \{ \?>
Замінити: {% for $2 in $1 %}
11. foreach з ключем
Знайти: <\?php foreach \(\$(\w+) as \$(\w+) => \$(\w+)\) \{ \?>
Замінити: {% for $2, $3 in $1 %}
12. foreach по елементу масиву
Знайти: <\?php foreach \(\$(\w+)\['(\w+)'\] as \$(\w+)\) \{ \?>
Замінити: {% for $3 in $1.$2 %}
13. Присвоєння нуля
Знайти: <\?php \$(\w+) = 0; \?>
Замінити: {% set $1 = 0 %}
14. Інкремент
Знайти: <\?php \$(\w+)\+\+; \?>
Замінити: {% set $1 = $1 + 1 %}
Після автоматичних замін залишаться рядки <?php } ?>. Їх потрібно вручну замінити на {% endif %} або {% endfor %} залежно від того, який блок вони закривають.
Чого в Twig робити не можна#
- Викликати PHP-функції й методи об'єктів ядра. Конструкції на кшталт
<?php echo $this->config->get('...'); ?>або<?php echo $this->url->link(...); ?>потрібно перенести в контролер і передати в шаблон як змінну$data['...']. - Підключати інші файли шаблонів. Адаптер Twig в ocStore 3.0.4.1 рендерить кожен шаблон окремо, тому
{% include %},{% extends %},{% embed %},{% import %}і функціяinclude()не працюють. Спільні фрагменти (шапка, підвал, колонки) підключаються в контролері через$this->load->controller()і виводяться як змінні:{{ header }},{{ footer }},{{ column_left }}. - Покладатися на автоекранування. Воно вимкнене, як і в
.tpl:{{ description }}виведе HTML як є. Екрануйте дані з ненадійних джерел фільтром|e.
ocStore 3.0.4.1 постачається з Twig 3. Якщо ви переносите тему з ocStore 3.0.3.x, де використовувалася старіша версія Twig, перевірте шаблони на застарілі конструкції:
{% for item in items if item.status %}— умова вforбільше не підтримується; використовуйте{% for item in items|filter(i => i.status) %}або{% if %}усередині циклу;- теги
{% spaceless %}і{% filter %}замініть на{% apply spaceless %}…{% endapply %}та{% apply upper %}…{% endapply %}; - у PHP-коді доповнень класи з префіксом
Twig_(наприклад,Twig_Loader_Array) замініть на класи з простором імен (\Twig\Loader\ArrayLoader).
Контролери й дані для шаблонів#
Тема в 3.0 отримує дані від стандартних контролерів 3.0. Якщо тема 2.3 використовувала змінні, яких стандартний контролер 3.0 не передає (часто так буває з темами, що постачалися з модифікованими контролерами), шаблон покаже порожні місця. Що робити:
- Не редагуйте контролери ядра — зміни зникнуть після оновлення.
- Додайте потрібні дані через подію
catalog/view/<маршрут>/before: обробник отримує масив$dataшаблону за посиланням і може доповнити його. - Або скористайтеся OCMOD, якщо зміна має бути всередині логіки контролера.
Приклад обробника, що додає змінну в шаблон сторінки товару:
<?php
// catalog/controller/extension/module/mytheme_data.php
class ControllerExtensionModuleMythemeData extends Controller {
// Подія: catalog/view/product/product/before
public function productBefore(&$route, &$data, &$code) {
$data['mytheme_phone'] = $this->config->get('config_telephone');
}
}
Пам'ятайте, що мовні рядки, завантажені контролером, у 3.0 доступні в шаблоні автоматично, тож переносити рядки на кшталт $data['text_x'] = $this->language->get('text_x'); у власні обробники не потрібно.
Кеш і перевірка змін#
- Скомпільовані шаблони Twig зберігаються в
storage/cache/template/. Під час зміни файлу шаблону кеш оновлюється автоматично, а щоб видалити старі файли кешу, використайте Панель стану → кнопка з шестернею («Налаштування розробника») → Шаблон. - Шаблони, змінені через Дизайн → Редактор шаблонів, зберігаються в базі даних і мають пріоритет над файлами теми. Якщо зміни у файлі не видно, перевірте, чи немає для цього шаблону перевизначення в Редакторі шаблонів.
- Після оновлення CSS очищайте кеш браузера (
Ctrl+F5) і, якщо сайт працює через CDN (наприклад, Cloudflare), кеш CDN.
Контрольний список перенесення#
- Розгорніть тестову копію магазину на ocStore 3.0.4.1 зі стандартною темою.
- Створіть теку
catalog/view/theme/mytheme/і перенесіть тудиstylesheet/,image/та JavaScript теми. - Конвертуйте шаблони
.tpl→.twig, починаючи зcommon/header,common/footer,common/home, а потімproduct/*,checkout/*,account/*. Шаблони, які тема не змінювала, не переносьте — спрацює файл зdefault. - Замініть у шаблонах
<?php … ?>-виклики об'єктів ядра на змінні, підготовлені контролером або подією. - Увімкніть тему: Доповнення → Доповнення → Шаблони → Шаблон за замовчуванням → Директорія шаблона.
- Перенесіть розміри зображень і кількість товарів у налаштування доповнення-шаблону.
- Пройдіть основні сценарії: головна, категорія, товар, пошук, кошик, оформлення замовлення, особистий кабінет — і переглядайте Система → Обслуговування → Журнал помилок.
- Перевірте сторонні модулі теми: їхні шаблони теж мають бути в
.twigі лежати вtemplate/extension/module/.