Care este cea mai bună practică pentru a executa scripturi one-time?
Problema
Cu toții am fost într-o situație de acest gen, și multe întrebări de pe acest site necesită o soluție similară. Fie trebuie să actualizați o bază de date, să inserați automat o mulțime de date, să convertiți meta_keys
, sau ceva asemănător.
Desigur, într-un sistem funcțional bazat pe cele mai bune practici, acest lucru nu ar trebui să se întâmple.
Dar pentru că se întâmplă, mi-ar plăcea să aud soluția voastră personală la această problemă și de ce ați ales-o.
Întrebarea
Cum implementați scripturile one-time în instalarea voastră WordPress (în funcțiune)?
Problema apare în principal din următoarele motive:
- Scripturile care inserează date nu ar trebui să ruleze mai mult de o singură dată
- Scripturile care necesită multe resurse nu ar trebui să ruleze într-un moment în care nu pot fi monitorizate
- Nu ar trebui să fie executate din greșeală
Motivul pentru care întreb
Am propria mea practică, pe care o voi posta în răspunsuri. Deoarece nu știu dacă este cea mai bună soluție disponibilă, aș dori să aflu despre a voastră. De asemenea, aceasta este o întrebare care este pusă de multe ori în contextul altor întrebări, și ar fi grozav să avem o resursă care să colecteze ideile.
aștept cu nerăbdare să învăț de la voi :)

Eu personal folosesc o combinație de:
- un fișier dedicat pentru scriptul de unică folosință
- folosirea unui transient pentru a împiedica rularea accidentală a scriptului de mai multe ori
- folosirea managementului de capabilități sau controlului utilizatorilor pentru a mă asigura că scriptul este rulat doar de mine.
Structură
Folosesc un fișier (onetime.php
) în directorul meu de include inc
, care este inclus în functions.php
, și șters de acolo după utilizare.
include( 'inc/onetime.php' );
Fișierul pentru scriptul propriu-zis
În onetime.php
se află funcția mea f711_my_onetime_function()
. Aceasta poate fi orice funcție. Presupun că scriptul tău este testat și funcționează corect.
Pentru a controla execuția scriptului, folosesc ambele metode:
Controlul capabilităților
Pentru a împiedica alți utilizatori să ruleze accidental scriptul meu:
if ( current_user_can( 'manage_options' ) ) // verifică drepturile de administrator
sau
if ( get_current_user_id() == 711 ) ) // verifică dacă sunt eu - prefer să restricționez execuția doar pentru mine, nu pentru toți adminii.
un transient
pentru a mă împiedica pe mine să rulez accidental scriptul de mai multe ori.
$transient = 'f711_my_onetime_check';
if ( !get_transient( $transient ) ) ) // verifică dacă funcția nu a fost executată.
Fișierul pentru execuția scriptului în funcția mea f711_my_onetime_function()
ar arăta astfel:
$transient = 'f711_my_onetime_check';
if ( get_current_user_id() == 711 && !get_transient( $transient ) ) {
set_transient( $transient, 'locked', 600 ); // blochează funcția pentru 10 minute
add_action( 'wp_footer', 'f711_my_onetime_function' ); // execută funcția mea pe hook-ul dorit.
}
function f711_my_onetime_function() {
// toată magia mea de unică folosință.
}
Motivul pentru care setez transient imediat după verificarea existenței acestuia este că vreau ca funcția să fie executată după ce scriptul a fost blocat pentru a nu fi folosit de două ori.
Dacă am nevoie de vreun output din funcția mea, îl afișez fie ca un comentariu în footer, fie uneori filtrez conținutul.
Timpul de blocare este setat la 10 minute, dar poate fi ajustat după nevoie.
Curățare
După execuția cu succes a scriptului meu, șterg include
-ul din functions.php
și elimin onetime.php
de pe server. Deoarece am folosit un timeout pentru transient, nu este nevoie să curăț baza de date, dar desigur poți șterge și transient-ul după ce ai eliminat fișierul.

Puteți face și asta:
rulați onetime.php
și redenumiți fișierul după execuție.
if ( current_user_can( 'manage_options' ) ) {
if( ! file_exists( '/path/to/onetime.php' ) )
return;
add_action( 'wp_footer', 'ravs_my_onetime_function' ); // execută funcția mea pe hook-ul dorit.
}
function ravs_my_onetime_function() {
// toată magia mea de unică folosință.
include( '/path/to/onetime.php' );
// după execuție redenumește fișierul;
rename( '/path/to/onetime.php', '/path/to/onetime-backup.php');
}

