PluginProbe
YayMail – WooCommerce Email Customizer / 4.4.5
YayMail – WooCommerce Email Customizer v4.4.5
4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 trunk 1.9.6 2.1.4 2.1.5 3.2.2 3.2.6 3.2.7.1 3.2.8.1 3.2.9 3.3 3.3.1 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4 3.4.1 All 57 releases
← All changes | src/Ajax.php +159 -34 4.4.2 → 4.4.5 View file →
@@ -7,9 +7,11 @@
7 7 use YayMail\Models\TemplateModel;
8 8 use YayMail\Models\RevisionModel;
9 9 use YayMail\Migrations\MainMigration;
10 10 use YayMail\Utils\Helpers;
11 -use YayMail\Migrations\AbstractMigration;
11 +use YayMail\Utils\Logger;
12 +use YayMail\Models\PatternTemplateModel;
13 +use YayMail\Utils\TemplateHelpers;
12 14
13 15 /**
14 16 * I18n Logic
15 17 *
@@ -36,10 +38,59 @@
36 38 add_action( 'wp_ajax_yaymail_dismiss_multi_select_notice', [ $this, 'dismiss_multi_select_notice' ] );
37 39 add_action( 'wp_ajax_yaymail_export_state', [ $this, 'export_state' ] );
38 40 add_action( 'wp_ajax_yaymail_import_state', [ $this, 'import_state' ] );
39 41 add_action( 'wp_ajax_yaymail_dismiss_new_element_notification', [ $this, 'dismiss_new_element_notification' ] );
42 + add_action( 'wp_ajax_yaymail_log_client_error', [ $this, 'log_client_error' ] );
40 43 }
41 44
45 + /**
46 + * Records a runtime error reported by the admin SPA (see apps/customizer/src/utils/client-error-reporter.ts).
47 + *
48 + * Goes to the WooCommerce log (WooCommerce > Status > Logs, source "yaymail-client") so support can ask the
49 + * customer to copy it from wp-admin; falls back to the yaymail-logs file when WooCommerce is absent.
50 + */
51 + public function log_client_error() {
52 + $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
53 + if ( ! wp_verify_nonce( $nonce, 'yaymail_frontend_nonce' ) || ! current_user_can( 'manage_woocommerce' ) ) {
54 + return wp_send_json_error( [ 'mess' => __( 'Verify nonce failed', 'yaymail' ) ], 403 );
55 + }
56 +
57 + // A render loop in one browser must not be able to fill the log.
58 + $rate_key = 'yaymail_client_error_rate_' . get_current_user_id();
59 + $count = (int) get_transient( $rate_key );
60 + if ( $count >= 20 ) {
61 + return wp_send_json_success( [ 'logged' => false ] );
62 + }
63 + set_transient( $rate_key, $count + 1, MINUTE_IN_SECONDS );
64 +
65 + $text = function ( $key, $max = 8000 ) {
66 + $value = isset( $_POST[ $key ] ) ? sanitize_textarea_field( wp_unslash( $_POST[ $key ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
67 + return mb_substr( $value, 0, $max );
68 + };
69 +
70 + $lines = [
71 + 'CLIENT ERROR [' . $text( 'source', 20 ) . ']: ' . $text( 'message', 1000 ),
72 + 'Version: ' . $text( 'version', 20 ) . ' | Platform: ' . $text( 'platform', 40 ) . ' | Template: ' . $text( 'template', 200 ),
73 + 'URL: ' . $text( 'url', 2000 ),
74 + 'User agent: ' . $text( 'user_agent', 500 ),
75 + ];
76 + foreach ( [ 'hint' => 'Hint', 'stack' => 'Stack', 'component_stack' => 'Component stack' ] as $key => $label ) {
77 + $value = $text( $key );
78 + if ( '' !== $value ) {
79 + $lines[] = $label . ':' . PHP_EOL . $value;
80 + }
81 + }
82 + $message = implode( PHP_EOL, $lines );
83 +
84 + if ( function_exists( 'wc_get_logger' ) ) {
85 + wc_get_logger()->error( $message, [ 'source' => 'yaymail-client' ] );
86 + } else {
87 + ( new Logger() )->log( $message );
88 + }
89 +
90 + wp_send_json_success( [ 'logged' => true ] );
91 + }
92 +
42 93 public function import_state() {
43 94 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
44 95 if ( ! wp_verify_nonce( $nonce, 'yaymail_frontend_nonce' ) ) {
45 96 return wp_send_json_error( [ 'mess' => __( 'Verify nonce failed', 'yaymail' ) ] );
@@ -61,9 +112,12 @@
61 112
62 113 $state_data = null;
63 114 for ( $i = 0; $i < $zip->numFiles; $i++ ) {
64 115 $filename = $zip->getNameIndex( $i );
65 - if ( $filename === 'yaymail_backup.json' ) {
116 + // Matches both "yaymail_backup.json" (YayMail) and "yaymagic_email_builder_backup.json"
117 + // (Email Builder) -- the zip always holds exactly this one entry, named per
118 + // getBrandName() in backup/index.tsx.
119 + if ( 1 === preg_match( '/_backup\.json$/', (string) $filename ) ) {
66 120 $state_data = $zip->getFromIndex( $i );
67 121 break;
68 122 }
69 123 }
@@ -70,9 +124,9 @@
70 124
71 125 $zip->close();
72 126
73 127 if ( ! $state_data ) {
74 - return wp_send_json_error( [ 'mess' => __( 'Cannot find yaymail_backup.json in the ZIP file.', 'yaymail' ) ] );
128 + return wp_send_json_error( [ 'mess' => __( 'Cannot find the backup JSON file in the ZIP file.', 'yaymail' ) ] );
75 129 }
76 130
77 131 $imported_data = json_decode( $state_data );
78 132 if ( json_last_error() !== JSON_ERROR_NONE ) {
@@ -196,10 +250,23 @@
196 250 $yaymail_options = $wpdb->get_results( $query_options ); // phpcs:ignore
197 251 $backup_data['options'] = $yaymail_options;
198 252
199 253 $backup_data['created_date'] = current_datetime()->format( 'Y-m-d H:i:s' );
200 - $backup_data['version'] = get_option( 'yaymail_version_backup' );
201 254
255 + // Fall back through running version / YAYWP_VERSION / '1.0' when the
256 + // backup option hasn't been set yet (e.g. init_modules() hasn't
257 + // recorded it), otherwise a falsy 'version' gets baked into the
258 + // exported ZIP and MigrationModel::reset() rejects it on import with
259 + // "Back up does not exist".
260 + $version_backup = get_option( 'yaymail_version_backup' );
261 + if ( ! empty( $version_backup ) ) {
262 + $backup_data['version'] = $version_backup;
263 + } elseif ( ! empty( yaymail_version() ) ) {
264 + $backup_data['version'] = yaymail_version();
265 + } else {
266 + $backup_data['version'] = defined( 'YAYWP_VERSION' ) ? YAYWP_VERSION : '1.0';
267 + }
268 +
202 269 $backup_data = apply_filters( 'yaymail_backup_state_data', $backup_data );
203 270
204 271 wp_send_json_success(
205 272 [
@@ -204,16 +271,18 @@
204 271 wp_send_json_success(
205 272 [
206 273 'message' => 'success',
207 274 'data' => $backup_data,
208 - 'file_name' => 'yaymail_export_backup_' . gmdate( 'm-d-Y' ),
275 + 'file_name' => $this->export_file_name_prefix() . '_export_backup_' . gmdate( 'm-d-Y' ),
209 276 ]
210 277 );
211 278
212 279 } catch ( \Error $error ) {
213 280 yaymail_get_logger( $error );
281 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
214 282 } catch ( \Exception $exception ) {
215 283 yaymail_get_logger( $exception );
284 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
216 285 }//end try
217 286 }
218 287
219 288 public function sanitize( $array ) {
@@ -220,8 +289,20 @@
220 289
221 290 return wp_kses_post_deep( $array );
222 291 }
223 292
293 + /**
294 + * File name prefix for exported/backup downloads, matching the frontend's
295 + * brand name per platform (constants/theme.ts WP_COLORS vs the free "yaymail"
296 + * default). Reads the "platform" the frontend sends alongside these AJAX
297 + * requests (see common/ajax's getPlatform() calls) since these admin-ajax.php
298 + * requests don't reliably expose the calling screen via get_current_screen().
299 + */
300 + private function export_file_name_prefix() {
301 + $platform = isset( $_POST['platform'] ) ? sanitize_text_field( wp_unslash( $_POST['platform'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Missing
302 + return 'yaymagic-email-builder' === $platform ? 'yaymagic_email_builder' : 'yaymail';
303 + }
304 +
224 305 public function process_plugin_installer( $slug ) {
225 306 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
226 307 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
227 308 require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
@@ -297,10 +378,12 @@
297 378 );
298 379
299 380 } catch ( \Error $error ) {
300 381 yaymail_get_logger( $error );
382 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
301 383 } catch ( \Exception $exception ) {
302 384 yaymail_get_logger( $exception );
385 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
303 386 }//end try
304 387 }
305 388
306 389 public function send_test_mail() {
@@ -325,9 +408,10 @@
325 408 if ( empty( $email ) ) {
326 409 return wp_send_json_error( [ 'mess' => __( 'Can\'t find email', 'yaymail' ) ] );
327 410 }
328 411
329 - $template = new YayMailTemplate( $template_name );
412 + $variant = yaymail_sanitize_template_variant( isset( $_POST['variant'] ) ? sanitize_text_field( wp_unslash( $_POST['variant'] ) ) : '' );
413 + $template = new YayMailTemplate( $template_name, '', $variant );
330 414
331 415 $render_data = [];
332 416
333 417 if ( empty( $order_id ) || ( 'sample_order' === $order_id ) ) {
@@ -349,8 +433,16 @@
349 433 if ( $is_wc_email ) {
350 434 $class_wc_email = \WC_Emails::instance();
351 435 $send_mail = $class_wc_email->send( $email, $subject, $html, $headers, [] );
352 436 } else {
437 + // $html is already the fully-rendered wp-core-mail template output.
438 + // Without this marker, WpMail::custom_wp_mail_body() (hooked on the
439 + // 'wp_mail' filter, meant to wrap *raw* WP core messages like a
440 + // password-reset email) can't tell it apart from unrendered content
441 + // and wraps it in the template a second time -- the same marker
442 + // AddonWpMailController::woocommerce_mail_content() uses for real
443 + // WooCommerce sends.
444 + $html = '<!-- yaymail:wp-has-template-email-content -->' . $html;
353 445 $send_mail = wp_mail( $email, $subject, $html, $headers, [] );
354 446 }
355 447 if ( ! $send_mail ) {
356 448 return wp_send_json_error( [ 'mess' => __( 'Can\'t send email', 'yaymail' ) ] );
@@ -363,10 +455,12 @@
363 455 ]
364 456 );
365 457 } catch ( \Error $error ) {
366 458 yaymail_get_logger( $error );
459 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
367 460 } catch ( \Exception $exception ) {
368 461 yaymail_get_logger( $exception );
462 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
369 463 }//end try
370 464 }
371 465
372 466 public function preview_mail() {
@@ -446,10 +540,12 @@
446 540 ]
447 541 );
448 542 } catch ( \Error $error ) {
449 543 yaymail_get_logger( $error );
544 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
450 545 } catch ( \Exception $exception ) {
451 546 yaymail_get_logger( $exception );
547 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
452 548 }//end try
453 549 }
454 550
455 551 public function preview_mail_for_woo() {
@@ -460,12 +556,13 @@
460 556 try {
461 557 $template_name = isset( $_POST['template_name'] ) ? sanitize_text_field( wp_unslash( $_POST['template_name'] ) ) : '';
462 558 $search_order_id = isset( $_POST['search_order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['search_order_id'] ) ) : null;
463 559 $email_address = isset( $_POST['email_address'] ) ? sanitize_text_field( wp_unslash( $_POST['email_address'] ) ) : '';
560 + $variant = yaymail_sanitize_template_variant( isset( $_POST['variant'] ) ? sanitize_text_field( wp_unslash( $_POST['variant'] ) ) : '' );
464 561
465 - $email_preview_output = PreviewEmail\PreviewEmailWoo::email_preview_output( $search_order_id, $template_name, $email_address );
562 + $email_preview_output = PreviewEmail\PreviewEmailWoo::email_preview_output( $search_order_id, $template_name, $email_address, $variant );
466 563
467 - $email_preview_output = apply_filters( 'yaymail_preview_email', $email_preview_output, $search_order_id, $template_name, $email_address );
564 + $email_preview_output = apply_filters( 'yaymail_preview_email', $email_preview_output, $search_order_id, $template_name, $email_address, $variant );
468 565
469 566 $send_mail = false;
470 567
471 568 if ( ! empty( $email_address ) && ! empty( $email_preview_output['html'] ) ) {
@@ -487,10 +584,12 @@
487 584 ]
488 585 );
489 586 } catch ( \Error $error ) {
490 587 yaymail_get_logger( $error );
588 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
491 589 } catch ( \Exception $exception ) {
492 590 yaymail_get_logger( $exception );
591 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
493 592 }//end try
494 593 }
495 594
496 595 public function export_templates() {
@@ -510,8 +609,13 @@
510 609 'key' => YayMailTemplate::META_KEYS['name'],
511 610 'value' => $templates,
512 611 'compare' => 'IN',
513 612 ],
613 + // Export the default design only; import keys on name and would collapse variants onto it.
614 + [
615 + 'key' => YayMailTemplate::META_KEYS['variant'],
616 + 'compare' => 'NOT EXISTS',
617 + ],
514 618 ],
515 619 ];
516 620 $export_data = [];
517 621 $query = new \WP_Query( $default );
@@ -524,24 +628,29 @@
524 628 $text_link_color = get_post_meta( $post->ID, YayMailTemplate::META_KEYS['text_link_color'], true );
525 629 $background_color = get_post_meta( $post->ID, YayMailTemplate::META_KEYS['background_color'], true );
526 630 $content_background_color = get_post_meta( $post->ID, YayMailTemplate::META_KEYS['content_background_color'], true );
527 631 $content_text_color = get_post_meta( $post->ID, YayMailTemplate::META_KEYS['content_text_color'], true );
632 + $title_color = get_post_meta( $post->ID, YayMailTemplate::META_KEYS['title_color'], true );
528 633 $file_name = "{$template_name}.json";
529 - if ( empty( $language ) ) {
530 - $export_data[] = [
531 - 'file_name' => $file_name,
532 - 'templates_data' => [
533 - 'template' => $template_name,
534 - 'elements' => $elements,
535 - 'language' => '',
536 - 'text_link_color' => $text_link_color,
537 - 'background_color' => $background_color,
538 - 'content_background_color' => $content_background_color,
539 - 'content_text_color' => $content_text_color,
540 - 'title_color' => $title_color,
541 - ],
542 - ];
543 - }
634 + // Every WP-core template (e.g. wp-core-mail) is saved with a
635 + // non-empty language meta (the site locale), unlike WooCommerce
636 + // templates -- gating on "no language" here used to silently
637 + // drop every WP-core template from the export with no error.
638 + // find_by_name() below only ever looks templates up by name, so
639 + // there's no separate per-language row to disambiguate against.
640 + $export_data[] = [
641 + 'file_name' => $file_name,
642 + 'templates_data' => [
643 + 'template' => $template_name,
644 + 'elements' => $elements,
645 + 'language' => $language,
646 + 'text_link_color' => $text_link_color,
647 + 'background_color' => $background_color,
648 + 'content_background_color' => $content_background_color,
649 + 'content_text_color' => $content_text_color,
650 + 'title_color' => $title_color,
651 + ],
652 + ];
544 653 }//end foreach
545 654 }//end if
546 655 wp_reset_postdata();
547 656 wp_send_json_success(
@@ -547,15 +656,17 @@
547 656 wp_send_json_success(
548 657 [
549 658 'message' => __( 'Export successfully', 'yaymail' ),
550 659 'data' => $export_data,
551 - 'file_name' => 'yaymail_customizer_templates_' . gmdate( 'm-d-Y' ),
660 + 'file_name' => $this->export_file_name_prefix() . '_customizer_templates_' . gmdate( 'm-d-Y' ),
552 661 ]
553 662 );
554 663 } catch ( \Error $error ) {
555 664 yaymail_get_logger( $error );
665 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
556 666 } catch ( \Exception $exception ) {
557 667 yaymail_get_logger( $exception );
668 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
558 669 }//end try
559 670 }
560 671
561 672 public function import_templates() {
@@ -575,10 +686,12 @@
575 686 wp_send_json_error( [ 'message' => __( 'Can\'t find import files.', 'yaymail' ) ] );
576 687 }
577 688 } catch ( \Error $error ) {
578 689 yaymail_get_logger( $error );
690 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
579 691 } catch ( \Exception $exception ) {
580 692 yaymail_get_logger( $exception );
693 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
581 694 }
582 695 }
583 696
584 697 public function process_import( $files ) {
@@ -636,13 +749,15 @@
636 749 return $updated_templates;
637 750 }
638 751
639 752 public function processing_import_update_data( $data, $is_legacy = false ) {
640 - $template_name = $data['template'] ?? null;
641 - $elements = $data['elements'] ?? null;
642 - $text_link_color = $data['text_link_color'] ?? null;
643 - $background_color = $data['background_color'] ?? null;
644 - $title_color = $data['title_color'] ?? null;
753 + $template_name = $data['template'] ?? null;
754 + $elements = $data['elements'] ?? null;
755 + $text_link_color = $data['text_link_color'] ?? null;
756 + $background_color = $data['background_color'] ?? null;
757 + $title_color = $data['title_color'] ?? null;
758 + $content_background_color = $data['content_background_color'] ?? null;
759 + $content_text_color = $data['content_text_color'] ?? null;
645 760
646 761 if ( empty( $template_name ) ) {
647 762 return null;
648 763 }
@@ -725,10 +840,12 @@
725 840 ]
726 841 );
727 842 } catch ( \Error $error ) {
728 843 yaymail_get_logger( $error );
844 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
729 845 } catch ( \Exception $exception ) {
730 846 yaymail_get_logger( $exception );
847 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
731 848 }//end try
732 849 }
733 850
734 851 /**
@@ -742,10 +859,11 @@
742 859 try {
743 860 $setting_model = SettingModel::get_instance();
744 861 $settings_data = $setting_model->find_all();
745 862
746 - $template_name = isset( $_POST['data']['template_name'] ) ? sanitize_text_field( $_POST['data']['template_name'] ) : 'new_order';
747 - $order_id = isset( $_POST['data']['order_id'] ) ? sanitize_text_field( $_POST['data']['order_id'] ) : 'sample_order';
863 + $template_name = isset( $_POST['data']['template_name'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['template_name'] ) ) : 'new_order';
864 + $order_id = isset( $_POST['data']['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['order_id'] ) ) : 'sample_order';
865 + $variant = yaymail_sanitize_template_variant( isset( $_POST['data']['variant'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['variant'] ) ) : '' );
748 866
749 867 $template_model = TemplateModel::get_instance();
750 868
751 869 $shortcodes_data = $template_model->get_shortcodes_by_template_name_and_order_id( $template_name, $order_id );
@@ -751,14 +869,14 @@
751 869 $shortcodes_data = $template_model->get_shortcodes_by_template_name_and_order_id( $template_name, $order_id );
752 870
753 871 $templates_data = apply_filters( 'yaymail_get_all_templates', $template_model->find_all() );
754 872
755 - $selected_template_data = $template_model->find_by_name( $template_name );
873 + $selected_template_data = $template_model->find_by_name( $template_name, '', $variant );
756 874
757 875 $elements_data = TemplateModel::get_elements_for_template( $template_name );
758 876
759 877 $revision_model = RevisionModel::get_instance();
760 - $revisions_data = $revision_model->get_by_template( $template_name );
878 + $revisions_data = $revision_model->get_by_template( $template_name, $variant );
761 879
762 880 wp_send_json_success(
763 881 [
764 882 'settings_data' => $settings_data,
@@ -797,10 +915,12 @@
797 915 );
798 916
799 917 } catch ( \Error $error ) {
800 918 yaymail_get_logger( $error );
919 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
801 920 } catch ( \Exception $exception ) {
802 921 yaymail_get_logger( $exception );
922 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
803 923 }
804 924 }
805 925
806 926 public function change_ghf_tour() {
@@ -819,10 +939,12 @@
819 939 ]
820 940 );
821 941 } catch ( \Error $error ) {
822 942 yaymail_get_logger( $error );
943 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
823 944 } catch ( \Exception $exception ) {
824 945 yaymail_get_logger( $exception );
946 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
825 947 }
826 948 }
827 949
828 950 public function dismiss_multi_select_notice() {
@@ -840,10 +962,12 @@
840 962 ]
841 963 );
842 964 } catch ( \Error $error ) {
843 965 yaymail_get_logger( $error );
966 + wp_send_json_error( [ 'mess' => $error->getMessage() ] );
844 967 } catch ( \Exception $exception ) {
845 968 yaymail_get_logger( $exception );
969 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
846 970 }
847 971 }
848 972
849 973 public function dismiss_new_element_notification() {
@@ -868,7 +992,8 @@
868 992 } catch ( \Error $error ) {
869 993 wp_send_json_error( [ 'mess' => $error->getMessage() ] );
870 994 } catch ( \Exception $exception ) {
871 995 yaymail_get_logger( $exception );
996 + wp_send_json_error( [ 'mess' => $exception->getMessage() ] );
872 997 }
873 998 }
874 -}
999 +}