Commit 5cdf67c9b3e for woocommerce
commit 5cdf67c9b3e316164f83a3a31a674980e2d121eb
Author: Poli Gilad <83961704+poligilad-auto@users.noreply.github.com>
Date: Thu Jun 25 16:26:24 2026 +0200
Improve legacy WooCommerce dashboard widgets (#65704)
* Improve legacy WooCommerce dashboard widgets
* Add changelog entry for dashboard widget improvements
* Fix dashboard widget static analysis
* Always show legacy WooCommerce dashboard widgets
* Show status widget labels before values
* Reduce status widget value size
* Darken status widget labels
* Refine dashboard widget visual polish
* Tighten empty reviews widget spacing
* Fix dashboard widget lint issues
* Fix dashboard widget PHP indentation
* Clarify dashboard widget order coverage
* Register status widget chart dependency
* Hide reviews widget when reviews are disabled
* Honor default reviews setting for dashboard widget
* Align dashboard color variable spacing
* Simplify dashboard loading assertions
* Address dashboard widget review feedback
* Fix dashboard setup array alignment
* Restore setup widget top spacing
* Align dashboard color variables
* Match status widget hover color
* Clip status widget hover corners
* Update dashboard widget status and review details
* Fix dashboard review widget translator comment
diff --git a/plugins/woocommerce/changelog/tweak-dashboard-setup-post-launch-widgets b/plugins/woocommerce/changelog/tweak-dashboard-setup-post-launch-widgets
new file mode 100644
index 00000000000..a0070b25a2b
--- /dev/null
+++ b/plugins/woocommerce/changelog/tweak-dashboard-setup-post-launch-widgets
@@ -0,0 +1,4 @@
+Significance: patch
+Type: tweak
+
+Show WooCommerce Status and Recent Reviews dashboard widgets during store setup.
diff --git a/plugins/woocommerce/client/legacy/css/_variables.scss b/plugins/woocommerce/client/legacy/css/_variables.scss
index 5e5dbe73c24..5bd91e4ad0f 100644
--- a/plugins/woocommerce/client/legacy/css/_variables.scss
+++ b/plugins/woocommerce/client/legacy/css/_variables.scss
@@ -25,8 +25,14 @@ $contentbg: #fff !default; // Conten
$subtext: #767676 !default; // small, breadcrumbs etc
// Mirrors @wordpress/base-styles/colors.scss (WP 7.0 admin gray scale).
-$gray-900: #1e1e1e !default; // Primary admin text
-$gray-700: #757575 !default; // Placeholder / secondary admin text
+$gray-900: #1e1e1e !default; // Primary admin text
+$gray-700: #757575 !default; // Placeholder / secondary admin text
+$wp-admin-hover-gray: #f0f0f0 !default; // WordPress dashboard hover background.
+$wp-admin-text-gray: #50575e !default; // WordPress dashboard text color.
+$wp-admin-icon-gray: #646970 !default; // WordPress dashboard icon color.
+
+// Mirrors @wordpress/base-styles/colors.scss alert colors.
+$wp-alert-red: #cc1818 !default;
// Border radius tokens — mirrors WP Design System naming.
$radius-s: 2px !default; // Small radius: inputs, buttons, selects.
@@ -59,7 +65,7 @@ $grid-unit-60: 48px !default; // Matche
--wc-form-border-radius: 4px;
--wc-form-border-width: 1px;
// Matches WP 7.0 $alert-red token for destructive actions.
- --wc-destructive: #cc1818;
+ --wc-destructive: #{$wp-alert-red};
// Matches WP 7.0 $radius-l token (used on dashboard widgets).
--wc-card-border-radius: 8px;
}
diff --git a/plugins/woocommerce/client/legacy/css/dashboard-setup.scss b/plugins/woocommerce/client/legacy/css/dashboard-setup.scss
index dba246e1f18..3a1f8b4287f 100644
--- a/plugins/woocommerce/client/legacy/css/dashboard-setup.scss
+++ b/plugins/woocommerce/client/legacy/css/dashboard-setup.scss
@@ -25,15 +25,62 @@
color: #757575;
}
- .description div {
+ .description {
+ display: flex;
+ align-items: center;
+ gap: 24px;
margin-top: 11px;
- float: left;
- width: 70%;
}
- .description img {
- float: right;
- width: 30%;
+ &__content {
+ flex: 1;
+ min-width: 0;
+
+ h3 {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: var(--grid-unit-10, 8px);
+ margin: var(--grid-unit-20, 16px) 0 var(--grid-unit-10, 8px);
+ font-size: 18px;
+ line-height: 1.3;
+ }
+
+ p {
+ margin: 0 0 16px;
+ color: #50575e;
+ }
+ }
+
+ #dashboard-widgets & &__content h3 {
+ margin: var(--grid-unit-20, 16px) 0 var(--grid-unit-10, 8px);
+ }
+
+ &__meta {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: var(--grid-unit-10, 8px);
+ }
+
+ &__in-progress {
+ background-color: var(--Alias-bg-bg-surface-warning, #fff2d7);
+ border-radius: 4px;
+ color: var(--Alias-text-text-warning, #4d3716);
+ display: inline-block;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 16px;
+ padding: var(--grid-unit-05, 4px) var(--grid-unit-10, 8px);
+ }
+
+ &__image {
+ flex: 0 0 90px;
+ width: 90px;
+ max-width: 90px;
+ height: auto;
+ margin-right: 24px;
}
.circle-progress {
@@ -42,11 +89,11 @@
circle {
stroke: #f0f0f0;
- stroke-width: 1px;
+ stroke-width: 2px;
}
.bar {
- stroke: #949494;
+ stroke: #3858e9;
}
}
}
diff --git a/plugins/woocommerce/client/legacy/css/dashboard.scss b/plugins/woocommerce/client/legacy/css/dashboard.scss
index d1c611f0518..9ccc6922128 100644
--- a/plugins/woocommerce/client/legacy/css/dashboard.scss
+++ b/plugins/woocommerce/client/legacy/css/dashboard.scss
@@ -13,6 +13,28 @@
/**
* Styling begins
*/
+.wc-dashboard-widget-loading {
+ padding: 16px 10px;
+ text-align: center;
+
+ p {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin: 0;
+ }
+
+ .spinner {
+ float: none;
+ margin: 0;
+ }
+
+ &__text {
+ color: $gray-700;
+ }
+}
+
ul.woocommerce_stats {
overflow: hidden;
zoom: 1;
@@ -48,10 +70,6 @@ ul.woocommerce_stats {
#woocommerce_dashboard_status {
- .wc-status-widget-loading {
- padding: 0 10px;
- }
-
.inside {
padding: 0;
margin: 0;
@@ -70,6 +88,7 @@ ul.woocommerce_stats {
.wc_status_list {
overflow: hidden;
margin: 0;
+ border-radius: 0 0 var(--wc-card-border-radius, 8px) var(--wc-card-border-radius, 8px);
li {
width: 50%;
@@ -82,9 +101,16 @@ ul.woocommerce_stats {
a {
display: block;
- padding: 9px 12px;
+ padding: 9px 12px 9px 44px;
position: relative;
- font-size: 12px;
+ font-size: 13px;
+ line-height: 1.5;
+ color: $wp-admin-icon-gray;
+
+ &:hover,
+ &:focus {
+ background-color: $wp-admin-hover-gray;
+ }
.wc_sparkline {
width: 4em;
@@ -99,23 +125,27 @@ ul.woocommerce_stats {
}
strong {
- font-size: 18px;
+ font-size: 16px;
line-height: 1.2em;
font-weight: normal;
display: block;
+ color: $gray-900;
}
&::before {
- @include icon();
- font-size: 2em;
- position: relative;
- width: auto;
- line-height: 1.2em;
- color: #464646;
- float: left;
- margin-right: 12px;
- margin-bottom: 12px;
+ @include icon_dashicons( "\f14c" );
+ font-size: 20px;
+ position: absolute;
+ top: 50%;
+ left: 12px;
+ transform: translateY(-50%);
+ width: 20px;
+ height: 20px;
+ line-height: 20px;
+ color: $wp-admin-icon-gray;
+ float: none;
+ margin: 0;
}
}
}
@@ -124,11 +154,15 @@ ul.woocommerce_stats {
border-top: 0;
}
+ li:not(.sales-this-month):not(.best-seller-this-month):not(.processing-orders):not(.on-hold-orders):not(.low-in-stock):not(.out-of-stock) {
+ clear: both;
+ width: 100%;
+ }
+
li.sales-this-month {
width: 100%;
a::before {
- font-family: "Dashicons";
content: "\f185";
}
}
@@ -137,7 +171,7 @@ ul.woocommerce_stats {
width: 100%;
a::before {
- content: "\e006";
+ content: "\f174";
}
}
@@ -145,16 +179,14 @@ ul.woocommerce_stats {
border-right: 1px solid #ececec;
a::before {
- content: "\e011";
- color: $green;
+ content: "\f469";
}
}
li.on-hold-orders {
a::before {
- content: "\e033";
- color: #999;
+ content: "\f523";
}
}
@@ -162,16 +194,14 @@ ul.woocommerce_stats {
border-right: 1px solid #ececec;
a::before {
- content: "\e016";
- color: $orange;
+ content: "\f14c";
}
}
li.out-of-stock {
a::before {
- content: "\e013";
- color: $red;
+ content: "\f158";
}
}
}
@@ -179,30 +209,58 @@ ul.woocommerce_stats {
#woocommerce_dashboard_recent_reviews {
+ #wc-recent-reviews-widget-content > p {
+ margin-bottom: 0;
+ }
+
+ ul {
+ margin: 0;
+ }
+
li {
- line-height: 1.5em;
+ display: grid;
+ grid-template-columns: 32px minmax(0, 1fr) max-content;
+ column-gap: 10px;
+ align-items: start;
+ font-size: 13px;
+ line-height: 1.4;
margin-bottom: 12px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
}
h4.meta {
- line-height: 1.4;
- margin: -0.2em 0 0 0;
+ grid-column: 2;
+ grid-row: 1;
+ font-size: 13px;
+ line-height: 1.5;
+ margin: 0;
font-weight: normal;
- color: #999;
+ color: $wp-admin-icon-gray;
}
blockquote {
+ grid-column: 2 / -1;
+ grid-row: 2;
+ line-height: 1.4;
padding: 0;
margin: 0;
}
.avatar {
- float: left;
- margin: 0 10px 5px 0;
+ float: none;
+ grid-column: 1;
+ grid-row: 1 / span 2;
+ margin: 0;
}
.star-rating {
- float: right;
+ float: none;
+ grid-column: 3;
+ grid-row: 1;
+ justify-self: end;
overflow: hidden;
position: relative;
height: 1.5em;
@@ -213,7 +271,7 @@ ul.woocommerce_stats {
&::before {
content: "\e021\e021\e021\e021\e021";
- color: darken(#ccc, 10%);
+ color: $wp-admin-icon-gray;
float: left;
top: 0;
left: 0;
@@ -238,7 +296,7 @@ ul.woocommerce_stats {
left: 0;
letter-spacing: 0.1em;
letter-spacing: 0\9; // IE8 & below hack ;-(
- color: var(--wc-primary);
+ color: $wp-admin-text-gray;
}
}
}
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard-setup.php b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard-setup.php
index 482654a1a6a..feeda7901f4 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard-setup.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard-setup.php
@@ -8,6 +8,7 @@
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Admin\Features\Features;
+use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
@@ -72,10 +73,13 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
return;
}
- $button_link = $this->get_button_link( $task );
- $completed_tasks_count = $this->get_completed_tasks_count();
- $step_number = $this->get_completed_tasks_count() + 1;
- $tasks_count = count( $this->get_tasks() );
+ $button_link = $this->get_button_link( $task );
+ $task_header = $this->get_task_header( $task );
+ $task_is_in_progress = $task->is_in_progress();
+ $task_in_progress_label = $task_is_in_progress ? $task->in_progress_label() : '';
+ $completed_tasks_count = $this->get_completed_tasks_count();
+ $step_number = $completed_tasks_count + 1;
+ $tasks_count = count( $this->get_tasks() );
// Given 'r' (circle element's r attr), dashoffset = ((100-$desired_percentage)/100) * PI * (r*2).
$progress_percentage = ( $completed_tasks_count / $tasks_count ) * 100;
@@ -85,6 +89,68 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
include __DIR__ . '/views/html-admin-dashboard-setup.php';
}
+ /**
+ * Get dashboard widget header data for a task.
+ *
+ * @param Task $task Task.
+ * @return array
+ */
+ private function get_task_header( $task ) {
+ $asset_url = WC()->plugin_url() . '/assets/';
+ $task_json = $task->get_json();
+ $default_task_header = array(
+ 'title' => $task->get_title(),
+ 'content' => $task_json['content'] ?? '',
+ 'button_label' => $task_json['actionLabel'] ?? $task->get_title(),
+ 'image_url' => $asset_url . 'images/dashboard-widget-setup.png',
+ 'image_alt' => __( 'WooCommerce setup illustration', 'woocommerce' ),
+ );
+ $task_images = array(
+ 'store_details' => array(
+ 'image_url' => $asset_url . 'images/task_list/store-details-illustration.png',
+ 'image_alt' => __( 'Store location illustration', 'woocommerce' ),
+ ),
+ 'customize-store' => array(
+ 'image_url' => $asset_url . 'images/task_list/customize-store-illustration.svg',
+ 'image_alt' => __( 'Customize your store illustration', 'woocommerce' ),
+ ),
+ 'tax' => array(
+ 'image_url' => $asset_url . 'images/task_list/tax-illustration.svg',
+ 'image_alt' => __( 'Tax illustration', 'woocommerce' ),
+ ),
+ 'shipping' => array(
+ 'image_url' => $asset_url . 'images/task_list/shipping-illustration.svg',
+ 'image_alt' => __( 'Shipping illustration', 'woocommerce' ),
+ ),
+ 'marketing' => array(
+ 'image_url' => $asset_url . 'images/task_list/sales-illustration.svg',
+ 'image_alt' => __( 'Marketing illustration', 'woocommerce' ),
+ ),
+ 'payments' => array(
+ 'image_url' => $asset_url . 'images/task_list/payment-illustration.svg',
+ 'image_alt' => __( 'Payment illustration', 'woocommerce' ),
+ ),
+ 'woocommerce-payments' => array(
+ 'image_url' => $asset_url . 'images/task_list/payment-illustration.svg',
+ 'image_alt' => __( 'Payment illustration', 'woocommerce' ),
+ ),
+ 'products' => array(
+ 'image_url' => $asset_url . 'images/task_list/sales-section-illustration.svg',
+ 'image_alt' => __( 'Products illustration', 'woocommerce' ),
+ ),
+ 'purchase' => array(
+ 'image_url' => $asset_url . 'images/task_list/purchase-illustration.png',
+ 'image_alt' => __( 'Purchase illustration', 'woocommerce' ),
+ ),
+ 'launch-your-store' => array(
+ 'image_url' => $asset_url . 'images/task_list/launch-your-store-illustration.svg',
+ 'image_alt' => __( 'Launch your store illustration', 'woocommerce' ),
+ ),
+ );
+
+ return array_merge( $default_task_header, $task_images[ $task->get_id() ] ?? array() );
+ }
+
/**
* Get the button link for a given task.
*
@@ -165,7 +231,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
/**
* Get the next task.
*
- * @return array|null
+ * @return Task|null
*/
private function get_next_task() {
foreach ( $this->get_tasks() as $task ) {
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
index 0d1cfb1cd7a..73ebe5cf967 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php
@@ -42,11 +42,12 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
* Init dashboard widgets.
*/
public function init() {
+ wp_add_dashboard_widget( 'woocommerce_dashboard_status', __( 'WooCommerce Status', 'woocommerce' ), array( $this, 'status_widget' ), null, null, 'normal', 'high' );
+
// Reviews Widget.
- if ( current_user_can( 'publish_shop_orders' ) && post_type_supports( 'product', 'comments' ) ) {
- wp_add_dashboard_widget( 'woocommerce_dashboard_recent_reviews', __( 'WooCommerce Recent Reviews', 'woocommerce' ), array( $this, 'recent_reviews' ) );
+ if ( 'yes' === get_option( 'woocommerce_enable_reviews', 'yes' ) && current_user_can( 'publish_shop_orders' ) && post_type_supports( 'product', 'comments' ) ) {
+ wp_add_dashboard_widget( 'woocommerce_dashboard_recent_reviews', __( 'WooCommerce Recent Reviews', 'woocommerce' ), array( $this, 'recent_reviews' ), null, null, 'normal', 'high' );
}
- wp_add_dashboard_widget( 'woocommerce_dashboard_status', __( 'WooCommerce Status', 'woocommerce' ), array( $this, 'status_widget' ) );
// Network Order Widget.
if ( is_multisite() && is_main_site() ) {
@@ -71,9 +72,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
return false;
}
- $has_permission = current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' );
- $task_completed_or_hidden = 'yes' === get_option( 'woocommerce_task_list_complete' ) || 'yes' === get_option( 'woocommerce_task_list_hidden' );
- return $task_completed_or_hidden && $has_permission;
+ return current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' );
}
/**
@@ -136,6 +135,10 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
$suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
$version = Constants::get_constant( 'WC_VERSION' );
+ if ( ! wp_script_is( 'wc-flot', 'registered' ) ) {
+ wp_register_script( 'wc-flot', WC()->plugin_url() . '/assets/js/jquery-flot/jquery.flot' . $suffix . '.js', array( 'jquery' ), $version, true );
+ }
+
wp_enqueue_script( 'wc-status-widget', WC()->plugin_url() . '/assets/js/admin/wc-status-widget' . $suffix . '.js', array( 'jquery', 'wc-flot' ), $version, true );
wp_enqueue_script( 'wc-status-widget-async', WC()->plugin_url() . '/assets/js/admin/wc-status-widget-async' . $suffix . '.js', array( 'jquery' ), $version, true );
@@ -150,8 +153,8 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
);
// Display loading placeholder.
- echo '<div id="wc-status-widget-loading" class="wc-status-widget-loading">';
- echo '<p>' . esc_html__( 'Loading status data...', 'woocommerce' ) . ' <span class="spinner is-active"></span></p>';
+ echo '<div id="wc-status-widget-loading" class="wc-dashboard-widget-loading wc-status-widget-loading" aria-busy="true">';
+ echo '<p><span class="spinner is-active"></span><span class="wc-dashboard-widget-loading__text">' . esc_html__( 'Loading status data...', 'woocommerce' ) . '</span></p>';
echo '</div>';
echo '<div id="wc-status-widget-content" style="display:none;"></div>';
}
@@ -217,7 +220,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
<?php
printf(
/* translators: %s: net sales */
- esc_html__( '%s net sales this month', 'woocommerce' ),
+ esc_html__( 'Net sales this month %s', 'woocommerce' ),
'<strong>' . wc_price( $report_data->net_sales ) . '</strong>'
); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
?>
@@ -236,8 +239,8 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
<?php echo wp_kses( $sparkline, $sparkline_allowed_html ); ?>
<?php
printf(
- /* translators: 1: top seller product title 2: top seller quantity */
- esc_html__( '%1$s top seller this month (sold %2$d)', 'woocommerce' ),
+ /* translators: 1: top seller product title 2: top seller quantity sold */
+ esc_html( _n( 'Top seller this month %1$s (%2$d sale)', 'Top seller this month %1$s (%2$d sales)', $top_seller->qty, 'woocommerce' ) ),
'<strong>' . get_the_title( $top_seller->product_id ) . '</strong>',
$top_seller->qty
); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
@@ -285,22 +288,26 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
<li class="processing-orders">
<a href="<?php echo esc_url( admin_url( 'edit.php?post_status=wc-processing&post_type=shop_order' ) ); ?>">
<?php
- printf(
- /* translators: %s: order count */
- _n( '<strong>%s order</strong> awaiting processing', '<strong>%s orders</strong> awaiting processing', $processing_count, 'woocommerce' ),
- $processing_count
- ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
+ echo wp_kses_post(
+ sprintf(
+ /* translators: %s: order count */
+ _n( 'Awaiting processing <strong>%s order</strong>', 'Awaiting processing <strong>%s orders</strong>', $processing_count, 'woocommerce' ),
+ $processing_count
+ )
+ );
?>
</a>
</li>
<li class="on-hold-orders">
<a href="<?php echo esc_url( admin_url( 'edit.php?post_status=wc-on-hold&post_type=shop_order' ) ); ?>">
<?php
- printf(
- /* translators: %s: order count */
- _n( '<strong>%s order</strong> on-hold', '<strong>%s orders</strong> on-hold', $on_hold_count, 'woocommerce' ),
- $on_hold_count
- ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
+ echo wp_kses_post(
+ sprintf(
+ /* translators: %s: order count */
+ _n( 'On-hold <strong>%s order</strong>', 'On-hold <strong>%s orders</strong>', $on_hold_count, 'woocommerce' ),
+ $on_hold_count
+ )
+ );
?>
</a>
</li>
@@ -390,22 +397,26 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
<li class="low-in-stock">
<a href="<?php echo esc_url( $lowstock_url ); ?>">
<?php
- printf(
- /* translators: %s: order count */
- _n( '<strong>%s product</strong> low in stock', '<strong>%s products</strong> low in stock', $lowinstock_count, 'woocommerce' ),
- $lowinstock_count
- ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
+ echo wp_kses_post(
+ sprintf(
+ /* translators: %s: order count */
+ _n( 'Low in stock <strong>%s product</strong>', 'Low in stock <strong>%s products</strong>', $lowinstock_count, 'woocommerce' ),
+ $lowinstock_count
+ )
+ );
?>
</a>
</li>
<li class="out-of-stock">
<a href="<?php echo esc_url( $outofstock_url ); ?>">
<?php
- printf(
- /* translators: %s: order count */
- _n( '<strong>%s product</strong> out of stock', '<strong>%s products</strong> out of stock', $outofstock_count, 'woocommerce' ),
- $outofstock_count
- ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
+ echo wp_kses_post(
+ sprintf(
+ /* translators: %s: order count */
+ _n( 'Out of stock <strong>%s product</strong>', 'Out of stock <strong>%s products</strong>', $outofstock_count, 'woocommerce' ),
+ $outofstock_count
+ )
+ );
?>
</a>
</li>
@@ -493,8 +504,8 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
);
// 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 id="wc-recent-reviews-widget-loading" class="wc-dashboard-widget-loading wc-recent-reviews-widget-loading" aria-busy="true">';
+ echo '<p><span class="spinner is-active"></span><span class="wc-dashboard-widget-loading__text">' . esc_html__( 'Loading reviews data...', 'woocommerce' ) . '</span></p>';
echo '</div>';
echo '<div id="wc-recent-reviews-widget-content" style="display:none;"></div>';
}
diff --git a/plugins/woocommerce/includes/admin/views/html-admin-dashboard-setup.php b/plugins/woocommerce/includes/admin/views/html-admin-dashboard-setup.php
index 0fe6a9fe366..3f0ac213ae0 100644
--- a/plugins/woocommerce/includes/admin/views/html-admin-dashboard-setup.php
+++ b/plugins/woocommerce/includes/admin/views/html-admin-dashboard-setup.php
@@ -8,22 +8,41 @@
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
-?>
-<div class="dashboard-widget-finish-setup" data-current-step="<?php echo esc_html( $step_number - 1 ); ?>" data-total-steps="<?php echo esc_html( $tasks_count ); ?>">
- <span class='progress-wrapper'>
- <svg class="circle-progress" width="17" height="17" version="1.1" xmlns="http://www.w3.org/2000/svg">
- <circle r="6.5" cx="10" cy="10" fill="transparent" stroke-dasharray="40.859" stroke-dashoffset="0"></circle>
- <circle class="bar" r="6.5" cx="190" cy="10" fill="transparent" stroke-dasharray="40.859" stroke-dashoffset="<?php echo esc_attr( $circle_dashoffset ); ?>" transform='rotate(-90 100 100)'></circle>
- </svg>
- <span><?php esc_html_e( 'Step', 'woocommerce' ); ?> <?php echo esc_html( $step_number ); ?> <?php esc_html_e( 'of', 'woocommerce' ); ?> <?php echo esc_html( $tasks_count ); ?></span>
- </span>
+/**
+ * Dashboard setup widget view variables.
+ *
+ * @var array{title:string, content:string, button_label:string, image_url:string, image_alt:string} $task_header Task header data.
+ * @var bool $task_is_in_progress Whether the task is in progress.
+ * @var string $task_in_progress_label Task in-progress label.
+ */
+?>
+<div class="dashboard-widget-finish-setup" data-current-step="<?php echo esc_attr( (string) ( $step_number - 1 ) ); ?>" data-total-steps="<?php echo esc_attr( (string) $tasks_count ); ?>">
<div class="description">
- <div>
- <?php esc_html_e( 'You\'re almost there! Once you complete store setup you can start receiving orders.', 'woocommerce' ); ?>
- <div><a href='<?php echo esc_attr( $button_link ); ?>' class='button button-primary'><?php esc_html_e( 'Start selling', 'woocommerce' ); ?></a></div>
+ <div class="dashboard-widget-finish-setup__content">
+ <div class="dashboard-widget-finish-setup__meta">
+ <span class='progress-wrapper'>
+ <svg class="circle-progress" width="17" height="17" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <circle r="6.5" cx="10" cy="10" fill="transparent" stroke-dasharray="40.859" stroke-dashoffset="0"></circle>
+ <circle class="bar" r="6.5" cx="190" cy="10" fill="transparent" stroke-dasharray="40.859" stroke-dashoffset="<?php echo esc_attr( $circle_dashoffset ); ?>" transform='rotate(-90 100 100)'></circle>
+ </svg>
+ <span><?php esc_html_e( 'Step', 'woocommerce' ); ?> <?php echo esc_html( $step_number ); ?> <?php esc_html_e( 'of', 'woocommerce' ); ?> <?php echo esc_html( $tasks_count ); ?></span>
+ </span>
+ </div>
+ <h3 class="dashboard-widget-finish-setup__title">
+ <?php echo esc_html( $task_header['title'] ); ?>
+ <?php if ( $task_is_in_progress && $task_in_progress_label ) : ?>
+ <span class="dashboard-widget-finish-setup__in-progress"><?php echo esc_html( $task_in_progress_label ); ?></span>
+ <?php endif; ?>
+ </h3>
+ <p><?php echo esc_html( $task_header['content'] ); ?></p>
+ <div><a href='<?php echo esc_url( $button_link ); ?>' class='button button-primary'><?php echo esc_html( $task_header['button_label'] ); ?></a></div>
</div>
- <img src="<?php echo esc_url( WC()->plugin_url() ); ?>/assets/images/dashboard-widget-setup.png" />
+ <img
+ class="dashboard-widget-finish-setup__image"
+ src="<?php echo esc_url( $task_header['image_url'] ); ?>"
+ alt="<?php echo esc_attr( $task_header['image_alt'] ); ?>"
+ />
</div>
<div class="clear"></div>
</div>
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 60057b96c5b..4bb5b3f118c 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -1722,18 +1722,6 @@ parameters:
count: 1
path: includes/admin/class-wc-admin-brands.php
- -
- message: '#^Call to method get_id\(\) on an unknown class Task\.$#'
- identifier: class.notFound
- count: 1
- path: includes/admin/class-wc-admin-dashboard-setup.php
-
- -
- message: '#^Call to method get_json\(\) on an unknown class Task\.$#'
- identifier: class.notFound
- count: 1
- path: includes/admin/class-wc-admin-dashboard-setup.php
-
-
message: '#^Cannot call method get_viewable_tasks\(\) on array\.$#'
identifier: method.nonObject
@@ -1776,18 +1764,6 @@ parameters:
count: 1
path: includes/admin/class-wc-admin-dashboard-setup.php
- -
- message: '#^Parameter \#1 \$task of method WC_Admin_Dashboard_Setup\:\:get_button_link\(\) expects Task, array given\.$#'
- identifier: argument.type
- count: 1
- path: includes/admin/class-wc-admin-dashboard-setup.php
-
- -
- message: '#^Parameter \$task of method WC_Admin_Dashboard_Setup\:\:get_button_link\(\) has invalid type Task\.$#'
- identifier: class.notFound
- count: 1
- path: includes/admin/class-wc-admin-dashboard-setup.php
-
-
message: '#^Property WC_Admin_Dashboard_Setup\:\:\$completed_tasks_count is never read, only written\.$#'
identifier: property.onlyWritten
@@ -7914,12 +7890,6 @@ parameters:
count: 1
path: includes/admin/settings/views/html-webhooks-edit.php
- -
- message: '#^Parameter \#1 \$text of function esc_html expects string, \(float\|int\) given\.$#'
- identifier: argument.type
- count: 1
- path: includes/admin/views/html-admin-dashboard-setup.php
-
-
message: '#^Variable \$button_link might not be defined\.$#'
identifier: variable.undefined
diff --git a/plugins/woocommerce/templates/dashboard-widget-reviews.php b/plugins/woocommerce/templates/dashboard-widget-reviews.php
index 6d52951d843..5a149f052b9 100644
--- a/plugins/woocommerce/templates/dashboard-widget-reviews.php
+++ b/plugins/woocommerce/templates/dashboard-widget-reviews.php
@@ -12,7 +12,7 @@
*
* @see https://woocommerce.com/document/template-structure/
* @package WooCommerce\Templates
- * @version 10.5.0
+ * @version 11.0.0
*/
defined( 'ABSPATH' ) || exit;
@@ -24,6 +24,11 @@ defined( 'ABSPATH' ) || exit;
* @var $comment \WP_Comment
*/
+$product_name = $product->get_name();
+$product_name_display = wc_trim_string( $product_name, 40 );
+$review_author = get_comment_author( $comment->comment_ID );
+$review_author_display = wc_trim_string( $review_author, 24 );
+
?>
<li>
@@ -36,10 +41,22 @@ defined( 'ABSPATH' ) || exit;
<?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>
+ <a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>" title="<?php echo esc_attr( $product_name ); ?>"><?php echo wp_kses_post( $product_name_display ); ?></a>
<?php
- /* translators: %s: review author */
- printf( esc_html__( 'reviewed by %s', 'woocommerce' ), esc_html( get_comment_author( $comment->comment_ID ) ) );
+ $reviewed_by = sprintf(
+ // translators: %s: review author.
+ __( 'reviewed by %s', 'woocommerce' ),
+ '<span title="' . esc_attr( $review_author ) . '">' . esc_html( $review_author_display ) . '</span>'
+ );
+
+ echo wp_kses(
+ $reviewed_by,
+ array(
+ 'span' => array(
+ 'title' => true,
+ ),
+ )
+ );
?>
</h4>
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 87863f5643d..fdd5fbe815a 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
@@ -44,6 +44,7 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case {
wp_set_current_user( $this->user );
( new WC_Admin_Dashboard() )->recent_reviews();
$this->expectOutputRegex( '/Loading reviews data.../' );
+ $this->expectOutputRegex( '/wc-dashboard-widget-loading/' );
$this->expectOutputRegex( '/wc-recent-reviews-widget-loading/' );
$this->expectOutputRegex( '/wc-recent-reviews-widget-content/' );
}
@@ -69,6 +70,39 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case {
$product->delete();
}
+ /**
+ * Test: recent reviews widget truncates long product and reviewer names.
+ */
+ public function test_recent_reviews_widget_content_truncates_long_product_and_reviewer_names() {
+ $product_name = 'Extra long dashboard review stress test product name with multiple descriptive words';
+ $author_name = 'Alexandria Montgomery-Silverstein With A Very Long Reviewer Name';
+ $product = WC_Helper_Product::create_simple_product();
+ $product->set_name( $product_name );
+ $product->save();
+
+ $comment_id = WC_Helper_Product::create_product_review( $product->get_id(), 'Review content here' );
+ wp_update_comment(
+ array(
+ 'comment_ID' => $comment_id,
+ 'comment_author' => $author_name,
+ 'comment_date' => current_time( 'mysql' ),
+ 'comment_date_gmt' => current_time( 'mysql', true ),
+ )
+ );
+
+ wp_set_current_user( $this->user );
+ ob_start();
+ ( new WC_Admin_Dashboard() )->recent_reviews_content();
+ $html = ob_get_clean();
+
+ $this->assertStringContainsString( 'title="' . esc_attr( $product_name ) . '"', $html );
+ $this->assertStringContainsString( '>' . esc_html( wc_trim_string( $product_name, 40 ) ) . '</a>', $html );
+ $this->assertStringContainsString( 'title="' . esc_attr( $author_name ) . '"', $html );
+ $this->assertStringContainsString( '>' . esc_html( wc_trim_string( $author_name, 24 ) ) . '</span>', $html );
+
+ $product->delete();
+ }
+
/**
* Test: recent reviews widget content (legacy).
*/
@@ -102,7 +136,7 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case {
wp_set_current_user( $this->user );
( new WC_Admin_Dashboard() )->status_widget();
$this->expectOutputRegex( '/Loading status data.../' );
- $this->expectOutputRegex( '/<div id="wc-status-widget-loading" class="wc-status-widget-loading">/' );
+ $this->expectOutputRegex( '/<div id="wc-status-widget-loading" class="wc-dashboard-widget-loading wc-status-widget-loading" aria-busy="true">/' );
}
/**
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php
index 76244247638..784dfd41713 100644
--- a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php
@@ -160,8 +160,8 @@ class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case {
$required_strings = array(
'Step \d+ of \d+',
- 'You're almost there! Once you complete store setup you can start receiving orders.',
- 'Start selling',
+ 'dashboard-widget-finish-setup__title',
+ 'dashboard-widget-finish-setup__image',
);
foreach ( $required_strings as $required_string ) {
@@ -169,6 +169,72 @@ class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case {
}
}
+ /**
+ * Tests the widget output when the next task is in progress.
+ *
+ * @testdox Widget output includes the in-progress label for the next task.
+ */
+ public function test_widget_output_includes_in_progress_label() {
+ // phpcs:disable Squiz.Commenting
+ $task_list = new class() {
+ public function is_complete() {
+ return false;
+ }
+ public function is_hidden() {
+ return false;
+ }
+ public function get_viewable_tasks() {
+ return array(
+ new class() extends \Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task {
+ public function get_id() {
+ return 'payments';
+ }
+ public function get_title() {
+ return 'Set up payments';
+ }
+ public function get_content() {
+ return 'Choose payment providers and enable payment methods at checkout.';
+ }
+ public function get_time() {
+ return '5 minutes';
+ }
+ public function get_action_url() {
+ return 'payments';
+ }
+ public function get_action_label() {
+ return 'Configure payments';
+ }
+ public function is_complete() {
+ return false;
+ }
+ public function is_in_progress() {
+ return true;
+ }
+ public function in_progress_label() {
+ return 'Test account';
+ }
+ },
+ );
+ }
+ };
+ // phpcs:enable Squiz.Commenting
+
+ $widget = $this->get_widget();
+ $widget->set_task_list( $task_list );
+
+ ob_start();
+ $widget->render();
+ $html = ob_get_clean();
+
+ $this->assertStringContainsString( 'dashboard-widget-finish-setup__in-progress', $html );
+ $this->assertStringContainsString( 'Set up payments', $html );
+ $this->assertStringContainsString( 'Choose payment providers and enable payment methods at checkout.', $html );
+ $this->assertStringContainsString( 'Configure payments', $html );
+ $this->assertStringContainsString( 'payment-illustration.svg', $html );
+ $this->assertStringContainsString( 'Test account', $html );
+ $this->assertMatchesRegularExpression( '/<h3 class="dashboard-widget-finish-setup__title">.*Test account.*<\/h3>/s', $html );
+ }
+
/**
* Tests completed task count as it completes one by one
*/
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-dashboard-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-dashboard-test.php
index 58becad8df2..1f0ff3b42de 100644
--- a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-dashboard-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-dashboard-test.php
@@ -55,6 +55,7 @@ class WC_Admin_Dashboard_Test extends WC_Unit_Test_Case {
delete_option( 'woocommerce_task_list_complete' );
remove_all_filters( 'pre_option_woocommerce_task_list_complete' );
remove_all_filters( 'pre_option_woocommerce_task_list_hidden' );
+ delete_option( 'woocommerce_enable_reviews' );
parent::tearDown();
}
@@ -98,18 +99,181 @@ class WC_Admin_Dashboard_Test extends WC_Unit_Test_Case {
}
/**
- * @testdox Widget does not show when neither complete nor hidden.
+ * @testdox Widget shows when task list is incomplete.
*/
- public function test_widget_does_not_show_when_neither_complete_nor_hidden(): void {
+ public function test_widget_shows_when_task_list_is_incomplete(): void {
delete_option( 'woocommerce_task_list_completed_lists' );
delete_option( 'woocommerce_task_list_hidden_lists' );
- $this->assertFalse(
+ $this->assertTrue(
$this->invoke_should_display_widget( $this->sut ),
- 'Widget should not display when task list is neither complete nor hidden'
+ 'Widget should display even when the task list is incomplete'
+ );
+ }
+
+ /**
+ * @testdox WooCommerce widgets are registered high in the normal dashboard column in their current order.
+ */
+ public function test_init_registers_woocommerce_widgets_in_high_normal_context_in_current_order(): void {
+ global $wp_meta_boxes;
+
+ require_once ABSPATH . 'wp-admin/includes/dashboard.php';
+ set_current_screen( 'dashboard' );
+ update_option( 'woocommerce_enable_reviews', 'yes' );
+ unset( $wp_meta_boxes['dashboard'] );
+
+ add_meta_box(
+ 'wc_admin_dashboard_setup',
+ 'WooCommerce Setup',
+ '__return_empty_string',
+ 'dashboard',
+ 'normal',
+ 'high'
+ );
+ $this->sut->init();
+
+ $this->assertArrayHasKey( 'wc_admin_dashboard_setup', $wp_meta_boxes['dashboard']['normal']['high'] );
+ $this->assertArrayHasKey( 'woocommerce_dashboard_status', $wp_meta_boxes['dashboard']['normal']['high'] );
+ $this->assertArrayHasKey( 'woocommerce_dashboard_recent_reviews', $wp_meta_boxes['dashboard']['normal']['high'] );
+
+ $widget_order = array_values(
+ array_intersect(
+ array_keys( $wp_meta_boxes['dashboard']['normal']['high'] ),
+ array(
+ 'wc_admin_dashboard_setup',
+ 'woocommerce_dashboard_status',
+ 'woocommerce_dashboard_recent_reviews',
+ )
+ )
+ );
+
+ $this->assertSame(
+ array(
+ 'wc_admin_dashboard_setup',
+ 'woocommerce_dashboard_status',
+ 'woocommerce_dashboard_recent_reviews',
+ ),
+ $widget_order
);
}
+ /**
+ * @testdox Recent reviews widget is not registered when product reviews are disabled.
+ */
+ public function test_init_does_not_register_recent_reviews_widget_when_reviews_are_disabled(): void {
+ global $wp_meta_boxes;
+
+ require_once ABSPATH . 'wp-admin/includes/dashboard.php';
+ set_current_screen( 'dashboard' );
+ update_option( 'woocommerce_enable_reviews', 'no' );
+ $had_comments_support = post_type_supports( 'product', 'comments' );
+ add_post_type_support( 'product', 'comments' );
+ unset( $wp_meta_boxes['dashboard'] );
+
+ try {
+ $this->sut->init();
+
+ $this->assertArrayHasKey( 'woocommerce_dashboard_status', $wp_meta_boxes['dashboard']['normal']['high'] );
+ $this->assertArrayNotHasKey( 'woocommerce_dashboard_recent_reviews', $wp_meta_boxes['dashboard']['normal']['high'] );
+ } finally {
+ if ( ! $had_comments_support ) {
+ remove_post_type_support( 'product', 'comments' );
+ }
+ }
+ }
+
+ /**
+ * @testdox Recent reviews widget is registered when product reviews are enabled.
+ */
+ public function test_init_registers_recent_reviews_widget_when_reviews_are_enabled(): void {
+ global $wp_meta_boxes;
+
+ require_once ABSPATH . 'wp-admin/includes/dashboard.php';
+ set_current_screen( 'dashboard' );
+ update_option( 'woocommerce_enable_reviews', 'yes' );
+ $had_comments_support = post_type_supports( 'product', 'comments' );
+ add_post_type_support( 'product', 'comments' );
+ unset( $wp_meta_boxes['dashboard'] );
+
+ try {
+ $this->sut->init();
+
+ $this->assertArrayHasKey( 'woocommerce_dashboard_recent_reviews', $wp_meta_boxes['dashboard']['normal']['high'] );
+ } finally {
+ if ( ! $had_comments_support ) {
+ remove_post_type_support( 'product', 'comments' );
+ }
+ }
+ }
+
+ /**
+ * @testdox Recent reviews widget uses the enabled default when the reviews setting has not been saved.
+ */
+ public function test_init_registers_recent_reviews_widget_when_reviews_setting_is_missing(): void {
+ global $wp_meta_boxes;
+
+ require_once ABSPATH . 'wp-admin/includes/dashboard.php';
+ set_current_screen( 'dashboard' );
+ delete_option( 'woocommerce_enable_reviews' );
+ $had_comments_support = post_type_supports( 'product', 'comments' );
+ add_post_type_support( 'product', 'comments' );
+ unset( $wp_meta_boxes['dashboard'] );
+
+ try {
+ $this->sut->init();
+
+ $this->assertArrayHasKey( 'woocommerce_dashboard_recent_reviews', $wp_meta_boxes['dashboard']['normal']['high'] );
+ } finally {
+ if ( ! $had_comments_support ) {
+ remove_post_type_support( 'product', 'comments' );
+ }
+ }
+ }
+
+ /**
+ * @testdox Status widget loading placeholder renders the spinner above the loading text.
+ */
+ public function test_status_widget_loading_placeholder_renders_stacked_loader(): void {
+ wp_deregister_script( 'wc-flot' );
+
+ ob_start();
+ $this->sut->status_widget();
+ $html = ob_get_clean();
+
+ $this->assertTrue( wp_script_is( 'wc-flot', 'registered' ) );
+ $this->assertStringContainsString( 'class="wc-dashboard-widget-loading wc-status-widget-loading"', $html );
+ $this->assertStringContainsString( 'aria-busy="true"', $html );
+ $this->assertStringContainsString( '<p><span class="spinner is-active"></span><span class="wc-dashboard-widget-loading__text">Loading status data...</span></p>', $html );
+ }
+
+ /**
+ * @testdox Recent reviews widget loading placeholder renders the spinner above the loading text.
+ */
+ public function test_recent_reviews_widget_loading_placeholder_renders_stacked_loader(): void {
+ ob_start();
+ $this->sut->recent_reviews();
+ $html = ob_get_clean();
+
+ $this->assertStringContainsString( 'class="wc-dashboard-widget-loading wc-recent-reviews-widget-loading"', $html );
+ $this->assertStringContainsString( 'aria-busy="true"', $html );
+ $this->assertStringContainsString( '<p><span class="spinner is-active"></span><span class="wc-dashboard-widget-loading__text">Loading reviews data...</span></p>', $html );
+ }
+
+ /**
+ * @testdox Status widget order rows render labels before counts.
+ */
+ public function test_status_widget_order_rows_render_labels_before_counts(): void {
+ $method = new ReflectionMethod( WC_Admin_Dashboard::class, 'status_widget_order_rows' );
+ $method->setAccessible( true );
+
+ ob_start();
+ $method->invoke( $this->sut );
+ $html = ob_get_clean();
+
+ $this->assertStringContainsString( 'Awaiting processing <strong>0 orders</strong>', $html );
+ $this->assertStringContainsString( 'On-hold <strong>0 orders</strong>', $html );
+ }
+
/**
* @testdox Widget does not show without proper capabilities.
*/