Qual è la tua migliore pratica per eseguire script una tantum?
Il Problema
Ci siamo tutti trovati in una situazione del genere, e molte domande su questo sito necessitano di una soluzione simile. Devi aggiornare un database, inserire molti dati automaticamente, convertire meta_keys
, o qualcosa di simile.
Naturalmente, in un sistema funzionante basato sulle migliori pratiche questo non dovrebbe accadere.
Ma poiché accade, mi piacerebbe conoscere la tua soluzione personale a questo problema e perché hai scelto la tua.
La Domanda
Come implementi gli script una tantum nella tua installazione WordPress (in esecuzione)?
Il problema qui è principalmente dovuto alle seguenti ragioni:
- Gli script che inseriscono dati non dovrebbero essere eseguiti più di una volta
- Gli script che richiedono molte risorse non dovrebbero essere eseguiti in un momento in cui non possono essere monitorati
- Non dovrebbero essere eseguiti per errore
Il Motivo per cui chiedo
Ho la mia pratica personale, la posterò nelle risposte. Poiché non so se sia la migliore soluzione disponibile, vorrei conoscere la vostra. Inoltre, questa è una domanda che viene posta molte volte nel contesto di altre domande, e sarebbe fantastico avere una risorsa che raccolga le idee.
non vedo l'ora di imparare da voi :)

Personalmente utilizzo una combinazione di:
- un file dedicato allo script one-time
- un transient per impedire l'esecuzione accidentale dello script più di una volta
- gestione delle capacità o controllo utente per assicurarmi che lo script venga eseguito solo da me.
Struttura
Utilizzo un file (onetime.php
) nella mia cartella inc
, che viene incluso nel functions.php
e poi eliminato dopo l'uso.
include( 'inc/onetime.php' );
Il file per lo script stesso
Nel mio onetime.php
inserisco la mia funzione f711_my_onetime_function()
. Potrebbe essere qualsiasi funzione. Suppongo che il tuo script sia già testato e funzioni correttamente.
Per controllare l'esecuzione dello script, utilizzo entrambi:
Controllo delle capacità
Per impedire ad altri utenti di eseguire accidentalmente il mio script:
if ( current_user_can( 'manage_options' ) ) // verifica i permessi di amministratore
oppure
if ( get_current_user_id() == 711 ) // verifica se sono io - preferisco limitare l'esecuzione solo a me, non a tutti gli amministratori.
Un transient
per impedire a me stesso di eseguire accidentalmente lo script più di una volta.
$transient = 'f711_my_onetime_check';
if ( !get_transient( $transient ) ) // verifica se la funzione non è stata già eseguita.
Il file per l'esecuzione dello script nella mia funzione f711_my_onetime_function()
sarebbe così:
$transient = 'f711_my_onetime_check';
if ( get_current_user_id() == 711 && !get_transient( $transient ) ) {
set_transient( $transient, 'locked', 600 ); // blocca la funzione per 10 minuti
add_action( 'wp_footer', 'f711_my_onetime_function' ); // esegue la mia funzione sull'hook desiderato.
}
function f711_my_onetime_function() {
// tutta la mia gloriosa magia one-time.
}
Il motivo per cui imposto il transient immediatamente dopo aver verificato la sua esistenza è che voglio che la funzione venga eseguita dopo che lo script è stato bloccato per evitare esecuzioni multiple.
Se ho bisogno di un output dalla mia funzione, lo stampo come commento nel footer o a volte applico un filtro al contenuto.
Il tempo di blocco è impostato a 10 minuti, ma può essere modificato in base alle tue esigenze.
Pulizia
Dopo l'esecuzione corretta del mio script, elimino l'include
dal functions.php
e rimuovo il file onetime.php
dal server. Poiché ho utilizzato un timeout per il transient, non è necessario ripulire il database, ma ovviamente potresti anche eliminare il transient dopo aver rimosso il file.

Puoi anche fare così:
esegui onetime.php
e rinominalo dopo l'esecuzione.
if ( current_user_can( 'manage_options' ) ) {
if( ! file_exists( '/percorso/di/onetime.php' ) )
return;
add_action( 'wp_footer', 'ravs_my_onetime_function' ); // esegue la mia funzione sull'hook desiderato.
}
function ravs_my_onetime_function() {
// tutta la mia gloriosa magia one-time.
include( '/percorso/di/onetime.php' );
// dopo tutta l'esecuzione rinomina il tuo file;
rename( '/percorso/di/onetime.php', '/percorso/di/onetime-backup.php');
}

