Commit 0700cf714f for woocommerce
commit 0700cf714f20c55f94cce8e34e89a488139eae9a
Author: Pavel Dohnal <pavel.dohnal@automattic.com>
Date: Fri Apr 25 13:46:05 2025 +0200
Add Woo email content to email preview (#57337)
* Replace placeholder content in email previews
Ensure email previews display dynamic content using actual email generation logic.
Improves preview accuracy and adds nonce verification for security.
[WOOPLUG-3316]
* Get the email type from post when sending preview
[WOOPLUG-3316]
* Refactor email preview content generation
Consolidate the logic for generating email placeholder content
used in different parts of the email editor and preview features.
This avoids code duplication and centralizes the generation process.
[WOOPLUG-3316]
* Add changefile(s) from automation for the following project(s): packages/php/email-editor, woocommerce
* Initialise a variable before usage
[WOOPLUG-3316]
* Get email type form post meta
[WOOPLUG-3316]
* Document the preview filter
[WOOPLUG-3316]
* Add changefile(s) from automation for the following project(s): packages/php/email-editor, woocommerce
* Improve changelog
[WOOPLUG-2216]
* Add changefile(s) from automation for the following project(s): packages/php/email-editor, woocommerce
---------
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Vladimir Reznichenko <kalessil@gmail.com>
diff --git a/packages/php/email-editor/README.md b/packages/php/email-editor/README.md
index cda1a9ee97..1bd4a05bec 100644
--- a/packages/php/email-editor/README.md
+++ b/packages/php/email-editor/README.md
@@ -81,4 +81,5 @@ We may add, update and delete any of them.
| `woocommerce_is_email_editor_page` | `boolean` $isEditorPage | `boolean` | Check current page is the email editor page |
| `woocommerce_email_editor_send_preview_email` | `Array` $postData | `boolean` Result of processing. Was email sent successfully? | Allows override of the send preview mail function. Folks may choose to use custom implementation |
| `woocommerce_email_editor_post_sent_status_args` | `Array` `sent` post status args | `Array` register_post_status args | Allows update of the argument for the sent post status |
-| `woocommerce_email_blocks_renderer_parsed_blocks` | `Array` Parsed blocks data z | `Array` Parsed blocks data | You can modify the parsed blocks before they are processed by email renderer. |
+| `woocommerce_email_blocks_renderer_parsed_blocks` | `Array` Parsed blocks data | `Array` Parsed blocks data | You can modify the parsed blocks before they are processed by email renderer. |
+| `woocommerce_email_editor_send_preview_email_rendered_data` | `string` $data Rendered email | `string` Rendered email | Allows modifying the rendered email when displaying or sending it in preview |
diff --git a/packages/php/email-editor/changelog/57337-wooplug-3316-add-woo-email-content-to-email-preview b/packages/php/email-editor/changelog/57337-wooplug-3316-add-woo-email-content-to-email-preview
new file mode 100644
index 0000000000..e131455ab1
--- /dev/null
+++ b/packages/php/email-editor/changelog/57337-wooplug-3316-add-woo-email-content-to-email-preview
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add Woo email content to the preview in the email editor
\ No newline at end of file
diff --git a/packages/php/email-editor/src/Engine/class-send-preview-email.php b/packages/php/email-editor/src/Engine/class-send-preview-email.php
index 33a0a03e22..15c6bfd5ae 100644
--- a/packages/php/email-editor/src/Engine/class-send-preview-email.php
+++ b/packages/php/email-editor/src/Engine/class-send-preview-email.php
@@ -93,6 +93,8 @@ class Send_Preview_Email {
$language
);
+ $rendered_data = apply_filters( 'woocommerce_email_editor_send_preview_email_rendered_data', $rendered_data );
+
return $this->set_personalize_content( $rendered_data['html'] );
}
diff --git a/plugins/woocommerce/changelog/57337-wooplug-3316-add-woo-email-content-to-email-preview b/plugins/woocommerce/changelog/57337-wooplug-3316-add-woo-email-content-to-email-preview
new file mode 100644
index 0000000000..e131455ab1
--- /dev/null
+++ b/plugins/woocommerce/changelog/57337-wooplug-3316-add-woo-email-content-to-email-preview
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add Woo email content to the preview in the email editor
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin.php b/plugins/woocommerce/includes/admin/class-wc-admin.php
index bbb6d32299..12622efb8c 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin.php
@@ -255,66 +255,38 @@ class WC_Admin {
* Preview email editor placeholder dummy content.
*/
public function preview_email_editor_dummy_content() {
- if ( isset( $_GET['preview_woocommerce_mail_editor_content'] ) ) {
- if ( ! ( isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'preview-mail' ) ) ) {
- die( 'Security check' );
- }
-
- /**
- * Email preview instance for rendering dummy content.
- *
- * @var EmailPreview $email_preview - email preview instance
- */
- $email_preview = wc_get_container()->get( EmailPreview::class );
-
- if ( isset( $_GET['type'] ) ) {
- $type_param = sanitize_text_field( wp_unslash( $_GET['type'] ) );
- try {
- $email_preview->set_email_type( $type_param );
- } catch ( InvalidArgumentException $e ) {
- wp_die( esc_html__( 'Invalid email type.', 'woocommerce' ), 400 );
- }
- }
-
- /**
- * Woo content processor service.
- *
- * @var WooContentProcessor $woo_content_processor - service for processing Woo content.
- */
- $woo_content_processor = wc_get_container()->get( WooContentProcessor::class );
-
- $generate_placeholder_content = function () use ( $email_preview, $woo_content_processor ) {
- add_filter( 'woocommerce_email_styles', array( $woo_content_processor, 'prepare_css' ), 10, 2 );
- $content = $woo_content_processor->get_woo_content( $email_preview->get_email() );
- $content = $email_preview->get_email()->style_inline( $content );
- $content = $email_preview->ensure_links_open_in_new_tab( $content );
- return $content;
- };
+ $message = '';
+ if ( ! isset( $_GET['preview_woocommerce_mail_editor_content'] ) ) {
+ return;
+ }
- $email_preview->set_up_filters();
+ if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'preview-mail' ) ) {
+ die( 'Security check' );
+ }
- if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
- $message = $generate_placeholder_content();
- } else {
- // Start output buffering to prevent partial renders with PHP notices or warnings.
- ob_start();
- try {
- $message = $generate_placeholder_content();
- } catch ( Throwable $e ) {
- ob_end_clean();
- wp_die( esc_html__( 'There was an error rendering the email editor placeholder content.', 'woocommerce' ), 404 );
- }
- ob_end_clean();
- }
+ /**
+ * Email preview instance for rendering dummy content.
+ *
+ * @var EmailPreview $email_preview - email preview instance
+ */
+ $email_preview = wc_get_container()->get( EmailPreview::class );
- $email_preview->clean_up_filters();
+ $type_param = EmailPreview::DEFAULT_EMAIL_TYPE;
+ if ( isset( $_GET['type'] ) ) {
+ $type_param = sanitize_text_field( wp_unslash( $_GET['type'] ) );
+ }
- // print the placeholder content.
- // phpcs:ignore WordPress.Security.EscapeOutput
- echo $message;
- // phpcs:enable
- exit;
+ try {
+ $message = $email_preview->generate_placeholder_content( $type_param );
+ } catch ( \Exception $e ) {
+ // Catch other potential errors during content generation.
+ wp_die( esc_html__( 'There was an error rendering the email preview.', 'woocommerce' ), 404 );
}
+
+ // Print the placeholder content.
+ // phpcs:ignore WordPress.Security.EscapeOutput
+ echo $message;
+ exit;
}
/**
diff --git a/plugins/woocommerce/src/Internal/Admin/EmailPreview/EmailPreview.php b/plugins/woocommerce/src/Internal/Admin/EmailPreview/EmailPreview.php
index 3e082bd7d3..d90de92cbd 100644
--- a/plugins/woocommerce/src/Internal/Admin/EmailPreview/EmailPreview.php
+++ b/plugins/woocommerce/src/Internal/Admin/EmailPreview/EmailPreview.php
@@ -7,7 +7,9 @@ declare( strict_types=1 );
namespace Automattic\WooCommerce\Internal\Admin\EmailPreview;
+use Automattic\WooCommerce\Internal\EmailEditor\WooContentProcessor;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
+use Throwable;
use WC_Email;
use WC_Order;
use WC_Product;
@@ -513,4 +515,56 @@ class EmailPreview {
public function get_placeholder_image() {
return '<img src="' . WC()->plugin_url() . '/assets/images/placeholder.png" width="48" height="48" alt="" />';
}
+
+ /**
+ * Generate placeholder content for a specific email type, typically used in the email editor.
+ *
+ * Encapsulates the logic for setting the email type, generating raw content, applying styles,
+ * ensuring links open in new tabs, and handling errors based on WP_DEBUG.
+ *
+ * @param string $email_type_class_name The class name of the WC_Email type (e.g., 'WC_Email_Customer_Processing_Order').
+ * @return string The generated and styled HTML content.
+ * @throws \RuntimeException If content generation fails. If rendering fails.
+ */
+ public function generate_placeholder_content( string $email_type_class_name ): string {
+ // Note: set_email_type can throw InvalidArgumentException.
+ $this->set_email_type( $email_type_class_name );
+
+ $woo_content_processor = wc_get_container()->get( WooContentProcessor::class );
+
+ $generate_content_closure = function () use ( $woo_content_processor ) {
+ // Note: If 'woocommerce_email_styles' filter was intentional and `prepare_css` isn't
+ // the intended callback, adjust accordingly. This assumes `prepare_css` applies styles
+ // needed for the Woo content block.
+ add_filter( 'woocommerce_email_styles', array( $woo_content_processor, 'prepare_css' ), 10, 2 );
+ $content = $woo_content_processor->get_woo_content( $this->get_email() );
+ $content = $this->get_email()->style_inline( $content );
+ $content = $this->ensure_links_open_in_new_tab( $content );
+ return $content;
+ };
+
+ $this->set_up_filters();
+
+ $message = '';
+ try {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+ $message = $generate_content_closure();
+ } else {
+ // Use output buffering to prevent partial renders with PHP notices or warnings when WP_DEBUG is off.
+ ob_start();
+ try {
+ $message = $generate_content_closure();
+ } catch ( Throwable $e ) {
+ ob_end_clean();
+ // Let the caller handle the exception.
+ throw new \RuntimeException( esc_html__( 'There was an error rendering the email editor placeholder content.', 'woocommerce' ), 0, $e );
+ }
+ ob_end_clean();
+ }
+ } finally {
+ $this->clean_up_filters();
+ }
+
+ return $message;
+ }
}
diff --git a/plugins/woocommerce/src/Internal/EmailEditor/Integration.php b/plugins/woocommerce/src/Internal/EmailEditor/Integration.php
index e5fb2f2ac1..82c38f3f68 100644
--- a/plugins/woocommerce/src/Internal/EmailEditor/Integration.php
+++ b/plugins/woocommerce/src/Internal/EmailEditor/Integration.php
@@ -6,11 +6,13 @@ namespace Automattic\WooCommerce\Internal\EmailEditor;
use Automattic\WooCommerce\EmailEditor\Email_Editor_Container;
use Automattic\WooCommerce\EmailEditor\Engine\Dependency_Check;
+use Automattic\WooCommerce\Internal\Admin\EmailPreview\EmailPreview;
use Automattic\WooCommerce\Internal\EmailEditor\EmailPatterns\PatternsController;
use Automattic\WooCommerce\Internal\EmailEditor\EmailTemplates\TemplatesController;
use Automattic\WooCommerce\Internal\EmailEditor\WCTransactionalEmails\WCTransactionalEmails;
use Automattic\WooCommerce\Internal\EmailEditor\WCTransactionalEmails\WCTransactionalEmailPostsManager;
use Automattic\WooCommerce\Internal\EmailEditor\EmailTemplates\TemplateApiController;
+use Throwable;
use WP_Post;
defined( 'ABSPATH' ) || exit;
@@ -97,6 +99,8 @@ class Integration {
add_filter( 'woocommerce_is_email_editor_page', array( $this, 'is_editor_page' ), 10, 1 );
add_filter( 'replace_editor', array( $this, 'replace_editor' ), 10, 2 );
add_action( 'before_delete_post', array( $this, 'delete_email_template_associated_with_email_editor_post' ), 10, 2 );
+ add_filter( 'woocommerce_email_editor_send_preview_email_rendered_data', array( $this, 'update_send_preview_email_rendered_data' ) );
+ add_filter( 'woocommerce_email_editor_preview_post_template_html', array( $this, 'update_preview_post_template_html_data' ), 100, 1 );
}
/**
@@ -200,4 +204,81 @@ class Integration {
)
);
}
+
+ /**
+ * Filter email preview data to replace placeholders with actual content.
+ *
+ * This method retrieves the appropriate email type based on the request,
+ * generates the email content using the WooContentProcessor, and replaces
+ * the placeholder in the preview HTML.
+ *
+ * @param string $data The preview data.
+ * @param string $email_type The email type identifier (e.g., 'customer_processing_order').
+ * @return string The updated preview data with placeholders replaced.
+ */
+ private function update_email_preview_data( $data, string $email_type ) {
+ $default_type_param = 'WC_Email_Customer_Processing_Order';
+ $type_param = $default_type_param;
+
+ if ( ! empty( $email_type ) ) {
+ $type_param = 'WC_Email_' . implode( '_', array_map( 'ucfirst', explode( '_', $email_type ) ) );
+ }
+
+ $email_preview = wc_get_container()->get( EmailPreview::class );
+
+ try {
+ $message = $email_preview->generate_placeholder_content( $type_param );
+ } catch ( \InvalidArgumentException $e ) {
+ // If the provided type was invalid, fall back to the default.
+ try {
+ $message = $email_preview->generate_placeholder_content( $default_type_param );
+ } catch ( \Throwable $e ) {
+ return $data;
+ }
+ } catch ( \Throwable $e ) {
+ return $data;
+ }
+
+ return str_replace( BlockEmailRenderer::WOO_EMAIL_CONTENT_PLACEHOLDER, $message, $data );
+ }
+
+ /**
+ * Filter email preview data used when sending a preview email.
+ *
+ * @param string $data The preview data.
+ * @return string The updated preview data with placeholders replaced.
+ */
+ public function update_send_preview_email_rendered_data( $data ) {
+ $email_type = '';
+ $post_body = file_get_contents( 'php://input' );
+
+ if ( $post_body ) {
+ $decoded_body = json_decode( $post_body );
+
+ if ( json_last_error() === JSON_ERROR_NONE && isset( $decoded_body->postId ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ $post_id = absint( $decoded_body->postId ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+ $email_type = get_post_meta( $post_id, self::WC_EMAIL_TYPE_ID_POST_META_KEY, true );
+ if ( ! empty( $email_type ) ) {
+ return $this->update_email_preview_data( $data, $email_type );
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Filter email preview data used when previewing the email in new tab.
+ *
+ * @param string $data The preview HTML string.
+ * @return string The updated preview HTML with placeholders replaced.
+ */
+ public function update_preview_post_template_html_data( $data ) {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended
+ // Nonce verification is disabled here because the preview action doesn't modify data,
+ // and the check caused issues with the 'Preview in new tab' feature due to context changes.
+ $type_param = isset( $_GET['woo_email'] ) ? sanitize_text_field( wp_unslash( $_GET['woo_email'] ) ) : '';
+ // phpcs:enable
+ return $this->update_email_preview_data( $data, $type_param );
+ }
}