Make WordPress Core

Opened 6 years ago

Last modified 4 weeks ago

#18408 reopened defect (bug)

Can't wp_reset_postdata after custom WP_Query in an admin edit page

Reported by: ericlewis Owned by: ericlewis
Milestone: Future Release Priority: normal
Severity: normal Version: 4.7.2
Component: Query Keywords: needs-testing ongoing needs-patch
Focuses: Cc:


While on an edit post page (or any post type), if you create a custom WP_Query object before the editor has been output, the post data from the custom WP_Query will fill out the edit post form, not the original content of the post that is actually trying to be edited.

I found this out when trying to create a custom metabox in the 'side' context. You can reproduce this by inserting this code into a plugin:

add_action( 'add_meta_boxes', 'myplugin_add_custom_box' );

function myplugin_add_custom_box() {
        __( 'My Post Section Title', 'myplugin_textdomain' ),

function myplugin_inner_custom_box() {
	global $post;
	$a = new WP_Query('post_type=page');
	while($a->have_posts() ) : $a->the_post();

This happens because $wp_query->post is never defined in the admin load, which wp_reset_postdata relies on to reset the original post data.

I am attaching a patch that defines $wp_query->post after the $post global has been defined.

Attachments (4)

post.php (60.2 KB) - added by ericlewis 6 years ago.
18408.patch (1.3 KB) - added by ericlewis 6 years ago.
.patch file version
18408.php (440 bytes) - added by ericlewis 2 years ago.
18408-wp-query-in-admin.php (585 bytes) - added by boonebgorges 2 years ago.

Download all attachments as: .zip

Change History (29)

6 years ago


6 years ago

.patch file version

#1 @ericlewis
6 years ago

  • Keywords needs-testing dev-feedback added

#2 @ericlewis
6 years ago

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

#3 @SergeyBiryukov
6 years ago

  • Milestone Awaiting Review deleted

#4 @billerickson
5 years ago

Why was this set to invalid? I'm having the same issue.

My solution has been to save the original post in $original_post, then at the bottom of my metabox put setup_postdata( $original_post );

While this works, it would be much better to be able to use wp_reset_query or wp_reset_postdata.

If this ticket is invalid, what's the recommended approach for restoring the query in a metabox?

#5 @ericlewis
5 years ago

I set it to invalid because the proper thing to do in this situation is to use get_posts() anywhere in the back-end, and not use any template tags.

#6 @billerickson
5 years ago

Regardless of what method you use to generate the loop, if you set the post data to something else you'll need to reset it. Using get_posts:

$original_post = $post;
$myposts = get_posts( $args );
foreach( $myposts as $post ) :	setup_postdata($post);
setup_postdata( $original_post);

Using WP_Query (how I'm doing it):

$original_post = $post;
$loop = new WP_Query( $args );
while( $loop->have_posts() ): $loop->the_post();
setup_postdata( $original_post );

I think it's reasonable to assume that most of the time you're doing a query in a metabox, you'll need access to the post data.

#7 @ericlewis
5 years ago

Bill - you can access all post data via the $myposts object in the foreach loop without using the setup_postdata() as class variables of each $post, I believe setup_postdata just makes it more simple to access via template tags.

I do agree with you that it would be nice to use template tags in meta boxes, just to keep things cleaner and one less thing to remember.

#8 @billerickson
5 years ago

Ah I see, I never use get_posts so was basing that simply on the examples of the codex page.

Nonetheless, I still think it's a valid complaint.

#9 @misternifty
3 years ago

  • Component changed from Editor to Options, Meta APIs
  • Resolution invalid deleted
  • Status changed from closed to reopened
  • Version changed from 3.2.1 to 3.8.1

This is not invalid. I was able to replicate this in 3.8.1. While using an explicit WP_Query object in a meta box, you cannot reset the postdata after the loop, no matter what you try. Any subsequent meta boxes identify global $post as the last post of the custom query.

@ericlewis assertion that WP_Query and template tags should not be used in wp-admin is not documented anywhere in the Codex that I can find nor in any posts by core contributors. Multiple loops using the an explicit creation of a WP_Query object in wp-admin post screens should work just like any other query method or function.

This is causing issues for other plugins that register meta boxes and generating support tickets like the following: http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-after-update-it-wont-save-seo?replies=32
I identified the noted support example here after trying to save post meta for WP SEO but the meta that surfaced after update was based off the post ID of the last post in my custom query in the previous meta box. After a look under the hood, the meta was actually saved properly to the origin post ID as it was a form option in the DOM already.

Using get_posts instead of new WP_Query works, but that's just applying a bandaid fix when this should work out-of-the-box. It's my opinion that this needs to be fixed rather than say, 'use another query method'. Plugin authors will continue to use WP_Query in meta boxes and it will continue to break the main query for the subsequent page load.

#10 @nacin
3 years ago

  • Milestone set to Awaiting Review

#11 @boonebgorges
2 years ago

  • Component changed from Options, Meta APIs to Query

#12 @boonebgorges
2 years ago

Is this still an issue? I can't figure out how to reproduce it. When registering a metabox containing a WP_Query using the code provided by ericlewis, the edit post form on post.php is filling in the data from the expected post. Maybe I'm doing it wrong?

2 years ago

#13 @ericlewis
2 years ago

In 2011, the right sidebar was output in markup before the main title/content. This has been switched sometime in the last three years (maybe during mp6?), so the bug doesn't express itself so clearly now.

See attachment:18408.php, which clearly shows the_title() after the wp_reset_postdata() is using data from the custom WP_Query, not the original post to be edited.

#14 @boonebgorges
2 years ago

  • Keywords 2nd-opinion added; dev-feedback removed

Thanks, ericlewis. Now I see what's going on.

The root issue is that wp_reset_postdata() is designed to reset globals to match the main query. But in the case of post.php, there is no main query - the $post global is populated manually, using get_post(), rather than through the normal $wp_query loop. So perhaps the most straightforward way to resolve this is by doing a "real" $wp_query loop on edit.php. See 18408-wp-query-in-admin.php. This is similar to your original suggestion to modify get_default_post_to_edit(), but far more targeted.

The good thing about this solution is that WP_Query instances on edit.php now work exactly like WP_Query instances anywhere else. The bad thing is that it's kinda arbitrary. On the front end, the WP bootstrap process (see especially WP::main()) fires up the main WP query based on the query vars parsed out of the URL. If we fake it on post.php as I've suggested in the patch, we're reproducing only part of this process, which could lead to other bits of confusion down the line. (For example, if you see a $wp_query global, do you also expect to find a $wp->matched_rule?)

It's possible that there would be odd side effects of the suggested $wp_query override - in particular, I guess, if a metabox is manipulating the global in some funny way. That being said, I'm having a hard time thinking of any realistic examples of possible breakage.

What do others think?

#15 @brettshumaker
2 years ago

I just ran into this inconsistency for a plugin I have that's doing some things in the admin bar. Originally it was only pulling a list of pages and adding their edit links to an admin bar item, but I had a feature request for the ability to add other post types to this item. I was doing so with WP_Query but found that it was breaking the edit screen in that the editable content was being pulled from the last post in my list of items in the admin bar. I thought that wp_reset_postdata() wasn't functioning properly at first but then realized what was going on.

I agree with @boonebgorges and @misternifty that this does need to be fixed so that the main query behavior is consistent across the admin area. It doesn't make sense for WP_Query to work on some but not all pages.

#16 @dezio1900
2 years ago

I experienced the same bug when I tried to change the Toolbar items inside the "wp_before_admin_bar_render" action hook. I wanted to add one custom type posts to the toolbar, and of course I used WP_Query, but that caused this bug to occur in posts edit screen.

I ended up using "get_posts" instead of "WP_Query" and that solved the problem. BUT this bug is not acceptable, and it should be fixed!

#17 follow-up: @boonebgorges
2 years ago

  • Milestone changed from Awaiting Review to Future Release

#18 in reply to: ↑ 17 @archon810
2 years ago

Replying to boonebgorges:

So FutureRelease - does it mean the fix is in place, waiting for the next release? It's a 4-year-old bug, and we just wasted several hours trying to figure out what's going on before finding this ticket.

#19 @barryceelen
20 months ago

This behaviour caused me some head scratching as well. Added a note to http://codex.wordpress.org/Class_Reference/WP_Query.

#20 @wonderboymusic
18 months ago

  • Keywords close added; 2nd-opinion removed

I can't begin to imagine what would break if we did:

	global $wp_query;

	if ( isset( $wp_query ) ) {
	} else {
              // CODE that is certain to break untold # of plugins and themes

Globals are super-fragile. I don't think we should start actively supporting theme functions and nested loops with proactive reset on this screen. You're better off toggling the global $post in your own code here.

#21 @wonderboymusic
18 months ago

  • Keywords ongoing added; close removed

This could be part of a larger discussion. I also want to immortalize EAL's first bug report.

#22 follow-up: @hungpham
14 months ago

  • Keywords needs-patch added; has-patch removed
  • Version 3.8.1 deleted

I am just facing to this trouble in version 4.4.1, and wondering how it is going? Had to change the $context in my meta_box to "normal" or keep default setting "advanced".

#23 in reply to: ↑ 22 @Mekku
11 months ago

Replying to hungpham:

I am just facing to this trouble in version 4.4.1, and wondering how it is going? Had to change the $context in my meta_box to "normal" or keep default setting "advanced".

My temporary solution is to add the following code in the same function/method that registers the metaboxes:

// the fix
global $post, $wp_query;
$wp_query->post = $post;

// add metaboxes

#24 in reply to: ↑ description ; follow-up: @quin452
7 weeks ago

  • Version set to 4.7.2

Any idea about this? I've encountered something similar.

I'm trying to populate a metabox dropdown with the titles of a custom post type.
The post which will hold this data is also a custom post type.

It works fine not using wp_query and hard coding the options, but when I use wp_query, there are issues in saving. It populates the dropdown, but the permalink is changed and the dropdown is reset/cannot be saved. It also affects the title sometimes.

#25 in reply to: ↑ 24 @jpSimkins
4 weeks ago

To bypass this issue, do not use the_post as this is the issue.

I use this syntax and I have numerous metaboxes with no issue to the default post object nor the need to call wp_reset_postdata();.

$query = new \WP_Query($args);
if ($query->have_posts()) {
    foreach ($query->get_posts() as $p) {
        // nomal loop logic using $p as a normal WP_Post object

It needs to be noted that you cannot use the loop methods like get_the_title() but you can easily call $p->post_title. I do not believe the filters are applied to these so you may need to use a different object for the backend than the frontend since the issue is only on the backend of WP.

Also, you could just use the same args with get_posts();

Last edited 4 weeks ago by jpSimkins (previous) (diff)
Note: See TracTickets for help on using tickets.