remove, czy remove_filter z klasami zewnętrznymi?

59

W sytuacji, gdy wtyczka zamknęła swoje metody w klasie, a następnie zarejestrowała filtr lub akcję przeciwko jednej z tych metod, w jaki sposób usunąć akcję lub filtr, jeśli nie masz już dostępu do instancji tej klasy?

Załóżmy na przykład, że masz wtyczkę, która to robi:

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

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

new MyClass();

Biorąc pod uwagę, że nie mam teraz dostępu do instancji, jak mogę wyrejestrować klasę? To: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) );nie wydaje się być właściwym podejściem - przynajmniej nie wydaje się, aby działało w moim przypadku.

Tom Auger
źródło
Nie dotyczy Czy poniżej A działa dla Ciebie?
kaiser

Odpowiedzi:

16

Najlepszym rozwiązaniem jest użycie klasy statycznej. Poniższy kod powinien być instrukcją:

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

Jeśli uruchomisz ten kod z wtyczki, powinieneś zauważyć, że metoda StaticClass oraz funkcja zostaną usunięte z wp_footer.

pola
źródło
7
Punkt, ale nie wszystkie klasy można po prostu przekonwertować na statyczne.
Geert
Przyjąłem tę odpowiedź, ponieważ odpowiada ona najbardziej na pytanie, chociaż odpowiedź Otto jest najlepszą praktyką. Zauważam tutaj, że nie sądzę, że musisz jawnie deklarować statyczność. Z mojego doświadczenia (choć mogę się mylić) możesz traktować funkcję tak, jakby była statyczną tablicą („MyClass”, „member_function”) i często działa bez słowa kluczowego „static”.
Tom Auger
@TomAuger nie, nie możesz, TYLKO jeśli jest dodany jako klasa statyczna, możesz użyć remove_actionfunkcji, w przeciwnym razie to nie zadziała ... dlatego musiałem napisać własną funkcję do obsługi, gdy nie jest to klasa statyczna. Ta odpowiedź byłaby najlepsza, gdyby twoje pytanie dotyczyło twojego kodu, w przeciwnym razie będziesz próbował usunąć inny filtr / akcję z bazy kodu innej osoby i nie możesz go zmienić na statyczny
sMyles
78

Ilekroć wtyczka tworzy new MyClass();, powinna przypisać ją do zmiennej o unikalnej nazwie. W ten sposób instancja klasy jest dostępna.

Więc jeśli to robił $myclass = new MyClass();, możesz to zrobić:

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

Działa to, ponieważ wtyczki są zawarte w globalnej przestrzeni nazw, więc niejawne deklaracje zmiennych w głównej części wtyczki są zmiennymi globalnymi.

Jeśli wtyczka nie zapisuje gdzieś identyfikatora nowej klasy , technicznie to błąd. Jedną z ogólnych zasad programowania obiektowego jest to, że obiekty, do których nie odwołuje się jakaś zmienna, podlegają czyszczeniu lub eliminacji.

Teraz PHP w szczególności nie robi tego tak, jak zrobiłby to Java, ponieważ PHP jest w pewnym sensie implementacją OOP na wpół arse. Zmienne instancji to po prostu ciągi znaków z unikalnymi nazwami obiektów. Działają tylko ze względu na sposób, w jaki interakcja nazwy funkcji zmiennej działa z ->operatorem. Więc samo robienie new class()może naprawdę działać idealnie, po prostu głupio. :)

Podsumowując, nigdy nie rób new class();. Zrób $var = new class();i udostępnij ten $ var w jakiś sposób, aby inne bity mogły się do niego odwoływać.

Edycja: lata później

Jedną z rzeczy, które widziałem wiele wtyczek, jest użycie czegoś podobnego do wzorca „Singleton”. Tworzą metodę getInstance (), aby uzyskać pojedyncze wystąpienie klasy. To chyba najlepsze rozwiązanie, jakie widziałem. Przykładowa wtyczka:

