diff --git a/src/wp-includes/class-wp-hook.php b/src/wp-includes/class-wp-hook.php new file mode 100755 index 0000000000..0bdde1d434 --- /dev/null +++ b/src/wp-includes/class-wp-hook.php @@ -0,0 +1,516 @@ +callbacks[ $priority ] ); + + $this->callbacks[ $priority ][ $idx ] = array( + 'function' => $function_to_add, + 'accepted_args' => $accepted_args + ); + + // if we're adding a new priority to the list, put them back in sorted order + if ( ! $priority_existed && count( $this->callbacks ) > 1 ) { + ksort( $this->callbacks, SORT_NUMERIC ); + } + + if ( $this->nesting_level > 0 ) { + $this->resort_active_iterations( $priority, $priority_existed ); + } + } + + /** + * Handles reseting callback priority keys mid-iteration. + * + * @since 4.7.0 + * @access private + * + * @param bool|int $new_priority The priority of the new filter being added. Default false, for no priority being added. + * @param bool $priority_existed Flag for whether the priority already existed before the new filter was added. + */ + private function resort_active_iterations( $new_priority = false, $priority_existed = false ) { + $new_priorities = array_keys( $this->callbacks ); + + // If there are no remaining hooks, clear out all running iterations. + if ( ! $new_priorities ) { + foreach ( $this->iterations as $index => $iteration ) { + $this->iterations[ $index ] = $new_priorities; + } + return; + } + + $min = min( $new_priorities ); + foreach ( $this->iterations as $index => &$iteration ) { + $current = current( $iteration ); + // If we're already at the end of this iteration, just leave the array pointer where it is. + if ( false === $current ) { + continue; + } + + $iteration = $new_priorities; + + if ( $current < $min ) { + array_unshift( $iteration, $current ); + continue; + } + + while ( current( $iteration ) < $current ) { + if ( false === next( $iteration ) ) { + break; + } + } + + // If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority... + if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) { + // ... and the new priority is the same as what $this->iterations thinks is the previous priority, + // We need to move back to it. + + if ( false === current( $iteration ) ) { + // If we've already moved off the end of the array, go back to the last element. + $prev = end( $iteration ); + } else { + // Otherwise, just go back to the previous element. + $prev = prev( $iteration ); + } + if ( false === $prev ) { + // Start of the array. Reset, and go about our day. + reset( $iteration ); + } elseif ( $new_priority !== $prev ) { + // Previous wasn't the same. Move forward again. + next( $iteration ); + } + } + } + unset( $iteration ); + } + + /** + * Unhooks a function or method from a specific filter action. + * + * @since 4.7.0 + * @access public + * + * @param string $tag The filter hook to which the function to be removed is hooked. Used + * for building the callback ID when SPL is not available. + * @param callable $function_to_remove The callback to be removed from running when the filter is applied. + * @param int $priority The exact priority used when adding the original filter callback. + * @return bool Whether the callback existed before it was removed. + */ + public function remove_filter( $tag, $function_to_remove, $priority ) { + $function_key = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority ); + + $exists = isset( $this->callbacks[ $priority ][ $function_key ] ); + if ( $exists ) { + unset( $this->callbacks[ $priority ][ $function_key ] ); + if ( ! $this->callbacks[ $priority ] ) { + unset( $this->callbacks[ $priority ] ); + if ( $this->nesting_level > 0 ) { + $this->resort_active_iterations(); + } + } + } + return $exists; + } + + /** + * Checks if a specific action has been registered for this hook. + * + * @since 4.7.0 + * @access public + * + * @param callable|bool $function_to_check Optional. The callback to check for. Default false. + * @param string $tag Optional. The name of the filter hook. Default empty. + * Used for building the callback ID when SPL is not available. + * @return bool|int The priority of that hook is returned, or false if the function is not attached. + */ + public function has_filter( $tag = '', $function_to_check = false ) { + if ( false === $function_to_check ) { + return $this->has_filters(); + } + + $function_key = _wp_filter_build_unique_id( $tag, $function_to_check, false ); + if ( ! $function_key ) { + return false; + } + + foreach ( $this->callbacks as $priority => $callbacks ) { + if ( isset( $callbacks[ $function_key ] ) ) { + return $priority; + } + } + + return false; + } + + /** + * Checks if any callbacks have been registered for this hook. + * + * @since 4.7.0 + * @access public + * + * @return bool True if callbacks have been registered for the current hook, false otherwise. + */ + public function has_filters() { + foreach ( $this->callbacks as $callbacks ) { + if ( $callbacks ) { + return true; + } + } + return false; + } + + /** + * Removes all callbacks from the current filter. + * + * @since 4.7.0 + * @access public + * + * @param int|bool $priority Optional. The priority number to remove. Default false. + */ + public function remove_all_filters( $priority = false ) { + if ( ! $this->callbacks ) { + return; + } + + if ( false === $priority ) { + $this->callbacks = array(); + } else if ( isset( $this->callbacks[ $priority ] ) ) { + unset( $this->callbacks[ $priority ] ); + } + + if ( $this->nesting_level > 0 ) { + $this->resort_active_iterations(); + } + } + + /** + * Calls the callback functions added to a filter hook. + * + * @since 4.7.0 + * @access public + * + * @param mixed $value The value to filter. + * @param array $args Arguments to pass to callbacks. + * @return mixed The filtered value after all hooked functions are applied to it. + */ + public function apply_filters( $value, $args ) { + if ( ! $this->callbacks ) { + return $value; + } + + $nesting_level = $this->nesting_level++; + + $this->iterations[ $nesting_level ] = array_keys( $this->callbacks ); + $num_args = count( $args ); + + do { + $this->current_priority[ $nesting_level ] = $priority = current( $this->iterations[ $nesting_level ] ); + + foreach ( $this->callbacks[ $priority ] as $the_ ) { + if( ! $this->doing_action ) { + $args[ 0 ] = $value; + } + + // Avoid the array_slice if possible. + if ( $the_['accepted_args'] == 0 ) { + $value = call_user_func_array( $the_['function'], array() ); + } elseif ( $the_['accepted_args'] >= $num_args ) { + $value = call_user_func_array( $the_['function'], $args ); + } else { + $value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int)$the_['accepted_args'] ) ); + } + } + } while ( false !== next( $this->iterations[ $nesting_level ] ) ); + + unset( $this->iterations[ $nesting_level ] ); + unset( $this->current_priority[ $nesting_level ] ); + + $this->nesting_level--; + + return $value; + } + + /** + * Executes the callback functions hooked on a specific action hook. + * + * @since 4.7.0 + * @access public + * + * @param mixed $args Arguments to pass to the hook callbacks. + */ + public function do_action( $args ) { + $this->doing_action = true; + $this->apply_filters( '', $args ); + + // If there are recursive calls to the current action, we haven't finished it until we get to the last one. + if ( ! $this->nesting_level ) { + $this->doing_action = false; + } + } + + /** + * Processes the functions hooked into the 'all' hook. + * + * @since 4.7.0 + * @access public + * + * @param array $args Arguments to pass to the hook callbacks. Passed by reference. + */ + public function do_all_hook( &$args ) { + $nesting_level = $this->nesting_level++; + $this->iterations[ $nesting_level ] = array_keys( $this->callbacks ); + + do { + $priority = current( $this->iterations[ $nesting_level ] ); + foreach ( $this->callbacks[ $priority ] as $the_ ) { + call_user_func_array( $the_['function'], $args ); + } + } while ( false !== next( $this->iterations[ $nesting_level ] ) ); + + unset( $this->iterations[ $nesting_level ] ); + $this->nesting_level--; + } + + /** + * Normalizes filters setup before WordPress has initialized to WP_Hook objects. + * + * @since 4.7.0 + * @access public + * @static + * + * @param array $filters Filters to normalize. + * @return WP_Hook[] Array of normalized filters. + */ + public static function build_preinitialized_hooks( $filters ) { + /** @var WP_Hook[] $normalized */ + $normalized = array(); + + foreach ( $filters as $tag => $callback_groups ) { + if ( is_object( $callback_groups ) && $callback_groups instanceof WP_Hook ) { + $normalized[ $tag ] = $callback_groups; + continue; + } + $hook = new WP_Hook(); + + // Loop through callback groups. + foreach ( $callback_groups as $priority => $callbacks ) { + + // Loop through callbacks. + foreach ( $callbacks as $cb ) { + $hook->add_filter( $tag, $cb['function'], $priority, $cb['accepted_args'] ); + } + } + $normalized[ $tag ] = $hook; + } + return $normalized; + } + + /** + * Determines whether an offset value exists. + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param mixed $offset An offset to check for. + * @return bool True if the offset exists, false otherwise. + */ + public function offsetExists( $offset ) { + return isset( $this->callbacks[ $offset ] ); + } + + /** + * Retrieves a value at a specified offset. + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param mixed $offset The offset to retrieve. + * @return mixed If set, the value at the specified offset, null otherwise. + */ + public function offsetGet( $offset ) { + return isset( $this->callbacks[ $offset ] ) ? $this->callbacks[ $offset ] : null; + } + + /** + * Sets a value at a specified offset. + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param mixed $offset The offset to assign the value to. + * @param mixed $value The value to set. + */ + public function offsetSet( $offset, $value ) { + if ( is_null( $offset ) ) { + $this->callbacks[] = $value; + } else { + $this->callbacks[ $offset ] = $value; + } + } + + /** + * Unsets a specified offset. + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param mixed $offset The offset to unset. + */ + public function offsetUnset( $offset ) { + unset( $this->callbacks[ $offset ] ); + } + + /** + * Return the current element + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/iterator.current.php + * + * @return array Of callbacks at current priority. + */ + public function current() { + return current( $this->callbacks ); + } + + /** + * Move forward to the next element + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/iterator.next.php + * + * @return array Of callbacks at next priority. + */ + public function next() { + return next( $this->callbacks ); + } + + /** + * Return the key of the current element + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/iterator.key.php + * + * @return mixed Returns current priority on success, or NULL on failure + */ + public function key() { + return key( $this->callbacks ); + } + + /** + * Checks if current position is valid + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/iterator.valid.php + * + * @return boolean + */ + public function valid() { + return key( $this->callbacks ) !== null; + } + + /** + * Rewind the Iterator to the first element + * + * @since 4.7.0 + * @access public + * + * @link http://php.net/manual/en/iterator.rewind.php + */ + public function rewind() { + reset( $this->callbacks ); + } + + +} diff --git a/src/wp-includes/plugin.php b/src/wp-includes/plugin.php index bdb9ebd17d..f2585c5a9e 100644 --- a/src/wp-includes/plugin.php +++ b/src/wp-includes/plugin.php @@ -22,17 +22,20 @@ */ // Initialize the filter globals. -global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter; +require( ABSPATH . WPINC . '/class-wp-hook.php' ); -if ( ! isset( $wp_filter ) ) +/** @var WP_Hook[] $wp_filter */ +global $wp_filter, $wp_actions, $wp_current_filter; + +if ( $wp_filter ) { + $wp_filter = WP_Hook::build_preinitialized_hooks( $wp_filter ); +} else { $wp_filter = array(); +} if ( ! isset( $wp_actions ) ) $wp_actions = array(); -if ( ! isset( $merged_filters ) ) - $merged_filters = array(); - if ( ! isset( $wp_current_filter ) ) $wp_current_filter = array(); @@ -89,8 +92,6 @@ if ( ! isset( $wp_current_filter ) ) * @since 0.71 * * @global array $wp_filter A multidimensional array of all hooks and the callbacks hooked to them. - * @global array $merged_filters Tracks the tags that need to be merged for later. If the hook is added, - * it doesn't need to run through that process. * * @param string $tag The name of the filter to hook the $function_to_add callback to. * @param callable $function_to_add The callback to be run when the filter is applied. @@ -103,11 +104,11 @@ if ( ! isset( $wp_current_filter ) ) * @return true */ function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { - global $wp_filter, $merged_filters; - - $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority); - $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args); - unset( $merged_filters[ $tag ] ); + global $wp_filter; + if ( ! isset( $wp_filter[ $tag ] ) ) { + $wp_filter[ $tag ] = new WP_Hook(); + } + $wp_filter[ $tag ]->add_filter( $tag, $function_to_add, $priority, $accepted_args ); return true; } @@ -128,38 +129,13 @@ function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 * return value. */ function has_filter($tag, $function_to_check = false) { - // Don't reset the internal array pointer - $wp_filter = $GLOBALS['wp_filter']; + global $wp_filter; - $has = ! empty( $wp_filter[ $tag ] ); - - // Make sure at least one priority has a filter callback - if ( $has ) { - $exists = false; - foreach ( $wp_filter[ $tag ] as $callbacks ) { - if ( ! empty( $callbacks ) ) { - $exists = true; - break; - } - } - - if ( ! $exists ) { - $has = false; - } - } - - if ( false === $function_to_check || false === $has ) - return $has; - - if ( !$idx = _wp_filter_build_unique_id($tag, $function_to_check, false) ) + if ( ! isset( $wp_filter[ $tag ] ) ) { return false; - - foreach ( (array) array_keys($wp_filter[$tag]) as $priority ) { - if ( isset($wp_filter[$tag][$priority][$idx]) ) - return $priority; } - return false; + return $wp_filter[ $tag ]->has_filter( $tag, $function_to_check ); } /** @@ -190,7 +166,6 @@ function has_filter($tag, $function_to_check = false) { * @since 0.71 * * @global array $wp_filter Stores all of the filters. - * @global array $merged_filters Merges the filter hooks using this function. * @global array $wp_current_filter Stores the list of current filters with the current one last. * * @param string $tag The name of the filter hook. @@ -199,7 +174,7 @@ function has_filter($tag, $function_to_check = false) { * @return mixed The filtered value after all hooked functions are applied to it. */ function apply_filters( $tag, $value ) { - global $wp_filter, $merged_filters, $wp_current_filter; + global $wp_filter, $wp_current_filter; $args = array(); @@ -219,29 +194,17 @@ function apply_filters( $tag, $value ) { if ( !isset($wp_filter['all']) ) $wp_current_filter[] = $tag; - // Sort. - if ( !isset( $merged_filters[ $tag ] ) ) { - ksort($wp_filter[$tag]); - $merged_filters[ $tag ] = true; - } - - reset( $wp_filter[ $tag ] ); - if ( empty($args) ) $args = func_get_args(); - do { - foreach ( (array) current($wp_filter[$tag]) as $the_ ) - if ( !is_null($the_['function']) ){ - $args[1] = $value; - $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args'])); - } + // don't pass the tag name to WP_Hook + array_shift( $args ); - } while ( next($wp_filter[$tag]) !== false ); + $filtered = $wp_filter[ $tag ]->apply_filters( $value, $args ); array_pop( $wp_current_filter ); - return $value; + return $filtered; } /** @@ -253,7 +216,6 @@ function apply_filters( $tag, $value ) { * functions hooked to `$tag` are supplied using an array. * * @global array $wp_filter Stores all of the filters - * @global array $merged_filters Merges the filter hooks using this function. * @global array $wp_current_filter Stores the list of current filters with the current one last * * @param string $tag The name of the filter hook. @@ -261,7 +223,7 @@ function apply_filters( $tag, $value ) { * @return mixed The filtered value after all hooked functions are applied to it. */ function apply_filters_ref_array($tag, $args) { - global $wp_filter, $merged_filters, $wp_current_filter; + global $wp_filter, $wp_current_filter; // Do 'all' actions first if ( isset($wp_filter['all']) ) { @@ -279,24 +241,11 @@ function apply_filters_ref_array($tag, $args) { if ( !isset($wp_filter['all']) ) $wp_current_filter[] = $tag; - // Sort - if ( !isset( $merged_filters[ $tag ] ) ) { - ksort($wp_filter[$tag]); - $merged_filters[ $tag ] = true; - } - - reset( $wp_filter[ $tag ] ); - - do { - foreach ( (array) current($wp_filter[$tag]) as $the_ ) - if ( !is_null($the_['function']) ) - $args[0] = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args'])); - - } while ( next($wp_filter[$tag]) !== false ); + $filtered = $wp_filter[ $tag ]->apply_filters( $args[0], $args ); array_pop( $wp_current_filter ); - return $args[0]; + return $filtered; } /** @@ -313,7 +262,6 @@ function apply_filters_ref_array($tag, $args) { * @since 1.2.0 * * @global array $wp_filter Stores all of the filters - * @global array $merged_filters Merges the filter hooks using this function. * * @param string $tag The filter hook to which the function to be removed is hooked. * @param callable $function_to_remove The name of the function which should be removed. @@ -321,19 +269,14 @@ function apply_filters_ref_array($tag, $args) { * @return bool Whether the function existed before it was removed. */ function remove_filter( $tag, $function_to_remove, $priority = 10 ) { - $function_to_remove = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority ); + global $wp_filter; - $r = isset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] ); - - if ( true === $r ) { - unset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] ); - if ( empty( $GLOBALS['wp_filter'][ $tag ][ $priority ] ) ) { - unset( $GLOBALS['wp_filter'][ $tag ][ $priority ] ); + $r = false; + if ( isset( $wp_filter[ $tag ] ) ) { + $r = $wp_filter[ $tag ]->remove_filter( $tag, $function_to_remove, $priority ); + if ( ! $wp_filter[ $tag ]->callbacks ) { + unset( $wp_filter[ $tag ] ); } - if ( empty( $GLOBALS['wp_filter'][ $tag ] ) ) { - $GLOBALS['wp_filter'][ $tag ] = array(); - } - unset( $GLOBALS['merged_filters'][ $tag ] ); } return $r; @@ -344,26 +287,22 @@ function remove_filter( $tag, $function_to_remove, $priority = 10 ) { * * @since 2.7.0 * - * @global array $wp_filter Stores all of the filters - * @global array $merged_filters Merges the filter hooks using this function. + * @global array $wp_filter Stores all of the filters * * @param string $tag The filter to remove hooks from. * @param int|bool $priority Optional. The priority number to remove. Default false. * @return true True when finished. */ function remove_all_filters( $tag, $priority = false ) { - global $wp_filter, $merged_filters; + global $wp_filter; if ( isset( $wp_filter[ $tag ]) ) { - if ( false === $priority ) { - $wp_filter[ $tag ] = array(); - } elseif ( isset( $wp_filter[ $tag ][ $priority ] ) ) { - $wp_filter[ $tag ][ $priority ] = array(); + $wp_filter[ $tag ]->remove_all_filters( $priority ); + if ( ! $wp_filter[ $tag ]->has_filters() ) { + unset( $wp_filter[ $tag ] ); } } - unset( $merged_filters[ $tag ] ); - return true; } @@ -473,7 +412,6 @@ function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) * * @global array $wp_filter Stores all of the filters * @global array $wp_actions Increments the amount of times action was triggered. - * @global array $merged_filters Merges the filter hooks using this function. * @global array $wp_current_filter Stores the list of current filters with the current one last * * @param string $tag The name of the action to be executed. @@ -481,7 +419,7 @@ function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) * functions hooked to the action. Default empty. */ function do_action($tag, $arg = '') { - global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter; + global $wp_filter, $wp_actions, $wp_current_filter; if ( ! isset($wp_actions[$tag]) ) $wp_actions[$tag] = 1; @@ -512,20 +450,7 @@ function do_action($tag, $arg = '') { for ( $a = 2, $num = func_num_args(); $a < $num; $a++ ) $args[] = func_get_arg($a); - // Sort - if ( !isset( $merged_filters[ $tag ] ) ) { - ksort($wp_filter[$tag]); - $merged_filters[ $tag ] = true; - } - - reset( $wp_filter[ $tag ] ); - - do { - foreach ( (array) current($wp_filter[$tag]) as $the_ ) - if ( !is_null($the_['function']) ) - call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args'])); - - } while ( next($wp_filter[$tag]) !== false ); + $wp_filter[ $tag ]->do_action( $args ); array_pop($wp_current_filter); } @@ -558,14 +483,13 @@ function did_action($tag) { * functions hooked to $tag< are supplied using an array. * @global array $wp_filter Stores all of the filters * @global array $wp_actions Increments the amount of times action was triggered. - * @global array $merged_filters Merges the filter hooks using this function. * @global array $wp_current_filter Stores the list of current filters with the current one last * * @param string $tag The name of the action to be executed. * @param array $args The arguments supplied to the functions hooked to `$tag`. */ function do_action_ref_array($tag, $args) { - global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter; + global $wp_filter, $wp_actions, $wp_current_filter; if ( ! isset($wp_actions[$tag]) ) $wp_actions[$tag] = 1; @@ -588,20 +512,7 @@ function do_action_ref_array($tag, $args) { if ( !isset($wp_filter['all']) ) $wp_current_filter[] = $tag; - // Sort - if ( !isset( $merged_filters[ $tag ] ) ) { - ksort($wp_filter[$tag]); - $merged_filters[ $tag ] = true; - } - - reset( $wp_filter[ $tag ] ); - - do { - foreach ( (array) current($wp_filter[$tag]) as $the_ ) - if ( !is_null($the_['function']) ) - call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args'])); - - } while ( next($wp_filter[$tag]) !== false ); + $wp_filter[ $tag ]->do_action( $args ); array_pop($wp_current_filter); } @@ -923,13 +834,7 @@ function register_uninstall_hook( $file, $callback ) { function _wp_call_all_hook($args) { global $wp_filter; - reset( $wp_filter['all'] ); - do { - foreach ( (array) current($wp_filter['all']) as $the_ ) - if ( !is_null($the_['function']) ) - call_user_func_array($the_['function'], $args); - - } while ( next($wp_filter['all']) !== false ); + $wp_filter['all']->do_all_hook( $args ); } /** diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php index 7a9ef13de4..ce8ea69a2d 100644 --- a/tests/phpunit/includes/functions.php +++ b/tests/phpunit/includes/functions.php @@ -21,18 +21,14 @@ function tests_reset__SERVER() { // For adding hooks before loading WP function tests_add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) { - global $wp_filter, $merged_filters; + global $wp_filter; $idx = _test_filter_build_unique_id($tag, $function_to_add, $priority); $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args); - unset( $merged_filters[ $tag ] ); return true; } function _test_filter_build_unique_id($tag, $function, $priority) { - global $wp_filter; - static $filter_id_count = 0; - if ( is_string($function) ) return $function; diff --git a/tests/phpunit/includes/testcase.php b/tests/phpunit/includes/testcase.php index 288f7fe01a..1c4d758539 100644 --- a/tests/phpunit/includes/testcase.php +++ b/tests/phpunit/includes/testcase.php @@ -223,10 +223,14 @@ class WP_UnitTestCase extends PHPUnit_Framework_TestCase { * @return void */ protected function _backup_hooks() { - $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' ); + $globals = array( 'wp_actions', 'wp_current_filter' ); foreach ( $globals as $key ) { self::$hooks_saved[ $key ] = $GLOBALS[ $key ]; } + self::$hooks_saved['wp_filter'] = array(); + foreach ( $GLOBALS['wp_filter'] as $hook_name => $hook_object ) { + self::$hooks_saved['wp_filter'][ $hook_name ] = clone $hook_object; + } } /** @@ -240,12 +244,18 @@ class WP_UnitTestCase extends PHPUnit_Framework_TestCase { * @return void */ protected function _restore_hooks() { - $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' ); + $globals = array( 'wp_actions', 'wp_current_filter' ); foreach ( $globals as $key ) { if ( isset( self::$hooks_saved[ $key ] ) ) { $GLOBALS[ $key ] = self::$hooks_saved[ $key ]; } } + if ( isset( self::$hooks_saved['wp_filter'] ) ) { + $GLOBALS['wp_filter'] = array(); + foreach ( self::$hooks_saved['wp_filter'] as $hook_name => $hook_object ) { + $GLOBALS['wp_filter'][ $hook_name ] = clone $hook_object; + } + } } static function flush_cache() { diff --git a/tests/phpunit/tests/actions.php b/tests/phpunit/tests/actions.php index 9bafdcadd6..0f2789a87a 100644 --- a/tests/phpunit/tests/actions.php +++ b/tests/phpunit/tests/actions.php @@ -114,6 +114,45 @@ class Tests_Actions extends WP_UnitTestCase { $this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) ); } + /** + * Test that multiple callbacks receive the correct number of args even when the number + * is less than, or greater than previous hooks. + * + * @see https://core.trac.wordpress.org/ticket/17817#comment:72 + * @ticket 17817 + */ + function test_action_args_3() { + $a1 = new MockAction(); + $a2 = new MockAction(); + $a3 = new MockAction(); + $tag = rand_str(); + $val1 = rand_str(); + $val2 = rand_str(); + + // a1 accepts two arguments, a2 doesn't, a3 accepts two arguments + add_action( $tag, array( &$a1, 'action' ), 10, 2 ); + add_action( $tag, array( &$a2, 'action' ) ); + add_action( $tag, array( &$a3, 'action' ), 10, 2 ); + // call the action with two arguments + do_action( $tag, $val1, $val2 ); + + $call_count = $a1->get_call_count(); + // a1 should be called with both args + $this->assertEquals( 1, $call_count ); + $argsvar1 = $a1->get_args(); + $this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar1 ) ); + + // a2 should be called with one only + $this->assertEquals( 1, $a2->get_call_count() ); + $argsvar2 = $a2->get_args(); + $this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) ); + + // a3 should be called with both args + $this->assertEquals( 1, $a3->get_call_count() ); + $argsvar3 = $a3->get_args(); + $this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar3 ) ); + } + function test_action_priority() { $a = new MockAction(); $tag = rand_str(); @@ -257,6 +296,120 @@ class Tests_Actions extends WP_UnitTestCase { remove_action( 'test_action_self_removal', array( $this, 'action_self_removal' ) ); } + /** + * @ticket 17817 + */ + function test_action_recursion() { + $tag = rand_str(); + $a = new MockAction(); + $b = new MockAction(); + + add_action( $tag, array( $a, 'action' ), 11, 1 ); + add_action( $tag, array( $b, 'action' ), 13, 1 ); + add_action( $tag, array( $this, 'action_that_causes_recursion' ), 12, 1 ); + do_action( $tag, $tag ); + + $this->assertEquals( 2, $a->get_call_count(), 'recursive actions should call all callbacks with earlier priority' ); + $this->assertEquals( 2, $b->get_call_count(), 'recursive actions should call callbacks with later priority' ); + } + + function action_that_causes_recursion( $tag ) { + static $recursing = false; + if ( ! $recursing ) { + $recursing = true; + do_action( $tag, $tag ); + } + $recursing = false; + } + + /** + * @ticket 9968 + * @ticket 17817 + */ + function test_action_callback_manipulation_while_running() { + $tag = rand_str(); + $a = new MockAction(); + $b = new MockAction(); + $c = new MockAction(); + $d = new MockAction(); + $e = new MockAction(); + + add_action( $tag, array( $a, 'action' ), 11, 2 ); + add_action( $tag, array( $this, 'action_that_manipulates_a_running_hook' ), 12, 2 ); + add_action( $tag, array( $b, 'action' ), 12, 2 ); + + do_action( $tag, $tag, array( $a, $b, $c, $d, $e ) ); + do_action( $tag, $tag, array( $a, $b, $c, $d, $e ) ); + + $this->assertEquals( 2, $a->get_call_count(), 'callbacks should run unless otherwise instructed' ); + $this->assertEquals( 1, $b->get_call_count(), 'callback removed by same priority callback should still get called' ); + $this->assertEquals( 1, $c->get_call_count(), 'callback added by same priority callback should not get called' ); + $this->assertEquals( 2, $d->get_call_count(), 'callback added by earlier priority callback should get called' ); + $this->assertEquals( 1, $e->get_call_count(), 'callback added by later priority callback should not get called' ); + } + + function action_that_manipulates_a_running_hook( $tag, $mocks ) { + remove_action( $tag, array( $mocks[ 1 ], 'action' ), 12, 2 ); + add_action( $tag, array( $mocks[ 2 ], 'action' ), 12, 2 ); + add_action( $tag, array( $mocks[ 3 ], 'action' ), 13, 2 ); + add_action( $tag, array( $mocks[ 4 ], 'action' ), 10, 2 ); + } + + /** + * @ticket 17817 + * + * This specificaly addresses the concern raised at + * https://core.trac.wordpress.org/ticket/17817#comment:52 + */ + function test_remove_anonymous_callback() { + $tag = rand_str(); + $a = new MockAction(); + add_action( $tag, array( $a, 'action' ), 12, 1 ); + $this->assertTrue( has_action( $tag ) ); + + $hook = $GLOBALS['wp_filter'][ $tag ]; + + // From http://wordpress.stackexchange.com/a/57088/6445 + foreach ( $hook as $priority => $filter ) { + foreach ( $filter as $identifier => $function ) { + if ( is_array( $function ) + && is_a( $function['function'][ 0 ], 'MockAction' ) + && 'action' === $function['function'][ 1 ] + ) { + remove_filter( + $tag, + array( $function['function'][ 0 ], 'action' ), + $priority + ); + } + } + } + + $this->assertFalse( has_action( $tag ) ); + } + + + /** + * Test the ArrayAccess methods of WP_Hook + * + * @ticket 17817 + */ + function test_array_access_of_wp_filter_global() { + global $wp_filter; + $tag = rand_str(); + + add_action( $tag, '__return_null', 11, 1 ); + + $this->assertTrue( isset( $wp_filter[ $tag ][ 11 ] ) ); + $this->assertArrayHasKey( '__return_null', $wp_filter[ $tag ][ 11 ] ); + + unset( $wp_filter[ $tag ][ 11 ] ); + $this->assertFalse( has_action( $tag, '__return_null' ) ); + + $wp_filter[ $tag ][ 11 ] = array( '__return_null' => array( 'function' => '__return_null', 'accepted_args' => 1 ) ); + $this->assertEquals( 11, has_action( $tag, '__return_null' ) ); + } + /** * Make sure current_action() behaves as current_filter() * diff --git a/tests/phpunit/tests/filters.php b/tests/phpunit/tests/filters.php index 88fa71340c..d2e6ddf0d0 100644 --- a/tests/phpunit/tests/filters.php +++ b/tests/phpunit/tests/filters.php @@ -295,27 +295,6 @@ class Tests_Filters extends WP_UnitTestCase { $this->assertFalse( has_filter( $tag ) ); } - /** - * @ticket 29070 - */ - function test_has_filter_doesnt_reset_wp_filter() { - add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 1 ); - add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 2 ); - add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 3 ); - add_action( 'action_test_has_filter_doesnt_reset_wp_filter', array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ), 4 ); - - do_action( 'action_test_has_filter_doesnt_reset_wp_filter' ); - } - function _action_test_has_filter_doesnt_reset_wp_filter() { - global $wp_filter; - - has_action( 'action_test_has_filter_doesnt_reset_wp_filter', '_function_that_doesnt_exist' ); - - $filters = current( $wp_filter['action_test_has_filter_doesnt_reset_wp_filter'] ); - $the_ = current( $filters ); - $this->assertEquals( $the_['function'], array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ) ); - } - /** * @ticket 10441 * @expectedDeprecated tests_apply_filters_deprecated diff --git a/tests/phpunit/tests/hooks/add_filter.php b/tests/phpunit/tests/hooks/add_filter.php new file mode 100755 index 0000000000..35ac55e7bc --- /dev/null +++ b/tests/phpunit/tests/hooks/add_filter.php @@ -0,0 +1,280 @@ +add_filter( $tag, $callback, $priority, $accepted_args ); + + $function_index = _wp_filter_build_unique_id( $tag, $callback, $priority ); + $this->assertEquals( $callback, $hook->callbacks[ $priority ][ $function_index ]['function'] ); + $this->assertEquals( $accepted_args, $hook->callbacks[ $priority ][ $function_index ]['accepted_args'] ); + } + + public function test_add_filter_with_object() { + $a = new MockAction(); + $callback = array( $a, 'action' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + + $function_index = _wp_filter_build_unique_id( $tag, $callback, $priority ); + $this->assertEquals( $callback, $hook->callbacks[ $priority ][ $function_index ]['function'] ); + $this->assertEquals( $accepted_args, $hook->callbacks[ $priority ][ $function_index ]['accepted_args'] ); + } + + public function test_add_filter_with_static_method() { + $callback = array( 'MockAction', 'action' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + + $function_index = _wp_filter_build_unique_id( $tag, $callback, $priority ); + $this->assertEquals( $callback, $hook->callbacks[ $priority ][ $function_index ]['function'] ); + $this->assertEquals( $accepted_args, $hook->callbacks[ $priority ][ $function_index ]['accepted_args'] ); + } + + public function test_add_two_filters_with_same_priority() { + $callback_one = '__return_null'; + $callback_two = '__return_false'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback_one, $priority, $accepted_args ); + $this->assertCount( 1, $hook->callbacks[ $priority ] ); + + $hook->add_filter( $tag, $callback_two, $priority, $accepted_args ); + $this->assertCount( 2, $hook->callbacks[ $priority ] ); + } + + public function test_add_two_filters_with_different_priority() { + $callback_one = '__return_null'; + $callback_two = '__return_false'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback_one, $priority, $accepted_args ); + $this->assertCount( 1, $hook->callbacks[ $priority ] ); + + $hook->add_filter( $tag, $callback_two, $priority + 1, $accepted_args ); + $this->assertCount( 1, $hook->callbacks[ $priority ] ); + $this->assertCount( 1, $hook->callbacks[ $priority + 1 ] ); + } + + public function test_readd_filter() { + $callback = '__return_null'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $this->assertCount( 1, $hook->callbacks[ $priority ] ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $this->assertCount( 1, $hook->callbacks[ $priority ] ); + } + + public function test_readd_filter_with_different_priority() { + $callback = '__return_null'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $this->assertCount( 1, $hook->callbacks[ $priority ] ); + + $hook->add_filter( $tag, $callback, $priority + 1, $accepted_args ); + $this->assertCount( 1, $hook->callbacks[ $priority ] ); + $this->assertCount( 1, $hook->callbacks[ $priority + 1 ] ); + } + + public function test_sort_after_add_filter() { + $a = new MockAction(); + $b = new MockAction(); + $c = new MockAction(); + $hook = new WP_Hook(); + $tag = rand_str(); + + $hook->add_filter( $tag, array( $a, 'action' ), 10, 1 ); + $hook->add_filter( $tag, array( $b, 'action' ), 5, 1 ); + $hook->add_filter( $tag, array( $c, 'action' ), 8, 1 ); + + $this->assertEquals( array( 5, 8, 10 ), array_keys( $hook->callbacks ) ); + } + + public function test_remove_and_add() { + $this->hook = new Wp_Hook(); + + $this->hook->add_filter( 'remove_and_add', '__return_empty_string', 10, 0 ); + + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add2' ), 11, 1 ); + + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add4' ), 12, 1 ); + + $value = $this->hook->apply_filters( '', array() ); + + $this->assertSame( '24', $value ); + } + + public function test_remove_and_add_last_filter() { + $this->hook = new Wp_Hook(); + + $this->hook->add_filter( 'remove_and_add', '__return_empty_string', 10, 0 ); + + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add1' ), 11, 1 ); + + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add2' ), 12, 1 ); + + $value = $this->hook->apply_filters( '', array() ); + + $this->assertSame( '12', $value ); + } + + public function test_remove_and_recurse_and_add() { + $this->hook = new Wp_Hook(); + + $this->hook->add_filter( 'remove_and_add', '__return_empty_string', 10, 0 ); + + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add1' ), 11, 1 ); + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_recurse_and_add2' ), 11, 1 ); + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add3' ), 11, 1 ); + + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add4' ), 12, 1 ); + + $value = $this->hook->apply_filters( '', array() ); + + $this->assertSame( '1-134-234', $value ); + } + + public function _filter_remove_and_add1( $string ) { + return $string . '1'; + } + + public function _filter_remove_and_add2( $string ) { + $this->hook->remove_filter( 'remove_and_add', array( $this, '_filter_remove_and_add2' ), 11 ); + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_add2' ), 11, 1 ); + + return $string . '2'; + } + + public function _filter_remove_and_recurse_and_add2( $string ) { + $this->hook->remove_filter( 'remove_and_add', array( $this, '_filter_remove_and_recurse_and_add2' ), 11 ); + + $string .= '-' . $this->hook->apply_filters( '', array() ) . '-'; + + $this->hook->add_filter( 'remove_and_add', array( $this, '_filter_remove_and_recurse_and_add2' ), 11, 1 ); + + return $string . '2'; + } + + public function _filter_remove_and_add3( $string ) { + return $string . '3'; + } + + public function _filter_remove_and_add4( $string ) { + return $string . '4'; + } + + public function test_remove_and_add_action() { + $this->hook = new Wp_Hook(); + $this->action_output = ''; + + $this->hook->add_filter( 'remove_and_add_action', '__return_empty_string', 10, 0 ); + + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add2' ), 11, 0 ); + + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add4' ), 12, 0 ); + + $this->hook->do_action( array() ); + + $this->assertSame( '24', $this->action_output ); + } + + public function test_remove_and_add_last_action() { + $this->hook = new Wp_Hook(); + $this->action_output = ''; + + $this->hook->add_filter( 'remove_and_add_action', '__return_empty_string', 10, 0 ); + + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add1' ), 11, 0 ); + + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add2' ), 12, 0 ); + + $this->hook->do_action( array() ); + + $this->assertSame( '12', $this->action_output ); + } + + public function test_remove_and_recurse_and_add_action() { + $this->hook = new Wp_Hook(); + $this->action_output = ''; + + $this->hook->add_filter( 'remove_and_add_action', '__return_empty_string', 10, 0 ); + + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add1' ), 11, 0 ); + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_recurse_and_add2' ), 11, 0 ); + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add3' ), 11, 0 ); + + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add4' ), 12, 0 ); + + $this->hook->do_action( array() ); + + $this->assertSame( '1-134-234', $this->action_output ); + } + + public function _action_remove_and_add1() { + $this->action_output .= 1; + } + + public function _action_remove_and_add2() { + $this->hook->remove_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add2' ), 11 ); + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_add2' ), 11, 0 ); + + $this->action_output .= '2'; + } + + public function _action_remove_and_recurse_and_add2() { + $this->hook->remove_filter( 'remove_and_add_action', array( $this, '_action_remove_and_recurse_and_add2' ), 11 ); + + $this->action_output .= '-'; + $this->hook->do_action( array() ); + $this->action_output .= '-'; + + $this->hook->add_filter( 'remove_and_add_action', array( $this, '_action_remove_and_recurse_and_add2' ), 11, 0 ); + + $this->action_output .= '2'; + } + + public function _action_remove_and_add3() { + $this->action_output .= '3'; + } + + public function _action_remove_and_add4() { + $this->action_output .= '4'; + } +} diff --git a/tests/phpunit/tests/hooks/apply_filters.php b/tests/phpunit/tests/hooks/apply_filters.php new file mode 100755 index 0000000000..048a00a78f --- /dev/null +++ b/tests/phpunit/tests/hooks/apply_filters.php @@ -0,0 +1,45 @@ +add_filter( $tag, $callback, $priority, $accepted_args ); + + $returned = $hook->apply_filters( $arg, array( $arg ) ); + + $this->assertEquals( $returned, $arg ); + $this->assertEquals( 1, $a->get_call_count() ); + } + + public function test_apply_filters_with_multiple_calls() { + $a = new MockAction(); + $callback = array( $a, 'filter' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + $arg = rand_str(); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + + $returned_one = $hook->apply_filters( $arg, array( $arg ) ); + $returned_two = $hook->apply_filters( $returned_one, array( $returned_one ) ); + + $this->assertEquals( $returned_two, $arg ); + $this->assertEquals( 2, $a->get_call_count() ); + } + +} diff --git a/tests/phpunit/tests/hooks/do_action.php b/tests/phpunit/tests/hooks/do_action.php new file mode 100755 index 0000000000..b810d4690b --- /dev/null +++ b/tests/phpunit/tests/hooks/do_action.php @@ -0,0 +1,172 @@ +events = array(); + } + + public function test_do_action_with_callback() { + $a = new MockAction(); + $callback = array( $a, 'action' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + $arg = rand_str(); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->do_action( array( $arg ) ); + + $this->assertEquals( 1, $a->get_call_count() ); + } + + public function test_do_action_with_multiple_calls() { + $a = new MockAction(); + $callback = array( $a, 'filter' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + $arg = rand_str(); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->do_action( array( $arg ) ); + $hook->do_action( array( $arg ) ); + + $this->assertEquals( 2, $a->get_call_count() ); + } + + public function test_do_action_with_multiple_callbacks_on_same_priority() { + $a = new MockAction(); + $b = new MockAction(); + $callback_one = array( $a, 'filter' ); + $callback_two = array( $b, 'filter' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + $arg = rand_str(); + + $hook->add_filter( $tag, $callback_one, $priority, $accepted_args ); + $hook->add_filter( $tag, $callback_two, $priority, $accepted_args ); + $hook->do_action( array( $arg ) ); + + $this->assertEquals( 1, $a->get_call_count() ); + $this->assertEquals( 1, $a->get_call_count() ); + } + + public function test_do_action_with_multiple_callbacks_on_different_priorities() { + $a = new MockAction(); + $b = new MockAction(); + $callback_one = array( $a, 'filter' ); + $callback_two = array( $b, 'filter' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + $arg = rand_str(); + + $hook->add_filter( $tag, $callback_one, $priority, $accepted_args ); + $hook->add_filter( $tag, $callback_two, $priority, $accepted_args ); + $hook->do_action( array( $arg ) ); + + $this->assertEquals( 1, $a->get_call_count() ); + $this->assertEquals( 1, $a->get_call_count() ); + } + + public function test_do_action_with_no_accepted_args() { + $callback = array( $this, '_action_callback' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = 0; + $arg = rand_str(); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->do_action( array( $arg ) ); + + $this->assertEmpty( $this->events[0]['args'] ); + } + + public function test_do_action_with_one_accepted_arg() { + $callback = array( $this, '_action_callback' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = 1; + $arg = rand_str(); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->do_action( array( $arg ) ); + + $this->assertCount( 1, $this->events[0]['args'] ); + } + + public function test_do_action_with_more_accepted_args() { + $callback = array( $this, '_action_callback' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = 1000; + $arg = rand_str(); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->do_action( array( $arg ) ); + + $this->assertCount( 1, $this->events[0]['args'] ); + } + + public function test_do_action_doesnt_change_value() { + $this->hook = new WP_Hook(); + $this->action_output = ''; + + $this->hook->add_filter( 'do_action_doesnt_change_value', array( $this, '_filter_do_action_doesnt_change_value1' ), 10, 1 ); + $this->hook->add_filter( 'do_action_doesnt_change_value', array( $this, '_filter_do_action_doesnt_change_value2' ), 10, 1 ); + $this->hook->add_filter( 'do_action_doesnt_change_value', array( $this, '_filter_do_action_doesnt_change_value3' ), 11, 1 ); + + $this->hook->do_action( array( 'a' ) ); + + $this->assertSame( 'a1-b1b3-a2a3', $this->action_output ); + } + + public function _filter_do_action_doesnt_change_value1( $value ) { + $this->action_output .= $value . 1; + return 'x1'; + } + public function _filter_do_action_doesnt_change_value2( $value ) { + $this->hook->remove_filter( 'do_action_doesnt_change_value', array( $this, '_filter_do_action_doesnt_change_value2' ), 10 ); + + $this->action_output .= '-'; + $this->hook->do_action( array( 'b' ) ); + $this->action_output .= '-'; + + $this->hook->add_filter( 'do_action_doesnt_change_value', array( $this, '_filter_do_action_doesnt_change_value2' ), 10, 1 ); + + $this->action_output .= $value . 2; + + return 'x2'; + } + + public function _filter_do_action_doesnt_change_value3( $value ) { + $this->action_output .= $value . 3; + return 'x3'; + } + + /** + * Use this rather than MockAction so we can test callbacks with no args + */ + public function _action_callback() { + $args = func_get_args(); + $this->events[] = array('action' => __FUNCTION__, 'args'=>$args); + } +} diff --git a/tests/phpunit/tests/hooks/do_all_hook.php b/tests/phpunit/tests/hooks/do_all_hook.php new file mode 100755 index 0000000000..a1e3586e84 --- /dev/null +++ b/tests/phpunit/tests/hooks/do_all_hook.php @@ -0,0 +1,26 @@ +add_filter( $tag, $callback, $priority, $accepted_args ); + $args = array( $arg ); + $hook->do_all_hook( $args ); + $hook->do_all_hook( $args ); + + $this->assertEquals( 2, $a->get_call_count() ); + } +} diff --git a/tests/phpunit/tests/hooks/has_filter.php b/tests/phpunit/tests/hooks/has_filter.php new file mode 100755 index 0000000000..0e65b4244c --- /dev/null +++ b/tests/phpunit/tests/hooks/has_filter.php @@ -0,0 +1,83 @@ +add_filter( $tag, $callback, $priority, $accepted_args ); + + $this->assertEquals( $priority, $hook->has_filter( $tag, $callback ) ); + } + + public function test_has_filter_with_object() { + $a = new MockAction(); + $callback = array( $a, 'action' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + + $this->assertEquals( $priority, $hook->has_filter( $tag, $callback ) ); + } + + public function test_has_filter_with_static_method() { + $callback = array( 'MockAction', 'action' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + + $this->assertEquals( $priority, $hook->has_filter( $tag, $callback ) ); + } + + public function test_has_filter_without_callback() { + $callback = '__return_null'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + + $this->assertTrue( $hook->has_filter() ); + } + + public function test_not_has_filter_without_callback() { + $hook = new WP_Hook(); + $this->assertFalse( $hook->has_filter() ); + } + + public function test_not_has_filter_with_callback() { + $callback = '__return_null'; + $hook = new WP_Hook(); + $tag = rand_str(); + + $this->assertFalse( $hook->has_filter( $tag, $callback ) ); + } + + public function test_has_filter_with_wrong_callback() { + $callback = '__return_null'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + + $this->assertFalse( $hook->has_filter( $tag, '__return_false' ) ); + } +} diff --git a/tests/phpunit/tests/hooks/has_filters.php b/tests/phpunit/tests/hooks/has_filters.php new file mode 100755 index 0000000000..becf4413b3 --- /dev/null +++ b/tests/phpunit/tests/hooks/has_filters.php @@ -0,0 +1,52 @@ +add_filter( $tag, $callback, $priority, $accepted_args ); + + $this->assertTrue( $hook->has_filters() ); + } + + public function test_has_filters_without_callback() { + $hook = new WP_Hook(); + $this->assertFalse( $hook->has_filters() ); + } + + public function test_not_has_filters_with_removed_callback() { + $callback = '__return_null'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->remove_filter( $tag, $callback, $priority ); + $this->assertFalse( $hook->has_filters() ); + } + + public function test_not_has_filter_with_directly_removed_callback() { + $callback = '__return_null'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $function_key = _wp_filter_build_unique_id( $tag, $callback, $priority ); + unset( $hook->callbacks[ $priority ][ $function_key ] ); + + $this->assertFalse( $hook->has_filters() ); + } +} diff --git a/tests/phpunit/tests/hooks/iterator.php b/tests/phpunit/tests/hooks/iterator.php new file mode 100755 index 0000000000..344d31d8f3 --- /dev/null +++ b/tests/phpunit/tests/hooks/iterator.php @@ -0,0 +1,32 @@ +add_filter( $tag, $callback_one, $priority, $accepted_args ); + $hook->add_filter( $tag, $callback_two, $priority + 1, $accepted_args ); + + $functions = array(); + $priorities = array(); + foreach ( $hook as $key => $callbacks ) { + $priorities[] = $key; + foreach ( $callbacks as $function_index => $the_ ) { + $functions[] = $the_['function']; + } + } + $this->assertEqualSets( array( $priority, $priority + 1 ), $priorities ); + $this->assertEqualSets( array( $callback_one, $callback_two ), $functions ); + } +} diff --git a/tests/phpunit/tests/hooks/preinit_hooks.php b/tests/phpunit/tests/hooks/preinit_hooks.php new file mode 100755 index 0000000000..e1d991c023 --- /dev/null +++ b/tests/phpunit/tests/hooks/preinit_hooks.php @@ -0,0 +1,39 @@ + array( + $priority1 => array( + 'test1' => array( + 'function' => '__return_false', + 'accepted_args' => 2, + ), + ), + ), + $tag2 => array( + $priority2 => array( + 'test1' => array( + 'function' => '__return_null', + 'accepted_args' => 1, + ), + ), + ), + ); + + $hooks = WP_Hook::build_preinitialized_hooks( $filters ); + + $this->assertEquals( $priority1, $hooks[ $tag1 ]->has_filter( $tag1, '__return_false' ) ); + $this->assertEquals( $priority2, $hooks[ $tag2 ]->has_filter( $tag2, '__return_null' ) ); + } +} diff --git a/tests/phpunit/tests/hooks/remove_all_filters.php b/tests/phpunit/tests/hooks/remove_all_filters.php new file mode 100755 index 0000000000..57f4d83875 --- /dev/null +++ b/tests/phpunit/tests/hooks/remove_all_filters.php @@ -0,0 +1,41 @@ +add_filter( $tag, $callback, $priority, $accepted_args ); + + $hook->remove_all_filters(); + + $this->assertFalse( $hook->has_filters() ); + } + + public function test_remove_all_filters_with_priority() { + $callback_one = '__return_null'; + $callback_two = '__return_false'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback_one, $priority, $accepted_args ); + $hook->add_filter( $tag, $callback_two, $priority + 1, $accepted_args ); + + $hook->remove_all_filters( $priority ); + + $this->assertFalse( $hook->has_filter( $tag, $callback_one ) ); + $this->assertTrue( $hook->has_filters() ); + $this->assertEquals( $priority + 1, $hook->has_filter( $tag, $callback_two ) ); + } +} diff --git a/tests/phpunit/tests/hooks/remove_filter.php b/tests/phpunit/tests/hooks/remove_filter.php new file mode 100755 index 0000000000..42a3f84623 --- /dev/null +++ b/tests/phpunit/tests/hooks/remove_filter.php @@ -0,0 +1,81 @@ +add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->remove_filter( $tag, $callback, $priority ); + + $this->assertFalse( isset( $hook->callbacks[ $priority ] ) ); + } + + public function test_remove_filter_with_object() { + $a = new MockAction(); + $callback = array( $a, 'action' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->remove_filter( $tag, $callback, $priority ); + + $this->assertFalse( isset( $hook->callbacks[ $priority ] ) ); + } + + public function test_remove_filter_with_static_method() { + $callback = array( 'MockAction', 'action' ); + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback, $priority, $accepted_args ); + $hook->remove_filter( $tag, $callback, $priority ); + + $this->assertFalse( isset( $hook->callbacks[ $priority ] ) ); + } + + public function test_remove_filters_with_another_at_same_priority() { + $callback_one = '__return_null'; + $callback_two = '__return_false'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback_one, $priority, $accepted_args ); + $hook->add_filter( $tag, $callback_two, $priority, $accepted_args ); + + $hook->remove_filter( $tag, $callback_one, $priority ); + + $this->assertCount( 1, $hook->callbacks[ $priority ] ); + } + + public function test_remove_filter_with_another_at_different_priority() { + $callback_one = '__return_null'; + $callback_two = '__return_false'; + $hook = new WP_Hook(); + $tag = rand_str(); + $priority = rand( 1, 100 ); + $accepted_args = rand( 1, 100 ); + + $hook->add_filter( $tag, $callback_one, $priority, $accepted_args ); + $hook->add_filter( $tag, $callback_two, $priority + 1, $accepted_args ); + + $hook->remove_filter( $tag, $callback_one, $priority ); + $this->assertFalse( isset( $hook->callbacks[ $priority ] ) ); + $this->assertCount( 1, $hook->callbacks[ $priority + 1 ] ); + } +} diff --git a/tests/phpunit/tests/post/types.php b/tests/phpunit/tests/post/types.php index 489266fc68..e7dcf84562 100644 --- a/tests/phpunit/tests/post/types.php +++ b/tests/phpunit/tests/post/types.php @@ -446,7 +446,7 @@ class Tests_Post_Types extends WP_UnitTestCase { $this->assertSame( 1, count( $wp_filter['future_foo'] ) ); $this->assertTrue( unregister_post_type( 'foo' ) ); - $this->assertSame( array(), $wp_filter['future_foo'] ); + $this->assertArrayNotHasKey( 'future_foo', $wp_filter ); } /** @@ -462,7 +462,7 @@ class Tests_Post_Types extends WP_UnitTestCase { $this->assertSame( 1, count( $wp_filter['add_meta_boxes_foo'] ) ); $this->assertTrue( unregister_post_type( 'foo' ) ); - $this->assertSame( array(), $wp_filter['add_meta_boxes_foo'] ); + $this->assertArrayNotHasKey( 'add_meta_boxes_foo', $wp_filter ); } /** diff --git a/tests/phpunit/tests/taxonomy.php b/tests/phpunit/tests/taxonomy.php index 4fa9550cf2..ca317bb395 100644 --- a/tests/phpunit/tests/taxonomy.php +++ b/tests/phpunit/tests/taxonomy.php @@ -697,7 +697,7 @@ class Tests_Taxonomy extends WP_UnitTestCase { $this->assertSame( 1, count( $wp_filter['wp_ajax_add-foo'] ) ); $this->assertTrue( unregister_taxonomy( 'foo' ) ); - $this->assertSame( array(), $wp_filter['wp_ajax_add-foo'] ); + $this->assertArrayNotHasKey( 'wp_ajax_add-foo', $wp_filter ); } /**