Disinstallare, Attivare, Disattivare un plugin: funzionalità tipiche e guida
Sto creando un plugin WordPress. Quali sono le operazioni tipiche che dovrei includere nella funzionalità di disinstallazione?
Ad esempio, dovrei eliminare le tabelle che ho creato nella funzione di installazione?
Devo pulire le mie voci nelle opzioni?
Qualcos'altro?

Ci sono tre diversi hook. Si attivano nei seguenti casi:
- Disinstallazione
- Disattivazione
- Attivazione
Come attivare le funzioni in sicurezza durante questi scenari
Di seguito vengono mostrati i modi corretti per agganciare in sicurezza le funzioni di callback che vengono attivate durante le azioni menzionate.
Poiché potresti utilizzare questo codice in un plugin che usa
- funzioni semplici,
- una classe o
- una classe esterna,
mostrerò tre diversi plugin dimostrativi che puoi esaminare e poi implementare il codice nel tuo/i plugin.
Nota importante preliminare!
Poiché questo argomento è estremamente complesso e molto dettagliato e presenta più di una dozzina di casi limite, questa risposta non sarà mai perfetta. Continuerò a migliorarla nel tempo, quindi controlla regolarmente.
(1) Attivare/Disattivare/Disinstallare plugin.
Le callback di configurazione del plugin vengono attivate dal core e non hai alcun controllo su come il core lo fa. Ci sono alcune cose da tenere a mente:
- Mai, in alcun caso, usare
echo/print
durante le callback di configurazione. Questo porterà a un messaggio diheaders already sent
e il core consiglierà di disattivare e cancellare il tuo plugin... non chiedere: lo so... - Non vedrai alcun output visivo. Ma ho aggiunto istruzioni
exit()
a tutte le diverse callback in modo che tu possa avere un'idea di cosa sta realmente accadendo. Basta commentarle per vedere il funzionamento. - È estremamente importante verificare se
__FILE__ != WP_PLUGIN_INSTALL
e (se no: interrompi!) per vedere se si sta davvero disinstallando il plugin. Consiglio di attivare semplicemente le callbackon_deactivation()
durante lo sviluppo, così da risparmiare il tempo necessario per ripristinare tutto. Almeno questo è quello che faccio io. - Faccio anche alcune cose per la sicurezza. Alcune sono fatte dal core, ma ehi! Meglio prevenire che curare!.
- Prima nego l'accesso diretto al file quando il core non è caricato:
defined( 'ABSPATH' ) OR exit;
- Poi verifico se l'utente corrente ha i permessi per eseguire questa operazione.
- Come ultima operazione, controllo il referrer. Nota: Potrebbero esserci risultati imprevisti con una schermata
wp_die()
che chiede i permessi appropriati (e se vuoi riprovare... sì, certo), quando si verifica un errore. Questo accade perché il core ti reindirizza, imposta l'azione corrente$GLOBALS['wp_list_table']->current_action();
suerror_scrape
e poi controlla il referrer percheck_admin_referer('plugin-activation-error_' . $plugin);
, dove$plugin
è$_REQUEST['plugin']
. Quindi il reindirizzamento avviene a metà del caricamento della pagina e ottieni questa strana barra di scorrimento e la schermata die all'interno del box giallo di avviso/messaggio dell'amministratore. Se succede: Mantieni la calma e cerca semplicemente l'errore con qualcheexit()
e debug passo-passo.
- Prima nego l'accesso diretto al file quando il core non è caricato:
(A) Plugin con funzioni semplici
Ricorda che questo potrebbe non funzionare se agganci le callback prima della definizione della funzione.
<?php
defined( 'ABSPATH' ) OR exit;
/**
* Plugin Name: (WCM) Attivazione/Disattivazione/Disinstallazione - Funzioni
* Description: Plugin di esempio per mostrare le callback di attivazione/disattivazione/disinstallazione per funzioni semplici.
* Author: Franz Josef Kaiser/wecodemore
* Author URL: http://unserkaiser.com
* Plugin URL: http://wordpress.stackexchange.com/questions/25910/uninstall-activate-deactivate-a-plugin-typical-features-how-to/25979#25979
*/
function WCM_Setup_Demo_on_activation()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
$plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
check_admin_referer( "activate-plugin_{$plugin}" );
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
function WCM_Setup_Demo_on_deactivation()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
$plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
check_admin_referer( "deactivate-plugin_{$plugin}" );
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
function WCM_Setup_Demo_on_uninstall()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
check_admin_referer( 'bulk-plugins' );
// Importante: Verifica se il file è quello
// registrato durante l'hook di disinstallazione.
if ( __FILE__ != WP_UNINSTALL_PLUGIN )
return;
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
register_activation_hook( __FILE__, 'WCM_Setup_Demo_on_activation' );
register_deactivation_hook( __FILE__, 'WCM_Setup_Demo_on_deactivation' );
register_uninstall_hook( __FILE__, 'WCM_Setup_Demo_on_uninstall' );
(B) Architettura basata su classi/OOP
Questo è l'esempio più comune nei plugin moderni.
<?php
defined( 'ABSPATH' ) OR exit;
/**
* Plugin Name: (WCM) Attivazione/Disattivazione/Disinstallazione - CLASSE
* Description: Plugin di esempio per mostrare le callback di attivazione/disattivazione/disinstallazione per classi/oggetti.
* Author: Franz Josef Kaiser/wecodemore
* Author URL: http://unserkaiser.com
* Plugin URL: http://wordpress.stackexchange.com/questions/25910/uninstall-activate-deactivate-a-plugin-typical-features-how-to/25979#25979
*/
register_activation_hook( __FILE__, array( 'WCM_Setup_Demo_Class', 'on_activation' ) );
register_deactivation_hook( __FILE__, array( 'WCM_Setup_Demo_Class', 'on_deactivation' ) );
register_uninstall_hook( __FILE__, array( 'WCM_Setup_Demo_Class', 'on_uninstall' ) );
add_action( 'plugins_loaded', array( 'WCM_Setup_Demo_Class', 'init' ) );
class WCM_Setup_Demo_Class
{
protected static $instance;
public static function init()
{
is_null( self::$instance ) AND self::$instance = new self;
return self::$instance;
}
public static function on_activation()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
$plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
check_admin_referer( "activate-plugin_{$plugin}" );
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
public static function on_deactivation()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
$plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
check_admin_referer( "deactivate-plugin_{$plugin}" );
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
public static function on_uninstall()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
check_admin_referer( 'bulk-plugins' );
// Importante: Verifica se il file è quello
// registrato durante l'hook di disinstallazione.
if ( __FILE__ != WP_UNINSTALL_PLUGIN )
return;
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
public function __construct()
{
# INIZIALIZZA il plugin: Aggancia le tue callback
}
}
(C) Architettura basata su classi/OOP con un oggetto di configurazione esterno
Questo scenario presuppone che tu abbia un file principale del plugin e un secondo file chiamato setup.php
in una sottocartella del plugin chiamata inc
: ~/wp-content/plugins/your_plugin/inc/setup.php
. Funzionerà anche quando la cartella del plugin è fuori dalla struttura predefinita di WP, quando la cartella dei contenuti è rinominata o nei casi in cui il tuo file di configurazione ha un nome diverso. Solo la cartella inc
deve avere lo stesso nome e posizione relativa dalla directory principale dei plugin.
Nota: Puoi semplicemente prendere le tre funzioni register_*_hook()*
e le classi e inserirle nel tuo plugin.
Il file principale del plugin:
<?php
defined( 'ABSPATH' ) OR exit;
/**
* Plugin Name: (WCM) Attivazione/Disattivazione/Disinstallazione - FILE/CLASSE
* Description: Plugin di esempio
* Author: Franz Josef Kaiser/wecodemore
* Author URL: http://unserkaiser.com
* Plugin URL: http://wordpress.stackexchange.com/questions/25910/uninstall-activate-deactivate-a-plugin-typical-features-how-to/25979#25979
*/
register_activation_hook( __FILE__, array( 'WCM_Setup_Demo_File_Inc', 'on_activation' ) );
register_deactivation_hook( __FILE__, array( 'WCM_Setup_Demo_File_Inc', 'on_deactivation' ) );
register_uninstall_hook( __FILE__, array( 'WCM_Setup_Demo_File_Inc', 'on_uninstall' ) );
add_action( 'plugins_loaded', array( 'WCM_Setup_Demo_File', 'init' ) );
class WCM_Setup_Demo_File
{
protected static $instance;
public static function init()
{
is_null( self::$instance ) AND self::$instance = new self;
return self::$instance;
}
public function __construct()
{
add_action( current_filter(), array( $this, 'load_files' ), 30 );
}
public function load_files()
{
foreach ( glob( plugin_dir_path( __FILE__ ).'inc/*.php' ) as $file )
include_once $file;
}
}
Il file di configurazione:
<?php
defined( 'ABSPATH' ) OR exit;
class WCM_Setup_Demo_File_Inc
{
public static function on_activation()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
$plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
check_admin_referer( "activate-plugin_{$plugin}" );
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
public static function on_deactivation()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
$plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
check_admin_referer( "deactivate-plugin_{$plugin}" );
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
public static function on_uninstall()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
check_admin_referer( 'bulk-plugins' );
// Importante: Verifica se il file è quello
// registrato durante l'hook di disinstallazione.
if ( __FILE__ != WP_UNINSTALL_PLUGIN )
return;
# Commenta la riga seguente per vedere la funzione in azione
# exit( var_dump( $_GET ) );
}
}
(2) Aggiornamenti del plugin
Se scrivi un plugin che ha la propria tabella del database o opzioni, potrebbero esserci scenari in cui è necessario modificare o aggiornare alcune cose.
Sfortunatamente, finora non c'è la possibilità di eseguire qualcosa durante l'installazione o l'aggiornamento/upgrade di plugin/temi. Fortunatamente c'è un work-around: Aggancia una funzione personalizzata a un'opzione personalizzata (sì, è un po' rozzo - ma funziona).
function prefix_upgrade_plugin()
{
$v = 'plugin_db_version';
$update_option = null;
// Aggiornamento alla versione 2
if ( 2 !== get_option( $v ) )
{
if ( 2 < get_option( $v ) )
{
// La funzione di callback deve restituire true in caso di successo
$update_option = custom_upgrade_cb_fn_v3();
// Aggiorna l'opzione solo se è andato a buon fine
if ( $update_option )
update_option( $v, 2 );
}
}
// Aggiornamento alla versione 3, eseguito subito dopo l'aggiornamento alla versione 2
if ( 3 !== get_option( $v ) )
{
// Riparti dall'inizio se l'aggiornamento precedente è fallito
if ( 2 < get_option( $v ) )
return prefix_upgrade_plugin();
if ( 3 < get_option( $v ) )
{
// La funzione di callback deve restituire true in caso di successo
$update_option = custom_upgrade_cb_fn_v3();
// Aggiorna l'opzione solo se è andato a buon fine
if ( $update_option )
update_option( $v, 3 );
}
}
// Restituisci il risultato dalla funzione di aggiornamento, così possiamo verificare successo/fallimento/errore
if ( $update_option )
return $update_option;
return false;
}
add_action('admin_init', 'prefix_upgrade_plugin' );
Questa funzione di aggiornamento è un esempio non molto bello/ben scritto, ma come detto: È un esempio e la tecnica funziona bene. Migliorerò con un aggiornamento successivo.

