Перенесення теми з 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.3 не запуститься на 3.0

Перед оновленням магазину з 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 підключається через доповнення типу Шаблони.

  1. Скопіюйте теку теми в catalog/view/theme/mytheme/.
  2. Відкрийте Доповнення → Доповнення, у списку «Оберіть тип доповнення» виберіть Шаблони.
  3. Відредагуйте «Шаблон за замовчуванням» (активуйте його кнопкою Активація, якщо він не активний).
  4. У полі Директорія шаблона виберіть mytheme, встановіть Статус «Включено» і збережіть.
  5. Відкрийте Система → Налаштування, відредагуйте магазин і на вкладці Загальні переконайтеся, що в полі Шаблон вибрано «Шаблон за замовчуванням». Збережіть.

У налаштуваннях доповнення-шаблону (а не в загальних налаштуваннях магазину) у 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.
Особливості Twig 3

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.

Контрольний список перенесення#

  1. Розгорніть тестову копію магазину на ocStore 3.0.4.1 зі стандартною темою.
  2. Створіть теку catalog/view/theme/mytheme/ і перенесіть туди stylesheet/, image/ та JavaScript теми.
  3. Конвертуйте шаблони .tpl → .twig, починаючи з common/header, common/footer, common/home, а потім product/*, checkout/*, account/*. Шаблони, які тема не змінювала, не переносьте — спрацює файл з default.
  4. Замініть у шаблонах <?php … ?>-виклики об'єктів ядра на змінні, підготовлені контролером або подією.
  5. Увімкніть тему: Доповнення → Доповнення → Шаблони → Шаблон за замовчуванням → Директорія шаблона.
  6. Перенесіть розміри зображень і кількість товарів у налаштування доповнення-шаблону.
  7. Пройдіть основні сценарії: головна, категорія, товар, пошук, кошик, оформлення замовлення, особистий кабінет — і переглядайте Система → Обслуговування → Журнал помилок.
  8. Перевірте сторонні модулі теми: їхні шаблони теж мають бути в .twig і лежати в template/extension/module/.
Сторінку перекладено й адаптовано для ocStore з офіційної документації OpenCart. Ліцензія — GNU GPL v3.