Desinstalar, ativar, desativar um plug-in: recursos típicos e instruções

100

Estou fazendo um plugin wordpress. Quais são as coisas típicas que devo incluir no recurso de desinstalação?

Por exemplo, devo excluir as tabelas que criei na função de instalação?

Eu limpo minhas entradas de opção?

Algo mais?

conservatório
fonte
Eu perdi muito tempo tentando fazê-lo funcionar. O problema é que o gancho init não funciona dentro dos ganchos de registro. Suponho que nem um gancho (ação ou filtro) funcione tão cedo. Leia as notas no link abaixo. codex.wordpress.org/Function_Reference/register_activation_hook Diz: "O registro do gancho dentro do seu gancho carregado com plug-ins é muito tarde e não será executado! (Mesmo quando parece funcionar para o register_deactivation_hook até o gancho wp_loaded.)"
Anton
Fui eu quem atualizou o codex para o que você mencionou, então isso é considerado na resposta ↑ acima. :)
Kaiser

Respostas:

150

Existem três ganchos diferentes . Eles são acionados nos seguintes casos:

  • Desinstalar
  • Desativação
  • Ativação

Como acionar funções com segurança durante os cenários

A seguir, mostramos as maneiras corretas de conectar com segurança funções de retorno de chamada que são acionadas durante as ações mencionadas.

Como você pode usar esse código em um plug-in que usa

  • funções simples,
  • uma classe ou
  • uma classe externa,

Mostrarei três plugins de demonstração diferentes que você pode inspecionar e, posteriormente, implementará o código em seus próprios plug-ins.

Nota importante antecipadamente!

Como esse tópico é extremamente difícil e muito detalhado e tem uma dúzia de casos extremos, essa resposta nunca será perfeita. Vou continuar aprimorando-o com o tempo, portanto, verifique regularmente.

(1) Ative / Desative / Desinstale plugins.

Os retornos de chamada de configuração do plug-in são acionados pelo núcleo e você não tem influência sobre como o núcleo faz isso. Há algumas coisas a serem lembradas:

  • Nunca , alguma echo/printcoisa (!) Durante retornos de chamada de instalação. Isso levará à headers already sentmensagem e o núcleo recomendará desativar e excluir seu plug-in ... não pergunte: eu sei ...
  • Você não verá nenhuma saída visual. Mas adicionei exit()declarações a todos os retornos de chamada diferentes para que você possa ter uma ideia do que realmente está acontecendo. Apenas descomente-os para ver as coisas funcionando.
  • É extremamente importante que você verifique se __FILE__ != WP_PLUGIN_INSTALLe (se não: abortar!) Para ver se alguém realmente está desinstalando o plug-in. Eu recomendaria simplesmente acionar on_deactivation()retornos de chamada durante o desenvolvimento, para que você economize o tempo necessário para recuperar tudo. Pelo menos é o que eu faço.
  • Eu também faço algumas coisas de segurança. Alguns são feitos pelo núcleo também, mas ei! Melhor prevenir do que remediar! .
    • Primeiro, desaprovo o acesso direto a arquivos quando o núcleo não está carregado: defined( 'ABSPATH' ) OR exit;
    • Depois, verifico se o usuário atual tem permissão para executar esta tarefa.
    • Como última tarefa, verifico o referenciador. Nota: Pode haver resultados inesperados com uma wp_die()tela solicitando permissões adequadas (e se você quiser tentar novamente ... sim, com certeza ), quando ocorrer um erro. Isso acontece quando o núcleo o redireciona, define a corrente $GLOBALS['wp_list_table']->current_action();como error_scrapee, em seguida, verifica o referenciador check_admin_referer('plugin-activation-error_' . $plugin);, onde $pluginestá $_REQUEST['plugin']. Portanto, o redirecionamento acontece na metade do carregamento da página e você obtém essa barra de rolagem com fio e a tela de matriz visualiza a caixa de aviso / mensagem amarela do administrador. Se isso acontecer: mantenha a calma e procure o erro com alguma exit()depuração passo a passo.

(A) Plugin de funções simples

Lembre-se de que isso pode não funcionar se você ligar os retornos de chamada antes da definição da função.

<?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) Uma arquitetura baseada em classe / OOP

Este é o exemplo mais comum nos plugins atuais.

<?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) Uma arquitetura baseada em classe / OOP com um objeto de configuração externo

Este cenário pressupõe que você tem um arquivo plugin principal e um segundo arquivo chamado setup.phpem um subdiretório do plugin chamado inc: ~/wp-content/plugins/your_plugin/inc/setup.php. Isso funcionará também quando a pasta do plug-in estiver fora da estrutura padrão da pasta WP, bem como quando o diretório de conteúdo for renomeado ou nos casos em que o arquivo de instalação tiver um nome diferente. Somente a incpasta precisa ter o mesmo nome e localização em relação ao diretório raiz dos plug-ins.

Nota: Você pode simplesmente pegar as três register_*_hook()*funções e as classes e soltá-las no seu plugin.

O principal arquivo de plug-in:

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

O arquivo de instalação:

<?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) Atualizações de plug-in

Se você escrever um plug-in que tenha sua própria tabela ou opções de banco de dados, poderá haver cenários em que você precise alterar ou atualizar as coisas.

Infelizmente até agora não há possibilidade de executar algo na instalação do plugin / tema ou atualização / atualização. Felizmente, há uma solução alternativa: conecte uma função personalizada a uma opção personalizada (sim, é ruim - mas 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' );

Fonte

Esta função de atualização é um exemplo não muito agradável / bem escrito, mas como dito: é um exemplo e a técnica funciona bem. Melhorará isso com uma atualização posterior.

kaiser
fonte
1
Isso é ótimo, mas o que eu realmente quero saber são coisas que devo incluir no meu método de desativação ... por exemplo, devo excluir minhas tabelas no banco de dados ou deixá-las, caso o usuário mude de idéia e reative o plug-in ?
Redconservatory
1
Anúncio "MAS": mencionei que existem três métodos. Um para ativação, um para desativação temporária e outro para desinstalação. Imho "uninstall" diz "Remova-me e tudo o que eu fiz", enquanto "desativar" é um estado temporário e pode ser refeito. Mas: veja atualização. Adicionei comentários sobre o seu Q + e estendi-o com algumas recomendações de desenvolvimento.
Kaiser
3
Ah eu entendo agora. Apenas uma pergunta, quando é chamado o desinstalado? Quando os arquivos são excluídos ??
Redconservatory
1
@aendrew Eles são usados ​​apenas no lado check_admin_referer(). Eles não precisam ser higienizados porque o núcleo não faz isso sozinho e o compararia com $_REQUESTvalores não higienizados . Mas se eles começarem a chorar como menininhas por causa disso, use filter_var()ou use esc_attr().
Kaiser #
2
Você não deve verificar se há WP_UNINSTALL_PLUGIN na função de retorno se estiver usando wp_register_uninstall_hook, apenas se você usar uninstall.php
paul
17

Para testar o sistema atual quanto às características necessárias, como a versão PHP ou extensões instaladas, você pode usar algo assim:

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

Teste com uma verificação do PHP 5.5:

insira a descrição da imagem aqui

fuxia
fonte
Toque confuso, então, basicamente, não há uma chamada para register_activation_hookaqui - por que não usá-lo? Também disparará antes ou depois register_activation_hooke register_activation_hookdisparará mesmo que o acima não passe?
orionrush
É executado após o gancho de ativação apenas na página do plug-in.
fuxia
Entendo - mas se o plug-in estiver ativado fora da página do plug-in (digamos, como parte de uma dependência de tema), suas verificações serão ignoradas, não? Então, tentei mudar add_action( 'admin_notices', 't5_check_admin_notices', 0 );para um gancho de ativação e o plug-in é ativado sem realizar as verificações. . .
31513 orionrush
O @kaiser explicou como o gancho de ativação funciona, eu queria mostrar uma alternativa. Se o plug-in não for ativado por página do plug-in, poderá ocorrer um erro fatal, sim. Essa abordagem não pode funcionar em um gancho de ativação sem reescrever seriamente, porque esse gancho é acionado depois admin_notices.
fuxia
Na verdade, apenas tropeçou no caminho mais fácil: stackoverflow.com/a/13927297/362445
orionrush