Questo è fantastico MA quello che voglio davvero sapere sono le cose che dovrei includere nel mio metodo di disattivazione... ad esempio, dovrei eliminare le mie tabelle nel database o lasciarle nel caso in cui l'utente cambi idea e riattivi il plugin?

Riguardo al "MA": ho menzionato che ci sono 3 metodi. Uno per l'attivazione, uno per la disattivazione temporanea e uno per la disinstallazione. Secondo me "uninstall" significa "Rimuovimi e tutto ciò che ho fatto", mentre "deactivate" è uno stato temporaneo e potrebbe essere annullato. Ma: vedi l'aggiornamento. Ho aggiunto commenti sulla tua domanda + l'ho estesa con alcuni consigli per lo sviluppo.

Ah ora capisco. Solo una domanda, quando viene chiamato uninstall? Quando i file vengono eliminati??

Hmm. Bella domanda. plugin.php
è il file che contiene register_uninstall_hook
e viene caricato con i file iniziali in wp-settings.php (che è il file principale da sempre controllare). Aggiorno la domanda con i docblock del core.

Ho anche aggiunto il controllo per la costante WP_UNINSTALL_PLUGIN
, che sostanzialmente è il nome del file, così puoi tenere le tue funzioni di disinstallazione in un file separato ed evitare l'esecuzione di altre funzioni presenti nei file del tuo plugin.

