Make WordPress Core

Opened 4 years ago

Closed 20 months ago

#18614 closed defect (bug) (fixed)

post_type_archive_title doesn't work when tax_query is added to wp_query

Reported by: wonderboymusic Owned by: wonderboymusic
Milestone: 3.7 Priority: normal
Severity: normal Version: 3.2
Component: Template Keywords: has-patch needs-unit-tests
Focuses: Cc:


post_type_archive_title( ) gets called by wp_title( ) on Custom Post Type archive pages

To get the data for the post type object, post_type_archive_title( ) calls get_queried_object( ). This is problematic. If the query has been altered in any way (such as adding a tax_query in pre_get_posts filter), get_queried_object( ) will probably return a term, not a post type object.

Hence, accessing $post_type_obj->labels will fail and their will be no title returned

//old potentially broken way
$post_type_obj = get_queried_object();

// new doesn't break way
$post_type_obj = get_post_type_object( get_post_type() );

Attachments (6)

patches-post-type-archive-title.diff (486 bytes) - added by wonderboymusic 4 years ago.
Revised Patch for this ticket
18614.diff (4.0 KB) - added by nacin 3 years ago.
18614.2.diff (4.2 KB) - added by nacin 3 years ago.
18614.3.diff (4.2 KB) - added by wonderboymusic 22 months ago.
18614.4.diff (4.0 KB) - added by wonderboymusic 21 months ago.
18614.5.diff (5.6 KB) - added by wonderboymusic 21 months ago.

Download all attachments as: .zip

Change History (47)

comment:1 @scribu4 years ago

  • Milestone Awaiting Review deleted
  • Resolution set to wontfix
  • Status changed from new to closed

What about when you are on a taxonomy page and use 'pre_get_posts' to set an author etc. etc.? We can't apply band-aids like this.

You can reset $wp_query->queried_object to whatever you need, without modifying Core:

