Commit 47d19d2350a for woocommerce
commit 47d19d2350a6dbc80996928db0661cf8e24d5509
Author: Taha Paksu <3295+tpaksu@users.noreply.github.com>
Date: Thu May 14 13:15:06 2026 +0300
Gate Customer Review Request behind a FeaturesController flag (#64845)
* Gate Customer Review Request behind a feature flag
New 'customer_review_request' feature in FeaturesController, off by
default. With the flag off:
- The OrderReviews DI services (Scheduler, Endpoint, SubmissionHandler,
ItemEligibility) aren't resolved, so none of their init() hooks fire.
- WC_Email_Customer_Review_Request isn't registered with WC_Emails.
- The 10.8.0 update callback that seeded the page is gone — the host
page is now created lazily on the first init() after the feature is
enabled (Endpoint::maybe_create_host_page at init:4, before the
rewrite rule registration).
Test classes in tests/php/src/Internal/OrderReviews enable the flag
in setUp() and clean it up in tearDown(); SchedulerTest also wires
its service and reruns WC_Emails::init() so the mailer map picks up
the gated email class.
* Add changefile(s) from automation for the following project(s): woocommerce
* Tag Endpoint::maybe_create_host_page() @since 10.8.0
* Address CodeRabbit review feedback
- Defer the OrderReviews container resolution to `init` priority 0
(class-woocommerce.php). FeaturesController's `init_feature_definitions()`
calls `__()` for the feature name; running `feature_is_enabled()` from
the constructor would trigger those translations too early.
- Wrap `WC_Install::create_pages()` in a try/finally inside
`Endpoint::maybe_create_host_page()` so the temporary
`woocommerce_create_pages` filter is removed even if the page
creation throws.
- Use `wc_get_container()->get( Scheduler::class )->init()` in
SchedulerTest::setUp instead of `new Scheduler()` so the container's
cached instance is reused across tests instead of stacking duplicate
hook registrations.
* Replace OrderReviews gate closure with a named WooCommerce method
Move the inline static closure that gates the OrderReviews container
resolution to a new WooCommerce::maybe_init_order_reviews() method
hooked at init priority 1.
* Move OrderReviews gate next to the other init add_action calls
* Remove review_order from WC_Install::create_pages default set
The page is now seeded lazily by Endpoint::maybe_create_host_page on
first feature-enabled init. Updated maybe_create_host_page to replace
the woocommerce_create_pages filter output with just the review_order
entry instead of intersecting against the default set.
* Drop static modifier from inline closure
* Use self::PAGE_KEY and self::SHORTCODE constants in the seed array
* Require a published page entry before skipping the lazy seed
If `woocommerce_review_order_page_id` points at a draft / private /
wrong-type post, recreate the page so `add_rewrite_rule` can register
the route (it requires `'publish'`).
* Republish a draft / private host page instead of re-running seed
WC_Install::create_pages() short-circuits when a page already exists
at the stored option ID, even if its status is draft / private.
Force-update the status to publish in place, then defer the rewrite
flush so add_rewrite_rule registers the route on the next request.
* Tighten the two new comments in maybe_create_host_page
* Make Review Order page-creation self-healing and label it in the Pages list
Surfaces two follow-ups from the manual test pass:
- `maybe_create_host_page()` is no longer option-driven. A new private
`find_existing_host_page()` looks up the host page by slug first (matching
what pretty-permalink routing will resolve `/review-order/` to), then
falls back to a `LIKE %[woocommerce_review_order]%` content search. If a
page is found, the option is re-pointed at it and a rewrite-flush is
queued; if the page is not published, it is republished in place. This
fixes a class of bug where leftover duplicate "Review your order" rows
from prior activations made `is_page($option_id)` diverge from WP's
slug routing, so `gate_request()` silently skipped enqueueing the
Review Order JS/CSS and the form rendered without progressive
enhancement.
- Added a `display_post_states` filter so the Pages admin list labels the
WC-managed Review Order page as "— Review Order Page", matching how
`WC_Admin_Post_Types` already labels Shop / Cart / Checkout / My
account. The filter is registered from Endpoint::init() so it stays
off whenever the feature flag is off.
Adds two regression tests in EndpointTest:
- `test_maybe_create_host_page_realigns_option_with_slug_routed_duplicate`
seeds two pages with slug `review-order` (forcing the clash via
`$wpdb->update`), points the option at the wrong one, and asserts the
function re-aligns the option to the slug-routed page and queues a
rewrite flush.
- `test_maybe_create_host_page_republishes_draft_host_page` seeds a
draft host page and asserts the function republishes it and queues a
rewrite flush.
Both tests share a `reset_review_order_pages()` helper that scrubs the
shared setUp's seeded page so each test controls its own state.
* Tighten host-page adoption to slug+shortcode signal and respect renames
Addresses two Major findings from the local Copilot pass on 97b52123c2:
- The shortcode-only fallback in `find_existing_host_page()` could silently
republish a merchant's intentionally-private staging page that happened
to embed `[woocommerce_review_order]`.
- The same fallback could hijack `woocommerce_review_order_page_id` to an
arbitrary older page on every request via `ORDER BY ID ASC LIMIT 1`.
`maybe_create_host_page()` now adopts a page only when both signals
agree: WP's slug routing resolves `/review-order/` to it AND the page
embeds our shortcode. If that combined match fails but the option still
points at a valid page (a merchant who renamed our slug), the function
respects it and only republishes a draft we already own. Only when
neither path applies does it fall through to `WC_Install::create_pages()`.
* Fix PHPCS: reformat indented inline comments in maybe_create_host_page
* Address prettyboymp review: restore Tools repair, cut per-request slug lookup
Three findings from the review on #64845:
- `Endpoint::init()` now registers a permanent `woocommerce_create_pages`
filter (`inject_review_order_page()`) that appends our entry whenever
the feature is on. Any caller of `WC_Install::create_pages()` —
including Status → Tools "Create default pages" repair — sees the
Review Order page again. The transient add/remove dance inside
`maybe_create_host_page()` is gone.
- `maybe_create_host_page()` short-circuits before the slug lookup when
the stored option already points at a published page whose content
embeds `[woocommerce_review_order]`. `get_post()` is served from the
posts cache by id, so the steady-state cost drops from an indexed
`wp_posts` SELECT (via `get_page_by_path`, invalidated by every
post insert/update/delete on the site) to a cached lookup.
- Updated the docblock on `maybe_flush_pending_rewrite()` so it no
longer references the removed 10.8.0 db update; now describes the
init:4 seeding + wp_loaded flush path actually in use.
Tests:
- New `test_inject_review_order_page_filter_adds_entry_for_third_party_callers`
asserting the public method appends our entry and passes non-array
values through.
- Renamed `test_maybe_create_host_page_realigns_option_...` to
`..._adopts_slug_canonical_when_option_dangles` and removed the
stale option pre-stage so the test exercises the slug-reconciliation
path with the fast path correctly skipped.
* Fix Show more button in filter blocks being visible when it was not necessary (#64798)
* Fix Show more button in filter blocks being visible when it was not necessary
* Add changelog
* Make sure overflow selected items are SSR
* Animate the quick edit drawer close transition and close on save (#64858)
* Animate the quick edit drawer close and close on successful save
* Use stable boolean dep so close animation isn't undone by re-render
* improve logic
---------
Co-authored-by: Luigi Teschio <gigitux@gmail.com>
* Add Shipping Class filter to the experimental products app product list (#64823)
* Add Shipping Class filter to the experimental products app product list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix shipping_class field getValue to return term ID so the filter aligns with row state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Luigi Teschio <gigitux@gmail.com>
* Address review follow-ups: image overflow, star sizing, @since/@internal
- `__item-image img` constrained to `width: 100%; height: auto;` so the WC
placeholder thumbnail respects its 120-px column on block themes that don't
ship the global `img { max-width: 100% }` reset.
- Star icon switched from `1.5em` to absolute `24px` so themes that reset
`font-size: 0` on `input[type=radio]+label` can't collapse it to 0x0.
- `@since 10.8.0` added to `inject_review_order_page`, `add_post_state_label`
and the `find_canonical_host_page` helper.
- `@internal` added to `inject_review_order_page` and `add_post_state_label`
to match the pattern on existing filter-callback methods; these are public
only because WP filter callbacks must be callable from outside.
* Add changefile(s) from automation for the following project(s): @woocommerce/experimental-products-app, woocommerce
---------
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
Co-authored-by: verofasulo <98944206+verofasulo@users.noreply.github.com>
Co-authored-by: Luigi Teschio <gigitux@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
diff --git a/packages/js/experimental-products-app/changelog/64845-wooplug-6696-gate-the-customer-review-request-feature-behind-a b/packages/js/experimental-products-app/changelog/64845-wooplug-6696-gate-the-customer-review-request-feature-behind-a
new file mode 100644
index 00000000000..8eca5bfa2a6
--- /dev/null
+++ b/packages/js/experimental-products-app/changelog/64845-wooplug-6696-gate-the-customer-review-request-feature-behind-a
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Gate the Customer Review Request feature behind a FeaturesController flag (off by default). The host page is created lazily on the first request after the feature is enabled, not on plugin install.
\ No newline at end of file
diff --git a/plugins/woocommerce/changelog/64845-wooplug-6696-gate-the-customer-review-request-feature-behind-a b/plugins/woocommerce/changelog/64845-wooplug-6696-gate-the-customer-review-request-feature-behind-a
new file mode 100644
index 00000000000..8eca5bfa2a6
--- /dev/null
+++ b/plugins/woocommerce/changelog/64845-wooplug-6696-gate-the-customer-review-request-feature-behind-a
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Gate the Customer Review Request feature behind a FeaturesController flag (off by default). The host page is created lazily on the first request after the feature is enabled, not on plugin install.
\ No newline at end of file
diff --git a/plugins/woocommerce/client/legacy/css/order-review.scss b/plugins/woocommerce/client/legacy/css/order-review.scss
index 6bb8bb792c3..6f0b1f6842d 100644
--- a/plugins/woocommerce/client/legacy/css/order-review.scss
+++ b/plugins/woocommerce/client/legacy/css/order-review.scss
@@ -48,6 +48,12 @@
&__item-image {
flex: 0 0 120px;
max-width: 120px;
+
+ img {
+ display: block;
+ width: 100%;
+ height: auto;
+ }
}
&__item-rating {
@@ -187,10 +193,12 @@
margin-left: 0;
}
+ // Absolute px so theme resets on `input[type=radio]+label` (e.g. font-size:0)
+ // can't collapse the icon to 0x0.
&__icon {
display: block;
- width: 1.5em;
- height: 1.5em;
+ width: 24px;
+ height: 24px;
fill: currentColor;
opacity: 0.3;
transition: opacity 100ms ease-in-out;
diff --git a/plugins/woocommerce/includes/class-wc-emails.php b/plugins/woocommerce/includes/class-wc-emails.php
index 551c4c48652..6441c8c6fd6 100644
--- a/plugins/woocommerce/includes/class-wc-emails.php
+++ b/plugins/woocommerce/includes/class-wc-emails.php
@@ -292,7 +292,6 @@ class WC_Emails {
'WC_Email_Customer_Processing_Order' => __DIR__ . '/emails/class-wc-email-customer-processing-order.php',
'WC_Email_Customer_Completed_Order' => __DIR__ . '/emails/class-wc-email-customer-completed-order.php',
'WC_Email_Customer_Refunded_Order' => __DIR__ . '/emails/class-wc-email-customer-refunded-order.php',
- 'WC_Email_Customer_Review_Request' => __DIR__ . '/emails/class-wc-email-customer-review-request.php',
'WC_Email_Customer_Invoice' => __DIR__ . '/emails/class-wc-email-customer-invoice.php',
'WC_Email_Customer_Note' => __DIR__ . '/emails/class-wc-email-customer-note.php',
'WC_Email_Customer_Reset_Password' => __DIR__ . '/emails/class-wc-email-customer-reset-password.php',
@@ -308,6 +307,9 @@ class WC_Emails {
$emails['WC_Email_Customer_Fulfillment_Updated'] = __DIR__ . '/emails/class-wc-email-customer-fulfillment-updated.php';
$emails['WC_Email_Customer_Fulfillment_Deleted'] = __DIR__ . '/emails/class-wc-email-customer-fulfillment-deleted.php';
}
+ if ( FeaturesUtil::feature_is_enabled( 'customer_review_request' ) ) {
+ $emails['WC_Email_Customer_Review_Request'] = __DIR__ . '/emails/class-wc-email-customer-review-request.php';
+ }
// Prime caches to reduce future queries.
wp_prime_option_caches(
diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php
index 8b7fb5fa5df..a81552692bc 100644
--- a/plugins/woocommerce/includes/class-wc-install.php
+++ b/plugins/woocommerce/includes/class-wc-install.php
@@ -46,7 +46,7 @@ class WC_Install {
* @var array
*/
private static $db_updates = array(
- '2.0.0' => array(
+ '2.0.0' => array(
'wc_update_200_file_paths',
'wc_update_200_permalinks',
'wc_update_200_subcat_display',
@@ -55,42 +55,42 @@ class WC_Install {
'wc_update_200_images',
'wc_update_200_db_version',
),
- '2.0.9' => array(
+ '2.0.9' => array(
'wc_update_209_brazillian_state',
'wc_update_209_db_version',
),
- '2.1.0' => array(
+ '2.1.0' => array(
'wc_update_210_remove_pages',
'wc_update_210_file_paths',
'wc_update_210_db_version',
),
- '2.2.0' => array(
+ '2.2.0' => array(
'wc_update_220_shipping',
'wc_update_220_order_status',
'wc_update_220_variations',
'wc_update_220_attributes',
'wc_update_220_db_version',
),
- '2.3.0' => array(
+ '2.3.0' => array(
'wc_update_230_options',
'wc_update_230_db_version',
),
- '2.4.0' => array(
+ '2.4.0' => array(
'wc_update_240_options',
'wc_update_240_shipping_methods',
'wc_update_240_api_keys',
'wc_update_240_refunds',
'wc_update_240_db_version',
),
- '2.4.1' => array(
+ '2.4.1' => array(
'wc_update_241_variations',
'wc_update_241_db_version',
),
- '2.5.0' => array(
+ '2.5.0' => array(
'wc_update_250_currency',
'wc_update_250_db_version',
),
- '2.6.0' => array(
+ '2.6.0' => array(
'wc_update_260_options',
'wc_update_260_termmeta',
'wc_update_260_zones',
@@ -98,26 +98,26 @@ class WC_Install {
'wc_update_260_refunds',
'wc_update_260_db_version',
),
- '3.0.0' => array(
+ '3.0.0' => array(
'wc_update_300_grouped_products',
'wc_update_300_settings',
'wc_update_300_product_visibility',
'wc_update_300_db_version',
),
- '3.1.0' => array(
+ '3.1.0' => array(
'wc_update_310_downloadable_products',
'wc_update_310_old_comments',
'wc_update_310_db_version',
),
- '3.1.2' => array(
+ '3.1.2' => array(
'wc_update_312_shop_manager_capabilities',
'wc_update_312_db_version',
),
- '3.2.0' => array(
+ '3.2.0' => array(
'wc_update_320_mexican_states',
'wc_update_320_db_version',
),
- '3.3.0' => array(
+ '3.3.0' => array(
'wc_update_330_image_options',
'wc_update_330_webhooks',
'wc_update_330_product_stock_status',
@@ -126,48 +126,48 @@ class WC_Install {
'wc_update_330_set_paypal_sandbox_credentials',
'wc_update_330_db_version',
),
- '3.4.0' => array(
+ '3.4.0' => array(
'wc_update_340_states',
'wc_update_340_state',
'wc_update_340_last_active',
'wc_update_340_db_version',
),
- '3.4.3' => array(
+ '3.4.3' => array(
'wc_update_343_cleanup_foreign_keys',
'wc_update_343_db_version',
),
- '3.4.4' => array(
+ '3.4.4' => array(
'wc_update_344_recreate_roles',
'wc_update_344_db_version',
),
- '3.5.0' => array(
+ '3.5.0' => array(
'wc_update_350_reviews_comment_type',
'wc_update_350_db_version',
),
- '3.5.2' => array(
+ '3.5.2' => array(
'wc_update_352_drop_download_log_fk',
),
- '3.5.4' => array(
+ '3.5.4' => array(
'wc_update_354_modify_shop_manager_caps',
'wc_update_354_db_version',
),
- '3.6.0' => array(
+ '3.6.0' => array(
'wc_update_360_product_lookup_tables',
'wc_update_360_term_meta',
'wc_update_360_downloadable_product_permissions_index',
'wc_update_360_db_version',
),
- '3.7.0' => array(
+ '3.7.0' => array(
'wc_update_370_tax_rate_classes',
'wc_update_370_mro_std_currency',
'wc_update_370_db_version',
),
- '3.9.0' => array(
+ '3.9.0' => array(
'wc_update_390_move_maxmind_database',
'wc_update_390_change_geolocation_database_update_cron',
'wc_update_390_db_version',
),
- '4.0.0' => array(
+ '4.0.0' => array(
'wc_update_product_lookup_tables',
'wc_update_400_increase_size_of_column',
'wc_update_400_reset_action_scheduler_migration_status',
@@ -176,27 +176,27 @@ class WC_Install {
'wc_admin_update_0251_remove_unsnooze_action',
'wc_update_400_db_version',
),
- '4.4.0' => array(
+ '4.4.0' => array(
'wc_update_440_insert_attribute_terms_for_variable_products',
'wc_admin_update_110_remove_facebook_note',
'wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note',
'wc_update_440_db_version',
),
- '4.5.0' => array(
+ '4.5.0' => array(
'wc_update_450_sanitize_coupons_code',
'wc_update_450_db_version',
),
- '5.0.0' => array(
+ '5.0.0' => array(
'wc_update_500_fix_product_review_count',
'wc_admin_update_160_remove_facebook_note',
'wc_admin_update_170_homescreen_layout',
'wc_update_500_db_version',
),
- '5.6.0' => array(
+ '5.6.0' => array(
'wc_update_560_create_refund_returns_page',
'wc_update_560_db_version',
),
- '6.0.0' => array(
+ '6.0.0' => array(
'wc_update_600_migrate_rate_limit_options',
'wc_admin_update_270_delete_report_downloads',
'wc_admin_update_271_update_task_list_options',
@@ -205,133 +205,130 @@ class WC_Install {
'wc_admin_update_290_delete_default_homepage_layout_option',
'wc_update_600_db_version',
),
- '6.3.0' => array(
+ '6.3.0' => array(
'wc_update_630_create_product_attributes_lookup_table',
'wc_admin_update_300_update_is_read_from_last_read',
'wc_update_630_db_version',
),
- '6.4.0' => array(
+ '6.4.0' => array(
'wc_update_640_add_primary_key_to_product_attributes_lookup_table',
'wc_admin_update_340_remove_is_primary_from_note_action',
'wc_update_640_db_version',
),
- '6.5.0' => array(
+ '6.5.0' => array(
'wc_update_650_approved_download_directories',
),
- '6.5.1' => array(
+ '6.5.1' => array(
'wc_update_651_approved_download_directories',
),
- '6.7.0' => array(
+ '6.7.0' => array(
'wc_update_670_purge_comments_count_cache',
'wc_update_670_delete_deprecated_remote_inbox_notifications_option',
),
- '7.0.0' => array(
+ '7.0.0' => array(
'wc_update_700_remove_download_log_fk',
'wc_update_700_remove_recommended_marketing_plugins_transient',
),
- '7.2.1' => array(
+ '7.2.1' => array(
'wc_update_721_adjust_new_zealand_states',
'wc_update_721_adjust_ukraine_states',
),
- '7.2.2' => array(
+ '7.2.2' => array(
'wc_update_722_adjust_new_zealand_states',
'wc_update_722_adjust_ukraine_states',
),
- '7.5.0' => array(
+ '7.5.0' => array(
'wc_update_750_add_columns_to_order_stats_table',
'wc_update_750_disable_new_product_management_experience',
),
- '7.7.0' => array(
+ '7.7.0' => array(
'wc_update_770_remove_multichannel_marketing_feature_options',
),
- '7.9.0' => array(
+ '7.9.0' => array(
'wc_update_790_blockified_product_grid_block',
),
- '8.1.0' => array(
+ '8.1.0' => array(
'wc_update_810_migrate_transactional_metadata_for_hpos',
),
- '8.3.0' => array(
+ '8.3.0' => array(
'wc_update_830_rename_checkout_template',
'wc_update_830_rename_cart_template',
),
- '8.6.0' => array(
+ '8.6.0' => array(
'wc_update_860_remove_recommended_marketing_plugins_transient',
),
- '8.7.0' => array(
+ '8.7.0' => array(
'wc_update_870_prevent_listing_of_transient_files_directory',
),
- '8.9.0' => array(
+ '8.9.0' => array(
'wc_update_890_update_connect_to_woocommerce_note',
'wc_update_890_update_paypal_standard_load_eligibility',
),
- '8.9.1' => array(
+ '8.9.1' => array(
'wc_update_891_create_plugin_autoinstall_history_option',
),
- '9.1.0' => array(
+ '9.1.0' => array(
'wc_update_910_add_launch_your_store_tour_option',
'wc_update_910_remove_obsolete_user_meta',
),
- '9.2.0' => array(
+ '9.2.0' => array(
'wc_update_920_add_wc_hooked_blocks_version_option',
),
- '9.3.0' => array(
+ '9.3.0' => array(
'wc_update_930_add_woocommerce_coming_soon_option',
'wc_update_930_migrate_user_meta_for_launch_your_store_tour',
),
- '9.4.0' => array(
+ '9.4.0' => array(
'wc_update_940_add_phone_to_order_address_fts_index',
'wc_update_940_remove_help_panel_highlight_shown',
),
- '9.5.0' => array(
+ '9.5.0' => array(
'wc_update_950_tracking_option_autoload',
),
- '9.6.1' => array(
+ '9.6.1' => array(
'wc_update_961_migrate_default_email_base_color',
),
- '9.8.0' => array(
+ '9.8.0' => array(
'wc_update_980_remove_order_attribution_install_banner_dismissed_option',
),
- '9.8.5' => array(
+ '9.8.5' => array(
'wc_update_985_enable_new_payments_settings_page_feature',
),
- '9.9.0' => array(
+ '9.9.0' => array(
'wc_update_990_remove_wc_count_comments_transient',
'wc_update_990_remove_email_notes',
),
- '10.0.0' => array(
+ '10.0.0' => array(
'wc_update_1000_multisite_visibility_setting',
'wc_update_1000_remove_patterns_toolkit_transient',
),
- '10.2.0' => array(
+ '10.2.0' => array(
'wc_update_1020_add_old_refunded_order_items_to_product_lookup_table',
),
- '10.3.0' => array(
+ '10.3.0' => array(
'wc_update_1030_add_comments_date_type_index',
),
- '10.4.0' => array(
+ '10.4.0' => array(
'wc_update_1040_add_idx_date_paid_status_parent',
'wc_update_1040_cleanup_legacy_ptk_patterns_fetching',
),
- '10.5.0' => array(
+ '10.5.0' => array(
'wc_update_1050_migrate_brand_permalink_setting',
'wc_update_1050_enable_autoload_options',
'wc_update_1050_add_idx_user_email',
'wc_update_1050_remove_deprecated_marketplace_option',
),
- '10.6.0' => array(
+ '10.6.0' => array(
'wc_update_1060_add_woo_idx_comment_approved_type_index',
),
- '10.7.0' => array(
+ '10.7.0' => array(
'wc_update_1070_disable_hpos_sync_on_read',
),
- '10.8.0' => array(
+ '10.8.0' => array(
'wc_update_1080_migrate_analytics_import_option',
'wc_update_1080_slim_orders_meta_key_index',
'wc_update_1080_backfill_email_template_sync_meta',
),
- '10.8.0-1' => array(
- 'wc_update_1080_create_review_order_page',
- ),
);
/**
@@ -1156,11 +1153,6 @@ class WC_Install {
'title' => _x( 'My account', 'Page title', 'woocommerce' ),
'content' => '<!-- wp:shortcode -->[' . $my_account_shortcode . ']<!-- /wp:shortcode -->',
),
- 'review_order' => array(
- 'name' => _x( 'review-order', 'Page slug', 'woocommerce' ),
- 'title' => _x( 'Review your order', 'Page title', 'woocommerce' ),
- 'content' => '<!-- wp:shortcode -->[woocommerce_review_order]<!-- /wp:shortcode -->',
- ),
'refund_returns' => array(
'name' => _x( 'refund_returns', 'Page slug', 'woocommerce' ),
'title' => _x( 'Refund and Returns Policy', 'Page title', 'woocommerce' ),
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index 626071d2a63..5999b66ea5c 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -322,6 +322,7 @@ final class WooCommerce {
add_action( 'after_setup_theme', array( $this, 'include_template_functions' ), 11 );
add_action( 'load-post.php', array( $this, 'includes' ) );
add_action( 'init', array( $this, 'init' ), 0 );
+ add_action( 'init', array( $this, 'maybe_init_order_reviews' ), 1 );
add_action( 'init', array( 'WC_Shortcodes', 'init' ) );
add_action( 'init', array( 'WC_Emails', 'init_transactional_emails' ) );
add_action( 'init', array( $this, 'add_image_sizes' ) );
@@ -376,10 +377,6 @@ final class WooCommerce {
$container->get( ProductVersionStringInvalidator::class );
$container->get( OrdersVersionStringInvalidator::class );
$container->get( TaxRateVersionStringInvalidator::class );
- $container->get( Automattic\WooCommerce\Internal\OrderReviews\Scheduler::class );
- $container->get( Automattic\WooCommerce\Internal\OrderReviews\Endpoint::class );
- $container->get( Automattic\WooCommerce\Internal\OrderReviews\SubmissionHandler::class );
- $container->get( Automattic\WooCommerce\Internal\OrderReviews\ItemEligibility::class );
// Feature flags.
if ( Constants::is_true( 'WOOCOMMERCE_BIS_ALPHA_ENABLED' ) ) {
@@ -963,6 +960,25 @@ final class WooCommerce {
do_action( 'woocommerce_init' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingSinceComment
}
+ /**
+ * Resolve the OrderReviews services when the `customer_review_request`
+ * feature flag is on. Hooked to `init` priority 1 from `init_hooks()`
+ * so it runs after the textdomain is loaded.
+ *
+ * @since 10.8.0
+ * @internal
+ */
+ public function maybe_init_order_reviews(): void {
+ if ( ! \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'customer_review_request' ) ) {
+ return;
+ }
+ $container = wc_get_container();
+ $container->get( \Automattic\WooCommerce\Internal\OrderReviews\Scheduler::class );
+ $container->get( \Automattic\WooCommerce\Internal\OrderReviews\Endpoint::class );
+ $container->get( \Automattic\WooCommerce\Internal\OrderReviews\SubmissionHandler::class );
+ $container->get( \Automattic\WooCommerce\Internal\OrderReviews\ItemEligibility::class );
+ }
+
/**
* Load Localisation files.
*
diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php
index 5630210607f..3648be5bb37 100644
--- a/plugins/woocommerce/includes/wc-update-functions.php
+++ b/plugins/woocommerce/includes/wc-update-functions.php
@@ -3511,31 +3511,3 @@ function wc_update_1080_slim_orders_meta_key_index(): void {
function wc_update_1080_backfill_email_template_sync_meta(): bool {
return WCEmailTemplateSyncBackfill::run();
}
-
-/**
- * Seeds the Review Order page on existing installs so the rewrite rule and
- * helper URL work after upgrading to 10.8.0. Mirrors how
- * `wc_update_560_create_refund_returns_page` backfilled the refund/returns
- * page when that feature shipped.
- *
- * @since 10.8.0
- *
- * @return void
- */
-function wc_update_1080_create_review_order_page(): void {
- $only_review_order = static function ( array $pages ): array {
- return array_intersect_key( $pages, array_flip( array( 'review_order' ) ) );
- };
-
- add_filter( 'woocommerce_create_pages', $only_review_order );
-
- WC_Install::create_pages();
-
- remove_filter( 'woocommerce_create_pages', $only_review_order );
-
- // `Endpoint::add_rewrite_rule` runs on init:10; this update routine fires
- // from WC_Install::check_version on init:5, so flushing here would
- // persist the rules table without the new /review-order/{id}/ rule.
- // Defer the flush via an option that the endpoint clears on wp_loaded.
- update_option( 'woocommerce_review_order_flush_rewrite_pending', 'yes' );
-}
diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
index 69bcb03f025..caa86505e46 100644
--- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php
+++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
@@ -439,6 +439,18 @@ class FeaturesController {
'enabled_by_default' => false,
'is_experimental' => false,
),
+ 'customer_review_request' => array(
+ 'name' => __( 'Customer review request (beta)', 'woocommerce' ),
+ 'description' => __(
+ 'Send customers a transactional email after order completion inviting them to review the products they bought, and host the per-order Review Order landing page.',
+ 'woocommerce'
+ ),
+ // Skip compatibility checks like the other opt-in transactional-email features.
+ 'skip_compatibility_checks' => true,
+ 'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
+ 'enabled_by_default' => false,
+ 'is_experimental' => false,
+ ),
'email_improvements' => array(
'name' => __( 'Email improvements', 'woocommerce' ),
'description' => __(
diff --git a/plugins/woocommerce/src/Internal/OrderReviews/Endpoint.php b/plugins/woocommerce/src/Internal/OrderReviews/Endpoint.php
index 7191d78e22f..9cabf5ebfdc 100644
--- a/plugins/woocommerce/src/Internal/OrderReviews/Endpoint.php
+++ b/plugins/woocommerce/src/Internal/OrderReviews/Endpoint.php
@@ -66,15 +66,169 @@ class Endpoint {
* @internal
*/
final public function init(): void {
+ // Seed the host page before `add_rewrite_rule` runs on init:10.
+ add_action( 'init', array( $this, 'maybe_create_host_page' ), 4 );
add_action( 'init', array( $this, 'add_rewrite_rule' ) );
add_filter( 'query_vars', array( $this, 'add_query_var' ), 0 );
add_action( 'template_redirect', array( $this, 'gate_request' ) );
add_action( 'wp_loaded', array( $this, 'maybe_flush_pending_rewrite' ) );
add_action( 'transition_post_status', array( $this, 'skip_auto_menu_for_self' ), 9, 3 );
add_filter( 'get_pages', array( $this, 'exclude_self_from_page_list' ) );
+ add_filter( 'display_post_states', array( $this, 'add_post_state_label' ), 10, 2 );
+ // Inject our entry into every `WC_Install::create_pages()` invocation so
+ // Status → Tools "Create default pages" and any other repair caller see it too.
+ add_filter( 'woocommerce_create_pages', array( $this, 'inject_review_order_page' ) );
add_shortcode( self::SHORTCODE, array( $this, 'render_shortcode' ) );
}
+ /**
+ * Create or adopt the Review Order host page on every feature-on init.
+ *
+ * Idempotent and self-healing: re-aligns the stored option with whichever
+ * row WP's permalink routing would resolve `/review-order/` to, so the
+ * page id `gate_request()` checks always matches the page that
+ * `add_rewrite_rule()` points at. Leftover duplicates from prior
+ * activation/disable cycles no longer cause asset enqueueing to silently
+ * skip.
+ *
+ * @since 10.8.0
+ *
+ * @internal
+ */
+ public function maybe_create_host_page(): void {
+ // Fast path: the stored option already points at a published page
+ // that still embeds our shortcode. `get_post()` is served from the
+ // posts cache so this short-circuit costs ~nothing per request and
+ // avoids the slug `wp_posts` lookup the reconciliation path runs.
+ $option_id = (int) wc_get_page_id( self::PAGE_KEY );
+ $option_page = $option_id > 0 ? get_post( $option_id ) : null;
+ if ( $option_page instanceof WP_Post
+ && 'page' === $option_page->post_type
+ && 'publish' === $option_page->post_status
+ && false !== strpos( (string) $option_page->post_content, '[' . self::SHORTCODE . ']' ) ) {
+ return;
+ }
+
+ // Reconcile: adopt the slug-routed page when it also embeds our
+ // shortcode. The combined signal avoids hijacking a merchant page
+ // that happens to share either the slug or the shortcode alone.
+ $canonical = $this->find_canonical_host_page();
+ if ( $canonical instanceof WP_Post ) {
+ $needs_save = false;
+
+ if ( $option_id !== (int) $canonical->ID ) {
+ update_option( 'woocommerce_review_order_page_id', (int) $canonical->ID );
+ $needs_save = true;
+ }
+ if ( 'publish' !== $canonical->post_status ) {
+ wp_update_post(
+ array(
+ 'ID' => (int) $canonical->ID,
+ 'post_status' => 'publish',
+ )
+ );
+ $needs_save = true;
+ }
+ if ( $needs_save ) {
+ update_option( 'woocommerce_review_order_flush_rewrite_pending', 'yes' );
+ }
+ return;
+ }
+
+ // No slug-canonical page. If the merchant renamed the host page away
+ // from our default slug but the stored option still resolves to a
+ // non-trashed page, respect it and only republish a draft we own.
+ if ( $option_page instanceof WP_Post && 'page' === $option_page->post_type && 'trash' !== $option_page->post_status ) {
+ if ( 'publish' !== $option_page->post_status ) {
+ wp_update_post(
+ array(
+ 'ID' => (int) $option_page->ID,
+ 'post_status' => 'publish',
+ )
+ );
+ update_option( 'woocommerce_review_order_flush_rewrite_pending', 'yes' );
+ }
+ return;
+ }
+
+ // No managed page anywhere. The permanent `woocommerce_create_pages`
+ // filter (registered in `init()`) makes the call inject our entry.
+ \WC_Install::create_pages();
+
+ // Defer the rewrite flush to wp_loaded; rewrite_rule fires later on init.
+ update_option( 'woocommerce_review_order_flush_rewrite_pending', 'yes' );
+ }
+
+ /**
+ * Append the Review Order page to any caller of
+ * `WC_Install::create_pages()` — keeps Status → Tools' "Create default
+ * pages" repair path and any third-party callers seeded with our page
+ * whenever the feature is on, without having to call create_pages()
+ * with a one-off filter in `maybe_create_host_page()`.
+ *
+ * @since 10.8.0
+ *
+ * @internal Public only because WP filter callbacks need to be callable from outside.
+ *
+ * @param array<string,array<string,string>>|mixed $pages Existing page definitions.
+ * @return array<string,array<string,string>>|mixed
+ */
+ public function inject_review_order_page( $pages ) {
+ if ( ! is_array( $pages ) ) {
+ return $pages;
+ }
+ $pages[ self::PAGE_KEY ] = array(
+ 'name' => _x( 'review-order', 'Page slug', 'woocommerce' ),
+ 'title' => _x( 'Review your order', 'Page title', 'woocommerce' ),
+ 'content' => '<!-- wp:shortcode -->[' . self::SHORTCODE . ']<!-- /wp:shortcode -->',
+ );
+ return $pages;
+ }
+
+ /**
+ * Return the slug-routed page if it also embeds our shortcode, so we only
+ * adopt rows that are unambiguously WC-owned (matching slug alone or the
+ * shortcode alone would hijack merchant-authored pages).
+ *
+ * @since 10.8.0
+ *
+ * @return WP_Post|null
+ */
+ private function find_canonical_host_page(): ?WP_Post {
+ $page = get_page_by_path( _x( 'review-order', 'Page slug', 'woocommerce' ), OBJECT, 'page' );
+ if ( ! $page instanceof WP_Post || 'trash' === $page->post_status ) {
+ return null;
+ }
+ if ( false === strpos( (string) $page->post_content, '[' . self::SHORTCODE . ']' ) ) {
+ return null;
+ }
+ return $page;
+ }
+
+ /**
+ * Label the Review Order page in the admin Pages list ("— Review Order
+ * Page"), mirroring how `WC_Admin_Post_Types` labels Shop / Cart /
+ * Checkout / My account so editors can spot it at a glance.
+ *
+ * @since 10.8.0
+ *
+ * @internal Public only because WP filter callbacks need to be callable from outside.
+ *
+ * @param array<string,string>|mixed $post_states Existing post-state labels keyed by id.
+ * @param \WP_Post|mixed $post Current post being listed.
+ * @return array<string,string>|mixed
+ */
+ public function add_post_state_label( $post_states, $post ) {
+ if ( ! is_array( $post_states ) || ! $post instanceof \WP_Post ) {
+ return $post_states;
+ }
+ $page_id = (int) wc_get_page_id( self::PAGE_KEY );
+ if ( $page_id > 0 && $page_id === (int) $post->ID ) {
+ $post_states['wc_page_for_review_order'] = __( 'Review Order Page', 'woocommerce' );
+ }
+ return $post_states;
+ }
+
/**
* Hide the Review Order page from `get_pages()` results.
*
@@ -230,12 +384,13 @@ class Endpoint {
}
/**
- * Flush rewrite rules once after the 10.8.0 upgrade installs the
- * Review Order page.
+ * Flush rewrite rules once after the Review Order page is seeded or
+ * republished.
*
- * The 10.8.0 db update runs on `init` priority 5 and only seeds the
- * page; `add_rewrite_rule()` doesn't fire until `init` priority 10, so
- * the flush has to happen later. `wp_loaded` runs after every `init`
+ * `maybe_create_host_page()` runs on `init` priority 4 and queues the
+ * flush by setting `woocommerce_review_order_flush_rewrite_pending`;
+ * `add_rewrite_rule()` doesn't fire until `init` priority 10, so the
+ * flush has to happen later. `wp_loaded` runs after every `init`
* callback, which is the earliest safe moment.
*/
public function maybe_flush_pending_rewrite(): void {
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/EndpointTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/EndpointTest.php
index bef8c22dd48..36c89b59d08 100644
--- a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/EndpointTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/EndpointTest.php
@@ -29,6 +29,8 @@ class EndpointTest extends WC_Unit_Test_Case {
*/
public function setUp(): void {
parent::setUp();
+ // Feature flag gates the OrderReviews stack.
+ update_option( 'woocommerce_feature_customer_review_request_enabled', 'yes' );
$this->endpoint = new Endpoint();
// `Endpoint::get_url()` derives the URL from the WC-managed Review
@@ -62,6 +64,7 @@ class EndpointTest extends WC_Unit_Test_Case {
}
wp_reset_postdata();
wp_set_current_user( 0 );
+ delete_option( 'woocommerce_feature_customer_review_request_enabled' );
parent::tearDown();
}
@@ -634,4 +637,115 @@ class EndpointTest extends WC_Unit_Test_Case {
$fresh = wc_get_order( $order->get_id() );
$this->assertSame( $preset, (string) $fresh->get_meta( SubmissionHandler::COMPLETED_META_KEY ) );
}
+
+ /**
+ * @testdox maybe_create_host_page() re-aligns the option with the slug-routed page when duplicates exist.
+ *
+ * Prior activation/disable cycles can leave multiple pages with slug
+ * `review-order`. WP's permalink routing resolves `/review-order/` to the
+ * lowest-id match, so the option must agree with that or `gate_request()`
+ * silently skips its work (assets never enqueue).
+ */
+ public function test_maybe_create_host_page_adopts_slug_canonical_when_option_dangles(): void {
+ global $wpdb;
+
+ // Wipe whatever the shared setUp seeded so this test controls state.
+ $this->reset_review_order_pages();
+
+ $first_id = (int) wp_insert_post(
+ array(
+ 'post_type' => 'page',
+ 'post_status' => 'publish',
+ 'post_title' => 'Review your order',
+ 'post_name' => 'review-order',
+ 'post_content' => '<!-- wp:shortcode -->[woocommerce_review_order]<!-- /wp:shortcode -->',
+ )
+ );
+ $second_id = (int) wp_insert_post(
+ array(
+ 'post_type' => 'page',
+ 'post_status' => 'publish',
+ 'post_title' => 'Review your order',
+ 'post_name' => 'review-order-alt',
+ 'post_content' => '<!-- wp:shortcode -->[woocommerce_review_order]<!-- /wp:shortcode -->',
+ )
+ );
+ // Force the slug clash that WP's uniqueness check would normally avoid.
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
+ $wpdb->update( $wpdb->posts, array( 'post_name' => 'review-order' ), array( 'ID' => $first_id ) );
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
+ $wpdb->update( $wpdb->posts, array( 'post_name' => 'review-order' ), array( 'ID' => $second_id ) );
+ clean_post_cache( $first_id );
+ clean_post_cache( $second_id );
+
+ // Option absent so the fast-path short-circuit fails and reconciliation runs.
+ delete_option( 'woocommerce_review_order_page_id' );
+ delete_option( 'woocommerce_review_order_flush_rewrite_pending' );
+
+ $this->endpoint->maybe_create_host_page();
+
+ $this->assertSame( $first_id, (int) wc_get_page_id( Endpoint::PAGE_KEY ), 'option should adopt the slug-routed (lowest-id) page' );
+ $this->assertSame( 'yes', get_option( 'woocommerce_review_order_flush_rewrite_pending' ), 'rewrite flush should be queued when the option moves' );
+ }
+
+ /**
+ * @testdox maybe_create_host_page() republishes a draft host page and queues a rewrite flush.
+ */
+ public function test_maybe_create_host_page_republishes_draft_host_page(): void {
+ $this->reset_review_order_pages();
+
+ $page_id = (int) wp_insert_post(
+ array(
+ 'post_type' => 'page',
+ 'post_status' => 'draft',
+ 'post_title' => 'Review your order',
+ 'post_name' => 'review-order',
+ 'post_content' => '<!-- wp:shortcode -->[woocommerce_review_order]<!-- /wp:shortcode -->',
+ )
+ );
+ update_option( 'woocommerce_review_order_page_id', $page_id );
+ delete_option( 'woocommerce_review_order_flush_rewrite_pending' );
+
+ $this->endpoint->maybe_create_host_page();
+
+ $fresh = get_post( $page_id );
+ $this->assertSame( 'publish', $fresh->post_status, 'draft host page should be republished' );
+ $this->assertSame( 'yes', get_option( 'woocommerce_review_order_flush_rewrite_pending' ) );
+ }
+
+ /**
+ * @testdox The `woocommerce_create_pages` filter injects the Review Order entry so any caller of `WC_Install::create_pages()` (e.g. Status → Tools repair) seeds the page.
+ */
+ public function test_inject_review_order_page_filter_adds_entry_for_third_party_callers(): void {
+ $pages = $this->endpoint->inject_review_order_page( array() );
+
+ $this->assertArrayHasKey( Endpoint::PAGE_KEY, $pages );
+ $this->assertSame( 'review-order', $pages[ Endpoint::PAGE_KEY ]['name'] );
+ $this->assertStringContainsString( '[woocommerce_review_order]', $pages[ Endpoint::PAGE_KEY ]['content'] );
+
+ // Defensive: a non-array value passes through untouched (matches the
+ // guard inside the method so other filters in the chain stay intact).
+ $this->assertNull( $this->endpoint->inject_review_order_page( null ) );
+ }
+
+ /**
+ * Remove every page that could match the Review Order lookup, plus the
+ * stored option, so a test can stage a clean slate before exercising
+ * `maybe_create_host_page()`.
+ */
+ private function reset_review_order_pages(): void {
+ $candidates = get_posts(
+ array(
+ 'name' => 'review-order',
+ 'post_type' => 'page',
+ 'post_status' => 'any',
+ 'numberposts' => -1,
+ 'suppress_filters' => false,
+ )
+ );
+ foreach ( $candidates as $page ) {
+ wp_delete_post( (int) $page->ID, true );
+ }
+ delete_option( 'woocommerce_review_order_page_id' );
+ }
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/ItemEligibilityTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/ItemEligibilityTest.php
index ce8dd2e5ecc..7b4cf23c4f8 100644
--- a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/ItemEligibilityTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/ItemEligibilityTest.php
@@ -17,11 +17,20 @@ use WC_Unit_Test_Case;
*/
class ItemEligibilityTest extends WC_Unit_Test_Case {
+ /**
+ * Feature flag gates the OrderReviews stack.
+ */
+ public function setUp(): void {
+ parent::setUp();
+ update_option( 'woocommerce_feature_customer_review_request_enabled', 'yes' );
+ }
+
/**
* Reset between tests.
*/
public function tearDown(): void {
ItemEligibility::reset_cache();
+ delete_option( 'woocommerce_feature_customer_review_request_enabled' );
parent::tearDown();
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php
index 0e225a6b965..dbd9f09448b 100644
--- a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php
@@ -22,8 +22,13 @@ class SchedulerTest extends WC_Unit_Test_Case {
public function setUp(): void {
parent::setUp();
- // Make sure the email class is available for WC()->mailer().
- WC()->mailer();
+ // Feature flag gates the OrderReviews stack. Enable it, then resolve
+ // the Scheduler from the container (singleton across the test run)
+ // and call init() to wire hooks. Re-init WC_Emails so the
+ // review-request email class lands in the mailer map.
+ update_option( 'woocommerce_feature_customer_review_request_enabled', 'yes' );
+ wc_get_container()->get( Scheduler::class )->init();
+ WC()->mailer()->init();
$this->set_review_email_enabled( true );
}
@@ -35,6 +40,7 @@ class SchedulerTest extends WC_Unit_Test_Case {
$this->set_review_email_enabled( false );
remove_all_filters( 'woocommerce_should_send_review_request' );
remove_all_filters( 'woocommerce_review_request_delay_seconds' );
+ delete_option( 'woocommerce_feature_customer_review_request_enabled' );
parent::tearDown();
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SubmissionHandlerTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SubmissionHandlerTest.php
index 669e2091f2b..56019616ace 100644
--- a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SubmissionHandlerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SubmissionHandlerTest.php
@@ -17,6 +17,14 @@ use WPAjaxDieContinueException;
*/
class SubmissionHandlerTest extends WC_Unit_Test_Case {
+ /**
+ * Feature flag gates the OrderReviews stack.
+ */
+ public function setUp(): void {
+ parent::setUp();
+ update_option( 'woocommerce_feature_customer_review_request_enabled', 'yes' );
+ }
+
/**
* Reset state between tests.
*/
@@ -29,6 +37,7 @@ class SubmissionHandlerTest extends WC_Unit_Test_Case {
remove_all_filters( 'wp_die_ajax_handler' );
remove_all_filters( 'wp_send_json_handler' );
remove_all_filters( 'wp_doing_ajax' );
+ delete_option( 'woocommerce_feature_customer_review_request_enabled' );
parent::tearDown();
}