Ticket #17817: 17817.10.diff
File 17817.10.diff, 18.7 KB (added by , 9 years ago) |
---|
-
src/wp-includes/plugin.php
20 20 */ 21 21 22 22 // Initialize the filter globals. 23 global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;23 require( ABSPATH . WPINC . '/class-wp-hook.php' ); 24 24 25 if ( ! isset( $wp_filter ) ) 25 /** @var WP_Hook[] $wp_filter */ 26 global $wp_filter, $wp_actions, $wp_current_filter; 27 28 if ( $wp_filter ) { 29 $wp_filter = WP_Hook::build_preinitialized_hooks( $wp_filter ); 30 } else { 26 31 $wp_filter = array(); 32 } 27 33 28 34 if ( ! isset( $wp_actions ) ) 29 35 $wp_actions = array(); 30 36 31 if ( ! isset( $merged_filters ) )32 $merged_filters = array();33 34 37 if ( ! isset( $wp_current_filter ) ) 35 38 $wp_current_filter = array(); 36 39 … … 87 90 * @since 0.71 88 91 * 89 92 * @global array $wp_filter A multidimensional array of all hooks and the callbacks hooked to them. 90 * @global array $merged_filters Tracks the tags that need to be merged for later. If the hook is added,91 * it doesn't need to run through that process.92 93 * 93 94 * @param string $tag The name of the filter to hook the $function_to_add callback to. 94 95 * @param callable $function_to_add The callback to be run when the filter is applied. … … 101 102 * @return true 102 103 */ 103 104 function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { 104 global $wp_filter , $merged_filters;105 106 $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);107 $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);108 unset( $merged_filters[ $tag ]);105 global $wp_filter; 106 if ( ! isset( $wp_filter[ $tag ] ) ) { 107 $wp_filter[ $tag ] = new WP_Hook(); 108 } 109 $wp_filter[ $tag ]->add_filter( $tag, $function_to_add, $priority, $accepted_args ); 109 110 return true; 110 111 } 111 112 … … 126 127 * return value. 127 128 */ 128 129 function has_filter($tag, $function_to_check = false) { 129 // Don't reset the internal array pointer 130 $wp_filter = $GLOBALS['wp_filter']; 130 global $wp_filter; 131 131 132 $has = ! empty( $wp_filter[ $tag ] ); 133 134 // Make sure at least one priority has a filter callback 135 if ( $has ) { 136 $exists = false; 137 foreach ( $wp_filter[ $tag ] as $callbacks ) { 138 if ( ! empty( $callbacks ) ) { 139 $exists = true; 140 break; 141 } 142 } 143 144 if ( ! $exists ) { 145 $has = false; 146 } 147 } 148 149 if ( false === $function_to_check || false === $has ) 150 return $has; 151 152 if ( !$idx = _wp_filter_build_unique_id($tag, $function_to_check, false) ) 132 if ( ! isset( $wp_filter[ $tag ] ) ) { 153 133 return false; 154 155 foreach ( (array) array_keys($wp_filter[$tag]) as $priority ) {156 if ( isset($wp_filter[$tag][$priority][$idx]) )157 return $priority;158 134 } 159 135 160 return false;136 return $wp_filter[ $tag ]->has_filter( $tag, $function_to_check ); 161 137 } 162 138 163 139 /** … … 188 164 * @since 0.71 189 165 * 190 166 * @global array $wp_filter Stores all of the filters. 191 * @global array $merged_filters Merges the filter hooks using this function.192 167 * @global array $wp_current_filter Stores the list of current filters with the current one last. 193 168 * 194 169 * @param string $tag The name of the filter hook. … … 197 172 * @return mixed The filtered value after all hooked functions are applied to it. 198 173 */ 199 174 function apply_filters( $tag, $value ) { 200 global $wp_filter, $ merged_filters, $wp_current_filter;175 global $wp_filter, $wp_current_filter; 201 176 202 177 $args = array(); 203 178 … … 217 192 if ( !isset($wp_filter['all']) ) 218 193 $wp_current_filter[] = $tag; 219 194 220 // Sort.221 if ( !isset( $merged_filters[ $tag ] ) ) {222 ksort($wp_filter[$tag]);223 $merged_filters[ $tag ] = true;224 }225 226 reset( $wp_filter[ $tag ] );227 228 195 if ( empty($args) ) 229 196 $args = func_get_args(); 230 197 231 do { 232 foreach ( (array) current($wp_filter[$tag]) as $the_ ) 233 if ( !is_null($the_['function']) ){ 234 $args[1] = $value; 235 $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args'])); 236 } 198 // don't pass the tag name to WP_Hook 199 array_shift( $args ); 237 200 238 } while ( next($wp_filter[$tag]) !== false);201 $filtered = $wp_filter[ $tag ]->apply_filters( $value, $args ); 239 202 240 203 array_pop( $wp_current_filter ); 241 204 242 return $ value;205 return $filtered; 243 206 } 244 207 245 208 /** … … 251 214 * functions hooked to `$tag` are supplied using an array. 252 215 * 253 216 * @global array $wp_filter Stores all of the filters 254 * @global array $merged_filters Merges the filter hooks using this function.255 217 * @global array $wp_current_filter Stores the list of current filters with the current one last 256 218 * 257 219 * @param string $tag The name of the filter hook. … … 259 221 * @return mixed The filtered value after all hooked functions are applied to it. 260 222 */ 261 223 function apply_filters_ref_array($tag, $args) { 262 global $wp_filter, $ merged_filters, $wp_current_filter;224 global $wp_filter, $wp_current_filter; 263 225 264 226 // Do 'all' actions first 265 227 if ( isset($wp_filter['all']) ) { … … 277 239 if ( !isset($wp_filter['all']) ) 278 240 $wp_current_filter[] = $tag; 279 241 280 // Sort 281 if ( !isset( $merged_filters[ $tag ] ) ) { 282 ksort($wp_filter[$tag]); 283 $merged_filters[ $tag ] = true; 284 } 242 $filtered = $wp_filter[ $tag ]->apply_filters( $args[0], $args ); 285 243 286 reset( $wp_filter[ $tag ] );287 288 do {289 foreach ( (array) current($wp_filter[$tag]) as $the_ )290 if ( !is_null($the_['function']) )291 $args[0] = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));292 293 } while ( next($wp_filter[$tag]) !== false );294 295 244 array_pop( $wp_current_filter ); 296 245 297 return $ args[0];246 return $filtered; 298 247 } 299 248 300 249 /** … … 319 268 * @return bool Whether the function existed before it was removed. 320 269 */ 321 270 function remove_filter( $tag, $function_to_remove, $priority = 10 ) { 322 $function_to_remove = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority );271 global $wp_filter; 323 272 324 $r = isset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] ); 325 326 if ( true === $r ) { 327 unset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] ); 328 if ( empty( $GLOBALS['wp_filter'][ $tag ][ $priority ] ) ) { 329 unset( $GLOBALS['wp_filter'][ $tag ][ $priority ] ); 273 $r = false; 274 if ( isset( $wp_filter[ $tag ] ) ) { 275 $r = $wp_filter[ $tag ]->remove_filter( $tag, $function_to_remove, $priority ); 276 if ( ! $wp_filter[ $tag ]->callbacks ) { 277 unset( $wp_filter[ $tag ] ); 330 278 } 331 if ( empty( $GLOBALS['wp_filter'][ $tag ] ) ) {332 $GLOBALS['wp_filter'][ $tag ] = array();333 }334 unset( $GLOBALS['merged_filters'][ $tag ] );335 279 } 336 280 337 281 return $r; … … 342 286 * 343 287 * @since 2.7.0 344 288 * 345 * @global array $wp_filter Stores all of the filters 346 * @global array $merged_filters Merges the filter hooks using this function. 289 * @global array $wp_filter Stores all of the filters 347 290 * 348 291 * @param string $tag The filter to remove hooks from. 349 292 * @param int|bool $priority Optional. The priority number to remove. Default false. 350 293 * @return true True when finished. 351 294 */ 352 295 function remove_all_filters( $tag, $priority = false ) { 353 global $wp_filter , $merged_filters;296 global $wp_filter; 354 297 355 298 if ( isset( $wp_filter[ $tag ]) ) { 356 if ( false === $priority ) { 357 $wp_filter[ $tag ] = array(); 358 } elseif ( isset( $wp_filter[ $tag ][ $priority ] ) ) { 359 $wp_filter[ $tag ][ $priority ] = array(); 299 $wp_filter[ $tag ]->remove_all_filters( $priority ); 300 if ( ! $wp_filter[ $tag ]->has_filters() ) { 301 unset( $wp_filter[ $tag ] ); 360 302 } 361 303 } 362 304 363 unset( $merged_filters[ $tag ] );364 365 305 return true; 366 306 } 367 307 … … 480 420 * functions hooked to the action. Default empty. 481 421 */ 482 422 function do_action($tag, $arg = '') { 483 global $wp_filter, $wp_actions, $ merged_filters, $wp_current_filter;423 global $wp_filter, $wp_actions, $wp_current_filter; 484 424 485 425 if ( ! isset($wp_actions[$tag]) ) 486 426 $wp_actions[$tag] = 1; … … 511 451 for ( $a = 2, $num = func_num_args(); $a < $num; $a++ ) 512 452 $args[] = func_get_arg($a); 513 453 514 // Sort 515 if ( !isset( $merged_filters[ $tag ] ) ) { 516 ksort($wp_filter[$tag]); 517 $merged_filters[ $tag ] = true; 518 } 454 $wp_filter[ $tag ]->do_action( $args ); 519 455 520 reset( $wp_filter[ $tag ] );521 522 do {523 foreach ( (array) current($wp_filter[$tag]) as $the_ )524 if ( !is_null($the_['function']) )525 call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));526 527 } while ( next($wp_filter[$tag]) !== false );528 529 456 array_pop($wp_current_filter); 530 457 } 531 458 … … 564 491 * @param array $args The arguments supplied to the functions hooked to `$tag`. 565 492 */ 566 493 function do_action_ref_array($tag, $args) { 567 global $wp_filter, $wp_actions, $ merged_filters, $wp_current_filter;494 global $wp_filter, $wp_actions, $wp_current_filter; 568 495 569 496 if ( ! isset($wp_actions[$tag]) ) 570 497 $wp_actions[$tag] = 1; … … 587 514 if ( !isset($wp_filter['all']) ) 588 515 $wp_current_filter[] = $tag; 589 516 590 // Sort 591 if ( !isset( $merged_filters[ $tag ] ) ) { 592 ksort($wp_filter[$tag]); 593 $merged_filters[ $tag ] = true; 594 } 517 $wp_filter[ $tag ]->do_action( $args ); 595 518 596 reset( $wp_filter[ $tag ] );597 598 do {599 foreach ( (array) current($wp_filter[$tag]) as $the_ )600 if ( !is_null($the_['function']) )601 call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));602 603 } while ( next($wp_filter[$tag]) !== false );604 605 519 array_pop($wp_current_filter); 606 520 } 607 521 … … 865 779 function _wp_call_all_hook($args) { 866 780 global $wp_filter; 867 781 868 reset( $wp_filter['all'] ); 869 do { 870 foreach ( (array) current($wp_filter['all']) as $the_ ) 871 if ( !is_null($the_['function']) ) 872 call_user_func_array($the_['function'], $args); 873 874 } while ( next($wp_filter['all']) !== false ); 782 $wp_filter['all']->do_all_hook( $args ); 875 783 } 876 784 877 785 /** -
tests/phpunit/includes/functions.php
18 18 19 19 // For adding hooks before loading WP 20 20 function tests_add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) { 21 global $wp_filter , $merged_filters;21 global $wp_filter; 22 22 23 23 $idx = _test_filter_build_unique_id($tag, $function_to_add, $priority); 24 24 $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args); 25 unset( $merged_filters[ $tag ] );26 25 return true; 27 26 } 28 27 29 28 function _test_filter_build_unique_id($tag, $function, $priority) { 30 global $wp_filter;31 static $filter_id_count = 0;32 33 29 if ( is_string($function) ) 34 30 return $function; 35 31 -
tests/phpunit/includes/testcase.php
218 218 * @return void 219 219 */ 220 220 protected function _backup_hooks() { 221 $globals = array( ' merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );221 $globals = array( 'wp_actions', 'wp_current_filter' ); 222 222 foreach ( $globals as $key ) { 223 223 self::$hooks_saved[ $key ] = $GLOBALS[ $key ]; 224 224 } 225 self::$hooks_saved['wp_filter'] = array(); 226 foreach ( $GLOBALS['wp_filter'] as $hook_name => $hook_object ) { 227 self::$hooks_saved['wp_filter'][ $hook_name ] = clone $hook_object; 228 } 225 229 } 226 230 227 231 /** … … 235 239 * @return void 236 240 */ 237 241 protected function _restore_hooks() { 238 $globals = array( ' merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );242 $globals = array( 'wp_actions', 'wp_current_filter' ); 239 243 foreach ( $globals as $key ) { 240 244 if ( isset( self::$hooks_saved[ $key ] ) ) { 241 245 $GLOBALS[ $key ] = self::$hooks_saved[ $key ]; 242 246 } 243 247 } 248 if ( isset( self::$hooks_saved['wp_filter'] ) ) { 249 $GLOBALS['wp_filter'] = array(); 250 foreach ( self::$hooks_saved['wp_filter'] as $hook_name => $hook_object ) { 251 $GLOBALS['wp_filter'][ $hook_name ] = clone $hook_object; 252 } 253 } 244 254 } 245 255 246 256 function flush_cache() { -
tests/phpunit/tests/actions.php
114 114 $this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) ); 115 115 } 116 116 117 /** 118 * Test that multiple callbacks receive the correct number of args even when the number 119 * is less than, or greater than previous hooks. 120 * 121 * @see https://core.trac.wordpress.org/ticket/17817#comment:72 122 * @ticket 17817 123 */ 124 function test_action_args_3() { 125 $a1 = new MockAction(); 126 $a2 = new MockAction(); 127 $a3 = new MockAction(); 128 $tag = rand_str(); 129 $val1 = rand_str(); 130 $val2 = rand_str(); 131 132 // a1 accepts two arguments, a2 doesn't, a3 accepts two arguments 133 add_action( $tag, array( &$a1, 'action' ), 10, 2 ); 134 add_action( $tag, array( &$a2, 'action' ) ); 135 add_action( $tag, array( &$a3, 'action' ), 10, 2 ); 136 // call the action with two arguments 137 do_action( $tag, $val1, $val2 ); 138 139 $call_count = $a1->get_call_count(); 140 // a1 should be called with both args 141 $this->assertEquals( 1, $call_count ); 142 $argsvar1 = $a1->get_args(); 143 $this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar1 ) ); 144 145 // a2 should be called with one only 146 $this->assertEquals( 1, $a2->get_call_count() ); 147 $argsvar2 = $a2->get_args(); 148 $this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) ); 149 150 // a3 should be called with both args 151 $this->assertEquals( 1, $a3->get_call_count() ); 152 $argsvar3 = $a3->get_args(); 153 $this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar3 ) ); 154 } 155 117 156 function test_action_priority() { 118 157 $a = new MockAction(); 119 158 $tag = rand_str(); … … 258 297 } 259 298 260 299 /** 300 * @ticket 17817 301 */ 302 function test_action_recursion() { 303 $tag = rand_str(); 304 $a = new MockAction(); 305 $b = new MockAction(); 306 307 add_action( $tag, array( $a, 'action' ), 11, 1 ); 308 add_action( $tag, array( $b, 'action' ), 13, 1 ); 309 add_action( $tag, array( $this, 'action_that_causes_recursion' ), 12, 1 ); 310 do_action( $tag, $tag ); 311 312 $this->assertEquals( 2, $a->get_call_count(), 'recursive actions should call all callbacks with earlier priority' ); 313 $this->assertEquals( 2, $b->get_call_count(), 'recursive actions should call callbacks with later priority' ); 314 } 315 316 function action_that_causes_recursion( $tag ) { 317 static $recursing = false; 318 if ( ! $recursing ) { 319 $recursing = true; 320 do_action( $tag, $tag ); 321 } 322 $recursing = false; 323 } 324 325 /** 326 * @ticket 9968 327 * @ticket 17817 328 */ 329 function test_action_callback_manipulation_while_running() { 330 $tag = rand_str(); 331 $a = new MockAction(); 332 $b = new MockAction(); 333 $c = new MockAction(); 334 $d = new MockAction(); 335 $e = new MockAction(); 336 337 add_action( $tag, array( $a, 'action' ), 11, 2 ); 338 add_action( $tag, array( $this, 'action_that_manipulates_a_running_hook' ), 12, 2 ); 339 add_action( $tag, array( $b, 'action' ), 12, 2 ); 340 341 do_action( $tag, $tag, array( $a, $b, $c, $d, $e ) ); 342 do_action( $tag, $tag, array( $a, $b, $c, $d, $e ) ); 343 344 $this->assertEquals( 2, $a->get_call_count(), 'callbacks should run unless otherwise instructed' ); 345 $this->assertEquals( 1, $b->get_call_count(), 'callback removed by same priority callback should still get called' ); 346 $this->assertEquals( 1, $c->get_call_count(), 'callback added by same priority callback should not get called' ); 347 $this->assertEquals( 2, $d->get_call_count(), 'callback added by earlier priority callback should get called' ); 348 $this->assertEquals( 1, $e->get_call_count(), 'callback added by later priority callback should not get called' ); 349 } 350 351 function action_that_manipulates_a_running_hook( $tag, $mocks ) { 352 remove_action( $tag, array( $mocks[ 1 ], 'action' ), 12, 2 ); 353 add_action( $tag, array( $mocks[ 2 ], 'action' ), 12, 2 ); 354 add_action( $tag, array( $mocks[ 3 ], 'action' ), 13, 2 ); 355 add_action( $tag, array( $mocks[ 4 ], 'action' ), 10, 2 ); 356 } 357 358 /** 359 * @ticket 17817 360 * 361 * This specificaly addresses the concern raised at 362 * https://core.trac.wordpress.org/ticket/17817#comment:52 363 */ 364 function test_remove_anonymous_callback() { 365 $tag = rand_str(); 366 $a = new MockAction(); 367 add_action( $tag, array( $a, 'action' ), 12, 1 ); 368 $this->assertTrue( has_action( $tag ) ); 369 370 $hook = $GLOBALS['wp_filter'][ $tag ]; 371 372 // From http://wordpress.stackexchange.com/a/57088/6445 373 foreach ( $hook as $priority => $filter ) { 374 foreach ( $filter as $identifier => $function ) { 375 if ( is_array( $function ) 376 && is_a( $function['function'][ 0 ], 'MockAction' ) 377 && 'action' === $function['function'][ 1 ] 378 ) { 379 remove_filter( 380 $tag, 381 array( $function['function'][ 0 ], 'action' ), 382 $priority 383 ); 384 } 385 } 386 } 387 388 $this->assertFalse( has_action( $tag ) ); 389 } 390 391 392 /** 393 * Test the ArrayAccess methods of WP_Hook 394 * 395 * @ticket 17817 396 */ 397 function test_array_access_of_wp_filter_global() { 398 global $wp_filter; 399 $tag = rand_str(); 400 401 add_action( $tag, '__return_null', 11, 1 ); 402 403 $this->assertTrue( isset( $wp_filter[ $tag ][ 11 ] ) ); 404 $this->assertArrayHasKey( '__return_null', $wp_filter[ $tag ][ 11 ] ); 405 406 unset( $wp_filter[ $tag ][ 11 ] ); 407 $this->assertFalse( has_action( $tag, '__return_null' ) ); 408 409 $wp_filter[ $tag ][ 11 ] = array( '__return_null' => array( 'function' => '__return_null', 'accepted_args' => 1 ) ); 410 $this->assertEquals( 11, has_action( $tag, '__return_null' ) ); 411 } 412 413 /** 261 414 * Make sure current_action() behaves as current_filter() 262 415 * 263 416 * @ticket 14994 -
tests/phpunit/tests/filters.php
294 294 remove_all_filters( $tag, 12 ); 295 295 $this->assertFalse( has_filter( $tag ) ); 296 296 } 297 298 /**299 * @ticket 29070300 */301 function test_has_filter_doesnt_reset_wp_filter() {302 add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 1 );303 add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 2 );304 add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 3 );305 add_action( 'action_test_has_filter_doesnt_reset_wp_filter', array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ), 4 );306 307 do_action( 'action_test_has_filter_doesnt_reset_wp_filter' );308 }309 function _action_test_has_filter_doesnt_reset_wp_filter() {310 global $wp_filter;311 312 has_action( 'action_test_has_filter_doesnt_reset_wp_filter', '_function_that_doesnt_exist' );313 314 $filters = current( $wp_filter['action_test_has_filter_doesnt_reset_wp_filter'] );315 $the_ = current( $filters );316 $this->assertEquals( $the_['function'], array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ) );317 }318 297 }