Per i lettori successivi: Ho eliminato i docBlock perché erano di WP 3.2.1. Puoi comunque vedere le modifiche di questa domanda per visualizzare le funzioni del core.

Ma ancora non capisco quando avviene la "disinstallazione". All'eliminazione del plugin? Alla disattivazione?

@OneTrickPony Verrà chiamato quando l'utente clicca sul link di disinstallazione che richiede al plugin di disinstallarsi. Aggiorno la risposta.

Non ho visto un link del genere. Immagino che dovrebbe apparire accanto a (dis)attiva/modifica/elimina?

@OneTrickPony Ok, non posso più aggiornare perché sono finito in un wiki della community, ma la risposta a "cosa succede durante la disinstallazione" è qui: La funzione register_uninstall_hook()
aggiunge un file di disinstallazione e una funzione di callback all'opzione 'uninstall_plugins'
nella tabella Options del database. Questa viene poi chiamata tramite la funzione uninstall_plugin($name)
, che viene invocata dalla funzione delete_plugin($array_of_plugins)
. L'ultima funzione viene attivata in base all'azione impostata in /wp-admin/plugin.php
, quando (nel switch
) il caso è 'delete-selected'
. Ciò significa che $_REQUEST['verify-delete']
è impostato. Chiaro fin qui? :)

