WordPress.org

Make WordPress Core

Ticket #17817: 17817.9.patch

File 17817.9.patch, 26.8 KB (added by jbrinley, 5 years ago)
  • new file src/wp-includes/class-wp-hook.php

    diff --git src/wp-includes/class-wp-hook.php src/wp-includes/class-wp-hook.php
    new file mode 100644
    index 0000000..9f056a0
    - +  
     1<?php
     2
     3/**
     4 * Class WP_Hook
     5 */
     6class WP_Hook implements IteratorAggregate, Countable {
     7        public $callbacks = array();
     8
     9        /** @var array The priority keys of actively running iterations of a hook */
     10        private $iterations = array();
     11
     12        /** @var int How recursively has this hook been called? */
     13        private $nesting_level = 0;
     14
     15        /**
     16         * Hook a function or method to a specific filter action.
     17         *
     18         * @param callback $function_to_add The callback to be run when the filter is applied.
     19         * @param int      $priority        (optional) The order in which the functions associated with a particular action are executed. Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the action.
     20         *                                  Default 10.
     21         * @param int      $accepted_args   (optional) The number of arguments the function accepts.
     22         *                                  Default 1.
     23         * @param string   $tag             The name of the filter to hook the $function_to_add callback to.
     24         * @return void
     25         */
     26        public function add_filter( $function_to_add, $priority, $accepted_args, $tag ) {
     27                $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);
     28                $priority_existed = isset($this->callbacks[$priority]);
     29                $this->callbacks[$priority][$idx] = array( 'function' => $function_to_add, 'accepted_args' => $accepted_args );
     30                if ( !$priority_existed && count($this->callbacks) > 1 ) {
     31                        ksort($this->callbacks, SORT_NUMERIC);
     32                }
     33
     34                if ( $this->nesting_level > 0 ) {
     35                        $this->resort_active_iterations();
     36                }
     37        }
     38
     39        /**
     40         * When a hook's callbacks are changed mid-iteration, the priority
     41         * keys need to be reset, with the array pointer at the correct
     42         * location
     43         *
     44         * @return void
     45         */
     46        private function resort_active_iterations() {
     47                $new_priorities = array_keys($this->callbacks);
     48
     49                // if there are no remaining hooks, clear out all running iterations
     50                if ( empty($new_priorities) ) {
     51                        foreach ( $this->iterations as $index => $iteration ) {
     52                                $this->iterations[$index] = $new_priorities;
     53                        }
     54                        return;
     55                }
     56
     57                $min = min($new_priorities);
     58                foreach ( $this->iterations as $index => $iteration ) {
     59                        $current = current($iteration);
     60                        $this->iterations[$index] = $new_priorities;
     61
     62                        if ( $current < $min ) {
     63                                array_unshift( $this->iterations[$index], $current );
     64                                continue;
     65                        }
     66                        while ( current($this->iterations[$index]) < $current ) {
     67                                if ( next($this->iterations[$index]) === FALSE ) {
     68                                        break;
     69                                };
     70                        }
     71                }
     72        }
     73
     74        /**
     75         * @param string $function_key
     76         * @param int $priority
     77         *
     78         * @return bool Whether the callback existed before it was removed
     79         */
     80        public function remove_filter( $function_key, $priority ) {
     81                $exists = isset($this->callbacks[$priority][$function_key]);
     82                if ( $exists ) {
     83                        unset($this->callbacks[$priority][$function_key]);
     84                        if ( empty($this->callbacks[$priority]) ) {
     85                                unset($this->callbacks[$priority]);
     86                                if ( $this->nesting_level > 0 ) {
     87                                        $this->resort_active_iterations();
     88                                }
     89                        }
     90                }
     91                return $exists;
     92        }
     93
     94        /**
     95         * Check if an specific action has been registered for this hook.
     96         *
     97         * @param string $function_key The hashed index of the filter
     98         * @return mixed The priority of that hook is returned, or false if the function is not attached.
     99         */
     100        public function has_filter( $function_key ) {
     101                foreach ( $this->callbacks as $priority => &$callbacks ) {
     102                        if ( isset($callbacks[$function_key]) ) {
     103                                return $priority;
     104                        }
     105                }
     106                return false;
     107        }
     108
     109        /**
     110         * Check if any callbacks have been registered for this hook
     111         *
     112         * @return bool
     113         */
     114        public function has_filters() {
     115                foreach ( $this->callbacks as &$callbacks ) {
     116                        if ( !empty($callbacks) ) {
     117                                return TRUE;
     118                        }
     119                }
     120                return FALSE;
     121        }
     122
     123        /**
     124         * Remove all of the callbacks from the filter.
     125         *
     126         * @param int|bool $priority The priority number to remove.
     127         * @return void
     128         */
     129        public function remove_all_filters( $priority = false ) {
     130                if ( empty($this->callbacks) ) {
     131                        return;
     132                }
     133                if( false !== $priority && isset($this->callbacks[$priority]) ) {
     134                        unset($this->callbacks[$priority]);
     135                } else {
     136                        $this->callbacks = array();
     137                }
     138                if ( $this->nesting_level > 0 ) {
     139                        $this->resort_active_iterations();
     140                }
     141        }
     142
     143        /**
     144         * Call the functions added to a filter hook.
     145         *
     146         * @param mixed $value The value to filter.
     147         * @param array $args Arguments to pass to callbacks
     148         *
     149         * @return mixed The filtered value after all hooked functions are applied to it
     150         */
     151        public function apply_filters( $value, &$args ) {
     152                if ( empty($this->callbacks) ) {
     153                        return $value;
     154                }
     155                $nesting_level = $this->nesting_level++;
     156                $this->iterations[$nesting_level] = array_keys($this->callbacks);
     157
     158                do {
     159                        $priority = current($this->iterations[$nesting_level]);
     160                        foreach ( $this->callbacks[$priority] as $the_ ) {
     161                                $args[0] = $value;
     162                                $value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int)$the_['accepted_args'] ) );
     163                        }
     164                } while ( next($this->iterations[$nesting_level]) !== FALSE );
     165
     166                unset($this->iterations[$nesting_level]);
     167                $this->nesting_level--;
     168                return $value;
     169        }
     170
     171        /**
     172         * Execute functions hooked on a specific action hook.
     173         *
     174         * @param mixed $args Arguments to pass to callbacks
     175         * @return void
     176         */
     177        public function do_action( &$args ) {
     178                if ( empty($this->callbacks) ) {
     179                        return;
     180                }
     181                $nesting_level = $this->nesting_level++;
     182                $this->iterations[$nesting_level] = array_keys($this->callbacks);
     183                $num_args = count($args);
     184
     185                do {
     186                        $priority = current($this->iterations[$nesting_level]);
     187                        foreach ( $this->callbacks[$priority] as $the_ ) {
     188                                $func =& $the_['function'];
     189                                // avoid the array_slice if possible
     190                                if ( $the_['accepted_args'] == 0 ) {
     191                                        call_user_func_array( $func, array() );
     192                                } elseif ( $the_['accepted_args'] >= $num_args ) {
     193                                        call_user_func_array( $func, $args );
     194                                } else {
     195                                        call_user_func_array( $func, array_slice( $args, 0, (int) $the_['accepted_args'] ) );
     196                                }
     197                        }
     198                } while ( next($this->iterations[$nesting_level]) !== FALSE );
     199
     200                unset($this->iterations[$nesting_level]);
     201                $this->nesting_level--;
     202        }
     203
     204        /**
     205         * Process the functions hooked into the 'all' hook.
     206         *
     207         * @param array $args Arguments to pass to callbacks
     208         * @return void
     209         */
     210        public function do_all_hook( &$args ) {
     211                $nesting_level = $this->nesting_level++;
     212                $this->iterations[$nesting_level] = array_keys($this->callbacks);
     213
     214                do {
     215                        $priority = current($this->iterations[$nesting_level]);
     216                        foreach ( $this->callbacks[$priority] as $the_ ) {
     217                                call_user_func_array($the_['function'], $args );
     218                        }
     219                } while ( next($this->iterations[$nesting_level]) !== FALSE );
     220
     221                unset($this->iterations[$nesting_level]);
     222                $this->nesting_level--;
     223        }
     224
     225        /**
     226         * Retrieve an external iterator
     227         *
     228         * Provided for backwards compatibility with plugins that iterate over the
     229         * $wp_filter global
     230         *
     231         * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
     232         * @return Traversable An instance of an object implementing Iterator or
     233         * Traversable
     234         */
     235        public function getIterator() {
     236                return new ArrayIterator($this->callbacks);
     237        }
     238
     239        /**
     240         * Count elements of an object
     241         *
     242         * Provided for backwards compatibility with plugins that access the
     243         * $wp_filter global
     244         *
     245         * @link http://php.net/manual/en/countable.count.php
     246         * @return int The custom count as an integer.
     247         *
     248         * The return value is cast to an integer.
     249         */
     250        public function count() {
     251                return count($this->callbacks);
     252        }
     253
     254        /**
     255         * Some plugins may set up filters before WordPress has initialized.
     256         * Normalize them to WP_Hook objects.
     257         *
     258         * @param array $filters
     259         * @return WP_Hook[]
     260         */
     261        public static function build_preinitialized_hooks( $filters ) {
     262                /** @var WP_Hook[] $normalized */
     263                $normalized = array();
     264                foreach ( $filters as $tag => $callback_groups ) {
     265                        if ( is_object($callback_groups) && $callback_groups instanceof WP_Hook ) {
     266                                $normalized[$tag] = $callback_groups;
     267                                continue;
     268                        }
     269                        $hook = new WP_Hook();
     270                        foreach ( $callback_groups as $priority => $callbacks ) {
     271                                foreach ( $callbacks as $cb ) {
     272                                        $hook->add_filter( $cb['function'], $priority, $cb['accepted_args'], $tag );
     273                                }
     274                        }
     275                        $normalized[$tag] = $hook;
     276                }
     277                return $normalized;
     278        }
     279
     280}
     281 
  • src/wp-includes/plugin.php

    diff --git src/wp-includes/plugin.php src/wp-includes/plugin.php
    index 3c51a9b..485b41f 100644
     
    2020 */
    2121
    2222// Initialize the filter globals.
    23 global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
     23require( ABSPATH . '/wp-includes/class-wp-hook.php' );
     24/** @var WP_Hook[] $wp_filter */
     25global $wp_filter, $wp_actions, $wp_current_filter;
    2426
    25 if ( ! isset( $wp_filter ) )
     27if ( !empty( $wp_filter ) ) {
     28        $wp_filter = WP_Hook::build_preinitialized_hooks( $wp_filter );
     29} else {
    2630        $wp_filter = array();
     31}
    2732
    2833if ( ! isset( $wp_actions ) )
    2934        $wp_actions = array();
    3035
    31 if ( ! isset( $merged_filters ) )
    32         $merged_filters = array();
    33 
    3436if ( ! isset( $wp_current_filter ) )
    3537        $wp_current_filter = array();
    3638
    if ( ! isset( $wp_current_filter ) ) 
    6668 * @since 0.71
    6769 *
    6870 * @global array $wp_filter      A multidimensional array of all hooks and the callbacks hooked to them.
    69  * @global array $merged_filters Tracks the tags that need to be merged for later. If the hook is added,
    70  *                               it doesn't need to run through that process.
    7171 *
    7272 * @param string   $tag             The name of the filter to hook the $function_to_add callback to.
    7373 * @param callback $function_to_add The callback to be run when the filter is applied.
    if ( ! isset( $wp_current_filter ) ) 
    8080 * @return boolean true
    8181 */
    8282function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
    83         global $wp_filter, $merged_filters;
     83        global $wp_filter;
     84        if ( !isset($wp_filter[$tag]) ) {
     85                $wp_filter[$tag] = new WP_Hook();
     86        }
     87        $wp_filter[$tag]->add_filter( $function_to_add, $priority, $accepted_args, $tag );
    8488
    85         $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);
    86         $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
    87         unset( $merged_filters[ $tag ] );
    8889        return true;
    8990}
    9091
    function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 
    105106 *                  return value.
    106107 */
    107108function has_filter($tag, $function_to_check = false) {
    108         // Don't reset the internal array pointer
    109         $wp_filter = $GLOBALS['wp_filter'];
    110 
    111         $has = ! empty( $wp_filter[ $tag ] );
    112 
    113         // Make sure at least one priority has a filter callback
    114         if ( $has ) {
    115                 $exists = false;
    116                 foreach ( $wp_filter[ $tag ] as $callbacks ) {
    117                         if ( ! empty( $callbacks ) ) {
    118                                 $exists = true;
    119                                 break;
    120                         }
    121                 }
     109        global $wp_filter;
    122110
    123                 if ( ! $exists ) {
    124                         $has = false;
    125                 }
    126         }
     111        $has = isset($wp_filter[$tag]) && $wp_filter[$tag]->has_filters();
    127112
    128113        if ( false === $function_to_check || false == $has )
    129114                return $has;
    function has_filter($tag, $function_to_check = false) { 
    131116        if ( !$idx = _wp_filter_build_unique_id($tag, $function_to_check, false) )
    132117                return false;
    133118
    134         foreach ( (array) array_keys($wp_filter[$tag]) as $priority ) {
    135                 if ( isset($wp_filter[$tag][$priority][$idx]) )
    136                         return $priority;
    137         }
    138 
    139         return false;
     119        return $wp_filter[$tag]->has_filter($idx);
    140120}
    141121
    142122/**
    function has_filter($tag, $function_to_check = false) { 
    167147 * @since 0.71
    168148 *
    169149 * @global array $wp_filter         Stores all of the filters.
    170  * @global array $merged_filters    Merges the filter hooks using this function.
    171150 * @global array $wp_current_filter Stores the list of current filters with the current one last.
    172151 *
    173152 * @param string $tag   The name of the filter hook.
    function has_filter($tag, $function_to_check = false) { 
    176155 * @return mixed The filtered value after all hooked functions are applied to it.
    177156 */
    178157function apply_filters( $tag, $value ) {
    179         global $wp_filter, $merged_filters, $wp_current_filter;
     158        global $wp_filter, $wp_current_filter;
    180159
    181160        $args = array();
    182161
    function apply_filters( $tag, $value ) { 
    185164                $wp_current_filter[] = $tag;
    186165                $args = func_get_args();
    187166                _wp_call_all_hook($args);
     167                array_shift($args);
    188168        }
    189169
    190170        if ( !isset($wp_filter[$tag]) ) {
    function apply_filters( $tag, $value ) { 
    196176        if ( !isset($wp_filter['all']) )
    197177                $wp_current_filter[] = $tag;
    198178
    199         // Sort.
    200         if ( !isset( $merged_filters[ $tag ] ) ) {
    201                 ksort($wp_filter[$tag]);
    202                 $merged_filters[ $tag ] = true;
    203         }
    204 
    205         reset( $wp_filter[ $tag ] );
    206 
    207         if ( empty($args) )
     179        if ( empty($args) ) {
    208180                $args = func_get_args();
     181                array_shift($args);
     182        }
    209183
    210         do {
    211                 foreach( (array) current($wp_filter[$tag]) as $the_ )
    212                         if ( !is_null($the_['function']) ){
    213                                 $args[1] = $value;
    214                                 $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args']));
    215                         }
    216 
    217         } while ( next($wp_filter[$tag]) !== false );
     184        $value = $wp_filter[$tag]->apply_filters( $value, $args );
    218185
    219186        array_pop( $wp_current_filter );
    220187
    function apply_filters( $tag, $value ) { 
    230197 * @since 3.0.0
    231198 *
    232199 * @global array $wp_filter         Stores all of the filters
    233  * @global array $merged_filters    Merges the filter hooks using this function.
    234200 * @global array $wp_current_filter Stores the list of current filters with the current one last
    235201 *
    236202 * @param string $tag  The name of the filter hook.
    function apply_filters( $tag, $value ) { 
    238204 * @return mixed The filtered value after all hooked functions are applied to it.
    239205 */
    240206function apply_filters_ref_array($tag, $args) {
    241         global $wp_filter, $merged_filters, $wp_current_filter;
     207        global $wp_filter, $wp_current_filter;
    242208
    243209        // Do 'all' actions first
    244210        if ( isset($wp_filter['all']) ) {
    function apply_filters_ref_array($tag, $args) { 
    256222        if ( !isset($wp_filter['all']) )
    257223                $wp_current_filter[] = $tag;
    258224
    259         // Sort
    260         if ( !isset( $merged_filters[ $tag ] ) ) {
    261                 ksort($wp_filter[$tag]);
    262                 $merged_filters[ $tag ] = true;
    263         }
    264 
    265         reset( $wp_filter[ $tag ] );
    266 
    267         do {
    268                 foreach( (array) current($wp_filter[$tag]) as $the_ )
    269                         if ( !is_null($the_['function']) )
    270                                 $args[0] = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
    271 
    272         } while ( next($wp_filter[$tag]) !== false );
     225        $value = $wp_filter[$tag]->apply_filters( $args[0], $args );
    273226
    274227        array_pop( $wp_current_filter );
    275228
    276         return $args[0];
     229        return $value;
    277230}
    278231
    279232/**
    function apply_filters_ref_array($tag, $args) { 
    295248 * @return boolean Whether the function existed before it was removed.
    296249 */
    297250function remove_filter( $tag, $function_to_remove, $priority = 10 ) {
    298         $function_to_remove = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority );
    299 
    300         $r = isset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] );
     251        global $wp_filter;
    301252
    302         if ( true === $r ) {
    303                 unset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] );
    304                 if ( empty( $GLOBALS['wp_filter'][ $tag ][ $priority ] ) ) {
    305                         unset( $GLOBALS['wp_filter'][ $tag ][ $priority ] );
    306                 }
    307                 if ( empty( $GLOBALS['wp_filter'][ $tag ] ) ) {
    308                         $GLOBALS['wp_filter'][ $tag ] = array();
     253        $r = false;
     254        if ( isset($wp_filter[$tag]) ) {
     255                $function_to_remove = _wp_filter_build_unique_id($tag, $function_to_remove, $priority);
     256                $r = $wp_filter[$tag]->remove_filter($function_to_remove, $priority);
     257                if ( empty($wp_filter[$tag]->callbacks) ) {
     258                        unset($wp_filter[$tag]);
    309259                }
    310                 unset( $GLOBALS['merged_filters'][ $tag ] );
    311260        }
    312261
    313262        return $r;
    function remove_filter( $tag, $function_to_remove, $priority = 10 ) { 
    323272 * @return bool True when finished.
    324273 */
    325274function remove_all_filters( $tag, $priority = false ) {
    326         global $wp_filter, $merged_filters;
    327 
    328         if ( isset( $wp_filter[ $tag ]) ) {
    329                 if ( false !== $priority && isset( $wp_filter[ $tag ][ $priority ] ) ) {
    330                         $wp_filter[ $tag ][ $priority ] = array();
    331                 } else {
    332                         $wp_filter[ $tag ] = array();
    333                 }
    334         }
     275        global $wp_filter;
    335276
    336         if ( isset( $merged_filters[ $tag ] ) ) {
    337                 unset( $merged_filters[ $tag ] );
     277        if( isset($wp_filter[$tag]) ) {
     278                $wp_filter[$tag]->remove_all_filters($priority);
     279                unset($wp_filter[$tag]);
    338280        }
    339281
    340282        return true;
    function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) 
    460402 * @return null Will return null if $tag does not exist in $wp_filter array.
    461403 */
    462404function do_action($tag, $arg = '') {
    463         global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
     405        global $wp_filter, $wp_actions, $wp_current_filter;
    464406
    465407        if ( ! isset($wp_actions[$tag]) )
    466408                $wp_actions[$tag] = 1;
    function do_action($tag, $arg = '') { 
    491433        for ( $a = 2; $a < func_num_args(); $a++ )
    492434                $args[] = func_get_arg($a);
    493435
    494         // Sort
    495         if ( !isset( $merged_filters[ $tag ] ) ) {
    496                 ksort($wp_filter[$tag]);
    497                 $merged_filters[ $tag ] = true;
    498         }
    499 
    500         reset( $wp_filter[ $tag ] );
    501 
    502         do {
    503                 foreach ( (array) current($wp_filter[$tag]) as $the_ )
    504                         if ( !is_null($the_['function']) )
    505                                 call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
    506 
    507         } while ( next($wp_filter[$tag]) !== false );
     436        $wp_filter[$tag]->do_action( $args );
    508437
    509438        array_pop($wp_current_filter);
    510439}
    function did_action($tag) { 
    543472 * @return null Will return null if $tag does not exist in $wp_filter array
    544473 */
    545474function do_action_ref_array($tag, $args) {
    546         global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
     475        global $wp_filter, $wp_actions, $wp_current_filter;
    547476
    548477        if ( ! isset($wp_actions[$tag]) )
    549478                $wp_actions[$tag] = 1;
    function do_action_ref_array($tag, $args) { 
    566495        if ( !isset($wp_filter['all']) )
    567496                $wp_current_filter[] = $tag;
    568497
    569         // Sort
    570         if ( !isset( $merged_filters[ $tag ] ) ) {
    571                 ksort($wp_filter[$tag]);
    572                 $merged_filters[ $tag ] = true;
    573         }
    574 
    575         reset( $wp_filter[ $tag ] );
    576 
    577         do {
    578                 foreach( (array) current($wp_filter[$tag]) as $the_ )
    579                         if ( !is_null($the_['function']) )
    580                                 call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
    581 
    582         } while ( next($wp_filter[$tag]) !== false );
     498        $wp_filter[$tag]->do_action( $args );
    583499
    584500        array_pop($wp_current_filter);
    585501}
    function register_uninstall_hook( $file, $callback ) { 
    838754 */
    839755function _wp_call_all_hook($args) {
    840756        global $wp_filter;
    841 
    842         reset( $wp_filter['all'] );
    843         do {
    844                 foreach( (array) current($wp_filter['all']) as $the_ )
    845                         if ( !is_null($the_['function']) )
    846                                 call_user_func_array($the_['function'], $args);
    847 
    848         } while ( next($wp_filter['all']) !== false );
     757        $wp_filter['all']->do_all_hook($args);
    849758}
    850759
    851760/**
  • tests/phpunit/includes/functions.php

    diff --git tests/phpunit/includes/functions.php tests/phpunit/includes/functions.php
    index e8d30cb..64f28f7 100644
     
    22
    33// For adding hooks before loading WP
    44function tests_add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
    5         global $wp_filter, $merged_filters;
     5        global $wp_filter;
    66
    77        $idx = _test_filter_build_unique_id($tag, $function_to_add, $priority);
    88        $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
    9         unset( $merged_filters[ $tag ] );
    109        return true;
    1110}
    1211
    1312function _test_filter_build_unique_id($tag, $function, $priority) {
    14         global $wp_filter;
    15         static $filter_id_count = 0;
    16 
    1713        if ( is_string($function) )
    1814                return $function;
    1915
  • tests/phpunit/includes/testcase.php

    diff --git tests/phpunit/includes/testcase.php tests/phpunit/includes/testcase.php
    index 3e40656..c903f67 100644
    class WP_UnitTestCase extends PHPUnit_Framework_TestCase { 
    119119         * @return void
    120120         */
    121121        protected function _backup_hooks() {
    122                 $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
     122                $globals = array( 'wp_actions', 'wp_current_filter', 'wp_filter' );
    123123                foreach ( $globals as $key ) {
    124124                        self::$hooks_saved[ $key ] = $GLOBALS[ $key ];
    125125                }
    class WP_UnitTestCase extends PHPUnit_Framework_TestCase { 
    136136         * @return void
    137137         */
    138138        protected function _restore_hooks() {
    139                 $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
     139                $globals = array( 'wp_actions', 'wp_current_filter', 'wp_filter' );
    140140                foreach ( $globals as $key ) {
    141141                        if ( isset( self::$hooks_saved[ $key ] ) ) {
    142142                                $GLOBALS[ $key ] = self::$hooks_saved[ $key ];
  • tests/phpunit/tests/actions.php

    diff --git tests/phpunit/tests/actions.php tests/phpunit/tests/actions.php
    index 583c8ce..ed0df73 100644
    class Tests_Actions extends WP_UnitTestCase { 
    114114                $this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) );
    115115        }
    116116
     117        /**
     118         * Test that multiple callbacks receive the correct number of args even when the number
     119         * is less than, or greater than previous hooks.
     120         *
     121         * @see https://core.trac.wordpress.org/ticket/17817#comment:72
     122         */
     123        function test_action_args_3() {
     124                $a1 = new MockAction();
     125                $a2 = new MockAction();
     126                $a3 = new MockAction();
     127                $tag = rand_str();
     128                $val1 = rand_str();
     129                $val2 = rand_str();
     130
     131                // a1 accepts two arguments, a2 doesn't, a3 accepts two arguments
     132                add_action($tag, array(&$a1, 'action'), 10, 2);
     133                add_action($tag, array(&$a2, 'action'));
     134                add_action($tag, array(&$a3, 'action'), 10, 2);
     135                // call the action with two arguments
     136                do_action($tag, $val1, $val2);
     137
     138                $call_count = $a1->get_call_count();
     139                // a1 should be called with both args
     140                $this->assertEquals(1, $call_count);
     141                $argsvar1 = $a1->get_args();
     142                $this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar1 ) );
     143
     144                // a2 should be called with one only
     145                $this->assertEquals(1, $a2->get_call_count());
     146                $argsvar2 = $a2->get_args();
     147                $this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) );
     148
     149                // a3 should be called with both args
     150                $this->assertEquals(1, $a3->get_call_count());
     151                $argsvar3 = $a3->get_args();
     152                $this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar3 ) );
     153        }
     154
    117155        function test_action_priority() {
    118156                $a = new MockAction();
    119157                $tag = rand_str();
    class Tests_Actions extends WP_UnitTestCase { 
    258296        }
    259297
    260298        /**
     299         * @ticket 17817
     300         */
     301        function test_action_recursion() {
     302                $tag = rand_str();
     303                $a = new MockAction();
     304                $b = new MockAction();
     305
     306                add_action( $tag, array($a, 'action'), 11, 1 );
     307                add_action( $tag, array($b, 'action'), 13, 1 );
     308                add_action( $tag, array($this, 'action_that_causes_recursion'), 12, 1 );
     309                do_action( $tag, $tag );
     310
     311                $this->assertEquals( 2, $a->get_call_count(), 'recursive actions should call all callbacks with earlier priority' );
     312                $this->assertEquals( 2, $b->get_call_count(), 'recursive actions should call callbacks with later priority' );
     313        }
     314
     315        function action_that_causes_recursion( $tag ) {
     316                static $recursing = FALSE;
     317                if ( !$recursing ) {
     318                        $recursing = TRUE;
     319                        do_action( $tag, $tag );
     320                }
     321                $recursing = FALSE;
     322        }
     323
     324        /**
     325         * @ticket 9968
     326         */
     327        function test_action_callback_manipulation_while_running() {
     328                $tag = rand_str();
     329                $a = new MockAction();
     330                $b = new MockAction();
     331                $c = new MockAction();
     332                $d = new MockAction();
     333                $e = new MockAction();
     334
     335                add_action( $tag, array($a, 'action'), 11, 2 );
     336                add_action( $tag, array($this, 'action_that_manipulates_a_running_hook'), 12, 2 );
     337                add_action( $tag, array($b, 'action'), 12, 2 );
     338
     339                do_action( $tag, $tag, array($a,$b,$c,$d,$e) );
     340                do_action( $tag, $tag, array($a,$b,$c,$d,$e) );
     341
     342                $this->assertEquals( 2, $a->get_call_count(), 'callbacks should run unless otherwise instructed' );
     343                $this->assertEquals( 1, $b->get_call_count(), 'callback removed by same priority callback should still get called' );
     344                $this->assertEquals( 1, $c->get_call_count(), 'callback added by same priority callback should not get called' );
     345                $this->assertEquals( 2, $d->get_call_count(), 'callback added by earlier priority callback should get called' );
     346                $this->assertEquals( 1, $e->get_call_count(), 'callback added by later priority callback should not get called' );
     347        }
     348
     349        function action_that_manipulates_a_running_hook( $tag, $mocks ) {
     350                remove_action( $tag, array($mocks[1], 'action'), 12, 2 );
     351                add_action( $tag, array($mocks[2], 'action' ), 12, 2 );
     352                add_action( $tag, array($mocks[3], 'action' ), 13, 2 );
     353                add_action( $tag, array($mocks[4], 'action' ), 10, 2 );
     354        }
     355
     356        /**
     357         * @ticket 17817
     358         *
     359         * This specificaly addresses the concern raised at
     360         * https://core.trac.wordpress.org/ticket/17817#comment:52
     361         */
     362        function test_remove_anonymous_callback() {
     363                $tag = rand_str();
     364                $a = new MockAction();
     365                add_action( $tag, array( $a, 'action' ), 12, 1 );
     366                $this->assertTrue( has_action( $tag ) );
     367
     368                $hook = $GLOBALS['wp_filter'][ $tag ];
     369
     370                // From http://wordpress.stackexchange.com/a/57088/6445
     371                foreach ( $hook as $priority => $filter ) {
     372                        foreach ( $filter as $identifier => $function ) {
     373                                if ( is_array( $function)
     374                                        && is_a( $function['function'][0], 'MockAction' )
     375                                        && 'action' === $function['function'][1]
     376                                ) {
     377                                        remove_filter(
     378                                                $tag,
     379                                                array ( $function['function'][0], 'action' ),
     380                                                $priority
     381                                        );
     382                                }
     383                        }
     384                }
     385
     386                $this->assertFalse( has_action( $tag ) );
     387        }
     388
     389        /**
    261390         * Make sure current_action() behaves as current_filter()
    262391         *
    263392         * @ticket 14994
  • tests/phpunit/tests/filters.php

    diff --git tests/phpunit/tests/filters.php tests/phpunit/tests/filters.php
    index b4ee12f..34fdf56 100644
    class Tests_Filters extends WP_UnitTestCase { 
    293293                remove_all_filters( $tag, 12 );
    294294                $this->assertFalse( has_filter( $tag ) );
    295295        }
    296 
    297         /**
    298          * @ticket 29070
    299          */
    300          function test_has_filter_doesnt_reset_wp_filter() {
    301                 add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 1 );
    302                 add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 2 );
    303                 add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 3 );
    304                 add_action( 'action_test_has_filter_doesnt_reset_wp_filter', array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ), 4 );
    305 
    306                 do_action( 'action_test_has_filter_doesnt_reset_wp_filter' );
    307          }
    308          function _action_test_has_filter_doesnt_reset_wp_filter() {
    309                 global $wp_filter;
    310 
    311                 has_action( 'action_test_has_filter_doesnt_reset_wp_filter', '_function_that_doesnt_exist' );
    312 
    313                 $filters = current( $wp_filter['action_test_has_filter_doesnt_reset_wp_filter'] );
    314                 $the_ = current( $filters );
    315                 $this->assertEquals( $the_['function'], array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ) );
    316          }
    317296}