详解 WordPress `deactivate_plugins()` 函数的源码:它如何通过 `deactivate_` 钩子停用插件。

同学们,大家好!今天咱们来聊聊WordPress里一个很重要的函数——deactivate_plugins(),它可是插件卸载的幕后英雄。咱们不光要会用,还要把它扒个底朝天,看看它到底是怎么运作的。

一、deactivate_plugins():插件卸载的指挥官

首先,咱们得认识一下这个函数。deactivate_plugins(),顾名思义,就是停用插件用的。它接受一个或多个插件的文件路径作为参数,然后执行一系列操作,最终让插件“下岗”。

/**
 * Deactivates one or more plugins.
 *
 * @since 2.5.0
 *
 * @param string|string[] $plugins Single plugin or array of plugins to deactivate.
 * @param bool            $silent  Optional. Whether to prevent calling the deactivate hooks. Default false.
 */
function deactivate_plugins( $plugins, $silent = false ) {
  // 函数体
}

参数说明:

  • $plugins: 需要停用的插件的文件路径。可以是单个字符串,也可以是字符串数组。比如 'my-awesome-plugin/my-awesome-plugin.php' 或者 array('my-awesome-plugin/my-awesome-plugin.php', 'another-plugin/another-plugin.php')
  • $silent: 一个可选参数,默认是 false。如果设置为 true,则会阻止调用插件的停用钩子(deactivate_{$plugin})。这个参数一般用不到,除非你真的不想让插件执行任何停用逻辑。

二、源码剖析:deactivate_plugins() 的内部运作

接下来,咱们进入正题,一步一步地拆解 deactivate_plugins() 的源码。为了方便讲解,我把源码简化了一下,去掉了错误处理和一些不太重要的部分,只保留了核心逻辑。

function deactivate_plugins( $plugins, $silent = false ) {
  if ( ! is_array( $plugins ) ) {
    $plugins = array( $plugins );
  }

  $current = get_option( 'active_plugins', array() );

  foreach ( $plugins as $plugin ) {
    $plugin = plugin_basename( trim( $plugin ) );

    if ( ! in_array( $plugin, $current, true ) ) {
      continue; // 插件没激活,跳过
    }

    $plugin_version = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin )['Version'];

    $key = array_search( $plugin, $current, true );
    if ( false !== $key ) {
      unset( $current[ $key ] );
    }

    if ( ! $silent ) {
      /**
       * Fires before a plugin is deactivated.
       *
       * @since 2.5.0
       *
       * @param string $plugin Plugin to deactivate.
       */
      do_action( 'deactivate_' . $plugin );
    }

    /**
     * Fires after a plugin is deactivated.
     *
     * @since 5.5.0
     *
     * @param string $plugin   Plugin to deactivate.
     * @param string $version  The plugin version.
     */
    do_action( 'deactivated_plugin', $plugin, $plugin_version );
  }

  update_option( 'active_plugins', array_values( $current ) );

  do_action( 'deactivated_plugins', $plugins );
}

