remove_action hoặc remove_filter với các lớp bên ngoài?


59

Trong trường hợp plugin đã gói gọn các phương thức của nó trong một lớp và sau đó đăng ký bộ lọc hoặc hành động chống lại một trong các phương thức đó, làm thế nào để bạn loại bỏ hành động hoặc bộ lọc nếu bạn không còn có quyền truy cập vào thể hiện của lớp đó?

Ví dụ: giả sử bạn có một plugin thực hiện việc này:

class MyClass {
    function __construct() {
       add_action( "plugins_loaded", array( $this, 'my_action' ) );
    }

    function my_action() {
       // do stuff...
    }
}

new MyClass();

Lưu ý rằng bây giờ tôi không có cách nào để truy cập thể hiện, làm cách nào để hủy đăng ký lớp? Điều này: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) );dường như không phải là phương pháp đúng đắn - ít nhất, dường như không hoạt động trong trường hợp của tôi.


N / P. Có dưới đây làm việc cho bạn?
kaiser

Câu trả lời:


16

Điều tốt nhất để làm ở đây là sử dụng một lớp tĩnh. Các mã sau đây nên được hướng dẫn:

class MyClass {
    function __construct() {
        add_action( 'wp_footer', array( $this, 'my_action' ) );
    }
    function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
new MyClass();


class MyStaticClass {
    public static function init() {
        add_action( 'wp_footer', array( __class__, 'my_action' ) );
    }
    public static function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
MyStaticClass::init();

function my_wp_footer() {
    print '<h1>my_wp_footer()</h1>';
}
add_action( 'wp_footer', 'my_wp_footer' );

function mfields_test_remove_actions() {
    remove_action( 'wp_footer', 'my_wp_footer' );
    remove_action( 'wp_footer', array( 'MyClass', 'my_action' ), 10 );
    remove_action( 'wp_footer', array( 'MyStaticClass', 'my_action' ), 10 );
}
add_action( 'wp_head', 'mfields_test_remove_actions' );

Nếu bạn chạy mã này từ một plugin, bạn sẽ nhận thấy rằng phương thức của StaticClass cũng như chức năng sẽ bị xóa khỏi wp_footer.


7
Điểm lấy, nhưng không phải tất cả các lớp chỉ có thể được chuyển đổi thành tĩnh.
Geert

Tôi chấp nhận câu trả lời này vì nó trả lời câu hỏi trực tiếp nhất, mặc dù câu trả lời của Otto là cách thực hành tốt nhất. Tôi lưu ý ở đây rằng tôi không nghĩ bạn cần phải khai báo tĩnh. Đó là kinh nghiệm của tôi (mặc dù tôi có thể sai) rằng bạn chỉ có thể coi hàm như thể đó là mảng tĩnh ('MyClass', 'Member_function') và nó thường hoạt động mà không có từ khóa 'tĩnh'.
Tom Auger

@TomAuger không bạn không thể, CHỈ nếu nó được thêm dưới dạng lớp tĩnh, bạn có thể sử dụng remove_actionhàm không, nếu không nó sẽ không hoạt động ... đó là lý do tại sao tôi phải viết hàm riêng để xử lý khi nó không phải là lớp tĩnh. Câu trả lời này sẽ chỉ là tốt nhất nếu câu hỏi của bạn liên quan đến mã của riêng bạn, nếu không, bạn sẽ cố xóa bộ lọc / hành động khác khỏi cơ sở mã của người khác và không thể thay đổi nó thành tĩnh
sMstyle

78

Bất cứ khi nào một plugin tạo ra một new MyClass();, nó nên gán nó cho một biến có tên duy nhất. Bằng cách đó, thể hiện của lớp có thể truy cập.

Vì vậy, nếu anh ấy đang làm $myclass = new MyClass();, thì bạn có thể làm điều này:

global $myclass;
remove_action( 'wp_footer', array( $myclass, 'my_action' ) );

Điều này hoạt động vì các plugin được bao gồm trong không gian tên toàn cục, do đó, khai báo biến ẩn trong phần chính của plugin là các biến toàn cục.

Nếu plugin không lưu mã định danh của lớp mới ở đâu đó , thì về mặt kỹ thuật, đó là một lỗi. Một trong những nguyên tắc chung của Lập trình hướng đối tượng là các đối tượng không được tham chiếu bởi một số biến ở đâu đó có thể được dọn dẹp hoặc loại bỏ.

Bây giờ, PHP đặc biệt không làm điều này giống như Java, bởi vì PHP là một triển khai OOP nửa vời. Các biến đối tượng chỉ là các chuỗi có tên đối tượng duy nhất trong đó, sắp xếp thứ. Chúng chỉ hoạt động vì cách tương tác tên hàm biến hoạt động với ->toán tử. Vì vậy, chỉ cần làm new class()thực sự có thể làm việc hoàn hảo, chỉ là ngu ngốc. :)

Vì vậy, dòng dưới cùng, không bao giờ làm new class();. Làm $var = new class();và làm cho $ var có thể truy cập theo một cách nào đó để các bit khác tham chiếu nó.

Chỉnh sửa: năm sau

Một điều tôi đã thấy rất nhiều plugin đang làm là sử dụng một cái gì đó tương tự như mẫu "Singleton". Họ tạo ra một phương thức getInstance () để lấy một thể hiện của lớp. Đây có lẽ là giải pháp tốt nhất tôi từng thấy. Plugin ví dụ:

class ExamplePlugin
{
    protected static $instance = NULL;

