remove_action o remove_filter con classi esterne?


59

In una situazione in cui un plugin ha incapsulato i suoi metodi all'interno di una classe e quindi registrato un filtro o un'azione su uno di questi metodi, come si rimuove l'azione o il filtro se non si ha più accesso all'istanza di quella classe?

Ad esempio, supponiamo di avere un plugin che fa questo:

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

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

new MyClass();

Notando che ora non ho modo di accedere all'istanza, come posso annullare la registrazione della classe? Questo: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) );non sembra essere l'approccio giusto - almeno, non sembra funzionare nel mio caso.


N / P. Di seguito A funziona per te?
Kaiser

Risposte:


16

La cosa migliore da fare qui è usare una classe statica. Il seguente codice dovrebbe essere istruttivo:

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

Se esegui questo codice da un plugin, dovresti notare che il metodo di StaticClass e la funzione verranno rimossi da wp_footer.


7
Punto preso, ma non tutte le classi possono essere semplicemente convertite in statiche.
Geert,

Ho accettato questa risposta perché risponde alla domanda più direttamente, sebbene la risposta di Otto sia la migliore pratica. Noto qui che non penso che tu debba dichiarare esplicitamente statico. È stata la mia esperienza (anche se potrei sbagliarmi) che puoi semplicemente considerare la funzione come se fosse una matrice statica ('MyClass', 'member_function') e spesso funziona senza la parola chiave 'statica'.
Tom Auger,

@TomAuger no non puoi, SOLO se viene aggiunta come classe statica puoi usare la remove_actionfunzione, altrimenti non funzionerà ... ecco perché ho dovuto scrivere la mia funzione da gestire quando non è una classe statica. Questa risposta sarebbe la migliore solo se la tua domanda riguardava il tuo codice, altrimenti
proverai

78

Ogni volta che un plugin crea un new MyClass();, dovrebbe assegnarlo a una variabile con un nome univoco. In questo modo, l'istanza della classe è accessibile.

Quindi, se lo stesse facendo $myclass = new MyClass();, allora potresti farlo:

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

Questo funziona perché i plugin sono inclusi nello spazio dei nomi globale, quindi le dichiarazioni di variabili implicite nel corpo principale di un plugin sono variabili globali.

Se il plugin non salva l'identificatore della nuova classe da qualche parte , quindi tecnicamente, questo è un bug. Uno dei principi generali della programmazione orientata agli oggetti è che gli oggetti a cui non viene fatto riferimento da qualche variabile da qualche parte sono soggetti a pulizia o eliminazione.

Ora, in particolare PHP non fa ciò come farebbe Java, poiché PHP è una sorta di implementazione OOP a metà corsa. Le variabili di istanza sono solo stringhe con nomi di oggetti univoci, una sorta di cosa. Funzionano solo per il modo in cui l'interazione con il nome della funzione variabile funziona con l' ->operatore. Quindi semplicemente fare new class()può davvero funzionare perfettamente, semplicemente stupidamente. :)

Quindi, in conclusione, non farlo mai new class();. Fai $var = new class();e rendi $ var accessibile in qualche modo per fare riferimento ad altri bit.

Modifica: anni dopo

Una cosa che ho visto fare molti plugin è usare qualcosa di simile al modello "Singleton". Creano un metodo getInstance () per ottenere la singola istanza della classe. Questa è probabilmente la migliore soluzione che abbia mai visto. Esempio di plugin:

