WordPress.org

Make WordPress Core

Opened 6 years ago

Closed 6 years ago

#26239 closed defect (bug) (duplicate)

$wp_filter global iteration issue with hook recursion

Reported by: sc0ttkclark Owned by:
Milestone: Priority: normal
Severity: normal Version: 3.7.1
Component: Plugins Keywords:
Focuses: Cc:

Description

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.

Attachments (1)

26239.patch (3.1 KB) - added by rzen 6 years ago.
Localizes $wp_filter global in each hook function for improved recursion.

Download all attachments as: .zip

Change History (3)

#1 @jchristopher
6 years ago

  • Cc jonathan@… added

#2 @SergeyBiryukov
6 years ago

  • Milestone Awaiting Review deleted
  • Resolution set to duplicate
  • Status changed from new to closed

Duplicate of #17817.

@rzen
6 years ago

Localizes $wp_filter global in each hook function for improved recursion.

Note: See TracTickets for help on using tickets.