    public static function getInstance() {
        NULL === self::$instance and self::$instance = new self;
        return self::$instance;
    }
}

Lần đầu tiên getInstance () được gọi, nó khởi tạo lớp và lưu con trỏ của nó. Bạn có thể sử dụng nó để móc trong hành động.

Một vấn đề với điều này là bạn không thể sử dụng getInstance () bên trong hàm tạo nếu bạn sử dụng một thứ như vậy. Điều này là do hàm mới gọi hàm tạo trước khi đặt $ dụ, do đó, việc gọi getInstance () từ hàm tạo dẫn đến một vòng lặp vô hạn và phá vỡ mọi thứ.

Một cách giải quyết khác là không sử dụng hàm tạo (hoặc, ít nhất, không sử dụng getInstance () trong nó), nhưng rõ ràng có một hàm "init" trong lớp để thiết lập hành động của bạn và như vậy. Như thế này:

public static function init() {
    add_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );
}

Với một cái gì đó như thế này, ở phần cuối của tệp, sau khi lớp đã được xác định tất cả và như vậy, việc khởi tạo plugin trở nên đơn giản như sau:

ExamplePlugin::init();

Init bắt đầu thêm hành động của bạn và để thực hiện nó gọi getInstance (), khởi tạo lớp và đảm bảo chỉ có một trong số chúng tồn tại. Nếu bạn không có hàm init, bạn sẽ làm điều này để khởi tạo lớp ban đầu thay vào đó:

ExamplePlugin::getInstance();

Để giải quyết câu hỏi ban đầu, loại bỏ móc hành động đó từ bên ngoài (hay còn gọi là trong một plugin khác) sau đó có thể được thực hiện như sau:

remove_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );

Đặt nó vào một cái gì đó được nối với plugins_loadedhook hành động và nó sẽ hoàn tác hành động được hook bởi plugin gốc.


3
+1 dữ liệu Tru. Đây rõ ràng là một thực hành tốt nhất. Tất cả chúng ta nên cố gắng viết mã plugin theo cách đó.
Tom Auger

3
+1 các hướng dẫn này thực sự đã giúp tôi xóa bộ lọc trong lớp mẫu đơn.
Devin Walker

+1, nhưng tôi nghĩ bạn thường nên kết nối wp_loaded, không plugins_loaded, có thể được gọi là quá sớm.
EML

4
Không, plugins_loadedsẽ là nơi chính xác. Các wp_loadedhành động xảy ra sau khi inithành động, vì vậy nếu plugin của bạn có bất kỳ hành động trên init(và hầu hết làm), sau đó bạn muốn khởi tạo các plugin và thiết lập nó trước đó. Các plugins_loadedmóc là nơi thích hợp cho rằng giai đoạn xây dựng.
Otto

13

2 hàm PHP nhỏ để cho phép xóa bộ lọc / hành động với lớp "ẩn danh": https://github.com/herewithme/wp-filters-extras/


Chức năng rất mát mẻ. Cảm ơn đã đăng bài ở đây!
Tom Auger

