diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 97122037c3..6c006fc446 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1519,8 +1519,9 @@ function unregister_term_meta( $taxonomy, $meta_key ) { * Conditional Tags} article in the Theme Developer Handbook. * * @since 3.0.0 + * @since 6.0.0 Converted to use `get_terms()`. * - * @global wpdb $wpdb WordPress database abstraction object. + * @global bool $_wp_suspend_cache_invalidation * * @param int|string $term The term to check. Accepts term ID, slug, or name. * @param string $taxonomy Optional. The taxonomy name to use. @@ -1531,65 +1532,71 @@ function unregister_term_meta( $taxonomy, $meta_key ) { * Returns 0 if term ID 0 is passed to the function. */ function term_exists( $term, $taxonomy = '', $parent = null ) { - global $wpdb; + global $_wp_suspend_cache_invalidation; if ( null === $term ) { return null; } - $select = "SELECT term_id FROM $wpdb->terms as t WHERE "; - $tax_select = "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE "; + $defaults = array( + 'get' => 'all', + 'fields' => 'ids', + 'number' => 1, + 'update_term_meta_cache' => false, + 'order' => 'ASC', + 'orderby' => 'term_id', + 'suppress_filter' => true, + ); + + // Ensure that while importing, queries are not cached. + if ( ! empty( $_wp_suspend_cache_invalidation ) ) { + // @todo Disable caching once #52710 is merged. + $defaults['cache_domain'] = microtime(); + } + + if ( ! empty( $taxonomy ) ) { + $defaults['taxonomy'] = $taxonomy; + $defaults['fields'] = 'all'; + } if ( is_int( $term ) ) { if ( 0 === $term ) { return 0; } - $where = 't.term_id = %d'; - if ( ! empty( $taxonomy ) ) { - // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber - return $wpdb->get_row( $wpdb->prepare( $tax_select . $where . ' AND tt.taxonomy = %s', $term, $taxonomy ), ARRAY_A ); - } else { - return $wpdb->get_var( $wpdb->prepare( $select . $where, $term ) ); + $args = wp_parse_args( array( 'include' => array( $term ) ), $defaults ); + $terms = get_terms( $args ); + } else { + $term = trim( wp_unslash( $term ) ); + if ( '' === $term ) { + return null; + } + + if ( ! empty( $taxonomy ) && is_numeric( $parent ) ) { + $defaults['parent'] = (int) $parent; + } + + $args = wp_parse_args( array( 'slug' => sanitize_title( $term ) ), $defaults ); + $terms = get_terms( $args ); + if ( empty( $terms ) || is_wp_error( $terms ) ) { + $args = wp_parse_args( array( 'name' => $term ), $defaults ); + $terms = get_terms( $args ); } } - $term = trim( wp_unslash( $term ) ); - $slug = sanitize_title( $term ); + if ( empty( $terms ) || is_wp_error( $terms ) ) { + return null; + } + + $_term = array_shift( $terms ); - $where = 't.slug = %s'; - $else_where = 't.name = %s'; - $where_fields = array( $slug ); - $else_where_fields = array( $term ); - $orderby = 'ORDER BY t.term_id ASC'; - $limit = 'LIMIT 1'; if ( ! empty( $taxonomy ) ) { - if ( is_numeric( $parent ) ) { - $parent = (int) $parent; - $where_fields[] = $parent; - $else_where_fields[] = $parent; - $where .= ' AND tt.parent = %d'; - $else_where .= ' AND tt.parent = %d'; - } - - $where_fields[] = $taxonomy; - $else_where_fields[] = $taxonomy; - - $result = $wpdb->get_row( $wpdb->prepare( "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $where AND tt.taxonomy = %s $orderby $limit", $where_fields ), ARRAY_A ); - if ( $result ) { - return $result; - } - - return $wpdb->get_row( $wpdb->prepare( "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $else_where AND tt.taxonomy = %s $orderby $limit", $else_where_fields ), ARRAY_A ); + return array( + 'term_id' => (string) $_term->term_id, + 'term_taxonomy_id' => (string) $_term->term_taxonomy_id, + ); } - // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare - $result = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->terms as t WHERE $where $orderby $limit", $where_fields ) ); - if ( $result ) { - return $result; - } - - // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare - return $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->terms as t WHERE $else_where $orderby $limit", $else_where_fields ) ); + return (string) $_term; } /** diff --git a/tests/phpunit/tests/term/getTerm.php b/tests/phpunit/tests/term/getTerm.php index 39e661bbe8..ec618e190d 100644 --- a/tests/phpunit/tests/term/getTerm.php +++ b/tests/phpunit/tests/term/getTerm.php @@ -31,6 +31,8 @@ class Tests_Term_GetTerm extends WP_UnitTestCase { array( '%d' ) ); + clean_term_cache( $t1['term_id'] ); + return array( array( 'term_id' => $t1['term_id'], diff --git a/tests/phpunit/tests/term/splitSharedTerm.php b/tests/phpunit/tests/term/splitSharedTerm.php index 52e4281016..ece1d534ad 100644 --- a/tests/phpunit/tests/term/splitSharedTerm.php +++ b/tests/phpunit/tests/term/splitSharedTerm.php @@ -54,6 +54,7 @@ class Tests_Term_SplitSharedTerm extends WP_UnitTestCase { array( '%d' ), array( '%d' ) ); + clean_term_cache( $t1['term_id'], 'category' ); $t2_child = wp_insert_term( 'Foo Child', @@ -151,6 +152,7 @@ class Tests_Term_SplitSharedTerm extends WP_UnitTestCase { array( '%d' ), array( '%d' ) ); + clean_term_cache( $t1['term_id'], 'category' ); $th = _get_term_hierarchy( 'wptests_tax_4' ); $new_term_id = _split_shared_term( $t1['term_id'], $t3['term_taxonomy_id'] ); @@ -179,6 +181,7 @@ class Tests_Term_SplitSharedTerm extends WP_UnitTestCase { array( '%d' ), array( '%d' ) ); + clean_term_cache( $t1['term_id'], 'category' ); $this->assertSame( $t1['term_id'], get_option( 'default_category', -1 ) ); @@ -207,6 +210,7 @@ class Tests_Term_SplitSharedTerm extends WP_UnitTestCase { array( '%d' ), array( '%d' ) ); + clean_term_cache( $t1['term_id'], 'category' ); $menu_id = wp_create_nav_menu( 'Nav Menu Bar' ); $cat_menu_item = wp_update_nav_menu_item( @@ -245,6 +249,7 @@ class Tests_Term_SplitSharedTerm extends WP_UnitTestCase { array( 'term_id' => $shared_term_id ), array( 'term_taxonomy_id' => $nav_term->term_taxonomy_id ) ); + clean_term_cache( $shared_term_id, 'category' ); set_theme_mod( 'nav_menu_locations', array( 'foo' => $shared_term_id ) ); @@ -274,6 +279,7 @@ class Tests_Term_SplitSharedTerm extends WP_UnitTestCase { array( 'term_id' => $shared_term_id ), array( 'term_taxonomy_id' => $nav_term->term_taxonomy_id ) ); + clean_term_cache( $shared_term_id, 'category' ); $t1 = wp_insert_term( 'Random term', 'category' ); $cat_menu_item = wp_update_nav_menu_item( diff --git a/tests/phpunit/tests/term/termExists.php b/tests/phpunit/tests/term/termExists.php index 8bd9a783c7..c50cf7086d 100644 --- a/tests/phpunit/tests/term/termExists.php +++ b/tests/phpunit/tests/term/termExists.php @@ -271,6 +271,132 @@ class Tests_TermExists extends WP_UnitTestCase { _unregister_taxonomy( 'wptests_tax' ); } + /** + * @ticket 36949 + * @covers ::term_exists() + */ + public function test_term_lookup_by_id_and_update() { + register_taxonomy( 'wptests_tax', 'post' ); + + $slug = __FUNCTION__; + $t = self::factory()->term->create( + array( + 'slug' => $slug, + 'taxonomy' => 'wptests_tax', + ) + ); + $this->assertEquals( $t, term_exists( $t ) ); + $this->assertTrue( wp_delete_term( $t, 'wptests_tax' ) ); + $this->assertNull( term_exists( $t ) ); + + // Clean up. + _unregister_taxonomy( 'wptests_tax' ); + } + + /** + * @ticket 36949 + * @covers ::term_exists() + */ + public function test_term_lookup_by_slug_and_update() { + register_taxonomy( 'wptests_tax', 'post' ); + + $slug = __FUNCTION__; + $t = self::factory()->term->create( + array( + 'slug' => $slug, + 'taxonomy' => 'wptests_tax', + ) + ); + $this->assertEquals( $t, term_exists( $slug ) ); + $this->assertTrue( wp_delete_term( $t, 'wptests_tax' ) ); + $this->assertNull( term_exists( $slug ) ); + + // Clean up. + _unregister_taxonomy( 'wptests_tax' ); + } + + /** + * @ticket 36949 + * @covers ::term_exists() + */ + public function test_term_exists_caching() { + global $wpdb; + register_taxonomy( 'wptests_tax', 'post' ); + + $slug = __FUNCTION__; + $t = self::factory()->term->create( + array( + 'slug' => $slug, + 'taxonomy' => 'wptests_tax', + ) + ); + $this->assertEquals( $t, term_exists( $slug ) ); + $num_queries = $wpdb->num_queries; + $this->assertEquals( $t, term_exists( $slug ) ); + $this->assertSame( $num_queries, $wpdb->num_queries ); + + $this->assertTrue( wp_delete_term( $t, 'wptests_tax' ) ); + $num_queries = $wpdb->num_queries; + $this->assertNull( term_exists( $slug ) ); + $this->assertSame( $num_queries + 2, $wpdb->num_queries ); + + // Clean up. + _unregister_taxonomy( 'wptests_tax' ); + } + + /** + * @ticket 36949 + * @covers ::term_exists() + */ + public function test_term_exists_caching_suspend_cache_invalidation() { + global $wpdb; + register_taxonomy( 'wptests_tax', 'post' ); + + wp_suspend_cache_invalidation( true ); + $slug = __FUNCTION__; + $t = self::factory()->term->create( + array( + 'slug' => $slug, + 'taxonomy' => 'wptests_tax', + ) + ); + + $this->assertEquals( $t, term_exists( $slug ) ); + $num_queries = $wpdb->num_queries; + $this->assertEquals( $t, term_exists( $slug ) ); + $this->assertSame( $num_queries + 1, $wpdb->num_queries ); + wp_suspend_cache_invalidation( false ); + + // Clean up. + _unregister_taxonomy( 'wptests_tax' ); + } + + /** + * @ticket 36949 + * @covers ::term_exists() + */ + public function test_term_exists_caching_by_int_suspend_cache_invalidation() { + register_taxonomy( 'wptests_tax', 'post' ); + + $slug = __FUNCTION__; + $t = self::factory()->term->create( + array( + 'slug' => $slug, + 'taxonomy' => 'wptests_tax', + ) + ); + + // Warm cache in get_term() via term_exists(). + term_exists( $t ); + wp_suspend_cache_invalidation( true ); + wp_delete_term( $t, 'wptests_tax' ); + $this->assertNull( term_exists( $t ) ); + + // Reneable cache invalidation. + wp_suspend_cache_invalidation( false ); + _unregister_taxonomy( 'wptests_tax' ); + } + public function test_term_exists_unknown() { $this->assertNull( term_exists( rand_str() ) ); $this->assertSame( 0, term_exists( 0 ) ); diff --git a/tests/phpunit/tests/term/wpGetObjectTerms.php b/tests/phpunit/tests/term/wpGetObjectTerms.php index 429086e2c2..f1a6a26fed 100644 --- a/tests/phpunit/tests/term/wpGetObjectTerms.php +++ b/tests/phpunit/tests/term/wpGetObjectTerms.php @@ -447,6 +447,8 @@ class Tests_Term_WpGetObjectTerms extends WP_UnitTestCase { $wpdb->update( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => 100006 ), array( 'term_taxonomy_id' => $term_2->term_taxonomy_id ) ); $wpdb->update( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => 100005 ), array( 'term_taxonomy_id' => $term_3->term_taxonomy_id ) ); + clean_term_cache( array( $t1, $t2, $t3 ), $this->taxonomy ); + $set = wp_set_object_terms( $p, array( $t1, $t2, $t3 ), $this->taxonomy ); $found = wp_get_object_terms(