Commit e80d7ea807 for woocommerce

commit e80d7ea807c1b1dd82f7240034eae232030e6d38
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Wed Feb 11 16:54:49 2026 +0100

    [Performance] Recent Reviews widget: address slow SQL query (take 2) (#63224)

    Re-introduced the hardened version of the widget.

diff --git a/plugins/woocommerce/changelog/perfromance-60194-optimize-recent-reviews-widget-take-2 b/plugins/woocommerce/changelog/perfromance-60194-optimize-recent-reviews-widget-take-2
new file mode 100644
index 0000000000..009cd4da41
--- /dev/null
+++ b/plugins/woocommerce/changelog/perfromance-60194-optimize-recent-reviews-widget-take-2
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Performance: reintroduced the optimized and hardened version of the recent reviews widget.
diff --git a/plugins/woocommerce/client/legacy/js/admin/wc-recent-reviews-widget-async.js b/plugins/woocommerce/client/legacy/js/admin/wc-recent-reviews-widget-async.js
new file mode 100644
index 0000000000..d0dfe92175
--- /dev/null
+++ b/plugins/woocommerce/client/legacy/js/admin/wc-recent-reviews-widget-async.js
@@ -0,0 +1,44 @@
+/**
+ * WooCommerce Recent reviews widget async loading
+ */
+jQuery(function($) {
+    'use strict';
+
+    // Only run on admin dashboard
+    if ( ! $( '#wc-recent-reviews-widget-loading' ).length ) {
+        return;
+    }
+
+    // Load the widget content via AJAX
+    function loadRecentReviewsWidget() {
+        $.ajax({
+            url: wc_recent_reviews_widget_params.ajax_url,
+            data: {
+                action: 'woocommerce_load_recent_reviews_widget',
+                security: wc_recent_reviews_widget_params.security
+            },
+            type: 'GET',
+            dataType: 'json',
+            success: function(response) {
+                if ( response && response.success && response.data.content ) {
+                    $( '#wc-recent-reviews-widget-content' ).html( response.data.content ).show();
+                    $( '#wc-recent-reviews-widget-loading' ).hide();
+                } else {
+                    showErrorMessage();
+                }
+            },
+            error: function() {
+                showErrorMessage();
+            }
+        });
+    }
+
+    function showErrorMessage() {
+		const message = wc_recent_reviews_widget_params.error_message || 'Error loading widget';
+        $( '#wc-recent-reviews-widget-loading' ).html( '<p>' + message + '</p>' );
+    }
+
+    // Start loading the widget after a very short delay
+    // This allows the dashboard to render quickly first
+    setTimeout( loadRecentReviewsWidget, 100 );
+});
diff --git a/plugins/woocommerce/client/legacy/js/admin/wc-status-widget-async.js b/plugins/woocommerce/client/legacy/js/admin/wc-status-widget-async.js
index 943a4ca847..b940906b99 100644
--- a/plugins/woocommerce/client/legacy/js/admin/wc-status-widget-async.js
+++ b/plugins/woocommerce/client/legacy/js/admin/wc-status-widget-async.js
@@ -3,12 +3,12 @@
  */
 jQuery(function($) {
     'use strict';
-
+
     // Only run on admin dashboard
     if (!$('#wc-status-widget-loading').length) {
         return;
     }
-
+
     // Load the widget content via AJAX
     function loadStatusWidget() {
         $.ajax({
@@ -32,12 +32,13 @@ jQuery(function($) {
             }
         });
     }
-
+
     function showErrorMessage() {
-        $('#wc-status-widget-loading').html('<p>' + 'Error loading widget' + '</p>');
+		const message = wc_status_widget_params.error_message || 'Error loading widget';
+        $('#wc-status-widget-loading').html('<p>' + message + '</p>');
     }
-
+
     // Start loading the widget after a very short delay
     // This allows the dashboard to render quickly first
     setTimeout(loadStatusWidget, 100);
-});
\ No newline at end of file
+});
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
index 0556c29f67..0d1cfb1cd7 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
@@ -143,8 +143,9 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
 				'wc-status-widget-async',
 				'wc_status_widget_params',
 				array(
-					'ajax_url' => admin_url( 'admin-ajax.php' ),
-					'security' => wp_create_nonce( 'wc-status-widget' ),
+					'ajax_url'      => admin_url( 'admin-ajax.php' ),
+					'security'      => wp_create_nonce( 'wc-status-widget' ),
+					'error_message' => esc_html__( 'Error loading widget', 'woocommerce' ),
 				)
 			);

