From c97f234e6f6125ccd068b073593ae40bbfbddadd Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Sat, 25 Jun 2016 19:56:19 +0000 Subject: [PATCH] Allow action and filter hooks to be deprecated. When a filter or action hook is deprecated, the corresponding `apply_filters()` or `do_action()` calls should be switched out with `apply_filters_deprecated()` or `do_action_deprecated()`. The latter functions will throw a deprecation before invoking the original hook. Props solarissmoke, SergeyBiryukov, DrewAPicture. Fixes #10441. git-svn-id: https://develop.svn.wordpress.org/trunk@37861 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/functions.php | 50 +++++++++++++++++ src/wp-includes/plugin.php | 54 +++++++++++++++++++ tests/phpunit/includes/testcase.php | 2 + tests/phpunit/tests/actions.php | 83 +++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f0e91851cb..690e2713d7 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3892,6 +3892,56 @@ function _deprecated_argument( $function, $version, $message = null ) { } } +/** + * Marks a deprecated action or filter hook as deprecated and throws a notice. + * + * Use the 'deprecated_hook_run' action to get the backtrace describing where the + * deprecated hook was called. + * + * Default behavior is to trigger a user error if WP_DEBUG is true. + * + * This function is called by the do_action_deprecated() and apply_filters_deprecated() + * functions, and so generally does not need to be called directly. + * + * @since 4.6.0 + * @access private + * + * @param string $hook The hook that was used. + * @param string $version The version of WordPress that deprecated the hook. + * @param string $replacement Optional. The hook that should have been used. + * @param string $message Optional. A message regarding the change. + */ +function _deprecated_hook( $hook, $version, $replacement = null, $message = null ) { + /** + * Fires when a deprecated hook is called. + * + * @since 4.6.0 + * + * @param string $hook The hook that was called. + * @param string $replacement The hook that should be used as a replacement. + * @param string $version The version of WordPress that deprecated the argument used. + * @param string $message A message regarding the change. + */ + do_action( 'deprecated_hook_run', $hook, $replacement, $version, $message ); + + /** + * Filter whether to trigger deprecated hook errors. + * + * @since 4.6.0 + * + * @param bool $trigger Whether to trigger deprecated hook errors. Requires + * `WP_DEBUG` to be defined true. + */ + if ( WP_DEBUG && apply_filters( 'deprecated_hook_trigger_error', true ) ) { + $message = empty( $message ) ? '' : ' ' . $message; + if ( ! is_null( $replacement ) ) { + trigger_error( sprintf( __( '%1$s is deprecated since version %2$s! Use %3$s instead.' ), $hook, $version, $replacement ) . $message ); + } else { + trigger_error( sprintf( __( '%1$s is deprecated since version %2$s with no alternative available.' ), $hook, $version ) . $message ); + } + } +} + /** * Mark something as being incorrectly called. * diff --git a/src/wp-includes/plugin.php b/src/wp-includes/plugin.php index acf58d88f6..44c45a6d7f 100644 --- a/src/wp-includes/plugin.php +++ b/src/wp-includes/plugin.php @@ -655,6 +655,60 @@ function remove_all_actions($tag, $priority = false) { return remove_all_filters($tag, $priority); } +/** + * Fires functions attached to a deprecated filter hook. + * + * When a filter hook is deprecated, the apply_filters() call is replaced with + * apply_filters_deprecated(), which triggers a deprecation notice and then fires + * the original filter hook. + * + * @since 4.6.0 + * + * @see _deprecated_hook() + * + * @param string $tag The name of the filter hook. + * @param array $args Array of additional function arguments to be passed to apply_filters(). + * @param string $version The version of WordPress that deprecated the hook. + * @param string $replacement Optional. The hook that should have been used. + * @param string $message Optional. A message regarding the change. + */ +function apply_filters_deprecated( $tag, $args, $version, $replacement = false, $message = null ) { + if ( ! has_filter( $tag ) ) { + return; + } + + _deprecated_hook( $tag, $version, $replacement, $message ); + + return apply_filters_ref_array( $tag, $args ); +} + +/** + * Fires functions attached to a deprecated action hook. + * + * When an action hook is deprecated, the do_action() call is replaced with + * do_action_deprecated(), which triggers a deprecation notice and then fires + * the original hook. + * + * @since 4.6.0 + * + * @see _deprecated_hook() + * + * @param string $tag The name of the action hook. + * @param array $args Array of additional function arguments to be passed to do_action(). + * @param string $version The version of WordPress that deprecated the hook. + * @param string $replacement Optional. The hook that should have been used. + * @param string $message Optional. A message regarding the change. + */ +function do_action_deprecated( $tag, $args, $version, $replacement = false, $message = null ) { + if ( ! has_action( $tag ) ) { + return; + } + + _deprecated_hook( $tag, $version, $replacement, $message ); + + do_action_ref_array( $tag, $args ); +} + // // Functions for handling plugins. // diff --git a/tests/phpunit/includes/testcase.php b/tests/phpunit/includes/testcase.php index 6c893cef49..6eed90af72 100644 --- a/tests/phpunit/includes/testcase.php +++ b/tests/phpunit/includes/testcase.php @@ -309,9 +309,11 @@ class WP_UnitTestCase extends PHPUnit_Framework_TestCase { } add_action( 'deprecated_function_run', array( $this, 'deprecated_function_run' ) ); add_action( 'deprecated_argument_run', array( $this, 'deprecated_function_run' ) ); + add_action( 'deprecated_hook_run', array( $this, 'deprecated_function_run' ) ); add_action( 'doing_it_wrong_run', array( $this, 'doing_it_wrong_run' ) ); add_action( 'deprecated_function_trigger_error', '__return_false' ); add_action( 'deprecated_argument_trigger_error', '__return_false' ); + add_action( 'deprecated_hook_trigger_error', '__return_false' ); add_action( 'doing_it_wrong_trigger_error', '__return_false' ); } diff --git a/tests/phpunit/tests/actions.php b/tests/phpunit/tests/actions.php index ad53ec0199..4b2f9c7118 100644 --- a/tests/phpunit/tests/actions.php +++ b/tests/phpunit/tests/actions.php @@ -419,4 +419,87 @@ class Tests_Actions extends WP_UnitTestCase { $this->assertTrue( doing_filter( 'testing_nested' ) ); $this->assertFalse( doing_filter( 'something_else' ) ); } + + /** + * @ticket 10441 + * @expectedDeprecated tests_do_action_deprecated + */ + public function test_do_action_deprecated() { + $p = new WP_Post( (object) array( 'post_title' => 'Foo' ) ); + + add_action( 'tests_do_action_deprecated', array( __CLASS__, 'deprecated_action_callback' ) ); + do_action_deprecated( 'tests_do_action_deprecated', array( $p ), '4.6' ); + remove_action( 'tests_do_action_deprecated', array( __CLASS__, 'deprecated_action_callback' ) ); + + $this->assertSame( 'Bar', $p->post_title ); + } + + public static function deprecated_action_callback( $p ) { + $p->post_title = 'Bar'; + } + + /** + * @ticket 10441 + * @expectedDeprecated tests_do_action_deprecated + */ + public function test_do_action_deprecated_with_multiple_params() { + $p1 = new WP_Post( (object) array( 'post_title' => 'Foo1' ) ); + $p2 = new WP_Post( (object) array( 'post_title' => 'Foo2' ) ); + + add_action( 'tests_do_action_deprecated', array( __CLASS__, 'deprecated_action_callback_multiple_params' ), 10, 2 ); + do_action_deprecated( 'tests_do_action_deprecated', array( $p1, $p2 ), '4.6' ); + remove_action( 'tests_do_action_deprecated', array( __CLASS__, 'deprecated_action_callback_multiple_params' ), 10, 2 ); + + $this->assertSame( 'Bar1', $p1->post_title ); + $this->assertSame( 'Bar2', $p2->post_title ); + } + + public static function deprecated_action_callback_multiple_params( $p1, $p2 ) { + $p1->post_title = 'Bar1'; + $p2->post_title = 'Bar2'; + } + + /** + * @ticket 10441 + * @expectedDeprecated tests_apply_filters_deprecated + */ + public function test_apply_filters_deprecated() { + $p = 'Foo'; + + add_filter( 'tests_apply_filters_deprecated', array( __CLASS__, 'deprecated_filter_callback' ) ); + $p = apply_filters_deprecated( 'tests_apply_filters_deprecated', array( $p ), '4.6' ); + remove_filter( 'tests_apply_filters_deprecated', array( __CLASS__, 'deprecated_filter_callback' ) ); + + $this->assertSame( 'Bar', $p ); + } + + public static function deprecated_filter_callback( $p ) { + $p = 'Bar'; + return $p; + } + + /** + * @ticket 10441 + * @expectedDeprecated tests_apply_filters_deprecated + */ + public function test_apply_filters_deprecated_with_multiple_params() { + $p1 = 'Foo1'; + $p2 = 'Foo2'; + + add_filter( 'tests_apply_filters_deprecated', array( __CLASS__, 'deprecated_filter_callback_multiple_params' ), 10, 2 ); + $p1 = apply_filters_deprecated( 'tests_apply_filters_deprecated', array( $p1, $p2 ), '4.6' ); + remove_filter( 'tests_apply_filters_deprecated', array( __CLASS__, 'deprecated_filter_callback_multiple_params' ), 10, 2 ); + + $this->assertSame( 'Bar1', $p1 ); + + // Not passed by reference, so not modified. + $this->assertSame( 'Foo2', $p2 ); + } + + public static function deprecated_filter_callback_multiple_params( $p1, $p2 ) { + $p1 = 'Bar1'; + $p2 = 'Bar2'; + + return $p1; + } }