咱们来一行一行地解读:

  1. 参数处理:

    if ( ! is_array( $plugins ) ) {
      $plugins = array( $plugins );
    }

    这段代码首先检查 $plugins 是否是数组。如果不是,就把它转换成数组,方便后续处理。

  2. 获取已激活插件列表:

    $current = get_option( 'active_plugins', array() );

    这里使用 get_option() 函数从数据库中获取 active_plugins 这个选项的值。这个选项存储了当前已激活的插件列表。如果这个选项不存在,就返回一个空数组。

  3. 循环处理每个插件:

    foreach ( $plugins as $plugin ) {
      // ...
    }

    接下来,代码循环遍历 $plugins 数组,对每个插件执行停用操作。

  4. 清理插件文件名:

    $plugin = plugin_basename( trim( $plugin ) );

    trim() 函数用于去除插件文件路径两端的空格。plugin_basename() 函数用于提取插件的文件名(basename)。 例如,如果 $plugin'my-awesome-plugin/my-awesome-plugin.php',那么 $plugin 会被转换成 'my-awesome-plugin/my-awesome-plugin.php'

  5. 判断插件是否已激活:

    if ( ! in_array( $plugin, $current, true ) ) {
      continue; // 插件没激活,跳过
    }

    这里使用 in_array() 函数检查当前插件是否在已激活的插件列表中。如果不在,说明插件没有激活,直接跳过本次循环。 true 参数表示进行严格类型比较。

  6. 获取插件版本信息:

    $plugin_version = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin )['Version'];

    使用 get_plugin_data() 函数获取插件的头部信息,其中包括插件版本。

  7. 从激活列表中移除插件:

    $key = array_search( $plugin, $current, true );
    if ( false !== $key ) {
      unset( $current[ $key ] );
    }

    array_search() 函数用于在 $current 数组中查找 $plugin 对应的键值。如果找到了,就使用 unset() 函数从 $current 数组中移除该插件。

  8. 触发停用钩子:

    if ( ! $silent ) {
      /**
       * Fires before a plugin is deactivated.
       *
       * @since 2.5.0
       *
       * @param string $plugin Plugin to deactivate.
       */
      do_action( 'deactivate_' . $plugin );
    }
    
    /**
     * Fires after a plugin is deactivated.
     *
     * @since 5.5.0
     *
     * @param string $plugin   Plugin to deactivate.
     * @param string $version  The plugin version.
     */
    do_action( 'deactivated_plugin', $plugin, $plugin_version );

    这是整个过程中最关键的部分!do_action( 'deactivate_' . $plugin ) 会触发一个动态的 action 钩子。这个钩子的名称是 deactivate_ 加上插件的文件名。 插件开发者可以在自己的插件中注册这个钩子的回调函数,以便在插件停用时执行一些清理工作,比如删除数据库表、清除缓存等等。

    do_action( 'deactivated_plugin', $plugin, $plugin_version ) 则触发一个通用的 action 钩子,传递了插件文件名和版本信息。

  9. 更新激活插件列表:

    update_option( 'active_plugins', array_values( $current ) );

    循环结束后,使用 update_option() 函数将更新后的 $current 数组保存到数据库中。array_values() 函数用于重新索引数组,确保键值是连续的数字。

  10. 触发停用插件后的钩子

    do_action( 'deactivated_plugins', $plugins );

    触发一个通用 action 钩子,传递所有停用插件的文件名。

三、deactivate_{$plugin} 钩子:插件的“遗言”

咱们重点说说 deactivate_{$plugin} 这个钩子。它是插件在被停用前执行的最后一段代码,也是插件开发者清理数据的最佳时机。

举个例子,假设你开发了一个名为 my-awesome-plugin 的插件,它的主文件是 my-awesome-plugin/my-awesome-plugin.php。你可以在这个插件中注册 deactivate_my-awesome-plugin/my-awesome-plugin.php 钩子的回调函数,像这样:

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Description: A plugin that does awesome things.
 * Version: 1.0.0
 */

// 在插件激活时执行的操作
function my_awesome_plugin_activate() {
  // 创建数据库表,初始化数据等等
}
register_activation_hook( __FILE__, 'my_awesome_plugin_activate' );

// 在插件停用时执行的操作
function my_awesome_plugin_deactivate() {
  // 删除数据库表,清除缓存等等
  global $wpdb;
  $table_name = $wpdb->prefix . 'my_awesome_table';
  $sql = "DROP TABLE IF EXISTS $table_name;";
  $wpdb->query( $sql );
  delete_option( 'my_awesome_plugin_options' );
}
register_deactivation_hook( __FILE__, 'my_awesome_plugin_deactivate' );

在这个例子中,my_awesome_plugin_deactivate() 函数会在插件被停用时执行。它会删除插件创建的数据库表,清除插件的配置选项。

注意: 使用 register_deactivation_hook 注册的函数,会在调用 deactivate_plugins() 时自动执行,无需使用 add_actionregister_deactivation_hook 内部会处理好与 deactivate_{$plugin} 钩子的关联。

四、deactivated_plugin 钩子:停用后的通知

deactivated_plugin 钩子提供了一个通用的通知机制,让你可以在插件停用后执行一些操作。这个钩子接收两个参数:插件文件名和版本号。

例如,你可以在你的主题或插件中注册 deactivated_plugin 钩子的回调函数,像这样:

function my_plugin_deactivated( $plugin, $version ) {
  // 记录日志,发送邮件等等
  error_log( "Plugin {$plugin} (version {$version}) has been deactivated." );
}
add_action( 'deactivated_plugin', 'my_plugin_deactivated', 10, 2 );

