public/blog/wp-includes/class-wp-hook.php line 324

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