| [ 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<string, Hook_Callback>> 24 * @phpstan-implements ArrayAccess<int, array<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<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 */ 205 public function remove_filter( $hook_name, $callback, $priority ) { 206 if ( null === $priority ) { 207 $priority = 0; 208 } 209 210 $function_key = _wp_filter_build_unique_id( $hook_name, $callback, $priority ); 211 212 $exists = isset( $function_key, $this->callbacks[ $priority ][ $function_key ] ); 213 214 if ( $exists ) { 215 unset( $this->callbacks[ $priority ][ $function_key ] ); 216 217 if ( ! $this->callbacks[ $priority ] ) { 218 unset( $this->callbacks[ $priority ] ); 219 220 $this->priorities = array_keys( $this->callbacks ); 221 222 if ( $this->nesting_level > 0 ) { 223 $this->resort_active_iterations(); 224 } 225 } 226 } 227 228 return $exists; 229 } 230 231 /** 232 * Checks if a specific callback has been registered for this hook. 233 * 234 * When using the `$callback` argument, this function may return a non-boolean value 235 * that evaluates to false (e.g. 0), so use the `===` operator for testing the return value. 236 * 237 * @since 4.7.0 238 * @since 6.9.0 Added the `$priority` parameter. 239 * 240 * @param string $hook_name Optional. The name of the filter hook. Default empty. 241 * @param callable|string|array|false $callback Optional. The callback to check for. 242 * This method can be called unconditionally to speculatively check 243 * a callback that may or may not exist. Default false. 244 * @param int|false $priority Optional. The specific priority at which to check for the callback. 245 * Default false. 246 * @return bool|int If `$callback` is omitted, returns boolean for whether the hook has 247 * anything registered. When checking a specific function, the priority 248 * of that hook is returned, or false if the function is not attached. 249 * If `$callback` and `$priority` are both provided, a boolean is returned 250 * for whether the specific function is registered at that priority. 251 */ 252 public function has_filter( $hook_name = '', $callback = false, $priority = false ) { 253 if ( false === $callback ) { 254 return $this->has_filters(); 255 } 256 257 $function_key = _wp_filter_build_unique_id( $hook_name, $callback, false ); 258 259 if ( ! $function_key ) { 260 return false; 261 } 262 263 if ( is_int( $priority ) ) { 264 return isset( $this->callbacks[ $priority ][ $function_key ] ); 265 } 266 267 foreach ( $this->callbacks as $callback_priority => $callbacks ) { 268 if ( isset( $callbacks[ $function_key ] ) ) { 269 return $callback_priority; 270 } 271 } 272 273 return false; 274 } 275 276 /** 277 * Checks if any callbacks have been registered for this hook. 278 * 279 * @since 4.7.0 280 * 281 * @return bool True if callbacks have been registered for the current hook, otherwise false. 282 */ 283 public function has_filters() { 284 foreach ( $this->callbacks as $callbacks ) { 285 if ( $callbacks ) { 286 return true; 287 } 288 } 289 290 return false; 291 } 292 293 /** 294 * Removes all callbacks from the current filter. 295 * 296 * @since 4.7.0 297 * 298 * @param int|false $priority Optional. The priority number to remove. Default false. 299 */ 300 public function remove_all_filters( $priority = false ) { 301 if ( ! $this->callbacks ) { 302 return; 303 } 304 305 if ( false === $priority ) { 306 $this->callbacks = array(); 307 $this->priorities = array(); 308 } elseif ( isset( $this->callbacks[ $priority ] ) ) { 309 unset( $this->callbacks[ $priority ] ); 310 $this->priorities = array_keys( $this->callbacks ); 311 } 312 313 if ( $this->nesting_level > 0 ) { 314 $this->resort_active_iterations(); 315 } 316 } 317 318 /** 319 * Calls the callback functions that have been added to a filter hook. 320 * 321 * @since 4.7.0 322 * 323 * @param mixed $value The value to filter. 324 * @param array $args Additional parameters to pass to the callback functions. 325 * This array is expected to include $value at index 0. 326 * @return mixed The filtered value after all hooked functions are applied to it. 327 */ 328 public function apply_filters( $value, $args ) { 329 if ( ! $this->callbacks ) { 330 return $value; 331 } 332 333 $nesting_level = $this->nesting_level++; 334 335 $this->iterations[ $nesting_level ] = $this->priorities; 336 337 $num_args = count( $args ); 338 339 do { 340 $this->current_priority[ $nesting_level ] = current( $this->iterations[ $nesting_level ] ); 341 342 $priority = $this->current_priority[ $nesting_level ]; 343 344 foreach ( $this->callbacks[ $priority ] as $the_ ) { 345 if ( ! $this->doing_action ) { 346 $args[0] = $value; 347 } 348 349 // Avoid the array_slice() if possible. 350 if ( 0 === $the_['accepted_args'] ) { 351 $value = call_user_func( $the_['function'] ); 352 } elseif ( $the_['accepted_args'] >= $num_args ) { 353 $value = call_user_func_array( $the_['function'], $args ); 354 } else { 355 $value = call_user_func_array( $the_['function'], array_slice( $args, 0, $the_['accepted_args'] ) ); 356 } 357 } 358 } while ( false !== next( $this->iterations[ $nesting_level ] ) ); 359 360 unset( $this->iterations[ $nesting_level ] ); 361 unset( $this->current_priority[ $nesting_level ] ); 362 363 --$this->nesting_level; 364 365 return $value; 366 } 367 368 /** 369 * Calls the callback functions that have been added to an action hook. 370 * 371 * @since 4.7.0 372 * 373 * @param array $args Parameters to pass to the callback functions. 374 */ 375 public function do_action( $args ) { 376 $this->doing_action = true; 377 $this->apply_filters( '', $args ); 378 379 // If there are recursive calls to the current action, we haven't finished it until we get to the last one. 380 if ( ! $this->nesting_level ) { 381 $this->doing_action = false; 382 } 383 } 384 385 /** 386 * Processes the functions hooked into the 'all' hook. 387 * 388 * @since 4.7.0 389 * 390 * @param array $args Arguments to pass to the hook callbacks. Passed by reference. 391 */ 392 public function do_all_hook( &$args ) { 393 $nesting_level = $this->nesting_level++; 394 $this->iterations[ $nesting_level ] = $this->priorities; 395 396 do { 397 $priority = current( $this->iterations[ $nesting_level ] ); 398 399 foreach ( $this->callbacks[ $priority ] as $the_ ) { 400 call_user_func_array( $the_['function'], $args ); 401 } 402 } while ( false !== next( $this->iterations[ $nesting_level ] ) ); 403 404 unset( $this->iterations[ $nesting_level ] ); 405 --$this->nesting_level; 406 } 407 408 /** 409 * Return the current priority level of the currently running iteration of the hook. 410 * 411 * @since 4.7.0 412 * 413 * @return int|false If the hook is running, return the current priority level. 414 * If it isn't running, return false. 415 */ 416 public function current_priority() { 417 if ( false === current( $this->iterations ) ) { 418 return false; 419 } 420 421 return current( current( $this->iterations ) ); 422 } 423 424 /** 425 * Normalizes filters set up before WordPress has initialized to WP_Hook objects. 426 * 427 * The `$filters` parameter should be an array keyed by hook name, with values 428 * containing either: 429 * 430 * - A `WP_Hook` instance 431 * - An array of callbacks keyed by their priorities 432 * 433 * Examples: 434 * 435 * $filters = array( 436 * 'wp_fatal_error_handler_enabled' => array( 437 * 10 => array( 438 * array( 439 * 'accepted_args' => 0, 440 * 'function' => function() { 441 * return false; 442 * }, 443 * ), 444 * ), 445 * ), 446 * ); 447 * 448 * @since 4.7.0 449 * 450 * @param array $filters Filters to normalize. See documentation above for details. 451 * @phpstan-param array<string, WP_Hook|array<int, array<Hook_Callback>>> $filters 452 * @return array<string, WP_Hook> Array of normalized filters keyed by hook name. 453 */ 454 public static function build_preinitialized_hooks( $filters ) { 455 /** @var array<string, WP_Hook> $normalized */ 456 $normalized = array(); 457 458 foreach ( $filters as $hook_name => $callback_groups ) { 459 if ( $callback_groups instanceof WP_Hook ) { 460 $normalized[ $hook_name ] = $callback_groups; 461 continue; 462 } 463 464 $hook = new WP_Hook(); 465 466 // Loop through callback groups. 467 foreach ( $callback_groups as $priority => $callbacks ) { 468 469 // Loop through callbacks. 470 foreach ( $callbacks as $cb ) { 471 $hook->add_filter( $hook_name, $cb['function'], $priority, $cb['accepted_args'] ); 472 } 473 } 474 475 $normalized[ $hook_name ] = $hook; 476 } 477 478 return $normalized; 479 } 480 481 /** 482 * Determines whether an offset value exists. 483 * 484 * @since 4.7.0 485 * 486 * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php 487 * 488 * @param int $offset An offset to check for. 489 * @return bool True if the offset exists, false otherwise. 490 */ 491 #[ReturnTypeWillChange] 492 public function offsetExists( $offset ) { 493 return isset( $this->callbacks[ $offset ] ); 494 } 495 496 /** 497 * Retrieves a value at a specified offset. 498 * 499 * @since 4.7.0 500 * 501 * @link https://www.php.net/manual/en/arrayaccess.offsetget.php 502 * 503 * @param int $offset The offset to retrieve. 504 * @return array|null If set, the value at the specified offset, null otherwise. 505 * @phpstan-return array<string, Hook_Callback>|null 506 */ 507 #[ReturnTypeWillChange] 508 public function offsetGet( $offset ) { 509 return $this->callbacks[ $offset ] ?? null; 510 } 511 512 /** 513 * Sets a value at a specified offset. 514 * 515 * @since 4.7.0 516 * 517 * @link https://www.php.net/manual/en/arrayaccess.offsetset.php 518 * 519 * @param int|null $offset The offset to assign the value to. 520 * @param array $value The value to set. 521 * @phpstan-param array<string, Hook_Callback> $value 522 */ 523 #[ReturnTypeWillChange] 524 public function offsetSet( $offset, $value ) { 525 if ( is_null( $offset ) ) { 526 $this->callbacks[] = $value; 527 } else { 528 $this->callbacks[ $offset ] = $value; 529 } 530 531 $this->priorities = array_keys( $this->callbacks ); 532 } 533 534 /** 535 * Unsets a specified offset. 536 * 537 * @since 4.7.0 538 * 539 * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php 540 * 541 * @param int $offset The offset to unset. 542 */ 543 #[ReturnTypeWillChange] 544 public function offsetUnset( $offset ) { 545 unset( $this->callbacks[ $offset ] ); 546 $this->priorities = array_keys( $this->callbacks ); 547 } 548 549 /** 550 * Returns the current element. 551 * 552 * @since 4.7.0 553 * 554 * @link https://www.php.net/manual/en/iterator.current.php 555 * 556 * @return array|false Array of callbacks at current priority, false if there are no more elements. 557 * @phpstan-return array<string, Hook_Callback>|false 558 */ 559 #[ReturnTypeWillChange] 560 public function current() { 561 return current( $this->callbacks ); 562 } 563 564 /** 565 * Moves forward to the next element. 566 * 567 * @since 4.7.0 568 * 569 * @link https://www.php.net/manual/en/iterator.next.php 570 * 571 * @return array|false Array of callbacks at next priority, false if there are no more elements. 572 * @phpstan-return array<string, Hook_Callback>|false 573 */ 574 #[ReturnTypeWillChange] 575 public function next() { 576 return next( $this->callbacks ); 577 } 578 579 /** 580 * Returns the key of the current element. 581 * 582 * @since 4.7.0 583 * 584 * @link https://www.php.net/manual/en/iterator.key.php 585 * 586 * @return int|null Returns current priority on success, or NULL on failure 587 */ 588 #[ReturnTypeWillChange] 589 public function key() { 590 return key( $this->callbacks ); 591 } 592 593 /** 594 * Checks if current position is valid. 595 * 596 * @since 4.7.0 597 * 598 * @link https://www.php.net/manual/en/iterator.valid.php 599 * 600 * @return bool Whether the current position is valid. 601 */ 602 #[ReturnTypeWillChange] 603 public function valid() { 604 return key( $this->callbacks ) !== null; 605 } 606 607 /** 608 * Rewinds the Iterator to the first element. 609 * 610 * @since 4.7.0 611 * 612 * @link https://www.php.net/manual/en/iterator.rewind.php 613 */ 614 #[ReturnTypeWillChange] 615 public function rewind() { 616 reset( $this->callbacks ); 617 } 618 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Jul 22 08:20:19 2026 | Cross-referenced by PHPXref |