Commit 47d2a22bbb for woocommerce

commit 47d2a22bbb9b780d36da90aa57b87efd1896ed01
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Mon Jan 12 08:57:57 2026 +0100

    [Performance] Recent Reviews widget: address slow SQL query (#62680)

    In this PR, we optimize the SQL query for fetching recent comments and (in a backward-compatible way) switch to templates for rendering the widget.

diff --git a/plugins/woocommerce/changelog/performance-60194-last-reviews-widget-slow-sql b/plugins/woocommerce/changelog/performance-60194-last-reviews-widget-slow-sql
new file mode 100644
index 0000000000..9a063d183d
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-60194-last-reviews-widget-slow-sql
@@ -0,0 +1,4 @@
+Significance: patch
+Type: performance
+
+Recent Reviews widget: improve widget performance.
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
index 39ae02406f..f54f6a9862 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
@@ -412,11 +412,18 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
 		}

 		/**
-		 * Recent reviews widget.
+		 * Recent reviews widget: legacy implementation.
 		 */
-		public function recent_reviews() {
+		private function legacy_recent_reviews(): void {
 			global $wpdb;

+			/**
+			 * Filters the from-clause used for fetching latest product reviews.
+			 *
+			 * @since 3.1.0
+			 *
+			 * @param string $clause The from-clause.
+			 */
 			$query_from = apply_filters(
 				'woocommerce_report_recent_reviews_query_from',
 				"FROM {$wpdb->comments} comments
@@ -442,13 +449,21 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :

 					echo get_avatar( $comment->comment_author_email, '32' );

-					$rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) );
+					/**
+					 * Filters the product name for display in the latest reviews.
+					 *
+					 * @param string    $product_title The product name.
+					 * @param \stdClass $comment      The comment.
+					 * @since 2.1.0
+					 */
+					$product_title = apply_filters( 'woocommerce_admin_dashboard_recent_reviews', $comment->post_title, $comment );
+					$rating        = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) );

 					/* translators: %s: rating */
 					echo '<div class="star-rating"><span style="width:' . esc_attr( $rating * 20 ) . '%">' . sprintf( esc_html__( '%s out of 5', 'woocommerce' ), esc_html( $rating ) ) . '</span></div>';

 					/* translators: %s: review author */
-					echo '<h4 class="meta"><a href="' . esc_url( get_permalink( $comment->ID ) ) . '#comment-' . esc_attr( absint( $comment->comment_ID ) ) . '">' . esc_html( apply_filters( 'woocommerce_admin_dashboard_recent_reviews', $comment->post_title, $comment ) ) . '</a> ' . sprintf( esc_html__( 'reviewed by %s', 'woocommerce' ), esc_html( $comment->comment_author ) ) . '</h4>';
+					echo '<h4 class="meta"><a href="' . esc_url( get_permalink( $comment->ID ) ) . '#comment-' . esc_attr( absint( $comment->comment_ID ) ) . '">' . esc_html( $product_title ) . '</a> ' . sprintf( esc_html__( 'reviewed by %s', 'woocommerce' ), esc_html( $comment->comment_author ) ) . '</h4>';
 					echo '<blockquote>' . wp_kses_data( $comment->comment_content ) . '</blockquote></li>';

 				}
@@ -458,6 +473,61 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
 			}
 		}