Ho creato uno script Phing da riga di comando per questo, non è niente di speciale se non il caricamento di uno script esterno da eseguire. Il motivo per cui l'ho utilizzato tramite CLI è perché:
- Non voglio che venga caricato per errore (bisogna digitare un comando)
- È sicuro poiché può essere eseguito al di fuori della root web, in altre parole può influenzare WP ma WP non può raggiungere lo script in alcun modo.
- Non aggiunge alcun codice a WP o al database stesso.
require('..percorso a ../wp-blog-header.php');
//vari globali di WP
define('WP_USE_THEMES', false);
//codice personalizzato
Quindi puoi usare Phing, o la CLI PHP e dormire sonni tranquilli. WP-CLI è anche una buona alternativa anche se non ricordo se si possa usare al di fuori della root web.
Dato che questo è un post popolare, ecco un esempio dello script: https://github.com/wycks/WordPhing (run.php)

In condizioni ideali accederei al server via SSH ed eseguirei la funzione manualmente utilizzando wp-cli.
Spesso però questo non è possibile, quindi tendo a impostare una variabile $_GET e ad agganciarla a 'init', ad esempio:
add_action( 'init', function() {
if( isset( $_GET['one_time'] ) && $_GET['one_time'] == 'an_unlikely_string' ) {
do_the_one_time_thing();
}
});
per poi visitare
http://my_blog.com/?one_time=an_unlikely_string
e disabilitare l'hook una volta completata l'operazione.

