From 3aa7d98b11c6d62c2d78832a13a50a68c4c9e4ab Mon Sep 17 00:00:00 2001 From: liaisontw Date: Wed, 15 Apr 2026 18:05:10 +0800 Subject: [PATCH] Taxonomy: Ensure `_pad_term_counts()` handles post types safely.This commit updates `_pad_term_counts()` to use `$wpdb->prepare()` for all database queries, improving security and consistency. Additionally, it introduces a filter using `post_type_exists()` to ensure that only registered post types are queried, aligning the behavior with `_update_post_term_count()`.Includes comprehensive unit tests to verify the logic and prevent regressions.Fixes #65055.See #11542. --- src/wp-includes/taxonomy.php | 16 +++++++-- tests/phpunit/tests/taxonomy.php | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 80f457de0e6f7..ee544279382f0 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -4065,8 +4065,20 @@ function _pad_term_counts( &$terms, $taxonomy ) { // Get the object and term IDs and stick them in a lookup table. $tax_obj = get_taxonomy( $taxonomy ); - $object_types = esc_sql( $tax_obj->object_type ); - $results = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (" . implode( ',', array_keys( $term_ids ) ) . ") AND post_type IN ('" . implode( "', '", $object_types ) . "') AND post_status = 'publish'" ); + $object_types = (array) $tax_obj->object_type; + + //Filter invalid post types for consistency with _update_post_term_count(). + $object_types = array_filter( $object_types, 'post_type_exists' ); + if ( empty( $object_types ) ) { + return; + } + + $results = $wpdb->get_results( + $wpdb->prepare( + "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (" . implode( ',', array_fill( 0, count( $term_ids ), '%d' ) ) . ') AND post_type IN (' . implode( ',', array_fill( 0, count( $object_types ), '%s' ) ) . ") AND post_status = 'publish'", + array_merge( array_keys( $term_ids ), $object_types ) + ) + ); foreach ( $results as $row ) { $id = $term_ids[ $row->term_taxonomy_id ]; diff --git a/tests/phpunit/tests/taxonomy.php b/tests/phpunit/tests/taxonomy.php index 13528c3015c6b..c9113ae1ef6f7 100644 --- a/tests/phpunit/tests/taxonomy.php +++ b/tests/phpunit/tests/taxonomy.php @@ -1124,4 +1124,66 @@ public function test_default_term_for_post_in_multiple_taxonomies() { $this->assertContains( $tax1, $taxonomies ); $this->assertContains( $tax2, $taxonomies ); } + + /** + * @ticket 65055 + * Filter invalid post types before SQL query + */ + public function test_pad_term_counts_with_invalid_post_types() { + register_taxonomy( 'invalid_tax', array( 'non_existent_type' ), array( 'hierarchical' => true ) ); + + $parent = self::factory()->term->create( array( 'taxonomy' => 'invalid_tax' ) ); + self::factory()->term->create( + array( + 'taxonomy' => 'invalid_tax', + 'parent' => $parent, + ) + ); + + _get_term_hierarchy( 'invalid_tax' ); + + $terms = get_terms( + array( + 'taxonomy' => 'invalid_tax', + 'hide_empty' => false, + ) + ); + + _pad_term_counts( $terms, 'invalid_tax' ); + + $this->assertEquals( 0, $terms[0]->count ); + } + + /** + * @ticket 65055 + */ + public function test_pad_term_counts_with_standard_post_types() { + register_post_type( 'book' ); + register_taxonomy( 'genre', array( 'book' ), array( 'hierarchical' => true ) ); + + $parent = self::factory()->term->create( array( 'taxonomy' => 'genre' ) ); + $child = self::factory()->term->create( + array( + 'taxonomy' => 'genre', + 'parent' => $parent, + ) + ); + + _get_term_hierarchy( 'genre' ); + + $post_id = self::factory()->post->create( array( 'post_type' => 'book' ) ); + wp_set_object_terms( $post_id, $child, 'genre' ); + + $terms = get_terms( + array( + 'taxonomy' => 'genre', + 'hide_empty' => false, + ) + ); + + _pad_term_counts( $terms, 'genre' ); + + $parent_term = wp_list_filter( $terms, array( 'term_id' => $parent ) ); + $this->assertEquals( 1, current( $parent_term )->count, 'Parent terms should include post counts from their child terms' ); + } }