WordPress函数:register_deactivation_hook 注册插件停用函数
编辑文章简介
register_deactivation_hook 用于在插件被停用时执行清理代码,如停止定时任务、移除临时数据、重置状态等,确保系统在插件停用后保持干净状态。
语法
函数定义于 wp-includes/plugin.php。当插件通过 WordPress 后台停用时,它会注册一个回调函数,该函数在插件停用过程中被执行。
register_deactivation_hook( string $file, callable $callback )
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
$file |
字符串 | 是 | 无 | 主插件文件的路径。通常使用 __FILE__ 魔术常量来引用当前文件。 |
$callback |
可调用函数 | 是 | 无 | 插件停用时要执行的回调函数。该函数不接受参数。 |
返回值:null。此函数不返回任何值,仅用于注册停用钩子。
用法
基础用法
在插件的主文件中,使用 register_deactivation_hook 注册一个函数,该函数将在插件被停用时执行。常用于清理插件运行时创建的临时数据。
<?php
/**
* Plugin Name: 邮件通知插件
* Description: 定期发送系统状态邮件通知。
*/
// 注册激活钩子 - 创建定时任务
register_activation_hook( __FILE__, 'email_notifier_activate' );
function email_notifier_activate() {
// 创建每日发送邮件的定时任务
if ( ! wp_next_scheduled( 'email_notifier_daily_report' ) ) {
wp_schedule_event( time(), 'daily', 'email_notifier_daily_report' );
}
}
// 注册停用钩子 - 清理定时任务
register_deactivation_hook( __FILE__, 'email_notifier_deactivate' );
function email_notifier_deactivate() {
// 获取定时任务的下次执行时间
$timestamp = wp_next_scheduled( 'email_notifier_daily_report' );
// 如果定时任务存在,则清除它
if ( $timestamp ) {
wp_unschedule_event( $timestamp, 'email_notifier_daily_report' );
}
// 也可以使用 wp_clear_scheduled_hook 一次性清除该钩子的所有计划事件
wp_clear_scheduled_hook( 'email_notifier_daily_report' );
// 清理插件生成的临时缓存数据
$cache_keys = array(
'email_notifier_last_sent',
'email_notifier_queue',
'email_notifier_lock'
);
foreach ( $cache_keys as $key ) {
wp_cache_delete( $key, 'email_notifier' );
}
}
// 定时任务的实际处理函数
add_action( 'email_notifier_daily_report', 'send_daily_email_report' );
function send_daily_email_report() {
// 发送邮件的逻辑
// ...
}
进阶用法
处理多站点网络停用、状态清理、与其他插件的依赖关系解除,以及提供用户友好的反馈。
register_deactivation_hook( __FILE__, 'advanced_plugin_deactivation' );
function advanced_plugin_deactivation( $network_wide ) {
// 参数 $network_wide 仅在 WordPress 4.6.0+ 中传递,表示是否网络范围停用
// 1. 清理多站点数据
if ( is_multisite() && $network_wide ) {
// 获取所有站点的 ID
$site_ids = get_sites( array(
'fields' => 'ids',
'number' => 0 // 获取所有站点
) );
foreach ( $site_ids as $site_id ) {
switch_to_blog( $site_id );
cleanup_plugin_for_site();
restore_current_blog();
}
} else {
// 单站点或非网络停用
cleanup_plugin_for_site();
}
// 2. 移除插件创建的角色和能力
$roles_to_remove = array( 'my_plugin_manager', 'my_plugin_editor' );
foreach ( $roles_to_remove as $role_name ) {
if ( get_role( $role_name ) ) {
remove_role( $role_name );
}
}
// 3. 清理自定义数据库表(但保留数据,因为可能重新激活)
// 注意:通常不在停用时删除表,除非明确用户要求
global $wpdb;
$temp_table_name = $wpdb->prefix . 'my_plugin_temp_data';
// 只删除临时表,不删除主数据表
$wpdb->query( "DROP TABLE IF EXISTS $temp_table_name" );
// 4. 通知其他插件或主题本插件已停用
do_action( 'my_plugin_deactivated' );
// 5. 刷新固定链接规则(如果插件注册了自定义文章类型或分类法)
// 这是必要的,因为停用后这些规则不再有效
flush_rewrite_rules( false );
// 6. 记录停用日志(用于调试)
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'My Plugin 已停用。清理完成。' );
}
}
/**
* 为单个站点执行插件清理
*/
function cleanup_plugin_for_site() {
global $wpdb;
// 清理站点特定选项
$options_to_clean = array(
'my_plugin_temp_settings',
'my_plugin_cache_timestamp',
'my_plugin_last_run'
);
foreach ( $options_to_clean as $option ) {
delete_option( $option );
}
// 清理用户元数据(移除插件添加的临时元数据)
// 注意:这里只删除临时数据,不删除用户的重要数据
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->usermeta WHERE meta_key LIKE %s",
'_my_plugin_temp_%'
)
);
// 清理瞬态数据(transients)
$transients_to_delete = array(
'my_plugin_data_cache',
'my_plugin_api_response'
);
foreach ( $transients_to_delete as $transient ) {
delete_transient( $transient );
}
}
易错点
- 与卸载钩子混淆:最常见的概念错误。停用钩子在插件停用时执行,卸载钩子在插件删除时执行。停用时应保留用户数据,卸载时才删除所有数据。如果在停用钩子中删除用户数据,当用户重新激活插件时,所有设置将丢失。
- 错误处理多站点:在多站点环境中,停用钩子默认只在当前站点执行。如果插件被网络范围停用,需要遍历所有站点进行清理。从 WordPress 4.6.0 开始,停用钩子回调函数会接收
$network_wide参数。 - 未清理所有定时任务:如果插件创建了多个定时任务,需要逐一清理。使用
wp_clear_scheduled_hook可以清除特定钩子的所有计划事件,但要注意避免误删其他插件的同名定时任务。 - 直接删除数据库表:在停用钩子中直接删除插件创建的主数据库表通常是错误的,因为用户可能只是暂时停用插件。这应该在卸载钩子中处理,除非有特殊原因。
- 在停用钩子中执行长时间操作:停用钩子执行时间应该尽可能短,因为 WordPress 会等待它完成才继续停用流程。长时间操作可能导致超时或给用户带来不好的体验。
- 忘记刷新固定链接规则:如果插件注册了自定义文章类型或分类法,停用时应该调用
flush_rewrite_rules()来移除相关的重写规则,否则可能导致 404 错误。 - 在停用钩子中调用未加载的函数:与激活钩子类似,停用钩子执行时,插件可能已经部分卸载。避免调用插件中其他可能已被卸载的函数,或使用函数存在性检查。
最佳实践
区分停用与卸载逻辑
明确区分哪些清理工作应该在停用时进行,哪些应该保留到卸载时。通常,停用钩子处理临时数据和运行时状态,卸载钩子处理永久数据。
// 停用钩子 - 清理运行时数据
register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );
function my_plugin_deactivate() {
// 停止定时任务
wp_clear_scheduled_hook( 'my_plugin_cron_job' );
// 清理临时缓存
wp_cache_delete( 'my_plugin_temp_data', 'my_plugin' );
// 移除临时文件(如果存在)
$upload_dir = wp_upload_dir();
$temp_file = $upload_dir['basedir'] . '/my_plugin_temp.json';
if ( file_exists( $temp_file ) ) {
unlink( $temp_file );
}
// 刷新重写规则
flush_rewrite_rules( false );
}
// 卸载钩子 - 删除永久数据(在插件删除时执行)
register_uninstall_hook( __FILE__, 'my_plugin_uninstall' );
function my_plugin_uninstall() {
global $wpdb;
// 删除数据库表
$table_name = $wpdb->prefix . 'my_plugin_data';
$wpdb->query( "DROP TABLE IF EXISTS $table_name" );
// 删除所有插件选项
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
'my_plugin_%'
)
);
// 删除用户元数据
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->usermeta WHERE meta_key LIKE %s",
'my_plugin_%'
)
);
}
提供状态恢复机制
考虑用户可能意外停用插件,或者在测试时需要频繁启停插件。可以创建快照或导出当前设置,以便重新激活时能够恢复。
register_deactivation_hook( __FILE__, 'my_plugin_deactivate_with_backup' );
function my_plugin_deactivate_with_backup() {
// 1. 导出当前设置到临时文件(可选)
$settings = get_option( 'my_plugin_settings', array() );
if ( ! empty( $settings ) ) {
$upload_dir = wp_upload_dir();
$backup_file = $upload_dir['basedir'] . '/my_plugin_settings_backup.json';
// 将设置保存为 JSON 文件
file_put_contents(
$backup_file,
wp_json_encode( $settings, JSON_PRETTY_PRINT )
);
// 记录备份文件路径(存储为瞬态,7天后过期)
set_transient(
'my_plugin_settings_backup_path',
$backup_file,
7 * DAY_IN_SECONDS
);
}
// 2. 执行常规清理
cleanup_plugin_temporary_data();
// 3. 显示管理员通知(通过瞬态,在下次管理员访问时显示)
set_transient( 'my_plugin_deactivation_notice',
'插件已停用。设置已备份,可在7天内重新激活时恢复。',
60 // 60秒后过期,确保只显示一次
);
}
// 在激活钩子中检查并恢复备份
register_activation_hook( __FILE__, 'my_plugin_activate_with_restore' );
function my_plugin_activate_with_restore() {
// 检查是否有备份文件
$backup_file = get_transient( 'my_plugin_settings_backup_path' );
if ( $backup_file && file_exists( $backup_file ) ) {
// 读取备份文件
$backup_data = file_get_contents( $backup_file );
$settings = json_decode( $backup_data, true );
if ( $settings && is_array( $settings ) ) {
// 恢复设置
update_option( 'my_plugin_settings', $settings );
// 删除备份文件
unlink( $backup_file );
delete_transient( 'my_plugin_settings_backup_path' );
}
}
// 执行常规激活逻辑
setup_plugin();
}
优雅处理依赖关系
如果插件与其他插件或主题有依赖关系,在停用时应该检查并通知用户可能的后续影响。
register_deactivation_hook( __FILE__, 'my_plugin_deactivate_with_dependency_check' );
function my_plugin_deactivate_with_dependency_check() {
// 检查是否有其他插件依赖本插件
$dependent_plugins = array();
// 假设我们维护一个列表,或通过某种方式检测
if ( function_exists( 'some_dependent_plugin_function' ) ) {
$dependent_plugins[] = '依赖插件 A';
}
// 或者通过选项检测
$integration_settings = get_option( 'other_plugin_my_plugin_integration', false );
if ( $integration_settings ) {
$dependent_plugins[] = '依赖插件 B';
}
// 如果有依赖的插件,显示警告
if ( ! empty( $dependent_plugins ) ) {
// 将警告保存到管理员通知
set_transient( 'my_plugin_deactivation_warning',
sprintf(
'以下插件可能依赖 My Plugin:%s。停用 My Plugin 可能导致它们功能异常。',
implode( ', ', $dependent_plugins )
),
300 // 5分钟后过期
);
}
// 清理与其他插件/主题的集成数据
cleanup_integration_data();
// 执行常规清理
perform_standard_cleanup();
}
// 在管理员界面显示警告
add_action( 'admin_notices', 'show_my_plugin_deactivation_warning' );
function show_my_plugin_deactivation_warning() {
$warning = get_transient( 'my_plugin_deactivation_warning' );
if ( $warning ) {
echo '<div class="notice notice-warning is-dismissible">';
echo '<p>' . esc_html( $warning ) . '</p>';
echo '</div>';
// 显示后删除瞬态
delete_transient( 'my_plugin_deactivation_warning' );
}
}
性能优化与错误处理
确保停用过程快速、可靠,即使部分清理失败也不会阻止整个停用流程。
register_deactivation_hook( __FILE__, 'my_plugin_safe_deactivation' );
function my_plugin_safe_deactivation() {
$errors = array();
try {
// 1. 清理定时任务(带有错误处理)
$timestamp = wp_next_scheduled( 'my_plugin_daily_task' );
if ( $timestamp ) {
$result = wp_unschedule_event( $timestamp, 'my_plugin_daily_task' );
if ( ! $result ) {
$errors[] = '无法清除定时任务。';
}
}
// 2. 清理数据库临时表(使用安全查询)
global $wpdb;
$temp_table = $wpdb->prefix . 'my_plugin_temp';
// 先检查表是否存在
$table_exists = $wpdb->get_var(
$wpdb->prepare(
"SHOW TABLES LIKE %s",
$temp_table
)
);
if ( $table_exists ) {
$result = $wpdb->query( "DROP TABLE IF EXISTS $temp_table" );
if ( false === $result ) {
$errors[] = '无法删除临时表。';
}
}
// 3. 清理文件(带有安全检查)
$upload_dir = wp_upload_dir();
$log_file = $upload_dir['basedir'] . '/my_plugin_debug.log';
if ( file_exists( $log_file ) && is_writable( $log_file ) ) {
if ( ! unlink( $log_file ) ) {
$errors[] = '无法删除日志文件。';
}
}
} catch ( Exception $e ) {
$errors[] = '停用过程中发生异常:' . $e->getMessage();
}
// 如果有错误,记录但不阻止停用
if ( ! empty( $errors ) && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'My Plugin 停用时发生错误:' . implode( ' ', $errors ) );
// 也可以将错误保存到选项,供后续查看
update_option(
'my_plugin_deactivation_errors',
array(
'time' => current_time( 'mysql' ),
'errors' => $errors
)
);
}
// 无论是否有错误,都继续执行必要的清理
flush_rewrite_rules( false );
// 最后清理错误记录选项本身(避免累积)
delete_option( 'my_plugin_deactivation_errors' );
}