Используя JavaScript-клиент REST API (v2) на частном маршруте с пространством имен

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

Я использую последнюю версию плагина REST API в проекте и добавил свои маршруты в отдельное пространство имен (как рекомендовано в документации). JavaScript-клиент, включенный в API, автоматически создает модели и коллекции на основе публичных маршрутов в пространстве имен wp/v2.

Кто-нибудь знает, возможно ли расширить встроенный клиент, чтобы использовать собственное пространство имен и автоматически разобрать корневую конечную точку для генерации моделей и коллекций для моего частного API?

Чтобы изменить префикс url rest, вы можете отфильтровать rest_url_prefix. Но это лишь изменяет префикс /wp-json/, который использует каждое пространство имен.

Попытка изменить wp/v2 означает модификацию плагина, и это пространство имен жестко закодировано в нескольких местах, таких как; WP_REST_Post_Statuses_Controller.

Чтобы добавить свои пользовательские конечные точки, register_rest_route встроен в ядро именно для этого.

<?php
add_action( 'rest_api_init', function () {
    register_rest_route( 'your-namespace-here/v1', '/author/(?P<id>\d+)', array(
        'methods' => 'GET',
        'callback' => 'my_awesome_func',
    ) );
} );

Вам нужно будет подробнее изучить процесс открытия, чтобы увидеть, что автоматически создается для вашего API.

Пример шаблона контроллера для плагина WP REST API должен дать вам хорошую идею о том, что может входить в WP REST Controller.

add_action( 'rest_api_init', function () {

    /*
     *      /wp-json/vendor/v1
     */

    $routes = new Slug_Custom_Route();
    $routes->register_routes();

});

class Slug_Custom_Route extends WP_REST_Controller {

    /**
     * Регистрация маршрутов для объектов контроллера.
     */
    public function register_routes() {
        $version = '1';
        $namespace="vendor/v" . $version;
        $base="route";
        register_rest_route( $namespace, "https://wordpress.stackexchange.com/" . $base, array(
            array(
                'methods'         => WP_REST_Server::READABLE,
                'callback'        => array( $this, 'get_items' ),
                'permission_callback' => array( $this, 'get_items_permissions_check' ),
                'args'            => array(

                ),
            ),
            array(
                'methods'         => WP_REST_Server::CREATABLE,
                'callback'        => array( $this, 'create_item' ),
                'permission_callback' => array( $this, 'create_item_permissions_check' ),
                'args'            => $this->get_endpoint_args_for_item_schema( true ),
            ),
        ) );
        register_rest_route( $namespace, "https://wordpress.stackexchange.com/" . $base . '/(?P<id>[\d]+)', array(
            array(
                'methods'         => WP_REST_Server::READABLE,
                'callback'        => array( $this, 'get_item' ),
                'permission_callback' => array( $this, 'get_item_permissions_check' ),
                'args'            => array(
                    'context'          => array(
                        'default'      => 'view',
                    ),
                ),
            ),
            array(
                'methods'         => WP_REST_Server::EDITABLE,
                'callback'        => array( $this, 'update_item' ),
                'permission_callback' => array( $this, 'update_item_permissions_check' ),
                'args'            => $this->get_endpoint_args_for_item_schema( false ),
            ),
            array(
                'methods'  => WP_REST_Server::DELETABLE,
                'callback' => array( $this, 'delete_item' ),
                'permission_callback' => array( $this, 'delete_item_permissions_check' ),
                'args'     => array(
                    'force'    => array(
                        'default'      => false,
                    ),
                ),
            ),
        ) );
        register_rest_route( $namespace, "https://wordpress.stackexchange.com/" . $base . '/schema', array(
            'methods'         => WP_REST_Server::READABLE,
            'callback'        => array( $this, 'get_public_item_schema' ),
        ) );
    }

    /**
     * Получить коллекцию элементов
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|WP_REST_Response
     */
    public function get_items( $request ) {
        $items = array(); //выполните запрос, вызовите другой класс и т.д.
        $data = array();
        foreach( $items as $item ) {
            $itemdata = $this->prepare_item_for_response( $item, $request );
            $data[] = $this->prepare_response_for_collection( $itemdata );
        }

        return new WP_REST_Response( $data, 200 );
    }

