Newsletter - Version 7.5.1

Version Description

  • Fixed PHPMailer loading for WP 5.4.10
  • Updated WP version compatibility
Download this release

Release Info

Developer satollo
Plugin Icon 128x128 Newsletter
Version 7.5.1
Comparing to
See all releases

Code changes from version 7.5.0 to 7.5.1

emails/blocks/hero/block.php CHANGED
@@ -1,112 +1,112 @@
1
- <?php
2
-
3
- /*
4
- * Name: Hero
5
- * Section: content
6
- * Description: Image, title, text and call to action all in one
7
- */
8
-
9
- /* @var $options array */
10
- /* @var $wpdb wpdb */
11
-
12
- $defaults = array(
13
- 'title' => 'An Awesome Title',
14
- 'text' => 'This is just a simple text you should change',
15
- 'font_family' => '',
16
- 'font_size' => '',
17
- 'font_weight' => '',
18
- 'font_color' => '',
19
- 'title_font_family' => '',
20
- 'title_font_size' => '',
21
- 'title_font_weight' => '',
22
- 'title_font_color' => '',
23
- 'layout' => 'full',
24
- 'button_url' => '',
25
- 'button_label' => __('Click Here', 'newsletter'),
26
- 'button_background' => '',
27
- 'button_font_color' => '',
28
- 'button_font_family' => '',
29
- 'button_font_size' => '',
30
- 'button_font_weight' => '',
31
- 'block_padding_top' => 30,
32
- 'block_padding_bottom' => 30,
33
- 'block_padding_left' => 0,
34
- 'block_padding_right' => 0,
35
- 'block_background' => '',
36
- );
37
-
38
- $options = array_merge($defaults, $options);
39
-
40
- if (!empty($options['schema'])) {
41
- if ($options['schema'] === 'dark') {
42
- $options['block_background'] = '#000000';
43
- $options['title_font_color'] = '#ffffff';
44
- $options['font_color'] = '#ffffff';
45
- $options['button_font_color'] = '#ffffff';
46
- $options['button_background'] = '#96969C';
47
- }
48
-
49
- if ($options['schema'] === 'bright') {
50
- $options['block_background'] = '#ffffff';
51
- $options['title_font_color'] = '#000000';
52
- $options['font_color'] = '#000000';
53
- $options['button_font_color'] = '#ffffff';
54
- $options['button_background'] = '#256F9C';
55
- }
56
- }
57
-
58
- $layout = $options['layout'];
59
-
60
- if ($layout == 'full') {
61
- $options = array_merge(array('block_padding_left' => 0, 'block_padding_right' => 0), $options);
62
- } else {
63
- $options = array_merge(array('block_padding_left' => 15, 'block_padding_right' => 15), $options);
64
- }
65
-
66
- $title_style = TNP_Composer::get_title_style($options, 'title', $composer);
67
- $text_style = TNP_Composer::get_text_style($options, '', $composer);
68
-
69
- $layout = $options['layout'];
70
-
71
- $button_options = $options;
72
- $button_options['button_font_family'] = empty($options['button_font_family']) ? $global_button_font_family : $options['button_font_family'];
73
- $button_options['button_font_size'] = empty($options['button_font_size']) ? $global_button_font_size : $options['button_font_size'];
74
- $button_options['button_font_color'] = empty($options['button_font_color']) ? $global_button_font_color : $options['button_font_color'];
75
- $button_options['button_font_weight'] = empty($options['button_font_weight']) ? $global_button_font_weight : $options['button_font_weight'];
76
- $button_options['button_background'] = empty($options['button_background']) ? $global_button_background_color : $options['button_background'];
77
-
78
- if (!empty($options['image']['id'])) {
79
- if ($layout === 'full') {
80
- $image_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
81
- $media = tnp_resize_2x($options['image']['id'], [$image_width, 0]);
82
- } else {
83
- $td_width = round((600 - $options['block_padding_left'] - $options['block_padding_right'] - 20)/2);
84
- //$image_width = 300 - $options['block_padding_left'];
85
- $media = tnp_resize_2x($options['image']['id'], [$td_width, 0]);
86
- }
87
- if ($media) {
88
- if (!empty($options['image_alt'])) {
89
- $media->alt = $options['image_alt'];
90
- } else if (!empty($options['title'])) {
91
- $media->alt = $options['title'];
92
- } else {
93
- $alt_texts = array('picture', 'image', 'pic', 'photo');
94
- $media->alt = $alt_texts[array_rand($alt_texts)];
95
- }
96
- $media->link = $options['button_url'];
97
- }
98
- } else {
99
- $media = false;
100
- }
101
-
102
- switch ($layout) {
103
- case 'left':
104
- include __DIR__ . '/block-left.php';
105
- return;
106
- case 'right':
107
- include __DIR__ . '/block-right.php';
108
- return;
109
- case 'full':
110
- include __DIR__ . '/block-full.php';
111
- return;
112
- }
1
+ <?php
2
+
3
+ /*
4
+ * Name: Hero
5
+ * Section: content
6
+ * Description: Image, title, text and call to action all in one
7
+ */
8
+
9
+ /* @var $options array */
10
+ /* @var $wpdb wpdb */
11
+
12
+ $defaults = array(
13
+ 'title' => 'An Awesome Title',
14
+ 'text' => 'This is just a simple text you should change',
15
+ 'font_family' => '',
16
+ 'font_size' => '',
17
+ 'font_weight' => '',
18
+ 'font_color' => '',
19
+ 'title_font_family' => '',
20
+ 'title_font_size' => '',
21
+ 'title_font_weight' => '',
22
+ 'title_font_color' => '',
23
+ 'layout' => 'full',
24
+ 'button_url' => '',
25
+ 'button_label' => __('Click Here', 'newsletter'),
26
+ 'button_background' => '',
27
+ 'button_font_color' => '',
28
+ 'button_font_family' => '',
29
+ 'button_font_size' => '',
30
+ 'button_font_weight' => '',
31
+ 'block_padding_top' => 30,
32
+ 'block_padding_bottom' => 30,
33
+ 'block_padding_left' => 0,
34
+ 'block_padding_right' => 0,
35
+ 'block_background' => '',
36
+ );
37
+
38
+ $options = array_merge($defaults, $options);
39
+
40
+ if (!empty($options['schema'])) {
41
+ if ($options['schema'] === 'dark') {
42
+ $options['block_background'] = '#000000';
43
+ $options['title_font_color'] = '#ffffff';
44
+ $options['font_color'] = '#ffffff';
45
+ $options['button_font_color'] = '#ffffff';
46
+ $options['button_background'] = '#96969C';
47
+ }
48
+
49
+ if ($options['schema'] === 'bright') {
50
+ $options['block_background'] = '#ffffff';
51
+ $options['title_font_color'] = '#000000';
52
+ $options['font_color'] = '#000000';
53
+ $options['button_font_color'] = '#ffffff';
54
+ $options['button_background'] = '#256F9C';
55
+ }
56
+ }
57
+
58
+ $layout = $options['layout'];
59
+
60
+ if ($layout == 'full') {
61
+ $options = array_merge(array('block_padding_left' => 0, 'block_padding_right' => 0), $options);
62
+ } else {
63
+ $options = array_merge(array('block_padding_left' => 15, 'block_padding_right' => 15), $options);
64
+ }
65
+
66
+ $title_style = TNP_Composer::get_title_style($options, 'title', $composer);
67
+ $text_style = TNP_Composer::get_text_style($options, '', $composer);
68
+
69
+ $layout = $options['layout'];
70
+
71
+ $button_options = $options;
72
+ $button_options['button_font_family'] = empty($options['button_font_family']) ? $global_button_font_family : $options['button_font_family'];
73
+ $button_options['button_font_size'] = empty($options['button_font_size']) ? $global_button_font_size : $options['button_font_size'];
74
+ $button_options['button_font_color'] = empty($options['button_font_color']) ? $global_button_font_color : $options['button_font_color'];
75
+ $button_options['button_font_weight'] = empty($options['button_font_weight']) ? $global_button_font_weight : $options['button_font_weight'];
76
+ $button_options['button_background'] = empty($options['button_background']) ? $global_button_background_color : $options['button_background'];
77
+
78
+ if (!empty($options['image']['id'])) {
79
+ if ($layout === 'full') {
80
+ $image_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
81
+ $media = tnp_resize_2x($options['image']['id'], [$image_width, 0]);
82
+ } else {
83
+ $td_width = round((600 - $options['block_padding_left'] - $options['block_padding_right'] - 20)/2);
84
+ //$image_width = 300 - $options['block_padding_left'];
85
+ $media = tnp_resize_2x($options['image']['id'], [$td_width, 0]);
86
+ }
87
+ if ($media) {
88
+ if (!empty($options['image_alt'])) {
89
+ $media->alt = $options['image_alt'];
90
+ } else if (!empty($options['title'])) {
91
+ $media->alt = $options['title'];
92
+ } else {
93
+ $alt_texts = array('picture', 'image', 'pic', 'photo');
94
+ $media->alt = $alt_texts[array_rand($alt_texts)];
95
+ }
96
+ $media->link = $options['button_url'];
97
+ }
98
+ } else {
99
+ $media = false;
100
+ }
101
+
102
+ switch ($layout) {
103
+ case 'left':
104
+ include __DIR__ . '/block-left.php';
105
+ return;
106
+ case 'right':
107
+ include __DIR__ . '/block-right.php';
108
+ return;
109
+ case 'full':
110
+ include __DIR__ . '/block-full.php';
111
+ return;
112
+ }
emails/blocks/image/block.php CHANGED
@@ -1,82 +1,82 @@
1
- <?php
2
- /*
3
- * Name: Single image
4
- * Section: content
5
- * Description: A single image with link
6
- */
7
-
8
- /* @var $options array */
9
- /* @var $wpdb wpdb */
10
-
11
- $defaults = array(
12
- 'image' => '',
13
- 'image-alt' => '',
14
- 'url' => '',
15
- 'width' => 0,
16
- 'align' => 'center',
17
- 'block_background' => '',
18
- 'block_padding_left' => 0,
19
- 'block_padding_right' => 0,
20
- 'block_padding_bottom' => 15,
21
- 'block_padding_top' => 15
22
- );
23
-
24
- $options = array_merge($defaults, $options);
25
-
26
- if (empty($options['image']['id'])) {
27
- if (!empty($options['image-url'])) {
28
- $media = new TNP_Media();
29
- $media->url = $options['image-url'];
30
- } else {
31
- $media = new TNP_Media();
32
- // A placeholder can be set by a preset and it is kept indefinitely
33
- if (!empty($options['placeholder'])) {
34
- $media->url = $options['placeholder'];
35
- $media->width = 600;
36
- $media->height = 250;
37
- } else {
38
- $media->url = 'https://source.unsplash.com/1200x500/daily';
39
- $media->width = 600;
40
- $media->height = 250;
41
- }
42
- }
43
- } else {
44
- $media = tnp_resize_2x($options['image']['id'], [600, 0]);
45
- // Should never happen but... it happens
46
- if (!$media) {
47
- echo 'The selected media file cannot be processed';
48
- return;
49
- }
50
- }
51
-
52
- if (!empty($options['width'])) {
53
- $media->set_width($options['width']);
54
- }
55
- $media->link = $options['url'];
56
- $media->alt = $options['image-alt'];
57
-
58
- echo '<table width="100%"><tr><td align="', esc_attr($options['align']), '">';
59
-
60
- if ($media->link) {
61
- echo '<a href="', esc_attr($media->link), '" target="_blank" rel="noopener nofollow" style="display: block; font-size: 0; text-decoration: none; line-height: normal!important">';
62
- } else {
63
- }
64
-
65
-
66
- echo '<img src="', esc_attr($media->url), '" width="', esc_attr($media->width), '"';
67
- if ($media->height) {
68
- echo ' height="', esc_attr($media->height), '"';
69
- }
70
- echo ' alt="', esc_attr($media->alt), '"';
71
- // The font size is important for the alt text
72
- echo ' border="0" style="display: block; max-width: ', esc_attr($media->width), 'px !important; width: 100%; padding: 0; border: 0; font-size: 12px"';
73
- echo '>';
74
-
75
- if ($media->link) {
76
- echo '</a>';
77
- } else {
78
- }
79
-
80
- echo '</td></tr></table>';
81
- ?>
82
-
1
+ <?php
2
+ /*
3
+ * Name: Single image
4
+ * Section: content
5
+ * Description: A single image with link
6
+ */
7
+
8
+ /* @var $options array */
9
+ /* @var $wpdb wpdb */
10
+
11
+ $defaults = array(
12
+ 'image' => '',
13
+ 'image-alt' => '',
14
+ 'url' => '',
15
+ 'width' => 0,
16
+ 'align' => 'center',
17
+ 'block_background' => '',
18
+ 'block_padding_left' => 0,
19
+ 'block_padding_right' => 0,
20
+ 'block_padding_bottom' => 15,
21
+ 'block_padding_top' => 15
22
+ );
23
+
24
+ $options = array_merge($defaults, $options);
25
+
26
+ if (empty($options['image']['id'])) {
27
+ if (!empty($options['image-url'])) {
28
+ $media = new TNP_Media();
29
+ $media->url = $options['image-url'];
30
+ } else {
31
+ $media = new TNP_Media();
32
+ // A placeholder can be set by a preset and it is kept indefinitely
33
+ if (!empty($options['placeholder'])) {
34
+ $media->url = $options['placeholder'];
35
+ $media->width = 600;
36
+ $media->height = 250;
37
+ } else {
38
+ $media->url = 'https://source.unsplash.com/1200x500/daily';
39
+ $media->width = 600;
40
+ $media->height = 250;
41
+ }
42
+ }
43
+ } else {
44
+ $media = tnp_resize_2x($options['image']['id'], [600, 0]);
45
+ // Should never happen but... it happens
46
+ if (!$media) {
47
+ echo 'The selected media file cannot be processed';
48
+ return;
49
+ }
50
+ }
51
+
52
+ if (!empty($options['width'])) {
53
+ $media->set_width($options['width']);
54
+ }
55
+ $media->link = $options['url'];
56
+ $media->alt = $options['image-alt'];
57
+
58
+ echo '<table width="100%"><tr><td align="', esc_attr($options['align']), '">';
59
+
60
+ if ($media->link) {
61
+ echo '<a href="', esc_attr($media->link), '" target="_blank" rel="noopener nofollow" style="display: block; font-size: 0; text-decoration: none; line-height: normal!important">';
62
+ } else {
63
+ }
64
+
65
+
66
+ echo '<img src="', esc_attr($media->url), '" width="', esc_attr($media->width), '"';
67
+ if ($media->height) {
68
+ echo ' height="', esc_attr($media->height), '"';
69
+ }
70
+ echo ' alt="', esc_attr($media->alt), '"';
71
+ // The font size is important for the alt text
72
+ echo ' border="0" style="display: block; max-width: ', esc_attr($media->width), 'px !important; width: 100%; padding: 0; border: 0; font-size: 12px"';
73
+ echo '>';
74
+
75
+ if ($media->link) {
76
+ echo '</a>';
77
+ } else {
78
+ }
79
+
80
+ echo '</td></tr></table>';
81
+ ?>
82
+
emails/blocks/posts/layout-big-image.php CHANGED
@@ -1,126 +1,126 @@
1
- <?php
2
- $size = ['width' => 600, 'height' => 0];
3
- $content_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
- $title_style = TNP_Composer::get_title_style($options, 'title', $composer);
5
- $text_style = TNP_Composer::get_style($options, '', $composer, 'text');
6
- ?>
7
- <style>
8
- .title {
9
- <?php echo $title_style->echo_css() ?>
10
- line-height: normal!important;
11
- padding: 0 0 5px 0;
12
- }
13
-
14
- .excerpt {
15
- <?php echo $text_style->echo_css() ?>
16
- line-height: 1.5em!important;
17
- padding: 10px 0 15px 0;
18
- }
19
-
20
- .meta {
21
- <?php echo $text_style->echo_css(0.9) ?>
22
- line-height: normal!important;
23
- padding: 0 0 5px 0;
24
- font-style: italic;
25
- }
26
- </style>
27
-
28
-
29
- <?php foreach ($posts as $post) { ?>
30
-
31
- <?php
32
- $url = tnp_post_permalink($post);
33
- $button_options['button_url'] = $url;
34
-
35
- $media = null;
36
- if ($show_image) {
37
- $media = tnp_composer_block_posts_get_media($post, $size);
38
-
39
- if ($media) {
40
- $media->set_width($content_width);
41
- $media->link = $url;
42
- }
43
- }
44
-
45
-
46
- $meta = [];
47
-
48
- if ($show_date) {
49
- $meta[] = tnp_post_date($post);
50
- }
51
-
52
- if ($show_author) {
53
- $author_object = get_user_by('id', $post->post_author);
54
- if ($author_object) {
55
- $meta[] = $author_object->display_name;
56
- }
57
- }
58
- ?>
59
-
60
- <?php if ($media) { ?>
61
- <table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 20px">
62
- <tr>
63
- <td align="center">
64
- <?php echo TNP_Composer::image($media) ?>
65
- </td>
66
- </tr>
67
- </table>
68
- <?php } ?>
69
-
70
- <table width="100%" cellpadding="0" cellspacing="0" border="0" class="responsive" style="margin: 0;">
71
- <tr>
72
- <td>
73
-
74
- <table border="0" cellspacing="0" cellpadding="0" width="100%">
75
-
76
-
77
- <tr>
78
- <td align="<?php echo $align_left ?>" inline-class="title" class="tnpc-row-edit tnpc-inline-editable"
79
- data-type="title" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
80
- <?php
81
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $post->ID) ?
82
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $post->ID) :
83
- tnp_post_title($post)
84
- ?>
85
- </td>
86
- </tr>
87
-
88
- <?php if ($meta) { ?>
89
- <tr>
90
- <td align="<?php echo $align_left ?>" inline-class="meta">
91
- <?php echo esc_html(implode(' - ', $meta)) ?>
92
- </td>
93
- </tr>
94
- <?php } ?>
95
-
96
- <?php if ($excerpt_length) { ?>
97
- <tr>
98
- <td align="<?php echo $align_left ?>" inline-class="excerpt" class="tnpc-row-edit tnpc-inline-editable" data-type="text" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
99
- <?php
100
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $post->ID) ?
101
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $post->ID) :
102
- tnp_post_excerpt($post, $excerpt_length)
103
- ?>
104
- </td>
105
- </tr>
106
- <?php } ?>
107
-
108
- <?php if ($show_read_more_button) { ?>
109
- <tr>
110
- <td align="<?php echo $align_left ?>" inline-class="button">
111
- <?php echo TNP_Composer::button($button_options) ?>
112
- </td>
113
- </tr>
114
- <?php } ?>
115
- <tr>
116
- <td style="padding: 10px">&nbsp;</td>
117
- </tr>
118
- </table>
119
-
120
- </td>
121
- </tr>
122
- </table>
123
-
124
- <?php } ?>
125
-
126
-
1
+ <?php
2
+ $size = ['width' => 600, 'height' => 0];
3
+ $content_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
+ $title_style = TNP_Composer::get_title_style($options, 'title', $composer);
5
+ $text_style = TNP_Composer::get_style($options, '', $composer, 'text');
6
+ ?>
7
+ <style>
8
+ .title {
9
+ <?php echo $title_style->echo_css() ?>
10
+ line-height: normal!important;
11
+ padding: 0 0 5px 0;
12
+ }
13
+
14
+ .excerpt {
15
+ <?php echo $text_style->echo_css() ?>
16
+ line-height: 1.5em!important;
17
+ padding: 10px 0 15px 0;
18
+ }
19
+
20
+ .meta {
21
+ <?php echo $text_style->echo_css(0.9) ?>
22
+ line-height: normal!important;
23
+ padding: 0 0 5px 0;
24
+ font-style: italic;
25
+ }
26
+ </style>
27
+
28
+
29
+ <?php foreach ($posts as $post) { ?>
30
+
31
+ <?php
32
+ $url = tnp_post_permalink($post);
33
+ $button_options['button_url'] = $url;
34
+
35
+ $media = null;
36
+ if ($show_image) {
37
+ $media = tnp_composer_block_posts_get_media($post, $size);
38
+
39
+ if ($media) {
40
+ $media->set_width($content_width);
41
+ $media->link = $url;
42
+ }
43
+ }
44
+
45
+
46
+ $meta = [];
47
+
48
+ if ($show_date) {
49
+ $meta[] = tnp_post_date($post);
50
+ }
51
+
52
+ if ($show_author) {
53
+ $author_object = get_user_by('id', $post->post_author);
54
+ if ($author_object) {
55
+ $meta[] = $author_object->display_name;
56
+ }
57
+ }
58
+ ?>
59
+
60
+ <?php if ($media) { ?>
61
+ <table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 20px">
62
+ <tr>
63
+ <td align="center">
64
+ <?php echo TNP_Composer::image($media) ?>
65
+ </td>
66
+ </tr>
67
+ </table>
68
+ <?php } ?>
69
+
70
+ <table width="100%" cellpadding="0" cellspacing="0" border="0" class="responsive" style="margin: 0;">
71
+ <tr>
72
+ <td>
73
+
74
+ <table border="0" cellspacing="0" cellpadding="0" width="100%">
75
+
76
+
77
+ <tr>
78
+ <td align="<?php echo $align_left ?>" inline-class="title" class="tnpc-row-edit tnpc-inline-editable"
79
+ data-type="title" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
80
+ <?php
81
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $post->ID) ?
82
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $post->ID) :
83
+ tnp_post_title($post)
84
+ ?>
85
+ </td>
86
+ </tr>
87
+
88
+ <?php if ($meta) { ?>
89
+ <tr>
90
+ <td align="<?php echo $align_left ?>" inline-class="meta">
91
+ <?php echo esc_html(implode(' - ', $meta)) ?>
92
+ </td>
93
+ </tr>
94
+ <?php } ?>
95
+
96
+ <?php if ($excerpt_length) { ?>
97
+ <tr>
98
+ <td align="<?php echo $align_left ?>" inline-class="excerpt" class="tnpc-row-edit tnpc-inline-editable" data-type="text" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
99
+ <?php
100
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $post->ID) ?
101
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $post->ID) :
102
+ tnp_post_excerpt($post, $excerpt_length)
103
+ ?>
104
+ </td>
105
+ </tr>
106
+ <?php } ?>
107
+
108
+ <?php if ($show_read_more_button) { ?>
109
+ <tr>
110
+ <td align="<?php echo $align_left ?>" inline-class="button">
111
+ <?php echo TNP_Composer::button($button_options) ?>
112
+ </td>
113
+ </tr>
114
+ <?php } ?>
115
+ <tr>
116
+ <td style="padding: 10px">&nbsp;</td>
117
+ </tr>
118
+ </table>
119
+
120
+ </td>
121
+ </tr>
122
+ </table>
123
+
124
+ <?php } ?>
125
+
126
+
emails/blocks/posts/layout-full-post.php CHANGED
@@ -1,106 +1,106 @@
1
- <?php
2
- $size = ['width' => 600, 'height' => 0];
3
- $content_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
- $title_style = TNP_Composer::get_title_style($options, 'title', $composer);
5
- $text_style = TNP_Composer::get_text_style($options, '', $composer);
6
- ?>
7
-
8
- <style>
9
- .title {
10
- <?php $title_style->echo_css() ?>
11
- line-height: normal;
12
- margin: 0;
13
- padding-bottom: 20px;
14
- }
15
-
16
- .paragraph {
17
- <?php $text_style->echo_css() ?>
18
- line-height: 1.5em!important;
19
- text-align: left;
20
- }
21
-
22
- .meta {
23
- <?php $text_style->echo_css(0.9) ?>
24
- line-height: normal!important;
25
- padding-bottom: 10px;
26
- text-align: center;
27
- font-style: italic;
28
- }
29
-
30
- .button {
31
- padding: 15px 0;
32
- }
33
-
34
- </style>
35
-
36
- <?php foreach ($posts as $post) { ?>
37
-
38
- <?php
39
- $url = tnp_post_permalink($post);
40
-
41
- $media = null;
42
- if ($show_image) {
43
- $media = tnp_composer_block_posts_get_media($post, $size);
44
- if ($media) {
45
- $media->set_width($content_width);
46
- $media->link = $url;
47
- }
48
- }
49
-
50
- $meta = [];
51
-
52
- if ($show_date) {
53
- $meta[] = tnp_post_date($post);
54
- }
55
-
56
- if ($show_author) {
57
- $author_object = get_user_by('id', $post->post_author);
58
- if ($author_object) {
59
- $meta[] = $author_object->display_name;
60
- }
61
- }
62
-
63
- $button_options['button_url'] = $url;
64
- ?>
65
-
66
-
67
- <table border="0" cellpadding="0" align="center" cellspacing="0" width="100%" class="responsive">
68
- <tr>
69
- <td inline-class="title">
70
- <?php echo $post->post_title ?>
71
- </td>
72
- </tr>
73
-
74
- <?php if ($meta) { ?>
75
- <tr>
76
- <td inline-class="meta">
77
- <?php echo esc_html(implode(' - ', $meta)) ?>
78
- </td>
79
- </tr>
80
- <?php } ?>
81
-
82
- <?php if ($media) { ?>
83
- <tr>
84
- <td align="center">
85
- <?php echo TNP_Composer::image($media) ?>
86
- </td>
87
- </tr>
88
- <?php } ?>
89
-
90
- <tr>
91
- <td>
92
- <?php echo TNP_Composer::post_content($post) ?>
93
- </td>
94
- </tr>
95
-
96
- <?php if ($show_read_more_button) { ?>
97
- <tr>
98
- <td align="center" inline-class="button">
99
- <?php echo TNP_Composer::button($button_options) ?>
100
- </td>
101
- </tr>
102
- <?php } ?>
103
- </table>
104
- <br><br>
105
-
106
- <?php } ?>
1
+ <?php
2
+ $size = ['width' => 600, 'height' => 0];
3
+ $content_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
+ $title_style = TNP_Composer::get_title_style($options, 'title', $composer);
5
+ $text_style = TNP_Composer::get_text_style($options, '', $composer);
6
+ ?>
7
+
8
+ <style>
9
+ .title {
10
+ <?php $title_style->echo_css() ?>
11
+ line-height: normal;
12
+ margin: 0;
13
+ padding-bottom: 20px;
14
+ }
15
+
16
+ .paragraph {
17
+ <?php $text_style->echo_css() ?>
18
+ line-height: 1.5em!important;
19
+ text-align: left;
20
+ }
21
+
22
+ .meta {
23
+ <?php $text_style->echo_css(0.9) ?>
24
+ line-height: normal!important;
25
+ padding-bottom: 10px;
26
+ text-align: center;
27
+ font-style: italic;
28
+ }
29
+
30
+ .button {
31
+ padding: 15px 0;
32
+ }
33
+
34
+ </style>
35
+
36
+ <?php foreach ($posts as $post) { ?>
37
+
38
+ <?php
39
+ $url = tnp_post_permalink($post);
40
+
41
+ $media = null;
42
+ if ($show_image) {
43
+ $media = tnp_composer_block_posts_get_media($post, $size);
44
+ if ($media) {
45
+ $media->set_width($content_width);
46
+ $media->link = $url;
47
+ }
48
+ }
49
+
50
+ $meta = [];
51
+
52
+ if ($show_date) {
53
+ $meta[] = tnp_post_date($post);
54
+ }
55
+
56
+ if ($show_author) {
57
+ $author_object = get_user_by('id', $post->post_author);
58
+ if ($author_object) {
59
+ $meta[] = $author_object->display_name;
60
+ }
61
+ }
62
+
63
+ $button_options['button_url'] = $url;
64
+ ?>
65
+
66
+
67
+ <table border="0" cellpadding="0" align="center" cellspacing="0" width="100%" class="responsive">
68
+ <tr>
69
+ <td inline-class="title">
70
+ <?php echo $post->post_title ?>
71
+ </td>
72
+ </tr>
73
+
74
+ <?php if ($meta) { ?>
75
+ <tr>
76
+ <td inline-class="meta">
77
+ <?php echo esc_html(implode(' - ', $meta)) ?>
78
+ </td>
79
+ </tr>
80
+ <?php } ?>
81
+
82
+ <?php if ($media) { ?>
83
+ <tr>
84
+ <td align="center">
85
+ <?php echo TNP_Composer::image($media) ?>
86
+ </td>
87
+ </tr>
88
+ <?php } ?>
89
+
90
+ <tr>
91
+ <td>
92
+ <?php echo TNP_Composer::post_content($post) ?>
93
+ </td>
94
+ </tr>
95
+
96
+ <?php if ($show_read_more_button) { ?>
97
+ <tr>
98
+ <td align="center" inline-class="button">
99
+ <?php echo TNP_Composer::button($button_options) ?>
100
+ </td>
101
+ </tr>
102
+ <?php } ?>
103
+ </table>
104
+ <br><br>
105
+
106
+ <?php } ?>
emails/blocks/posts/layout-one-2.php CHANGED
@@ -1,138 +1,138 @@
1
- <?php
2
- $size = ['width' => 600, 'height' => 0];
3
- $total_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
- $column_width = $total_width / 2 - 10;
5
-
6
- $title_style = TNP_Composer::get_style($options, 'title', $composer, 'title');
7
- $text_style = TNP_Composer::get_style($options, '', $composer, 'text');
8
- ?>
9
- <style>
10
- .title {
11
- <?php $title_style->echo_css() ?>
12
- line-height: normal;
13
- padding: 0 0 10px 0;
14
- }
15
-
16
- .excerpt {
17
- <?php $text_style->echo_css() ?>
18
- line-height: 1.5em;
19
- padding: 0 0 15px 0;
20
- text-decoration: none;
21
- }
22
-
23
- .meta {
24
- font-family: <?php echo $text_style->font_family ?>;
25
- color: <?php echo $text_style->font_color ?>;
26
- font-size: <?php echo round($text_style->font_size * 0.9) ?>px;
27
- font-weight: <?php echo $text_style->font_weight ?>;
28
- padding: 0 0 5px 0;
29
- line-height: normal !important;
30
- font-style: italic;
31
- }
32
- .button {
33
- padding: 15px 0;
34
- }
35
- </style>
36
-
37
-
38
- <table border="0" cellpadding="0" cellspacing="0" width="100%" class="responsive">
39
-
40
- <?php foreach ($posts as $post) { ?>
41
- <?php
42
- $url = tnp_post_permalink($post);
43
-
44
- $media = null;
45
- if ($show_image) {
46
- $media = tnp_composer_block_posts_get_media($post, $size);
47
- if ($media) {
48
- $media->link = $url;
49
- $media->set_width($column_width);
50
- }
51
- }
52
-
53
- $meta = [];
54
-
55
- if ($show_date) {
56
- $meta[] = tnp_post_date($post);
57
- }
58
-
59
- if ($show_author) {
60
- $author_object = get_user_by('id', $post->post_author);
61
- if ($author_object) {
62
- $meta[] = $author_object->display_name;
63
- }
64
- }
65
-
66
- $button_options['button_url'] = $url;
67
- $button_options['button_align'] = 'left';
68
- ?>
69
-
70
- <tr>
71
- <td align="<?php echo $align_left ?>" inline-class="title" class="tnpc-row-edit tnpc-inline-editable" data-type="title" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
72
- <?php
73
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $post->ID) ?
74
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $post->ID) :
75
- tnp_post_title($post)
76
- ?>
77
- </td>
78
- </tr>
79
-
80
- <tr>
81
-
82
- <td valign="top" style="padding: 20px 0 25px 0;">
83
-
84
- <?php if ($media) { ?>
85
- <table width="<?php echo $column_width ?>" cellpadding="0" cellspacing="0" border="0" align="left" style="margin: 0;" class="responsive">
86
- <tr>
87
- <td class="pb-1">
88
- <?php echo TNP_Composer::image($media, ['class' => 'fluid']) ?>
89
- </td>
90
- </tr>
91
- </table>
92
- <?php } ?>
93
-
94
- <table width="<?php echo $media ? $column_width : '100%' ?>" cellpadding="0" cellspacing="0" border="0" style="margin: 0;" class="responsive" align="right">
95
- <tr>
96
- <td>
97
- <table border="0" cellspacing="0" cellpadding="0" width="100%" style="margin: 0;">
98
- <?php if ($meta) { ?>
99
- <tr>
100
- <td inline-class="meta" dir="<?php echo $dir ?>" align="<?php echo $align_left ?>">
101
- <?php echo esc_html(implode(' - ', $meta)) ?>
102
- </td>
103
- </tr>
104
- <?php } ?>
105
-
106
- <?php if ($excerpt_length) { ?>
107
- <tr>
108
- <td align="<?php echo $align_left ?>" dir="<?php echo $dir ?>">
109
- <a href="<?php $url ?>" data-id="<?php echo $post->ID ?>" inline-class="excerpt" class="tnpc-row-edit tnpc-inline-editable" data-type="text">
110
- <?php
111
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $post->ID) ?
112
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $post->ID) :
113
- tnp_post_excerpt($post, $excerpt_length)
114
- ?>
115
- </a>
116
- </td>
117
- </tr>
118
- <?php } ?>
119
-
120
- <?php if ($show_read_more_button) { ?>
121
- <tr>
122
- <td align="<?php echo $align_left ?>" inline-class="button">
123
- <?php echo TNP_Composer::button($button_options) ?>
124
- </td>
125
- </tr>
126
- <?php } ?>
127
- </table>
128
-
129
- </td>
130
- </tr>
131
- </table>
132
-
133
- </td>
134
- </tr>
135
-
136
- <?php } ?>
137
-
138
- </table>
1
+ <?php
2
+ $size = ['width' => 600, 'height' => 0];
3
+ $total_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
+ $column_width = $total_width / 2 - 10;
5
+
6
+ $title_style = TNP_Composer::get_style($options, 'title', $composer, 'title');
7
+ $text_style = TNP_Composer::get_style($options, '', $composer, 'text');
8
+ ?>
9
+ <style>
10
+ .title {
11
+ <?php $title_style->echo_css() ?>
12
+ line-height: normal;
13
+ padding: 0 0 10px 0;
14
+ }
15
+
16
+ .excerpt {
17
+ <?php $text_style->echo_css() ?>
18
+ line-height: 1.5em;
19
+ padding: 0 0 15px 0;
20
+ text-decoration: none;
21
+ }
22
+
23
+ .meta {
24
+ font-family: <?php echo $text_style->font_family ?>;
25
+ color: <?php echo $text_style->font_color ?>;
26
+ font-size: <?php echo round($text_style->font_size * 0.9) ?>px;
27
+ font-weight: <?php echo $text_style->font_weight ?>;
28
+ padding: 0 0 5px 0;
29
+ line-height: normal !important;
30
+ font-style: italic;
31
+ }
32
+ .button {
33
+ padding: 15px 0;
34
+ }
35
+ </style>
36
+
37
+
38
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" class="responsive">
39
+
40
+ <?php foreach ($posts as $post) { ?>
41
+ <?php
42
+ $url = tnp_post_permalink($post);
43
+
44
+ $media = null;
45
+ if ($show_image) {
46
+ $media = tnp_composer_block_posts_get_media($post, $size);
47
+ if ($media) {
48
+ $media->link = $url;
49
+ $media->set_width($column_width);
50
+ }
51
+ }
52
+
53
+ $meta = [];
54
+
55
+ if ($show_date) {
56
+ $meta[] = tnp_post_date($post);
57
+ }
58
+
59
+ if ($show_author) {
60
+ $author_object = get_user_by('id', $post->post_author);
61
+ if ($author_object) {
62
+ $meta[] = $author_object->display_name;
63
+ }
64
+ }
65
+
66
+ $button_options['button_url'] = $url;
67
+ $button_options['button_align'] = 'left';
68
+ ?>
69
+
70
+ <tr>
71
+ <td align="<?php echo $align_left ?>" inline-class="title" class="tnpc-row-edit tnpc-inline-editable" data-type="title" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
72
+ <?php
73
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $post->ID) ?
74
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $post->ID) :
75
+ tnp_post_title($post)
76
+ ?>
77
+ </td>
78
+ </tr>
79
+
80
+ <tr>
81
+
82
+ <td valign="top" style="padding: 20px 0 25px 0;">
83
+
84
+ <?php if ($media) { ?>
85
+ <table width="<?php echo $column_width ?>" cellpadding="0" cellspacing="0" border="0" align="left" style="margin: 0;" class="responsive">
86
+ <tr>
87
+ <td class="pb-1">
88
+ <?php echo TNP_Composer::image($media, ['class' => 'fluid']) ?>
89
+ </td>
90
+ </tr>
91
+ </table>
92
+ <?php } ?>
93
+
94
+ <table width="<?php echo $media ? $column_width : '100%' ?>" cellpadding="0" cellspacing="0" border="0" style="margin: 0;" class="responsive" align="right">
95
+ <tr>
96
+ <td>
97
+ <table border="0" cellspacing="0" cellpadding="0" width="100%" style="margin: 0;">
98
+ <?php if ($meta) { ?>
99
+ <tr>
100
+ <td inline-class="meta" dir="<?php echo $dir ?>" align="<?php echo $align_left ?>">
101
+ <?php echo esc_html(implode(' - ', $meta)) ?>
102
+ </td>
103
+ </tr>
104
+ <?php } ?>
105
+
106
+ <?php if ($excerpt_length) { ?>
107
+ <tr>
108
+ <td align="<?php echo $align_left ?>" dir="<?php echo $dir ?>">
109
+ <a href="<?php $url ?>" data-id="<?php echo $post->ID ?>" inline-class="excerpt" class="tnpc-row-edit tnpc-inline-editable" data-type="text">
110
+ <?php
111
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $post->ID) ?
112
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $post->ID) :
113
+ tnp_post_excerpt($post, $excerpt_length)
114
+ ?>
115
+ </a>
116
+ </td>
117
+ </tr>
118
+ <?php } ?>
119
+
120
+ <?php if ($show_read_more_button) { ?>
121
+ <tr>
122
+ <td align="<?php echo $align_left ?>" inline-class="button">
123
+ <?php echo TNP_Composer::button($button_options) ?>
124
+ </td>
125
+ </tr>
126
+ <?php } ?>
127
+ </table>
128
+
129
+ </td>
130
+ </tr>
131
+ </table>
132
+
133
+ </td>
134
+ </tr>
135
+
136
+ <?php } ?>
137
+
138
+ </table>
emails/emails.php CHANGED
@@ -1,1356 +1,1356 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- class NewsletterEmails extends NewsletterModule {
6
-
7
- static $instance;
8
-
9
- const EDITOR_COMPOSER = 2;
10
- const EDITOR_HTML = 1;
11
- const EDITOR_TINYMCE = 0;
12
-
13
- static $PRESETS_LIST;
14
-
15
- const PRESET_EMAIL_TYPE = 'composer_template';
16
-
17
- // Cache
18
- var $blocks = null;
19
-
20
- /**
21
- * @return NewsletterEmails
22
- */
23
- static function instance() {
24
- if (self::$instance == null) {
25
- self::$instance = new NewsletterEmails();
26
- }
27
-
28
- return self::$instance;
29
- }
30
-
31
- function __construct() {
32
- self::$PRESETS_LIST = array("cta", "invite", "announcement", "posts", "sales", "product", "tour", "simple");
33
- $this->themes = new NewsletterThemes('emails');
34
- parent::__construct('emails', '1.1.5');
35
- add_action('newsletter_action', array($this, 'hook_newsletter_action'), 13, 3);
36
- add_action('newsletter_init', [$this, 'hook_newsletter_init']);
37
-
38
- if (is_admin()) {
39
- // Thank you to plugins which add the WP editor on other admin plugin pages...
40
- if (isset($_GET['page']) && $_GET['page'] == 'newsletter_emails_edit') {
41
- global $wp_actions;
42
- $wp_actions['wp_enqueue_editor'] = 1;
43
- }
44
- }
45
- }
46
-
47
- function hook_newsletter_init() {
48
- if (is_admin()) {
49
- if (defined('DOING_AJAX') && DOING_AJAX) {
50
- if (Newsletter::instance()->is_allowed()) {
51
- add_action('wp_ajax_tnpc_render', array($this, 'tnpc_render_callback'));
52
- add_action('wp_ajax_tnpc_preview', array($this, 'tnpc_preview_callback'));
53
- add_action('wp_ajax_tnpc_css', array($this, 'tnpc_css_callback'));
54
- add_action('wp_ajax_tnpc_options', array($this, 'hook_wp_ajax_tnpc_options'));
55
- add_action('wp_ajax_tnpc_get_all_presets', array($this, 'ajax_get_all_presets'));
56
- add_action('wp_ajax_tnpc_get_preset', array($this, 'ajax_get_preset'));
57
- add_action('wp_ajax_tnpc_delete_preset', array($this, 'hook_wp_ajax_tnpc_delete_preset'));
58
- add_action('wp_ajax_tnpc_regenerate_email', array($this, 'hook_wp_ajax_tnpc_regenerate_email'));
59
- }
60
- }
61
- }
62
- }
63
-
64
- function options_decode($options) {
65
- // Old "query string" format
66
- if (is_string($options) && strpos($options, 'options[') !== false) {
67
- $opts = [];
68
- parse_str($options, $opts);
69
- $options = $opts['options'];
70
- }
71
-
72
- if (is_array($options)) {
73
- return $options;
74
- }
75
-
76
- // Json data should be base64 encoded, but for short time it wasn't
77
- $tmp = json_decode($options, true);
78
- if (is_null($tmp)) {
79
- return json_decode(base64_decode($options), true);
80
- } else {
81
- return $tmp;
82
- }
83
- }
84
-
85
- /**
86
- *
87
- * @param array $options Options array
88
- */
89
- function options_encode($options) {
90
- return base64_encode(json_encode($options, JSON_HEX_TAG | JSON_HEX_AMP));
91
- }
92
-
93
- /**
94
- * Builds and returns the HTML with the form fields of a specific block.
95
- *
96
- * @global wpdb $wpdb
97
- */
98
- function hook_wp_ajax_tnpc_options() {
99
- global $wpdb;
100
-
101
- $block = $this->get_block($_REQUEST['id']);
102
- if (!$block) {
103
- die('Block not found with id ' . esc_html($_REQUEST['id']));
104
- }
105
-
106
- if (!class_exists('NewsletterControls')) {
107
- include NEWSLETTER_INCLUDES_DIR . '/controls.php';
108
- }
109
-
110
- $options = $this->options_decode(stripslashes_deep($_REQUEST['options']));
111
- $composer = isset($_POST['composer']) ? $_POST['composer'] : [];
112
-
113
- $context = array('type' => '');
114
- if (isset($_REQUEST['context_type'])) {
115
- $context['type'] = $_REQUEST['context_type'];
116
- }
117
-
118
- $controls = new NewsletterControls($options);
119
- $fields = new NewsletterFields($controls);
120
-
121
- $controls->init();
122
- echo '<input type="hidden" name="action" value="tnpc_render">';
123
- echo '<input type="hidden" name="id" value="' . esc_attr($_REQUEST['id']) . '">';
124
- echo '<input type="hidden" name="context_type" value="' . esc_attr($context['type']) . '">';
125
- $inline_edits = '';
126
- if (isset($controls->data['inline_edits'])) {
127
- $inline_edits = $controls->data['inline_edits'];
128
- }
129
- echo '<input type="hidden" name="options[inline_edits]" value="', esc_attr($this->options_encode($inline_edits)), '">';
130
- echo "<h2>", esc_html($block["name"]), "</h2>";
131
- include $block['dir'] . '/options.php';
132
- wp_die();
133
- }
134
-
135
- /**
136
- * Retrieves the presets list (no id in GET) or a specific preset id in GET)
137
- */
138
- public function ajax_get_all_presets() {
139
- wp_send_json_success($this->get_all_preset());
140
- }
141
-
142
- public function ajax_get_preset() {
143
-
144
- if (empty($_REQUEST['id'])) {
145
- wp_send_json_error([
146
- 'msg' => __('Invalid preset ID')
147
- ]);
148
- }
149
-
150
- $preset_id = $_REQUEST['id'];
151
- $preset_content = $this->get_preset_content($preset_id);
152
- $global_options = $this->get_preset_global_options($preset_id);
153
-
154
- wp_send_json_success([
155
- 'content' => $preset_content,
156
- 'globalOptions' => $global_options,
157
- ]);
158
- }
159
-
160
- private function get_preset_content($preset_id) {
161
-
162
- $content = '';
163
-
164
- if ($this->is_a_tnp_default_preset($preset_id)) {
165
-
166
- // Get preset from file
167
- $preset = $this->get_preset_from_file($preset_id);
168
-
169
- foreach ($preset->blocks as $item) {
170
- ob_start();
171
- $this->render_block($item->block, true, (array) $item->options);
172
- $content .= trim(ob_get_clean());
173
- }
174
- } else {
175
-
176
- // Get preset from db
177
- $preset_email = $this->get_email(intval($preset_id));
178
- $global_options = $this->extract_global_options_from($preset_email);
179
- $content = $this->regenerate_email_blocks($preset_email->message, $global_options);
180
- }
181
-
182
- return $content;
183
- }
184
-
185
- private function get_preset_global_options($preset_id) {
186
-
187
- if ($this->is_a_tnp_default_preset($preset_id)) {
188
- return [];
189
- }
190
-
191
- // Get preset from db
192
- $preset_email = $this->get_email(intval($preset_id));
193
- $global_options = $this->extract_global_options_from($preset_email);
194
-
195
- return $global_options;
196
- }
197
-
198
- private function extract_global_options_from($email) {
199
- $global_options = [];
200
- foreach ($email->options as $global_option_name => $global_option) {
201
- if (strpos($global_option_name, 'composer_') === 0) {
202
- $global_options[str_replace('composer_', '', $global_option_name)] = $global_option;
203
- }
204
- }
205
-
206
- return $global_options;
207
- }
208
-
209
- private function is_a_tnp_default_preset($preset_id) {
210
- return in_array($preset_id, self::$PRESETS_LIST);
211
- }
212
-
213
- private function get_all_preset() {
214
-
215
- $content = "<div class='tnpc-preset-container'>";
216
-
217
- if ($this->is_normal_context_request()) {
218
- $content .= "<div class='tnpc-preset-legacy-themes'><a href='" . $this->get_admin_page_url('theme') . "'>" . __('Looking for legacy themes?', 'newsletter') . "</a></div>";
219
- }
220
-
221
- // LOAD USER PRESETS
222
- $user_preset_list = $this->get_emails(self::PRESET_EMAIL_TYPE);
223
-
224
- foreach ($user_preset_list as $user_preset) {
225
-
226
- $default_icon_url = NEWSLETTER_URL . "/emails/presets/default-icon.png?ver=2";
227
- $preset_name = $user_preset->subject;
228
- $delete_preset_text = __('Delete', 'newsletter');
229
- $edit_preset_text = __('Edit', 'newsletter');
230
-
231
- // esc_js() assumes the string will be in single quote (arghhh!!!)
232
- $onclick_edit = 'tnpc_edit_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
233
- $onclick_delete = 'tnpc_delete_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
234
- $onclick_load = 'tnpc_load_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
235
-
236
- $content .= "<div class='tnpc-preset' onclick='" . esc_attr($onclick_load) . "'>\n";
237
- $content .= "<img src='$default_icon_url' title='" . esc_attr($preset_name) . "' alt='" . esc_attr($preset_name) . "'>\n";
238
- $content .= "<span class='tnpc-preset-label'>" . esc_html($user_preset->subject) . "</span>\n";
239
- $content .= "<span class='tnpc-delete-preset' onclick='" . esc_attr($onclick_delete) . "' title='" . esc_attr($delete_preset_text) . "'><i class='fas fa-times'></i></span>\n";
240
- $content .= "<span class='tnpc-edit-preset' onclick='" . esc_attr($onclick_edit) . "' title='" . esc_attr($edit_preset_text) . "'><i class='fas fa-pencil-alt'></i></span>\n";
241
- $content .= "</div>";
242
- }
243
-
244
- // LOAD TNP PRESETS
245
- foreach (self::$PRESETS_LIST as $id) {
246
- $preset = $this->get_preset_from_file($id);
247
- $preset_name = esc_html($preset->name);
248
- $content .= "<div class='tnpc-preset' onclick='tnpc_load_preset(\"$id\")'>";
249
- $content .= "<img src='$preset->icon' title='$preset_name' alt='$preset_name'/>";
250
- $content .= "<span class='tnpc-preset-label'>$preset_name</span>";
251
- $content .= "</div>";
252
- }
253
-
254
- if ($this->is_normal_context_request()) {
255
- $content .= $this->get_automated_spot_element();
256
- $content .= $this->get_autoresponder_spot_element();
257
- $content .= $this->get_raw_html_preset_element();
258
- }
259
-
260
- return $content;
261
- }
262
-
263
- private function is_normal_context_request() {
264
- return empty($_REQUEST['context_type']);
265
- }
266
-
267
- private function is_automated_context_request() {
268
- return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'automated';
269
- }
270
-
271
- private function is_autoresponder_context_request() {
272
- return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'autoresponder';
273
- }
274
-
275
- private function get_automated_spot_element() {
276
- $result = "<div class='tnpc-preset'>";
277
- if (class_exists('NewsletterAutomated')) {
278
- $result .= "<a href='?page=newsletter_automated_index'>";
279
- } else {
280
- $result .= "<a href='https://www.thenewsletterplugin.com/automated?utm_source=composer&utm_campaign=plugin&utm_medium=automated'>";
281
- }
282
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/automated.png' title='Automated addon' alt='Automated'/>";
283
- $result .= "<span class='tnpc-preset-label'>Daily, weekly and monthly newsletters</span></a>";
284
- $result .= "</div>";
285
-
286
- return $result;
287
- }
288
-
289
- private function get_autoresponder_spot_element() {
290
- $result = "<div class='tnpc-preset'>";
291
- if (class_exists('NewsletterAutoresponder')) {
292
- $result .= "<a href='?page=newsletter_autoresponder_index'>";
293
- } else {
294
- $result .= "<a href='https://www.thenewsletterplugin.com/autoresponder?utm_source=composer&utm_campaign=plugin&utm_medium=autoresponder' target='_blank'>";
295
- }
296
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/autoresponder.png' title='Autoresponder addon' alt='Autoresponder'/>";
297
- $result .= "<span class='tnpc-preset-label'>Autoresponders</span></a>";
298
- $result .= "</div>";
299
-
300
- return $result;
301
- }
302
-
303
- private function get_raw_html_preset_element() {
304
-
305
- $result = "<div class='tnpc-preset tnpc-preset-html' onclick='location.href=\"" . wp_nonce_url('admin.php?page=newsletter_emails_new&id=rawhtml', 'newsletter-new') . "\"'>";
306
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/rawhtml.png' title='RAW HTML' alt='RAW'/>";
307
- $result .= "<span class='tnpc-preset-label'>Raw HTML</span>";
308
- $result .= "</div>";
309
-
310
- $result .= "<div class='clear'></div>";
311
- $result .= "</div>";
312
-
313
- return $result;
314
- }
315
-
316
- function has_dynamic_blocks($theme) {
317
- preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
318
- foreach ($matches[1] as $match) {
319
- $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
320
- $options = $this->options_decode($a);
321
-
322
- $block = $this->get_block($options['block_id']);
323
- if (!$block) {
324
- continue;
325
- }
326
- if ($block['type'] == 'dynamic') {
327
- return true;
328
- }
329
- }
330
- return false;
331
- }
332
-
333
- /**
334
- * Regenerates a saved composed email rendering each block. Regeneration is
335
- * conditioned (possibly) by the context. The context is usually passed to blocks
336
- * so they can act in the right manner.
337
- *
338
- * $context contains a type and, for automated, the last_run.
339
- *
340
- * $email can actually be even a string containing the full newsletter HTML code.
341
- *
342
- * @param TNP_Email $email
343
- * @return string
344
- */
345
- function regenerate($email, $context = []) {
346
-
347
- $context = array_merge(['last_run' => 0, 'type' => ''], $context);
348
-
349
- preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
350
-
351
- $result = '';
352
- $subject = '';
353
-
354
- foreach ($matches[1] as $match) {
355
- $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
356
- $options = $this->options_decode($a);
357
-
358
- $block = $this->get_block($options['block_id']);
359
- if (!$block) {
360
- $this->logger->debug('Unable to load the block ' . $options['block_id']);
361
- //continue;
362
- }
363
-
364
- ob_start();
365
- $out = $this->render_block($options['block_id'], true, $options, $context);
366
- if (is_array($out)) {
367
- if ($out['return_empty_message'] || $out['stop']) {
368
- return false;
369
- }
370
- if ($out['skip']) {
371
- continue;
372
- }
373
- if (empty($subject) && !empty($out['subject'])) {
374
- $subject = $out['subject'];
375
- }
376
- }
377
- $block_html = ob_get_clean();
378
- $result .= $block_html;
379
- }
380
-
381
- $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
382
- $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
383
- $email->subject = $subject;
384
- return true;
385
- }
386
-
387
- function remove_block_data($text) {
388
- // TODO: Lavorare!
389
- return $text;
390
- }
391
-
392
- static function get_outlook_wrapper_open($width = 600) {
393
- return '<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '" style="vertical-align:top;width:' . $width . 'px;"><![endif]-->';
394
- }
395
-
396
- static function get_outlook_wrapper_close() {
397
- return "<!--[if mso | IE]></td></tr></table><![endif]-->";
398
- }
399
-
400
- function hook_safe_style_css($rules) {
401
- $rules[] = 'display';
402
- return $rules;
403
- }
404
-
405
- /**
406
- * Renders a block identified by its id, using the block options and adding a wrapper
407
- * if required (for the first block rendering).
408
- *
409
- * @param string $block_id
410
- * @param boolean $wrapper
411
- * @param array $options
412
- * @param array $context
413
- * @param array $composer
414
- */
415
- function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
416
- static $kses_style_filter = false;
417
- include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
418
-
419
- //Remove 'options_composer_' prefix
420
- $composer_defaults = [];
421
- foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
422
- $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
423
- }
424
- $composer = array_merge($composer_defaults, $composer);
425
-
426
- $width = 600;
427
- $font_family = 'Helvetica, Arial, sans-serif';
428
-
429
- $global_title_font_family = $composer['title_font_family'];
430
- $global_title_font_size = $composer['title_font_size'];
431
- $global_title_font_color = $composer['title_font_color'];
432
- $global_title_font_weight = $composer['title_font_weight'];
433
-
434
- $global_text_font_family = $composer['text_font_family'];
435
- $global_text_font_size = $composer['text_font_size'];
436
- $global_text_font_color = $composer['text_font_color'];
437
- $global_text_font_weight = $composer['text_font_weight'];
438
-
439
- $global_button_font_family = $composer['button_font_family'];
440
- $global_button_font_size = $composer['button_font_size'];
441
- $global_button_font_color = $composer['button_font_color'];
442
- $global_button_font_weight = $composer['button_font_weight'];
443
- $global_button_background_color = $composer['button_background_color'];
444
-
445
- $global_block_background = $composer['block_background'];
446
-
447
- $info = Newsletter::instance()->get_options('info');
448
-
449
- // Just in case...
450
- if (!is_array($options)) {
451
- $options = array();
452
- }
453
-
454
- // This code filters the HTML to remove javascript and unsecure attributes and enable the
455
- // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
456
- add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
457
- $options = wp_kses_post_deep($options);
458
- remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
459
-
460
- $block_options = get_option('newsletter_main');
461
-
462
- $block = $this->get_block($block_id);
463
-
464
- if (!isset($context['type']))
465
- $context['type'] = '';
466
-
467
- // Block not found
468
- if (!$block) {
469
- if ($wrapper) {
470
- echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">';
471
- echo '<tr>';
472
- echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
473
- }
474
- echo $this->get_outlook_wrapper_open($width);
475
-
476
- echo '<p>Ops, this block type is not avalable.</p>';
477
-
478
- echo $this->get_outlook_wrapper_close();
479
-
480
- if ($wrapper) {
481
- echo '</td></tr></table>';
482
- }
483
- return;
484
- }
485
-
486
- $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
487
-
488
- $dir = is_rtl() ? 'rtl' : 'ltr';
489
- $align_left = is_rtl() ? 'right' : 'left';
490
- $align_right = is_rtl() ? 'left' : 'right';
491
-
492
- ob_start();
493
- $logger = $this->logger;
494
- include $block['dir'] . '/block.php';
495
- $content = trim(ob_get_clean());
496
-
497
- if (empty($content)) {
498
- return $out;
499
- }
500
-
501
- $common_defaults = array(
502
- 'block_padding_top' => 0,
503
- 'block_padding_bottom' => 0,
504
- 'block_padding_right' => 0,
505
- 'block_padding_left' => 0,
506
- 'block_background' => '',
507
- 'block_background_2' => '',
508
- 'block_width' => 600,
509
- 'block_align' => 'center'
510
- );
511
-
512
- $options = array_merge($common_defaults, $options);
513
-
514
- // Obsolete
515
- $content = str_replace('{width}', $width, $content);
516
-
517
- $content = $this->inline_css($content, true);
518
-
519
- // CSS driven by the block
520
- // Requited for the server side parsing and rendering
521
- $options['block_id'] = $block_id;
522
-
523
- $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
524
- $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
525
- $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
526
- $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
527
-
528
- $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
529
-
530
- // Internal TD wrapper
531
- $style = 'text-align: center; ';
532
- $style .= 'width: 100% !important; ';
533
- $style .= 'line-height: normal !important; ';
534
- $style .= 'letter-spacing: normal; ';
535
- $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
536
- $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
537
- $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
538
- $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
539
- $style .= 'background-color: ' . $block_background . ';';
540
-
541
- if (isset($options['block_background_gradient'])) {
542
- $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
543
- }
544
-
545
- $data = $this->options_encode($options);
546
- // First time block creation wrapper
547
- if ($wrapper) {
548
- echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">', "\n";
549
- echo "<tr>";
550
- echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
551
- }
552
-
553
- // Container that fixes the width and makes the block responsive
554
- echo $this->get_outlook_wrapper_open($options['block_width']);
555
-
556
- echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $options['block_width'], 'px!important">', "\n";
557
- echo "<tr>";
558
- echo '<td align="', esc_attr($options['block_align']), '" style="', esc_attr($style), '" bgcolor="', esc_attr($block_background), '" width="100%">';
559
-
560
- //echo "<!-- block generated content -->\n";
561
- echo trim($content);
562
- //echo "\n<!-- /block generated content -->\n";
563
-
564
- echo "</td></tr></table>";
565
- echo $this->get_outlook_wrapper_close();
566
-
567
- // First time block creation wrapper
568
- if ($wrapper) {
569
- echo "</td></tr></table>";
570
- }
571
-
572
- return $out;
573
- }
574
-
575
- /**
576
- * Ajax call to render a block with a new set of options after the settings popup
577
- * has been saved.
578
- *
579
- * @param type $block_id
580
- * @param type $wrapper
581
- */
582
- function tnpc_render_callback() {
583
- if (!check_ajax_referer('save')) {
584
- $this->dienow('Expired request');
585
- }
586
-
587
- $block_id = $_POST['id'];
588
- $wrapper = isset($_POST['full']);
589
- $options = $this->restore_options_from_request();
590
-
591
- $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
592
- wp_die();
593
- }
594
-
595
- function hook_wp_ajax_tnpc_regenerate_email() {
596
-
597
- $content = stripslashes($_POST['content']);
598
- $global_options = $_POST['composer'];
599
-
600
- $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
601
-
602
- wp_send_json_success([
603
- 'content' => $regenerated_content,
604
- 'message' => __('Successfully updated', 'newsletter')
605
- ]);
606
- }
607
-
608
- private function regenerate_email_blocks($content, $global_options) {
609
-
610
- $raw_block_options = $this->extract_encoded_blocks_options($content);
611
-
612
- $regenerated_content = '';
613
-
614
- foreach ($raw_block_options as $raw_block_option) {
615
-
616
- /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
617
- $block_options = $this->options_decode( $a ); */
618
-
619
- $block_options = $this->options_decode($raw_block_option);
620
-
621
- $block = $this->get_block($block_options['block_id']);
622
- if (!$block) {
623
- $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
624
- }
625
-
626
- ob_start();
627
- $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
628
- $block_html = ob_get_clean();
629
-
630
- $regenerated_content .= $block_html;
631
- }
632
-
633
- return $regenerated_content;
634
- }
635
-
636
- /**
637
- * @param string $html_email_content Email html content
638
- *
639
- * @return string[] Encoded options of email blocks
640
- */
641
- private function extract_encoded_blocks_options($html_email_content) {
642
-
643
- preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
644
-
645
- return $raw_block_options[1];
646
- }
647
-
648
- function tnpc_preview_callback() {
649
- $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
650
-
651
- if (empty($email)) {
652
- echo 'Wrong email identifier';
653
- return;
654
- }
655
-
656
- echo $email['message'];
657
-
658
- wp_die();
659
- }
660
-
661
- function tnpc_css_callback() {
662
- include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
663
- wp_die();
664
- }
665
-
666
- /** Returns the correct admin page to edit the newsletter with the correct editor. */
667
- function get_editor_url($email_id, $editor_type) {
668
- switch ($editor_type) {
669
- case NewsletterEmails::EDITOR_COMPOSER:
670
- return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
671
- case NewsletterEmails::EDITOR_HTML:
672
- return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
673
- case NewsletterEmails::EDITOR_TINYMCE:
674
- return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
675
- }
676
- }
677
-
678
- /**
679
- * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
680
- * or the targeting page (it depends on newsletter status).
681
- *
682
- * @param TNP_Email $email
683
- */
684
- function get_edit_button($email, $only_icon = false) {
685
-
686
- $editor_type = $this->get_editor_type($email);
687
- if ($email->status == 'new') {
688
- $edit_url = $this->get_editor_url($email->id, $editor_type);
689
- } else {
690
- $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
691
- }
692
- switch ($editor_type) {
693
- case NewsletterEmails::EDITOR_COMPOSER:
694
- $icon_class = 'th-large';
695
- break;
696
- case NewsletterEmails::EDITOR_HTML:
697
- $icon_class = 'code';
698
- break;
699
- default:
700
- $icon_class = 'edit';
701
- break;
702
- }
703
- if ($only_icon) {
704
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
705
- '<i class="fas fa-' . $icon_class . '"></i></a>';
706
- } else {
707
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
708
- '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
709
- }
710
- }
711
-
712
- /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
713
- function get_editor_type($email) {
714
- $email = (object) $email;
715
- $editor_type = $email->editor;
716
-
717
- // Backward compatibility
718
- $email_options = maybe_unserialize($email->options);
719
- if (isset($email_options['composer'])) {
720
- $editor_type = NewsletterEmails::EDITOR_COMPOSER;
721
- }
722
- // End backward compatibility
723
-
724
- return $editor_type;
725
- }
726
-
727
- /**
728
- *
729
- * @param type $action
730
- * @param type $user
731
- * @param type $email
732
- * @return type
733
- * @global wpdb $wpdb
734
- */
735
- function hook_newsletter_action($action, $user, $email) {
736
- global $wpdb;
737
-
738
- switch ($action) {
739
- case 'v':
740
- case 'view':
741
- $id = $_GET['id'];
742
- if ($id == 'last') {
743
- $email = $wpdb->get_row("select * from " . NEWSLETTER_EMAILS_TABLE . " where private=0 and type='message' and status='sent' order by send_on desc limit 1");
744
- } else {
745
- $email = $this->get_email($_GET['id']);
746
- }
747
- if (empty($email)) {
748
- header("HTTP/1.0 404 Not Found");
749
- die('Email not found');
750
- }
751
-
752
- if (!Newsletter::instance()->is_allowed()) {
753
-
754
- if ($email->status == 'new') {
755
- header("HTTP/1.0 404 Not Found");
756
- die('Not sent yet');
757
- }
758
-
759
- if ($email->private == 1) {
760
- if (!$user) {
761
- header("HTTP/1.0 404 Not Found");
762
- die('No available for online view');
763
- }
764
- $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
765
- if (!$sent) {
766
- header("HTTP/1.0 404 Not Found");
767
- die('No available for online view');
768
- }
769
- }
770
- }
771
-
772
-
773
- header('Content-Type: text/html;charset=UTF-8');
774
- header('X-Robots-Tag: noindex,nofollow,noarchive');
775
- header('Cache-Control: no-cache,no-store,private');
776
-
777
- echo $this->replace($email->message, $user, $email);
778
-
779
- die();
780
- break;
781
-
782
- case 'emails-css':
783
- $email_id = (int) $_GET['id'];
784
-
785
- $body = Newsletter::instance()->get_email_field($email_id, 'message');
786
-
787
- $x = strpos($body, '<style');
788
- if ($x === false)
789
- return;
790
-
791
- $x = strpos($body, '>', $x);
792
- $y = strpos($body, '</style>');
793
-
794
- header('Content-Type: text/css;charset=UTF-8');
795
-
796
- echo substr($body, $x + 1, $y - $x - 1);
797
-
798
- die();
799
- break;
800
-
801
- case 'emails-composer-css':
802
- header('Cache: no-cache');
803
- header('Content-Type: text/css');
804
- echo $this->get_composer_css();
805
- die();
806
- break;
807
-
808
- case 'emails-preview':
809
- if (!Newsletter::instance()->is_allowed()) {
810
- die('Not enough privileges');
811
- }
812
-
813
- if (!check_admin_referer('view')) {
814
- die();
815
- }
816
-
817
- $theme_id = $_GET['id'];
818
- $theme = $this->themes->get_theme($theme_id);
819
-
820
- // Used by theme code
821
- $theme_options = $this->themes->get_options($theme_id);
822
-
823
- $theme_url = $theme['url'];
824
-
825
- header('Content-Type: text/html;charset=UTF-8');
826
-
827
- include $theme['dir'] . '/theme.php';
828
-
829
- die();
830
- break;
831
-
832
- case 'emails-preview-text':
833
- header('Content-Type: text/plain;charset=UTF-8');
834
- if (!Newsletter::instance()->is_allowed()) {
835
- die('Not enough privileges');
836
- }
837
-
838
- if (!check_admin_referer('view')) {
839
- die();
840
- }
841
-
842
- // Used by theme code
843
- $theme_options = $this->get_current_theme_options();
844
-
845
- $file = include $theme['dir'] . '/theme-text.php';
846
-
847
- if (is_file($file)) {
848
- include $file;
849
- }
850
-
851
- die();
852
- break;
853
-
854
- case 'emails-create':
855
- // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
856
- // excerpt, thumbnail are extracted.
857
- if (!Newsletter::instance()->is_allowed()) {
858
- die('Not enough privileges');
859
- }
860
-
861
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
862
- $controls = new NewsletterControls();
863
-
864
- if (!$controls->is_action('create')) {
865
- die('Wrong call');
866
- }
867
-
868
- $theme_id = $controls->data['id'];
869
- $theme = $this->themes->get_theme($theme_id);
870
-
871
- if (!$theme) {
872
- die('invalid theme');
873
- }
874
-
875
- $this->themes->save_options($theme_id, $controls->data);
876
-
877
- $email = array();
878
- $email['status'] = 'new';
879
- $email['subject'] = ''; //__('Here the email subject', 'newsletter');
880
- $email['track'] = Newsletter::instance()->options['track'];
881
- $email['send_on'] = time();
882
- $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
883
- $email['type'] = 'message';
884
-
885
- $theme_options = $this->themes->get_options($theme_id);
886
-
887
- $theme_url = $theme['url'];
888
- $theme_subject = '';
889
-
890
- ob_start();
891
- include $theme['dir'] . '/theme.php';
892
- $email['message'] = ob_get_clean();
893
-
894
- if (!empty($theme_subject)) {
895
- $email['subject'] = $theme_subject;
896
- }
897
-
898
- if (file_exists($theme['dir'] . '/theme-text.php')) {
899
- ob_start();
900
- include $theme['dir'] . '/theme-text.php';
901
- $email['message_text'] = ob_get_clean();
902
- } else {
903
- $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
904
- }
905
-
906
- $email = $this->save_email($email);
907
-
908
- $edit_url = $this->get_editor_url($email->id, $email->editor);
909
-
910
- header('Location: ' . $edit_url);
911
-
912
- die();
913
- break;
914
- }
915
- }
916
-
917
- function admin_menu() {
918
- $this->add_menu_page('index', 'Newsletters');
919
- $this->add_admin_page('list', 'Email List');
920
- $this->add_admin_page('new', 'Email New');
921
- $this->add_admin_page('edit', 'Email Edit');
922
- $this->add_admin_page('theme', 'Email Themes');
923
- $this->add_admin_page('composer', 'The Composer');
924
- $this->add_admin_page('editorhtml', 'HTML Editor');
925
- $this->add_admin_page('editortinymce', 'TinyMCE Editor');
926
- }
927
-
928
- /**
929
- * Builds a block data structure starting from the folder containing the block
930
- * files.
931
- *
932
- * @param string $dir
933
- * @return array | WP_Error
934
- */
935
- function build_block($dir) {
936
- $dir = realpath($dir);
937
- $dir = wp_normalize_path($dir);
938
- $full_file = $dir . '/block.php';
939
- if (!is_file($full_file)) {
940
- return new WP_Error('1', 'Missing block.php file in ' . $dir);
941
- }
942
-
943
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
944
- $file = basename($dir);
945
-
946
- $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
947
- $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
948
- $data = array_merge($defaults, $data);
949
-
950
- if (is_file($dir . '/icon.png')) {
951
- $data['icon'] = content_url($relative_dir . '/icon.png');
952
- }
953
-
954
- $data['id'] = sanitize_key($file);
955
-
956
- // Absolute path of the block files
957
- $data['dir'] = $dir;
958
- $data['url'] = content_url($relative_dir);
959
-
960
- return $data;
961
- }
962
-
963
- /**
964
- *
965
- * @param type $dir
966
- * @return type
967
- */
968
- function scan_blocks_dir($dir) {
969
- $dir = realpath($dir);
970
- if (!$dir) {
971
- return [];
972
- }
973
- $dir = wp_normalize_path($dir);
974
-
975
- $list = [];
976
- $handle = opendir($dir);
977
- while ($file = readdir($handle)) {
978
-
979
- $data = $this->build_block($dir . '/' . $file);
980
-
981
- if (is_wp_error($data)) {
982
- $this->logger->error($data);
983
- continue;
984
- }
985
- $list[$data['id']] = $data;
986
- }
987
- closedir($handle);
988
- return $list;
989
- }
990
-
991
- /**
992
- * Array of arrays with every registered block and legacy block converted to the new
993
- * format.
994
- *
995
- * @return array
996
- */
997
- function get_blocks() {
998
-
999
- if (!is_null($this->blocks)) {
1000
- return $this->blocks;
1001
- }
1002
-
1003
- $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1004
-
1005
- $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1006
-
1007
- $this->blocks = array_merge($extended, $this->blocks);
1008
-
1009
- $dirs = apply_filters('newsletter_blocks_dir', array());
1010
-
1011
- //$this->logger->debug('Block dirs:');
1012
- //$this->logger->debug($dirs);
1013
-
1014
- foreach ($dirs as $dir) {
1015
- $list = $this->scan_blocks_dir($dir);
1016
- $this->blocks = array_merge($list, $this->blocks);
1017
- }
1018
-
1019
- do_action('newsletter_register_blocks');
1020
-
1021
- foreach (TNP_Composer::$block_dirs as $dir) {
1022
- $block = $this->build_block($dir);
1023
- if (is_wp_error($block)) {
1024
- $this->logger->error($block);
1025
- continue;
1026
- }
1027
- if (!isset($this->blocks[$block['id']])) {
1028
- $this->blocks[$block['id']] = $block;
1029
- } else {
1030
- $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1031
- }
1032
- }
1033
-
1034
- $this->blocks = array_reverse($this->blocks);
1035
- return $this->blocks;
1036
- }
1037
-
1038
- /**
1039
- * Return a single block (associative array) checking for legacy ID as well.
1040
- *
1041
- * @param string $id
1042
- * @return array
1043
- */
1044
- function get_block($id) {
1045
- switch ($id) {
1046
- case 'content-03-text.block':
1047
- $id = 'text';
1048
- break;
1049
- case 'footer-03-social.block':
1050
- $id = 'social';
1051
- break;
1052
- case 'footer-02-canspam.block':
1053
- $id = 'canspam';
1054
- break;
1055
- case 'content-05-image.block':
1056
- $id = 'image';
1057
- break;
1058
- case 'header-01-header.block':
1059
- $id = 'header';
1060
- break;
1061
- case 'footer-01-footer.block':
1062
- $id = 'footer';
1063
- break;
1064
- case 'content-02-heading.block':
1065
- $id = 'heading';
1066
- break;
1067
- case 'content-07-twocols.block':
1068
- case 'content-06-posts.block':
1069
- $id = 'posts';
1070
- break;
1071
- case 'content-04-cta.block':
1072
- $id = 'cta';
1073
- break;
1074
- case 'content-01-hero.block':
1075
- $id = 'hero';
1076
- break;
1077
- // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1078
- // break;
1079
- }
1080
-
1081
- // Conversion for old full path ID
1082
- $id = sanitize_key(basename($id));
1083
-
1084
- // TODO: Correct id for compatibility
1085
- $blocks = $this->get_blocks();
1086
- if (!isset($blocks[$id])) {
1087
- return null;
1088
- }
1089
- return $blocks[$id];
1090
- }
1091
-
1092
- function scan_presets_dir($dir = null) {
1093
-
1094
- if (is_null($dir)) {
1095
- $dir = __DIR__ . '/presets';
1096
- }
1097
-
1098
- if (!is_dir($dir)) {
1099
- return array();
1100
- }
1101
-
1102
- $handle = opendir($dir);
1103
- $list = array();
1104
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1105
- while ($file = readdir($handle)) {
1106
-
1107
- if ($file == '.' || $file == '..')
1108
- continue;
1109
-
1110
- // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1111
- $preset_id = sanitize_key($file);
1112
-
1113
- $full_file = $dir . '/' . $file . '/preset.json';
1114
-
1115
- if (!is_file($full_file)) {
1116
- continue;
1117
- }
1118
-
1119
- $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1120
-
1121
- $list[$preset_id] = $icon;
1122
- }
1123
- closedir($handle);
1124
- return $list;
1125
- }
1126
-
1127
- function get_preset_from_file($id, $dir = null) {
1128
-
1129
- if (is_null($dir)) {
1130
- $dir = __DIR__ . '/presets';
1131
- }
1132
-
1133
- $id = $this->sanitize_file_name($id);
1134
-
1135
- if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1136
- return array();
1137
- }
1138
-
1139
- $json_content = file_get_contents("$dir/$id/preset.json");
1140
- $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1141
- $json = json_decode($json_content);
1142
- $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1143
-
1144
- return $json;
1145
- }
1146
-
1147
- function get_composer_css() {
1148
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1149
- $blocks = $this->get_blocks();
1150
- foreach ($blocks as $block) {
1151
- if (!file_exists($block['dir'] . '/style.css')) {
1152
- continue;
1153
- }
1154
- $css .= "\n\n";
1155
- $css .= "/* " . $block['name'] . " */\n";
1156
- $css .= file_get_contents($block['dir'] . '/style.css');
1157
- }
1158
- return $css;
1159
- }
1160
-
1161
- function get_composer_backend_css() {
1162
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1163
- $css .= "\n\n";
1164
- $css .= $this->get_composer_css();
1165
- return $css;
1166
- }
1167
-
1168
- /**
1169
- * Send an email to the test subscribers.
1170
- *
1171
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1172
- * @param NewsletterControls $controls
1173
- */
1174
- function send_test_email($email, $controls) {
1175
- if (!$email) {
1176
- $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1177
- return;
1178
- }
1179
-
1180
- $original_subject = $email->subject;
1181
- $this->set_test_subject_to($email);
1182
-
1183
- $users = NewsletterUsers::instance()->get_test_users();
1184
- if (count($users) == 0) {
1185
- $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1186
- '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1187
- __('Read more', 'newsletter') . '</strong></a>.';
1188
- } else {
1189
- $r = Newsletter::instance()->send($email, $users, true);
1190
- $emails = array();
1191
- foreach ($users as $user) {
1192
- $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1193
- }
1194
- if (is_wp_error($r)) {
1195
- $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1196
- $controls->errors .= __('Test subscribers:', 'newsletter');
1197
- $controls->errors .= ' ' . implode(', ', $emails);
1198
- $controls->errors .= '<br>';
1199
- $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1200
- $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1201
- } else {
1202
- $controls->messages = __('Test subscribers:', 'newsletter');
1203
-
1204
- $controls->messages .= ' ' . implode(', ', $emails);
1205
- $controls->messages .= '.<br>';
1206
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1207
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1208
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1209
- }
1210
- }
1211
- $email->subject = $original_subject;
1212
- }
1213
-
1214
- /**
1215
- * Send an email to the test subscribers.
1216
- *
1217
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1218
- * @param string $email_address
1219
- *
1220
- * @throws Exception
1221
- */
1222
- function send_test_newsletter_to_email_address($email, $email_address) {
1223
-
1224
- if (!$email) {
1225
- throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1226
- }
1227
-
1228
- $this->set_test_subject_to($email);
1229
-
1230
- $dummy_subscriber = $this->make_dummy_subscriber();
1231
- $dummy_subscriber->email = $email_address;
1232
-
1233
- $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1234
-
1235
- $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1236
-
1237
- if (is_wp_error($result)) {
1238
- $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1239
- $error_message .= __('Test subscribers:', 'newsletter');
1240
- $error_message .= ' ' . $email;
1241
- $error_message .= '<br>';
1242
- $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1243
- $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1244
- throw new Exception($error_message);
1245
- }
1246
-
1247
- $messages = __('Test subscribers:', 'newsletter');
1248
-
1249
- $messages .= ' ' . $email;
1250
- $messages .= '.<br>';
1251
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1252
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1253
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1254
-
1255
- return $messages;
1256
- }
1257
-
1258
- private function set_test_subject_to($email) {
1259
- if ($email->subject == '') {
1260
- $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1261
- } else {
1262
- $email->subject = $email->subject . ' (TEST)';
1263
- }
1264
- }
1265
-
1266
- private function make_dummy_subscriber() {
1267
- $dummy_user = new TNP_User();
1268
- $dummy_user->id = 0;
1269
- $dummy_user->email = 'john.doe@example.org';
1270
- $dummy_user->name = 'John';
1271
- $dummy_user->surname = 'Doe';
1272
- $dummy_user->sex = 'n';
1273
- $dummy_user->language = '';
1274
- $dummy_user->ip = '';
1275
-
1276
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1277
- $profile_key = "profile_$i";
1278
- $dummy_user->$profile_key = '';
1279
- }
1280
-
1281
- return $dummy_user;
1282
- }
1283
-
1284
- function restore_options_from_request() {
1285
-
1286
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1287
- $controls = new NewsletterControls();
1288
- $options = $controls->data;
1289
-
1290
- if (isset($_POST['options']) && is_array($_POST['options'])) {
1291
- // Get all block options
1292
- //$options = stripslashes_deep($_POST['options']);
1293
- // Deserialize inline edits when
1294
- // render is preformed on saving block options
1295
- if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1296
- $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1297
- }
1298
-
1299
- // Restore inline edits from data-json
1300
- // coming from inline editing
1301
- // and merge with current inline edit
1302
- if (isset($_POST['encoded_options'])) {
1303
- $decoded_options = $this->options_decode($_POST['encoded_options']);
1304
-
1305
- $to_merge_inline_edits = [];
1306
-
1307
- if (isset($decoded_options['inline_edits'])) {
1308
- foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1309
- $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1310
- }
1311
- }
1312
-
1313
- //Overwrite with new edited content
1314
- if (isset($options['inline_edits'])) {
1315
- foreach ($options['inline_edits'] as $inline_edit) {
1316
- $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1317
- }
1318
- }
1319
-
1320
- $options['inline_edits'] = array_values($to_merge_inline_edits);
1321
- $options = array_merge($decoded_options, $options);
1322
- }
1323
-
1324
- return $options;
1325
- }
1326
-
1327
- return array();
1328
- }
1329
-
1330
- public function hook_wp_ajax_tnpc_delete_preset() {
1331
-
1332
- if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1333
- wp_send_json_error('Expired request');
1334
- }
1335
-
1336
- $preset_id = (int) $_REQUEST['presetId'];
1337
-
1338
- $newsletter = Newsletter::instance();
1339
-
1340
- if ($preset_id > 0) {
1341
- $preset = $newsletter->get_email($preset_id);
1342
-
1343
- if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1344
- Newsletter::instance()->delete_email($preset_id);
1345
- wp_send_json_success();
1346
- } else {
1347
- wp_send_json_error(__('Is not a preset!', 'newsletter'));
1348
- }
1349
- } else {
1350
- wp_send_json_error();
1351
- }
1352
- }
1353
-
1354
- }
1355
-
1356
- NewsletterEmails::instance();
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterEmails extends NewsletterModule {
6
+
7
+ static $instance;
8
+
9
+ const EDITOR_COMPOSER = 2;
10
+ const EDITOR_HTML = 1;
11
+ const EDITOR_TINYMCE = 0;
12
+
13
+ static $PRESETS_LIST;
14
+
15
+ const PRESET_EMAIL_TYPE = 'composer_template';
16
+
17
+ // Cache
18
+ var $blocks = null;
19
+
20
+ /**
21
+ * @return NewsletterEmails
22
+ */
23
+ static function instance() {
24
+ if (self::$instance == null) {
25
+ self::$instance = new NewsletterEmails();
26
+ }
27
+
28
+ return self::$instance;
29
+ }
30
+
31
+ function __construct() {
32
+ self::$PRESETS_LIST = array("cta", "invite", "announcement", "posts", "sales", "product", "tour", "simple");
33
+ $this->themes = new NewsletterThemes('emails');
34
+ parent::__construct('emails', '1.1.5');
35
+ add_action('newsletter_action', array($this, 'hook_newsletter_action'), 13, 3);
36
+ add_action('newsletter_init', [$this, 'hook_newsletter_init']);
37
+
38
+ if (is_admin()) {
39
+ // Thank you to plugins which add the WP editor on other admin plugin pages...
40
+ if (isset($_GET['page']) && $_GET['page'] == 'newsletter_emails_edit') {
41
+ global $wp_actions;
42
+ $wp_actions['wp_enqueue_editor'] = 1;
43
+ }
44
+ }
45
+ }
46
+
47
+ function hook_newsletter_init() {
48
+ if (is_admin()) {
49
+ if (defined('DOING_AJAX') && DOING_AJAX) {
50
+ if (Newsletter::instance()->is_allowed()) {
51
+ add_action('wp_ajax_tnpc_render', array($this, 'tnpc_render_callback'));
52
+ add_action('wp_ajax_tnpc_preview', array($this, 'tnpc_preview_callback'));
53
+ add_action('wp_ajax_tnpc_css', array($this, 'tnpc_css_callback'));
54
+ add_action('wp_ajax_tnpc_options', array($this, 'hook_wp_ajax_tnpc_options'));
55
+ add_action('wp_ajax_tnpc_get_all_presets', array($this, 'ajax_get_all_presets'));
56
+ add_action('wp_ajax_tnpc_get_preset', array($this, 'ajax_get_preset'));
57
+ add_action('wp_ajax_tnpc_delete_preset', array($this, 'hook_wp_ajax_tnpc_delete_preset'));
58
+ add_action('wp_ajax_tnpc_regenerate_email', array($this, 'hook_wp_ajax_tnpc_regenerate_email'));
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ function options_decode($options) {
65
+ // Old "query string" format
66
+ if (is_string($options) && strpos($options, 'options[') !== false) {
67
+ $opts = [];
68
+ parse_str($options, $opts);
69
+ $options = $opts['options'];
70
+ }
71
+
72
+ if (is_array($options)) {
73
+ return $options;
74
+ }
75
+
76
+ // Json data should be base64 encoded, but for short time it wasn't
77
+ $tmp = json_decode($options, true);
78
+ if (is_null($tmp)) {
79
+ return json_decode(base64_decode($options), true);
80
+ } else {
81
+ return $tmp;
82
+ }
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @param array $options Options array
88
+ */
89
+ function options_encode($options) {
90
+ return base64_encode(json_encode($options, JSON_HEX_TAG | JSON_HEX_AMP));
91
+ }
92
+
93
+ /**
94
+ * Builds and returns the HTML with the form fields of a specific block.
95
+ *
96
+ * @global wpdb $wpdb
97
+ */
98
+ function hook_wp_ajax_tnpc_options() {
99
+ global $wpdb;
100
+
101
+ $block = $this->get_block($_REQUEST['id']);
102
+ if (!$block) {
103
+ die('Block not found with id ' . esc_html($_REQUEST['id']));
104
+ }
105
+
106
+ if (!class_exists('NewsletterControls')) {
107
+ include NEWSLETTER_INCLUDES_DIR . '/controls.php';
108
+ }
109
+
110
+ $options = $this->options_decode(stripslashes_deep($_REQUEST['options']));
111
+ $composer = isset($_POST['composer']) ? $_POST['composer'] : [];
112
+
113
+ $context = array('type' => '');
114
+ if (isset($_REQUEST['context_type'])) {
115
+ $context['type'] = $_REQUEST['context_type'];
116
+ }
117
+
118
+ $controls = new NewsletterControls($options);
119
+ $fields = new NewsletterFields($controls);
120
+
121
+ $controls->init();
122
+ echo '<input type="hidden" name="action" value="tnpc_render">';
123
+ echo '<input type="hidden" name="id" value="' . esc_attr($_REQUEST['id']) . '">';
124
+ echo '<input type="hidden" name="context_type" value="' . esc_attr($context['type']) . '">';
125
+ $inline_edits = '';
126
+ if (isset($controls->data['inline_edits'])) {
127
+ $inline_edits = $controls->data['inline_edits'];
128
+ }
129
+ echo '<input type="hidden" name="options[inline_edits]" value="', esc_attr($this->options_encode($inline_edits)), '">';
130
+ echo "<h2>", esc_html($block["name"]), "</h2>";
131
+ include $block['dir'] . '/options.php';
132
+ wp_die();
133
+ }
134
+
135
+ /**
136
+ * Retrieves the presets list (no id in GET) or a specific preset id in GET)
137
+ */
138
+ public function ajax_get_all_presets() {
139
+ wp_send_json_success($this->get_all_preset());
140
+ }
141
+
142
+ public function ajax_get_preset() {
143
+
144
+ if (empty($_REQUEST['id'])) {
145
+ wp_send_json_error([
146
+ 'msg' => __('Invalid preset ID')
147
+ ]);
148
+ }
149
+
150
+ $preset_id = $_REQUEST['id'];
151
+ $preset_content = $this->get_preset_content($preset_id);
152
+ $global_options = $this->get_preset_global_options($preset_id);
153
+
154
+ wp_send_json_success([
155
+ 'content' => $preset_content,
156
+ 'globalOptions' => $global_options,
157
+ ]);
158
+ }
159
+
160
+ private function get_preset_content($preset_id) {
161
+
162
+ $content = '';
163
+
164
+ if ($this->is_a_tnp_default_preset($preset_id)) {
165
+
166
+ // Get preset from file
167
+ $preset = $this->get_preset_from_file($preset_id);
168
+
169
+ foreach ($preset->blocks as $item) {
170
+ ob_start();
171
+ $this->render_block($item->block, true, (array) $item->options);
172
+ $content .= trim(ob_get_clean());
173
+ }
174
+ } else {
175
+
176
+ // Get preset from db
177
+ $preset_email = $this->get_email(intval($preset_id));
178
+ $global_options = $this->extract_global_options_from($preset_email);
179
+ $content = $this->regenerate_email_blocks($preset_email->message, $global_options);
180
+ }
181
+
182
+ return $content;
183
+ }
184
+
185
+ private function get_preset_global_options($preset_id) {
186
+
187
+ if ($this->is_a_tnp_default_preset($preset_id)) {
188
+ return [];
189
+ }
190
+
191
+ // Get preset from db
192
+ $preset_email = $this->get_email(intval($preset_id));
193
+ $global_options = $this->extract_global_options_from($preset_email);
194
+
195
+ return $global_options;
196
+ }
197
+
198
+ private function extract_global_options_from($email) {
199
+ $global_options = [];
200
+ foreach ($email->options as $global_option_name => $global_option) {
201
+ if (strpos($global_option_name, 'composer_') === 0) {
202
+ $global_options[str_replace('composer_', '', $global_option_name)] = $global_option;
203
+ }
204
+ }
205
+
206
+ return $global_options;
207
+ }
208
+
209
+ private function is_a_tnp_default_preset($preset_id) {
210
+ return in_array($preset_id, self::$PRESETS_LIST);
211
+ }
212
+
213
+ private function get_all_preset() {
214
+
215
+ $content = "<div class='tnpc-preset-container'>";
216
+
217
+ if ($this->is_normal_context_request()) {
218
+ $content .= "<div class='tnpc-preset-legacy-themes'><a href='" . $this->get_admin_page_url('theme') . "'>" . __('Looking for legacy themes?', 'newsletter') . "</a></div>";
219
+ }
220
+
221
+ // LOAD USER PRESETS
222
+ $user_preset_list = $this->get_emails(self::PRESET_EMAIL_TYPE);
223
+
224
+ foreach ($user_preset_list as $user_preset) {
225
+
226
+ $default_icon_url = NEWSLETTER_URL . "/emails/presets/default-icon.png?ver=2";
227
+ $preset_name = $user_preset->subject;
228
+ $delete_preset_text = __('Delete', 'newsletter');
229
+ $edit_preset_text = __('Edit', 'newsletter');
230
+
231
+ // esc_js() assumes the string will be in single quote (arghhh!!!)
232
+ $onclick_edit = 'tnpc_edit_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
233
+ $onclick_delete = 'tnpc_delete_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
234
+ $onclick_load = 'tnpc_load_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
235
+
236
+ $content .= "<div class='tnpc-preset' onclick='" . esc_attr($onclick_load) . "'>\n";
237
+ $content .= "<img src='$default_icon_url' title='" . esc_attr($preset_name) . "' alt='" . esc_attr($preset_name) . "'>\n";
238
+ $content .= "<span class='tnpc-preset-label'>" . esc_html($user_preset->subject) . "</span>\n";
239
+ $content .= "<span class='tnpc-delete-preset' onclick='" . esc_attr($onclick_delete) . "' title='" . esc_attr($delete_preset_text) . "'><i class='fas fa-times'></i></span>\n";
240
+ $content .= "<span class='tnpc-edit-preset' onclick='" . esc_attr($onclick_edit) . "' title='" . esc_attr($edit_preset_text) . "'><i class='fas fa-pencil-alt'></i></span>\n";
241
+ $content .= "</div>";
242
+ }
243
+
244
+ // LOAD TNP PRESETS
245
+ foreach (self::$PRESETS_LIST as $id) {
246
+ $preset = $this->get_preset_from_file($id);
247
+ $preset_name = esc_html($preset->name);
248
+ $content .= "<div class='tnpc-preset' onclick='tnpc_load_preset(\"$id\")'>";
249
+ $content .= "<img src='$preset->icon' title='$preset_name' alt='$preset_name'/>";
250
+ $content .= "<span class='tnpc-preset-label'>$preset_name</span>";
251
+ $content .= "</div>";
252
+ }
253
+
254
+ if ($this->is_normal_context_request()) {
255
+ $content .= $this->get_automated_spot_element();
256
+ $content .= $this->get_autoresponder_spot_element();
257
+ $content .= $this->get_raw_html_preset_element();
258
+ }
259
+
260
+ return $content;
261
+ }
262
+
263
+ private function is_normal_context_request() {
264
+ return empty($_REQUEST['context_type']);
265
+ }
266
+
267
+ private function is_automated_context_request() {
268
+ return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'automated';
269
+ }
270
+
271
+ private function is_autoresponder_context_request() {
272
+ return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'autoresponder';
273
+ }
274
+
275
+ private function get_automated_spot_element() {
276
+ $result = "<div class='tnpc-preset'>";
277
+ if (class_exists('NewsletterAutomated')) {
278
+ $result .= "<a href='?page=newsletter_automated_index'>";
279
+ } else {
280
+ $result .= "<a href='https://www.thenewsletterplugin.com/automated?utm_source=composer&utm_campaign=plugin&utm_medium=automated'>";
281
+ }
282
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/automated.png' title='Automated addon' alt='Automated'/>";
283
+ $result .= "<span class='tnpc-preset-label'>Daily, weekly and monthly newsletters</span></a>";
284
+ $result .= "</div>";
285
+
286
+ return $result;
287
+ }
288
+
289
+ private function get_autoresponder_spot_element() {
290
+ $result = "<div class='tnpc-preset'>";
291
+ if (class_exists('NewsletterAutoresponder')) {
292
+ $result .= "<a href='?page=newsletter_autoresponder_index'>";
293
+ } else {
294
+ $result .= "<a href='https://www.thenewsletterplugin.com/autoresponder?utm_source=composer&utm_campaign=plugin&utm_medium=autoresponder' target='_blank'>";
295
+ }
296
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/autoresponder.png' title='Autoresponder addon' alt='Autoresponder'/>";
297
+ $result .= "<span class='tnpc-preset-label'>Autoresponders</span></a>";
298
+ $result .= "</div>";
299
+
300
+ return $result;
301
+ }
302
+
303
+ private function get_raw_html_preset_element() {
304
+
305
+ $result = "<div class='tnpc-preset tnpc-preset-html' onclick='location.href=\"" . wp_nonce_url('admin.php?page=newsletter_emails_new&id=rawhtml', 'newsletter-new') . "\"'>";
306
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/rawhtml.png' title='RAW HTML' alt='RAW'/>";
307
+ $result .= "<span class='tnpc-preset-label'>Raw HTML</span>";
308
+ $result .= "</div>";
309
+
310
+ $result .= "<div class='clear'></div>";
311
+ $result .= "</div>";
312
+
313
+ return $result;
314
+ }
315
+
316
+ function has_dynamic_blocks($theme) {
317
+ preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
318
+ foreach ($matches[1] as $match) {
319
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
320
+ $options = $this->options_decode($a);
321
+
322
+ $block = $this->get_block($options['block_id']);
323
+ if (!$block) {
324
+ continue;
325
+ }
326
+ if ($block['type'] == 'dynamic') {
327
+ return true;
328
+ }
329
+ }
330
+ return false;
331
+ }
332
+
333
+ /**
334
+ * Regenerates a saved composed email rendering each block. Regeneration is
335
+ * conditioned (possibly) by the context. The context is usually passed to blocks
336
+ * so they can act in the right manner.
337
+ *
338
+ * $context contains a type and, for automated, the last_run.
339
+ *
340
+ * $email can actually be even a string containing the full newsletter HTML code.
341
+ *
342
+ * @param TNP_Email $email
343
+ * @return string
344
+ */
345
+ function regenerate($email, $context = []) {
346
+
347
+ $context = array_merge(['last_run' => 0, 'type' => ''], $context);
348
+
349
+ preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
350
+
351
+ $result = '';
352
+ $subject = '';
353
+
354
+ foreach ($matches[1] as $match) {
355
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
356
+ $options = $this->options_decode($a);
357
+
358
+ $block = $this->get_block($options['block_id']);
359
+ if (!$block) {
360
+ $this->logger->debug('Unable to load the block ' . $options['block_id']);
361
+ //continue;
362
+ }
363
+
364
+ ob_start();
365
+ $out = $this->render_block($options['block_id'], true, $options, $context);
366
+ if (is_array($out)) {
367
+ if ($out['return_empty_message'] || $out['stop']) {
368
+ return false;
369
+ }
370
+ if ($out['skip']) {
371
+ continue;
372
+ }
373
+ if (empty($subject) && !empty($out['subject'])) {
374
+ $subject = $out['subject'];
375
+ }
376
+ }
377
+ $block_html = ob_get_clean();
378
+ $result .= $block_html;
379
+ }
380
+
381
+ $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
382
+ $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
383
+ $email->subject = $subject;
384
+ return true;
385
+ }
386
+
387
+ function remove_block_data($text) {
388
+ // TODO: Lavorare!
389
+ return $text;
390
+ }
391
+
392
+ static function get_outlook_wrapper_open($width = 600) {
393
+ return '<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '" style="vertical-align:top;width:' . $width . 'px;"><![endif]-->';
394
+ }
395
+
396
+ static function get_outlook_wrapper_close() {
397
+ return "<!--[if mso | IE]></td></tr></table><![endif]-->";
398
+ }
399
+
400
+ function hook_safe_style_css($rules) {
401
+ $rules[] = 'display';
402
+ return $rules;
403
+ }
404
+
405
+ /**
406
+ * Renders a block identified by its id, using the block options and adding a wrapper
407
+ * if required (for the first block rendering).
408
+ *
409
+ * @param string $block_id
410
+ * @param boolean $wrapper
411
+ * @param array $options
412
+ * @param array $context
413
+ * @param array $composer
414
+ */
415
+ function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
416
+ static $kses_style_filter = false;
417
+ include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
418
+
419
+ //Remove 'options_composer_' prefix
420
+ $composer_defaults = [];
421
+ foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
422
+ $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
423
+ }
424
+ $composer = array_merge($composer_defaults, $composer);
425
+
426
+ $width = 600;
427
+ $font_family = 'Helvetica, Arial, sans-serif';
428
+
429
+ $global_title_font_family = $composer['title_font_family'];
430
+ $global_title_font_size = $composer['title_font_size'];
431
+ $global_title_font_color = $composer['title_font_color'];
432
+ $global_title_font_weight = $composer['title_font_weight'];
433
+
434
+ $global_text_font_family = $composer['text_font_family'];
435
+ $global_text_font_size = $composer['text_font_size'];
436
+ $global_text_font_color = $composer['text_font_color'];
437
+ $global_text_font_weight = $composer['text_font_weight'];
438
+
439
+ $global_button_font_family = $composer['button_font_family'];
440
+ $global_button_font_size = $composer['button_font_size'];
441
+ $global_button_font_color = $composer['button_font_color'];
442
+ $global_button_font_weight = $composer['button_font_weight'];
443
+ $global_button_background_color = $composer['button_background_color'];
444
+
445
+ $global_block_background = $composer['block_background'];
446
+
447
+ $info = Newsletter::instance()->get_options('info');
448
+
449
+ // Just in case...
450
+ if (!is_array($options)) {
451
+ $options = array();
452
+ }
453
+
454
+ // This code filters the HTML to remove javascript and unsecure attributes and enable the
455
+ // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
456
+ add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
457
+ $options = wp_kses_post_deep($options);
458
+ remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
459
+
460
+ $block_options = get_option('newsletter_main');
461
+
462
+ $block = $this->get_block($block_id);
463
+
464
+ if (!isset($context['type']))
465
+ $context['type'] = '';
466
+
467
+ // Block not found
468
+ if (!$block) {
469
+ if ($wrapper) {
470
+ echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">';
471
+ echo '<tr>';
472
+ echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
473
+ }
474
+ echo $this->get_outlook_wrapper_open($width);
475
+
476
+ echo '<p>Ops, this block type is not avalable.</p>';
477
+
478
+ echo $this->get_outlook_wrapper_close();
479
+
480
+ if ($wrapper) {
481
+ echo '</td></tr></table>';
482
+ }
483
+ return;
484
+ }
485
+
486
+ $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
487
+
488
+ $dir = is_rtl() ? 'rtl' : 'ltr';
489
+ $align_left = is_rtl() ? 'right' : 'left';
490
+ $align_right = is_rtl() ? 'left' : 'right';
491
+
492
+ ob_start();
493
+ $logger = $this->logger;
494
+ include $block['dir'] . '/block.php';
495
+ $content = trim(ob_get_clean());
496
+
497
+ if (empty($content)) {
498
+ return $out;
499
+ }
500
+
501
+ $common_defaults = array(
502
+ 'block_padding_top' => 0,
503
+ 'block_padding_bottom' => 0,
504
+ 'block_padding_right' => 0,
505
+ 'block_padding_left' => 0,
506
+ 'block_background' => '',
507
+ 'block_background_2' => '',
508
+ 'block_width' => 600,
509
+ 'block_align' => 'center'
510
+ );
511
+
512
+ $options = array_merge($common_defaults, $options);
513
+
514
+ // Obsolete
515
+ $content = str_replace('{width}', $width, $content);
516
+
517
+ $content = $this->inline_css($content, true);
518
+
519
+ // CSS driven by the block
520
+ // Requited for the server side parsing and rendering
521
+ $options['block_id'] = $block_id;
522
+
523
+ $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
524
+ $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
525
+ $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
526
+ $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
527
+
528
+ $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
529
+
530
+ // Internal TD wrapper
531
+ $style = 'text-align: center; ';
532
+ $style .= 'width: 100% !important; ';
533
+ $style .= 'line-height: normal !important; ';
534
+ $style .= 'letter-spacing: normal; ';
535
+ $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
536
+ $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
537
+ $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
538
+ $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
539
+ $style .= 'background-color: ' . $block_background . ';';
540
+
541
+ if (isset($options['block_background_gradient'])) {
542
+ $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
543
+ }
544
+
545
+ $data = $this->options_encode($options);
546
+ // First time block creation wrapper
547
+ if ($wrapper) {
548
+ echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">', "\n";
549
+ echo "<tr>";
550
+ echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
551
+ }
552
+
553
+ // Container that fixes the width and makes the block responsive
554
+ echo $this->get_outlook_wrapper_open($options['block_width']);
555
+
556
+ echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $options['block_width'], 'px!important">', "\n";
557
+ echo "<tr>";
558
+ echo '<td align="', esc_attr($options['block_align']), '" style="', esc_attr($style), '" bgcolor="', esc_attr($block_background), '" width="100%">';
559
+
560
+ //echo "<!-- block generated content -->\n";
561
+ echo trim($content);
562
+ //echo "\n<!-- /block generated content -->\n";
563
+
564
+ echo "</td></tr></table>";
565
+ echo $this->get_outlook_wrapper_close();
566
+
567
+ // First time block creation wrapper
568
+ if ($wrapper) {
569
+ echo "</td></tr></table>";
570
+ }
571
+
572
+ return $out;
573
+ }
574
+
575
+ /**
576
+ * Ajax call to render a block with a new set of options after the settings popup
577
+ * has been saved.
578
+ *
579
+ * @param type $block_id
580
+ * @param type $wrapper
581
+ */
582
+ function tnpc_render_callback() {
583
+ if (!check_ajax_referer('save')) {
584
+ $this->dienow('Expired request');
585
+ }
586
+
587
+ $block_id = $_POST['id'];
588
+ $wrapper = isset($_POST['full']);
589
+ $options = $this->restore_options_from_request();
590
+
591
+ $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
592
+ wp_die();
593
+ }
594
+
595
+ function hook_wp_ajax_tnpc_regenerate_email() {
596
+
597
+ $content = stripslashes($_POST['content']);
598
+ $global_options = $_POST['composer'];
599
+
600
+ $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
601
+
602
+ wp_send_json_success([
603
+ 'content' => $regenerated_content,
604
+ 'message' => __('Successfully updated', 'newsletter')
605
+ ]);
606
+ }
607
+
608
+ private function regenerate_email_blocks($content, $global_options) {
609
+
610
+ $raw_block_options = $this->extract_encoded_blocks_options($content);
611
+
612
+ $regenerated_content = '';
613
+
614
+ foreach ($raw_block_options as $raw_block_option) {
615
+
616
+ /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
617
+ $block_options = $this->options_decode( $a ); */
618
+
619
+ $block_options = $this->options_decode($raw_block_option);
620
+
621
+ $block = $this->get_block($block_options['block_id']);
622
+ if (!$block) {
623
+ $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
624
+ }
625
+
626
+ ob_start();
627
+ $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
628
+ $block_html = ob_get_clean();
629
+
630
+ $regenerated_content .= $block_html;
631
+ }
632
+
633
+ return $regenerated_content;
634
+ }
635
+
636
+ /**
637
+ * @param string $html_email_content Email html content
638
+ *
639
+ * @return string[] Encoded options of email blocks
640
+ */
641
+ private function extract_encoded_blocks_options($html_email_content) {
642
+
643
+ preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
644
+
645
+ return $raw_block_options[1];
646
+ }
647
+
648
+ function tnpc_preview_callback() {
649
+ $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
650
+
651
+ if (empty($email)) {
652
+ echo 'Wrong email identifier';
653
+ return;
654
+ }
655
+
656
+ echo $email['message'];
657
+
658
+ wp_die();
659
+ }
660
+
661
+ function tnpc_css_callback() {
662
+ include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
663
+ wp_die();
664
+ }
665
+
666
+ /** Returns the correct admin page to edit the newsletter with the correct editor. */
667
+ function get_editor_url($email_id, $editor_type) {
668
+ switch ($editor_type) {
669
+ case NewsletterEmails::EDITOR_COMPOSER:
670
+ return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
671
+ case NewsletterEmails::EDITOR_HTML:
672
+ return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
673
+ case NewsletterEmails::EDITOR_TINYMCE:
674
+ return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
680
+ * or the targeting page (it depends on newsletter status).
681
+ *
682
+ * @param TNP_Email $email
683
+ */
684
+ function get_edit_button($email, $only_icon = false) {
685
+
686
+ $editor_type = $this->get_editor_type($email);
687
+ if ($email->status == 'new') {
688
+ $edit_url = $this->get_editor_url($email->id, $editor_type);
689
+ } else {
690
+ $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
691
+ }
692
+ switch ($editor_type) {
693
+ case NewsletterEmails::EDITOR_COMPOSER:
694
+ $icon_class = 'th-large';
695
+ break;
696
+ case NewsletterEmails::EDITOR_HTML:
697
+ $icon_class = 'code';
698
+ break;
699
+ default:
700
+ $icon_class = 'edit';
701
+ break;
702
+ }
703
+ if ($only_icon) {
704
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
705
+ '<i class="fas fa-' . $icon_class . '"></i></a>';
706
+ } else {
707
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
708
+ '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
709
+ }
710
+ }
711
+
712
+ /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
713
+ function get_editor_type($email) {
714
+ $email = (object) $email;
715
+ $editor_type = $email->editor;
716
+
717
+ // Backward compatibility
718
+ $email_options = maybe_unserialize($email->options);
719
+ if (isset($email_options['composer'])) {
720
+ $editor_type = NewsletterEmails::EDITOR_COMPOSER;
721
+ }
722
+ // End backward compatibility
723
+
724
+ return $editor_type;
725
+ }
726
+
727
+ /**
728
+ *
729
+ * @param type $action
730
+ * @param type $user
731
+ * @param type $email
732
+ * @return type
733
+ * @global wpdb $wpdb
734
+ */
735
+ function hook_newsletter_action($action, $user, $email) {
736
+ global $wpdb;
737
+
738
+ switch ($action) {
739
+ case 'v':
740
+ case 'view':
741
+ $id = $_GET['id'];
742
+ if ($id == 'last') {
743
+ $email = $wpdb->get_row("select * from " . NEWSLETTER_EMAILS_TABLE . " where private=0 and type='message' and status='sent' order by send_on desc limit 1");
744
+ } else {
745
+ $email = $this->get_email($_GET['id']);
746
+ }
747
+ if (empty($email)) {
748
+ header("HTTP/1.0 404 Not Found");
749
+ die('Email not found');
750
+ }
751
+
752
+ if (!Newsletter::instance()->is_allowed()) {
753
+
754
+ if ($email->status == 'new') {
755
+ header("HTTP/1.0 404 Not Found");
756
+ die('Not sent yet');
757
+ }
758
+
759
+ if ($email->private == 1) {
760
+ if (!$user) {
761
+ header("HTTP/1.0 404 Not Found");
762
+ die('No available for online view');
763
+ }
764
+ $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
765
+ if (!$sent) {
766
+ header("HTTP/1.0 404 Not Found");
767
+ die('No available for online view');
768
+ }
769
+ }
770
+ }
771
+
772
+
773
+ header('Content-Type: text/html;charset=UTF-8');
774
+ header('X-Robots-Tag: noindex,nofollow,noarchive');
775
+ header('Cache-Control: no-cache,no-store,private');
776
+
777
+ echo $this->replace($email->message, $user, $email);
778
+
779
+ die();
780
+ break;
781
+
782
+ case 'emails-css':
783
+ $email_id = (int) $_GET['id'];
784
+
785
+ $body = Newsletter::instance()->get_email_field($email_id, 'message');
786
+
787
+ $x = strpos($body, '<style');
788
+ if ($x === false)
789
+ return;
790
+
791
+ $x = strpos($body, '>', $x);
792
+ $y = strpos($body, '</style>');
793
+
794
+ header('Content-Type: text/css;charset=UTF-8');
795
+
796
+ echo substr($body, $x + 1, $y - $x - 1);
797
+
798
+ die();
799
+ break;
800
+
801
+ case 'emails-composer-css':
802
+ header('Cache: no-cache');
803
+ header('Content-Type: text/css');
804
+ echo $this->get_composer_css();
805
+ die();
806
+ break;
807
+
808
+ case 'emails-preview':
809
+ if (!Newsletter::instance()->is_allowed()) {
810
+ die('Not enough privileges');
811
+ }
812
+
813
+ if (!check_admin_referer('view')) {
814
+ die();
815
+ }
816
+
817
+ $theme_id = $_GET['id'];
818
+ $theme = $this->themes->get_theme($theme_id);
819
+
820
+ // Used by theme code
821
+ $theme_options = $this->themes->get_options($theme_id);
822
+
823
+ $theme_url = $theme['url'];
824
+
825
+ header('Content-Type: text/html;charset=UTF-8');
826
+
827
+ include $theme['dir'] . '/theme.php';
828
+
829
+ die();
830
+ break;
831
+
832
+ case 'emails-preview-text':
833
+ header('Content-Type: text/plain;charset=UTF-8');
834
+ if (!Newsletter::instance()->is_allowed()) {
835
+ die('Not enough privileges');
836
+ }
837
+
838
+ if (!check_admin_referer('view')) {
839
+ die();
840
+ }
841
+
842
+ // Used by theme code
843
+ $theme_options = $this->get_current_theme_options();
844
+
845
+ $file = include $theme['dir'] . '/theme-text.php';
846
+
847
+ if (is_file($file)) {
848
+ include $file;
849
+ }
850
+
851
+ die();
852
+ break;
853
+
854
+ case 'emails-create':
855
+ // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
856
+ // excerpt, thumbnail are extracted.
857
+ if (!Newsletter::instance()->is_allowed()) {
858
+ die('Not enough privileges');
859
+ }
860
+
861
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
862
+ $controls = new NewsletterControls();
863
+
864
+ if (!$controls->is_action('create')) {
865
+ die('Wrong call');
866
+ }
867
+
868
+ $theme_id = $controls->data['id'];
869
+ $theme = $this->themes->get_theme($theme_id);
870
+
871
+ if (!$theme) {
872
+ die('invalid theme');
873
+ }
874
+
875
+ $this->themes->save_options($theme_id, $controls->data);
876
+
877
+ $email = array();
878
+ $email['status'] = 'new';
879
+ $email['subject'] = ''; //__('Here the email subject', 'newsletter');
880
+ $email['track'] = Newsletter::instance()->options['track'];
881
+ $email['send_on'] = time();
882
+ $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
883
+ $email['type'] = 'message';
884
+
885
+ $theme_options = $this->themes->get_options($theme_id);
886
+
887
+ $theme_url = $theme['url'];
888
+ $theme_subject = '';
889
+
890
+ ob_start();
891
+ include $theme['dir'] . '/theme.php';
892
+ $email['message'] = ob_get_clean();
893
+
894
+ if (!empty($theme_subject)) {
895
+ $email['subject'] = $theme_subject;
896
+ }
897
+
898
+ if (file_exists($theme['dir'] . '/theme-text.php')) {
899
+ ob_start();
900
+ include $theme['dir'] . '/theme-text.php';
901
+ $email['message_text'] = ob_get_clean();
902
+ } else {
903
+ $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
904
+ }
905
+
906
+ $email = $this->save_email($email);
907
+
908
+ $edit_url = $this->get_editor_url($email->id, $email->editor);
909
+
910
+ header('Location: ' . $edit_url);
911
+
912
+ die();
913
+ break;
914
+ }
915
+ }
916
+
917
+ function admin_menu() {
918
+ $this->add_menu_page('index', 'Newsletters');
919
+ $this->add_admin_page('list', 'Email List');
920
+ $this->add_admin_page('new', 'Email New');
921
+ $this->add_admin_page('edit', 'Email Edit');
922
+ $this->add_admin_page('theme', 'Email Themes');
923
+ $this->add_admin_page('composer', 'The Composer');
924
+ $this->add_admin_page('editorhtml', 'HTML Editor');
925
+ $this->add_admin_page('editortinymce', 'TinyMCE Editor');
926
+ }
927
+
928
+ /**
929
+ * Builds a block data structure starting from the folder containing the block
930
+ * files.
931
+ *
932
+ * @param string $dir
933
+ * @return array | WP_Error
934
+ */
935
+ function build_block($dir) {
936
+ $dir = realpath($dir);
937
+ $dir = wp_normalize_path($dir);
938
+ $full_file = $dir . '/block.php';
939
+ if (!is_file($full_file)) {
940
+ return new WP_Error('1', 'Missing block.php file in ' . $dir);
941
+ }
942
+
943
+ $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
944
+ $file = basename($dir);
945
+
946
+ $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
947
+ $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
948
+ $data = array_merge($defaults, $data);
949
+
950
+ if (is_file($dir . '/icon.png')) {
951
+ $data['icon'] = content_url($relative_dir . '/icon.png');
952
+ }
953
+
954
+ $data['id'] = sanitize_key($file);
955
+
956
+ // Absolute path of the block files
957
+ $data['dir'] = $dir;
958
+ $data['url'] = content_url($relative_dir);
959
+
960
+ return $data;
961
+ }
962
+
963
+ /**
964
+ *
965
+ * @param type $dir
966
+ * @return type
967
+ */
968
+ function scan_blocks_dir($dir) {
969
+ $dir = realpath($dir);
970
+ if (!$dir) {
971
+ return [];
972
+ }
973
+ $dir = wp_normalize_path($dir);
974
+
975
+ $list = [];
976
+ $handle = opendir($dir);
977
+ while ($file = readdir($handle)) {
978
+
979
+ $data = $this->build_block($dir . '/' . $file);
980
+
981
+ if (is_wp_error($data)) {
982
+ $this->logger->error($data);
983
+ continue;
984
+ }
985
+ $list[$data['id']] = $data;
986
+ }
987
+ closedir($handle);
988
+ return $list;
989
+ }
990
+
991
+ /**
992
+ * Array of arrays with every registered block and legacy block converted to the new
993
+ * format.
994
+ *
995
+ * @return array
996
+ */
997
+ function get_blocks() {
998
+
999
+ if (!is_null($this->blocks)) {
1000
+ return $this->blocks;
1001
+ }
1002
+
1003
+ $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1004
+
1005
+ $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1006
+
1007
+ $this->blocks = array_merge($extended, $this->blocks);
1008
+
1009
+ $dirs = apply_filters('newsletter_blocks_dir', array());
1010
+
1011
+ //$this->logger->debug('Block dirs:');
1012
+ //$this->logger->debug($dirs);
1013
+
1014
+ foreach ($dirs as $dir) {
1015
+ $list = $this->scan_blocks_dir($dir);
1016
+ $this->blocks = array_merge($list, $this->blocks);
1017
+ }
1018
+
1019
+ do_action('newsletter_register_blocks');
1020
+
1021
+ foreach (TNP_Composer::$block_dirs as $dir) {
1022
+ $block = $this->build_block($dir);
1023
+ if (is_wp_error($block)) {
1024
+ $this->logger->error($block);
1025
+ continue;
1026
+ }
1027
+ if (!isset($this->blocks[$block['id']])) {
1028
+ $this->blocks[$block['id']] = $block;
1029
+ } else {
1030
+ $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1031
+ }
1032
+ }
1033
+
1034
+ $this->blocks = array_reverse($this->blocks);
1035
+ return $this->blocks;
1036
+ }
1037
+
1038
+ /**
1039
+ * Return a single block (associative array) checking for legacy ID as well.
1040
+ *
1041
+ * @param string $id
1042
+ * @return array
1043
+ */
1044
+ function get_block($id) {
1045
+ switch ($id) {
1046
+ case 'content-03-text.block':
1047
+ $id = 'text';
1048
+ break;
1049
+ case 'footer-03-social.block':
1050
+ $id = 'social';
1051
+ break;
1052
+ case 'footer-02-canspam.block':
1053
+ $id = 'canspam';
1054
+ break;
1055
+ case 'content-05-image.block':
1056
+ $id = 'image';
1057
+ break;
1058
+ case 'header-01-header.block':
1059
+ $id = 'header';
1060
+ break;
1061
+ case 'footer-01-footer.block':
1062
+ $id = 'footer';
1063
+ break;
1064
+ case 'content-02-heading.block':
1065
+ $id = 'heading';
1066
+ break;
1067
+ case 'content-07-twocols.block':
1068
+ case 'content-06-posts.block':
1069
+ $id = 'posts';
1070
+ break;
1071
+ case 'content-04-cta.block':
1072
+ $id = 'cta';
1073
+ break;
1074
+ case 'content-01-hero.block':
1075
+ $id = 'hero';
1076
+ break;
1077
+ // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1078
+ // break;
1079
+ }
1080
+
1081
+ // Conversion for old full path ID
1082
+ $id = sanitize_key(basename($id));
1083
+
1084
+ // TODO: Correct id for compatibility
1085
+ $blocks = $this->get_blocks();
1086
+ if (!isset($blocks[$id])) {
1087
+ return null;
1088
+ }
1089
+ return $blocks[$id];
1090
+ }
1091
+
1092
+ function scan_presets_dir($dir = null) {
1093
+
1094
+ if (is_null($dir)) {
1095
+ $dir = __DIR__ . '/presets';
1096
+ }
1097
+
1098
+ if (!is_dir($dir)) {
1099
+ return array();
1100
+ }
1101
+
1102
+ $handle = opendir($dir);
1103
+ $list = array();
1104
+ $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1105
+ while ($file = readdir($handle)) {
1106
+
1107
+ if ($file == '.' || $file == '..')
1108
+ continue;
1109
+
1110
+ // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1111
+ $preset_id = sanitize_key($file);
1112
+
1113
+ $full_file = $dir . '/' . $file . '/preset.json';
1114
+
1115
+ if (!is_file($full_file)) {
1116
+ continue;
1117
+ }
1118
+
1119
+ $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1120
+
1121
+ $list[$preset_id] = $icon;
1122
+ }
1123
+ closedir($handle);
1124
+ return $list;
1125
+ }
1126
+
1127
+ function get_preset_from_file($id, $dir = null) {
1128
+
1129
+ if (is_null($dir)) {
1130
+ $dir = __DIR__ . '/presets';
1131
+ }
1132
+
1133
+ $id = $this->sanitize_file_name($id);
1134
+
1135
+ if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1136
+ return array();
1137
+ }
1138
+
1139
+ $json_content = file_get_contents("$dir/$id/preset.json");
1140
+ $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1141
+ $json = json_decode($json_content);
1142
+ $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1143
+
1144
+ return $json;
1145
+ }
1146
+
1147
+ function get_composer_css() {
1148
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1149
+ $blocks = $this->get_blocks();
1150
+ foreach ($blocks as $block) {
1151
+ if (!file_exists($block['dir'] . '/style.css')) {
1152
+ continue;
1153
+ }
1154
+ $css .= "\n\n";
1155
+ $css .= "/* " . $block['name'] . " */\n";
1156
+ $css .= file_get_contents($block['dir'] . '/style.css');
1157
+ }
1158
+ return $css;
1159
+ }
1160
+
1161
+ function get_composer_backend_css() {
1162
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1163
+ $css .= "\n\n";
1164
+ $css .= $this->get_composer_css();
1165
+ return $css;
1166
+ }
1167
+
1168
+ /**
1169
+ * Send an email to the test subscribers.
1170
+ *
1171
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1172
+ * @param NewsletterControls $controls
1173
+ */
1174
+ function send_test_email($email, $controls) {
1175
+ if (!$email) {
1176
+ $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1177
+ return;
1178
+ }
1179
+
1180
+ $original_subject = $email->subject;
1181
+ $this->set_test_subject_to($email);
1182
+
1183
+ $users = NewsletterUsers::instance()->get_test_users();
1184
+ if (count($users) == 0) {
1185
+ $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1186
+ '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1187
+ __('Read more', 'newsletter') . '</strong></a>.';
1188
+ } else {
1189
+ $r = Newsletter::instance()->send($email, $users, true);
1190
+ $emails = array();
1191
+ foreach ($users as $user) {
1192
+ $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1193
+ }
1194
+ if (is_wp_error($r)) {
1195
+ $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1196
+ $controls->errors .= __('Test subscribers:', 'newsletter');
1197
+ $controls->errors .= ' ' . implode(', ', $emails);
1198
+ $controls->errors .= '<br>';
1199
+ $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1200
+ $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1201
+ } else {
1202
+ $controls->messages = __('Test subscribers:', 'newsletter');
1203
+
1204
+ $controls->messages .= ' ' . implode(', ', $emails);
1205
+ $controls->messages .= '.<br>';
1206
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1207
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1208
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1209
+ }
1210
+ }
1211
+ $email->subject = $original_subject;
1212
+ }
1213
+
1214
+ /**
1215
+ * Send an email to the test subscribers.
1216
+ *
1217
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1218
+ * @param string $email_address
1219
+ *
1220
+ * @throws Exception
1221
+ */
1222
+ function send_test_newsletter_to_email_address($email, $email_address) {
1223
+
1224
+ if (!$email) {
1225
+ throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1226
+ }
1227
+
1228
+ $this->set_test_subject_to($email);
1229
+
1230
+ $dummy_subscriber = $this->make_dummy_subscriber();
1231
+ $dummy_subscriber->email = $email_address;
1232
+
1233
+ $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1234
+
1235
+ $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1236
+
1237
+ if (is_wp_error($result)) {
1238
+ $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1239
+ $error_message .= __('Test subscribers:', 'newsletter');
1240
+ $error_message .= ' ' . $email;
1241
+ $error_message .= '<br>';
1242
+ $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1243
+ $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1244
+ throw new Exception($error_message);
1245
+ }
1246
+
1247
+ $messages = __('Test subscribers:', 'newsletter');
1248
+
1249
+ $messages .= ' ' . $email;
1250
+ $messages .= '.<br>';
1251
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1252
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1253
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1254
+
1255
+ return $messages;
1256
+ }
1257
+
1258
+ private function set_test_subject_to($email) {
1259
+ if ($email->subject == '') {
1260
+ $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1261
+ } else {
1262
+ $email->subject = $email->subject . ' (TEST)';
1263
+ }
1264
+ }
1265
+
1266
+ private function make_dummy_subscriber() {
1267
+ $dummy_user = new TNP_User();
1268
+ $dummy_user->id = 0;
1269
+ $dummy_user->email = 'john.doe@example.org';
1270
+ $dummy_user->name = 'John';
1271
+ $dummy_user->surname = 'Doe';
1272
+ $dummy_user->sex = 'n';
1273
+ $dummy_user->language = '';
1274
+ $dummy_user->ip = '';
1275
+
1276
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1277
+ $profile_key = "profile_$i";
1278
+ $dummy_user->$profile_key = '';
1279
+ }
1280
+
1281
+ return $dummy_user;
1282
+ }
1283
+
1284
+ function restore_options_from_request() {
1285
+
1286
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1287
+ $controls = new NewsletterControls();
1288
+ $options = $controls->data;
1289
+
1290
+ if (isset($_POST['options']) && is_array($_POST['options'])) {
1291
+ // Get all block options
1292
+ //$options = stripslashes_deep($_POST['options']);
1293
+ // Deserialize inline edits when
1294
+ // render is preformed on saving block options
1295
+ if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1296
+ $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1297
+ }
1298
+
1299
+ // Restore inline edits from data-json
1300
+ // coming from inline editing
1301
+ // and merge with current inline edit
1302
+ if (isset($_POST['encoded_options'])) {
1303
+ $decoded_options = $this->options_decode($_POST['encoded_options']);
1304
+
1305
+ $to_merge_inline_edits = [];
1306
+
1307
+ if (isset($decoded_options['inline_edits'])) {
1308
+ foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1309
+ $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1310
+ }
1311
+ }
1312
+
1313
+ //Overwrite with new edited content
1314
+ if (isset($options['inline_edits'])) {
1315
+ foreach ($options['inline_edits'] as $inline_edit) {
1316
+ $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1317
+ }
1318
+ }
1319
+
1320
+ $options['inline_edits'] = array_values($to_merge_inline_edits);
1321
+ $options = array_merge($decoded_options, $options);
1322
+ }
1323
+
1324
+ return $options;
1325
+ }
1326
+
1327
+ return array();
1328
+ }
1329
+
1330
+ public function hook_wp_ajax_tnpc_delete_preset() {
1331
+
1332
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1333
+ wp_send_json_error('Expired request');
1334
+ }
1335
+
1336
+ $preset_id = (int) $_REQUEST['presetId'];
1337
+
1338
+ $newsletter = Newsletter::instance();
1339
+
1340
+ if ($preset_id > 0) {
1341
+ $preset = $newsletter->get_email($preset_id);
1342
+
1343
+ if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1344
+ Newsletter::instance()->delete_email($preset_id);
1345
+ wp_send_json_success();
1346
+ } else {
1347
+ wp_send_json_error(__('Is not a preset!', 'newsletter'));
1348
+ }
1349
+ } else {
1350
+ wp_send_json_error();
1351
+ }
1352
+ }
1353
+
1354
+ }
1355
+
1356
+ NewsletterEmails::instance();
emails/index.php CHANGED
@@ -1,123 +1,123 @@
1
- <?php
2
- /* @var $this NewsletterEmails */
3
- defined('ABSPATH') || exit;
4
-
5
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
- require_once NEWSLETTER_INCLUDES_DIR . '/paginator.php';
7
-
8
- $controls = new NewsletterControls();
9
-
10
- if ($controls->is_action('copy')) {
11
- $original = Newsletter::instance()->get_email($_POST['btn']);
12
- $email = array();
13
- $email['subject'] = $original->subject;
14
- $email['message'] = $original->message;
15
- $email['message_text'] = $original->message_text;
16
- $email['send_on'] = time();
17
- $email['type'] = 'message';
18
- $email['editor'] = $original->editor;
19
- $email['track'] = $original->track;
20
- $email['options'] = $original->options;
21
-
22
- $this->save_email($email);
23
- $controls->messages .= __('Message duplicated.', 'newsletter');
24
- }
25
-
26
- if ($controls->is_action('delete')) {
27
- $this->delete_email($_POST['btn']);
28
- $controls->add_message_deleted();
29
- }
30
-
31
- if ($controls->is_action('delete_selected')) {
32
- $r = Newsletter::instance()->delete_email($_POST['ids']);
33
- $controls->messages .= $r . ' message(s) deleted';
34
- }
35
-
36
- $pagination_controller = new TNP_Pagination_Controller(NEWSLETTER_EMAILS_TABLE, 'id', ['type' => 'message']);
37
- $emails = $pagination_controller->get_items();
38
- ?>
39
-
40
- <div class="wrap tnp-emails tnp-emails-index" id="tnp-wrap">
41
-
42
- <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
43
-
44
- <div id="tnp-heading">
45
-
46
- <h2><?php _e('Newsletters', 'newsletter') ?></h2>
47
-
48
- </div>
49
-
50
- <div id="tnp-body">
51
-
52
- <form method="post" action="">
53
- <?php $controls->init(); ?>
54
-
55
- <a href="<?php echo $this->get_admin_page_url('composer'); ?>" class="button-primary"><?php _e('New newsletter', 'newsletter') ?></a>
56
-
57
- <?php $controls->button_confirm('delete_selected', __('Delete selected newsletters', 'newsletter')); ?>
58
-
59
- <?php $pagination_controller->display_paginator(); ?>
60
-
61
- <table class="widefat tnp-newsletters-list" style="width: 100%">
62
- <thead>
63
- <tr>
64
- <th><input type="checkbox" onchange="jQuery('input.tnp-selector').prop('checked', this.checked)"></th>
65
- <th>Id</th>
66
- <th><?php _e('Subject', 'newsletter') ?></th>
67
- <th><?php _e('Status', 'newsletter') ?></th>
68
- <th><?php _e('Progress', 'newsletter') ?>&nbsp;(*)</th>
69
- <th><?php _e('Date', 'newsletter') ?></th>
70
- <th>&nbsp;</th>
71
- <th>&nbsp;</th>
72
- <th>&nbsp;</th>
73
- <th>&nbsp;</th>
74
- <th>&nbsp;</th>
75
- </tr>
76
- </thead>
77
-
78
- <tbody>
79
- <?php foreach ($emails as $email) { ?>
80
- <tr>
81
- <td><input type="checkbox" class="tnp-selector" name="ids[]" value="<?php echo $email->id; ?>"/></td>
82
- <td><?php echo $email->id; ?></td>
83
- <td><?php
84
- if ($email->subject)
85
- echo htmlspecialchars($email->subject);
86
- else
87
- echo "Newsletter #" . $email->id;
88
- ?>
89
- </td>
90
-
91
- <td>
92
- <?php $this->show_email_status_label($email) ?>
93
- </td>
94
- <td>
95
- <?php $this->show_email_progress_bar($email, array('numbers' => true)) ?>
96
- </td>
97
- <td><?php if ($email->status == 'sent' || $email->status == 'sending') echo $this->format_date($email->send_on); ?></td>
98
- <td>
99
- <?php echo $this->get_edit_button($email) ?>
100
- </td>
101
-
102
- <td style="white-space: nowrap">
103
- <?php $controls->button_icon_statistics(NewsletterStatistics::instance()->get_statistics_url($email->id)) ?>
104
- <?php $controls->button_icon_view(home_url('/') . '?na=view&id=' . $email->id) ?>
105
- </td>
106
-
107
- <td style="white-space: nowrap">
108
- <?php $controls->button_icon_copy($email->id); ?>
109
- <?php $controls->button_icon_delete($email->id); ?>
110
- </td>
111
- </tr>
112
- <?php } ?>
113
- </tbody>
114
- </table>
115
- <p>
116
- (*) <?php _e('Expected total at the end of the delivery may differ due to subscriptions/unsubscriptions occurred meanwhile.', 'newsletter') ?>
117
- </p>
118
- </form>
119
- </div>
120
-
121
- <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
122
-
123
- </div>
1
+ <?php
2
+ /* @var $this NewsletterEmails */
3
+ defined('ABSPATH') || exit;
4
+
5
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
+ require_once NEWSLETTER_INCLUDES_DIR . '/paginator.php';
7
+
8
+ $controls = new NewsletterControls();
9
+
10
+ if ($controls->is_action('copy')) {
11
+ $original = Newsletter::instance()->get_email($_POST['btn']);
12
+ $email = array();
13
+ $email['subject'] = $original->subject;
14
+ $email['message'] = $original->message;
15
+ $email['message_text'] = $original->message_text;
16
+ $email['send_on'] = time();
17
+ $email['type'] = 'message';
18
+ $email['editor'] = $original->editor;
19
+ $email['track'] = $original->track;
20
+ $email['options'] = $original->options;
21
+
22
+ $this->save_email($email);
23
+ $controls->messages .= __('Message duplicated.', 'newsletter');
24
+ }
25
+
26
+ if ($controls->is_action('delete')) {
27
+ $this->delete_email($_POST['btn']);
28
+ $controls->add_message_deleted();
29
+ }
30
+
31
+ if ($controls->is_action('delete_selected')) {
32
+ $r = Newsletter::instance()->delete_email($_POST['ids']);
33
+ $controls->messages .= $r . ' message(s) deleted';
34
+ }
35
+
36
+ $pagination_controller = new TNP_Pagination_Controller(NEWSLETTER_EMAILS_TABLE, 'id', ['type' => 'message']);
37
+ $emails = $pagination_controller->get_items();
38
+ ?>
39
+
40
+ <div class="wrap tnp-emails tnp-emails-index" id="tnp-wrap">
41
+
42
+ <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
43
+
44
+ <div id="tnp-heading">
45
+
46
+ <h2><?php _e('Newsletters', 'newsletter') ?></h2>
47
+
48
+ </div>
49
+
50
+ <div id="tnp-body">
51
+
52
+ <form method="post" action="">
53
+ <?php $controls->init(); ?>
54
+
55
+ <a href="<?php echo $this->get_admin_page_url('composer'); ?>" class="button-primary"><?php _e('New newsletter', 'newsletter') ?></a>
56
+
57
+ <?php $controls->button_confirm('delete_selected', __('Delete selected newsletters', 'newsletter')); ?>
58
+
59
+ <?php $pagination_controller->display_paginator(); ?>
60
+
61
+ <table class="widefat tnp-newsletters-list" style="width: 100%">
62
+ <thead>
63
+ <tr>
64
+ <th><input type="checkbox" onchange="jQuery('input.tnp-selector').prop('checked', this.checked)"></th>
65
+ <th>Id</th>
66
+ <th><?php _e('Subject', 'newsletter') ?></th>
67
+ <th><?php _e('Status', 'newsletter') ?></th>
68
+ <th><?php _e('Progress', 'newsletter') ?>&nbsp;(*)</th>
69
+ <th><?php _e('Date', 'newsletter') ?></th>
70
+ <th>&nbsp;</th>
71
+ <th>&nbsp;</th>
72
+ <th>&nbsp;</th>
73
+ <th>&nbsp;</th>
74
+ <th>&nbsp;</th>
75
+ </tr>
76
+ </thead>
77
+
78
+ <tbody>
79
+ <?php foreach ($emails as $email) { ?>
80
+ <tr>
81
+ <td><input type="checkbox" class="tnp-selector" name="ids[]" value="<?php echo $email->id; ?>"/></td>
82
+ <td><?php echo $email->id; ?></td>
83
+ <td><?php
84
+ if ($email->subject)
85
+ echo htmlspecialchars($email->subject);
86
+ else
87
+ echo "Newsletter #" . $email->id;
88
+ ?>
89
+ </td>
90
+
91
+ <td>
92
+ <?php $this->show_email_status_label($email) ?>
93
+ </td>
94
+ <td>
95
+ <?php $this->show_email_progress_bar($email, array('numbers' => true)) ?>
96
+ </td>
97
+ <td><?php if ($email->status == 'sent' || $email->status == 'sending') echo $this->format_date($email->send_on); ?></td>
98
+ <td>
99
+ <?php echo $this->get_edit_button($email) ?>
100
+ </td>
101
+
102
+ <td style="white-space: nowrap">
103
+ <?php $controls->button_icon_statistics(NewsletterStatistics::instance()->get_statistics_url($email->id)) ?>
104
+ <?php $controls->button_icon_view(home_url('/') . '?na=view&id=' . $email->id) ?>
105
+ </td>
106
+
107
+ <td style="white-space: nowrap">
108
+ <?php $controls->button_icon_copy($email->id); ?>
109
+ <?php $controls->button_icon_delete($email->id); ?>
110
+ </td>
111
+ </tr>
112
+ <?php } ?>
113
+ </tbody>
114
+ </table>
115
+ <p>
116
+ (*) <?php _e('Expected total at the end of the delivery may differ due to subscriptions/unsubscriptions occurred meanwhile.', 'newsletter') ?>
117
+ </p>
118
+ </form>
119
+ </div>
120
+
121
+ <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
122
+
123
+ </div>
emails/tnp-composer/_scripts/newsletter-builder-v2.js CHANGED
@@ -1,952 +1,952 @@
1
- // add delete buttons
2
- jQuery.fn.add_delete = function () {
3
- this.append('<div class="tnpc-row-delete" title="Delete"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/delete.png" width="32"></div>');
4
- this.find('.tnpc-row-delete').perform_delete();
5
- };
6
-
7
- // delete row
8
- jQuery.fn.perform_delete = function () {
9
- this.click(function () {
10
- tnpc_hide_block_options();
11
- // remove block
12
- jQuery(this).parent().remove();
13
- tnpc_mobile_preview();
14
- });
15
- }
16
-
17
- // add edit button
18
- jQuery.fn.add_block_edit = function () {
19
- this.append('<div class="tnpc-row-edit-block" title="Edit"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/edit.png" width="32"></div>');
20
- this.find('.tnpc-row-edit-block').perform_block_edit();
21
- }
22
-
23
- // edit block
24
- jQuery.fn.perform_block_edit = function () {
25
-
26
- jQuery(".tnpc-row-edit-block").click(function (e) {
27
- e.preventDefault()
28
- });
29
-
30
- this.click(function (e) {
31
-
32
- e.preventDefault();
33
-
34
- target = jQuery(this).parent().find('.edit-block');
35
-
36
- // The row container which is a global variable and used later after the options save
37
- container = jQuery(this).closest("table");
38
-
39
- if (container.hasClass('tnpc-row-block')) {
40
-
41
- tnpc_show_block_options();
42
-
43
- var options = container.find(".tnpc-block-content").attr("data-json");
44
-
45
- // Compatibility
46
- if (!options) {
47
- options = target.attr("data-options");
48
- }
49
-
50
- var data = {
51
- action: "tnpc_options",
52
- id: container.data("id"),
53
- context_type: tnp_context_type,
54
- options: options
55
- };
56
-
57
- tnpc_add_global_options(data);
58
-
59
- builderAreaHelper.lock();
60
- jQuery("#tnpc-block-options-form").load(ajaxurl, data, function () {
61
- console.log('Block form options loaded');
62
- start_options = jQuery("#tnpc-block-options-form").serializeArray();
63
- tnpc_add_global_options(start_options);
64
- builderAreaHelper.unlock();
65
- });
66
-
67
- } else {
68
- alert("This is deprecated block version and cannot be edited. Please replace it with a new one.");
69
- }
70
-
71
- });
72
-
73
- };
74
-
75
- // add clone button
76
- jQuery.fn.add_block_clone = function () {
77
- this.append('<div class="tnpc-row-clone" title="Clone"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/copy.png" width="32"></div>');
78
- this.find('.tnpc-row-clone').perform_clone();
79
- }
80
-
81
- // clone block
82
- jQuery.fn.perform_clone = function () {
83
-
84
- jQuery(".tnpc-row-clone").click(function (e) {
85
- e.preventDefault()
86
- });
87
-
88
- this.click(function (e) {
89
-
90
- e.preventDefault();
91
-
92
- // hide block edit form
93
- tnpc_hide_block_options();
94
-
95
- // find the row
96
- let row = jQuery(this).closest('.tnpc-row');
97
-
98
- // clone the block
99
- let new_row = row.clone();
100
- new_row.find(".tnpc-row-delete").remove();
101
- new_row.find(".tnpc-row-edit-block").remove();
102
- new_row.find(".tnpc-row-clone").remove();
103
-
104
- new_row.add_delete();
105
- new_row.add_block_edit();
106
- new_row.add_block_clone();
107
- // if (new_row.hasClass('tnpc-row-block')) {
108
- // new_row.find(".tnpc-row-edit-block i").click();
109
- // }
110
- new_row.insertAfter(row);
111
- tnpc_mobile_preview();
112
- });
113
- };
114
-
115
- let start_options = null;
116
- let container = null;
117
-
118
- jQuery(function () {
119
-
120
- // open blocks tab
121
- document.getElementById("defaultOpen").click();
122
-
123
- // preload content from a body named input
124
- var preloadedContent = jQuery('input[name="message"]').val();
125
- if (!preloadedContent) {
126
- preloadedContent = jQuery('input[name="options[message]"]').val();
127
- }
128
-
129
- if (!preloadedContent) {
130
- tnpc_show_presets_modal();
131
- } else {
132
- jQuery('#newsletter-builder-area-center-frame-content').html(preloadedContent);
133
- start_composer();
134
- }
135
-
136
- // subject management
137
- jQuery('#options-subject').val(jQuery('#tnpc-form input[name="options[subject]"]').val());
138
-
139
- // preheader management
140
- jQuery('#options-preheader').val(jQuery('#tnpc-form input[name="options[options_preheader]"]').val());
141
-
142
- // ======================== //
143
- // == BACKGROUND COLOR == //
144
- // ======================== //
145
- _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
146
-
147
- function _setBuilderAreaBackgroundColor(color) {
148
- jQuery('#newsletter-builder-area-center-frame-content').css('background-color', color);
149
- }
150
-
151
- window._setBuilderAreaBackgroundColor = _setBuilderAreaBackgroundColor; //BAD STUFF!!!
152
-
153
- // ======================== //
154
- // == BACKGROUND COLOR == //
155
- // ======================== //
156
-
157
- });
158
-
159
- function BuilderAreaHelper() {
160
-
161
- var _builderAreaEl = document.querySelector('#newsletter-builder-area');
162
- var _overlayEl = document.createElement('div');
163
- _overlayEl.style.zIndex = 99999;
164
- _overlayEl.style.position = 'absolute';
165
- _overlayEl.style.top = 0;
166
- _overlayEl.style.left = 0;
167
- _overlayEl.style.width = '100%';
168
- _overlayEl.style.height = '100%';
169
-
170
- this.lock = function () {
171
- console.log('Lock builder area');
172
- _builderAreaEl.appendChild(_overlayEl);
173
- }
174
-
175
- this.unlock = function () {
176
- console.log('Unlock builder area');
177
- _builderAreaEl.removeChild(_overlayEl);
178
- }
179
-
180
- }
181
-
182
- let builderAreaHelper = new BuilderAreaHelper();
183
-
184
- function init_builder_area() {
185
-
186
- //Drag & Drop
187
- jQuery("#newsletter-builder-area-center-frame-content").sortable({
188
- revert: false,
189
- placeholder: "placeholder",
190
- forcePlaceholderSize: true,
191
- opacity: 0.6,
192
- tolerance: "pointer",
193
- helper: function (e) {
194
- var helper = jQuery(document.getElementById("sortable-helper")).clone();
195
- return helper;
196
- },
197
- update: function (event, ui) {
198
- if (ui.item.attr("id") == "draggable-helper") {
199
- loading_row = jQuery('<div style="text-align: center; padding: 20px; background-color: #d4d5d6; color: #52BE7F;"><i class="fa fa-cog fa-2x fa-spin" /></div>');
200
- ui.item.before(loading_row);
201
- ui.item.remove();
202
- var data = new Array(
203
- {"name": 'action', "value": 'tnpc_render'},
204
- {"name": 'id', "value": ui.item.data("id")},
205
- {"name": 'b', "value": ui.item.data("id")},
206
- {"name": 'full', "value": 1},
207
- {"name": '_wpnonce', "value": tnp_nonce}
208
- );
209
-
210
- tnpc_add_global_options(data);
211
-
212
- jQuery.post(ajaxurl, data, function (response) {
213
-
214
- var new_row = jQuery(response);
215
- // ui.item.before(new_row);
216
- // ui.item.remove();
217
- loading_row.before(new_row);
218
- loading_row.remove();
219
- new_row.add_delete();
220
- new_row.add_block_edit();
221
- new_row.add_block_clone();
222
- // new_row.find(".tnpc-row-edit").hover_edit();
223
- if (new_row.hasClass('tnpc-row-block')) {
224
- new_row.find(".tnpc-row-edit-block").click();
225
- }
226
- tnpc_mobile_preview();
227
- }).fail(function () {
228
- alert("Block rendering failed.");
229
- loading_row.remove();
230
- });
231
- } else {
232
- tnpc_mobile_preview();
233
- }
234
- }
235
- });
236
-
237
- jQuery(".newsletter-sidebar-buttons-content-tab").draggable({
238
- connectToSortable: "#newsletter-builder-area-center-frame-content",
239
-
240
- // Build the helper for dragging
241
- helper: function (e) {
242
- var helper = jQuery(document.getElementById("draggable-helper")).clone();
243
- // Do not uset .data() with jQuery
244
- helper.attr("data-id", e.currentTarget.dataset.id);
245
- helper.html(e.currentTarget.dataset.name);
246
- return helper;
247
- },
248
- revert: false,
249
- start: function () {
250
- if (jQuery('.tnpc-row').length) {
251
- } else {
252
- jQuery('#newsletter-builder-area-center-frame-content').append('<div class="tnpc-drop-here">Drag&Drop blocks here!</div>');
253
- }
254
- },
255
- stop: function (event, ui) {
256
- jQuery('.tnpc-drop-here').remove();
257
- }
258
- });
259
-
260
- jQuery(".tnpc-row").add_delete();
261
- jQuery(".tnpc-row").add_block_edit();
262
- jQuery(".tnpc-row").add_block_clone();
263
-
264
- }
265
-
266
- function start_composer() {
267
-
268
- init_builder_area();
269
-
270
- // Closes the block options layer (without saving)
271
- jQuery("#tnpc-block-options-cancel").click(function () {
272
-
273
- tnpc_hide_block_options();
274
-
275
- var _target = target;
276
-
277
- jQuery.post(ajaxurl, start_options, function (response) {
278
- _target.html(response);
279
- jQuery("#tnpc-block-options-form").html("");
280
- });
281
- });
282
-
283
- // Fires the save event for block options
284
- jQuery("#tnpc-block-options-save").click(function (e) {
285
- e.preventDefault();
286
-
287
- var _target = target;
288
-
289
- // fix for Codemirror
290
- if (typeof templateEditor !== 'undefined') {
291
- templateEditor.save();
292
- }
293
-
294
- if (window.tinymce)
295
- window.tinymce.triggerSave();
296
-
297
- var data = jQuery("#tnpc-block-options-form").serializeArray();
298
-
299
- tnpc_add_global_options(data);
300
-
301
- tnpc_hide_block_options();
302
-
303
- jQuery.post(ajaxurl, data, function (response) {
304
- _target.html(response);
305
- tnpc_mobile_preview();
306
- jQuery("#tnpc-block-options-form").html("");
307
- });
308
- });
309
-
310
- jQuery('#tnpc-block-options-form').change(function (event) {
311
- var data = jQuery("#tnpc-block-options-form").serializeArray();
312
-
313
- var _container = container;
314
- var _target = target;
315
-
316
- tnpc_add_global_options(data);
317
-
318
- jQuery.post(ajaxurl, data, function (response) {
319
- _target.html(response);
320
- if (event.target.dataset.afterRendering === 'reload') {
321
- _container.find(".tnpc-row-edit-block").click();
322
- }
323
- }).fail(function () {
324
- alert("Block rendering failed");
325
- });
326
-
327
- });
328
-
329
- tnpc_mobile_preview();
330
-
331
- }
332
-
333
- function tnpc_show_block_options() {
334
-
335
- const animationDuration = 500;
336
-
337
- //jQuery("#tnpc-blocks").fadeOut(animationDuration);
338
- //jQuery("#tnpc-global-styles").fadeOut(animationDuration);
339
- //jQuery("#tnpc-mobile-tab").fadeOut(animationDuration);
340
- //jQuery("#tnpc-test-tab").fadeOut(animationDuration);
341
-
342
- jQuery("#tnpc-block-options").fadeIn(animationDuration);
343
- jQuery("#tnpc-block-options").css('display', 'flex');
344
-
345
- }
346
-
347
- function tnpc_hide_block_options() {
348
-
349
- const animationDuration = 500;
350
-
351
- jQuery("#tnpc-block-options").fadeOut(animationDuration);
352
-
353
- //var $activeTab = jQuery(".tnpc-tabs .tablinks.active");
354
- //jQuery('#' + $activeTab.data('tabId')).fadeIn(animationDuration);
355
-
356
- jQuery("#tnpc-block-options-form").html('');
357
-
358
- }
359
-
360
- function tnpc_mobile_preview() {
361
-
362
- return;
363
-
364
- }
365
-
366
- function tnpc_save(form) {
367
-
368
- form.elements["options[message]"].value = tnpc_get_email_content_from_builder_area();
369
-
370
- // When the composer is not showing the subject field (for example in Automated)
371
- if (document.getElementById("options-preheader")) {
372
- form.elements["options[options_preheader]"].value = jQuery('#options-preheader').val();
373
- } else {
374
- form.elements["options[options_preheader]"].value = "";
375
- }
376
- if (document.getElementById("options-subject")) {
377
- form.elements["options[subject]"].value = jQuery('#options-subject-subject').val();
378
- } else {
379
- form.elements["options[subject]"].value = "";
380
- }
381
-
382
- var global_form = document.getElementById("tnpc-global-styles-form");
383
- //Copy "Global styles" form inputs into main form
384
- tnpc_copy_form(global_form, form);
385
-
386
- }
387
-
388
- function tnpc_get_email_content_from_builder_area() {
389
-
390
- var $elMessage = jQuery("#newsletter-builder-area-center-frame-content").clone();
391
-
392
- $elMessage.find('.tnpc-row-delete').remove();
393
- $elMessage.find('.tnpc-row-edit-block').remove();
394
- $elMessage.find('.tnpc-row-clone').remove();
395
- $elMessage.find('.tnpc-row').removeClass('ui-draggable');
396
- $elMessage.find('#sortable-helper').remove();
397
-
398
- return $elMessage.html();
399
-
400
- }
401
-
402
- function tnpc_copy_form(source, dest) {
403
- for (var i = 0; i < source.elements.length; i++) {
404
- var field = document.createElement("input");
405
- field.type = "hidden";
406
- field.name = source.elements[i].name;
407
- field.value = source.elements[i].value;
408
-
409
- // Non clona le select!
410
- //var clonedEl = source.elements[i].cloneNode();
411
- //clonedEl.style.display = 'none';
412
- dest.appendChild(field);
413
- }
414
- }
415
-
416
- function tnpc_test() {
417
- let form = document.getElementById("tnpc-form");
418
- tnpc_save(form);
419
- form.act.value = "test";
420
- form.submit();
421
- }
422
-
423
- function openTab(evt, tabName) {
424
- evt.preventDefault();
425
- // Declare all variables
426
- var i, tabcontent, tablinks;
427
-
428
- // Get all elements with class="tabcontent" and hide them
429
- tabcontent = document.getElementsByClassName("tabcontent");
430
- for (i = 0; i < tabcontent.length; i++) {
431
- tabcontent[i].style.display = "none";
432
- }
433
-
434
- // Get all elements with class="tablinks" and remove the class "active"
435
- tablinks = document.getElementsByClassName("tablinks");
436
- for (i = 0; i < tablinks.length; i++) {
437
- tablinks[i].className = tablinks[i].className.replace(" active", "");
438
- }
439
-
440
- // Show the current tab, and add an "active" class to the button that opened the tab
441
- document.getElementById(tabName).style.display = "block";
442
- evt.currentTarget.className += " active";
443
- }
444
-
445
- function tnpc_scratch() {
446
-
447
- jQuery('#newsletter-builder-area-center-frame-content').html(" ");
448
- init_builder_area();
449
-
450
- }
451
-
452
- function tnpc_reload_options(e) {
453
- e.preventDefault();
454
- let options = jQuery("#tnpc-block-options-form").serializeArray();
455
- for (let i = 0; i < options.length; i++) {
456
- if (options[i].name === 'action') {
457
- options[i].value = 'tnpc_options';
458
- }
459
- }
460
-
461
- jQuery("#tnpc-block-options-form").load(ajaxurl, options);
462
- }
463
-
464
- function tnpc_add_global_options(data) {
465
- let globalOptions = jQuery("#tnpc-global-styles-form").serializeArray();
466
- for (let i = 0; i < globalOptions.length; i++) {
467
- globalOptions[i].name = globalOptions[i].name.replace("[options_", "[").replace("options[", "composer[").replace("composer_", "");
468
- if (Array.isArray(data)) {
469
- data.push(globalOptions[i]);
470
- } else {
471
- //Inline edit data format is object not array
472
- data[globalOptions[i].name] = globalOptions[i].value;
473
- }
474
- }
475
- }
476
-
477
- // ==================================================== //
478
- // ================= PRESET ===================== //
479
- // ==================================================== //
480
-
481
- //TODO non va bene tenere nel global space variabili che altri potrebbero accidentalmente modificare/usare
482
- // ma questo è un test
483
- const toastBottom = new TnpToast({duration: 5000, position: 'bottom right', wrapperPadding: '70px 20px'});
484
-
485
- //TODO - spostare gestione dei preset in contesto privato ma aggiungendo comunque a window le funzioni triggerate da html (load_preset, delete_preset,...) per mantenere compatibilità?
486
- const presetListModal = new TNPModal({
487
- closeWhenClickOutside: true,
488
- showClose: true,
489
- style: {
490
- backgroundColor: '#ECF0F1',
491
- height: '400px',
492
- width: '740px',
493
- },
494
- onClose: function () {
495
- start_composer();
496
- //Enable buttons
497
- jQuery('.tnpc-controls input[type=button]').attr('disabled', false);
498
- }
499
- });
500
-
501
- function tnpc_show_presets_modal() {
502
-
503
- jQuery('.tnpc-controls input[type=button]').attr('disabled', true);
504
-
505
- const elModalContent = presetListModal.open();
506
-
507
- jQuery.ajax({
508
- type: "POST",
509
- url: ajaxurl,
510
- data: {
511
- action: "tnpc_get_all_presets",
512
- context_type: tnp_context_type,
513
- },
514
- success: function (res) {
515
- jQuery(elModalContent).html(res.data);
516
- },
517
- });
518
-
519
- }
520
-
521
- function tnpc_load_preset(id, subject, isEditMode) {
522
-
523
- presetListModal.close();
524
-
525
- jQuery.ajax({
526
- type: "POST",
527
- url: ajaxurl,
528
- data: {
529
- action: "tnpc_get_preset",
530
- id: id
531
- },
532
- success: function (res) {
533
- jQuery('#newsletter-builder-area-center-frame-content').html(res.data.content);
534
- _restore_global_options(res.data.globalOptions);
535
-
536
- start_composer();
537
-
538
- if (!isEditMode) {
539
- //Enable buttons
540
- jQuery('.tnpc-controls input[type=button]').attr('disabled', false);
541
- }
542
-
543
- if (subject && subject.length > 0) {
544
- jQuery('#options-subject-subject').val(tnpc_remove_double_quotes_escape_from(subject));
545
- }
546
- },
547
- });
548
-
549
- function _restore_global_options(options) {
550
- jQuery.each(options, function (name, value) {
551
- var el = jQuery(`#tnpc-global-styles-form #options-options_composer_${name}`);
552
- if (el.length) {
553
- el.val(value);
554
- }
555
- });
556
-
557
- tnp_controls_init();
558
- _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
559
- }
560
-
561
- }
562
-
563
- function tnpc_save_preset(form) {
564
- const presetName = document.getElementById('options-subject-subject').value.replace('"', '');
565
-
566
- const presetNameModal = new TNPModal({
567
- title: 'Choose a preset name',
568
- content: '<input type="text" id="preset_name" style="width: 100%" placeholder="Preset name" value="' + presetName + '"/>',
569
- showConfirm: true,
570
- clickConfirmOnPressEnter: true,
571
- onConfirm: function () {
572
- const inputEl = document.querySelector('#preset_name');
573
- document.querySelector('#options-subject-subject').value = inputEl.value;
574
- tnpc_save(form);
575
- form.submit();
576
- }
577
- });
578
-
579
- presetNameModal.open();
580
-
581
- }
582
-
583
- function tnpc_delete_preset(presetId, name, event) {
584
- event.stopPropagation();
585
-
586
- const presetDeleteModal = new TNPModal({
587
- title: `Are you sure to delete "${name}" preset?`,
588
- confirmText: 'DELETE PRESET',
589
- confirmClassName: 'button-secondary button-danger',
590
- showConfirm: true,
591
- onConfirm: function () {
592
-
593
- const wrapperPresetEl = event.target.closest(".tnpc-preset");
594
-
595
- jQuery.ajax({
596
- type: 'POST',
597
- dataType: 'json',
598
- url: ajaxurl,
599
- data: {
600
- action: 'tnpc_delete_preset',
601
- _wpnonce: tnp_preset_nonce,
602
- presetId: presetId
603
- },
604
- success: function (response) {
605
- if (response.success) {
606
- wrapperPresetEl.parentNode.removeChild(wrapperPresetEl);
607
- toastBottom.success('Preset successfully deleted!');
608
- }
609
- }
610
- });
611
-
612
- }
613
- });
614
-
615
- presetDeleteModal.open();
616
-
617
- }
618
-
619
- function tnpc_edit_preset(presetId, name, event) {
620
- event.stopPropagation();
621
- tnpc_load_preset(presetId, name, true);
622
-
623
- const composerForm = document.querySelector('#tnpc-form');
624
-
625
- jQuery('#save-preset-button').hide();
626
- jQuery('#update-preset-button').show();
627
-
628
- //Add preset id hidden field
629
- const presetIdfield = document.createElement("input");
630
- presetIdfield.type = "hidden";
631
- presetIdfield.name = "preset_id";
632
- presetIdfield.value = presetId;
633
- composerForm.appendChild(presetIdfield);
634
-
635
- }
636
-
637
- function tnpc_remove_double_quotes_escape_from(str) {
638
- return str.replace(/\\"/g, '"');
639
- }
640
-
641
- function tnpc_remove_double_quotes_from(str) {
642
- return str.replace(/['"]+/g, '');
643
- }
644
-
645
- function tnpc_update_preset(form) {
646
- tnpc_save(form);
647
- form.submit();
648
- }
649
-
650
- // ========================================================= //
651
- // ================= PRESET FINE ===================== //
652
- // ========================================================= //
653
-
654
- jQuery(document).ready(function () {
655
- 'use strict'
656
-
657
- var TNPInlineEditor = (function () {
658
-
659
- var className = 'tnpc-inline-editable';
660
- var newInputName = 'new_name';
661
- var activeInlineElements = [];
662
-
663
- function init() {
664
- // find all inline editable elements
665
- jQuery('#newsletter-builder-area-center-frame-content').on('click', '.' + className, function (e) {
666
- e.preventDefault();
667
- removeAllActiveElements();
668
-
669
- var originalEl = jQuery(this).hide();
670
- var newEl = jQuery(getEditableComponent(this.innerText.trim(), this.dataset.id, this.dataset.type, originalEl)).insertAfter(this);
671
-
672
- activeInlineElements.push({'originalEl': originalEl, 'newEl': newEl});
673
-
674
- //Add submit event listener for newly created block
675
- jQuery('.tnpc-inline-editable-form-' + this.dataset.type + this.dataset.id).on('submit', function (e) {
676
- submit(e, newEl, jQuery(originalEl));
677
- });
678
-
679
- //Add close event listener for newly created block
680
- jQuery('.tnpc-inline-editable-form-actions .tnpc-dismiss-' + this.dataset.type + this.dataset.id).on('click', function (e) {
681
- removeAllActiveElements();
682
- });
683
-
684
- });
685
-
686
- // Close all created elements if clicked outside
687
- jQuery('#newsletter-builder-area-center-frame-content').on('click', function (e) {
688
- if (activeInlineElements.length > 0
689
- && !jQuery(e.target).hasClass(className)
690
- && jQuery(e.target).closest('.tnpc-inline-editable-container').length === 0) {
691
- removeAllActiveElements();
692
- }
693
- });
694
-
695
- }
696
-
697
- function removeAllActiveElements() {
698
- activeInlineElements.forEach(function (obj) {
699
- obj.originalEl.show();
700
-
701
- obj.newEl.off();
702
- obj.newEl.remove();
703
- });
704
-
705
- activeInlineElements = []
706
- }
707
-
708
- function getEditableComponent(value, id, type, originalEl) {
709
-
710
- var element = '';
711
-
712
- //COPY FONT STYLE FROM ORIGINAL ELEMENT
713
- var fontFamily = originalEl.css('font-family');
714
- var fontSize = originalEl.css('font-size');
715
- var styleAttr = "style='font-family:" + fontFamily + ";font-size:" + fontSize + ";'";
716
-
717
- switch (type) {
718
- case 'text':
719
- {
720
- element = "<textarea name='" + newInputName + "' class='" + className + "-textarea' rows='5' " + styleAttr + ">" + value + "</textarea>";
721
- break;
722
- }
723
- case 'title':
724
- {
725
- element = "<textarea name='" + newInputName + "' class='" + className + "-textarea' rows='2'" + styleAttr + ">" + value + "</textarea>";
726
- break;
727
- }
728
- }
729
-
730
- var component = "<td>";
731
- component += "<form class='tnpc-inline-editable-form tnpc-inline-editable-form-" + type + id + "'>";
732
- component += "<input type='hidden' name='id' value='" + id + "'>";
733
- component += "<input type='hidden' name='type' value='" + type + "'>";
734
- component += "<input type='hidden' name='old_value' value='" + value + "'>";
735
- component += "<div class='tnpc-inline-editable-container'>";
736
- component += element;
737
- component += "<div class='tnpc-inline-editable-form-actions'>";
738
- component += "<button type='submit'><span class='dashicons dashicons-yes-alt' title='save'></span></button>";
739
- component += "<span class='dashicons dashicons-dismiss tnpc-dismiss-" + type + id + "' title='close'></span>";
740
- component += "</div>";
741
- component += "</div>";
742
- component += "</form>";
743
- component += "</td>";
744
- return component;
745
- }
746
-
747
- function submit(e, elementToDeleteAfterSubmit, elementToShow) {
748
- e.preventDefault();
749
-
750
- var id = elementToDeleteAfterSubmit.find('form input[name=id]').val();
751
- var type = elementToDeleteAfterSubmit.find('form input[name=type]').val();
752
- var newValue = elementToDeleteAfterSubmit.find('form [name="' + newInputName + '"]').val();
753
-
754
- ajax_render_block(elementToShow, type, id, newValue);
755
-
756
- elementToDeleteAfterSubmit.remove();
757
- elementToShow.show();
758
-
759
- }
760
-
761
- function ajax_render_block(inlineElement, type, postId, newContent) {
762
-
763
- var target = inlineElement.closest('.edit-block');
764
- var container = target.closest('table');
765
- var blockContent = target.children('.tnpc-block-content');
766
-
767
- if (container.hasClass('tnpc-row-block')) {
768
- var data = {
769
- 'action': 'tnpc_render',
770
- 'id': container.data('id'),
771
- 'b': container.data('id'),
772
- 'full': 1,
773
- '_wpnonce': tnp_nonce,
774
- 'options': {
775
- 'inline_edits': [{
776
- 'type': type,
777
- 'post_id': postId,
778
- 'content': newContent
779
- }]
780
- },
781
- 'encoded_options': blockContent.data('json')
782
- };
783
-
784
- tnpc_add_global_options(data);
785
-
786
- jQuery.post(ajaxurl, data, function (response) {
787
- var new_row = jQuery(response);
788
-
789
- container.before(new_row);
790
- container.remove();
791
-
792
- new_row.add_delete();
793
- new_row.add_block_edit();
794
- new_row.add_block_clone();
795
-
796
- //Force reload options
797
- if (new_row.hasClass('tnpc-row-block')) {
798
- new_row.find(".tnpc-row-edit-block").click();
799
- }
800
-
801
- tnpc_mobile_preview();
802
-
803
- }).fail(function () {
804
- alert("Block rendering failed.");
805
- });
806
-
807
- }
808
-
809
- }
810
-
811
- return {init};
812
- })();
813
-
814
- TNPInlineEditor.init();
815
-
816
- });
817
-
818
- // =================================================== //
819
- // =============== GLOBAL STYLE ================== //
820
- // =================================================== //
821
-
822
- (function globalStyleIIFE() {
823
-
824
- var _elTrigger = document.querySelector('#tnpc-global-styles-form [name="apply"]');
825
-
826
- _elTrigger.addEventListener('click', function (e) {
827
- e.preventDefault();
828
-
829
- var data = {
830
- 'action': 'tnpc_regenerate_email',
831
- 'content': tnpc_get_email_content_from_builder_area(),
832
- '_wpnonce': tnp_nonce,
833
- };
834
-
835
- tnpc_add_global_options(data);
836
-
837
- jQuery.post(ajaxurl, data, function (response) {
838
- if (response && response.success) {
839
- jQuery('#newsletter-builder-area-center-frame-content').html(response.data.content);
840
- //Change background color of builder area
841
- _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
842
- init_builder_area();
843
- tnpc_mobile_preview();
844
-
845
- toastBottom.success(response.data.message);
846
- } else {
847
- toastBottom.error(response.data.message);
848
- }
849
- });
850
-
851
- });
852
-
853
- })();
854
-
855
- // ========================================================= //
856
- // ================= SEND A TEST ===================== //
857
- // ========================================================= //
858
-
859
- (function sendATestIIFE($) {
860
-
861
- var testNewsletterWithEmailFormId = '#test-newsletter-form';
862
- var testNewsletterWithEmailForm = document.querySelector(testNewsletterWithEmailFormId);
863
- testNewsletterWithEmailForm.addEventListener('submit', function (e) {
864
- e.preventDefault();
865
- var testEmail = testNewsletterWithEmailForm.querySelector('input[name="email"]').value;
866
-
867
- let form = document.getElementById("tnpc-form");
868
- tnpc_save(form);
869
-
870
- form.act.value = "send-test-to-email-address";
871
- var input = document.createElement("input");
872
- input.setAttribute("type", "hidden");
873
- input.setAttribute("name", "test_address_email");
874
- input.setAttribute("value", testEmail);
875
- form.appendChild(input);
876
-
877
- form.submit();
878
- });
879
-
880
- })(jQuery);
881
-
882
- // ================================================================== //
883
- // ================= SUBJECT LENGTH ICONS ===================== //
884
- // ================================================================== //
885
-
886
- (function subjectLengthIconsIIFE($) {
887
- var $subjectContainer = $('#tnpc-subject');
888
- var $subjectInput = $('#tnpc-subject input');
889
- var subjectCharCounterEl = null;
890
-
891
- $subjectInput.on('focusin', function (e) {
892
- $subjectContainer.find('img').fadeTo(400, 1);
893
- });
894
-
895
- $subjectInput.on('keyup', function (e) {
896
- setSubjectCharactersLenght(this.value.length);
897
- });
898
-
899
- $subjectInput.on('focusout', function (e) {
900
- $subjectContainer.find('img').fadeTo(300, 0);
901
- });
902
-
903
- function setSubjectCharactersLenght(length = 0) {
904
-
905
- if (length === 0 && subjectCharCounterEl !== null) {
906
- subjectCharCounterEl.remove();
907
- subjectCharCounterEl = null;
908
- return;
909
- }
910
-
911
- if (!subjectCharCounterEl) {
912
- subjectCharCounterEl = document.createElement("span");
913
- subjectCharCounterEl.style.position = 'absolute';
914
- subjectCharCounterEl.style.top = '-18px';
915
- subjectCharCounterEl.style.right = $subjectContainer[0].getBoundingClientRect().width - $subjectInput[0].getBoundingClientRect().width + 'px';
916
- subjectCharCounterEl.style.color = '#999';
917
- subjectCharCounterEl.style.fontSize = '0.8rem';
918
- $subjectContainer.find('div')[0].appendChild(subjectCharCounterEl);
919
- }
920
-
921
- const word = length === 1 ? 'character' : 'characters';
922
- subjectCharCounterEl.innerHTML = `${length} ${word}`;
923
- }
924
-
925
- })(jQuery);
926
-
927
- // ======================================================================= //
928
- // ================= COMPOSER MODE VIEW SWITCH ===================== //
929
- // ======================================================================= //
930
-
931
- (function composerModeViewIIFE($) {
932
- const activeClass = 'composer-view-mode__item--active';
933
- var status = 'desktop';
934
-
935
- $('.composer-view-mode__item[data-view-mode="' + status + '"]').addClass(activeClass);
936
-
937
- $('.composer-view-mode__item').on('click', function () {
938
- var $el = $(this);
939
-
940
- if ($el.data('viewMode') === 'desktop') {
941
- status = 'desktop';
942
- $('.composer-view-mode__item[data-view-mode="desktop"]').addClass(activeClass);
943
- $('.composer-view-mode__item[data-view-mode="mobile"]').removeClass(activeClass);
944
- } else if ($el.data('viewMode') === 'mobile') {
945
- status = 'mobile';
946
- $('.composer-view-mode__item[data-view-mode="desktop"]').removeClass(activeClass);
947
- $('.composer-view-mode__item[data-view-mode="mobile"]').addClass(activeClass);
948
- }
949
-
950
- tnp_view(status);
951
- });
952
- })(jQuery);
1
+ // add delete buttons
2
+ jQuery.fn.add_delete = function () {
3
+ this.append('<div class="tnpc-row-delete" title="Delete"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/delete.png" width="32"></div>');
4
+ this.find('.tnpc-row-delete').perform_delete();
5
+ };
6
+
7
+ // delete row
8
+ jQuery.fn.perform_delete = function () {
9
+ this.click(function () {
10
+ tnpc_hide_block_options();
11
+ // remove block
12
+ jQuery(this).parent().remove();
13
+ tnpc_mobile_preview();
14
+ });
15
+ }
16
+
17
+ // add edit button
18
+ jQuery.fn.add_block_edit = function () {
19
+ this.append('<div class="tnpc-row-edit-block" title="Edit"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/edit.png" width="32"></div>');
20
+ this.find('.tnpc-row-edit-block').perform_block_edit();
21
+ }
22
+
23
+ // edit block
24
+ jQuery.fn.perform_block_edit = function () {
25
+
26
+ jQuery(".tnpc-row-edit-block").click(function (e) {
27
+ e.preventDefault()
28
+ });
29
+
30
+ this.click(function (e) {
31
+
32
+ e.preventDefault();
33
+
34
+ target = jQuery(this).parent().find('.edit-block');
35
+
36
+ // The row container which is a global variable and used later after the options save
37
+ container = jQuery(this).closest("table");
38
+
39
+ if (container.hasClass('tnpc-row-block')) {
40
+
41
+ tnpc_show_block_options();
42
+
43
+ var options = container.find(".tnpc-block-content").attr("data-json");
44
+
45
+ // Compatibility
46
+ if (!options) {
47
+ options = target.attr("data-options");
48
+ }
49
+
50
+ var data = {
51
+ action: "tnpc_options",
52
+ id: container.data("id"),
53
+ context_type: tnp_context_type,
54
+ options: options
55
+ };
56
+
57
+ tnpc_add_global_options(data);
58
+
59
+ builderAreaHelper.lock();
60
+ jQuery("#tnpc-block-options-form").load(ajaxurl, data, function () {
61
+ console.log('Block form options loaded');
62
+ start_options = jQuery("#tnpc-block-options-form").serializeArray();
63
+ tnpc_add_global_options(start_options);
64
+ builderAreaHelper.unlock();
65
+ });
66
+
67
+ } else {
68
+ alert("This is deprecated block version and cannot be edited. Please replace it with a new one.");
69
+ }
70
+
71
+ });
72
+
73
+ };
74
+
75
+ // add clone button
76
+ jQuery.fn.add_block_clone = function () {
77
+ this.append('<div class="tnpc-row-clone" title="Clone"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/copy.png" width="32"></div>');
78
+ this.find('.tnpc-row-clone').perform_clone();
79
+ }
80
+
81
+ // clone block
82
+ jQuery.fn.perform_clone = function () {
83
+
84
+ jQuery(".tnpc-row-clone").click(function (e) {
85
+ e.preventDefault()
86
+ });
87
+
88
+ this.click(function (e) {
89
+
90
+ e.preventDefault();
91
+
92
+ // hide block edit form
93
+ tnpc_hide_block_options();
94
+
95
+ // find the row
96
+ let row = jQuery(this).closest('.tnpc-row');
97
+
98
+ // clone the block
99
+ let new_row = row.clone();
100
+ new_row.find(".tnpc-row-delete").remove();
101
+ new_row.find(".tnpc-row-edit-block").remove();
102
+ new_row.find(".tnpc-row-clone").remove();
103
+
104
+ new_row.add_delete();
105
+ new_row.add_block_edit();
106
+ new_row.add_block_clone();
107
+ // if (new_row.hasClass('tnpc-row-block')) {
108
+ // new_row.find(".tnpc-row-edit-block i").click();
109
+ // }
110
+ new_row.insertAfter(row);
111
+ tnpc_mobile_preview();
112
+ });
113
+ };
114
+
115
+ let start_options = null;
116
+ let container = null;
117
+
118
+ jQuery(function () {
119
+
120
+ // open blocks tab
121
+ document.getElementById("defaultOpen").click();
122
+
123
+ // preload content from a body named input
124
+ var preloadedContent = jQuery('input[name="message"]').val();
125
+ if (!preloadedContent) {
126
+ preloadedContent = jQuery('input[name="options[message]"]').val();
127
+ }
128
+
129
+ if (!preloadedContent) {
130
+ tnpc_show_presets_modal();
131
+ } else {
132
+ jQuery('#newsletter-builder-area-center-frame-content').html(preloadedContent);
133
+ start_composer();
134
+ }
135
+
136
+ // subject management
137
+ jQuery('#options-subject').val(jQuery('#tnpc-form input[name="options[subject]"]').val());
138
+
139
+ // preheader management
140
+ jQuery('#options-preheader').val(jQuery('#tnpc-form input[name="options[options_preheader]"]').val());
141
+
142
+ // ======================== //
143
+ // == BACKGROUND COLOR == //
144
+ // ======================== //
145
+ _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
146
+
147
+ function _setBuilderAreaBackgroundColor(color) {
148
+ jQuery('#newsletter-builder-area-center-frame-content').css('background-color', color);
149
+ }
150
+
151
+ window._setBuilderAreaBackgroundColor = _setBuilderAreaBackgroundColor; //BAD STUFF!!!
152
+
153
+ // ======================== //
154
+ // == BACKGROUND COLOR == //
155
+ // ======================== //
156
+
157
+ });
158
+
159
+ function BuilderAreaHelper() {
160
+
161
+ var _builderAreaEl = document.querySelector('#newsletter-builder-area');
162
+ var _overlayEl = document.createElement('div');
163
+ _overlayEl.style.zIndex = 99999;
164
+ _overlayEl.style.position = 'absolute';
165
+ _overlayEl.style.top = 0;
166
+ _overlayEl.style.left = 0;
167
+ _overlayEl.style.width = '100%';
168
+ _overlayEl.style.height = '100%';
169
+
170
+ this.lock = function () {
171
+ console.log('Lock builder area');
172
+ _builderAreaEl.appendChild(_overlayEl);
173
+ }
174
+
175
+ this.unlock = function () {
176
+ console.log('Unlock builder area');
177
+ _builderAreaEl.removeChild(_overlayEl);
178
+ }
179
+
180
+ }
181
+
182
+ let builderAreaHelper = new BuilderAreaHelper();
183
+
184
+ function init_builder_area() {
185
+
186
+ //Drag & Drop
187
+ jQuery("#newsletter-builder-area-center-frame-content").sortable({
188
+ revert: false,
189
+ placeholder: "placeholder",
190
+ forcePlaceholderSize: true,
191
+ opacity: 0.6,
192
+ tolerance: "pointer",
193
+ helper: function (e) {
194
+ var helper = jQuery(document.getElementById("sortable-helper")).clone();
195
+ return helper;
196
+ },
197
+ update: function (event, ui) {
198
+ if (ui.item.attr("id") == "draggable-helper") {
199
+ loading_row = jQuery('<div style="text-align: center; padding: 20px; background-color: #d4d5d6; color: #52BE7F;"><i class="fa fa-cog fa-2x fa-spin" /></div>');
200
+ ui.item.before(loading_row);
201
+ ui.item.remove();
202
+ var data = new Array(
203
+ {"name": 'action', "value": 'tnpc_render'},
204
+ {"name": 'id', "value": ui.item.data("id")},
205
+ {"name": 'b', "value": ui.item.data("id")},
206
+ {"name": 'full', "value": 1},
207
+ {"name": '_wpnonce', "value": tnp_nonce}
208
+ );
209
+
210
+ tnpc_add_global_options(data);
211
+
212
+ jQuery.post(ajaxurl, data, function (response) {
213
+
214
+ var new_row = jQuery(response);
215
+ // ui.item.before(new_row);
216
+ // ui.item.remove();
217
+ loading_row.before(new_row);
218
+ loading_row.remove();
219
+ new_row.add_delete();
220
+ new_row.add_block_edit();
221
+ new_row.add_block_clone();
222
+ // new_row.find(".tnpc-row-edit").hover_edit();
223
+ if (new_row.hasClass('tnpc-row-block')) {
224
+ new_row.find(".tnpc-row-edit-block").click();
225
+ }
226
+ tnpc_mobile_preview();
227
+ }).fail(function () {
228
+ alert("Block rendering failed.");
229
+ loading_row.remove();
230
+ });
231
+ } else {
232
+ tnpc_mobile_preview();
233
+ }
234
+ }
235
+ });
236
+
237
+ jQuery(".newsletter-sidebar-buttons-content-tab").draggable({
238
+ connectToSortable: "#newsletter-builder-area-center-frame-content",
239
+
240
+ // Build the helper for dragging
241
+ helper: function (e) {
242
+ var helper = jQuery(document.getElementById("draggable-helper")).clone();
243
+ // Do not uset .data() with jQuery
244
+ helper.attr("data-id", e.currentTarget.dataset.id);
245
+ helper.html(e.currentTarget.dataset.name);
246
+ return helper;
247
+ },
248
+ revert: false,
249
+ start: function () {
250
+ if (jQuery('.tnpc-row').length) {
251
+ } else {
252
+ jQuery('#newsletter-builder-area-center-frame-content').append('<div class="tnpc-drop-here">Drag&Drop blocks here!</div>');
253
+ }
254
+ },
255
+ stop: function (event, ui) {
256
+ jQuery('.tnpc-drop-here').remove();
257
+ }
258
+ });
259
+
260
+ jQuery(".tnpc-row").add_delete();
261
+ jQuery(".tnpc-row").add_block_edit();
262
+ jQuery(".tnpc-row").add_block_clone();
263
+
264
+ }
265
+
266
+ function start_composer() {
267
+
268
+ init_builder_area();
269
+
270
+ // Closes the block options layer (without saving)
271
+ jQuery("#tnpc-block-options-cancel").click(function () {
272
+
273
+ tnpc_hide_block_options();
274
+
275
+ var _target = target;
276
+
277
+ jQuery.post(ajaxurl, start_options, function (response) {
278
+ _target.html(response);
279
+ jQuery("#tnpc-block-options-form").html("");
280
+ });
281
+ });
282
+
283
+ // Fires the save event for block options
284
+ jQuery("#tnpc-block-options-save").click(function (e) {
285
+ e.preventDefault();
286
+
287
+ var _target = target;
288
+
289
+ // fix for Codemirror
290
+ if (typeof templateEditor !== 'undefined') {
291
+ templateEditor.save();
292
+ }
293
+
294
+ if (window.tinymce)
295
+ window.tinymce.triggerSave();
296
+
297
+ var data = jQuery("#tnpc-block-options-form").serializeArray();
298
+
299
+ tnpc_add_global_options(data);
300
+
301
+ tnpc_hide_block_options();
302
+
303
+ jQuery.post(ajaxurl, data, function (response) {
304
+ _target.html(response);
305
+ tnpc_mobile_preview();
306
+ jQuery("#tnpc-block-options-form").html("");
307
+ });
308
+ });
309
+
310
+ jQuery('#tnpc-block-options-form').change(function (event) {
311
+ var data = jQuery("#tnpc-block-options-form").serializeArray();
312
+
313
+ var _container = container;
314
+ var _target = target;
315
+
316
+ tnpc_add_global_options(data);
317
+
318
+ jQuery.post(ajaxurl, data, function (response) {
319
+ _target.html(response);
320
+ if (event.target.dataset.afterRendering === 'reload') {
321
+ _container.find(".tnpc-row-edit-block").click();
322
+ }
323
+ }).fail(function () {
324
+ alert("Block rendering failed");
325
+ });
326
+
327
+ });
328
+
329
+ tnpc_mobile_preview();
330
+
331
+ }
332
+
333
+ function tnpc_show_block_options() {
334
+
335
+ const animationDuration = 500;
336
+
337
+ //jQuery("#tnpc-blocks").fadeOut(animationDuration);
338
+ //jQuery("#tnpc-global-styles").fadeOut(animationDuration);
339
+ //jQuery("#tnpc-mobile-tab").fadeOut(animationDuration);
340
+ //jQuery("#tnpc-test-tab").fadeOut(animationDuration);
341
+
342
+ jQuery("#tnpc-block-options").fadeIn(animationDuration);
343
+ jQuery("#tnpc-block-options").css('display', 'flex');
344
+
345
+ }
346
+
347
+ function tnpc_hide_block_options() {
348
+
349
+ const animationDuration = 500;
350
+
351
+ jQuery("#tnpc-block-options").fadeOut(animationDuration);
352
+
353
+ //var $activeTab = jQuery(".tnpc-tabs .tablinks.active");
354
+ //jQuery('#' + $activeTab.data('tabId')).fadeIn(animationDuration);
355
+
356
+ jQuery("#tnpc-block-options-form").html('');
357
+
358
+ }
359
+
360
+ function tnpc_mobile_preview() {
361
+
362
+ return;
363
+
364
+ }
365
+
366
+ function tnpc_save(form) {
367
+
368
+ form.elements["options[message]"].value = tnpc_get_email_content_from_builder_area();
369
+
370
+ // When the composer is not showing the subject field (for example in Automated)
371
+ if (document.getElementById("options-preheader")) {
372
+ form.elements["options[options_preheader]"].value = jQuery('#options-preheader').val();
373
+ } else {
374
+ form.elements["options[options_preheader]"].value = "";
375
+ }
376
+ if (document.getElementById("options-subject")) {
377
+ form.elements["options[subject]"].value = jQuery('#options-subject-subject').val();
378
+ } else {
379
+ form.elements["options[subject]"].value = "";
380
+ }
381
+
382
+ var global_form = document.getElementById("tnpc-global-styles-form");
383
+ //Copy "Global styles" form inputs into main form
384
+ tnpc_copy_form(global_form, form);
385
+
386
+ }
387
+
388
+ function tnpc_get_email_content_from_builder_area() {
389
+
390
+ var $elMessage = jQuery("#newsletter-builder-area-center-frame-content").clone();
391
+
392
+ $elMessage.find('.tnpc-row-delete').remove();
393
+ $elMessage.find('.tnpc-row-edit-block').remove();
394
+ $elMessage.find('.tnpc-row-clone').remove();
395
+ $elMessage.find('.tnpc-row').removeClass('ui-draggable');
396
+ $elMessage.find('#sortable-helper').remove();
397
+
398
+ return $elMessage.html();
399
+
400
+ }
401
+
402
+ function tnpc_copy_form(source, dest) {
403
+ for (var i = 0; i < source.elements.length; i++) {
404
+ var field = document.createElement("input");
405
+ field.type = "hidden";
406
+ field.name = source.elements[i].name;
407
+ field.value = source.elements[i].value;
408
+
409
+ // Non clona le select!
410
+ //var clonedEl = source.elements[i].cloneNode();
411
+ //clonedEl.style.display = 'none';
412
+ dest.appendChild(field);
413
+ }
414
+ }
415
+
416
+ function tnpc_test() {
417
+ let form = document.getElementById("tnpc-form");
418
+ tnpc_save(form);
419
+ form.act.value = "test";
420
+ form.submit();
421
+ }
422
+
423
+ function openTab(evt, tabName) {
424
+ evt.preventDefault();
425
+ // Declare all variables
426
+ var i, tabcontent, tablinks;
427
+
428
+ // Get all elements with class="tabcontent" and hide them
429
+ tabcontent = document.getElementsByClassName("tabcontent");
430
+ for (i = 0; i < tabcontent.length; i++) {
431
+ tabcontent[i].style.display = "none";
432
+ }
433
+
434
+ // Get all elements with class="tablinks" and remove the class "active"
435
+ tablinks = document.getElementsByClassName("tablinks");
436
+ for (i = 0; i < tablinks.length; i++) {
437
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
438
+ }
439
+
440
+ // Show the current tab, and add an "active" class to the button that opened the tab
441
+ document.getElementById(tabName).style.display = "block";
442
+ evt.currentTarget.className += " active";
443
+ }
444
+
445
+ function tnpc_scratch() {
446
+
447
+ jQuery('#newsletter-builder-area-center-frame-content').html(" ");
448
+ init_builder_area();
449
+
450
+ }
451
+
452
+ function tnpc_reload_options(e) {
453
+ e.preventDefault();
454
+ let options = jQuery("#tnpc-block-options-form").serializeArray();
455
+ for (let i = 0; i < options.length; i++) {
456
+ if (options[i].name === 'action') {
457
+ options[i].value = 'tnpc_options';
458
+ }
459
+ }
460
+
461
+ jQuery("#tnpc-block-options-form").load(ajaxurl, options);
462
+ }
463
+
464
+ function tnpc_add_global_options(data) {
465
+ let globalOptions = jQuery("#tnpc-global-styles-form").serializeArray();
466
+ for (let i = 0; i < globalOptions.length; i++) {
467
+ globalOptions[i].name = globalOptions[i].name.replace("[options_", "[").replace("options[", "composer[").replace("composer_", "");
468
+ if (Array.isArray(data)) {
469
+ data.push(globalOptions[i]);
470
+ } else {
471
+ //Inline edit data format is object not array
472
+ data[globalOptions[i].name] = globalOptions[i].value;
473
+ }
474
+ }
475
+ }
476
+
477
+ // ==================================================== //
478
+ // ================= PRESET ===================== //
479
+ // ==================================================== //
480
+
481
+ //TODO non va bene tenere nel global space variabili che altri potrebbero accidentalmente modificare/usare
482
+ // ma questo è un test
483
+ const toastBottom = new TnpToast({duration: 5000, position: 'bottom right', wrapperPadding: '70px 20px'});
484
+
485
+ //TODO - spostare gestione dei preset in contesto privato ma aggiungendo comunque a window le funzioni triggerate da html (load_preset, delete_preset,...) per mantenere compatibilità?
486
+ const presetListModal = new TNPModal({
487
+ closeWhenClickOutside: true,
488
+ showClose: true,
489
+ style: {
490
+ backgroundColor: '#ECF0F1',
491
+ height: '400px',
492
+ width: '740px',
493
+ },
494
+ onClose: function () {
495
+ start_composer();
496
+ //Enable buttons
497
+ jQuery('.tnpc-controls input[type=button]').attr('disabled', false);
498
+ }
499
+ });
500
+
501
+ function tnpc_show_presets_modal() {
502
+
503
+ jQuery('.tnpc-controls input[type=button]').attr('disabled', true);
504
+
505
+ const elModalContent = presetListModal.open();
506
+
507
+ jQuery.ajax({
508
+ type: "POST",
509
+ url: ajaxurl,
510
+ data: {
511
+ action: "tnpc_get_all_presets",
512
+ context_type: tnp_context_type,
513
+ },
514
+ success: function (res) {
515
+ jQuery(elModalContent).html(res.data);
516
+ },
517
+ });
518
+
519
+ }
520
+
521
+ function tnpc_load_preset(id, subject, isEditMode) {
522
+
523
+ presetListModal.close();
524
+
525
+ jQuery.ajax({
526
+ type: "POST",
527
+ url: ajaxurl,
528
+ data: {
529
+ action: "tnpc_get_preset",
530
+ id: id
531
+ },
532
+ success: function (res) {
533
+ jQuery('#newsletter-builder-area-center-frame-content').html(res.data.content);
534
+ _restore_global_options(res.data.globalOptions);
535
+
536
+ start_composer();
537
+
538
+ if (!isEditMode) {
539
+ //Enable buttons
540
+ jQuery('.tnpc-controls input[type=button]').attr('disabled', false);
541
+ }
542
+
543
+ if (subject && subject.length > 0) {
544
+ jQuery('#options-subject-subject').val(tnpc_remove_double_quotes_escape_from(subject));
545
+ }
546
+ },
547
+ });
548
+
549
+ function _restore_global_options(options) {
550
+ jQuery.each(options, function (name, value) {
551
+ var el = jQuery(`#tnpc-global-styles-form #options-options_composer_${name}`);
552
+ if (el.length) {
553
+ el.val(value);
554
+ }
555
+ });
556
+
557
+ tnp_controls_init();
558
+ _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
559
+ }
560
+
561
+ }
562
+
563
+ function tnpc_save_preset(form) {
564
+ const presetName = document.getElementById('options-subject-subject').value.replace('"', '');
565
+
566
+ const presetNameModal = new TNPModal({
567
+ title: 'Choose a preset name',
568
+ content: '<input type="text" id="preset_name" style="width: 100%" placeholder="Preset name" value="' + presetName + '"/>',
569
+ showConfirm: true,
570
+ clickConfirmOnPressEnter: true,
571
+ onConfirm: function () {
572
+ const inputEl = document.querySelector('#preset_name');
573
+ document.querySelector('#options-subject-subject').value = inputEl.value;
574
+ tnpc_save(form);
575
+ form.submit();
576
+ }
577
+ });
578
+
579
+ presetNameModal.open();
580
+
581
+ }
582
+
583
+ function tnpc_delete_preset(presetId, name, event) {
584
+ event.stopPropagation();
585
+
586
+ const presetDeleteModal = new TNPModal({
587
+ title: `Are you sure to delete "${name}" preset?`,
588
+ confirmText: 'DELETE PRESET',
589
+ confirmClassName: 'button-secondary button-danger',
590
+ showConfirm: true,
591
+ onConfirm: function () {
592
+
593
+ const wrapperPresetEl = event.target.closest(".tnpc-preset");
594
+
595
+ jQuery.ajax({
596
+ type: 'POST',
597
+ dataType: 'json',
598
+ url: ajaxurl,
599
+ data: {
600
+ action: 'tnpc_delete_preset',
601
+ _wpnonce: tnp_preset_nonce,
602
+ presetId: presetId
603
+ },
604
+ success: function (response) {
605
+ if (response.success) {
606
+ wrapperPresetEl.parentNode.removeChild(wrapperPresetEl);
607
+ toastBottom.success('Preset successfully deleted!');
608
+ }
609
+ }
610
+ });
611
+
612
+ }
613
+ });
614
+
615
+ presetDeleteModal.open();
616
+
617
+ }
618
+
619
+ function tnpc_edit_preset(presetId, name, event) {
620
+ event.stopPropagation();
621
+ tnpc_load_preset(presetId, name, true);
622
+
623
+ const composerForm = document.querySelector('#tnpc-form');
624
+
625
+ jQuery('#save-preset-button').hide();
626
+ jQuery('#update-preset-button').show();
627
+
628
+ //Add preset id hidden field
629
+ const presetIdfield = document.createElement("input");
630
+ presetIdfield.type = "hidden";
631
+ presetIdfield.name = "preset_id";
632
+ presetIdfield.value = presetId;
633
+ composerForm.appendChild(presetIdfield);
634
+
635
+ }
636
+
637
+ function tnpc_remove_double_quotes_escape_from(str) {
638
+ return str.replace(/\\"/g, '"');
639
+ }
640
+
641
+ function tnpc_remove_double_quotes_from(str) {
642
+ return str.replace(/['"]+/g, '');
643
+ }
644
+
645
+ function tnpc_update_preset(form) {
646
+ tnpc_save(form);
647
+ form.submit();
648
+ }
649
+
650
+ // ========================================================= //
651
+ // ================= PRESET FINE ===================== //
652
+ // ========================================================= //
653
+
654
+ jQuery(document).ready(function () {
655
+ 'use strict'
656
+
657
+ var TNPInlineEditor = (function () {
658
+
659
+ var className = 'tnpc-inline-editable';
660
+ var newInputName = 'new_name';
661
+ var activeInlineElements = [];
662
+
663
+ function init() {
664
+ // find all inline editable elements
665
+ jQuery('#newsletter-builder-area-center-frame-content').on('click', '.' + className, function (e) {
666
+ e.preventDefault();
667
+ removeAllActiveElements();
668
+
669
+ var originalEl = jQuery(this).hide();
670
+ var newEl = jQuery(getEditableComponent(this.innerText.trim(), this.dataset.id, this.dataset.type, originalEl)).insertAfter(this);
671
+
672
+ activeInlineElements.push({'originalEl': originalEl, 'newEl': newEl});
673
+
674
+ //Add submit event listener for newly created block
675
+ jQuery('.tnpc-inline-editable-form-' + this.dataset.type + this.dataset.id).on('submit', function (e) {
676
+ submit(e, newEl, jQuery(originalEl));
677
+ });
678
+
679
+ //Add close event listener for newly created block
680
+ jQuery('.tnpc-inline-editable-form-actions .tnpc-dismiss-' + this.dataset.type + this.dataset.id).on('click', function (e) {
681
+ removeAllActiveElements();
682
+ });
683
+
684
+ });
685
+
686
+ // Close all created elements if clicked outside
687
+ jQuery('#newsletter-builder-area-center-frame-content').on('click', function (e) {
688
+ if (activeInlineElements.length > 0
689
+ && !jQuery(e.target).hasClass(className)
690
+ && jQuery(e.target).closest('.tnpc-inline-editable-container').length === 0) {
691
+ removeAllActiveElements();
692
+ }
693
+ });
694
+
695
+ }
696
+
697
+ function removeAllActiveElements() {
698
+ activeInlineElements.forEach(function (obj) {
699
+ obj.originalEl.show();
700
+
701
+ obj.newEl.off();
702
+ obj.newEl.remove();
703
+ });
704
+
705
+ activeInlineElements = []
706
+ }
707
+
708
+ function getEditableComponent(value, id, type, originalEl) {
709
+
710
+ var element = '';
711
+
712
+ //COPY FONT STYLE FROM ORIGINAL ELEMENT
713
+ var fontFamily = originalEl.css('font-family');
714
+ var fontSize = originalEl.css('font-size');
715
+ var styleAttr = "style='font-family:" + fontFamily + ";font-size:" + fontSize + ";'";
716
+
717
+ switch (type) {
718
+ case 'text':
719
+ {
720
+ element = "<textarea name='" + newInputName + "' class='" + className + "-textarea' rows='5' " + styleAttr + ">" + value + "</textarea>";
721
+ break;
722
+ }
723
+ case 'title':
724
+ {
725
+ element = "<textarea name='" + newInputName + "' class='" + className + "-textarea' rows='2'" + styleAttr + ">" + value + "</textarea>";
726
+ break;
727
+ }
728
+ }
729
+
730
+ var component = "<td>";
731
+ component += "<form class='tnpc-inline-editable-form tnpc-inline-editable-form-" + type + id + "'>";
732
+ component += "<input type='hidden' name='id' value='" + id + "'>";
733
+ component += "<input type='hidden' name='type' value='" + type + "'>";
734
+ component += "<input type='hidden' name='old_value' value='" + value + "'>";
735
+ component += "<div class='tnpc-inline-editable-container'>";
736
+ component += element;
737
+ component += "<div class='tnpc-inline-editable-form-actions'>";
738
+ component += "<button type='submit'><span class='dashicons dashicons-yes-alt' title='save'></span></button>";
739
+ component += "<span class='dashicons dashicons-dismiss tnpc-dismiss-" + type + id + "' title='close'></span>";
740
+ component += "</div>";
741
+ component += "</div>";
742
+ component += "</form>";
743
+ component += "</td>";
744
+ return component;
745
+ }
746
+
747
+ function submit(e, elementToDeleteAfterSubmit, elementToShow) {
748
+ e.preventDefault();
749
+
750
+ var id = elementToDeleteAfterSubmit.find('form input[name=id]').val();
751
+ var type = elementToDeleteAfterSubmit.find('form input[name=type]').val();
752
+ var newValue = elementToDeleteAfterSubmit.find('form [name="' + newInputName + '"]').val();
753
+
754
+ ajax_render_block(elementToShow, type, id, newValue);
755
+
756
+ elementToDeleteAfterSubmit.remove();
757
+ elementToShow.show();
758
+
759
+ }
760
+
761
+ function ajax_render_block(inlineElement, type, postId, newContent) {
762
+
763
+ var target = inlineElement.closest('.edit-block');
764
+ var container = target.closest('table');
765
+ var blockContent = target.children('.tnpc-block-content');
766
+
767
+ if (container.hasClass('tnpc-row-block')) {
768
+ var data = {
769
+ 'action': 'tnpc_render',
770
+ 'id': container.data('id'),
771
+ 'b': container.data('id'),
772
+ 'full': 1,
773
+ '_wpnonce': tnp_nonce,
774
+ 'options': {
775
+ 'inline_edits': [{
776
+ 'type': type,
777
+ 'post_id': postId,
778
+ 'content': newContent
779
+ }]
780
+ },
781
+ 'encoded_options': blockContent.data('json')
782
+ };
783
+
784
+ tnpc_add_global_options(data);
785
+
786
+ jQuery.post(ajaxurl, data, function (response) {
787
+ var new_row = jQuery(response);
788
+
789
+ container.before(new_row);
790
+ container.remove();
791
+
792
+ new_row.add_delete();
793
+ new_row.add_block_edit();
794
+ new_row.add_block_clone();
795
+
796
+ //Force reload options
797
+ if (new_row.hasClass('tnpc-row-block')) {
798
+ new_row.find(".tnpc-row-edit-block").click();
799
+ }
800
+
801
+ tnpc_mobile_preview();
802
+
803
+ }).fail(function () {
804
+ alert("Block rendering failed.");
805
+ });
806
+
807
+ }
808
+
809
+ }
810
+
811
+ return {init};
812
+ })();
813
+
814
+ TNPInlineEditor.init();
815
+
816
+ });
817
+
818
+ // =================================================== //
819
+ // =============== GLOBAL STYLE ================== //
820
+ // =================================================== //
821
+
822
+ (function globalStyleIIFE() {
823
+
824
+ var _elTrigger = document.querySelector('#tnpc-global-styles-form [name="apply"]');
825
+
826
+ _elTrigger.addEventListener('click', function (e) {
827
+ e.preventDefault();
828
+
829
+ var data = {
830
+ 'action': 'tnpc_regenerate_email',
831
+ 'content': tnpc_get_email_content_from_builder_area(),
832
+ '_wpnonce': tnp_nonce,
833
+ };
834
+
835
+ tnpc_add_global_options(data);
836
+
837
+ jQuery.post(ajaxurl, data, function (response) {
838
+ if (response && response.success) {
839
+ jQuery('#newsletter-builder-area-center-frame-content').html(response.data.content);
840
+ //Change background color of builder area
841
+ _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
842
+ init_builder_area();
843
+ tnpc_mobile_preview();
844
+
845
+ toastBottom.success(response.data.message);
846
+ } else {
847
+ toastBottom.error(response.data.message);
848
+ }
849
+ });
850
+
851
+ });
852
+
853
+ })();
854
+
855
+ // ========================================================= //
856
+ // ================= SEND A TEST ===================== //
857
+ // ========================================================= //
858
+
859
+ (function sendATestIIFE($) {
860
+
861
+ var testNewsletterWithEmailFormId = '#test-newsletter-form';
862
+ var testNewsletterWithEmailForm = document.querySelector(testNewsletterWithEmailFormId);
863
+ testNewsletterWithEmailForm.addEventListener('submit', function (e) {
864
+ e.preventDefault();
865
+ var testEmail = testNewsletterWithEmailForm.querySelector('input[name="email"]').value;
866
+
867
+ let form = document.getElementById("tnpc-form");
868
+ tnpc_save(form);
869
+
870
+ form.act.value = "send-test-to-email-address";
871
+ var input = document.createElement("input");
872
+ input.setAttribute("type", "hidden");
873
+ input.setAttribute("name", "test_address_email");
874
+ input.setAttribute("value", testEmail);
875
+ form.appendChild(input);
876
+
877
+ form.submit();
878
+ });
879
+
880
+ })(jQuery);
881
+
882
+ // ================================================================== //
883
+ // ================= SUBJECT LENGTH ICONS ===================== //
884
+ // ================================================================== //
885
+
886
+ (function subjectLengthIconsIIFE($) {
887
+ var $subjectContainer = $('#tnpc-subject');
888
+ var $subjectInput = $('#tnpc-subject input');
889
+ var subjectCharCounterEl = null;
890
+
891
+ $subjectInput.on('focusin', function (e) {
892
+ $subjectContainer.find('img').fadeTo(400, 1);
893
+ });
894
+
895
+ $subjectInput.on('keyup', function (e) {
896
+ setSubjectCharactersLenght(this.value.length);
897
+ });
898
+
899
+ $subjectInput.on('focusout', function (e) {
900
+ $subjectContainer.find('img').fadeTo(300, 0);
901
+ });
902
+
903
+ function setSubjectCharactersLenght(length = 0) {
904
+
905
+ if (length === 0 && subjectCharCounterEl !== null) {
906
+ subjectCharCounterEl.remove();
907
+ subjectCharCounterEl = null;
908
+ return;
909
+ }
910
+
911
+ if (!subjectCharCounterEl) {
912
+ subjectCharCounterEl = document.createElement("span");
913
+ subjectCharCounterEl.style.position = 'absolute';
914
+ subjectCharCounterEl.style.top = '-18px';
915
+ subjectCharCounterEl.style.right = $subjectContainer[0].getBoundingClientRect().width - $subjectInput[0].getBoundingClientRect().width + 'px';
916
+ subjectCharCounterEl.style.color = '#999';
917
+ subjectCharCounterEl.style.fontSize = '0.8rem';
918
+ $subjectContainer.find('div')[0].appendChild(subjectCharCounterEl);
919
+ }
920
+
921
+ const word = length === 1 ? 'character' : 'characters';
922
+ subjectCharCounterEl.innerHTML = `${length} ${word}`;
923
+ }
924
+
925
+ })(jQuery);
926
+
927
+ // ======================================================================= //
928
+ // ================= COMPOSER MODE VIEW SWITCH ===================== //
929
+ // ======================================================================= //
930
+
931
+ (function composerModeViewIIFE($) {
932
+ const activeClass = 'composer-view-mode__item--active';
933
+ var status = 'desktop';
934
+
935
+ $('.composer-view-mode__item[data-view-mode="' + status + '"]').addClass(activeClass);
936
+
937
+ $('.composer-view-mode__item').on('click', function () {
938
+ var $el = $(this);
939
+
940
+ if ($el.data('viewMode') === 'desktop') {
941
+ status = 'desktop';
942
+ $('.composer-view-mode__item[data-view-mode="desktop"]').addClass(activeClass);
943
+ $('.composer-view-mode__item[data-view-mode="mobile"]').removeClass(activeClass);
944
+ } else if ($el.data('viewMode') === 'mobile') {
945
+ status = 'mobile';
946
+ $('.composer-view-mode__item[data-view-mode="desktop"]').removeClass(activeClass);
947
+ $('.composer-view-mode__item[data-view-mode="mobile"]').addClass(activeClass);
948
+ }
949
+
950
+ tnp_view(status);
951
+ });
952
+ })(jQuery);
emails/tnp-composer/index-v2.php CHANGED
@@ -1,205 +1,205 @@
1
- <?php
2
- /**
3
- * This file is included by NewsletterControls to create the composer.
4
- */
5
- /* @var $this NewsletterControls */
6
-
7
- defined('ABSPATH') || exit;
8
-
9
- $list = NewsletterEmails::instance()->get_blocks();
10
-
11
- $blocks = array();
12
- foreach ($list as $key => $data) {
13
- if (!isset($blocks[$data['section']])) {
14
- $blocks[$data['section']] = array();
15
- }
16
- $blocks[$data['section']][$key]['name'] = $data['name'];
17
- $blocks[$data['section']][$key]['filename'] = $key;
18
- $blocks[$data['section']][$key]['icon'] = $data['icon'];
19
- }
20
-
21
- // order the sections
22
- $blocks = array_merge(array_flip(array('header', 'content', 'footer')), $blocks);
23
-
24
- // prepare the options for the default blocks
25
- $block_options = get_option('newsletter_main');
26
-
27
- $fields = new NewsletterFields($controls);
28
-
29
- $dir = is_rtl() ? 'rtl' : 'ltr';
30
- $rev_dir = is_rtl() ? 'ltr' : 'rlt';
31
- ?>
32
- <script type="text/javascript">
33
- if (window.innerWidth < 1550) {
34
- document.body.classList.add('folded');
35
- }
36
-
37
- function tnp_view(type) {
38
- if (type === 'mobile') {
39
- jQuery('#newsletter-builder-area-center-frame-content').addClass('tnp-view-mobile');
40
- } else {
41
- jQuery('#newsletter-builder-area-center-frame-content').removeClass('tnp-view-mobile');
42
- }
43
- }
44
- </script>
45
-
46
-
47
- <style>
48
- <?php echo NewsletterEmails::instance()->get_composer_backend_css(); ?>
49
- </style>
50
-
51
- <div id="newsletter-builder" dir="ltr">
52
-
53
- <div id="newsletter-builder-area" class="tnp-builder-column">
54
-
55
- <?php if ($tnpc_show_subject) { ?>
56
- <div id="tnpc-subject-wrap" dir="<?php echo $dir ?>">
57
- <table role="presentation" style="width: 100%">
58
- <?php if (!empty($controls->data['sender_email'])) { ?>
59
- <tr>
60
- <th dir="<?php echo $dir ?>"><?php _e('From', 'newsletter') ?></th>
61
- <td dir="<?php echo $dir ?>"><?php echo esc_html($controls->data['sender_email']) ?></td>
62
- </tr>
63
- <?php } ?>
64
- <tr>
65
- <th dir="<?php echo $dir ?>">
66
- <?php _e('Subject', 'newsletter') ?>
67
- <?php if ($context_type === 'automated') { ?>
68
- <?php $this->field_help('https://www.thenewsletterplugin.com/documentation/addons/extended-features/automated-extension/#subject') ?>
69
- <?php } ?>
70
- </th>
71
- <td dir="<?php echo $dir ?>">
72
- <div id="tnpc-subject">
73
- <?php $this->subject('subject'); ?>
74
- </div>
75
- </td>
76
- </tr>
77
- <tr>
78
- <th dir="<?php echo $dir ?>"><span title="<?php esc_attr_e('Shown by some email clients as excerpt', 'newsletter') ?>"><?php _e('Snippet', 'newsletter') ?></span>
79
- <?php $this->field_help('https://www.thenewsletterplugin.com/documentation/newsletters/composer/#subject') ?>
80
- </th>
81
- <td dir="<?php echo $dir ?>"><?php $this->text('preheader') ?></td>
82
- </tr>
83
- </table>
84
-
85
- <div style="text-align: left; margin-left: 1em;">
86
- <a href="https://www.thenewsletterplugin.com/documentation/newsletters/newsletter-tags/"
87
- target="_blank">You can use tags to inject subscriber fields</a>. Even on subject.
88
- </div>
89
-
90
- <div class="composer-actions">
91
-
92
- <div id="attachment-newsletter-button" class="button-primary" data-tnp-modal-target="#attachment-modal">
93
- <i class="fas fa-paperclip"></i>
94
- </div>
95
-
96
- <?php if ($show_test) { ?>
97
- <div id="test-newsletter-button" class="button-primary" data-tnp-modal-target="#test-newsletter-modal">
98
- <i class="fas fa-paper-plane"></i> <?php _e('Test', 'newsletter') ?>
99
- </div>
100
- <?php } ?>
101
-
102
- <div class="composer-view-mode">
103
-
104
- <span class="composer-view-mode__item" data-view-mode="desktop"><i class="fas fa-desktop"></i></span>
105
-
106
- <span class="composer-view-mode__item" data-view-mode="mobile"><i class="fas fa-mobile"></i></span>
107
- </div>
108
-
109
- </div>
110
-
111
- <?php include NEWSLETTER_DIR . '/emails/tnp-composer/modal/test-newsletter.php' ?>
112
- <?php include NEWSLETTER_DIR . '/emails/tnp-composer/modal/attachment.php' ?>
113
-
114
- </div>
115
- <?php } ?>
116
-
117
-
118
- <div id="newsletter-builder-area-center-frame-content" dir="<?php echo $dir ?>">
119
-
120
- <!-- Composer content -->
121
-
122
- </div>
123
- </div>
124
-
125
-
126
-
127
- <div id="newsletter-builder-sidebar" dir="<?php echo $dir ?>">
128
-
129
- <div class="tnpc-tabs">
130
- <button class="tablinks" onclick="openTab(event, 'tnpc-blocks')" data-tab-id='tnpc-blocks' id="defaultOpen"><?php _e('Blocks', 'newsletter') ?></button>
131
- <button class="tablinks" onclick="openTab(event, 'tnpc-global-styles')" data-tab-id='tnpc-global-styles'><?php _e('Settings', 'newsletter') ?></button>
132
- </div>
133
-
134
- <div id="tnpc-blocks" class="tabcontent">
135
- <?php foreach ($blocks as $k => $section) { ?>
136
- <div class="newsletter-sidebar-add-buttons" id="sidebar-add-<?php echo $k ?>">
137
- <!--<h4><span><?php echo ucfirst($k) ?></span></h4>-->
138
- <?php foreach ($section AS $key => $block) { ?>
139
- <div class="newsletter-sidebar-buttons-content-tab" data-id="<?php echo $key ?>" data-name="<?php echo esc_attr($block['name']) ?>">
140
- <img src="<?php echo $block['icon'] ?>" title="<?php echo esc_attr($block['name']) ?>">
141
- </div>
142
- <?php } ?>
143
- </div>
144
- <?php } ?>
145
- </div>
146
-
147
- <div id="tnpc-global-styles" class="tabcontent">
148
-
149
- <form id="tnpc-global-styles-form">
150
-
151
- <div class="tnp-field-row">
152
- <div class="tnp-field-col-2">
153
- <?php $fields->color('options_composer_background', __('Main background', 'newsletter')) ?>
154
- </div>
155
- <div class="tnp-field-col-2">
156
- <?php $fields->color('options_composer_block_background', 'Blocks background') ?>
157
- </div>
158
- </div>
159
-
160
- <?php $fields->font('options_composer_title_font', __('Titles font', 'newsletter')) ?>
161
- <?php $fields->font('options_composer_text_font', __('Text font', 'newsletter')) ?>
162
- <?php $fields->button_style('options_composer_button', __('Button style', 'newsletter')); ?>
163
-
164
- <button class="button-secondary" name="apply"><?php _e("Apply", 'newsletter') ?></button>
165
-
166
- </form>
167
-
168
- </div>
169
-
170
- <!-- Block options container (dynamically loaded -->
171
- <div id="tnpc-block-options">
172
- <div id="tnpc-block-options-buttons">
173
- <span id="tnpc-block-options-cancel" class="button-secondary"><?php _e("Cancel", "newsletter") ?></span>
174
- <span id="tnpc-block-options-save" class="button-primary"><?php _e("Apply", "newsletter") ?></span>
175
- </div>
176
- <form id="tnpc-block-options-form" onsubmit="return false;"></form>
177
- </div>
178
-
179
- </div>
180
-
181
- <div style="clear: both"></div>
182
-
183
- </div>
184
-
185
- <div style="display: none">
186
- <div id="newsletter-preloaded-export"></div>
187
- <!-- Block placeholder used by jQuery UI -->
188
- <div id="draggable-helper"></div>
189
- <div id="sortable-helper"></div>
190
- </div>
191
-
192
- <script type="text/javascript">
193
- TNP_PLUGIN_URL = "<?php echo esc_js(NEWSLETTER_URL) ?>";
194
- TNP_HOME_URL = "<?php echo esc_js(home_url('/', is_ssl() ? 'https' : 'http')) ?>";
195
- tnp_context_type = "<?php echo esc_js($context_type) ?>";
196
- tnp_nonce = '<?php echo esc_js(wp_create_nonce('save')) ?>';
197
- tnp_preset_nonce = '<?php echo esc_js(wp_create_nonce('preset')) ?>';
198
- </script>
199
- <?php
200
- wp_enqueue_script('tnp-composer', plugins_url('newsletter') . '/emails/tnp-composer/_scripts/newsletter-builder-v2.js', ['tnp-modal', 'tnp-toast'], NEWSLETTER_VERSION);
201
- ?>
202
-
203
- <?php include NEWSLETTER_DIR . '/emails/subjects.php'; ?>
204
-
205
- <?php if (function_exists('wp_enqueue_editor')) wp_enqueue_editor(); ?>
1
+ <?php
2
+ /**
3
+ * This file is included by NewsletterControls to create the composer.
4
+ */
5
+ /* @var $this NewsletterControls */
6
+
7
+ defined('ABSPATH') || exit;
8
+
9
+ $list = NewsletterEmails::instance()->get_blocks();
10
+
11
+ $blocks = array();
12
+ foreach ($list as $key => $data) {
13
+ if (!isset($blocks[$data['section']])) {
14
+ $blocks[$data['section']] = array();
15
+ }
16
+ $blocks[$data['section']][$key]['name'] = $data['name'];
17
+ $blocks[$data['section']][$key]['filename'] = $key;
18
+ $blocks[$data['section']][$key]['icon'] = $data['icon'];
19
+ }
20
+
21
+ // order the sections
22
+ $blocks = array_merge(array_flip(array('header', 'content', 'footer')), $blocks);
23
+
24
+ // prepare the options for the default blocks
25
+ $block_options = get_option('newsletter_main');
26
+
27
+ $fields = new NewsletterFields($controls);
28
+
29
+ $dir = is_rtl() ? 'rtl' : 'ltr';
30
+ $rev_dir = is_rtl() ? 'ltr' : 'rlt';
31
+ ?>
32
+ <script type="text/javascript">
33
+ if (window.innerWidth < 1550) {
34
+ document.body.classList.add('folded');
35
+ }
36
+
37
+ function tnp_view(type) {
38
+ if (type === 'mobile') {
39
+ jQuery('#newsletter-builder-area-center-frame-content').addClass('tnp-view-mobile');
40
+ } else {
41
+ jQuery('#newsletter-builder-area-center-frame-content').removeClass('tnp-view-mobile');
42
+ }
43
+ }
44
+ </script>
45
+
46
+
47
+ <style>
48
+ <?php echo NewsletterEmails::instance()->get_composer_backend_css(); ?>
49
+ </style>
50
+
51
+ <div id="newsletter-builder" dir="ltr">
52
+
53
+ <div id="newsletter-builder-area" class="tnp-builder-column">
54
+
55
+ <?php if ($tnpc_show_subject) { ?>
56
+ <div id="tnpc-subject-wrap" dir="<?php echo $dir ?>">
57
+ <table role="presentation" style="width: 100%">
58
+ <?php if (!empty($controls->data['sender_email'])) { ?>
59
+ <tr>
60
+ <th dir="<?php echo $dir ?>"><?php _e('From', 'newsletter') ?></th>
61
+ <td dir="<?php echo $dir ?>"><?php echo esc_html($controls->data['sender_email']) ?></td>
62
+ </tr>
63
+ <?php } ?>
64
+ <tr>
65
+ <th dir="<?php echo $dir ?>">
66
+ <?php _e('Subject', 'newsletter') ?>
67
+ <?php if ($context_type === 'automated') { ?>
68
+ <?php $this->field_help('https://www.thenewsletterplugin.com/documentation/addons/extended-features/automated-extension/#subject') ?>
69
+ <?php } ?>
70
+ </th>
71
+ <td dir="<?php echo $dir ?>">
72
+ <div id="tnpc-subject">
73
+ <?php $this->subject('subject'); ?>
74
+ </div>
75
+ </td>
76
+ </tr>
77
+ <tr>
78
+ <th dir="<?php echo $dir ?>"><span title="<?php esc_attr_e('Shown by some email clients as excerpt', 'newsletter') ?>"><?php _e('Snippet', 'newsletter') ?></span>
79
+ <?php $this->field_help('https://www.thenewsletterplugin.com/documentation/newsletters/composer/#subject') ?>
80
+ </th>
81
+ <td dir="<?php echo $dir ?>"><?php $this->text('preheader') ?></td>
82
+ </tr>
83
+ </table>
84
+
85
+ <div style="text-align: left; margin-left: 1em;">
86
+ <a href="https://www.thenewsletterplugin.com/documentation/newsletters/newsletter-tags/"
87
+ target="_blank">You can use tags to inject subscriber fields</a>. Even on subject.
88
+ </div>
89
+
90
+ <div class="composer-actions">
91
+
92
+ <div id="attachment-newsletter-button" class="button-primary" data-tnp-modal-target="#attachment-modal">
93
+ <i class="fas fa-paperclip"></i>
94
+ </div>
95
+
96
+ <?php if ($show_test) { ?>
97
+ <div id="test-newsletter-button" class="button-primary" data-tnp-modal-target="#test-newsletter-modal">
98
+ <i class="fas fa-paper-plane"></i> <?php _e('Test', 'newsletter') ?>
99
+ </div>
100
+ <?php } ?>
101
+
102
+ <div class="composer-view-mode">
103
+
104
+ <span class="composer-view-mode__item" data-view-mode="desktop"><i class="fas fa-desktop"></i></span>
105
+
106
+ <span class="composer-view-mode__item" data-view-mode="mobile"><i class="fas fa-mobile"></i></span>
107
+ </div>
108
+
109
+ </div>
110
+
111
+ <?php include NEWSLETTER_DIR . '/emails/tnp-composer/modal/test-newsletter.php' ?>
112
+ <?php include NEWSLETTER_DIR . '/emails/tnp-composer/modal/attachment.php' ?>
113
+
114
+ </div>
115
+ <?php } ?>
116
+
117
+
118
+ <div id="newsletter-builder-area-center-frame-content" dir="<?php echo $dir ?>">
119
+
120
+ <!-- Composer content -->
121
+
122
+ </div>
123
+ </div>
124
+
125
+
126
+
127
+ <div id="newsletter-builder-sidebar" dir="<?php echo $dir ?>">
128
+
129
+ <div class="tnpc-tabs">
130
+ <button class="tablinks" onclick="openTab(event, 'tnpc-blocks')" data-tab-id='tnpc-blocks' id="defaultOpen"><?php _e('Blocks', 'newsletter') ?></button>
131
+ <button class="tablinks" onclick="openTab(event, 'tnpc-global-styles')" data-tab-id='tnpc-global-styles'><?php _e('Settings', 'newsletter') ?></button>
132
+ </div>
133
+
134
+ <div id="tnpc-blocks" class="tabcontent">
135
+ <?php foreach ($blocks as $k => $section) { ?>
136
+ <div class="newsletter-sidebar-add-buttons" id="sidebar-add-<?php echo $k ?>">
137
+ <!--<h4><span><?php echo ucfirst($k) ?></span></h4>-->
138
+ <?php foreach ($section AS $key => $block) { ?>
139
+ <div class="newsletter-sidebar-buttons-content-tab" data-id="<?php echo $key ?>" data-name="<?php echo esc_attr($block['name']) ?>">
140
+ <img src="<?php echo $block['icon'] ?>" title="<?php echo esc_attr($block['name']) ?>">
141
+ </div>
142
+ <?php } ?>
143
+ </div>
144
+ <?php } ?>
145
+ </div>
146
+
147
+ <div id="tnpc-global-styles" class="tabcontent">
148
+
149
+ <form id="tnpc-global-styles-form">
150
+
151
+ <div class="tnp-field-row">
152
+ <div class="tnp-field-col-2">
153
+ <?php $fields->color('options_composer_background', __('Main background', 'newsletter')) ?>
154
+ </div>
155
+ <div class="tnp-field-col-2">
156
+ <?php $fields->color('options_composer_block_background', 'Blocks background') ?>
157
+ </div>
158
+ </div>
159
+
160
+ <?php $fields->font('options_composer_title_font', __('Titles font', 'newsletter')) ?>
161
+ <?php $fields->font('options_composer_text_font', __('Text font', 'newsletter')) ?>
162
+ <?php $fields->button_style('options_composer_button', __('Button style', 'newsletter')); ?>
163
+
164
+ <button class="button-secondary" name="apply"><?php _e("Apply", 'newsletter') ?></button>
165
+
166
+ </form>
167
+
168
+ </div>
169
+
170
+ <!-- Block options container (dynamically loaded -->
171
+ <div id="tnpc-block-options">
172
+ <div id="tnpc-block-options-buttons">
173
+ <span id="tnpc-block-options-cancel" class="button-secondary"><?php _e("Cancel", "newsletter") ?></span>
174
+ <span id="tnpc-block-options-save" class="button-primary"><?php _e("Apply", "newsletter") ?></span>
175
+ </div>
176
+ <form id="tnpc-block-options-form" onsubmit="return false;"></form>
177
+ </div>
178
+
179
+ </div>
180
+
181
+ <div style="clear: both"></div>
182
+
183
+ </div>
184
+
185
+ <div style="display: none">
186
+ <div id="newsletter-preloaded-export"></div>
187
+ <!-- Block placeholder used by jQuery UI -->
188
+ <div id="draggable-helper"></div>
189
+ <div id="sortable-helper"></div>
190
+ </div>
191
+
192
+ <script type="text/javascript">
193
+ TNP_PLUGIN_URL = "<?php echo esc_js(NEWSLETTER_URL) ?>";
194
+ TNP_HOME_URL = "<?php echo esc_js(home_url('/', is_ssl() ? 'https' : 'http')) ?>";
195
+ tnp_context_type = "<?php echo esc_js($context_type) ?>";
196
+ tnp_nonce = '<?php echo esc_js(wp_create_nonce('save')) ?>';
197
+ tnp_preset_nonce = '<?php echo esc_js(wp_create_nonce('preset')) ?>';
198
+ </script>
199
+ <?php
200
+ wp_enqueue_script('tnp-composer', plugins_url('newsletter') . '/emails/tnp-composer/_scripts/newsletter-builder-v2.js', ['tnp-modal', 'tnp-toast'], NEWSLETTER_VERSION);
201
+ ?>
202
+
203
+ <?php include NEWSLETTER_DIR . '/emails/subjects.php'; ?>
204
+
205
+ <?php if (function_exists('wp_enqueue_editor')) wp_enqueue_editor(); ?>
emails/tnp-composer/modal/test-newsletter.php CHANGED
@@ -1,49 +1,49 @@
1
- <div class="tnp-modal2"
2
- id="test-newsletter-modal"
3
- aria-hidden="true">
4
- <div class="tnp-modal2__content"
5
- role="dialog"
6
- style="width: 600px">
7
- <header class="tnp-modal2__header">
8
- <h2><?php _e( "Send a test", 'newsletter' ) ?></h2>
9
- <span class="tnp-modal2__close"
10
- data-tnp-modal-close
11
- aria-label="Close modal"></span>
12
- </header>
13
- <div class="tnp-modal2__body">
14
-
15
- <form id="test-newsletter-form">
16
- <h4><?php _e( "Send a test to", 'newsletter' ) ?></h4>
17
- <input name="email"
18
- type="email"
19
- placeholder="<?php _e( "Email", 'newsletter' ) ?>"
20
- id="test-newsletter-email">
21
- <button class="button-secondary"
22
- type="submit">
23
- <?php _e( "Send", 'newsletter' ) ?>
24
- </button>
25
- </form>
26
-
27
- <div class="separator"><?php _e( "or", 'newsletter' ) ?></div>
28
-
29
- <div class="test-subscribers">
30
- <?php if ( ! empty( NewsletterUsers::instance()->get_test_users() ) ): ?>
31
- <h4><?php _e( "Send a test to test subscribers", 'newsletter' ) ?></h4>
32
- <ul>
33
- <?php foreach ( NewsletterUsers::instance()->get_test_users() as $user ) { ?>
34
- <li><?php echo $user->email ?></li>
35
- <?php } ?>
36
- </ul>
37
- <button class="button-secondary"
38
- onclick="tnpc_test()"><?php _e( "Send", 'newsletter' ) ?></button>
39
- <?php endif; ?>
40
- <p style="float: right">
41
- <a href="https://www.thenewsletterplugin.com/documentation/subscribers#test"
42
- target="_blank">
43
- <?php _e( 'Read more about test subscribers', 'newsletter' ) ?></a>
44
- </p>
45
- </div>
46
-
47
- </div>
48
- </div>
49
- </div>
1
+ <div class="tnp-modal2"
2
+ id="test-newsletter-modal"
3
+ aria-hidden="true">
4
+ <div class="tnp-modal2__content"
5
+ role="dialog"
6
+ style="width: 600px">
7
+ <header class="tnp-modal2__header">
8
+ <h2><?php _e( "Send a test", 'newsletter' ) ?></h2>
9
+ <span class="tnp-modal2__close"
10
+ data-tnp-modal-close
11
+ aria-label="Close modal"></span>
12
+ </header>
13
+ <div class="tnp-modal2__body">
14
+
15
+ <form id="test-newsletter-form">
16
+ <h4><?php _e( "Send a test to", 'newsletter' ) ?></h4>
17
+ <input name="email"
18
+ type="email"
19
+ placeholder="<?php _e( "Email", 'newsletter' ) ?>"
20
+ id="test-newsletter-email">
21
+ <button class="button-secondary"
22
+ type="submit">
23
+ <?php _e( "Send", 'newsletter' ) ?>
24
+ </button>
25
+ </form>
26
+
27
+ <div class="separator"><?php _e( "or", 'newsletter' ) ?></div>
28
+
29
+ <div class="test-subscribers">
30
+ <?php if ( ! empty( NewsletterUsers::instance()->get_test_users() ) ): ?>
31
+ <h4><?php _e( "Send a test to test subscribers", 'newsletter' ) ?></h4>
32
+ <ul>
33
+ <?php foreach ( NewsletterUsers::instance()->get_test_users() as $user ) { ?>
34
+ <li><?php echo $user->email ?></li>
35
+ <?php } ?>
36
+ </ul>
37
+ <button class="button-secondary"
38
+ onclick="tnpc_test()"><?php _e( "Send", 'newsletter' ) ?></button>
39
+ <?php endif; ?>
40
+ <p style="float: right">
41
+ <a href="https://www.thenewsletterplugin.com/documentation/subscribers#test"
42
+ target="_blank">
43
+ <?php _e( 'Read more about test subscribers', 'newsletter' ) ?></a>
44
+ </p>
45
+ </div>
46
+
47
+ </div>
48
+ </div>
49
+ </div>
includes/PHPMailerLoader.php CHANGED
@@ -1,45 +1,45 @@
1
- <?php
2
-
3
- namespace TNP\Mailer;
4
-
5
- class PHPMailerLoader {
6
-
7
- /**
8
- *
9
- */
10
- public static function load() {
11
-
12
- global $wp_version;
13
-
14
- if ( class_exists( 'PHPMailer' ) ) {
15
- return;
16
- }
17
-
18
- if ( version_compare( $wp_version, '5.4.9' ) > 0 ) {
19
- require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
20
- require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
21
- require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
22
-
23
- class_alias( \PHPMailer\PHPMailer\PHPMailer::class, 'PHPMailer' );
24
- class_alias( \PHPMailer\PHPMailer\SMTP::class, 'SMTP' );
25
- class_alias( \PHPMailer\PHPMailer\Exception::class, 'phpmailerException' );
26
- } else {
27
- require_once ABSPATH . WPINC . '/class-phpmailer.php';
28
- require_once ABSPATH . WPINC . '/class-smtp.php';
29
- }
30
-
31
- }
32
-
33
- /**
34
- *
35
- * @param boolean $exceptions
36
- * @return \PHPMailer\PHPMailer\PHPMailer
37
- */
38
- public static function make_instance($exceptions = false) {
39
- self::load();
40
-
41
- return new \PHPMailer( $exceptions );
42
- }
43
-
44
-
45
- }
1
+ <?php
2
+
3
+ namespace TNP\Mailer;
4
+
5
+ class PHPMailerLoader {
6
+
7
+ /**
8
+ *
9
+ */
10
+ public static function load() {
11
+
12
+ global $wp_version;
13
+
14
+ if ( class_exists( 'PHPMailer' ) ) {
15
+ return;
16
+ }
17
+
18
+ if ( version_compare( $wp_version, '5.5' ) >= 0 ) {
19
+ require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
20
+ require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
21
+ require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
22
+
23
+ class_alias( \PHPMailer\PHPMailer\PHPMailer::class, 'PHPMailer' );
24
+ class_alias( \PHPMailer\PHPMailer\SMTP::class, 'SMTP' );
25
+ class_alias( \PHPMailer\PHPMailer\Exception::class, 'phpmailerException' );
26
+ } else {
27
+ require_once ABSPATH . WPINC . '/class-phpmailer.php';
28
+ require_once ABSPATH . WPINC . '/class-smtp.php';
29
+ }
30
+
31
+ }
32
+
33
+ /**
34
+ *
35
+ * @param boolean $exceptions
36
+ * @return \PHPMailer\PHPMailer\PHPMailer
37
+ */
38
+ public static function make_instance($exceptions = false) {
39
+ self::load();
40
+
41
+ return new \PHPMailer( $exceptions );
42
+ }
43
+
44
+
45
+ }
includes/TNP.php CHANGED
@@ -1,262 +1,262 @@
1
- <?php
2
-
3
- /*
4
- * TNP classes for internal API
5
- *
6
- * Error reference
7
- * 404 Object not found
8
- * 403 Not allowed (when the API key is missing or wrong)
9
- * 400 Bad request, when the parameters are not correct or required parameters are missing
10
- *
11
- */
12
-
13
- /**
14
- * Main API functions
15
- *
16
- * @author roby
17
- */
18
- class TNP {
19
- /*
20
- * The full process of subscription
21
- */
22
-
23
- public static function subscribe($params) {
24
-
25
- if ($params instanceof TNP_Subscription) {
26
- return NewsletterSubscription::instance()->subscribe2($params);
27
- }
28
-
29
- $logger = new NewsletterLogger('phpapi');
30
- $logger->debug($params);
31
-
32
- apply_filters('newsletter_api_subscribe', $params);
33
-
34
- $newsletter = Newsletter::instance();
35
-
36
- $subscription = NewsletterSubscription::instance()->get_default_subscription();
37
- $subscription->spamcheck = isset($params['spamcheck']);
38
- $data = $subscription->data;
39
-
40
- $subscription->send_emails = !empty($params['send_emails']);
41
-
42
- // Form field configuration
43
- $options_profile = get_option('newsletter_profile', array());
44
-
45
- $data->email = $params['email'];
46
-
47
- if (isset($params['name'])) {
48
- $data->name = $params['name'];
49
- }
50
-
51
- if (isset($params['surname'])) {
52
- $data->surname = $params['surname'];
53
- }
54
-
55
- // Lists
56
- if (isset($params['lists']) && is_array($params['lists'])) {
57
- $public_lists = array_keys($newsletter->get_lists_public());
58
- $list_ids = array_intersect($public_lists, $params['lists']);
59
-
60
- foreach ($list_ids as $list_id) {
61
- $data->lists['' . $list_id] = 1;
62
- }
63
- }
64
-
65
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
66
- // If the profile cannot be set by subscriber, skip it.
67
- if ($options_profile['profile_' . $i . '_status'] == 0) {
68
- continue;
69
- }
70
- if (isset($params['profile_' . $i])) {
71
- $data->profiles['' . $i] = stripslashes($params['profile_' . $i]);
72
- }
73
- }
74
-
75
- $data->ip = $newsletter->get_remote_ip();
76
-
77
- $user = NewsletterSubscription::instance()->subscribe2($subscription);
78
-
79
- return $user;
80
- }
81
-
82
- /*
83
- * The UNsubscription
84
- */
85
-
86
- public static function unsubscribe($params) {
87
-
88
- $newsletter = Newsletter::instance();
89
- $user = $newsletter->get_user($params['email']);
90
-
91
- // $newsletter->logger->debug($params);
92
-
93
- if (!$user) {
94
- return new WP_Error('-1', 'Email address not found', array('status' => 404));
95
- }
96
-
97
- if ($user->status == TNP_User::STATUS_UNSUBSCRIBED) {
98
- return;
99
- }
100
-
101
- $user = $newsletter->set_user_status($user, 'U');
102
- $newsletter->add_user_log($user, 'unsubscribe');
103
-
104
- NewsletterUnsubscription::instance()->send_unsubscribed_email($user);
105
-
106
- NewsletterUnsubscription::instance()->notify_admin_on_unsubscription($user);
107
-
108
- do_action('newsletter_unsubscribed', $user);
109
-
110
- return;
111
- }
112
-
113
- /*
114
- * Adds a subscriber if not already in
115
- */
116
-
117
- public static function add_subscriber($params) {
118
-
119
- $newsletter = Newsletter::instance();
120
- $subscription = NewsletterSubscription::instance();
121
-
122
- $email = $newsletter->normalize_email(stripslashes($params['email']));
123
-
124
- if (!$email) {
125
- return new WP_Error('-1', 'Email address not valid', array('status' => 400));
126
- }
127
-
128
- $user = $newsletter->get_user($email);
129
-
130
- if ($user) {
131
- return new WP_Error('-1', 'Email address already exists', array('status' => 400));
132
- }
133
-
134
- $user = array('email' => $email);
135
-
136
- if (isset($params['name'])) {
137
- $user['name'] = $newsletter->normalize_name(stripslashes($params['name']));
138
- }
139
-
140
- if (isset($params['surname'])) {
141
- $user['surname'] = $newsletter->normalize_name(stripslashes($params['surname']));
142
- }
143
-
144
- if (!empty($params['gender'])) {
145
- $user['sex'] = $newsletter->normalize_sex($params['gender']);
146
- }
147
-
148
- if (!empty($params['country'])) {
149
- $user['country'] = sanitize_text_field($params['country']);
150
- }
151
-
152
- if (!empty($params['region'])) {
153
- $user['region'] = sanitize_text_field($params['region']);
154
- }
155
-
156
- if (!empty($params['city'])) {
157
- $user['city'] = sanitize_text_field($params['city']);
158
- }
159
-
160
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i ++) {
161
- if (isset($params['profile_' . $i])) {
162
- $user['profile_' . $i] = trim(stripslashes($params['profile_' . $i]));
163
- }
164
- }
165
-
166
- // Lists (an array under the key "lists")
167
- //(field names are nl[] and values the list number so special forms with radio button can work)
168
- if (isset($params['lists']) && is_array($params['lists'])) {
169
- foreach ($params['lists'] as $list_id) {
170
- $user['list_' . ( (int) $list_id )] = 1;
171
- }
172
- }
173
-
174
-
175
- if (!empty($params['status'])) {
176
- $user['status'] = $params['status'];
177
- } else {
178
- $user['status'] = 'C';
179
- }
180
-
181
- if (!empty($params['language'])) {
182
- $user['language'] = $params['language'];
183
- }
184
-
185
- $user['token'] = $newsletter->get_token();
186
- $user['updated'] = time();
187
-
188
- $user['ip'] = Newsletter::get_remote_ip();
189
-
190
- $user = $newsletter->save_user($user);
191
-
192
- return $user;
193
- }
194
-
195
- /*
196
- * Subscribers list
197
- */
198
-
199
- public static function subscribers($params) {
200
-
201
- global $wpdb;
202
- $newsletter = Newsletter::instance();
203
-
204
- $items_per_page = 20;
205
- $where = "";
206
-
207
- $query = "select name, email from " . NEWSLETTER_USERS_TABLE . ' ' . $where . " order by id desc";
208
- $query .= " limit 0," . $items_per_page;
209
- $list = $wpdb->get_results($query);
210
-
211
- return $list;
212
- }
213
-
214
- /*
215
- * Deletes a subscriber
216
- */
217
-
218
- public static function delete_subscriber($params) {
219
-
220
- global $wpdb;
221
- $newsletter = Newsletter::instance();
222
-
223
- $user = $newsletter->get_user($params['email']);
224
-
225
- if (!$user) {
226
- return new WP_Error('-1', 'Email address not found', array('status' => 404));
227
- }
228
-
229
- if ($wpdb->query($wpdb->prepare("delete from " . NEWSLETTER_USERS_TABLE . " where id=%d", (int) $user->id))) {
230
- return "OK";
231
- } else {
232
- $newsletter->logger->debug($wpdb->last_query);
233
-
234
- return new WP_Error('-1', $wpdb->last_error, array('status' => 400));
235
- }
236
- }
237
-
238
- /*
239
- * Newsletters list
240
- */
241
-
242
- public static function newsletters($params) {
243
-
244
- global $wpdb;
245
- $newsletter = Newsletter::instance();
246
-
247
- $list = $wpdb->get_results("SELECT id, subject, created, status, total, sent, send_on FROM " . NEWSLETTER_EMAILS_TABLE . " ORDER BY id DESC LIMIT 10", OBJECT);
248
-
249
- if ($wpdb->last_error) {
250
- $newsletter->logger->error($wpdb->last_error);
251
-
252
- return false;
253
- }
254
-
255
- if (empty($list)) {
256
- return array();
257
- }
258
-
259
- return $list;
260
- }
261
-
262
- }
1
+ <?php
2
+
3
+ /*
4
+ * TNP classes for internal API
5
+ *
6
+ * Error reference
7
+ * 404 Object not found
8
+ * 403 Not allowed (when the API key is missing or wrong)
9
+ * 400 Bad request, when the parameters are not correct or required parameters are missing
10
+ *
11
+ */
12
+
13
+ /**
14
+ * Main API functions
15
+ *
16
+ * @author roby
17
+ */
18
+ class TNP {
19
+ /*
20
+ * The full process of subscription
21
+ */
22
+
23
+ public static function subscribe($params) {
24
+
25
+ if ($params instanceof TNP_Subscription) {
26
+ return NewsletterSubscription::instance()->subscribe2($params);
27
+ }
28
+
29
+ $logger = new NewsletterLogger('phpapi');
30
+ $logger->debug($params);
31
+
32
+ apply_filters('newsletter_api_subscribe', $params);
33
+
34
+ $newsletter = Newsletter::instance();
35
+
36
+ $subscription = NewsletterSubscription::instance()->get_default_subscription();
37
+ $subscription->spamcheck = isset($params['spamcheck']);
38
+ $data = $subscription->data;
39
+
40
+ $subscription->send_emails = !empty($params['send_emails']);
41
+
42
+ // Form field configuration
43
+ $options_profile = get_option('newsletter_profile', array());
44
+
45
+ $data->email = $params['email'];
46
+
47
+ if (isset($params['name'])) {
48
+ $data->name = $params['name'];
49
+ }
50
+
51
+ if (isset($params['surname'])) {
52
+ $data->surname = $params['surname'];
53
+ }
54
+
55
+ // Lists
56
+ if (isset($params['lists']) && is_array($params['lists'])) {
57
+ $public_lists = array_keys($newsletter->get_lists_public());
58
+ $list_ids = array_intersect($public_lists, $params['lists']);
59
+
60
+ foreach ($list_ids as $list_id) {
61
+ $data->lists['' . $list_id] = 1;
62
+ }
63
+ }
64
+
65
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
66
+ // If the profile cannot be set by subscriber, skip it.
67
+ if ($options_profile['profile_' . $i . '_status'] == 0) {
68
+ continue;
69
+ }
70
+ if (isset($params['profile_' . $i])) {
71
+ $data->profiles['' . $i] = stripslashes($params['profile_' . $i]);
72
+ }
73
+ }
74
+
75
+ $data->ip = $newsletter->get_remote_ip();
76
+
77
+ $user = NewsletterSubscription::instance()->subscribe2($subscription);
78
+
79
+ return $user;
80
+ }
81
+
82
+ /*
83
+ * The UNsubscription
84
+ */
85
+
86
+ public static function unsubscribe($params) {
87
+
88
+ $newsletter = Newsletter::instance();
89
+ $user = $newsletter->get_user($params['email']);
90
+
91
+ // $newsletter->logger->debug($params);
92
+
93
+ if (!$user) {
94
+ return new WP_Error('-1', 'Email address not found', array('status' => 404));
95
+ }
96
+
97
+ if ($user->status == TNP_User::STATUS_UNSUBSCRIBED) {
98
+ return;
99
+ }
100
+
101
+ $user = $newsletter->set_user_status($user, 'U');
102
+ $newsletter->add_user_log($user, 'unsubscribe');
103
+
104
+ NewsletterUnsubscription::instance()->send_unsubscribed_email($user);
105
+
106
+ NewsletterUnsubscription::instance()->notify_admin_on_unsubscription($user);
107
+
108
+ do_action('newsletter_unsubscribed', $user);
109
+
110
+ return;
111
+ }
112
+
113
+ /*
114
+ * Adds a subscriber if not already in
115
+ */
116
+
117
+ public static function add_subscriber($params) {
118
+
119
+ $newsletter = Newsletter::instance();
120
+ $subscription = NewsletterSubscription::instance();
121
+
122
+ $email = $newsletter->normalize_email(stripslashes($params['email']));
123
+
124
+ if (!$email) {
125
+ return new WP_Error('-1', 'Email address not valid', array('status' => 400));
126
+ }
127
+
128
+ $user = $newsletter->get_user($email);
129
+
130
+ if ($user) {
131
+ return new WP_Error('-1', 'Email address already exists', array('status' => 400));
132
+ }
133
+
134
+ $user = array('email' => $email);
135
+
136
+ if (isset($params['name'])) {
137
+ $user['name'] = $newsletter->normalize_name(stripslashes($params['name']));
138
+ }
139
+
140
+ if (isset($params['surname'])) {
141
+ $user['surname'] = $newsletter->normalize_name(stripslashes($params['surname']));
142
+ }
143
+
144
+ if (!empty($params['gender'])) {
145
+ $user['sex'] = $newsletter->normalize_sex($params['gender']);
146
+ }
147
+
148
+ if (!empty($params['country'])) {
149
+ $user['country'] = sanitize_text_field($params['country']);
150
+ }
151
+
152
+ if (!empty($params['region'])) {
153
+ $user['region'] = sanitize_text_field($params['region']);
154
+ }
155
+
156
+ if (!empty($params['city'])) {
157
+ $user['city'] = sanitize_text_field($params['city']);
158
+ }
159
+
160
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i ++) {
161
+ if (isset($params['profile_' . $i])) {
162
+ $user['profile_' . $i] = trim(stripslashes($params['profile_' . $i]));
163
+ }
164
+ }
165
+
166
+ // Lists (an array under the key "lists")
167
+ //(field names are nl[] and values the list number so special forms with radio button can work)
168
+ if (isset($params['lists']) && is_array($params['lists'])) {
169
+ foreach ($params['lists'] as $list_id) {
170
+ $user['list_' . ( (int) $list_id )] = 1;
171
+ }
172
+ }
173
+
174
+
175
+ if (!empty($params['status'])) {
176
+ $user['status'] = $params['status'];
177
+ } else {
178
+ $user['status'] = 'C';
179
+ }
180
+
181
+ if (!empty($params['language'])) {
182
+ $user['language'] = $params['language'];
183
+ }
184
+
185
+ $user['token'] = $newsletter->get_token();
186
+ $user['updated'] = time();
187
+
188
+ $user['ip'] = Newsletter::get_remote_ip();
189
+
190
+ $user = $newsletter->save_user($user);
191
+
192
+ return $user;
193
+ }
194
+
195
+ /*
196
+ * Subscribers list
197
+ */
198
+
199
+ public static function subscribers($params) {
200
+
201
+ global $wpdb;
202
+ $newsletter = Newsletter::instance();
203
+
204
+ $items_per_page = 20;
205
+ $where = "";
206
+
207
+ $query = "select name, email from " . NEWSLETTER_USERS_TABLE . ' ' . $where . " order by id desc";
208
+ $query .= " limit 0," . $items_per_page;
209
+ $list = $wpdb->get_results($query);
210
+
211
+ return $list;
212
+ }
213
+
214
+ /*
215
+ * Deletes a subscriber
216
+ */
217
+
218
+ public static function delete_subscriber($params) {
219
+
220
+ global $wpdb;
221
+ $newsletter = Newsletter::instance();
222
+
223
+ $user = $newsletter->get_user($params['email']);
224
+
225
+ if (!$user) {
226
+ return new WP_Error('-1', 'Email address not found', array('status' => 404));
227
+ }
228
+
229
+ if ($wpdb->query($wpdb->prepare("delete from " . NEWSLETTER_USERS_TABLE . " where id=%d", (int) $user->id))) {
230
+ return "OK";
231
+ } else {
232
+ $newsletter->logger->debug($wpdb->last_query);
233
+
234
+ return new WP_Error('-1', $wpdb->last_error, array('status' => 400));
235
+ }
236
+ }
237
+
238
+ /*
239
+ * Newsletters list
240
+ */
241
+
242
+ public static function newsletters($params) {
243
+
244
+ global $wpdb;
245
+ $newsletter = Newsletter::instance();
246
+
247
+ $list = $wpdb->get_results("SELECT id, subject, created, status, total, sent, send_on FROM " . NEWSLETTER_EMAILS_TABLE . " ORDER BY id DESC LIMIT 10", OBJECT);
248
+
249
+ if ($wpdb->last_error) {
250
+ $newsletter->logger->error($wpdb->last_error);
251
+
252
+ return false;
253
+ }
254
+
255
+ if (empty($list)) {
256
+ return array();
257
+ }
258
+
259
+ return $list;
260
+ }
261
+
262
+ }
includes/composer.php CHANGED
@@ -1,987 +1,987 @@
1
- <?php
2
-
3
- /** For old style coders */
4
- function tnp_register_block($dir) {
5
- return TNP_Composer::register_block($dir);
6
- }
7
-
8
- /**
9
- * Generates and HTML button for email using the values found on $options and
10
- * prefixed by $prefix, with the standard syntax of NewsletterFields::button().
11
- *
12
- * @param array $options
13
- * @param string $prefix
14
- * @return string
15
- */
16
- function tnpc_button($options, $prefix = 'button') {
17
- return TNP_Composer::button($options, $prefix);
18
- }
19
-
20
- class TNP_Composer {
21
-
22
- static $block_dirs = array();
23
-
24
- static function register_block($dir) {
25
- // Checks
26
- $dir = realpath($dir);
27
- if (!$dir) {
28
- $error = new WP_Error('1', 'Seems not a valid path: ' . $dir);
29
- NewsletterEmails::instance()->logger->error($error);
30
- return $error;
31
- }
32
-
33
- $dir = wp_normalize_path($dir);
34
-
35
- if (!file_exists($dir . '/block.php')) {
36
- $error = new WP_Error('1', 'block.php missing on folder ' . $dir);
37
- NewsletterEmails::instance()->logger->error($error);
38
- return $error;
39
- }
40
-
41
- self::$block_dirs[] = $dir;
42
- return true;
43
- }
44
-
45
- /**
46
- * @param string $open
47
- * @param string $inner
48
- * @param string $close
49
- * @param string[] $markers
50
- *
51
- * @return string
52
- */
53
- static function wrap_html_element($open, $inner, $close, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
54
-
55
- return $open . $markers[0] . $inner . $markers[1] . $close;
56
- }
57
-
58
- /**
59
- * @param string $block
60
- * @param string[] $markers
61
- *
62
- * @return string
63
- */
64
- static function unwrap_html_element($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
65
- if (self::_has_markers($block, $markers)) {
66
- self::_escape_markers($markers);
67
- $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
68
-
69
- $matches = array();
70
- preg_match($pattern, $block, $matches);
71
-
72
- return $matches[1];
73
- }
74
-
75
- return $block;
76
- }
77
-
78
- /**
79
- * @param string $block
80
- * @param string[] $markers
81
- *
82
- * @return bool
83
- */
84
- private static function _has_markers($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
85
-
86
- self::_escape_markers($markers);
87
-
88
- $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
89
-
90
- return preg_match($pattern, $block);
91
- }
92
-
93
- /**
94
- * Sources:
95
- * - https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
96
- *
97
- * @param type $email
98
- * @return type
99
- */
100
- static function get_html_open($email) {
101
- $open = '<!DOCTYPE html>' . "\n";
102
- $open .= '<html xmlns="https://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">' . "\n";
103
- $open .= '<head>' . "\n";
104
- $open .= '<title>{email_subject}</title>' . "\n";
105
- $open .= '<meta charset="utf-8">' . "\n";
106
- $open .= '<meta name="viewport" content="width=device-width, initial-scale=1">' . "\n";
107
- $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge">' . "\n";
108
- $open .= '<meta name="format-detection" content="address=no">' . "\n";
109
- $open .= '<meta name="format-detection" content="telephone=no">' . "\n";
110
- $open .= '<meta name="format-detection" content="email=no">' . "\n";
111
- $open .= '<meta name="x-apple-disable-message-reformatting">' . "\n";
112
-
113
- // $open .= '<!--[if !mso]><!-->' . "\n";
114
- // $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
115
- // $open .= '<!--<![endif]-->' . "\n";
116
-
117
- // $open .= '<!--[if mso]>' . "\n";
118
-
119
- $open .= '<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->' . "\n";
120
-
121
- // $open .= '<style type="text/css">';
122
- // $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
123
- // $open .= 'div, td {padding:0;}';
124
- // $open .= 'div {margin:0 !important;}';
125
- // $open .= '</style>';
126
- // $open .= "\n";
127
- // $open .= '<noscript>';
128
- // $open .= '<xml>';
129
- // $open .= '<o:OfficeDocumentSettings>';
130
- // $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
131
- // $open .= '</o:OfficeDocumentSettings>';
132
- // $open .= '</xml>';
133
- // $open .= '</noscript>';
134
- // $open .= "\n";
135
- // $open .= '<![endif]-->';
136
- // $open .= "\n";
137
- $open .= '<style type="text/css">' . "\n";
138
- $open .= NewsletterEmails::instance()->get_composer_css();
139
- $open .= "\n</style>\n";
140
- $open .= "</head>\n";
141
- $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
142
- $open .= "\n";
143
- $open .= self::get_html_preheader($email);
144
-
145
- return $open;
146
- }
147
-
148
- static private function get_html_preheader($email) {
149
-
150
- if (empty($email->options['preheader'])) {
151
- return "";
152
- }
153
-
154
- $preheader_text = esc_html($email->options['preheader']);
155
- $html = "<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">$preheader_text</div>";
156
- $html .= "\n";
157
-
158
- return $html;
159
- }
160
-
161
- static function get_html_close($email) {
162
- return "</body>\n</html>";
163
- }
164
-
165
- /**
166
- *
167
- * @param TNP_Email $email
168
- * @return string
169
- */
170
- static function get_main_wrapper_open($email) {
171
- if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
172
- $bgcolor = '';
173
- } else {
174
- $bgcolor = $email->options['composer_background'];
175
- }
176
-
177
- return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
178
- "<tr>\n" .
179
- "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
180
- }
181
-
182
- /**
183
- *
184
- * @param TNP_Email $email
185
- * @return string
186
- */
187
- static function get_main_wrapper_close($email) {
188
- return "\n<!-- /tnp -->\n" .
189
- "</td>\n" .
190
- "</tr>\n" .
191
- "</table>\n\n";
192
- }
193
-
194
- /**
195
- * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
196
- *
197
- * @param string $html_email
198
- *
199
- * @return string
200
- */
201
- static function unwrap_email($html_email) {
202
-
203
- if (self::_has_markers($html_email)) {
204
- $html_email = self::unwrap_html_element($html_email);
205
- } else {
206
- //KEEP FOR OLD EMAIL COMPATIBILITY
207
- // Extracts only the body part
208
- $x = strpos($html_email, '<body');
209
- if ($x) {
210
- $x = strpos($html_email, '>', $x);
211
- $y = strpos($html_email, '</body>');
212
- $html_email = substr($html_email, $x + 1, $y - $x - 1);
213
- }
214
-
215
- /* Cleans up uncorrectly stored newsletter bodies */
216
- $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
217
- $html_email = preg_replace('/<meta.*?>/', '', $html_email);
218
- $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
219
- $html_email = trim($html_email);
220
- }
221
-
222
- // Required since esc_html DOES NOT escape the HTML entities (apparently)
223
- $html_email = str_replace('&', '&amp;', $html_email);
224
- $html_email = str_replace('"', '&quot;', $html_email);
225
- $html_email = str_replace('<', '&lt;', $html_email);
226
- $html_email = str_replace('>', '&gt;', $html_email);
227
-
228
- return $html_email;
229
- }
230
-
231
- private static function _escape_markers(&$markers) {
232
- $markers[0] = str_replace('/', '\/', $markers[0]);
233
- $markers[1] = str_replace('/', '\/', $markers[1]);
234
- }
235
-
236
- /**
237
- * Using the data collected inside $controls (and submitted by a form containing the
238
- * composer fields), updates the email. The message body is completed with doctype,
239
- * head, style and the main wrapper.
240
- *
241
- * @param TNP_Email $email
242
- * @param NewsletterControls $controls
243
- */
244
- static function update_email($email, $controls) {
245
- if (isset($controls->data['subject'])) {
246
- $email->subject = $controls->data['subject'];
247
- }
248
-
249
- // They should be only composer options
250
- foreach ($controls->data as $name => $value) {
251
- if (strpos($name, 'options_') === 0) {
252
- $email->options[substr($name, 8)] = $value;
253
- }
254
- }
255
-
256
- //if (isset($controls->data['preheader'])) {
257
- // $email->options['preheader'] = $controls->data['preheader'];
258
- //}
259
-
260
- $email->editor = NewsletterEmails::EDITOR_COMPOSER;
261
-
262
- $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
263
- $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
264
- }
265
-
266
- /**
267
- * Prepares a controls object injecting the relevant fields from an email
268
- * which cannot be directly used by controls. If $email is null or missing,
269
- * $controls is prepared with default values.
270
- *
271
- * @param NewsletterControls $controls
272
- * @param TNP_Email $email
273
- */
274
- static function prepare_controls($controls, $email = null) {
275
-
276
- // Controls for a new email (which actually does not exist yet
277
- if (!empty($email)) {
278
-
279
- foreach ($email->options as $name => $value) {
280
- $controls->data['options_' . $name] = $value;
281
- }
282
-
283
- $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
284
- $controls->data['subject'] = $email->subject;
285
- $controls->data['updated'] = $email->updated;
286
- }
287
-
288
- if (!empty($email->options['sender_email'])) {
289
- $controls->data['sender_email'] = $email->options['sender_email'];
290
- } else {
291
- $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
292
- }
293
-
294
- if (!empty($email->options['sender_name'])) {
295
- $controls->data['sender_name'] = $email->options['sender_name'];
296
- } else {
297
- $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
298
- }
299
-
300
- $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
301
- }
302
-
303
- /**
304
- * Extract inline edited post field from inline_edit_list[]
305
- *
306
- * @param array $inline_edit_list
307
- * @param string $field_type
308
- * @param int $post_id
309
- *
310
- * @return string
311
- */
312
- static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
313
-
314
- foreach ($inline_edit_list as $edit) {
315
- if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
316
- return $edit['content'];
317
- }
318
- }
319
-
320
- return '';
321
- }
322
-
323
- /**
324
- * Check if inline_edit_list[] have inline edit field for specific post
325
- *
326
- * @param array $inline_edit_list
327
- * @param string $field_type
328
- * @param int $post_id
329
- *
330
- * @return bool
331
- */
332
- static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
333
- if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
334
- return false;
335
- }
336
- foreach ($inline_edit_list as $edit) {
337
- if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
338
- return true;
339
- }
340
- }
341
-
342
- return false;
343
- }
344
-
345
- /**
346
- * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
347
- *
348
- * - [prefix]_url The button URL
349
- * - [prefix]_font_family
350
- * - [prefix]_font_size
351
- * - [prefix]_font_weight
352
- * - [prefix]_label
353
- * - [prefix]_font_color The label color
354
- * - [prefix]_background The button color
355
- *
356
- * TODO: Add radius and possiblt the alignment
357
- *
358
- * @param array $options
359
- * @param string $prefix
360
- * @return string
361
- */
362
- static function button($options, $prefix = 'button') {
363
-
364
- if (empty($options[$prefix . '_label'])) {
365
- return;
366
- }
367
- $defaults = [
368
- $prefix . '_url' => '#',
369
- $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
370
- $prefix . '_font_color' => '#ffffff',
371
- $prefix . '_font_weight' => 'bold',
372
- $prefix . '_font_size' => 20,
373
- $prefix . '_background' => '#256F9C',
374
- $prefix . '_align' => 'center'
375
- ];
376
-
377
- $options = array_merge($defaults, array_filter($options));
378
-
379
- $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
380
- if (!empty($options[$prefix . '_align'])) {
381
- $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
382
- }
383
- if (!empty($options[$prefix . '_width'])) {
384
- $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
385
- }
386
- $b .= '>';
387
- $b .= '<tr>';
388
- $b .= '<td align="center" bgcolor="' . $options[$prefix . '_background'] . '" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:' . $options[$prefix . '_background'] . '" valign="middle">';
389
- $b .= '<a href="' . $options[$prefix . '_url'] . '"';
390
- $b .= ' style="display:inline-block;background:' . $options[$prefix . '_background'] . ';color:' . $options[$prefix . '_font_color'] . ';font-family:' . $options[$prefix . '_font_family'] . ';font-size:' . $options[$prefix . '_font_size'] . 'px;font-weight:' . $options[$prefix . '_font_weight'] . ';line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"';
391
- $b .= ' target="_blank">';
392
- $b .= $options[$prefix . '_label'];
393
- $b .= '</a>';
394
- $b .= '</td></tr></table>';
395
- return $b;
396
- }
397
-
398
- /**
399
- * Generates an IMG tag, linked if the media has an URL.
400
- *
401
- * @param TNP_Media $media
402
- * @param string $style
403
- * @return string
404
- */
405
- static function image($media, $attr = []) {
406
-
407
- $default_attrs = [
408
- 'style' => 'max-width: 100%; height: auto; display: inline-block',
409
- 'class' => '',
410
- 'link-style' => 'text-decoration: none; display: inline-block',
411
- 'link-class' => null,
412
- ];
413
-
414
- $attr = array_merge($default_attrs, $attr);
415
-
416
- //Class and style attribute are mutually exclusive.
417
- //Class take priority to style because classes will transform to inline style inside block rendering operation
418
- if (!empty($attr['inline-class'])) {
419
- $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
420
- } else {
421
- $styling = ' style="' . esc_attr($attr['style']) . '" ';
422
- }
423
-
424
- if (!empty($attr['class'])) {
425
- $styling .= ' class="' . esc_attr($attr['class']) . '" ';
426
- }
427
-
428
- //Class and style attribute are mutually exclusive.
429
- //Class take priority to style because classes will transform to inline style inside block rendering operation
430
- if (!empty($attr['link-class'])) {
431
- $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
432
- } else {
433
- $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
434
- }
435
-
436
- $b = '';
437
- if ($media->link) {
438
- $b .= '<a href="' . esc_attr($media->link) . '" target="_blank" rel="noopener nofollow" style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
439
- } else {
440
- // The span grants images are not upscaled when fluid (two columns posts block)
441
- $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
442
- }
443
- if ($media) {
444
- $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
445
- if ($media->height) {
446
- $b .= ' height="' . esc_attr($media->height) . '"';
447
- }
448
- $b .= ' alt="' . esc_attr($media->alt) . '"'
449
- . ' border="0"'
450
- . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0; font-size: 12px"'
451
- . ' class="' . esc_attr($attr['class']) . '" '
452
- . '>';
453
- }
454
-
455
- if ($media->link) {
456
- $b .= '</a>';
457
- } else {
458
- $b .= '</span>';
459
- }
460
-
461
- return $b;
462
- }
463
-
464
- /**
465
- * Returns a WP media ID for the specified post (or false if nothing can be found)
466
- * looking for the featured image or, if missing, taking the first media in the gallery and
467
- * if again missing, searching the first reference to a media in the post content.
468
- *
469
- * The media ID is not checked for real existance of the associated attachment.
470
- *
471
- * @param int $post_id
472
- * @return int
473
- */
474
- static function get_post_thumbnail_id($post_id) {
475
- if (is_object($post_id)) {
476
- $post_id = $post_id->ID;
477
- }
478
-
479
- // Find a media id to be used as featured image
480
- $media_id = get_post_thumbnail_id($post_id);
481
- if (!empty($media_id)) {
482
- return $media_id;
483
- }
484
-
485
- $attachments = get_children(array('numberpost' => 1, 'post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order'));
486
- if (!empty($attachments)) {
487
- foreach ($attachments as $id => &$attachment) {
488
- return $id;
489
- }
490
- }
491
-
492
- $post = get_post($post_id);
493
-
494
- $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
495
- if ($matches) {
496
- return (int) $matches[1];
497
- }
498
-
499
- return false;
500
- }
501
-
502
- /**
503
- * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
504
- * media has a size which best match the one requested (this is the standard WP behavior, plugins
505
- * could change it).
506
- *
507
- * @param int $media_id
508
- * @param array $size
509
- * @return \TNP_Media
510
- */
511
- function get_media($media_id, $size) {
512
- $src = wp_get_attachment_image_src($media_id, $size);
513
- if (!$src) {
514
- return null;
515
- }
516
- $media = new TNP_Media();
517
- $media->id = $media_id;
518
- $media->url = $src[0];
519
- $media->width = $src[1];
520
- $media->height = $src[2];
521
- return $media;
522
- }
523
-
524
- static function post_content($post) {
525
- $content = $post->post_content;
526
- $content = wpautop($content);
527
- if (true || $options['enable shortcodes']) {
528
- remove_shortcode('gallery');
529
- add_shortcode('gallery', 'tnp_gallery_shortcode');
530
- $content = do_shortcode($content);
531
- }
532
- $content = str_replace('<p>', '<p class="paragraph">', $content);
533
-
534
- $selected_images = array();
535
- if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
536
- foreach ($matches[0] as $image) {
537
- if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
538
- $selected_images[$image] = $attachment_id;
539
- }
540
- }
541
- }
542
-
543
- foreach ($selected_images as $image => $attachment_id) {
544
- $src = tnp_media_resize($attachment_id, array(600, 0));
545
- if (is_wp_error($src)) {
546
- continue;
547
- }
548
- $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
549
- }
550
-
551
- return $content;
552
- }
553
-
554
- static function get_global_style_defaults() {
555
- return [
556
- 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
557
- 'options_composer_title_font_size' => 32,
558
- 'options_composer_title_font_weight' => 'normal',
559
- 'options_composer_title_font_color' => '#222222',
560
- 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
561
- 'options_composer_text_font_size' => 16,
562
- 'options_composer_text_font_weight' => 'normal',
563
- 'options_composer_text_font_color' => '#222222',
564
- 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
565
- 'options_composer_button_font_size' => 16,
566
- 'options_composer_button_font_weight' => 'normal',
567
- 'options_composer_button_font_color' => '#FFFFFF',
568
- 'options_composer_button_background_color' => '#256F9C',
569
- 'options_composer_background' => '#FFFFFF',
570
- 'options_composer_block_background' => '#FFFFFF',
571
- ];
572
- }
573
-
574
- /**
575
- * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
576
- *
577
- * Attributes:
578
- * - columns: number of columns [2]
579
- * - padding: cells padding [10]
580
- * - responsive: il on mobile the cell should stack up [true]
581
- * - width: the whole row width, it should reduced by the external row padding [600]
582
- *
583
- * @param string[] $items
584
- * @param array $attrs
585
- * @return string
586
- */
587
- static function grid($items = [], $attrs = []) {
588
- $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
589
- $width = (int) $attrs['width'];
590
- $columns = (int) $attrs['columns'];
591
- $padding = (int) $attrs['padding'];
592
- $column_width = $width / $columns;
593
- $td_width = 100 / $columns;
594
- $chunks = array_chunk($items, $columns);
595
-
596
- if ($attrs['responsive']) {
597
-
598
- $e = '';
599
- foreach ($chunks as &$chunk) {
600
- $e .= '<div style="text-align:center;font-size:0;">';
601
- $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
602
- foreach ($chunk as &$item) {
603
- $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
604
-
605
- $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
606
-
607
- // This element to add padding without deal with border-box not well supported
608
- $e .= '<div style="padding:' . $padding . 'px;">';
609
- $e .= $item;
610
- $e .= '</div>';
611
- $e .= '</div>';
612
-
613
- $e .= '<!--[if mso]></td><![endif]-->';
614
- }
615
- $e .= '<!--[if mso]></tr></table><![endif]-->';
616
- $e .= '</div>';
617
- }
618
-
619
- return $e;
620
- } else {
621
- $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
622
- foreach ($chunks as &$chunk) {
623
- $e .= '<tr>';
624
- foreach ($chunk as &$item) {
625
- $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
626
- $e .= $item;
627
- $e .= '</td>';
628
- }
629
- $e .= '</tr>';
630
- }
631
- $e .= '</table>';
632
- return $e;
633
- }
634
- }
635
-
636
- static function get_text_style($options, $prefix, $composer, $attrs = []) {
637
- return self::get_style($options, $prefix, $composer, 'text', $attrs);
638
- }
639
-
640
- static function get_title_style($options, $prefix, $composer, $attrs = []) {
641
- return self::get_style($options, $prefix, $composer, 'title', $attrs);
642
- }
643
-
644
- static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
645
- $style = new TNP_Style();
646
- $scale = 1.0;
647
- if (!empty($attrs['scale'])) {
648
- $scale = (float) $attrs['scale'];
649
- }
650
- if (!empty($prefix))
651
- $prefix .= '_';
652
-
653
- $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
654
- $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
655
- $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
656
- $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
657
- if ($type === 'button') {
658
- $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
659
- }
660
- return $style;
661
- }
662
-
663
- static function get_button_options($options, $prefix, $composer) {
664
- $button_options = [];
665
- $scale = 1;
666
- $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
667
- $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
668
- $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
669
- $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
670
- $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
671
- $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
672
- $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
673
- $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
674
- $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
675
-
676
- return $button_options;
677
- }
678
-
679
- static function convert_to_text($html) {
680
- if (!class_exists('DOMDocument')) {
681
- return '';
682
- }
683
- // Replace '&' with '&amp;' in URLs to avoid warnings about inavlid entities from loadHTML()
684
- // Todo: make this more general using a regular expression
685
- //$logger = PlaintextNewsletterAddon::$instance->get_logger();
686
- //$logger->debug('html="' . $html . '"');
687
- $html = str_replace(
688
- array('&nk=', '&nek=', '&id='),
689
- array('&amp;nk=', '&amp;nek=', '&amp;id='),
690
- $html);
691
- //$logger->debug('new html="' . $html . '"');
692
- //
693
- $output = '';
694
-
695
- // Prevents warnings for problems with the HTML
696
- if (function_exists('libxml_use_internal_errors')) {
697
- libxml_use_internal_errors(true);
698
- }
699
- $dom = new DOMDocument();
700
- $r = $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html);
701
- if (!$r) {
702
- return '';
703
- }
704
- $bodylist = $dom->getElementsByTagName('body');
705
- // Of course it should be a single element
706
- foreach ($bodylist as $body) {
707
- self::process_dom_element($body, $output);
708
- }
709
- return $output;
710
- }
711
-
712
- static function process_dom_element(DOMElement $parent, &$output) {
713
- foreach ($parent->childNodes as $node) {
714
- if (is_a($node, 'DOMElement') && ($node->tagName != 'style')) {
715
- if ($node->tagName== 'br') {
716
- $output .= "\n";
717
- continue;
718
- }
719
- self::process_dom_element($node, $output);
720
-
721
- // If the containing tag was a block level tag, we add a couple of line ending
722
- if ($node->tagName == 'p' || $node->tagName == 'div' || $node->tagName == 'td') {
723
- // Avoid more than one blank line between elements
724
- if ((strlen($output) >= 2) && (substr($output, -2) != "\n\n")) {
725
- $output .= "\n\n";
726
- }
727
- }
728
-
729
- if ($node->tagName == 'a') {
730
- $output .= ' (' . $node->getAttribute('href') . ') ';
731
- continue;
732
- }
733
- elseif ($node->tagName == 'img') {
734
- $output .= $node->getAttribute('alt');
735
- }
736
- }
737
- elseif (is_a($node, 'DOMText')) {
738
- $decoded = utf8_decode($node->wholeText);
739
- if (ctype_space($decoded)) {
740
- // Append blank only if last character output is not blank.
741
- if ((strlen($output) > 0) && !ctype_space(substr($output, -1))) {
742
- $output .= ' ';
743
- }
744
- } else {
745
- $output .= trim($node->wholeText);
746
- }
747
- }
748
- }
749
- }
750
- }
751
-
752
-
753
-
754
- class TNP_Style {
755
-
756
- var $font_family;
757
- var $font_size;
758
- var $font_weight;
759
- var $font_color;
760
- var $background;
761
-
762
- function echo_css($scale = 1.0) {
763
- echo 'font-size: ', round($this->font_size * $scale), 'px;';
764
- echo 'font-family: ', $this->font_family, ';';
765
- echo 'font-weight: ', $this->font_weight, ';';
766
- echo 'color: ', $this->font_color, ';';
767
- }
768
-
769
- }
770
-
771
- /**
772
- * Generate multicolumn and responsive html template for email.
773
- * Initialize class with max columns per row and start to add cells.
774
- */
775
- class TNP_Composer_Grid_System {
776
-
777
- /**
778
- * @var TNP_Composer_Grid_Row[]
779
- */
780
- private $rows;
781
-
782
- /**
783
- * @var int
784
- */
785
- private $cells_per_row;
786
-
787
- /**
788
- * @var int
789
- */
790
- private $cells_counter;
791
-
792
- /**
793
- * TNP_Composer_Grid_System constructor.
794
- *
795
- * @param int $columns_per_row Max columns per row
796
- */
797
- public function __construct($columns_per_row) {
798
- $this->cells_per_row = $columns_per_row;
799
- $this->cells_counter = 0;
800
- $this->rows = [];
801
- }
802
-
803
- public function __toString() {
804
- return $this->render();
805
- }
806
-
807
- /**
808
- * Add cell to grid
809
- *
810
- * @param TNP_Composer_Grid_Cell $cell
811
- */
812
- public function add_cell($cell) {
813
-
814
- if ($this->cells_counter % $this->cells_per_row === 0) {
815
- $this->add_row(new TNP_Composer_Grid_Row());
816
- }
817
-
818
- $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
819
- $this->rows[$row_idx]->add_cell($cell);
820
- $this->cells_counter++;
821
- }
822
-
823
- private function add_row($row) {
824
- $this->rows[] = $row;
825
- }
826
-
827
- public function render() {
828
-
829
- $str = '';
830
- foreach ($this->rows as $row) {
831
- $str .= $row->render();
832
- }
833
-
834
- return $str;
835
- }
836
-
837
- }
838
-
839
- /**
840
- * Class TNP_Composer_Grid_Row
841
- */
842
- class TNP_Composer_Grid_Row {
843
-
844
- /**
845
- * @var TNP_Composer_Grid_Cell[]
846
- */
847
- private $cells;
848
-
849
- public function __construct(...$cells) {
850
- if (!empty($cells)) {
851
- foreach ($cells as $cell) {
852
- $this->add_cell($cell);
853
- }
854
- }
855
- }
856
-
857
- /**
858
- * @param TNP_Composer_Grid_Cell $cell
859
- */
860
- public function add_cell($cell) {
861
- $this->cells[] = $cell;
862
- }
863
-
864
- public function render() {
865
- $rendered_cells = '';
866
- $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
867
- foreach ($this->cells as $cell) {
868
- $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
869
- }
870
-
871
- $row_template = $this->get_template();
872
-
873
- return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
874
- }
875
-
876
- private function cells_count() {
877
- return count($this->cells);
878
- }
879
-
880
- private function get_template() {
881
- return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
882
- }
883
-
884
- }
885
-
886
- /**
887
- * Class TNP_Composer_Grid_Cell
888
- */
889
- class TNP_Composer_Grid_Cell {
890
-
891
- /**
892
- * @var string
893
- */
894
- private $content;
895
-
896
- /**
897
- * @var array
898
- */
899
- public $args;
900
-
901
- public function __construct($content = null, $args = []) {
902
- $default_args = [
903
- 'width' => '100%',
904
- 'class' => '',
905
- 'align' => 'left',
906
- 'valign' => 'top'
907
- ];
908
-
909
- $this->args = array_merge($default_args, $args);
910
-
911
- $this->content = $content ? $content : '';
912
- }
913
-
914
- public function add_content($content) {
915
- $this->content .= $content;
916
- }
917
-
918
- public function render($args) {
919
- $this->args = array_merge($this->args, $args);
920
-
921
- $column_template = $this->get_template();
922
- $column = str_replace(
923
- [
924
- 'TNP_ALIGN_PH',
925
- 'TNP_VALIGN_PH',
926
- 'TNP_WIDTH_PH',
927
- 'TNP_CLASS_PH',
928
- 'TNP_COLUMN_CONTENT_PH'
929
- ], [
930
- $this->args['align'],
931
- $this->args['valign'],
932
- $this->args['width'],
933
- $this->args['class'],
934
- $this->content
935
- ], $column_template);
936
-
937
- return $column;
938
- }
939
-
940
- private function get_template() {
941
- return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
942
- <tbody>
943
- <tr>
944
- <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
945
- TNP_COLUMN_CONTENT_PH
946
- </td>
947
- </tr>
948
- </tbody>
949
- </table>";
950
- }
951
-
952
- }
953
-
954
- class TNP_Composer_Component_Factory {
955
-
956
- private $options;
957
-
958
- /**
959
- * TNP_Composer_Component_Factory constructor.
960
- *
961
- * @param Controller$controller
962
- */
963
- public function __construct($controller) {
964
-
965
- }
966
-
967
- function heading() {
968
-
969
- }
970
-
971
- function paragraph() {
972
-
973
- }
974
-
975
- function link() {
976
-
977
- }
978
-
979
- function button() {
980
-
981
- }
982
-
983
- function image() {
984
-
985
- }
986
-
987
- }
1
+ <?php
2
+
3
+ /** For old style coders */
4
+ function tnp_register_block($dir) {
5
+ return TNP_Composer::register_block($dir);
6
+ }
7
+
8
+ /**
9
+ * Generates and HTML button for email using the values found on $options and
10
+ * prefixed by $prefix, with the standard syntax of NewsletterFields::button().
11
+ *
12
+ * @param array $options
13
+ * @param string $prefix
14
+ * @return string
15
+ */
16
+ function tnpc_button($options, $prefix = 'button') {
17
+ return TNP_Composer::button($options, $prefix);
18
+ }
19
+
20
+ class TNP_Composer {
21
+
22
+ static $block_dirs = array();
23
+
24
+ static function register_block($dir) {
25
+ // Checks
26
+ $dir = realpath($dir);
27
+ if (!$dir) {
28
+ $error = new WP_Error('1', 'Seems not a valid path: ' . $dir);
29
+ NewsletterEmails::instance()->logger->error($error);
30
+ return $error;
31
+ }
32
+
33
+ $dir = wp_normalize_path($dir);
34
+
35
+ if (!file_exists($dir . '/block.php')) {
36
+ $error = new WP_Error('1', 'block.php missing on folder ' . $dir);
37
+ NewsletterEmails::instance()->logger->error($error);
38
+ return $error;
39
+ }
40
+
41
+ self::$block_dirs[] = $dir;
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * @param string $open
47
+ * @param string $inner
48
+ * @param string $close
49
+ * @param string[] $markers
50
+ *
51
+ * @return string
52
+ */
53
+ static function wrap_html_element($open, $inner, $close, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
54
+
55
+ return $open . $markers[0] . $inner . $markers[1] . $close;
56
+ }
57
+
58
+ /**
59
+ * @param string $block
60
+ * @param string[] $markers
61
+ *
62
+ * @return string
63
+ */
64
+ static function unwrap_html_element($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
65
+ if (self::_has_markers($block, $markers)) {
66
+ self::_escape_markers($markers);
67
+ $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
68
+
69
+ $matches = array();
70
+ preg_match($pattern, $block, $matches);
71
+
72
+ return $matches[1];
73
+ }
74
+
75
+ return $block;
76
+ }
77
+
78
+ /**
79
+ * @param string $block
80
+ * @param string[] $markers
81
+ *
82
+ * @return bool
83
+ */
84
+ private static function _has_markers($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
85
+
86
+ self::_escape_markers($markers);
87
+
88
+ $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
89
+
90
+ return preg_match($pattern, $block);
91
+ }
92
+
93
+ /**
94
+ * Sources:
95
+ * - https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
96
+ *
97
+ * @param type $email
98
+ * @return type
99
+ */
100
+ static function get_html_open($email) {
101
+ $open = '<!DOCTYPE html>' . "\n";
102
+ $open .= '<html xmlns="https://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">' . "\n";
103
+ $open .= '<head>' . "\n";
104
+ $open .= '<title>{email_subject}</title>' . "\n";
105
+ $open .= '<meta charset="utf-8">' . "\n";
106
+ $open .= '<meta name="viewport" content="width=device-width, initial-scale=1">' . "\n";
107
+ $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge">' . "\n";
108
+ $open .= '<meta name="format-detection" content="address=no">' . "\n";
109
+ $open .= '<meta name="format-detection" content="telephone=no">' . "\n";
110
+ $open .= '<meta name="format-detection" content="email=no">' . "\n";
111
+ $open .= '<meta name="x-apple-disable-message-reformatting">' . "\n";
112
+
113
+ // $open .= '<!--[if !mso]><!-->' . "\n";
114
+ // $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
115
+ // $open .= '<!--<![endif]-->' . "\n";
116
+
117
+ // $open .= '<!--[if mso]>' . "\n";
118
+
119
+ $open .= '<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->' . "\n";
120
+
121
+ // $open .= '<style type="text/css">';
122
+ // $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
123
+ // $open .= 'div, td {padding:0;}';
124
+ // $open .= 'div {margin:0 !important;}';
125
+ // $open .= '</style>';
126
+ // $open .= "\n";
127
+ // $open .= '<noscript>';
128
+ // $open .= '<xml>';
129
+ // $open .= '<o:OfficeDocumentSettings>';
130
+ // $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
131
+ // $open .= '</o:OfficeDocumentSettings>';
132
+ // $open .= '</xml>';
133
+ // $open .= '</noscript>';
134
+ // $open .= "\n";
135
+ // $open .= '<![endif]-->';
136
+ // $open .= "\n";
137
+ $open .= '<style type="text/css">' . "\n";
138
+ $open .= NewsletterEmails::instance()->get_composer_css();
139
+ $open .= "\n</style>\n";
140
+ $open .= "</head>\n";
141
+ $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
142
+ $open .= "\n";
143
+ $open .= self::get_html_preheader($email);
144
+
145
+ return $open;
146
+ }
147
+
148
+ static private function get_html_preheader($email) {
149
+
150
+ if (empty($email->options['preheader'])) {
151
+ return "";
152
+ }
153
+
154
+ $preheader_text = esc_html($email->options['preheader']);
155
+ $html = "<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">$preheader_text</div>";
156
+ $html .= "\n";
157
+
158
+ return $html;
159
+ }
160
+
161
+ static function get_html_close($email) {
162
+ return "</body>\n</html>";
163
+ }
164
+
165
+ /**
166
+ *
167
+ * @param TNP_Email $email
168
+ * @return string
169
+ */
170
+ static function get_main_wrapper_open($email) {
171
+ if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
172
+ $bgcolor = '';
173
+ } else {
174
+ $bgcolor = $email->options['composer_background'];
175
+ }
176
+
177
+ return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
178
+ "<tr>\n" .
179
+ "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
180
+ }
181
+
182
+ /**
183
+ *
184
+ * @param TNP_Email $email
185
+ * @return string
186
+ */
187
+ static function get_main_wrapper_close($email) {
188
+ return "\n<!-- /tnp -->\n" .
189
+ "</td>\n" .
190
+ "</tr>\n" .
191
+ "</table>\n\n";
192
+ }
193
+
194
+ /**
195
+ * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
196
+ *
197
+ * @param string $html_email
198
+ *
199
+ * @return string
200
+ */
201
+ static function unwrap_email($html_email) {
202
+
203
+ if (self::_has_markers($html_email)) {
204
+ $html_email = self::unwrap_html_element($html_email);
205
+ } else {
206
+ //KEEP FOR OLD EMAIL COMPATIBILITY
207
+ // Extracts only the body part
208
+ $x = strpos($html_email, '<body');
209
+ if ($x) {
210
+ $x = strpos($html_email, '>', $x);
211
+ $y = strpos($html_email, '</body>');
212
+ $html_email = substr($html_email, $x + 1, $y - $x - 1);
213
+ }
214
+
215
+ /* Cleans up uncorrectly stored newsletter bodies */
216
+ $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
217
+ $html_email = preg_replace('/<meta.*?>/', '', $html_email);
218
+ $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
219
+ $html_email = trim($html_email);
220
+ }
221
+
222
+ // Required since esc_html DOES NOT escape the HTML entities (apparently)
223
+ $html_email = str_replace('&', '&amp;', $html_email);
224
+ $html_email = str_replace('"', '&quot;', $html_email);
225
+ $html_email = str_replace('<', '&lt;', $html_email);
226
+ $html_email = str_replace('>', '&gt;', $html_email);
227
+
228
+ return $html_email;
229
+ }
230
+
231
+ private static function _escape_markers(&$markers) {
232
+ $markers[0] = str_replace('/', '\/', $markers[0]);
233
+ $markers[1] = str_replace('/', '\/', $markers[1]);
234
+ }
235
+
236
+ /**
237
+ * Using the data collected inside $controls (and submitted by a form containing the
238
+ * composer fields), updates the email. The message body is completed with doctype,
239
+ * head, style and the main wrapper.
240
+ *
241
+ * @param TNP_Email $email
242
+ * @param NewsletterControls $controls
243
+ */
244
+ static function update_email($email, $controls) {
245
+ if (isset($controls->data['subject'])) {
246
+ $email->subject = $controls->data['subject'];
247
+ }
248
+
249
+ // They should be only composer options
250
+ foreach ($controls->data as $name => $value) {
251
+ if (strpos($name, 'options_') === 0) {
252
+ $email->options[substr($name, 8)] = $value;
253
+ }
254
+ }
255
+
256
+ //if (isset($controls->data['preheader'])) {
257
+ // $email->options['preheader'] = $controls->data['preheader'];
258
+ //}
259
+
260
+ $email->editor = NewsletterEmails::EDITOR_COMPOSER;
261
+
262
+ $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
263
+ $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
264
+ }
265
+
266
+ /**
267
+ * Prepares a controls object injecting the relevant fields from an email
268
+ * which cannot be directly used by controls. If $email is null or missing,
269
+ * $controls is prepared with default values.
270
+ *
271
+ * @param NewsletterControls $controls
272
+ * @param TNP_Email $email
273
+ */
274
+ static function prepare_controls($controls, $email = null) {
275
+
276
+ // Controls for a new email (which actually does not exist yet
277
+ if (!empty($email)) {
278
+
279
+ foreach ($email->options as $name => $value) {
280
+ $controls->data['options_' . $name] = $value;
281
+ }
282
+
283
+ $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
284
+ $controls->data['subject'] = $email->subject;
285
+ $controls->data['updated'] = $email->updated;
286
+ }
287
+
288
+ if (!empty($email->options['sender_email'])) {
289
+ $controls->data['sender_email'] = $email->options['sender_email'];
290
+ } else {
291
+ $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
292
+ }
293
+
294
+ if (!empty($email->options['sender_name'])) {
295
+ $controls->data['sender_name'] = $email->options['sender_name'];
296
+ } else {
297
+ $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
298
+ }
299
+
300
+ $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
301
+ }
302
+
303
+ /**
304
+ * Extract inline edited post field from inline_edit_list[]
305
+ *
306
+ * @param array $inline_edit_list
307
+ * @param string $field_type
308
+ * @param int $post_id
309
+ *
310
+ * @return string
311
+ */
312
+ static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
313
+
314
+ foreach ($inline_edit_list as $edit) {
315
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
316
+ return $edit['content'];
317
+ }
318
+ }
319
+
320
+ return '';
321
+ }
322
+
323
+ /**
324
+ * Check if inline_edit_list[] have inline edit field for specific post
325
+ *
326
+ * @param array $inline_edit_list
327
+ * @param string $field_type
328
+ * @param int $post_id
329
+ *
330
+ * @return bool
331
+ */
332
+ static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
333
+ if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
334
+ return false;
335
+ }
336
+ foreach ($inline_edit_list as $edit) {
337
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
338
+ return true;
339
+ }
340
+ }
341
+
342
+ return false;
343
+ }
344
+
345
+ /**
346
+ * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
347
+ *
348
+ * - [prefix]_url The button URL
349
+ * - [prefix]_font_family
350
+ * - [prefix]_font_size
351
+ * - [prefix]_font_weight
352
+ * - [prefix]_label
353
+ * - [prefix]_font_color The label color
354
+ * - [prefix]_background The button color
355
+ *
356
+ * TODO: Add radius and possiblt the alignment
357
+ *
358
+ * @param array $options
359
+ * @param string $prefix
360
+ * @return string
361
+ */
362
+ static function button($options, $prefix = 'button') {
363
+
364
+ if (empty($options[$prefix . '_label'])) {
365
+ return;
366
+ }
367
+ $defaults = [
368
+ $prefix . '_url' => '#',
369
+ $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
370
+ $prefix . '_font_color' => '#ffffff',
371
+ $prefix . '_font_weight' => 'bold',
372
+ $prefix . '_font_size' => 20,
373
+ $prefix . '_background' => '#256F9C',
374
+ $prefix . '_align' => 'center'
375
+ ];
376
+
377
+ $options = array_merge($defaults, array_filter($options));
378
+
379
+ $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
380
+ if (!empty($options[$prefix . '_align'])) {
381
+ $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
382
+ }
383
+ if (!empty($options[$prefix . '_width'])) {
384
+ $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
385
+ }
386
+ $b .= '>';
387
+ $b .= '<tr>';
388
+ $b .= '<td align="center" bgcolor="' . $options[$prefix . '_background'] . '" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:' . $options[$prefix . '_background'] . '" valign="middle">';
389
+ $b .= '<a href="' . $options[$prefix . '_url'] . '"';
390
+ $b .= ' style="display:inline-block;background:' . $options[$prefix . '_background'] . ';color:' . $options[$prefix . '_font_color'] . ';font-family:' . $options[$prefix . '_font_family'] . ';font-size:' . $options[$prefix . '_font_size'] . 'px;font-weight:' . $options[$prefix . '_font_weight'] . ';line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"';
391
+ $b .= ' target="_blank">';
392
+ $b .= $options[$prefix . '_label'];
393
+ $b .= '</a>';
394
+ $b .= '</td></tr></table>';
395
+ return $b;
396
+ }
397
+
398
+ /**
399
+ * Generates an IMG tag, linked if the media has an URL.
400
+ *
401
+ * @param TNP_Media $media
402
+ * @param string $style
403
+ * @return string
404
+ */
405
+ static function image($media, $attr = []) {
406
+
407
+ $default_attrs = [
408
+ 'style' => 'max-width: 100%; height: auto; display: inline-block',
409
+ 'class' => '',
410
+ 'link-style' => 'text-decoration: none; display: inline-block',
411
+ 'link-class' => null,
412
+ ];
413
+
414
+ $attr = array_merge($default_attrs, $attr);
415
+
416
+ //Class and style attribute are mutually exclusive.
417
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
418
+ if (!empty($attr['inline-class'])) {
419
+ $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
420
+ } else {
421
+ $styling = ' style="' . esc_attr($attr['style']) . '" ';
422
+ }
423
+
424
+ if (!empty($attr['class'])) {
425
+ $styling .= ' class="' . esc_attr($attr['class']) . '" ';
426
+ }
427
+
428
+ //Class and style attribute are mutually exclusive.
429
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
430
+ if (!empty($attr['link-class'])) {
431
+ $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
432
+ } else {
433
+ $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
434
+ }
435
+
436
+ $b = '';
437
+ if ($media->link) {
438
+ $b .= '<a href="' . esc_attr($media->link) . '" target="_blank" rel="noopener nofollow" style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
439
+ } else {
440
+ // The span grants images are not upscaled when fluid (two columns posts block)
441
+ $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
442
+ }
443
+ if ($media) {
444
+ $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
445
+ if ($media->height) {
446
+ $b .= ' height="' . esc_attr($media->height) . '"';
447
+ }
448
+ $b .= ' alt="' . esc_attr($media->alt) . '"'
449
+ . ' border="0"'
450
+ . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0; font-size: 12px"'
451
+ . ' class="' . esc_attr($attr['class']) . '" '
452
+ . '>';
453
+ }
454
+
455
+ if ($media->link) {
456
+ $b .= '</a>';
457
+ } else {
458
+ $b .= '</span>';
459
+ }
460
+
461
+ return $b;
462
+ }
463
+
464
+ /**
465
+ * Returns a WP media ID for the specified post (or false if nothing can be found)
466
+ * looking for the featured image or, if missing, taking the first media in the gallery and
467
+ * if again missing, searching the first reference to a media in the post content.
468
+ *
469
+ * The media ID is not checked for real existance of the associated attachment.
470
+ *
471
+ * @param int $post_id
472
+ * @return int
473
+ */
474
+ static function get_post_thumbnail_id($post_id) {
475
+ if (is_object($post_id)) {
476
+ $post_id = $post_id->ID;
477
+ }
478
+
479
+ // Find a media id to be used as featured image
480
+ $media_id = get_post_thumbnail_id($post_id);
481
+ if (!empty($media_id)) {
482
+ return $media_id;
483
+ }
484
+
485
+ $attachments = get_children(array('numberpost' => 1, 'post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order'));
486
+ if (!empty($attachments)) {
487
+ foreach ($attachments as $id => &$attachment) {
488
+ return $id;
489
+ }
490
+ }
491
+
492
+ $post = get_post($post_id);
493
+
494
+ $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
495
+ if ($matches) {
496
+ return (int) $matches[1];
497
+ }
498
+
499
+ return false;
500
+ }
501
+
502
+ /**
503
+ * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
504
+ * media has a size which best match the one requested (this is the standard WP behavior, plugins
505
+ * could change it).
506
+ *
507
+ * @param int $media_id
508
+ * @param array $size
509
+ * @return \TNP_Media
510
+ */
511
+ function get_media($media_id, $size) {
512
+ $src = wp_get_attachment_image_src($media_id, $size);
513
+ if (!$src) {
514
+ return null;
515
+ }
516
+ $media = new TNP_Media();
517
+ $media->id = $media_id;
518
+ $media->url = $src[0];
519
+ $media->width = $src[1];
520
+ $media->height = $src[2];
521
+ return $media;
522
+ }
523
+
524
+ static function post_content($post) {
525
+ $content = $post->post_content;
526
+ $content = wpautop($content);
527
+ if (true || $options['enable shortcodes']) {
528
+ remove_shortcode('gallery');
529
+ add_shortcode('gallery', 'tnp_gallery_shortcode');
530
+ $content = do_shortcode($content);
531
+ }
532
+ $content = str_replace('<p>', '<p class="paragraph">', $content);
533
+
534
+ $selected_images = array();
535
+ if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
536
+ foreach ($matches[0] as $image) {
537
+ if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
538
+ $selected_images[$image] = $attachment_id;
539
+ }
540
+ }
541
+ }
542
+
543
+ foreach ($selected_images as $image => $attachment_id) {
544
+ $src = tnp_media_resize($attachment_id, array(600, 0));
545
+ if (is_wp_error($src)) {
546
+ continue;
547
+ }
548
+ $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
549
+ }
550
+
551
+ return $content;
552
+ }
553
+
554
+ static function get_global_style_defaults() {
555
+ return [
556
+ 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
557
+ 'options_composer_title_font_size' => 32,
558
+ 'options_composer_title_font_weight' => 'normal',
559
+ 'options_composer_title_font_color' => '#222222',
560
+ 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
561
+ 'options_composer_text_font_size' => 16,
562
+ 'options_composer_text_font_weight' => 'normal',
563
+ 'options_composer_text_font_color' => '#222222',
564
+ 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
565
+ 'options_composer_button_font_size' => 16,
566
+ 'options_composer_button_font_weight' => 'normal',
567
+ 'options_composer_button_font_color' => '#FFFFFF',
568
+ 'options_composer_button_background_color' => '#256F9C',
569
+ 'options_composer_background' => '#FFFFFF',
570
+ 'options_composer_block_background' => '#FFFFFF',
571
+ ];
572
+ }
573
+
574
+ /**
575
+ * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
576
+ *
577
+ * Attributes:
578
+ * - columns: number of columns [2]
579
+ * - padding: cells padding [10]
580
+ * - responsive: il on mobile the cell should stack up [true]
581
+ * - width: the whole row width, it should reduced by the external row padding [600]
582
+ *
583
+ * @param string[] $items
584
+ * @param array $attrs
585
+ * @return string
586
+ */
587
+ static function grid($items = [], $attrs = []) {
588
+ $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
589
+ $width = (int) $attrs['width'];
590
+ $columns = (int) $attrs['columns'];
591
+ $padding = (int) $attrs['padding'];
592
+ $column_width = $width / $columns;
593
+ $td_width = 100 / $columns;
594
+ $chunks = array_chunk($items, $columns);
595
+
596
+ if ($attrs['responsive']) {
597
+
598
+ $e = '';
599
+ foreach ($chunks as &$chunk) {
600
+ $e .= '<div style="text-align:center;font-size:0;">';
601
+ $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
602
+ foreach ($chunk as &$item) {
603
+ $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
604
+
605
+ $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
606
+
607
+ // This element to add padding without deal with border-box not well supported
608
+ $e .= '<div style="padding:' . $padding . 'px;">';
609
+ $e .= $item;
610
+ $e .= '</div>';
611
+ $e .= '</div>';
612
+
613
+ $e .= '<!--[if mso]></td><![endif]-->';
614
+ }
615
+ $e .= '<!--[if mso]></tr></table><![endif]-->';
616
+ $e .= '</div>';
617
+ }
618
+
619
+ return $e;
620
+ } else {
621
+ $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
622
+ foreach ($chunks as &$chunk) {
623
+ $e .= '<tr>';
624
+ foreach ($chunk as &$item) {
625
+ $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
626
+ $e .= $item;
627
+ $e .= '</td>';
628
+ }
629
+ $e .= '</tr>';
630
+ }
631
+ $e .= '</table>';
632
+ return $e;
633
+ }
634
+ }
635
+
636
+ static function get_text_style($options, $prefix, $composer, $attrs = []) {
637
+ return self::get_style($options, $prefix, $composer, 'text', $attrs);
638
+ }
639
+
640
+ static function get_title_style($options, $prefix, $composer, $attrs = []) {
641
+ return self::get_style($options, $prefix, $composer, 'title', $attrs);
642
+ }
643
+
644
+ static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
645
+ $style = new TNP_Style();
646
+ $scale = 1.0;
647
+ if (!empty($attrs['scale'])) {
648
+ $scale = (float) $attrs['scale'];
649
+ }
650
+ if (!empty($prefix))
651
+ $prefix .= '_';
652
+
653
+ $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
654
+ $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
655
+ $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
656
+ $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
657
+ if ($type === 'button') {
658
+ $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
659
+ }
660
+ return $style;
661
+ }
662
+
663
+ static function get_button_options($options, $prefix, $composer) {
664
+ $button_options = [];
665
+ $scale = 1;
666
+ $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
667
+ $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
668
+ $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
669
+ $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
670
+ $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
671
+ $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
672
+ $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
673
+ $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
674
+ $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
675
+
676
+ return $button_options;
677
+ }
678
+
679
+ static function convert_to_text($html) {
680
+ if (!class_exists('DOMDocument')) {
681
+ return '';
682
+ }
683
+ // Replace '&' with '&amp;' in URLs to avoid warnings about inavlid entities from loadHTML()
684
+ // Todo: make this more general using a regular expression
685
+ //$logger = PlaintextNewsletterAddon::$instance->get_logger();
686
+ //$logger->debug('html="' . $html . '"');
687
+ $html = str_replace(
688
+ array('&nk=', '&nek=', '&id='),
689
+ array('&amp;nk=', '&amp;nek=', '&amp;id='),
690
+ $html);
691
+ //$logger->debug('new html="' . $html . '"');
692
+ //
693
+ $output = '';
694
+
695
+ // Prevents warnings for problems with the HTML
696
+ if (function_exists('libxml_use_internal_errors')) {
697
+ libxml_use_internal_errors(true);
698
+ }
699
+ $dom = new DOMDocument();
700
+ $r = $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html);
701
+ if (!$r) {
702
+ return '';
703
+ }
704
+ $bodylist = $dom->getElementsByTagName('body');
705
+ // Of course it should be a single element
706
+ foreach ($bodylist as $body) {
707
+ self::process_dom_element($body, $output);
708
+ }
709
+ return $output;
710
+ }
711
+
712
+ static function process_dom_element(DOMElement $parent, &$output) {
713
+ foreach ($parent->childNodes as $node) {
714
+ if (is_a($node, 'DOMElement') && ($node->tagName != 'style')) {
715
+ if ($node->tagName== 'br') {
716
+ $output .= "\n";
717
+ continue;
718
+ }
719
+ self::process_dom_element($node, $output);
720
+
721
+ // If the containing tag was a block level tag, we add a couple of line ending
722
+ if ($node->tagName == 'p' || $node->tagName == 'div' || $node->tagName == 'td') {
723
+ // Avoid more than one blank line between elements
724
+ if ((strlen($output) >= 2) && (substr($output, -2) != "\n\n")) {
725
+ $output .= "\n\n";
726
+ }
727
+ }
728
+
729
+ if ($node->tagName == 'a') {
730
+ $output .= ' (' . $node->getAttribute('href') . ') ';
731
+ continue;
732
+ }
733
+ elseif ($node->tagName == 'img') {
734
+ $output .= $node->getAttribute('alt');
735
+ }
736
+ }
737
+ elseif (is_a($node, 'DOMText')) {
738
+ $decoded = utf8_decode($node->wholeText);
739
+ if (ctype_space($decoded)) {
740
+ // Append blank only if last character output is not blank.
741
+ if ((strlen($output) > 0) && !ctype_space(substr($output, -1))) {
742
+ $output .= ' ';
743
+ }
744
+ } else {
745
+ $output .= trim($node->wholeText);
746
+ }
747
+ }
748
+ }
749
+ }
750
+ }
751
+
752
+
753
+
754
+ class TNP_Style {
755
+
756
+ var $font_family;
757
+ var $font_size;
758
+ var $font_weight;
759
+ var $font_color;
760
+ var $background;
761
+
762
+ function echo_css($scale = 1.0) {
763
+ echo 'font-size: ', round($this->font_size * $scale), 'px;';
764
+ echo 'font-family: ', $this->font_family, ';';
765
+ echo 'font-weight: ', $this->font_weight, ';';
766
+ echo 'color: ', $this->font_color, ';';
767
+ }
768
+
769
+ }
770
+
771
+ /**
772
+ * Generate multicolumn and responsive html template for email.
773
+ * Initialize class with max columns per row and start to add cells.
774
+ */
775
+ class TNP_Composer_Grid_System {
776
+
777
+ /**
778
+ * @var TNP_Composer_Grid_Row[]
779
+ */
780
+ private $rows;
781
+
782
+ /**
783
+ * @var int
784
+ */
785
+ private $cells_per_row;
786
+
787
+ /**
788
+ * @var int
789
+ */
790
+ private $cells_counter;
791
+
792
+ /**
793
+ * TNP_Composer_Grid_System constructor.
794
+ *
795
+ * @param int $columns_per_row Max columns per row
796
+ */
797
+ public function __construct($columns_per_row) {
798
+ $this->cells_per_row = $columns_per_row;
799
+ $this->cells_counter = 0;
800
+ $this->rows = [];
801
+ }
802
+
803
+ public function __toString() {
804
+ return $this->render();
805
+ }
806
+
807
+ /**
808
+ * Add cell to grid
809
+ *
810
+ * @param TNP_Composer_Grid_Cell $cell
811
+ */
812
+ public function add_cell($cell) {
813
+
814
+ if ($this->cells_counter % $this->cells_per_row === 0) {
815
+ $this->add_row(new TNP_Composer_Grid_Row());
816
+ }
817
+
818
+ $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
819
+ $this->rows[$row_idx]->add_cell($cell);
820
+ $this->cells_counter++;
821
+ }
822
+
823
+ private function add_row($row) {
824
+ $this->rows[] = $row;
825
+ }
826
+
827
+ public function render() {
828
+
829
+ $str = '';
830
+ foreach ($this->rows as $row) {
831
+ $str .= $row->render();
832
+ }
833
+
834
+ return $str;
835
+ }
836
+
837
+ }
838
+
839
+ /**
840
+ * Class TNP_Composer_Grid_Row
841
+ */
842
+ class TNP_Composer_Grid_Row {
843
+
844
+ /**
845
+ * @var TNP_Composer_Grid_Cell[]
846
+ */
847
+ private $cells;
848
+
849
+ public function __construct(...$cells) {
850
+ if (!empty($cells)) {
851
+ foreach ($cells as $cell) {
852
+ $this->add_cell($cell);
853
+ }
854
+ }
855
+ }
856
+
857
+ /**
858
+ * @param TNP_Composer_Grid_Cell $cell
859
+ */
860
+ public function add_cell($cell) {
861
+ $this->cells[] = $cell;
862
+ }
863
+
864
+ public function render() {
865
+ $rendered_cells = '';
866
+ $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
867
+ foreach ($this->cells as $cell) {
868
+ $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
869
+ }
870
+
871
+ $row_template = $this->get_template();
872
+
873
+ return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
874
+ }
875
+
876
+ private function cells_count() {
877
+ return count($this->cells);
878
+ }
879
+
880
+ private function get_template() {
881
+ return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
882
+ }
883
+
884
+ }
885
+
886
+ /**
887
+ * Class TNP_Composer_Grid_Cell
888
+ */
889
+ class TNP_Composer_Grid_Cell {
890
+
891
+ /**
892
+ * @var string
893
+ */
894
+ private $content;
895
+
896
+ /**
897
+ * @var array
898
+ */
899
+ public $args;
900
+
901
+ public function __construct($content = null, $args = []) {
902
+ $default_args = [
903
+ 'width' => '100%',
904
+ 'class' => '',
905
+ 'align' => 'left',
906
+ 'valign' => 'top'
907
+ ];
908
+
909
+ $this->args = array_merge($default_args, $args);
910
+
911
+ $this->content = $content ? $content : '';
912
+ }
913
+
914
+ public function add_content($content) {
915
+ $this->content .= $content;
916
+ }
917
+
918
+ public function render($args) {
919
+ $this->args = array_merge($this->args, $args);
920
+
921
+ $column_template = $this->get_template();
922
+ $column = str_replace(
923
+ [
924
+ 'TNP_ALIGN_PH',
925
+ 'TNP_VALIGN_PH',
926
+ 'TNP_WIDTH_PH',
927
+ 'TNP_CLASS_PH',
928
+ 'TNP_COLUMN_CONTENT_PH'
929
+ ], [
930
+ $this->args['align'],
931
+ $this->args['valign'],
932
+ $this->args['width'],
933
+ $this->args['class'],
934
+ $this->content
935
+ ], $column_template);
936
+
937
+ return $column;
938
+ }
939
+
940
+ private function get_template() {
941
+ return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
942
+ <tbody>
943
+ <tr>
944
+ <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
945
+ TNP_COLUMN_CONTENT_PH
946
+ </td>
947
+ </tr>
948
+ </tbody>
949
+ </table>";
950
+ }
951
+
952
+ }
953
+
954
+ class TNP_Composer_Component_Factory {
955
+
956
+ private $options;
957
+
958
+ /**
959
+ * TNP_Composer_Component_Factory constructor.
960
+ *
961
+ * @param Controller$controller
962
+ */
963
+ public function __construct($controller) {
964
+
965
+ }
966
+
967
+ function heading() {
968
+
969
+ }
970
+
971
+ function paragraph() {
972
+
973
+ }
974
+
975
+ function link() {
976
+
977
+ }
978
+
979
+ function button() {
980
+
981
+ }
982
+
983
+ function image() {
984
+
985
+ }
986
+
987
+ }
plugin.php CHANGED
@@ -4,7 +4,7 @@
4
  Plugin Name: Newsletter
5
  Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
  Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
- Version: 7.5.0
8
  Author: Stefano Lissa & The Newsletter Team
9
  Author URI: https://www.thenewsletterplugin.com
10
  Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
@@ -37,7 +37,7 @@ if (version_compare(phpversion(), '5.6', '<')) {
37
  return;
38
  }
39
 
40
- define('NEWSLETTER_VERSION', '7.5.0');
41
 
42
  global $newsletter, $wpdb;
43
 
4
  Plugin Name: Newsletter
5
  Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
  Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
+ Version: 7.5.1
8
  Author: Stefano Lissa & The Newsletter Team
9
  Author URI: https://www.thenewsletterplugin.com
10
  Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
37
  return;
38
  }
39
 
40
+ define('NEWSLETTER_VERSION', '7.5.1');
41
 
42
  global $newsletter, $wpdb;
43
 
profile/profile.php CHANGED
@@ -1,494 +1,494 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- class NewsletterProfile extends NewsletterModule {
6
-
7
- static $instance;
8
-
9
- /**
10
- * @return NewsletterProfile
11
- */
12
- static function instance() {
13
- if (self::$instance == null) {
14
- self::$instance = new NewsletterProfile();
15
- }
16
- return self::$instance;
17
- }
18
-
19
- function __construct() {
20
- parent::__construct('profile', '1.1.0');
21
- add_shortcode('newsletter_profile', array($this, 'shortcode_newsletter_profile'));
22
- add_filter('newsletter_replace', array($this, 'hook_newsletter_replace'), 10, 4);
23
- add_filter('newsletter_page_text', array($this, 'hook_newsletter_page_text'), 10, 3);
24
- add_action('newsletter_action', array($this, 'hook_newsletter_action'), 12, 3);
25
- }
26
-
27
- function hook_newsletter_action($action, $user, $email) {
28
-
29
- if (in_array($action, ['p', 'profile', 'pe', 'profile-save', 'profile_export', 'ps'])) {
30
- if (!$user || $user->status != TNP_User::STATUS_CONFIRMED) {
31
-
32
- $this->dienow(__('The subscriber was not found or is not confirmed.', 'newsletter'), '', 404);
33
- }
34
- }
35
-
36
- switch ($action) {
37
- case 'profile':
38
- case 'p':
39
- case 'pe':
40
-
41
- $profile_url = $this->build_message_url($this->options['url'], 'profile', $user, $email);
42
- $profile_url = apply_filters('newsletter_profile_url', $profile_url, $user);
43
-
44
- wp_redirect($profile_url);
45
- die();
46
-
47
- break;
48
-
49
- case 'profile-save':
50
- case 'ps':
51
- $res = $this->save_profile($user);
52
- if (is_wp_error($res)) {
53
- wp_redirect($this->build_message_url($this->options['url'], 'profile', $user, $email, $res->get_error_message()));
54
- die();
55
- }
56
-
57
- wp_redirect($this->build_message_url($this->options['url'], 'profile', $user, $email, $res));
58
- die();
59
- break;
60
-
61
- case 'profile_export':
62
- header('Content-Type: application/json;charset=UTF-8');
63
- echo $this->to_json($user);
64
- die();
65
- }
66
- }
67
-
68
- /**
69
- *
70
- * @param stdClass $user
71
- */
72
- function get_profile_export_url($user) {
73
- return $this->build_action_url('profile_export', $user);
74
- }
75
-
76
- /**
77
- * URL to the subscriber profile edit action. This URL MUST NEVER be changed by
78
- * 3rd party plugins. Plugins can change the final URL after the action has been executed using the
79
- * <code>newsletter_profile_url</code> filter.
80
- *
81
- * @param stdClass $user
82
- */
83
- function get_profile_url($user, $email = null) {
84
- return $this->build_action_url('profile', $user, $email);
85
- }
86
-
87
- function hook_newsletter_replace($text, $user, $email, $html = true) {
88
- if (!$user) {
89
- $text = $this->replace_url($text, 'PROFILE_URL', $this->build_action_url('nul'));
90
- return $text;
91
- }
92
-
93
- // Profile edit page URL and link
94
- $url = $this->get_profile_url($user, $email);
95
- $text = $this->replace_url($text, 'profile_url', $url);
96
- // Profile export URL and link
97
- $url = $this->get_profile_export_url($user);
98
- $text = $this->replace_url($text, 'profile_export_url', $url);
99
-
100
- if (strpos($text, '{profile_form}') !== false) {
101
- $text = str_replace('{profile_form}', $this->get_profile_form($user), $text);
102
- }
103
- return $text;
104
- }
105
-
106
- /**
107
- *
108
- * @param type $text
109
- * @param type $key
110
- * @param TNP_User $user
111
- * @return string
112
- */
113
- function hook_newsletter_page_text($text, $key, $user) {
114
- if ($key == 'profile') {
115
- if (!$user || $user->status == TNP_User::STATUS_UNSUBSCRIBED) {
116
- return 'Subscriber not found.';
117
- }
118
- $options = $this->get_options('main', $this->get_current_language($user));
119
- return $options['text'];
120
- }
121
- return $text;
122
- }
123
-
124
- function shortcode_newsletter_profile($attrs, $content) {
125
- $user = $this->check_user();
126
-
127
- if (empty($user)) {
128
- if (empty($content)) {
129
- return __('Subscriber not found.', 'newsletter');
130
- } else {
131
- return $content;
132
- }
133
- }
134
-
135
- return $this->get_profile_form($user);
136
- }
137
-
138
- function to_json($user) {
139
- global $wpdb;
140
-
141
-
142
- $fields = array('name', 'surname', 'sex', 'created', 'ip', 'email');
143
- $data = array(
144
- 'email' => $user->email,
145
- 'name' => $user->name,
146
- 'last_name' => $user->surname,
147
- 'gender' => $user->sex,
148
- 'created' => $user->created,
149
- 'ip' => $user->ip,
150
- );
151
-
152
- // Lists
153
- $data['lists'] = array();
154
-
155
- $lists = $this->get_lists_public();
156
- foreach ($lists as $list) {
157
- $field = 'list_' . $list->id;
158
- if ($user->$field == 1) {
159
- $data['lists'][] = $list->name;
160
- }
161
- }
162
-
163
- // Profile
164
- $options_profile = get_option('newsletter_profile', array());
165
- $data['profiles'] = array();
166
- for ($i = 1; $i < NEWSLETTER_PROFILE_MAX; $i++) {
167
- $field = 'profile_' . $i;
168
- if ($options_profile[$field . '_status'] != 1 && $options_profile[$field . '_status'] != 2) {
169
- continue;
170
- }
171
- $data['profiles'][] = array('name' => $options_profile[$field], 'value' => $user->$field);
172
- }
173
-
174
- // Newsletters
175
- if ($this->options['export_newsletters']) {
176
- $sent = $wpdb->get_results($wpdb->prepare("select * from {$wpdb->prefix}newsletter_sent where user_id=%d order by email_id asc", $user->id));
177
- $newsletters = array();
178
- foreach ($sent as $item) {
179
- $action = 'none';
180
- if ($item->open == 1) {
181
- $action = 'read';
182
- } else if ($item->open == 2) {
183
- $action = 'click';
184
- }
185
-
186
- $email = $this->get_email($item->email_id);
187
- if (!$email) {
188
- continue;
189
- }
190
- // 'id'=>$item->email_id,
191
- $newsletters[] = array('subject' => $email->subject, 'action' => $action, 'sent' => date('Y-m-d h:i:s', $email->send_on));
192
- }
193
-
194
- $data['newsletters'] = $newsletters;
195
- }
196
-
197
- $extra = apply_filters('newsletter_profile_export_extra', array());
198
-
199
- $data = array_merge($extra, $data);
200
-
201
- return json_encode($data, JSON_PRETTY_PRINT);
202
- }
203
-
204
- /**
205
- * Build the profile editing form for the specified subscriber.
206
- *
207
- * @param TNP_User $user
208
- * @return string
209
- */
210
- function get_profile_form($user) {
211
- // Do not pay attention to option name here, it's a compatibility problem
212
-
213
- $language = $this->get_user_language($user);
214
- $options = NewsletterSubscription::instance()->get_options('profile', $language);
215
-
216
- $buffer = '';
217
-
218
- $buffer .= '<div class="tnp tnp-profile">';
219
- $buffer .= '<form action="' . $this->build_action_url('ps') . '" method="post">';
220
- $buffer .= '<input type="hidden" name="nk" value="' . esc_attr($user->id . '-' . $user->token) . '">';
221
-
222
- $buffer .= '<div class="tnp-field tnp-field-email">';
223
- $buffer .= '<label>' . esc_html($options['email']) . '</label>';
224
- $buffer .= '<input class="tnp-email" type="text" name="ne" required value="' . esc_attr($user->email) . '">';
225
- $buffer .= "</div>\n";
226
-
227
-
228
- if ($options['name_status'] >= 1) {
229
- $buffer .= '<div class="tnp-field tnp-field-firstname">';
230
- $buffer .= '<label>' . esc_html($options['name']) . '</label>';
231
- $buffer .= '<input class="tnp-firstname" type="text" name="nn" value="' . esc_attr($user->name) . '"' . ($options['name_rules'] == 1 ? ' required' : '') . '>';
232
- $buffer .= "</div>\n";
233
- }
234
-
235
- if ($options['surname_status'] >= 1) {
236
- $buffer .= '<div class="tnp-field tnp-field-lastname">';
237
- $buffer .= '<label>' . esc_html($options['surname']) . '</label>';
238
- $buffer .= '<input class="tnp-lastname" type="text" name="ns" value="' . esc_attr($user->surname) . '"' . ($options['surname_rules'] == 1 ? ' required' : '') . '>';
239
- $buffer .= "</div>\n";
240
- }
241
-
242
- if ($options['sex_status'] >= 1) {
243
- $buffer .= '<div class="tnp-field tnp-field-gender">';
244
- $buffer .= '<label>' . esc_html($options['sex']) . '</label>';
245
- $buffer .= '<select name="nx" class="tnp-gender"';
246
- if ($options['sex_rules']) {
247
- $buffer .= ' required ';
248
- }
249
- $buffer .= '>';
250
- if ($options['sex_rules']) {
251
- $buffer .= '<option value=""></option>';
252
- }
253
- $buffer .= '<option value="n"' . ($user->sex == 'n' ? ' selected' : '') . '>' . esc_html($options['sex_none']) . '</option>';
254
- $buffer .= '<option value="f"' . ($user->sex == 'f' ? ' selected' : '') . '>' . esc_html($options['sex_female']) . '</option>';
255
- $buffer .= '<option value="m"' . ($user->sex == 'm' ? ' selected' : '') . '>' . esc_html($options['sex_male']) . '</option>';
256
- $buffer .= '</select>';
257
- $buffer .= "</div>\n";
258
- }
259
-
260
- if ($this->is_multilanguage()) {
261
-
262
- $languages = $this->get_languages();
263
-
264
- $buffer .= '<div class="tnp-field tnp-field-language">';
265
- $buffer .= '<label>' . __('Language', 'Newsletter') . '</label>';
266
- $buffer .= '<select name="nlng" class="tnp-language">';
267
-
268
- $buffer .= '<option value="" disabled ' . ( empty($user->language) ? ' selected' : '' ) . '>' . __('Select language', 'newsletter') . '</option>';
269
- foreach ($languages as $key => $language) {
270
- $buffer .= '<option value="' . $key . '"' . ( $user->language == $key ? ' selected' : '' ) . '>' . esc_html($language) . '</option>';
271
- }
272
-
273
- $buffer .= '</select>';
274
- $buffer .= "</div>\n";
275
- }
276
-
277
- // Profile
278
- $profiles = NewsletterSubscription::instance()->get_profiles_for_profile($user->language);
279
- foreach ($profiles as $profile) {
280
- $i = $profile->id; // I'm lazy
281
-
282
- $buffer .= '<div class="tnp-field tnp-field-profile">';
283
- $buffer .= '<label>' . esc_html($profile->name) . '</label>';
284
-
285
- $field = 'profile_' . $i;
286
-
287
- if ($profile->is_text()) {
288
- $buffer .= '<input class="tnp-profile tnp-profile-' . $i . '" type="text" name="np' . $i . '" value="' . esc_attr($user->$field) . '"' .
289
- ($profile->is_required() ? ' required' : '') . '>';
290
- }
291
-
292
- if ($profile->is_select()) {
293
- $buffer .= '<select class="tnp-profile tnp-profile-' . $i . '" name="np' . $i . '"' . ($profile->is_required() ? ' required' : '') . '>';
294
- foreach ($profile->options as $option) {
295
- $buffer .= '<option';
296
- if ($option == $user->$field) {
297
- $buffer .= ' selected';
298
- }
299
- $buffer .= '>' . esc_html($option) . '</option>';
300
- }
301
- $buffer .= '</select>';
302
- }
303
-
304
- $buffer .= "</div>\n";
305
- }
306
-
307
- // Lists
308
- $lists = $this->get_lists_for_profile($language);
309
- $tmp = '';
310
- foreach ($lists as $list) {
311
-
312
- $tmp .= '<div class="tnp-field tnp-field-list">';
313
- $tmp .= '<label><input class="tnp-list tnp-list-' . $list->id . '" type="checkbox" name="nl[]" value="' . $list->id . '"';
314
- $field = 'list_' . $list->id;
315
- if ($user->$field == 1) {
316
- $tmp .= ' checked';
317
- }
318
- $tmp .= '><span class="tnp-list-label">' . esc_html($list->name) . '</span></label>';
319
- $tmp .= "</div>\n";
320
- }
321
-
322
- if (!empty($tmp)) {
323
- $buffer .= '<div class="tnp-lists">' . "\n" . $tmp . "\n" . '</div>';
324
- }
325
-
326
- // Obsolete
327
- $extra = apply_filters('newsletter_profile_extra', array(), $user);
328
- foreach ($extra as $x) {
329
- $buffer .= '<div class="tnp-field">';
330
- $buffer .= '<label>' . $x['label'] . "</label>";
331
- $buffer .= $x['field'];
332
- $buffer .= "</div>\n";
333
- }
334
-
335
- $local_options = $this->get_options('', $this->get_user_language($user));
336
-
337
- // Privacy
338
- $privacy_url = NewsletterSubscription::instance()->get_privacy_url();
339
- if (!empty($local_options['privacy_label']) && !empty($privacy_url)) {
340
- $buffer .= '<div class="tnp-field tnp-field-privacy">';
341
- if ($privacy_url) {
342
- $buffer .= '<a href="' . $privacy_url . '" target="_blank">';
343
- }
344
-
345
- $buffer .= $local_options['privacy_label'];
346
-
347
- if ($privacy_url) {
348
- $buffer .= '</a>';
349
- }
350
- $buffer .= "</div>\n";
351
- }
352
-
353
- $buffer .= '<div class="tnp-field tnp-field-button">';
354
- $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($local_options['save_label']) . '">';
355
- $buffer .= "</div>\n";
356
-
357
- $buffer .= "</form>\n</div>\n";
358
-
359
- return $buffer;
360
- }
361
-
362
- /**
363
- * Saves the subscriber data extracting them from the $_REQUEST and for the
364
- * subscriber identified by the <code>$user</code> object.
365
- *
366
- * @return string|WP_Error If not an error the string represent the message to show
367
- */
368
- function save_profile($user) {
369
- global $wpdb;
370
-
371
- // Conatains the cleaned up user data to be saved
372
- $data = array();
373
- $data['id'] = $user->id;
374
-
375
- $options = $this->get_options('', $this->get_current_language($user));
376
- $options_profile = get_option('newsletter_profile', array());
377
- $options_main = get_option('newsletter_main', array());
378
-
379
- // Not an elegant interaction between modules but...
380
- $subscription_module = NewsletterSubscription::instance();
381
-
382
- require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
383
-
384
- $antispam = NewsletterAntispam::instance();
385
-
386
- $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
387
-
388
- if ($antispam->is_address_blacklisted($email)) {
389
- return new WP_Error('spam', 'That email address is not accepted');
390
- }
391
-
392
- if (!$email) {
393
- return new WP_Error('email', $options['error']);
394
- }
395
-
396
- $email_changed = ($email != $user->email);
397
-
398
- // If the email has been changed, check if it is available
399
- if ($email_changed) {
400
- $tmp = $this->get_user($email);
401
- if ($tmp != null && $tmp->id != $user->id) {
402
- return new WP_Error('inuse', $options['error']);
403
- }
404
- }
405
-
406
- if ($email_changed && $subscription_module->is_double_optin()) {
407
- set_transient('newsletter_user_' . $user->id . '_email', $email, DAY_IN_SECONDS);
408
- } else {
409
- $data['email'] = $email;
410
- }
411
-
412
- if (isset($_REQUEST['nn'])) {
413
- $data['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
414
- if ($antispam->is_spam_text($data['name'])) {
415
- return new WP_Error('spam', 'That name/surname');
416
- }
417
- }
418
- if (isset($_REQUEST['ns'])) {
419
- $data['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
420
- if ($antispam->is_spam_text($data['surname'])) {
421
- return new WP_Error('spam', 'That name/surname');
422
- }
423
- }
424
- if ($options_profile['sex_status'] >= 1) {
425
- $data['sex'] = $_REQUEST['nx'][0];
426
- // Wrong data injection check
427
- if ($data['sex'] != 'm' && $data['sex'] != 'f' && $data['sex'] != 'n') {
428
- die('Wrong sex field');
429
- }
430
- }
431
- if (isset($_REQUEST['nlng'])) {
432
- $languages = $this->get_languages();
433
- if (isset($languages[$_REQUEST['nlng']])) {
434
- $data['language'] = $_REQUEST['nlng'];
435
- }
436
- }
437
-
438
- // Lists. If not list is present or there is no list to choose or all are unchecked.
439
- $nl = array();
440
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
441
- $nl = $_REQUEST['nl'];
442
- }
443
-
444
- // Every possible list shown in the profile must be processed
445
- $lists = $this->get_lists_for_profile();
446
- foreach ($lists as $list) {
447
- $field_name = 'list_' . $list->id;
448
- $data[$field_name] = in_array($list->id, $nl) ? 1 : 0;
449
- }
450
-
451
- // Profile
452
- $profiles = $this->get_profiles_public();
453
- foreach ($profiles as $profile) {
454
- if (isset($_REQUEST['np' . $profile->id])) {
455
- $data['profile_' . $profile->id] = stripslashes($_REQUEST['np' . $profile->id]);
456
- }
457
- }
458
-
459
- // Feed by Mail service is saved here
460
- $data = apply_filters('newsletter_profile_save', $data);
461
-
462
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
463
- $data['status'] = TNP_User::STATUS_CONFIRMED;
464
- }
465
-
466
- $user = $this->save_user($data);
467
- $this->add_user_log($user, 'profile');
468
-
469
- // Send the activation again only if we use double opt-in, otherwise it has no meaning
470
- if ($email_changed && $subscription_module->is_double_optin()) {
471
- $user->email = $email;
472
- $subscription_module->send_activation_email($user);
473
- return $options['email_changed'];
474
- }
475
-
476
- return $options['saved'];
477
- }
478
-
479
- function admin_menu() {
480
- $this->add_admin_page('index', 'Profile');
481
- }
482
-
483
- // Patch to avoid conflicts with the "newsletter_profile" option of the subscription module
484
- // TODO: Fix it
485
- public function get_prefix($sub = '', $language = '') {
486
- if (empty($sub)) {
487
- $sub = 'main';
488
- }
489
- return parent::get_prefix($sub, $language);
490
- }
491
-
492
- }
493
-
494
- NewsletterProfile::instance();
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterProfile extends NewsletterModule {
6
+
7
+ static $instance;
8
+
9
+ /**
10
+ * @return NewsletterProfile
11
+ */
12
+ static function instance() {
13
+ if (self::$instance == null) {
14
+ self::$instance = new NewsletterProfile();
15
+ }
16
+ return self::$instance;
17
+ }
18
+
19
+ function __construct() {
20
+ parent::__construct('profile', '1.1.0');
21
+ add_shortcode('newsletter_profile', array($this, 'shortcode_newsletter_profile'));
22
+ add_filter('newsletter_replace', array($this, 'hook_newsletter_replace'), 10, 4);
23
+ add_filter('newsletter_page_text', array($this, 'hook_newsletter_page_text'), 10, 3);
24
+ add_action('newsletter_action', array($this, 'hook_newsletter_action'), 12, 3);
25
+ }
26
+
27
+ function hook_newsletter_action($action, $user, $email) {
28
+
29
+ if (in_array($action, ['p', 'profile', 'pe', 'profile-save', 'profile_export', 'ps'])) {
30
+ if (!$user || $user->status != TNP_User::STATUS_CONFIRMED) {
31
+
32
+ $this->dienow(__('The subscriber was not found or is not confirmed.', 'newsletter'), '', 404);
33
+ }
34
+ }
35
+
36
+ switch ($action) {
37
+ case 'profile':
38
+ case 'p':
39
+ case 'pe':
40
+
41
+ $profile_url = $this->build_message_url($this->options['url'], 'profile', $user, $email);
42
+ $profile_url = apply_filters('newsletter_profile_url', $profile_url, $user);
43
+
44
+ wp_redirect($profile_url);
45
+ die();
46
+
47
+ break;
48
+
49
+ case 'profile-save':
50
+ case 'ps':
51
+ $res = $this->save_profile($user);
52
+ if (is_wp_error($res)) {
53
+ wp_redirect($this->build_message_url($this->options['url'], 'profile', $user, $email, $res->get_error_message()));
54
+ die();
55
+ }
56
+
57
+ wp_redirect($this->build_message_url($this->options['url'], 'profile', $user, $email, $res));
58
+ die();
59
+ break;
60
+
61
+ case 'profile_export':
62
+ header('Content-Type: application/json;charset=UTF-8');
63
+ echo $this->to_json($user);
64
+ die();
65
+ }
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param stdClass $user
71
+ */
72
+ function get_profile_export_url($user) {
73
+ return $this->build_action_url('profile_export', $user);
74
+ }
75
+
76
+ /**
77
+ * URL to the subscriber profile edit action. This URL MUST NEVER be changed by
78
+ * 3rd party plugins. Plugins can change the final URL after the action has been executed using the
79
+ * <code>newsletter_profile_url</code> filter.
80
+ *
81
+ * @param stdClass $user
82
+ */
83
+ function get_profile_url($user, $email = null) {
84
+ return $this->build_action_url('profile', $user, $email);
85
+ }
86
+
87
+ function hook_newsletter_replace($text, $user, $email, $html = true) {
88
+ if (!$user) {
89
+ $text = $this->replace_url($text, 'PROFILE_URL', $this->build_action_url('nul'));
90
+ return $text;
91
+ }
92
+
93
+ // Profile edit page URL and link
94
+ $url = $this->get_profile_url($user, $email);
95
+ $text = $this->replace_url($text, 'profile_url', $url);
96
+ // Profile export URL and link
97
+ $url = $this->get_profile_export_url($user);
98
+ $text = $this->replace_url($text, 'profile_export_url', $url);
99
+
100
+ if (strpos($text, '{profile_form}') !== false) {
101
+ $text = str_replace('{profile_form}', $this->get_profile_form($user), $text);
102
+ }
103
+ return $text;
104
+ }
105
+
106
+ /**
107
+ *
108
+ * @param type $text
109
+ * @param type $key
110
+ * @param TNP_User $user
111
+ * @return string
112
+ */
113
+ function hook_newsletter_page_text($text, $key, $user) {
114
+ if ($key == 'profile') {
115
+ if (!$user || $user->status == TNP_User::STATUS_UNSUBSCRIBED) {
116
+ return 'Subscriber not found.';
117
+ }
118
+ $options = $this->get_options('main', $this->get_current_language($user));
119
+ return $options['text'];
120
+ }
121
+ return $text;
122
+ }
123
+
124
+ function shortcode_newsletter_profile($attrs, $content) {
125
+ $user = $this->check_user();
126
+
127
+ if (empty($user)) {
128
+ if (empty($content)) {
129
+ return __('Subscriber not found.', 'newsletter');
130
+ } else {
131
+ return $content;
132
+ }
133
+ }
134
+
135
+ return $this->get_profile_form($user);
136
+ }
137
+
138
+ function to_json($user) {
139
+ global $wpdb;
140
+
141
+
142
+ $fields = array('name', 'surname', 'sex', 'created', 'ip', 'email');
143
+ $data = array(
144
+ 'email' => $user->email,
145
+ 'name' => $user->name,
146
+ 'last_name' => $user->surname,
147
+ 'gender' => $user->sex,
148
+ 'created' => $user->created,
149
+ 'ip' => $user->ip,
150
+ );
151
+
152
+ // Lists
153
+ $data['lists'] = array();
154
+
155
+ $lists = $this->get_lists_public();
156
+ foreach ($lists as $list) {
157
+ $field = 'list_' . $list->id;
158
+ if ($user->$field == 1) {
159
+ $data['lists'][] = $list->name;
160
+ }
161
+ }
162
+
163
+ // Profile
164
+ $options_profile = get_option('newsletter_profile', array());
165
+ $data['profiles'] = array();
166
+ for ($i = 1; $i < NEWSLETTER_PROFILE_MAX; $i++) {
167
+ $field = 'profile_' . $i;
168
+ if ($options_profile[$field . '_status'] != 1 && $options_profile[$field . '_status'] != 2) {
169
+ continue;
170
+ }
171
+ $data['profiles'][] = array('name' => $options_profile[$field], 'value' => $user->$field);
172
+ }
173
+
174
+ // Newsletters
175
+ if ($this->options['export_newsletters']) {
176
+ $sent = $wpdb->get_results($wpdb->prepare("select * from {$wpdb->prefix}newsletter_sent where user_id=%d order by email_id asc", $user->id));
177
+ $newsletters = array();
178
+ foreach ($sent as $item) {
179
+ $action = 'none';
180
+ if ($item->open == 1) {
181
+ $action = 'read';
182
+ } else if ($item->open == 2) {
183
+ $action = 'click';
184
+ }
185
+
186
+ $email = $this->get_email($item->email_id);
187
+ if (!$email) {
188
+ continue;
189
+ }
190
+ // 'id'=>$item->email_id,
191
+ $newsletters[] = array('subject' => $email->subject, 'action' => $action, 'sent' => date('Y-m-d h:i:s', $email->send_on));
192
+ }
193
+
194
+ $data['newsletters'] = $newsletters;
195
+ }
196
+
197
+ $extra = apply_filters('newsletter_profile_export_extra', array());
198
+
199
+ $data = array_merge($extra, $data);
200
+
201
+ return json_encode($data, JSON_PRETTY_PRINT);
202
+ }
203
+
204
+ /**
205
+ * Build the profile editing form for the specified subscriber.
206
+ *
207
+ * @param TNP_User $user
208
+ * @return string
209
+ */
210
+ function get_profile_form($user) {
211
+ // Do not pay attention to option name here, it's a compatibility problem
212
+
213
+ $language = $this->get_user_language($user);
214
+ $options = NewsletterSubscription::instance()->get_options('profile', $language);
215
+
216
+ $buffer = '';
217
+
218
+ $buffer .= '<div class="tnp tnp-profile">';
219
+ $buffer .= '<form action="' . $this->build_action_url('ps') . '" method="post">';
220
+ $buffer .= '<input type="hidden" name="nk" value="' . esc_attr($user->id . '-' . $user->token) . '">';
221
+
222
+ $buffer .= '<div class="tnp-field tnp-field-email">';
223
+ $buffer .= '<label>' . esc_html($options['email']) . '</label>';
224
+ $buffer .= '<input class="tnp-email" type="text" name="ne" required value="' . esc_attr($user->email) . '">';
225
+ $buffer .= "</div>\n";
226
+
227
+
228
+ if ($options['name_status'] >= 1) {
229
+ $buffer .= '<div class="tnp-field tnp-field-firstname">';
230
+ $buffer .= '<label>' . esc_html($options['name']) . '</label>';
231
+ $buffer .= '<input class="tnp-firstname" type="text" name="nn" value="' . esc_attr($user->name) . '"' . ($options['name_rules'] == 1 ? ' required' : '') . '>';
232
+ $buffer .= "</div>\n";
233
+ }
234
+
235
+ if ($options['surname_status'] >= 1) {
236
+ $buffer .= '<div class="tnp-field tnp-field-lastname">';
237
+ $buffer .= '<label>' . esc_html($options['surname']) . '</label>';
238
+ $buffer .= '<input class="tnp-lastname" type="text" name="ns" value="' . esc_attr($user->surname) . '"' . ($options['surname_rules'] == 1 ? ' required' : '') . '>';
239
+ $buffer .= "</div>\n";
240
+ }
241
+
242
+ if ($options['sex_status'] >= 1) {
243
+ $buffer .= '<div class="tnp-field tnp-field-gender">';
244
+ $buffer .= '<label>' . esc_html($options['sex']) . '</label>';
245
+ $buffer .= '<select name="nx" class="tnp-gender"';
246
+ if ($options['sex_rules']) {
247
+ $buffer .= ' required ';
248
+ }
249
+ $buffer .= '>';
250
+ if ($options['sex_rules']) {
251
+ $buffer .= '<option value=""></option>';
252
+ }
253
+ $buffer .= '<option value="n"' . ($user->sex == 'n' ? ' selected' : '') . '>' . esc_html($options['sex_none']) . '</option>';
254
+ $buffer .= '<option value="f"' . ($user->sex == 'f' ? ' selected' : '') . '>' . esc_html($options['sex_female']) . '</option>';
255
+ $buffer .= '<option value="m"' . ($user->sex == 'm' ? ' selected' : '') . '>' . esc_html($options['sex_male']) . '</option>';
256
+ $buffer .= '</select>';
257
+ $buffer .= "</div>\n";
258
+ }
259
+
260
+ if ($this->is_multilanguage()) {
261
+
262
+ $languages = $this->get_languages();
263
+
264
+ $buffer .= '<div class="tnp-field tnp-field-language">';
265
+ $buffer .= '<label>' . __('Language', 'newsletter') . '</label>';
266
+ $buffer .= '<select name="nlng" class="tnp-language">';
267
+
268
+ $buffer .= '<option value="" disabled ' . ( empty($user->language) ? ' selected' : '' ) . '>' . __('Select language', 'newsletter') . '</option>';
269
+ foreach ($languages as $key => $language) {
270
+ $buffer .= '<option value="' . $key . '"' . ( $user->language == $key ? ' selected' : '' ) . '>' . esc_html($language) . '</option>';
271
+ }
272
+
273
+ $buffer .= '</select>';
274
+ $buffer .= "</div>\n";
275
+ }
276
+
277
+ // Profile
278
+ $profiles = NewsletterSubscription::instance()->get_profiles_for_profile($user->language);
279
+ foreach ($profiles as $profile) {
280
+ $i = $profile->id; // I'm lazy
281
+
282
+ $buffer .= '<div class="tnp-field tnp-field-profile">';
283
+ $buffer .= '<label>' . esc_html($profile->name) . '</label>';
284
+
285
+ $field = 'profile_' . $i;
286
+
287
+ if ($profile->is_text()) {
288
+ $buffer .= '<input class="tnp-profile tnp-profile-' . $i . '" type="text" name="np' . $i . '" value="' . esc_attr($user->$field) . '"' .
289
+ ($profile->is_required() ? ' required' : '') . '>';
290
+ }
291
+
292
+ if ($profile->is_select()) {
293
+ $buffer .= '<select class="tnp-profile tnp-profile-' . $i . '" name="np' . $i . '"' . ($profile->is_required() ? ' required' : '') . '>';
294
+ foreach ($profile->options as $option) {
295
+ $buffer .= '<option';
296
+ if ($option == $user->$field) {
297
+ $buffer .= ' selected';
298
+ }
299
+ $buffer .= '>' . esc_html($option) . '</option>';
300
+ }
301
+ $buffer .= '</select>';
302
+ }
303
+
304
+ $buffer .= "</div>\n";
305
+ }
306
+
307
+ // Lists
308
+ $lists = $this->get_lists_for_profile($language);
309
+ $tmp = '';
310
+ foreach ($lists as $list) {
311
+
312
+ $tmp .= '<div class="tnp-field tnp-field-list">';
313
+ $tmp .= '<label><input class="tnp-list tnp-list-' . $list->id . '" type="checkbox" name="nl[]" value="' . $list->id . '"';
314
+ $field = 'list_' . $list->id;
315
+ if ($user->$field == 1) {
316
+ $tmp .= ' checked';
317
+ }
318
+ $tmp .= '><span class="tnp-list-label">' . esc_html($list->name) . '</span></label>';
319
+ $tmp .= "</div>\n";
320
+ }
321
+
322
+ if (!empty($tmp)) {
323
+ $buffer .= '<div class="tnp-lists">' . "\n" . $tmp . "\n" . '</div>';
324
+ }
325
+
326
+ // Obsolete
327
+ $extra = apply_filters('newsletter_profile_extra', array(), $user);
328
+ foreach ($extra as $x) {
329
+ $buffer .= '<div class="tnp-field">';
330
+ $buffer .= '<label>' . $x['label'] . "</label>";
331
+ $buffer .= $x['field'];
332
+ $buffer .= "</div>\n";
333
+ }
334
+
335
+ $local_options = $this->get_options('', $language);
336
+
337
+ // Privacy
338
+ $privacy_url = NewsletterSubscription::instance()->get_privacy_url();
339
+ if (!empty($local_options['privacy_label']) && !empty($privacy_url)) {
340
+ $buffer .= '<div class="tnp-field tnp-field-privacy">';
341
+ if ($privacy_url) {
342
+ $buffer .= '<a href="' . $privacy_url . '" target="_blank">';
343
+ }
344
+
345
+ $buffer .= $local_options['privacy_label'];
346
+
347
+ if ($privacy_url) {
348
+ $buffer .= '</a>';
349
+ }
350
+ $buffer .= "</div>\n";
351
+ }
352
+
353
+ $buffer .= '<div class="tnp-field tnp-field-button">';
354
+ $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($local_options['save_label']) . '">';
355
+ $buffer .= "</div>\n";
356
+
357
+ $buffer .= "</form>\n</div>\n";
358
+
359
+ return $buffer;
360
+ }
361
+
362
+ /**
363
+ * Saves the subscriber data extracting them from the $_REQUEST and for the
364
+ * subscriber identified by the <code>$user</code> object.
365
+ *
366
+ * @return string|WP_Error If not an error the string represent the message to show
367
+ */
368
+ function save_profile($user) {
369
+ global $wpdb;
370
+
371
+ // Conatains the cleaned up user data to be saved
372
+ $data = array();
373
+ $data['id'] = $user->id;
374
+
375
+ $options = $this->get_options('', $this->get_current_language($user));
376
+ $options_profile = get_option('newsletter_profile', array());
377
+ $options_main = get_option('newsletter_main', array());
378
+
379
+ // Not an elegant interaction between modules but...
380
+ $subscription_module = NewsletterSubscription::instance();
381
+
382
+ require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
383
+
384
+ $antispam = NewsletterAntispam::instance();
385
+
386
+ $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
387
+
388
+ if ($antispam->is_address_blacklisted($email)) {
389
+ return new WP_Error('spam', 'That email address is not accepted');
390
+ }
391
+
392
+ if (!$email) {
393
+ return new WP_Error('email', $options['error']);
394
+ }
395
+
396
+ $email_changed = ($email != $user->email);
397
+
398
+ // If the email has been changed, check if it is available
399
+ if ($email_changed) {
400
+ $tmp = $this->get_user($email);
401
+ if ($tmp != null && $tmp->id != $user->id) {
402
+ return new WP_Error('inuse', $options['error']);
403
+ }
404
+ }
405
+
406
+ if ($email_changed && $subscription_module->is_double_optin()) {
407
+ set_transient('newsletter_user_' . $user->id . '_email', $email, DAY_IN_SECONDS);
408
+ } else {
409
+ $data['email'] = $email;
410
+ }
411
+
412
+ if (isset($_REQUEST['nn'])) {
413
+ $data['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
414
+ if ($antispam->is_spam_text($data['name'])) {
415
+ return new WP_Error('spam', 'That name/surname');
416
+ }
417
+ }
418
+ if (isset($_REQUEST['ns'])) {
419
+ $data['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
420
+ if ($antispam->is_spam_text($data['surname'])) {
421
+ return new WP_Error('spam', 'That name/surname');
422
+ }
423
+ }
424
+ if ($options_profile['sex_status'] >= 1) {
425
+ $data['sex'] = $_REQUEST['nx'][0];
426
+ // Wrong data injection check
427
+ if ($data['sex'] != 'm' && $data['sex'] != 'f' && $data['sex'] != 'n') {
428
+ die('Wrong sex field');
429
+ }
430
+ }
431
+ if (isset($_REQUEST['nlng'])) {
432
+ $languages = $this->get_languages();
433
+ if (isset($languages[$_REQUEST['nlng']])) {
434
+ $data['language'] = $_REQUEST['nlng'];
435
+ }
436
+ }
437
+
438
+ // Lists. If not list is present or there is no list to choose or all are unchecked.
439
+ $nl = array();
440
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
441
+ $nl = $_REQUEST['nl'];
442
+ }
443
+
444
+ // Every possible list shown in the profile must be processed
445
+ $lists = $this->get_lists_for_profile();
446
+ foreach ($lists as $list) {
447
+ $field_name = 'list_' . $list->id;
448
+ $data[$field_name] = in_array($list->id, $nl) ? 1 : 0;
449
+ }
450
+
451
+ // Profile
452
+ $profiles = $this->get_profiles_public();
453
+ foreach ($profiles as $profile) {
454
+ if (isset($_REQUEST['np' . $profile->id])) {
455
+ $data['profile_' . $profile->id] = stripslashes($_REQUEST['np' . $profile->id]);
456
+ }
457
+ }
458
+
459
+ // Feed by Mail service is saved here
460
+ $data = apply_filters('newsletter_profile_save', $data);
461
+
462
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
463
+ $data['status'] = TNP_User::STATUS_CONFIRMED;
464
+ }
465
+
466
+ $user = $this->save_user($data);
467
+ $this->add_user_log($user, 'profile');
468
+
469
+ // Send the activation again only if we use double opt-in, otherwise it has no meaning
470
+ if ($email_changed && $subscription_module->is_double_optin()) {
471
+ $user->email = $email;
472
+ $subscription_module->send_activation_email($user);
473
+ return $options['email_changed'];
474
+ }
475
+
476
+ return $options['saved'];
477
+ }
478
+
479
+ function admin_menu() {
480
+ $this->add_admin_page('index', 'Profile');
481
+ }
482
+
483
+ // Patch to avoid conflicts with the "newsletter_profile" option of the subscription module
484
+ // TODO: Fix it
485
+ public function get_prefix($sub = '', $language = '') {
486
+ if (empty($sub)) {
487
+ $sub = 'main';
488
+ }
489
+ return parent::get_prefix($sub, $language);
490
+ }
491
+
492
+ }
493
+
494
+ NewsletterProfile::instance();
readme.txt CHANGED
@@ -1,7 +1,7 @@
1
  === Newsletter - Send awesome emails from WordPress ===
2
  Tags: newsletter, email marketing, welcome email, signup forms, lead generation, marketing automation
3
- Tested up to: 6.0
4
- Stable tag: 7.5.0
5
  Contributors: satollo,webagile,michael-travan
6
  License: GPLv2 or later
7
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -126,6 +126,11 @@ Thank you, The Newsletter Team
126
 
127
  == Changelog ==
128
 
 
 
 
 
 
129
  = 7.5.0 =
130
 
131
  * Fixed image block width for small images or specific width
1
  === Newsletter - Send awesome emails from WordPress ===
2
  Tags: newsletter, email marketing, welcome email, signup forms, lead generation, marketing automation
3
+ Tested up to: 6.0.1
4
+ Stable tag: 7.5.1
5
  Contributors: satollo,webagile,michael-travan
6
  License: GPLv2 or later
7
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
126
 
127
  == Changelog ==
128
 
129
+ = 7.5.1 =
130
+
131
+ * Fixed PHPMailer loading for WP 5.4.10
132
+ * Updated WP version compatibility
133
+
134
  = 7.5.0 =
135
 
136
  * Fixed image block width for small images or specific width
subscription/profile.php CHANGED
@@ -1,246 +1,246 @@
1
- <?php
2
- /* @var $this NewsletterSubscription */
3
- defined('ABSPATH') || exit;
4
-
5
- include_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
- $controls = new NewsletterControls();
7
-
8
- $current_language = $this->get_current_language();
9
-
10
- $is_all_languages = $this->is_all_languages();
11
-
12
- if (!$is_all_languages) {
13
- $controls->warnings[] = 'You are configuring the language "<strong>' . $current_language . '</strong>". Switch to "all languages" to see every options.';
14
- }
15
-
16
- if (!$controls->is_action()) {
17
- $controls->data = $this->get_options('profile', $current_language);
18
- } else {
19
- if ($controls->is_action('save')) {
20
- $this->save_options($controls->data, 'profile', null, $current_language);
21
- $controls->data = $this->get_options('profile', $current_language);
22
- $controls->add_message_saved();
23
- }
24
-
25
- if ($controls->is_action('reset')) {
26
- $controls->data = $this->reset_options('profile');
27
- $controls->add_message_done();
28
- }
29
- }
30
-
31
- $status = array(0 => __('Private', 'newsletter'), 1 => __('Show on profile page', 'newsletter'), 2 => __('Show on subscription form', 'newsletter'));
32
- $rules = array(0 => __('Optional', 'newsletter'), 1 => __('Required', 'newsletter'));
33
- $extra_type = array('text' => __('Text', 'newsletter'), 'select' => __('List', 'newsletter'));
34
- ?>
35
-
36
- <div class="wrap" id="tnp-wrap">
37
-
38
- <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
39
-
40
- <div id="tnp-heading">
41
-
42
- <h2><?php _e('Subscription Form Fields and Layout', 'newsletter') ?></h2>
43
-
44
- <p>
45
- <a href="?page=newsletter_subscription_forms"><?php _e('HTML samples and hand coded forms', 'newsletter') ?></a>
46
- </p>
47
-
48
- </div>
49
-
50
- <div id="tnp-body">
51
-
52
- <form action="" method="post">
53
- <?php $controls->init(); ?>
54
-
55
- <div id="tabs">
56
-
57
- <ul>
58
- <li><a href="#tabs-2"><?php _e('Main profile fields', 'newsletter') ?></a></li>
59
- <li><a href="#tabs-3"><?php _e('Extra profile fields', 'newsletter') ?></a></li>
60
- </ul>
61
-
62
- <div id="tabs-2">
63
-
64
- <p><?php _e('The main subscriber fields. Only the email field is, of course, mandatory.', 'newsletter') ?></p>
65
-
66
- <table class="form-table">
67
- <tr>
68
- <th>Email</th>
69
- <td>
70
- <table class="tnpc-grid">
71
- <tr><th><?php _e('Field label', 'newsletter') ?></th><td><?php $controls->text('email', 50); ?></td></tr>
72
- <tr><th><?php _e('Error message', 'newsletter') ?></th><td><?php $controls->text('email_error', 50); ?></td></tr>
73
- </table>
74
- </td>
75
- </tr>
76
- <tr>
77
- <th><?php _e('First name', 'newsletter') ?></th>
78
- <td>
79
- <table class="tnpc-grid">
80
- <tr><th><?php _e('Field label', 'newsletter') ?></th><td><?php $controls->text('name', 50); ?></td></tr>
81
- <?php if ($is_all_languages) { ?>
82
- <tr><th><?php _e('When to show', 'newsletter') ?></th><td><?php $controls->select('name_status', $status); ?></td></tr>
83
- <tr><th><?php _e('Rules', 'newsletter') ?></th><td><?php $controls->select('name_rules', $rules); ?></td></tr>
84
- <?php } ?>
85
- </table>
86
- <p class="description">
87
- <?php _e('If you want to collect only a generic "name", use only this field and not the last name field.', 'newsletter') ?>
88
- </p>
89
- </td>
90
- </tr>
91
- <tr>
92
- <th><?php _e('Last name', 'newsletter') ?></th>
93
- <td>
94
- <table class="tnpc-grid">
95
- <tr><th><?php _e('Field label', 'newsletter') ?></th><td><?php $controls->text('surname', 50); ?></td></tr>
96
- <?php if ($is_all_languages) { ?>
97
- <tr><th><?php _e('When to show', 'newsletter') ?></th><td><?php $controls->select('surname_status', $status); ?></td></tr>
98
- <tr><th><?php _e('Rules', 'newsletter') ?></th><td><?php $controls->select('surname_rules', $rules); ?></td></tr>
99
- <?php } ?>
100
- </table>
101
- </td>
102
- </tr>
103
- <tr>
104
- <th><?php _e('Gender', 'newsletter') ?></th>
105
- <td>
106
- <table class="tnpc-grid">
107
- <tr><th><?php _e('Field label', 'newsletter') ?></th><td><?php $controls->text('sex', 50); ?></td></tr>
108
- <?php if ($is_all_languages) { ?>
109
- <tr><th><?php _e('When to show', 'newsletter') ?></th><td><?php $controls->select('sex_status', $status); ?></td></tr>
110
- <tr><th><?php _e('Rules', 'newsletter') ?></th><td><?php $controls->select('sex_rules', $rules); ?></td></tr>
111
- <?php } ?>
112
- <tr>
113
- <th><?php _e('Value labels', 'newsletter') ?></th>
114
- <td>
115
- <?php $controls->text('sex_none', 20, __('not specified', 'newsletter')); ?>
116
- <?php $controls->text('sex_female', 20, __('female', 'newsletter')); ?>
117
- <?php $controls->text('sex_male', 20, __('male', 'newsletter')); ?>
118
- </td>
119
- </tr>
120
- <tr>
121
- <th><?php _e('Salutation titles', 'newsletter') ?></th>
122
- <td>
123
- <?php _e('not specified', 'newsletter') ?>: <?php $controls->text('title_none'); ?><br>
124
- <?php _e('for females', 'newsletter') ?>: <?php $controls->text('title_female'); ?> (ex. "Mrs")<br>
125
- <?php _e('for males', 'newsletter') ?>: <?php $controls->text('title_male'); ?> (ex. "Mr")
126
- </td>
127
- </tr>
128
- </table>
129
- <p class="description">
130
- <?php _e('Salutation titles are inserted in emails message when the tag {title} is used. For example "Good morning {title} {surname} {name}".', 'newsletter') ?>
131
- </p>
132
- </td>
133
- </tr>
134
-
135
- <tr>
136
- <th><?php _e('"Subscribe" label', 'newsletter') ?></th>
137
- <td>
138
- <?php $controls->text('subscribe', 40); ?>
139
-
140
- <p class="description">
141
- <?php _e('You can use an image URL', 'newsletter') ?> (http://...).
142
- </p>
143
- </td>
144
- </tr>
145
-
146
- <tr>
147
- <th><?php _e('Privacy checkbox/notice', 'newsletter') ?></th>
148
- <td>
149
- <table>
150
- <?php if ($is_all_languages) { ?>
151
- <tr><th><?php _e('Enabled?', 'newsletter') ?></th><td><?php $controls->select('privacy_status', array(0 => __('No', 'newsletter'), 1 => __('Yes', 'newsletter'), 2 => __('Only the notice', 'newsletter'))); ?></td></tr>
152
- <?php } ?>
153
- <tr><th><?php _e('Label', 'newsletter') ?></th><td><?php $controls->text('privacy', 50); ?></td></tr>
154
- <tr>
155
- <th>Privacy URL</th>
156
- <td>
157
- <?php if (!$is_all_languages && !empty($controls->data['privacy_use_wp_url'])) { ?>
158
- <?php _e('The "all language" setting is set to use the WordPress default privacy page. Please translate that page.', 'newsletter') ?>
159
- <?php } else { ?>
160
- <?php if ($is_all_languages) { ?>
161
- <?php if (function_exists('get_privacy_policy_url') && get_privacy_policy_url()) { ?>
162
- <?php $controls->checkbox('privacy_use_wp_url', __('Use WordPress privacy URL', 'newsletter')); ?>
163
- (<a href="<?php echo esc_attr(get_privacy_policy_url()) ?>"><?php echo esc_html(get_privacy_policy_url()) ?></a>)
164
- <br>OR<br>
165
- <?php } ?>
166
- <?php } ?>
167
- <?php if (!$is_all_languages) { ?>
168
- <?php _e('To use the WordPress privacy page, switch to "all language" and activate it.', 'newsletter') ?><br>
169
- <?php } ?>
170
- <?php $controls->text_url('privacy_url', 50); ?>
171
- <?php } ?>
172
- </td>
173
- </tr>
174
- </table>
175
- <div class="tnpc-hint">
176
- <?php _e('The privacy acceptance checkbox (required in many Europen countries) forces the subscriber to check it before proceeding. If an URL is specified the label becomes a link.', 'newsletter') ?>
177
- </div>
178
- </td>
179
- </tr>
180
-
181
- </table>
182
- </div>
183
-
184
-
185
- <div id="tabs-3">
186
- <p>
187
- <?php _e('Generic textual profile fields that can be collected during the subscription. Field formats can be one line text
188
- or selection list. Fields of type "list" must be configured with a set of options, comma separated
189
- like: "first option, second option, third option".', 'newsletter') ?>
190
- </p>
191
- <p>
192
- <?php _e('The placeholder works only on HTML 5 compliant browsers.', 'newsletter') ?>
193
- </p>
194
-
195
- <table class="widefat">
196
- <thead>
197
- <tr>
198
- <th><?php _e('Field', 'newsletter') ?></th>
199
- <th><?php _e('Name/Label', 'newsletter') ?></th>
200
- <th><?php _e('Placeholder', 'newsletter') ?></th>
201
-
202
- <th><?php _e('When/Where', 'newsletter') ?></th>
203
- <th><?php _e('Type', 'newsletter') ?></th>
204
- <th><?php _e('Rule', 'newsletter') ?></th>
205
-
206
- <th><?php _e('List values comma separated', 'newsletter') ?></th>
207
- </tr>
208
- </thead>
209
- <?php for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) { ?>
210
- <tr>
211
- <td><?php echo $i; ?></td>
212
- <td><?php $controls->text('profile_' . $i); ?></td>
213
- <td><?php $controls->text('profile_' . $i . '_placeholder'); ?></td>
214
- <?php if ($is_all_languages) { ?>
215
- <td><?php $controls->select('profile_' . $i . '_status', $status); ?></td>
216
- <td><?php $controls->select('profile_' . $i . '_type', $extra_type); ?></td>
217
- <td><?php $controls->select('profile_' . $i . '_rules', $rules); ?></td>
218
- <?php } else { ?>
219
- <td><?php echo esc_html($status[$controls->get_value('profile_' . $i . '_status')]) ?></td>
220
- <td><?php echo esc_html($extra_type[$controls->get_value('profile_' . $i . '_type')]) ?></td>
221
- <td><?php echo esc_html($rules[$controls->get_value('profile_' . $i . '_rules')]) ?></td>
222
- <?php } ?>
223
- <td>
224
- <?php $controls->textarea_fixed('profile_' . $i . '_options', '200px', '50px'); ?>
225
- </td>
226
- </tr>
227
- <?php } ?>
228
- </table>
229
-
230
- </div>
231
-
232
-
233
-
234
- </div>
235
-
236
- <p>
237
- <?php $controls->button_save(); ?>
238
- <?php $controls->button_reset(); ?>
239
- </p>
240
-
241
- </form>
242
- </div>
243
-
244
- <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
245
-
246
- </div>
1
+ <?php
2
+ /* @var $this NewsletterSubscription */
3
+ defined('ABSPATH') || exit;
4
+
5
+ include_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
+ $controls = new NewsletterControls();
7
+
8
+ $current_language = $this->get_current_language();
9
+
10
+ $is_all_languages = $this->is_all_languages();
11
+
12
+ if (!$is_all_languages) {
13
+ $controls->warnings[] = 'You are configuring the language "<strong>' . $current_language . '</strong>". Switch to "all languages" to see every options.';
14
+ }
15
+
16
+ if (!$controls->is_action()) {
17
+ $controls->data = $this->get_options('profile', $current_language);
18
+ } else {
19
+ if ($controls->is_action('save')) {
20
+ $this->save_options($controls->data, 'profile', null, $current_language);
21
+ $controls->data = $this->get_options('profile', $current_language);
22
+ $controls->add_message_saved();
23
+ }
24
+
25
+ if ($controls->is_action('reset')) {
26
+ $controls->data = $this->reset_options('profile');
27
+ $controls->add_message_done();
28
+ }
29
+ }
30
+
31
+ $status = array(0 => __('Private', 'newsletter'), 1 => __('Show on profile page', 'newsletter'), 2 => __('Show on subscription form', 'newsletter'));
32
+ $rules = array(0 => __('Optional', 'newsletter'), 1 => __('Required', 'newsletter'));
33
+ $extra_type = array('text' => __('Text', 'newsletter'), 'select' => __('List', 'newsletter'));
34
+ ?>
35
+
36
+ <div class="wrap" id="tnp-wrap">
37
+
38
+ <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
39
+
40
+ <div id="tnp-heading">
41
+
42
+ <h2><?php _e('Subscription Form Fields and Layout', 'newsletter') ?></h2>
43
+
44
+ <p>
45
+ <a href="?page=newsletter_subscription_forms"><?php _e('HTML samples and hand coded forms', 'newsletter') ?></a>
46
+ </p>
47
+
48
+ </div>
49
+
50
+ <div id="tnp-body">
51
+
52
+ <form action="" method="post">
53
+ <?php $controls->init(); ?>
54
+
55
+ <div id="tabs">
56
+
57
+ <ul>
58
+ <li><a href="#tabs-2"><?php _e('Main profile fields', 'newsletter') ?></a></li>
59
+ <li><a href="#tabs-3"><?php _e('Extra profile fields', 'newsletter') ?></a></li>
60
+ </ul>
61
+
62
+ <div id="tabs-2">
63
+
64
+ <p><?php _e('The main subscriber fields. Only the email field is, of course, mandatory.', 'newsletter') ?></p>
65
+
66
+ <table class="form-table">
67
+ <tr>
68
+ <th>Email</th>
69
+ <td>
70
+ <table class="tnpc-grid">
71
+ <tr><th><?php _e('Field label', 'newsletter') ?></th><td><?php $controls->text('email', 50); ?></td></tr>
72
+ <tr><th><?php _e('Error message', 'newsletter') ?></th><td><?php $controls->text('email_error', 50); ?></td></tr>
73
+ </table>
74
+ </td>
75
+ </tr>
76
+ <tr>
77
+ <th><?php _e('First name', 'newsletter') ?></th>
78
+ <td>
79
+ <table class="tnpc-grid">
80
+ <tr><th><?php _e('Field label', 'newsletter') ?></th><td><?php $controls->text('name', 50); ?></td></tr>
81
+ <?php if ($is_all_languages) { ?>
82
+ <tr><th><?php _e('When to show', 'newsletter') ?></th><td><?php $controls->select('name_status', $status); ?></td></tr>
83
+ <tr><th><?php _e('Rules', 'newsletter') ?></th><td><?php $controls->select('name_rules', $rules); ?></td></tr>
84
+ <?php } ?>
85
+ </table>
86
+ <p class="description">
87
+ <?php _e('If you want to collect only a generic "name", use only this field and not the last name field.', 'newsletter') ?>
88
+ </p>
89
+ </td>
90
+ </tr>
91
+ <tr>
92
+ <th><?php _e('Last name', 'newsletter') ?></th>
93
+ <td>
94
+ <table class="tnpc-grid">
95
+ <tr><th><?php _e('Field label', 'newsletter') ?></th><td><?php $controls->text('surname', 50); ?></td></tr>
96
+ <?php if ($is_all_languages) { ?>
97
+ <tr><th><?php _e('When to show', 'newsletter') ?></th><td><?php $controls->select('surname_status', $status); ?></td></tr>
98
+ <tr><th><?php _e('Rules', 'newsletter') ?></th><td><?php $controls->select('surname_rules', $rules); ?></td></tr>
99
+ <?php } ?>
100
+ </table>
101
+ </td>
102
+ </tr>
103
+ <tr>
104
+ <th><?php _e('Gender', 'newsletter') ?></th>
105
+ <td>
106
+ <table class="tnpc-grid">
107
+ <tr><th><?php _e('Field label', 'newsletter') ?></th><td><?php $controls->text('sex', 50); ?></td></tr>
108
+ <?php if ($is_all_languages) { ?>
109
+ <tr><th><?php _e('When to show', 'newsletter') ?></th><td><?php $controls->select('sex_status', $status); ?></td></tr>
110
+ <tr><th><?php _e('Rules', 'newsletter') ?></th><td><?php $controls->select('sex_rules', $rules); ?></td></tr>
111
+ <?php } ?>
112
+ <tr>
113
+ <th><?php _e('Value labels', 'newsletter') ?></th>
114
+ <td>
115
+ <?php $controls->text('sex_none', 20, __('not specified', 'newsletter')); ?>
116
+ <?php $controls->text('sex_female', 20, __('female', 'newsletter')); ?>
117
+ <?php $controls->text('sex_male', 20, __('male', 'newsletter')); ?>
118
+ </td>
119
+ </tr>
120
+ <tr>
121
+ <th><?php _e('Salutation titles', 'newsletter') ?></th>
122
+ <td>
123
+ <?php _e('not specified', 'newsletter') ?>: <?php $controls->text('title_none'); ?><br>
124
+ <?php _e('for females', 'newsletter') ?>: <?php $controls->text('title_female'); ?> (ex. "Mrs")<br>
125
+ <?php _e('for males', 'newsletter') ?>: <?php $controls->text('title_male'); ?> (ex. "Mr")
126
+ </td>
127
+ </tr>
128
+ </table>
129
+ <p class="description">
130
+ <?php _e('Salutation titles are inserted in emails message when the tag {title} is used. For example "Good morning {title} {surname} {name}".', 'newsletter') ?>
131
+ </p>
132
+ </td>
133
+ </tr>
134
+
135
+ <tr>
136
+ <th><?php _e('"Subscribe" label', 'newsletter') ?></th>
137
+ <td>
138
+ <?php $controls->text('subscribe', 40); ?>
139
+
140
+ <p class="description">
141
+ <?php _e('You can use an image URL', 'newsletter') ?> (http://...).
142
+ </p>
143
+ </td>
144
+ </tr>
145
+
146
+ <tr>
147
+ <th><?php _e('Privacy checkbox/notice', 'newsletter') ?></th>
148
+ <td>
149
+ <table>
150
+ <?php if ($is_all_languages) { ?>
151
+ <tr><th><?php _e('Enabled?', 'newsletter') ?></th><td><?php $controls->select('privacy_status', array(0 => __('No', 'newsletter'), 1 => __('Yes', 'newsletter'), 2 => __('Only the notice', 'newsletter'))); ?></td></tr>
152
+ <?php } ?>
153
+ <tr><th><?php _e('Label', 'newsletter') ?></th><td><?php $controls->text('privacy', 50); ?></td></tr>
154
+ <tr>
155
+ <th>Privacy URL</th>
156
+ <td>
157
+ <?php if (!$is_all_languages && !empty($controls->data['privacy_use_wp_url'])) { ?>
158
+ <?php _e('The "all language" setting is set to use the WordPress default privacy page. Please translate that page.', 'newsletter') ?>
159
+ <?php } else { ?>
160
+ <?php if ($is_all_languages) { ?>
161
+ <?php if (function_exists('get_privacy_policy_url') && get_privacy_policy_url()) { ?>
162
+ <?php $controls->checkbox('privacy_use_wp_url', __('Use WordPress privacy URL', 'newsletter')); ?>
163
+ (<a href="<?php echo esc_attr(get_privacy_policy_url()) ?>"><?php echo esc_html(get_privacy_policy_url()) ?></a>)
164
+ <br>OR<br>
165
+ <?php } ?>
166
+ <?php } ?>
167
+ <?php if (!$is_all_languages) { ?>
168
+ <?php _e('To use the WordPress privacy page, switch to "all language" and activate it.', 'newsletter') ?><br>
169
+ <?php } ?>
170
+ <?php $controls->text_url('privacy_url', 50); ?>
171
+ <?php } ?>
172
+ </td>
173
+ </tr>
174
+ </table>
175
+ <div class="tnpc-hint">
176
+ <?php _e('The privacy acceptance checkbox (required in many Europen countries) forces the subscriber to check it before proceeding. If an URL is specified the label becomes a link.', 'newsletter') ?>
177
+ </div>
178
+ </td>
179
+ </tr>
180
+
181
+ </table>
182
+ </div>
183
+
184
+
185
+ <div id="tabs-3">
186
+ <p>
187
+ <?php _e('Generic textual profile fields that can be collected during the subscription. Field formats can be one line text
188
+ or selection list. Fields of type "list" must be configured with a set of options, comma separated
189
+ like: "first option, second option, third option".', 'newsletter') ?>
190
+ </p>
191
+ <p>
192
+ <?php _e('The placeholder works only on HTML 5 compliant browsers.', 'newsletter') ?>
193
+ </p>
194
+
195
+ <table class="widefat">
196
+ <thead>
197
+ <tr>
198
+ <th><?php _e('Field', 'newsletter') ?></th>
199
+ <th><?php _e('Name/Label', 'newsletter') ?></th>
200
+ <th><?php _e('Placeholder', 'newsletter') ?></th>
201
+
202
+ <th><?php _e('When/Where', 'newsletter') ?></th>
203
+ <th><?php _e('Type', 'newsletter') ?></th>
204
+ <th><?php _e('Rule', 'newsletter') ?></th>
205
+
206
+ <th><?php _e('List values comma separated', 'newsletter') ?></th>
207
+ </tr>
208
+ </thead>
209
+ <?php for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) { ?>
210
+ <tr>
211
+ <td><?php echo $i; ?></td>
212
+ <td><?php $controls->text('profile_' . $i); ?></td>
213
+ <td><?php $controls->text('profile_' . $i . '_placeholder'); ?></td>
214
+ <?php if ($is_all_languages) { ?>
215
+ <td><?php $controls->select('profile_' . $i . '_status', $status); ?></td>
216
+ <td><?php $controls->select('profile_' . $i . '_type', $extra_type); ?></td>
217
+ <td><?php $controls->select('profile_' . $i . '_rules', $rules); ?></td>
218
+ <?php } else { ?>
219
+ <td><?php echo esc_html($status[$controls->get_value('profile_' . $i . '_status')]) ?></td>
220
+ <td><?php echo esc_html($extra_type[$controls->get_value('profile_' . $i . '_type')]) ?></td>
221
+ <td><?php echo esc_html($rules[$controls->get_value('profile_' . $i . '_rules')]) ?></td>
222
+ <?php } ?>
223
+ <td>
224
+ <?php $controls->textarea_fixed('profile_' . $i . '_options', '200px', '50px'); ?>
225
+ </td>
226
+ </tr>
227
+ <?php } ?>
228
+ </table>
229
+
230
+ </div>
231
+
232
+
233
+
234
+ </div>
235
+
236
+ <p>
237
+ <?php $controls->button_save(); ?>
238
+ <?php $controls->button_reset(); ?>
239
+ </p>
240
+
241
+ </form>
242
+ </div>
243
+
244
+ <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
245
+
246
+ </div>
users/import.php CHANGED
@@ -1,36 +1,36 @@
1
- <?php
2
- defined('ABSPATH') || exit;
3
-
4
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
5
- $controls = new NewsletterControls();
6
- ?>
7
-
8
- <div class="wrap" id="tnp-wrap">
9
-
10
- <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
11
-
12
- <div id="tnp-heading">
13
-
14
- <h2><?php _e('Import', 'newsletter') ?></h2>
15
-
16
- </div>
17
-
18
- <div id="tnp-body" class="tnp-users tnp-users-import">
19
- <p>
20
- The import features have been consolidated in the <strong>free</strong> "Advanced Import" addon you can find on
21
- <a href="?page=<?php echo class_exists('NewsletterExtensions') ? 'newsletter_extensions_index' : 'newsletter_main_extensions' ?>">addons management panel</a>. Please install that addon to have:
22
- </p>
23
- <ul style="color: #fff; margin-left: 1em;">
24
- <li>File upload or copy and paste of data</li>
25
- <li>Background processing for long set of data</li>
26
- <li>Quick bounced address import</li>
27
- </ul>
28
-
29
- <p>
30
- Documentation about Advanced Import addon can be <a href="https://www.thenewsletterplugin.com/documentation/addons/extended-features/advanced-import/" target="_blank">found here</a>.</p>
31
- </p>
32
- </div>
33
-
34
- <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
35
-
36
- </div>
1
+ <?php
2
+ defined('ABSPATH') || exit;
3
+
4
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
5
+ $controls = new NewsletterControls();
6
+ ?>
7
+
8
+ <div class="wrap" id="tnp-wrap">
9
+
10
+ <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
11
+
12
+ <div id="tnp-heading">
13
+
14
+ <h2><?php _e('Import', 'newsletter') ?></h2>
15
+
16
+ </div>
17
+
18
+ <div id="tnp-body" class="tnp-users tnp-users-import">
19
+ <p>
20
+ The import features have been consolidated in the <strong>free</strong> "Advanced Import" addon you can find on
21
+ <a href="?page=<?php echo class_exists('NewsletterExtensions') ? 'newsletter_extensions_index' : 'newsletter_main_extensions' ?>">addons management panel</a>. Please install that addon to have:
22
+ </p>
23
+ <ul style="color: #fff; margin-left: 1em;">
24
+ <li>File upload or copy and paste of data</li>
25
+ <li>Background processing for long set of data</li>
26
+ <li>Quick bounced address import</li>
27
+ </ul>
28
+
29
+ <p>
30
+ Documentation about Advanced Import addon can be <a href="https://www.thenewsletterplugin.com/documentation/addons/extended-features/advanced-import/" target="_blank">found here</a>.</p>
31
+ </p>
32
+ </div>
33
+
34
+ <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
35
+
36
+ </div>
users/statistics-time.php CHANGED
@@ -1,53 +1,53 @@
1
- <?php
2
- defined('ABSPATH') || exit;
3
- ?>
4
-
5
- <div class="row">
6
- <div class="col-md-6">
7
- <h3><?php _e('Subscriptions by month (max 12 months)', 'newsletter') ?></h3>
8
- <?php
9
- $months = $wpdb->get_results("select count(*) as c, concat(year(created), '-', date_format(created, '%m')) as d from " . NEWSLETTER_USERS_TABLE . " where status='C' group by concat(year(created), '-', date_format(created, '%m')) order by d desc limit 12");
10
- ?>
11
-
12
- <table class="widefat">
13
- <thead>
14
- <tr>
15
- <th><?php _e('Year and month', 'newsletter') ?></th>
16
- <th><?php _e('Total', 'newsletter') ?></th>
17
- </tr>
18
- </thead>
19
- <?php foreach ($months as &$day) { ?>
20
- <tr>
21
- <td><?php echo $day->d; ?></td>
22
- <td><?php echo $day->c; ?></td>
23
- </tr>
24
- <?php } ?>
25
- </table>
26
-
27
- </div>
28
-
29
- <div class="col-md-6">
30
-
31
- <h3><?php _e('Subscriptions by day (max 90 days)', 'newsletter') ?></h3>
32
- <?php
33
- $list = $wpdb->get_results("select count(*) as c, date(created) as d from " . NEWSLETTER_USERS_TABLE . " where status='C' group by date(created) order by d desc limit 90");
34
- ?>
35
- <table class="widefat">
36
- <thead>
37
- <tr>
38
- <th><?php _e('Date', 'newsletter') ?></th>
39
- <th><?php _e('Total', 'newsletter') ?></th>
40
- </tr>
41
- </thead>
42
- <?php foreach ($list as $day) { ?>
43
- <tr>
44
- <td><?php echo $day->d; ?></td>
45
- <td><?php echo $day->c; ?></td>
46
- </tr>
47
- <?php } ?>
48
- </table>
49
-
50
- </div>
51
-
52
- </div>
53
-
1
+ <?php
2
+ defined('ABSPATH') || exit;
3
+ ?>
4
+
5
+ <div class="row">
6
+ <div class="col-md-6">
7
+ <h3><?php _e('Subscriptions by month (max 12 months)', 'newsletter') ?></h3>
8
+ <?php
9
+ $months = $wpdb->get_results("select count(*) as c, concat(year(created), '-', date_format(created, '%m')) as d from " . NEWSLETTER_USERS_TABLE . " where status='C' group by concat(year(created), '-', date_format(created, '%m')) order by d desc limit 12");
10
+ ?>
11
+
12
+ <table class="widefat">
13
+ <thead>
14
+ <tr>
15
+ <th><?php _e('Year and month', 'newsletter') ?></th>
16
+ <th><?php _e('Total', 'newsletter') ?></th>
17
+ </tr>
18
+ </thead>
19
+ <?php foreach ($months as &$day) { ?>
20
+ <tr>
21
+ <td><?php echo $day->d; ?></td>
22
+ <td><?php echo $day->c; ?></td>
23
+ </tr>
24
+ <?php } ?>
25
+ </table>
26
+
27
+ </div>
28
+
29
+ <div class="col-md-6">
30
+
31
+ <h3><?php _e('Subscriptions by day (max 90 days)', 'newsletter') ?></h3>
32
+ <?php
33
+ $list = $wpdb->get_results("select count(*) as c, date(created) as d from " . NEWSLETTER_USERS_TABLE . " where status='C' group by date(created) order by d desc limit 90");
34
+ ?>
35
+ <table class="widefat">
36
+ <thead>
37
+ <tr>
38
+ <th><?php _e('Date', 'newsletter') ?></th>
39
+ <th><?php _e('Total', 'newsletter') ?></th>
40
+ </tr>
41
+ </thead>
42
+ <?php foreach ($list as $day) { ?>
43
+ <tr>
44
+ <td><?php echo $day->d; ?></td>
45
+ <td><?php echo $day->c; ?></td>
46
+ </tr>
47
+ <?php } ?>
48
+ </table>
49
+
50
+ </div>
51
+
52
+ </div>
53
+