Перейти до основного вмісту

Правки у встановленому модулі

Модуль на сайті — це копія пакета. Оновлення замінює цю копію, тож будь-який відредагований файл у app/Modules/<Name>/ тимчасовий за визначенням: наступний реліз модуля — або нічний cms9:auto-update — принесе свої файли.

Мовчки ECMS9 цього не зробить. Кожен пакет везе .baseline.json (sha256 на кожен файл, пише збирач пакетів), і інсталятор звіряє з ним дерево, перш ніж щось чіпати:

  • файли, які відрізняються або додані, зупиняють оновлення з local_changes і перелічуються поіменно;
  • автооновлювач пропускає модуль і піднімає банер в адмінці замість перезапису;
  • правки записуються патчем — див. нижче, — щоб їх можна було перенести в нову версію.

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

Подивитись, що змінив цей сайт: cms9:module-drift​

php spark cms9:module-drift # усі модулі з baseline
php spark cms9:module-drift my_module # один модуль
php spark cms9:module-drift my_module --diff # ... і самі правки
php spark cms9:module-drift --all --json # машинний вивід

Команда лише читає: вона ніколи не пише в модуль, а без --diff не робить жодного мережевого запиту — відповідь бере з .baseline.json на диску.

Коди виходу зроблені для CI і скриптів розкатки:

кодщо означає
0нічого не відрізняється від пакетів
1щонайменше один модуль правили на цьому сайті
7помилка виклику (невідома назва модуля)

Що --diff може показати, а що ні​

--diff потрібні оригінальні байти саме тієї версії, що стоїть тут. Сайт їх не зберігає (.baseline.json тримає хеші, не файли), тож вони тягнуться з каталогу — а download каталогу віддає лише найновіший пакет модуля. Два наслідки, і обидва проговорені у виводі, а не обійдені здогадкою:

  • якщо на сайті версія старіша за каталог, оригіналу тієї ж версії в каталозі нема: команда каже це прямо й перелічує файли без diff;
  • download штампує last_seen у рядку ліцензії на сервері оновлень, тож --diff (і тільки він) чіпає ліцензію. Звичайні прогони — ні.

Завантажений пакет розпаковується в writable/store_drift_tmp/ і прибирається, коли команда завершується.

Патч: writable/store_local_changes/​

Обидва моменти, коли оновлення зустрічає руками правлений модуль, пишуть writable/store_local_changes/<Studly>-<версія>-<дата>.patch:

  • оновлення відмовлено (local_changes) — з CLI, з вкладки «Магазин» чи з автоматичного прогону cms9:auto-update;
  • оновлення свідомо перезаписало правки (--overwrite-local або «Оновити все одно» у вкладці «Магазин»).

Файл — unified diff зі шляхами a/ b/ відносно кореня модуля:

cd app4/app/Modules/MyModule
patch -p1 --dry-run < /шлях/до/writable/store_local_changes/MyModule-1.0.4-20260924-011500.patch
# конфліктів нема → запустіть ще раз без --dry-run
# конфлікти → файли *.rej тримають хунки, за якими треба рішення

Коментар-шапка в самому патчі повторює ці два рядки, називає модуль і версію та каже, чому патч написано.

Там, де оригінал не доводиться — каталог уже не має тієї версії або файл бінарний, — нічого не вигадується: увесь змінений файл кладеться поруч із патчем (<та сама назва>/files/… плюс manifest.json), а патч перелічує ці файли. Патч, який вгадав оригінал, наклався б чисто й дав неправильний код — це гірше, ніж відсутність патча.

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

Запис патча робиться за принципом «як вийде». Не вийшло — оновлення однаково їде (або однаково відмовлене), а в лог іде попередження: патч не має права бути причиною, через яку сайт не може оновитись.

Перезапис лишає дві копії

--overwrite-local ще й переносить усе змінене дерево в writable/store_backups/<Studly>-<дата>/, а cms9:module-rollback його повертає. Патч — переносна форма того самого: бекап — це старий модуль, патч — лише ваша частина в ньому.

Куди перенести правки, щоб вони жили​

Патч, який накладають після кожного релізу, — рутина, а не рішення. Три підтримані місця для зміни:

1. Тема, коли зміна — це подача​

Шаблони, розмітка, CSS і JS вітрини належать темі, а тема не входить у жоден пакет — вона переживає будь-яке оновлення ядра й модулів. Віджет модуля, який має виглядати інакше, перекривають у темі, а не правлять у app/Modules/.

2. Власний маленький модуль, що слухає події​

Це загальна відповідь для поведінки. Власний модуль ніколи не конфліктує з оновленням чужого, а auto-discovery CI4 підхоплює його Config/Events.php, щойно простір імен відомий:

// app/Modules/MyShopTweaks/Config/Events.php
use CodeIgniter\Events\Events;

Events::on('order_placed', static function (array $payload): void {
// Слухач не має права зламати операцію, яку спостерігає.
if (! module_enabled('my_shop_tweaks')) {
return;
}

try {
service('myTweaks')->exportOrder((int) $payload['order_id']);
} catch (\Throwable $e) {
log_message('error', 'my_shop_tweaks: ' . $e->getMessage());
}
});

Розмітка вітрини додається так само — через точки рендеру, слухачем storefront_render_point:<name>, а не правкою в'юх.

3. Попросити подію в модулі​

Якщо модуль, який треба змінити, не кидає події там, де вона потрібна, — правильна правка це подія в тому модулі, випущена разом із ним, а не локальна правка його коду. Це маленька зміна, яку видно на рев'ю і яку потім отримує кожен сайт; різниця між однорядковим патчем у модулі й патчем, який ви накладаєте вічно.

Чеклист​

  • Правили модуль, щоб щось запрацювало? Прогоніть cms9:module-drift --diff і збережіть вивід — це і є специфікація того, що треба перенести.
  • Переносите в тему чи власний модуль? Робіть це до наступного релізу правленого модуля, а не після.
  • Перенести не можна (події ще нема)? Заведіть задачу на подію й очікуйте, що оновлення зупинятиметься на local_changes, доки її нема, — з патчем, що чекає в writable/store_local_changes/.
  • Наклали патч? Прогоніть cms9:module-drift ще раз: він має показати рівно ті файли, які ви свідомо лишили, і нічого більше.

Див. також: Структура модуля, Точки рендеру, Магазин і оновлення.