Often, it’s very simple to disable a filter or action in WordPress. Core’s adherence, merit notwithstanding, to working in the global namespace meant many plugins and themes followed suit. So unhooking a function was as simple as:
remove_action( 'admin_head', 'some_function' );
Code language: JavaScript (javascript)
But as PHP has matured, plugin and theme authors are more often using modern structures and practices, like objects and namespaces. This often means their actions or filters, if called within the context of an object, look like this:
add_action( 'admin_head', [$this, 'some_function'] );
Code language: JavaScript (javascript)
From your own plugin or theme, to utilize remove_action
, you need access to $this
, which is generally a reference to an instantiated object. But often, the plugin or theme may not have even assigned the object they instantiated to a variable, let alone a global one that you can find down the road:
// Case 1: An object you can get a reference to and unhook functions.
global $some_object = new SomeClass();
// Case 2: An object you can't.
// Often happens when everything the object needs to do happens in the constructor.
new SomeClass();
// Your code, which works for case 1, not for case 2.
global $some_object;
remove_action( 'admin_head', [$some_object, 'some_function'] );
Code language: PHP (php)
Hopefully the problem here is clear. For a specific example, I was using a plugin that added an admin notice whenever the API key for receiving plugin updates wasn’t enabled. At Sterner Stuff, we only run updates in dev environments, so on production, these keys aren’t configured, and we really don’t need admin notices about it.
But as laid out here, they were adding admin notices by hooking an object method, and that object wasn’t something I could get a reference to.
To get around this, we can dig a reference to this object out of the global collection of all hooks that have been registered.
Where WordPress Stores Hooks
All hooks registered in WordPress are stored in $GLOBALS
. You can get a full list of hooks currently registered (which themselves are WP_Hook
objects) with $GLOBALS['wp_filter']
. That array is keyed by the hook tag, so if you dump something like $GLOBALS['wp_filter']['admin_notices']
, it looks something like this:
You can see the array is keyed by hook priority, and that array is keyed by a unique ID WordPress generates when the function is hooked.
You can keep exploring this thing, but ultimately, the contents of each callback will vary a bit depending on how it was registered – did it belong to an object? Was it a global function? Anonymous? I won’t get into what each case looks like. We’ll stick to the problem at hand: a function that belongs to an object we need to find a reference to.
Recreate the Callable
Let’s review the information we do have: the name of the class that this object is an instance of, the name of the function that was hooked, the priority it was hooked at, and the tag it was hooked to. With all that information, we can find the stored reference to the object and recreate the callable to remove it.
// These values are examples. Swap them out as needed.
$tag = 'admin_notices';
$priority = 10;
$class = 'SomeClass';
$function = 'some_function';
foreach($GLOBALS['wp_filter'][$tag]->callbacks[$priority] as $callback) {
// Make sure the callback is properly a function, that it's an instance of the class we want, and that it's the function we want to unhook
if($callback['function']
&& is_a( $callback['function'][0], $class )
&& $callback['function'][1] == $function) {
// Call remove_filter on the WP_Hook object, using a callable created from the references we just found
// See https://developer.wordpress.org/reference/classes/wp_hook/
$callable = [ $callback['function'][0], $function ];
$GLOBALS['wp_filter'][$tag]->remove_filter( $tag, $callable, $priority );
break;
}
}
Code language: PHP (php)
Make sure your code is running after the code that hooks the function you want to unhook but before that action or filter actually fires. Oftentimes, an easy way to do this is to hook into the same tag as the hook you’re trying to undo at a slightly higher priority. This is true for any situation in which you’re modifying existing actions or filters.
For example, if you want to unhook an admin notice at the default priority (10), you might do something like this:
add_action( 'admin_notices', function() {
// Your unhook code
}, 5 ); // Notice 5, higher priority than 10
Code language: PHP (php)
Leave a Reply