Создание YML-фида для Яндекс.Маркета в WordPress с ACF

При работе с интернет-магазинами на WordPress часто возникает необходимость создания YML-фида для загрузки товаров в Яндекс.Маркет. Если товары в проекте представлены кастомными постами, а их характеристики и вариации хранятся в полях Advanced Custom Fields (ACF), то можно создать YML-фид программно. В этой статье подробно разберём, как это сделать, с примерами кода и рекомендациями.

Подготовка данных

Прежде чем приступить к генерации YML-фида, необходимо убедиться, что все товары содержат необходимые данные. Для этого в ACF должны быть настроены соответствующие поля, например:

Также стоит проверить, какие таксономии используются в теме WordPress. Это можно сделать в файле /wp-content/themes/[название темы]/functions.php или в подключённых к нему модулях. Например, если кастомный тип записей — catalog, а таксономия для категорий — category_catalog, то их регистрация может выглядеть так:

// Регистрация нового типа записей "catalog" (пользовательский post type).
add_action( 'init', function() {
    register_post_type( 'catalog', array(
        // ...
    )
});

// Создание таксономии "category_catalog" и привязка её к новому типу записей
add_action('init', function () {
    register_taxonomy('category_catalog', array('catalog'), array(
        // ...
    ));
});

Чтобы определить, какие ACF-поля используются для хранения характеристик товаров, нужно открыть ACF → Группы полей в админке WordPress. Найти соответствующие группы и запомните их системные имена. Например, если есть "configurator" (с полями "power" и "price") и "custom_parametrs" (с полями "nazvanie", "znachenie" и "czena"), то их можно использовать в фиде.

Создание YML-фида

Для создания фида нужно создать PHP-скрипт, который будет формировать XML-документ с данными о товарах для специального робота.

Далее нужно создать файл, например, "yml-feed.php" в корне сайта:

<?php
require_once __DIR__ . '/wp-load.php';
header("Content-Type: application/xml; charset=UTF-8");
echo '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;

// Специальный класс для товара
class Offer
{
    public $id;
    public $name;
    public $url;
    public $price;
    public $oldprice;
    public $currencyId;
    public $categoryId;
    public $picture;
    public $params = [];

    // Добавить свободные параметры в товары
    public function addParam($name, $value)
    {
        if (!empty(trim($value))) {
            $this->params[] = [
                'name' => trim($name),
                'value' => trim($value),
            ];
        }
    }

    // Вывести товар в XML формате
    public function toXml()
    {
        $xml = "<offer id=\"\">" . PHP_EOL;
        $xml .= "<name>" . lh_esc_xml($this->name) . "</name>" . PHP_EOL;
        $xml .= "<url>" . lh_esc_xml($this->url) . "</url>" . PHP_EOL;
        $xml .= "<price>" . lh_esc_xml($this->price) . "</price>" . PHP_EOL;
        if (!empty($this->oldprice)) {
            $xml .= "<oldprice>" . lh_esc_xml($this->oldprice) . "</oldprice>" . PHP_EOL;
        }
        $xml .= "<currencyId>" . lh_esc_xml($this->currencyId) . "</currencyId>" . PHP_EOL;
        $xml .= "<categoryId></categoryId>" . PHP_EOL;
        if (!empty($this->picture)) {
            $xml .= "<picture>" . lh_esc_xml($this->picture) . "</picture>" . PHP_EOL;
        }
        foreach ($this->params as $param) {
            $xml .= "<param name=\"" . lh_esc_xml($param['name']) . "\">" . lh_esc_xml($param['value']) . "</param>" . PHP_EOL;
        }
        $xml .= "</offer>" . PHP_EOL;
        return $xml;
    }
}

// Массив со всеми товарами для вывода
$offers = [];

// Начало вывода самого файла
?>
<yml_catalog date="<?= date('Y-m-d H:i') ?>">
    <shop>
        <name>Название магазина</name>
        <company>Название компании</company>
        <url><?= lh_esc_xml(get_site_url()) ?></url>

        <currencies>
            <currency id="RUB" rate="1"/>
            <currency id="USD" rate="2"/>
        </currencies>

        // Список всех категорий
        <categories>
            <?php
            $categories = get_terms(['taxonomy' => 'category_catalog', 'hide_empty' => false]);
            foreach ($categories as $category) {
                echo '<category id="' . $category->term_id . '">' . lh_esc_xml($category->name) . '</category>' . PHP_EOL;
            }
            ?>
        </categories>

        // Список товаров
        <offers>
            <?php
            $args = [
                'post_type'      => 'catalog',
                'posts_per_page' => -1,
                'post_status'    => 'publish'
            ];
            $query = new WP_Query($args);

            while ($query->have_posts()) {
                $query->the_post();
                $id          = get_the_ID();
                $title       = get_the_title();
                $link        = get_permalink();
                $category_id = wp_get_post_terms($id, 'category_catalog', ['fields' => 'ids'])[0] ?? 0;
                $image       = get_the_post_thumbnail_url($id, 'full');

                $product_info = get_field('product_main_info', $id);
                $model        = $product_info['model'] ?? '';
                $brand        = $product_info['brand'] ?? '';
                $main_price   = lh_format_price($product_info['main_price'] ?? '');
                $currencyIdM  = lh_detect_currency($product_info['main_price'] ?? '');
                $sale_price   = lh_format_price($product_info['sale_price'] ?? '');

                // Если у базового товара есть цена, то добавить в список товаров для вывода
                if (!empty($main_price)) {
                    $offer = new Offer();
                    $offer->id = $id;
                    $offer->name = $title;
                    $offer->url = $link;
                    $offer->price = $main_price;
                    $offer->oldprice = (!empty($sale_price)) ? $sale_price : $main_price;
                    $offer->currencyId = $currencyIdM;
                    $offer->categoryId = $category_id;
                    $offer->picture = $image;
                    $offer->addParam('Модель', $model);
                    $offer->addParam('Производитель', $brand);
                    $offers[] = $offer;
                }

                // Создать товары с модификаторами из "custom_parametrs"
                $variations = get_field('custom_parametrs', $id);
                if (!empty($variations) && is_array($variations)) {
                    foreach ($variations as $index => $item) {
                        $param_name  = $item['nazvanie'] ?? '';
                        if ($param_name == '') {
                            $param_name = 'Размер';
                        }
                        $value       = $item['znachenie'] ?? '';
                        $price       = lh_format_price($item['czena'] ?? '');
                        $currencyId  = lh_detect_currency($item['czena'] ?? '');

                        if (empty($param_name) || empty($value) || empty($price)) {
                            continue;
                        }

                        $offer = new Offer();
                        $offer->id = $id . '-' . ($index + 1);
                        $offer->name = "$title ($value)";
                        $offer->url = $link;
                        $offer->price = $price;
                        $offer->oldprice = (!empty($sale_price)) ? $main_price : '';
                        $offer->currencyId = $currencyId;
                        $offer->categoryId = $category_id;
                        $offer->picture = $image;
                        $offer->addParam('Модель', $model);
                        $offer->addParam('Производитель', $brand);
                        $offer->addParam($param_name, $value);

                        $offers[] = $offer;
                    }
                }

                // Создать товары с модификаторами из "configurator"
                $configurator = get_field('configurator', $id);
                if (!empty($configurator) && is_array($configurator) && !empty($configurator['configurator'])) {
                    foreach ($configurator['configurator'] as $index => $config) {
                        $power = $config['power'] ?? '';
                        $price = lh_format_price($config['price'] ?? '');
                        $currencyId = lh_detect_currency($config['price'] ?? '');

                        if (empty($power) || empty($price)) {
                            continue;
                        }

                        $offer = new Offer();
                        $offer->id = $id . '-cfg-' . ($index + 1);
                        $offer->name = "$title (Мощность: $power)";
                        $offer->url = $link;
                        $offer->price = $price;
                        $offer->currencyId = $currencyId;
                        $offer->categoryId = $category_id;
                        $offer->picture = $image;
                        $offer->addParam('Модель', $model);
                        $offer->addParam('Производитель', $brand);
                        $offer->addParam('Мощность', $power);

                        $offers[] = $offer;
                    }
                }
            }

            wp_reset_postdata();

            // Вывести товары
            foreach ($offers as $offer) {
                echo $offer->toXml();
            }
            ?>
        </offers>
    </shop>
</yml_catalog>

<?php
// Функция экранирования XML
function lh_esc_xml($string) {
    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}

// Функция очистки цены от "USD"
function lh_format_price($price) {
    return preg_replace('/[^0-9,.]/', '', $price);
}

// Определение валюты по цене
function lh_detect_currency($price) {
    return (stripos($price, 'USD') !== false) ? 'USD' : 'RUB';
}
?>

В этом посте рассмотрено создание YML-фида для Яндекс.Маркета на WordPress, используя плагин ACF для хранения параметров товаров. Такой метод позволяет гибко настраивать товарные позиции и легко адаптировать фид под требования маркетплейса. Это удобное решение для автоматического обновления товарного каталога и интеграции с Яндекс.Маркетом.