    /**
     * Получить один элемент из коллекции
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|WP_REST_Response
     */
    public function get_item( $request ) {
        //получить параметры из запроса
        $params = $request->get_params();
        $item = array();//выполните запрос, вызовите другой класс и т.д.
        $data = $this->prepare_item_for_response( $item, $request );

        //вернуть ответ или ошибку на основе некоторых условий
        if ( 1 == 1 ) {
            return new WP_REST_Response( $data, 200 );
        }else{
            return new WP_Error( 'code', __( 'message', 'text-domain' ) );
        }
    }

    /**
     * Создать один элемент из коллекции
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|WP_REST_Request
     */
    public function create_item( $request ) {

        $item = $this->prepare_item_for_database( $request );

        if ( function_exists( 'slug_some_function_to_create_item')  ) {
            $data = slug_some_function_to_create_item( $item );
            if ( is_array( $data ) ) {
                return new WP_REST_Response( $data, 200 );
            }
        }

        return new WP_Error( 'cant-create', __( 'message', 'text-domain'), array( 'status' => 500 ) );

    }

    /**
     * Обновить один элемент из коллекции
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|WP_REST_Request
     */
    public function update_item( $request ) {
        $item = $this->prepare_item_for_database( $request );

        if ( function_exists( 'slug_some_function_to_update_item')  ) {
            $data = slug_some_function_to_update_item( $item );
            if ( is_array( $data ) ) {
                return new WP_REST_Response( $data, 200 );
            }
        }

        return new WP_Error( 'cant-update', __( 'message', 'text-domain'), array( 'status' => 500 ) );

    }

    /**
     * Удалить один элемент из коллекции
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|WP_REST_Request
     */
    public function delete_item( $request ) {
        $item = $this->prepare_item_for_database( $request );

        if ( function_exists( 'slug_some_function_to_delete_item')  ) {
            $deleted = slug_some_function_to_delete_item( $item );
            if (  $deleted  ) {
                return new WP_REST_Response( true, 200 );
            }
        }

        return new WP_Error( 'cant-delete', __( 'message', 'text-domain'), array( 'status' => 500 ) );
    }

    /**
     * Проверьте, имеет ли данный запрос доступ к получению элементов
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|bool
     */
    public function get_items_permissions_check( $request ) {
        //вернуть true; <--используйте, чтобы сделать доступным для всех
        return current_user_can( 'edit_something' );
    }

    /**
     * Проверьте, имеет ли данный запрос доступ к получению конкретного элемента
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|bool
     */
    public function get_item_permissions_check( $request ) {
        return $this->get_items_permissions_check( $request );
    }

    /**
     * Проверьте, имеет ли данный запрос доступ к созданию элементов
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|bool
     */
    public function create_item_permissions_check( $request ) {
        return current_user_can( 'edit_something' );
    }

    /**
     * Проверьте, имеет ли данный запрос доступ к обновлению конкретного элемента
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|bool
     */
    public function update_item_permissions_check( $request ) {
        return $this->create_item_permissions_check( $request );
    }

    /**
     * Проверьте, имеет ли данный запрос доступ к удалению конкретного элемента
     *
     * @param WP_REST_Request $request Полные данные о запросе.
     * @return WP_Error|bool
     */
    public function delete_item_permissions_check( $request ) {
        return $this->create_item_permissions_check( $request );
    }

    /**
     * Подготовьте элемент для операции создания или обновления
     *
     * @param WP_REST_Request $request Объект запроса
     * @return WP_Error|object $prepared_item
     */
    protected function prepare_item_for_database( $request ) {
        return array();
    }

    /**
     * Подготовьте элемент для REST-ответа
     *
     * @param mixed $item Представление элемента в WordPress.
     * @param WP_REST_Request $request Объект запроса.
     * @return mixed
     */
    public function prepare_item_for_response( $item, $request ) {
        return array();
    }

    /**
     * Получите параметры запроса для коллекций
     *
     * @return array
     */
    public function get_collection_params() {
        return array(
            'page'                   => array(
                'description'        => 'Текущая страница коллекции.',
                'type'               => 'integer',
                'default'            => 1,
                'sanitize_callback'  => 'absint',
            ),
            'per_page'               => array(
                'description'        => 'Максимальное количество элементов, которые будут возвращены в результате.',
                'type'               => 'integer',
                'default'            => 10,
                'sanitize_callback'  => 'absint',
            ),
            'search'                 => array(
                'description'        => 'Ограничить результаты теми, которые соответствуют строке.',
                'type'               => 'string',
                'sanitize_callback'  => 'sanitize_text_field',
            ),
        );
    }
}

И когда вы перейдете непосредственно к конечной точке пространства имен /wp-json/vendor/v1, вы должны увидеть:

