diff -r 7b1b88e27a20 -r 48c4eec2b7e6 wp/wp-includes/class-wp-term-query.php --- a/wp/wp-includes/class-wp-term-query.php Thu Sep 29 08:06:27 2022 +0200 +++ b/wp/wp-includes/class-wp-term-query.php Fri Sep 05 18:40:08 2025 +0200 @@ -15,6 +15,7 @@ * * @see WP_Term_Query::__construct() for accepted arguments. */ +#[AllowDynamicProperties] class WP_Term_Query { /** @@ -90,6 +91,7 @@ * @since 4.9.0 Added 'slug__in' support for 'orderby'. * @since 5.1.0 Introduced the 'meta_compare_key' parameter. * @since 5.3.0 Introduced the 'meta_type_key' parameter. + * @since 6.4.0 Introduced the 'cache_results' parameter. * * @param string|array $query { * Optional. Array or query string of term query parameters. Default empty. @@ -177,19 +179,20 @@ * Default false. * @type string $cache_domain Unique cache key to be produced when this query is stored in * an object cache. Default 'core'. + * @type bool $cache_results Whether to cache term information. Default true. * @type bool $update_term_meta_cache Whether to prime meta caches for matched terms. Default true. * @type string|string[] $meta_key Meta key or keys to filter by. * @type string|string[] $meta_value Meta value or values to filter by. * @type string $meta_compare MySQL operator used for comparing the meta value. - * See WP_Meta_Query::__construct for accepted values and default value. + * See WP_Meta_Query::__construct() for accepted values and default value. * @type string $meta_compare_key MySQL operator used for comparing the meta key. - * See WP_Meta_Query::__construct for accepted values and default value. + * See WP_Meta_Query::__construct() for accepted values and default value. * @type string $meta_type MySQL data type that the meta_value column will be CAST to for comparisons. - * See WP_Meta_Query::__construct for accepted values and default value. + * See WP_Meta_Query::__construct() for accepted values and default value. * @type string $meta_type_key MySQL data type that the meta_key column will be CAST to for comparisons. - * See WP_Meta_Query::__construct for accepted values and default value. + * See WP_Meta_Query::__construct() for accepted values and default value. * @type array $meta_query An associative array of WP_Meta_Query arguments. - * See WP_Meta_Query::__construct for accepted values. + * See WP_Meta_Query::__construct() for accepted values. * } */ public function __construct( $query = '' ) { @@ -219,6 +222,7 @@ 'parent' => '', 'childless' => false, 'cache_domain' => 'core', + 'cache_results' => true, 'update_term_meta_cache' => true, 'meta_query' => '', 'meta_key' => '', @@ -237,7 +241,7 @@ * * @since 4.6.0 * - * @param string|array $query WP_Term_Query arguments. See WP_Term_Query::__construct() + * @param string|array $query WP_Term_Query arguments. See WP_Term_Query::__construct() for accepted arguments. */ public function parse_query( $query = '' ) { if ( empty( $query ) ) { @@ -484,6 +488,7 @@ if ( ! empty( $exclude_tree ) ) { $exclude_tree = wp_parse_id_list( $exclude_tree ); $excluded_children = $exclude_tree; + foreach ( $exclude_tree as $extrunk ) { $excluded_children = array_merge( $excluded_children, @@ -497,6 +502,7 @@ ) ); } + $exclusions = array_merge( $excluded_children, $exclusions ); } @@ -531,7 +537,7 @@ $exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies ); if ( ! empty( $exclusions ) ) { - // Must do string manipulation here for backward compatibility with filter. + // Strip leading 'AND'. Must do string manipulation here for backward compatibility with filter. $this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions ); } @@ -543,6 +549,7 @@ if ( ! empty( $args['name'] ) ) { $names = $args['name']; + foreach ( $names as &$_name ) { // `sanitize_term_field()` returns slashed data. $_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) ); @@ -650,9 +657,12 @@ $meta_clauses = $this->meta_query->get_clauses(); if ( ! empty( $meta_clauses ) ) { - $join .= $mq_sql['join']; + $join .= $mq_sql['join']; + + // Strip leading 'AND'. $this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] ); - $distinct .= 'DISTINCT'; + + $distinct .= 'DISTINCT'; } @@ -700,7 +710,7 @@ $where = implode( ' AND ', $this->sql_clauses['where'] ); - $clauses = array( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ); + $pieces = array( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ); /** * Filters the terms query SQL clauses. @@ -721,7 +731,7 @@ * @param string[] $taxonomies An array of taxonomy names. * @param array $args An array of term query arguments. */ - $clauses = apply_filters( 'terms_clauses', compact( $clauses ), $taxonomies, $args ); + $clauses = apply_filters( 'terms_clauses', compact( $pieces ), $taxonomies, $args ); $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : ''; $join = isset( $clauses['join'] ) ? $clauses['join'] : ''; @@ -731,6 +741,8 @@ $order = isset( $clauses['order'] ) ? $clauses['order'] : ''; $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : ''; + $fields_is_filtered = implode( ', ', $selects ) !== $fields; + if ( $where ) { $where = "WHERE $where"; } @@ -740,13 +752,13 @@ $this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : ''; $this->sql_clauses['limits'] = $limits; - $this->request = " - {$this->sql_clauses['select']} - {$this->sql_clauses['from']} - {$where} - {$this->sql_clauses['orderby']} - {$this->sql_clauses['limits']} - "; + // Beginning of the string is on a new line to prevent leading whitespace. See https://core.trac.wordpress.org/ticket/56841. + $this->request = + "{$this->sql_clauses['select']} + {$this->sql_clauses['from']} + {$where} + {$this->sql_clauses['orderby']} + {$this->sql_clauses['limits']}"; $this->terms = null; @@ -767,48 +779,47 @@ return $this->terms; } - // $args can be anything. Only use the args defined in defaults to compute the key. - $cache_args = wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ); - - unset( $cache_args['update_term_meta_cache'] ); - - if ( 'count' !== $_fields && 'all_with_object_id' !== $_fields ) { - $cache_args['fields'] = 'all'; - } - - $key = md5( serialize( $cache_args ) . serialize( $taxonomies ) . $this->request ); - $last_changed = wp_cache_get_last_changed( 'terms' ); - $cache_key = "get_terms:$key:$last_changed"; - $cache = wp_cache_get( $cache_key, 'terms' ); + if ( $args['cache_results'] ) { + $cache_key = $this->generate_cache_key( $args, $this->request ); + $cache = wp_cache_get( $cache_key, 'term-queries' ); - if ( false !== $cache ) { - if ( 'ids' === $_fields ) { - $cache = array_map( 'intval', $cache ); - } elseif ( 'count' !== $_fields ) { - if ( ( 'all_with_object_id' === $_fields && ! empty( $args['object_ids'] ) ) || ( 'all' === $_fields && $args['pad_counts'] ) ) { - $term_ids = wp_list_pluck( $cache, 'term_id' ); - } else { - $term_ids = array_map( 'intval', $cache ); + if ( false !== $cache ) { + if ( 'ids' === $_fields ) { + $cache = array_map( 'intval', $cache ); + } elseif ( 'count' !== $_fields ) { + if ( ( 'all_with_object_id' === $_fields && ! empty( $args['object_ids'] ) ) + || ( 'all' === $_fields && $args['pad_counts'] || $fields_is_filtered ) + ) { + $term_ids = wp_list_pluck( $cache, 'term_id' ); + } else { + $term_ids = array_map( 'intval', $cache ); + } + + _prime_term_caches( $term_ids, $args['update_term_meta_cache'] ); + + $term_objects = $this->populate_terms( $cache ); + $cache = $this->format_terms( $term_objects, $_fields ); } - _prime_term_caches( $term_ids, $args['update_term_meta_cache'] ); - $term_objects = $this->populate_terms( $cache ); - $cache = $this->format_terms( $term_objects, $_fields ); + + $this->terms = $cache; + return $this->terms; } - - $this->terms = $cache; - return $this->terms; } if ( 'count' === $_fields ) { $count = $wpdb->get_var( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - wp_cache_set( $cache_key, $count, 'terms' ); + if ( $args['cache_results'] ) { + wp_cache_set( $cache_key, $count, 'term-queries' ); + } return $count; } $terms = $wpdb->get_results( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( empty( $terms ) ) { - wp_cache_add( $cache_key, array(), 'terms' ); + if ( $args['cache_results'] ) { + wp_cache_add( $cache_key, array(), 'term-queries' ); + } return array(); } @@ -837,6 +848,7 @@ foreach ( $term_objects as $k => $term ) { if ( ! $term->count ) { $children = get_term_children( $term->term_id, $term->taxonomy ); + if ( is_array( $children ) ) { foreach ( $children as $child_id ) { $child = get_term( $child_id, $term->taxonomy ); @@ -864,7 +876,7 @@ // Prime termmeta cache. if ( $args['update_term_meta_cache'] ) { $term_ids = wp_list_pluck( $term_objects, 'term_id' ); - update_termmeta_cache( $term_ids ); + wp_lazyload_term_meta( $term_ids ); } if ( 'all_with_object_id' === $_fields && ! empty( $args['object_ids'] ) ) { @@ -883,10 +895,16 @@ $object->count = $term->count; $term_cache[] = $object; } + } elseif ( $fields_is_filtered ) { + $term_cache = $term_objects; } else { $term_cache = wp_list_pluck( $term_objects, 'term_id' ); } - wp_cache_add( $cache_key, $term_cache, 'terms' ); + + if ( $args['cache_results'] ) { + wp_cache_add( $cache_key, $term_cache, 'term-queries' ); + } + $this->terms = $this->format_terms( $term_objects, $_fields ); return $this->terms; @@ -897,8 +915,6 @@ * * @since 4.6.0 * - * @global wpdb $wpdb WordPress database abstraction object. - * * @param string $orderby_raw Alias for the field to order by. * @return string|false Value to used in the ORDER clause. False otherwise. */ @@ -1131,4 +1147,36 @@ return $term_objects; } + + /** + * Generate cache key. + * + * @since 6.2.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param array $args WP_Term_Query arguments. + * @param string $sql SQL statement. + * + * @return string Cache key. + */ + protected function generate_cache_key( array $args, $sql ) { + global $wpdb; + // $args can be anything. Only use the args defined in defaults to compute the key. + $cache_args = wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ); + + unset( $cache_args['cache_results'], $cache_args['update_term_meta_cache'] ); + + if ( 'count' !== $args['fields'] && 'all_with_object_id' !== $args['fields'] ) { + $cache_args['fields'] = 'all'; + } + $taxonomies = (array) $args['taxonomy']; + + // Replace wpdb placeholder in the SQL statement used by the cache key. + $sql = $wpdb->remove_placeholder_escape( $sql ); + + $key = md5( serialize( $cache_args ) . serialize( $taxonomies ) . $sql ); + $last_changed = wp_cache_get_last_changed( 'terms' ); + return "get_terms:$key:$last_changed"; + } }