I have run into a bug a few times in the past little while that had me stumped. When creating meta boxes on the admin side of WordPress that contained custom loops, I couldn’t reset $post using wp_reset_postdata() – it just didn’t work.

Originally, I thought maybe I was doing it wrong because I was using get_posts, so I tried WP_Query with the same results. So, thinking I had a legitimate bug, I went to report it and found that there was already a ticket and a patch for it ( gotta love the WordPress community ). The patch is a nice, elegant fix that worked well when I tested it – but until it’s accepted into core it’s not really an option to use because hacking core is bad. So I rolled my own in the meantime.

My solution

My approach was to create two new function that are similar to the ones we use now to setup and reset post data. I’d rather not introduce new items to the API but in this situation ( no hooks available and can’t edit core ) it was my only option.

/**
 * Setup a post object and store the original loop item so we can reset it later
 *
 * @param obj $post_to_setup The post that we want to use from our custom loop
 */
function setup_admin_postdata( $post_to_setup ) {

	//only on the admin side
	if( is_admin() ) {

		//get the post for both setup_postdata() and to be cached
		global $post;

		//only cache $post the first time through the loop
		if ( ! isset( $GLOBALS['post_cache'] ) ) {
			$GLOBALS['post_cache'] = $post;
		}

		//setup the post data as usual
		$post = $post_to_setup;
		setup_postdata( $post );
	}
}


/**
 * Reset $post back to the original item
 *
 */
function wp_reset_admin_postdata() {

	//only on the admin and if post_cache is set
	if( is_admin() && !empty( $GLOBALS[ 'post_cache' ] ) ) {

		//globalize post as usual
		global $post;

		//set $post back to the cached version and set it up
		$post = $GLOBALS[ 'post_cache' ];
		setup_postdata( $post );

		//cleanup
		unset( $GLOBALS[ 'post_cache' ] );
	}
}

The general approach is to store a copy of the original $post object before we change it with setup_postdata() and then retrieve it afterwards and reset $post accordingly.

In setup_admin_postdata we look for the existence of $GLOBALS[‘post_cache’] and use it to store $post. If this is the first iteration of the loop, then $GLOBALS[‘post_cache’] shouldn’t exist and we know that $post is original one for the page/post/cpt we’re currently editing.

We can only do this the first time as any subsequent calls to $post will give us the object setup with setup_postdata() and the data will be incorrect. The next part is the same as if we were using setup_postdata() normally in the loop. We set $post to the object we want to setup and then pass it to setup_postdata().

The wp_reset_admin_postdata retrieves the post object stored in $GLOBALS[‘post_cache’], passes it to setup_postdata(), then removes $GLOBALS[‘post_cache’]. This last part is important should there be more than one loop in play.

Here is an example of using it to create a meta box with a select menu:

function render_item_assignment_meta_box( $post_obj ) {

	$selected_item = ( $item = get_post_meta( $post_obj->ID, 'selected-item', true ) ) ? $item : '';

	//must pass a reference to the global $post object
	global $post;

	$options = '';

	//get all of the active drives
	$items = get_posts( array('post_type' => 'my-custom-items' ) );

	foreach ( $items as $item ) :
		/*
		standard way of setting up data when looping

		$post = $item
		setup_postdata( $item );
		*/

		// custom method
		setup_admin_postdata( $item );

		$options .= '<option value="' . get_the_ID() . '" '. selected( $selected_item, get_the_ID(), false ) .'>'. get_the_title() .'</option>';
	endforeach;
	/*
	standard way of resetting the post - doesn't work on admin side.

	wp_reset_postdata();
	*/

	// new method
	wp_reset_admin_postdata();

	printf( '<select name="selected-item"><option value="">%s</option>%s</select>', __('Choose'), $options );
}

I have not done a tone of testing for this but so far I haven’t seen any bugs popup. I’m not entirely sold on using $GLOBAL either but other storage options haven’t seemed to work so far.

Hopefully the proper patch gets committed to core and this won’t matter anyway, but in the meantime please let any comments or suggestions in the comments below.

 

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *