Пользовательский конечный точка, возвращающая XML

Вопрос или проблема

Мне нужно создать конечную точку, которая при вызове возвращает XML с продуктами магазина. У меня есть сгенерированный XML, но я не знаю, как сделать так, чтобы конечная точка возвращала его в формате XML, потому что сейчас она возвращает его в формате JSON, и как XML он не отображается правильно.

Вот мой код:

<?php

/**
* Название плагина: пример
* URI плагина: https://www.example.es/
* Описание: пример
* Версия: 1.0
* Автор: пример
* URI автора: https://www.example.es/
*/

function smg_feed() {
   $args = array( 'post_status' => 'publish', numberposts => -1 );
   $products = wc_get_products($args);

   $xml_header="<?xml version=\"1.0\" encoding=\"UTF-8\"?><Document></Document>";
   $xml = new SimpleXMLElement($xml_header);

   foreach($products as $product) {
       $data = $product->get_data();
       $sku = $data['sku'];
       $categoriasNombres = array();
       $subcategoriasNombres = array();
       foreach( wp_get_post_terms( $data['id'], 'product_cat' ) as $term ){
           if( $term ){
               if ($term->name == 'XXXX' || $term->name == 'YYYY') {
                   array_push($categoriasNombres, $term->name);
               } else {
                array_push($subcategoriasNombres, $term->name);
               }
           }
       }
       $categoriasNombres = implode(',', $categoriasNombres);
       $subcategoriasNombres = implode(',', $subcategoriasNombres);
       $propiedadesNombres = array();
       foreach( wp_get_post_terms( $data['id'], 'product_tag' ) as $term ){
           if( $term ){
               array_push($propiedadesNombres, $term->name);
           }
       }
       $propiedadesNombres = implode(',', $propiedadesNombres);
       $nombre = $data['name'];
       $formatosNombres = array();
       foreach( wp_get_post_terms( $data['id'], 'pa_formato' ) as $term ){
           if( $term ){
               array_push($formatosNombres, $term->name);
           }
       }
       $formatosNombres = implode(',', $formatosNombres);
       $metaData = $data['meta_data'];

       foreach($metaData as $item) {
           if ($item->key == '_role_based_price') {
               $obj = $item->value;
               $precio = $obj['api1']['regular_price'];
               $precioEspecial = $obj['api2']['regular_price'];
           }
       }

       $row = $xml->addChild('row');
       $row->addChild('sku', $sku);
       $row->addChild('categorias', $categoriasNombres);

       $row->addChild('subcategorias', $subcategoriasNombres);
       $row->addChild('propiedades', $propiedadesNombres);
       $row->addChild('nombre', $nombre);
       // $row->addChild('imagen', );

       $row->addChild('formatos', $formatosNombres);
       $row->addChild('precio', $data['regular_price']);
       $row->addChild('precio_especial', $precioEspecial);
   }

   $output = $xml->asXML();

   return $output;
}

add_action('rest_api_init', function() {

   register_rest_route('smg/v1', 'feed', [
       'methods' => 'GET',
       'callback' => 'smg_feed',
   ]);

});

Но когда я вызываю конечную точку, она возвращает следующее:

"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
<Document>
<row>
<sku\/>
<categorias>Nutrici\u00f3n<\/categorias>
<subcategorias>Nutridefense Plus<\/subcategorias>
<propiedades>todo<\/propiedades>
<nombre>NutriDefense plus<\/nombre>
<formatos\/>
<precio>29,90<\/precio>
<precio_especial>26,52<\/precio_especial>
<\/row>
<\/Document>\n"

Как мне сделать так, чтобы он возвращал это в формате XML? Большое спасибо.

С уважением.

По умолчанию вывод, возвращаемый вашей конечной точкой/обработчиком, всегда будет отправляться как строка, закодированная в JSON (а заголовок Content-Type также будет/содержит application/json), так что вы не можете использовать этот обработчик для отправки XML-канала.

Тем не менее, вы можете использовать rest_pre_serve_request хук, если хотите, чтобы ваш маршрут REST API обслуживал другой тип контента, как, например, XML-канал в вашем случае.

Так что, например, вы можете сделать следующее:

  1. Настройте ваш обработчик (smg_feed()) так, чтобы он возвращал XML-данные, которые будут выведены в maybe_smg_feed() ниже: (обратите внимание, что permission_callback всегда должен быть установлен, смотрите руководство по REST API для получения более подробной информации)

    function smg_feed( $request ) {
        // ... ваш код ...
        return 'ваши XML данные';
    }
    
    add_action( 'rest_api_init', function () {
        register_rest_route( 'smg/v1', 'feed', [
            'methods'             => 'GET',
            'callback'            => 'smg_feed', // убедитесь, что он возвращает строку XML
            'permission_callback' => '__return_true',
        ]);
    });
    
  2. Затем отправьте XML-ответ следующим образом:

    function maybe_smg_feed( $served, $result, $request, $server ) {
        // Прекратите выполнение, если маршрут текущего запроса REST API не наш собственный маршрут.
        if ( '/smg/v1/feed' !== $request->get_route() ||
            // Также проверьте, что обработчик - smg_feed().
            'smg_feed' !== $request->get_attributes()['callback'] ) {
            return $served;
        }
    
        // Отправьте заголовки.
        $server->send_header( 'Content-Type', 'text/xml' );
    
        // Выведите XML, который возвращает smg_feed().
        echo $result->get_data();
    
        // И затем завершите выполнение.
        exit;
    }
    add_filter( 'rest_pre_serve_request', 'maybe_smg_feed', 10, 4 );
    

