Created
December 13, 2020 20:53
-
-
Save hansschuijff/3526246fd690a3c9582b3e16e4aa5185 to your computer and use it in GitHub Desktop.
An alternative for remove_filter() that can remove all types of hooked callables.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Unhook callbacks from WordPress filter or action hooks. | |
* including the removal of closures and methods of anonymous objects. | |
* | |
* @package DeWittePrins\CoreFunctionality | |
* @author Hans Schuijff | |
* @since 1.0.0 | |
* @license GPL-2.0+ | |
**/ | |
namespace DeWittePrins\CoreFunctionality; | |
if ( ! function_exists( 'is_plugin_active' ) ) { | |
include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); | |
} | |
if ( ! function_exists( __NAMESPACE__ . '\remove_filter' ) ) { | |
/** | |
* Removes a filter. | |
* | |
* This custom function is able to remove all kind of callbacks from a filter or action hook. | |
* In addition to the normal remove_action() it can remove static class methods, dynamic object methods and closures. | |
* When removing a closure callback, it will remove all closures attached to the hook, | |
* which matches the priority and number of arguments. | |
* | |
* The $callback argument should normally be filled with the (string or array) value | |
* that was passed in the add_action() or add_filter() statement. | |
* | |
* In case of an anonomous object callback (recognizable by the hashed callback ID) then you can leave it empty | |
* (in case of a closure, any value will be ignored) or pass an array containing the classname and methodname. | |
* | |
* The type is used to target the selection and method of removal. | |
* | |
* @since 1.7.14 | |
* @param string $hook Hook name. | |
* @param string|array|void $callback Function name, or array( class, method ), or null. | |
* @param int|void $priority Priority. Defaults to 10. | |
* @param int|void $no_args Number of args as passed in the add_action or add_filter call. Defaults to 1. | |
* @param string $callback_type 'function', 'named object', 'anonymous object', 'closure'. | |
* @return int Number of unhooked callbacks. | |
*/ | |
function remove_filter( string $hook, $callback, int $priority = 10, int $no_args = 1, string $callback_type ): int { | |
/** | |
* If callable is a normal 'function' or the method of a 'named object', | |
* then use the normal \remove_filter() function. | |
*/ | |
if ( in_array( $callback_type, array( 'function', 'named object' ) ) ) { | |
if ( 'named object' === $callback_type ) { | |
$method = array( $class, $method ); | |
} | |
\remove_filter( $hook, $method, $priority, $no_args ); | |
if ( $removed ) { | |
return 1; | |
} | |
return 0; | |
} | |
if ( ! in_array( $callback_type, array( 'closure', 'anonymous object' ) ) ) { | |
return 0; | |
} | |
/** | |
* Remove anonymous object methods and closures. | |
*/ | |
global $wp_filter; | |
$priority = (int) $priority; | |
if ( empty( $wp_filter[ $hook ]->callbacks[$priority] ) ) { | |
return 0; | |
} | |
$callback_ids = get_callback_ids( $callback_type, $callback, $no_args, $wp_filter[ $hook ]->callbacks[$priority] ); | |
foreach ( (array) $callback_ids as $callback_id ) { | |
unset( $wp_filter[$hook]->callbacks[$priority][$callback_id] ); | |
} | |
return count( (array) $callback_ids ); | |
} | |
} | |
if ( ! function_exists( __NAMESPACE__ . '\get_callback_ids' ) ) { | |
/** | |
* Retrieve the callback_ids that match the arguments. | |
* | |
* @since 1.7.14 | |
* @param string $callback_type 'anonymous object', or 'closure' | |
* @param array|void $callback_method [description] | |
* @param int $no_args Number or arguments passed to add_action(), or add_filter(). | |
* @param array $callbacks Array of callbacks of a hook within a priority. | |
* @return array Array containing zero or more callback id's. | |
*/ | |
function get_callback_ids( string $callback_type, array $callback_method, $no_args = 1, array $callbacks ): array { | |
if ( ! in_array( $callback_type, array( 'anonymous object', 'closure' ) ) ) { | |
return []; | |
} | |
if ( 'anonymous object' === $callback_type | |
&& ( ! is_array( $callback_method ) || 2 < count( $callback_method ) ) ) { | |
return []; | |
} | |
$callback_ids = array(); | |
foreach ( $callbacks as $callback_id => $callback ) { | |
if ( $callback['accepted_args'] != $no_args | |
|| ! is_array( $callback['function'] ) | |
|| ! is_object( $callback['function'][0] ) ) { | |
continue; | |
} | |
switch ( $callback_type ) { | |
case 'closure': | |
/* | |
* Closures cannot be individually targetted, | |
* but we can remove all closures within this priority. | |
*/ | |
if ( is_closure( $callback['function'][0] ) ) { | |
$callback_ids[] = $callback_id; | |
} | |
continue; | |
case 'anonymous object': | |
default: | |
/** | |
* When the classname and method name match | |
*/ | |
if ( is_a( $callback['function'][0], $callback_method[0] ) | |
&& $callback['function'][1] === $callback_method[1] ) { | |
return (array) $callback_id; | |
} | |
continue; | |
} | |
} | |
return $callback_ids; | |
} | |
} | |
if ( ! function_exists( __NAMESPACE__ . '\is_closure' ) ) { | |
/** | |
* Is this a closure? | |
* | |
* @since 1.7.14 | |
* @param mixed $function Variable that will be determined as closure or not. | |
* @return bool True if $function is an instance of class Closure, otherwise false. | |
*/ | |
function is_closure( $function ) { | |
return is_object( $function ) && $function instanceof \Closure; | |
} | |
} | |
/** | |
* Unhooks callbacks from admin_notices hook. | |
* | |
* @return void | |
*/ | |
function remove_admin_notices() { | |
/** | |
* the get_config function is part of a runtime configuration management module | |
* and reads a runtime configuration file returning an array in the following format: | |
* array( | |
* array( | |
* 'hook' => {hook}, | |
* 'callback' => string:{function-name}|array( {class name}{method name} ), | |
* 'priority' => {priority}, | |
* 'no-args' => {hook}, | |
* 'cb-type' => {'function'|'named object'|'anonymous object'|'closure'}, | |
* ), | |
* ); | |
*/ | |
$notices = get_config('remove-admin-notices'); | |
$removed = 0; | |
foreach ( $notices as $notice ) { | |
$removed += remove_filter( $notice['hook'], $notice['callback'], $notice['priority'], $notice['args'], $notice['cb-type'] ); | |
} | |
} | |
add_action( 'admin_notices', __NAMESPACE__ . '\remove_admin_notices', 0 ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment