Commit a75effc19b1 for woocommerce
commit a75effc19b19bdfb945c0a6ee57958248d36062c
Author: Néstor Soriano <konamiman@konamiman.com>
Date: Tue Jun 30 14:29:32 2026 +0200
Fix product attributes lookup table tools failing via REST API (#66082)
diff --git a/plugins/woocommerce/changelog/32831-fix-regenerate-attributes-lookup-table-via-rest-api b/plugins/woocommerce/changelog/32831-fix-regenerate-attributes-lookup-table-via-rest-api
new file mode 100644
index 00000000000..d03f8ffcb3e
--- /dev/null
+++ b/plugins/woocommerce/changelog/32831-fix-regenerate-attributes-lookup-table-via-rest-api
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Allow the "Regenerate the product attributes lookup table" tool (and the related abort and resume tools) to run via the REST API instead of failing with an "Invalid nonce" error.
diff --git a/plugins/woocommerce/src/Internal/ProductAttributesLookup/DataRegenerator.php b/plugins/woocommerce/src/Internal/ProductAttributesLookup/DataRegenerator.php
index fb96dd3a225..24746702640 100644
--- a/plugins/woocommerce/src/Internal/ProductAttributesLookup/DataRegenerator.php
+++ b/plugins/woocommerce/src/Internal/ProductAttributesLookup/DataRegenerator.php
@@ -335,7 +335,7 @@ class DataRegenerator {
'desc' => __( 'This tool will abort the regenerate product attributes lookup table regeneration. After this is done the process can be either started over, or resumed to continue where it stopped.', 'woocommerce' ),
'requires_refresh' => true,
'callback' => function () {
- $this->abort_regeneration( true );
+ $this->abort_regeneration( false );
return __( 'Product attributes lookup table regeneration process has been aborted.', 'woocommerce' );
},
'button' => __( 'Abort', 'woocommerce' ),
@@ -353,7 +353,7 @@ class DataRegenerator {
),
'requires_refresh' => true,
'callback' => function () {
- $this->resume_regeneration( true );
+ $this->resume_regeneration( false );
return __( 'Product attributes lookup table regeneration process has been resumed.', 'woocommerce' );
},
'button' => __( 'Resume', 'woocommerce' ),
@@ -365,13 +365,14 @@ class DataRegenerator {
}
/**
- * Callback to initiate the regeneration process from the Status - Tools page.
+ * Callback to initiate the regeneration process when the tool is run.
+ *
+ * Runs both from the Tools admin page and from the REST API. Authorization is
+ * enforced by the caller (nonce for the admin page, capability check for the REST endpoint).
*
* @throws \Exception The regeneration is already in progress.
*/
private function initiate_regeneration_from_tools_page() {
- $this->verify_tool_execution_nonce();
-
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['regenerate_product_attribute_lookup_data_product_id'] ) ) {
$product_id = (int) $_REQUEST['regenerate_product_attribute_lookup_data_product_id'];
diff --git a/plugins/woocommerce/tests/php/src/Internal/ProductAttributesLookup/DataRegeneratorTest.php b/plugins/woocommerce/tests/php/src/Internal/ProductAttributesLookup/DataRegeneratorTest.php
index 7695928f487..aa1742fa186 100644
--- a/plugins/woocommerce/tests/php/src/Internal/ProductAttributesLookup/DataRegeneratorTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/ProductAttributesLookup/DataRegeneratorTest.php
@@ -321,4 +321,93 @@ class DataRegeneratorTest extends \WC_Unit_Test_Case {
$actual_final_option_value = get_option( 'woocommerce_attribute_lookup_enabled' );
$this->assertEquals( $expected_final_option_value, $actual_final_option_value );
}
+
+ /**
+ * @testdox The 'regenerate' tool callback runs without requiring a nonce (e.g. when invoked via the REST API).
+ */
+ public function test_regenerate_tool_callback_runs_without_a_nonce() {
+ global $wpdb;
+
+ // Simulate a non-admin-page invocation (REST API / WP-CLI), where no 'debug_action' nonce is present.
+ unset( $_REQUEST['_wpnonce'] );
+
+ // Ensure the full-table path is exercised, not the admin product-selector branch.
+ unset( $_REQUEST['regenerate_product_attribute_lookup_data_product_id'] );
+
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
+ $wpdb->query( 'DROP TABLE IF EXISTS ' . $this->lookup_table_name );
+ $wpdb->query( 'CREATE TABLE ' . $this->lookup_table_name . ' ( foo int );' );
+ // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
+
+ $sut = $this->get_sut_for_existing_lookup_table();
+ $tools = $sut->add_initiate_regeneration_entry_to_tools_array( array() );
+
+ $message = $tools['regenerate_product_attributes_lookup_table']['callback']();
+
+ $this->assertStringContainsString( 'regenerating', $message, 'The regenerate tool must run without a nonce.' );
+ }
+
+ /**
+ * @testdox The 'abort' tool callback runs without requiring a nonce (e.g. when invoked via the REST API).
+ */
+ public function test_abort_tool_callback_runs_without_a_nonce() {
+ // Simulate a non-admin-page invocation (REST API / WP-CLI), where no 'debug_action' nonce is present.
+ unset( $_REQUEST['_wpnonce'] );
+
+ $sut = $this->get_sut_for_existing_lookup_table( true );
+
+ $tools = $sut->add_initiate_regeneration_entry_to_tools_array( array() );
+
+ $message = $tools['abort_product_attributes_lookup_table_regeneration']['callback']();
+
+ $this->assertStringContainsString( 'aborted', $message, 'The abort tool must run without a nonce.' );
+ }
+
+ /**
+ * @testdox The 'resume' tool callback runs without requiring a nonce (e.g. when invoked via the REST API).
+ */
+ public function test_resume_tool_callback_runs_without_a_nonce() {
+ // Simulate a non-admin-page invocation (REST API / WP-CLI), where no 'debug_action' nonce is present.
+ unset( $_REQUEST['_wpnonce'] );
+
+ $sut = $this->get_sut_for_existing_lookup_table( false, true );
+
+ $tools = $sut->add_initiate_regeneration_entry_to_tools_array( array() );
+
+ $message = $tools['resume_product_attributes_lookup_table_regeneration']['callback']();
+
+ $this->assertStringContainsString( 'resumed', $message, 'The resume tool must run without a nonce.' );
+ }
+
+ /**
+ * Build a DataRegenerator whose data store reports that the lookup table exists, so that the
+ * Status - Tools entries (and their callbacks) get registered. The real existence check relies
+ * on SHOW TABLES, which doesn't list the temporary tables that PHPUnit creates.
+ *
+ * @param bool $regeneration_in_progress Whether to flag a regeneration as currently in progress.
+ * @param bool $regeneration_aborted Whether to flag the last regeneration as aborted.
+ * @return DataRegenerator
+ */
+ private function get_sut_for_existing_lookup_table( bool $regeneration_in_progress = false, bool $regeneration_aborted = false ): DataRegenerator {
+ // phpcs:disable Squiz.Commenting
+ $data_store = new class() extends LookupDataStore {
+ public function check_lookup_table_exists() {
+ return true;
+ }
+ };
+ // phpcs:enable Squiz.Commenting
+
+ $container = wc_get_container();
+ $container->reset_all_resolved();
+ $container->replace( LookupDataStore::class, $data_store );
+
+ if ( $regeneration_in_progress ) {
+ $data_store->set_regeneration_in_progress_flag();
+ }
+ if ( $regeneration_aborted ) {
+ $data_store->set_regeneration_aborted_flag();
+ }
+
+ return $container->get( DataRegenerator::class );
+ }
}