Désinstaller, Activer, Désactiver un plugin: caractéristiques typiques et procédures

100

Je fais un plugin wordpress. Quelles sont les choses typiques que je devrais inclure dans la fonctionnalité de désinstallation?

Par exemple, dois-je supprimer les tables que j'ai créées dans la fonction d'installation?

Est-ce que je nettoie mes entrées d'option?

Rien d'autre?

redconservatory
la source
J'ai perdu tellement de temps à essayer de le faire fonctionner. Le problème est que le hook init ne fonctionne pas à l'intérieur des hooks d'enregistrement. Je suppose que pas un crochet (action ou filtre) ne fonctionnera pas si tôt. Lire les notes par lien ci-dessous. codex.wordpress.org/Function_Reference/register_activation_hook Il est écrit: "L'enregistrement du hook dans votre hook plugins_loaded est trop tardif et il ne fonctionnera pas! (Même si cela semble fonctionner pour register_deactivation_hook jusqu'au hook wp_loaded.)"
Anton
Je suis celui qui a mis à jour le codex avec ce que vous avez mentionné, c'est donc considéré dans la réponse ↑ ci-dessus. :)
kaiser

Réponses:

150

Il y a trois crochets différents . Ils se déclenchent dans les cas suivants:

  • Désinstaller
  • Désactivation
  • Activation

Comment déclencher des fonctions en toute sécurité pendant les scénarios

Ce qui suit montre les bonnes manières d’accrocher en toute sécurité des fonctions de rappel déclenchées au cours des actions mentionnées.

Comme vous pourriez utiliser ce code dans un plugin qui utilise

  • fonctions simples,
  • une classe ou
  • une classe externe,

Je vais montrer trois plugins de démonstration que vous pouvez inspecter, puis implémenter le code dans vos propres plugins.

Remarque importante dès le départ!

Comme ce sujet est extrêmement difficile et très détaillé et compte une douzaine de cas, cette réponse ne sera jamais parfaite. Je vais continuer à l'améliorer avec le temps, alors revenez régulièrement.

(1) Activer / Désactiver / Désinstaller des plugins.

Les rappels de configuration du plugin sont déclenchés par core et vous n’avez aucune influence sur la manière dont le core le fait. Il y a quelques points à garder à l'esprit:

  • Jamais , jamais echo/printrien (!) Pendant les rappels d'installation. Cela conduira à un headers already sentmessage et le noyau recommandera de désactiver et de supprimer votre plugin ... ne demandez pas: je sais ...
  • Vous ne verrez aucune sortie visuelle. Mais j'ai ajouté des exit()déclarations à tous les rappels afin que vous puissiez avoir un aperçu de ce qui se passe réellement. Il suffit de les commenter pour que les choses fonctionnent.
  • Il est extrêmement important de vérifier si __FILE__ != WP_PLUGIN_INSTALLet (si ce n’est pas le cas: abandonnez!) Pour voir s’il désinstalle réellement le plug-in. Je recommanderais simplement de déclencher des on_deactivation()rappels pendant le développement, afin de vous faire gagner du temps. Tout au moins, c'est ce que je fais.
  • Je fais aussi des trucs de sécurité. Certains sont effectués par noyau également, mais bon! Mieux vaut prévenir que guérir! .
    • Tout d'abord, je refuse l'accès direct aux fichiers lorsque le coeur n'est pas chargé: defined( 'ABSPATH' ) OR exit;
    • Ensuite, je vérifie si l'utilisateur actuel est autorisé à effectuer cette tâche.
    • En dernier lieu, je vérifie le référant. Remarque: il peut y avoir des résultats inattendus avec un wp_die()écran demandant les autorisations appropriées (et si vous voulez réessayer ... ouais, bien sûr ), lorsque vous recevez une erreur. Cela se produit lorsque le noyau vous redirige, définit le courant $GLOBALS['wp_list_table']->current_action();sur error_scrape, puis vérifie le référent check_admin_referer('plugin-activation-error_' . $plugin);, où $pluginest $_REQUEST['plugin']. Donc, la redirection se produit à la moitié du chargement de la page et vous obtenez cette barre de défilement câblée et l'écran des puces insight la notification / message jaune de l'administrateur. Si cela se produit: restez calme et recherchez l'erreur avec un exit()débogage pas à pas.

(A) Plugin des fonctions simples

N'oubliez pas que cela pourrait ne pas fonctionner si vous associez les rappels avant la définition de la fonction.

<?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) Une architecture de classe / POO

C'est l'exemple le plus courant dans les plugins actuels.

<?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) Une architecture de classe / OOP avec un objet de configuration externe

Ce scénario suppose que vous avez un fichier de plugin principal et un second fichier nommé setup.phpdans un sous - répertoire du plugin nommé inc: ~/wp-content/plugins/your_plugin/inc/setup.php. Cela fonctionnera également lorsque le dossier du plug-in se trouve en dehors de la structure de dossiers WP par défaut, ainsi que lorsque le répertoire de contenu est renommé ou dans les cas où votre fichier d'installation porte un nom différent. Seul le incdossier doit avoir le même nom et le même emplacement que le répertoire racine des plugins.

Note: Vous pouvez simplement prendre les trois register_*_hook()*fonctions et les classes et les déposer dans votre plugin.

Le fichier de plugin 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;
    }
}

Le fichier d'installation:

<?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) Mises à jour du plugin

Si vous écrivez un plug-in qui possède sa propre table de base de données ou ses propres options, il peut arriver que vous deviez modifier ou mettre à niveau des éléments.

Malheureusement, il n’existe jusqu’à présent aucune possibilité d’exécuter quelque chose sur l’installation ou la mise à jour / mise à niveau du plugin / thème. Heureusement, il existe un moyen de contourner le problème: associez une fonction personnalisée à une option personnalisée (oui, c'est nul, mais cela fonctionne).

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' );

La source

Cette fonction de mise à jour est un exemple pas très agréable / bien écrit, mais comme il est dit: C’est un exemple et la technique fonctionne bien. Améliorera cela avec une mise à jour ultérieure.

kaiser
la source
1
C’est génial MAIS ce que je veux vraiment savoir, ce sont des choses que je devrais inclure dans ma méthode de désactivation ... par exemple, dois-je supprimer mes tables dans la base de données ou les laisser au cas où l’utilisateur changerait d’avis et réactiverait le plugin? ?
redconservatory
1
Annonce "MAIS": J'ai mentionné qu'il y a 3 méthodes. Un pour l'activation, un pour la désactivation temporaire et un pour le désinstaller. À mon humble avis, "désinstaller" dit "Supprimez-moi et tout ce que j'ai fait", tandis que "désactiver" est un état temporaire et peut être refait. Mais: voir mise à jour. J'ai ajouté des commentaires à propos de votre Q +, avec quelques recommandations de développement.
Kaiser
3
Ah je comprends maintenant. Juste une question, quand désinstallé s'appelle-t-il? Quand les fichiers sont supprimés ??
redconservatory
1
@aendrew Ils ne sont utilisés que dans les côtés check_admin_referer(). Ils n'ont pas besoin d'être désinfectés, car le noyau ne le fait pas lui-même et le comparerait de toute façon à des $_REQUESTvaleurs non normalisées . Mais si elles se mettent à pleurer comme des petites filles à cause de cela, utilisez-les filter_var()ou esc_attr()sur elles.
Kaiser
2
Vous ne devez pas rechercher WP_UNINSTALL_PLUGIN dans la fonction de rappel si vous utilisez wp_register_uninstall_hook, uniquement si vous utilisez uninstall.php
paul le
17

Pour tester le système actuel des fonctionnalités requises telles que la version PHP ou les extensions installées, vous pouvez utiliser quelque chose comme ça:

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

Testez avec un contrôle pour PHP 5.5:

entrez la description de l'image ici

fuxia
la source
Toucher confus, donc, en gros, il n'y a pas d'appel register_activation_hook- pourquoi ne pas l'utiliser? Aussi, ce feu va-t-il avant ou après register_activation_hooket le register_activation_hookfeu même si ce qui précède ne passe pas?
orionrush
Il ne fonctionne qu'après le raccordement d'activation sur la page du plugin uniquement.
fuxia
Je vois - mais si le plugin est activé en dehors de la page du plugin (par exemple dans le cadre d'une dépendance à un thème), vos contrôles seront ignorés non? J'ai donc essayé de passer add_action( 'admin_notices', 't5_check_admin_notices', 0 );à un hook d'activation et le plugin s'active sans effectuer les vérifications. . .
orionrush
@ Kaiser a expliqué comment fonctionne le hook d'activation, je voulais montrer une alternative. Si le plugin n'est pas activé par page de plugin, une erreur fatale peut se produire, oui. Cette approche ne peut pas fonctionner sur un hook d'activation sans une réécriture sérieuse, car ce hook se déclenche après admin_notices.
fuxia
En fait, juste tombé sur la solution de facilité: stackoverflow.com/a/13927297/362445
orionrush