Commit 1553dbf5667 for woocommerce
commit 1553dbf56675c9843fdec07d90a5d4f80c2c294c
Author: Ján Mikláš <neosinner@gmail.com>
Date: Wed Nov 11 16:28:02 2026 +0100
Fix Blueprint imports interrupted by cache flushes (#70721)
* Fix Blueprint sessions lost after cache flushes
* Add changelog entry for Blueprint session fix
diff --git a/plugins/woocommerce/changelog/fix-wooplug-4307-blueprint-session-cache b/plugins/woocommerce/changelog/fix-wooplug-4307-blueprint-session-cache
new file mode 100644
index 00000000000..36d367bf9a7
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-4307-blueprint-session-cache
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Allow Blueprint imports to finish when imported settings clear the object cache.
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php b/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
index 7d691714598..510d8777415 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
@@ -286,10 +286,6 @@ class RestApi {
);
}
- if ( false === get_transient( 'blueprint_import_session_' . $session_token ) ) {
- set_transient( 'blueprint_import_session_' . $session_token, true, 10 * MINUTE_IN_SECONDS );
- }
-
// Get the raw body size.
$body_size = strlen( $request->get_body() );
if ( $body_size > $this->get_max_file_size() ) {
@@ -313,6 +309,11 @@ class RestApi {
$step_importer = new ImportStep( $step_definition );
$result = $step_importer->import();
+ // Import steps can flush the object cache, so store the session after they finish.
+ if ( false === get_transient( 'blueprint_import_session_' . $session_token ) ) {
+ set_transient( 'blueprint_import_session_' . $session_token, true, 10 * MINUTE_IN_SECONDS );
+ }
+
$response = new \WP_REST_Response(
array(
'success' => $result->is_success(),
diff --git a/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/RestApiTest.php b/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/RestApiTest.php
index 5e5fb77d849..97348cc09c0 100644
--- a/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/RestApiTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/RestApiTest.php
@@ -243,4 +243,78 @@ class RestApiTest extends WC_Unit_Test_Case {
$this->assertCount( 1, $response_data['messages'] );
$this->assertStringContainsString( 'woocommerce_store_address has been updated', $response_data['messages'][0]['message'] );
}
+
+ /**
+ * @testdox Should continue an import after its steps change site visibility and flush the cache.
+ * @testWith [false]
+ * [true]
+ *
+ * @param bool $external_object_cache Whether transients are stored in the object cache.
+ */
+ public function test_import_continues_after_site_visibility_changes( bool $external_object_cache ): void {
+ $previous_external_object_cache = wp_using_ext_object_cache();
+ wp_using_ext_object_cache( $external_object_cache );
+
+ try {
+ update_option( 'woocommerce_coming_soon', 'yes' );
+ update_option( 'woocommerce_store_pages_only', 'no' );
+
+ $response = $this->rest_api->import_step(
+ $this->create_import_request( array( 'woocommerce_coming_soon' => 'no' ) )
+ );
+ $this->assertInstanceOf( \WP_REST_Response::class, $response );
+ $this->assertTrue( $response->get_data()['success'], 'The step that makes the site live should succeed.' );
+ $this->assertSame( 'no', get_option( 'woocommerce_coming_soon' ) );
+
+ $session_token = $response->get_headers()['X-Blueprint-Import-Session'];
+ $this->assertNotEmpty( $session_token, 'The first response should supply a session token.' );
+
+ $response = $this->rest_api->import_step(
+ $this->create_import_request( array( 'woocommerce_store_pages_only' => 'yes' ), $session_token )
+ );
+ $this->assertInstanceOf( \WP_REST_Response::class, $response, 'The existing session should continue on a live site.' );
+ $this->assertTrue( $response->get_data()['success'] );
+
+ $response = $this->rest_api->import_step(
+ $this->create_import_request( array( 'woocommerce_store_address' => 'Continued import' ), $session_token )
+ );
+ $this->assertInstanceOf( \WP_REST_Response::class, $response, 'The session should survive a cache flush in a later step.' );
+ $this->assertTrue( $response->get_data()['success'] );
+ $this->assertSame( 'Continued import', get_option( 'woocommerce_store_address' ) );
+
+ foreach ( array( '', wp_generate_uuid4() ) as $invalid_session_token ) {
+ $response = $this->rest_api->import_step(
+ $this->create_import_request( array( 'woocommerce_store_address' => 'Blocked import' ), $invalid_session_token )
+ );
+ $this->assertFalse( $response['success'], 'A missing or unknown session must not allow an import on a live site.' );
+ $this->assertSame( 'Continued import', get_option( 'woocommerce_store_address' ) );
+ }
+ } finally {
+ wp_using_ext_object_cache( $previous_external_object_cache );
+ }
+ }
+
+ /**
+ * Create an import step request for site options.
+ *
+ * @param array $options Site options to import.
+ * @param string $session_token Import session token.
+ * @return WP_REST_Request
+ */
+ private function create_import_request( array $options, string $session_token = '' ): WP_REST_Request {
+ $request = new WP_REST_Request( 'POST', '/wc-admin/blueprint/import-step' );
+ $request->set_header( 'Content-Type', 'application/json' );
+ $request->set_header( 'X-Blueprint-Import-Session', $session_token );
+ $request->set_body(
+ wp_json_encode(
+ array(
+ 'step_definition' => array(
+ 'step' => 'setSiteOptions',
+ 'options' => $options,
+ ),
+ )
+ )
+ );
+ return $request;
+ }
}