[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