Desinstalar, activar, desactivar un complemento: características típicas y procedimientos

100

Estoy haciendo un complemento de WordPress. ¿Cuáles son las cosas típicas que debo incluir en la función de desinstalación?

Por ejemplo, ¿debería eliminar las tablas que creé en la función de instalación?

¿Limpio mis entradas de opciones?

¿Algo más?

conservador rojo
fuente
Perdí mucho tiempo tratando de hacerlo funcionar. El problema es que el gancho de inicio no funciona dentro de los ganchos de registro. Supongo que ningún gancho (acción o filtro) no funcionará tan temprano. Lea las notas por enlace a continuación. codex.wordpress.org/Function_Reference/register_activation_hook Dice: "¡Registrar el gancho dentro de su gancho plugins_loaded es demasiado tarde y no se ejecutará! (¡Incluso cuando parece funcionar para register_deactivation_hook hasta que wp_loaded hook.")
Anton
Yo fui quien actualizó el códice a lo que mencionaste, así que esto se considera en la respuesta anterior ↑. :)
kaiser

Respuestas:

150

Hay tres ganchos diferentes . Se disparan en los siguientes casos:

  • Desinstalar
  • Desactivación
  • Activación

Cómo activar funciones de forma segura durante los escenarios

A continuación se muestran las formas correctas de enganchar de forma segura las funciones de devolución de llamada que se activan durante las acciones mencionadas.

Como podría usar este código en un complemento que usa

  • funciones simples,
  • una clase o
  • una clase externa

Le mostraré tres complementos de demostración diferentes que puede inspeccionar y luego implementará el código en sus propios complementos.

Nota importante por adelantado!

Como este tema es extremadamente difícil y muy detallado y tiene una docena de casos extremos, esta respuesta nunca será perfecta. Seguiré mejorando con el tiempo, así que vuelve a consultar de forma regular.

(1) Activar / Desactivar / Desinstalar complementos.

Las devoluciones de llamada de configuración del complemento son activadas por core y usted no tiene influencia sobre cómo lo hace core. Hay algunas cosas a tener en cuenta:

  • Nunca , nunca echo/printnada (!) Durante las devoluciones de llamada de configuración. Esto conducirá a un headers already sentmensaje y Core recomendará desactivar y eliminar su complemento ... no pregunte: Lo sé ...
  • No verá ninguna salida visual. Pero agregué exit()declaraciones a todas las devoluciones de llamada diferentes para que pueda obtener algunas ideas sobre lo que realmente está sucediendo. Simplemente descomente para ver las cosas funcionando.
  • Es extremadamente importante que compruebe si __FILE__ != WP_PLUGIN_INSTALLy (si no: ¡abortar!) Para ver si realmente está desinstalando el complemento. Recomiendo simplemente activar las on_deactivation()devoluciones de llamada durante el desarrollo, para que se ahorre el tiempo que necesitaría para recuperar todo. Al menos esto es lo que hago.
  • También hago algunas cosas de seguridad. Algunas también se hacen por núcleo, pero ¡oye! ¡Más vale prevenir que curar! .
    • Primero, no autorizo ​​el acceso directo a archivos cuando el núcleo no está cargado: defined( 'ABSPATH' ) OR exit;
    • Luego verifico si el usuario actual puede realizar esta tarea.
    • Como última tarea, verifico el referente. Nota: Puede haber resultados inesperados con una wp_die()pantalla que solicita los permisos adecuados (y si desea volver a intentarlo ... sí, claro ), cuando obtiene un error. Esto sucede cuando el núcleo lo redirige, establece la corriente $GLOBALS['wp_list_table']->current_action();en error_scrapey luego verifica el referente para saber check_admin_referer('plugin-activation-error_' . $plugin);dónde $pluginestá $_REQUEST['plugin']. Por lo tanto, la redirección ocurre a la mitad de la carga de la página y obtienes esta barra de desplazamiento con cable y la pantalla del troquel muestra el cuadro de aviso / mensaje de administrador amarillo. Si esto sucede: mantén la calma y solo busca el error con algunos exit()y depuración paso a paso.

(A) Complemento de funciones simples

Recuerde que esto podría no funcionar si conecta las devoluciones de llamada antes de la definición de la función.