Như đã đề cập đến những người khác trong bài viết của tôi dưới đây, những điều này sẽ bị phá vỡ trong WordPress 4.7 (trừ khi repo được cập nhật, nhưng không có trong 2 năm)
sMstyle

1
Chỉ cần lưu ý rằng repo wp-bộ lọc-bổ sung thực sự đã được cập nhật cho v4.7 và lớp WP_Hook.
Dave Romsey

13

Đây là một chức năng được ghi lại rộng rãi mà tôi đã tạo để xóa các bộ lọc khi bạn không có quyền truy cập vào đối tượng lớp (hoạt động với WordPress 1.2+, bao gồm 4.7+):

https://gist.github.com/tripflex/c6518efc1753cf2392559866b4bd1a53

/**
 * Remove Class Filter Without Access to Class Object
 *
 * In order to use the core WordPress remove_filter() on a filter added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove filters with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 * Updated 2-27-2017 to use internal WordPress removal for 4.7+ (to prevent PHP warnings output)
 *
 * @param string $tag         Filter to remove
 * @param string $class_name  Class name for the filter's callback
 * @param string $method_name Method name for the filter's callback
 * @param int    $priority    Priority of the filter (default 10)
 *
 * @return bool Whether the function is removed.
 */
function remove_class_filter( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;

    // Check that filter actually exists first
    if ( ! isset( $wp_filter[ $tag ] ) ) return FALSE;

    /**
     * If filter config is an object, means we're using WordPress 4.7+ and the config is no longer
     * a simple array, rather it is an object that implements the ArrayAccess interface.
     *
     * To be backwards compatible, we set $callbacks equal to the correct array as a reference (so $wp_filter is updated)
     *
     * @see https://make.wordpress.org/core/2016/09/08/wp_hook-next-generation-actions-and-filters/
     */
    if ( is_object( $wp_filter[ $tag ] ) && isset( $wp_filter[ $tag ]->callbacks ) ) {
        // Create $fob object from filter tag, to use below
        $fob = $wp_filter[ $tag ];
        $callbacks = &$wp_filter[ $tag ]->callbacks;
    } else {
        $callbacks = &$wp_filter[ $tag ];
    }

    // Exit if there aren't any callbacks for specified priority
    if ( ! isset( $callbacks[ $priority ] ) || empty( $callbacks[ $priority ] ) ) return FALSE;

    // Loop through each filter for the specified priority, looking for our class & method
    foreach( (array) $callbacks[ $priority ] as $filter_id => $filter ) {

        // Filter should always be an array - array( $this, 'method' ), if not goto next
        if ( ! isset( $filter[ 'function' ] ) || ! is_array( $filter[ 'function' ] ) ) continue;

        // If first value in array is not an object, it can't be a class
        if ( ! is_object( $filter[ 'function' ][ 0 ] ) ) continue;

        // Method doesn't match the one we're looking for, goto next
        if ( $filter[ 'function' ][ 1 ] !== $method_name ) continue;

        // Method matched, now let's check the Class
        if ( get_class( $filter[ 'function' ][ 0 ] ) === $class_name ) {

            // WordPress 4.7+ use core remove_filter() since we found the class object
            if( isset( $fob ) ){
                // Handles removing filter, reseting callback priority keys mid-iteration, etc.
                $fob->remove_filter( $tag, $filter['function'], $priority );

            } else {
                // Use legacy removal process (pre 4.7)
                unset( $callbacks[ $priority ][ $filter_id ] );
                // and if it was the only filter in that priority, unset that priority
                if ( empty( $callbacks[ $priority ] ) ) {
                    unset( $callbacks[ $priority ] );
                }
                // and if the only filter for that tag, set the tag to an empty array
                if ( empty( $callbacks ) ) {
                    $callbacks = array();
                }
                // Remove this filter from merged_filters, which specifies if filters have been sorted
                unset( $GLOBALS['merged_filters'][ $tag ] );
            }

            return TRUE;
        }
    }

    return FALSE;
}

/**
 * Remove Class Action Without Access to Class Object
 *
 * In order to use the core WordPress remove_action() on an action added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove actions with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 *
 * @param string $tag         Action to remove
 * @param string $class_name  Class name for the action's callback
 * @param string $method_name Method name for the action's callback
 * @param int    $priority    Priority of the action (default 10)
 *
 * @return bool               Whether the function is removed.
 */
