Make WordPress Core

Ticket #17817: 17817.6.patch

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

    diff --git src/wp-includes/plugin.php src/wp-includes/plugin.php
    index d616a2a..f94730b 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 1e8ea11..bac2538 100644
    class WP_UnitTestCase extends PHPUnit_Framework_TestCase { 
    7979         * @return void
    8080         */
    8181        protected function _backup_hooks() {
    82                 $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
     82                $globals = array( 'wp_actions', 'wp_current_filter', 'wp_filter' );
    8383                foreach ( $globals as $key ) {
    8484                        self::$hooks_saved[ $key ] = $GLOBALS[ $key ];
    8585                }
    class WP_UnitTestCase extends PHPUnit_Framework_TestCase { 
    9696         * @return void
    9797         */
    9898        protected function _restore_hooks() {
    99                 $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
     99                $globals = array( 'wp_actions', 'wp_current_filter', 'wp_filter' );
    100100                foreach ( $globals as $key ) {
    101101                        if ( isset( self::$hooks_saved[ $key ] ) ) {
    102102                                $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..fc1e89c 100644
    class Tests_Actions extends WP_UnitTestCase { 
    258258        }
    259259
    260260        /**
     261         * @ticket 17817
     262         */
     263        function test_action_recursion() {
     264                $tag = rand_str();
     265                $a = new MockAction();
     266                $b = new MockAction();
     267
     268                add_action( $tag, array($a, 'action'), 11, 1 );
     269                add_action( $tag, array($b, 'action'), 13, 1 );
     270                add_action( $tag, array($this, 'action_that_causes_recursion'), 12, 1 );
     271                do_action( $tag, $tag );
     272
     273                $this->assertEquals( 2, $a->get_call_count(), 'recursive actions should call all callbacks with earlier priority' );
     274                $this->assertEquals( 2, $b->get_call_count(), 'recursive actions should call callbacks with later priority' );
     275        }
     276
     277        function action_that_causes_recursion( $tag ) {
     278                static $recursing = FALSE;
     279                if ( !$recursing ) {
     280                        $recursing = TRUE;
     281                        do_action( $tag, $tag );
     282                }
     283                $recursing = FALSE;
     284        }
     285
     286        /**
     287         * @ticket 9968
     288         */
     289        function test_action_callback_manipulation_while_running() {
     290                $tag = rand_str();
     291                $a = new MockAction();
     292                $b = new MockAction();
     293                $c = new MockAction();
     294                $d = new MockAction();
     295                $e = new MockAction();
     296
     297                add_action( $tag, array($a, 'action'), 11, 2 );
     298                add_action( $tag, array($this, 'action_that_manipulates_a_running_hook'), 12, 2 );
     299                add_action( $tag, array($b, 'action'), 12, 2 );
     300
     301                do_action( $tag, $tag, array($a,$b,$c,$d,$e) );
     302                do_action( $tag, $tag, array($a,$b,$c,$d,$e) );
     303
     304                $this->assertEquals( 2, $a->get_call_count(), 'callbacks should run unless otherwise instructed' );
     305                $this->assertEquals( 1, $b->get_call_count(), 'callback removed by same priority callback should still get called' );
     306                $this->assertEquals( 1, $c->get_call_count(), 'callback added by same priority callback should not get called' );
     307                $this->assertEquals( 2, $d->get_call_count(), 'callback added by earlier priority callback should get called' );
     308                $this->assertEquals( 1, $e->get_call_count(), 'callback added by later priority callback should not get called' );
     309        }
     310
     311        function action_that_manipulates_a_running_hook( $tag, $mocks ) {
     312                remove_action( $tag, array($mocks[1], 'action'), 12, 2 );
     313                add_action( $tag, array($mocks[2], 'action' ), 12, 2 );
     314                add_action( $tag, array($mocks[3], 'action' ), 13, 2 );
     315                add_action( $tag, array($mocks[4], 'action' ), 10, 2 );
     316        }
     317
     318        /**
     319         * @ticket 17817
     320         *
     321         * This specificaly addresses the concern raised at
     322         * https://core.trac.wordpress.org/ticket/17817#comment:52
     323         */
     324        function test_remove_anonymous_callback() {
     325                $tag = rand_str();
     326                $a = new MockAction();
     327                add_action( $tag, array( $a, 'action' ), 12, 1 );
     328                $this->assertTrue( has_action( $tag ) );
     329
     330                $hook = $GLOBALS['wp_filter'][ $tag ];
     331
     332                // From http://wordpress.stackexchange.com/a/57088/6445
     333                foreach ( $hook as $priority => $filter ) {
     334                        foreach ( $filter as $identifier => $function ) {
     335                                if ( is_array( $function)
     336                                        && is_a( $function['function'][0], 'MockAction' )
     337                                        && 'action' === $function['function'][1]
     338                                ) {
     339                                        remove_filter(
     340                                                $tag,
     341                                                array ( $function['function'][0], 'action' ),
     342                                                $priority
     343                                        );
     344                                }
     345                        }
     346                }
     347
     348                $this->assertFalse( has_action( $tag ) );
     349        }
     350
     351        /**
    261352         * Make sure current_action() behaves as current_filter()
    262353         *
    263354         * @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}