Newsletter - Version 7.5.7

Version Description

  • Check on ctype_space function (there are installation without it)
  • Improved the html-2-text conversion
  • Added language to the export
  • Status labels fixed on subscriber search and edit panel
  • Stripped HTML tags on block generated subjects
Download this release

Release Info

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

Code changes from version 7.5.6 to 7.5.7

emails/blocks/posts/layout-one.php CHANGED
@@ -1,135 +1,139 @@
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', ['scale' => .8]);
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 !important;
13
- padding: 0 0 5px 0;
14
- }
15
-
16
- .excerpt {
17
- <?php $text_style->echo_css()?>
18
- line-height: 1.5em !important;
19
- padding: 10px 0 15px 0;
20
- }
21
-
22
- .meta {
23
- font-family: <?php echo $text_style->font_family ?>;
24
- color: <?php echo $text_style->font_color ?>;
25
- font-size: <?php echo round($text_style->font_size * 0.9) ?>px;
26
- font-weight: <?php echo $text_style->font_weight ?>;
27
- font-style: italic;
28
- padding: 0 0 10px 0;
29
- line-height: normal !important;
30
- }
31
- .button {
32
- padding: 15px 0;
33
- }
34
- </style>
35
-
36
-
37
- <table border="0" cellpadding="0" cellspacing="0" width="100%" class="responsive">
38
-
39
- <?php foreach ($posts as $post) { ?>
40
- <?php
41
- $url = tnp_post_permalink($post);
42
-
43
- $media = null;
44
- if ($show_image) {
45
- $media = tnp_composer_block_posts_get_media($post, $size);
46
- if ($media) {
47
- $media->link = $url;
48
- $media->set_width($column_width);
49
- }
50
- }
51
-
52
- $meta = [];
53
-
54
- if ($show_date) {
55
- $meta[] = tnp_post_date($post);
56
- }
57
-
58
- if ($show_author) {
59
- $author_object = get_user_by('id', $post->post_author);
60
- if ($author_object) {
61
- $meta[] = $author_object->display_name;
62
- }
63
- }
64
-
65
- $button_options['button_url'] = $url;
66
- $button_options['button_align'] = 'left';
67
- ?>
68
-
69
- <tr>
70
- <td valign="top" style="padding: 20px 0 0 0;">
71
-
72
- <?php if ($media) { ?>
73
- <table width="<?php echo $column_width ?>" cellpadding="0" cellspacing="0" border="0" align="left" class="responsive">
74
- <tr>
75
- <td style="padding-bottom: 20px;" width="100%">
76
- <?php echo TNP_Composer::image($media, ['class' => 'fluid']) ?>
77
- </td>
78
- </tr>
79
- </table>
80
- <?php } ?>
81
-
82
- <table width="<?php echo $media ? $column_width : '100%' ?>" cellpadding="0" cellspacing="0" border="0" class="responsive" align="right"><tr><td>
83
-
84
- <table border="0" cellspacing="0" cellpadding="0" width="100%">
85
- <?php if ($meta) { ?>
86
- <tr>
87
- <td inline-class="meta" dir="<?php echo $dir ?>" align="<?php echo $align_left ?>">
88
- <?php echo esc_html(implode(' - ', $meta)) ?>
89
- </td>
90
- </tr>
91
- <?php } ?>
92
-
93
- <tr>
94
- <td align="<?php echo $align_left ?>" inline-class="title" class="title tnpc-row-edit tnpc-inline-editable"
95
- data-type="title" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
96
- <span>
97
- <?php
98
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $post->ID) ?
99
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $post->ID) :
100
- tnp_post_title($post)
101
- ?>
102
- </span>
103
- </td>
104
- </tr>
105
-
106
- <?php if ($excerpt_length) { ?>
107
- <tr>
108
- <td align="<?php echo $align_left ?>" inline-class="excerpt" class="tnpc-row-edit tnpc-inline-editable"
109
- data-type="text" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
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
- </td>
116
- </tr>
117
- <?php } ?>
118
-
119
- <?php if ($show_read_more_button) { ?>
120
- <tr>
121
- <td align="<?php echo $align_left ?>" inline-class="button">
122
- <?php echo TNP_Composer::button($button_options) ?>
123
- </td>
124
- </tr>
125
- <?php } ?>
126
- </table>
127
-
128
- </td></tr></table>
129
-
130
- </td>
131
- </tr>
132
-
133
- <?php } ?>
134
-
135
- </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', ['scale' => .8]);
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 !important;
13
+ padding: 0 0 5px 0;
14
+ }
15
+ .excerpt-td {
16
+ padding: 10px 0 15px 0;
17
+ }
18
+ .excerpt {
19
+ <?php $text_style->echo_css()?>
20
+ line-height: 1.5em !important;
21
+
22
+ }
23
+
24
+ .meta {
25
+ font-family: <?php echo $text_style->font_family ?>;
26
+ color: <?php echo $text_style->font_color ?>;
27
+ font-size: <?php echo round($text_style->font_size * 0.9) ?>px;
28
+ font-weight: <?php echo $text_style->font_weight ?>;
29
+ font-style: italic;
30
+ padding: 0 0 10px 0;
31
+ line-height: normal !important;
32
+ }
33
+ .button {
34
+ padding: 15px 0;
35
+ }
36
+ </style>
37
+
38
+
39
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" class="responsive">
40
+
41
+ <?php foreach ($posts as $post) { ?>
42
+ <?php
43
+ $url = tnp_post_permalink($post);
44
+
45
+ $media = null;
46
+ if ($show_image) {
47
+ $media = tnp_composer_block_posts_get_media($post, $size);
48
+ if ($media) {
49
+ $media->link = $url;
50
+ $media->set_width($column_width);
51
+ }
52
+ }
53
+
54
+ $meta = [];
55
+
56
+ if ($show_date) {
57
+ $meta[] = tnp_post_date($post);
58
+ }
59
+
60
+ if ($show_author) {
61
+ $author_object = get_user_by('id', $post->post_author);
62
+ if ($author_object) {
63
+ $meta[] = $author_object->display_name;
64
+ }
65
+ }
66
+
67
+ $button_options['button_url'] = $url;
68
+ $button_options['button_align'] = 'left';
69
+ ?>
70
+
71
+ <tr>
72
+ <td valign="top" style="padding: 20px 0 0 0;">
73
+
74
+ <?php if ($media) { ?>
75
+ <table width="<?php echo $column_width ?>" cellpadding="0" cellspacing="0" border="0" align="left" class="responsive">
76
+ <tr>
77
+ <td style="padding-bottom: 20px;" width="100%">
78
+ <?php echo TNP_Composer::image($media, ['class' => 'fluid']) ?>
79
+ </td>
80
+ </tr>
81
+ </table>
82
+ <?php } ?>
83
+
84
+ <table width="<?php echo $media ? $column_width : '100%' ?>" cellpadding="0" cellspacing="0" border="0" class="responsive" align="right"><tr><td>
85
+
86
+ <table border="0" cellspacing="0" cellpadding="0" width="100%">
87
+ <?php if ($meta) { ?>
88
+ <tr>
89
+ <td inline-class="meta" dir="<?php echo $dir ?>" align="<?php echo $align_left ?>">
90
+ <?php echo esc_html(implode(' - ', $meta)) ?>
91
+ </td>
92
+ </tr>
93
+ <?php } ?>
94
+
95
+ <tr>
96
+ <td align="<?php echo $align_left ?>" inline-class="title" class="title tnpc-row-edit tnpc-inline-editable"
97
+ data-type="title" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
98
+ <span>
99
+ <?php
100
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $post->ID) ?
101
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $post->ID) :
102
+ tnp_post_title($post)
103
+ ?>
104
+ </span>
105
+ </td>
106
+ </tr>
107
+
108
+ <?php if ($excerpt_length) { ?>
109
+ <tr>
110
+ <td align="<?php echo $align_left ?>" inline-class="excerpt-td">
111
+ <a href="<?php echo $url?>" inline-class="excerpt" class="tnpc-row-edit tnpc-inline-editable"
112
+ data-type="text" data-id="<?php echo $post->ID ?>" dir="<?php echo $dir ?>">
113
+ <?php
114
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $post->ID) ?
115
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $post->ID) :
116
+ tnp_post_excerpt($post, $excerpt_length)
117
+ ?>
118
+ </a>
119
+ </td>
120
+ </tr>
121
+ <?php } ?>
122
+
123
+ <?php if ($show_read_more_button) { ?>
124
+ <tr>
125
+ <td align="<?php echo $align_left ?>" inline-class="button">
126
+ <?php echo TNP_Composer::button($button_options) ?>
127
+ </td>
128
+ </tr>
129
+ <?php } ?>
130
+ </table>
131
+
132
+ </td></tr></table>
133
+
134
+ </td>
135
+ </tr>
136
+
137
+ <?php } ?>
138
+
139
+ </table>
emails/blocks/posts/layout-two.php CHANGED
@@ -1,136 +1,142 @@
1
- <?php
2
- $size = array('width' => 600, 'height' => 400, "crop" => true);
3
- $total_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
- $column_width = $total_width / 2 - 20;
5
-
6
- $title_style = TNP_Composer::get_style($options, 'title', $composer, 'title', ['scale' => .8]);
7
- $text_style = TNP_Composer::get_style($options, '', $composer, 'text');
8
-
9
- $items = [];
10
- ?>
11
- <style>
12
- .title {
13
- font-family: <?php echo $title_style->font_family ?>;
14
- font-size: <?php echo $title_style->font_size ?>px;
15
- font-weight: <?php echo $title_style->font_weight ?>;
16
- color: <?php echo $title_style->font_color ?>;
17
- line-height: 1.3em;
18
- padding: 15px 0 0 0;
19
- }
20
-
21
- .excerpt {
22
- font-family: <?php echo $text_style->font_family ?>;
23
- font-size: <?php echo $text_style->font_size ?>px;
24
- font-weight: <?php echo $text_style->font_weight ?>;
25
- color: <?php echo $text_style->font_color ?>;
26
- line-height: 1.4em;
27
- padding: 5px 0 0 0;
28
- }
29
-
30
- .meta {
31
- font-family: <?php echo $text_style->font_family ?>;
32
- color: <?php echo $text_style->font_color ?>;
33
- font-size: <?php echo round($text_style->font_size * 0.9) ?>px;
34
- font-weight: <?php echo $text_style->font_weight ?>;
35
- padding: 10px 0 0 0;
36
- font-style: italic;
37
- line-height: normal !important;
38
- }
39
- .button {
40
- padding: 15px 0;
41
- }
42
- .column-left {
43
- padding-right: 10px;
44
- padding-bottom: 20px;
45
- }
46
- .column-right {
47
- padding-left: 10px;
48
- padding-bottom: 20px;
49
- }
50
-
51
- </style>
52
-
53
-
54
- <?php foreach ($posts AS $p) { ?>
55
- <?php
56
- $media = null;
57
- if ($show_image) {
58
- $media = tnp_composer_block_posts_get_media($p, $size, $image_placeholder_url);
59
- if ($media) {
60
- $media->link = tnp_post_permalink($p);
61
- $media->set_width($column_width);
62
- }
63
- }
64
-
65
- $meta = [];
66
-
67
- if ($show_date) {
68
- $meta[] = tnp_post_date($p);
69
- }
70
-
71
- if ($show_author) {
72
- $author_object = get_user_by('id', $p->post_author);
73
- if ($author_object) {
74
- $meta[] = $author_object->display_name;
75
- }
76
- }
77
-
78
- $button_options['button_url'] = tnp_post_permalink($p);
79
- ob_start();
80
- ?>
81
- <table cellpadding="0" cellspacing="0" border="0" width="100%">
82
- <?php if ($media) { ?>
83
- <tr>
84
- <td align="center" valign="middle">
85
- <?php echo TNP_Composer::image($media, ['class' => 'fluid']) ?>
86
- </td>
87
- </tr>
88
- <?php } ?>
89
- <tr>
90
- <td align="center" inline-class="title" class="title tnpc-row-edit tnpc-inline-editable" data-type="title" data-id="<?php echo $p->ID ?>">
91
- <?php
92
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $p->ID) ?
93
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $p->ID) :
94
- tnp_post_title($p)
95
- ?>
96
- </td>
97
- </tr>
98
- <?php if ($meta) { ?>
99
- <tr>
100
- <td align="center" inline-class="meta" class="meta">
101
- <?php echo esc_html(implode(' - ', $meta)) ?>
102
- </td>
103
- </tr>
104
- <?php } ?>
105
-
106
-
107
- <?php if ($excerpt_length) { ?>
108
- <tr>
109
- <td align="center" inline-class="excerpt" class="title tnpc-row-edit tnpc-inline-editable" data-type="text" data-id="<?php echo $p->ID ?>">
110
- <?php
111
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $p->ID) ?
112
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $p->ID) :
113
- tnp_post_excerpt($p, $excerpt_length)
114
- ?>
115
- </td>
116
- </tr>
117
- <?php } ?>
118
-
119
- <?php if ($show_read_more_button) { ?>
120
- <tr>
121
- <td align="center" inline-class="button">
122
- <?php echo TNP_Composer::button($button_options) ?>
123
- </td>
124
- </tr>
125
- <?php } ?>
126
- </table>
127
- <?php
128
- $items[] = ob_get_clean();
129
- }
130
- ?>
131
-
132
-
133
- <?php echo TNP_Composer::grid($items, ['width' => $total_width, 'responsive' => true, 'padding' => 5]) ?>
134
-
135
-
136
-
 
 
 
 
 
 
1
+ <?php
2
+ $size = array('width' => 600, 'height' => 400, "crop" => true);
3
+ $total_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
+ $column_width = $total_width / 2 - 20;
5
+
6
+ $title_style = TNP_Composer::get_style($options, 'title', $composer, 'title', ['scale' => .8]);
7
+ $text_style = TNP_Composer::get_style($options, '', $composer, 'text');
8
+
9
+ $items = [];
10
+ ?>
11
+ <style>
12
+ .title {
13
+ font-family: <?php echo $title_style->font_family ?>;
14
+ font-size: <?php echo $title_style->font_size ?>px;
15
+ font-weight: <?php echo $title_style->font_weight ?>;
16
+ color: <?php echo $title_style->font_color ?>;
17
+ line-height: 1.3em;
18
+ padding: 15px 0 0 0;
19
+ }
20
+
21
+ .excerpt-td {
22
+ padding: 5px 0 0 0;
23
+ }
24
+ .excerpt {
25
+ font-family: <?php echo $text_style->font_family ?>;
26
+ font-size: <?php echo $text_style->font_size ?>px;
27
+ font-weight: <?php echo $text_style->font_weight ?>;
28
+ color: <?php echo $text_style->font_color ?>;
29
+ line-height: 1.4em;
30
+ text-decoration: none;
31
+ }
32
+
33
+ .meta {
34
+ font-family: <?php echo $text_style->font_family ?>;
35
+ color: <?php echo $text_style->font_color ?>;
36
+ font-size: <?php echo round($text_style->font_size * 0.9) ?>px;
37
+ font-weight: <?php echo $text_style->font_weight ?>;
38
+ padding: 10px 0 0 0;
39
+ font-style: italic;
40
+ line-height: normal !important;
41
+ }
42
+ .button {
43
+ padding: 15px 0;
44
+ }
45
+ .column-left {
46
+ padding-right: 10px;
47
+ padding-bottom: 20px;
48
+ }
49
+ .column-right {
50
+ padding-left: 10px;
51
+ padding-bottom: 20px;
52
+ }
53
+
54
+ </style>
55
+
56
+
57
+ <?php foreach ($posts AS $p) { ?>
58
+ <?php
59
+ $url = tnp_post_permalink($p);
60
+ $media = null;
61
+ if ($show_image) {
62
+ $media = tnp_composer_block_posts_get_media($p, $size, $image_placeholder_url);
63
+ if ($media) {
64
+ $media->link = $url;
65
+ $media->set_width($column_width);
66
+ }
67
+ }
68
+
69
+ $meta = [];
70
+
71
+ if ($show_date) {
72
+ $meta[] = tnp_post_date($p);
73
+ }
74
+
75
+ if ($show_author) {
76
+ $author_object = get_user_by('id', $p->post_author);
77
+ if ($author_object) {
78
+ $meta[] = $author_object->display_name;
79
+ }
80
+ }
81
+
82
+ $button_options['button_url'] = $url;
83
+ ob_start();
84
+ ?>
85
+ <table cellpadding="0" cellspacing="0" border="0" width="100%">
86
+ <?php if ($media) { ?>
87
+ <tr>
88
+ <td align="center" valign="middle">
89
+ <?php echo TNP_Composer::image($media, ['class' => 'fluid']) ?>
90
+ </td>
91
+ </tr>
92
+ <?php } ?>
93
+ <tr>
94
+ <td align="center" inline-class="title" class="title tnpc-row-edit tnpc-inline-editable" data-type="title" data-id="<?php echo $p->ID ?>">
95
+ <?php
96
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $p->ID) ?
97
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $p->ID) :
98
+ tnp_post_title($p)
99
+ ?>
100
+ </td>
101
+ </tr>
102
+ <?php if ($meta) { ?>
103
+ <tr>
104
+ <td align="center" inline-class="meta" class="meta">
105
+ <?php echo esc_html(implode(' - ', $meta)) ?>
106
+ </td>
107
+ </tr>
108
+ <?php } ?>
109
+
110
+
111
+ <?php if ($excerpt_length) { ?>
112
+ <tr>
113
+ <td align="center" inline-class="excerpt-td">
114
+ <a href="<?php echo $url?>" inline-class="excerpt" class="title tnpc-row-edit tnpc-inline-editable" data-type="text" data-id="<?php echo $p->ID ?>" dir="<?php echo $dir ?>">
115
+ <?php
116
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $p->ID) ?
117
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $p->ID) :
118
+ tnp_post_excerpt($p, $excerpt_length)
119
+ ?>
120
+ </a>
121
+ </td>
122
+ </tr>
123
+ <?php } ?>
124
+
125
+ <?php if ($show_read_more_button) { ?>
126
+ <tr>
127
+ <td align="center" inline-class="button">
128
+ <?php echo TNP_Composer::button($button_options) ?>
129
+ </td>
130
+ </tr>
131
+ <?php } ?>
132
+ </table>
133
+ <?php
134
+ $items[] = ob_get_clean();
135
+ }
136
+ ?>
137
+
138
+
139
+ <?php echo TNP_Composer::grid($items, ['width' => $total_width, 'responsive' => true, 'padding' => 5]) ?>
140
+
141
+
142
+
emails/emails.php CHANGED
@@ -1,1365 +1,1365 @@
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
- $composer = [];
350
- foreach ($email->options as $k=>$v) {
351
- if (strpos($k, 'composer_') !== 0) continue;
352
- $composer[substr($k, 9)] = $v;
353
- }
354
-
355
-
356
- preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
357
-
358
- $result = '';
359
- $subject = '';
360
-
361
- foreach ($matches[1] as $match) {
362
- $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
363
- $options = $this->options_decode($a);
364
-
365
- $block = $this->get_block($options['block_id']);
366
- if (!$block) {
367
- $this->logger->debug('Unable to load the block ' . $options['block_id']);
368
- //continue;
369
- }
370
-
371
- ob_start();
372
- $out = $this->render_block($options['block_id'], true, $options, $context, $composer);
373
- if (is_array($out)) {
374
- if ($out['return_empty_message'] || $out['stop']) {
375
- return false;
376
- }
377
- if ($out['skip']) {
378
- continue;
379
- }
380
- if (empty($subject) && !empty($out['subject'])) {
381
- $subject = $out['subject'];
382
- }
383
- }
384
- $block_html = ob_get_clean();
385
- $result .= $block_html;
386
- }
387
-
388
- $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
389
- $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
390
- $email->subject = $subject;
391
- return true;
392
- }
393
-
394
- function remove_block_data($text) {
395
- // TODO: Lavorare!
396
- return $text;
397
- }
398
-
399
- static function get_outlook_wrapper_open($width = 600) {
400
- 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]-->';
401
- }
402
-
403
- static function get_outlook_wrapper_close() {
404
- return "<!--[if mso | IE]></td></tr></table><![endif]-->";
405
- }
406
-
407
- function hook_safe_style_css($rules) {
408
- $rules[] = 'display';
409
- return $rules;
410
- }
411
-
412
- /**
413
- * Renders a block identified by its id, using the block options and adding a wrapper
414
- * if required (for the first block rendering).
415
- *
416
- * @param string $block_id
417
- * @param boolean $wrapper
418
- * @param array $options
419
- * @param array $context
420
- * @param array $composer
421
- */
422
- function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
423
- static $kses_style_filter = false;
424
- include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
425
-
426
- //Remove 'options_composer_' prefix
427
- $composer_defaults = [];
428
- foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
429
- $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
430
- }
431
- $composer = array_merge($composer_defaults, $composer);
432
-
433
- $width = 600;
434
- $font_family = 'Helvetica, Arial, sans-serif';
435
-
436
- $global_title_font_family = $composer['title_font_family'];
437
- $global_title_font_size = $composer['title_font_size'];
438
- $global_title_font_color = $composer['title_font_color'];
439
- $global_title_font_weight = $composer['title_font_weight'];
440
-
441
- $global_text_font_family = $composer['text_font_family'];
442
- $global_text_font_size = $composer['text_font_size'];
443
- $global_text_font_color = $composer['text_font_color'];
444
- $global_text_font_weight = $composer['text_font_weight'];
445
-
446
- $global_button_font_family = $composer['button_font_family'];
447
- $global_button_font_size = $composer['button_font_size'];
448
- $global_button_font_color = $composer['button_font_color'];
449
- $global_button_font_weight = $composer['button_font_weight'];
450
- $global_button_background_color = $composer['button_background_color'];
451
-
452
- $global_block_background = $composer['block_background'];
453
-
454
- $info = Newsletter::instance()->get_options('info');
455
-
456
- // Just in case...
457
- if (!is_array($options)) {
458
- $options = array();
459
- }
460
-
461
- // This code filters the HTML to remove javascript and unsecure attributes and enable the
462
- // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
463
- add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
464
- $options = wp_kses_post_deep($options);
465
- remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
466
-
467
- $block_options = get_option('newsletter_main');
468
-
469
- $block = $this->get_block($block_id);
470
-
471
- if (!isset($context['type']))
472
- $context['type'] = '';
473
-
474
- // Block not found
475
- if (!$block) {
476
- if ($wrapper) {
477
- 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), '">';
478
- echo '<tr>';
479
- echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
480
- }
481
- echo $this->get_outlook_wrapper_open($width);
482
-
483
- echo '<p>Ops, this block type is not avalable.</p>';
484
-
485
- echo $this->get_outlook_wrapper_close();
486
-
487
- if ($wrapper) {
488
- echo '</td></tr></table>';
489
- }
490
- return;
491
- }
492
-
493
- $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
494
-
495
- $dir = is_rtl() ? 'rtl' : 'ltr';
496
- $align_left = is_rtl() ? 'right' : 'left';
497
- $align_right = is_rtl() ? 'left' : 'right';
498
-
499
- ob_start();
500
- $logger = $this->logger;
501
- include $block['dir'] . '/block.php';
502
- $content = trim(ob_get_clean());
503
-
504
- if (empty($content)) {
505
- return $out;
506
- }
507
-
508
- $common_defaults = array(
509
- 'block_padding_top' => 0,
510
- 'block_padding_bottom' => 0,
511
- 'block_padding_right' => 0,
512
- 'block_padding_left' => 0,
513
- 'block_background' => '',
514
- 'block_background_2' => '',
515
- 'block_width' => 600,
516
- 'block_align' => 'center'
517
- );
518
-
519
- $options = array_merge($common_defaults, $options);
520
-
521
- // Obsolete
522
- $content = str_replace('{width}', $width, $content);
523
-
524
- $content = $this->inline_css($content, true);
525
-
526
- // CSS driven by the block
527
- // Requited for the server side parsing and rendering
528
- $options['block_id'] = $block_id;
529
-
530
- $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
531
- $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
532
- $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
533
- $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
534
-
535
- $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
536
-
537
- // Internal TD wrapper
538
- $style = 'text-align: center; ';
539
- $style .= 'width: 100% !important; ';
540
- $style .= 'line-height: normal !important; ';
541
- $style .= 'letter-spacing: normal; ';
542
- $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
543
- $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
544
- $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
545
- $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
546
- $style .= 'background-color: ' . $block_background . ';';
547
-
548
- if (isset($options['block_background_gradient'])) {
549
- $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
550
- }
551
-
552
- $data = $this->options_encode($options);
553
- // First time block creation wrapper
554
- if ($wrapper) {
555
- 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";
556
- echo "<tr>";
557
- echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
558
- }
559
-
560
- // Container that fixes the width and makes the block responsive
561
- echo $this->get_outlook_wrapper_open($options['block_width']);
562
-
563
- 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";
564
- echo "<tr>";
565
- echo '<td align="', esc_attr($options['block_align']), '" style="', esc_attr($style), '" bgcolor="', esc_attr($block_background), '" width="100%">';
566
-
567
- //echo "<!-- block generated content -->\n";
568
- echo trim($content);
569
- //echo "\n<!-- /block generated content -->\n";
570
-
571
- echo "</td></tr></table>";
572
- echo $this->get_outlook_wrapper_close();
573
-
574
- // First time block creation wrapper
575
- if ($wrapper) {
576
- echo "</td></tr></table>";
577
- }
578
-
579
- return $out;
580
- }
581
-
582
- /**
583
- * Ajax call to render a block with a new set of options after the settings popup
584
- * has been saved.
585
- *
586
- * @param type $block_id
587
- * @param type $wrapper
588
- */
589
- function tnpc_render_callback() {
590
- if (!check_ajax_referer('save')) {
591
- $this->dienow('Expired request');
592
- }
593
-
594
- $block_id = $_POST['id'];
595
- $wrapper = isset($_POST['full']);
596
- $options = $this->restore_options_from_request();
597
-
598
- $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
599
- wp_die();
600
- }
601
-
602
- function hook_wp_ajax_tnpc_regenerate_email() {
603
-
604
- $content = stripslashes($_POST['content']);
605
- $global_options = $_POST['composer'];
606
-
607
- $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
608
-
609
- wp_send_json_success([
610
- 'content' => $regenerated_content,
611
- 'message' => __('Successfully updated', 'newsletter')
612
- ]);
613
- }
614
-
615
- private function regenerate_email_blocks($content, $global_options) {
616
-
617
- $raw_block_options = $this->extract_encoded_blocks_options($content);
618
-
619
- $regenerated_content = '';
620
-
621
- foreach ($raw_block_options as $raw_block_option) {
622
-
623
- /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
624
- $block_options = $this->options_decode( $a ); */
625
-
626
- $block_options = $this->options_decode($raw_block_option);
627
-
628
- $block = $this->get_block($block_options['block_id']);
629
- if (!$block) {
630
- $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
631
- }
632
-
633
- ob_start();
634
- $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
635
- $block_html = ob_get_clean();
636
-
637
- $regenerated_content .= $block_html;
638
- }
639
-
640
- return $regenerated_content;
641
- }
642
-
643
- /**
644
- * @param string $html_email_content Email html content
645
- *
646
- * @return string[] Encoded options of email blocks
647
- */
648
- private function extract_encoded_blocks_options($html_email_content) {
649
-
650
- preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
651
-
652
- return $raw_block_options[1];
653
- }
654
-
655
- function tnpc_preview_callback() {
656
- $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
657
-
658
- if (empty($email)) {
659
- echo 'Wrong email identifier';
660
- return;
661
- }
662
-
663
- echo $email['message'];
664
-
665
- wp_die();
666
- }
667
-
668
- function tnpc_css_callback() {
669
- include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
670
- wp_die();
671
- }
672
-
673
- /** Returns the correct admin page to edit the newsletter with the correct editor. */
674
- function get_editor_url($email_id, $editor_type) {
675
- switch ($editor_type) {
676
- case NewsletterEmails::EDITOR_COMPOSER:
677
- return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
678
- case NewsletterEmails::EDITOR_HTML:
679
- return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
680
- case NewsletterEmails::EDITOR_TINYMCE:
681
- return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
682
- }
683
- }
684
-
685
- /**
686
- * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
687
- * or the targeting page (it depends on newsletter status).
688
- *
689
- * @param TNP_Email $email
690
- */
691
- function get_edit_button($email, $only_icon = false) {
692
-
693
- $editor_type = $this->get_editor_type($email);
694
- if ($email->status == 'new') {
695
- $edit_url = $this->get_editor_url($email->id, $editor_type);
696
- } else {
697
- $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
698
- }
699
- switch ($editor_type) {
700
- case NewsletterEmails::EDITOR_COMPOSER:
701
- $icon_class = 'th-large';
702
- break;
703
- case NewsletterEmails::EDITOR_HTML:
704
- $icon_class = 'code';
705
- break;
706
- default:
707
- $icon_class = 'edit';
708
- break;
709
- }
710
- if ($only_icon) {
711
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
712
- '<i class="fas fa-' . $icon_class . '"></i></a>';
713
- } else {
714
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
715
- '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
716
- }
717
- }
718
-
719
- /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
720
- function get_editor_type($email) {
721
- $email = (object) $email;
722
- $editor_type = $email->editor;
723
-
724
- // Backward compatibility
725
- $email_options = maybe_unserialize($email->options);
726
- if (isset($email_options['composer'])) {
727
- $editor_type = NewsletterEmails::EDITOR_COMPOSER;
728
- }
729
- // End backward compatibility
730
-
731
- return $editor_type;
732
- }
733
-
734
- /**
735
- *
736
- * @param type $action
737
- * @param type $user
738
- * @param type $email
739
- * @return type
740
- * @global wpdb $wpdb
741
- */
742
- function hook_newsletter_action($action, $user, $email) {
743
- global $wpdb;
744
-
745
- switch ($action) {
746
- case 'v':
747
- case 'view':
748
- $id = $_GET['id'];
749
- if ($id == 'last') {
750
- $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");
751
- } else {
752
- $email = $this->get_email($_GET['id']);
753
- }
754
- if (empty($email)) {
755
- header("HTTP/1.0 404 Not Found");
756
- die('Email not found');
757
- }
758
-
759
- if (!Newsletter::instance()->is_allowed()) {
760
-
761
- if ($email->status == 'new') {
762
- header("HTTP/1.0 404 Not Found");
763
- die('Not sent yet');
764
- }
765
-
766
- if ($email->private == 1) {
767
- if (!$user) {
768
- header("HTTP/1.0 404 Not Found");
769
- die('No available for online view');
770
- }
771
- $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));
772
- if (!$sent) {
773
- header("HTTP/1.0 404 Not Found");
774
- die('No available for online view');
775
- }
776
- }
777
- }
778
-
779
-
780
- header('Content-Type: text/html;charset=UTF-8');
781
- header('X-Robots-Tag: noindex,nofollow,noarchive');
782
- header('Cache-Control: no-cache,no-store,private');
783
-
784
- echo $this->replace($email->message, $user, $email);
785
-
786
- die();
787
- break;
788
-
789
- case 'emails-css':
790
- $email_id = (int) $_GET['id'];
791
-
792
- $body = Newsletter::instance()->get_email_field($email_id, 'message');
793
-
794
- $x = strpos($body, '<style');
795
- if ($x === false)
796
- return;
797
-
798
- $x = strpos($body, '>', $x);
799
- $y = strpos($body, '</style>');
800
-
801
- header('Content-Type: text/css;charset=UTF-8');
802
-
803
- echo substr($body, $x + 1, $y - $x - 1);
804
-
805
- die();
806
- break;
807
-
808
- case 'emails-composer-css':
809
- header('Cache: no-cache');
810
- header('Content-Type: text/css');
811
- echo $this->get_composer_css();
812
- die();
813
- break;
814
-
815
- case 'emails-preview':
816
- if (!Newsletter::instance()->is_allowed()) {
817
- die('Not enough privileges');
818
- }
819
-
820
- if (!check_admin_referer('view')) {
821
- die();
822
- }
823
-
824
- $theme_id = $_GET['id'];
825
- $theme = $this->themes->get_theme($theme_id);
826
-
827
- // Used by theme code
828
- $theme_options = $this->themes->get_options($theme_id);
829
-
830
- $theme_url = $theme['url'];
831
-
832
- header('Content-Type: text/html;charset=UTF-8');
833
-
834
- include $theme['dir'] . '/theme.php';
835
-
836
- die();
837
- break;
838
-
839
- case 'emails-preview-text':
840
- header('Content-Type: text/plain;charset=UTF-8');
841
- if (!Newsletter::instance()->is_allowed()) {
842
- die('Not enough privileges');
843
- }
844
-
845
- if (!check_admin_referer('view')) {
846
- die();
847
- }
848
-
849
- // Used by theme code
850
- $theme_options = $this->get_current_theme_options();
851
-
852
- $file = include $theme['dir'] . '/theme-text.php';
853
-
854
- if (is_file($file)) {
855
- include $file;
856
- }
857
-
858
- die();
859
- break;
860
-
861
- case 'emails-create':
862
- // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
863
- // excerpt, thumbnail are extracted.
864
- if (!Newsletter::instance()->is_allowed()) {
865
- die('Not enough privileges');
866
- }
867
-
868
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
869
- $controls = new NewsletterControls();
870
-
871
- if (!$controls->is_action('create')) {
872
- die('Wrong call');
873
- }
874
-
875
- $theme_id = $controls->data['id'];
876
- $theme = $this->themes->get_theme($theme_id);
877
-
878
- if (!$theme) {
879
- die('invalid theme');
880
- }
881
-
882
- $this->themes->save_options($theme_id, $controls->data);
883
-
884
- $email = array();
885
- $email['status'] = 'new';
886
- $email['subject'] = ''; //__('Here the email subject', 'newsletter');
887
- $email['track'] = Newsletter::instance()->options['track'];
888
- $email['send_on'] = time();
889
- $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
890
- $email['type'] = 'message';
891
-
892
- $theme_options = $this->themes->get_options($theme_id);
893
-
894
- $theme_url = $theme['url'];
895
- $theme_subject = '';
896
-
897
- ob_start();
898
- include $theme['dir'] . '/theme.php';
899
- $email['message'] = ob_get_clean();
900
-
901
- if (!empty($theme_subject)) {
902
- $email['subject'] = $theme_subject;
903
- }
904
-
905
- if (file_exists($theme['dir'] . '/theme-text.php')) {
906
- ob_start();
907
- include $theme['dir'] . '/theme-text.php';
908
- $email['message_text'] = ob_get_clean();
909
- } else {
910
- $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
911
- }
912
-
913
- $email = $this->save_email($email);
914
-
915
- $edit_url = $this->get_editor_url($email->id, $email->editor);
916
-
917
- header('Location: ' . $edit_url);
918
-
919
- die();
920
- break;
921
- }
922
- }
923
-
924
- function admin_menu() {
925
- $this->add_menu_page('index', 'Newsletters');
926
- $this->add_admin_page('list', 'Email List');
927
- $this->add_admin_page('new', 'Email New');
928
- $this->add_admin_page('edit', 'Email Edit');
929
- $this->add_admin_page('theme', 'Email Themes');
930
- $this->add_admin_page('composer', 'The Composer');
931
- $this->add_admin_page('editorhtml', 'HTML Editor');
932
- $this->add_admin_page('editortinymce', 'TinyMCE Editor');
933
- }
934
-
935
- /**
936
- * Builds a block data structure starting from the folder containing the block
937
- * files.
938
- *
939
- * @param string $dir
940
- * @return array | WP_Error
941
- */
942
- function build_block($dir) {
943
- $dir = realpath($dir);
944
- $dir = wp_normalize_path($dir);
945
- $full_file = $dir . '/block.php';
946
- if (!is_file($full_file)) {
947
- return new WP_Error('1', 'Missing block.php file in ' . $dir);
948
- }
949
-
950
- $wp_content_dir = wp_normalize_path(realpath(WP_CONTENT_DIR));
951
-
952
- $relative_dir = substr($dir, strlen($wp_content_dir));
953
- $file = basename($dir);
954
-
955
- $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
956
- $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
957
- $data = array_merge($defaults, $data);
958
-
959
- if (is_file($dir . '/icon.png')) {
960
- $data['icon'] = content_url($relative_dir . '/icon.png');
961
- }
962
-
963
- $data['id'] = sanitize_key($file);
964
-
965
- // Absolute path of the block files
966
- $data['dir'] = $dir;
967
- $data['url'] = content_url($relative_dir);
968
-
969
- return $data;
970
- }
971
-
972
- /**
973
- *
974
- * @param type $dir
975
- * @return type
976
- */
977
- function scan_blocks_dir($dir) {
978
- $dir = realpath($dir);
979
- if (!$dir) {
980
- return [];
981
- }
982
- $dir = wp_normalize_path($dir);
983
-
984
- $list = [];
985
- $handle = opendir($dir);
986
- while ($file = readdir($handle)) {
987
-
988
- $data = $this->build_block($dir . '/' . $file);
989
-
990
- if (is_wp_error($data)) {
991
- $this->logger->error($data);
992
- continue;
993
- }
994
- $list[$data['id']] = $data;
995
- }
996
- closedir($handle);
997
- return $list;
998
- }
999
-
1000
- /**
1001
- * Array of arrays with every registered block and legacy block converted to the new
1002
- * format.
1003
- *
1004
- * @return array
1005
- */
1006
- function get_blocks() {
1007
-
1008
- if (!is_null($this->blocks)) {
1009
- return $this->blocks;
1010
- }
1011
-
1012
- $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1013
-
1014
- $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1015
-
1016
- $this->blocks = array_merge($extended, $this->blocks);
1017
-
1018
- $dirs = apply_filters('newsletter_blocks_dir', array());
1019
-
1020
- //$this->logger->debug('Block dirs:');
1021
- //$this->logger->debug($dirs);
1022
-
1023
- foreach ($dirs as $dir) {
1024
- $list = $this->scan_blocks_dir($dir);
1025
- $this->blocks = array_merge($list, $this->blocks);
1026
- }
1027
-
1028
- do_action('newsletter_register_blocks');
1029
-
1030
- foreach (TNP_Composer::$block_dirs as $dir) {
1031
- $block = $this->build_block($dir);
1032
- if (is_wp_error($block)) {
1033
- $this->logger->error($block);
1034
- continue;
1035
- }
1036
- if (!isset($this->blocks[$block['id']])) {
1037
- $this->blocks[$block['id']] = $block;
1038
- } else {
1039
- $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1040
- }
1041
- }
1042
-
1043
- $this->blocks = array_reverse($this->blocks);
1044
- return $this->blocks;
1045
- }
1046
-
1047
- /**
1048
- * Return a single block (associative array) checking for legacy ID as well.
1049
- *
1050
- * @param string $id
1051
- * @return array
1052
- */
1053
- function get_block($id) {
1054
- switch ($id) {
1055
- case 'content-03-text.block':
1056
- $id = 'text';
1057
- break;
1058
- case 'footer-03-social.block':
1059
- $id = 'social';
1060
- break;
1061
- case 'footer-02-canspam.block':
1062
- $id = 'canspam';
1063
- break;
1064
- case 'content-05-image.block':
1065
- $id = 'image';
1066
- break;
1067
- case 'header-01-header.block':
1068
- $id = 'header';
1069
- break;
1070
- case 'footer-01-footer.block':
1071
- $id = 'footer';
1072
- break;
1073
- case 'content-02-heading.block':
1074
- $id = 'heading';
1075
- break;
1076
- case 'content-07-twocols.block':
1077
- case 'content-06-posts.block':
1078
- $id = 'posts';
1079
- break;
1080
- case 'content-04-cta.block':
1081
- $id = 'cta';
1082
- break;
1083
- case 'content-01-hero.block':
1084
- $id = 'hero';
1085
- break;
1086
- // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1087
- // break;
1088
- }
1089
-
1090
- // Conversion for old full path ID
1091
- $id = sanitize_key(basename($id));
1092
-
1093
- // TODO: Correct id for compatibility
1094
- $blocks = $this->get_blocks();
1095
- if (!isset($blocks[$id])) {
1096
- return null;
1097
- }
1098
- return $blocks[$id];
1099
- }
1100
-
1101
- function scan_presets_dir($dir = null) {
1102
-
1103
- if (is_null($dir)) {
1104
- $dir = __DIR__ . '/presets';
1105
- }
1106
-
1107
- if (!is_dir($dir)) {
1108
- return array();
1109
- }
1110
-
1111
- $handle = opendir($dir);
1112
- $list = array();
1113
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1114
- while ($file = readdir($handle)) {
1115
-
1116
- if ($file == '.' || $file == '..')
1117
- continue;
1118
-
1119
- // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1120
- $preset_id = sanitize_key($file);
1121
-
1122
- $full_file = $dir . '/' . $file . '/preset.json';
1123
-
1124
- if (!is_file($full_file)) {
1125
- continue;
1126
- }
1127
-
1128
- $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1129
-
1130
- $list[$preset_id] = $icon;
1131
- }
1132
- closedir($handle);
1133
- return $list;
1134
- }
1135
-
1136
- function get_preset_from_file($id, $dir = null) {
1137
-
1138
- if (is_null($dir)) {
1139
- $dir = __DIR__ . '/presets';
1140
- }
1141
-
1142
- $id = $this->sanitize_file_name($id);
1143
-
1144
- if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1145
- return array();
1146
- }
1147
-
1148
- $json_content = file_get_contents("$dir/$id/preset.json");
1149
- $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1150
- $json = json_decode($json_content);
1151
- $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1152
-
1153
- return $json;
1154
- }
1155
-
1156
- function get_composer_css() {
1157
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1158
- $blocks = $this->get_blocks();
1159
- foreach ($blocks as $block) {
1160
- if (!file_exists($block['dir'] . '/style.css')) {
1161
- continue;
1162
- }
1163
- $css .= "\n\n";
1164
- $css .= "/* " . $block['name'] . " */\n";
1165
- $css .= file_get_contents($block['dir'] . '/style.css');
1166
- }
1167
- return $css;
1168
- }
1169
-
1170
- function get_composer_backend_css() {
1171
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1172
- $css .= "\n\n";
1173
- $css .= $this->get_composer_css();
1174
- return $css;
1175
- }
1176
-
1177
- /**
1178
- * Send an email to the test subscribers.
1179
- *
1180
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1181
- * @param NewsletterControls $controls
1182
- */
1183
- function send_test_email($email, $controls) {
1184
- if (!$email) {
1185
- $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1186
- return;
1187
- }
1188
-
1189
- $original_subject = $email->subject;
1190
- $this->set_test_subject_to($email);
1191
-
1192
- $users = NewsletterUsers::instance()->get_test_users();
1193
- if (count($users) == 0) {
1194
- $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1195
- '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1196
- __('Read more', 'newsletter') . '</strong></a>.';
1197
- } else {
1198
- $r = Newsletter::instance()->send($email, $users, true);
1199
- $emails = array();
1200
- foreach ($users as $user) {
1201
- $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1202
- }
1203
- if (is_wp_error($r)) {
1204
- $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1205
- $controls->errors .= __('Test subscribers:', 'newsletter');
1206
- $controls->errors .= ' ' . implode(', ', $emails);
1207
- $controls->errors .= '<br>';
1208
- $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1209
- $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1210
- } else {
1211
- $controls->messages = __('Test subscribers:', 'newsletter');
1212
-
1213
- $controls->messages .= ' ' . implode(', ', $emails);
1214
- $controls->messages .= '.<br>';
1215
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1216
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1217
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1218
- }
1219
- }
1220
- $email->subject = $original_subject;
1221
- }
1222
-
1223
- /**
1224
- * Send an email to the test subscribers.
1225
- *
1226
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1227
- * @param string $email_address
1228
- *
1229
- * @throws Exception
1230
- */
1231
- function send_test_newsletter_to_email_address($email, $email_address) {
1232
-
1233
- if (!$email) {
1234
- throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1235
- }
1236
-
1237
- $this->set_test_subject_to($email);
1238
-
1239
- $dummy_subscriber = $this->make_dummy_subscriber();
1240
- $dummy_subscriber->email = $email_address;
1241
-
1242
- $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1243
-
1244
- $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1245
-
1246
- if (is_wp_error($result)) {
1247
- $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1248
- $error_message .= __('Test subscribers:', 'newsletter');
1249
- $error_message .= ' ' . $email;
1250
- $error_message .= '<br>';
1251
- $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1252
- $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1253
- throw new Exception($error_message);
1254
- }
1255
-
1256
- $messages = __('Test subscribers:', 'newsletter');
1257
-
1258
- $messages .= ' ' . $email;
1259
- $messages .= '.<br>';
1260
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1261
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1262
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1263
-
1264
- return $messages;
1265
- }
1266
-
1267
- private function set_test_subject_to($email) {
1268
- if ($email->subject == '') {
1269
- $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1270
- } else {
1271
- $email->subject = $email->subject . ' (TEST)';
1272
- }
1273
- }
1274
-
1275
- private function make_dummy_subscriber() {
1276
- $dummy_user = new TNP_User();
1277
- $dummy_user->id = 0;
1278
- $dummy_user->email = 'john.doe@example.org';
1279
- $dummy_user->name = 'John';
1280
- $dummy_user->surname = 'Doe';
1281
- $dummy_user->sex = 'n';
1282
- $dummy_user->language = '';
1283
- $dummy_user->ip = '';
1284
-
1285
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1286
- $profile_key = "profile_$i";
1287
- $dummy_user->$profile_key = '';
1288
- }
1289
-
1290
- return $dummy_user;
1291
- }
1292
-
1293
- function restore_options_from_request() {
1294
-
1295
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1296
- $controls = new NewsletterControls();
1297
- $options = $controls->data;
1298
-
1299
- if (isset($_POST['options']) && is_array($_POST['options'])) {
1300
- // Get all block options
1301
- //$options = stripslashes_deep($_POST['options']);
1302
- // Deserialize inline edits when
1303
- // render is preformed on saving block options
1304
- if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1305
- $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1306
- }
1307
-
1308
- // Restore inline edits from data-json
1309
- // coming from inline editing
1310
- // and merge with current inline edit
1311
- if (isset($_POST['encoded_options'])) {
1312
- $decoded_options = $this->options_decode($_POST['encoded_options']);
1313
-
1314
- $to_merge_inline_edits = [];
1315
-
1316
- if (isset($decoded_options['inline_edits'])) {
1317
- foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1318
- $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1319
- }
1320
- }
1321
-
1322
- //Overwrite with new edited content
1323
- if (isset($options['inline_edits'])) {
1324
- foreach ($options['inline_edits'] as $inline_edit) {
1325
- $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1326
- }
1327
- }
1328
-
1329
- $options['inline_edits'] = array_values($to_merge_inline_edits);
1330
- $options = array_merge($decoded_options, $options);
1331
- }
1332
-
1333
- return $options;
1334
- }
1335
-
1336
- return array();
1337
- }
1338
-
1339
- public function hook_wp_ajax_tnpc_delete_preset() {
1340
-
1341
- if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1342
- wp_send_json_error('Expired request');
1343
- }
1344
-
1345
- $preset_id = (int) $_REQUEST['presetId'];
1346
-
1347
- $newsletter = Newsletter::instance();
1348
-
1349
- if ($preset_id > 0) {
1350
- $preset = $newsletter->get_email($preset_id);
1351
-
1352
- if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1353
- Newsletter::instance()->delete_email($preset_id);
1354
- wp_send_json_success();
1355
- } else {
1356
- wp_send_json_error(__('Is not a preset!', 'newsletter'));
1357
- }
1358
- } else {
1359
- wp_send_json_error();
1360
- }
1361
- }
1362
-
1363
- }
1364
-
1365
- 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
+ $composer = [];
350
+ foreach ($email->options as $k=>$v) {
351
+ if (strpos($k, 'composer_') !== 0) continue;
352
+ $composer[substr($k, 9)] = $v;
353
+ }
354
+
355
+
356
+ preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
357
+
358
+ $result = '';
359
+ $subject = '';
360
+
361
+ foreach ($matches[1] as $match) {
362
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
363
+ $options = $this->options_decode($a);
364
+
365
+ $block = $this->get_block($options['block_id']);
366
+ if (!$block) {
367
+ $this->logger->debug('Unable to load the block ' . $options['block_id']);
368
+ //continue;
369
+ }
370
+
371
+ ob_start();
372
+ $out = $this->render_block($options['block_id'], true, $options, $context, $composer);
373
+ if (is_array($out)) {
374
+ if ($out['return_empty_message'] || $out['stop']) {
375
+ return false;
376
+ }
377
+ if ($out['skip']) {
378
+ continue;
379
+ }
380
+ if (empty($subject) && !empty($out['subject'])) {
381
+ $subject = strip_tags($out['subject']);
382
+ }
383
+ }
384
+ $block_html = ob_get_clean();
385
+ $result .= $block_html;
386
+ }
387
+
388
+ $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
389
+ $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
390
+ $email->subject = $subject;
391
+ return true;
392
+ }
393
+
394
+ function remove_block_data($text) {
395
+ // TODO: Lavorare!
396
+ return $text;
397
+ }
398
+
399
+ static function get_outlook_wrapper_open($width = 600) {
400
+ 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]-->';
401
+ }
402
+
403
+ static function get_outlook_wrapper_close() {
404
+ return "<!--[if mso | IE]></td></tr></table><![endif]-->";
405
+ }
406
+
407
+ function hook_safe_style_css($rules) {
408
+ $rules[] = 'display';
409
+ return $rules;
410
+ }
411
+
412
+ /**
413
+ * Renders a block identified by its id, using the block options and adding a wrapper
414
+ * if required (for the first block rendering).
415
+ *
416
+ * @param string $block_id
417
+ * @param boolean $wrapper
418
+ * @param array $options
419
+ * @param array $context
420
+ * @param array $composer
421
+ */
422
+ function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
423
+ static $kses_style_filter = false;
424
+ include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
425
+
426
+ //Remove 'options_composer_' prefix
427
+ $composer_defaults = [];
428
+ foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
429
+ $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
430
+ }
431
+ $composer = array_merge($composer_defaults, $composer);
432
+
433
+ $width = 600;
434
+ $font_family = 'Helvetica, Arial, sans-serif';
435
+
436
+ $global_title_font_family = $composer['title_font_family'];
437
+ $global_title_font_size = $composer['title_font_size'];
438
+ $global_title_font_color = $composer['title_font_color'];
439
+ $global_title_font_weight = $composer['title_font_weight'];
440
+
441
+ $global_text_font_family = $composer['text_font_family'];
442
+ $global_text_font_size = $composer['text_font_size'];
443
+ $global_text_font_color = $composer['text_font_color'];
444
+ $global_text_font_weight = $composer['text_font_weight'];
445
+
446
+ $global_button_font_family = $composer['button_font_family'];
447
+ $global_button_font_size = $composer['button_font_size'];
448
+ $global_button_font_color = $composer['button_font_color'];
449
+ $global_button_font_weight = $composer['button_font_weight'];
450
+ $global_button_background_color = $composer['button_background_color'];
451
+
452
+ $global_block_background = $composer['block_background'];
453
+
454
+ $info = Newsletter::instance()->get_options('info');
455
+
456
+ // Just in case...
457
+ if (!is_array($options)) {
458
+ $options = array();
459
+ }
460
+
461
+ // This code filters the HTML to remove javascript and unsecure attributes and enable the
462
+ // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
463
+ add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
464
+ $options = wp_kses_post_deep($options);
465
+ remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
466
+
467
+ $block_options = get_option('newsletter_main');
468
+
469
+ $block = $this->get_block($block_id);
470
+
471
+ if (!isset($context['type']))
472
+ $context['type'] = '';
473
+
474
+ // Block not found
475
+ if (!$block) {
476
+ if ($wrapper) {
477
+ 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), '">';
478
+ echo '<tr>';
479
+ echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
480
+ }
481
+ echo $this->get_outlook_wrapper_open($width);
482
+
483
+ echo '<p>Ops, this block type is not avalable.</p>';
484
+
485
+ echo $this->get_outlook_wrapper_close();
486
+
487
+ if ($wrapper) {
488
+ echo '</td></tr></table>';
489
+ }
490
+ return;
491
+ }
492
+
493
+ $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
494
+
495
+ $dir = is_rtl() ? 'rtl' : 'ltr';
496
+ $align_left = is_rtl() ? 'right' : 'left';
497
+ $align_right = is_rtl() ? 'left' : 'right';
498
+
499
+ ob_start();
500
+ $logger = $this->logger;
501
+ include $block['dir'] . '/block.php';
502
+ $content = trim(ob_get_clean());
503
+
504
+ if (empty($content)) {
505
+ return $out;
506
+ }
507
+
508
+ $common_defaults = array(
509
+ 'block_padding_top' => 0,
510
+ 'block_padding_bottom' => 0,
511
+ 'block_padding_right' => 0,
512
+ 'block_padding_left' => 0,
513
+ 'block_background' => '',
514
+ 'block_background_2' => '',
515
+ 'block_width' => 600,
516
+ 'block_align' => 'center'
517
+ );
518
+
519
+ $options = array_merge($common_defaults, $options);
520
+
521
+ // Obsolete
522
+ $content = str_replace('{width}', $width, $content);
523
+
524
+ $content = $this->inline_css($content, true);
525
+
526
+ // CSS driven by the block
527
+ // Requited for the server side parsing and rendering
528
+ $options['block_id'] = $block_id;
529
+
530
+ $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
531
+ $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
532
+ $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
533
+ $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
534
+
535
+ $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
536
+
537
+ // Internal TD wrapper
538
+ $style = 'text-align: center; ';
539
+ $style .= 'width: 100% !important; ';
540
+ $style .= 'line-height: normal !important; ';
541
+ $style .= 'letter-spacing: normal; ';
542
+ $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
543
+ $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
544
+ $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
545
+ $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
546
+ $style .= 'background-color: ' . $block_background . ';';
547
+
548
+ if (isset($options['block_background_gradient'])) {
549
+ $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
550
+ }
551
+
552
+ $data = $this->options_encode($options);
553
+ // First time block creation wrapper
554
+ if ($wrapper) {
555
+ 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";
556
+ echo "<tr>";
557
+ echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
558
+ }
559
+
560
+ // Container that fixes the width and makes the block responsive
561
+ echo $this->get_outlook_wrapper_open($options['block_width']);
562
+
563
+ 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";
564
+ echo "<tr>";
565
+ echo '<td align="', esc_attr($options['block_align']), '" style="', esc_attr($style), '" bgcolor="', esc_attr($block_background), '" width="100%">';
566
+
567
+ //echo "<!-- block generated content -->\n";
568
+ echo trim($content);
569
+ //echo "\n<!-- /block generated content -->\n";
570
+
571
+ echo "</td></tr></table>";
572
+ echo $this->get_outlook_wrapper_close();
573
+
574
+ // First time block creation wrapper
575
+ if ($wrapper) {
576
+ echo "</td></tr></table>";
577
+ }
578
+
579
+ return $out;
580
+ }
581
+
582
+ /**
583
+ * Ajax call to render a block with a new set of options after the settings popup
584
+ * has been saved.
585
+ *
586
+ * @param type $block_id
587
+ * @param type $wrapper
588
+ */
589
+ function tnpc_render_callback() {
590
+ if (!check_ajax_referer('save')) {
591
+ $this->dienow('Expired request');
592
+ }
593
+
594
+ $block_id = $_POST['id'];
595
+ $wrapper = isset($_POST['full']);
596
+ $options = $this->restore_options_from_request();
597
+
598
+ $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
599
+ wp_die();
600
+ }
601
+
602
+ function hook_wp_ajax_tnpc_regenerate_email() {
603
+
604
+ $content = stripslashes($_POST['content']);
605
+ $global_options = $_POST['composer'];
606
+
607
+ $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
608
+
609
+ wp_send_json_success([
610
+ 'content' => $regenerated_content,
611
+ 'message' => __('Successfully updated', 'newsletter')
612
+ ]);
613
+ }
614
+
615
+ private function regenerate_email_blocks($content, $global_options) {
616
+
617
+ $raw_block_options = $this->extract_encoded_blocks_options($content);
618
+
619
+ $regenerated_content = '';
620
+
621
+ foreach ($raw_block_options as $raw_block_option) {
622
+
623
+ /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
624
+ $block_options = $this->options_decode( $a ); */
625
+
626
+ $block_options = $this->options_decode($raw_block_option);
627
+
628
+ $block = $this->get_block($block_options['block_id']);
629
+ if (!$block) {
630
+ $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
631
+ }
632
+
633
+ ob_start();
634
+ $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
635
+ $block_html = ob_get_clean();
636
+
637
+ $regenerated_content .= $block_html;
638
+ }
639
+
640
+ return $regenerated_content;
641
+ }
642
+
643
+ /**
644
+ * @param string $html_email_content Email html content
645
+ *
646
+ * @return string[] Encoded options of email blocks
647
+ */
648
+ private function extract_encoded_blocks_options($html_email_content) {
649
+
650
+ preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
651
+
652
+ return $raw_block_options[1];
653
+ }
654
+
655
+ function tnpc_preview_callback() {
656
+ $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
657
+
658
+ if (empty($email)) {
659
+ echo 'Wrong email identifier';
660
+ return;
661
+ }
662
+
663
+ echo $email['message'];
664
+
665
+ wp_die();
666
+ }
667
+
668
+ function tnpc_css_callback() {
669
+ include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
670
+ wp_die();
671
+ }
672
+
673
+ /** Returns the correct admin page to edit the newsletter with the correct editor. */
674
+ function get_editor_url($email_id, $editor_type) {
675
+ switch ($editor_type) {
676
+ case NewsletterEmails::EDITOR_COMPOSER:
677
+ return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
678
+ case NewsletterEmails::EDITOR_HTML:
679
+ return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
680
+ case NewsletterEmails::EDITOR_TINYMCE:
681
+ return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
687
+ * or the targeting page (it depends on newsletter status).
688
+ *
689
+ * @param TNP_Email $email
690
+ */
691
+ function get_edit_button($email, $only_icon = false) {
692
+
693
+ $editor_type = $this->get_editor_type($email);
694
+ if ($email->status == 'new') {
695
+ $edit_url = $this->get_editor_url($email->id, $editor_type);
696
+ } else {
697
+ $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
698
+ }
699
+ switch ($editor_type) {
700
+ case NewsletterEmails::EDITOR_COMPOSER:
701
+ $icon_class = 'th-large';
702
+ break;
703
+ case NewsletterEmails::EDITOR_HTML:
704
+ $icon_class = 'code';
705
+ break;
706
+ default:
707
+ $icon_class = 'edit';
708
+ break;
709
+ }
710
+ if ($only_icon) {
711
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
712
+ '<i class="fas fa-' . $icon_class . '"></i></a>';
713
+ } else {
714
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
715
+ '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
716
+ }
717
+ }
718
+
719
+ /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
720
+ function get_editor_type($email) {
721
+ $email = (object) $email;
722
+ $editor_type = $email->editor;
723
+
724
+ // Backward compatibility
725
+ $email_options = maybe_unserialize($email->options);
726
+ if (isset($email_options['composer'])) {
727
+ $editor_type = NewsletterEmails::EDITOR_COMPOSER;
728
+ }
729
+ // End backward compatibility
730
+
731
+ return $editor_type;
732
+ }
733
+
734
+ /**
735
+ *
736
+ * @param type $action
737
+ * @param type $user
738
+ * @param type $email
739
+ * @return type
740
+ * @global wpdb $wpdb
741
+ */
742
+ function hook_newsletter_action($action, $user, $email) {
743
+ global $wpdb;
744
+
745
+ switch ($action) {
746
+ case 'v':
747
+ case 'view':
748
+ $id = $_GET['id'];
749
+ if ($id == 'last') {
750
+ $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");
751
+ } else {
752
+ $email = $this->get_email($_GET['id']);
753
+ }
754
+ if (empty($email)) {
755
+ header("HTTP/1.0 404 Not Found");
756
+ die('Email not found');
757
+ }
758
+
759
+ if (!Newsletter::instance()->is_allowed()) {
760
+
761
+ if ($email->status == 'new') {
762
+ header("HTTP/1.0 404 Not Found");
763
+ die('Not sent yet');
764
+ }
765
+
766
+ if ($email->private == 1) {
767
+ if (!$user) {
768
+ header("HTTP/1.0 404 Not Found");
769
+ die('No available for online view');
770
+ }
771
+ $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));
772
+ if (!$sent) {
773
+ header("HTTP/1.0 404 Not Found");
774
+ die('No available for online view');
775
+ }
776
+ }
777
+ }
778
+
779
+
780
+ header('Content-Type: text/html;charset=UTF-8');
781
+ header('X-Robots-Tag: noindex,nofollow,noarchive');
782
+ header('Cache-Control: no-cache,no-store,private');
783
+
784
+ echo $this->replace($email->message, $user, $email);
785
+
786
+ die();
787
+ break;
788
+
789
+ case 'emails-css':
790
+ $email_id = (int) $_GET['id'];
791
+
792
+ $body = Newsletter::instance()->get_email_field($email_id, 'message');
793
+
794
+ $x = strpos($body, '<style');
795
+ if ($x === false)
796
+ return;
797
+
798
+ $x = strpos($body, '>', $x);
799
+ $y = strpos($body, '</style>');
800
+
801
+ header('Content-Type: text/css;charset=UTF-8');
802
+
803
+ echo substr($body, $x + 1, $y - $x - 1);
804
+
805
+ die();
806
+ break;
807
+
808
+ case 'emails-composer-css':
809
+ header('Cache: no-cache');
810
+ header('Content-Type: text/css');
811
+ echo $this->get_composer_css();
812
+ die();
813
+ break;
814
+
815
+ case 'emails-preview':
816
+ if (!Newsletter::instance()->is_allowed()) {
817
+ die('Not enough privileges');
818
+ }
819
+
820
+ if (!check_admin_referer('view')) {
821
+ die();
822
+ }
823
+
824
+ $theme_id = $_GET['id'];
825
+ $theme = $this->themes->get_theme($theme_id);
826
+
827
+ // Used by theme code
828
+ $theme_options = $this->themes->get_options($theme_id);
829
+
830
+ $theme_url = $theme['url'];
831
+
832
+ header('Content-Type: text/html;charset=UTF-8');
833
+
834
+ include $theme['dir'] . '/theme.php';
835
+
836
+ die();
837
+ break;
838
+
839
+ case 'emails-preview-text':
840
+ header('Content-Type: text/plain;charset=UTF-8');
841
+ if (!Newsletter::instance()->is_allowed()) {
842
+ die('Not enough privileges');
843
+ }
844
+
845
+ if (!check_admin_referer('view')) {
846
+ die();
847
+ }
848
+
849
+ // Used by theme code
850
+ $theme_options = $this->get_current_theme_options();
851
+
852
+ $file = include $theme['dir'] . '/theme-text.php';
853
+
854
+ if (is_file($file)) {
855
+ include $file;
856
+ }
857
+
858
+ die();
859
+ break;
860
+
861
+ case 'emails-create':
862
+ // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
863
+ // excerpt, thumbnail are extracted.
864
+ if (!Newsletter::instance()->is_allowed()) {
865
+ die('Not enough privileges');
866
+ }
867
+
868
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
869
+ $controls = new NewsletterControls();
870
+
871
+ if (!$controls->is_action('create')) {
872
+ die('Wrong call');
873
+ }
874
+
875
+ $theme_id = $controls->data['id'];
876
+ $theme = $this->themes->get_theme($theme_id);
877
+
878
+ if (!$theme) {
879
+ die('invalid theme');
880
+ }
881
+
882
+ $this->themes->save_options($theme_id, $controls->data);
883
+
884
+ $email = array();
885
+ $email['status'] = 'new';
886
+ $email['subject'] = ''; //__('Here the email subject', 'newsletter');
887
+ $email['track'] = Newsletter::instance()->options['track'];
888
+ $email['send_on'] = time();
889
+ $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
890
+ $email['type'] = 'message';
891
+
892
+ $theme_options = $this->themes->get_options($theme_id);
893
+
894
+ $theme_url = $theme['url'];
895
+ $theme_subject = '';
896
+
897
+ ob_start();
898
+ include $theme['dir'] . '/theme.php';
899
+ $email['message'] = ob_get_clean();
900
+
901
+ if (!empty($theme_subject)) {
902
+ $email['subject'] = $theme_subject;
903
+ }
904
+
905
+ if (file_exists($theme['dir'] . '/theme-text.php')) {
906
+ ob_start();
907
+ include $theme['dir'] . '/theme-text.php';
908
+ $email['message_text'] = ob_get_clean();
909
+ } else {
910
+ $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
911
+ }
912
+
913
+ $email = $this->save_email($email);
914
+
915
+ $edit_url = $this->get_editor_url($email->id, $email->editor);
916
+
917
+ header('Location: ' . $edit_url);
918
+
919
+ die();
920
+ break;
921
+ }
922
+ }
923
+
924
+ function admin_menu() {
925
+ $this->add_menu_page('index', 'Newsletters');
926
+ $this->add_admin_page('list', 'Email List');
927
+ $this->add_admin_page('new', 'Email New');
928
+ $this->add_admin_page('edit', 'Email Edit');
929
+ $this->add_admin_page('theme', 'Email Themes');
930
+ $this->add_admin_page('composer', 'The Composer');
931
+ $this->add_admin_page('editorhtml', 'HTML Editor');
932
+ $this->add_admin_page('editortinymce', 'TinyMCE Editor');
933
+ }
934
+
935
+ /**
936
+ * Builds a block data structure starting from the folder containing the block
937
+ * files.
938
+ *
939
+ * @param string $dir
940
+ * @return array | WP_Error
941
+ */
942
+ function build_block($dir) {
943
+ $dir = realpath($dir);
944
+ $dir = wp_normalize_path($dir);
945
+ $full_file = $dir . '/block.php';
946
+ if (!is_file($full_file)) {
947
+ return new WP_Error('1', 'Missing block.php file in ' . $dir);
948
+ }
949
+
950
+ $wp_content_dir = wp_normalize_path(realpath(WP_CONTENT_DIR));
951
+
952
+ $relative_dir = substr($dir, strlen($wp_content_dir));
953
+ $file = basename($dir);
954
+
955
+ $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
956
+ $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
957
+ $data = array_merge($defaults, $data);
958
+
959
+ if (is_file($dir . '/icon.png')) {
960
+ $data['icon'] = content_url($relative_dir . '/icon.png');
961
+ }
962
+
963
+ $data['id'] = sanitize_key($file);
964
+
965
+ // Absolute path of the block files
966
+ $data['dir'] = $dir;
967
+ $data['url'] = content_url($relative_dir);
968
+
969
+ return $data;
970
+ }
971
+
972
+ /**
973
+ *
974
+ * @param type $dir
975
+ * @return type
976
+ */
977
+ function scan_blocks_dir($dir) {
978
+ $dir = realpath($dir);
979
+ if (!$dir) {
980
+ return [];
981
+ }
982
+ $dir = wp_normalize_path($dir);
983
+
984
+ $list = [];
985
+ $handle = opendir($dir);
986
+ while ($file = readdir($handle)) {
987
+
988
+ $data = $this->build_block($dir . '/' . $file);
989
+
990
+ if (is_wp_error($data)) {
991
+ $this->logger->error($data);
992
+ continue;
993
+ }
994
+ $list[$data['id']] = $data;
995
+ }
996
+ closedir($handle);
997
+ return $list;
998
+ }
999
+
1000
+ /**
1001
+ * Array of arrays with every registered block and legacy block converted to the new
1002
+ * format.
1003
+ *
1004
+ * @return array
1005
+ */
1006
+ function get_blocks() {
1007
+
1008
+ if (!is_null($this->blocks)) {
1009
+ return $this->blocks;
1010
+ }
1011
+
1012
+ $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1013
+
1014
+ $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1015
+
1016
+ $this->blocks = array_merge($extended, $this->blocks);
1017
+
1018
+ $dirs = apply_filters('newsletter_blocks_dir', array());
1019
+
1020
+ //$this->logger->debug('Block dirs:');
1021
+ //$this->logger->debug($dirs);
1022
+
1023
+ foreach ($dirs as $dir) {
1024
+ $list = $this->scan_blocks_dir($dir);
1025
+ $this->blocks = array_merge($list, $this->blocks);
1026
+ }
1027
+
1028
+ do_action('newsletter_register_blocks');
1029
+
1030
+ foreach (TNP_Composer::$block_dirs as $dir) {
1031
+ $block = $this->build_block($dir);
1032
+ if (is_wp_error($block)) {
1033
+ $this->logger->error($block);
1034
+ continue;
1035
+ }
1036
+ if (!isset($this->blocks[$block['id']])) {
1037
+ $this->blocks[$block['id']] = $block;
1038
+ } else {
1039
+ $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1040
+ }
1041
+ }
1042
+
1043
+ $this->blocks = array_reverse($this->blocks);
1044
+ return $this->blocks;
1045
+ }
1046
+
1047
+ /**
1048
+ * Return a single block (associative array) checking for legacy ID as well.
1049
+ *
1050
+ * @param string $id
1051
+ * @return array
1052
+ */
1053
+ function get_block($id) {
1054
+ switch ($id) {
1055
+ case 'content-03-text.block':
1056
+ $id = 'text';
1057
+ break;
1058
+ case 'footer-03-social.block':
1059
+ $id = 'social';
1060
+ break;
1061
+ case 'footer-02-canspam.block':
1062
+ $id = 'canspam';
1063
+ break;
1064
+ case 'content-05-image.block':
1065
+ $id = 'image';
1066
+ break;
1067
+ case 'header-01-header.block':
1068
+ $id = 'header';
1069
+ break;
1070
+ case 'footer-01-footer.block':
1071
+ $id = 'footer';
1072
+ break;
1073
+ case 'content-02-heading.block':
1074
+ $id = 'heading';
1075
+ break;
1076
+ case 'content-07-twocols.block':
1077
+ case 'content-06-posts.block':
1078
+ $id = 'posts';
1079
+ break;
1080
+ case 'content-04-cta.block':
1081
+ $id = 'cta';
1082
+ break;
1083
+ case 'content-01-hero.block':
1084
+ $id = 'hero';
1085
+ break;
1086
+ // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1087
+ // break;
1088
+ }
1089
+
1090
+ // Conversion for old full path ID
1091
+ $id = sanitize_key(basename($id));
1092
+
1093
+ // TODO: Correct id for compatibility
1094
+ $blocks = $this->get_blocks();
1095
+ if (!isset($blocks[$id])) {
1096
+ return null;
1097
+ }
1098
+ return $blocks[$id];
1099
+ }
1100
+
1101
+ function scan_presets_dir($dir = null) {
1102
+
1103
+ if (is_null($dir)) {
1104
+ $dir = __DIR__ . '/presets';
1105
+ }
1106
+
1107
+ if (!is_dir($dir)) {
1108
+ return array();
1109
+ }
1110
+
1111
+ $handle = opendir($dir);
1112
+ $list = array();
1113
+ $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1114
+ while ($file = readdir($handle)) {
1115
+
1116
+ if ($file == '.' || $file == '..')
1117
+ continue;
1118
+
1119
+ // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1120
+ $preset_id = sanitize_key($file);
1121
+
1122
+ $full_file = $dir . '/' . $file . '/preset.json';
1123
+
1124
+ if (!is_file($full_file)) {
1125
+ continue;
1126
+ }
1127
+
1128
+ $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1129
+
1130
+ $list[$preset_id] = $icon;
1131
+ }
1132
+ closedir($handle);
1133
+ return $list;
1134
+ }
1135
+
1136
+ function get_preset_from_file($id, $dir = null) {
1137
+
1138
+ if (is_null($dir)) {
1139
+ $dir = __DIR__ . '/presets';
1140
+ }
1141
+
1142
+ $id = $this->sanitize_file_name($id);
1143
+
1144
+ if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1145
+ return array();
1146
+ }
1147
+
1148
+ $json_content = file_get_contents("$dir/$id/preset.json");
1149
+ $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1150
+ $json = json_decode($json_content);
1151
+ $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1152
+
1153
+ return $json;
1154
+ }
1155
+
1156
+ function get_composer_css() {
1157
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1158
+ $blocks = $this->get_blocks();
1159
+ foreach ($blocks as $block) {
1160
+ if (!file_exists($block['dir'] . '/style.css')) {
1161
+ continue;
1162
+ }
1163
+ $css .= "\n\n";
1164
+ $css .= "/* " . $block['name'] . " */\n";
1165
+ $css .= file_get_contents($block['dir'] . '/style.css');
1166
+ }
1167
+ return $css;
1168
+ }
1169
+
1170
+ function get_composer_backend_css() {
1171
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1172
+ $css .= "\n\n";
1173
+ $css .= $this->get_composer_css();
1174
+ return $css;
1175
+ }
1176
+
1177
+ /**
1178
+ * Send an email to the test subscribers.
1179
+ *
1180
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1181
+ * @param NewsletterControls $controls
1182
+ */
1183
+ function send_test_email($email, $controls) {
1184
+ if (!$email) {
1185
+ $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1186
+ return;
1187
+ }
1188
+
1189
+ $original_subject = $email->subject;
1190
+ $this->set_test_subject_to($email);
1191
+
1192
+ $users = NewsletterUsers::instance()->get_test_users();
1193
+ if (count($users) == 0) {
1194
+ $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1195
+ '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1196
+ __('Read more', 'newsletter') . '</strong></a>.';
1197
+ } else {
1198
+ $r = Newsletter::instance()->send($email, $users, true);
1199
+ $emails = array();
1200
+ foreach ($users as $user) {
1201
+ $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1202
+ }
1203
+ if (is_wp_error($r)) {
1204
+ $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1205
+ $controls->errors .= __('Test subscribers:', 'newsletter');
1206
+ $controls->errors .= ' ' . implode(', ', $emails);
1207
+ $controls->errors .= '<br>';
1208
+ $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1209
+ $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1210
+ } else {
1211
+ $controls->messages = __('Test subscribers:', 'newsletter');
1212
+
1213
+ $controls->messages .= ' ' . implode(', ', $emails);
1214
+ $controls->messages .= '.<br>';
1215
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1216
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1217
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1218
+ }
1219
+ }
1220
+ $email->subject = $original_subject;
1221
+ }
1222
+
1223
+ /**
1224
+ * Send an email to the test subscribers.
1225
+ *
1226
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1227
+ * @param string $email_address
1228
+ *
1229
+ * @throws Exception
1230
+ */
1231
+ function send_test_newsletter_to_email_address($email, $email_address) {
1232
+
1233
+ if (!$email) {
1234
+ throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1235
+ }
1236
+
1237
+ $this->set_test_subject_to($email);
1238
+
1239
+ $dummy_subscriber = $this->make_dummy_subscriber();
1240
+ $dummy_subscriber->email = $email_address;
1241
+
1242
+ $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1243
+
1244
+ $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1245
+
1246
+ if (is_wp_error($result)) {
1247
+ $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1248
+ $error_message .= __('Test subscribers:', 'newsletter');
1249
+ $error_message .= ' ' . $email;
1250
+ $error_message .= '<br>';
1251
+ $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1252
+ $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1253
+ throw new Exception($error_message);
1254
+ }
1255
+
1256
+ $messages = __('Test subscribers:', 'newsletter');
1257
+
1258
+ $messages .= ' ' . $email;
1259
+ $messages .= '.<br>';
1260
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1261
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1262
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1263
+
1264
+ return $messages;
1265
+ }
1266
+
1267
+ private function set_test_subject_to($email) {
1268
+ if ($email->subject == '') {
1269
+ $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1270
+ } else {
1271
+ $email->subject = $email->subject . ' (TEST)';
1272
+ }
1273
+ }
1274
+
1275
+ private function make_dummy_subscriber() {
1276
+ $dummy_user = new TNP_User();
1277
+ $dummy_user->id = 0;
1278
+ $dummy_user->email = 'john.doe@example.org';
1279
+ $dummy_user->name = 'John';
1280
+ $dummy_user->surname = 'Doe';
1281
+ $dummy_user->sex = 'n';
1282
+ $dummy_user->language = '';
1283
+ $dummy_user->ip = '';
1284
+
1285
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1286
+ $profile_key = "profile_$i";
1287
+ $dummy_user->$profile_key = '';
1288
+ }
1289
+
1290
+ return $dummy_user;
1291
+ }
1292
+
1293
+ function restore_options_from_request() {
1294
+
1295
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1296
+ $controls = new NewsletterControls();
1297
+ $options = $controls->data;
1298
+
1299
+ if (isset($_POST['options']) && is_array($_POST['options'])) {
1300
+ // Get all block options
1301
+ //$options = stripslashes_deep($_POST['options']);
1302
+ // Deserialize inline edits when
1303
+ // render is preformed on saving block options
1304
+ if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1305
+ $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1306
+ }
1307
+
1308
+ // Restore inline edits from data-json
1309
+ // coming from inline editing
1310
+ // and merge with current inline edit
1311
+ if (isset($_POST['encoded_options'])) {
1312
+ $decoded_options = $this->options_decode($_POST['encoded_options']);
1313
+
1314
+ $to_merge_inline_edits = [];
1315
+
1316
+ if (isset($decoded_options['inline_edits'])) {
1317
+ foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1318
+ $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1319
+ }
1320
+ }
1321
+
1322
+ //Overwrite with new edited content
1323
+ if (isset($options['inline_edits'])) {
1324
+ foreach ($options['inline_edits'] as $inline_edit) {
1325
+ $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1326
+ }
1327
+ }
1328
+
1329
+ $options['inline_edits'] = array_values($to_merge_inline_edits);
1330
+ $options = array_merge($decoded_options, $options);
1331
+ }
1332
+
1333
+ return $options;
1334
+ }
1335
+
1336
+ return array();
1337
+ }
1338
+
1339
+ public function hook_wp_ajax_tnpc_delete_preset() {
1340
+
1341
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1342
+ wp_send_json_error('Expired request');
1343
+ }
1344
+
1345
+ $preset_id = (int) $_REQUEST['presetId'];
1346
+
1347
+ $newsletter = Newsletter::instance();
1348
+
1349
+ if ($preset_id > 0) {
1350
+ $preset = $newsletter->get_email($preset_id);
1351
+
1352
+ if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1353
+ Newsletter::instance()->delete_email($preset_id);
1354
+ wp_send_json_success();
1355
+ } else {
1356
+ wp_send_json_error(__('Is not a preset!', 'newsletter'));
1357
+ }
1358
+ } else {
1359
+ wp_send_json_error();
1360
+ }
1361
+ }
1362
+
1363
+ }
1364
+
1365
+ NewsletterEmails::instance();
includes/composer.php CHANGED
@@ -1,987 +1,1008 @@
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
+ // $open .= '<!--[if mso]>' . "\n";
117
+
118
+ $open .= '<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->' . "\n";
119
+
120
+ // $open .= '<style type="text/css">';
121
+ // $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
122
+ // $open .= 'div, td {padding:0;}';
123
+ // $open .= 'div {margin:0 !important;}';
124
+ // $open .= '</style>';
125
+ // $open .= "\n";
126
+ // $open .= '<noscript>';
127
+ // $open .= '<xml>';
128
+ // $open .= '<o:OfficeDocumentSettings>';
129
+ // $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
130
+ // $open .= '</o:OfficeDocumentSettings>';
131
+ // $open .= '</xml>';
132
+ // $open .= '</noscript>';
133
+ // $open .= "\n";
134
+ // $open .= '<![endif]-->';
135
+ // $open .= "\n";
136
+ $open .= '<style type="text/css">' . "\n";
137
+ $open .= NewsletterEmails::instance()->get_composer_css();
138
+ $open .= "\n</style>\n";
139
+ $open .= "</head>\n";
140
+ $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
141
+ $open .= "\n";
142
+ $open .= self::get_html_preheader($email);
143
+
144
+ return $open;
145
+ }
146
+
147
+ static private function get_html_preheader($email) {
148
+
149
+ if (empty($email->options['preheader'])) {
150
+ return "";
151
+ }
152
+
153
+ $preheader_text = esc_html($email->options['preheader']);
154
+ $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>";
155
+ $html .= "\n";
156
+
157
+ return $html;
158
+ }
159
+
160
+ static function get_html_close($email) {
161
+ return "</body>\n</html>";
162
+ }
163
+
164
+ /**
165
+ *
166
+ * @param TNP_Email $email
167
+ * @return string
168
+ */
169
+ static function get_main_wrapper_open($email) {
170
+ if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
171
+ $bgcolor = '';
172
+ } else {
173
+ $bgcolor = $email->options['composer_background'];
174
+ }
175
+
176
+ return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
177
+ "<tr>\n" .
178
+ "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
179
+ }
180
+
181
+ /**
182
+ *
183
+ * @param TNP_Email $email
184
+ * @return string
185
+ */
186
+ static function get_main_wrapper_close($email) {
187
+ return "\n<!-- /tnp -->\n" .
188
+ "</td>\n" .
189
+ "</tr>\n" .
190
+ "</table>\n\n";
191
+ }
192
+
193
+ /**
194
+ * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
195
+ *
196
+ * @param string $html_email
197
+ *
198
+ * @return string
199
+ */
200
+ static function unwrap_email($html_email) {
201
+
202
+ if (self::_has_markers($html_email)) {
203
+ $html_email = self::unwrap_html_element($html_email);
204
+ } else {
205
+ //KEEP FOR OLD EMAIL COMPATIBILITY
206
+ // Extracts only the body part
207
+ $x = strpos($html_email, '<body');
208
+ if ($x) {
209
+ $x = strpos($html_email, '>', $x);
210
+ $y = strpos($html_email, '</body>');
211
+ $html_email = substr($html_email, $x + 1, $y - $x - 1);
212
+ }
213
+
214
+ /* Cleans up uncorrectly stored newsletter bodies */
215
+ $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
216
+ $html_email = preg_replace('/<meta.*?>/', '', $html_email);
217
+ $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
218
+ $html_email = trim($html_email);
219
+ }
220
+
221
+ // Required since esc_html DOES NOT escape the HTML entities (apparently)
222
+ $html_email = str_replace('&', '&amp;', $html_email);
223
+ $html_email = str_replace('"', '&quot;', $html_email);
224
+ $html_email = str_replace('<', '&lt;', $html_email);
225
+ $html_email = str_replace('>', '&gt;', $html_email);
226
+
227
+ return $html_email;
228
+ }
229
+
230
+ private static function _escape_markers(&$markers) {
231
+ $markers[0] = str_replace('/', '\/', $markers[0]);
232
+ $markers[1] = str_replace('/', '\/', $markers[1]);
233
+ }
234
+
235
+ /**
236
+ * Using the data collected inside $controls (and submitted by a form containing the
237
+ * composer fields), updates the email. The message body is completed with doctype,
238
+ * head, style and the main wrapper.
239
+ *
240
+ * @param TNP_Email $email
241
+ * @param NewsletterControls $controls
242
+ */
243
+ static function update_email($email, $controls) {
244
+ if (isset($controls->data['subject'])) {
245
+ $email->subject = $controls->data['subject'];
246
+ }
247
+
248
+ // They should be only composer options
249
+ foreach ($controls->data as $name => $value) {
250
+ if (strpos($name, 'options_') === 0) {
251
+ $email->options[substr($name, 8)] = $value;
252
+ }
253
+ }
254
+
255
+ //if (isset($controls->data['preheader'])) {
256
+ // $email->options['preheader'] = $controls->data['preheader'];
257
+ //}
258
+
259
+ $email->editor = NewsletterEmails::EDITOR_COMPOSER;
260
+
261
+ $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
262
+ $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
263
+ }
264
+
265
+ /**
266
+ * Prepares a controls object injecting the relevant fields from an email
267
+ * which cannot be directly used by controls. If $email is null or missing,
268
+ * $controls is prepared with default values.
269
+ *
270
+ * @param NewsletterControls $controls
271
+ * @param TNP_Email $email
272
+ */
273
+ static function prepare_controls($controls, $email = null) {
274
+
275
+ // Controls for a new email (which actually does not exist yet
276
+ if (!empty($email)) {
277
+
278
+ foreach ($email->options as $name => $value) {
279
+ $controls->data['options_' . $name] = $value;
280
+ }
281
+
282
+ $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
283
+ $controls->data['subject'] = $email->subject;
284
+ $controls->data['updated'] = $email->updated;
285
+ }
286
+
287
+ if (!empty($email->options['sender_email'])) {
288
+ $controls->data['sender_email'] = $email->options['sender_email'];
289
+ } else {
290
+ $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
291
+ }
292
+
293
+ if (!empty($email->options['sender_name'])) {
294
+ $controls->data['sender_name'] = $email->options['sender_name'];
295
+ } else {
296
+ $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
297
+ }
298
+
299
+ $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
300
+ }
301
+
302
+ /**
303
+ * Extract inline edited post field from inline_edit_list[]
304
+ *
305
+ * @param array $inline_edit_list
306
+ * @param string $field_type
307
+ * @param int $post_id
308
+ *
309
+ * @return string
310
+ */
311
+ static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
312
+
313
+ foreach ($inline_edit_list as $edit) {
314
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
315
+ return $edit['content'];
316
+ }
317
+ }
318
+
319
+ return '';
320
+ }
321
+
322
+ /**
323
+ * Check if inline_edit_list[] have inline edit field for specific post
324
+ *
325
+ * @param array $inline_edit_list
326
+ * @param string $field_type
327
+ * @param int $post_id
328
+ *
329
+ * @return bool
330
+ */
331
+ static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
332
+ if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
333
+ return false;
334
+ }
335
+ foreach ($inline_edit_list as $edit) {
336
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
337
+ return true;
338
+ }
339
+ }
340
+
341
+ return false;
342
+ }
343
+
344
+ /**
345
+ * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
346
+ *
347
+ * - [prefix]_url The button URL
348
+ * - [prefix]_font_family
349
+ * - [prefix]_font_size
350
+ * - [prefix]_font_weight
351
+ * - [prefix]_label
352
+ * - [prefix]_font_color The label color
353
+ * - [prefix]_background The button color
354
+ *
355
+ * TODO: Add radius and possiblt the alignment
356
+ *
357
+ * @param array $options
358
+ * @param string $prefix
359
+ * @return string
360
+ */
361
+ static function button($options, $prefix = 'button') {
362
+
363
+ if (empty($options[$prefix . '_label'])) {
364
+ return;
365
+ }
366
+ $defaults = [
367
+ $prefix . '_url' => '#',
368
+ $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
369
+ $prefix . '_font_color' => '#ffffff',
370
+ $prefix . '_font_weight' => 'bold',
371
+ $prefix . '_font_size' => 20,
372
+ $prefix . '_background' => '#256F9C',
373
+ $prefix . '_align' => 'center'
374
+ ];
375
+
376
+ $options = array_merge($defaults, array_filter($options));
377
+
378
+ $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
379
+ if (!empty($options[$prefix . '_align'])) {
380
+ $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
381
+ }
382
+ if (!empty($options[$prefix . '_width'])) {
383
+ $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
384
+ }
385
+ $b .= '>';
386
+ $b .= '<tr>';
387
+ $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">';
388
+ $b .= '<a href="' . $options[$prefix . '_url'] . '"';
389
+ $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;"';
390
+ $b .= ' target="_blank">';
391
+ $b .= $options[$prefix . '_label'];
392
+ $b .= '</a>';
393
+ $b .= '</td></tr></table>';
394
+ return $b;
395
+ }
396
+
397
+ /**
398
+ * Generates an IMG tag, linked if the media has an URL.
399
+ *
400
+ * @param TNP_Media $media
401
+ * @param string $style
402
+ * @return string
403
+ */
404
+ static function image($media, $attr = []) {
405
+
406
+ $default_attrs = [
407
+ 'style' => 'max-width: 100%; height: auto; display: inline-block',
408
+ 'class' => '',
409
+ 'link-style' => 'text-decoration: none; display: inline-block',
410
+ 'link-class' => null,
411
+ ];
412
+
413
+ $attr = array_merge($default_attrs, $attr);
414
+
415
+ //Class and style attribute are mutually exclusive.
416
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
417
+ if (!empty($attr['inline-class'])) {
418
+ $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
419
+ } else {
420
+ $styling = ' style="' . esc_attr($attr['style']) . '" ';
421
+ }
422
+
423
+ if (!empty($attr['class'])) {
424
+ $styling .= ' class="' . esc_attr($attr['class']) . '" ';
425
+ }
426
+
427
+ //Class and style attribute are mutually exclusive.
428
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
429
+ if (!empty($attr['link-class'])) {
430
+ $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
431
+ } else {
432
+ $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
433
+ }
434
+
435
+ $b = '';
436
+ if ($media->link) {
437
+ $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">';
438
+ } else {
439
+ // The span grants images are not upscaled when fluid (two columns posts block)
440
+ $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
441
+ }
442
+ if ($media) {
443
+ $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
444
+ if ($media->height) {
445
+ $b .= ' height="' . esc_attr($media->height) . '"';
446
+ }
447
+ $b .= ' alt="' . esc_attr($media->alt) . '"'
448
+ . ' border="0"'
449
+ . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0; font-size: 12px"'
450
+ . ' class="' . esc_attr($attr['class']) . '" '
451
+ . '>';
452
+ }
453
+
454
+ if ($media->link) {
455
+ $b .= '</a>';
456
+ } else {
457
+ $b .= '</span>';
458
+ }
459
+
460
+ return $b;
461
+ }
462
+
463
+ /**
464
+ * Returns a WP media ID for the specified post (or false if nothing can be found)
465
+ * looking for the featured image or, if missing, taking the first media in the gallery and
466
+ * if again missing, searching the first reference to a media in the post content.
467
+ *
468
+ * The media ID is not checked for real existance of the associated attachment.
469
+ *
470
+ * @param int $post_id
471
+ * @return int
472
+ */
473
+ static function get_post_thumbnail_id($post_id) {
474
+ if (is_object($post_id)) {
475
+ $post_id = $post_id->ID;
476
+ }
477
+
478
+ // Find a media id to be used as featured image
479
+ $media_id = get_post_thumbnail_id($post_id);
480
+ if (!empty($media_id)) {
481
+ return $media_id;
482
+ }
483
+
484
+ $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'));
485
+ if (!empty($attachments)) {
486
+ foreach ($attachments as $id => &$attachment) {
487
+ return $id;
488
+ }
489
+ }
490
+
491
+ $post = get_post($post_id);
492
+
493
+ $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
494
+ if ($matches) {
495
+ return (int) $matches[1];
496
+ }
497
+
498
+ return false;
499
+ }
500
+
501
+ /**
502
+ * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
503
+ * media has a size which best match the one requested (this is the standard WP behavior, plugins
504
+ * could change it).
505
+ *
506
+ * @param int $media_id
507
+ * @param array $size
508
+ * @return \TNP_Media
509
+ */
510
+ function get_media($media_id, $size) {
511
+ $src = wp_get_attachment_image_src($media_id, $size);
512
+ if (!$src) {
513
+ return null;
514
+ }
515
+ $media = new TNP_Media();
516
+ $media->id = $media_id;
517
+ $media->url = $src[0];
518
+ $media->width = $src[1];
519
+ $media->height = $src[2];
520
+ return $media;
521
+ }
522
+
523
+ static function post_content($post) {
524
+ $content = $post->post_content;
525
+ $content = wpautop($content);
526
+ if (true || $options['enable shortcodes']) {
527
+ remove_shortcode('gallery');
528
+ add_shortcode('gallery', 'tnp_gallery_shortcode');
529
+ $content = do_shortcode($content);
530
+ }
531
+ $content = str_replace('<p>', '<p class="paragraph">', $content);
532
+
533
+ $selected_images = array();
534
+ if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
535
+ foreach ($matches[0] as $image) {
536
+ if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
537
+ $selected_images[$image] = $attachment_id;
538
+ }
539
+ }
540
+ }
541
+
542
+ foreach ($selected_images as $image => $attachment_id) {
543
+ $src = tnp_media_resize($attachment_id, array(600, 0));
544
+ if (is_wp_error($src)) {
545
+ continue;
546
+ }
547
+ $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
548
+ }
549
+
550
+ return $content;
551
+ }
552
+
553
+ static function get_global_style_defaults() {
554
+ return [
555
+ 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
556
+ 'options_composer_title_font_size' => 32,
557
+ 'options_composer_title_font_weight' => 'normal',
558
+ 'options_composer_title_font_color' => '#222222',
559
+ 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
560
+ 'options_composer_text_font_size' => 16,
561
+ 'options_composer_text_font_weight' => 'normal',
562
+ 'options_composer_text_font_color' => '#222222',
563
+ 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
564
+ 'options_composer_button_font_size' => 16,
565
+ 'options_composer_button_font_weight' => 'normal',
566
+ 'options_composer_button_font_color' => '#FFFFFF',
567
+ 'options_composer_button_background_color' => '#256F9C',
568
+ 'options_composer_background' => '#FFFFFF',
569
+ 'options_composer_block_background' => '#FFFFFF',
570
+ ];
571
+ }
572
+
573
+ /**
574
+ * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
575
+ *
576
+ * Attributes:
577
+ * - columns: number of columns [2]
578
+ * - padding: cells padding [10]
579
+ * - responsive: il on mobile the cell should stack up [true]
580
+ * - width: the whole row width, it should reduced by the external row padding [600]
581
+ *
582
+ * @param string[] $items
583
+ * @param array $attrs
584
+ * @return string
585
+ */
586
+ static function grid($items = [], $attrs = []) {
587
+ $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
588
+ $width = (int) $attrs['width'];
589
+ $columns = (int) $attrs['columns'];
590
+ $padding = (int) $attrs['padding'];
591
+ $column_width = $width / $columns;
592
+ $td_width = 100 / $columns;
593
+ $chunks = array_chunk($items, $columns);
594
+
595
+ if ($attrs['responsive']) {
596
+
597
+ $e = '';
598
+ foreach ($chunks as &$chunk) {
599
+ $e .= '<div style="text-align:center;font-size:0;">';
600
+ $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
601
+ foreach ($chunk as &$item) {
602
+ $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
603
+
604
+ $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
605
+
606
+ // This element to add padding without deal with border-box not well supported
607
+ $e .= '<div style="padding:' . $padding . 'px;">';
608
+ $e .= $item;
609
+ $e .= '</div>';
610
+ $e .= '</div>';
611
+
612
+ $e .= '<!--[if mso]></td><![endif]-->';
613
+ }
614
+ $e .= '<!--[if mso]></tr></table><![endif]-->';
615
+ $e .= '</div>';
616
+ }
617
+
618
+ return $e;
619
+ } else {
620
+ $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
621
+ foreach ($chunks as &$chunk) {
622
+ $e .= '<tr>';
623
+ foreach ($chunk as &$item) {
624
+ $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
625
+ $e .= $item;
626
+ $e .= '</td>';
627
+ }
628
+ $e .= '</tr>';
629
+ }
630
+ $e .= '</table>';
631
+ return $e;
632
+ }
633
+ }
634
+
635
+ static function get_text_style($options, $prefix, $composer, $attrs = []) {
636
+ return self::get_style($options, $prefix, $composer, 'text', $attrs);
637
+ }
638
+
639
+ static function get_title_style($options, $prefix, $composer, $attrs = []) {
640
+ return self::get_style($options, $prefix, $composer, 'title', $attrs);
641
+ }
642
+
643
+ static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
644
+ $style = new TNP_Style();
645
+ $scale = 1.0;
646
+ if (!empty($attrs['scale'])) {
647
+ $scale = (float) $attrs['scale'];
648
+ }
649
+ if (!empty($prefix))
650
+ $prefix .= '_';
651
+
652
+ $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
653
+ $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
654
+ $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
655
+ $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
656
+ if ($type === 'button') {
657
+ $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
658
+ }
659
+ return $style;
660
+ }
661
+
662
+ static function get_button_options($options, $prefix, $composer) {
663
+ $button_options = [];
664
+ $scale = 1;
665
+ $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
666
+ $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
667
+ $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
668
+ $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
669
+ $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
670
+ $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
671
+ $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
672
+ $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
673
+ $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
674
+
675
+ return $button_options;
676
+ }
677
+
678
+ static function convert_to_text($html) {
679
+ if (!class_exists('DOMDocument')) {
680
+ return '';
681
+ }
682
+
683
+ if (!function_exists('ctype_space')) {
684
+ return '';
685
+ }
686
+
687
+ // Replace '&' with '&amp;' in URLs to avoid warnings about inavlid entities from loadHTML()
688
+ // Todo: make this more general using a regular expression
689
+ //$logger = PlaintextNewsletterAddon::$instance->get_logger();
690
+ //$logger->debug('html="' . $html . '"');
691
+ $html = str_replace(
692
+ array('&nk=', '&nek=', '&id='),
693
+ array('&amp;nk=', '&amp;nek=', '&amp;id='),
694
+ $html);
695
+ //$logger->debug('new html="' . $html . '"');
696
+ //
697
+ $output = '';
698
+
699
+ // Prevents warnings for problems with the HTML
700
+ if (function_exists('libxml_use_internal_errors')) {
701
+ libxml_use_internal_errors(true);
702
+ }
703
+ $dom = new DOMDocument();
704
+ $r = $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html);
705
+ if (!$r) {
706
+ return '';
707
+ }
708
+ $bodylist = $dom->getElementsByTagName('body');
709
+ // Of course it should be a single element
710
+ foreach ($bodylist as $body) {
711
+ self::process_dom_element($body, $output);
712
+ }
713
+ return $output;
714
+ }
715
+
716
+ static function process_dom_element(DOMElement $parent, &$output) {
717
+ foreach ($parent->childNodes as $node) {
718
+ if (is_a($node, 'DOMElement') && ($node->tagName != 'style')) {
719
+
720
+ if ($node->tagName == 'br') {
721
+ $output .= "\n";
722
+ continue;
723
+ }
724
+
725
+ self::process_dom_element($node, $output);
726
+
727
+ if ($node->tagName == 'li') {
728
+ if ((strlen($output) >= 1) && (substr($output, -1) != "\n")) {
729
+ $output .= "\n";
730
+ }
731
+ continue;
732
+ }
733
+
734
+ // If the containing tag was a block level tag, we add a couple of line ending
735
+ if ($node->tagName == 'p' || $node->tagName == 'div' || $node->tagName == 'td') {
736
+ // Avoid more than one blank line between elements
737
+ if ((strlen($output) >= 2) && (substr($output, -2) != "\n\n")) {
738
+ $output .= "\n\n";
739
+ }
740
+ }
741
+
742
+ if ($node->tagName == 'a') {
743
+ // Check if the children is an image
744
+ if (is_a($node->childNodes[0], 'DOMElement')) {
745
+ if ($node->childNodes[0]->tagName == 'img') {
746
+ continue;
747
+ }
748
+ }
749
+ $output .= ' (' . $node->getAttribute('href') . ') ';
750
+ continue;
751
+ } elseif ($node->tagName == 'img') {
752
+ $output .= $node->getAttribute('alt');
753
+ }
754
+ } elseif (is_a($node, 'DOMText')) {
755
+
756
+ // ???
757
+ $decoded = utf8_decode($node->wholeText);
758
+ //$decoded = trim(html_entity_decode($node->wholeText));
759
+ // We could avoid ctype_*
760
+ if (ctype_space($decoded)) {
761
+ // Append blank only if last character output is not blank.
762
+ if ((strlen($output) > 0) && !ctype_space(substr($output, -1))) {
763
+ $output .= ' ';
764
+ }
765
+ } else {
766
+ $output .= trim($node->wholeText);
767
+ $output .= ' ';
768
+ }
769
+ }
770
+ }
771
+ }
772
+
773
+ }
774
+
775
+ class TNP_Style {
776
+
777
+ var $font_family;
778
+ var $font_size;
779
+ var $font_weight;
780
+ var $font_color;
781
+ var $background;
782
+
783
+ function echo_css($scale = 1.0) {
784
+ echo 'font-size: ', round($this->font_size * $scale), 'px;';
785
+ echo 'font-family: ', $this->font_family, ';';
786
+ echo 'font-weight: ', $this->font_weight, ';';
787
+ echo 'color: ', $this->font_color, ';';
788
+ }
789
+
790
+ }
791
+
792
+ /**
793
+ * Generate multicolumn and responsive html template for email.
794
+ * Initialize class with max columns per row and start to add cells.
795
+ */
796
+ class TNP_Composer_Grid_System {
797
+
798
+ /**
799
+ * @var TNP_Composer_Grid_Row[]
800
+ */
801
+ private $rows;
802
+
803
+ /**
804
+ * @var int
805
+ */
806
+ private $cells_per_row;
807
+
808
+ /**
809
+ * @var int
810
+ */
811
+ private $cells_counter;
812
+
813
+ /**
814
+ * TNP_Composer_Grid_System constructor.
815
+ *
816
+ * @param int $columns_per_row Max columns per row
817
+ */
818
+ public function __construct($columns_per_row) {
819
+ $this->cells_per_row = $columns_per_row;
820
+ $this->cells_counter = 0;
821
+ $this->rows = [];
822
+ }
823
+
824
+ public function __toString() {
825
+ return $this->render();
826
+ }
827
+
828
+ /**
829
+ * Add cell to grid
830
+ *
831
+ * @param TNP_Composer_Grid_Cell $cell
832
+ */
833
+ public function add_cell($cell) {
834
+
835
+ if ($this->cells_counter % $this->cells_per_row === 0) {
836
+ $this->add_row(new TNP_Composer_Grid_Row());
837
+ }
838
+
839
+ $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
840
+ $this->rows[$row_idx]->add_cell($cell);
841
+ $this->cells_counter++;
842
+ }
843
+
844
+ private function add_row($row) {
845
+ $this->rows[] = $row;
846
+ }
847
+
848
+ public function render() {
849
+
850
+ $str = '';
851
+ foreach ($this->rows as $row) {
852
+ $str .= $row->render();
853
+ }
854
+
855
+ return $str;
856
+ }
857
+
858
+ }
859
+
860
+ /**
861
+ * Class TNP_Composer_Grid_Row
862
+ */
863
+ class TNP_Composer_Grid_Row {
864
+
865
+ /**
866
+ * @var TNP_Composer_Grid_Cell[]
867
+ */
868
+ private $cells;
869
+
870
+ public function __construct(...$cells) {
871
+ if (!empty($cells)) {
872
+ foreach ($cells as $cell) {
873
+ $this->add_cell($cell);
874
+ }
875
+ }
876
+ }
877
+
878
+ /**
879
+ * @param TNP_Composer_Grid_Cell $cell
880
+ */
881
+ public function add_cell($cell) {
882
+ $this->cells[] = $cell;
883
+ }
884
+
885
+ public function render() {
886
+ $rendered_cells = '';
887
+ $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
888
+ foreach ($this->cells as $cell) {
889
+ $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
890
+ }
891
+
892
+ $row_template = $this->get_template();
893
+
894
+ return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
895
+ }
896
+
897
+ private function cells_count() {
898
+ return count($this->cells);
899
+ }
900
+
901
+ private function get_template() {
902
+ return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
903
+ }
904
+
905
+ }
906
+
907
+ /**
908
+ * Class TNP_Composer_Grid_Cell
909
+ */
910
+ class TNP_Composer_Grid_Cell {
911
+
912
+ /**
913
+ * @var string
914
+ */
915
+ private $content;
916
+
917
+ /**
918
+ * @var array
919
+ */
920
+ public $args;
921
+
922
+ public function __construct($content = null, $args = []) {
923
+ $default_args = [
924
+ 'width' => '100%',
925
+ 'class' => '',
926
+ 'align' => 'left',
927
+ 'valign' => 'top'
928
+ ];
929
+
930
+ $this->args = array_merge($default_args, $args);
931
+
932
+ $this->content = $content ? $content : '';
933
+ }
934
+
935
+ public function add_content($content) {
936
+ $this->content .= $content;
937
+ }
938
+
939
+ public function render($args) {
940
+ $this->args = array_merge($this->args, $args);
941
+
942
+ $column_template = $this->get_template();
943
+ $column = str_replace(
944
+ [
945
+ 'TNP_ALIGN_PH',
946
+ 'TNP_VALIGN_PH',
947
+ 'TNP_WIDTH_PH',
948
+ 'TNP_CLASS_PH',
949
+ 'TNP_COLUMN_CONTENT_PH'
950
+ ], [
951
+ $this->args['align'],
952
+ $this->args['valign'],
953
+ $this->args['width'],
954
+ $this->args['class'],
955
+ $this->content
956
+ ], $column_template);
957
+
958
+ return $column;
959
+ }
960
+
961
+ private function get_template() {
962
+ return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
963
+ <tbody>
964
+ <tr>
965
+ <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
966
+ TNP_COLUMN_CONTENT_PH
967
+ </td>
968
+ </tr>
969
+ </tbody>
970
+ </table>";
971
+ }
972
+
973
+ }
974
+
975
+ class TNP_Composer_Component_Factory {
976
+
977
+ private $options;
978
+
979
+ /**
980
+ * TNP_Composer_Component_Factory constructor.
981
+ *
982
+ * @param Controller$controller
983
+ */
984
+ public function __construct($controller) {
985
+
986
+ }
987
+
988
+ function heading() {
989
+
990
+ }
991
+
992
+ function paragraph() {
993
+
994
+ }
995
+
996
+ function link() {
997
+
998
+ }
999
+
1000
+ function button() {
1001
+
1002
+ }
1003
+
1004
+ function image() {
1005
+
1006
+ }
1007
+
1008
+ }
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.6
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.6');
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.7
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.7');
41
 
