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;
+ }
}