{
    "namespace": "vendor\/v1",
    "routes": {
        "\/vendor\/v1": {
            "namespace": "vendor\/v1",
            "methods": ["GET"],
            "endpoints": [{
                "methods": ["GET"],
                "args": {
                    "namespace": {
                        "required": false,
                        "default": "vendor\/v1"
                    },
                    "context": {
                        "required": false,
                        "default": "view"
                    }
                }
            }],
            "_links": {
                "self": "http:\/\/example.com\/wp-json\/vendor\/v1"
            }
        },
        "\/vendor\/v1\/route": {
            "namespace": "vendor\/v1",
            "methods": ["GET", "POST", "GET", "POST"],
            "endpoints": [{
                "methods": ["GET"],
                "args": []
            }, {
                "methods": ["POST"],
                "args": []
            }, {
                "methods": ["GET"],
                "args": []
            }, {
                "methods": ["POST"],
                "args": []
            }],
            "_links": {
                "self": "http:\/\/example.com\/wp-json\/vendor\/v1\/route"
            }
        },
        "\/vendor\/v1\/route\/(?P<id>[\\d]+)": {
            "namespace": "vendor\/v1",
            "methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "GET", "POST", "PUT", "PATCH", "DELETE"],
            "endpoints": [{
                "methods": ["GET"],
                "args": {
                    "context": {
                        "required": false,
                        "default": "view"
                    }
                }
            }, {
                "methods": ["POST", "PUT", "PATCH"],
                "args": []
            }, {
                "methods": ["DELETE"],
                "args": {
                    "force": {
                        "required": false,
                        "default": false
                    }
                }
            }, {
                "methods": ["GET"],
                "args": {
                    "context": {
                        "required": false,
                        "default": "view"
                    }
                }
            }, {
                "methods": ["POST", "PUT", "PATCH"],
                "args": []
            }, {
                "methods": ["DELETE"],
                "args": {
                    "force": {
                        "required": false,
                        "default": false
                    }
                }
            }]
        },
        "\/vendor\/v1\/route\/schema": {
            "namespace": "vendor\/v1",
            "methods": ["GET", "GET"],
            "endpoints": [{
                "methods": ["GET"],
                "args": []
            }, {
                "methods": ["GET"],
                "args": []
            }],
            "_links": {
                "self": "http:\/\/example.com\/wp-json\/vendor\/v1\/route\/schema"
            }
        }
    },
    "_links": {
        "up": [{
            "href": "http:\/\/example.com\/wp-json\/"
        }]
    }
}

Две основные вещи, которые выделяются:

'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE )

и

'args'     => array (
    'force' => array (
        'default' => false,
    ),
),

Еще один пример передачи $args в register_rest_route, который будет отображаться в корне пространства имен:

register_rest_route( "{$root}/{$version}", '/products', array(
        array(
            'methods'         => \WP_REST_Server::READABLE,
            'callback'        => array( $cb_class, 'get_items' ),
            'args'            => array(
                'per_page' => array(
                    'default' => 10,
                    'sanitize_callback' => 'absint',
                ),
                'page' => array(
                    'default' => 1,
                    'sanitize_callback' => 'absint',
                ),
                'soon' => array(
                    'default' => 0,
                    'sanitize_callback' => 'absint',
                ),
                'slug' => array(
                    'default' => false,
                    'sanitize_callback' => 'sanitize_title',
                )

            ),

            'permission_callback' => array( $this, 'permissions_check' )
        ),
    )

);

Смотрим немного дальше, get_data_for_route используется для сбора того, что в конечном итоге попадает в конечные точки открытия, и это можно фильтровать с помощью rest_endpoints_description или get_data_for_route. Поэтому, в теории, если по умолчанию это не дает вам то, что вы хотите, вы можете использовать этот фильтр, чтобы добавить свои модели и/или коллекции для вашего частного API.


Итак, собирая все вместе и переопределяя автоматические конечные точки:

function my_awesome_func( WP_REST_Request $request) {
    return "awesome! " . $request->get_param( 'id' );
}

add_action( 'rest_api_init', function() {
    register_rest_route( 'my-thing/v1', '/awesome/(?P<id>\d+)', array (
        'methods'  => 'GET',
        'callback' => 'my_awesome_func',
    ) );
} );