<?php
defined( 'ABSPATH' ) OR exit;
/**
 * Plugin Name: (WCM) Activate/Deactivate/Uninstall - Functions
 * Description: Example Plugin to show activation/deactivation/uninstall callbacks for plain functions.
 * 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}" );

    # Uncomment the following line to see the function in action
    # 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}" );

    # Uncomment the following line to see the function in action
    # exit( var_dump( $_GET ) );
}

function WCM_Setup_Demo_on_uninstall()
{
    if ( ! current_user_can( 'activate_plugins' ) )
        return;
    check_admin_referer( 'bulk-plugins' );

    // Important: Check if the file is the one
    // that was registered during the uninstall hook.
    if ( __FILE__ != WP_UNINSTALL_PLUGIN )
        return;

    # Uncomment the following line to see the function in action
    # 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) Una arquitectura basada en clases / OOP

Este es el ejemplo más común en los complementos de hoy en día.

<?php
defined( 'ABSPATH' ) OR exit;
/**
 * Plugin Name: (WCM) Activate/Deactivate/Uninstall - CLASS
 * Description: Example Plugin to show activation/deactivation/uninstall callbacks for classes/objects.
 * 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}" );

        # Uncomment the following line to see the function in action
        # 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}" );

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }

    public static function on_uninstall()
    {
        if ( ! current_user_can( 'activate_plugins' ) )
            return;
        check_admin_referer( 'bulk-plugins' );

        // Important: Check if the file is the one
        // that was registered during the uninstall hook.
        if ( __FILE__ != WP_UNINSTALL_PLUGIN )
            return;

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }

    public function __construct()
    {
        # INIT the plugin: Hook your callbacks
    }
}

(C) Una arquitectura basada en clases / OOP con un objeto de configuración externo

Este escenario supone que tienes un archivo plugin principal y un segundo archivo con el nombre setup.phpen un subdirectorio del plugin llamado inc: ~/wp-content/plugins/your_plugin/inc/setup.php. Esto funcionará también cuando la carpeta del complemento esté fuera de la estructura de carpetas WP predeterminada, así como cuando se cambie el nombre del directorio de contenido o en los casos en que su archivo de instalación tenga un nombre diferente. Solo la inccarpeta debe tener el mismo nombre y ubicación relativa del directorio raíz de complementos.

Nota: Simplemente puede tomar las tres register_*_hook()*funciones y las clases y soltarlas en su complemento.

El archivo de complemento principal:

<?php
defined( 'ABSPATH' ) OR exit;
/**
 * Plugin Name: (WCM) Activate/Deactivate/Uninstall - FILE/CLASS
 * Description: Example Plugin
 * 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;
    }
}

El archivo de instalación:

<?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}" );

        # Uncomment the following line to see the function in action
        # 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}" );

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }

    public static function on_uninstall()
    {
        if ( ! current_user_can( 'activate_plugins' ) )
            return;
        check_admin_referer( 'bulk-plugins' );

        // Important: Check if the file is the one
        // that was registered during the uninstall hook.
        if ( __FILE__ != WP_UNINSTALL_PLUGIN )
            return;

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }
}

(2) Actualizaciones de complementos

Si escribe un complemento que tiene su propia tabla u opciones de base de datos, puede haber situaciones en las que necesite cambiar o actualizar cosas.

Lamentablemente, hasta ahora no hay posibilidad de ejecutar algo en la instalación del complemento / tema o actualización / actualización. Con mucho gusto hay una solución alternativa: conecta una función personalizada a una opción personalizada (sí, es poco convincente, pero funciona).

function prefix_upgrade_plugin() 
{
    $v = 'plugin_db_version';
    $update_option = null;
    // Upgrade to version 2
    if ( 2 !== get_option( $v ) ) 
    {
        if ( 2 < get_option( $v ) )
        {
            // Callback function must return true on success
            $update_option = custom_upgrade_cb_fn_v3();

            // Only update option if it was an success
            if ( $update_option )
                update_option( $v, 2 );
        }
    }

    // Upgrade to version 3, runs just after upgrade to version 2
    if ( 3 !== get_option( $v ) ) 
    {
        // re-run from beginning if previous update failed
        if ( 2 < get_option( $v ) )
            return prefix_upgrade_plugin();

        if ( 3 < get_option( $v ) )
        {
            // Callback function must return true on success
            $update_option = custom_upgrade_cb_fn_v3();

            // Only update option if it was an success
            if ( $update_option )
                update_option( $v, 3 );
        }
    }

    // Return the result from the update cb fn, so we can test for success/fail/error
    if ( $update_option )
        return $update_option;

return false;
}
add_action('admin_init', 'prefix_upgrade_plugin' );

Fuente

Esta función de actualización es un ejemplo no tan bueno / bien escrito, pero como se dijo: es un ejemplo y la técnica funciona bien. Mejorará eso con una actualización posterior.

emperador
fuente
1
Esto es genial, PERO lo que realmente quiero saber son cosas que debo incluir en mi método de desactivación ... por ejemplo, ¿debería eliminar mis tablas en la base de datos o dejarlas en caso de que el usuario cambie de opinión y vuelva a activar el complemento? ?
redconservatory
1
Anuncio "PERO": mencioné que hay 3 métodos. Uno para la activación, uno para la desactivación temporal y otro para la desinstalación. Imho "desinstalar" dice "Eliminarme y todo lo que hice", mientras que "desactivar" es un estado temporal y podría rehacerse. Pero: ver actualización. Agregué comentarios sobre su Q + y lo extendí con algunas recomendaciones de desarrollo.
Kaiser
3
Ah, ahora entiendo. Solo una pregunta, ¿cuándo se llama desinstalado? Cuando se eliminan los archivos?
redconservatory
1
@aendrew Solo se usan de lado check_admin_referer(). No necesitan desinfectarse porque el núcleo no lo hace por sí mismo y de todos modos lo compararía con $_REQUESTvalores no desinfectados . Pero si comienzan a llorar como niñas por eso, solo utilícelo filter_var()o esc_attr()en él.
Kaiser
2
No debe verificar WP_UNINSTALL_PLUGIN en la función de devolución de llamada si usa wp_register_uninstall_hook, solo si usa uninstall.php
paul
17

Para probar el sistema actual en busca de características requeridas, como la versión PHP o las extensiones instaladas, puede usar algo como eso:

<?php  # -*- coding: utf-8 -*-
/**
 * Plugin Name: T5 Check Plugin Requirements
 * Description: Test for PHP version and installed extensions
 * Plugin URI:
 * Version:     2013.03.31
 * Author:      Thomas Scholz
 * Author URI:  http://toscho.de
 * Licence:     MIT
 * License URI: http://opensource.org/licenses/MIT
 */

