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