class ExamplePlugin
{
    protected static $instance = NULL;

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

Przy pierwszym wywołaniu getInstance () tworzy instancję klasy i zapisuje jej wskaźnik. Możesz go użyć, aby włączyć działania.

Jednym z problemów jest to, że nie można używać getInstance () wewnątrz konstruktora, jeśli używa się takiej rzeczy. Wynika to z faktu, że nowe wywołuje konstruktor przed ustawieniem instancji $, więc wywołanie getInstance () z konstruktora prowadzi do nieskończonej pętli i psuje wszystko.

Jednym z obejść tego problemu jest nieużywanie konstruktora (lub przynajmniej nie używanie w nim getInstance ()), ale jawne posiadanie funkcji „init” w klasie do konfigurowania akcji i tym podobnych. Lubię to:

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

Z czymś takim na końcu pliku, po zdefiniowaniu całej klasy i utworzeniu instancji wtyczki staje się tak proste:

ExamplePlugin::init();

Init zaczyna dodawać twoje akcje, a tym samym wywołuje metodę getInstance (), która tworzy instancję klasy i upewnia się, że istnieje tylko jedna z nich. Jeśli nie masz funkcji init, zrób to, aby początkowo utworzyć instancję klasy:

ExamplePlugin::getInstance();

Aby odpowiedzieć na pierwotne pytanie, można usunąć hak akcji z zewnątrz (inaczej w innej wtyczce) w następujący sposób:

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

Umieść to w czymś zaczepionym do plugins_loadedhaka akcji, a cofnie akcję zaczepioną przez oryginalną wtyczkę.

Otto
źródło
3
+1 Tru dat. Jest to zdecydowanie najlepsza praktyka. Wszyscy powinniśmy starać się pisać nasz kod wtyczki w ten sposób.
Tom Auger
3
+1 te instrukcje naprawdę pomogły mi usunąć filtr w klasie wzorców singletonów.
Devin Walker
+1, ale myślę, że generalnie powinieneś się zaczepić wp_loaded, nie plugins_loaded, co można nazwać zbyt wcześnie.
EML
4
Nie, plugins_loadedbyłoby właściwe miejsce. wp_loadedAkcja dzieje się po initdziałaniu, więc jeśli plugin wykonuje żadnych czynności na init(i najbardziej zrobić), a następnie chcesz zainicjować wtyczki i ustawić go przed tym. plugins_loadedHak jest właściwym miejscem dla tego etapu budowy.
Otto
13

2 małe funkcje PHP umożliwiające usunięcie filtru / akcji z klasą „anonimową”: https://github.com/herewithme/wp-filters-extras/

tutaj ze mną
źródło
Bardzo fajne funkcje. Dziękujemy za opublikowanie tego tutaj!
Tom Auger,
Jak wspomniano w innych w moim poście poniżej, będą one działać w WordPress 4.7 (chyba że repo zostanie zaktualizowane, ale nie
nastąpi
1
Wystarczy zauważyć, że repo wp-filter-extras zostało rzeczywiście zaktualizowane dla wersji 4.7 i klasy WP_Hook.
Dave Romsey
13

Oto obszernie udokumentowana funkcja, którą stworzyłem do usuwania filtrów, gdy nie masz dostępu do obiektu klasy (działa z WordPress 1.2+, w tym 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 );
}
sMyles
źródło
2
Pytanie - czy testowałeś to w 4.7? Wprowadzono pewne zmiany w sposobie rejestrowania wywołań zwrotnych w zupełnie nowych filtrach. Nie zagłębiłem się
Tom Auger
tak, całkiem pewne, że to złamie w 4.7
gmazzap
Ahh! Nie, nie, ale dziękuję.
Zdecyduję
1
@TomAuger dzięki za heads up! Zaktualizowałem tę funkcję, przetestowałem działanie na WordPress 4.7+ (z zachowaną kompatybilnością wsteczną)
sMyles 20.09.16
1
Właśnie zaktualizowałem to, aby użyć podstawowej metody wewnętrznego usuwania (do obsługi pośredniej iteracji i zapobiegania ostrzeżeniom php)
sMyles
2

Powyższe rozwiązania wyglądają jak przestarzałe, musiałem napisać własne ...

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]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}
Digerkam
źródło
0

Ta funkcja oparta jest na odpowiedzi @Digerkam. Dodano porównanie, jeśli $def['function'][0]jest łańcuchem i nareszcie zadziałało dla mnie.

Również używanie $wp_filter[$tag]->remove_filter()powinno uczynić go bardziej stabilnym.

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

Przykładowe użycie:

Dokładne dopasowanie

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

Dowolny priorytet

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

Każda klasa i każdy priorytet

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', '', 'my_action');
});
Jonny
źródło
0

To nie jest ogólna odpowiedź, ale jedna specyficzna dla motywu Avada i WooCommerce , które moim zdaniem mogą być pomocne dla innych osób:

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' );
nabrown
źródło