Make WordPress Core

Changeset 35537


Ignore:
Timestamp:
11/05/2015 04:44:59 PM (11 years ago)
Author:
boonebgorges
Message:

Make get_term() behave more consistently in the context of shared terms.

When WP_Term was introduced in [34997], the $taxonomy parameter for
get_term() was made optional. This meant that, when the optional param was
omitted, get_term() had no way of determining which term was intended when
the term_id was shared between multiple taxonomies. As a (somewhat sneaky) way
of fixing things, get_term() split any shared terms it found. But this could
cause problems with developer expectations: it's not clear why requesting a
term should result in a database update, much less a potential change in the
ID of a term.

In place of this technique, this changeset introduces a number of changes that
make the handling of shared terms a bit less insane:

  • When a taxonomy is provided to get_term(), and a cached term is found matching the term_id, make sure the taxonomy also matches before returning it.
  • When a taxonomy is not provided, ensure that the term is not shared before adding it to the cache.
  • When a term is shared between taxonomies and no taxonomy is provided, return a WP_Error rather than splitting the term.
  • When a term is shared between taxonomies, only one of which is valid, return the term from that taxonomy.

Props boonebgorges, dlh.
Fixes #34533.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-term.php

    r35269 r35537  
    116116         * @global wpdb $wpdb WordPress database abstraction object.
    117117         *
    118          * @param int $term_id Term ID.
    119          * @return WP_Term|false Term object, false otherwise.
    120          */
    121         public static function get_instance( $term_id ) {
     118         * @param int    $term_id  Term ID.
     119         * @param string $taxonomy Optional. Limit matched terms to those matching `$taxonomy`. Only used for
     120         *                         disambiguating potentially shared terms.
     121         * @return WP_Term|WP_Error|false Term object, if found. WP_Error if `$term_id` is shared between taxonomies and
     122         *                                there's insufficient data to distinguish which term is intended.
     123         *                                False for other failures.
     124         */
     125        public static function get_instance( $term_id, $taxonomy = null ) {
    122126                global $wpdb;
    123127
     
    130134
    131135                // If there isn't a cached version, hit the database.
    132                 if ( ! $_term ) {
    133                         $_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE t.term_id = %d LIMIT 1", $term_id ) );
     136                if ( ! $_term || ( $taxonomy && $taxonomy !== $_term->taxonomy ) ) {
     137                        // Grab all matching terms, in case any are shared between taxonomies.
     138                        $terms = $wpdb->get_results( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE t.term_id = %d", $term_id ) );
     139                        if ( ! $terms ) {
     140                                return false;
     141                        }
     142
     143                        // If a taxonomy was specified, find a match.
     144                        if ( $taxonomy ) {
     145                                foreach ( $terms as $match ) {
     146                                        if ( $taxonomy === $match->taxonomy ) {
     147                                                $_term = $match;
     148                                                break;
     149                                        }
     150                                }
     151
     152                        // If only one match was found, it's the one we want.
     153                        } elseif ( 1 === count( $terms ) ) {
     154                                $_term = reset( $terms );
     155
     156                        // Otherwise, the term must be shared between taxonomies.
     157                        } else {
     158                                // If the term is shared only with invalid taxonomies, return the one valid term.
     159                                foreach ( $terms as $t ) {
     160                                        if ( ! taxonomy_exists( $t->taxonomy ) ) {
     161                                                continue;
     162                                        }
     163
     164                                        // Only hit if we've already identified a term in a valid taxonomy.
     165                                        if ( $_term ) {
     166                                                return new WP_Error( 'ambiguous_term_id', __( 'Term ID is shared between multiple taxonomies' ), $term_id );
     167                                        }
     168
     169                                        $_term = $t;
     170                                }
     171                        }
     172
    134173                        if ( ! $_term ) {
    135174                                return false;
    136175                        }
    137176
     177                        // Don't return terms from invalid taxonomies.
     178                        if ( ! taxonomy_exists( $_term->taxonomy ) ) {
     179                                return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy' ) );
     180                        }
     181
    138182                        $_term = sanitize_term( $_term, $_term->taxonomy, 'raw' );
    139                         wp_cache_add( $term_id, $_term, 'terms' );
     183
     184                        // Don't cache terms that are shared between taxonomies.
     185                        if ( 1 === count( $terms ) ) {
     186                                wp_cache_add( $term_id, $_term, 'terms' );
     187                        }
    140188                }
    141189
  • trunk/src/wp-includes/taxonomy-functions.php

    r35515 r35537  
    765765                }
    766766        } else {
    767                 $_term = WP_Term::get_instance( $term );
    768         }
    769 
    770         // If `$taxonomy` was provided, make sure it matches the taxonomy of the located term.
    771         if ( $_term && $taxonomy && $taxonomy !== $_term->taxonomy ) {
    772                 // If there are two terms with the same ID, split the other one to a new term.
    773                 $new_term_id = _split_shared_term( $_term->term_id, $_term->term_taxonomy_id );
    774 
    775                 // If no split occurred, this is an invalid request. Return null (not WP_Error) for back compat.
    776                 if ( $new_term_id === $_term->term_id ) {
    777                         return null;
    778 
    779                 // The term has been split. Refetch the term from the proper taxonomy.
    780                 } else {
    781                         return get_term( $_term->term_id, $taxonomy, $output, $filter );
    782                 }
    783         }
    784 
    785         if ( ! $_term ) {
     767                $_term = WP_Term::get_instance( $term, $taxonomy );
     768        }
     769
     770        if ( is_wp_error( $_term ) ) {
     771                return $_term;
     772        } elseif ( ! $_term ) {
    786773                return null;
    787774        }
  • trunk/tests/phpunit/tests/term/getTerm.php

    r35242 r35537  
    1010        }
    1111
     12        /**
     13         * Utility function for generating two shared terms, in the 'wptests_tax' and 'wptests_tax_2' taxonomies.
     14         *
     15         * @return array Array of term_id/old_term_id/term_taxonomy_id triplets.
     16         */
     17        protected function generate_shared_terms() {
     18                global $wpdb;
     19
     20                register_taxonomy( 'wptests_tax_2', 'post' );
     21
     22                $t1 = wp_insert_term( 'Foo', 'wptests_tax' );
     23                $t2 = wp_insert_term( 'Foo', 'wptests_tax_2' );
     24
     25                // Manually modify because shared terms shouldn't naturally occur.
     26                $wpdb->update( $wpdb->term_taxonomy,
     27                        array( 'term_id' => $t1['term_id'] ),
     28                        array( 'term_taxonomy_id' => $t2['term_taxonomy_id'] ),
     29                        array( '%d' ),
     30                        array( '%d' )
     31                );
     32
     33                return array(
     34                        array(
     35                                'term_id' => $t1['term_id'],
     36                                'old_term_id' => $t1['term_id'],
     37                                'term_taxonomy_id' => $t1['term_taxonomy_id'],
     38                        ),
     39                        array(
     40                                'term_id' => $t1['term_id'],
     41                                'old_term_id' => $t2['term_id'],
     42                                'term_taxonomy_id' => $t2['term_taxonomy_id'],
     43                        ),
     44                );
     45        }
     46
    1247        public function test_should_return_error_for_empty_term() {
    1348                $found = get_term( '', 'wptests_tax' );
     
    115150                $this->assertNull( get_term( $term_id, 'category' ) );
    116151        }
     152
     153        /**
     154         * @ticket 34533
     155         */
     156        public function test_should_return_wp_error_when_term_is_shared_and_no_taxonomy_is_specified() {
     157                $terms = $this->generate_shared_terms();
     158
     159                $found = get_term( $terms[0]['term_id'] );
     160
     161                $this->assertWPError( $found );
     162        }
     163
     164        /**
     165         * @ticket 34533
     166         */
     167        public function test_should_return_term_when_term_is_shared_and_correct_taxonomy_is_specified() {
     168                $terms = $this->generate_shared_terms();
     169
     170                $found = get_term( $terms[0]['term_id'], 'wptests_tax' );
     171
     172                $this->assertInstanceOf( 'WP_Term', $found );
     173                $this->assertSame( $terms[0]['term_id'], $found->term_id );
     174        }
     175
     176        /**
     177         * @ticket 34533
     178         */
     179        public function test_should_return_null_when_term_is_shared_and_incorrect_taxonomy_is_specified() {
     180                $terms = $this->generate_shared_terms();
     181
     182                $found = get_term( $terms[0]['term_id'], 'post_tag' );
     183
     184                $this->assertNull( $found );
     185        }
     186
     187        /**
     188         * @ticket 34533
     189         */
     190        public function test_shared_term_in_cache_should_be_ignored_when_specifying_a_different_taxonomy() {
     191                global $wpdb;
     192
     193                $terms = $this->generate_shared_terms();
     194
     195                // Prime cache for 'wptests_tax'.
     196                get_term( $terms[0]['term_id'], 'wptests_tax' );
     197                $num_queries = $wpdb->num_queries;
     198
     199                // Database should be hit again.
     200                $found = get_term( $terms[1]['term_id'], 'wptests_tax_2' );
     201                $num_queries++;
     202
     203                $this->assertSame( $num_queries, $wpdb->num_queries );
     204                $this->assertInstanceOf( 'WP_Term', $found );
     205                $this->assertSame( 'wptests_tax_2', $found->taxonomy );
     206        }
     207
     208        /**
     209         * @ticket 34533
     210         */
     211        public function test_should_return_error_when_only_matching_term_is_in_an_invalid_taxonomy() {
     212                $t = self::factory()->term->create( array( 'taxonomy' => 'wptests_tax' ) );
     213
     214                _unregister_taxonomy( 'wptests_tax' );
     215
     216                $found = get_term( $t );
     217                $this->assertWPError( $found );
     218                $this->assertSame( 'invalid_taxonomy', $found->get_error_code() );
     219        }
     220
     221        /**
     222         * @ticket 34533
     223         */
     224        public function test_term_should_be_returned_when_id_is_shared_only_with_invalid_taxonomies() {
     225                $terms = $this->generate_shared_terms();
     226
     227                _unregister_taxonomy( 'wptests_tax' );
     228
     229                $found = get_term( $terms[1]['term_id'] );
     230                $this->assertInstanceOf( 'WP_Term', $found );
     231                $this->assertSame( 'wptests_tax_2', $found->taxonomy );
     232                $this->assertSame( $terms[1]['term_id'], $found->term_id );
     233        }
    117234}
Note: See TracChangeset for help on using the changeset viewer.