Cómo crear una página "virtual" en WordPress

20 feb 2011, 06:05:46
Vistas: 68.3K
Votos: 61

Estoy intentando crear un endpoint API personalizado en WordPress, y necesito redirigir las solicitudes a una página virtual en la raíz de WordPress a una página real que viene con mi plugin. Básicamente, todas las solicitudes a una página deben ser redirigidas a la otra.

Ejemplo:
http://misitio.com/mi-api.php => http://misitio.com/wp-content/plugins/mi-plugin/mi-api.php

El objetivo de esto es hacer la URL del endpoint API lo más corta posible (similar a http://misitio.com/xmlrpc.php) pero incluir el archivo real del endpoint API con el plugin en lugar de requerir que el usuario mueva archivos en su instalación o modifique el núcleo.

Mi primer intento fue agregar una regla de reescritura personalizada. Sin embargo, esto tuvo dos problemas:

  1. El endpoint siempre tenía una barra diagonal al final. Se convertía en http://misitio.com/mi-api.php/
  2. Mi regla de reescritura solo se aplicaba parcialmente. No redirigía a wp-content/plugins..., sino a index.php&wp-content/plugins.... Esto hacía que WordPress mostrara un error de página no encontrada o simplemente mostrara la página de inicio.

¿Ideas? ¿Sugerencias?

0
Todas las respuestas a la pregunta 9
15
63

Existen dos tipos de reglas de reescritura en WordPress: reglas internas (almacenadas en la base de datos y analizadas por WP::parse_request()), y reglas externas (almacenadas en .htaccess y analizadas por Apache). Puedes elegir cualquiera de las dos opciones, dependiendo de cuánto de WordPress necesites en tu archivo llamado.

Reglas externas:

La regla externa es la más fácil de configurar y seguir. Ejecutará my-api.php en el directorio de tu plugin, sin cargar nada de 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 );
    // El patrón tiene como prefijo '^'
    // La sustitución tiene como prefijo la "raíz del sitio", al menos un '/'
    // Esto es equivalente a añadirlo a `non_wp_rules`
    $wp_rewrite->add_external_rule( 'my-api.php$', $plugin_url );
}

Reglas internas:

La regla interna requiere algo más de trabajo: primero añadimos una regla de reescritura que agrega variables de consulta, luego hacemos pública esta variable de consulta, y finalmente necesitamos verificar la existencia de esta variable de consulta para pasar el control a nuestro archivo de plugin. Para cuando hacemos esto, la inicialización habitual de WordPress ya habrá ocurrido (interrumpimos justo antes de la consulta regular de entradas).

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 feb 2011 15:57:32
Comentarios

Solo quiero agregar que es importante ir a la página de Enlaces permanentes y hacer clic en "Guardar cambios" en el WP-Admin. Estuve probando esto durante una hora antes de pensar que necesitaba actualizar los enlaces permanentes... A menos que alguien conozca una función que pueda hacer eso?

ethanpil ethanpil
28 may 2013 21:21:50

Para la regla externa: Debido a que la ruta a mi raíz web tenía un carácter de espacio en blanco, esto hizo que Apache fallara. Los espacios en blanco deben escaparse si existen en la ruta a tu instalación de WordPress.

Willster Willster
4 jun 2013 12:41:23

Funciona, pero no puedo acceder a las variables de consulta pasadas con get_query_vars() en my-api.php. Verifiqué qué variables están cargadas. Y la única variable que está configurada es un objeto WP llamado $wp. ¿Cómo puedo acceder o transformar esto en un objeto WP_Query para poder acceder a las variables pasadas con get_query_vars()?

Jules Jules
13 ago 2013 13:24:43

@Jules: Cuando incluyes (include) un archivo, este se ejecuta en el ámbito actual. En este caso, es la función wpse9870_parse_request, que solo tiene el parámetro $wp. Es posible que el objeto global $wp_query no se haya establecido en este momento, por lo que get_query_var() no funcionará. Sin embargo, tienes suerte: $wp es la clase que contiene el miembro query_vars que necesitas - yo mismo lo uso en el código anterior.

Jan Fabry Jan Fabry
13 ago 2013 18:49:30

@JanFabry Gracias. Lo descubrí después de un rato. Solo me preguntaba si hay una manera de convertir un objeto WP a un objeto WP_Query. Pero no estoy muy seguro de si alguna vez habría necesidad de algo así, así que no importa. Gracias por esto y por todos tus otros fragmentos de código. Ya he encontrado mucha información útil en este sitio gracias a ti.

Jules Jules
14 ago 2013 19:59:29

intentando crear reglas de reescritura externas. agregué tu primer fragmento de código pero sigo recibiendo 404. por cierto: limpié las reglas de reescritura

Sisir Sisir
30 nov 2013 10:36:36

Todavía estoy recibiendo un error 404 con las reglas de reescritura externas. Solo para aclarar, el código anterior va en el archivo PHP principal del plugin, no en el archivo de funciones de plantillas, ¿correcto?

Lee Lee
6 feb 2015 12:37:56

@ethanpil puedes (¿ahora?) limpiar las reglas para incluir tus nuevas reglas de reescritura si las reglas de reescritura de WP no las incluyen. El método está documentado aquí http://codex.wordpress.org/Class_Reference/WP_Rewrite

Ejaz Ejaz
31 ago 2015 11:31:21

En mi caso funciona en la URL index.php?wpse9870_api=1 y también en my-api.php?wpse9870_api=1 ¿Cómo puedo eliminar la cadena de consulta?

er.irfankhan11 er.irfankhan11
12 feb 2016 16:51:58

@Irfan Estoy intentando lograr lo mismo, ¿puedes decirme qué debería escribir en mi my-api.php?

Prafulla Kumar Sahu Prafulla Kumar Sahu
4 may 2016 13:32:31

@Prafulla Kumar Sahu Estoy usando algo así: `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;
    }`

y la url es http://homeurl/?getrequest

er.irfankhan11 er.irfankhan11
6 may 2016 10:53:38

¿La regla interna realmente se almacena en la base de datos? ¿add_rewrite_rule hace un INSERT en la base de datos? Parece que solo se almacena en el código fuente.

Jeff Jeff
10 abr 2017 19:03:20

@ethanpil flush_rewrite_rules( true );

mircobabini mircobabini
16 may 2017 11:01:32

Perdón por la pregunta de novato pero... ¿dónde debería ir la llamada add_action( 'init'...? La puse en el método __construct() de mi plugin, pero el método callback nunca se ejecuta. He intentado reiniciar el servidor, etc.

T Nguyen T Nguyen
14 abr 2021 11:34:34

Intentando usar la regla Externa pero obtengo un error 403 cuando intento acceder directamente. El .htaccess por defecto en wp-content está bloqueando el acceso al archivo php en mi directorio de plugin, y ninguna de las reglas .htaccess que he añadido para permitir el acceso funciona. ¿Algún consejo?

AutoBaker AutoBaker
14 feb 2023 11:09:04
Mostrar los 10 comentarios restantes
2
13

Esto funcionó para mí. Nunca toco la API de reescritura, pero siempre estoy dispuesto a empujarme en nuevas direcciones. Lo siguiente funcionó en mi servidor de prueba para 3.0 ubicado en una subcarpeta de localhost. No preveo ningún problema si WordPress está instalado en la raíz web.

Solo coloca este código en un plugin y sube el archivo llamado "taco-kittens.php" directamente en la carpeta de plugins. Necesitarás forzar un hard flush para tus enlaces permanentes. Creo que dicen que el mejor momento para hacer esto es en la activación del plugin.

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' );

Los mejores deseos, -Mike

20 feb 2011 08:00:23
Comentarios

Recibí un error de acceso denegado al intentar este código. Sospecho que a mi servidor o a WordPress no les gustó la URL absoluta. Esto, por otro lado, funcionó bien: add_rewrite_rule( 'taco-kittens', 'wp-content/plugins/taco-kittens.php', 'top' );

Jules Jules
13 ago 2013 12:46:54

¿Podrías decirme qué debería poner en taco-kittens.php? No tengo conocimiento de .htaccess o reescritura de URL.

Prafulla Kumar Sahu Prafulla Kumar Sahu
4 may 2016 13:47:01
3
11

¿Alguna razón para no hacer algo como esto en su lugar?

http://misitio.com/?mi-api=1

Luego simplemente engancha tu plugin en el hook 'init' y verifica esa variable GET. Si existe, haz lo que tu plugin necesite hacer y usa die()

20 feb 2011 06:49:28
Comentarios

Eso funcionaría, pero estoy intentando proporcionar una distinción muy clara entre las variables de consulta y el endpoint real. Podría haber otros argumentos de consulta en el futuro, y no quiero que los usuarios confundan las cosas.

EAMann EAMann
20 feb 2011 06:53:50

¿Qué tal si mantienes la reescritura, pero la reescribes a la variable GET? También podrías ver cómo funciona la reescritura para robots.txt. Podría ayudarte a descubrir cómo evitar la redirección a my-api.php/

Will Anderson Will Anderson
20 feb 2011 07:34:25

Es la solución perfecta si no te importan los robots como las llamadas API.

kuzey beytar kuzey beytar
11 mar 2017 19:43:15
0

Puede que no esté entendiendo completamente tus preguntas, pero ¿un simple shortcode resolvería tu problema?

Pasos:

  1. Haz que el cliente cree una página, por ejemplo http://mysite.com/my-api
  2. Haz que el cliente agregue un shortcode en esa página, por ejemplo [my-api-shortcode]

La nueva página actúa como un punto final de API y tu shortcode envía solicitudes al código de tu plugin en http://mysite.com/wp-content/plugins/my-plugin/my-api.php

(por supuesto, esto significa que my-api.php tendría definido el shortcode)

Probablemente puedas automatizar los pasos 1 y 2 a través del plugin.

20 feb 2011 10:13:21
0

No he trabajado mucho con reescrituras todavía, así que esto probablemente sea un poco rudimentario, pero parece funcionar:

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() );
}

