WP_Hook

Core class used to implement action and filter hook functionality.

Defined (1)

The class is defined in the following location(s).

/wp-includes/class-wp-hook.php  
  1. final class WP_Hook implements Iterator, ArrayAccess { 
  2.  
  3. /** 
  4. * Hook callbacks. 
  5. * @since 4.7.0 
  6. * @access public 
  7. * @var array 
  8. */ 
  9. public $callbacks = array(); 
  10.  
  11. /** 
  12. * The priority keys of actively running iterations of a hook. 
  13. * @since 4.7.0 
  14. * @access private 
  15. * @var array 
  16. */ 
  17. private $iterations = array(); 
  18.  
  19. /** 
  20. * The current priority of actively running iterations of a hook. 
  21. * @since 4.7.0 
  22. * @access private 
  23. * @var array 
  24. */ 
  25. private $current_priority = array(); 
  26.  
  27. /** 
  28. * Number of levels this hook can be recursively called. 
  29. * @since 4.7.0 
  30. * @access private 
  31. * @var int 
  32. */ 
  33. private $nesting_level = 0; 
  34.  
  35. /** 
  36. * Flag for if we're current doing an action, rather than a filter. 
  37. * @since 4.7.0 
  38. * @access private 
  39. * @var bool 
  40. */ 
  41. private $doing_action = false; 
  42.  
  43. /** 
  44. * Hooks a function or method to a specific filter action. 
  45. * @since 4.7.0 
  46. * @access public 
  47. * @param string $tag The name of the filter to hook the $function_to_add callback to. 
  48. * @param callable $function_to_add The callback to be run when the filter is applied. 
  49. * @param int $priority The order in which the functions associated with a 
  50. * particular action are executed. Lower numbers correspond with 
  51. * earlier execution, and functions with the same priority are executed 
  52. * in the order in which they were added to the action. 
  53. * @param int $accepted_args The number of arguments the function accepts. 
  54. */ 
  55. public function add_filter( $tag, $function_to_add, $priority, $accepted_args ) { 
  56. $idx = _wp_filter_build_unique_id( $tag, $function_to_add, $priority ); 
  57. $priority_existed = isset( $this->callbacks[ $priority ] ); 
  58.  
  59. $this->callbacks[ $priority ][ $idx ] = array( 
  60. 'function' => $function_to_add,  
  61. 'accepted_args' => $accepted_args 
  62. ); 
  63.  
  64. // if we're adding a new priority to the list, put them back in sorted order 
  65. if ( ! $priority_existed && count( $this->callbacks ) > 1 ) { 
  66. ksort( $this->callbacks, SORT_NUMERIC ); 
  67.  
  68. if ( $this->nesting_level > 0 ) { 
  69. $this->resort_active_iterations( $priority, $priority_existed ); 
  70.  
  71. /** 
  72. * Handles reseting callback priority keys mid-iteration. 
  73. * @since 4.7.0 
  74. * @access private 
  75. * @param bool|int $new_priority Optional. The priority of the new filter being added. Default false,  
  76. * for no priority being added. 
  77. * @param bool $priority_existed Optional. Flag for whether the priority already existed before the new 
  78. * filter was added. Default false. 
  79. */ 
  80. private function resort_active_iterations( $new_priority = false, $priority_existed = false ) { 
  81. $new_priorities = array_keys( $this->callbacks ); 
  82.  
  83. // If there are no remaining hooks, clear out all running iterations. 
  84. if ( ! $new_priorities ) { 
  85. foreach ( $this->iterations as $index => $iteration ) { 
  86. $this->iterations[ $index ] = $new_priorities; 
  87. return; 
  88.  
  89. $min = min( $new_priorities ); 
  90. foreach ( $this->iterations as $index => &$iteration ) { 
  91. $current = current( $iteration ); 
  92. // If we're already at the end of this iteration, just leave the array pointer where it is. 
  93. if ( false === $current ) { 
  94. continue; 
  95.  
  96. $iteration = $new_priorities; 
  97.  
  98. if ( $current < $min ) { 
  99. array_unshift( $iteration, $current ); 
  100. continue; 
  101.  
  102. while ( current( $iteration ) < $current ) { 
  103. if ( false === next( $iteration ) ) { 
  104. break; 
  105.  
  106. // If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority... 
  107. if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) { 
  108. /** 
  109. * ... and the new priority is the same as what $this->iterations thinks is the previous 
  110. * priority, we need to move back to it. 
  111. */ 
  112.  
  113. if ( false === current( $iteration ) ) { 
  114. // If we've already moved off the end of the array, go back to the last element. 
  115. $prev = end( $iteration ); 
  116. } else { 
  117. // Otherwise, just go back to the previous element. 
  118. $prev = prev( $iteration ); 
  119. if ( false === $prev ) { 
  120. // Start of the array. Reset, and go about our day. 
  121. reset( $iteration ); 
  122. } elseif ( $new_priority !== $prev ) { 
  123. // Previous wasn't the same. Move forward again. 
  124. next( $iteration ); 
  125. unset( $iteration ); 
  126.  
  127. /** 
  128. * Unhooks a function or method from a specific filter action. 
  129. * @since 4.7.0 
  130. * @access public 
  131. * @param string $tag The filter hook to which the function to be removed is hooked. Used 
  132. * for building the callback ID when SPL is not available. 
  133. * @param callable $function_to_remove The callback to be removed from running when the filter is applied. 
  134. * @param int $priority The exact priority used when adding the original filter callback. 
  135. * @return bool Whether the callback existed before it was removed. 
  136. */ 
  137. public function remove_filter( $tag, $function_to_remove, $priority ) { 
  138. $function_key = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority ); 
  139.  
  140. $exists = isset( $this->callbacks[ $priority ][ $function_key ] ); 
  141. if ( $exists ) { 
  142. unset( $this->callbacks[ $priority ][ $function_key ] ); 
  143. if ( ! $this->callbacks[ $priority ] ) { 
  144. unset( $this->callbacks[ $priority ] ); 
  145. if ( $this->nesting_level > 0 ) { 
  146. $this->resort_active_iterations(); 
  147. return $exists; 
  148.  
  149. /** 
  150. * Checks if a specific action has been registered for this hook. 
  151. * @since 4.7.0 
  152. * @access public 
  153. * @param callable|bool $function_to_check Optional. The callback to check for. Default false. 
  154. * @param string $tag Optional. The name of the filter hook. Used for building 
  155. * the callback ID when SPL is not available. Default empty. 
  156. * @return bool|int The priority of that hook is returned, or false if the function is not attached. 
  157. */ 
  158. public function has_filter( $tag = '', $function_to_check = false ) { 
  159. if ( false === $function_to_check ) { 
  160. return $this->has_filters(); 
  161.  
  162. $function_key = _wp_filter_build_unique_id( $tag, $function_to_check, false ); 
  163. if ( ! $function_key ) { 
  164. return false; 
  165.  
  166. foreach ( $this->callbacks as $priority => $callbacks ) { 
  167. if ( isset( $callbacks[ $function_key ] ) ) { 
  168. return $priority; 
  169.  
  170. return false; 
  171.  
  172. /** 
  173. * Checks if any callbacks have been registered for this hook. 
  174. * @since 4.7.0 
  175. * @access public 
  176. * @return bool True if callbacks have been registered for the current hook, otherwise false. 
  177. */ 
  178. public function has_filters() { 
  179. foreach ( $this->callbacks as $callbacks ) { 
  180. if ( $callbacks ) { 
  181. return true; 
  182. return false; 
  183.  
  184. /** 
  185. * Removes all callbacks from the current filter. 
  186. * @since 4.7.0 
  187. * @access public 
  188. * @param int|bool $priority Optional. The priority number to remove. Default false. 
  189. */ 
  190. public function remove_all_filters( $priority = false ) { 
  191. if ( ! $this->callbacks ) { 
  192. return; 
  193.  
  194. if ( false === $priority ) { 
  195. $this->callbacks = array(); 
  196. } else if ( isset( $this->callbacks[ $priority ] ) ) { 
  197. unset( $this->callbacks[ $priority ] ); 
  198.  
  199. if ( $this->nesting_level > 0 ) { 
  200. $this->resort_active_iterations(); 
  201.  
  202. /** 
  203. * Calls the callback functions added to a filter hook. 
  204. * @since 4.7.0 
  205. * @access public 
  206. * @param mixed $value The value to filter. 
  207. * @param array $args Arguments to pass to callbacks. 
  208. * @return mixed The filtered value after all hooked functions are applied to it. 
  209. */ 
  210. public function apply_filters( $value, $args ) { 
  211. if ( ! $this->callbacks ) { 
  212. return $value; 
  213.  
  214. $nesting_level = $this->nesting_level++; 
  215.  
  216. $this->iterations[ $nesting_level ] = array_keys( $this->callbacks ); 
  217. $num_args = count( $args ); 
  218.  
  219. do { 
  220. $this->current_priority[ $nesting_level ] = $priority = current( $this->iterations[ $nesting_level ] ); 
  221.  
  222. foreach ( $this->callbacks[ $priority ] as $the_ ) { 
  223. if( ! $this->doing_action ) { 
  224. $args[ 0 ] = $value; 
  225.  
  226. // Avoid the array_slice if possible. 
  227. if ( $the_['accepted_args'] == 0 ) { 
  228. $value = call_user_func_array( $the_['function'], array() ); 
  229. } elseif ( $the_['accepted_args'] >= $num_args ) { 
  230. $value = call_user_func_array( $the_['function'], $args ); 
  231. } else { 
  232. $value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int)$the_['accepted_args'] ) ); 
  233. } while ( false !== next( $this->iterations[ $nesting_level ] ) ); 
  234.  
  235. unset( $this->iterations[ $nesting_level ] ); 
  236. unset( $this->current_priority[ $nesting_level ] ); 
  237.  
  238. $this->nesting_level--; 
  239.  
  240. return $value; 
  241.  
  242. /** 
  243. * Executes the callback functions hooked on a specific action hook. 
  244. * @since 4.7.0 
  245. * @access public 
  246. * @param mixed $args Arguments to pass to the hook callbacks. 
  247. */ 
  248. public function do_action( $args ) { 
  249. $this->doing_action = true; 
  250. $this->apply_filters( '', $args ); 
  251.  
  252. // If there are recursive calls to the current action, we haven't finished it until we get to the last one. 
  253. if ( ! $this->nesting_level ) { 
  254. $this->doing_action = false; 
  255.  
  256. /** 
  257. * Processes the functions hooked into the 'all' hook. 
  258. * @since 4.7.0 
  259. * @access public 
  260. * @param array $args Arguments to pass to the hook callbacks. Passed by reference. 
  261. */ 
  262. public function do_all_hook( &$args ) { 
  263. $nesting_level = $this->nesting_level++; 
  264. $this->iterations[ $nesting_level ] = array_keys( $this->callbacks ); 
  265.  
  266. do { 
  267. $priority = current( $this->iterations[ $nesting_level ] ); 
  268. foreach ( $this->callbacks[ $priority ] as $the_ ) { 
  269. call_user_func_array( $the_['function'], $args ); 
  270. } while ( false !== next( $this->iterations[ $nesting_level ] ) ); 
  271.  
  272. unset( $this->iterations[ $nesting_level ] ); 
  273. $this->nesting_level--; 
  274.  
  275. /** 
  276. * Return the current priority level of the currently running iteration of the hook. 
  277. * @since 4.7.0 
  278. * @access public 
  279. * @return int|false If the hook is running, return the current priority level. If it isn't running, return false. 
  280. */ 
  281. public function current_priority() { 
  282. if ( false === current( $this->iterations ) ) { 
  283. return false; 
  284.  
  285. return current( current( $this->iterations ) ); 
  286.  
  287. /** 
  288. * Normalizes filters set up before WordPress has initialized to WP_Hook objects. 
  289. * @since 4.7.0 
  290. * @access public 
  291. * @static 
  292. * @param array $filters Filters to normalize. 
  293. * @return WP_Hook[] Array of normalized filters. 
  294. */ 
  295. public static function build_preinitialized_hooks( $filters ) { 
  296. /** @var WP_Hook[] $normalized */ 
  297. $normalized = array(); 
  298.  
  299. foreach ( $filters as $tag => $callback_groups ) { 
  300. if ( is_object( $callback_groups ) && $callback_groups instanceof WP_Hook ) { 
  301. $normalized[ $tag ] = $callback_groups; 
  302. continue; 
  303. $hook = new WP_Hook(); 
  304.  
  305. // Loop through callback groups. 
  306. foreach ( $callback_groups as $priority => $callbacks ) { 
  307.  
  308. // Loop through callbacks. 
  309. foreach ( $callbacks as $cb ) { 
  310. $hook->add_filter( $tag, $cb['function'], $priority, $cb['accepted_args'] ); 
  311. $normalized[ $tag ] = $hook; 
  312. return $normalized; 
  313.  
  314. /** 
  315. * Determines whether an offset value exists. 
  316. * @since 4.7.0 
  317. * @access public 
  318. * @link http://php.net/manual/en/arrayaccess.offsetexists.php 
  319. * @param mixed $offset An offset to check for. 
  320. * @return bool True if the offset exists, false otherwise. 
  321. */ 
  322. public function offsetExists( $offset ) { 
  323. return isset( $this->callbacks[ $offset ] ); 
  324.  
  325. /** 
  326. * Retrieves a value at a specified offset. 
  327. * @since 4.7.0 
  328. * @access public 
  329. * @link http://php.net/manual/en/arrayaccess.offsetget.php 
  330. * @param mixed $offset The offset to retrieve. 
  331. * @return mixed If set, the value at the specified offset, null otherwise. 
  332. */ 
  333. public function offsetGet( $offset ) { 
  334. return isset( $this->callbacks[ $offset ] ) ? $this->callbacks[ $offset ] : null; 
  335.  
  336. /** 
  337. * Sets a value at a specified offset. 
  338. * @since 4.7.0 
  339. * @access public 
  340. * @link http://php.net/manual/en/arrayaccess.offsetset.php 
  341. * @param mixed $offset The offset to assign the value to. 
  342. * @param mixed $value The value to set. 
  343. */ 
  344. public function offsetSet( $offset, $value ) { 
  345. if ( is_null( $offset ) ) { 
  346. $this->callbacks[] = $value; 
  347. } else { 
  348. $this->callbacks[ $offset ] = $value; 
  349.  
  350. /** 
  351. * Unsets a specified offset. 
  352. * @since 4.7.0 
  353. * @access public 
  354. * @link http://php.net/manual/en/arrayaccess.offsetunset.php 
  355. * @param mixed $offset The offset to unset. 
  356. */ 
  357. public function offsetUnset( $offset ) { 
  358. unset( $this->callbacks[ $offset ] ); 
  359.  
  360. /** 
  361. * Returns the current element. 
  362. * @since 4.7.0 
  363. * @access public 
  364. * @link http://php.net/manual/en/iterator.current.php 
  365. * @return array Of callbacks at current priority. 
  366. */ 
  367. public function current() { 
  368. return current( $this->callbacks ); 
  369.  
  370. /** 
  371. * Moves forward to the next element. 
  372. * @since 4.7.0 
  373. * @access public 
  374. * @link http://php.net/manual/en/iterator.next.php 
  375. * @return array Of callbacks at next priority. 
  376. */ 
  377. public function next() { 
  378. return next( $this->callbacks ); 
  379.  
  380. /** 
  381. * Returns the key of the current element. 
  382. * @since 4.7.0 
  383. * @access public 
  384. * @link http://php.net/manual/en/iterator.key.php 
  385. * @return mixed Returns current priority on success, or NULL on failure 
  386. */ 
  387. public function key() { 
  388. return key( $this->callbacks ); 
  389.  
  390. /** 
  391. * Checks if current position is valid. 
  392. * @since 4.7.0 
  393. * @access public 
  394. * @link http://php.net/manual/en/iterator.valid.php 
  395. * @return boolean 
  396. */ 
  397. public function valid() { 
  398. return key( $this->callbacks ) !== null; 
  399.  
  400. /** 
  401. * Rewinds the Iterator to the first element. 
  402. * @since 4.7.0 
  403. * @access public 
  404. * @link http://php.net/manual/en/iterator.rewind.php 
  405. */ 
  406. public function rewind() { 
  407. reset( $this->callbacks ); 
  408.