Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hansschuijff/3526246fd690a3c9582b3e16e4aa5185 to your computer and use it in GitHub Desktop.
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.
<?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