Funciona si enganchas esto al hook 'generate_rewrite_rules', pero debe haber una mejor manera, ya que no quieres reescribir el .htaccess en cada carga de página.
Parece que no puedo dejar de editar mis propias publicaciones... probablemente debería ir en tu callback de activación y hacer referencia a global $wp_rewrite en su lugar. Y luego eliminar la entrada de non_wp_rules y volver a escribir en .htaccess en tu callback de desactivación.

Y finalmente, la escritura en .htaccess debería ser un poco más sofisticada, querrás reemplazar solo la sección de WordPress allí.

20 feb 2011 07:26:48
0

Tuve un requerimiento similar y quería crear varios endpoints basados en slugs únicos que apuntaran a contenido generado por el plugin.

Echa un vistazo al código fuente de mi plugin: https://wordpress.org/extend/plugins/picasa-album-uploader/

La técnica que utilicé comienza añadiendo un filtro para the_posts para examinar la solicitud entrante. Si el plugin debe manejarla, se genera un post ficticio y se añade una acción para template_redirect.

Cuando se llama a la acción template_redirect, debe resultar en la salida de todo el contenido de la página a mostrar y finalizar, o debe retornar sin generar ninguna salida. Mira el código en wp-include/template-loader.php y entenderás por qué.

9 abr 2011 01:22:31
0

Estoy utilizando otro enfoque que consiste en forzar a la página de inicio para que cargue un título personalizado, contenido y plantilla de página.

La solución es muy limpia ya que se puede implementar cuando un usuario sigue un enlace amigable como http://ejemplo.com/?plugin_page=myfakepage

Es muy fácil de implementar y debería permitir páginas ilimitadas.

Código e instrucciones aquí: Generar una página personalizada/falsa/virtual de Wordpress sobre la marcha

18 ene 2012 13:50:34
0

Es un ejemplo listo para producción, primero crea la clase de página virtual:


class VirtualPage
{

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

    function __construct($query = '/index2', $template = 'page', $title = 'Sin título')
    {
        $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;

        // Actualizar la consulta principal
        $wp_query->current_post = $this->wp_post->ID;
        $wp_query->found_posts = 1;
        $wp_query->is_page = true;//parte importante
        $wp_query->is_singular = true;//parte importante
        $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); // añadir número aleatorio para evitar colisión
            $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;
    }
}

En el siguiente paso engancha la acción template_redirect y maneja tu página virtual como se muestra a continuación

    add_action( 'template_redirect', function () {


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

                        case 'contact':
                            // http://tusitio/contact  ==> carga page-contact.php
                            $page = new VirtualPage( "/contact", 'contact',__('Contáctame') );
                            $page->createPage();
                            break;

                        case 'archive':
                            // http://tusitio/archive  ==> carga page-archive.php
                            $page = new VirtualPage( "/archive", 'archive' ,__('Archivos'));
                            $page->createPage();
                            break;

                        case 'blog':
                            // http://tusitio/blog  ==> carga page-blog.php
                            $page = new VirtualPage( "/blog", 'blog' ,__('Blog'));
                            $page->createPage();
                            break;


                }


            } );
12 jul 2019 11:29:35
0

Estoy utilizando un enfoque similar al de Xavi Esteve mencionado anteriormente, que dejó de funcionar debido a una actualización de WordPress, según pude notar en la segunda mitad de 2013.

Está documentado con gran detalle aquí: https://stackoverflow.com/questions/17960649/wordpress-plugin-generating-virtual-pages-and-using-theme-template

La parte clave de mi enfoque es utilizar la plantilla existente para que la página resultante parezca que es parte del sitio; quería que fuera lo más compatible posible con todos los temas, con suerte a través de las versiones de WordPress. ¡El tiempo dirá si estaba en lo correcto!

1 mar 2014 07:39:01