Система модифікацій OCMOD

Що таке OCMOD#

OCMOD — вбудована в ocStore система модифікацій, яка дає змогу змінювати код ядра, не редагуючи самі файли ядра. Модифікатор — це XML-файл з інструкціями на кшталт «знайди в такому-то файлі цей рядок і встав після нього ось цей код». Під час оновлення кешу модифікацій ocStore читає оригінальні файли, застосовує до них усі ввімкнені модифікатори й записує змінені копії в окрему теку кешу. Саме ці копії потім і виконуються замість оригіналів.

Завдяки цьому:

  • оригінальні файли ядра лишаються незмінними — після видалення модифікатора нічого не треба відновлювати вручну;
  • до одного файлу можна застосувати кілька модифікаторів від різних доповнень;
  • оновлення ocStore не затирає ваші зміни (хоча модифікатор може перестати «знаходити» потрібний рядок, якщо код ядра змінився).

OCMOD створено як спрощену версію системи vQmod. Порівняно з нею тут немає позицій top, bottom, ibefore, iafter, зате є replace, before, after, регулярні вирази, offset, index, limit, ignoreif та обробка помилок через атрибут error.

Події чи OCMOD?

Якщо потрібного результату можна досягти через систему подій, віддавайте перевагу подіям: вони не залежать від конкретних рядків коду й рідше ламаються після оновлень. OCMOD використовуйте тоді, коли потрібно змінити логіку всередині методу чи розмітку шаблону, до якої подіями не дістатися.

Як OCMOD працює в 3.0.4.1#

Розуміння механізму допомагає уникнути більшості проблем.

  1. Модифікатори зберігаються в базі даних (таблиця oc_modification). Туди вони потрапляють під час встановлення пакета через Доповнення → Установка доповнень.
  2. Коли ви натискаєте Оновити в розділі Доповнення → Модифікатори, ocStore:
    • вмикає режим обслуговування (на час оновлення) і очищає журнал ocmod.log;
    • видаляє весь попередній кеш модифікацій;
    • по черзі застосовує модифікатори та записує змінені файли в теку кешу модифікацій;
    • повертає режим обслуговування в попередній стан.
  3. Під час виконання ocStore для кожного файлу перевіряє, чи є його змінена копія в кеші модифікацій, і якщо є — підключає її замість оригіналу.

