/wp-includes/class-wp-hook.php

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