function change_queried_object() {
  global $wp_query;

  if ( is_post_type_archive() )
    $wp_query->queried_object = get_post_type_object( get_post_type() );
add_action( 'template_redirect', 'change_queried_object' );
Last edited 4 years ago by scribu (previous) (diff)

comment:2 @wonderboymusic4 years ago

post_type_archive_title( ) exits if the current context is not post_type_archive( ) - meaning post_type is set in $wp_query.

The name of the variable is $post_type_obj which leads me to believe it should always be a post type object, which get_queried_object isn't guaranteed to return. Since post_type is in wp_query, get_post_type will work and get_post_type_object( get_post_type() ) will work and return a post_type_object, so then $post_type_obj->labels won't fail and log errors

This isn't a band aid, writing a filter to correct what should be default functionality is

@wonderboymusic4 years ago

Revised Patch for this ticket

comment:3 follow-up: @scribu4 years ago

Ok, and what happens if 'post_type' is set to an array by some other code that alters $wp_query?

comment:4 @scribu4 years ago

PS: When you upload a revised patch, don't overwrite the previous version.

comment:5 in reply to: ↑ 3 ; follow-up: @wonderboymusic4 years ago

Replying to scribu:

Ok, and what happens if 'post_type' is set to an array by some other code that alters $wp_query?

funny you should ask - I did some testing that looks at exactly that - removes the tax_query and instead sets post_type (also tested with tax_query AND post_type set to something custom)

the original post_type was "emusic_icon" and I added "post" and "emusic_spotlight"

// altered function
function post_type_archive_title( $prefix = '', $display = true ) {
    global $wp_query;
    var_dump( $wp_query );
    var_dump( get_post_type_object( get_post_type() ) ); exit();
    if ( ! is_post_type_archive() )
	$post_type_obj = get_post_type_object( get_post_type() );
	$title = apply_filters('post_type_archive_title', $post_type_obj->labels->name );

	if ( $display )
		echo $prefix . $title;
		return $title;

Here's the output for $wp_query with an Array of post_types:

object(WP_Query)#129 (47) {
  array(58) {
    array(3) {
      string(4) "post"
      string(11) "emusic_icon"
      string(16) "emusic_spotlight"

Here's the output for get_post_type_object( get_post_type() ) - returns the ORIGINAL post_type:

object(stdClass)#354 (26) {
  object(stdClass)#356 (14) {
    string(5) "Icons"
    string(4) "Icon"
    string(7) "Add New"
    string(12) "Add New Icon"
    string(9) "Edit Icon"
    string(8) "New Icon"
    string(9) "View Icon"
    string(12) "Search Icons"
    string(14) "No Icons found"
    string(23) "No Icons found in Trash"
    string(5) "Icons"
    string(5) "Icons"
    string(4) "Icon"
  string(0) ""
  string(4) "post"
  string(16) "post.php?post=%d"
  array(4) {
    string(15) "music-news/icon"
  string(16) "music-news/icons"
  string(11) "emusic_icon"
  object(Closure)#353 (1) {
    array(2) {
      string(11) "emusic_icon"
      object(Closure)#352 (1) {
        array(1) {
          string(10) "<required>"
  array(5) {
    string(6) "region"
    string(5) "genre"
    string(8) "location"
    string(8) "post_tag"
    string(8) "category"
  string(11) "emusic_icon"
  object(stdClass)#355 (14) {
    string(9) "edit_post"
    string(9) "read_post"
    string(11) "delete_post"
    string(10) "edit_posts"
    string(17) "edit_others_posts"
    string(13) "publish_posts"
    string(18) "read_private_posts"
    string(4) "read"
    string(12) "delete_posts"
    string(20) "delete_private_posts"
    string(22) "delete_published_posts"
    string(19) "delete_others_posts"
    string(18) "edit_private_posts"
    string(20) "edit_published_posts"
  string(5) "Icons"

comment:6 in reply to: ↑ 5 @wonderboymusic4 years ago

I think the above behavior is correct - not to mention that it returns a post_type label name, instead of whatever taxonomy label name for whatever term that get_queried_object returns

comment:7 @scribu4 years ago

Yes, but do you know why it works?

Because get_post_type() checks the $post global:


In other words, it is to be used only within The Loop.

Last edited 2 years ago by scribu (previous) (diff)

comment:8 @wonderboymusic4 years ago

ok - then this is still better than returning a term and trying to access a property that doesn't exist:

$type = get_query_var( 'post_type' ) ;
$post_type_obj = get_post_type_object( is_array( $type ) ? $type[0] : $type );

comment:9 @scribu4 years ago

  • Keywords 2nd-opinion added
  • Milestone set to Awaiting Review
  • Resolution wontfix deleted
  • Status changed from closed to reopened

Fair enough. Will let someone else weigh in.

comment:10 @dd324 years ago

Just to chime in here, This is something I've run into a few times - not this specifically, but relying upon get_queried_object() to return the same data as expected. If it's an "advanced Query" (ie. Taxonomy(s) or Authors set - any type of archive I believe) then the return may not be exactly what the calling code is expecting.

comment:11 @johnbillion4 years ago

It's worth noting that this is reproducable using public query vars (eg. example.com/my_post_type/?my_taxonomy=my_term) as reported in #19035.

comment:12 @nacin4 years ago

  • Version changed from 3.3 to 3.2

So is_post_type_archive() returns true but the queried object is wrong? I'm fine with the patch, but this seems like it's a larger issue.

comment:13 @SergeyBiryukov4 years ago

From #19035:

Example: When viewing example.com/my_post_type/?my_taxonomy=my_term, the return value of get_queried_object() is the term object, not the post type object. This causes a PHP notice when post_type_archive_title() attempts to access the label for the post type object, and results in a blank title.

comment:14 @billerickson3 years ago

  • Cc bill.erickson@… added

+1 for the patch.

I just ran into this issue on taxonomy archives limited to a specific post type ( example.com/taxonomy/term/?post_type=my_post_type ).

I'm using the filter to rebuild the title and just ignoring the PHP notice for now.

@nacin3 years ago

@nacin3 years ago

comment:15 @nacin3 years ago

18614.diff is the first pass at different route to solve the larger problem here, which includes #19939 as well as this ticket.

18614.2.diff is a second take, which, primarily, does not re-order anything in get_queried_object(). What both patches do make sure of, though, is that is_post_type_archive() is always requested prior to taxonomy data.

This was a bit inconsistent in the original implementation. wp_title() is going to be correct since it will always allow a post type archive to override a taxonomy. Given the hierarchy inherent to posts and terms, I would think that a post type archive should nearly always take precedence. While it is nearly impossible to tell the difference between /tag/some-tag/?post_type=type and /type/?tag=some-tag (and certainly so when rewrite rules are off), I think a good assumption would be that if the post type is has_archive = true, it is going to be the main target of a drill-down, rather than an option of a drill-down. (Or, at least, it is a higher priority than a tag or category in the subject of a drill-down, generally speaking.)

I thought about get_queried_object( $type_of_object ), get_queried_objects(), and a few other things here. I think greater changes should wait for another day. For now, the attached patch attempts to bring post types above taxonomies when has_archive is true, and solve the current issue of template tags malfunctioning.

comment:16 @scribu3 years ago

  • Keywords 2nd-opinion removed
  • Milestone changed from Awaiting Review to 3.4

I haven't tested either of the patches, but I agree with the reasoning:

If we start with the premise that post types should never be mixed between them, then the post type should indeed take precedence over taxonomies, which can accept multiple terms.

comment:18 @ryan3 years ago

  • Milestone changed from 3.4 to Future Release

comment:19 @SergeyBiryukov3 years ago

Closed #20994 as a duplicate.

comment:21 follow-up: @styledev2 years ago

  • Cc styledev added

It seems as if a part of 18614.2.diff​ was included in Wordpress 3.5 in general-template.php lines 1666 to 1669 in function feed_links_extra( $args = array() ):

} elseif ( is_post_type_archive() ) {
	$title = sprintf( $args['posttypetitle'], get_bloginfo('name'), $args['separator'], post_type_archive_title( '', false ) );
	$href = get_post_type_archive_feed_link( get_queried_object()->name );

Which now is causing the following notices:

Notice: Undefined property: stdClass::$labels in /Users/dbushaw/Sites/trainelite/wp-includes/general-template.php on line 658

Notice: Trying to get property of non-object in /Users/dbushaw/Sites/trainelite/wp-includes/general-template.php on line 658

Note: If you apply the latest patch (18614.2.diff) then everything is fixed.

comment:22 in reply to: ↑ 21 @SergeyBiryukov2 years ago

Replying to styledev:

It seems as if a part of 18614.2.diff​ was included in Wordpress 3.5 in general-template.php lines 1666 to 1669 in function feed_links_extra( $args = array() )

Related: [21984], [22483] (for #21648).

comment:23 @benjmay2 years ago

  • Cc benjmay added

I was getting the same notices as @styledev, however, it wasn't from wp_title as I was being clever and only using wp_title on non-CPT pages.

The error was coming from the 'extra' feeds in wp_header.

I was able to prevent the error without making mods to core by simply adding:

remove_action( 'wp_head', 'feed_links_extra', 3 ); 

comment:24 @markoheijnen2 years ago

Ben, I guess the notices weren't the same and for your notices there is a patch for that in ticket #21394

comment:25 @wonderboymusic2 years ago

  • Keywords needs-refresh added
  • Milestone changed from Future Release to 3.6

While it is nearly impossible to tell the difference between /tag/some-tag/?post_type=type and /type/?tag=some-tag (and certainly so when rewrite rules are off)

WP::$matched_query is always correct when it comes to precedence when rewrite rules are on.

When the URL is wordpress-core/tag/woo/?post_type=sport:

["matched_query"]=> string(7) "tag=woo"

When the URL is wordpress-core/sport/?tag=woo:

["matched_query"]=> string(15) "post_type=sport"

Relying on the WP property will also prevent a tax_query from stomping the matched_query in get_queried_object

comment:26 @styledev2 years ago

Since this is taking a while to get fixed here is a function you can put in your functions.php file to turn of the is_tax on these types of queries. https://gist.github.com/styledev/5278527

comment:27 @ryan2 years ago

  • Milestone changed from 3.6 to Future Release

comment:28 @Frank Klein23 months ago

  • Cc contact@… added

comment:29 @dominicp22 months ago

  • Cc solaveritasteliberum@… added

@wonderboymusic22 months ago

comment:30 @wonderboymusic22 months ago

  • Keywords needs-unit-tests added; needs-refresh removed
  • Milestone changed from Future Release to 3.7

I forgot I even filed this ticket - @nacin patched this properly a long time ago, it was just never committed. I have refreshed against trunk, the scenario described in the ticket is still valid.

@wonderboymusic21 months ago

@wonderboymusic21 months ago

comment:31 @wonderboymusic21 months ago

  • Owner set to wonderboymusic
  • Resolution set to fixed
  • Status changed from reopened to closed

In 25291:

Ensure that the post type object is the queried object when a post type has been registered with has_archive => true. Ensure it is not stomped when decorated with tax_query. Adds unit tests.

Props nacin.
Fixes #18614.

comment:32 @nacin21 months ago

  1. get_post_type_object( get_query_var( 'post_type' ) ); — could that fail if the post_type QV is an array? That's the only thing that jumps out.
  1. Could we get away with demoting the is_post_type_archive() and is_tax() conditionals in template-loader.php to below is_front_page() and is_home()? Then is_home() will happen right before is_post_type_archive(), possibly setting the stage for that to be cleaned up in the future, as it is essentially the 'post' post type archive (plus some other special sauce). It would also make a lot more sense as is_tax() was kind of nonchalantly stuffed up at the top originally. Does anyone see any side effects?

comment:33 @wonderboymusic21 months ago

  1. This check needs to happen in get_post_type_object(). If an array is passed, in needs to do $post_type = reset( $post_type );. There are 8-10 places in core where the query var is passed.
  1. As long as is_tax() is false when the post_type obj is the queried object, I think it is safe.

comment:34 @wonderboymusic21 months ago

In 25292:

Check the value passed to get_post_type_object(). If it's an array, use the first item. get_query_var( 'post_type' ) can be an array if the query has been altered via filters/actions. There are several places in core that pass the query var. Adds unit tests.

In template-loader.php, move is_post_type_archive() and is_tax() directly below is_home().

See #18614, [25291].

comment:35 @nacin21 months ago

  • Resolution fixed deleted
  • Status changed from closed to reopened

Sorry to be a pain, but I feel like the reset() in [25292] might not be the best place for this. Or at the very least, it needs docs.

If you are passing an array to get_post_type_object(), you *may* be doing it wrong. For us to choose the first one (essentially picking favorites) could mean we are suppressing issues. While it might make sense in some places to choose the first one, it might not make sense in all places. This could potentially paper over a developer error. (Including, yes, an error where we have done this 8-10 times ourselves.)

I was only musing for the template-loader.php change. Let's dig into that a bit more to make sure it won't break anything. Also, [25292] should have been two commits.

comment:36 @wonderboymusic21 months ago

yeah, I was a shade overeager - I'll inspect those places in core and see if the checks make more sense inline

comment:37 @nacin21 months ago

By wonderboymusic, in [25312]: Move checks for post_type being an array inline. See [25291], [25292], #18614.

(The ticket(s) has to come before any changeset references for "see" or "fixes" to work.)

comment:38 @nacin21 months ago

And regarding [25312], even better, some of these aren't even needed inline, because is_post_type_archive is only true when post_type is *not* an array:

if ( !empty( $qv['post_type'] ) && ! is_array( $qv['post_type'] ) ) {
	$post_type_obj = get_post_type_object( $qv['post_type'] );
	if ( ! empty( $post_type_obj->has_archive ) )
		$this->is_post_type_archive = true;

comment:39 @kovshenin21 months ago

  • Cc kovshenin added

comment:40 @nacin21 months ago

See #25341 for problems.

comment:41 @nacin20 months ago

  • Resolution set to fixed
  • Status changed from reopened to closed

This ticket still has a needs-unit-tests keyword, but seems like everything is good for 3.7.

Note: See TracTickets for help on using tickets.