<? phpukraine СПІВБЕСІДИ
⌕Пошук по платформі
WORDPRESS · MIDDLE ЧАСТО ПИТАЮТЬ

Як кастомізувати WooCommerce без правок ядра: хуки, шаблони, власні поля?

Шаблони перевизначають копією у `wp-content/themes/{тема}/woocommerce/` зі збереженням шляху й контролем `@version`, логіку чіпляють на хуки WooCommerce, а дані пишуть через CRUD (`$product->update_meta_data()`, `$order->update_meta_data()`), бо при HPOS замовлення більше не живуть у `wp_posts`. Класичний і блоковий checkout мають різні точки розширення.

Після оновлення WooCommerce магазин зламався, хоча ми нічого не чіпали в плагіні. Як таке можливе?
Куди покласти правлений single-product.php, щоб його не затерло?
Ми додали поле «коментар до подарунка» через woocommerce_checkout_fields, а на новому сайті його немає. Чому?
Як дописати дані в замовлення так, щоб працювало і з HPOS?
WooCommerce хуки шаблони HPOS checkout blocks

Будь-яка кастомізація WooCommerce починається з wc_get_template(). Функція отримує ім'я шаблону й через wc_locate_template() шукає його спершу в {дочірня тема}/woocommerce/{шлях}, потім у {батьківська тема}/woocommerce/{шлях}, і тільки якщо не знайшла, бере файл із plugins/woocommerce/templates/. Звідси й вимога копіювати зі збереженням відносного шляху: так влаштований пошук. templates/cart/cart-totals.php мусить стати themes/mytheme/woocommerce/cart/cart-totals.php, інакше ваш файл ніхто не побачить. У шапці кожного шаблону стоїть @version, а сторінка WooCommerce → Статус порівнює її з версією в плагіні й показує список копій, які відстали. Інших вбудованих запобіжників проти тихо зламаного магазину після апдейту немає, тож заглядайте туди після кожного мінорного оновлення.

Копія шаблону дорога в супроводі, тому спершу шукають хук. Розмітка каталогу й картки товару майже вся зібрана з actions: woocommerce_before_shop_loop_item, woocommerce_single_product_summary, woocommerce_after_add_to_cart_button. Переставити блоки можна remove_action + add_action з іншим пріоритетом, не торкаючись файлів. У кошику й оформленні підхід той самий, набір хуків інший: woocommerce_before_calculate_totals дає змінити ціну позиції, woocommerce_cart_calculate_fees додає доплату або знижку окремим рядком, woocommerce_package_rates фільтрує методи доставки, woocommerce_checkout_create_order дозволяє дописати дані в замовлення до збереження. Різниця, на якій легко обпектися: woocommerce_cart_calculate_fees виконується на кожному перерахунку totals, а woocommerce_add_cart_item_data лише один раз, при додаванні в кошик. Порахуєте доплату в другому - отримаєте суму, яка не реагує на зміну кількості.

Власні поля товару й власні поля замовлення роблять по-різному. Для товару це хелпери woocommerce_wp_text_input() і сусідні, повішені на woocommerce_product_options_general_product_data, зі збереженням на woocommerce_admin_process_product_object, куди приходить уже готовий WC_Product. Із замовленнями складніше. Після появи HPOS (High-Performance Order Storage, типово увімкнене для нових інсталяцій з WooCommerce 8.2) даних у wp_postmeta просто немає: замовлення лежать у wp_wc_orders і wp_wc_orders_meta. Код, який робить get_post_meta($order_id, '_gift_note', true), повертає порожній рядок і не падає, тому баг знаходять уже на проді. Працює тільки CRUD: $order->get_meta(), $order->update_meta_data(), $order->save(). Плагін ще має заявити сумісність через Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true) на хуку before_woocommerce_init, інакше WooCommerce покаже попередження й у магазина не буде можливості увімкнути HPOS.

Найсильніше старі статті розходяться з реальністю на checkout. Класична сторінка це shortcode [woocommerce_checkout] плюс шаблони checkout/form-*.php, де форма збирається на PHP і все кастомізується фільтром woocommerce_checkout_fields. Блоковий checkout, типовий для нових магазинів з WooCommerce 8.3, рендериться React'ом, а дані ходять через Store API, тому на нього не впливають ні ці фільтри, ні копії шаблонів. Власні поля там реєструють через woocommerce_register_additional_checkout_field() (з'явилось у WooCommerce 8.9) з локаціями contact, address або order, валідують фільтром woocommerce_validate_additional_field, а в замовлення дописують на woocommerce_store_api_checkout_update_order_from_request. Перевірити, який режим у клієнта, можна за вмістом сторінки оформлення: shortcode чи блок woocommerce/checkout.

Вирішує тут шар, на якому написаний код. Усе, що живе в WC_Cart, WC_Order і класах доставки та оплати, працює однаково в обох режимах, бо це серверна логіка: комісії, тарифи, статуси, листи, інтеграції. Все, що стосується форми й верстки checkout, доводиться писати двічі або свідомо лишатись на shortcode. Для магазину, який уже їде на класичному оформленні з десятком правок, міграція на блоки це окремий проєкт із переписуванням, а не галочка в налаштуваннях, і оцінювати його треба саме так.

