Система модифікацій OCMOD
Що таке OCMOD#
OCMOD — вбудована в ocStore система модифікацій, яка дає змогу змінювати код ядра, не редагуючи самі файли ядра. Модифікатор — це XML-файл з інструкціями на кшталт «знайди в такому-то файлі цей рядок і встав після нього ось цей код». Під час оновлення кешу модифікацій ocStore читає оригінальні файли, застосовує до них усі ввімкнені модифікатори й записує змінені копії в окрему теку кешу. Саме ці копії потім і виконуються замість оригіналів.
Завдяки цьому:
- оригінальні файли ядра лишаються незмінними — після видалення модифікатора нічого не треба відновлювати вручну;
- до одного файлу можна застосувати кілька модифікаторів від різних доповнень;
- оновлення ocStore не затирає ваші зміни (хоча модифікатор може перестати «знаходити» потрібний рядок, якщо код ядра змінився).
OCMOD створено як спрощену версію системи vQmod. Порівняно з нею тут немає позицій top, bottom, ibefore, iafter, зате є replace, before, after, регулярні вирази, offset, index, limit, ignoreif та обробка помилок через атрибут error.
Якщо потрібного результату можна досягти через систему подій, віддавайте перевагу подіям: вони не залежать від конкретних рядків коду й рідше ламаються після оновлень. OCMOD використовуйте тоді, коли потрібно змінити логіку всередині методу чи розмітку шаблону, до якої подіями не дістатися.
Як OCMOD працює в 3.0.4.1#
Розуміння механізму допомагає уникнути більшості проблем.
- Модифікатори зберігаються в базі даних (таблиця
oc_modification). Туди вони потрапляють під час встановлення пакета через Доповнення → Установка доповнень. - Коли ви натискаєте Оновити в розділі Доповнення → Модифікатори, ocStore:
- вмикає режим обслуговування (на час оновлення) і очищає журнал
ocmod.log; - видаляє весь попередній кеш модифікацій;
- по черзі застосовує модифікатори та записує змінені файли в теку кешу модифікацій;
- повертає режим обслуговування в попередній стан.
- вмикає режим обслуговування (на час оновлення) і очищає журнал
- Під час виконання ocStore для кожного файлу перевіряє, чи є його змінена копія в кеші модифікацій, і якщо є — підключає її замість оригіналу.
Модифікатори застосовуються в такому порядку:
- Базовий модифікатор ядра
system/modification.xml(він «вчить» завантажувач ядра підхоплювати змінені копії файлів — не видаляйте й не редагуйте його). - Файли розробника
system/*.ocmod.xml(див. Режим розробки). - Увімкнені модифікатори з бази даних — в алфавітному порядку їхніх назв (
<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> нічого не знайшов (див. Обробка помилок). |
Тег 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! |
Після abort ocStore не лише скасовує поточний модифікатор, а й припиняє обробку всіх наступних за алфавітом модифікаторів — вони під час цього оновлення не застосуються взагалі. Використовуйте abort лише тоді, коли часткове застосування справді небезпечне, а для необов'язкових операцій вказуйте error="skip".
Встановлення модифікатора#
- Запакуйте
install.xml(і за потреби текуupload/) у ZIP-архів з назвою на.ocmod.zip, наприкладmy_first_mod.ocmod.zip. Файли мають лежати в корені архіву, а не у вкладеній теці. - Відкрийте Доповнення → Установка доповнень, натисніть Завантажити та виберіть архів.
- Зачекайте, поки смуга процесу дійде до кінця. Інсталятор розпакує архів, перенесе файли з
upload/, збережеinstall.xmlу базі даних і видалить тимчасові файли. Пакет з'явиться в Історії установок. - Перейдіть у Доповнення → Модифікатори і натисніть синю кнопку Оновити. Без цього кроку зміни не набудуть чинності.
Розмір архіву обмежено параметрами 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>
Діагностика проблем#
Модифікатор встановлено, але змін не видно
- Натисніть Оновити в Доповнення → Модифікатори.
- Переконайтеся, що модифікатор увімкнений.
- Відкрийте вкладку Лог і знайдіть свій модифікатор: чи немає там
NOT FOUND. - Очистіть кеш шаблонів: Панель стану → кнопка з шестернею («Налаштування розробника») → Шаблон.
- Для шаблонів вітрини перевірте, чи немає перевизначення в Дизайн → Редактор шаблонів і чи змінюєте ви шаблон саме тієї теми, яка активна.
Після оновлення модифікаторів сайт показує білий екран або фатальну помилку
Натисніть Очистити в Доповнення → Модифікатори — магазин повернеться до оригінальних файлів. Потім вимкніть останній встановлений модифікатор, натисніть Оновити й перевірте журнал помилок у Система → Обслуговування → Журнал помилок. Якщо адмінка недоступна, видаліть вміст теки storage/modification/ через FTP (крім index.html).
Після оновлення ocStore з'явилася помилка про відсутній клас Twig чи інший клас ядра
У кеші модифікацій лишилися копії файлів від попередньої версії. Видаліть вміст storage/modification/ (через кнопку Очистити або FTP), а потім натисніть Оновити. Модифікатори, несумісні з новою версією, вимкніть.
Модифікатор від іншого доповнення перестав працювати після встановлення мого
Модифікатори застосовуються за алфавітом назв. Можливо, ваш змінив рядок, який шукає наступний модифікатор, або ваш спрацював з error="abort" і зупинив обробку решти. Перегляньте журнал і за потреби використайте ignoreif, error="skip" або інший, стабільніший рядок для пошуку.
Поради#
- Шукайте короткі, стабільні й унікальні фрагменти коду. Чим довший рядок пошуку, тим вища ймовірність, що він зміниться в наступній версії.
- Для необов'язкових змін вказуйте
error="skip", щоб невдалий пошук не зупиняв інші операції. - Не покладайтеся на
offsetіindexбез нагальної потреби — вони крихкі. - Виносьте нову логіку в окремі файли доповнення (контролери, моделі в
extension/), а через OCMOD додавайте лише мінімальний «місток» до них. - Після кожної зміни дивіться журнал модифікацій.