WordPress.org

Make WordPress Core

Opened 3 years ago

Closed 18 months ago

#41679 closed defect (bug) (invalid)

get_the_terms does not return expected results

Reported by: rslotb Owned by:
Milestone: Priority: normal
Severity: normal Version: 4.8.1
Component: Taxonomy Keywords: reporter-feedback
Focuses: Cc:

Description

Wordpress fails to report the (custom) categories a post is in.

$terms = get_the_terms( $post->ID, 'project_category' );

returns no results.

$terms = wp_get_object_terms( $post->ID, 'project_category' );

does, which I believe to be correct.

I have traced the problem back to:

class-wp-term-query.php.

The caching code that calls this relies on object_ids (all_with_object_id) to come back, but the object_ids are eaten by:

     if ( 'all' === $_fields || 'all_with_object_id' === $_fields) {
			$terms = array_map( 'get_term', $terms );
		}

commenting out the 'all_with_object_id' === $_fields part yields correct results. But I am not sure that is the correct fix. presumably having a WP_Term object come back in this case had some intention?

Change History (5)

#1 @boonebgorges
3 years ago

  • Keywords reporter-feedback added

Hi @rslotb - Thanks for your ticket, and welcome to WordPress Trac!

Can you say more about your debugging? If get_the_terms() returns a different value from wp_get_object_terms(), it suggests that the cache is corrupt. See https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/category-template.php?marks=1200#L1186. If get_the_terms() finds an empty cache, it should call wp_get_object_terms() (see line 1202), in which case it would be impossible for the two values to differ.

It's also unclear to me how converting $terms to WP_Term objects would result in a null result (I assume "...returns no results" means it's an empty array?). If $terms is an array of database query results, then the WP_Term objects should always be populated. What is the value of $terms before and after line 814? https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/class-wp-term-query.php?marks=814#L811

#2 @rslotb
3 years ago

Well, yes, I debugged through get_the_terms(), which, as you say, will get the required value from the cache 1202. At least in my installation, this is always the case, I never get to 1205. So the cache must be corrupt. Which it is, in my case.

The cache is initialised by {{function update_object_term_cache($object_ids, $object_type), taxonomy.php, 3084}}.

And the results coming back from this:

$terms = wp_get_object_terms( $ids, $taxonomies, array(
		'fields' => 'all_with_object_id',
		'orderby' => 'name',
		'update_term_meta_cache' => false,
	) ); 

are the problem. As you can see, the query specifies 'all_with_object_id', but this is not honoured by the called code. results do come back, but they do not contain object_ids.

After this:

$object_terms = array();
	foreach ( (array) $terms as $term ) {
		$object_terms[ $term->object_id ][ $term->taxonomy ][] = $term->term_id;
	}

My result is:

$object_terms = (
    [] => Array
        (
            [project_category] => Array
                (
                    [0] => 60
                    [1] => 56
                )
            [category] => Array
                (
                    [0] => 4
                )
        )
)

And after:

foreach ( $ids as $id ) {
		foreach ( $taxonomies as $taxonomy ) {
			if ( ! isset($object_terms[$id][$taxonomy]) ) {
				if ( !isset($object_terms[$id]) )
					$object_terms[$id] = array();
				$object_terms[$id][$taxonomy] = array();
			}
		}
	}

It is:

(
    [] => Array
        (
            [project_category] => Array
                (
                    [0] => 60
                    [1] => 56
                )

            [category] => Array
                (
                    [0] => 4
                )
        )
    [2144] => Array
        (
            [category] => Array
                (
                )
            [post_tag] => Array
                (
                )
            [project_category] => Array
                (
                )
        )
)

With 2144 being my postid. And this is why the cache is corrupt. That second array, without associated terms, is what is put into the cache for 2144.

It it is not that converting $terms to WP_Term objects results in a null result. But the result is missing the object_ids (one in each term). The objects coming back each look like this:

WP_Term Object
        (
            [term_id] => 60
            [name] => Coming soon
            [slug] => coming-soon
            [term_group] => 0
            [term_taxonomy_id] => 60
            [taxonomy] => project_category
            [description] => pre-launch stage
            [parent] => 0
            [count] => 2
            [filter] => raw
        ) 

Which, I hope is obvious, does not contain an [object_id] => 2144.

Eliminating the call to: array_map( 'get_term', $terms ) keeps the original query result intact, which makes the calling code work as expected.

The problem probably is broader: WP_Query::get_terms() currently does not honour the passed in 'all_with_object_id' parameter correctly. If there is a unit test for it, I think it needs looking at.

Regards, and thanks for looking at this,

Remco.


#3 @boonebgorges
3 years ago

@rslotb Thanks a lot for the detailed explanation. I think we are getting closer to the root of the issue.

In the general case, object_id *is* set on WP_Term objects returned from wp_get_object_terms(). There are a number of tests that verify this, at least indirectly. See eg https://core.trac.wordpress.org/browser/tags/4.8.1/tests/phpunit/tests/term/wpGetObjectTerms.php?marks=704#L690. Also, if this were not generally the case, much of WordPress would break :) So something more specific is going on here.

It's not totally obvious how this works, so here's a quick rundown. Hopefully this will help you to go back and debug which part of the chain is failing in your case.

  1. When querying for terms using all_with_object_id, the tr.object_id field is part of the database query. https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/class-wp-term-query.php?marks=593-595#L586 The resulting objects thus have object_id set.
  2. These objects are then run through get_term(). https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/class-wp-term-query.php?marks=813-815#L811 (so far this matches what you've reported)
  3. A stdClass object (the result of the database query) passed to get_term() will hit this conditional https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/taxonomy.php?marks=735-738#L724, which results in the database object being passed to WP_Term::__construct()
  4. The WP_Term object is then set up with each property of the __construct() payload. https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/class-wp-term.php?marks=205-207#L196 This includes object_id, so that $term->object_id is set as the object_id property on the WP_Term object.

My guess is that one of the filters in get_term() has a callback that's stripping the object_id property. https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/taxonomy.php?marks=761,775#L752 Check before and after these filters.

If that's not it, maybe you can do some more detailed debugging to see exactly where in steps 1-4 the object_id param is not being properly passed along.

#4 @RomSocial
3 years ago

@rslotb - did you figure out the reasons or solutions for the issue?
Do you happen to use WPML for the site that experiences the issue, and if so, which version?

#5 @boonebgorges
18 months ago

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

Closing this ticket due to lack of activity. If someone manages to reproduce in a reliable way, please reopen with details.

Note: See TracTickets for help on using tickets.