// 1. Поле товару: вкладка «Загальні» в картці товару
add_action('woocommerce_product_options_general_product_data', function (): void {
    woocommerce_wp_text_input([
        'id'                => '_engraving_fee',
        'label'             => 'Доплата за гравіювання, грн',
        'type'              => 'number',
        'custom_attributes' => ['step' => '0.01', 'min' => '0'],
    ]);
});

// Зберігаємо через CRUD: об'єкт уже готовий, save() викличе WooCommerce
add_action('woocommerce_admin_process_product_object', function (WC_Product $product): void {
    $fee = wc_clean(wp_unslash($_POST['_engraving_fee'] ?? ''));
    $product->update_meta_data('_engraving_fee', wc_format_decimal($fee));
});

// 2. Кошик: доплата рахується на КОЖНОМУ перерахунку totals,
// тому реагує на зміну кількості й видалення позиції
add_action('woocommerce_cart_calculate_fees', function (WC_Cart $cart): void {
    if (is_admin() && ! wp_doing_ajax()) {
        return;
    }
    $sum = 0.0;
    foreach ($cart->get_cart() as $item) {
        if (! empty($item['engraving'])) {
            $sum += (float) $item['data']->get_meta('_engraving_fee') * $item['quantity'];
        }
    }
    if ($sum > 0) {
        $cart->add_fee('Гравіювання', $sum, true); // true = оподатковувана
    }
});

// 3. Замовлення: при HPOS даних у wp_postmeta немає, пишемо в об'єкт
add_action('woocommerce_checkout_create_order', function (WC_Order $order): void {
    $order->update_meta_data('_gift_note', sanitize_textarea_field(wp_unslash($_POST['gift_note'] ?? '')));
});
// Для блокового checkout той самий запис вішають на інший хук:
// woocommerce_store_api_checkout_update_order_from_request($order, $request)
Що `wc_get_template()` шукає файл спершу в `{child}/woocommerce/`, потім у `{parent}/woocommerce/`, і лише тоді бере плагінний, тому структуру підтек треба повторити точно.
Що в шапці кожного шаблону є `@version`, а WooCommerce → Статус → Шаблони показує застарілі копії; це і є механізм, який ловить поламані оновлення.
Що переважна більшість правок робиться хуками (`woocommerce_cart_calculate_fees`, `woocommerce_checkout_create_order`, `woocommerce_before_calculate_totals`), а копія шаблону це останній засіб, бо її доведеться супроводжувати роками.
Що після HPOS (типово для нових інсталяцій з WooCommerce 8.2) `get_post_meta($order_id, ...)` не бачить даних замовлення, і треба `$order->get_meta()` плюс декларація сумісності `FeaturesUtil::declare_compatibility('custom_order_tables', ...)`.
Що блоковий checkout (типовий з WooCommerce 8.3) не виконує ні шаблони, ні `woocommerce_checkout_fields`, ні `woocommerce_checkout_process`: там Additional Checkout Fields API (WooCommerce 8.9+) і хуки Store API.
Правити файли в `wp-content/plugins/woocommerce/templates/` і втрачати все на першому ж оновленні.
Класти копію шаблону в корінь теми (`themes/mytheme/content-product.php`) замість `themes/mytheme/woocommerce/content/content-product.php`.
Копіювати шаблон цілком заради одного рядка, хоча поруч є готовий хук на кшталт `woocommerce_after_shop_loop_item`.
Зберігати мета товару через `update_post_meta()` у `woocommerce_process_product_meta`, ігноруючи CRUD і об'єктний кеш `WC_Product`.
Читати мета замовлення через `get_post_meta()` і дивуватись порожнім значенням на магазині з HPOS.
Рахувати доплату в `woocommerce_add_cart_item_data` один раз замість `woocommerce_cart_calculate_fees`, після чого сума не перераховується при зміні кількості.
Забути `add_theme_support('woocommerce')` у власній темі й отримати каталог без стилів і без обгорток.
ПОРАДА

Назвіть сходинку пріоритетів: налаштування → хук → фільтр рядка/ціни → і тільки потім копія шаблону. Окремо згадайте, що для блокового checkout ваш звичний набір фільтрів не працює, і покажіть, що знаєте `woocommerce_register_additional_checkout_field()` та `woocommerce_store_api_checkout_update_order_from_request`. Це та деталь, яка відрізняє людину з бойовим магазином від людини з туторіалу 2019 року.

оновлено 3 жовтня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

`wc_locate_template()` шукає файл за тим самим відносним шляхом усередині теки `woocommerce/` активної теми, тому `templates/single-product/title.php` стає `themes/mytheme/woocommerce/single-product/title.php`. Заголовок `@version` потрібен, щоб WooCommerce → Статус показав, коли ваша копія відстала від плагіна.