42
  global $newsletter, $wpdb;
43
 
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.1
4
- Stable tag: 7.5.6
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,14 @@ Thank you, The Newsletter Team
126
 
127
  == Changelog ==
128
 
 
 
 
 
 
 
 
 
129
  = 7.5.6 =
130
 
131
  * Added new filter for multilanguage support for GTranslate (experimental)
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.1.1
4
+ Stable tag: 7.5.7
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.7 =
130
+
131
+ * Check on ctype_space function (there are installation without it)
132
+ * Improved the html-2-text conversion
133
+ * Added language to the export
134
+ * Status labels fixed on subscriber search and edit panel
135
+ * Stripped HTML tags on block generated subjects
136
+
137
  = 7.5.6 =
138
 
139
  * Added new filter for multilanguage support for GTranslate (experimental)
tnp-header.php CHANGED
@@ -1,349 +1,349 @@
1
- <?php
2
- global $current_user, $wpdb;
3
-
4
- defined('ABSPATH') || exit;
5
-
6
- $dismissed = get_option('newsletter_dismissed', []);
7
-
8
- $user_count = Newsletter::instance()->get_user_count();
9
-
10
- $is_administrator = current_user_can('administrator');
11
-
12
- function newsletter_print_entries($group) {
13
- $entries = apply_filters('newsletter_menu_' . $group, array());
14
- if (!$entries) {
15
- return;
16
- }
17
-
18
- foreach ($entries as &$entry) {
19
- echo '<li><a href="', $entry['url'], '">', $entry['label'];
20
- if (!empty($entry['description'])) {
21
- echo '<small>', $entry['description'], '</small>';
22
- }
23
- echo '</a></li>';
24
- }
25
- }
26
-
27
- // Check the status to show a warning if needed
28
- $status_options = Newsletter::instance()->get_options('status');
29
- $warning = false;
30
-
31
- //$warning |= empty($status_options['mail']);
32
-
33
- $current_user_email = ''; //$current_user->user_email;
34
- //if (strpos($current_user_email, 'admin@') === 0) {
35
- // $current_user_email = '';
36
- //}
37
- ?>
38
-
39
- <div class="tnp-drowpdown" id="tnp-header">
40
- <a href="?page=newsletter_main_index"><img src="<?php echo plugins_url('newsletter'); ?>/admin/images/logo-red.png" class="tnp-header-logo" style="vertical-align: bottom;"></a>
41
- <ul>
42
- <li><a href="#"><i class="fas fa-users"></i> <?php _e('Subscribers', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
43
- <ul>
44
- <li>
45
- <a href="?page=newsletter_users_index"><i class="fas fa-search"></i> <?php _e('Search And Edit', 'newsletter') ?>
46
- <small><?php _e('Add, edit, search', 'newsletter') ?></small></a>
47
- </li>
48
-
49
- <li>
50
- <a href="?page=newsletter_profile_index"><i class="fas fa-user-circle"></i> <?php _e('Profile page', 'newsletter') ?>
51
- <small><?php _e('The subscriber personal profile editing panel', 'newsletter') ?></small></a>
52
- </li>
53
-
54
- <?php if (!class_exists('NewsletterImport')) { ?>
55
- <li>
56
- <a href="?page=newsletter_users_import"><i class="fas fa-upload"></i> <?php _e('Import', 'newsletter') ?>
57
- <small><?php _e('Import from external sources', 'newsletter') ?></small></a>
58
- </li>
59
- <?php } ?>
60
-
61
- <li>
62
- <a href="?page=newsletter_users_export"><i class="fas fa-download"></i> <?php _e('Export', 'newsletter') ?>
63
- <small><?php _e('Export your subscribers list', 'newsletter') ?></small></a>
64
- </li>
65
-
66
- <li>
67
- <a href="?page=newsletter_users_massive"><i class="fas fa-wrench"></i> <?php _e('Maintenance', 'newsletter') ?>
68
- <small><?php _e('Massive actions: change list, clean up, ...', 'newsletter') ?></small></a>
69
- </li>
70
-
71
- <li>
72
- <a href="?page=newsletter_users_statistics"><i class="fas fa-chart-bar"></i> <?php _e('Statistics', 'newsletter') ?>
73
- <small><?php _e('All about your subscribers', 'newsletter') ?></small></a>
74
- </li>
75
-
76
- <?php newsletter_print_entries('subscribers') ?>
77
- </ul>
78
- </li>
79
- <li><a href="#"><i class="fas fa-list"></i> <?php _e('List Building', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
80
- <ul>
81
- <li>
82
- <a href="?page=newsletter_subscription_profile"><i class="fas fa-check-square"></i> <?php _e('Subscription Form Fields, Buttons, Labels', 'newsletter') ?>
83
- <small><?php _e('When and what data to collect', 'newsletter') ?></small></a>
84
- </li>
85
-
86
- <li>
87
- <a href="?page=newsletter_subscription_options"><i class="fas fa-sign-in-alt"></i> <?php _e('Subscription', 'newsletter') ?>
88
- <small><?php _e('The subscription process in detail', 'newsletter') ?></small></a>
89
- </li>
90
-
91
- <li>
92
- <a href="?page=newsletter_subscription_lists"><i class="fas fa-th-list"></i> <?php _e('Lists', 'newsletter') ?>
93
- <small><?php _e('Profile the subscribers for a better targeting', 'newsletter') ?></small></a>
94
- </li>
95
-
96
- <li>
97
- <a href="?page=newsletter_subscription_antibot"><i class="fas fa-lock"></i> <?php _e('Security', 'newsletter') ?>
98
- <small><?php _e('Spam subscriptions control', 'newsletter') ?></small></a>
99
- </li>
100
-
101
- <li>
102
- <a href="?page=newsletter_unsubscription_index"><i class="fas fa-sign-out-alt"></i> <?php _e('Unsubscription', 'newsletter') ?>
103
- <small><?php _e('How to give the last goodbye (or avoid it!)', 'newsletter') ?></small></a>
104
- </li>
105
-
106
- <?php
107
- newsletter_print_entries('subscription');
108
- ?>
109
- </ul>
110
- </li>
111
-
112
- <li>
113
- <a href="#"><i class="fas fa-newspaper"></i> <?php _e('Newsletters', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
114
- <ul>
115
- <li>
116
- <a href="?page=newsletter_emails_composer"><i class="fas fa-plus"></i> <?php _e('Create newsletter', 'newsletter') ?>
117
- <small><?php _e('Start your new campaign', 'newsletter') ?></small></a>
118
- </li>
119
-
120
- <li>
121
- <a href="?page=newsletter_emails_index"><i class="fas fa-newspaper"></i> <?php _e('Newsletters', 'newsletter') ?>
122
- <small><?php _e('The classic "write & send" newsletters', 'newsletter') ?></small></a>
123
- </li>
124
-
125
- <li>
126
- <a href="<?php echo NewsletterStatistics::instance()->get_index_url() ?>"><i class="fas fa-chart-bar"></i> <?php _e('Statistics', 'newsletter') ?>
127
- <small><?php _e('Tracking configuration and basic data', 'newsletter') ?></small></a>
128
- </li>
129
- <?php
130
- newsletter_print_entries('newsletters');
131
- ?>
132
- </ul>
133
- </li>
134
-
135
- <li>
136
- <a href="#"><i class="fas fa-cog"></i> <?php _e('Settings', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
137
- <ul>
138
- <?php if ($is_administrator) { ?>
139
- <li>
140
- <a href="?page=newsletter_main_main"><i class="fas fa-cogs"></i> <?php _e('General Settings', 'newsletter') ?>
141
- <small><?php _e('Delivery speed, sender details, ...', 'newsletter') ?></small></a>
142
- </li>
143
- <?php if (!class_exists('NewsletterSmtp')) { ?>
144
- <li>
145
- <a href="?page=newsletter_main_smtp"><i class="fas fa-server"></i> <?php _e('SMTP', 'newsletter') ?>
146
- <small><?php _e('External mail server', 'newsletter') ?></small>
147
- </a>
148
- </li>
149
- <?php } ?>
150
- <?php } ?>
151
-
152
- <li>
153
- <a href="?page=newsletter_main_info"><i class="fas fa-info"></i> <?php _e('Company Info', 'newsletter') ?>
154
- <small><?php _e('Social, address, logo and general info', 'newsletter') ?></small></a>
155
- </li>
156
-
157
- <li>
158
- <a href="?page=newsletter_subscription_template"><i class="fas fa-file-alt"></i> <?php _e('Messages Template', 'newsletter') ?>
159
- <small><?php _e('Change the look of your service emails', 'newsletter') ?></small></a>
160
- </li>
161
-
162
- <?php
163
- newsletter_print_entries('settings');
164
- ?>
165
- </ul>
166
- </li>
167
-
168
- <?php if ($is_administrator) { ?>
169
- <li>
170
- <a href="#"><i class="fas fa-thermometer"></i> <?php _e('System', 'newsletter') ?>
171
- <?php if ($warning) { ?>
172
- <i class="fas fa-exclamation-triangle" style="color: red;"></i>
173
- <?php } ?>
174
- </a>
175
- <ul>
176
- <li>
177
- <a href="<?php echo admin_url('site-health.php') ?> "><i class="fas fa-file"></i> <?php _e('Site health') ?>
178
- <small><?php _e('WP native site health checks', 'newsletter') ?></small></a>
179
- </li>
180
- <li>
181
- <a href="?page=newsletter_system_delivery"><i class="fas fa-file"></i> <?php _e('Delivery Diagnostic', 'newsletter') ?>
182
- <small><?php _e('Delivery analysis and test', 'newsletter') ?></small></a>
183
- </li>
184
- <li>
185
- <a href="?page=newsletter_system_scheduler"><i class="fas fa-robot"></i> <?php _e('Scheduler', 'newsletter') ?>
186
- <small><?php _e('WordPress background jobs', 'newsletter') ?></small></a>
187
- </li>
188
- <li>
189
- <a href="?page=newsletter_system_status"><i class="fas fa-file"></i> <?php _e('Status', 'newsletter') ?>
190
- <small><?php _e('Checks and parameters', 'newsletter') ?></small></a>
191
- </li>
192
-
193
- <li>
194
- <a href="?page=newsletter_system_logs"><i class="fas fa-file"></i> <?php _e('Logs', 'newsletter') ?>
195
- <small><?php _e('Plugin and addons logs', 'newsletter') ?></small></a>
196
- </li>
197
- <li>
198
- <a href="https://www.thenewsletterplugin.com/documentation/developers/backup-recovery/" target="_blank"><i class="fas fa-file"></i> <?php _e('Backup', 'newsletter') ?>
199
- <small><?php _e('How to backup, recovery, delete', 'newsletter') ?></small></a>
200
- </li>
201
- </ul>
202
- </li>
203
- <?php } ?>
204
-
205
- <?php
206
- $license_data = Newsletter::instance()->get_license_data();
207
- $premium_url = 'https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=plugin&utm_content=' . urlencode($_GET['page']);
208
- ?>
209
-
210
- <?php if (empty($license_data)) { ?>
211
- <?php if (time() < 1638226799) { ?>
212
- <li class="tnp-professional-extensions-button" style="background-color: #000; color: #fff; border-color: #FF5F65!important; border-width: 2px !important;"><a href="https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=black-friday" target="_blank">
213
- <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
214
- </li>
215
- <?php } else { ?>
216
- <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
217
- <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
218
- </li>
219
- <?php } ?>
220
- <?php } elseif (is_wp_error($license_data)) { ?>
221
- <li class="tnp-professional-extensions-button-red">
222
- <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('Unable to check', 'newsletter') ?></a>
223
- </li>
224
-
225
- <?php } elseif ($license_data->expire == 0) { ?>
226
- <?php if (time() < 1638226799) { ?>
227
- <li class="tnp-professional-extensions-button" style="background-color: #000; color: #fff; border-color: #FF5F65!important; border-width: 2px !important;"><a href="https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=black-friday" target="_blank">
228
- <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
229
- </li>
230
- <?php } else { ?>
231
- <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
232
- <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
233
- </li>
234
- <?php } ?>
235
-
236
- <?php } elseif ($license_data->expire < time()) { ?>
237
-
238
- <li class="tnp-professional-extensions-button-red">
239
- <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('License expired', 'newsletter') ?></a>
240
- </li>
241
-
242
- <?php } elseif ($license_data->expire >= time()) { ?>
243
-
244
- <?php $p = class_exists('NewsletterExtensions') ? 'newsletter_extensions_index' : 'newsletter_main_extensions'; ?>
245
- <li class="tnp-professional-extensions-button">
246
- <a href="?page=<?php echo $p ?>"><i class="fas fa-check-square"></i> <?php _e('License active', 'newsletter') ?></a>
247
- </li>
248
-
249
- <?php } ?>
250
- </ul>
251
- </div>
252
-
253
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-shortcode'])) { ?>
254
- <?php
255
- // Check of Newsletter dedicated page
256
- if (!empty(Newsletter::instance()->options['page'])) {
257
- if (get_post_status(Newsletter::instance()->options['page']) === 'publish') {
258
- $content = get_post_field('post_content', Newsletter::instance()->options['page']);
259
- // With and without attributes
260
- if (strpos($content, '[newsletter]') === false && strpos($content, '[newsletter ') === false) {
261
- ?>
262
- <div class="tnp-notice">
263
- <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-shortcode' ?>" class="tnp-dismiss">&times;</a>
264
- The Newsletter dedicated page does not contain the [newsletter] shortcode. If you're using a visual composer it could be ok.
265
- <a href="<?php echo site_url('/wp-admin/post.php') ?>?post=<?php echo esc_attr(Newsletter::instance()->options['page']) ?>&action=edit"><strong>Edit the page</strong></a>.
266
-
267
- </div>
268
- <?php
269
- }
270
- }
271
- }
272
- ?>
273
- <?php } ?>
274
-
275
- <?php if (isset($_GET['debug']) || !isset($dismissed['rate']) && $user_count > 300) { ?>
276
- <div class="tnp-notice">
277
- <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=rate' ?>" class="tnp-dismiss">&times;</a>
278
-
279
- We never asked before and we're curious: <a href="http://wordpress.org/plugins/newsletter/" target="_blank">would you rate this plugin</a>?
280
- (few seconds required - account on WordPress.org required, every blog owner should have one...). <strong>Really appreciated, The Newsletter Team</strong>.
281
-
282
- </div>
283
- <?php } ?>
284
-
285
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-page']) && empty(Newsletter::instance()->options['page'])) { ?>
286
- <div class="tnp-notice">
287
- <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-page' ?>" class="tnp-dismiss">&times;</a>
288
-
289
- You should create a blog page to show the subscription form and the subscription messages. Go to the
290
- <a href="?page=newsletter_main_main">general settings panel</a> to configure it.
291
-
292
- </div>
293
- <?php } ?>
294
-
295
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-subscribe']) && get_option('newsletter_install_time') && get_option('newsletter_install_time') < time() - 86400 * 15) { ?>
296
- <div class="tnp-notice">
297
- <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-subscribe' ?>" class="tnp-dismiss">&times;</a>
298
- Subscribe to our news, promotions and getting started lessons!
299
- Proceeding you agree to the <a href="https://www.thenewsletterplugin.com/privacy" target="_blank">privacy policy</a>.
300
- <br>
301
- <form action="https://www.thenewsletterplugin.com/?na=s" target="_blank" method="post">
302
- <input type="hidden" value="plugin-header" name="nr">
303
- <input type="hidden" value="3" name="nl[]">
304
- <input type="hidden" value="1" name="nl[]">
305
- <input type="hidden" value="double" name="optin">
306
- <input type="email" name="ne" value="<?php echo esc_attr($current_user_email) ?>">
307
- <input type="submit" value="<?php esc_attr_e('Subscribe', 'newsletter') ?>">
308
- </form>
309
- </div>
310
- <?php } ?>
311
-
312
- <?php
313
- if (!defined('NEWSLETTER_CRON_WARNINGS') || NEWSLETTER_CRON_WARNINGS) {
314
- $x = NewsletterSystem::instance()->get_job_status();
315
- if ($x !== NewsletterSystem::JOB_OK) {
316
- echo '<div class="tnpc-warning">There are issues with the delivery engine. Please <a href="?page=newsletter_system_scheduler">check them here</a>.</div>';
317
- }
318
- }
319
- ?>
320
-
321
- <?php
322
- if ($_GET['page'] !== 'newsletter_emails_edit') {
323
-
324
- $last_failed_newsletters = Newsletter::instance()->get_emails_by_status(TNP_Email::STATUS_ERROR);
325
- if ($last_failed_newsletters) {
326
- $c = new NewsletterControls();
327
- foreach ($last_failed_newsletters as $n) {
328
- echo '<div class="tnpc-error">';
329
- printf(__('Newsletter "%s" stopped by fatal error.', 'newsletter'), esc_html($n->subject));
330
- echo '&nbsp;';
331
-
332
- $c->btn_link('?page=newsletter_emails_edit&id=' . $n->id, __('Check', 'newsletter'));
333
- echo '</div>';
334
- }
335
- }
336
- }
337
- ?>
338
-
339
- <div id="tnp-notification">
340
- <?php
341
- if (isset($controls)) {
342
- $controls->show();
343
- $controls->messages = '';
344
- $controls->errors = '';
345
- }
346
- ?>
347
- </div>
348
-
349
-
1
+ <?php
2
+ global $current_user, $wpdb;
3
+
4
+ defined('ABSPATH') || exit;
5
+
6
+ $dismissed = get_option('newsletter_dismissed', []);
7
+
8
+ $user_count = Newsletter::instance()->get_user_count();
9
+
10
+ $is_administrator = current_user_can('administrator');
11
+
12
+ function newsletter_print_entries($group) {
13
+ $entries = apply_filters('newsletter_menu_' . $group, array());
14
+ if (!$entries) {
15
+ return;
16
+ }
17
+
18
+ foreach ($entries as &$entry) {
19
+ echo '<li><a href="', $entry['url'], '">', $entry['label'];
20
+ if (!empty($entry['description'])) {
21
+ echo '<small>', $entry['description'], '</small>';
22
+ }
23
+ echo '</a></li>';
24
+ }
25
+ }
26
+
27
+ // Check the status to show a warning if needed
28
+ $status_options = Newsletter::instance()->get_options('status');
29
+ $warning = false;
30
+
31
+ //$warning |= empty($status_options['mail']);
32
+
33
+ $current_user_email = ''; //$current_user->user_email;
34
+ //if (strpos($current_user_email, 'admin@') === 0) {
35
+ // $current_user_email = '';
36
+ //}
37
+ ?>
38
+
39
+ <div class="tnp-drowpdown" id="tnp-header">
40
+ <a href="?page=newsletter_main_index"><img src="<?php echo plugins_url('newsletter'); ?>/admin/images/logo-red.png" class="tnp-header-logo" style="vertical-align: bottom;"></a>
41
+ <ul>
42
+ <li><a href="#"><i class="fas fa-users"></i> <?php _e('Subscribers', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
43
+ <ul>
44
+ <li>
45
+ <a href="?page=newsletter_users_index"><i class="fas fa-search"></i> <?php _e('Search And Edit', 'newsletter') ?>
46
+ <small><?php _e('Add, edit, search', 'newsletter') ?></small></a>
47
+ </li>
48
+
49
+ <li>
50
+ <a href="?page=newsletter_profile_index"><i class="fas fa-user-circle"></i> <?php _e('Profile page', 'newsletter') ?>
51
+ <small><?php _e('The subscriber personal profile editing panel', 'newsletter') ?></small></a>
52
+ </li>
53
+
54
+ <?php if (!class_exists('NewsletterImport')) { ?>
55
+ <li>
56
+ <a href="?page=newsletter_users_import"><i class="fas fa-upload"></i> <?php _e('Import', 'newsletter') ?>
57
+ <small><?php _e('Import from external sources', 'newsletter') ?></small></a>
58
+ </li>
59
+ <?php } ?>
60
+
61
+ <li>
62
+ <a href="?page=newsletter_users_export"><i class="fas fa-download"></i> <?php _e('Export', 'newsletter') ?>
63
+ <small><?php _e('Export your subscribers list', 'newsletter') ?></small></a>
64
+ </li>
65
+
66
+ <li>
67
+ <a href="?page=newsletter_users_massive"><i class="fas fa-wrench"></i> <?php _e('Maintenance', 'newsletter') ?>
68
+ <small><?php _e('Massive actions: change list, clean up, ...', 'newsletter') ?></small></a>
69
+ </li>
70
+
71
+ <li>
72
+ <a href="?page=newsletter_users_statistics"><i class="fas fa-chart-bar"></i> <?php _e('Statistics', 'newsletter') ?>
73
+ <small><?php _e('All about your subscribers', 'newsletter') ?></small></a>
74
+ </li>
75
+
76
+ <?php newsletter_print_entries('subscribers') ?>
77
+ </ul>
78
+ </li>
79
+ <li><a href="#"><i class="fas fa-list"></i> <?php _e('List Building', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
80
+ <ul>
81
+ <li>
82
+ <a href="?page=newsletter_subscription_profile"><i class="fas fa-check-square"></i> <?php _e('Subscription Form Fields, Buttons, Labels', 'newsletter') ?>
83
+ <small><?php _e('When and what data to collect', 'newsletter') ?></small></a>
84
+ </li>
85
+
86
+ <li>
87
+ <a href="?page=newsletter_subscription_options"><i class="fas fa-sign-in-alt"></i> <?php _e('Subscription', 'newsletter') ?>
88
+ <small><?php _e('The subscription process in detail', 'newsletter') ?></small></a>
89
+ </li>
90
+
91
+ <li>
92
+ <a href="?page=newsletter_subscription_lists"><i class="fas fa-th-list"></i> <?php _e('Lists', 'newsletter') ?>
93
+ <small><?php _e('Profile the subscribers for a better targeting', 'newsletter') ?></small></a>
94
+ </li>
95
+
96
+ <li>
97
+ <a href="?page=newsletter_subscription_antibot"><i class="fas fa-lock"></i> <?php _e('Security', 'newsletter') ?>
98
+ <small><?php _e('Spam subscriptions control', 'newsletter') ?></small></a>
99
+ </li>
100
+
101
+ <li>
102
+ <a href="?page=newsletter_unsubscription_index"><i class="fas fa-sign-out-alt"></i> <?php _e('Unsubscription', 'newsletter') ?>
103
+ <small><?php _e('How to give the last goodbye (or avoid it!)', 'newsletter') ?></small></a>
104
+ </li>
105
+
106
+ <?php
107
+ newsletter_print_entries('subscription');
108
+ ?>
109
+ </ul>
110
+ </li>
111
+
112
+ <li>
113
+ <a href="#"><i class="fas fa-newspaper"></i> <?php _e('Newsletters', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
114
+ <ul>
115
+ <li>
116
+ <a href="?page=newsletter_emails_composer"><i class="fas fa-plus"></i> <?php _e('Create newsletter', 'newsletter') ?>
117
+ <small><?php _e('Start your new campaign', 'newsletter') ?></small></a>
118
+ </li>
119
+
120
+ <li>
121
+ <a href="?page=newsletter_emails_index"><i class="fas fa-newspaper"></i> <?php _e('Newsletters', 'newsletter') ?>
122
+ <small><?php _e('The classic "write & send" newsletters', 'newsletter') ?></small></a>
123
+ </li>
124
+
125
+ <li>
126
+ <a href="<?php echo NewsletterStatistics::instance()->get_index_url() ?>"><i class="fas fa-chart-bar"></i> <?php _e('Statistics', 'newsletter') ?>
127
+ <small><?php _e('Tracking configuration and basic data', 'newsletter') ?></small></a>
128
+ </li>
129
+ <?php
130
+ newsletter_print_entries('newsletters');
131
+ ?>
132
+ </ul>
133
+ </li>
134
+
135
+ <li>
136
+ <a href="#"><i class="fas fa-cog"></i> <?php _e('Settings', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
137
+ <ul>
138
+ <?php if ($is_administrator) { ?>
139
+ <li>
140
+ <a href="?page=newsletter_main_main"><i class="fas fa-cogs"></i> <?php _e('General Settings', 'newsletter') ?>
141
+ <small><?php _e('Delivery speed, sender details, ...', 'newsletter') ?></small></a>
142
+ </li>
143
+ <?php if (!class_exists('NewsletterSmtp')) { ?>
144
+ <li>
145
+ <a href="?page=newsletter_main_smtp"><i class="fas fa-server"></i> <?php _e('SMTP', 'newsletter') ?>
146
+ <small><?php _e('External mail server', 'newsletter') ?></small>
147
+ </a>
148
+ </li>
149
+ <?php } ?>
150
+ <?php } ?>
151
+
152
+ <li>
153
+ <a href="?page=newsletter_main_info"><i class="fas fa-info"></i> <?php _e('Company Info', 'newsletter') ?>
154
+ <small><?php _e('Social, address, logo and general info', 'newsletter') ?></small></a>
155
+ </li>
156
+
157
+ <li>
158
+ <a href="?page=newsletter_subscription_template"><i class="fas fa-file-alt"></i> <?php _e('Messages Template', 'newsletter') ?>
159
+ <small><?php _e('Change the look of your service emails', 'newsletter') ?></small></a>
160
+ </li>
161
+
162
+ <?php
163
+ newsletter_print_entries('settings');
164
+ ?>
165
+ </ul>
166
+ </li>
167
+
168
+ <?php if ($is_administrator) { ?>
169
+ <li>
170
+ <a href="#"><i class="fas fa-thermometer"></i> <?php _e('System', 'newsletter') ?>
171
+ <?php if ($warning) { ?>
172
+ <i class="fas fa-exclamation-triangle" style="color: red;"></i>
173
+ <?php } ?>
174
+ </a>
175
+ <ul>
176
+ <li>
177
+ <a href="<?php echo admin_url('site-health.php') ?> "><i class="fas fa-file"></i> <?php _e('Site health') ?>
178
+ <small><?php _e('WP native site health checks', 'newsletter') ?></small></a>
179
+ </li>
180
+ <li>
181
+ <a href="?page=newsletter_system_delivery"><i class="fas fa-file"></i> <?php _e('Delivery Diagnostic', 'newsletter') ?>
182
+ <small><?php _e('Delivery analysis and test', 'newsletter') ?></small></a>
183
+ </li>
184
+ <li>
185
+ <a href="?page=newsletter_system_scheduler"><i class="fas fa-robot"></i> <?php _e('Scheduler', 'newsletter') ?>
186
+ <small><?php _e('WordPress background jobs', 'newsletter') ?></small></a>
187
+ </li>
188
+ <li>
189
+ <a href="?page=newsletter_system_status"><i class="fas fa-file"></i> <?php _e('Status', 'newsletter') ?>
190
+ <small><?php _e('Checks and parameters', 'newsletter') ?></small></a>
191
+ </li>
192
+
193
+ <li>
194
+ <a href="?page=newsletter_system_logs"><i class="fas fa-file"></i> <?php _e('Logs', 'newsletter') ?>
195
+ <small><?php _e('Plugin and addons logs', 'newsletter') ?></small></a>
196
+ </li>
197
+ <li>
198
+ <a href="https://www.thenewsletterplugin.com/documentation/developers/backup-recovery/" target="_blank"><i class="fas fa-file"></i> <?php _e('Backup', 'newsletter') ?>
199
+ <small><?php _e('How to backup, recovery, delete', 'newsletter') ?></small></a>
200
+ </li>
201
+ </ul>
202
+ </li>
203
+ <?php } ?>
204
+
205
+ <?php
206
+ $license_data = Newsletter::instance()->get_license_data();
207
+ $premium_url = 'https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=plugin&utm_content=' . urlencode($_GET['page']);
208
+ ?>
209
+
210
+ <?php if (empty($license_data)) { ?>
211
+ <?php if (time() < 1638226799) { ?>
212
+ <li class="tnp-professional-extensions-button" style="background-color: #000; color: #fff; border-color: #FF5F65!important; border-width: 2px !important;"><a href="https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=black-friday" target="_blank">
213
+ <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
214
+ </li>
215
+ <?php } else { ?>
216
+ <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
217
+ <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
218
+ </li>
219
+ <?php } ?>
220
+ <?php } elseif (is_wp_error($license_data)) { ?>
221
+ <li class="tnp-professional-extensions-button-red">
222
+ <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('Unable to check', 'newsletter') ?></a>
223
+ </li>
224
+
225
+ <?php } elseif ($license_data->expire == 0) { ?>
226
+ <?php if (time() < 1638226799) { ?>
227
+ <li class="tnp-professional-extensions-button" style="background-color: #000; color: #fff; border-color: #FF5F65!important; border-width: 2px !important;"><a href="https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=black-friday" target="_blank">
228
+ <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
229
+ </li>
230
+ <?php } else { ?>
231
+ <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
232
+ <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
233
+ </li>
234
+ <?php } ?>
235
+
236
+ <?php } elseif ($license_data->expire < time()) { ?>
237
+
238
+ <li class="tnp-professional-extensions-button-red">
239
+ <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('License expired', 'newsletter') ?></a>
240
+ </li>
241
+
242
+ <?php } elseif ($license_data->expire >= time()) { ?>
243
+
244
+ <?php $p = class_exists('NewsletterExtensions') ? 'newsletter_extensions_index' : 'newsletter_main_extensions'; ?>
245
+ <li class="tnp-professional-extensions-button">
246
+ <a href="?page=<?php echo $p ?>"><i class="fas fa-check-square"></i> <?php _e('License active', 'newsletter') ?></a>
247
+ </li>
248
+
249
+ <?php } ?>
250
+ </ul>
251
+ </div>
252
+
253
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-shortcode'])) { ?>
254
+ <?php
255
+ // Check of Newsletter dedicated page
256
+ if (!empty(Newsletter::instance()->options['page'])) {
257
+ if (get_post_status(Newsletter::instance()->options['page']) === 'publish') {
258
+ $content = get_post_field('post_content', Newsletter::instance()->options['page']);
259
+ // With and without attributes
260
+ if (strpos($content, '[newsletter]') === false && strpos($content, '[newsletter ') === false) {
261
+ ?>
262
+ <div class="tnp-notice">
263
+ <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-shortcode' ?>" class="tnp-dismiss">&times;</a>
264
+ The Newsletter dedicated page does not contain the [newsletter] shortcode. If you're using a visual composer it could be ok.
265
+ <a href="<?php echo site_url('/wp-admin/post.php') ?>?post=<?php echo esc_attr(Newsletter::instance()->options['page']) ?>&action=edit"><strong>Edit the page</strong></a>.
266
+
267
+ </div>
268
+ <?php
269
+ }
270
+ }
271
+ }
272
+ ?>
273
+ <?php } ?>
274
+
275
+ <?php if (isset($_GET['debug']) || !isset($dismissed['rate']) && $user_count > 300) { ?>
276
+ <div class="tnp-notice">
277
+ <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=rate' ?>" class="tnp-dismiss">&times;</a>
278
+
279
+ We never asked before and we're curious: <a href="http://wordpress.org/plugins/newsletter/" target="_blank">would you rate this plugin</a>?
280
+ (few seconds required - account on WordPress.org required, every blog owner should have one...). <strong>Really appreciated, The Newsletter Team</strong>.
281
+
282
+ </div>
283
+ <?php } ?>
284
+
285
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-page']) && empty(Newsletter::instance()->options['page'])) { ?>
286
+ <div class="tnp-notice">
287
+ <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-page' ?>" class="tnp-dismiss">&times;</a>
288
+
289
+ You should create a blog page to show the subscription form and the subscription messages. Go to the
290
+ <a href="?page=newsletter_main_main">general settings panel</a> to configure it.
291
+
292
+ </div>
293
+ <?php } ?>
294
+
295
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-subscribe']) && get_option('newsletter_install_time') && get_option('newsletter_install_time') < time() - 86400 * 15) { ?>
296
+ <div class="tnp-notice">
297
+ <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-subscribe' ?>" class="tnp-dismiss">&times;</a>
298
+ Subscribe to our news, promotions and getting started lessons!
299
+ Proceeding you agree to the <a href="https://www.thenewsletterplugin.com/privacy" target="_blank">privacy policy</a>.
300
+ <br>
301
+ <form action="https://www.thenewsletterplugin.com/?na=s" target="_blank" method="post">
302
+ <input type="hidden" value="plugin-header" name="nr">
303
+ <input type="hidden" value="3" name="nl[]">
304
+ <input type="hidden" value="1" name="nl[]">
305
+ <input type="hidden" value="double" name="optin">
306
+ <input type="email" name="ne" value="<?php echo esc_attr($current_user_email) ?>">
307
+ <input type="submit" value="<?php esc_attr_e('Subscribe', 'newsletter') ?>">
308
+ </form>
309
+ </div>
310
+ <?php } ?>
311
+
312
+ <?php
313
+ if (!defined('NEWSLETTER_CRON_WARNINGS') || NEWSLETTER_CRON_WARNINGS) {
314
+ $x = NewsletterSystem::instance()->get_job_status();
315
+ if ($x !== NewsletterSystem::JOB_OK) {
316
+ echo '<div class="tnpc-warning">There are issues with the delivery engine. Please <a href="?page=newsletter_system_scheduler">check them here</a>.</div>';
317
+ }
318
+ }
319
+ ?>
320
+
321
+ <?php
322
+ if ($_GET['page'] !== 'newsletter_emails_edit') {
323
+
324
+ $last_failed_newsletters = Newsletter::instance()->get_emails_by_status(TNP_Email::STATUS_ERROR);
325
+ if ($last_failed_newsletters) {
326
+ $c = new NewsletterControls();
327
+ foreach ($last_failed_newsletters as $n) {
328
+ echo '<div class="tnpc-error">';
329
+ printf(__('Newsletter "%s" stopped by fatal error.', 'newsletter'), esc_html($n->subject));
330
+ echo '&nbsp;';
331
+
332
+ $c->btn_link('?page=newsletter_emails_edit&id=' . $n->id, __('Check', 'newsletter'));
333
+ echo '</div>';
334
+ }
335
+ }
336
+ }
337
+ ?>
338
+
339
+ <div id="tnp-notification">
340
+ <?php
341
+ if (isset($controls)) {
342
+ $controls->show();
343
+ $controls->messages = '';
344
+ $controls->errors = '';
345
+ }
346
+ ?>
347
+ </div>
348
+
349
+
users/index.php CHANGED
@@ -1,225 +1,226 @@
1
- <?php
2
- /* @var $this NewsletterUsers */
3
- defined('ABSPATH') || exit;
4
-
5
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
-
7
- $controls = new NewsletterControls();
8
-
9
- $options = $controls->data;
10
- $options_profile = get_option('newsletter_profile');
11
- $options_main = get_option('newsletter_main');
12
-
13
- // Move to base zero
14
- if ($controls->is_action()) {
15
- if ($controls->is_action('reset')) {
16
- $controls->data = array();
17
- } else {
18
- $controls->data['search_page'] = (int) $controls->data['search_page'] - 1;
19
- }
20
- $this->save_options($controls->data, 'search');
21
- } else {
22
- $controls->data = $this->get_options('search');
23
- if (empty($controls->data['search_page']))
24
- $controls->data['search_page'] = 0;
25
- }
26
-
27
- if ($controls->is_action('resend')) {
28
- $user = $this->get_user($controls->button_data);
29
- NewsletterSubscription::instance()->send_message('confirmation', $user, true);
30
- $controls->messages = __('Activation email sent.', 'newsletter');
31
- }
32
-
33
- if ($controls->is_action('resend_welcome')) {
34
- $user = $this->get_user($controls->button_data);
35
- NewsletterSubscription::instance()->send_message('confirmed', $user, true);
36
- $controls->messages = __('Welcome email sent.', 'newsletter');
37
- }
38
-
39
- if ($controls->is_action('delete')) {
40
- $this->delete_user($controls->button_data);
41
- unset($controls->data['subscriber_id']);
42
- }
43
-
44
- if ($controls->is_action('delete_selected')) {
45
- $r = Newsletter::instance()->delete_user($_POST['ids']);
46
- $controls->messages .= $r . ' user(s) deleted';
47
- }
48
-
49
- // We build the query condition
50
- $where = 'where 1=1';
51
- $query_args = array();
52
- $text = trim($controls->get_value('search_text'));
53
- if ($text) {
54
- $query_args[] = '%' . $text . '%';
55
- $query_args[] = '%' . $text . '%';
56
- $query_args[] = '%' . $text . '%';
57
- $where .= " and (email like %s or name like %s or surname like %s)";
58
- }
59
-
60
- if (!empty($controls->data['search_status'])) {
61
- if ($controls->data['search_status'] == 'T') {
62
- $where .= " and test=1";
63
- } else {
64
- $query_args[] = $controls->data['search_status'];
65
- $where .= " and status=%s";
66
- }
67
- }
68
-
69
- if (!empty($controls->data['search_list'])) {
70
- $where .= " and list_" . ((int) $controls->data['search_list']) . "=1";
71
- }
72
-
73
- $filtered = $where != 'where 1=1';
74
-
75
- // Total items, total pages
76
- $items_per_page = 20;
77
- if (!empty($query_args)) {
78
- $where = $wpdb->prepare($where, $query_args);
79
- }
80
- $count = Newsletter::instance()->store->get_count(NEWSLETTER_USERS_TABLE, $where);
81
- $last_page = floor($count / $items_per_page) - ($count % $items_per_page == 0 ? 1 : 0);
82
- if ($last_page < 0)
83
- $last_page = 0;
84
-
85
- if ($controls->is_action('last')) {
86
- $controls->data['search_page'] = $last_page;
87
- }
88
- if ($controls->is_action('first')) {
89
- $controls->data['search_page'] = 0;
90
- }
91
- if ($controls->is_action('next')) {
92
- $controls->data['search_page'] = (int) $controls->data['search_page'] + 1;
93
- }
94
- if ($controls->is_action('prev')) {
95
- $controls->data['search_page'] = (int) $controls->data['search_page'] - 1;
96
- }
97
- if ($controls->is_action('search')) {
98
- $controls->data['search_page'] = 0;
99
- }
100
-
101
- // Eventually fix the page
102
- if (!isset($controls->data['search_page']) || $controls->data['search_page'] < 0)
103
- $controls->data['search_page'] = 0;
104
- if ($controls->data['search_page'] > $last_page)
105
- $controls->data['search_page'] = $last_page;
106
-
107
- $query = "select * from " . NEWSLETTER_USERS_TABLE . ' ' . $where . " order by id desc";
108
- $query .= " limit " . ($controls->data['search_page'] * $items_per_page) . "," . $items_per_page;
109
- $list = $wpdb->get_results($query);
110
-
111
- // Move to base 1
112
- $controls->data['search_page'] ++;
113
- ?>
114
-
115
- <div class="wrap tnp-users tnp-users-index" id="tnp-wrap">
116
-
117
- <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
118
-
119
- <div id="tnp-heading">
120
-
121
- <h2><?php _e('Subscribers', 'newsletter') ?>
122
- <a class="tnp-btn-h1" href="?page=newsletter_users_new"><?php _e('Add a subscriber', 'newsletter') ?></a>
123
- </h2>
124
-
125
- <p>
126
- See the <a href="admin.php?page=newsletter_users_massive">maintenance panel</a> to move subscribers between list, massively delete and so on.
127
- </p>
128
-
129
- </div>
130
-
131
- <div id="tnp-body">
132
-
133
- <form id="channel" method="post" action="">
134
- <?php $controls->init(); ?>
135
-
136
- <div class="tnp-subscribers-search">
137
- <?php $controls->text('search_text', 45, __('Search text', 'newsletter')); ?>
138
-
139
- <?php _e('filter by', 'newsletter') ?>:
140
- <?php $controls->select('search_status', ['' => 'Any status', 'T' => 'Test subscribers', 'C' => 'Confirmed', 'S' => 'Not confirmed', 'U' => 'Unsubscribed', 'B' => 'Bounced', 'P'=> TNP_User::get_status_label('P')]); ?>
141
- <?php $controls->lists_select('search_list', '-'); ?>
142
-
143
- <?php $controls->button('search', __('Search', 'newsletter')); ?>
144
- <?php if ($where != "where 1=1") { ?>
145
- <?php $controls->button('reset', __('Reset Filters', 'newsletter')); ?>
146
- <?php } ?>
147
- <br>
148
- <?php $controls->checkbox('show_preferences', __('Show lists', 'newsletter')); ?>
149
- </div>
150
-
151
- <?php if ($filtered) { ?>
152
- <p><?php _e('The list below is filtered.', 'newsletter') ?></p>
153
- <?php } ?>
154
-
155
- <div class="tnp-paginator">
156
-
157
- <?php $controls->button('first', '«'); ?>
158
- <?php $controls->button('prev', '‹'); ?>
159
- <?php $controls->text('search_page', 3); ?> of <?php echo $last_page + 1 ?> <?php $controls->button('go', __('Go', 'newsletter')); ?>
160
- <?php $controls->button('next', '›'); ?>
161
- <?php $controls->button('last', '»'); ?>
162
-
163
- <?php echo $count ?> <?php _e('subscriber(s) found', 'newsletter') ?>
164
-
165
- <?php $controls->button_confirm('delete_selected', __('Delete selected', 'newsletter')); ?>
166
-
167
- </div>
168
-
169
- <table class="widefat">
170
- <thead>
171
- <tr>
172
- <td class="check-column"><input type="checkbox" onchange="jQuery('input.tnp-selector').prop('checked', this.checked)"></th>
173
- <th>Id</th>
174
- <th>Email</th>
175
- <th><?php _e('Name', 'newsletter') ?></th>
176
- <th><?php _e('Status', 'newsletter') ?></th>
177
- <?php if (isset($options['show_preferences']) && $options['show_preferences'] == 1) { ?>
178
- <th><?php _e('Lists', 'newsletter') ?></th>
179
- <?php } ?>
180
- <th>&nbsp;</th>
181
-
182
- <th>&nbsp;</th>
183
- </tr>
184
- </thead>
185
- <?php $i = 0; ?>
186
- <?php foreach ($list as $s) { ?>
187
- <tr>
188
- <th scope="row" class="check-column" style="vertical-align: middle"><input class="tnp-selector" type="checkbox" name="ids[]" value="<?php echo $s->id; ?>"/></td>
189
- <td><?php echo $s->id; ?></td>
190
- <td><?php echo esc_html($s->email); ?></td>
191
- <td><?php echo esc_html($s->name); ?> <?php echo esc_html($s->surname); ?></td>
192
- <td><small><?php echo $this->get_user_status_label($s, true) ?></small></td>
193
- <?php if (isset($options['show_preferences']) && $options['show_preferences'] == 1) { ?>
194
- <td><small><?php
195
- $lists = $this->get_lists();
196
- foreach ($lists as $item) {
197
- $l = 'list_' . $item->id;
198
- if ($s->$l == 1)
199
- echo esc_html($item->name) . '<br>';
200
- }
201
- ?></small></td>
202
- <?php } ?>
203
- <td><?php $controls->button_icon_edit($this->get_admin_page_url('edit') . '&amp;id=' . $s->id)?></td>
204
- <td style="white-space: nowrap"><?php $controls->button_icon_delete($s->id); ?>
205
- <?php if ($s->status == "C") { ?>
206
- <?php $controls->button_icon('resend_welcome', 'fa-redo', __('Resend welcome', 'newsletter'), $s->id, true); ?>
207
- <?php } else { ?>
208
- <?php $controls->button_icon('resend', 'fa-redo', __('Resend activation', 'newsletter'), $s->id, true); ?>
209
- <?php } ?></td>
210
- </tr>
211
- <?php } ?>
212
- </table>
213
- <div class="tnp-paginator">
214
-
215
- <?php $controls->button('first', '«'); ?>
216
- <?php $controls->button('prev', '‹'); ?>
217
- <?php $controls->button('next', '›'); ?>
218
- <?php $controls->button('last', '»'); ?>
219
- </div>
220
- </form>
221
- </div>
222
-
223
- <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
224
-
225
- </div>
 
1
+ <?php
2
+ /* @var $this NewsletterUsers */
3
+ defined('ABSPATH') || exit;
4
+
5
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
+
7
+ $controls = new NewsletterControls();
8
+
9
+ $options = $controls->data;
10
+ $options_profile = get_option('newsletter_profile');
11
+ $options_main = get_option('newsletter_main');
12
+
13
+ // Move to base zero
14
+ if ($controls->is_action()) {
15
+ if ($controls->is_action('reset')) {
16
+ $controls->data = array();
17
+ } else {
18
+ $controls->data['search_page'] = (int) $controls->data['search_page'] - 1;
19
+ }
20
+ $this->save_options($controls->data, 'search');
21
+ } else {
22
+ $controls->data = $this->get_options('search');
23
+ if (empty($controls->data['search_page']))
24
+ $controls->data['search_page'] = 0;
25
+ }
26
+
27
+ if ($controls->is_action('resend')) {
28
+ $user = $this->get_user($controls->button_data);
29
+ NewsletterSubscription::instance()->send_message('confirmation', $user, true);
30
+ $controls->messages = __('Activation email sent.', 'newsletter');
31
+ }
32
+
33
+ if ($controls->is_action('resend_welcome')) {
34
+ $user = $this->get_user($controls->button_data);
35
+ NewsletterSubscription::instance()->send_message('confirmed', $user, true);
36
+ $controls->messages = __('Welcome email sent.', 'newsletter');
37
+ }
38
+
39
+ if ($controls->is_action('delete')) {
40
+ $this->delete_user($controls->button_data);
41
+ unset($controls->data['subscriber_id']);
42
+ }
43
+
44
+ if ($controls->is_action('delete_selected')) {
45
+ $r = Newsletter::instance()->delete_user($_POST['ids']);
46
+ $controls->messages .= $r . ' user(s) deleted';
47
+ }
48
+
49
+ // We build the query condition
50
+ $where = 'where 1=1';
51
+ $query_args = array();
52
+ $text = trim($controls->get_value('search_text'));
53
+ if ($text) {
54
+ $query_args[] = '%' . $text . '%';
55
+ $query_args[] = '%' . $text . '%';
56
+ $query_args[] = '%' . $text . '%';
57
+ $where .= " and (email like %s or name like %s or surname like %s)";
58
+ }
59
+
60
+ if (!empty($controls->data['search_status'])) {
61
+ if ($controls->data['search_status'] == 'T') {
62
+ $where .= " and test=1";
63
+ } else {
64
+ $query_args[] = $controls->data['search_status'];
65
+ $where .= " and status=%s";
66
+ }
67
+ }
68
+
69
+ if (!empty($controls->data['search_list'])) {
70
+ $where .= " and list_" . ((int) $controls->data['search_list']) . "=1";
71
+ }
72
+
73
+ $filtered = $where != 'where 1=1';
74
+
75
+ // Total items, total pages
76
+ $items_per_page = 20;
77
+ if (!empty($query_args)) {
78
+ $where = $wpdb->prepare($where, $query_args);
79
+ }
80
+ $count = Newsletter::instance()->store->get_count(NEWSLETTER_USERS_TABLE, $where);
81
+ $last_page = floor($count / $items_per_page) - ($count % $items_per_page == 0 ? 1 : 0);
82
+ if ($last_page < 0)
83
+ $last_page = 0;
84
+
85
+ if ($controls->is_action('last')) {
86
+ $controls->data['search_page'] = $last_page;
87
+ }
88
+ if ($controls->is_action('first')) {
89
+ $controls->data['search_page'] = 0;
90
+ }
91
+ if ($controls->is_action('next')) {
92
+ $controls->data['search_page'] = (int) $controls->data['search_page'] + 1;
93
+ }
94
+ if ($controls->is_action('prev')) {
95
+ $controls->data['search_page'] = (int) $controls->data['search_page'] - 1;
96
+ }
97
+ if ($controls->is_action('search')) {
98
+ $controls->data['search_page'] = 0;
99
+ }
100
+
101
+ // Eventually fix the page
102
+ if (!isset($controls->data['search_page']) || $controls->data['search_page'] < 0)
103
+ $controls->data['search_page'] = 0;
104
+ if ($controls->data['search_page'] > $last_page)
105
+ $controls->data['search_page'] = $last_page;
106
+
107
+ $query = "select * from " . NEWSLETTER_USERS_TABLE . ' ' . $where . " order by id desc";
108
+ $query .= " limit " . ($controls->data['search_page'] * $items_per_page) . "," . $items_per_page;
109
+ $list = $wpdb->get_results($query);
110
+
111
+ // Move to base 1
112
+ $controls->data['search_page'] ++;
113
+ ?>
114
+
115
+ <div class="wrap tnp-users tnp-users-index" id="tnp-wrap">
116
+
117
+ <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
118
+
119
+ <div id="tnp-heading">
120
+
121
+ <h2><?php _e('Subscribers', 'newsletter') ?>
122
+ <a class="tnp-btn-h1" href="?page=newsletter_users_new"><?php _e('Add a subscriber', 'newsletter') ?></a>
123
+ </h2>
124
+
125
+ <p>
126
+ See the <a href="admin.php?page=newsletter_users_massive">maintenance panel</a> to move subscribers between list, massively delete and so on.
127
+ </p>
128
+
129
+ </div>
130
+
131
+ <div id="tnp-body">
132
+
133
+ <form id="channel" method="post" action="">
134
+ <?php $controls->init(); ?>
135
+
136
+ <div class="tnp-subscribers-search">
137
+ <?php $controls->text('search_text', 45, __('Search text', 'newsletter')); ?>
138
+
139
+ <?php _e('filter by', 'newsletter') ?>:
140
+ <?php $controls->select('search_status', ['' => __('Any', 'newsletter'), 'T' => __('Test subscribers', 'newsletter'), 'C' => TNP_User::get_status_label('C'),
141
+ 'S' => TNP_User::get_status_label('S'), 'U' => TNP_User::get_status_label('U'), 'B' => TNP_User::get_status_label('B'), 'P'=> TNP_User::get_status_label('P')]); ?>
142
+ <?php $controls->lists_select('search_list', '-'); ?>
143
+
144
+ <?php $controls->button('search', __('Search', 'newsletter')); ?>
145
+ <?php if ($where != "where 1=1") { ?>
146
+ <?php $controls->button('reset', __('Reset Filters', 'newsletter')); ?>
147
+ <?php } ?>
148
+ <br>
149
+ <?php $controls->checkbox('show_preferences', __('Show lists', 'newsletter')); ?>
150
+ </div>
151
+
152
+ <?php if ($filtered) { ?>
153
+ <p><?php _e('The list below is filtered.', 'newsletter') ?></p>
154
+ <?php } ?>
155
+
156
+ <div class="tnp-paginator">
157
+
158
+ <?php $controls->button('first', '«'); ?>
159
+ <?php $controls->button('prev', '‹'); ?>
160
+ <?php $controls->text('search_page', 3); ?> of <?php echo $last_page + 1 ?> <?php $controls->button('go', __('Go', 'newsletter')); ?>
161
+ <?php $controls->button('next', '›'); ?>
162
+ <?php $controls->button('last', '»'); ?>
163
+
164
+ <?php echo $count ?> <?php _e('subscriber(s) found', 'newsletter') ?>
165
+
166
+ <?php $controls->button_confirm('delete_selected', __('Delete selected', 'newsletter')); ?>
167
+
168
+ </div>
169
+
170
+ <table class="widefat">
171
+ <thead>
172
+ <tr>
173
+ <td class="check-column"><input type="checkbox" onchange="jQuery('input.tnp-selector').prop('checked', this.checked)"></th>
174
+ <th>Id</th>
175
+ <th>Email</th>
176
+ <th><?php _e('Name', 'newsletter') ?></th>
177
+ <th><?php _e('Status', 'newsletter') ?></th>
178
+ <?php if (isset($options['show_preferences']) && $options['show_preferences'] == 1) { ?>
179
+ <th><?php _e('Lists', 'newsletter') ?></th>
180
+ <?php } ?>
181
+ <th>&nbsp;</th>
182
+
183
+ <th>&nbsp;</th>
184
+ </tr>
185
+ </thead>
186
+ <?php $i = 0; ?>
187
+ <?php foreach ($list as $s) { ?>
188
+ <tr>
189
+ <th scope="row" class="check-column" style="vertical-align: middle"><input class="tnp-selector" type="checkbox" name="ids[]" value="<?php echo $s->id; ?>"/></td>
190
+ <td><?php echo $s->id; ?></td>
191
+ <td><?php echo esc_html($s->email); ?></td>
192
+ <td><?php echo esc_html($s->name); ?> <?php echo esc_html($s->surname); ?></td>
193
+ <td><small><?php echo $this->get_user_status_label($s, true) ?></small></td>
194
+ <?php if (isset($options['show_preferences']) && $options['show_preferences'] == 1) { ?>
195
+ <td><small><?php
196
+ $lists = $this->get_lists();
197
+ foreach ($lists as $item) {
198
+ $l = 'list_' . $item->id;
199
+ if ($s->$l == 1)
200
+ echo esc_html($item->name) . '<br>';
201
+ }
202
+ ?></small></td>
203
+ <?php } ?>
204
+ <td><?php $controls->button_icon_edit($this->get_admin_page_url('edit') . '&amp;id=' . $s->id)?></td>
205
+ <td style="white-space: nowrap"><?php $controls->button_icon_delete($s->id); ?>
206
+ <?php if ($s->status == "C") { ?>
207
+ <?php $controls->button_icon('resend_welcome', 'fa-redo', __('Resend welcome', 'newsletter'), $s->id, true); ?>
208
+ <?php } else { ?>
209
+ <?php $controls->button_icon('resend', 'fa-redo', __('Resend activation', 'newsletter'), $s->id, true); ?>
210
+ <?php } ?></td>
211
+ </tr>
212
+ <?php } ?>
213
+ </table>
214
+ <div class="tnp-paginator">
215
+
216
+ <?php $controls->button('first', '«'); ?>
217
+ <?php $controls->button('prev', '‹'); ?>
218
+ <?php $controls->button('next', '›'); ?>
219
+ <?php $controls->button('last', '»'); ?>
220
+ </div>
221
+ </form>
222
+ </div>
223
+
224
+ <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
225
+
226
+ </div>
users/users.php CHANGED
@@ -1,252 +1,253 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- class NewsletterUsers extends NewsletterModule {
6
-
7
- static $instance;
8
-
9
- /**
10
- * @return NewsletterUsers
11
- */
12
- static function instance() {
13
- if (self::$instance == null) {
14
- self::$instance = new NewsletterUsers();
15
- }
16
-
17
- return self::$instance;
18
- }
19
-
20
- function __construct() {
21
- parent::__construct('users', '1.3.0');
22
- if (is_admin()) {
23
- add_action('wp_ajax_newsletter_users_export', array($this, 'hook_wp_ajax_newsletter_users_export'));
24
- }
25
- }
26
-
27
- function hook_wp_ajax_newsletter_users_export() {
28
-
29
- $newsletter = Newsletter::instance();
30
- if ($newsletter->is_allowed()) {
31
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
32
- $controls = new NewsletterControls();
33
-
34
- if ($controls->is_action('export')) {
35
- $this->export($controls->data);
36
- }
37
- } else {
38
- die('Not allowed.');
39
- }
40
- }
41
-
42
- function upgrade() {
43
- global $wpdb, $charset_collate;
44
-
45
- parent::upgrade();
46
-
47
- $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter` (
48
- `name` varchar(100) NOT NULL DEFAULT '',
49
- `email` varchar(100) NOT NULL DEFAULT '',
50
- `token` varchar(50) NOT NULL DEFAULT '',
51
- `language` varchar(10) NOT NULL DEFAULT '',
52
- `status` varchar(1) NOT NULL DEFAULT 'S',
53
- `id` int(11) NOT NULL AUTO_INCREMENT,
54
- `profile` mediumtext,
55
- `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
56
- `updated` int(11) NOT NULL DEFAULT '0',
57
- `last_activity` int(11) NOT NULL DEFAULT '0',
58
- `followup_step` tinyint(4) NOT NULL DEFAULT '0',
59
- `followup_time` bigint(20) NOT NULL DEFAULT '0',
60
- `followup` tinyint(4) NOT NULL DEFAULT '0',
61
- `surname` varchar(100) NOT NULL DEFAULT '',
62
- `sex` char(1) NOT NULL DEFAULT 'n',
63
- `feed_time` bigint(20) NOT NULL DEFAULT '0',
64
- `feed` tinyint(4) NOT NULL DEFAULT '0',
65
- `referrer` varchar(50) NOT NULL DEFAULT '',
66
- `ip` varchar(50) NOT NULL DEFAULT '',
67
- `wp_user_id` int(11) NOT NULL DEFAULT '0',
68
- `http_referer` varchar(255) NOT NULL DEFAULT '',
69
- `geo` tinyint(4) NOT NULL DEFAULT '0',
70
- `country` varchar(4) NOT NULL DEFAULT '',
71
- `region` varchar(100) NOT NULL DEFAULT '',
72
- `city` varchar(100) NOT NULL DEFAULT '',
73
- `bounce_type` varchar(50) NOT NULL DEFAULT '',
74
- `bounce_time` int(11) NOT NULL DEFAULT '0',
75
- `unsub_email_id` int(11) NOT NULL DEFAULT '0',
76
- `unsub_time` int(11) NOT NULL DEFAULT '0',\n";
77
-
78
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
79
- $sql .= "`list_$i` tinyint(4) NOT NULL DEFAULT '0',\n";
80
- }
81
-
82
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
83
- $sql .= "`profile_$i` varchar(255) NOT NULL DEFAULT '',\n";
84
- }
85
- // Leave as last
86
- $sql .= "`test` tinyint(4) NOT NULL DEFAULT '0',\n";
87
- $sql .= "PRIMARY KEY (`id`),\nUNIQUE KEY `email` (`email`),\nKEY `wp_user_id` (`wp_user_id`)\n) $charset_collate;";
88
-
89
- dbDelta($sql);
90
-
91
- if ($this->old_version < '1.2.7') {
92
- $this->query("update " . NEWSLETTER_USERS_TABLE . " set geo=1 where country<>''");
93
- }
94
- if ($this->old_version > '1.2.5' && $this->old_version < '1.2.9') {
95
- $this->upgrade_query("ALTER TABLE " . NEWSLETTER_USERS_TABLE . " DROP COLUMN last_ip;");
96
- }
97
- }
98
-
99
- function admin_menu() {
100
- $this->add_menu_page('index', __('Subscribers', 'newsletter'));
101
- $this->add_admin_page('new', __('New subscriber', 'newsletter'));
102
- $this->add_admin_page('edit', __('Subscriber Edit', 'newsletter'));
103
- $this->add_admin_page('massive', __('Subscribers Maintenance', 'newsletter'));
104
- $this->add_admin_page('export', __('Export', 'newsletter'));
105
- $this->add_admin_page('import', __('Import', 'newsletter'));
106
- $this->add_admin_page('statistics', __('Statistics', 'newsletter'));
107
- }
108
-
109
- function export($options = null) {
110
- global $wpdb;
111
-
112
- @setlocale(LC_CTYPE, 'en_US.UTF-8');
113
- header('Content-Type: application/octet-stream;charset=UTF-8');
114
- header('Content-Disposition: attachment; filename="newsletter-subscribers.csv"');
115
-
116
- // BOM
117
- echo "\xEF\xBB\xBF";
118
-
119
- $sep = ';';
120
- if ($options) {
121
- $sep = $options['separator'];
122
- }
123
- if ($sep == 'tab') {
124
- $sep = "\t";
125
- }
126
-
127
- // CSV header
128
- echo '"Email"' . $sep . '"Name"' . $sep . '"Surname"' . $sep . '"Gender"' . $sep . '"Status"' . $sep . '"Date"' . $sep . '"Token"' . $sep;
129
-
130
- // In table profiles
131
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
132
- echo '"Profile ' . $i . '"' . $sep; // To adjust with field name
133
- }
134
-
135
- // Lists
136
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
137
- echo '"List ' . $i . '"' . $sep;
138
- }
139
-
140
- echo '"Feed by mail"' . $sep . '"Follow up"' . $sep;
141
- echo '"IP"' . $sep . '"Referrer"' . $sep . '"Country"';
142
-
143
- echo "\n";
144
-
145
- $page = 0;
146
- while (true) {
147
- $query = "select * from " . NEWSLETTER_USERS_TABLE . "";
148
- $list = (int) $_POST['options']['list'];
149
- if (!empty($list)) {
150
- $query .= " where list_" . $list . "=1";
151
- }
152
- $recipients = $wpdb->get_results($query . " order by email limit " . $page * 500 . ",500");
153
- for ($i = 0; $i < count($recipients); $i++) {
154
- echo '"' . $recipients[$i]->email . '"' . $sep . '"' . $this->sanitize_csv($recipients[$i]->name) .
155
- '"' . $sep . '"' . $this->sanitize_csv($recipients[$i]->surname) .
156
- '"' . $sep . '"' . $recipients[$i]->sex .
157
- '"' . $sep . '"' . $recipients[$i]->status . '"' . $sep . '"' . $recipients[$i]->created . '"' . $sep . '"' . $recipients[$i]->token . '"' . $sep;
158
-
159
- for ($j = 1; $j <= NEWSLETTER_PROFILE_MAX; $j++) {
160
- $column = 'profile_' . $j;
161
- echo '"' . $this->sanitize_csv($recipients[$i]->$column) . '"' . $sep;
162
- }
163
-
164
- for ($j = 1; $j <= NEWSLETTER_LIST_MAX; $j++) {
165
- $list = 'list_' . $j;
166
- echo '"' . $recipients[$i]->$list . '"' . $sep;
167
- }
168
-
169
- echo '"' . $recipients[$i]->feed . '"' . $sep;
170
- echo '"' . $recipients[$i]->followup . '"' . $sep;
171
- echo '"' . $recipients[$i]->ip . '"' . $sep;
172
- echo '"' . $recipients[$i]->referrer . '"' . $sep;
173
- echo '"' . $recipients[$i]->country . '"' . $sep;
174
-
175
- echo "\n";
176
- flush();
177
- }
178
- if (count($recipients) < 500) {
179
- break;
180
- }
181
- $page++;
182
- }
183
- die();
184
- }
185
-
186
- function sanitize_csv($text) {
187
- $text = str_replace('"', "'", $text);
188
- $text = str_replace("\n", ' ', $text);
189
- $text = str_replace("\r", ' ', $text);
190
- $text = str_replace(";", ' ', $text);
191
-
192
- // Do you wonder? Excel...
193
- $first = substr($text, 0, 1);
194
- if ($first == '=' || $first == '+' || $first == '-' || $first == '@') {
195
- $text = "'" . $text;
196
- }
197
-
198
- return $text;
199
- }
200
-
201
- /**
202
- * @param array $args
203
- * @param string $format
204
- *
205
- * @return array|object|null
206
- */
207
- function get_users($args, $format = OBJECT) {
208
- global $wpdb;
209
-
210
- $default_args = array(
211
- 'page' => 1,
212
- 'per_page' => 10
213
- );
214
-
215
- $args = array_merge($default_args, $args);
216
-
217
- $query = 'SELECT * FROM ' . NEWSLETTER_USERS_TABLE . ' ';
218
- $query_args = [];
219
-
220
- $query .= ' LIMIT %d OFFSET %d';
221
- $query_args[] = (int) $args['per_page'];
222
- $query_args[] = ( (int) $args['page'] - 1 ) * (int) $args['per_page'];
223
-
224
- $records = $wpdb->get_results($wpdb->prepare($query, $query_args), $format);
225
-
226
- if ($wpdb->last_error) {
227
- $this->logger->error($wpdb->last_error);
228
-
229
- return null;
230
- }
231
-
232
- return $records;
233
- }
234
-
235
- /**
236
- * Check if email exists
237
- *
238
- * @param string $email
239
- *
240
- * @return bool
241
- */
242
- function email_exists($email) {
243
-
244
- $email = parent::normalize_email($email);
245
- $user = parent::get_user($email);
246
-
247
- return $user ? true : false;
248
- }
249
-
250
- }
251
-
252
- NewsletterUsers::instance();
 
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterUsers extends NewsletterModule {
6
+
7
+ static $instance;
8
+
9
+ /**
10
+ * @return NewsletterUsers
11
+ */
12
+ static function instance() {
13
+ if (self::$instance == null) {
14
+ self::$instance = new NewsletterUsers();
15
+ }
16
+
17
+ return self::$instance;
18
+ }
19
+
20
+ function __construct() {
21
+ parent::__construct('users', '1.3.0');
22
+ if (is_admin()) {
23
+ add_action('wp_ajax_newsletter_users_export', array($this, 'hook_wp_ajax_newsletter_users_export'));
24
+ }
25
+ }
26
+
27
+ function hook_wp_ajax_newsletter_users_export() {
28
+
29
+ $newsletter = Newsletter::instance();
30
+ if ($newsletter->is_allowed()) {
31
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
32
+ $controls = new NewsletterControls();
33
+
34
+ if ($controls->is_action('export')) {
35
+ $this->export($controls->data);
36
+ }
37
+ } else {
38
+ die('Not allowed.');
39
+ }
40
+ }
41
+
42
+ function upgrade() {
43
+ global $wpdb, $charset_collate;
44
+
45
+ parent::upgrade();
46
+
47
+ $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter` (
48
+ `name` varchar(100) NOT NULL DEFAULT '',
49
+ `email` varchar(100) NOT NULL DEFAULT '',
50
+ `token` varchar(50) NOT NULL DEFAULT '',
51
+ `language` varchar(10) NOT NULL DEFAULT '',
52
+ `status` varchar(1) NOT NULL DEFAULT 'S',
53
+ `id` int(11) NOT NULL AUTO_INCREMENT,
54
+ `profile` mediumtext,
55
+ `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
56
+ `updated` int(11) NOT NULL DEFAULT '0',
57
+ `last_activity` int(11) NOT NULL DEFAULT '0',
58
+ `followup_step` tinyint(4) NOT NULL DEFAULT '0',
59
+ `followup_time` bigint(20) NOT NULL DEFAULT '0',
60
+ `followup` tinyint(4) NOT NULL DEFAULT '0',
61
+ `surname` varchar(100) NOT NULL DEFAULT '',
62
+ `sex` char(1) NOT NULL DEFAULT 'n',
63
+ `feed_time` bigint(20) NOT NULL DEFAULT '0',
64
+ `feed` tinyint(4) NOT NULL DEFAULT '0',
65
+ `referrer` varchar(50) NOT NULL DEFAULT '',
66
+ `ip` varchar(50) NOT NULL DEFAULT '',
67
+ `wp_user_id` int(11) NOT NULL DEFAULT '0',
68
+ `http_referer` varchar(255) NOT NULL DEFAULT '',
69
+ `geo` tinyint(4) NOT NULL DEFAULT '0',
70
+ `country` varchar(4) NOT NULL DEFAULT '',
71
+ `region` varchar(100) NOT NULL DEFAULT '',
72
+ `city` varchar(100) NOT NULL DEFAULT '',
73
+ `bounce_type` varchar(50) NOT NULL DEFAULT '',
74
+ `bounce_time` int(11) NOT NULL DEFAULT '0',
75
+ `unsub_email_id` int(11) NOT NULL DEFAULT '0',
76
+ `unsub_time` int(11) NOT NULL DEFAULT '0',\n";
77
+
78
+ for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
79
+ $sql .= "`list_$i` tinyint(4) NOT NULL DEFAULT '0',\n";
80
+ }
81
+
82
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
83
+ $sql .= "`profile_$i` varchar(255) NOT NULL DEFAULT '',\n";
84
+ }
85
+ // Leave as last
86
+ $sql .= "`test` tinyint(4) NOT NULL DEFAULT '0',\n";
87
+ $sql .= "PRIMARY KEY (`id`),\nUNIQUE KEY `email` (`email`),\nKEY `wp_user_id` (`wp_user_id`)\n) $charset_collate;";
88
+
89
+ dbDelta($sql);
90
+
91
+ if ($this->old_version < '1.2.7') {
92
+ $this->query("update " . NEWSLETTER_USERS_TABLE . " set geo=1 where country<>''");
93
+ }
94
+ if ($this->old_version > '1.2.5' && $this->old_version < '1.2.9') {
95
+ $this->upgrade_query("ALTER TABLE " . NEWSLETTER_USERS_TABLE . " DROP COLUMN last_ip;");
96
+ }
97
+ }
98
+
99
+ function admin_menu() {
100
+ $this->add_menu_page('index', __('Subscribers', 'newsletter'));
101
+ $this->add_admin_page('new', __('New subscriber', 'newsletter'));
102
+ $this->add_admin_page('edit', __('Subscriber Edit', 'newsletter'));
103
+ $this->add_admin_page('massive', __('Subscribers Maintenance', 'newsletter'));
104
+ $this->add_admin_page('export', __('Export', 'newsletter'));
105
+ $this->add_admin_page('import', __('Import', 'newsletter'));
106
+ $this->add_admin_page('statistics', __('Statistics', 'newsletter'));
107
+ }
108
+
109
+ function export($options = null) {
110
+ global $wpdb;
111
+
112
+ @setlocale(LC_CTYPE, 'en_US.UTF-8');
113
+ header('Content-Type: application/octet-stream;charset=UTF-8');
114
+ header('Content-Disposition: attachment; filename="newsletter-subscribers.csv"');
115
+
116
+ // BOM
117
+ echo "\xEF\xBB\xBF";
118
+
119
+ $sep = ';';
120
+ if ($options) {
121
+ $sep = $options['separator'];
122
+ }
123
+ if ($sep == 'tab') {
124
+ $sep = "\t";
125
+ }
126
+
127
+ // CSV header
128
+ echo '"Email"' . $sep . '"Name"' . $sep . '"Surname"' . $sep . '"Gender"' . $sep . '"Status"' . $sep . '"Date"' . $sep . '"Token"' . $sep;
129
+
130
+ // In table profiles
131
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
132
+ echo '"Profile ' . $i . '"' . $sep; // To adjust with field name
133
+ }
134
+
135
+ // Lists
136
+ for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
137
+ echo '"List ' . $i . '"' . $sep;
138
+ }
139
+
140
+ echo '"Feed by mail"' . $sep . '"Follow up"' . $sep;
141
+ echo '"IP"' . $sep . '"Referrer"' . $sep . '"Country"' . $sep . '"Language"';
142
+
143
+ echo "\n";
144
+
145
+ $page = 0;
146
+ while (true) {
147
+ $query = "select * from " . NEWSLETTER_USERS_TABLE . "";
148
+ $list = (int) $_POST['options']['list'];
149
+ if (!empty($list)) {
150
+ $query .= " where list_" . $list . "=1";
151
+ }
152
+ $recipients = $wpdb->get_results($query . " order by email limit " . $page * 500 . ",500");
153
+ for ($i = 0; $i < count($recipients); $i++) {
154
+ echo '"' . $recipients[$i]->email . '"' . $sep . '"' . $this->sanitize_csv($recipients[$i]->name) .
155
+ '"' . $sep . '"' . $this->sanitize_csv($recipients[$i]->surname) .
156
+ '"' . $sep . '"' . $recipients[$i]->sex .
157
+ '"' . $sep . '"' . $recipients[$i]->status . '"' . $sep . '"' . $recipients[$i]->created . '"' . $sep . '"' . $recipients[$i]->token . '"' . $sep;
158
+
159
+ for ($j = 1; $j <= NEWSLETTER_PROFILE_MAX; $j++) {
160
+ $column = 'profile_' . $j;
161
+ echo '"' . $this->sanitize_csv($recipients[$i]->$column) . '"' . $sep;
162
+ }
163
+
164
+ for ($j = 1; $j <= NEWSLETTER_LIST_MAX; $j++) {
165
+ $list = 'list_' . $j;
166
+ echo '"' . $recipients[$i]->$list . '"' . $sep;
167
+ }
168
+
169
+ echo '"' . $recipients[$i]->feed . '"' . $sep;
170
+ echo '"' . $recipients[$i]->followup . '"' . $sep;
171
+ echo '"' . $recipients[$i]->ip . '"' . $sep;
172
+ echo '"' . $recipients[$i]->referrer . '"' . $sep;
173
+ echo '"' . $recipients[$i]->country . '"' . $sep;
174
+ echo '"' . $recipients[$i]->language . '"' . $sep;
175
+
176
+ echo "\n";
177
+ flush();
178
+ }
179
+ if (count($recipients) < 500) {
180
+ break;
181
+ }
182
+ $page++;
183
+ }
184
+ die();
185
+ }
186
+
187
+ function sanitize_csv($text) {
188
+ $text = str_replace('"', "'", $text);
189
+ $text = str_replace("\n", ' ', $text);
190
+ $text = str_replace("\r", ' ', $text);
191
+ $text = str_replace(";", ' ', $text);
192
+
193
+ // Do you wonder? Excel...
194
+ $first = substr($text, 0, 1);
195
+ if ($first == '=' || $first == '+' || $first == '-' || $first == '@') {
196
+ $text = "'" . $text;
197
+ }
198
+
199
+ return $text;
200
+ }
201
+
202
+ /**
203
+ * @param array $args
204
+ * @param string $format
205
+ *
206
+ * @return array|object|null
207
+ */
208
+ function get_users($args, $format = OBJECT) {
209
+ global $wpdb;
210
+
211
+ $default_args = array(
212
+ 'page' => 1,
213
+ 'per_page' => 10
214
+ );
215
+
216
+ $args = array_merge($default_args, $args);
217
+
218
+ $query = 'SELECT * FROM ' . NEWSLETTER_USERS_TABLE . ' ';
219
+ $query_args = [];
220
+
221
+ $query .= ' LIMIT %d OFFSET %d';
222
+ $query_args[] = (int) $args['per_page'];
223
+ $query_args[] = ( (int) $args['page'] - 1 ) * (int) $args['per_page'];
224
+
225
+ $records = $wpdb->get_results($wpdb->prepare($query, $query_args), $format);
226
+
227
+ if ($wpdb->last_error) {
228
+ $this->logger->error($wpdb->last_error);
229
+
230
+ return null;
231
+ }
232
+
233
+ return $records;
234
+ }
235
+
236
+ /**
237
+ * Check if email exists
238
+ *
239
+ * @param string $email
240
+ *
241
+ * @return bool
242
+ */
243
+ function email_exists($email) {
244
+
245
+ $email = parent::normalize_email($email);
246
+ $user = parent::get_user($email);
247
+
248
+ return $user ? true : false;
249
+ }
250
+
251
+ }
252
+
253
+ NewsletterUsers::instance();