Модифікатори застосовуються в такому порядку:

  1. Базовий модифікатор ядра system/modification.xml (він «вчить» завантажувач ядра підхоплювати змінені копії файлів — не видаляйте й не редагуйте його).
  2. Файли розробника system/*.ocmod.xml (див. Режим розробки).
  3. Увімкнені модифікатори з бази даних — в алфавітному порядку їхніх назв (<name>).

Кожен наступний модифікатор працює з уже зміненим попередніми модифікаторами вмістом файлу.

Кеш модифікацій#

Змінені копії файлів записуються в теку modification/ усередині сховища (storage). За замовчуванням це system/storage/modification/, але якщо ви перенесли storage за межі кореня сайту (як рекомендовано), тека буде за новим шляхом — його визначає константа DIR_MODIFICATION у файлах config.php та admin/config.php.

Структура кешу повторює структуру магазину:

storage/modification/
├── admin/controller/common/column_left.php
├── catalog/controller/product/product.php
├── catalog/view/theme/default/template/product/product.twig
└── system/library/...

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

Ніколи не редагуйте файли в кеші модифікацій

Усе, що лежить у storage/modification/, повністю перезаписується під час кожного натискання Оновити. Зміни, внесені туди вручну, буде втрачено. Редагуйте XML модифікатора.

Які файли можна модифікувати#

  • Шлях у модифікаторі має починатися з admin/, catalog/ або system/. Файли поза цими теками (наприклад, кореневий index.php, config.php, тека install/) OCMOD не змінює.
  • Для адмінки завжди пишіть admin/, навіть якщо тека адмінки на сервері перейменована, — шлях підставляється автоматично.
  • Змінені копії підхоплюються для контролерів, моделей, мовних файлів, файлів system/config/, бібліотек system/library/ і шаблонів Twig адмінки й вітрини.
  • Файли, які ядро підключає напряму під час старту, не беручи до уваги кеш модифікацій, змінити через OCMOD не вийде. Зокрема це system/startup.php, system/framework.php і помічники system/helper/general.php та system/helper/utf8.php.
Шаблони, змінені в «Редакторі шаблонів»

Якщо шаблон вітрини редагували в Дизайн → Редактор шаблонів, у базі даних зберігається його перевизначення. Під час показу сторінки ocStore 3.0.4.1 бере код саме з цього перевизначення, тому зміни OCMOD для такого шаблону на вітрині не з'являться. Якщо модифікатор «не працює» для якогось шаблону, перевірте, чи немає для нього перевизначення в Редакторі шаблонів.

Структура пакета .ocmod.zip#

Модифікатор зазвичай постачається в складі пакета доповнення — ZIP-архіву, назва якого обов'язково закінчується на .ocmod.zip:

my_extension.ocmod.zip
├── install.xml        ← модифікатор OCMOD (необов'язковий)
└── upload/            ← файли, що копіюються в корінь магазину (необов'язково)
    ├── admin/...
    ├── catalog/...
    └── ...
  • install.xml — XML-модифікатор. Має лежати в корені архіву й називатися саме так.
  • upload/ — файли, які інсталятор переносить у магазин зі збереженням шляхів.

Пакет може містити лише install.xml (чиста модифікація), лише upload/ (доповнення без модифікацій) або обидва.

Інсталятор копіює з upload/ файли лише в такі дозволені теки:

Розділ Дозволені теки
Адмінка admin/controller/extension/, admin/language/, admin/model/extension/, admin/view/image/, admin/view/javascript/, admin/view/stylesheet/, admin/view/template/extension/
Вітрина catalog/controller/extension/, catalog/language/, catalog/model/extension/, catalog/view/javascript/, catalog/view/theme/
Система system/config/, system/library/
Зображення image/catalog/

Якщо в архіві є файл поза цими теками, встановлення зупиниться з помилкою «Каталог … не доступний для запису». Змінювати файли ядра за межами цих тек можна тільки через install.xml.

Довідник XML#

Мінімальний приклад#

<?xml version="1.0" encoding="utf-8"?>
<modification>
    <name>Мій перший модифікатор</name>
    <code>my_first_mod</code>
    <version>1.0</version>
    <author>Ваше ім'я</author>
    <link>https://example.com</link>
    <file path="catalog/controller/common/footer.php">
        <operation>
            <search><![CDATA[$data['powered'] = sprintf(]]></search>
            <add position="after"><![CDATA[
        $data['powered'] .= ' | Працює на ocStore';
            ]]></add>
        </operation>
    </file>
</modification>

Заголовок модифікатора#

Тег Обов'язковий Опис
<modification> так Кореневий елемент.
<name> так Назва, яку видно в списку модифікаторів. Від неї залежить порядок застосування (за алфавітом).
<code> так Унікальний ідентифікатор. Без нього встановлення завершиться помилкою «Модифікатор потребує унікального ідентифікаційного коду». Якщо модифікатор з таким самим code уже встановлено, старий буде замінено новим.
<version> ні Версія модифікатора.
<author> ні Автор.
<link> ні Посилання на сайт автора чи сторінку доповнення (кнопка «Посилання» у списку).
Порада

Робіть code унікальним і стабільним, наприклад з префіксом свого бренду: mybrand_product_badges. Саме за ним ocStore розпізнає оновлення модифікатора.

Тег file#

Визначає файл або групу файлів, до яких застосовуються вкладені операції. Підтримується лише атрибут path.

<file path="catalog/controller/product/product.php">

Кілька файлів перелічуються через вертикальну риску | (не через кому):

<file path="catalog/controller/product/product.php|catalog/controller/product/category.php">

Шлях обробляється PHP-функцією glob() з прапорцем GLOB_BRACE, тому можна використовувати шаблони * і фігурні дужки:

<file path="catalog/controller/extension/module/*.php">
<file path="system/{engine,library}/{action,loader,config,language}*.php">

Для шаблонів Twig вказуйте повний шлях до файлу теми:

<file path="catalog/view/theme/default/template/product/product.twig">
<file path="admin/view/template/catalog/product_form.twig">
Примітка

Якщо за шляхом не знайдено жодного файлу, <file> просто пропускається — без помилки в журналі. Перевіряйте шляхи уважно.

В одному модифікаторі може бути скільки завгодно тегів <file>, а в кожному з них — скільки завгодно <operation>.

Тег operation#

Одна операція «знайти — змінити». Містить рівно один <search>, один <add> і необов'язковий <ignoreif>.

Атрибут Значення Опис
error skip, abort або не вказано Що робити, якщо <search> нічого не знайшов (див. Обробка помилок).

Що шукати. Текст бажано загортати в <![CDATA[ … ]]>, щоб не екранувати символи <, > і &.

Атрибут Значення За замовчуванням Опис
trim true / false true Обрізати пробіли й переведення рядків на початку та в кінці шуканого тексту.
regex true / false false Шукати за регулярним виразом (див. Регулярні вирази).
index номери через кому усі збіги Застосувати операцію лише до вказаних збігів. Нумерація з нуля: index="0" — перший збіг, index="0,2" — перший і третій.
limit число усі збіги Лише для regex="true": максимальна кількість замін.
Пошук ведеться в межах одного рядка

У звичайному режимі (без regex) ocStore перевіряє файл рядок за рядком: операція спрацьовує на кожному рядку, який містить шуканий фрагмент. Тому текст у <search> має бути частиною одного рядка коду. Багаторядковий пошук ніколи нічого не знайде — для нього використовуйте regex="true".

Інші особливості звичайного пошуку:

  • Порівняння не враховує регістр символів.
  • Шуканий текст може бути лише фрагментом рядка — не обов'язково весь рядок.
  • index рахує рядки зі збігом, а не окремі входження всередині рядка.

Тег add#

Що вставити або на що замінити.

Атрибут Значення За замовчуванням Опис
position replace, before, after replace Куди додати код відносно знайденого рядка.
offset число 0 Зсув у рядках (див. нижче).
trim true / false false Обрізати пробіли й переведення рядків на початку та в кінці коду.

Зверніть увагу: для <add> значення trim за замовчуванням — false. Якщо в CDATA є переведення рядка на початку чи в кінці, у файл потрапить порожній рядок. Зазвичай це не шкодить, але для вставки всередину рядка (у режимі replace) вказуйте trim="true".

Позиції#

replace — замінює знайдений фрагмент у рядку на вміст <add>. Решта рядка лишається без змін.

<operation>
    <search><![CDATA[$data['tags'] = array();]]></search>
    <add position="replace" trim="true"><![CDATA[$data['tags'] = array(); // змінено модифікатором]]></add>
</operation>
Увага

Хоча пошук не враховує регістр, сама заміна в режимі replace його враховує. Якщо регістр у <search> відрізняється від коду у файлі, рядок буде «знайдено», але не змінено. Копіюйте шуканий фрагмент із файлу точно.

before — вставляє вміст <add> окремими рядками перед знайденим рядком.

after — вставляє вміст <add> окремими рядками після знайденого рядка.

<operation>
    <search><![CDATA[<div class="tab-pane active" id="tab-description">{{ description }}</div>]]></search>
    <add position="before"><![CDATA[
            <div class="alert alert-info">Безкоштовна доставка від 1000 грн!</div>
    ]]></add>
</operation>

Атрибут offset#

Позиція Додатне значення offset="N"
before Вставити код на N рядків вище знайденого.
after Вставити код на N рядків нижче знайденого.
replace Замінити фрагмент у знайденому рядку й видалити N наступних рядків.

Для replace можна вказати від'ємне значення: offset="-N" видаляє N рядків перед знайденим. Користуйтеся зсувом обережно: він прив'язує модифікатор до точної кількості рядків, і будь-яка зміна коду ядра може зламати результат.

<!-- Вставити код на 2 рядки нижче знайденого рядка -->
<operation>
    <search><![CDATA[$data['review_status'] = $this->config->get('config_review_status');]]></search>
    <add position="after" offset="2"><![CDATA[
            // цей код з'явиться на два рядки нижче знайденого
    ]]></add>
</operation>

Регулярні вирази #

Якщо в <search> вказано regex="true", пошук і заміна виконуються функцією PHP preg_replace() по всьому вмісту файлу одразу, а не по рядках. Це єдиний спосіб знайти багаторядковий фрагмент.

  • У <search> записується повний регулярний вираз разом з розділювачами й модифікаторами, наприклад ~...~s або /.../i.
  • У <add> — рядок заміни; можна використовувати групи $1, $2 тощо.
  • Текст в обох тегах завжди обрізається (trim).
  • Атрибути position, offset, index і trim у цьому режимі ігноруються; доступний лише limit.
<operation>
    <search regex="true" limit="1"><![CDATA[~\$data\['powered'\] = sprintf\((.*?)\);~]]></search>
    <add><![CDATA[$data['powered'] = sprintf($1) . ' | Працює на ocStore';]]></add>
</operation>

Приклад із самого ядра (system/modification.xml) — обгортання всіх require/include у функцію modification():

<file path="system/{engine,library}/{action,loader,config,language}*.php|system/library/template/template.php">
    <operation>
        <search regex="true">
            <![CDATA[~(require|include)(_once)?\(([^)]+)~]]>
        </search>
        <add position="replace">
            <![CDATA[$1$2(modification($3)]]>
        </add>
    </operation>
</file>
Порада

Перевіряйте регулярні вирази заздалегідь, наприклад на regex101.com з вибраним варіантом PCRE (PHP). Помилка в синтаксисі виразу призводить до попередження PHP, а змінена копія файлу в кеші може виявитися порожньою — і сторінка перестане працювати.

Тег ignoreif#

Необов'язковий тег усередині <operation>. Якщо його текст уже є у файлі (з урахуванням змін попередніх модифікаторів), операція пропускається. Це зручно, щоб не вставити той самий код двічі, наприклад коли інше доповнення вже зробило таку саму зміну.

<operation>
    <ignoreif><![CDATA[$data['powered'] .= ' | Працює на ocStore';]]></ignoreif>
    <search><![CDATA[$data['powered'] = sprintf(]]></search>
    <add position="after"><![CDATA[
        $data['powered'] .= ' | Працює на ocStore';
    ]]></add>
</operation>

З атрибутом regex="true" вміст <ignoreif> вважається регулярним виразом (з розділювачами):

<ignoreif regex="true"><![CDATA[~Працює на ocStore~]]></ignoreif>

Пропущена через ignoreif операція не вважається помилкою.

Обробка помилок #

Якщо <search> нічого не знайшов, поведінку визначає атрибут error тегу <operation>:

Значення Що відбувається Запис у журналі
не вказано (або будь-яке інше значення, наприклад log) Решта операцій для цього файлу в цьому модифікаторі пропускаються. Зміни, які вже встигли внести, зберігаються; обробка інших файлів триває. NOT FOUND - OPERATIONS ABORTED!
skip Пропускається лише ця операція, наступні виконуються як звичайно. NOT FOUND - OPERATION SKIPPED!
abort Усі зміни цього модифікатора скасовуються (файли повертаються до стану після попередніх модифікаторів). NOT FOUND - ABORTING!
Особливість error="abort" у 3.0.4.1

Після abort ocStore не лише скасовує поточний модифікатор, а й припиняє обробку всіх наступних за алфавітом модифікаторів — вони під час цього оновлення не застосуються взагалі. Використовуйте abort лише тоді, коли часткове застосування справді небезпечне, а для необов'язкових операцій вказуйте error="skip".

Встановлення модифікатора#

  1. Запакуйте install.xml (і за потреби теку upload/) у ZIP-архів з назвою на .ocmod.zip, наприклад my_first_mod.ocmod.zip. Файли мають лежати в корені архіву, а не у вкладеній теці.
  2. Відкрийте Доповнення → Установка доповнень, натисніть Завантажити та виберіть архів.
  3. Зачекайте, поки смуга процесу дійде до кінця. Інсталятор розпакує архів, перенесе файли з upload/, збереже install.xml у базі даних і видалить тимчасові файли. Пакет з'явиться в Історії установок.
  4. Перейдіть у Доповнення → Модифікатори і натисніть синю кнопку Оновити. Без цього кроку зміни не набудуть чинності.
Інформація

Розмір архіву обмежено параметрами PHP upload_max_filesize і post_max_size. Якщо попередня установка не завершилася, інсталятор попросить зачекати кілька секунд перед повторною спробою.

Щоб видалити доповнення разом з його файлами, скористайтеся кнопкою видалення в Історії установок на сторінці Доповнення → Установка доповнень: ocStore видалить скопійовані з upload/ файли (крім файлів у system/config/) і модифікатори цього пакета. Після цього знову натисніть Оновити в Модифікаторах.

Увага

Видалення з Історії установок прибирає всі файли, записані пакетом, — зокрема й ті, що під час встановлення перезаписали наявні файли. Не кладіть в upload/ змінені копії стандартних файлів: для змін ядра використовуйте install.xml.

Керування в розділі «Модифікатори»#

На сторінці Доповнення → Модифікатори зібрано всі встановлені модифікатори.

Кнопки над списком:

  • Оновити (синя, зі стрілками) — перебудувати кеш модифікацій з урахуванням усіх увімкнених модифікаторів. Натискайте її після кожного встановлення, увімкнення, вимкнення, редагування чи видалення модифікатора.
  • Очистити (помаранчева, з гумкою) — видалити кеш модифікацій. Магазин працюватиме з оригінальними файлами, доки ви знову не натиснете Оновити. Це зручно, щоб швидко перевірити, чи не модифікатор спричиняє помилку.
  • Видалити (червона) — видалити вибрані модифікатори з бази даних.

Кнопки в рядку кожного модифікатора:

  • Редагувати — відкрити вбудований редактор XML-коду модифікатора. Кнопка Застосувати зміни зберігає код і одразу оновлює кеш. Перед кожним збереженням ocStore робить резервну копію попередньої версії — її можна відновити на вкладці Резервна копія (або очистити всі копії).
  • Завантажити xml файл — зберегти XML модифікатора на комп'ютер.
  • Завантажити — замінити код модифікатора файлом code.ocmod.xml.
  • Посилання — перейти за адресою з тегу <link>.
  • Увімкнути / Вимкнути — змінити статус модифікатора (після цього натисніть Оновити).

Журнал#

Вкладка Лог на сторінці модифікаторів показує вміст файлу ocmod.log (лежить у теці журналів storage/logs/). Журнал перезаписується під час кожного оновлення й містить такі записи:

MOD: Мій перший модифікатор

FILE: catalog/controller/common/footer.php
CODE: $data['powered'] = sprintf(
LINE: 64
----------------------------------------------------------------
Запис Значення
MOD: Назва модифікатора, що обробляється.
FILE: Файл, до якого застосовуються операції. FILE: (sub modification) означає, що файл уже змінено попереднім модифікатором.
CODE: / REGEX: Шуканий текст або регулярний вираз.
LINE: Номер рядка, на якому спрацювала операція (для before/after — з урахуванням уже вставлених рядків).
NOT FOUND - … Пошук нічого не знайшов (див. Обробка помилок).

Кнопка Очистити на вкладці Лог очищає журнал.

Режим розробки #

Щоб не перепаковувати й не перевстановлювати архів після кожної правки, під час розробки покладіть XML-файл безпосередньо в теку system/ з назвою на .ocmod.xml, наприклад system/my_first_mod.ocmod.xml. Такі файли застосовуються при кожному натисканні Оновити без встановлення.

Увага

Файли system/*.ocmod.xml не відображаються в списку модифікаторів, їх не можна вимкнути з адмінки, і вони застосовуються завжди. Після завершення розробки видаліть файл із system/ і встановіть модифікатор звичайним способом через .ocmod.zip, інакше зміни застосуються двічі.

Альтернатива — встановити модифікатор один раз, а далі правити його у вбудованому редакторі (Редагувати → Застосувати зміни).

Приклади#

Додати пункт меню в адмінку#

Приклад наведено в посібнику Доповнення (розділ про OCMOD): вставка нового пункту меню перед блоком «Маркетплейс» у admin/controller/common/column_left.php (рядок if ($marketplace) {).

Кілька операцій, кілька файлів#

<?xml version="1.0" encoding="utf-8"?>
<modification>
    <name>Плашка доставки на сторінці товару</name>
    <code>mybrand_delivery_badge</code>
    <version>1.0</version>
    <author>MyBrand</author>
    <link>https://example.com</link>

    <!-- 1. Контролер: передаємо текст у шаблон -->
    <file path="catalog/controller/product/product.php">
        <operation error="skip">
            <search><![CDATA[$data['tags'] = array();]]></search>
            <add position="before"><![CDATA[
            $data['delivery_badge'] = 'Безкоштовна доставка від 1000 грн';
            ]]></add>
        </operation>
    </file>

    <!-- 2. Шаблон: виводимо плашку над описом -->
    <file path="catalog/view/theme/default/template/product/product.twig">
        <operation error="skip">
            <ignoreif><![CDATA[delivery_badge]]></ignoreif>
            <search><![CDATA[<div class="tab-pane active" id="tab-description">]]></search>
            <add position="before"><![CDATA[
            {% if delivery_badge %}<div class="alert alert-info">{{ delivery_badge }}</div>{% endif %}
            ]]></add>
        </operation>
    </file>
</modification>

Змінити шаблон у кількох темах одразу#

<file path="catalog/view/theme/*/template/common/footer.twig">
    <operation error="skip">
        <search><![CDATA[<p>{{ powered }}</p>]]></search>
        <add position="replace" trim="true"><![CDATA[<p class="text-center">{{ powered }}</p>]]></add>
    </operation>
</file>

Діагностика проблем#

Модифікатор встановлено, але змін не видно
  1. Натисніть Оновити в Доповнення → Модифікатори.
  2. Переконайтеся, що модифікатор увімкнений.
  3. Відкрийте вкладку Лог і знайдіть свій модифікатор: чи немає там NOT FOUND.
  4. Очистіть кеш шаблонів: Панель стану → кнопка з шестернею («Налаштування розробника») → Шаблон.
  5. Для шаблонів вітрини перевірте, чи немає перевизначення в Дизайн → Редактор шаблонів і чи змінюєте ви шаблон саме тієї теми, яка активна.
Після оновлення модифікаторів сайт показує білий екран або фатальну помилку

Натисніть Очистити в Доповнення → Модифікатори — магазин повернеться до оригінальних файлів. Потім вимкніть останній встановлений модифікатор, натисніть Оновити й перевірте журнал помилок у Система → Обслуговування → Журнал помилок. Якщо адмінка недоступна, видаліть вміст теки storage/modification/ через FTP (крім index.html).

Після оновлення ocStore з'явилася помилка про відсутній клас Twig чи інший клас ядра

У кеші модифікацій лишилися копії файлів від попередньої версії. Видаліть вміст storage/modification/ (через кнопку Очистити або FTP), а потім натисніть Оновити. Модифікатори, несумісні з новою версією, вимкніть.

Модифікатор від іншого доповнення перестав працювати після встановлення мого

Модифікатори застосовуються за алфавітом назв. Можливо, ваш змінив рядок, який шукає наступний модифікатор, або ваш спрацював з error="abort" і зупинив обробку решти. Перегляньте журнал і за потреби використайте ignoreif, error="skip" або інший, стабільніший рядок для пошуку.

Поради#

  • Шукайте короткі, стабільні й унікальні фрагменти коду. Чим довший рядок пошуку, тим вища ймовірність, що він зміниться в наступній версії.
  • Для необов'язкових змін вказуйте error="skip", щоб невдалий пошук не зупиняв інші операції.
  • Не покладайтеся на offset і index без нагальної потреби — вони крихкі.
  • Виносьте нову логіку в окремі файли доповнення (контролери, моделі в extension/), а через OCMOD додавайте лише мінімальний «місток» до них.
  • Після кожної зміни дивіться журнал модифікацій.
Сторінку перекладено й адаптовано для ocStore з офіційної документації OpenCart. Ліцензія — GNU GPL v3.