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>