Make WordPress Core

Ticket #17817: 17817.8.patch

File 17817.8.patch, 25.7 KB (added by leewillis77, 10 years ago)
  • src/wp-includes/class-wp-hook.php

     
     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
     179                if ( empty($this->callbacks) ) {
     180                        return;
     181                }
     182                $nesting_level = $this->nesting_level++;
     183                $this->iterations[$nesting_level] = array_keys($this->callbacks);
     184                $num_args = count($args);
     185
     186                do {
     187                        $priority = current($this->iterations[$nesting_level]);
     188                        foreach ( $this->callbacks[$priority] as $the_ ) {
     189                                $func =& $the_['function'];
     190                                if ( $the_['accepted_args'] == 0 ) {
     191                                        $func_args = array();
     192                                } elseif ( $the_['accepted_args'] >= $num_args ) {
     193                                        $func_args = $args;
     194                                } else {
     195                                        $func_args = array_slice( $args, 0, (int) $the_['accepted_args'] );
     196                                }
     197                                call_user_func_array($func, $func_args );
     198                        }
     199                } while ( next($this->iterations[$nesting_level]) !== FALSE );
     200
     201                unset($this->iterations[$nesting_level]);
     202                $this->nesting_level--;
     203        }
     204
     205        /**
     206         * Process the functions hooked into the 'all' hook.
     207         *
     208         * @param array $args Arguments to pass to callbacks
     209         * @return void
     210         */
     211        public function do_all_hook( &$args ) {
     212                $nesting_level = $this->nesting_level++;
     213                $this->iterations[$nesting_level] = array_keys($this->callbacks);
     214
     215                do {
     216                        $priority = current($this->iterations[$nesting_level]);
     217                        foreach ( $this->callbacks[$priority] as $the_ ) {
     218                                call_user_func_array($the_['function'], $args );
     219                        }
     220                } while ( next($this->iterations[$nesting_level]) !== FALSE );
     221
     222                unset($this->iterations[$nesting_level]);
     223                $this->nesting_level--;
     224        }
     225
     226        /**
     227         * Retrieve an external iterator
     228         *
     229         * Provided for backwards compatibility with plugins that iterate over the
     230         * $wp_filter global
     231         *
     232         * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
     233         * @return Traversable An instance of an object implementing Iterator or
     234         * Traversable
     235         */
     236        public function getIterator() {
     237                return new ArrayIterator($this->callbacks);
     238        }
     239
     240        /**
     241         * Count elements of an object
     242         *
     243         * Provided for backwards compatibility with plugins that access the
     244         * $wp_filter global
     245         *
     246         * @link http://php.net/manual/en/countable.count.php
     247         * @return int The custom count as an integer.
     248         *
     249         * The return value is cast to an integer.
     250         */
     251        public function count() {
     252                return count($this->callbacks);
     253        }
     254
     255        /**
     256         * Some plugins may set up filters before WordPress has initialized.
     257         * Normalize them to WP_Hook objects.
     258         *
     259         * @param array $filters
     260         * @return WP_Hook[]
     261         */
     262        public static function build_preinitialized_hooks( $filters ) {
     263                /** @var WP_Hook[] $normalized */
     264                $normalized = array();
     265                foreach ( $filters as $tag => $callback_groups ) {
     266                        if ( is_object($callback_groups) && $callback_groups instanceof WP_Hook ) {
     267                                $normalized[$tag] = $callback_groups;
     268                                continue;
     269                        }
     270                        $hook = new WP_Hook();
     271                        foreach ( $callback_groups as $priority => $callbacks ) {
     272                                foreach ( $callbacks as $cb ) {
     273                                        $hook->add_filter( $cb['function'], $priority, $cb['accepted_args'], $tag );
     274                                }
     275                        }
     276                        $normalized[$tag] = $hook;
     277                }
     278                return $normalized;
     279        }
     280
     281}
     282
  • src/wp-includes/plugin.php

     
    2020 */
    2121
    2222// Initialize the filter globals.
    23 global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
    24 
    25 if ( ! isset( $wp_filter ) )
     23require( ABSPATH . '/wp-includes/class-wp-hook.php' );
     24/** @var WP_Hook[] $wp_filter */
     25global $wp_filter, $wp_actions, $wp_current_filter;
     26
     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
     
    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.
     
    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
     
    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 ] );
     109        global $wp_filter;
    112110
    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                 }
    122 
    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;
     
    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/**
     
    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.
     
    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
     
    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]) ) {
     
    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
     
    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.
     
    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']) ) {
     
    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/**
     
    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 ] );
     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]);
    306259                }
    307                 if ( empty( $GLOBALS['wp_filter'][ $tag ] ) ) {
    308                         $GLOBALS['wp_filter'][ $tag ] = array();
    309                 }
    310                 unset( $GLOBALS['merged_filters'][ $tag ] );
    311260        }
    312261
    313262        return $r;
     
    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;
     
    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;
     
    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}
     
    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;
     
    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}
     
    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

     
    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

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

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

     
    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}