这个例子中,my_plugin_deactivated() 函数会在任何插件被停用时执行。它会将插件的文件名和版本号记录到错误日志中。

五、deactivated_plugins 钩子:批量停用后的总汇
deactivated_plugins 钩子在所有插件停用后触发,接收一个包含所有已停用插件文件名的数组。

function my_plugins_deactivated( $plugins ) {
  // 记录日志,发送邮件等等
  error_log( "Plugins " . implode(', ', $plugins) . " have been deactivated." );
}
add_action( 'deactivated_plugins', 'my_plugins_deactivated', 10, 1 );

六、实战演练:自定义插件停用逻辑

现在,咱们来做一个实战演练,演示如何在插件中自定义停用逻辑。

假设咱们要开发一个名为 my-custom-plugin 的插件,它会在数据库中创建一个名为 my_custom_table 的表。在插件停用时,咱们需要删除这个表。

首先,创建 my-custom-plugin.php 文件:

<?php
/**
 * Plugin Name: My Custom Plugin
 * Description: A plugin that creates a custom database table.
 * Version: 1.0.0
 */

// 在插件激活时执行的操作
function my_custom_plugin_activate() {
  global $wpdb;
  $table_name = $wpdb->prefix . 'my_custom_table';

  $charset_collate = $wpdb->get_charset_collate();

  $sql = "CREATE TABLE $table_name (
    id mediumint(9) NOT NULL AUTO_INCREMENT,
    time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
    name varchar(255) NOT NULL,
    PRIMARY KEY  (id)
  ) $charset_collate;";

  require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
  dbDelta( $sql );
}
register_activation_hook( __FILE__, 'my_custom_plugin_activate' );

// 在插件停用时执行的操作
function my_custom_plugin_deactivate() {
  global $wpdb;
  $table_name = $wpdb->prefix . 'my_custom_table';
  $sql = "DROP TABLE IF EXISTS $table_name;";
  $wpdb->query( $sql );
}
register_deactivation_hook( __FILE__, 'my_custom_plugin_deactivate' );

在这个例子中,my_custom_plugin_activate() 函数会在插件激活时创建 my_custom_table 表。my_custom_plugin_deactivate() 函数会在插件停用时删除这个表。

七、deactivate_plugins() 的使用场景

deactivate_plugins() 函数主要用于以下场景:

  • 插件管理页面: WordPress 后台的插件管理页面使用 deactivate_plugins() 函数来停用插件。
  • 主题: 主题可以使用 deactivate_plugins() 函数来停用与主题不兼容的插件。
  • 插件: 插件可以使用 deactivate_plugins() 函数来停用其他插件,例如,在升级插件时,可以先停用旧版本,再激活新版本。
  • 代码中: 在一些特殊情况下,你可能需要在代码中使用 deactivate_plugins() 函数来停用插件,例如,在检测到安全漏洞时,可以自动停用存在漏洞的插件。

八、总结:deactivate_plugins() 的核心价值

总的来说,deactivate_plugins() 函数的核心价值在于:

  • 提供了一种标准的插件停用机制: 它确保插件能够被安全地停用,而不会导致系统崩溃或其他问题。
  • 允许插件开发者自定义停用逻辑: 通过 deactivate_{$plugin} 钩子,插件开发者可以在插件停用时执行一些清理工作,确保插件的数据被正确地删除。
  • 提供了一种灵活的扩展机制: 通过 deactivated_plugin 钩子,其他插件或主题可以监听插件的停用事件,并执行相应的操作。

九、常见问题

问题 解决方案
插件停用后无法完全删除数据? 确保在 deactivate_{$plugin} 钩子的回调函数中编写了完整的删除逻辑。
插件停用后出现错误? 检查 deactivate_{$plugin} 钩子的回调函数是否抛出了异常。
如何在代码中判断插件是否已激活? 使用 is_plugin_active() 函数。
如何在代码中激活插件? 使用 activate_plugin() 函数。
为什么停用插件后,网站仍然显示插件的功能? 可能是因为插件使用了缓存。尝试清除缓存。也可能是主题或其它插件使用了插件的函数,停用该插件会导致代码报错。

好了,今天的讲座就到这里。希望大家通过今天的学习,能够对 deactivate_plugins() 函数有更深入的了解。记住,理解源码是成为 WordPress 大神的必经之路!下次咱们再聊聊 activate_plugins(),看看插件激活又是怎么一回事。下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注