Commit 5ff846ea12 for wordpress.org

commit 5ff846ea12a94819a98f367abc022dd7e598854c
Author: westonruter <westonruter@git.wordpress.org>
Date:   Tue Oct 20 05:50:52 2026 +0000

    Posts, Post Types: Add `pre_wp_count_attachments` filter.

    Introduces the `pre_wp_count_attachments` filter to `wp_count_attachments()`, the counterpart of `pre_wp_count_posts`. Returning a `stdClass` object short-circuits the function, skipping the object cache lookup and both of its queries. Since the `counts` cache group is non-persistent, those queries otherwise run on every request that needs the counts, and on a site with millions of attachments the `GROUP BY post_mime_type` aggregate alone can take several seconds. The filter allows the counts to be served instead from a value pre-computed in the background.

    Unlike post statuses, the set of MIME types is not known ahead of time, so only a missing trash count is filled in with zero. The filtered value should therefore contain a count for every MIME type matching `$mime_type`, and only those types. As with `pre_wp_count_posts`, the value is not cached and is passed through the `wp_count_attachments` filter.

    The `pre_wp_count_posts` filter introduced in r64245 is brought in line with the new one. A filtered value now short-circuits only when it is a `stdClass` object: any other non-null value was previously cast to an object, turning `false` into a nonsensical result, and filling in missing counts on another class can trigger a dynamic property deprecation. Both filters also clone the returned object, so that a callback which keeps it, such as in a static variable, does not have it modified by core or by later callbacks on the `wp_count_posts` and `wp_count_attachments` filters.

    Developed in https://github.com/WordPress/wordpress-develop/pull/14210.
    Follow-up to r64245.

    Props jdeep, westonruter, peterwilsoncc.
    See #66098.
    Fixes #66291.

    Built from https://develop.svn.wordpress.org/trunk@64589


    git-svn-id: http://core.svn.wordpress.org/trunk@63739 1a063a9b-81f0-0310-95a4-ce76da25c4cd

diff --git a/wp-includes/post.php b/wp-includes/post.php
index de0ff1d0c8..78fa26a1b0 100644
--- a/wp-includes/post.php
+++ b/wp-includes/post.php
@@ -3544,12 +3544,12 @@ function wp_count_posts( $type = 'post', $perm = '' ) {
 	/**
 	 * Filters the post counts before the query is run.
 	 *
-	 * Returning a non-null value short-circuits wp_count_posts(), skipping both
-	 * the object cache lookup and the database query. This allows the counts to
-	 * be served from another source, such as a value pre-computed in the
-	 * background, when an exact real-time count is not required.
+	 * Returning a stdClass object short-circuits wp_count_posts(), skipping
+	 * both the object cache lookup and the database query. This allows the
+	 * counts to be served from another source, such as a value pre-computed in
+	 * the background, when an exact real-time count is not required.
 	 *
-	 * Any registered post status missing from the filtered value are added
+	 * Any registered post status missing from the filtered value is added
 	 * to the result and set to zero.
 	 *
 	 * The returned value is not cached and passed through the
@@ -3565,8 +3565,8 @@ function wp_count_posts( $type = 'post', $perm = '' ) {
 	 */
 	$counts = apply_filters( 'pre_wp_count_posts', null, $type, $perm );

-	if ( null !== $counts ) {
-		$counts = (object) $counts;
+	if ( $counts instanceof stdClass ) {
+		$counts = clone $counts;

 		foreach ( get_post_stati() as $status ) {
 			if ( ! isset( $counts->{$status} ) ) {
@@ -3669,6 +3669,47 @@ function wp_count_posts( $type = 'post', $perm = '' ) {
 function wp_count_attachments( $mime_type = '' ) {
 	global $wpdb;

+	/**
+	 * Filters the attachment counts before the query is run.
+	 *
+	 * Returning a stdClass object short-circuits wp_count_attachments(),
+	 * skipping both the object cache lookup and the database queries. This
+	 * allows the counts to be served from another source, such as a value
+	 * pre-computed in the background, when an exact real-time count is not
+	 * required.
+	 *
+	 * If the filtered value does not contain a trash count, one is added to the
+	 * result and set to zero. Missing MIME types are not added, as they depend on
+	 * the attachments that exist. The filtered value should therefore contain a
+	 * count for every MIME type matching `$mime_type` that has attachments,
+	 * including types first uploaded after the value was computed, as callers
+	 * may read the count for any MIME type that exists. It should also contain
+	 * only MIME types matching `$mime_type`, with a trash count limited to
+	 * attachments of those types, as the default query does.
+	 *
+	 * The returned value is not cached and passed through the
+	 * {@see 'wp_count_attachments'} filter.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @param stdClass|null   $counts    An object containing the attachment counts by
+	 *                                   mime type, or null to run the default query.
+	 *                                   Default null.
+	 * @param string|string[] $mime_type Array or comma-separated list of MIME patterns.
+	 */
+	$counts = apply_filters( 'pre_wp_count_attachments', null, $mime_type );
+
+	if ( $counts instanceof stdClass ) {
+		$counts = clone $counts;
+
+		if ( ! isset( $counts->trash ) ) {
+			$counts->trash = 0;
+		}
+
+		/** This filter is documented in wp-includes/post.php */
+		return apply_filters( 'wp_count_attachments', $counts, $mime_type );
+	}
+
 	$cache_key = sprintf(
 		'attachments%s',
 		! empty( $mime_type ) ? ':' . str_replace( '/', '_', implode( '-', (array) $mime_type ) ) : ''
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 4ee11ee863..2a6ea03638 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-alpha-64588';
+$wp_version = '7.2-alpha-64589';

 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.