Come creare una pagina "virtuale" in WordPress
Sto cercando di creare un endpoint API personalizzato in WordPress e ho bisogno di reindirizzare le richieste a una pagina virtuale nella root di WordPress verso una pagina effettiva inclusa nel mio plugin. In sostanza, tutte le richieste a una pagina vengono effettivamente instradate all'altra.
Esempio:
http://miosito.com/my-api.php
=> http://miosito.com/wp-content/plugins/mio-plugin/my-api.php
L'obiettivo è mantenere l'URL dell'endpoint API il più breve possibile (simile a http://miosito.com/xmlrpc.php
), ma fornire il file effettivo dell'endpoint API con il plugin invece di richiedere all'utente di spostare file nell'installazione o modificare il core.
Il mio primo tentativo è stato aggiungere una regola di rewrite personalizzata. Tuttavia, questo ha presentato due problemi:
- L'endpoint aveva sempre una barra finale. Diventava
http://miosito.com/my-api.php/
- La mia regola di rewrite veniva applicata solo parzialmente. Non reindirizzava a
wp-content/plugins...
, ma aindex.php&wp-content/plugins...
. Questo portava WordPress a mostrare un errore di pagina non trovata o semplicemente a visualizzare la homepage.
Idee? Suggerimenti?

Esistono due tipi di regole di riscrittura in WordPress: regole interne (memorizzate nel database e analizzate da WP::parse_request()), e regole esterne (memorizzate in .htaccess
e analizzate da Apache). Puoi scegliere uno dei due metodi, a seconda di quanto di WordPress hai bisogno nel tuo file chiamato.
Regole Esterne:
La regola esterna è la più semplice da configurare e da seguire. Eseguirà my-api.php
nella directory del tuo plugin, senza caricare nulla da 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 );
// Il pattern è preceduto da '^'
// La sostituzione è preceduta dalla "radice home", almeno un '/'
// Questo è equivalente ad aggiungerlo a `non_wp_rules`
$wp_rewrite->add_external_rule( 'my-api.php$', $plugin_url );
}
Regole Interne:
La regola interna richiede un po' più di lavoro: prima aggiungiamo una regola di riscrittura che aggiunge una variabile di query, poi rendiamo questa variabile di query pubblica, e infine dobbiamo verificare l'esistenza di questa variabile di query per passare il controllo al nostro file del plugin. Quando facciamo questo, l'inizializzazione normale di WordPress sarà già avvenuta (interrompiamo il flusso appena prima della query regolare dei post).
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;
}

Voglio solo aggiungere che è importante andare nella pagina Permalink e cliccare su "Salva modifiche" in WP-Admin. Ho perso un'ora prima di pensare che dovevo aggiornare i permalink... A meno che qualcuno non conosca una funzione che possa farlo?

Per la regola esterna: Poiché il percorso alla mia root web conteneva un carattere spazio, questo faceva crashare apache. Gli spazi devono essere escape se presenti nel percorso della tua installazione WordPress.

Funziona, ma non riesco ad accedere alle variabili di query passate con get_query_vars()
in my-api.php. Ho controllato quali variabili sono caricate. E l'unica variabile impostata è un oggetto WP
chiamato $wp
. Come posso accedere o trasformarlo in un oggetto WP_Query
per poter accedere alle variabili passate con get_query_vars()
?

@Jules: Quando includi un file con include
, questo viene eseguito nello scope corrente. In questo caso, è la funzione wpse9870_parse_request
, che ha solo il parametro $wp
. È possibile che l'oggetto globale $wp_query
non sia stato ancora impostato in questo momento, quindi get_query_var()
non funzionerà. Tuttavia, sei fortunato: $wp
è la classe che contiene il membro query_vars
di cui hai bisogno - lo uso io stesso nel codice sopra.

@JanFabry Grazie. L'ho capito dopo un po'. Mi chiedevo solo se ci fosse un modo per convertire un oggetto WP in un oggetto WP_Query. Ma non sono sicuro che ci sarebbe mai bisogno di una cosa del genere, quindi non importa. Grazie per questo e per tutti gli altri tuoi pezzi di codice. Ho già trovato molte informazioni utili su questo sito grazie a te.

