[wp-trac] [WordPress Trac] #65055: _pad_term_counts() uses string-concatenated SQL without prepared statement
WordPress Trac
noreply at wordpress.org
Tue Apr 14 10:00:34 UTC 2026
#65055: _pad_term_counts() uses string-concatenated SQL without prepared statement
--------------------------+------------------------------
Reporter: rajeshcp | Owner: rajeshcp
Type: defect (bug) | Status: assigned
Priority: normal | Milestone: Awaiting Review
Component: Database | Version: trunk
Severity: major | Resolution:
Keywords: has-patch | Focuses:
--------------------------+------------------------------
Comment (by liaison):
Replying to [comment:5 abcd95]:
> Replying to [comment:4 liaison]:
>
> Thanks @liaison! agreed on the alignment with _update_post_term_count().
The updated patch incorporating post_type_exists() filtering looks solid.
Test Report:
I verified the fix using a zero-dependency test script that loads the core
wp-includes/taxonomy.php while mocking the database environment.
Before Patch:
The logic uses direct string interpolation and includes unregistered post
types in the query.
Method: get_results
Query: ... AND post_type IN ('post', 'ghost_type') ...
❌ FAILED: 'ghost_type' present in raw SQL string.
After Patch:
The logic now correctly filters post types via post_type_exists() and uses
wpdb::prepare() for the final query.
Method: prepare
Arguments: 123, post
✅ SUCCESS: 'ghost_type' filtered from prepare arguments.
test-65055-sql-filter.php
{{{#!php
<?php
<?php
/**
* Trac #65055: Zero-Dependency Test for Core wp-includes/taxonomy.php
* Supports both patched (prepare) and unpatched (direct query) versions.
*/
define( 'ABSPATH', __DIR__ . '/' );
define( 'WPINC', 'wp-includes' );
/**
* 1. Robust Mock_WPDB
* Handles table name properties and captures both prepare/get_results.
*/
class Mock_WPDB {
public $queries = [];
public $prefix = 'wp_';
// Core table names used in _pad_term_counts
public $term_relationships = 'wp_term_relationships';
public $posts = 'wp_posts';
public function prepare( $query, ...$args ) {
// Flatten arguments if they were passed as an array
$flat_args = ( is_array( $args[0] ) && count( $args ) === 1 ) ?
$args[0] : $args;
$this->queries[] = [
'method' => 'prepare',
'query' => $query,
'args' => $flat_args
];
return $query;
}
public function get_results( $query ) {
// If query was not recorded by prepare(), it's a direct string
query
$already_recorded = false;
foreach ( $this->queries as $q ) {
if ( $q['query'] === $query ) {
$already_recorded = true;
break;
}
}
if ( ! $already_recorded ) {
$this->queries[] = [
'method' => 'get_results',
'query' => $query,
'args' => []
];
}
// Return empty result to satisfy the function
return array();
}
}
$GLOBALS['wpdb'] = new Mock_WPDB();
/**
* 2. Mock Global Functions (Stubbing)
*/
if ( ! function_exists( 'post_type_exists' ) ) {
function post_type_exists( $post_type ) {
return in_array( $post_type, array( 'post' ), true );
}
}
if ( ! function_exists( 'wp_installing' ) ) {
function wp_installing() { return false; }
}
if ( ! function_exists( 'esc_sql' ) ) {
function esc_sql( $data ) { return $data; }
}
if ( ! function_exists( 'get_option' ) ) {
function get_option( $option, $default = false ) {
// Force get_term_hierarchy to believe Term 123 has a child 999
if ( strpos( $option, '_children' ) !== false ) {
return array( 123 => array( 999 ) );
}
return array();
}
}
/**
* 3. Load actual Core file
*/
if ( file_exists( ABSPATH . WPINC . '/taxonomy.php' ) ) {
require_once ABSPATH . WPINC . '/taxonomy.php';
} else {
die( "❌ Error: wp-includes/taxonomy.php not found. Check your
ABSPATH.\n" );
}
/**
* 4. Setup Global State
*/
global $wp_taxonomies;
$test_tax = new stdClass();
$test_tax->name = 'test_tax';
$test_tax->object_type = array( 'post', 'ghost_type' ); // Mixed
valid/invalid
$test_tax->hierarchical = true;
$wp_taxonomies['test_tax'] = $test_tax;
/**
* 5. Execute Test
*/
echo "--- Starting Core _pad_term_counts() Test ---\n";
$term = new stdClass();
$term->term_id = 123;
$term->term_taxonomy_id = 123;
$term->parent = 0;
$term->count = 0;
$terms_array = array( $term );
_pad_term_counts( $terms_array, 'test_tax' );
/**
* 6. Final Analysis
*/
$last_execution = end( $GLOBALS['wpdb']->queries );
if ( ! $last_execution ) {
die( "❌ FAILED: No database activity detected.\n" );
}
echo "\n[Execution Result]\n";
echo "Method: " . $last_execution['method'] . "\n";
if ( $last_execution['method'] === 'prepare' ) {
$args = $last_execution['args'];
echo "Arguments: " . implode( ', ', $args ) . "\n";
if ( in_array( 'ghost_type', $args, true ) ) {
echo "❌ FAILED: 'ghost_type' remains in prepare arguments.\n";
} else {
echo "✅ SUCCESS: 'ghost_type' filtered from prepare
arguments.\n";
}
} else {
echo "Query: " . $last_execution['query'] . "\n";
if ( strpos( $last_execution['query'], 'ghost_type' ) !== false ) {
echo "❌ FAILED: 'ghost_type' present in raw SQL string.\n";
} else {
echo "✅ SUCCESS: 'ghost_type' not found in SQL string.\n";
}
}
echo "\n--- Test Completed ---\n";
}}}
--
Ticket URL: <https://core.trac.wordpress.org/ticket/65055#comment:6>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list