Dezinstalare, Activare, Dezactivare plugin: funcții tipice și cum se implementează
Dezvolt un plugin WordPress. Ce lucruri tipice ar trebui să includ în funcția de dezinstalare?
De exemplu, ar trebui să șterg tabelele pe care le-am creat în funcția de instalare?
Trebuie să curăț intrările din opțiuni?
Mai este altceva de făcut?

Există trei diferite hook-uri. Acestea se declanșează în următoarele cazuri:
- Dezinstalare
- Dezactivare
- Activare
Cum să declanșăm funcții în siguranță în aceste scenarii
Următorul cod arată metodele corecte de a declanșa funcții callback în siguranță în timpul acțiunilor menționate.
Deoarece puteți folosi acest cod într-un plugin care utilizează
- funcții simple,
- o clasă sau
- o clasă externă,
voi prezenta trei demo-uri de plugin pe care le puteți inspecta și apoi implementa codul în propriile plugin-uri.
Notă importantă prealabilă!
Deoarece acest subiect este extrem de complex și are numeroase cazuri particulare, acest răspuns nu va fi niciodată perfect. Îl voi îmbunătăți în timp, așa că reveniți periodic.
(1) Activare/Dezactivare/Dezinstalare plugin-uri.
Callback-urile de configurare a plugin-ului sunt declanșate de WordPress și nu aveți niciun control asupra modului în care acesta o face. Există câteva lucruri de reținut:
- Niciodată nu folosiți
echo/print
în callback-urile de configurare(!). Acest lucru va duce la mesajulheaders already sent
și WordPress vă va recomanda să dezactivați și să ștergeți plugin-ul... nu întrebați: știu... - Nu veți vedea nicio ieșire vizuală. Dar am adăugat instrucțiuni
exit()
în toate callback-urile pentru a putea urmări ce se întâmplă cu adevărat. Decomentați-le pentru a vedea funcționalitatea. - Este extrem de important să verificați dacă
__FILE__ != WP_PLUGIN_INSTALL
și (dacă nu: întrerupeți!) pentru a vedea dacă plugin-ul este cu adevărat dezinstalat. Recomand să declanșați callback-urileon_deactivation()
în timpul dezvoltării, pentru a vă economisi timpul necesar repunerii la loc. Cel puțin eu așa fac. - De asemenea, implementez câteva măsuri de securitate. Unele sunt făcute și de WordPress, dar hei! Mai bine prevenit decât tratat!.
- Mai întâi interzic accesul direct la fișiere când WordPress nu este încărcat:
defined( 'ABSPATH' ) OR exit;
- Apoi verific dacă utilizatorul curent are permisiunile necesare.
- În final, verific referrerul. Notă: Pot apărea rezultate neașteptate cu un ecran
wp_die()
care solicită permisiuni corespunzătoare (și dacă doriți să încercați din nou... da, sigur), când apare o eroare. Acest lucru se întâmplă deoarece WordPress vă redirecționează, setează$GLOBALS['wp_list_table']->current_action();
laerror_scrape
și apoi verifică referrerul pentrucheck_admin_referer('plugin-activation-error_' . $plugin);
, unde$plugin
este$_REQUEST['plugin']
. Deci redirecționarea are loc în mijlocul încărcării paginii și obțineți această bară de derulare ciudată și ecranul de închidere în interiorul casetei galbene de notificare/mesaj. Dacă se întâmplă acest lucru: rămâneți calmi și căutați eroarea cu ajutorul unor instrucțiuniexit()
și depanare pas cu pas.
- Mai întâi interzic accesul direct la fișiere când WordPress nu este încărcat:
(A) Plugin cu funcții simple
Rețineți că acest lucru poate să nu funcționeze dacă înregistrați callback-urile înaintea definiției funcției.
<?php
defined( 'ABSPATH' ) OR exit;
/**
* Plugin Name: (WCM) Activare/Dezactivare/Dezinstalare - Funcții
* Description: Plugin exemplu pentru a arăta callback-urile de activare/dezactivare/dezinstalare pentru funcții simple.
* 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}" );
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# 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}" );
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# exit( var_dump( $_GET ) );
}
function WCM_Setup_Demo_on_uninstall()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
check_admin_referer( 'bulk-plugins' );
// Important: Verificați dacă fișierul este același
// care a fost înregistrat în timpul hook-ului de dezinstalare.
if ( __FILE__ != WP_UNINSTALL_PLUGIN )
return;
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# 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) Arhitectură bazată pe clase/OOP
Acesta este cel mai comun exemplu în plugin-urile actuale.
<?php
defined( 'ABSPATH' ) OR exit;
/**
* Plugin Name: (WCM) Activare/Dezactivare/Dezinstalare - CLASĂ
* Description: Plugin exemplu pentru a arăta callback-urile de activare/dezactivare/dezinstalare pentru clase/obiecte.
* 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}" );
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# 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}" );
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# exit( var_dump( $_GET ) );
}
public static function on_uninstall()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
check_admin_referer( 'bulk-plugins' );
// Important: Verificați dacă fișierul este același
// care a fost înregistrat în timpul hook-ului de dezinstalare.
if ( __FILE__ != WP_UNINSTALL_PLUGIN )
return;
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# exit( var_dump( $_GET ) );
}
public function __construct()
{
# Inițializați plugin-ul: Legați callback-urile
}
}
(C) Arhitectură bazată pe clase/OOP cu un obiect de configurare extern
Acest scenariu presupune că aveți un fișier principal de plugin și un al doilea fișier numit setup.php
într-un subdirector al plugin-ului numit inc
: ~/wp-content/plugins/your_plugin/inc/setup.php
. Acest lucru va funcționa și atunci când folderul plugin-ului este în afara structurii implicite WordPress, când directorul de conținut este redenumit sau când fișierul de configurare are alt nume. Doar folderul inc
trebuie să aibă același nume și locație relativă față de directorul rădăcină al plugin-ului.
Notă: Puteți pur și simplu să luați cele trei funcții register_*_hook()*
și clasele și să le adăugați în plugin-ul dvs.
Fișierul principal al plugin-ului:
<?php
defined( 'ABSPATH' ) OR exit;
/**
* Plugin Name: (WCM) Activare/Dezactivare/Dezinstalare - FIȘIER/CLASĂ
* Description: Plugin exemplu
* 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;
}
}
Fișierul de configurare:
<?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}" );
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# 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}" );
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# exit( var_dump( $_GET ) );
}
public static function on_uninstall()
{
if ( ! current_user_can( 'activate_plugins' ) )
return;
check_admin_referer( 'bulk-plugins' );
// Important: Verificați dacă fișierul este același
// care a fost înregistrat în timpul hook-ului de dezinstalare.
if ( __FILE__ != WP_UNINSTALL_PLUGIN )
return;
# Decomentați următoarea linie pentru a vedea funcția în acțiune
# exit( var_dump( $_GET ) );
}
}
(2) Actualizări de plugin
Dacă scrieți un plugin care are propriul tabel în baza de date sau opțiuni, pot exista scenarii în care este nevoie să modificați sau să actualizați anumite lucruri.
Din păcate, până acum nu există posibilitatea de a rula ceva la instalarea/actualizarea unui plugin sau temă. Din fericire există o soluție: legați o funcție personalizată la o opțiune personalizată (da, este cam rudimentar - dar funcționează).
function prefix_upgrade_plugin()
{
$v = 'plugin_db_version';
$update_option = null;
// Actualizare la versiunea 2
if ( 2 !== get_option( $v ) )
{
if ( 2 < get_option( $v ) )
{
// Funcția callback trebuie să returneze true la succes
$update_option = custom_upgrade_cb_fn_v3();
// Actualizați opțiunea doar dacă a fost un succes
if ( $update_option )
update_option( $v, 2 );
}
}
// Actualizare la versiunea 3, rulează imediat după actualizarea la versiunea 2
if ( 3 !== get_option( $v ) )
{
// reluați de la început dacă actualizarea anterioară a eșuat
if ( 2 < get_option( $v ) )
return prefix_upgrade_plugin();
if ( 3 < get_option( $v ) )
{
// Funcția callback trebuie să returneze true la succes
$update_option = custom_upgrade_cb_fn_v3();
// Actualizați opțiunea doar dacă a fost un succes
if ( $update_option )
update_option( $v, 3 );
}
}
// Returnează rezultatul funcției de actualizare, pentru a testa succesul/eșecul/eroarea
if ( $update_option )
return $update_option;
return false;
}
add_action('admin_init', 'prefix_upgrade_plugin' );
Această funcție de actualizare este un exemplu nu prea elegant/bine scris, dar, după cum am menționat: este un exemplu și tehnica funcționează bine. O voi îmbunătăți într-o actualizare ulterioară.

Aceasta este minunat, DAR ceea ce chiar vreau să știu sunt lucrurile pe care ar trebui să le includ în metoda mea de dezactivare... de exemplu, ar trebui să șterg tabelele din baza de date sau să le las în cazul în care utilizatorul își schimbă părerea și reactivează pluginul?

Referitor la "DAR": Am menționat că există 3 metode. Una pentru activare, una pentru dezactivare temporară și una pentru dezinstalare. În opinia mea, "uninstall" înseamnă "Șterge-mă și tot ce am făcut", în timp ce "deactivate" este o stare temporară și ar putea fi anulată. Dar: Vezi actualizarea. Am adăugat comentarii despre întrebarea ta + l-am extins cu câteva recomandări de dezvoltare.

Ah, acum înțeleg. Doar o întrebare, când este apelată uninstall? Când fișierele sunt șterse??

Hm. Bună întrebare. plugin.php
este fișierul care conține register_uninstall_hook
și este încărcat împreună cu fișierele inițiale în wp-settings.php (care este fișierul principal pe care să-l verifici întotdeauna). Am actualizat întrebarea cu docblocks din nucleu.

Am adăugat și verificarea constantei WP_UNINSTALL_PLUGIN
, care reprezintă practic numele fișierului, astfel încât să poți păstra funcțiile de dezinstalare într-un fișier separat și să eviți executarea altor funcții care se află în fișierele pluginului tău.

Pentru cititorii ulteriori: Am șters docBlocks-urile deoarece erau din WP 3.2.1. Poți totuși să consulți modificările acestei întrebări pentru a vedea funcțiile din nucleu.

Totuși, nu înțeleg când se întâmplă "uninstall". La ștergerea plugin-ului? La dezactivare?

@OneTrickPony Va fi apelat când utilizatorul apasă pe link-ul de dezinstalare care solicită plugin-ului să se dezinstaleze singur. Am actualizat Răspunsul.

Nu am văzut un astfel de link. Presupun că ar trebui să apară lângă (de)activare/editare/ștergere?

@OneTrickPony Ok, nu mai pot actualiza deoarece am întâlnit wiki-ul comunității, dar Răspunsul la "ce se întâmplă la dezinstalare" este aici: Funcția register_uninstall_hook()
adaugă un fișier de dezinstalare și o funcție callback în opțiunea 'uninstall_plugins'
din tabelul de opțiuni (Options Table). Aceasta este apoi apelată prin funcția uninstall_plugin($name)
, care este apelată de funcția delete_plugin($array_of_plugins)
. Ultima funcție este declanșată în funcție de acțiunea setată în /wp-admin/plugin.php
, când (switch
) cazul este 'delete-selected'
. Asta înseamnă că $_REQUEST['verify-delete']
este setat. E clar până aici? :)

Deci, pentru a rezuma: În interfața de administrare /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')
ȘI include WP_PLUGIN_DIR.'/'.dirname($file).'/uninstall.php'

WP_UNINSTALL_PLUGIN este definit doar dacă se găsește un fișier uninstall.php în directorul plugin-ului. Dacă utilizați tehnica hook-ului de dezinstalare și nu tehnica uninstall.php, constanta nu va fi definită. Acest lucru este evident prin analizarea codului sursă. La momentul scrierii acestui răspuns, aceasta era versiunea curentă: http://core.trac.wordpress.org/browser/tags/3.2.1/wp-admin/includes/plugin.php#L804

Ai dreptate. O să revizuiesc acest răspuns într-o săptămână oricum și voi încărca un depozit GitHub care este mai ușor de întreținut.

Editare: Am inspectat-o și am postat un comentariu la o cerere de pull pe care am făcut-o. Actualizări vor urma.

Verific niște cod bazat pe abordarea orientată pe clase, iar standardele de cod VIP îmi dau probleme cu acele variabile globale $_REQUEST
, spunând că nu sunt sânitizate. Aveți idei cum să ocolesc asta?

@aendrew Ele sunt folosite doar în funcția check_admin_referer()
. Nu este nevoie să fie sanitizate deoarece nucleul WordPress nu face acest lucru și oricum le va compara cu valorile nesanitizate din $_REQUEST
. Dar dacă încep să plângă ca niște fetițe din cauza asta, poți folosi filter_var()
sau esc_attr()
.

@kaiser Așa m-am gândit și eu. Răspunsul tău este menționat chiar în documentația oficială WP, așa că am presupus că nu e o problemă. Mulțumesc pentru răspuns!

@kaiser Totuși, doar ca informație - phpcs
continuă să genereze aceeași eroare chiar dacă înfășori $_REQUEST în filter_var()
sau esc_attr()
. Se pare că e mai degrabă o problemă cu phpcs
decât altceva...

Nu ar trebui să verifici WP_UNINSTALL_PLUGIN în funcția callback dacă folosești wp_register_uninstall_hook, doar dacă folosești uninstall.php

Nu sunt 100% sigur că opțiunea (c) funcționează în versiunea 4.2.4 (sau poate chiar mai devreme). În testele mele, metoda on_activation()
nu este niciodată apelată. În schimb, apare o avertizare PHP în logs; call_user_func_array() expects parameter 1 to be a valid callback, class 'WCM_Setup_Demo_File_Inc' not found in /path/to/wp-includes/plugin.php on line 496
Acest lucru pare logic deoarece fișierul inclus nu este inclus decât atunci când plugin-ul este de fapt activ (deoarece __construct()
este ceea ce include fișierul și asta se întâmplă doar la plugins_loaded

@kaiser În acest răspuns sugerezi să verifici __FILE__ != WP_UNINSTALL_PLUGIN
, totuși, este necesar doar să verifici dacă constanta este definită. Nu este nevoie să verifici valoarea constantei, și de fapt nu va fi întotdeauna corectă.

@J.D. Nu știam despre acea eroare la dezinstalarea în masă. Motivul pentru care verific este simplu: să mă asigur că plugin-ul corect apelează acest lucru. După 5 ani, nu mai sunt foarte sigur despre intențiile mele de atunci. Referitor la link-ul tău către Trac: ai putea să reevaluezi problema cu care te confrunți acolo: dezinstalarea în masă nu funcționează. Degradarea constantei poate să nu fie cea mai bună soluție. În opinia mea, ar trebui să existe în continuare o modalitate de a determina care plugin este dezinstalat.

Codul de dezinstalare are 2 erori.
1. După cum spune @paul, WP_UNINSTALL_PLUGIN nu este definit.
2. Și check_admin_referer( 'bulk-plugins' )
eșuează, împiedicând dezinstalarea.
"Mai bine prevenit decât tratat" sună bine, dar modificările în nucleul WP pot provoca erori.
Folosirea register_uninstall_hook()
în loc de uninstall.php
înseamnă că trebuie să fii atent, inclusiv în orice __construct()
. Dacă totul este static, s-ar putea să fie în regulă.
În comentarii s-a menționat https://tommcfarlin.com/wordpress-plugin-boilerplate/
Este mai actualizat și nu folosește current_user_can
, check_admin_referer
sau register_uninstall_hook
.

Pentru a testa sistemul actual pentru caracteristicile necesare, cum ar fi versiunea PHP sau extensiile instalate, puteți utiliza ceva de genul:
<?php # -*- coding: utf-8 -*-
/**
* Plugin Name: T5 Verifică Cerințele Pluginului
* Description: Testează versiunea PHP și extensiile instalate
* Plugin URI:
* Version: 2013.03.31
* Author: Fuxia Scholz
* Licence: MIT
* License URI: http://opensource.org/licenses/MIT
*/
/*
* Nu porni pe fiecare pagină, este suficient doar pe pagina de plugin-uri.
*/
if ( ! empty ( $GLOBALS['pagenow'] ) && 'plugins.php' === $GLOBALS['pagenow'] )
add_action( 'admin_notices', 't5_check_admin_notices', 0 );
/**
* Testează sistemul actual pentru caracteristicile necesare pluginului.
*
* @return array Erori sau array gol
*/
function t5_check_plugin_requirements()
{
$php_min_version = '5.4';
// vezi 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[] = "Serverul dumneavoastră rulează PHP versiunea $php_current_version, dar
acest plugin necesită cel puțin PHP $php_min_version. Vă rugăm să faceți un upgrade.";
foreach ( $extensions as $extension )
if ( ! extension_loaded( $extension ) )
$errors[] = "Vă rugăm să instalați extensia $extension pentru a rula acest plugin.";
return $errors;
}
/**
* Apelează t5_check_plugin_requirements() și dezactivează acest plugin dacă există erori.
*
* @wp-hook admin_notices
* @return void
*/
function t5_check_admin_notices()
{
$errors = t5_check_plugin_requirements();
if ( empty ( $errors ) )
return;
// Suprimă notificarea "Plugin activat".
unset( $_GET['activate'] );
// numele acestui plugin
$name = get_file_data( __FILE__, array ( 'Plugin Name' ), 'plugin' );
printf(
'<div class="error"><p>%1$s</p>
<p><i>%2$s</i> a fost dezactivat.</p></div>',
join( '</p><p>', $errors ),
$name[0]
);
deactivate_plugins( plugin_basename( __FILE__ ) );
}
Testați cu o verificare pentru PHP 5.5:

Puțin confuz, deci practic nu există un apel la register_activation_hook
aici - de ce să nu-l folosești? De asemenea, acest lucru se va declanșa înainte sau după register_activation_hook
și register_activation_hook
se va declanșa chiar dacă condiția de mai sus nu este îndeplinită?

Am înțeles - dar dacă plugin-ul este activat în afara paginii de plugin (de exemplu ca parte a unei dependințe de temă), atunci verificările tale vor fi omise, nu? Așa că am încercat să mut add_action( 'admin_notices', 't5_check_admin_notices', 0 );
într-un hook de activare și plugin-ul se activează fără a efectua verificările...

@kaiser a explicat cum funcționează hook-ul de activare, eu am vrut să arăt o alternativă. Dacă plugin-ul nu este activat prin pagina de plugin-uri, poate apărea o eroare fatală, da. Această abordare nu poate funcționa pe un hook de activare fără o rescriere serioasă, deoarece acel hook se declanșează după admin_notices
.

De fapt, am dat peste soluția simplă: http://stackoverflow.com/a/13927297/362445

Cred că asta ar trebui să funcționeze: `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 ); } }`
