wp/wp-includes/class-wp-term-query.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
--- 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";
+	}
 }