Un altro modo piuttosto semplice per eseguire uno script una tantum è farlo utilizzando un plugin MU.
Inserisci il codice in un file PHP (ad esempio, one-time.php
) che carichi nella cartella dei plugin MU (di default /wp-content/mu-plugins
), modifica i permessi del file, esegui il plugin (cioè, in base all'hook scelto, fondamentalmente devi solo visitare il frontend/backend), e il gioco è fatto.
Ecco uno scheletro di base:
/**
* Classe principale (e unica).
*/
class OneTimeScript {
/**
* Hook per la funzione del plugin.
*
* @type string
*/
public static $hook = 'init';
/**
* Priorità della funzione del plugin.
*
* @type int
*/
public static $priority = 0;
/**
* Esegui lo script una tantum.
*
* @hook self::$hook
* @return void
*/
public static function run() {
// azione una tantum va qui...
// pulizia
add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
} // function run
/**
* Rimuovi il file.
*
* @hook shutdown
* @return void
*/
public static function unlink() {
unlink(__FILE__);
} // function unlink
} // class OneTimeScript
add_action(OneTimeScript::$hook, array('OneTimeScript', 'run'), OneTimeScript::$priority);
Senza i commenti e il resto, appare semplicemente così:
class OneTimeScript {
public static $hook = 'init';
public static $priority = 0;
public static function run() {
// azione una tantum va qui...
add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
} // function run
public static function unlink() {
unlink(__FILE__);
} // function unlink
} // class OneTimeScript
add_action(OneTimeScript::$hook, array('OneTimeScript', 'run'), OneTimeScript::$priority);

Certamente puoi farlo, crea il tuo codice one-time come un plugin.
add_action('admin_init', 'one_time_call');
function one_time_call()
{
/* I TUOI SCRIPT */
deactivate_plugins('onetime/index.php'); //disattiva il plugin corrente
}
Il problema è come attivare questo plugin senza cliccare sul link Attiva?
basta aggiungere activate_plugins('onetime/index.php');
nel functions.php
oppure Usa i must use plugins, http://codex.wordpress.org/Must_Use_Plugins
Prova con diverse azioni a seconda di quando vuoi eseguire il plugin one-time,
admin_init - dopo l'inizializzazione dell'admin
init - inizializzazione di WordPress
wp - quando WordPress è caricato

A volte ho utilizzato una funzione agganciata alla disattivazione di un plugin.
Vedi qui Aggiornare i vecchi link ai permalink personalizzati per i Custom Post Type
Dato che solo gli amministratori possono attivare i plugin, c'è un controllo delle capacità come effetto collaterale.
Non è necessario eliminare il file una volta disattivato, poiché WordPress non lo includerà più. Inoltre, se vuoi eseguirlo di nuovo puoi farlo. Basta attivarlo e disattivarlo nuovamente.
A volte ho utilizzato transient come nella risposta di @fischi. Ad esempio qui query per creare prodotti WooCommerce dalle immagini o qui Eliminare/sostituire i tag img nel contenuto dei post pubblicati automaticamente
Una combinazione di entrambi i metodi può essere un'alternativa valida.

Anche questa è davvero una buona idea. Se diventa fastidioso dover sempre attivare e disattivare, potresti anche agganciare la stessa funzione all'attivazione del plugin, giusto?

Sì, se vuoi. Tuttavia, penso che 2 clic non siano uno sforzo eccessivo per eseguire uno script una tantum. Qualsiasi altra soluzione che coinvolga comandi CLI o gestione di file (rinominare, eliminare) richiede più "lavoro". Inoltre, ogni volta che ti affidi agli hook, stai contando su variabili globali, aggiungendo un ulteriore livello di potenziali problemi riguardanti la sicurezza/prevedibilità del codice. @fischi

Un altro modo è impostare un'opzione globale wp_option quando il lavoro è completato e verificare quella opzione ogni volta che viene eseguito l'hook init.
function my_one_time_function() {
// Esce se il lavoro è già stato fatto.
if ( get_option( 'my_one_time_function', '0' ) == '1' ) {
return;
}
/***** ESEGUI IL TUO LAVORO UNA TANTUM *****/
// Aggiungi o aggiorna l'opzione wp
update_option( 'my_one_time_function', '1' );
}
add_action( 'init', 'my_one_time_function' );
Naturalmente non è necessario mantenere questo codice per sempre (anche se si tratta di una semplice lettura dal database), quindi probabilmente puoi rimuovere il codice quando il lavoro è completato. Inoltre puoi modificare manualmente il valore di questa opzione a 0 se hai bisogno di rieseguire il codice.

Utilizzare wp-cli eval-file
è fantastico. Puoi persino farlo su un sistema remoto (usando un alias ssh con ‘@‘) con uno script locale.
Se hai il tuo codice in one-time.php
nella directory base di WordPress e hai accesso alla riga di comando sul sistema su cui vuoi eseguirlo, puoi fare:
wp eval-file one-time.php
Se hai il file one-time.php
localmente e vuoi eseguirlo su un WordPress remoto usando @, il comando sarà così:
wp @remote eval-file - < one-time.php

Il mio approccio è leggermente diverso su questo. Mi piace aggiungere il mio script temporaneo come una funzione nel file function.php del mio tema e eseguirlo su una specifica query GET.
if ( isset($_GET['linkupdate']) ) {
add_action('init', 'link_update', 10);
}
function link_update() {
// Script Temporaneo
die;
}
Per eseguirlo, basta visitare l'URL "www.sitename.com/?linkupdate"
Funziona bene per me finora...
Questo metodo ha degli svantaggi? Mi chiedevo...

Utilizzo semplicemente un singolo template personalizzato per i prodotti che non sto utilizzando e che non è connesso a nulla sul server pubblico.
Ad esempio, se ho una pagina di testimonianze che non è pubblicata (in modalità bozza, o simile), ma collegata a un template di pagina singola, come single-testimonial.php
- posso inserire delle funzioni lì dentro, caricare la pagina tramite preview
e la funzione o qualsiasi altra cosa viene lanciata una volta sola. È anche molto facile apportare modifiche alla funzione in caso di debug.
È davvero semplice e lo preferisco rispetto all'uso di init
perché ho più controllo su quando e come viene avviato. È solo una mia preferenza.

Nel caso possa essere utile, ecco cosa ho fatto e funziona bene:
add_action( 'init', 'upsubscriptions_setup');
function upsubscriptions_setup()
{
$version = get_option('upsubscriptions_setup_version');
// Se non è ancora stata registrata alcuna versione nel database
if (!$version) {
add_option('upsubscriptions_setup_version', '0.1');
$version = get_option('upsubscriptions_setup_version');
}
if (version_compare($version, "0.1") <= 0) {
// esegui operazioni
update_option('upsubscriptions_setup_version', '0.2');
}
if (version_compare($version, "0.2") <= 0) {
// esegui operazioni
update_option('upsubscriptions_setup_version', '0.3');
}
if (version_compare($version, "0.3") <= 0) {
// esegui operazioni
update_option('upsubscriptions_setup_version', '0.4');
}
// ecc...
}
