WordPress函数:remove_action 移除已注册的动作钩子

编辑文章

简介

remove_action 用于从特定的动作钩子(Action Hook)中移除之前通过 add_action 注册的回调函数,允许你禁用或修改其他插件、主题或核心功能的行为。

语法

函数定义于 wp-includes/plugin.php。它内部会调用 WP_Hook 类的相关方法来移除指定的回调函数。

remove_action( string $hook_name, callable $callback, int $priority = 10 )
参数 类型 必需 默认值 说明
$hook_name 字符串 要从中移除回调函数的动作钩子名称。
$callback 可调用函数 要移除的回调函数。必须与 add_action 注册时的函数完全相同(函数名或对象方法引用)。
$priority 整数 10 要移除的回调函数的优先级。必须与 add_action 注册时的优先级完全一致。

返回值:布尔值。成功移除返回 true,失败返回 false

用法

基础用法

移除 WordPress 核心、主题或其他插件添加的特定功能。常见场景是移除不必要的脚本、样式或管理界面元素。

例如,你想移除 WordPress 后台管理栏中的”我的站点”菜单(针对多站点网络):

// 在主题的 functions.php 或自定义插件中
add_action( 'init', 'remove_admin_bar_items' );

function remove_admin_bar_items() {
    // 确保用户已登录且有管理权限
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    // 移除"我的站点"菜单
    remove_action( 'admin_bar_menu', 'wp_admin_bar_my_sites_menu', 20 );
}

移除 WordPress 默认的 Emoji 支持,以提高前端性能:

// 在主题的 functions.php 中
add_action( 'init', 'disable_emoji_support' );

function disable_emoji_support() {
    // 移除前端相关的 Emoji 动作
    remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
    remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
    remove_action( 'wp_print_styles', 'print_emoji_styles' );
    remove_action( 'admin_print_styles', 'print_emoji_styles' );

    // 移除 feed 中的 Emoji
    remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
    remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );

    // 移除电子邮件中的 Emoji
    remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
}

进阶用法

在特定条件下动态移除回调函数,或者在插件/主题开发中提供可配置的钩子移除机制。

场景:你正在开发一个插件,需要根据用户设置移除 WooCommerce 的某些默认功能:

class WooCommerce_Customizer {

    public function __construct() {
        add_action( 'wp_loaded', array( $this, 'setup_customizations' ) );
    }

    public function setup_customizations() {
        // 获取用户设置
        $settings = get_option( 'my_woocommerce_settings', array() );

        // 根据设置移除相应的动作
        if ( isset( $settings['disable_product_reviews'] ) && $settings['disable_product_reviews'] ) {
            $this->remove_product_reviews();
        }

        if ( isset( $settings['disable_related_products'] ) && $settings['disable_related_products'] ) {
            $this->remove_related_products();
        }

        if ( isset( $settings['custom_checkout_fields'] ) && $settings['custom_checkout_fields'] ) {
            $this->customize_checkout_fields();
        }
    }

    private function remove_product_reviews() {
        // 移除 WooCommerce 的评论功能
        remove_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_rating', 5 );
        remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_rating', 10 );
        remove_action( 'woocommerce_product_tabs', 'woocommerce_reviews_tab', 30 );
        remove_action( 'woocommerce_product_tab_panels', 'woocommerce_reviews_panel', 30 );

        // 还需要从产品类型中移除评论支持
        add_filter( 'woocommerce_product_tabs', array( $this, 'remove_reviews_tab' ), 98 );
    }

    private function remove_related_products() {
        // 移除相关产品展示
        remove_action( 'woocommerce_after_single_product_summary', 'woocommerce_output_related_products', 20 );
    }

    private function customize_checkout_fields() {
        // 移除默认的结账字段,以便添加自定义字段
        add_filter( 'woocommerce_checkout_fields', array( $this, 'override_checkout_fields' ), 999 );
    }

    public function remove_reviews_tab( $tabs ) {
        unset( $tabs['reviews'] );
        return $tabs;
    }

    public function override_checkout_fields( $fields ) {
        // 移除账单地址中的公司字段
        unset( $fields['billing']['billing_company'] );

        // 移除送货地址中的地址2字段
        unset( $fields['shipping']['shipping_address_2'] );

        return $fields;
    }
}

// 初始化自定义器
new WooCommerce_Customizer();

另一个场景:在主题开发中,根据页面模板或条件移除某些动作:

add_action( 'template_redirect', 'conditionally_remove_actions' );

