Make WordPress Core

source: tags/5.4/src/wp-includes/class-wp-hook.php

Last change on this file was 47219, checked in by SergeyBiryukov, 4 years ago

Coding Standards: Use Yoda conditions where appropriate.

See #49222.

  • Property svn:eol-style set to native
File size: 13.7 KB
Line 
1<?php
2/**
3 * Plugin API: WP_Hook class
4 *
5 * @package WordPress
6 * @subpackage Plugin
7 * @since 4.7.0
8 */
9
10/**
11 * Core class used to implement action and filter hook functionality.
12 *
13 * @since 4.7.0
14 *
15 * @see Iterator
16 * @see ArrayAccess
17 */
18final class WP_Hook implements Iterator, ArrayAccess {
19
20        /**
21         * Hook callbacks.
22         *
23         * @since 4.7.0
24         * @var array
25         */
26        public $callbacks = array();
27
28        /**
29         * The priority keys of actively running iterations of a hook.
30         *
31         * @since 4.7.0
32         * @var array
33         */
34        private $iterations = array();
35
36        /**
37         * The current priority of actively running iterations of a hook.
38         *
39         * @since 4.7.0
40         * @var array
41         */
42        private $current_priority = array();
43
44        /**
45         * Number of levels this hook can be recursively called.
46         *
47         * @since 4.7.0
48         * @var int
49         */
50        private $nesting_level = 0;
51
52        /**
53         * Flag for if we're current doing an action, rather than a filter.
54         *
55         * @since 4.7.0
56         * @var bool
57         */
58        private $doing_action = false;
59
60        /**
61         * Hooks a function or method to a specific filter action.
62         *
63         * @since 4.7.0
64         *
65         * @param string   $tag             The name of the filter to hook the $function_to_add callback to.
66         * @param callable $function_to_add The callback to be run when the filter is applied.
67         * @param int      $priority        The order in which the functions associated with a particular action
68         *                                  are executed. Lower numbers correspond with earlier execution,
69         *                                  and functions with the same priority are executed in the order
70         *                                  in which they were added to the action.
71         * @param int      $accepted_args   The number of arguments the function accepts.
72         */
73        public function add_filter( $tag, $function_to_add, $priority, $accepted_args ) {
74                $idx = _wp_filter_build_unique_id( $tag, $function_to_add, $priority );
75
76                $priority_existed = isset( $this->callbacks[ $priority ] );
77
78                $this->callbacks[ $priority ][ $idx ] = array(
79                        'function'      => $function_to_add,
80                        'accepted_args' => $accepted_args,
81                );
82
83                // If we're adding a new priority to the list, put them back in sorted order.
84                if ( ! $priority_existed && count( $this->callbacks ) > 1 ) {
85                        ksort( $this->callbacks, SORT_NUMERIC );
86                }
87
88                if ( $this->nesting_level > 0 ) {
89                        $this->resort_active_iterations( $priority, $priority_existed );
90                }
91        }
92
93        /**
94         * Handles resetting callback priority keys mid-iteration.
95         *
96         * @since 4.7.0
97         *
98         * @param bool|int $new_priority     Optional. The priority of the new filter being added. Default false,
99         *                                   for no priority being added.
100         * @param bool     $priority_existed Optional. Flag for whether the priority already existed before the new
101         *                                   filter was added. Default false.
102         */
103        private function resort_active_iterations( $new_priority = false, $priority_existed = false ) {
104                $new_priorities = array_keys( $this->callbacks );
105
106                // If there are no remaining hooks, clear out all running iterations.
107                if ( ! $new_priorities ) {
108                        foreach ( $this->iterations as $index => $iteration ) {
109                                $this->iterations[ $index ] = $new_priorities;
110                        }
111                        return;
112                }
113
114                $min = min( $new_priorities );
115                foreach ( $this->iterations as $index => &$iteration ) {
116                        $current = current( $iteration );
117                        // If we're already at the end of this iteration, just leave the array pointer where it is.
118                        if ( false === $current ) {
119                                continue;
120                        }
121
122                        $iteration = $new_priorities;
123
124                        if ( $current < $min ) {
125                                array_unshift( $iteration, $current );
126                                continue;
127                        }
128
129                        while ( current( $iteration ) < $current ) {
130                                if ( false === next( $iteration ) ) {
131                                        break;
132                                }
133                        }
134
135                        // If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority...
136                        if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) {
137                                /*
138                                 * ...and the new priority is the same as what $this->iterations thinks is the previous
139                                 * priority, we need to move back to it.
140                                 */
141
142                                if ( false === current( $iteration ) ) {
143                                        // If we've already moved off the end of the array, go back to the last element.
144                                        $prev = end( $iteration );
145                                } else {
146                                        // Otherwise, just go back to the previous element.
147                                        $prev = prev( $iteration );
148                                }
149                                if ( false === $prev ) {
150                                        // Start of the array. Reset, and go about our day.
151                                        reset( $iteration );
152                                } elseif ( $new_priority !== $prev ) {
153                                        // Previous wasn't the same. Move forward again.
154                                        next( $iteration );
155                                }
156                        }
157                }
158                unset( $iteration );
159        }
160
161        /**
162         * Unhooks a function or method from a specific filter action.
163         *
164         * @since 4.7.0
165         *
166         * @param string   $tag                The filter hook to which the function to be removed is hooked.
167         * @param callable $function_to_remove The callback to be removed from running when the filter is applied.
168         * @param int      $priority           The exact priority used when adding the original filter callback.
169         * @return bool Whether the callback existed before it was removed.
170         */
171        public function remove_filter( $tag, $function_to_remove, $priority ) {
172                $function_key = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority );
173
174                $exists = isset( $this->callbacks[ $priority ][ $function_key ] );
175                if ( $exists ) {
176                        unset( $this->callbacks[ $priority ][ $function_key ] );
177                        if ( ! $this->callbacks[ $priority ] ) {
178                                unset( $this->callbacks[ $priority ] );
179                                if ( $this->nesting_level > 0 ) {
180                                        $this->resort_active_iterations();
181                                }
182                        }
183                }
184                return $exists;
185        }
186
187        /**
188         * Checks if a specific action has been registered for this hook.
189         *
190         * @since 4.7.0
191         *
192         * @param string        $tag               Optional. The name of the filter hook. Default empty.
193         * @param callable|bool $function_to_check Optional. The callback to check for. Default false.
194         * @return bool|int The priority of that hook is returned, or false if the function is not attached.
195         */
196        public function has_filter( $tag = '', $function_to_check = false ) {
197                if ( false === $function_to_check ) {
198                        return $this->has_filters();
199                }
200
201                $function_key = _wp_filter_build_unique_id( $tag, $function_to_check, false );
202                if ( ! $function_key ) {
203                        return false;
204                }
205
206                foreach ( $this->callbacks as $priority => $callbacks ) {
207                        if ( isset( $callbacks[ $function_key ] ) ) {
208                                return $priority;
209                        }
210                }
211
212                return false;
213        }
214
215        /**
216         * Checks if any callbacks have been registered for this hook.
217         *
218         * @since 4.7.0
219         *
220         * @return bool True if callbacks have been registered for the current hook, otherwise false.
221         */
222        public function has_filters() {
223                foreach ( $this->callbacks as $callbacks ) {
224                        if ( $callbacks ) {
225                                return true;
226                        }
227                }
228                return false;
229        }
230
231        /**
232         * Removes all callbacks from the current filter.
233         *
234         * @since 4.7.0
235         *
236         * @param int|bool $priority Optional. The priority number to remove. Default false.
237         */
238        public function remove_all_filters( $priority = false ) {
239                if ( ! $this->callbacks ) {
240                        return;
241                }
242
243                if ( false === $priority ) {
244                        $this->callbacks = array();
245                } elseif ( isset( $this->callbacks[ $priority ] ) ) {
246                        unset( $this->callbacks[ $priority ] );
247                }
248
249                if ( $this->nesting_level > 0 ) {
250                        $this->resort_active_iterations();
251                }
252        }
253
254        /**
255         * Calls the callback functions that have been added to a filter hook.
256         *
257         * @since 4.7.0
258         *
259         * @param mixed $value The value to filter.
260         * @param array $args  Additional parameters to pass to the callback functions.
261         *                     This array is expected to include $value at index 0.
262         * @return mixed The filtered value after all hooked functions are applied to it.
263         */
264        public function apply_filters( $value, $args ) {
265                if ( ! $this->callbacks ) {
266                        return $value;
267                }
268
269                $nesting_level = $this->nesting_level++;
270
271                $this->iterations[ $nesting_level ] = array_keys( $this->callbacks );
272                $num_args                           = count( $args );
273
274                do {
275                        $this->current_priority[ $nesting_level ] = current( $this->iterations[ $nesting_level ] );
276                        $priority                                 = $this->current_priority[ $nesting_level ];
277
278                        foreach ( $this->callbacks[ $priority ] as $the_ ) {
279                                if ( ! $this->doing_action ) {
280                                        $args[0] = $value;
281                                }
282
283                                // Avoid the array_slice() if possible.
284                                if ( 0 == $the_['accepted_args'] ) {
285                                        $value = call_user_func( $the_['function'] );
286                                } elseif ( $the_['accepted_args'] >= $num_args ) {
287                                        $value = call_user_func_array( $the_['function'], $args );
288                                } else {
289                                        $value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int) $the_['accepted_args'] ) );
290                                }
291                        }
292                } while ( false !== next( $this->iterations[ $nesting_level ] ) );
293
294                unset( $this->iterations[ $nesting_level ] );
295                unset( $this->current_priority[ $nesting_level ] );
296
297                $this->nesting_level--;
298
299                return $value;
300        }
301
302        /**
303         * Calls the callback functions that have been added to an action hook.
304         *
305         * @since 4.7.0
306         *
307         * @param array $args Parameters to pass to the callback functions.
308         */
309        public function do_action( $args ) {
310                $this->doing_action = true;
311                $this->apply_filters( '', $args );
312
313                // If there are recursive calls to the current action, we haven't finished it until we get to the last one.
314                if ( ! $this->nesting_level ) {
315                        $this->doing_action = false;
316                }
317        }
318
319        /**
320         * Processes the functions hooked into the 'all' hook.
321         *
322         * @since 4.7.0
323         *
324         * @param array $args Arguments to pass to the hook callbacks. Passed by reference.
325         */
326        public function do_all_hook( &$args ) {
327                $nesting_level                      = $this->nesting_level++;
328                $this->iterations[ $nesting_level ] = array_keys( $this->callbacks );
329
330                do {
331                        $priority = current( $this->iterations[ $nesting_level ] );
332                        foreach ( $this->callbacks[ $priority ] as $the_ ) {
333                                call_user_func_array( $the_['function'], $args );
334                        }
335                } while ( false !== next( $this->iterations[ $nesting_level ] ) );
336
337                unset( $this->iterations[ $nesting_level ] );
338                $this->nesting_level--;
339        }
340
341        /**
342         * Return the current priority level of the currently running iteration of the hook.
343         *
344         * @since 4.7.0
345         *
346         * @return int|false If the hook is running, return the current priority level. If it isn't running, return false.
347         */
348        public function current_priority() {
349                if ( false === current( $this->iterations ) ) {
350                        return false;
351                }
352
353                return current( current( $this->iterations ) );
354        }
355
356        /**
357         * Normalizes filters set up before WordPress has initialized to WP_Hook objects.
358         *
359         * @since 4.7.0
360         *
361         * @param array $filters Filters to normalize.
362         * @return WP_Hook[] Array of normalized filters.
363         */
364        public static function build_preinitialized_hooks( $filters ) {
365                /** @var WP_Hook[] $normalized */
366                $normalized = array();
367
368                foreach ( $filters as $tag => $callback_groups ) {
369                        if ( is_object( $callback_groups ) && $callback_groups instanceof WP_Hook ) {
370                                $normalized[ $tag ] = $callback_groups;
371                                continue;
372                        }
373                        $hook = new WP_Hook();
374
375                        // Loop through callback groups.
376                        foreach ( $callback_groups as $priority => $callbacks ) {
377
378                                // Loop through callbacks.
379                                foreach ( $callbacks as $cb ) {
380                                        $hook->add_filter( $tag, $cb['function'], $priority, $cb['accepted_args'] );
381                                }
382                        }
383                        $normalized[ $tag ] = $hook;
384                }
385                return $normalized;
386        }
387
388        /**
389         * Determines whether an offset value exists.
390         *
391         * @since 4.7.0
392         *
393         * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php
394         *
395         * @param mixed $offset An offset to check for.
396         * @return bool True if the offset exists, false otherwise.
397         */
398        public function offsetExists( $offset ) {
399                return isset( $this->callbacks[ $offset ] );
400        }
401
402        /**
403         * Retrieves a value at a specified offset.
404         *
405         * @since 4.7.0
406         *
407         * @link https://www.php.net/manual/en/arrayaccess.offsetget.php
408         *
409         * @param mixed $offset The offset to retrieve.
410         * @return mixed If set, the value at the specified offset, null otherwise.
411         */
412        public function offsetGet( $offset ) {
413                return isset( $this->callbacks[ $offset ] ) ? $this->callbacks[ $offset ] : null;
414        }
415
416        /**
417         * Sets a value at a specified offset.
418         *
419         * @since 4.7.0
420         *
421         * @link https://www.php.net/manual/en/arrayaccess.offsetset.php
422         *
423         * @param mixed $offset The offset to assign the value to.
424         * @param mixed $value The value to set.
425         */
426        public function offsetSet( $offset, $value ) {
427                if ( is_null( $offset ) ) {
428                        $this->callbacks[] = $value;
429                } else {
430                        $this->callbacks[ $offset ] = $value;
431                }
432        }
433
434        /**
435         * Unsets a specified offset.
436         *
437         * @since 4.7.0
438         *
439         * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php
440         *
441         * @param mixed $offset The offset to unset.
442         */
443        public function offsetUnset( $offset ) {
444                unset( $this->callbacks[ $offset ] );
445        }
446
447        /**
448         * Returns the current element.
449         *
450         * @since 4.7.0
451         *
452         * @link https://www.php.net/manual/en/iterator.current.php
453         *
454         * @return array Of callbacks at current priority.
455         */
456        public function current() {
457                return current( $this->callbacks );
458        }
459
460        /**
461         * Moves forward to the next element.
462         *
463         * @since 4.7.0
464         *
465         * @link https://www.php.net/manual/en/iterator.next.php
466         *
467         * @return array Of callbacks at next priority.
468         */
469        public function next() {
470                return next( $this->callbacks );
471        }
472
473        /**
474         * Returns the key of the current element.
475         *
476         * @since 4.7.0
477         *
478         * @link https://www.php.net/manual/en/iterator.key.php
479         *
480         * @return mixed Returns current priority on success, or NULL on failure
481         */
482        public function key() {
483                return key( $this->callbacks );
484        }
485
486        /**
487         * Checks if current position is valid.
488         *
489         * @since 4.7.0
490         *
491         * @link https://www.php.net/manual/en/iterator.valid.php
492         *
493         * @return boolean
494         */
495        public function valid() {
496                return key( $this->callbacks ) !== null;
497        }
498
499        /**
500         * Rewinds the Iterator to the first element.
501         *
502         * @since 4.7.0
503         *
504         * @link https://www.php.net/manual/en/iterator.rewind.php
505         */
506        public function rewind() {
507                reset( $this->callbacks );
508        }
509
510}
Note: See TracBrowser for help on using the repository browser.