Quindi, per riassumere: Nell'interfaccia di amministrazione /wp-admin/plugin.php » isset( $_REQUEST['verify-delete'] )
» delete_plugin($array_of_plugins)
» uninstall_plugin($name)
» update_option('uninstall_plugins',$array)
» define('WP_UNINSTALL_PLUGIN')
E include WP_PLUGIN_DIR.'/'.dirname($file).'/uninstall.php'

WP_UNINSTALL_PLUGIN è definito solo se viene trovato un file uninstall.php nella cartella del plugin. Se stai utilizzando la tecnica dell'hook di disinstallazione e non quella del file uninstall.php, la costante non sarà definita. Questo è evidente esaminando il codice sorgente. Al momento in cui questa risposta è stata scritta, questa era la versione corrente: http://core.trac.wordpress.org/browser/tags/3.2.1/wp-admin/includes/plugin.php#L804

Hai ragione. Comunque, tra una settimana rivedrò quella risposta e metterò su un repository GitHub più facile da mantenere.

Modifica: Ho esaminato la situazione e ho pubblicato un commento su una pull request che ho creato. Seguiranno aggiornamenti.

Sto verificando del codice basato sull'approccio a classi e gli standard di codice VIP mi stanno dando problemi con quelle variabili globali $_REQUEST
, dicendo che non sono sanificate. Hai idee su come aggirare il problema?

@aendrew Vengono utilizzati solo nel lato check_admin_referer()
. Non hanno bisogno di essere sanificati perché il core non lo fa da solo e comunque li confronta con valori non sanificati di $_REQUEST
. Ma se iniziano a lamentarsi come ragazzine per questo, puoi semplicemente usare filter_var()
o esc_attr()
su di essi.

@kaiser È quello che pensavo. La tua risposta è persino citata nella documentazione ufficiale di WP, quindi immaginavo non fosse un problema. Grazie per avermi risposto!

@kaiser Comunque, giusto per informazione — phpcs
genera ancora lo stesso errore anche se avvolgi $_REQUEST in filter_var()
o esc_attr()
. Sembra più un problema di phpcs
che altro...

Non dovresti verificare WP_UNINSTALL_PLUGIN nella funzione di callback se usi wp_register_uninstall_hook, solo se usi uninstall.php