function remove_class_action( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    remove_class_filter( $tag, $class_name, $method_name, $priority );
}

2
Câu hỏi - bạn đã thử nghiệm điều này trong 4.7 chưa? Có một số thay đổi về cách gọi lại được đăng ký trong các bộ lọc hoàn toàn mới. Tôi chưa xem mã của bạn một cách sâu sắc, nhưng đó là thứ bạn có thể muốn kiểm tra: make.wordpress.org/core/2016/09/08/ Khăn
Tom Auger

vâng, khá chắc chắn rằng điều này sẽ phá vỡ trong 4,7
gmazzap

À! Không, tôi không làm thế nhưng cảm ơn bạn, tôi sẽ xem xét và cập nhật nó để nó tương thích (nếu cần)
sMstyle

1
@TomAuger cảm ơn vì đã ngẩng cao đầu! Tôi đã cập nhật chức năng, đã thử nghiệm hoạt động trên WordPress 4.7+ (vẫn duy trì khả năng tương thích ngược)
sMstyle

1
Chỉ cần cập nhật điều này để sử dụng phương pháp loại bỏ nội bộ cốt lõi (để xử lý lặp lại giữa
chừng

2

Các giải pháp trên có vẻ như đã lỗi thời, phải tự viết ...

function remove_class_action ($action,$class,$method) {
    global $wp_filter ;
    if (isset($wp_filter[$action])) {
        $len = strlen($method) ;
        foreach ($wp_filter[$action] as $pri => $actions) {
            foreach ($actions as $name => $def) {
                if (substr($name,-$len) == $method) {
                    if (is_array($def['function'])) {
                        if (get_class($def['function'][0]) == $class) {
                            if (is_object($wp_filter[$action]) && isset($wp_filter[$action]->callbacks)) {
                                unset($wp_filter[$action]->callbacks[$pri][$name]) ;
                            } else {
                                unset($wp_filter[$action][$pri][$name]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}

0

Hàm này dựa trên câu trả lời @Digerkam. Đã thêm so sánh nếu $def['function'][0]là chuỗi và cuối cùng nó đã làm việc cho tôi.

Cũng sử dụng $wp_filter[$tag]->remove_filter()nên làm cho nó ổn định hơn.

function remove_class_action($tag, $class = '', $method, $priority = null) : bool {
    global $wp_filter;
    if (isset($wp_filter[$tag])) {
        $len = strlen($method);

        foreach($wp_filter[$tag] as $_priority => $actions) {

            if ($actions) {
                foreach($actions as $function_key => $data) {

                    if ($data) {
                        if (substr($function_key, -$len) == $method) {

                            if ($class !== '') {
                                $_class = '';
                                if (is_string($data['function'][0])) {
                                    $_class = $data['function'][0];
                                }
                                elseif (is_object($data['function'][0])) {
                                    $_class = get_class($data['function'][0]);
                                }
                                else {
                                    return false;
                                }

                                if ($_class !== '' && $_class == $class) {
                                    if (is_numeric($priority)) {
                                        if ($_priority == $priority) {
                                            //if (isset( $wp_filter->callbacks[$_priority][$function_key])) {}
                                            return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                        }
                                    }
                                    else {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                            }
                            else {
                                if (is_numeric($priority)) {
                                    if ($_priority == $priority) {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                                else {
                                    return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                }
                            }

                        }
                    }
                }
            }
        }

    }

    return false;
}

Ví dụ sử dụng:

Kết hợp chuẩn xác

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action', 0);
});

Bất kỳ ưu tiên

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action');
});

Bất kỳ lớp học và bất kỳ ưu tiên

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', '', 'my_action');
});

0

Đây không phải là một câu trả lời chung chung, nhưng là một câu trả lời cụ thể cho chủ đề Avada và WooC Commerce , mà tôi nghĩ người khác có thể thấy hữu ích:

function remove_woo_commerce_hooks() {
    global $avada_woocommerce;
    remove_action( 'woocommerce_single_product_summary', array( $avada_woocommerce, 'add_product_border' ), 19 );
}
add_action( 'after_setup_theme', 'remove_woo_commerce_hooks' );
Licensed under cc by-sa 3.0 with attribution required.