function conditionally_remove_actions() {
    // 只在单篇文章页面执行
    if ( is_single() ) {
        // 移除文章作者信息显示
        remove_action( 'twenty_twenty_one_entry_meta_header', 'twenty_twenty_one_posted_by', 10 );

        // 添加自定义的作者显示
        add_action( 'twenty_twenty_one_entry_meta_header', 'custom_posted_by', 10 );
    }

    // 在首页移除侧边栏
    if ( is_front_page() ) {
        remove_action( 'twenty_twenty_one_sidebar', 'twenty_twenty_one_sidebar', 10 );
    }

    // 在 WooCommerce 产品页面移除特定的脚本
    if ( is_product() ) {
        // 移除 WooCommerce 的某些脚本
        add_action( 'wp_enqueue_scripts', 'remove_woocommerce_scripts', 100 );
    }
}

function custom_posted_by() {
    // 自定义作者显示逻辑
    printf(
        '<span class="byline">%s <span class="author vcard"><a class="url fn n" href="%s">%s</a></span></span>',
        esc_html_x( '由', 'post author', 'my-theme' ),
        esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ),
        esc_html( get_the_author() )
    );
}

function remove_woocommerce_scripts() {
    // 移除 WooCommerce 的灯箱脚本
    wp_dequeue_script( 'prettyPhoto' );
    wp_dequeue_script( 'prettyPhoto-init' );
    wp_dequeue_style( 'woocommerce_prettyPhoto_css' );
}

易错点

  • 移除时机不正确:最常见的问题是尝试在回调函数执行之后才移除它。必须在回调函数执行之前调用 remove_action。通常需要在 initwp_loaded 或更早的钩子中执行移除操作,具体取决于目标回调函数被添加的时间。
  • 优先级不匹配:如果 add_action 时指定了优先级(第三个参数),那么 remove_action 时必须使用完全相同的优先级。默认优先级是10,但如果目标回调函数使用了不同的优先级,你必须指定相同的值。
  • 回调函数引用不一致:当移除类方法时,必须使用与添加时完全相同的格式。如果使用数组(array($object, 'method_name')),移除时也必须使用相同的数组格式。匿名函数(闭包)无法被移除,因为无法获得相同的引用。
  • 移除未添加的回调函数:尝试移除一个从未添加过的回调函数不会产生错误,但会返回 false。这通常不是问题,但如果你依赖于返回值进行逻辑判断,需要注意。
  • 移除核心功能导致意外后果:移除 WordPress 核心、主题或其他插件的功能可能导致意外行为或错误。在移除前,确保你了解该功能的作用,并测试移除后的兼容性。
  • 在多站点环境中考虑不周:某些动作可能只在特定条件下添加,或者根据多站点配置有所不同。移除操作前应进行适当的条件检查。

最佳实践

确定正确的移除时机

了解目标回调函数是在何时被添加的,以确保在正确的时间移除它。使用 WordPress 的标准加载顺序作为参考。

add_action( 'after_setup_theme', 'remove_unwanted_theme_features' );

function remove_unwanted_theme_features() {
    // 主题功能通常在 after_setup_theme 钩子中设置
    // 所以在此之后移除是安全的

    // 移除主题自定义背景支持
    remove_theme_support( 'custom-background' );

    // 移除主题的某种布局支持
    remove_action( 'init', 'theme_specific_layout_setup' );
}

add_action( 'wp_loaded', 'remove_plugin_functionality' );

function remove_plugin_functionality() {
    // 大多数插件在 init 或 wp_loaded 钩子中注册功能
    // wp_loaded 在 init 之后,确保所有插件已加载

    if ( class_exists( 'Some_Plugin_Class' ) ) {
        // 移除插件添加的某个前端功能
        remove_action( 'wp_enqueue_scripts', array( 'Some_Plugin_Class', 'enqueue_styles' ), 10 );
    }
}

安全地移除回调函数

在移除回调函数之前进行检查,确保目标回调函数确实存在,避免不必要的操作或错误。

add_action( 'wp_loaded', 'safely_remove_actions' );

function safely_remove_actions() {
    global $wp_filter;

    // 检查钩子是否存在以及回调函数是否已注册
    if ( isset( $wp_filter['wp_head'] ) ) {
        // 检查特定的回调函数是否在优先级10处被添加
        $callbacks = $wp_filter['wp_head']->callbacks;

        if ( isset( $callbacks[10] ) ) {
            foreach ( $callbacks[10] as $key => $callback ) {
                // 查找并移除特定的函数
                if ( is_string( $callback['function'] ) && 'unwanted_function' === $callback['function'] ) {
                    remove_action( 'wp_head', 'unwanted_function', 10 );
                    break;
                }
            }
        }
    }

    // 更简单的方法:使用 has_action 检查
    if ( has_action( 'wp_footer', 'some_function_to_remove' ) ) {
        remove_action( 'wp_footer', 'some_function_to_remove' );
    }
}