class ExamplePlugin
{
    protected static $instance = NULL;

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

La prima volta che viene chiamato getInstance (), crea un'istanza della classe e salva il puntatore. Puoi usarlo per agganciare le azioni.

Un problema con questo è che non puoi usare getInstance () all'interno del costruttore se usi una cosa del genere. Questo perché il nuovo chiama il costruttore prima di impostare l'istanza $, quindi chiamare getInstance () dal costruttore porta a un ciclo infinito e interrompe tutto.

Una soluzione alternativa è quella di non usare il costruttore (o, almeno, non usare getInstance () al suo interno), ma di avere esplicitamente una funzione "init" nella classe per impostare le tue azioni e simili. Come questo:

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

Con qualcosa del genere, alla fine del file, dopo che la classe è stata definita e tale, istanziare il plugin diventa semplice come questo:

ExamplePlugin::init();

Init inizia ad aggiungere le tue azioni, e così facendo chiama getInstance (), che crea un'istanza della classe e si assicura che ne esista solo una. Se non si dispone di una funzione init, si dovrebbe fare ciò per creare un'istanza della classe inizialmente:

ExamplePlugin::getInstance();

Per rispondere alla domanda originale, rimuovere quel gancio di azione dall'esterno (aka, in un altro plugin) può quindi essere fatto in questo modo:

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

Inseriscilo in qualcosa agganciato al plugins_loadedgancio di azione e annullerà l'azione che viene agganciata dal plugin originale.


3
+1 Tru dat. Questa è chiaramente una buona pratica. Dovremmo tutti sforzarci di scrivere il nostro codice plugin in questo modo.
Tom Auger,

3
+1 queste istruzioni mi hanno davvero aiutato a rimuovere un filtro in una classe di pattern singleton.
Devin Walker,

+1, ma penso che dovresti agganciarti wp_loaded, noplugins_loaded , che potrebbe essere chiamato troppo presto.
EML

4
No, plugins_loadedsarebbe il posto giusto. L' wp_loadedazione si svolge dopo l' initazione, quindi se il plug-in esegue azioni init(e la maggior parte lo fanno), quindi si desidera inizializzare il plug-in e configurarlo prima. Il plugins_loadedgancio è il posto giusto per quella fase di costruzione.
Otto,

13

2 piccole funzioni PHP per consentire la rimozione di filtri / azioni con classe "anonima": https://github.com/herewithme/wp-filters-extras/


Funzioni molto interessanti. Grazie per averlo pubblicato qui!
Tom Auger,

Come accennato, altri nel mio post qui sotto, questi si romperanno in WordPress 4.7 (a meno che il repository non venga aggiornato, ma non lo sia tra 2 anni)
sMyles

1
Solo notando che il repository wp-filters-extra è stato effettivamente aggiornato per v4.7 e la classe WP_Hook.
Dave Romsey,

13

Ecco una funzione ampiamente documentata che ho creato per rimuovere i filtri quando non si ha accesso all'oggetto classe (funziona con WordPress 1.2+, incluso 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
Domanda: l'hai testato in 4.7? Sono state apportate alcune modifiche al modo in cui i callback sono registrati in filtri nuovi di zecca. Non ho approfondito il tuo codice, ma è qualcosa che potresti voler controllare: make.wordpress.org/core/2016/09/08/…
Tom Auger,

sì, abbastanza sicuro che questo si spezzerà in 4.7
gmazzap

Ahh! No, ma grazie, sicuramente esaminerò questo e lo aggiornerò in modo che sia compatibile (se necessario)
sMyles,

1
@ TomAuger grazie per il testa a testa! Ho aggiornato la funzione, testato lavorando su WordPress 4.7+ (con compatibilità retroattiva ancora mantenuta)
sMyles

1
L'
ho

2

Sopra le soluzioni sembrano obsolete, ho dovuto scrivere il mio ...

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

Questa funzione si basa sulla risposta di @Digerkam. Aggiunto confronto se $def['function'][0]è stringa e finalmente ha funzionato per me.

Anche l'utilizzo $wp_filter[$tag]->remove_filter()dovrebbe renderlo più stabile.

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

Esempio di utilizzo:

Corrispondenza esatta

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

Qualsiasi priorità

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

Qualsiasi classe e qualsiasi priorità

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

0

Questa non è una risposta generica, ma specifica per il tema Avada e WooCommerce , che penso che altre persone possano trovare utili:

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' );
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.