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