Am creat un script Phing pentru linia de comandă pentru asta, nu este nimic special în afară de încărcarea unui script extern pentru rulare. Motivul pentru care l-am folosit prin CLI este următorul:
- Nu vreau să se încarce din greșeală (trebuie tastată o comandă)
- Este sigur deoarece poate fi rulat în afara rădăcinii web, cu alte cuvinte poate afecta WP dar WP nu poate accesa scriptul în niciun fel.
- Nu adaugă niciun cod suplimentar în WP sau în baza de date.
require('..cale către ../wp-blog-header.php');
//o grămadă de variabile globale WP
define('WP_USE_THEMES', false);
//cod personalizat
Deci poți folosi Phing sau PHP CLI și poți dormi liniștit. WP-CLI este de asemenea o alternativă bună, deși nu mai țin minte dacă poate fi folosit în afara rădăcinii web.
Deoarece acesta este un post popular, iată un exemplu de script: https://github.com/wycks/WordPhing (run.php)

În condiții ideale, m-aș conecta prin SSH la server și aș executa funcția manual folosind wp-cli.
Totuși, acest lucru nu este întotdeauna posibil, așa că de obicei setez o variabilă $_GET și o atașez la acțiunea 'init', de exemplu:
add_action( 'init', function() {
if( isset( $_GET['one_time'] ) && $_GET['one_time'] == 'an_unlikely_string' ) {
do_the_one_time_thing();
}
});
apoi accesez
http://my_blog.com/?one_time=an_unlikely_string
și dezactivez hook-ul când acțiunea este finalizată.

O altă metodă destul de simplă de a rula un script o singură dată este să faci acest lucru prin intermediul unui plugin MU.
Pune codul într-un fișier PHP (de exemplu, one-time.php
) pe care îl încarci în folderul de MU plugins (implicit /wp-content/mu-plugins
), ajustează permisiunile fișierului, rulează pluginul (adică, conform hook-ului ales, practic trebuie doar să vizitezi frontend-ul/backend-ul), și ai terminat.
Aici este un șablon:
/**
* Clasa principală (și singura).
*/
class OneTimeScript {
/**
* Hook-ul funcției pluginului.
*
* @type string
*/
public static $hook = 'init';
/**
* Prioritatea funcției pluginului.
*
* @type int
*/
public static $priority = 0;
/**
* Rulează scriptul o singură dată.
*
* @hook self::$hook
* @return void
*/
public static function run() {
// acțiunea de o singură dată se adaugă aici...
// curăță
add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
} // funcția run
/**
* Șterge fișierul.
*
* @hook shutdown
* @return void
*/
public static function unlink() {
unlink(__FILE__);
} // funcția unlink
} // clasa OneTimeScript
add_action(OneTimeScript::$hook, array('OneTimeScript', 'run'), OneTimeScript::$priority);
Fără comentarii și altele, arată astfel:
class OneTimeScript {
public static $hook = 'init';
public static $priority = 0;
public static function run() {
// acțiunea de o singură dată se adaugă aici...
add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
} // funcția run
public static function unlink() {
unlink(__FILE__);
} // funcția unlink
} // clasa OneTimeScript
add_action(OneTimeScript::$hook, array('OneTimeScript', 'run'), OneTimeScript::$priority);

Desigur că poți, creează-ți propriul cod one-time ca un plugin.
add_action('admin_init', 'one_time_call');
function one_time_call()
{
/* SCRIPTURILE TALE */
deactivate_plugins('onetime/index.php'); //dezactivează pluginul curent
}
Problema este cum activez acest plugin fără să apăs pe linkul Activate?
pur și simplu adaugă activate_plugins('onetime/index.php');
în functions.php
sau Folosește must use plugins, http://codex.wordpress.org/Must_Use_Plugins
Încearcă cu diferite acțiuni în funcție de când vrei să execuți pluginul one-time,
admin_init - după inițializarea admin-ului
init - inițializarea WordPress
wp - când WordPress este încărcat

Uneori am folosit o funcție legată de dezactivarea pluginului.
Vezi aici Actualizare legături vechi la permalinkuri personalizate pentru tipuri de postare
Deoarece doar administratorii pot activa pluginuri, există o verificare a capabilității ca efect secundar.
Nu este nevoie să ștergi fișierul odată dezactivat, acesta nu va fi inclus de WordPress. În plus, dacă dorești să o rulezi din nou, poți face acest lucru prin activarea și dezactivarea din nou.
Și uneori am folosit transient-uri ca în răspunsul lui @fischi. De exemplu aici interogare pentru creare produse WooCommerce din imagini sau aici Șterge/înlocuiește tag-uri img în conținutul postărilor pentru postări publicate automat
O combinație dintre cele două poate fi o alternativă.

