OwlCyberSecurity - MANAGER
Edit File: Hook.php
<?php /** * Plugin API: WP_Hook class * * From wordpress WP_Hook class */ namespace Duplicator\Installer\Core\Hooks; /** * Core class used to implement action and filter hook functionality. * * @see Iterator * @see ArrayAccess */ final class Hook implements \Iterator, \ArrayAccess { /** * Hook callbacks. * * @var array */ public $callbacks = array(); /** * The priority keys of actively running iterations of a hook. * * @var array */ private $iterations = array(); /** * The current priority of actively running iterations of a hook. * * @var array */ private $currentPriority = array(); /** * Number of levels this hook can be recursively called. * * @var int */ private $nestingLevel = 0; /** * Flag for if we're current doing an action, rather than a filter. * * @var bool */ private $doingAction = false; /** * Hooks a function or method to a specific filter action. * * @param string $tag The name of the filter to hook the $function_to_add callback to. * @param callable $function_to_add The callback to be run when the filter is applied. * @param int $priority The order in which the functions associated with a particular action * are executed. Lower numbers correspond with earlier execution, * and functions with the same priority are executed in the order * in which they were added to the action. * @param int $accepted_args The number of arguments the function accepts. * * @return void */ public function addFilter($tag, $function_to_add, $priority, $accepted_args) { $idx = self::wpFilterBuildUniqueId($tag, $function_to_add, $priority); $priority_existed = isset($this->callbacks[$priority]); $this->callbacks[$priority][$idx] = array( 'function' => $function_to_add, 'accepted_args' => $accepted_args, ); // If we're adding a new priority to the list, put them back in sorted order. if (!$priority_existed && count($this->callbacks) > 1) { ksort($this->callbacks, SORT_NUMERIC); } if ($this->nestingLevel > 0) { $this->resortActiveIterations($priority, $priority_existed); } } /** * Handles resetting callback priority keys mid-iteration. * * @param false|int $new_priority Optional. The priority of the new filter being added. Default false, * for no priority being added. * @param bool $priority_existed Optional. Flag for whether the priority already existed before the new * filter was added. Default false. * * @return void */ private function resortActiveIterations($new_priority = false, $priority_existed = false) { $new_priorities = array_keys($this->callbacks); // If there are no remaining hooks, clear out all running iterations. if (!$new_priorities) { foreach ($this->iterations as $index => $iteration) { $this->iterations[$index] = $new_priorities; } return; } $min = min($new_priorities); foreach ($this->iterations as $index => &$iteration) { $current = current($iteration); // If we're already at the end of this iteration, just leave the array pointer where it is. if (false === $current) { continue; } $iteration = $new_priorities; if ($current < $min) { array_unshift($iteration, $current); continue; } while (current($iteration) < $current) { if (false === next($iteration)) { break; } } // If we have a new priority that didn't exist, but ::applyFilters() or ::doAction() thinks it's the current priority... if ($new_priority === $this->currentPriority[$index] && !$priority_existed) { /* * ...and the new priority is the same as what $this->iterations thinks is the previous * priority, we need to move back to it. */ if (false === current($iteration)) { // If we've already moved off the end of the array, go back to the last element. $prev = end($iteration); } else { // Otherwise, just go back to the previous element. $prev = prev($iteration); } if (false === $prev) { // Start of the array. Reset, and go about our day. reset($iteration); } elseif ($new_priority !== $prev) { // Previous wasn't the same. Move forward again. next($iteration); } } } unset($iteration); } /** * Unhooks a function or method from a specific filter action. * * @param string $tag The filter hook to which the function to be removed is hooked. * @param callable $function_to_remove The callback to be removed from running when the filter is applied. * @param int $priority The exact priority used when adding the original filter callback. * * @return bool Whether the callback existed before it was removed. */ public function removeFilter($tag, $function_to_remove, $priority) { $function_key = self::wpFilterBuildUniqueId($tag, $function_to_remove, $priority); $exists = isset($this->callbacks[$priority][$function_key]); if ($exists) { unset($this->callbacks[$priority][$function_key]); if (!$this->callbacks[$priority]) { unset($this->callbacks[$priority]); if ($this->nestingLevel > 0) { $this->resortActiveIterations(); } } } return $exists; } /** * Checks if a specific action has been registered for this hook. * * When using the `$function_to_check` argument, this function may return a non-boolean value * that evaluates to false (e.g. 0), so use the `===` operator for testing the return value. * * @param string $tag Optional. The name of the filter hook. Default empty. * @param callable|false $function_to_check Optional. The callback to check for. Default false. * * @return bool|int If `$function_to_check` is omitted, returns boolean for whether the hook has * anything registered. When checking a specific function, the priority of that * hook is returned, or false if the function is not attached. */ public function hasFilter($tag = '', $function_to_check = false) { if (false === $function_to_check) { return $this->hasFilters(); } $function_key = self::wpFilterBuildUniqueId($tag, $function_to_check, false); if (!$function_key) { return false; } foreach ($this->callbacks as $priority => $callbacks) { if (isset($callbacks[$function_key])) { return $priority; } } return false; } /** * Checks if any callbacks have been registered for this hook. * * @return bool True if callbacks have been registered for the current hook, otherwise false. */ public function hasFilters() { foreach ($this->callbacks as $callbacks) { if ($callbacks) { return true; } } return false; } /** * Removes all callbacks from the current filter. * * @param int|false $priority Optional. The priority number to remove. Default false. * * @return void */ public function removeAllFilters($priority = false) { if (!$this->callbacks) { return; } if (false === $priority) { $this->callbacks = array(); } elseif (isset($this->callbacks[$priority])) { unset($this->callbacks[$priority]); } if ($this->nestingLevel > 0) { $this->resortActiveIterations(); } } /** * Calls the callback functions that have been added to a filter hook. * * @param mixed $value The value to filter. * @param array $args Additional parameters to pass to the callback functions. * This array is expected to include $value at index 0. * * @return mixed The filtered value after all hooked functions are applied to it. */ public function applyFilters($value, $args) { if (!$this->callbacks) { return $value; } $nestingLevel = $this->nestingLevel++; $this->iterations[$nestingLevel] = array_keys($this->callbacks); $num_args = count($args); do { $this->currentPriority[$nestingLevel] = current($this->iterations[$nestingLevel]); $priority = $this->currentPriority[$nestingLevel]; foreach ($this->callbacks[$priority] as $the_) { if (!$this->doingAction) { $args[0] = $value; } // Avoid the array_slice() if possible. if (0 == $the_['accepted_args']) { $value = call_user_func($the_['function']); } elseif ($the_['accepted_args'] >= $num_args) { $value = call_user_func_array($the_['function'], $args); } else { $value = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args'])); } } } while (false !== next($this->iterations[$nestingLevel])); unset($this->iterations[$nestingLevel]); unset($this->currentPriority[$nestingLevel]); $this->nestingLevel--; return $value; } /** * Calls the callback functions that have been added to an action hook. * * @param array $args Parameters to pass to the callback functions. * * @return void */ public function doAction($args) { $this->doingAction = true; $this->applyFilters('', $args); // If there are recursive calls to the current action, we haven't finished it until we get to the last one. if (!$this->nestingLevel) { $this->doingAction = false; } } /** * Processes the functions hooked into the 'all' hook. * * @param array $args Arguments to pass to the hook callbacks. Passed by reference. * * @return void */ public function doAllHook(&$args) { $nestingLevel = $this->nestingLevel++; $this->iterations[$nestingLevel] = array_keys($this->callbacks); do { $priority = current($this->iterations[$nestingLevel]); foreach ($this->callbacks[$priority] as $the_) { call_user_func_array($the_['function'], $args); } } while (false !== next($this->iterations[$nestingLevel])); unset($this->iterations[$nestingLevel]); $this->nestingLevel--; } /** * Return the current priority level of the currently running iteration of the hook. * * @return int|false If the hook is running, return the current priority level. If it isn't running, return false. */ public function currentPriority() { if (false === current($this->iterations)) { return false; } return current(current($this->iterations)); } /** * Normalizes filters set up before WordPress has initialized to Hook objects. * * The `$filters` parameter should be an array keyed by hook name, with values * containing either: * * - A `Hook` instance * - An array of callbacks keyed by their priorities * * Examples: * * $filters = array( * 'wp_fatal_error_handler_enabled' => array( * 10 => array( * array( * 'accepted_args' => 0, * 'function' => function() { * return false; * }, * ), * ), * ), * ); * * @param array $filters Filters to normalize. See documentation above for details. * * @return WP_Hook[] Array of normalized filters. */ public static function buildPreinitializedHooks($filters) { /** @var WP_Hook[] $normalized */ $normalized = array(); foreach ($filters as $tag => $callback_groups) { if (is_object($callback_groups) && $callback_groups instanceof self) { $normalized[$tag] = $callback_groups; continue; } $hook = new self(); // Loop through callback groups. foreach ($callback_groups as $priority => $callbacks) { // Loop through callbacks. foreach ($callbacks as $cb) { $hook->addFilter($tag, $cb['function'], $priority, $cb['accepted_args']); } } $normalized[$tag] = $hook; } return $normalized; } /** * Determines whether an offset value exists. * * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php * * @param mixed $offset An offset to check for. * * @return bool True if the offset exists, false otherwise. */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->callbacks[$offset]); } /** * Retrieves a value at a specified offset. * * @link https://www.php.net/manual/en/arrayaccess.offsetget.php * * @param mixed $offset The offset to retrieve. * * @return mixed If set, the value at the specified offset, null otherwise. */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return isset($this->callbacks[$offset]) ? $this->callbacks[$offset] : null; } /** * Sets a value at a specified offset. * * @link https://www.php.net/manual/en/arrayaccess.offsetset.php * * @param mixed $offset The offset to assign the value to. * @param mixed $value The value to set. * * @return void */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if (is_null($offset)) { $this->callbacks[] = $value; } else { $this->callbacks[$offset] = $value; } } /** * Unsets a specified offset. * * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php * * @param mixed $offset The offset to unset. * * @return void */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->callbacks[$offset]); } /** * Returns the current element. * * @link https://www.php.net/manual/en/iterator.current.php * * @return array Of callbacks at current priority. */ #[\ReturnTypeWillChange] public function current() { return current($this->callbacks); } /** * Moves forward to the next element. * * @link https://www.php.net/manual/en/iterator.next.php * * @return array Of callbacks at next priority. */ #[\ReturnTypeWillChange] public function next() { return next($this->callbacks); } /** * Returns the key of the current element. * * @link https://www.php.net/manual/en/iterator.key.php * * @return mixed Returns current priority on success, or NULL on failure */ #[\ReturnTypeWillChange] public function key() { return key($this->callbacks); } /** * Checks if current position is valid. * * @link https://www.php.net/manual/en/iterator.valid.php * * @return bool Whether the current position is valid. */ #[\ReturnTypeWillChange] public function valid() { return key($this->callbacks) !== null; } /** * Rewinds the Iterator to the first element. * * @link https://www.php.net/manual/en/iterator.rewind.php * * @return void */ #[\ReturnTypeWillChange] public function rewind() { reset($this->callbacks); } /** * Build Unique ID for storage and retrieval. * * The old way to serialize the callback caused issues and this function is the * solution. It works by checking for objects and creating a new property in * the class to keep track of the object and new objects of the same class that * need to be added. * * It also allows for the removal of actions and filters for objects after they * change class properties. It is possible to include the property $wp_filter_id * in your class and set it to "null" or a number to bypass the workaround. * However this will prevent you from adding new classes and any new classes * will overwrite the previous hook by the same class. * * Functions and static method callbacks are just returned as strings and * shouldn't have any speed penalty. * * @link https://core.trac.wordpress.org/ticket/3875 * * @since 2.2.3 * @since 5.3.0 Removed workarounds for spl_object_hash(). * `$tag` and `$priority` are no longer used, * and the function always returns a string. * @access private * * @param string $tag Unused. The name of the filter to build ID for. * @param callable $function The function to generate ID for. * @param int $priority Unused. The order in which the functions * associated with a particular action are executed. * * @return string Unique function ID for usage as array key. */ protected static function wpFilterBuildUniqueId($tag, $function, $priority) { if (is_string($function)) { return $function; } if (is_object($function)) { // Closures are currently implemented as objects. $function = array($function, ''); } else { $function = (array) $function; } if (is_object($function[0])) { // Object class calling. return spl_object_hash($function[0]) . $function[1]; } elseif (is_string($function[0])) { // Static calling. return $function[0] . '::' . $function[1]; } } }