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