sto cercando di creare delle regole di rewrite esterne. Ho aggiunto il tuo primo pezzo di codice ma ottengo ancora 404. btw: ho svuotato le regole di rewrite

Ricevo ancora un errore 404 con le regole di riscrittura esterne. Solo per chiarire, il codice sopra va nel file PHP principale del plugin, non nel file delle funzioni dei template, corretto?

@ethanpil puoi (ora?) svuotare le regole per includere le tue nuove regole di riscrittura se le regole di riscrittura di WP non le includono. Il metodo è documentato qui http://codex.wordpress.org/Class_Reference/WP_Rewrite

Nel mio caso funziona sull'URL index.php?wpse9870_api=1
e anche my-api.php?wpse9870_api=1
come posso rimuovere la query string?

@Irfan Sto cercando di ottenere lo stesso risultato, puoi dirmi cosa dovrei scrivere nel mio my-api.php?

@Prafulla Kumar Sahu Sto usando in questo modo: `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;
}`
e l'URL è http://homeurl/?getrequest

La regola interna è veramente memorizzata nel database? add_rewrite_rule
esegue un INSERT nel database? Sembra che sia memorizzata solo nel codice sorgente.

Scusa la domanda da principiante ma... dove dovrebbe andare la chiamata add_action( 'init'...
? L'ho messa nel metodo __construct()
del mio plugin, ma il metodo callback non viene mai eseguito. Ho provato a riavviare il server, ecc.

Sto cercando di usare la regola External ma ricevo un errore 403 quando provo ad accedere direttamente. Il file .htaccess predefinito in wp-content blocca l'accesso al file php nella directory del mio plugin, e nessuna delle regole .htaccess che ho aggiunto per consentire l'accesso funziona. Qualche suggerimento?

Questo ha funzionato per me. Non ho mai toccato l'API di riscrittura, ma sono sempre pronto a spingermi in nuove direzioni. Quanto segue ha funzionato sul mio server di test per la versione 3.0 situato in una sottocartella di localhost. Non prevedo alcun problema se WordPress è installato nella radice del sito web.
Basta inserire questo codice in un plugin e caricare il file denominato "taco-kittens.php" direttamente nella cartella dei plugin. Sarà necessario eseguire un hard flush per i tuoi permalink. Credo che il momento migliore per farlo sia durante l'attivazione 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' );
Buona fortuna, -Mike

Ho ricevuto un errore di accesso negato mentre provavo questo codice. Sospetto che il mio server o WP non abbiano apprezzato l'URL assoluto. Questo invece ha funzionato bene: add_rewrite_rule( 'taco-kittens', 'wp-content/plugins/taco-kittens.php', 'top' );

Qualche motivo per non fare qualcosa del genere invece?
Quindi collega semplicemente il tuo plugin a 'init' e controlla quella variabile GET. Se esiste, fai ciò che il tuo plugin deve fare e usa die()

Funzionerebbe, ma sto cercando di fornire una chiara distinzione tra le variabili della query e l'endpoint effettivo. Potrebbero esserci altri argomenti della query in futuro, e non voglio che gli utenti facciano confusione.

E se mantenessi la riscrittura, ma la riscrivessi alla variabile GET? Potresti anche dare un'occhiata a come funziona la riscrittura per robots.txt. Potrebbe aiutarti a capire come evitare il reindirizzamento a my-api.php/

Potrei non capire appieno le tue domande, ma un semplice shortcode potrebbe risolvere il tuo problema?
Passaggi:
- Fai creare al cliente una pagina, ad esempio http://mysite.com/my-api
- Fai aggiungere al cliente uno shortcode in quella pagina, ad esempio [my-api-shortcode]
La nuova pagina funge da endpoint API e il tuo shortcode invia richieste al codice del tuo plugin in http://mysite.com/wp-content/plugins/my-plugin/my-api.php
(ovviamente questo significa che my-api.php avrebbe lo shortcode definito)
Probabilmente puoi automatizzare i passaggi 1 e 2 tramite il plugin.