add_filter( 'rest_endpoints_description', function( $data ) {

    if ( $data[ 'namespace' ] === 'my-thing/v1' ) {

        $data[ 'endpoints' ] = array (
            'foo'     => 'bar',
            'my'      => 'custom-api',
            'awesome' => 'it really is',
        );
    }

    return $data;
} );

/*
 {
    "namespace": "my-thing\/v1",
    "routes": {
        "\/my-thing\/v1": {
            "namespace": "my-thing\/v1",
            "methods": ["GET"],
            "endpoints": {
                "foo": "bar",
                "my": "custom-api",
                "awesome": "it really is"
            },
            "_links": {
                "self": "http:\/\/example.com\/wp-json\/my-thing\/v1"
            }
        },
        "\/my-thing\/v1\/awesome\/(?P<id>\\d+)": {
            "namespace": "my-thing\/v1",
            "methods": ["GET"],
            "endpoints": {
                "foo": "bar",
                "my": "custom-api",
                "awesome": "it really is"
            }
        }
    },
    "_links": {
        "up": [{
            "href": "http:\/\/example.com\/wp-json\/"
        }]
    }
}
*/

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

Использование REST API (v2) JavaScript клиента для работы с маршрутами в закрытом пространстве имен

Введение

REST API в WordPress обеспечивает разработчикам мощный инструмент для создания и управления данными на их сайте. Особенно важно, что начиная с последних версий, вы можете расширять API, добавляя маршруты в собственное пространство имен. Это особенно полезно, когда вы хотите создать закрытые или специализированные API, доступные только определенным пользователям или приложениям.

Создание маршрутов в собственном пространстве имен

Чтобы зарегистрировать новые маршруты в вашем пространстве имен, вы можете воспользоваться хук-методом rest_api_init. Например, для добавления маршрута, который возвращает информацию о пользователе по его идентификатору, воспользуйтесь следующим кодом:

add_action('rest_api_init', function () {
    register_rest_route('your-namespace/v1', '/author/(?P<id>\d+)', array(
        'methods' => 'GET',
        'callback' => 'my_awesome_func',
    ));
});

В этом примере your-namespace является заменой для вашего конкретного пространства имен, а my_awesome_func – это функция, которая будет вызываться при обращении к данному маршруту.

Расширение JavaScript клиента

По умолчанию JavaScript клиент REST API автоматически создает модели и коллекции на основе публичных маршрутов в пространстве имен wp/v2. Однако, для работы с вашими закрытыми маршрутами, вы должны вручную настроить клиент, чтобы он мог использовать ваше пользовательское пространство имен.

  1. Создание клиента с кастомизацией пространства имен: Для инициализации вашего клиента вам необходимо создать экземпляр объекта, который будет указывать на ваше пространство имен:
const customApi = wp.apiFetch({ path: '/wp-json/your-namespace/v1' });
  1. Автоматический парсинг корневого конечного пункта: Для автоматического создания моделей и коллекций необходимо вручную настроить клиент, чтобы он мог обрабатывать данные, возвращаемые вашим API. Один из способов – создать функции, которые будут запрашивать данные и формировать модели.

Обработка разрешений и маршрутов

Важно учитывать, что при работе с закрытыми маршрутами необходимо управлять разрешениями пользователей. Например, вы можете использовать permission_callback, чтобы убедиться, что только авторизованные пользователи смогут получить доступ к определенным маршрутам:

add_action('rest_api_init', function () {
    register_rest_route('your-namespace/v1', '/author/(?P<id>\d+)', array(
        'methods' => 'GET',
        'callback' => 'my_awesome_func',
        'permission_callback' => function () {
            return current_user_can('read'); // Пример разрешений
        }
    ));
});

Использование фильтров и хуков

Если вы хотите расширить стандартные эндпоинты, вы можете использовать фильтры, такие как rest_endpoints_description, чтобы изменять данные, которые возвращаются при обращении к вашему пространству имен.

Вот пример применения фильтра:

add_filter('rest_endpoints_description', function ($data) {
    if ($data['namespace'] === 'your-namespace/v1') {
        $data['endpoints'] = array(
            'foo' => 'bar',
            'custom' => 'api description',
        );
    }
    return $data;
});

Заключение

В этом ответе мы рассмотрели, как использовать REST API в WordPress для работы с собственными маршрутами в закрытых пространствах имен. Мы обсудили регистрацию маршрутов, настройку клиента JavaScript, а также управление разрешениями. Это знание позволит вам создавать более безопасные и адаптированные API, которые отвечают потребностям вашего проекта и пользовательской базы.

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

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

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