[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-hook.php (source)

   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  }


Generated : Wed Jun 24 08:20:11 2026 Cross-referenced by PHPXref