В качестве альтернативы (как я уже упоминал в оригинальном ответе), не используя REST API (что работает), вы можете использовать add_feed(), чтобы предоставить XML-канал.

( PS: Спасибо @TimothyJacobs за его помощь/комментарий. 🙂 )

function smg_feed( $request ) {
 ....
 echo $xml->asXML();
 return new WP_REST_Response( null, 200, ['Content-Type' => 'text/xml'] );
}

Ответ или решение

Чтобы создать конечную точку (endpoint), которая возвращает XML с продуктами магазина, вам нужно внести некоторые изменения в ваш текущий код. Поскольку стандартный обработчик REST API WordPress именно так обрабатывает ответы, что они возвращаются в формате JSON, нам нужно будет использовать хук rest_pre_serve_request, чтобы отправить XML-ответ с правильным заголовком.

Вот шаги, как это сделать:

  1. Изменить вашу функцию smg_feed, чтобы она возвращала XML как текст, а также использовать новый тип WP_REST_Response для правильной установки заголовка.

  2. Добавить фильтр rest_pre_serve_request, который будет отправлять XML в ответе на ваш запрос.

Вот ваш обновленный код:

<?php
/**
* Plugin Name: example
* Plugin URI: https://www.example.es/
* Description: example
* Version: 1.0
* Author: example
* Author URI: https://www.example.es/
*/

function smg_feed() {
    // Извлекаем все опубликованные продукты
    $args = array('post_status' => 'publish', 'numberposts' => -1);
    $products = wc_get_products($args);

    // Создаем XML-структуру
    $xml_header = '<?xml version="1.0" encoding="UTF-8"?><Document></Document>';
    $xml = new SimpleXMLElement($xml_header);

    foreach ($products as $product) {
        $data = $product->get_data();
        $sku = $data['sku'];
        $categoriesNames = [];
        $subcategoriesNames = [];

        // Получаем термины категорий
        foreach (wp_get_post_terms($data['id'], 'product_cat') as $term) {
            if ($term) {
                if ($term->name == 'XXXX' || $term->name == 'YYYY') {
                    array_push($categoriesNames, $term->name);
                } else {
                    array_push($subcategoriesNames, $term->name);
                }
            }
        }

        // Преобразуем массивы в строки
        $categoriesNames = implode(',', $categoriesNames);
        $subcategoriesNames = implode(',', $subcategoriesNames);
        $propertiesNames = [];

        // Получаем термины свойств
        foreach (wp_get_post_terms($data['id'], 'product_tag') as $term) {
            if ($term) {
                array_push($propertiesNames, $term->name);
            }
        }
        $propertiesNames = implode(',', $propertiesNames);
        $name = $data['name'];
        $formatsNames = [];

        // Получаем термины форматов
        foreach (wp_get_post_terms($data['id'], 'pa_formato') as $term) {
            if ($term) {
                array_push($formatsNames, $term->name);
            }
        }
        $formatsNames = implode(',', $formatsNames);
        $metaData = $data['meta_data'];

        $specialPrice = '';

        // Получаем цены
        foreach ($metaData as $item) {
            if ($item->key == '_role_based_price') {
                $obj = $item->value;
                $price = $obj['api1']['regular_price'];
                $specialPrice = $obj['api2']['regular_price'];
            }
        }

        // Добавляем данные продукта в XML
        $row = $xml->addChild('row');
        $row->addChild('sku', $sku);
        $row->addChild('categories', $categoriesNames);
        $row->addChild('subcategories', $subcategoriesNames);
        $row->addChild('properties', $propertiesNames);
        $row->addChild('name', $name);
        $row->addChild('formats', $formatsNames);
        $row->addChild('price', $price);
        $row->addChild('special_price', $specialPrice);
    }

    // Возвращаем XML в виде строки
    return $xml->asXML();
}

// Регистрация маршрута
add_action('rest_api_init', function() {
    register_rest_route('smg/v1', 'feed', [
        'methods' => 'GET',
        'callback' => 'smg_feed',
        'permission_callback' => '__return_true',
    ]);
});

// Хук для установки заголовков и возвращения ответа
add_filter('rest_pre_serve_request', function($served, $result, $request, $server) {
    // Проверяем, вызываем ли мы нужный маршрут
    if ('/smg/v1/feed' !== $request->get_route() || 'smg_feed' !== $request->get_attributes()['callback']) {
        return $served;
    }

    // Устанавливаем заголовок ответа
    $server->send_header('Content-Type', 'text/xml');

    // Отправляем XML
    echo $result->get_data();

    // Завершаем выполнение
    exit;
}, 10, 4);
?>

Объяснение изменений:

  1. Функция smg_feed(): Мы возвращаем XML-строку, созданную с помощью SimpleXMLElement. Теперь мы не используем return для ответа в формe JSON.

  2. Фильтр rest_pre_serve_request: Этот фильтр позволяет изменить способ обработки ответа и позволяет настроить, что будет отправлено в ответе. Мы устанавливаем заголовок Content-Type на text/xml, и выводим содержимое XML напрямую, завшая выполнение скрипта с помощью exit.

Эти изменения позволят вашему API возвращать XML-ответ, как это нужно в вашем случае. Теперь, делая запрос к вашему конечному пути /wp-json/smg/v1/feed, вы получите ответ в нужном формате XML.

Оцените материал
Добавить комментарий

Капча загружается...