@@ -474,10 +475,87 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
 		}

 		/**
-		 * Recent reviews widget.
+		 * Recent reviews widget: placeholder.
 		 */
 		public function recent_reviews() {
-			$this->legacy_recent_reviews();
+			$suffix  = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
+			$version = Constants::get_constant( 'WC_VERSION' );
+
+			wp_enqueue_script( 'wc-recent-reviews-widget-async', WC()->plugin_url() . '/assets/js/admin/wc-recent-reviews-widget-async' . $suffix . '.js', array( 'jquery' ), $version, true );
+			wp_localize_script(
+				'wc-recent-reviews-widget-async',
+				'wc_recent_reviews_widget_params',
+				array(
+					'ajax_url'      => admin_url( 'admin-ajax.php' ),
+					'security'      => wp_create_nonce( 'wc-recent-reviews-widget' ),
+					'error_message' => esc_html__( 'Error loading widget', 'woocommerce' ),
+				)
+			);
+
+			// Display loading placeholder.
+			echo '<div id="wc-recent-reviews-widget-loading" class="wc-recent-reviews-widget-loading">';
+			echo '<p>' . esc_html__( 'Loading reviews data...', 'woocommerce' ) . ' <span class="spinner is-active"></span></p>';
+			echo '</div>';
+			echo '<div id="wc-recent-reviews-widget-content" style="display:none;"></div>';
+		}
+
+		/**
+		 * Recent reviews widget: content.
+		 */
+		public function recent_reviews_content(): void {
+			// 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',
+					'parent'                    => 0,
+					'number'                    => 25,
+					'update_comment_post_cache' => true,
+				)
+			);
+			$comments = array_filter(
+				$comments,
+				static fn( \WP_Comment $comment ) => current_user_can( 'read_product', $comment->comment_post_ID ) && ! post_password_required( (int) $comment->comment_post_ID )
+			);
+			if ( $comments ) {
+				echo '<ul>';
+				$count_rendered = 0;
+				foreach ( $comments as $comment ) {
+					$product = wc_get_product( $comment->comment_post_ID );
+					if ( $product ) {
+						wc_get_template(
+							'dashboard-widget-reviews.php',
+							array(
+								'product' => $product,
+								'comment' => $comment,
+							)
+						);
+						if ( 5 === ++$count_rendered ) {
+							break;
+						}
+					}
+				}
+				echo '</ul>';
+			} else {
+				echo '<p>' . esc_html__( 'There are no product reviews yet.', 'woocommerce' ) . '</p>';
+			}
 		}

 		/**
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index 1768c9d222..20123fb36b 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -215,6 +215,7 @@ class WC_AJAX {
 			'shipping_classes_save_changes',
 			'toggle_gateway_enabled',
 			'load_status_widget',
+			'load_recent_reviews_widget',
 		);

 		foreach ( $ajax_events as $ajax_event ) {
@@ -3900,6 +3901,26 @@ class WC_AJAX {
 		wp_send_json_success( array( 'content' => $content ) );
 	}

+	/**
+	 * AJAX handler for asynchronously loading the recent reviews widget content.
+	 *
+	 * @return void
+	 */
+	public static function load_recent_reviews_widget() {
+		check_ajax_referer( 'wc-recent-reviews-widget', 'security' );
+
+		if ( ! current_user_can( 'publish_shop_orders' ) || ! post_type_supports( 'product', 'comments' ) ) {
+			wp_send_json_error( 'missing_permissions' );
+		}
+
+		include_once __DIR__ . '/admin/class-wc-admin-dashboard.php';
+		ob_start();
+		$wc_admin_dashboard = new WC_Admin_Dashboard();
+		$wc_admin_dashboard->recent_reviews_content();
+		$content = ob_get_clean();
+		wp_send_json_success( array( 'content' => $content ) );
+	}
+
 	/**
 	 * Reimplementation of WP core's `wp_ajax_add_meta` method to support order custom meta updates with custom tables.
 	 *
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php b/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php
index d1069287bf..87863f5643 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php
@@ -37,6 +37,63 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case {
 		remove_filter( 'woocommerce_dashboard_status_widget_reports', array( $this, 'mock_replace_dashboard_status_widget_reports' ) );
 	}

+	/**
+	 * Test: recent reviews widget placeholder.
+	 */
+	public function test_recent_reviews_widget_placeholder() {
+		wp_set_current_user( $this->user );
+		( new WC_Admin_Dashboard() )->recent_reviews();
+		$this->expectOutputRegex( '/Loading reviews data.../' );
+		$this->expectOutputRegex( '/wc-recent-reviews-widget-loading/' );
+		$this->expectOutputRegex( '/wc-recent-reviews-widget-content/' );
+	}
+
+	/**
+	 * Test: recent reviews widget content (new).
+	 */
+	public function test_recent_reviews_widget_content_new_version() {
+		$product     = WC_Helper_Product::create_simple_product();
+		$product_id  = $product->get_id();
+		$comment_id1 = WC_Helper_Product::create_product_review( $product_id );
+		WC_Helper_Product::create_product_review( $product_id );
+		WC_Helper_Product::create_product_review( $product_id );
+		WC_Helper_Product::create_product_review( $product_id );
+		$comment_id5 = WC_Helper_Product::create_product_review( $product_id );
+
+		wp_set_current_user( $this->user );
+		( new WC_Admin_Dashboard() )->recent_reviews_content();
+		$this->expectOutputRegex( "/#comment-{$comment_id1}/" );
+		$this->expectOutputRegex( "/#comment-{$comment_id5}/" );
+		$this->expectOutputRegex( '/reviewed by/' );
+
+		$product->delete();
+	}
+
+	/**
+	 * Test: recent reviews widget content (legacy).
+	 */
+	public function test_recent_reviews_widget_content_legacy_version() {
+		add_filter( 'woocommerce_report_recent_reviews_query_from', $legacy_filter = fn( string $sql ) => $sql );
+		$this->expected_deprecated = array_merge( $this->expected_deprecated, array( 'woocommerce_report_recent_reviews_query_from' ) );
+
+		$product     = WC_Helper_Product::create_simple_product();
+		$product_id  = $product->get_id();
+		$comment_id1 = WC_Helper_Product::create_product_review( $product_id );
+		WC_Helper_Product::create_product_review( $product_id );
+		WC_Helper_Product::create_product_review( $product_id );
+		WC_Helper_Product::create_product_review( $product_id );
+		$comment_id5 = WC_Helper_Product::create_product_review( $product_id );
+
+		wp_set_current_user( $this->user );
+		( new WC_Admin_Dashboard() )->recent_reviews_content();
+		$this->expectOutputRegex( "/#comment-{$comment_id1}/" );
+		$this->expectOutputRegex( "/#comment-{$comment_id5}/" );
+		$this->expectOutputRegex( '/reviewed by/' );
+
+		$product->delete();
+		remove_filter( 'woocommerce_report_recent_reviews_query_from', $legacy_filter );
+	}
+
 	/**
 	 * Test: status_widget placeholder
 	 */