创建可配置的移除机制

在插件或主题开发中,提供设置选项让用户选择要移除的功能,而不是硬编码移除逻辑。

class Configurable_Action_Remover {

    private $removals = array();

    public function __construct() {
        // 从数据库获取配置
        $this->removals = get_option( 'custom_action_removals', array() );

        // 在适当的时机应用移除
        add_action( 'wp_loaded', array( $this, 'apply_removals' ) );
    }

    public function apply_removals() {
        foreach ( $this->removals as $hook => $callbacks ) {
            foreach ( $callbacks as $callback => $priority ) {
                // 使用 has_action 验证回调函数存在
                if ( false !== has_action( $hook, $callback ) ) {
                    remove_action( $hook, $callback, $priority );

                    // 可选的:记录移除操作
                    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                        error_log( sprintf( 
                            'Removed action: %s - %s (priority: %d)', 
                            $hook, 
                            is_string( $callback ) ? $callback : 'callback', 
                            $priority 
                        ) );
                    }
                }
            }
        }
    }

    // 在管理界面提供配置选项
    public function register_settings() {
        register_setting( 'reading', 'custom_action_removals' );

        add_settings_section(
            'action_removal_section',
            '动作钩子移除设置',
            array( $this, 'render_section_description' ),
            'reading'
        );

        add_settings_field(
            'action_removals',
            '要移除的动作',
            array( $this, 'render_removal_settings' ),
            'reading',
            'action_removal_section'
        );
    }

    public function render_section_description() {
        echo '<p>选择要从系统中移除的默认功能。</p>';
    }

    public function render_removal_settings() {
        $options = array(
            'wp_head' => array(
                'print_emoji_detection_script' => '移除 Emoji 脚本',
                'wp_generator' => '移除 WordPress 版本信息',
            ),
            'admin_bar_menu' => array(
                'wp_admin_bar_wp_menu' => '移除 WordPress 标志菜单',
                'wp_admin_bar_my_sites_menu' => '移除"我的站点"菜单',
            ),
        );

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

        foreach ( $options as $hook => $callbacks ) {
            echo '<h4>' . esc_html( $hook ) . '</h4>';

            foreach ( $callbacks as $callback => $label ) {
                $checked = isset( $current[ $hook ][ $callback ] ) ? 'checked' : '';
                echo '<label><input type="checkbox" name="custom_action_removals[' . esc_attr( $hook ) . '][' . esc_attr( $callback ) . ']" value="10" ' . $checked . '> ' . esc_html( $label ) . '</label><br>';
            }
        }
    }
}

// 初始化
if ( is_admin() ) {
    $remover = new Configurable_Action_Remover();
    add_action( 'admin_init', array( $remover, 'register_settings' ) );
}

与现代开发模式结合

在区块编辑器(Gutenberg)或 REST API 开发中,remove_action 仍然有重要作用,例如移除默认的 REST API 端点或修改区块行为。

// 移除 WordPress 核心的某些 REST API 端点
add_action( 'rest_api_init', 'customize_rest_api', 20 );

function customize_rest_api() {
    // 移除用户端点(如果需要)
    remove_action( 'rest_api_init', 'create_initial_rest_routes', 0 );

    // 但实际上,更常见的做法是通过权限控制来限制访问
    // 或者使用 rest_endpoints 过滤器

    add_filter( 'rest_endpoints', function( $endpoints ) {
        // 移除 WordPress 核心的用户端点
        if ( isset( $endpoints['/wp/v2/users'] ) ) {
            unset( $endpoints['/wp/v2/users'] );
        }

        // 移除特定文章的修订版本端点
        if ( isset( $endpoints['/wp/v2/posts/(?P<id>[\d]+)/revisions'] ) ) {
            unset( $endpoints['/wp/v2/posts/(?P<id>[\d]+)/revisions'] );
        }

        return $endpoints;
    } );
}

// 在区块编辑器中移除某些功能
add_action( 'enqueue_block_editor_assets', 'customize_block_editor' );

function customize_block_editor() {
    // 移除核心区块的某些功能
    add_filter( 'block_editor_settings_all', function( $settings ) {
        // 通过 JavaScript 移除功能
        wp_add_inline_script(
            'wp-blocks',
            'wp.hooks.addFilter("blocks.registerBlockType", "my-plugin/remove-block-supports", function(settings, name) {
                if (name === "core/paragraph") {
                    // 移除段落区块的颜色支持
                    settings.supports = {
                        ...settings.supports,
                        color: false
                    };
                }
                return settings;
            });'
        );

        return $settings;
    } );
}