[wp-trac] [WordPress Trac] #26239: $wp_filter global iteration issue with hook recursion

WordPress Trac noreply at wordpress.org
Mon Nov 25 17:02:29 UTC 2013


#26239: $wp_filter global iteration issue with hook recursion
--------------------------+-----------------------------
 Reporter:  sc0ttkclark   |      Owner:
     Type:  defect (bug)  |     Status:  new
 Priority:  normal        |  Milestone:  Awaiting Review
Component:  Plugins       |    Version:  3.7.1
 Severity:  normal        |   Keywords:
--------------------------+-----------------------------
 do_action uses the global $wp_filter[ $tag ] for iteration in reset(),
 current(), and next().

 This works as expected for most cases, however, when you call the same
 action, while an action of the same name was being iterated, an unexpected
 side-effect happens.

 Let's take this real world example, used in BadgeOS currently, which led
 to the discovery of this particular problem, where we have actions set to
 the same hook at different priorities:

 {{{
 // Simplified for readability
 add_action( 'badgeos_award_achievement',
 'badgeos_maybe_award_additional_achievements_to_user', 10 );
 add_action( 'badgeos_award_achievement', 'badgeos_award_user_points', 999
 );
 }}}

 What we expect to happen from the code above is what we get in most cases.
 do_action gets called and both of our functions get called as a result, in
 order of priority. However, inside the
 "badgeos_maybe_award_additional_achievements_to_user" function, we may
 choose to award additional achievements for a number of reasons based on
 specific rules related to that achievement. So, at that point, we call our
 awarding function, which then triggers a new do_action on that same action
 name "badgeos_award_achievement".

 All is great up to this point, the actions are hooking in everywhere we
 are expecting. But.. wait..... look at that! Our priority 999 action never
 runs for the original action that was run, only for the "sub-actions" that
 were run. This is because our action iterates off of the $wp_filter global
 itself, which gets reset() and next() looped through at different levels,
 but the main action continues to work off of the global, even though its
 been reset and iterated through separately.

 The solution to this issue is a pretty simple one, we just set a localized
 variable $the_filters (or similar name), to $wp_filter[ $tag ], and
 iterate off of that instead. This global is actually iterated in 5
 different places in core, through: do_action, do_action_ref_array,
 apply_filters, apply_filters_ref_array, and _wp_call_all_hook

 This was a messy bug to track down, but now that we've found it, we can
 prevent days of pain and suffering across the world through a simple patch
 :)

 @rzen will be providing a patch shortly as I am in-between workflows for
 my core contributions, should have my new environment setup and ready
 soon.

--
Ticket URL: <http://core.trac.wordpress.org/ticket/26239>
WordPress Trac <http://core.trac.wordpress.org/>
WordPress blogging software


More information about the wp-trac mailing list