/*
 * Don't start on every page, the plugin page is enough.
 */
if ( ! empty ( $GLOBALS['pagenow'] ) && 'plugins.php' === $GLOBALS['pagenow'] )
    add_action( 'admin_notices', 't5_check_admin_notices', 0 );

/**
 * Test current system for the features the plugin needs.
 *
 * @return array Errors or empty array
 */
function t5_check_plugin_requirements()
{
    $php_min_version = '5.4';
    // see 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[] = "Your server is running PHP version $php_current_version but
            this plugin requires at least PHP $php_min_version. Please run an upgrade.";

    foreach ( $extensions as $extension )
        if ( ! extension_loaded( $extension ) )
            $errors[] = "Please install the extension $extension to run this plugin.";

    return $errors;

}

/**
 * Call t5_check_plugin_requirements() and deactivate this plugin if there are error.
 *
 * @wp-hook admin_notices
 * @return  void
 */
function t5_check_admin_notices()
{
    $errors = t5_check_plugin_requirements();

    if ( empty ( $errors ) )
        return;

    // Suppress "Plugin activated" notice.
    unset( $_GET['activate'] );

    // this plugin's name
    $name = get_file_data( __FILE__, array ( 'Plugin Name' ), 'plugin' );

    printf(
        '<div class="error"><p>%1$s</p>
        <p><i>%2$s</i> has been deactivated.</p></div>',
        join( '</p><p>', $errors ),
        $name[0]
    );
    deactivate_plugins( plugin_basename( __FILE__ ) );
}

Prueba con un cheque para PHP 5.5:

ingrese la descripción de la imagen aquí

fuxia
fuente
Toque confundido, así que básicamente no hay una llamada register_activation_hookaquí, ¿por qué no usarlo? ¿Esto también se disparará antes o después register_activation_hooky se register_activation_hookdisparará incluso si lo anterior no pasa?
orionrush
Se ejecuta después del enlace de activación en la página del complemento solamente.
fuxia
Ya veo, pero si el complemento se activa fuera de la página del complemento (por ejemplo, como parte de una dependencia del tema), entonces sus comprobaciones se omitirán, ¿no? Así que intenté pasar add_action( 'admin_notices', 't5_check_admin_notices', 0 );a un gancho de activación y el complemento se activa sin realizar las comprobaciones. . .
orionrush
@kaiser ha explicado cómo funciona el gancho de activación, quería mostrar una alternativa. Si el complemento no está activado por página de complemento, puede ocurrir un error fatal, sí. Este enfoque no puede funcionar en un gancho de activación sin una reescritura seria, porque ese gancho se dispara después admin_notices.
fuxia
En realidad, me topé de la manera fácil: stackoverflow.com/a/13927297/362445
orionrush el