Non ho lavorato molto con le rewrite, quindi probabilmente questa soluzione è un po' approssimativa, ma sembra funzionare:
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() );
}
Funziona se colleghi questa funzione all'hook 'generate_rewrite_rules', ma ci deve essere un modo migliore, perché non vuoi riscrivere il file .htaccess ad ogni caricamento di pagina.
Sembra che non riesca a smettere di modificare i miei stessi post...probabilmente sarebbe meglio inserirlo nella callback di attivazione e fare riferimento a global $wp_rewrite. E poi rimuovere la voce da non_wp_rules e riscrivere nel file .htaccess nella callback di disattivazione.
Infine, la scrittura su .htaccess dovrebbe essere un po' più sofisticata, dovresti sostituire solo la sezione relativa a WordPress.

Ho avuto un requisito simile e volevo creare diversi endpoint basati su slug unici che puntassero a contenuti generati dal plugin.
Dai un'occhiata al codice sorgente del mio plugin: https://wordpress.org/extend/plugins/picasa-album-uploader/
La tecnica che ho utilizzato inizia aggiungendo un filtro per the_posts
per esaminare la richiesta in ingresso. Se il plugin dovesse gestirla, viene generato un post fittizio e viene aggiunta un'azione per template_redirect
.
Quando viene chiamata l'azione template_redirect
, deve risultare nell'output dell'intero contenuto della pagina da visualizzare e terminare l'esecuzione, oppure deve ritornare senza generare alcun output. Guarda il codice in wp-include/template-loader.php
e capirai il perché.

Sto utilizzando un altro approccio che consiste nel forzare la pagina principale a caricare un titolo personalizzato, contenuto e un modello di pagina personalizzato.
La soluzione è molto pulita poiché può essere implementata quando un utente segue un link amichevole come http://example.com/?plugin_page=myfakepage
È molto semplice da implementare e dovrebbe consentire un numero illimitato di pagine.
Codice e istruzioni qui: Genera una pagina Wordpress personalizzata/fittizia/virtuale al volo

Ecco un esempio pronto per la produzione, prima crea la classe per la pagina virtuale:
class VirtualPage
{
private $query;
private $title;
private $content;
private $template;
private $wp_post;
function __construct($query = '/index2', $template = 'page', $title = 'Senza titolo')
{
$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;
// Aggiorna la query principale
$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); // aggiungi numero casuale per evitare conflitti
$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;
}
}
Nel passaggio successivo aggancia l'azione template_redirect
e gestisci la tua pagina virtuale come mostrato di seguito
add_action( 'template_redirect', function () {
switch ( get_query_var( 'name' ,'') ) {
case 'contact':
// http://tuosito/contact ==> carica page-contact.php
$page = new VirtualPage( "/contact", 'contact',__('Contattami') );
$page->createPage();
break;
case 'archive':
// http://tuosito/archive ==> carica page-archive.php
$page = new VirtualPage( "/archive", 'archive' ,__('Archivi'));
$page->createPage();
break;
case 'blog':
// http://tuosito/blog ==> carica page-blog.php
$page = new VirtualPage( "/blog", 'blog' ,__('Blog'));
$page->createPage();
break;
}
} );

Sto utilizzando un approccio simile a quello di Xavi Esteve sopra menzionato, che ha smesso di funzionare a causa di un aggiornamento di WordPress, per quanto ho potuto capire nella seconda metà del 2013.
È documentato in grande dettaglio qui: https://stackoverflow.com/questions/17960649/wordpress-plugin-generating-virtual-pages-and-using-theme-template
La parte chiave del mio approccio è utilizzare il template esistente in modo che la pagina risultante appaia come parte del sito; volevo che fosse il più compatibile possibile con tutti i temi, si spera attraverso le diverse versioni di WordPress. Il tempo dirà se avevo ragione!
