it-swarm.com.de

remove_action oder remove_filter mit externen Klassen?

Wie können Sie in einer Situation, in der ein Plugin seine Methoden in einer Klasse gekapselt und dann einen Filter oder eine Aktion für eine dieser Methoden registriert hat, die Aktion oder den Filter entfernen, wenn Sie keinen Zugriff mehr auf die Instanz dieser Klasse haben?

Angenommen, Sie haben ein Plugin, das dies ausführt:

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

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

new MyClass();

Wie kann ich die Registrierung der Klasse aufheben, da ich jetzt keine Möglichkeit mehr habe, auf die Instanz zuzugreifen? Dies: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) ); scheint nicht der richtige Ansatz zu sein - zumindest schien dies in meinem Fall nicht zu funktionieren.

57
Tom Auger

Am besten verwenden Sie hier eine statische Klasse. Der folgende Code sollte anweisend sein:

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

Wenn Sie diesen Code von einem Plugin ausführen, sollten Sie beachten, dass die Methode der StaticClass sowie die Funktion von wp_footer entfernt werden.

15
mfields

Jedes Mal, wenn ein Plugin eine new MyClass(); erstellt, sollte es einer eindeutig benannten Variablen zugewiesen werden. Auf diese Weise kann auf die Instanz der Klasse zugegriffen werden.

Wenn er also $myclass = new MyClass(); macht, dann könnten Sie dies tun:

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

Dies funktioniert, weil Plugins im globalen Namespace enthalten sind, sodass implizite Variablendeklarationen im Hauptteil eines Plugins globale Variablen sind.

Wenn das Plugin den Bezeichner der neuen Klasse irgendwo nicht speichert, ist das technisch gesehen ein Fehler. Eines der allgemeinen Prinzipien der objektorientierten Programmierung ist, dass Objekte, auf die von keiner Variablen irgendwo verwiesen wird, bereinigt oder beseitigt werden müssen.

Nun, PHP tut dies nicht wie Java, da PHP eine halbherzige OOP Implementierung ist. Die Instanzvariablen sind nur Strings mit eindeutigen Objektnamen. Sie funktionieren nur aufgrund der Art und Weise, wie die Interaktion mit dem Variablenfunktionsnamen mit dem Operator -> funktioniert. Nur new class() zu tun, kann in der Tat perfekt funktionieren, einfach nur dumm. :)

Unterm Strich tun Sie also niemals new class();. Führen Sie $var = new class(); aus und machen Sie $ var auf irgendeine Weise zugänglich, damit andere Bits darauf verweisen können.

Edit: Jahre später

Eine Sache, die ich bei vielen Plugins gesehen habe, ist die Verwendung eines ähnlichen Musters wie "Singleton". Sie erstellen eine getInstance () -Methode, um die einzelne Instanz der Klasse abzurufen. Dies ist wahrscheinlich die beste Lösung, die ich gesehen habe. Beispiel Plugin:

class ExamplePlugin
{
    protected static $instance = NULL;

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

Beim ersten Aufruf von getInstance () wird die Klasse instanziiert und der Zeiger gespeichert. Damit können Sie Aktionen einbinden.

Ein Problem dabei ist, dass Sie getInstance () nicht im Konstruktor verwenden können, wenn Sie so etwas verwenden. Dies liegt daran, dass das new den Konstruktor aufruft, bevor die $ -Instanz festgelegt wird. Daher führt der Aufruf von getInstance () aus dem Konstruktor zu einer Endlosschleife und unterbricht alles.

Eine Problemumgehung besteht darin, den Konstruktor nicht zu verwenden (oder zumindest getInstance () darin nicht zu verwenden), sondern explizit eine "init" -Funktion in der Klasse zu haben, um Ihre Aktionen und dergleichen einzurichten. So was:

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

Mit so etwas wird am Ende der Datei, nachdem alle Klassen definiert wurden, das Instanziieren des Plugins so einfach:

ExamplePlugin::init();

Init fügt Ihre Aktionen hinzu und ruft dabei getInstance () auf, das die Klasse instanziiert und sicherstellt, dass nur eine von ihnen existiert. Wenn Sie keine init-Funktion haben, würden Sie dies tun, um die Klasse stattdessen zunächst zu instanziieren:

ExamplePlugin::getInstance();

Um die ursprüngliche Frage zu beantworten, können Sie diesen Aktionshaken von außen (auch bekannt als in einem anderen Plugin) wie folgt entfernen:

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

Fügen Sie dies in einen Hook ein, der mit dem Aktions-Hook plugins_loaded verknüpft ist, und der Vorgang wird rückgängig gemacht, der vom ursprünglichen Plugin verknüpft wird.

75
Otto

2 kleine PHP Funktionen zum Entfernen von Filtern/Aktionen mit "anonymer" Klasse: https://github.com/herewithme/wp-filters-extras/

13
herewithme

Hier ist eine ausführlich dokumentierte Funktion, die ich zum Entfernen von Filtern erstellt habe, wenn Sie keinen Zugriff auf das Klassenobjekt haben (funktioniert mit WordPress 1.2+, einschließlich 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 );
}
13
sMyles

Die obigen Lösungen sehen aus wie veraltet, mussten meine eigenen schreiben ...

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]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}
2
Digerkam

Diese Funktion basiert auf @ Digerkam Antwort. Ein Vergleich wurde hinzugefügt, wenn $def['function'][0] eine Zeichenfolge ist und es endlich für mich funktioniert hat.

Die Verwendung von $wp_filter[$tag]->remove_filter() sollte die Stabilität erhöhen.

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

Beispielverwendung:

Genaue Übereinstimmung

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

Beliebige Priorität

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

Jede Klasse und jede Priorität

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