Logger
11 months ago
Patterns
11 months ago
PersonalizationTags
3 days ago
Renderer
3 days ago
Templates
4 months ago
class-assets-manager.php
5 months ago
class-dependency-check.php
11 months ago
class-email-api-controller.php
6 months ago
class-email-editor.php
3 days ago
class-email-styles-schema.php
11 months ago
class-personalizer.php
3 days ago
class-send-preview-email.php
5 months ago
class-settings-controller.php
3 months ago
class-site-style-sync-controller.php
4 months ago
class-theme-controller.php
3 months ago
class-user-theme.php
11 months ago
content-editor.css
2 weeks ago
content-shared.css
11 months ago
index.php
11 months ago
theme.json
3 months ago
class-email-editor.php
243 lines
| 1 | <?php |
| 2 | declare(strict_types = 1); |
| 3 | namespace Automattic\WooCommerce\EmailEditor\Engine; |
| 4 | if (!defined('ABSPATH')) exit; |
| 5 | use Automattic\WooCommerce\EmailEditor\Engine\Patterns\Patterns; |
| 6 | use Automattic\WooCommerce\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry; |
| 7 | use Automattic\WooCommerce\EmailEditor\Engine\Templates\Templates; |
| 8 | use Automattic\WooCommerce\EmailEditor\Engine\Logger\Email_Editor_Logger; |
| 9 | use WP_Post; |
| 10 | use WP_REST_Request; |
| 11 | use WP_Theme_JSON; |
| 12 | class Email_Editor { |
| 13 | public const WOOCOMMERCE_EMAIL_META_THEME_TYPE = 'woocommerce_email_theme'; |
| 14 | private Email_Api_Controller $email_api_controller; |
| 15 | private Templates $templates; |
| 16 | private Patterns $patterns; |
| 17 | private Send_Preview_Email $send_preview_email; |
| 18 | private Personalization_Tags_Registry $personalization_tags_registry; |
| 19 | private Email_Editor_Logger $logger; |
| 20 | private Assets_Manager $assets_manager; |
| 21 | public function __construct( |
| 22 | Email_Api_Controller $email_api_controller, |
| 23 | Templates $templates, |
| 24 | Patterns $patterns, |
| 25 | Send_Preview_Email $send_preview_email, |
| 26 | Personalization_Tags_Registry $personalization_tags_controller, |
| 27 | Email_Editor_Logger $logger, |
| 28 | Assets_Manager $assets_manager |
| 29 | ) { |
| 30 | $this->email_api_controller = $email_api_controller; |
| 31 | $this->templates = $templates; |
| 32 | $this->patterns = $patterns; |
| 33 | $this->send_preview_email = $send_preview_email; |
| 34 | $this->personalization_tags_registry = $personalization_tags_controller; |
| 35 | $this->logger = $logger; |
| 36 | $this->assets_manager = $assets_manager; |
| 37 | } |
| 38 | public function initialize(): void { |
| 39 | $this->logger->info( 'Initializing email editor' ); |
| 40 | do_action( 'woocommerce_email_editor_initialized' ); |
| 41 | add_filter( 'woocommerce_email_editor_rendering_theme_styles', array( $this, 'extend_email_theme_styles' ), 10, 2 ); |
| 42 | $this->register_block_patterns(); |
| 43 | $this->register_email_post_types(); |
| 44 | $this->register_block_templates(); |
| 45 | $this->register_email_post_sent_status(); |
| 46 | $this->register_personalization_tags(); |
| 47 | $is_editor_page = apply_filters( 'woocommerce_is_email_editor_page', false ); |
| 48 | if ( $is_editor_page ) { |
| 49 | $this->extend_email_post_api(); |
| 50 | // Initialize the assets manager. |
| 51 | $this->assets_manager->initialize(); |
| 52 | } |
| 53 | add_action( 'rest_api_init', array( $this, 'register_email_editor_api_routes' ) ); |
| 54 | add_filter( 'woocommerce_email_editor_send_preview_email', array( $this->send_preview_email, 'send_preview_email' ), 11, 1 ); // allow for other filter methods to take precedent. |
| 55 | add_filter( 'single_template', array( $this, 'load_email_preview_template' ) ); |
| 56 | add_filter( 'preview_post_link', array( $this, 'update_preview_post_link' ), 10, 2 ); |
| 57 | $this->logger->info( 'Email editor initialized successfully' ); |
| 58 | } |
| 59 | private function register_block_templates(): void { |
| 60 | // Since we cannot currently disable blocks in the editor for specific templates, disable templates when viewing site editor. @see https://github.com/WordPress/gutenberg/issues/41062. |
| 61 | $request_uri = ''; |
| 62 | if ( isset( $_SERVER['REQUEST_URI'] ) && is_string( $_SERVER['REQUEST_URI'] ) ) { |
| 63 | $request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); |
| 64 | } |
| 65 | if ( strstr( $request_uri, 'site-editor.php' ) === false ) { |
| 66 | $post_types = array_column( $this->get_post_types(), 'name' ); |
| 67 | $this->templates->initialize( $post_types ); |
| 68 | } |
| 69 | } |
| 70 | private function register_block_patterns(): void { |
| 71 | $this->patterns->initialize(); |
| 72 | } |
| 73 | private function register_email_post_types(): void { |
| 74 | foreach ( $this->get_post_types() as $post_type ) { |
| 75 | register_post_type( |
| 76 | $post_type['name'], |
| 77 | array_merge( $this->get_default_email_post_args(), $post_type['args'] ) |
| 78 | ); |
| 79 | } |
| 80 | } |
| 81 | private function register_personalization_tags(): void { |
| 82 | $this->personalization_tags_registry->initialize(); |
| 83 | } |
| 84 | private function get_post_types(): array { |
| 85 | $post_types = array(); |
| 86 | return apply_filters( 'woocommerce_email_editor_post_types', $post_types ); |
| 87 | } |
| 88 | private function get_default_email_post_args(): array { |
| 89 | return array( |
| 90 | 'public' => false, |
| 91 | 'hierarchical' => false, |
| 92 | 'show_ui' => true, |
| 93 | 'show_in_menu' => false, |
| 94 | 'show_in_nav_menus' => false, |
| 95 | 'supports' => array( |
| 96 | 'editor' => array( |
| 97 | 'default-mode' => 'template-locked', |
| 98 | ), |
| 99 | 'title', |
| 100 | 'custom-fields', |
| 101 | ), // 'custom-fields' is required for loading meta fields via API. |
| 102 | 'has_archive' => true, |
| 103 | 'show_in_rest' => true, // Important to enable Gutenberg editor. |
| 104 | 'default_rendering_mode' => 'template-locked', |
| 105 | 'publicly_queryable' => true, // required by the preview in new tab feature. |
| 106 | ); |
| 107 | } |
| 108 | private function register_email_post_sent_status(): void { |
| 109 | $default_args = array( |
| 110 | 'public' => false, |
| 111 | 'exclude_from_search' => true, |
| 112 | 'internal' => true, // for now, we hide it, if we use the status in the listings we may flip this and following values. |
| 113 | 'show_in_admin_all_list' => false, |
| 114 | 'show_in_admin_status_list' => false, |
| 115 | 'private' => true, // required by the preview in new tab feature for sent post (newsletter). Posts are only visible to site admins and editors. |
| 116 | ); |
| 117 | $args = apply_filters( 'woocommerce_email_editor_post_sent_status_args', $default_args ); |
| 118 | register_post_status( |
| 119 | 'sent', |
| 120 | $args |
| 121 | ); |
| 122 | } |
| 123 | public function extend_email_post_api() { |
| 124 | $email_post_types = array_column( $this->get_post_types(), 'name' ); |
| 125 | register_rest_field( |
| 126 | $email_post_types, |
| 127 | 'email_data', |
| 128 | array( |
| 129 | 'get_callback' => array( $this->email_api_controller, 'get_email_data' ), |
| 130 | 'update_callback' => array( $this->email_api_controller, 'save_email_data' ), |
| 131 | 'schema' => $this->email_api_controller->get_email_data_schema(), |
| 132 | ) |
| 133 | ); |
| 134 | } |
| 135 | public function register_email_editor_api_routes() { |
| 136 | register_rest_route( |
| 137 | 'woocommerce-email-editor/v1', |
| 138 | '/send_preview_email', |
| 139 | array( |
| 140 | 'methods' => 'POST', |
| 141 | 'callback' => array( $this->email_api_controller, 'send_preview_email_data' ), |
| 142 | 'permission_callback' => function ( WP_REST_Request $request ) { |
| 143 | if ( ! current_user_can( 'edit_posts' ) ) { |
| 144 | return false; |
| 145 | } |
| 146 | $post_id = $request->get_param( 'postId' ); |
| 147 | if ( is_numeric( $post_id ) && (int) $post_id > 0 ) { |
| 148 | return current_user_can( 'edit_post', (int) $post_id ); |
| 149 | } |
| 150 | return (bool) apply_filters( 'woocommerce_email_editor_send_preview_email_without_post_permission', false, $request ); |
| 151 | }, |
| 152 | ) |
| 153 | ); |
| 154 | register_rest_route( |
| 155 | 'woocommerce-email-editor/v1', |
| 156 | '/get_personalization_tags', |
| 157 | array( |
| 158 | 'methods' => 'GET', |
| 159 | 'callback' => array( $this->email_api_controller, 'get_personalization_tags' ), |
| 160 | 'permission_callback' => function () { |
| 161 | return current_user_can( 'edit_posts' ); |
| 162 | }, |
| 163 | ) |
| 164 | ); |
| 165 | register_rest_route( |
| 166 | 'woocommerce-email-editor/v1', |
| 167 | '/personalization_tags', |
| 168 | array( |
| 169 | 'methods' => 'GET', |
| 170 | 'callback' => array( $this->email_api_controller, 'get_personalization_tags_collection' ), |
| 171 | 'permission_callback' => function () { |
| 172 | return current_user_can( 'edit_posts' ); |
| 173 | }, |
| 174 | 'args' => array( |
| 175 | 'post_id' => array( |
| 176 | 'description' => __( 'The post ID for context-aware tag filtering.', 'woocommerce' ), |
| 177 | 'type' => 'integer', |
| 178 | 'required' => false, |
| 179 | 'sanitize_callback' => 'absint', |
| 180 | ), |
| 181 | ), |
| 182 | ) |
| 183 | ); |
| 184 | } |
| 185 | public function extend_email_theme_styles( WP_Theme_JSON $theme, WP_Post $post ): WP_Theme_JSON { |
| 186 | $email_theme = get_post_meta( $post->ID, self::WOOCOMMERCE_EMAIL_META_THEME_TYPE, true ); |
| 187 | if ( $email_theme && is_array( $email_theme ) ) { |
| 188 | $theme->merge( new WP_Theme_JSON( $email_theme ) ); |
| 189 | } |
| 190 | return $theme; |
| 191 | } |
| 192 | public function get_current_post() { |
| 193 | if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 194 | $post_id = 0; |
| 195 | if ( is_string( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- data valid |
| 196 | $post_id = intval( $_GET['post'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- data valid |
| 197 | } |
| 198 | $current_post = get_post( $post_id ); |
| 199 | } else { |
| 200 | $current_post = $GLOBALS['post']; |
| 201 | } |
| 202 | return $current_post; |
| 203 | } |
| 204 | private function current_post_is_email_post_type( $current_post_type ): bool { |
| 205 | if ( ! $current_post_type ) { |
| 206 | return false; |
| 207 | } |
| 208 | $email_post_types = array_column( $this->get_post_types(), 'name' ); |
| 209 | return in_array( $current_post_type, $email_post_types, true ); |
| 210 | } |
| 211 | public function load_email_preview_template( $template ) { |
| 212 | $post = $this->get_current_post(); |
| 213 | if ( ! $post instanceof \WP_Post ) { |
| 214 | return $template; |
| 215 | } |
| 216 | if ( ! $this->current_post_is_email_post_type( $post->post_type ) ) { |
| 217 | return $template; |
| 218 | } |
| 219 | // Anyone can see a published email. For other statuses the user must be able to read the post. |
| 220 | if ( ! is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post->ID ) ) { |
| 221 | return $template; |
| 222 | } |
| 223 | add_filter( |
| 224 | 'woocommerce_email_editor_preview_post_template_html', |
| 225 | function () use ( $post ) { |
| 226 | // Generate HTML content for email editor post. |
| 227 | return $this->send_preview_email->render_html( $post ); |
| 228 | } |
| 229 | ); |
| 230 | return __DIR__ . '/Templates/single-email-post-template.php'; |
| 231 | } |
| 232 | public function update_preview_post_link( $preview_link, $post ) { |
| 233 | if ( ! $post instanceof \WP_Post ) { |
| 234 | return $preview_link; |
| 235 | } |
| 236 | if ( ! $this->current_post_is_email_post_type( $post->post_type ) ) { |
| 237 | return $preview_link; |
| 238 | } |
| 239 | // Remove preview_nonce from the link. |
| 240 | return remove_query_arg( 'preview_nonce', $preview_link ); |
| 241 | } |
| 242 | } |
| 243 |