Как создать "виртуальную" страницу в WordPress

20 февр. 2011 г., 06:05:46
Просмотры: 68.3K
Голосов: 61

Я пытаюсь создать пользовательский API эндпоинт в WordPress, и мне нужно перенаправлять запросы к виртуальной странице в корне WordPress на реальную страницу, которая идет с моим плагином. По сути, все запросы к одной странице должны перенаправляться на другую.

Пример:
http://mysite.com/my-api.php => http://mysite.com/wp-content/plugins/my-plugin/my-api.php

Цель этого - сделать URL для API эндпоинта максимально коротким (аналогично http://mysite.com/xmlrpc.php), но при этом хранить реальный файл API эндпоинта в плагине, а не требовать от пользователя перемещать файлы в их установке или изменять ядро WordPress.

Моя первая попытка заключалась в добавлении пользовательского правила перезаписи URL. Однако это вызвало две проблемы:

  1. Эндпоинт всегда имел завершающий слеш. Получалось http://mysite.com/my-api.php/
  2. Мое правило перезаписи применялось только частично. Вместо перенаправления на wp-content/plugins..., оно перенаправляло на index.php&wp-content/plugins.... Это приводило к тому, что WordPress либо показывал ошибку "страница не найдена", либо просто отображал главную страницу.

Есть идеи? Предложения?

0
Все ответы на вопрос 9
15
63

В WordPress существует два типа правил перезаписи: внутренние правила (хранятся в базе данных и обрабатываются функцией WP::parse_request()) и внешние правила (хранятся в .htaccess и обрабатываются Apache). Вы можете выбрать любой вариант в зависимости от того, насколько вам необходим функционал WordPress в вызываемом файле.

Внешние правила:

Внешние правила проще всего настроить и понять. Они выполняют файл my-api.php в директории вашего плагина, не загружая ничего из WordPress.

add_action( 'init', 'wpse9870_init_external' );
function wpse9870_init_external()
{
    global $wp_rewrite;
    $plugin_url = plugins_url( 'my-api.php', __FILE__ );
    $plugin_url = substr( $plugin_url, strlen( home_url() ) + 1 );
    // Шаблон начинается с символа '^'
    // Подстановка начинается с "корня сайта", по крайней мере с '/'
    // Это эквивалентно добавлению к `non_wp_rules`
    $wp_rewrite->add_external_rule( 'my-api.php$', $plugin_url );
}

Внутренние правила:

Внутренние правила требуют больше работы: сначала мы добавляем правило перезаписи, которое добавляет переменные запроса, затем делаем эту переменную запроса публичной, и, наконец, проверяем её наличие для передачи управления нашему файлу плагина. К этому моменту обычная инициализация WordPress уже произойдет (мы прерываем выполнение прямо перед стандартным запросом постов).

add_action( 'init', 'wpse9870_init_internal' );
function wpse9870_init_internal()
{
    add_rewrite_rule( 'my-api.php$', 'index.php?wpse9870_api=1', 'top' );
}

add_filter( 'query_vars', 'wpse9870_query_vars' );
function wpse9870_query_vars( $query_vars )
{
    $query_vars[] = 'wpse9870_api';
    return $query_vars;
}

add_action( 'parse_request', 'wpse9870_parse_request' );
function wpse9870_parse_request( &$wp )
{
    if ( array_key_exists( 'wpse9870_api', $wp->query_vars ) ) {
        include 'my-api.php';
        exit();
    }
    return;
}
21 февр. 2011 г. 15:57:32
Комментарии

Хочу добавить, что важно зайти на страницу Постоянных ссылок и нажать "Сохранить изменения" в админке WordPress. Я провозился с этим целый час, прежде чем догадался, что нужно обновить постоянные ссылки... Если кто-то знает функцию, которая может это сделать?

ethanpil ethanpil
28 мая 2013 г. 21:21:50

Для внешнего правила: Поскольку путь к корню моего сайта содержал пробел, это вызывало ошибку в Apache. Пробелы в пути к вашей WordPress-установке необходимо экранировать.

Willster Willster
4 июн. 2013 г. 12:41:23

Работает, но я не могу получить доступ к переданным переменным запроса через get_query_vars() в my-api.php. Я проверил, какие переменные загружаются. Единственная установленная переменная - это WP object под названием $wp. Как мне получить доступ или преобразовать это в объект WP_Query, чтобы я мог получить доступ к переданным переменным через get_query_vars()?

Jules Jules
13 авг. 2013 г. 13:24:43

@Jules: Когда вы include файл, он выполняется в текущей области видимости. В данном случае это функция wpse9870_parse_request, которая имеет только параметр $wp. Вполне возможно, что глобальный объект $wp_query ещё не установлен в этот момент, поэтому get_query_var() не будет работать. Однако вам повезло: $wp — это класс, который содержит нужный вам член query_vars — я сам использую его в приведённом выше коде.

Jan Fabry Jan Fabry
13 авг. 2013 г. 18:49:30

@JanFabry Спасибо. Разобрался с этим спустя некоторое время. Мне просто было интересно, есть ли способ преобразовать объект WP в объект WP_Query. Но я не уверен, что в этом вообще есть необходимость, так что не берите в голову. Спасибо за этот и все остальные кусочки кода. Уже нашёл много полезной информации на этом сайте благодаря вам.

Jules Jules
14 авг. 2013 г. 19:59:29

пытаюсь создать внешние правила перезаписи. добавил ваш первый фрагмент кода, но всё равно получаю 404. кстати: сбросил правила перезаписи

Sisir Sisir
30 нояб. 2013 г. 10:36:36

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

Lee Lee
6 февр. 2015 г. 12:37:56

@ethanpil вы можете (теперь?) сбросить правила, чтобы включить ваши новые правила перезаписи, если правила перезаписи WP их не включают. Метод описан здесь http://codex.wordpress.org/Class_Reference/WP_Rewrite

Ejaz Ejaz
31 авг. 2015 г. 11:31:21

В моем случае это работает на URL index.php?wpse9870_api=1 и также my-api.php?wpse9870_api=1. Как я могу удалить строку запроса?

er.irfankhan11 er.irfankhan11
12 февр. 2016 г. 16:51:58

@Irfan Я пытаюсь достичь того же самого, не могли бы вы подсказать, что мне следует написать в my-api.php?

Prafulla Kumar Sahu Prafulla Kumar Sahu
4 мая 2016 г. 13:32:31

@Prafulla Kumar Sahu Я использую вот так: `add_filter( 'query_vars', 'wpse9870_query_vars' ); function wpse9870_query_vars( $query_vars ) { $query_vars[] = 'getrequest'; return $query_vars; }

    add_action( 'parse_request', 'wpse9870_parse_request' );
    function wpse9870_parse_request( &$wp )
    {
        if ( array_key_exists( 'getrequest', $wp->query_vars ) ) {
            include 'my-api.php';
            exit();
        }
        return;
    }`

и URL выглядит так: http://homeurl/?getrequest

er.irfankhan11 er.irfankhan11
6 мая 2016 г. 10:53:38

Действительно ли внутреннее правило хранится в базе данных? Выполняет ли add_rewrite_rule INSERT в базу данных? Похоже, что оно просто хранится в исходном коде.

Jeff Jeff
10 апр. 2017 г. 19:03:20

@ethanpil flush_rewrite_rules( true );

mircobabini mircobabini
16 мая 2017 г. 11:01:32

Извините за нубский вопрос, но... куда должен идти вызов add_action( 'init'...? Я поместил его в метод __construct() моего плагина, но callback-метод никогда не выполняется. Пробовал перезапускать сервер и т.д.

T Nguyen T Nguyen
14 апр. 2021 г. 11:34:34

Пытаюсь использовать External rule, но получаю ошибку 403 при попытке прямого доступа. Стандартный .htaccess в wp-content блокирует доступ к php-файлу в директории моего плагина, и ни одно из добавленных мной правил .htaccess для разрешения доступа не работает. Есть советы?

AutoBaker AutoBaker
14 февр. 2023 г. 11:09:04
Показать остальные 10 комментариев
2
13

Это сработало у меня. Я никогда не трогаю Rewrite API, но всегда готов двигаться в новых направлениях. Следующий код работал на моем тестовом сервере для WordPress 3.0, расположенном в подпапке localhost. Не вижу проблем, если WordPress установлен в корень веб-сайта.

Просто поместите этот код в плагин и загрузите файл с именем "taco-kittens.php" напрямую в папку плагинов. Вам потребуется выполнить жесткий сброс постоянных ссылок. Думаю, лучшее время для этого — активация плагина.

function taco_kitten_rewrite() {
    $url = str_replace( trailingslashit( site_url() ), '', plugins_url( '/taco-kittens.php', __FILE__ ) );
    add_rewrite_rule( 'taco-kittens\\.php$', $url, 'top' );
}
add_action( 'wp_loaded', 'taco_kitten_rewrite' );

С наилучшими пожеланиями, -Майк

20 февр. 2011 г. 08:00:23
Комментарии

Я получил ошибку "доступ запрещен" при попытке использовать этот код. Подозреваю, что мой сервер или WordPress не приняли абсолютный URL. С другой стороны, этот код сработал нормально: add_rewrite_rule( 'taco-kittens', 'wp-content/plugins/taco-kittens.php', 'top' );

Jules Jules
13 авг. 2013 г. 12:46:54

Пожалуйста, подскажите, что я должен поместить в taco-kittens.php, у меня нет знаний о .htaccess или перезаписи URL.

Prafulla Kumar Sahu Prafulla Kumar Sahu
4 мая 2016 г. 13:47:01
3
11

Есть ли причина не делать что-то подобное?

http://mysite.com/?my-api=1

Затем просто подключите свой плагин к хуку 'init' и проверьте наличие этой GET-переменной. Если она существует, выполните необходимые действия в вашем плагине и завершите выполнение с помощью die()

20 февр. 2011 г. 06:49:28
Комментарии

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

EAMann EAMann
20 февр. 2011 г. 06:53:50

Что если оставить перезапись, но перенаправлять её в GET-переменную? Также можно посмотреть, как работает перезапись для robots.txt. Это может помочь понять, как избежать перенаправления к my-api.php/

Will Anderson Will Anderson
20 февр. 2011 г. 07:34:25

Это идеальное решение, если вас не волнуют роботы вроде API-вызовов.

kuzey beytar kuzey beytar
11 мар. 2017 г. 19:43:15
0

Возможно, я не до конца понимаю ваш вопрос, но может ли простая шорткод-функция решить вашу проблему?

Шаги:

  1. Попросите клиента создать страницу, например: http://mysite.com/my-api
  2. Попросите клиента добавить шорткод на эту страницу, например: [my-api-shortcode]

Эта новая страница будет выступать в качестве API-эндпоинта, а ваш шорткод будет отправлять запросы к коду вашего плагина по адресу http://mysite.com/wp-content/plugins/my-plugin/my-api.php

(разумеется, это означает, что в файле my-api.php должен быть определён этот шорткод)

Шаги 1 и 2 можно, вероятно, автоматизировать через плагин.

20 февр. 2011 г. 10:13:21
0

Я еще не так много работал с перезаписью URL, поэтому это решение, вероятно, немного грубовато, но оно работает:

function api_rewrite($wp_rewrite) {
    $wp_rewrite->non_wp_rules['my-api\.php'] = 'wp-content/plugins/my-plugin/my-api.php';
    file_put_contents(ABSPATH.'.htaccess', $wp_rewrite->mod_rewrite_rules() );
}

Это работает, если подключить функцию к хуку 'generate_rewrite_rules', но должно быть лучшее решение, так как нежелательно перезаписывать .htaccess при каждой загрузке страницы.
Кажется, я не могу перестать редактировать свои собственные сообщения... вероятно, лучше поместить это в callback активации плагина и использовать глобальную переменную $wp_rewrite. А затем удалить запись из non_wp_rules и снова записать в .htaccess в callback деактивации.

И наконец, запись в .htaccess должна быть более продуманной — нужно заменять только секцию WordPress в файле.

20 февр. 2011 г. 07:26:48
0

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

Посмотрите исходный код моего плагина: https://wordpress.org/extend/plugins/picasa-album-uploader/

Используемая мной техника начинается с добавления фильтра для the_posts, чтобы анализировать входящий запрос. Если плагин должен его обработать, создается фиктивный пост и добавляется действие для template_redirect.

Когда вызывается действие template_redirect, оно должно либо вывести все содержимое страницы для отображения и завершить выполнение, либо вернуться без генерации вывода. Посмотрите код в wp_include/template-loader.php, и вы поймете почему.

9 апр. 2011 г. 01:22:31
0

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

Это решение очень аккуратное, так как может быть реализовано, когда пользователь переходит по дружественной ссылке, например http://example.com/?plugin_page=myfakepage

Очень легко реализовать и позволяет создавать неограниченное количество страниц.

Код и инструкции здесь: Создание пользовательской/фейковой/виртуальной страницы WordPress на лету

18 янв. 2012 г. 13:50:34
0

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


class VirtualPage
{

    private $query;
    private $title;
    private $content;
    private $template;
    private $wp_post;

    function __construct($query = '/index2', $template = 'page', $title = 'Без названия')
    {
        $this->query = filter_var($query, FILTER_SANITIZE_URL);
        $this->setTemplate($template);
        $this->setTitle($title);
    }

    function getQuery()
    {
        return $this->query;
    }

    function getTemplate()
    {
        return $this->template;
    }

    function getTitle()
    {
        return $this->title;
    }

    function setTitle($title)
    {
        $this->title = filter_var($title, FILTER_SANITIZE_STRING);

        return $this;
    }

    function setContent($content)
    {
        $this->content = $content;

        return $this;
    }

    function setTemplate($template)
    {
        $this->template = $template;

        return $this;
    }

    public function updateWpQuery()
    {

        global $wp, $wp_query;

        // Обновляем основной запрос
        $wp_query->current_post = $this->wp_post->ID;
        $wp_query->found_posts = 1;
        $wp_query->is_page = true;//важная часть
        $wp_query->is_singular = true;//важная часть
        $wp_query->is_single = false;
        $wp_query->is_attachment = false;
        $wp_query->is_archive = false;
        $wp_query->is_category = false;
        $wp_query->is_tag = false;
        $wp_query->is_tax = false;
        $wp_query->is_author = false;
        $wp_query->is_date = false;
        $wp_query->is_year = false;
        $wp_query->is_month = false;
        $wp_query->is_day = false;
        $wp_query->is_time = false;
        $wp_query->is_search = false;
        $wp_query->is_feed = false;
        $wp_query->is_comment_feed = false;
        $wp_query->is_trackback = false;
        $wp_query->is_home = false;
        $wp_query->is_embed = false;
        $wp_query->is_404 = false;
        $wp_query->is_paged = false;
        $wp_query->is_admin = false;
        $wp_query->is_preview = false;
        $wp_query->is_robots = false;
        $wp_query->is_posts_page = false;
        $wp_query->is_post_type_archive = false;
        $wp_query->max_num_pages = 1;
        $wp_query->post = $this->wp_post;
        $wp_query->posts = array($this->wp_post);
        $wp_query->post_count = 1;
        $wp_query->queried_object = $this->wp_post;
        $wp_query->queried_object_id = $this->wp_post->ID;
        $wp_query->query_vars['error'] = '';
        unset($wp_query->query['error']);

        $GLOBALS['wp_query'] = $wp_query;

        $wp->query = array();
        $wp->register_globals();

    }

    public function createPage()
    {
        if (is_null($this->wp_post)) {
            $post = new stdClass();
            $post->ID = -99;
            $post->ancestors = array(); // 3.6
            $post->comment_status = 'closed';
            $post->comment_count = 0;
            $post->filter = 'raw';
            $post->guid = home_url($this->query);
            $post->is_virtual = true;
            $post->menu_order = 0;
            $post->pinged = '';
            $post->ping_status = 'closed';
            $post->post_title = $this->title;
            $post->post_name = sanitize_title($this->template); // добавляем случайное число чтобы избежать конфликтов
            $post->post_content = $this->content ?: '';
            $post->post_excerpt = '';
            $post->post_parent = 0;
            $post->post_type = 'page';
            $post->post_status = 'publish';
            $post->post_date = current_time('mysql');
            $post->post_date_gmt = current_time('mysql', 1);
            $post->modified = $post->post_date;
            $post->modified_gmt = $post->post_date_gmt;
            $post->post_password = '';
            $post->post_content_filtered = '';
            $post->post_author = is_user_logged_in() ? get_current_user_id() : 0;
            $post->post_content = '';
            $post->post_mime_type = '';
            $post->to_ping = '';

            $this->wp_post = new WP_Post($post);
            $this->updateWpQuery();

            @status_header(200);
            wp_cache_add(-99, $this->wp_post, 'posts');

        }


        return $this->wp_post;
    }
}

На следующем шаге подключаем действие template_redirect и обрабатываем виртуальную страницу как показано ниже

    add_action( 'template_redirect', function () {


                    switch ( get_query_var( 'name' ,'') ) {

                        case 'contact':
                            // http://вашсайт/contact  ==> загружает page-contact.php
                            $page = new VirtualPage( "/contact", 'contact',__('Свяжитесь со мной') );
                            $page->createPage();
                            break;

                        case 'archive':
                            // http://вашсайт/archive  ==> загружает page-archive.php
                            $page = new VirtualPage( "/archive", 'archive' ,__('Архивы'));
                            $page->createPage();
                            break;

                        case 'blog':
                            // http://вашсайт/blog  ==> загружает page-blog.php
                            $page = new VirtualPage( "/blog", 'blog' ,__('Блог'));
                            $page->createPage();
                            break;


                }


            } );
12 июл. 2019 г. 11:29:35
0

Я использую подход, аналогичный тому, что описал выше Xavi Esteve, который перестал работать из-за обновления WordPress, насколько я мог понять, во второй половине 2013 года.

Это подробно описано здесь: https://stackoverflow.com/questions/17960649/wordpress-plugin-generating-virtual-pages-and-using-theme-template

Ключевая часть моего подхода — использование существующего шаблона, чтобы итоговая страница выглядела как часть сайта; я хотел, чтобы она была максимально совместима со всеми темами, желательно и в будущих версиях WordPress. Время покажет, был ли я прав!

1 мар. 2014 г. 07:39:01