Commit 771516e98cd for woocommerce
commit 771516e98cd3ecf2303c3a3bfbe90ade220ddd73
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Wed Aug 5 07:56:27 2026 +0200
Return early in Product block if product is not viewable (#67332)
* Return early in Product block if product is not viewable
* Add changelog
* Fixes
* Add tests
* PHPStan
* Avoid regular expression in tests
* Fix tests
* Make it so the password form redirects to the current page instead of the product page
diff --git a/plugins/woocommerce/changelog/fix-WOO6-93 b/plugins/woocommerce/changelog/fix-WOO6-93
new file mode 100644
index 00000000000..24ba76d362c
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-WOO6-93
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Return early in Product block if product is not viewable
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/SingleProduct.php b/plugins/woocommerce/src/Blocks/BlockTypes/SingleProduct.php
index 7db69b6efa2..ccdb0e872ed 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/SingleProduct.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/SingleProduct.php
@@ -184,18 +184,40 @@ class SingleProduct extends AbstractBlock {
protected function render( $attributes, $content, $block ) {
$product = wc_get_product( $block->context['postId'] );
- if ( ! $product instanceof \WC_Product ) {
+ if (
+ ! $product instanceof \WC_Product ||
+ ! $product->is_viewable()
+ ) {
return '';
}
+ $product_id = $product->get_id();
+
+ if ( post_password_required( $product_id ) ) {
+ $password_form = get_the_password_form( $product_id );
+ $html = new \WP_HTML_Tag_Processor( $password_form );
+ $current_url = home_url( add_query_arg( null, null ) );
+
+ while ( $html->next_tag( array( 'tag_name' => 'input' ) ) ) {
+ if ( 'redirect_to' !== $html->get_attribute( 'name' ) ) {
+ continue;
+ }
+
+ $html->set_attribute( 'value', $current_url );
+ break;
+ }
+
+ return $html->get_updated_html();
+ }
+
// Load product into the shared products store.
wc_interactivity_api_load_product(
'I acknowledge that using experimental APIs means my theme or plugin will inevitably break in the next version of WooCommerce',
- $product->get_id()
+ $product_id
);
$interactivity_context = array(
- 'productId' => $product->get_id(),
+ 'productId' => $product_id,
'variationId' => null,
);
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php
index 548eaf58200..5d3a0d2be2a 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php
@@ -147,4 +147,70 @@ class SingleProduct extends \WP_UnitTestCase {
$this->delete_product_with_gallery_attachments( $data );
}
}
+
+ /**
+ * @testdox Password-protected products render the password form instead of product content.
+ */
+ public function test_password_protected_product_renders_password_form() {
+ $product = WC_Helper_Product::create_simple_product();
+ $product->set_post_password( 'secret' );
+ $product->save();
+
+ try {
+ $markup = do_blocks(
+ sprintf(
+ '<!-- wp:woocommerce/single-product {"productId":%d} -->
+<div class="wp-block-woocommerce-single-product"><p>VISIBLE_PRODUCT_CONTENT</p></div>
+<!-- /wp:woocommerce/single-product -->',
+ $product->get_id()
+ )
+ );
+
+ $this->assertStringContainsString(
+ 'This content is password-protected',
+ $markup,
+ 'Password-protected products should render the password form.'
+ );
+ $this->assertStringNotContainsString(
+ 'VISIBLE_PRODUCT_CONTENT',
+ $markup,
+ 'Password-protected products should not render block content.'
+ );
+ $this->assertStringNotContainsString(
+ '?product=' . $product->get_slug(),
+ $markup,
+ 'Password form should redirect to the current page instead of the product page.'
+ );
+ } finally {
+ WC_Helper_Product::delete_product( $product->get_id() );
+ }
+ }
+
+ /**
+ * @testdox Draft products that are not viewable render no content.
+ */
+ public function test_draft_product_renders_nothing() {
+ wp_set_current_user( 0 );
+
+ $product = WC_Helper_Product::create_simple_product( true, array( 'status' => 'draft' ) );
+
+ try {
+ $markup = do_blocks(
+ sprintf(
+ '<!-- wp:woocommerce/single-product {"productId":%d} -->
+<div class="wp-block-woocommerce-single-product"><p>VISIBLE_PRODUCT_CONTENT</p></div>
+<!-- /wp:woocommerce/single-product -->',
+ $product->get_id()
+ )
+ );
+
+ $this->assertSame(
+ '',
+ $markup,
+ 'Draft products should not render any Single Product block output.'
+ );
+ } finally {
+ WC_Helper_Product::delete_product( $product->get_id() );
+ }
+ }
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Utils/WC_Product_Custom.php b/plugins/woocommerce/tests/php/src/Blocks/Utils/WC_Product_Custom.php
index 0e0edef3e7b..926c6da0e61 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Utils/WC_Product_Custom.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Utils/WC_Product_Custom.php
@@ -11,15 +11,6 @@ use WC_Product;
* Custom product class.
*/
class WC_Product_Custom extends WC_Product {
- /**
- * Initialize custom product.
- *
- * @param WC_Product|int $product Product instance or ID.
- */
- public function __construct( $product = 0 ) {
- parent::__construct();
- }
-
/**
* Get internal type.
*