Non sono sicuro al 100% che l'opzione (c) funzioni a partire dalla versione 4.2.4 (o forse prima). Nei miei test il metodo on_activation()
non viene mai chiamato. Invece, c'è un avviso PHP nei log: call_user_func_array() si aspetta che il parametro 1 sia un callback valido, classe 'WCM_Setup_Demo_File_Inc' non trovata in /percorso/di/wp-includes/plugin.php alla riga 496
Questo sembra avere senso perché il file incluso non viene incluso finché il plugin non è effettivamente attivo (poiché è il __construct()
che include il file e questo viene fatto solo su plugins_loaded

@kaiser In questa risposta suggerisci di verificare __FILE__ != WP_UNINSTALL_PLUGIN
, tuttavia, è necessario verificare solo se la costante è definita. Non è necessario verificare il valore della costante e infatti non sarà sempre corretto.

@J.D. Non ero a conoscenza di quell'errore di disinstallazione in blocco. Il motivo per cui controllo è semplice: assicurarmi che sia il plugin corretto a richiamare questo. Dopo 5 anni non sono più così sicuro delle mie intenzioni originali. Riguardo al tuo link Trac: potresti voler riconsiderare il problema che stai affrontando: la disinstallazione in blocco non funziona. Degradare la costante potrebbe non essere la soluzione migliore. Secondo me dovrebbe comunque esserci un modo per determinare quale plugin viene disinstallato.

Il codice di disinstallazione presenta 2 errori.
1. Come dice @paul, WP_UNINSTALL_PLUGIN non è definito.
2. E check_admin_referer( 'bulk-plugins' )
fallisce, impedendo la disinstallazione.
"Meglio prevenire che curare" suona bene ma i cambiamenti del core di WP potrebbero causare errori.
Usare register_uninstall_hook()
invece di uninstall.php
significa che devi stare attento, anche in qualsiasi __construct()
. Se tutto è statico, potresti essere al sicuro.
Nei commenti è stato menzionato https://tommcfarlin.com/wordpress-plugin-boilerplate/
È più aggiornato e non ha current_user_can
, check_admin_referer
, o register_uninstall_hook
.

Per verificare i requisiti di sistema come la versione di PHP o le estensioni installate, puoi utilizzare qualcosa di simile:
<?php # -*- coding: utf-8 -*-
/**
* Plugin Name: T5 Verifica Requisiti Plugin
* Description: Verifica la versione PHP e le estensioni installate
* Plugin URI:
* Version: 2013.03.31
* Author: Fuxia Scholz
* Licence: MIT
* License URI: http://opensource.org/licenses/MIT
*/
/*
* Non eseguire su ogni pagina, basta la pagina dei plugin.
*/
if ( ! empty ( $GLOBALS['pagenow'] ) && 'plugins.php' === $GLOBALS['pagenow'] )
add_action( 'admin_notices', 't5_check_admin_notices', 0 );
/**
* Verifica i requisiti di sistema necessari per il plugin.
*
* @return array Errori o array vuoto
*/
function t5_check_plugin_requirements()
{
$php_min_version = '5.4';
// vedi http://www.php.net/manual/en/extensions.alphabetical.php
$extensions = array (
'iconv',
'mbstring',
'id3'
);
$errors = array ();
$php_current_version = phpversion();
if ( version_compare( $php_min_version, $php_current_version, '>' ) )
$errors[] = "Il tuo server sta eseguendo PHP versione $php_current_version ma
questo plugin richiede almeno PHP $php_min_version. Esegui un aggiornamento.";
foreach ( $extensions as $extension )
if ( ! extension_loaded( $extension ) )
$errors[] = "Installa l'estensione $extension per eseguire questo plugin.";
return $errors;
}
/**
* Chiama t5_check_plugin_requirements() e disattiva questo plugin se ci sono errori.
*
* @wp-hook admin_notices
* @return void
*/
function t5_check_admin_notices()
{
$errors = t5_check_plugin_requirements();
if ( empty ( $errors ) )
return;
// Sopprime il messaggio "Plugin attivato".
unset( $_GET['activate'] );
// nome di questo plugin
$name = get_file_data( __FILE__, array ( 'Plugin Name' ), 'plugin' );
printf(
'<div class="error"><p>%1$s</p>
<p><i>%2$s</i> è stato disattivato.</p></div>',
join( '</p><p>', $errors ),
$name[0]
);
deactivate_plugins( plugin_basename( __FILE__ ) );
}
Test con una verifica per PHP 5.5:

Sono un po' confuso, quindi fondamentalmente qui non c'è una chiamata a register_activation_hook
-- perché non usarla? Inoltre, questo verrà eseguito prima o dopo register_activation_hook
e register_activation_hook
verrà eseguito comunque anche se il codice sopra non passa?

Capisco - ma se il plugin viene attivato al di fuori della pagina del plugin (ad esempio come parte delle dipendenze di un tema) allora i tuoi controlli verranno saltati, giusto? Quindi ho provato a spostare add_action( 'admin_notices', 't5_check_admin_notices', 0 );
in un hook di attivazione e il plugin si attiva senza eseguire i controlli...

@kaiser ha spiegato come funziona l'hook di attivazione, volevo mostrare un'alternativa. Se il plugin non viene attivato tramite la pagina dei plugin, potrebbe verificarsi un errore fatale, sì. Questo approccio non può funzionare su un hook di attivazione senza una seria riscrittura, perché quell'hook viene attivato dopo admin_notices
.

In realtà mi sono appena imbattuto nel modo semplice: http://stackoverflow.com/a/13927297/362445

Penso che dovrebbe funzionare così: `register_activation_hook( FILE, function() { add_option('Activated_Plugin','Plugin-Slug'); });
add_action('admin_init', 'load_plugin'); function load_plugin() { if ( ! current_user_can( 'activate_plugins' ) ) return; if(is_admin()&&get_option('Activated_Plugin')=='Plugin-Slug') { delete_option('Activated_Plugin'); add_action( 'admin_notices', 't5_check_admin_notices', 0 ); } }`
