Skip to content

Instantly share code, notes, and snippets.

@tripflex
Last active July 12, 2024 13:20
Show Gist options
  • Save tripflex/c6518efc1753cf2392559866b4bd1a53 to your computer and use it in GitHub Desktop.
Save tripflex/c6518efc1753cf2392559866b4bd1a53 to your computer and use it in GitHub Desktop.
WordPress Remove Filter (remove_filter converted to remove_class_filter) to remove Filter/Action without Class Object access. Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
<?php
/**
* Make sure the function does not exist before defining it
*/
if( ! function_exists( 'remove_class_filter' ) ){
/**
* 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;
}
}
/**
* Make sure the function does not exist before defining it
*/
if( ! function_exists( 'remove_class_action') ){
/**
* 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 ) {
return remove_class_filter( $tag, $class_name, $method_name, $priority );
}
}
@tripflex
Copy link
Author

tripflex commented Jan 6, 2017

Thanks!

@23b
Copy link

23b commented Feb 20, 2017

Thank you so much for sharing this! This little helper made me stop banging my head just in time.

@aliaghdam
Copy link

Thanks

@eriktorsner
Copy link

Thanks for sharing!

@tripflex
Copy link
Author

tripflex commented Jun 8, 2017

No problem, I updated the gist to include a check if the functions exists first, in case devs decide to add this to plugins, or themes, without checking if it exists first 👍

@akkara
Copy link

akkara commented Dec 25, 2017

the Code wont work, so I had to write my own:

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

@tripflex
Copy link
Author

tripflex commented May 2, 2018

@akkara what was the issue/error you were having with the above code?

@justlevine
Copy link

What do you hook this to? I tried plugins_loaded, but it didnt do anything

@davelavoie
Copy link

davelavoie commented Jan 29, 2019

I've made the function even simpler for those who are running WP 4.7 or higher (it won't work for older WP versions). It works well!

function remove_class_hook( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;
    $is_hook_removed = false;
    if ( ! empty( $wp_filter[ $tag ]->callbacks[ $priority ] ) ) {
	    $methods     = wp_list_pluck( $wp_filter[ $tag ]->callbacks[ $priority ], 'function' );
	    $found_hooks = ! empty( $methods ) ? wp_list_filter( $methods, array( 1 => $method_name ) ) : array();
	    foreach( $found_hooks as $hook_key => $hook ) {
	    	if ( ! empty( $hook[0] ) && is_object( $hook[0] ) && get_class( $hook[0] ) === $class_name ) {
	    		$wp_filter[ $tag ]->remove_filter( $tag, $hook, $priority );
	    		$is_hook_removed = true;
	    	}
	    }
    }
    return $is_hook_removed;
}

@tripflex
Copy link
Author

What do you hook this to? I tried plugins_loaded, but it didnt do anything

All depends on what you're trying to remove. You need to determine when the add_action is being called (that you want to remove), and make sure you're calling your code to remove it, AFTER that point ... either with a higher priority to another action, or after that code has been ran to add that action/filter

@harryqt
Copy link

harryqt commented Mar 31, 2019

Thank you so much @davelavoie it's working perfectly.

@chrisjangl
Copy link

Awesome! Thanks.

@thomasfromwood
Copy link

@davelavoie Thank you very much! It save me a lot of time !

@ouun
Copy link

ouun commented Aug 30, 2020

Very useful! Thank you! I think remove_class_action should return remove_class_filter as latter returns trueif successful. So it should read return remove_class_filter($tag, $class_name, $method_name, $priority);

@tripflex
Copy link
Author

@ouun yes i agree, updated the gist to reflect that, thanks!

@eversionsystems
Copy link

Thanks a lot, struggled to remove an action with a class name and your functions worked straight away!

@ferrelucas
Copy link

Hi all,
From WP 5.5, there seems to be an issue when removing anonymous functions that could cause an Uncaught Error: Closure object cannot have properties in /var/www/html/wp-includes/class-wp-list-util.php:115.

I have seen a related issue here: senlin/so-clean-up-wp-seo#88
Where a new filter to this function was added on this commit:
senlin/so-clean-up-wp-seo@aa061ca

function remove_class_hook( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;
    $is_hook_removed = false;
    if ( ! empty( $wp_filter[ $tag ]->callbacks[ $priority ] ) ) {
        $methods = array_filter(wp_list_pluck(
            $wp_filter[ $tag ]->callbacks[ $priority ],
            'function'
        ), function ($method) {
            /**
             * Allow only array & string notation for hooks, since we're
             * looking to remove an exact method of a class anyway. And the
             * method of the class is passed in as a string anyway.
             */
            return is_string($method) || is_array($method);
        });
        $found_hooks = ! empty( $methods ) ? wp_list_filter( $methods, array( 1 => $method_name ) ) : array();
        foreach( $found_hooks as $hook_key => $hook ) {
            if ( ! empty( $hook[0] ) && is_object( $hook[0] ) && get_class( $hook[0] ) === $class_name ) {
                $wp_filter[ $tag ]->remove_filter( $tag, $hook, $priority );
                $is_hook_removed = true;
            }
        }
    }
    return $is_hook_removed;
}

@Lapieldelmercado
Copy link

Lapieldelmercado commented Nov 16, 2020 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment