Commit d9220a21f42 for woocommerce

commit d9220a21f427e650d762256a21ac0e5afd644d51
Author: Yuliyan Slavchev <yuliyan.slavchev@gmail.com>
Date:   Fri Jul 17 13:39:48 2026 +0300

    Limit front end email preview to published emails or users who can read the post (#66727)

diff --git a/packages/php/email-editor/changelog/wooprd-3572-any-user-can-read-unpublished-emails-via-post-on-the-front b/packages/php/email-editor/changelog/wooprd-3572-any-user-can-read-unpublished-emails-via-post-on-the-front
new file mode 100644
index 00000000000..37e079541c7
--- /dev/null
+++ b/packages/php/email-editor/changelog/wooprd-3572-any-user-can-read-unpublished-emails-via-post-on-the-front
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Limit the front end email preview to published emails or users who can read the post, so unpublished emails are no longer shown to logged out visitors.
diff --git a/packages/php/email-editor/src/Engine/class-email-editor.php b/packages/php/email-editor/src/Engine/class-email-editor.php
index 3b26a1f95b0..f784b12edef 100644
--- a/packages/php/email-editor/src/Engine/class-email-editor.php
+++ b/packages/php/email-editor/src/Engine/class-email-editor.php
@@ -372,6 +372,11 @@ class Email_Editor {
 			return $template;
 		}

+		// Anyone can see a published email. For other statuses the user must be able to read the post.
+		if ( ! is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post->ID ) ) {
+			return $template;
+		}
+
 		add_filter(
 			'woocommerce_email_editor_preview_post_template_html',
 			function () use ( $post ) {
diff --git a/packages/php/email-editor/tests/integration/Engine/Email_Editor_Test.php b/packages/php/email-editor/tests/integration/Engine/Email_Editor_Test.php
index 6a20ba20bc8..f3aaffc9f0d 100644
--- a/packages/php/email-editor/tests/integration/Engine/Email_Editor_Test.php
+++ b/packages/php/email-editor/tests/integration/Engine/Email_Editor_Test.php
@@ -58,10 +58,70 @@ class Email_Editor_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertArrayHasKey( 'custom_email_type', $post_types );
 	}

+	/**
+	 * A logged out visitor should not see a draft email passed via ?post=.
+	 */
+	public function testItDoesNotRenderUnpublishedEmailForAnonymousVisitor(): void {
+		wp_set_current_user( 0 );
+		$post_id      = $this->create_email_post( 'draft' );
+		$_GET['post'] = (string) $post_id;
+		$fallback     = 'fallback-template.php';
+
+		$template = $this->email_editor->load_email_preview_template( $fallback );
+
+		$this->assertSame( $fallback, $template );
+	}
+
+	/**
+	 * Published emails stay public so preview in a new tab keeps working.
+	 */
+	public function testItRendersPublishedEmailForAnonymousVisitor(): void {
+		wp_set_current_user( 0 );
+		$post_id      = $this->create_email_post( 'publish' );
+		$_GET['post'] = (string) $post_id;
+
+		$template = $this->email_editor->load_email_preview_template( 'fallback-template.php' );
+
+		$this->assertStringEndsWith( 'single-email-post-template.php', $template );
+	}
+
+	/**
+	 * A user who can read the email can still preview it while it is a draft.
+	 */
+	public function testItRendersUnpublishedEmailForUserWithReadAccess(): void {
+		$user_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
+		$this->assertIsInt( $user_id );
+		wp_set_current_user( $user_id );
+		$post_id      = $this->create_email_post( 'draft' );
+		$_GET['post'] = (string) $post_id;
+
+		$template = $this->email_editor->load_email_preview_template( 'fallback-template.php' );
+
+		$this->assertStringEndsWith( 'single-email-post-template.php', $template );
+	}
+
+	/**
+	 * Create an email post of the registered custom type.
+	 *
+	 * @param string $status The post status.
+	 * @return int The created post ID.
+	 */
+	private function create_email_post( string $status ): int {
+		$post_id = self::factory()->post->create(
+			array(
+				'post_type'   => 'custom_email_type',
+				'post_status' => $status,
+			)
+		);
+		$this->assertIsInt( $post_id );
+		return $post_id;
+	}
+
 	/**
 	 * Clean up after each test
 	 */
 	public function tearDown(): void {
+		unset( $_GET['post'] );
 		parent::tearDown();
 		remove_filter( 'woocommerce_email_editor_post_types', $this->post_register_callback );
 	}