Aceasta este de asemenea o idee foarte bună. Dacă devine enervant să tot activezi și dezactivezi funcționalitatea, ai putea să folosești același hook și la activarea plugin-ului, nu-i așa?

Da, dacă dorești. Totuși, cred că 2 click-uri nu reprezintă un efort prea mare pentru a rula un script o singură dată. Orice altă soluție care implică comenzi CLI sau manipulare de fișiere (redenumire, ștergere) necesită mai multă "muncă". În plus, de fiecare dată când te bazezi pe hooks, te bazezi pe variabile globale, adăugând un strat suplimentar de potențiale probleme legate de securitate/predictibilitatea codului. @fischi

O altă metodă este să setezi o opțiune globală wp_option când sarcina este finalizată și să verifici această opțiune de fiecare dată când acțiunea init este executată.
function my_one_time_function() {
// Ieși dacă sarcina a fost deja efectuată.
if ( get_option( 'my_one_time_function', '0' ) == '1' ) {
return;
}
/***** EXECUTĂ SARCINA TA O SINGURĂ DATĂ *****/
// Adaugă sau actualizează opțiunea wp_option
update_option( 'my_one_time_function', '1' );
}
add_action( 'init', 'my_one_time_function' );
Desigur, nu este necesar să păstrezi acest cod pentru totdeauna (chiar dacă este doar o simplă citire din baza de date), așa că îl poți elimina după ce sarcina este finalizată. De asemenea, poți modifica manual valoarea acestei opțiuni la 0 dacă ai nevoie să re-executi codul.

Folosirea comenzii wp-cli eval-file
este foarte utilă. Poți chiar să o execuți pe un sistem la distanță (folosind un alias ssh '@') cu un script local.
Dacă ai codul tău în fișierul one-time.php
în directorul de bază WordPress și ai acces la linia de comandă pe sistemul pe care vrei să-l rulezi, poți face:
wp eval-file one-time.php
Dacă ai fișierul one-time.php
local și vrei să-l rulezi pe un WordPress la distanță folosind @, arată astfel:
wp @remote eval-file - < one-time.php

Abordarea mea este puțin diferită în acest caz. Îmi place să adaug scripturile mele unice ca funcții în fișierul functions.php al temei mele și să le rulez pe baza unei interogări GET specifice.
if ( isset($_GET['linkupdate']) ) {
add_action('init', 'link_update', 10);
}
function link_update() {
// Script Unic
die;
}
Pentru a rula acest script, pur și simplu accesează URL-ul "www.sitename.com/?linkupdate"
Această metodă a funcționat bine pentru mine până acum...
Există vreun dezavantaj la această abordare? Mă întrebam doar...

Eu folosesc doar o singură pagină șablon personalizată pentru produse pe care nu o folosesc și care nu este conectată la nimic pe serverul public.
De exemplu, dacă am o pagină de testimonialuri care nu este live (în modul draft sau altceva), dar este conectată la un șablon de pagină unic, cum ar fi single-testimonial.php
- pot să adaug funcții acolo, să încărc pagina prin intermediul unei previzualizări
iar funcția sau orice altceva este lansată o singură dată. Este de asemenea foarte ușor să fac modificări la funcție în cazul depanării.
Este foarte ușor și prefer asta în loc să folosesc init
pentru că am mai mult control asupra momentului și modului în care este lansată. Doar preferința mea.

Doar în caz că ajută, iată ce am făcut eu și funcționează bine:
add_action( 'init', 'upsubscriptions_setup');
function upsubscriptions_setup()
{
$version = get_option('upsubscriptions_setup_version');
// Dacă încă nu există o versiune înregistrată în baza de date
if (!$version) {
add_option('upsubscriptions_setup_version', '0.1');
$version = get_option('upsubscriptions_setup_version');
}
if (version_compare($version, "0.1") <= 0) {
// executați acțiuni
update_option('upsubscriptions_setup_version', '0.2');
}
if (version_compare($version, "0.2") <= 0) {
// executați acțiuni
update_option('upsubscriptions_setup_version', '0.3');
}
if (version_compare($version, "0.3") <= 0) {
// executați acțiuni
update_option('upsubscriptions_setup_version', '0.4');
}
// etc
}
