diff --git a/src/wp-includes/class-wp-hook.php b/src/wp-includes/class-wp-hook.php index f9f0f8826be94..0740d52ac1f45 100644 --- a/src/wp-includes/class-wp-hook.php +++ b/src/wp-includes/class-wp-hook.php @@ -223,16 +223,21 @@ public function remove_filter( $hook_name, $callback, $priority ) { * that evaluates to false (e.g. 0), so use the `===` operator for testing the return value. * * @since 4.7.0 + * @since 6.9.0 Added the `$priority` parameter. * * @param string $hook_name Optional. The name of the filter hook. Default empty. * @param callable|string|array|false $callback Optional. The callback to check for. * This method can be called unconditionally to speculatively check * a callback that may or may not exist. Default false. + * @param int|false $priority Optional. The specific priority at which to check for the callback. + * Default false. * @return bool|int If `$callback` is omitted, returns boolean for whether the hook has * anything registered. When checking a specific function, the priority * of that hook is returned, or false if the function is not attached. + * If `$callback` and `$priority` are both provided, a boolean is returned + * for whether the specific function is registered at that priority. */ - public function has_filter( $hook_name = '', $callback = false ) { + public function has_filter( $hook_name = '', $callback = false, $priority = false ) { if ( false === $callback ) { return $this->has_filters(); } @@ -243,9 +248,13 @@ public function has_filter( $hook_name = '', $callback = false ) { return false; } - foreach ( $this->callbacks as $priority => $callbacks ) { + if ( is_int( $priority ) ) { + return isset( $this->callbacks[ $priority ][ $function_key ] ); + } + + foreach ( $this->callbacks as $callback_priority => $callbacks ) { if ( isset( $callbacks[ $function_key ] ) ) { - return $priority; + return $callback_priority; } } diff --git a/src/wp-includes/plugin.php b/src/wp-includes/plugin.php index 5b4079b0bdd81..0ca495b6f76d4 100644 --- a/src/wp-includes/plugin.php +++ b/src/wp-includes/plugin.php @@ -267,6 +267,7 @@ function apply_filters_ref_array( $hook_name, $args ) { * that evaluates to false (e.g. 0), so use the `===` operator for testing the return value. * * @since 2.5.0 + * @since 6.9.0 Added the `$priority` parameter. * * @global WP_Hook[] $wp_filter Stores all of the filters and actions. * @@ -274,18 +275,22 @@ function apply_filters_ref_array( $hook_name, $args ) { * @param callable|string|array|false $callback Optional. The callback to check for. * This function can be called unconditionally to speculatively check * a callback that may or may not exist. Default false. + * @param int|false $priority Optional. The specific priority at which to check for the callback. + * Default false. * @return bool|int If `$callback` is omitted, returns boolean for whether the hook has * anything registered. When checking a specific function, the priority * of that hook is returned, or false if the function is not attached. + * If `$callback` and `$priority` are both provided, a boolean is returned + * for whether the specific function is registered at that priority. */ -function has_filter( $hook_name, $callback = false ) { +function has_filter( $hook_name, $callback = false, $priority = false ) { global $wp_filter; if ( ! isset( $wp_filter[ $hook_name ] ) ) { return false; } - return $wp_filter[ $hook_name ]->has_filter( $hook_name, $callback ); + return $wp_filter[ $hook_name ]->has_filter( $hook_name, $callback, $priority ); } /** @@ -574,6 +579,7 @@ function do_action_ref_array( $hook_name, $args ) { * that evaluates to false (e.g. 0), so use the `===` operator for testing the return value. * * @since 2.5.0 + * @since 6.9.0 Added the `$priority` parameter. * * @see has_filter() This function is an alias of has_filter(). * @@ -581,12 +587,16 @@ function do_action_ref_array( $hook_name, $args ) { * @param callable|string|array|false $callback Optional. The callback to check for. * This function can be called unconditionally to speculatively check * a callback that may or may not exist. Default false. + * @param int|false $priority Optional. The specific priority at which to check for the callback. + * Default false. * @return bool|int If `$callback` is omitted, returns boolean for whether the hook has * anything registered. When checking a specific function, the priority * of that hook is returned, or false if the function is not attached. + * If `$callback` and `$priority` are both provided, a boolean is returned + * for whether the specific function is registered at that priority. */ -function has_action( $hook_name, $callback = false ) { - return has_filter( $hook_name, $callback ); +function has_action( $hook_name, $callback = false, $priority = false ) { + return has_filter( $hook_name, $callback, $priority ); } /** diff --git a/tests/phpunit/tests/actions.php b/tests/phpunit/tests/actions.php index ce4d896015ad6..6e7f292b6b71b 100644 --- a/tests/phpunit/tests/actions.php +++ b/tests/phpunit/tests/actions.php @@ -64,20 +64,23 @@ public function test_remove_action() { $hook_name = __FUNCTION__; add_action( $hook_name, array( &$a, 'action' ) ); + add_action( $hook_name, array( &$a, 'action' ), 100 ); do_action( $hook_name ); // Make sure our hook was called correctly. - $this->assertSame( 1, $a->get_call_count() ); - $this->assertSame( array( $hook_name ), $a->get_hook_names() ); + $this->assertSame( 2, $a->get_call_count() ); + $this->assertSame( array( $hook_name, $hook_name ), $a->get_hook_names() ); // Now remove the action, do it again, and make sure it's not called this time. remove_action( $hook_name, array( &$a, 'action' ) ); + remove_action( $hook_name, array( &$a, 'action' ), 100 ); do_action( $hook_name ); - $this->assertSame( 1, $a->get_call_count() ); - $this->assertSame( array( $hook_name ), $a->get_hook_names() ); + $this->assertSame( 2, $a->get_call_count() ); + $this->assertSame( array( $hook_name, $hook_name ), $a->get_hook_names() ); } /** + * @ticket 64186 * @covers ::has_action */ public function test_has_action() { @@ -89,8 +92,21 @@ public function test_has_action() { add_action( $hook_name, $callback ); $this->assertSame( 10, has_action( $hook_name, $callback ) ); + $this->assertFalse( has_action( $hook_name, $callback, 9 ) ); $this->assertTrue( has_action( $hook_name ) ); + add_action( $hook_name, $callback, 9 ); + add_action( $hook_name, $callback, 11 ); + $this->assertSame( 9, has_action( $hook_name, $callback ) ); + $this->assertTrue( has_action( $hook_name, $callback, 9 ) ); + $this->assertTrue( has_action( $hook_name, $callback, 10 ) ); + $this->assertTrue( has_action( $hook_name, $callback, 11 ) ); + $this->assertTrue( has_action( $hook_name ) ); + + remove_action( $hook_name, $callback, 9 ); + remove_action( $hook_name, $callback, 11 ); + $this->assertSame( 10, has_action( $hook_name, $callback ) ); + remove_action( $hook_name, $callback ); $this->assertFalse( has_action( $hook_name, $callback ) ); $this->assertFalse( has_action( $hook_name ) ); diff --git a/tests/phpunit/tests/filters.php b/tests/phpunit/tests/filters.php index 8294d07d6e75e..8ce599c388f29 100644 --- a/tests/phpunit/tests/filters.php +++ b/tests/phpunit/tests/filters.php @@ -25,25 +25,34 @@ public function test_simple_filter() { $this->assertSame( array( $val ), $args ); } + /** + * @covers ::remove_filter + */ public function test_remove_filter() { $a = new MockAction(); $hook_name = __FUNCTION__; $val = __FUNCTION__ . '_val'; add_filter( $hook_name, array( $a, 'filter' ) ); + add_filter( $hook_name, array( $a, 'filter' ), 100 ); $this->assertSame( $val, apply_filters( $hook_name, $val ) ); // Make sure our hook was called correctly. - $this->assertSame( 1, $a->get_call_count() ); - $this->assertSame( array( $hook_name ), $a->get_hook_names() ); + $this->assertSame( 2, $a->get_call_count() ); + $this->assertSame( array( $hook_name, $hook_name ), $a->get_hook_names() ); // Now remove the filter, do it again, and make sure it's not called this time. remove_filter( $hook_name, array( $a, 'filter' ) ); + remove_filter( $hook_name, array( $a, 'filter' ), 100 ); $this->assertSame( $val, apply_filters( $hook_name, $val ) ); - $this->assertSame( 1, $a->get_call_count() ); - $this->assertSame( array( $hook_name ), $a->get_hook_names() ); + $this->assertSame( 2, $a->get_call_count() ); + $this->assertSame( array( $hook_name, $hook_name ), $a->get_hook_names() ); } + /** + * @ticket 64186 + * @covers ::has_filter + */ public function test_has_filter() { $hook_name = __FUNCTION__; $callback = __FUNCTION__ . '_func'; @@ -53,8 +62,21 @@ public function test_has_filter() { add_filter( $hook_name, $callback ); $this->assertSame( 10, has_filter( $hook_name, $callback ) ); + $this->assertFalse( has_filter( $hook_name, $callback, 9 ) ); + $this->assertTrue( has_filter( $hook_name ) ); + + add_filter( $hook_name, $callback, 9 ); + add_filter( $hook_name, $callback, 11 ); + $this->assertSame( 9, has_filter( $hook_name, $callback ) ); + $this->assertTrue( has_filter( $hook_name, $callback, 9 ) ); + $this->assertTrue( has_filter( $hook_name, $callback, 10 ) ); + $this->assertTrue( has_filter( $hook_name, $callback, 11 ) ); $this->assertTrue( has_filter( $hook_name ) ); + remove_filter( $hook_name, $callback, 9 ); + remove_filter( $hook_name, $callback, 11 ); + $this->assertSame( 10, has_filter( $hook_name, $callback ) ); + remove_filter( $hook_name, $callback ); $this->assertFalse( has_filter( $hook_name, $callback ) ); $this->assertFalse( has_filter( $hook_name ) ); diff --git a/tests/phpunit/tests/hooks/hasFilter.php b/tests/phpunit/tests/hooks/hasFilter.php index a13a16b390e89..ce4f2e9fb57ec 100644 --- a/tests/phpunit/tests/hooks/hasFilter.php +++ b/tests/phpunit/tests/hooks/hasFilter.php @@ -8,16 +8,25 @@ */ class Tests_Hooks_HasFilter extends WP_UnitTestCase { + /** + * @ticket 64186 + */ public function test_has_filter_with_function() { $callback = '__return_null'; $hook = new WP_Hook(); $hook_name = __FUNCTION__; - $priority = 1; + $priority_a = 1; + $priority_b = 10; $accepted_args = 2; - $hook->add_filter( $hook_name, $callback, $priority, $accepted_args ); + $hook->add_filter( $hook_name, $callback, $priority_a, $accepted_args ); + $hook->add_filter( $hook_name, $callback, $priority_b, $accepted_args ); - $this->assertSame( $priority, $hook->has_filter( $hook_name, $callback ) ); + $this->assertSame( $priority_a, $hook->has_filter( $hook_name, $callback ) ); + $this->assertTrue( $hook->has_filter( $hook_name, $callback, $priority_a ) ); + $this->assertTrue( $hook->has_filter( $hook_name, $callback, $priority_b ) ); + $hook->remove_filter( $hook_name, $callback, $priority_a ); + $this->assertSame( $priority_b, $hook->has_filter( $hook_name, $callback ) ); } public function test_has_filter_with_object() {