卸载,激活,停用插件:典型功能和操作方法


100

我正在制作一个wordpress插件。我应该在卸载功能中包括哪些典型的东西?

例如,是否应该删除在安装功能中创建的任何表?

我要清理我的期权条目吗?

还要别的吗?


我浪费了很多时间试图使它工作。问题是init钩子在注册钩子内部不起作用。我想没有一个钩子(动作或过滤器)不会这么早起作用。阅读下面的链接注释。codex.wordpress.org/Function_Reference/register_activation_hook它说:“将钩子注册到plugins_loaded钩子中为时已晚,它将无法运行!(即使直到wp_loaded钩子似乎也可以对register_deactivation_hook起作用。”)
Anton

我是将编解码器更新为您提到的内容的人,因此在以上↑答案中对此进行了考虑。:)
kaiser 2012年

Answers:


150

有三个不同的钩子。它们在以下情况下触发:

  • 卸载
  • 停用
  • 激活

如何在场景中安全触发功能

下面显示了正确挂钩在上述操作期间触发的回调函数的正确方法。

因为您可以在使用以下代码的插件中使用此代码

  • 普通功能
  • 一堂课或
  • 外部课程,

我将展示三个不同的演示插件,您可以检查它们,然后在自己的插件中实施代码。

提前重要提示!

由于该主题非常困难且非常详细,并且有十几个极端案例,因此该答案永远不会是完美的。我会随着时间的推移不断改进它,因此请定期检查。

(1)激活/停用/卸载插件。

插件设置回调由core触发,您对core的执行方式没有影响。有一些事情要记住:

  • echo/print在安装回调过程中,从不,任何(!)。这将导致出现headers already sent消息,核心将建议停用并删除您的插件...不要问:我知道...
  • 您将看不到任何视觉输出。但是exit()在所有不同的回调中添加了语句,以便您可以了解实际发生的情况。只需取消注释即可看到工作正常。
  • 检查是否__FILE__ != WP_PLUGIN_INSTALL和(如果不是:中止!)以查看是否确实在卸载插件,这一点非常重要。我建议仅on_deactivation()在开发过程中触发回调,这样可以节省您将所有内容恢复原状的时间。至少这是我要做的。
  • 我也做一些安全工作。有些也是由核心完成的,但是,嘿!安全胜过遗憾!
    • 首先,当内核未加载时,我不允许直接文件访问: defined( 'ABSPATH' ) OR exit;
    • 然后检查是否允许当前用户执行此任务。
    • 作为最后一项任务,我检查引荐来源。注意:当您收到错误消息时可能会出现意外的结果,wp_die()要求您提供适当权限的屏幕(如果要再次尝试... 是的,请确保)。这种情况为核心您重定向,设置当前$GLOBALS['wp_list_table']->current_action();error_scrape,然后检查引荐的check_admin_referer('plugin-activation-error_' . $plugin);,这里$plugin$_REQUEST['plugin']。因此,重定向发生在页面加载量的一半时,您将获得此有线滚动条,并且die屏幕会看到黄色的管理员通知/消息框。如果发生这种情况:请保持冷静,并通过一些exit()逐步调试来查找错误。

(A)普通功能插件

请记住,如果在函数定义之前挂接了回调,这可能不起作用。

<?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)基于类/ OOP架构

这是当今插件中最常见的示例。

<?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)具有外部设置对象的基于类/ OOP的体系结构

此方案假定你有一个主要的插件文件并命名第二个文件setup.php中指定的插件的子目录inc~/wp-content/plugins/your_plugin/inc/setup.php。当plugin文件夹不在默认WP文件夹结构之外时,这也将起作用,当内容目录被重命名时,或者在您的安装文件被命名为不同的情况下,这也将起作用。inc相对于插件根目录,仅该文件夹必须具有相同的名称和位置。

注意:您可以简单地使用这三个register_*_hook()*函数和类并将它们放入插件中。

主插件文件:

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

设置文件:

<?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)插件更新

如果编写的插件具有自己的数据库表或选项,则在某些情况下可能需要更改或升级。

遗憾的是,到目前为止,无法在插件/主题安装或更新/升级上运行某些东西。很高兴有一个解决方法:将自定义函数挂接到自定义选项(是的,这很la脚-但它可以工作)。

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

资源

此更新功能不是一个很好/写得很好的示例,但是如前所述:这是一个示例,该技术效果很好。在以后的更新中将对此进行改进。


1
很棒,但是我真正想知道的是我应该在停用方法中包括的东西...例如,如果我删除数据库中的表,还是应该保留它们以防用户改变主意并重新激活插件, ?
redconservatory 2011年

1
广告“但”:我提到了3种方法。一种用于激活,一种用于临时停用,另一种用于失速。Imho的“卸载”说“删除我和我所做的一切”,而“停用”是一个临时状态,可能会重做。但是:请参阅更新。我添加了有关您的Q +的注释,并扩展了一些开发建议。
kaiser

3
啊,我现在明白了。只是一个问题,何时卸载会被调用?什么时候删除文件?
redconservatory 2011年

1
@aendrew它们仅在side中使用check_admin_referer()。他们不需要进行消毒,因为core本身不会这样做,并且无论如何都会将其与未消毒的$_REQUEST值进行比较。但是,如果他们开始哭像,因为那个小女孩,只要使用filter_var()esc_attr()在其上。
kaiser 2014年

2
你不应该检查在回调函数WP_UNINSTALL_PLUGIN如果使用wp_register_uninstall_hook,只有当你使用uninstall.php
保罗

17

要测试当前系统的必需特性(如PHP版本或已安装的扩展名),可以使用以下类似的代码:

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

用支票检查PHP 5.5:

在此处输入图片说明


触摸很困惑,所以基本上没有电话到register_activation_hook这里-为什么不使用它?还会在此之前或之后触发,register_activation_hook并且register_activation_hook即使以上都不通过,也会触发吗?
Orionrush 2013年

它仅在插件页面上的激活挂钩之后运行。
fuxia

我知道-但是,如果插件在插件页面之外被激活(例如作为主题依赖项的一部分),那么您的检查将被跳过否?因此,我尝试add_action( 'admin_notices', 't5_check_admin_notices', 0 );进入激活钩,该插件在不执行检查的情况下激活。。。
Orionrush 2013年

@kaiser解释了激活挂钩的工作原理,我想展示一个替代方法。如果未按插件页面激活插件,则可能发生致命错误,是的。如果不认真重写,此方法无法在激活挂钩上使用,因为该挂钩会在之后触发admin_notices
fuxia

实际上只是偶然发现了一种简单的方法:stackoverflow.com/a/13927297/362445
orionrush
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.