WordPress function leads to endless fun

Came across a fun one today. A person in the WordPress.org Support Forums wanted a function that would set a post status to Private if the post was assigned to a certain category. My approach was to prevent future posts from not missing out on the opportunity to set the status and does not really address posts that are already in the database.

To start things off, I wandered over to the WordPress Codex for the wp_update_post function and adapted the example listed.

function post_saved_set_to_private ( $post_id ) {
if ( in_category( 'switcheroo' )) {
	// Update post with the following
	  $my_post = array( 'ID' => $post_id, 'post_status' => 'private' );

	// Update the post into the database
	  wp_update_post( $my_post );

	}
}
add_action('save_post', 'post_saved_set_to_private');

Talk about easy! I tossed this function into my theme’s functions.php file so I could try things out on my localhost installation. I was going to be the original poster’s savior.

I tried creating a regular post in a random category. Success! Now the real test — add a new post in the switcheroo category. Hit Publish. Why is it taking so long? What’s the deal? I’m dead in the water. Nothing is happening. This can’t be good. I opened up a new tab to my Dashboard and jumped into All Posts. 900+ new posts set to Private! Hey, that’s not right! So while I set to work on deleting those posts (200 at a time), I began to think about what had happened and I figured it must have something to do with post revisions.

Indeed. Now here’s the good part. If I had only scrolled down the wp_update_post Codex page just a little more, I would have avoided the problem I had created.

===Caution – Infinite loop===
When executed by an action hooked into save_post (e.g. a custom metabox), wp_update_post() has the potential to create an infinite loop. This happens because (1) wp_update_post() results in save_post being fired and (2) save_post is called twice when revisions are enabled (first when creating the revision, then when updating the original post—resulting in the creation of endless revisions).

If you must update a post from code called by save_post, make sure to verify the post_type is not set to 'revision' and that the $post object does indeed need to be updated.

function my_function( $post_id ) {
	if ( ! wp_is_post_revision( $post_id ) ) {
	
		// unhook this function so it doesn't loop infinitely
		remove_action('save_post', 'my_function');
	
		// update the post, which calls save_post again
		wp_update_post( $my_args );

		// re-hook this function
		add_action('save_post', 'my_function');
	}
}
add_action('save_post', 'my_function');

In the example above, you see a check to make sure that the post is not a revision. If that passes, the function then removes the action, does the update and then adds action back again. This is how you avoid an infinite loop and finding out you have 900+ posts that you didn’t want at all.

Armed with new information, I updated my original function to include the crucial part about avoiding an infinite loop and came up with the following function.

function post_saved_set_to_private ( $post_id ) {
	if ( ! wp_is_post_revision( $post_id ) ) {

		// unhook this function so it doesn't loop infinitely
		remove_action('save_post', 'post_saved_set_to_private');

		if ( in_category( 'switcheroo' )) {
			// Update post
		    $my_post = array( 'ID' => $post_id, 'post_status' => 'private' );

			// Update the post into the database
			wp_update_post( $my_post );
		}

		// re-hook this function
		add_action('save_post', 'post_saved_set_to_private');
	}
}
add_action('save_post', 'post_saved_set_to_private');

I believe I got the function working and learned a little something along the way.

Advertisements