Make WordPress Core

Opened 4 months ago

Closed 3 months ago

Last modified 3 months ago

#60434 closed enhancement (invalid)

Implementing a core solution for the infinite loop issue on `save_post` and `save_post_{$post->post_type}` action hooks.

Reported by: gerardreches's profile gerardreches Owned by:
Milestone: Priority: normal
Severity: minor Version:
Component: Posts, Post Types Keywords: dev-feedback
Focuses: Cc:

Description

We all find ourselves having to use a manual fix when using wp_update_post() in a save_post or save_post_{$post->post_type} callback to avoid an infinite loop.

https://developer.wordpress.org/reference/hooks/save_post/
https://developer.wordpress.org/reference/hooks/save_post_post-post_type/

The fix consists on removing the current action before updating the post, and then adding it again. But this only solves the problem for the current callback, as other actions that may have been added to the same hook may be firing twice.

I thought of a way to prevent this. This code would be contained in your save_post action callback.

<?php
/**
 * Prevent infinite loop and repeated actions.
 */

global $wp_actions, $wp_filters, $wp_filter;

$actions = $wp_actions;
$filters = $wp_filters;
$filter  = $wp_filter;

remove_all_actions( 'save_post' );
remove_all_actions( "save_post_{$post->post_type}" );

wp_update_post( $post );

$wp_actions = $actions;
$wp_filters = $filters;
$wp_filter  = $filter;

And then I was thinking that the core could include a wrapper function for this:

<?php
function wp_update_post_silent( array|object $postarr = array(), bool $wp_error = false, bool $fire_after_hooks = true ): int|WP_Error {

        /**
         * Prevent infinite loop and repeating actions.
         */
        global $wp_actions, $wp_filters, $wp_filter;

        $actions = $wp_actions;
        $filters = $wp_filters;
        $filter  = $wp_filter;

        remove_all_actions( 'save_post' );

        if ( is_object( $postarr ) ) {
                remove_all_actions( "save_post_{$postarr->post_type}" );
        } elseif ( is_array( $postarr ) ) {
                remove_all_actions( "save_post_{$postarr['post_type']}" );
        }

        $result = wp_update_post( $postarr, $wp_error, $fire_after_hooks );

        $wp_actions = $actions;
        $wp_filters = $filters;
        $wp_filter  = $filter;
        
        return $result;
}

Or another option would be to add an optional 4th parameter to wp_update_post(), similar to its third $fire_after_hooks = true parameter. Something like $fire_save_hooks = true.

Change History (2)

#1 @gerardreches
3 months ago

  • Resolution set to invalid
  • Status changed from new to closed

#2 @swissspidy
3 months ago

  • Milestone Awaiting Review deleted
Note: See TracTickets for help on using tickets.