WordPress.org

Make WordPress Core

Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#27488 closed defect (bug) (duplicate)

Problem with action hook recursion causing later callbacks not to be called

Reported by: PolyMe Owned by:
Milestone: Priority: normal
Severity: normal Version: 3.8.1
Component: Plugins Keywords:
Focuses: Cc:

Description

Calling an action hook from within the first action hook callback for an action hook that contains 2+ callbacks will cause the additional callbacks not to be called for the initial action calls.

Bug was discovered using the following example code...

function add_more_meta( $meta_id, $post_id, $meta_key, $_meta_value ) {

        if ( 'initial_meta' !== $meta_key ) {
                return;
        }

        echo "Updating 'some_key'<br>\n";

        update_post_meta( $post_id, 'some_key', 'some_value' . rand( 0, 9999 ) );
        echo "'some_key' updated<br>\n";
}

function echo_meta( $meta_id, $post_id, $meta_key, $_meta_value ) {

        echo "echoing...$meta_key<br>\n";

        echo get_post_meta( $post_id, $meta_key, true ) . "<br>\n";
}

add_action( 'updated_post_meta', 'add_more_meta', 5, 4 );
add_action( 'updated_post_meta', 'echo_meta', 10, 4 );

$post_id = 1;

update_post_meta( $post_id, 'initial_meta', 'initial_meta_value' . rand( 0, 9999 ) );

In the above example, the updated_post_meta action hook is called within another updated_post_meta action hook callback.

Because the code that calls the action hooks, uses an array pointed on the global variable, the secondary action hook call, moves the pointer, so that when execution returns to the original callback, it thinks that it has completed all the callbacks.

The fix...
Instead of using the $wp_filter global array, by copying the array to a variable with function scope, this pointer is not reset by subsequent calls to the action hook.

Update the code at the bottom of the do_action() function as follows...

Before...

        reset( $wp_filter[ $tag ] );

        do {
                foreach( (array) current($wp_filter[$tag]) as $the_ )
                        if ( !is_null($the_['function']) )
                                call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));

        } while ( next($wp_filter[$tag]) !== false );

After...

        $callbacks = $wp_filter[$tag];

        reset( $wp_filter[ $tag ] );

        do {
                foreach( (array) current($callbacks) as $the_ )
                        if ( !is_null($the_['function']) )
                                call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));

        } while ( next($callbacks) !== false );

This will also need updating in the following functions...

  • apply_filters()
  • apply_filters_ref_array()
  • do_action_ref_array()

I'll now look at preparing a patch to fix this.

Change History (3)

#1 @SergeyBiryukov
5 years ago

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

Duplicate of #17817.

#2 follow-up: @Denis-de-Bernardy
5 years ago

Curious to know if this idea fixes #17817. If so, gets my vote.

#3 in reply to: ↑ 2 @SergeyBiryukov
5 years ago

Replying to Denis-de-Bernardy:

Curious to know if this idea fixes #17817. If so, gets my vote.

Looks like it was previously suggested in comment:9:ticket:17817.

Note: See TracTickets for help on using tickets.