| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 * 18 * @phpstan-type Hook_Callback array{ 19 * function: callable, 20 * accepted_args: int, 21 * } 22 * 23 * @phpstan-implements Iterator<int, array<non-decimal-int-string, Hook_Callback>> 24 * @phpstan-implements ArrayAccess<int, array<non-decimal-int-string, Hook_Callback>> 25 */ 26 #[AllowDynamicProperties] 27 final class WP_Hook implements Iterator, ArrayAccess { 28 29 /** 30 * Hook callbacks keyed by priority. 31 * 32 * @since 4.7.0 33 * @var array 34 * @phpstan-var array<int, array<non-decimal-int-string, Hook_Callback>> 35 */ 36 public $callbacks = array(); 37 38 /** 39 * Priorities list. 40 * 41 * @since 6.4.0 42 * @var list<int> 43 */ 44 protected $priorities = array(); 45 46 /** 47 * The priority keys of actively running iterations of a hook. 48 * 49 * @since 4.7.0 50 * @var array<int, list<int>> 51 */ 52 private $iterations = array(); 53 54 /** 55 * The current priority of actively running iterations of a hook. 56 * 57 * @since 4.7.0 58 * @var array<int, int> 59 */ 60 private $current_priority = array(); 61 62 /** 63 * Number of levels this hook can be recursively called. 64 * 65 * @since 4.7.0 66 * @var int 67 */ 68 private $nesting_level = 0; 69 70 /** 71 * Flag for if we're currently doing an action, rather than a filter. 72 * 73 * @since 4.7.0 74 * @var bool 75 */ 76 private $doing_action = false; 77 78 /** 79 * Adds a callback function to a filter hook. 80 * 81 * @since 4.7.0 82 * 83 * @param string $hook_name The name of the filter to add the callback to. 84 * @param callable $callback The callback to be run when the filter is applied. 85 * @param int $priority The order in which the functions associated with a particular filter 86 * are executed. Lower numbers correspond with earlier execution, 87 * and functions with the same priority are executed in the order 88 * in which they were added to the filter. 89 * @param int $accepted_args The number of arguments the function accepts. 90 */ 91 public function add_filter( $hook_name, $callback, $priority, $accepted_args ) { 92 if ( null === $priority ) { 93 $priority = 0; 94 } 95 96 $idx = _wp_filter_build_unique_id( $hook_name, $callback, $priority ); 97 if ( null === $idx ) { 98 return; 99 } 100 101 $priority_existed = isset( $this->callbacks[ $priority ] ); 102 103 $this->callbacks[ $priority ][ $idx ] = array( 104 'function' => $callback, 105 'accepted_args' => (int) $accepted_args, 106 ); 107 108 // If we're adding a new priority to the list, put them back in sorted order. 109 if ( ! $priority_existed && count( $this->callbacks ) > 1 ) { 110 ksort( $this->callbacks, SORT_NUMERIC ); 111 } 112 113 $this->priorities = array_keys( $this->callbacks ); 114 115 if ( $this->nesting_level > 0 ) { 116 $this->resort_active_iterations( $priority, $priority_existed ); 117 } 118 } 119 120 /** 121 * Handles resetting callback priority keys mid-iteration. 122 * 123 * @since 4.7.0 124 * 125 * @param false|int $new_priority Optional. The priority of the new filter being added. Default false, 126 * for no priority being added. 127 * @param bool $priority_existed Optional. Flag for whether the priority already existed before the new 128 * filter was added. Default false. 129 */ 130 private function resort_active_iterations( $new_priority = false, $priority_existed = false ) { 131 $new_priorities = $this->priorities; 132 133 // If there are no remaining hooks, clear out all running iterations. 134 if ( ! $new_priorities ) { 135 foreach ( $this->iterations as $index => $iteration ) { 136 $this->iterations[ $index ] = $new_priorities; 137 } 138 139 return; 140 } 141 142 $min = min( $new_priorities ); 143 144 foreach ( $this->iterations as $index => &$iteration ) { 145 $current = current( $iteration ); 146 147 // If we're already at the end of this iteration, just leave the array pointer where it is. 148 if ( false === $current ) { 149 continue; 150 } 151 152 $iteration = $new_priorities; 153 154 if ( $current < $min ) { 155 array_unshift( $iteration, $current ); 156 continue; 157 } 158 159 while ( current( $iteration ) < $current ) { 160 if ( false === next( $iteration ) ) { 161 break; 162 } 163 } 164 165 // If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority... 166 if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) { 167 /* 168 * ...and the new priority is the same as what $this->iterations thinks is the previous 169 * priority, we need to move back to it. 170 */ 171 172 if ( false === current( $iteration ) ) { 173 // If we've already moved off the end of the array, go back to the last element. 174 $prev = end( $iteration ); 175 } else { 176 // Otherwise, just go back to the previous element. 177 $prev = prev( $iteration ); 178 } 179 180 if ( false === $prev ) { 181 // Start of the array. Reset, and go about our day. 182 reset( $iteration ); 183 } elseif ( $new_priority !== $prev ) { 184 // Previous wasn't the same. Move forward again. 185 next( $iteration ); 186 } 187 } 188 } 189 190 unset( $iteration ); 191 } 192 193 /** 194 * Removes a callback function from a filter hook. 195 * 196 * @since 4.7.0 197 * 198 * @param string $hook_name The filter hook to which the function to be removed is hooked. 199 * @param callable|string|array $callback The callback to be removed from running when the filter is applied. 200 * This method can be called unconditionally to speculatively remove 201 * a callback that may or may not exist. 202 * @param int $priority The exact priority used when adding the original filter callback. 203 * @return bool Whether the callback existed before it was removed. 204 * @phpstan-param Maybe_Callable $callback 205 */ 206 public function remove_filter( $hook_name, $callback, $priority ) { 207 if ( null === $priority ) { 208 $priority = 0; 209 } 210 211 $function_key = _wp_filter_build_unique_id( $hook_name, $callback, $priority ); 212 213 $exists = isset( $function_key, $this->callbacks[ $priority ][ $function_key ] ); 214 215 if ( $exists ) { 216 unset( $this->callbacks[ $priority ][ $function_key ] ); 217 218 if ( ! $this->callbacks[ $priority ] ) { 219 unset( $this->callbacks[ $priority ] ); 220 221 $this->priorities = array_keys( $this->callbacks ); 222 223 if ( $this->nesting_level > 0 ) { 224 $this->resort_active_iterations(); 225 } 226 } 227 } 228 229 return $exists; 230 } 231 232 /** 233 * Checks if a specific callback has been registered for this hook. 234 * 235 * When using the `$callback` argument, this function may return a non-boolean value 236 * that evaluates to false (e.g. 0), so use the `===` operator for testing the return value. 237 * 238 * @since 4.7.0 239 * @since 6.9.0 Added the `$priority` parameter. 240 * 241 * @param string $hook_name Optional. The name of the filter hook. Default empty. 242 * @param callable|string|array|false $callback Optional. The callback to check for. 243 * This method can be called unconditionally to speculatively check 244 * a callback that may or may not exist. Default false. 245 * @param int|false $priority Optional. The specific priority at which to check for the callback. 246 * Default false. 247 * @return bool|int If `$callback` is omitted, returns boolean for whether the hook has 248 * anything registered. When checking a specific function, the priority 249 * of that hook is returned, or false if the function is not attached. 250 * If `$callback` and `$priority` are both provided, a boolean is returned 251 * for whether the specific function is registered at that priority. 252 * @phpstan-param Maybe_Callable|false $callback 253 * @phpstan-return ( 254 * $callback is false 255 * ? bool 256 * : ( $priority is int 257 * ? bool 258 * : false|int ) 259 * ) 260 */ 261 public function has_filter( $hook_name = '', $callback = false, $priority = false ) { 262 if ( false === $callback ) { 263 return $this->has_filters(); 264 } 265 266 $function_key = _wp_filter_build_unique_id( $hook_name, $callback, is_int( $priority ) ? $priority : 10 ); 267 268 if ( ! $function_key ) { 269 return false; 270 } 271 272 if ( is_int( $priority ) ) { 273 return isset( $this->callbacks[ $priority ][ $function_key ] ); 274 } 275 276 foreach ( $this->callbacks as $callback_priority => $callbacks ) { 277 if ( isset( $callbacks[ $function_key ] ) ) { 278 return $callback_priority; 279 } 280 } 281 282 return false; 283 } 284 285 /** 286 * Checks if any callbacks have been registered for this hook. 287 * 288 * @since 4.7.0 289 * 290 * @return bool True if callbacks have been registered for the current hook, otherwise false. 291 */ 292 public function has_filters() { 293 foreach ( $this->callbacks as $callbacks ) { 294 if ( $callbacks ) { 295 return true; 296 } 297 } 298 299 return false; 300 } 301 302 /** 303 * Removes all callbacks from the current filter. 304 * 305 * @since 4.7.0 306 * 307 * @param int|false $priority Optional. The priority number to remove. Default false. 308 */ 309 public function remove_all_filters( $priority = false ) { 310 if ( ! $this->callbacks ) { 311 return; 312 } 313 314 if ( false === $priority ) { 315 $this->callbacks = array(); 316 $this->priorities = array(); 317 } elseif ( isset( $this->callbacks[ $priority ] ) ) { 318 unset( $this->callbacks[ $priority ] ); 319 $this->priorities = array_keys( $this->callbacks ); 320 } 321 322 if ( $this->nesting_level > 0 ) { 323 $this->resort_active_iterations(); 324 } 325 } 326 327 /** 328 * Calls the callback functions that have been added to a filter hook. 329 * 330 * @since 4.7.0 331 * 332 * @param mixed $value The value to filter. 333 * @param list<mixed> $args Additional parameters to pass to the callback functions. 334 * When filtering, this array is expected to include $value 335 * at index 0. When called for an action by WP_Hook::do_action(), 336 * it contains only the action arguments. 337 * @return mixed The filtered value after all hooked functions are applied to it. 338 */ 339 public function apply_filters( $value, $args ) { 340 if ( ! $this->callbacks ) { 341 return $value; 342 } 343 344 $nesting_level = $this->nesting_level++; 345 346 $this->iterations[ $nesting_level ] = $this->priorities; 347 348 $num_args = count( $args ); 349 350 do { 351 $priority = current( $this->iterations[ $nesting_level ] ); 352 353 if ( false === $priority ) { 354 // This is not expected to occur since the hook is known to have callbacks at one or more priorities. 355 break; 356 } 357 358 $this->current_priority[ $nesting_level ] = $priority; 359 360 foreach ( $this->callbacks[ $priority ] as $the_ ) { 361 if ( ! $this->doing_action ) { 362 $args[0] = $value; 363 } 364 365 // Avoid the array_slice() if possible. 366 if ( 0 === $the_['accepted_args'] ) { 367 $value = call_user_func( $the_['function'] ); 368 } elseif ( $the_['accepted_args'] >= $num_args ) { 369 $value = call_user_func_array( $the_['function'], $args ); 370 } else { 371 $value = call_user_func_array( $the_['function'], array_slice( $args, 0, $the_['accepted_args'] ) ); 372 } 373 } 374 } while ( false !== next( $this->iterations[ $nesting_level ] ) ); 375 376 unset( $this->iterations[ $nesting_level ] ); 377 unset( $this->current_priority[ $nesting_level ] ); 378 379 --$this->nesting_level; 380 381 return $value; 382 } 383 384 /** 385 * Calls the callback functions that have been added to an action hook. 386 * 387 * @since 4.7.0 388 * 389 * @param list<mixed> $args Parameters to pass to the callback functions. 390 */ 391 public function do_action( $args ) { 392 $this->doing_action = true; 393 $this->apply_filters( '', $args ); 394 395 // If there are recursive calls to the current action, we haven't finished it until we get to the last one. 396 if ( ! $this->nesting_level ) { 397 $this->doing_action = false; 398 } 399 } 400 401 /** 402 * Processes the functions hooked into the 'all' hook. 403 * 404 * @since 4.7.0 405 * 406 * @param list<mixed> $args Arguments to pass to the hook callbacks. Passed by reference. 407 */ 408 public function do_all_hook( &$args ) { 409 $nesting_level = $this->nesting_level++; 410 $this->iterations[ $nesting_level ] = $this->priorities; 411 412 do { 413 $priority = current( $this->iterations[ $nesting_level ] ); 414 415 foreach ( $this->callbacks[ $priority ] as $the_ ) { 416 call_user_func_array( $the_['function'], $args ); 417 } 418 } while ( false !== next( $this->iterations[ $nesting_level ] ) ); 419 420 unset( $this->iterations[ $nesting_level ] ); 421 --$this->nesting_level; 422 } 423 424 /** 425 * Return the current priority level of the currently running iteration of the hook. 426 * 427 * @since 4.7.0 428 * 429 * @return int|false If the hook is running, return the current priority level. 430 * If it isn't running, return false. 431 */ 432 public function current_priority() { 433 if ( false === current( $this->iterations ) ) { 434 return false; 435 } 436 437 return current( current( $this->iterations ) ); 438 } 439 440 /** 441 * Normalizes filters set up before WordPress has initialized to WP_Hook objects. 442 * 443 * The `$filters` parameter should be an array keyed by hook name, with values 444 * containing either: 445 * 446 * - A `WP_Hook` instance 447 * - An array of callbacks keyed by their priorities 448 * 449 * Examples: 450 * 451 * $filters = array( 452 * 'wp_fatal_error_handler_enabled' => array( 453 * 10 => array( 454 * array( 455 * 'accepted_args' => 0, 456 * 'function' => function() { 457 * return false; 458 * }, 459 * ), 460 * ), 461 * ), 462 * ); 463 * 464 * @since 4.7.0 465 * 466 * @param array $filters Filters to normalize. See documentation above for details. 467 * @phpstan-param array<string, WP_Hook|array<int, array<Hook_Callback>>> $filters 468 * @return array<string, WP_Hook> Array of normalized filters keyed by hook name. 469 */ 470 public static function build_preinitialized_hooks( $filters ) { 471 /** @var array<string, WP_Hook> $normalized */ 472 $normalized = array(); 473 474 foreach ( $filters as $hook_name => $callback_groups ) { 475 if ( $callback_groups instanceof WP_Hook ) { 476 $normalized[ $hook_name ] = $callback_groups; 477 continue; 478 } 479 480 $hook = new WP_Hook(); 481 482 // Loop through callback groups. 483 foreach ( $callback_groups as $priority => $callbacks ) { 484 485 // Loop through callbacks. 486 foreach ( $callbacks as $cb ) { 487 $hook->add_filter( $hook_name, $cb['function'], $priority, $cb['accepted_args'] ); 488 } 489 } 490 491 $normalized[ $hook_name ] = $hook; 492 } 493 494 return $normalized; 495 } 496 497 /** 498 * Determines whether an offset value exists. 499 * 500 * @since 4.7.0 501 * 502 * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php 503 * 504 * @param int $offset An offset to check for. 505 * @return bool True if the offset exists, false otherwise. 506 */ 507 #[ReturnTypeWillChange] 508 public function offsetExists( $offset ) { 509 return isset( $this->callbacks[ $offset ] ); 510 } 511 512 /** 513 * Retrieves a value at a specified offset. 514 * 515 * @since 4.7.0 516 * 517 * @link https://www.php.net/manual/en/arrayaccess.offsetget.php 518 * 519 * @param int $offset The offset to retrieve. 520 * @return array|null If set, the value at the specified offset, null otherwise. 521 * @phpstan-return array<non-decimal-int-string, Hook_Callback>|null 522 */ 523 #[ReturnTypeWillChange] 524 public function offsetGet( $offset ) { 525 return $this->callbacks[ $offset ] ?? null; 526 } 527 528 /** 529 * Sets a value at a specified offset. 530 * 531 * @since 4.7.0 532 * 533 * @link https://www.php.net/manual/en/arrayaccess.offsetset.php 534 * 535 * @param int|null $offset The offset to assign the value to. 536 * @param array $value The value to set. 537 * @phpstan-param array<non-decimal-int-string, Hook_Callback> $value 538 */ 539 #[ReturnTypeWillChange] 540 public function offsetSet( $offset, $value ) { 541 if ( is_null( $offset ) ) { 542 $this->callbacks[] = $value; 543 } else { 544 $this->callbacks[ $offset ] = $value; 545 } 546 547 $this->priorities = array_keys( $this->callbacks ); 548 } 549 550 /** 551 * Unsets a specified offset. 552 * 553 * @since 4.7.0 554 * 555 * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php 556 * 557 * @param int $offset The offset to unset. 558 */ 559 #[ReturnTypeWillChange] 560 public function offsetUnset( $offset ) { 561 unset( $this->callbacks[ $offset ] ); 562 $this->priorities = array_keys( $this->callbacks ); 563 } 564 565 /** 566 * Returns the current element. 567 * 568 * @since 4.7.0 569 * 570 * @link https://www.php.net/manual/en/iterator.current.php 571 * 572 * @return array|false Array of callbacks at current priority, false if there are no more elements. 573 * @phpstan-return array<non-decimal-int-string, Hook_Callback>|false 574 */ 575 #[ReturnTypeWillChange] 576 public function current() { 577 return current( $this->callbacks ); 578 } 579 580 /** 581 * Moves forward to the next element. 582 * 583 * @since 4.7.0 584 * 585 * @link https://www.php.net/manual/en/iterator.next.php 586 * 587 * @return array|false Array of callbacks at next priority, false if there are no more elements. 588 * @phpstan-return array<non-decimal-int-string, Hook_Callback>|false 589 */ 590 #[ReturnTypeWillChange] 591 public function next() { 592 return next( $this->callbacks ); 593 } 594 595 /** 596 * Returns the key of the current element. 597 * 598 * @since 4.7.0 599 * 600 * @link https://www.php.net/manual/en/iterator.key.php 601 * 602 * @return int|null Returns current priority on success, or NULL on failure 603 */ 604 #[ReturnTypeWillChange] 605 public function key() { 606 return key( $this->callbacks ); 607 } 608 609 /** 610 * Checks if current position is valid. 611 * 612 * @since 4.7.0 613 * 614 * @link https://www.php.net/manual/en/iterator.valid.php 615 * 616 * @return bool Whether the current position is valid. 617 */ 618 #[ReturnTypeWillChange] 619 public function valid() { 620 return key( $this->callbacks ) !== null; 621 } 622 623 /** 624 * Rewinds the Iterator to the first element. 625 * 626 * @since 4.7.0 627 * 628 * @link https://www.php.net/manual/en/iterator.rewind.php 629 */ 630 #[ReturnTypeWillChange] 631 public function rewind() { 632 reset( $this->callbacks ); 633 } 634 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Sep 30 08:20:29 2026 | Cross-referenced by PHPXref |