+		/**
+		 * Recent reviews widget.
+		 */
+		public function recent_reviews() {
+			// Backward compatibility mode: if any of the checked below hooks are in use, use the legacy implementation.
+			$has_legacy_query_filter         = has_filter( 'woocommerce_report_recent_reviews_query_from' );
+			$has_legacy_product_title_filter = has_filter( 'woocommerce_admin_dashboard_recent_reviews' );
+			$use_legacy_implementation       = $has_legacy_query_filter || $has_legacy_product_title_filter;
+			if ( $use_legacy_implementation ) {
+				if ( $has_legacy_query_filter ) {
+					wc_deprecated_hook( 'woocommerce_report_recent_reviews_query_from', '10.5.0' );
+				}
+				if ( $has_legacy_product_title_filter ) {
+					wc_deprecated_hook( 'woocommerce_admin_dashboard_recent_reviews', '10.5.0', 'dashboard-widget-reviews.php template' );
+				}
+				$this->legacy_recent_reviews();
+
+				return;
+			}
+
+			// Optimized version of the widget: faster SQL queries and templates-based rendering for customization.
+			/** @var \WP_Comment[] $comments */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
+			$comments = get_comments(
+				array(
+					'type'   => 'review',
+					'status' => 'approve',
+					'number' => 5,
+				)
+			);
+			_prime_post_caches(
+				array_map( static fn( \WP_Comment $comment ) => (int) $comment->comment_post_ID, $comments ),
+				false,
+				false
+			);
+			$comments = array_filter(
+				$comments,
+				static fn( \WP_Comment $comment ) => current_user_can( 'read_product', $comment->comment_post_ID )
+			);
+			if ( $comments ) {
+				echo '<ul>';
+				foreach ( $comments as $comment ) {
+					wc_get_template(
+						'dashboard-widget-reviews.php',
+						array(
+							'product' => wc_get_product( $comment->comment_post_ID ),
+							'comment' => $comment,
+						)
+					);
+				}
+				echo '</ul>';
+			} else {
+				echo '<p>' . esc_html__( 'There are no product reviews yet.', 'woocommerce' ) . '</p>';
+			}
+		}
+
 		/**
 		 * Network orders widget.
 		 */
diff --git a/plugins/woocommerce/includes/class-wc-comments.php b/plugins/woocommerce/includes/class-wc-comments.php
index 14a4634066..71a95ca5e7 100644
--- a/plugins/woocommerce/includes/class-wc-comments.php
+++ b/plugins/woocommerce/includes/class-wc-comments.php
@@ -65,7 +65,7 @@ class WC_Comments {
 		// Count comments.
 		add_filter( 'wp_count_comments', array( __CLASS__, 'wp_count_comments' ), 10, 2 );

-		// Delete comments count cache whenever there is a new comment or a comment status changes.
+		// Actualize comments count cache whenever there is a new comment or a comment status changes.
 		add_action( 'wp_insert_comment', array( __CLASS__, 'increment_comments_count_cache_on_wp_insert_comment' ), 10, 2 );
 		add_action( 'transition_comment_status', array( __CLASS__, 'update_comments_count_cache_on_comment_status_change' ), 10, 3 );

diff --git a/plugins/woocommerce/templates/dashboard-widget-reviews.php b/plugins/woocommerce/templates/dashboard-widget-reviews.php
new file mode 100644
index 0000000000..6d52951d84
--- /dev/null
+++ b/plugins/woocommerce/templates/dashboard-widget-reviews.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * This template displays the recent product review widget on the WordPress dashboard.
+ *
+ * This template can be overridden by copying it to yourtheme/woocommerce/dashboard-widget-reviews.php
+ *
+ * HOWEVER, on occasion WooCommerce will need to update template files and you
+ * (the theme developer) will need to copy the new files to your theme to
+ * maintain compatibility. We try to do this as little as possible, but it does
+ * happen. When this occurs the version of the template file will be bumped and
+ * the readme will list any important changes.
+ *
+ * @see     https://woocommerce.com/document/template-structure/
+ * @package WooCommerce\Templates
+ * @version 10.5.0
+ */
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * For this template, the following variables are available
+ *
+ * @var $product \WC_Product
+ * @var $comment \WP_Comment
+ */
+
+?>
+
+<li>
+	<?php
+	// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
+	?>
+
+	<?php echo get_avatar( $comment->comment_author_email, '32' ); ?>
+
+	<?php echo wc_get_rating_html( (int) get_comment_meta( $comment->comment_ID, 'rating', true ) ); ?>
+
+	<h4 class="meta">
+		<a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>"><?php echo wp_kses_post( $product->get_name() ); ?></a>
+		<?php
+		/* translators: %s: review author */
+		printf( esc_html__( 'reviewed by %s', 'woocommerce' ), esc_html( get_comment_author( $comment->comment_ID ) ) );
+		?>
+	</h4>
+
+	<blockquote><?php echo wp_kses_data( $comment->comment_content ); ?></blockquote>
+
+	<?php
+	// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
+	?>
+</li>