Newsletter - Version 7.4.5

Version Description

  • Fixed image block font-size to show the alt text (not for Outlook, it never shows the alt text)
  • XSS security fix
Download this release

Release Info

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

Code changes from version 7.4.6 to 7.4.5

Files changed (7) hide show
  1. admin/flows.css +71 -0
  2. admin/toast.css +131 -131
  3. includes/composer.php +972 -972
  4. plugin.php +2 -2
  5. readme.txt +1 -5
  6. subscription/subscription.php +1941 -1941
  7. tnp-header.php +345 -345
admin/flows.css ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .flow {
3
+ display: flex;
4
+ align-items: center;
5
+ }
6
+ .item {
7
+ display: block;
8
+ border: 1px solid #fff;
9
+ width: 150px;
10
+ min-height: 100px;
11
+ color: #444 !important;
12
+ text-decoration: none;
13
+ text-align: center;
14
+ font-size: 16px;
15
+ padding: 15px;
16
+ margin: 10px;
17
+ cursor: pointer;
18
+ background-color: #fff;
19
+ box-shadow: 0 0 3px #fff;
20
+ }
21
+ .arr {
22
+ color: #ddd !important;
23
+ font-size: 20px;
24
+ }
25
+ .item.source {
26
+ min-height: 50px;
27
+ }
28
+ .fieldset {
29
+ display: none;
30
+ justify-content: center;
31
+ position: fixed;
32
+ xdisplay: none;
33
+ max-width: 100%;
34
+ height: 100%;
35
+ padding-bottom: 50px;
36
+ top: 0;
37
+ left: 0;
38
+ width: 100%;
39
+ height: 100%;
40
+ background-color: #000000aa;
41
+ z-index: 1000;
42
+ }
43
+
44
+ .fieldset>div {
45
+ position: relative;
46
+ max-width: 100%;
47
+ width: 1024px;
48
+ overflow-y: auto;
49
+ height: fit-content;
50
+ max-height: 80%;
51
+ background-color: #fff;
52
+ margin-top: 60px;
53
+ padding: 15px;
54
+ border-radius: 5px;
55
+ box-shadow: 0 0 5px #999;
56
+
57
+ }
58
+
59
+ .fieldset i.fas.fa-times {
60
+ display: block;
61
+ position: absolute;
62
+ right: 10px;
63
+ top: 10px;
64
+ font-size: 30px;
65
+ }
66
+
67
+
68
+
69
+ .fieldset>div>p {
70
+ color: #000 !important;
71
+ }
admin/toast.css CHANGED
@@ -1,131 +1,131 @@
1
- .tnp-toast-main-wrapper {
2
- position: fixed;
3
- top: 0;
4
- left: 0;
5
- box-sizing: border-box;
6
- height: 100%;
7
- width: 100%;
8
- z-index: 9991;
9
- pointer-events: none;
10
-
11
- display: flex;
12
- flex-direction: column;
13
-
14
- }
15
-
16
- .tnp-toast-main-wrapper .notification {
17
- display: block;
18
- overflow: hidden;
19
- pointer-events: auto;
20
- box-shadow: 0 3px 7px 0 rgba(0, 0, 0, .25);
21
- position: relative;
22
- padding: 15px;
23
- padding-left: 20px;
24
- border-radius: 2px;
25
- max-width: 300px;
26
- transform: translateY(25%);
27
- box-sizing: border-box;
28
- flex-shrink: 0;
29
- font-weight: bold;
30
- animation: .4s ease-in forwards;
31
- background-color: #FFF;
32
- }
33
-
34
- .tnp-toast-main-wrapper .notification:after {
35
- content: ' ';
36
- position: absolute;
37
- left: 0;
38
- top: 0;
39
- width: 5px;
40
- height: 100%;
41
- }
42
-
43
- .tnp-toast-main-wrapper .notification.push-up {
44
- animation-name: notification-fadeinup;
45
- }
46
-
47
- .tnp-toast-main-wrapper .notification.push-down {
48
- animation-name: notification-fadeindown;
49
- }
50
-
51
- .tnp-toast-main-wrapper .notification.pop-down {
52
- transform: translateY(0);
53
- animation: notification-fadeoutdown .4s forwards;
54
- animation-delay: .25s;
55
- }
56
-
57
- .tnp-toast-main-wrapper .notification.pop-up {
58
- transform: translateY(0);
59
- animation: notification-fadeoutup .4s forwards;
60
- animation-delay: .25s;
61
- }
62
-
63
- .tnp-toast-main-wrapper .notification.top-to-bottom {
64
- margin-bottom: 20px;
65
- }
66
-
67
- .tnp-toast-main-wrapper .notification.bottom-to-top {
68
- margin-top: 20px;
69
- }
70
-
71
- .tnp-toast-main-wrapper .notification.notification-success:after {
72
- background-color: #46b450;
73
- }
74
-
75
- .tnp-toast-main-wrapper .notification.notification-error:after {
76
- background-color: #dd0000;
77
- }
78
-
79
- .tnp-toast-main-wrapper .notification.notification-info:after {
80
- background-color: #0073aa;
81
- }
82
-
83
- .tnp-toast-main-wrapper .notification.notification-warning:after {
84
- background-color: #ffb900;
85
- }
86
-
87
- @keyframes notification-fadeinup {
88
- 0% {
89
- opacity: 0;
90
- transform: translateY(25%);
91
- }
92
-
93
- 100% {
94
- opacity: 1;
95
- transform: translateY(0);
96
- }
97
- }
98
-
99
- @keyframes notification-fadeoutdown {
100
- 100% {
101
- opacity: 1;
102
- transform: translateY(0);
103
- }
104
- 100% {
105
- opacity: 0;
106
- transform: translateY(25%);
107
- }
108
- }
109
-
110
- @keyframes notification-fadeindown {
111
- 0% {
112
- opacity: 0;
113
- transform: translateY(-25%);
114
- }
115
-
116
- 100% {
117
- opacity: 1;
118
- transform: translateY(0);
119
- }
120
- }
121
-
122
- @keyframes notification-fadeoutup {
123
- 100% {
124
- opacity: 1;
125
- transform: translateY(0);
126
- }
127
- 100% {
128
- opacity: 0;
129
- transform: translateY(-25%);
130
- }
131
- }
1
+ .tnp-toast-main-wrapper {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ box-sizing: border-box;
6
+ height: 100%;
7
+ width: 100%;
8
+ z-index: 9991;
9
+ pointer-events: none;
10
+
11
+ display: flex;
12
+ flex-direction: column;
13
+
14
+ }
15
+
16
+ .tnp-toast-main-wrapper .notification {
17
+ display: block;
18
+ overflow: hidden;
19
+ pointer-events: auto;
20
+ box-shadow: 0 3px 7px 0 rgba(0, 0, 0, .25);
21
+ position: relative;
22
+ padding: 15px;
23
+ padding-left: 20px;
24
+ border-radius: 2px;
25
+ max-width: 300px;
26
+ transform: translateY(25%);
27
+ box-sizing: border-box;
28
+ flex-shrink: 0;
29
+ font-weight: bold;
30
+ animation: .4s ease-in forwards;
31
+ background-color: #FFF;
32
+ }
33
+
34
+ .tnp-toast-main-wrapper .notification:after {
35
+ content: ' ';
36
+ position: absolute;
37
+ left: 0;
38
+ top: 0;
39
+ width: 5px;
40
+ height: 100%;
41
+ }
42
+
43
+ .tnp-toast-main-wrapper .notification.push-up {
44
+ animation-name: notification-fadeinup;
45
+ }
46
+
47
+ .tnp-toast-main-wrapper .notification.push-down {
48
+ animation-name: notification-fadeindown;
49
+ }
50
+
51
+ .tnp-toast-main-wrapper .notification.pop-down {
52
+ transform: translateY(0);
53
+ animation: notification-fadeoutdown .4s forwards;
54
+ animation-delay: .25s;
55
+ }
56
+
57
+ .tnp-toast-main-wrapper .notification.pop-up {
58
+ transform: translateY(0);
59
+ animation: notification-fadeoutup .4s forwards;
60
+ animation-delay: .25s;
61
+ }
62
+
63
+ .tnp-toast-main-wrapper .notification.top-to-bottom {
64
+ margin-bottom: 20px;
65
+ }
66
+
67
+ .tnp-toast-main-wrapper .notification.bottom-to-top {
68
+ margin-top: 20px;
69
+ }
70
+
71
+ .tnp-toast-main-wrapper .notification.notification-success:after {
72
+ background-color: #46b450;
73
+ }
74
+
75
+ .tnp-toast-main-wrapper .notification.notification-error:after {
76
+ background-color: #dd0000;
77
+ }
78
+
79
+ .tnp-toast-main-wrapper .notification.notification-info:after {
80
+ background-color: #0073aa;
81
+ }
82
+
83
+ .tnp-toast-main-wrapper .notification.notification-warning:after {
84
+ background-color: #ffb900;
85
+ }
86
+
87
+ @keyframes notification-fadeinup {
88
+ 0% {
89
+ opacity: 0;
90
+ transform: translateY(25%);
91
+ }
92
+
93
+ 100% {
94
+ opacity: 1;
95
+ transform: translateY(0);
96
+ }
97
+ }
98
+
99
+ @keyframes notification-fadeoutdown {
100
+ 100% {
101
+ opacity: 1;
102
+ transform: translateY(0);
103
+ }
104
+ 100% {
105
+ opacity: 0;
106
+ transform: translateY(25%);
107
+ }
108
+ }
109
+
110
+ @keyframes notification-fadeindown {
111
+ 0% {
112
+ opacity: 0;
113
+ transform: translateY(-25%);
114
+ }
115
+
116
+ 100% {
117
+ opacity: 1;
118
+ transform: translateY(0);
119
+ }
120
+ }
121
+
122
+ @keyframes notification-fadeoutup {
123
+ 100% {
124
+ opacity: 1;
125
+ transform: translateY(0);
126
+ }
127
+ 100% {
128
+ opacity: 0;
129
+ transform: translateY(-25%);
130
+ }
131
+ }
includes/composer.php CHANGED
@@ -1,972 +1,972 @@
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<head>\n<title>{email_subject}</title>\n";
103
- $open .= "<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n";
104
-
105
- $open .= '<!--[if !mso]><!-->' . "\n";
106
- $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
107
- $open .= '<!--<![endif]-->' . "\n";
108
-
109
- $open .= '<!--[if mso]>' . "\n";
110
- ;
111
- $open .= '<style type="text/css">';
112
- $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
113
- $open .= 'div, td {padding:0;}';
114
- $open .= 'div {margin:0 !important;}';
115
- $open .= '</style>';
116
- $open .= "\n";
117
- $open .= '<noscript>';
118
- $open .= '<xml>';
119
- $open .= '<o:OfficeDocumentSettings>';
120
- $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
121
- $open .= '</o:OfficeDocumentSettings>';
122
- $open .= '</xml>';
123
- $open .= '</noscript>';
124
- $open .= "\n";
125
- $open .= '<![endif]-->';
126
- $open .= "\n";
127
- $open .= "<style type=\"text/css\">\n";
128
- $open .= NewsletterEmails::instance()->get_composer_css();
129
- $open .= "\n</style>\n";
130
- $open .= "</head>\n";
131
- $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
132
- $open .= "\n";
133
- $open .= self::get_html_preheader($email);
134
-
135
- return $open;
136
- }
137
-
138
- static private function get_html_preheader($email) {
139
-
140
- if (empty($email->options['preheader'])) {
141
- return "";
142
- }
143
-
144
- $preheader_text = esc_html($email->options['preheader']);
145
- $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>";
146
- $html .= "\n";
147
-
148
- return $html;
149
- }
150
-
151
- static function get_html_close($email) {
152
- return "</body>\n</html>";
153
- }
154
-
155
- /**
156
- *
157
- * @param TNP_Email $email
158
- * @return string
159
- */
160
- static function get_main_wrapper_open($email) {
161
- if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
162
- $bgcolor = '';
163
- } else {
164
- $bgcolor = $email->options['composer_background'];
165
- }
166
-
167
- return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
168
- "<tr>\n" .
169
- "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
170
- }
171
-
172
- /**
173
- *
174
- * @param TNP_Email $email
175
- * @return string
176
- */
177
- static function get_main_wrapper_close($email) {
178
- return "\n<!-- /tnp -->\n" .
179
- "</td>\n" .
180
- "</tr>\n" .
181
- "</table>\n\n";
182
- }
183
-
184
- /**
185
- * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
186
- *
187
- * @param string $html_email
188
- *
189
- * @return string
190
- */
191
- static function unwrap_email($html_email) {
192
-
193
- if (self::_has_markers($html_email)) {
194
- $html_email = self::unwrap_html_element($html_email);
195
- } else {
196
- //KEEP FOR OLD EMAIL COMPATIBILITY
197
- // Extracts only the body part
198
- $x = strpos($html_email, '<body');
199
- if ($x) {
200
- $x = strpos($html_email, '>', $x);
201
- $y = strpos($html_email, '</body>');
202
- $html_email = substr($html_email, $x + 1, $y - $x - 1);
203
- }
204
-
205
- /* Cleans up uncorrectly stored newsletter bodies */
206
- $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
207
- $html_email = preg_replace('/<meta.*?>/', '', $html_email);
208
- $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
209
- $html_email = trim($html_email);
210
- }
211
-
212
- // Required since esc_html DOES NOT escape the HTML entities (apparently)
213
- $html_email = str_replace('&', '&amp;', $html_email);
214
- $html_email = str_replace('"', '&quot;', $html_email);
215
- $html_email = str_replace('<', '&lt;', $html_email);
216
- $html_email = str_replace('>', '&gt;', $html_email);
217
-
218
- return $html_email;
219
- }
220
-
221
- private static function _escape_markers(&$markers) {
222
- $markers[0] = str_replace('/', '\/', $markers[0]);
223
- $markers[1] = str_replace('/', '\/', $markers[1]);
224
- }
225
-
226
- /**
227
- * Using the data collected inside $controls (and submitted by a form containing the
228
- * composer fields), updates the email. The message body is completed with doctype,
229
- * head, style and the main wrapper.
230
- *
231
- * @param TNP_Email $email
232
- * @param NewsletterControls $controls
233
- */
234
- static function update_email($email, $controls) {
235
- if (isset($controls->data['subject'])) {
236
- $email->subject = $controls->data['subject'];
237
- }
238
-
239
- // They should be only composer options
240
- foreach ($controls->data as $name => $value) {
241
- if (strpos($name, 'options_') === 0) {
242
- $email->options[substr($name, 8)] = $value;
243
- }
244
- }
245
-
246
- //if (isset($controls->data['preheader'])) {
247
- // $email->options['preheader'] = $controls->data['preheader'];
248
- //}
249
-
250
- $email->editor = NewsletterEmails::EDITOR_COMPOSER;
251
-
252
- $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
253
- $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
254
- }
255
-
256
- /**
257
- * Prepares a controls object injecting the relevant fields from an email
258
- * which cannot be directly used by controls. If $email is null or missing,
259
- * $controls is prepared with default values.
260
- *
261
- * @param NewsletterControls $controls
262
- * @param TNP_Email $email
263
- */
264
- static function prepare_controls($controls, $email = null) {
265
-
266
- // Controls for a new email (which actually does not exist yet
267
- if (!empty($email)) {
268
-
269
- foreach ($email->options as $name => $value) {
270
- $controls->data['options_' . $name] = $value;
271
- }
272
-
273
- $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
274
- $controls->data['subject'] = $email->subject;
275
- $controls->data['updated'] = $email->updated;
276
- }
277
-
278
- if (!empty($email->options['sender_email'])) {
279
- $controls->data['sender_email'] = $email->options['sender_email'];
280
- } else {
281
- $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
282
- }
283
-
284
- if (!empty($email->options['sender_name'])) {
285
- $controls->data['sender_name'] = $email->options['sender_name'];
286
- } else {
287
- $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
288
- }
289
-
290
- $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
291
- }
292
-
293
- /**
294
- * Extract inline edited post field from inline_edit_list[]
295
- *
296
- * @param array $inline_edit_list
297
- * @param string $field_type
298
- * @param int $post_id
299
- *
300
- * @return string
301
- */
302
- static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
303
-
304
- foreach ($inline_edit_list as $edit) {
305
- if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
306
- return $edit['content'];
307
- }
308
- }
309
-
310
- return '';
311
- }
312
-
313
- /**
314
- * Check if inline_edit_list[] have inline edit field for specific post
315
- *
316
- * @param array $inline_edit_list
317
- * @param string $field_type
318
- * @param int $post_id
319
- *
320
- * @return bool
321
- */
322
- static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
323
- if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
324
- return false;
325
- }
326
- foreach ($inline_edit_list as $edit) {
327
- if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
328
- return true;
329
- }
330
- }
331
-
332
- return false;
333
- }
334
-
335
- /**
336
- * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
337
- *
338
- * - [prefix]_url The button URL
339
- * - [prefix]_font_family
340
- * - [prefix]_font_size
341
- * - [prefix]_font_weight
342
- * - [prefix]_label
343
- * - [prefix]_font_color The label color
344
- * - [prefix]_background The button color
345
- *
346
- * TODO: Add radius and possiblt the alignment
347
- *
348
- * @param array $options
349
- * @param string $prefix
350
- * @return string
351
- */
352
- static function button($options, $prefix = 'button') {
353
-
354
- if (empty($options[$prefix . '_label'])) {
355
- return;
356
- }
357
- $defaults = [
358
- $prefix . '_url' => '#',
359
- $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
360
- $prefix . '_font_color' => '#ffffff',
361
- $prefix . '_font_weight' => 'bold',
362
- $prefix . '_font_size' => 20,
363
- $prefix . '_background' => '#256F9C',
364
- $prefix . '_align' => 'center'
365
- ];
366
-
367
- $options = array_merge($defaults, array_filter($options));
368
-
369
- $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
370
- if (!empty($options[$prefix . '_align'])) {
371
- $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
372
- }
373
- if (!empty($options[$prefix . '_width'])) {
374
- $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
375
- }
376
- $b .= '>';
377
- $b .= '<tr>';
378
- $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">';
379
- $b .= '<a href="' . $options[$prefix . '_url'] . '"';
380
- $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;"';
381
- $b .= ' target="_blank">';
382
- $b .= $options[$prefix . '_label'];
383
- $b .= '</a>';
384
- $b .= '</td></tr></table>';
385
- return $b;
386
- }
387
-
388
- /**
389
- * Generates an IMG tag, linked if the media has an URL.
390
- *
391
- * @param TNP_Media $media
392
- * @param string $style
393
- * @return string
394
- */
395
- static function image($media, $attr = []) {
396
-
397
- $default_attrs = [
398
- 'style' => 'max-width: 100%; height: auto; display: inline-block',
399
- 'class' => '',
400
- 'link-style' => 'text-decoration: none; display: inline-block',
401
- 'link-class' => null,
402
- ];
403
-
404
- $attr = array_merge($default_attrs, $attr);
405
-
406
- //Class and style attribute are mutually exclusive.
407
- //Class take priority to style because classes will transform to inline style inside block rendering operation
408
- if (!empty($attr['inline-class'])) {
409
- $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
410
- } else {
411
- $styling = ' style="' . esc_attr($attr['style']) . '" ';
412
- }
413
-
414
- if (!empty($attr['class'])) {
415
- $styling .= ' class="' . esc_attr($attr['class']) . '" ';
416
- }
417
-
418
- //Class and style attribute are mutually exclusive.
419
- //Class take priority to style because classes will transform to inline style inside block rendering operation
420
- if (!empty($attr['link-class'])) {
421
- $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
422
- } else {
423
- $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
424
- }
425
-
426
- $b = '';
427
- if ($media->link) {
428
- $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">';
429
- } else {
430
- // The span grants images are not upscaled when fluid (two columns posts block)
431
- $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
432
- }
433
- if ($media) {
434
- $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
435
- if ($media->height) {
436
- $b .= ' height="' . esc_attr($media->height) . '"';
437
- }
438
- $b .= ' alt="' . esc_attr($media->alt) . '"'
439
- . ' border="0"'
440
- . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0; font-size: 12px"'
441
- . ' class="' . esc_attr($attr['class']) . '" '
442
- . '>';
443
- }
444
-
445
- if ($media->link) {
446
- $b .= '</a>';
447
- } else {
448
- $b .= '</span>';
449
- }
450
-
451
- return $b;
452
- }
453
-
454
- /**
455
- * Returns a WP media ID for the specified post (or false if nothing can be found)
456
- * looking for the featured image or, if missing, taking the first media in the gallery and
457
- * if again missing, searching the first reference to a media in the post content.
458
- *
459
- * The media ID is not checked for real existance of the associated attachment.
460
- *
461
- * @param int $post_id
462
- * @return int
463
- */
464
- static function get_post_thumbnail_id($post_id) {
465
- if (is_object($post_id)) {
466
- $post_id = $post_id->ID;
467
- }
468
-
469
- // Find a media id to be used as featured image
470
- $media_id = get_post_thumbnail_id($post_id);
471
- if (!empty($media_id)) {
472
- return $media_id;
473
- }
474
-
475
- $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'));
476
- if (!empty($attachments)) {
477
- foreach ($attachments as $id => &$attachment) {
478
- return $id;
479
- }
480
- }
481
-
482
- $post = get_post($post_id);
483
-
484
- $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
485
- if ($matches) {
486
- return (int) $matches[1];
487
- }
488
-
489
- return false;
490
- }
491
-
492
- /**
493
- * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
494
- * media has a size which best match the one requested (this is the standard WP behavior, plugins
495
- * could change it).
496
- *
497
- * @param int $media_id
498
- * @param array $size
499
- * @return \TNP_Media
500
- */
501
- function get_media($media_id, $size) {
502
- $src = wp_get_attachment_image_src($media_id, $size);
503
- if (!$src) {
504
- return null;
505
- }
506
- $media = new TNP_Media();
507
- $media->id = $media_id;
508
- $media->url = $src[0];
509
- $media->width = $src[1];
510
- $media->height = $src[2];
511
- return $media;
512
- }
513
-
514
- static function post_content($post) {
515
- $content = $post->post_content;
516
- $content = wpautop($content);
517
- if (true || $options['enable shortcodes']) {
518
- remove_shortcode('gallery');
519
- add_shortcode('gallery', 'tnp_gallery_shortcode');
520
- $content = do_shortcode($content);
521
- }
522
- $content = str_replace('<p>', '<p class="paragraph">', $content);
523
-
524
- $selected_images = array();
525
- if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
526
- foreach ($matches[0] as $image) {
527
- if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
528
- $selected_images[$image] = $attachment_id;
529
- }
530
- }
531
- }
532
-
533
- foreach ($selected_images as $image => $attachment_id) {
534
- $src = tnp_media_resize($attachment_id, array(600, 0));
535
- if (is_wp_error($src)) {
536
- continue;
537
- }
538
- $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
539
- }
540
-
541
- return $content;
542
- }
543
-
544
- static function get_global_style_defaults() {
545
- return [
546
- 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
547
- 'options_composer_title_font_size' => 32,
548
- 'options_composer_title_font_weight' => 'normal',
549
- 'options_composer_title_font_color' => '#222222',
550
- 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
551
- 'options_composer_text_font_size' => 16,
552
- 'options_composer_text_font_weight' => 'normal',
553
- 'options_composer_text_font_color' => '#222222',
554
- 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
555
- 'options_composer_button_font_size' => 16,
556
- 'options_composer_button_font_weight' => 'normal',
557
- 'options_composer_button_font_color' => '#FFFFFF',
558
- 'options_composer_button_background_color' => '#256F9C',
559
- 'options_composer_background' => '#FFFFFF',
560
- 'options_composer_block_background' => '#FFFFFF',
561
- ];
562
- }
563
-
564
- /**
565
- * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
566
- *
567
- * Attributes:
568
- * - columns: number of columns [2]
569
- * - padding: cells padding [10]
570
- * - responsive: il on mobile the cell should stack up [true]
571
- * - width: the whole row width, it should reduced by the external row padding [600]
572
- *
573
- * @param string[] $items
574
- * @param array $attrs
575
- * @return string
576
- */
577
- static function grid($items = [], $attrs = []) {
578
- $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
579
- $width = (int) $attrs['width'];
580
- $columns = (int) $attrs['columns'];
581
- $padding = (int) $attrs['padding'];
582
- $column_width = $width / $columns;
583
- $td_width = 100 / $columns;
584
- $chunks = array_chunk($items, $columns);
585
-
586
- if ($attrs['responsive']) {
587
-
588
- $e = '';
589
- foreach ($chunks as &$chunk) {
590
- $e .= '<div style="text-align:center;font-size:0;">';
591
- $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
592
- foreach ($chunk as &$item) {
593
- $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
594
-
595
- $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
596
-
597
- // This element to add padding without deal with border-box not well supported
598
- $e .= '<div style="padding:' . $padding . 'px;">';
599
- $e .= $item;
600
- $e .= '</div>';
601
- $e .= '</div>';
602
-
603
- $e .= '<!--[if mso]></td><![endif]-->';
604
- }
605
- $e .= '<!--[if mso]></tr></table><![endif]-->';
606
- $e .= '</div>';
607
- }
608
-
609
- return $e;
610
- } else {
611
- $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
612
- foreach ($chunks as &$chunk) {
613
- $e .= '<tr>';
614
- foreach ($chunk as &$item) {
615
- $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
616
- $e .= $item;
617
- $e .= '</td>';
618
- }
619
- $e .= '</tr>';
620
- }
621
- $e .= '</table>';
622
- return $e;
623
- }
624
- }
625
-
626
- static function get_text_style($options, $prefix, $composer, $attrs = []) {
627
- return self::get_style($options, $prefix, $composer, 'text', $attrs);
628
- }
629
-
630
- static function get_title_style($options, $prefix, $composer, $attrs = []) {
631
- return self::get_style($options, $prefix, $composer, 'title', $attrs);
632
- }
633
-
634
- static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
635
- $style = new TNP_Style();
636
- $scale = 1.0;
637
- if (!empty($attrs['scale'])) {
638
- $scale = (float) $attrs['scale'];
639
- }
640
- if (!empty($prefix))
641
- $prefix .= '_';
642
-
643
- $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
644
- $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
645
- $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
646
- $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
647
- if ($type === 'button') {
648
- $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
649
- }
650
- return $style;
651
- }
652
-
653
- static function get_button_options($options, $prefix, $composer) {
654
- $button_options = [];
655
- $scale = 1;
656
- $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
657
- $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
658
- $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
659
- $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
660
- $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
661
- $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
662
- $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
663
- $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
664
- $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
665
-
666
- return $button_options;
667
- }
668
-
669
- static function convert_to_text($html) {
670
- if (!class_exists('DOMDocument')) {
671
- return '';
672
- }
673
- // Replace '&' with '&amp;' in URLs to avoid warnings about inavlid entities from loadHTML()
674
- // Todo: make this more general using a regular expression
675
- //$logger = PlaintextNewsletterAddon::$instance->get_logger();
676
- //$logger->debug('html="' . $html . '"');
677
- $html = str_replace(
678
- array('&nk=', '&nek=', '&id='),
679
- array('&amp;nk=', '&amp;nek=', '&amp;id='),
680
- $html);
681
- //$logger->debug('new html="' . $html . '"');
682
- //
683
- $output = '';
684
- $dom = new DOMDocument();
685
- $r = $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html);
686
- if (!$r) {
687
- return '';
688
- }
689
- $bodylist = $dom->getElementsByTagName('body');
690
- // Of course it should be a single element
691
- foreach ($bodylist as $body) {
692
- self::process_dom_element($body, $output);
693
- }
694
- return $output;
695
- }
696
-
697
- static function process_dom_element(DOMElement $parent, &$output) {
698
- foreach ($parent->childNodes as $node) {
699
- if (is_a($node, 'DOMElement') && ($node->tagName != 'style')) {
700
- if ($node->tagName== 'br') {
701
- $output .= "\n";
702
- continue;
703
- }
704
- self::process_dom_element($node, $output);
705
-
706
- // If the containing tag was a block level tag, we add a couple of line ending
707
- if ($node->tagName == 'p' || $node->tagName == 'div' || $node->tagName == 'td') {
708
- // Avoid more than one blank line between elements
709
- if ((strlen($output) >= 2) && (substr($output, -2) != "\n\n")) {
710
- $output .= "\n\n";
711
- }
712
- }
713
-
714
- if ($node->tagName == 'a') {
715
- $output .= ' (' . $node->getAttribute('href') . ') ';
716
- continue;
717
- }
718
- elseif ($node->tagName == 'img') {
719
- $output .= $node->getAttribute('alt');
720
- }
721
- }
722
- elseif (is_a($node, 'DOMText')) {
723
- $decoded = utf8_decode($node->wholeText);
724
- if (ctype_space($decoded)) {
725
- // Append blank only if last character output is not blank.
726
- if ((strlen($output) > 0) && !ctype_space(substr($output, -1))) {
727
- $output .= ' ';
728
- }
729
- } else {
730
- $output .= trim($node->wholeText);
731
- }
732
- }
733
- }
734
- }
735
- }
736
-
737
-
738
-
739
- class TNP_Style {
740
-
741
- var $font_family;
742
- var $font_size;
743
- var $font_weight;
744
- var $font_color;
745
- var $background;
746
-
747
- function echo_css($scale = 1.0) {
748
- echo 'font-size: ', round($this->font_size * $scale), 'px;';
749
- echo 'font-family: ', $this->font_family, ';';
750
- echo 'font-weight: ', $this->font_weight, ';';
751
- echo 'color: ', $this->font_color, ';';
752
- }
753
-
754
- }
755
-
756
- /**
757
- * Generate multicolumn and responsive html template for email.
758
- * Initialize class with max columns per row and start to add cells.
759
- */
760
- class TNP_Composer_Grid_System {
761
-
762
- /**
763
- * @var TNP_Composer_Grid_Row[]
764
- */
765
- private $rows;
766
-
767
- /**
768
- * @var int
769
- */
770
- private $cells_per_row;
771
-
772
- /**
773
- * @var int
774
- */
775
- private $cells_counter;
776
-
777
- /**
778
- * TNP_Composer_Grid_System constructor.
779
- *
780
- * @param int $columns_per_row Max columns per row
781
- */
782
- public function __construct($columns_per_row) {
783
- $this->cells_per_row = $columns_per_row;
784
- $this->cells_counter = 0;
785
- $this->rows = [];
786
- }
787
-
788
- public function __toString() {
789
- return $this->render();
790
- }
791
-
792
- /**
793
- * Add cell to grid
794
- *
795
- * @param TNP_Composer_Grid_Cell $cell
796
- */
797
- public function add_cell($cell) {
798
-
799
- if ($this->cells_counter % $this->cells_per_row === 0) {
800
- $this->add_row(new TNP_Composer_Grid_Row());
801
- }
802
-
803
- $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
804
- $this->rows[$row_idx]->add_cell($cell);
805
- $this->cells_counter++;
806
- }
807
-
808
- private function add_row($row) {
809
- $this->rows[] = $row;
810
- }
811
-
812
- public function render() {
813
-
814
- $str = '';
815
- foreach ($this->rows as $row) {
816
- $str .= $row->render();
817
- }
818
-
819
- return $str;
820
- }
821
-
822
- }
823
-
824
- /**
825
- * Class TNP_Composer_Grid_Row
826
- */
827
- class TNP_Composer_Grid_Row {
828
-
829
- /**
830
- * @var TNP_Composer_Grid_Cell[]
831
- */
832
- private $cells;
833
-
834
- public function __construct(...$cells) {
835
- if (!empty($cells)) {
836
- foreach ($cells as $cell) {
837
- $this->add_cell($cell);
838
- }
839
- }
840
- }
841
-
842
- /**
843
- * @param TNP_Composer_Grid_Cell $cell
844
- */
845
- public function add_cell($cell) {
846
- $this->cells[] = $cell;
847
- }
848
-
849
- public function render() {
850
- $rendered_cells = '';
851
- $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
852
- foreach ($this->cells as $cell) {
853
- $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
854
- }
855
-
856
- $row_template = $this->get_template();
857
-
858
- return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
859
- }
860
-
861
- private function cells_count() {
862
- return count($this->cells);
863
- }
864
-
865
- private function get_template() {
866
- return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
867
- }
868
-
869
- }
870
-
871
- /**
872
- * Class TNP_Composer_Grid_Cell
873
- */
874
- class TNP_Composer_Grid_Cell {
875
-
876
- /**
877
- * @var string
878
- */
879
- private $content;
880
-
881
- /**
882
- * @var array
883
- */
884
- public $args;
885
-
886
- public function __construct($content = null, $args = []) {
887
- $default_args = [
888
- 'width' => '100%',
889
- 'class' => '',
890
- 'align' => 'left',
891
- 'valign' => 'top'
892
- ];
893
-
894
- $this->args = array_merge($default_args, $args);
895
-
896
- $this->content = $content ? $content : '';
897
- }
898
-
899
- public function add_content($content) {
900
- $this->content .= $content;
901
- }
902
-
903
- public function render($args) {
904
- $this->args = array_merge($this->args, $args);
905
-
906
- $column_template = $this->get_template();
907
- $column = str_replace(
908
- [
909
- 'TNP_ALIGN_PH',
910
- 'TNP_VALIGN_PH',
911
- 'TNP_WIDTH_PH',
912
- 'TNP_CLASS_PH',
913
- 'TNP_COLUMN_CONTENT_PH'
914
- ], [
915
- $this->args['align'],
916
- $this->args['valign'],
917
- $this->args['width'],
918
- $this->args['class'],
919
- $this->content
920
- ], $column_template);
921
-
922
- return $column;
923
- }
924
-
925
- private function get_template() {
926
- return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
927
- <tbody>
928
- <tr>
929
- <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
930
- TNP_COLUMN_CONTENT_PH
931
- </td>
932
- </tr>
933
- </tbody>
934
- </table>";
935
- }
936
-
937
- }
938
-
939
- class TNP_Composer_Component_Factory {
940
-
941
- private $options;
942
-
943
- /**
944
- * TNP_Composer_Component_Factory constructor.
945
- *
946
- * @param Controller$controller
947
- */
948
- public function __construct($controller) {
949
-
950
- }
951
-
952
- function heading() {
953
-
954
- }
955
-
956
- function paragraph() {
957
-
958
- }
959
-
960
- function link() {
961
-
962
- }
963
-
964
- function button() {
965
-
966
- }
967
-
968
- function image() {
969
-
970
- }
971
-
972
- }
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<head>\n<title>{email_subject}</title>\n";
103
+ $open .= "<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n";
104
+
105
+ $open .= '<!--[if !mso]><!-->' . "\n";
106
+ $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
107
+ $open .= '<!--<![endif]-->' . "\n";
108
+
109
+ $open .= '<!--[if mso]>' . "\n";
110
+ ;
111
+ $open .= '<style type="text/css">';
112
+ $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
113
+ $open .= 'div, td {padding:0;}';
114
+ $open .= 'div {margin:0 !important;}';
115
+ $open .= '</style>';
116
+ $open .= "\n";
117
+ $open .= '<noscript>';
118
+ $open .= '<xml>';
119
+ $open .= '<o:OfficeDocumentSettings>';
120
+ $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
121
+ $open .= '</o:OfficeDocumentSettings>';
122
+ $open .= '</xml>';
123
+ $open .= '</noscript>';
124
+ $open .= "\n";
125
+ $open .= '<![endif]-->';
126
+ $open .= "\n";
127
+ $open .= "<style type=\"text/css\">\n";
128
+ $open .= NewsletterEmails::instance()->get_composer_css();
129
+ $open .= "\n</style>\n";
130
+ $open .= "</head>\n";
131
+ $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
132
+ $open .= "\n";
133
+ $open .= self::get_html_preheader($email);
134
+
135
+ return $open;
136
+ }
137
+
138
+ static private function get_html_preheader($email) {
139
+
140
+ if (empty($email->options['preheader'])) {
141
+ return "";
142
+ }
143
+
144
+ $preheader_text = $email->options['preheader'];
145
+ $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>";
146
+ $html .= "\n";
147
+
148
+ return $html;
149
+ }
150
+
151
+ static function get_html_close($email) {
152
+ return "</body>\n</html>";
153
+ }
154
+
155
+ /**
156
+ *
157
+ * @param TNP_Email $email
158
+ * @return string
159
+ */
160
+ static function get_main_wrapper_open($email) {
161
+ if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
162
+ $bgcolor = '';
163
+ } else {
164
+ $bgcolor = $email->options['composer_background'];
165
+ }
166
+
167
+ return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
168
+ "<tr>\n" .
169
+ "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
170
+ }
171
+
172
+ /**
173
+ *
174
+ * @param TNP_Email $email
175
+ * @return string
176
+ */
177
+ static function get_main_wrapper_close($email) {
178
+ return "\n<!-- /tnp -->\n" .
179
+ "</td>\n" .
180
+ "</tr>\n" .
181
+ "</table>\n\n";
182
+ }
183
+
184
+ /**
185
+ * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
186
+ *
187
+ * @param string $html_email
188
+ *
189
+ * @return string
190
+ */
191
+ static function unwrap_email($html_email) {
192
+
193
+ if (self::_has_markers($html_email)) {
194
+ $html_email = self::unwrap_html_element($html_email);
195
+ } else {
196
+ //KEEP FOR OLD EMAIL COMPATIBILITY
197
+ // Extracts only the body part
198
+ $x = strpos($html_email, '<body');
199
+ if ($x) {
200
+ $x = strpos($html_email, '>', $x);
201
+ $y = strpos($html_email, '</body>');
202
+ $html_email = substr($html_email, $x + 1, $y - $x - 1);
203
+ }
204
+
205
+ /* Cleans up uncorrectly stored newsletter bodies */
206
+ $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
207
+ $html_email = preg_replace('/<meta.*?>/', '', $html_email);
208
+ $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
209
+ $html_email = trim($html_email);
210
+ }
211
+
212
+ // Required since esc_html DOES NOT escape the HTML entities (apparently)
213
+ $html_email = str_replace('&', '&amp;', $html_email);
214
+ $html_email = str_replace('"', '&quot;', $html_email);
215
+ $html_email = str_replace('<', '&lt;', $html_email);
216
+ $html_email = str_replace('>', '&gt;', $html_email);
217
+
218
+ return $html_email;
219
+ }
220
+
221
+ private static function _escape_markers(&$markers) {
222
+ $markers[0] = str_replace('/', '\/', $markers[0]);
223
+ $markers[1] = str_replace('/', '\/', $markers[1]);
224
+ }
225
+
226
+ /**
227
+ * Using the data collected inside $controls (and submitted by a form containing the
228
+ * composer fields), updates the email. The message body is completed with doctype,
229
+ * head, style and the main wrapper.
230
+ *
231
+ * @param TNP_Email $email
232
+ * @param NewsletterControls $controls
233
+ */
234
+ static function update_email($email, $controls) {
235
+ if (isset($controls->data['subject'])) {
236
+ $email->subject = $controls->data['subject'];
237
+ }
238
+
239
+ // They should be only composer options
240
+ foreach ($controls->data as $name => $value) {
241
+ if (strpos($name, 'options_') === 0) {
242
+ $email->options[substr($name, 8)] = $value;
243
+ }
244
+ }
245
+
246
+ //if (isset($controls->data['preheader'])) {
247
+ // $email->options['preheader'] = $controls->data['preheader'];
248
+ //}
249
+
250
+ $email->editor = NewsletterEmails::EDITOR_COMPOSER;
251
+
252
+ $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
253
+ $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
254
+ }
255
+
256
+ /**
257
+ * Prepares a controls object injecting the relevant fields from an email
258
+ * which cannot be directly used by controls. If $email is null or missing,
259
+ * $controls is prepared with default values.
260
+ *
261
+ * @param NewsletterControls $controls
262
+ * @param TNP_Email $email
263
+ */
264
+ static function prepare_controls($controls, $email = null) {
265
+
266
+ // Controls for a new email (which actually does not exist yet
267
+ if (!empty($email)) {
268
+
269
+ foreach ($email->options as $name => $value) {
270
+ $controls->data['options_' . $name] = $value;
271
+ }
272
+
273
+ $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
274
+ $controls->data['subject'] = $email->subject;
275
+ $controls->data['updated'] = $email->updated;
276
+ }
277
+
278
+ if (!empty($email->options['sender_email'])) {
279
+ $controls->data['sender_email'] = $email->options['sender_email'];
280
+ } else {
281
+ $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
282
+ }
283
+
284
+ if (!empty($email->options['sender_name'])) {
285
+ $controls->data['sender_name'] = $email->options['sender_name'];
286
+ } else {
287
+ $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
288
+ }
289
+
290
+ $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
291
+ }
292
+
293
+ /**
294
+ * Extract inline edited post field from inline_edit_list[]
295
+ *
296
+ * @param array $inline_edit_list
297
+ * @param string $field_type
298
+ * @param int $post_id
299
+ *
300
+ * @return string
301
+ */
302
+ static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
303
+
304
+ foreach ($inline_edit_list as $edit) {
305
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
306
+ return $edit['content'];
307
+ }
308
+ }
309
+
310
+ return '';
311
+ }
312
+
313
+ /**
314
+ * Check if inline_edit_list[] have inline edit field for specific post
315
+ *
316
+ * @param array $inline_edit_list
317
+ * @param string $field_type
318
+ * @param int $post_id
319
+ *
320
+ * @return bool
321
+ */
322
+ static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
323
+ if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
324
+ return false;
325
+ }
326
+ foreach ($inline_edit_list as $edit) {
327
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
328
+ return true;
329
+ }
330
+ }
331
+
332
+ return false;
333
+ }
334
+
335
+ /**
336
+ * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
337
+ *
338
+ * - [prefix]_url The button URL
339
+ * - [prefix]_font_family
340
+ * - [prefix]_font_size
341
+ * - [prefix]_font_weight
342
+ * - [prefix]_label
343
+ * - [prefix]_font_color The label color
344
+ * - [prefix]_background The button color
345
+ *
346
+ * TODO: Add radius and possiblt the alignment
347
+ *
348
+ * @param array $options
349
+ * @param string $prefix
350
+ * @return string
351
+ */
352
+ static function button($options, $prefix = 'button') {
353
+
354
+ if (empty($options[$prefix . '_label'])) {
355
+ return;
356
+ }
357
+ $defaults = [
358
+ $prefix . '_url' => '#',
359
+ $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
360
+ $prefix . '_font_color' => '#ffffff',
361
+ $prefix . '_font_weight' => 'bold',
362
+ $prefix . '_font_size' => 20,
363
+ $prefix . '_background' => '#256F9C',
364
+ $prefix . '_align' => 'center'
365
+ ];
366
+
367
+ $options = array_merge($defaults, array_filter($options));
368
+
369
+ $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
370
+ if (!empty($options[$prefix . '_align'])) {
371
+ $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
372
+ }
373
+ if (!empty($options[$prefix . '_width'])) {
374
+ $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
375
+ }
376
+ $b .= '>';
377
+ $b .= '<tr>';
378
+ $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">';
379
+ $b .= '<a href="' . $options[$prefix . '_url'] . '"';
380
+ $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;"';
381
+ $b .= ' target="_blank">';
382
+ $b .= $options[$prefix . '_label'];
383
+ $b .= '</a>';
384
+ $b .= '</td></tr></table>';
385
+ return $b;
386
+ }
387
+
388
+ /**
389
+ * Generates an IMG tag, linked if the media has an URL.
390
+ *
391
+ * @param TNP_Media $media
392
+ * @param string $style
393
+ * @return string
394
+ */
395
+ static function image($media, $attr = []) {
396
+
397
+ $default_attrs = [
398
+ 'style' => 'max-width: 100%; height: auto; display: inline-block',
399
+ 'class' => '',
400
+ 'link-style' => 'text-decoration: none; display: inline-block',
401
+ 'link-class' => null,
402
+ ];
403
+
404
+ $attr = array_merge($default_attrs, $attr);
405
+
406
+ //Class and style attribute are mutually exclusive.
407
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
408
+ if (!empty($attr['inline-class'])) {
409
+ $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
410
+ } else {
411
+ $styling = ' style="' . esc_attr($attr['style']) . '" ';
412
+ }
413
+
414
+ if (!empty($attr['class'])) {
415
+ $styling .= ' class="' . esc_attr($attr['class']) . '" ';
416
+ }
417
+
418
+ //Class and style attribute are mutually exclusive.
419
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
420
+ if (!empty($attr['link-class'])) {
421
+ $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
422
+ } else {
423
+ $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
424
+ }
425
+
426
+ $b = '';
427
+ if ($media->link) {
428
+ $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">';
429
+ } else {
430
+ // The span grants images are not upscaled when fluid (two columns posts block)
431
+ $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
432
+ }
433
+ if ($media) {
434
+ $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
435
+ if ($media->height) {
436
+ $b .= ' height="' . esc_attr($media->height) . '"';
437
+ }
438
+ $b .= ' alt="' . esc_attr($media->alt) . '"'
439
+ . ' border="0"'
440
+ . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0; font-size: 12px"'
441
+ . ' class="' . esc_attr($attr['class']) . '" '
442
+ . '>';
443
+ }
444
+
445
+ if ($media->link) {
446
+ $b .= '</a>';
447
+ } else {
448
+ $b .= '</span>';
449
+ }
450
+
451
+ return $b;
452
+ }
453
+
454
+ /**
455
+ * Returns a WP media ID for the specified post (or false if nothing can be found)
456
+ * looking for the featured image or, if missing, taking the first media in the gallery and
457
+ * if again missing, searching the first reference to a media in the post content.
458
+ *
459
+ * The media ID is not checked for real existance of the associated attachment.
460
+ *
461
+ * @param int $post_id
462
+ * @return int
463
+ */
464
+ static function get_post_thumbnail_id($post_id) {
465
+ if (is_object($post_id)) {
466
+ $post_id = $post_id->ID;
467
+ }
468
+
469
+ // Find a media id to be used as featured image
470
+ $media_id = get_post_thumbnail_id($post_id);
471
+ if (!empty($media_id)) {
472
+ return $media_id;
473
+ }
474
+
475
+ $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'));
476
+ if (!empty($attachments)) {
477
+ foreach ($attachments as $id => &$attachment) {
478
+ return $id;
479
+ }
480
+ }
481
+
482
+ $post = get_post($post_id);
483
+
484
+ $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
485
+ if ($matches) {
486
+ return (int) $matches[1];
487
+ }
488
+
489
+ return false;
490
+ }
491
+
492
+ /**
493
+ * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
494
+ * media has a size which best match the one requested (this is the standard WP behavior, plugins
495
+ * could change it).
496
+ *
497
+ * @param int $media_id
498
+ * @param array $size
499
+ * @return \TNP_Media
500
+ */
501
+ function get_media($media_id, $size) {
502
+ $src = wp_get_attachment_image_src($media_id, $size);
503
+ if (!$src) {
504
+ return null;
505
+ }
506
+ $media = new TNP_Media();
507
+ $media->id = $media_id;
508
+ $media->url = $src[0];
509
+ $media->width = $src[1];
510
+ $media->height = $src[2];
511
+ return $media;
512
+ }
513
+
514
+ static function post_content($post) {
515
+ $content = $post->post_content;
516
+ $content = wpautop($content);
517
+ if (true || $options['enable shortcodes']) {
518
+ remove_shortcode('gallery');
519
+ add_shortcode('gallery', 'tnp_gallery_shortcode');
520
+ $content = do_shortcode($content);
521
+ }
522
+ $content = str_replace('<p>', '<p class="paragraph">', $content);
523
+
524
+ $selected_images = array();
525
+ if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
526
+ foreach ($matches[0] as $image) {
527
+ if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
528
+ $selected_images[$image] = $attachment_id;
529
+ }
530
+ }
531
+ }
532
+
533
+ foreach ($selected_images as $image => $attachment_id) {
534
+ $src = tnp_media_resize($attachment_id, array(600, 0));
535
+ if (is_wp_error($src)) {
536
+ continue;
537
+ }
538
+ $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
539
+ }
540
+
541
+ return $content;
542
+ }
543
+
544
+ static function get_global_style_defaults() {
545
+ return [
546
+ 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
547
+ 'options_composer_title_font_size' => 32,
548
+ 'options_composer_title_font_weight' => 'normal',
549
+ 'options_composer_title_font_color' => '#222222',
550
+ 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
551
+ 'options_composer_text_font_size' => 16,
552
+ 'options_composer_text_font_weight' => 'normal',
553
+ 'options_composer_text_font_color' => '#222222',
554
+ 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
555
+ 'options_composer_button_font_size' => 16,
556
+ 'options_composer_button_font_weight' => 'normal',
557
+ 'options_composer_button_font_color' => '#FFFFFF',
558
+ 'options_composer_button_background_color' => '#256F9C',
559
+ 'options_composer_background' => '#FFFFFF',
560
+ 'options_composer_block_background' => '#FFFFFF',
561
+ ];
562
+ }
563
+
564
+ /**
565
+ * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
566
+ *
567
+ * Attributes:
568
+ * - columns: number of columns [2]
569
+ * - padding: cells padding [10]
570
+ * - responsive: il on mobile the cell should stack up [true]
571
+ * - width: the whole row width, it should reduced by the external row padding [600]
572
+ *
573
+ * @param string[] $items
574
+ * @param array $attrs
575
+ * @return string
576
+ */
577
+ static function grid($items = [], $attrs = []) {
578
+ $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
579
+ $width = (int) $attrs['width'];
580
+ $columns = (int) $attrs['columns'];
581
+ $padding = (int) $attrs['padding'];
582
+ $column_width = $width / $columns;
583
+ $td_width = 100 / $columns;
584
+ $chunks = array_chunk($items, $columns);
585
+
586
+ if ($attrs['responsive']) {
587
+
588
+ $e = '';
589
+ foreach ($chunks as &$chunk) {
590
+ $e .= '<div style="text-align:center;font-size:0;">';
591
+ $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
592
+ foreach ($chunk as &$item) {
593
+ $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
594
+
595
+ $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
596
+
597
+ // This element to add padding without deal with border-box not well supported
598
+ $e .= '<div style="padding:' . $padding . 'px;">';
599
+ $e .= $item;
600
+ $e .= '</div>';
601
+ $e .= '</div>';
602
+
603
+ $e .= '<!--[if mso]></td><![endif]-->';
604
+ }
605
+ $e .= '<!--[if mso]></tr></table><![endif]-->';
606
+ $e .= '</div>';
607
+ }
608
+
609
+ return $e;
610
+ } else {
611
+ $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
612
+ foreach ($chunks as &$chunk) {
613
+ $e .= '<tr>';
614
+ foreach ($chunk as &$item) {
615
+ $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
616
+ $e .= $item;
617
+ $e .= '</td>';
618
+ }
619
+ $e .= '</tr>';
620
+ }
621
+ $e .= '</table>';
622
+ return $e;
623
+ }
624
+ }
625
+
626
+ static function get_text_style($options, $prefix, $composer, $attrs = []) {
627
+ return self::get_style($options, $prefix, $composer, 'text', $attrs);
628
+ }
629
+
630
+ static function get_title_style($options, $prefix, $composer, $attrs = []) {
631
+ return self::get_style($options, $prefix, $composer, 'title', $attrs);
632
+ }
633
+
634
+ static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
635
+ $style = new TNP_Style();
636
+ $scale = 1.0;
637
+ if (!empty($attrs['scale'])) {
638
+ $scale = (float) $attrs['scale'];
639
+ }
640
+ if (!empty($prefix))
641
+ $prefix .= '_';
642
+
643
+ $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
644
+ $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
645
+ $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
646
+ $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
647
+ if ($type === 'button') {
648
+ $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
649
+ }
650
+ return $style;
651
+ }
652
+
653
+ static function get_button_options($options, $prefix, $composer) {
654
+ $button_options = [];
655
+ $scale = 1;
656
+ $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
657
+ $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
658
+ $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
659
+ $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
660
+ $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
661
+ $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
662
+ $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
663
+ $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
664
+ $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
665
+
666
+ return $button_options;
667
+ }
668
+
669
+ static function convert_to_text($html) {
670
+ if (!class_exists('DOMDocument')) {
671
+ return '';
672
+ }
673
+ // Replace '&' with '&amp;' in URLs to avoid warnings about inavlid entities from loadHTML()
674
+ // Todo: make this more general using a regular expression
675
+ //$logger = PlaintextNewsletterAddon::$instance->get_logger();
676
+ //$logger->debug('html="' . $html . '"');
677
+ $html = str_replace(
678
+ array('&nk=', '&nek=', '&id='),
679
+ array('&amp;nk=', '&amp;nek=', '&amp;id='),
680
+ $html);
681
+ //$logger->debug('new html="' . $html . '"');
682
+ //
683
+ $output = '';
684
+ $dom = new DOMDocument();
685
+ $r = $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html);
686
+ if (!$r) {
687
+ return '';
688
+ }
689
+ $bodylist = $dom->getElementsByTagName('body');
690
+ // Of course it should be a single element
691
+ foreach ($bodylist as $body) {
692
+ self::process_dom_element($body, $output);
693
+ }
694
+ return $output;
695
+ }
696
+
697
+ static function process_dom_element(DOMElement $parent, &$output) {
698
+ foreach ($parent->childNodes as $node) {
699
+ if (is_a($node, 'DOMElement') && ($node->tagName != 'style')) {
700
+ if ($node->tagName== 'br') {
701
+ $output .= "\n";
702
+ continue;
703
+ }
704
+ self::process_dom_element($node, $output);
705
+
706
+ // If the containing tag was a block level tag, we add a couple of line ending
707
+ if ($node->tagName == 'p' || $node->tagName == 'div' || $node->tagName == 'td') {
708
+ // Avoid more than one blank line between elements
709
+ if ((strlen($output) >= 2) && (substr($output, -2) != "\n\n")) {
710
+ $output .= "\n\n";
711
+ }
712
+ }
713
+
714
+ if ($node->tagName == 'a') {
715
+ $output .= ' (' . $node->getAttribute('href') . ') ';
716
+ continue;
717
+ }
718
+ elseif ($node->tagName == 'img') {
719
+ $output .= $node->getAttribute('alt');
720
+ }
721
+ }
722
+ elseif (is_a($node, 'DOMText')) {
723
+ $decoded = utf8_decode($node->wholeText);
724
+ if (ctype_space($decoded)) {
725
+ // Append blank only if last character output is not blank.
726
+ if ((strlen($output) > 0) && !ctype_space(substr($output, -1))) {
727
+ $output .= ' ';
728
+ }
729
+ } else {
730
+ $output .= trim($node->wholeText);
731
+ }
732
+ }
733
+ }
734
+ }
735
+ }
736
+
737
+
738
+
739
+ class TNP_Style {
740
+
741
+ var $font_family;
742
+ var $font_size;
743
+ var $font_weight;
744
+ var $font_color;
745
+ var $background;
746
+
747
+ function echo_css($scale = 1.0) {
748
+ echo 'font-size: ', round($this->font_size * $scale), 'px;';
749
+ echo 'font-family: ', $this->font_family, ';';
750
+ echo 'font-weight: ', $this->font_weight, ';';
751
+ echo 'color: ', $this->font_color, ';';
752
+ }
753
+
754
+ }
755
+
756
+ /**
757
+ * Generate multicolumn and responsive html template for email.
758
+ * Initialize class with max columns per row and start to add cells.
759
+ */
760
+ class TNP_Composer_Grid_System {
761
+
762
+ /**
763
+ * @var TNP_Composer_Grid_Row[]
764
+ */
765
+ private $rows;
766
+
767
+ /**
768
+ * @var int
769
+ */
770
+ private $cells_per_row;
771
+
772
+ /**
773
+ * @var int
774
+ */
775
+ private $cells_counter;
776
+
777
+ /**
778
+ * TNP_Composer_Grid_System constructor.
779
+ *
780
+ * @param int $columns_per_row Max columns per row
781
+ */
782
+ public function __construct($columns_per_row) {
783
+ $this->cells_per_row = $columns_per_row;
784
+ $this->cells_counter = 0;
785
+ $this->rows = [];
786
+ }
787
+
788
+ public function __toString() {
789
+ return $this->render();
790
+ }
791
+
792
+ /**
793
+ * Add cell to grid
794
+ *
795
+ * @param TNP_Composer_Grid_Cell $cell
796
+ */
797
+ public function add_cell($cell) {
798
+
799
+ if ($this->cells_counter % $this->cells_per_row === 0) {
800
+ $this->add_row(new TNP_Composer_Grid_Row());
801
+ }
802
+
803
+ $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
804
+ $this->rows[$row_idx]->add_cell($cell);
805
+ $this->cells_counter++;
806
+ }
807
+
808
+ private function add_row($row) {
809
+ $this->rows[] = $row;
810
+ }
811
+
812
+ public function render() {
813
+
814
+ $str = '';
815
+ foreach ($this->rows as $row) {
816
+ $str .= $row->render();
817
+ }
818
+
819
+ return $str;
820
+ }
821
+
822
+ }
823
+
824
+ /**
825
+ * Class TNP_Composer_Grid_Row
826
+ */
827
+ class TNP_Composer_Grid_Row {
828
+
829
+ /**
830
+ * @var TNP_Composer_Grid_Cell[]
831
+ */
832
+ private $cells;
833
+
834
+ public function __construct(...$cells) {
835
+ if (!empty($cells)) {
836
+ foreach ($cells as $cell) {
837
+ $this->add_cell($cell);
838
+ }
839
+ }
840
+ }
841
+
842
+ /**
843
+ * @param TNP_Composer_Grid_Cell $cell
844
+ */
845
+ public function add_cell($cell) {
846
+ $this->cells[] = $cell;
847
+ }
848
+
849
+ public function render() {
850
+ $rendered_cells = '';
851
+ $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
852
+ foreach ($this->cells as $cell) {
853
+ $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
854
+ }
855
+
856
+ $row_template = $this->get_template();
857
+
858
+ return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
859
+ }
860
+
861
+ private function cells_count() {
862
+ return count($this->cells);
863
+ }
864
+
865
+ private function get_template() {
866
+ return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
867
+ }
868
+
869
+ }
870
+
871
+ /**
872
+ * Class TNP_Composer_Grid_Cell
873
+ */
874
+ class TNP_Composer_Grid_Cell {
875
+
876
+ /**
877
+ * @var string
878
+ */
879
+ private $content;
880
+
881
+ /**
882
+ * @var array
883
+ */
884
+ public $args;
885
+
886
+ public function __construct($content = null, $args = []) {
887
+ $default_args = [
888
+ 'width' => '100%',
889
+ 'class' => '',
890
+ 'align' => 'left',
891
+ 'valign' => 'top'
892
+ ];
893
+
894
+ $this->args = array_merge($default_args, $args);
895
+
896
+ $this->content = $content ? $content : '';
897
+ }
898
+
899
+ public function add_content($content) {
900
+ $this->content .= $content;
901
+ }
902
+
903
+ public function render($args) {
904
+ $this->args = array_merge($this->args, $args);
905
+
906
+ $column_template = $this->get_template();
907
+ $column = str_replace(
908
+ [
909
+ 'TNP_ALIGN_PH',
910
+ 'TNP_VALIGN_PH',
911
+ 'TNP_WIDTH_PH',
912
+ 'TNP_CLASS_PH',
913
+ 'TNP_COLUMN_CONTENT_PH'
914
+ ], [
915
+ $this->args['align'],
916
+ $this->args['valign'],
917
+ $this->args['width'],
918
+ $this->args['class'],
919
+ $this->content
920
+ ], $column_template);
921
+
922
+ return $column;
923
+ }
924
+
925
+ private function get_template() {
926
+ return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
927
+ <tbody>
928
+ <tr>
929
+ <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
930
+ TNP_COLUMN_CONTENT_PH
931
+ </td>
932
+ </tr>
933
+ </tbody>
934
+ </table>";
935
+ }
936
+
937
+ }
938
+
939
+ class TNP_Composer_Component_Factory {
940
+
941
+ private $options;
942
+
943
+ /**
944
+ * TNP_Composer_Component_Factory constructor.
945
+ *
946
+ * @param Controller$controller
947
+ */
948
+ public function __construct($controller) {
949
+
950
+ }
951
+
952
+ function heading() {
953
+
954
+ }
955
+
956
+ function paragraph() {
957
+
958
+ }
959
+
960
+ function link() {
961
+
962
+ }
963
+
964
+ function button() {
965
+
966
+ }
967
+
968
+ function image() {
969
+
970
+ }
971
+
972
+ }
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.4.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.4.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.4.5
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.4.5');
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.0
4
- Stable tag: 7.4.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,10 +126,6 @@ Thank you, The Newsletter Team
126
 
127
  == Changelog ==
128
 
129
- = 7.4.6 =
130
-
131
- * XSS security fix
132
-
133
  = 7.4.5 =
134
 
135
  * Fixed image block font-size to show the alt text (not for Outlook, it never shows the alt text)
1
  === Newsletter - Send awesome emails from WordPress ===
2
  Tags: newsletter, email marketing, welcome email, signup forms, lead generation, marketing automation
3
  Tested up to: 6.0
4
+ Stable tag: 7.4.5
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.4.5 =
130
 
131
  * Fixed image block font-size to show the alt text (not for Outlook, it never shows the alt text)
subscription/subscription.php CHANGED
@@ -1,1941 +1,1941 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- class NewsletterSubscription extends NewsletterModule {
6
-
7
- const MESSAGE_CONFIRMED = 'confirmed';
8
- const OPTIN_DOUBLE = 0;
9
- const OPTIN_SINGLE = 1;
10
-
11
- static $instance;
12
-
13
- /**
14
- * @var array
15
- */
16
- var $options_profile;
17
-
18
- /**
19
- * Contains the options for the current language to build a subscription form. Must be initialized with
20
- * setup_form_options() before use.
21
- *
22
- * @var array
23
- */
24
- var $form_options = null;
25
-
26
- /**
27
- * Contains the antibot/antispam options. Must be initialized with
28
- * setup_antibot_options() before use.
29
- *
30
- * @var array
31
- */
32
- var $antibot_options = null;
33
-
34
- /**
35
- * @var array
36
- */
37
- var $options_lists;
38
-
39
- /**
40
- * @return NewsletterSubscription
41
- */
42
- static function instance() {
43
- if (self::$instance == null) {
44
- self::$instance = new NewsletterSubscription();
45
- }
46
- return self::$instance;
47
- }
48
-
49
- function __construct() {
50
-
51
- parent::__construct('subscription', '2.2.7', null, array('lists', 'template', 'profile', 'antibot'));
52
- $this->options_profile = $this->get_options('profile');
53
- $this->options_lists = $this->get_options('lists');
54
-
55
- // Must be called after the Newsletter::hook_init, since some constants are defined
56
- // there.
57
- add_action('init', array($this, 'hook_init'), 90);
58
- }
59
-
60
- function hook_init() {
61
- add_action('newsletter_action', array($this, 'hook_newsletter_action'), 10, 3);
62
- if (is_admin()) {
63
- add_action('admin_init', array($this, 'hook_admin_init'));
64
- } else {
65
- // Shortcode for the Newsletter page
66
- add_shortcode('newsletter', array($this, 'shortcode_newsletter'));
67
- add_shortcode('newsletter_form', array($this, 'shortcode_newsletter_form'));
68
- add_shortcode('newsletter_field', array($this, 'shortcode_newsletter_field'));
69
- }
70
- }
71
-
72
- function hook_admin_init() {
73
- // So the user can add JS and other code
74
- if (isset($_GET['page']) && $_GET['page'] === 'newsletter_subscription_forms') {
75
- header('X-XSS-Protection: 0');
76
- }
77
-
78
- if (function_exists('register_block_type')) {
79
- // Add custom blocks to Gutenberg
80
- wp_register_script('tnp-blocks', plugins_url('newsletter') . '/includes/tnp-blocks.js', array('wp-block-editor', 'wp-blocks', 'wp-element', 'wp-components'), NEWSLETTER_VERSION);
81
- register_block_type('tnp/minimal', array('editor_script' => 'tnp-blocks'));
82
- }
83
- }
84
-
85
- /**
86
- *
87
- * @global wpdb $wpdb
88
- * @return mixed
89
- */
90
- function hook_newsletter_action($action, $user, $email) {
91
- global $wpdb;
92
-
93
- switch ($action) {
94
- case 'profile-change':
95
- if ($this->antibot_form_check()) {
96
-
97
- if (!$user || $user->status != TNP_user::STATUS_CONFIRMED) {
98
- $this->dienow('Subscriber not found or not confirmed.', 'Even the wrong subscriber token can lead to this error.', 404);
99
- }
100
-
101
- if (!$email) {
102
- $this->dienow('Newsletter not found', 'The newsletter containing the link has been deleted.', 404);
103
- }
104
-
105
- if (isset($_REQUEST['list'])) {
106
- $list_id = (int) $_REQUEST['list'];
107
-
108
- // Check if the list is public
109
- $list = $this->get_list($list_id);
110
- if (!$list || $list->status == TNP_List::STATUS_PRIVATE) {
111
- $this->dienow('List change not allowed.', 'Please check if the list is marked as "private".', 400);
112
- }
113
-
114
- if (empty($_REQUEST['redirect'])) {
115
- $url = home_url();
116
- } else {
117
- $url = esc_url_raw($_REQUEST['redirect']);
118
- }
119
- $this->set_user_list($user, $list_id, $_REQUEST['value']);
120
-
121
- $user = $this->get_user($user->id);
122
- $this->add_user_log($user, 'cta');
123
- NewsletterStatistics::instance()->add_click($url, $user->id, $email->id);
124
- wp_redirect($url);
125
- die();
126
- }
127
- } else {
128
- $this->request_to_antibot_form('Continue');
129
- }
130
-
131
- die();
132
-
133
- case 'm':
134
- case 'message':
135
- include dirname(__FILE__) . '/page.php';
136
- die();
137
-
138
- // normal subscription
139
- case 's':
140
- case 'subscribe':
141
-
142
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
143
- $this->dienow('Invalid request', 'The subscription request was not made with a HTTP POST', 400);
144
- }
145
-
146
- $options_antibot = $this->get_options('antibot');
147
-
148
- $captcha = !empty($options_antibot['captcha']);
149
-
150
- if (!empty($_GET['_wp_amp_action_xhr_converted']) || !empty($options_antibot['disabled']) || $this->antibot_form_check($captcha)) {
151
-
152
- $subscription = $this->build_subscription();
153
-
154
- $user = $this->subscribe2($subscription);
155
-
156
- if (is_wp_error($user)) {
157
- if ($user->get_error_code() === 'exists') {
158
- $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
159
- $options = $this->get_options('', $language);
160
- $this->dienow($options['error_text'], $user->get_error_message(), 200);
161
- }
162
- $this->dienow('Registration failed.', $user->get_error_message(), 400);
163
- }
164
-
165
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
166
- $this->show_message('confirmed', $user);
167
- }
168
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
169
- $this->show_message('confirmation', $user);
170
- }
171
- } else {
172
- $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
173
- $options = $this->get_form_options($language);
174
- $this->request_to_antibot_form($options['subscribe'], $captcha);
175
- }
176
- die();
177
-
178
- // AJAX subscription
179
- case 'ajaxsub':
180
-
181
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
182
- $this->dienow('Invalid request');
183
- }
184
-
185
- $subscription = $this->build_subscription();
186
-
187
- $user = $this->subscribe2($subscription);
188
-
189
- if (is_wp_error($user)) {
190
- if ($user->get_error_code() === 'exists') {
191
- echo $this->options['error_text'];
192
- die();
193
- } else {
194
- $this->dienow('Registration failed.', $user->get_error_message(), 400);
195
- }
196
- } else {
197
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
198
- $key = 'confirmed';
199
- }
200
-
201
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
202
- $key = 'confirmation';
203
- }
204
- }
205
-
206
- $message = $this->replace($this->options[$key . '_text'], $user);
207
- if (isset($this->options[$key . '_tracking'])) {
208
- $message .= $this->options[$key . '_tracking'];
209
- }
210
- echo $message;
211
- die();
212
-
213
- case 'c':
214
- case 'confirm':
215
- if (!$user) {
216
- $this->dienow(__('Subscriber not found.', 'newsletter'), 'Or it is not present or the secret key does not match.', 404);
217
- }
218
-
219
- if ($this->antibot_form_check()) {
220
- $user = $this->confirm($user);
221
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
222
- $this->show_message('confirmed', $user);
223
- } else {
224
- $this->request_to_antibot_form('Confirm');
225
- }
226
- die();
227
- break;
228
-
229
- default:
230
- return;
231
- }
232
- }
233
-
234
- function upgrade() {
235
- global $wpdb, $charset_collate;
236
-
237
- parent::upgrade();
238
-
239
- $newsletter = Newsletter::instance();
240
- $lists_options = $this->get_options('lists');
241
- $profile_options = $this->get_options('profile');
242
-
243
- if (empty($lists_options)) {
244
- foreach ($profile_options as $key => $value) {
245
- if (strpos($key, 'list_') === 0) {
246
- $lists_options[$key] = $value;
247
- }
248
- }
249
- }
250
-
251
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
252
- // Options migration to the new set
253
- if (!empty($profile_options['list_' . $i]) && empty($lists_options['list_' . $i])) {
254
- $lists_options['list_' . $i] = $profile_options['list_' . $i];
255
- $lists_options['list_' . $i . '_checked'] = $profile_options['list_' . $i . '_checked'];
256
- $lists_options['list_' . $i . '_forced'] = $profile_options['list_' . $i . '_forced'];
257
- }
258
-
259
- if (!isset($profile_options['list_' . $i . '_forced'])) {
260
- $profile_options['list_' . $i . '_forced'] = empty($this->options['preferences_' . $i]) ? 0 : 1;
261
- $lists_options['list_' . $i . '_forced'] = empty($this->options['preferences_' . $i]) ? 0 : 1;
262
- }
263
- }
264
-
265
- $this->save_options($profile_options, 'profile');
266
- $this->save_options($lists_options, 'lists');
267
-
268
- $default_options = $this->get_default_options();
269
-
270
- if (empty($this->options['error_text'])) {
271
- $this->options['error_text'] = $default_options['error_text'];
272
- $this->save_options($this->options);
273
- }
274
-
275
- $this->init_options('template', false);
276
-
277
- global $wpdb, $charset_collate;
278
-
279
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
280
-
281
- $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_user_logs` (
282
- `id` int(11) NOT NULL AUTO_INCREMENT,
283
- `user_id` int(11) NOT NULL DEFAULT 0,
284
- `ip` varchar(50) NOT NULL DEFAULT '',
285
- `source` varchar(50) NOT NULL DEFAULT '',
286
- `data` longtext,
287
- `created` int(11) NOT NULL DEFAULT 0,
288
- PRIMARY KEY (`id`)
289
- ) $charset_collate;";
290
-
291
- dbDelta($sql);
292
-
293
- return true;
294
- }
295
-
296
- function first_install() {
297
-
298
- }
299
-
300
- function admin_menu() {
301
- $this->add_menu_page('options', __('List building', 'newsletter'));
302
- $this->add_admin_page('lists', __('Lists', 'newsletter'));
303
- $this->add_admin_page('profile', __('Subscription Form', 'newsletter'));
304
- $this->add_menu_page('antibot', __('Security', 'newsletter'));
305
- $this->add_admin_page('forms', __('Forms', 'newsletter'));
306
- $this->add_admin_page('template', __('Template', 'newsletter'));
307
- }
308
-
309
- /**
310
- * This method has been redefined for compatibility with the old options naming. It would
311
- * be better to change them instead. The subscription options should be named
312
- * "newsletter_subscription" while the form field options, actually named
313
- * "newsletter_profile", should be renamed "newsletter_subscription_profile" (since
314
- * they are retrived with get_options('profile')) or "newsletter_subscription_fields" or
315
- * "newsletter_subscription_form".
316
- *
317
- * @param array $options
318
- * @param string $sub
319
- */
320
- function save_options($options, $sub = '', $autoload = null, $language = '') {
321
- if (empty($sub) && empty($language)) {
322
- // For compatibility the options are wrongly named
323
- return update_option('newsletter', $options, $autoload);
324
- }
325
-
326
- if (empty($sub) && !empty($language)) {
327
- return update_option('newsletter_' . $language, $options, $autoload);
328
- }
329
-
330
- if ($sub == 'profile') {
331
- if (empty($language)) {
332
- $this->options_profile = $options;
333
- return update_option('newsletter_profile', $options, $autoload);
334
- } else {
335
- return update_option('newsletter_profile_' . $language, $options, $autoload);
336
- }
337
- // For compatibility the options are wrongly named
338
- }
339
-
340
- if ($sub == 'forms') {
341
- // For compatibility the options are wrongly named
342
- return update_option('newsletter_forms', $options, $autoload);
343
- }
344
-
345
- if ($sub == 'lists') {
346
- $this->options_lists = $options;
347
- }
348
- return parent::save_options($options, $sub, $autoload, $language);
349
- }
350
-
351
- function get_options($sub = '', $language = '') {
352
- if ($sub == '') {
353
- // For compatibility the options are wrongly named
354
- if ($language) {
355
- $options = get_option('newsletter_' . $language, []);
356
- $options = array_merge(get_option('newsletter', []), $options);
357
- } else {
358
- $options = get_option('newsletter', []);
359
- }
360
- if (!is_array($options)) {
361
- $options = array();
362
- }
363
-
364
- return $options;
365
- }
366
- if ($sub == 'profile') {
367
- if ($language) {
368
- // All that because for unknown reasome, sometime the options are returned as string, maybe a WPML
369
- // interference...
370
- $i18n_options = get_option('newsletter_profile_' . $language, []);
371
- if (!is_array($i18n_options)) {
372
- $i18n_options = [];
373
- }
374
- $options = get_option('newsletter_profile', []);
375
- if (!is_array($options)) {
376
- $options = [];
377
- }
378
- $options = array_merge($options, array_filter($i18n_options));
379
- } else {
380
- $options = get_option('newsletter_profile', []);
381
- }
382
- // For compatibility the options are wrongly named
383
- return $options;
384
- }
385
- if ($sub == 'forms') {
386
- // For compatibility the options are wrongly named
387
- return get_option('newsletter_forms', []);
388
- }
389
- return parent::get_options($sub, $language);
390
- }
391
-
392
- /**
393
- * Prepares the options used to build a subscription form for the current language
394
- * in internal variable $form_options (for optimization). Can be called many times.
395
- */
396
- function setup_form_options() {
397
- if (empty($this->form_options)) {
398
- $this->form_options = $this->get_options('profile', $this->get_current_language());
399
- }
400
- }
401
-
402
- function get_form_options($language = '') {
403
- return $this->get_options('profile', $language);
404
- }
405
-
406
- function set_updated($user, $time = 0, $ip = '') {
407
- global $wpdb;
408
- if (!$time) {
409
- $time = time();
410
- }
411
-
412
- if (!$ip) {
413
- $ip = $this->get_remote_ip();
414
- }
415
- $ip = $this->process_ip($ip);
416
-
417
- if (is_object($user)) {
418
- $id = $user->id;
419
- } else if (is_array($user)) {
420
- $id = $user['id'];
421
- }
422
-
423
- $id = (int) $id;
424
-
425
- $wpdb->update(NEWSLETTER_USERS_TABLE, array('updated' => $time, 'ip' => $ip, 'geo' => 0), array('id' => $id));
426
- }
427
-
428
- /**
429
- * Sanitize the subscription data collected before process them. Cleanup the lists, the optin mode, email, first name,
430
- * last name, adds mandatory lists, get (if not provided) and process the IP.
431
- *
432
- * @param TNP_Subscription_Data $data
433
- */
434
- private function sanitize($data) {
435
- $data->email = $this->normalize_email($data->email);
436
- if (!empty($data->name)) {
437
- $data->name = $this->normalize_name($data->name);
438
- }
439
- if (!empty($data->surname)) {
440
- $data->surname = $this->normalize_name($data->surname);
441
- }
442
-
443
- if (empty($data->ip)) {
444
- $data->ip = $this->get_remote_ip();
445
- }
446
- $data->ip = $this->process_ip($data->ip);
447
-
448
- if (isset($data->http_referer)) {
449
- $data->http_referer = mb_substr(strip_tags($data->http_referer), 0, 200);
450
- }
451
-
452
- if (isset($data->sex)) {
453
- $data->sex = $this->normalize_sex($data->sex);
454
- }
455
-
456
- if (!isset($data->language)) {
457
- $data->language = $this->get_current_language();
458
- } else {
459
- $data->language = strtolower(strip_tags($data->language));
460
- }
461
-
462
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
463
- $key = 'profile_' . $i;
464
- if (isset($data->$key)) {
465
- $data->$key = trim($data->$key);
466
- }
467
- }
468
- }
469
-
470
- /**
471
- * Builds a default subscription object to be used to collect data and subscription options.
472
- *
473
- * @return TNP_Subscription
474
- */
475
- function get_default_subscription($language = null) {
476
- $subscription = new TNP_Subscription();
477
-
478
- $language = is_null($language) ? $this->get_current_language() : $language;
479
-
480
- $subscription->data->language = $language;
481
- $subscription->optin = $this->is_double_optin() ? 'double' : 'single';
482
-
483
- if (empty($this->options['multiple'])) {
484
- $subscription->if_exists = TNP_Subscription::EXISTING_ERROR;
485
- } else if ($this->options['multiple'] == 1) {
486
- $subscription->if_exists = TNP_Subscription::EXISTING_MERGE;
487
- } else {
488
- $subscription->if_exists = TNP_Subscription::EXISTING_SINGLE_OPTIN;
489
- }
490
-
491
- $lists = $this->get_lists();
492
- foreach ($lists as $list) {
493
- if ($list->forced) {
494
- $subscription->data->lists['' . $list->id] = 1;
495
- continue;
496
- }
497
- // Enforced by language
498
- if ($language && in_array($language, $list->languages)) {
499
- $subscription->data->lists['' . $list->id] = 1;
500
- }
501
- }
502
-
503
- return $subscription;
504
- }
505
-
506
- /**
507
- *
508
- * @param TNP_Subscription $subscription
509
- *
510
- * @return TNP_User|WP_Error
511
- */
512
- function subscribe2(TNP_Subscription $subscription) {
513
-
514
- $this->logger->debug($subscription);
515
-
516
- $this->sanitize($subscription->data);
517
-
518
- if (empty($subscription->data->email)) {
519
- return new WP_Error('email', 'Wrong email address');
520
- }
521
-
522
- if (!empty($subscription->data->country) && strlen($subscription->data->country) != 2) {
523
- return new WP_Error('country', 'Country code length error. ISO 3166-1 alpha-2 format (2 letters)');
524
- }
525
-
526
- // Here we should have a clean subscription data
527
- // Filter?
528
-
529
- if ($subscription->spamcheck) {
530
- // TODO: Use autoload
531
- require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
532
- $antispam = NewsletterAntispam::instance();
533
- if ($antispam->is_spam($subscription)) {
534
- return new WP_Error('spam', 'This looks like a spam subscription');
535
- }
536
- }
537
-
538
- // Exists?
539
- $user = $this->get_user_by_email($subscription->data->email);
540
-
541
- $subscription = apply_filters('newsletter_subscription', $subscription, $user);
542
-
543
- // Do we accept repeated subscriptions?
544
- if ($user != null && $subscription->if_exists === TNP_Subscription::EXISTING_ERROR) {
545
- //$this->show_message('error', $user);
546
- return new WP_Error('exists', 'Email address already registered and Newsletter sets to block repeated registrations. You can change this behavior or the user message above on subscription configuration panel.');
547
- }
548
-
549
-
550
- if ($user != null) {
551
-
552
- $this->logger->info('Subscription of an address with status ' . $user->status);
553
-
554
- // We cannot communicate with bounced addresses, there is no reason to proceed
555
- // TODO: Evaluate if the bounce status is very old, possible reset it
556
- if ($user->status == TNP_User::STATUS_BOUNCED || $user->status == TNP_User::STATUS_COMPLAINED) {
557
- return new WP_Error('bounced', 'Subscriber present and blocked');
558
- }
559
-
560
- if ($user->status == TNP_User::STATUS_UNSUBSCRIBED) {
561
- // Special behavior?
562
- }
563
-
564
- if ($subscription->optin == 'single') {
565
- $user->status = TNP_User::STATUS_CONFIRMED;
566
- } else {
567
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
568
-
569
- set_transient('newsletter_subscription_' . $user->id, $subscription->data, 3600 * 48);
570
-
571
- // This status is *not* stored it indicate a temporary status to show the correct messages
572
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
573
-
574
- $this->send_message('confirmation', $user);
575
-
576
- return $user;
577
- } else {
578
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
579
- }
580
- }
581
-
582
- // Can be updated on the fly?
583
- $subscription->data->merge_in($user);
584
- } else {
585
- $this->logger->info('New subscriber');
586
-
587
- $user = new TNP_User();
588
- $subscription->data->merge_in($user);
589
-
590
- $user->token = $this->get_token();
591
-
592
- $user->status = $subscription->optin == 'single' ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
593
- $user->updated = time();
594
- }
595
-
596
- $user->ip = $this->process_ip($user->ip);
597
-
598
- $user = apply_filters('newsletter_user_subscribe', $user);
599
-
600
- $user = $this->save_user($user);
601
-
602
- $this->add_user_log($user, 'subscribe');
603
-
604
- // Notification to admin (only for new confirmed subscriptions)
605
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
606
- do_action('newsletter_user_confirmed', $user);
607
- $this->notify_admin_on_subscription($user);
608
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
609
- }
610
-
611
- if ($subscription->send_emails) {
612
- $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
613
- }
614
-
615
- // Used by Autoresponder (probably)
616
- do_action('newsletter_user_post_subscribe', $user);
617
-
618
- return $user;
619
- }
620
-
621
- /**
622
- * Create a subscription using the $_REQUEST data. Does security checks.
623
- *
624
- * @deprecated since version 6.9.0
625
- * @param string $status The status to use for this subscription (confirmed, not confirmed, ...)
626
- * @param bool $emails If the confirmation/welcome email should be sent or the subscription should be silent
627
- * @return TNP_User
628
- */
629
- function subscribe($status = null, $emails = true) {
630
-
631
- $this->logger->debug('Subscription start');
632
-
633
- // Validation
634
- $ip = $this->get_remote_ip();
635
- $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
636
- $first_name = '';
637
- if (isset($_REQUEST['nn'])) {
638
- $first_name = $this->normalize_name(stripslashes($_REQUEST['nn']));
639
- }
640
-
641
- $last_name = '';
642
- if (isset($_REQUEST['ns'])) {
643
- $last_name = $this->normalize_name(stripslashes($_REQUEST['ns']));
644
- }
645
-
646
- $opt_in = (int) $this->options['noconfirmation']; // 0 - double, 1 - single
647
- if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
648
- switch ($_REQUEST['optin']) {
649
- case 'single': $opt_in = self::OPTIN_SINGLE;
650
- break;
651
- case 'double': $opt_in = self::OPTIN_DOUBLE;
652
- break;
653
- }
654
- }
655
-
656
- if ($status != null) {
657
- // If a status is forced and it is requested to be "confirmed" is like a single opt in
658
- // $status here can only be confirmed or not confirmed
659
- // TODO: Add a check on status values
660
- if ($status == TNP_User::STATUS_CONFIRMED) {
661
- $opt_in = self::OPTIN_SINGLE;
662
- } else {
663
- $opt_in = self::OPTIN_DOUBLE;
664
- }
665
- }
666
-
667
- $user = $this->get_user($email);
668
-
669
- if ($user != null) {
670
- // Email already registered in our database
671
- $this->logger->info('Subscription of an address with status ' . $user->status);
672
-
673
- // Bounced
674
- // TODO: Manage other cases when added
675
- if ($user->status == 'B') {
676
- // Non persistent status to decide which message to show (error)
677
- $user->status = 'E';
678
- return $user;
679
- }
680
-
681
- // Is there any relevant data change? If so we can proceed otherwise if repeated subscriptions are disabled
682
- // show an already subscribed message
683
-
684
- if (empty($this->options['multiple'])) {
685
- $user->status = 'E';
686
- return $user;
687
- }
688
-
689
- // If the subscriber is confirmed, we cannot change his data in double opt in mode, we need to
690
- // temporary store and wait for activation
691
- if ($user->status == TNP_User::STATUS_CONFIRMED && $opt_in == self::OPTIN_DOUBLE) {
692
-
693
- set_transient($this->get_user_key($user), $_REQUEST, 3600 * 48);
694
-
695
- // This status is *not* stored it indicate a temporary status to show the correct messages
696
- $user->status = 'S';
697
-
698
- $this->send_message('confirmation', $user);
699
-
700
- return $user;
701
- }
702
- }
703
-
704
- // Here we have a new subscription or we can process the subscription even with a pre-existant user for example
705
- // because it is not confirmed
706
- if ($user != null) {
707
- $this->logger->info("Email address subscribed but not confirmed");
708
- $user = array('id' => $user->id);
709
- } else {
710
- $this->logger->info("New email address");
711
- $user = array('email' => $email);
712
- }
713
-
714
- $user = $this->update_user_from_request($user);
715
-
716
- $user['token'] = $this->get_token();
717
- $ip = $this->process_ip($ip);
718
- $user['ip'] = $ip;
719
- $user['geo'] = 0;
720
- $user['status'] = $opt_in == self::OPTIN_SINGLE ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
721
-
722
- $user['updated'] = time();
723
-
724
- $user = apply_filters('newsletter_user_subscribe', $user);
725
-
726
- $user = $this->save_user($user);
727
-
728
- $this->add_user_log($user, 'subscribe');
729
-
730
- // Notification to admin (only for new confirmed subscriptions)
731
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
732
- do_action('newsletter_user_confirmed', $user);
733
- $this->notify_admin_on_subscription($user);
734
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
735
- }
736
-
737
- if ($emails) {
738
- $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
739
- }
740
-
741
- $user = apply_filters('newsletter_user_post_subscribe', $user);
742
-
743
- return $user;
744
- }
745
-
746
- function add_microdata($message) {
747
- return $message . '<span itemscope itemtype="http://schema.org/EmailMessage"><span itemprop="description" content="Email address confirmation"></span><span itemprop="action" itemscope itemtype="http://schema.org/ConfirmAction"><meta itemprop="name" content="Confirm Subscription"><span itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler"><meta itemprop="url" content="{subscription_confirm_url}"><link itemprop="method" href="http://schema.org/HttpRequestMethod/POST"></span></span></span>';
748
- }
749
-
750
- /**
751
- * Builds a subscription object starting from values in the $_REQUEST
752
- * global variable. It DOES NOT sanitizie or formally check the values.
753
- * Usually data comes from a form submission.
754
- * https://www.thenewsletterplugin.com/documentation/subscription/newsletter-forms/
755
- *
756
- * @return TNP_Subscription
757
- */
758
- function build_subscription() {
759
-
760
- $language = '';
761
- if (!empty($_REQUEST['nlang'])) {
762
- $language = $_REQUEST['nlang'];
763
- } else {
764
- $language = $this->get_current_language();
765
- }
766
-
767
- $subscription = $this->get_default_subscription($language);
768
- $data = $subscription->data;
769
-
770
- $data->email = $_REQUEST['ne'];
771
-
772
- if (isset($_REQUEST['nn'])) {
773
- $data->name = stripslashes($_REQUEST['nn']);
774
- }
775
-
776
- if (isset($_REQUEST['ns'])) {
777
- $data->surname = stripslashes($_REQUEST['ns']);
778
- }
779
-
780
- if (!empty($_REQUEST['nx'])) {
781
- $data->sex = $_REQUEST['nx'][0];
782
- }
783
-
784
- if (isset($_REQUEST['nr'])) {
785
- $data->referrer = $_REQUEST['nr'];
786
- }
787
-
788
- // From the antibot form
789
- if (isset($_REQUEST['nhr'])) {
790
- $data->http_referer = stripslashes($_REQUEST['nhr']);
791
- } else if (isset($_SERVER['HTTP_REFERER'])) {
792
- $data->http_referer = $_SERVER['HTTP_REFERER'];
793
- }
794
-
795
- // New profiles
796
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
797
- // If the profile cannot be set by subscriber, skip it.
798
- if ($this->options_profile['profile_' . $i . '_status'] == 0) {
799
- continue;
800
- }
801
- if (isset($_REQUEST['np' . $i])) {
802
- $data->profiles['' . $i] = stripslashes($_REQUEST['np' . $i]);
803
- }
804
- }
805
-
806
- // Lists (field name is nl[] and values the list number so special forms with radio button can work)
807
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
808
- $this->logger->debug($_REQUEST['nl']);
809
- foreach ($_REQUEST['nl'] as $list_id) {
810
- $list = $this->get_list($list_id);
811
- if (!$list || $list->is_private()) {
812
- // To administrator show an error to make him aware of the wrong form configuration
813
- if (current_user_can('administrator')) {
814
- $this->dienow('Invalid list', 'List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
815
- }
816
- // Ignore this list
817
- continue;
818
- }
819
- $data->lists['' . $list_id] = 1;
820
- }
821
- } else {
822
- $this->logger->debug('No lists received');
823
- }
824
-
825
- // Opt-in mode
826
- if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
827
- switch ($_REQUEST['optin']) {
828
- case 'single': $subscription->optin = 'single';
829
- break;
830
- case 'double': $subscription->optin = 'double';
831
- break;
832
- }
833
- }
834
-
835
- return $subscription;
836
- }
837
-
838
- /**
839
- * Processes the request and fill in the *array* representing a subscriber with submitted values
840
- * (filtering when necessary).
841
- *
842
- * @deprecated since version 6.9.0
843
- * @param array $user An array partially filled with subscriber data
844
- * @return array The filled array representing a subscriber
845
- */
846
- function update_user_from_request($user) {
847
-
848
- if (isset($_REQUEST['nn'])) {
849
- $user['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
850
- }
851
- // TODO: required checking
852
-
853
- if (isset($_REQUEST['ns'])) {
854
- $user['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
855
- }
856
- // TODO: required checking
857
-
858
- if (!empty($_REQUEST['nx'])) {
859
- $user['sex'] = $this->normalize_sex($_REQUEST['nx'][0]);
860
- }
861
- // TODO: valid values check
862
-
863
- if (isset($_REQUEST['nr'])) {
864
- $user['referrer'] = strip_tags(trim($_REQUEST['nr']));
865
- }
866
-
867
- $language = '';
868
- if (!empty($_REQUEST['nlang'])) {
869
- $language = strtolower(strip_tags($_REQUEST['nlang']));
870
- // TODO: Check if it's an allowed language code
871
- $user['language'] = $language;
872
- } else {
873
- $language = $this->get_current_language();
874
- $user['language'] = $language;
875
- }
876
-
877
- // From the antibot form
878
- if (isset($_REQUEST['nhr'])) {
879
- $user['http_referer'] = strip_tags(trim($_REQUEST['nhr']));
880
- } else if (isset($_SERVER['HTTP_REFERER'])) {
881
- $user['http_referer'] = strip_tags(trim($_SERVER['HTTP_REFERER']));
882
- }
883
-
884
- if (strlen($user['http_referer']) > 200) {
885
- $user['http_referer'] = mb_substr($user['http_referer'], 0, 200);
886
- }
887
-
888
- // New profiles
889
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
890
- // If the profile cannot be set by subscriber, skip it.
891
- if ($this->options_profile['profile_' . $i . '_status'] == 0) {
892
- continue;
893
- }
894
- if (isset($_REQUEST['np' . $i])) {
895
- $user['profile_' . $i] = trim(stripslashes($_REQUEST['np' . $i]));
896
- }
897
- }
898
-
899
- // Extra validation to explain the administrator while the submitted data could
900
- // be interpreted only partially
901
- if (current_user_can('administrator')) {
902
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
903
- foreach ($_REQUEST['nl'] as $list_id) {
904
- $list = $this->get_list($list_id);
905
- if ($list && $list->status == TNP_List::STATUS_PRIVATE) {
906
- $this->dienow('Invalid list', '[old] List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
907
- }
908
- }
909
- }
910
- }
911
- // Preferences (field names are nl[] and values the list number so special forms with radio button can work)
912
- // Permetto l'aggiunta solo delle liste pubbliche
913
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
914
- $lists = $this->get_lists_public();
915
- //$this->logger->debug($_REQUEST['nl']);
916
- foreach ($lists as $list) {
917
- if (in_array('' . $list->id, $_REQUEST['nl'])) {
918
- $user['list_' . $list->id] = 1;
919
- }
920
- }
921
- } else {
922
- $this->logger->debug('No lists received');
923
- }
924
-
925
- // Forced lists (general or by language)
926
- // Forzo l'aggiunta delle liste forzate
927
- $lists = $this->get_lists();
928
- foreach ($lists as $list) {
929
- if ($list->forced) {
930
- $user['list_' . $list->id] = 1;
931
- }
932
- if (in_array($language, $list->languages)) {
933
- $user['list_' . $list->id] = 1;
934
- }
935
- }
936
-
937
- // TODO: should be removed!!!
938
- if (defined('NEWSLETTER_FEED_VERSION')) {
939
- $options_feed = get_option('newsletter_feed', array());
940
- if ($options_feed['add_new'] == 1) {
941
- $user['feed'] = 1;
942
- }
943
- }
944
- return $user;
945
- }
946
-
947
- /**
948
- * Sends a service message applying the template to the HTML part
949
- *
950
- * @param TNP_User $user
951
- * @param string $subject
952
- * @param string|array $message If string it is considered HTML, if array it should contains the key "html" and "text"
953
- * @return type
954
- */
955
- function mail($user, $subject, $message) {
956
- $language = $this->get_user_language($user);
957
-
958
- $options_template = $this->get_options('template', $language);
959
-
960
- $template = trim($options_template['template']);
961
- if (empty($template) || strpos($template, '{message}') === false) {
962
- $template = '{message}';
963
- }
964
-
965
- if (is_array($message)) {
966
- $message['html'] = str_replace('{message}', $message['html'], $template);
967
- $message['html'] = $this->replace($message['html'], $user);
968
- $message['text'] = $this->replace($message['text'], $user);
969
- } else {
970
- $message = str_replace('{message}', $message, $template);
971
- $message = $this->replace($message, $user);
972
- }
973
-
974
- $headers = [];
975
-
976
- // Replaces tags from the template
977
-
978
- $subject = $this->replace($subject, $user);
979
-
980
- return Newsletter::instance()->mail($user->email, $subject, $message, $headers);
981
- }
982
-
983
- /**
984
- * Confirms a subscription changing the user status and, possibly, merging the
985
- * temporary data if present.
986
- *
987
- * @param TNP_User $user Optionally it can be null (user search from requests paramaters, but deprecated, or a user id)
988
- * @return TNP_User
989
- */
990
- function confirm($user = null, $emails = true) {
991
-
992
- // Compatibility with WP Registration Addon
993
- if (!$user) {
994
- $user = $this->get_user_from_request(true);
995
- } else if (is_numeric($user)) {
996
- $user = $this->get_user($user);
997
- }
998
-
999
- if (!$user) {
1000
- $this->dienow('Subscriber not found', '', 404);
1001
- }
1002
- // End compatibility
1003
- // Should be merged?
1004
- $data = get_transient('newsletter_subscription_' . $user->id);
1005
- if ($data !== false) {
1006
- $data->merge_in($user);
1007
- //$this->merge($user, $data);
1008
- $user = $this->save_user($user);
1009
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
1010
- delete_transient('newsletter_subscription_' . $user->id);
1011
- } else {
1012
- $new_email = get_transient('newsletter_user_' . $user->id . '_email');
1013
- if ($new_email) {
1014
- $data = ['id' => $user->id, 'email' => $new_email];
1015
- $this->save_user($data);
1016
- delete_transient('newsletter_user_' . $user->id . '_email');
1017
- }
1018
- }
1019
-
1020
-
1021
- $this->update_user_last_activity($user);
1022
-
1023
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
1024
-
1025
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
1026
- $this->add_user_log($user, 'activate');
1027
- do_action('newsletter_user_confirmed', $user);
1028
- return $user;
1029
- }
1030
-
1031
- $this->set_user_status($user, TNP_User::STATUS_CONFIRMED);
1032
-
1033
- $user = $this->get_user($user);
1034
-
1035
- $this->add_user_log($user, 'activate');
1036
-
1037
- do_action('newsletter_user_confirmed', $user);
1038
- $this->notify_admin_on_subscription($user);
1039
-
1040
- if ($emails) {
1041
- $this->send_message('confirmed', $user);
1042
- }
1043
-
1044
- return $user;
1045
- }
1046
-
1047
- /**
1048
- * Sends a message (activation, welcome, cancellation, ...) with the correct template
1049
- * and checking if the message itself is disabled
1050
- *
1051
- * @param string $type
1052
- * @param TNP_User $user
1053
- */
1054
- function send_message($type, $user, $force = false) {
1055
- if (!$force && !empty($this->options[$type . '_disabled'])) {
1056
- return true;
1057
- }
1058
-
1059
- $language = $this->get_user_language($user);
1060
-
1061
- $options = $this->get_options('', $language);
1062
- $message = [];
1063
- $message['html'] = do_shortcode($options[$type . '_message']);
1064
- $message['text'] = $this->get_text_message($type);
1065
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
1066
- $message['html'] = $this->add_microdata($message['html']);
1067
- }
1068
- $subject = $options[$type . '_subject'];
1069
-
1070
- return $this->mail($user, $subject, $message);
1071
- }
1072
-
1073
- function get_text_message($type) {
1074
- switch ($type) {
1075
- case 'confirmation':
1076
- return __('To confirm your subscription follow the link below.', 'newsletter') . "\n\n{subscription_confirm_url}";
1077
- case 'confirmed':
1078
- return __('Your subscription has been confirmed.', 'newsletter');
1079
- }
1080
- return '';
1081
- }
1082
-
1083
- function is_double_optin() {
1084
- return $this->options['noconfirmation'] == 0;
1085
- }
1086
-
1087
- /**
1088
- * Sends the activation email without conditions.
1089
- *
1090
- * @param stdClass $user
1091
- * @return bool
1092
- */
1093
- function send_activation_email($user) {
1094
- return $this->send_message('confirmation', $user, true);
1095
- }
1096
-
1097
- /**
1098
- * Finds the right way to show the message identified by $key (welcome, unsubscription, ...) redirecting the user to the
1099
- * WordPress page or loading the configured url or activating the standard page.
1100
- */
1101
- function show_message($key, $user, $alert = '', $email = null) {
1102
- $url = '';
1103
-
1104
- if (isset($_REQUEST['ncu'])) {
1105
- // Custom URL from the form
1106
- $url = $_REQUEST['ncu'];
1107
- } else {
1108
- // Per message custom URL from configuration (language variants could not be supported)
1109
- $options = $this->get_options('', $this->get_user_language($user));
1110
- if (!empty($options[$key . '_url'])) {
1111
- $url = $options[$key . '_url'];
1112
- }
1113
- }
1114
-
1115
- $url = Newsletter::instance()->build_message_url($url, $key, $user, $email, $alert);
1116
- wp_redirect($url);
1117
-
1118
- die();
1119
- }
1120
-
1121
- function get_message_key_from_request() {
1122
- if (empty($_GET['nm'])) {
1123
- return 'subscription';
1124
- }
1125
- $key = $_GET['nm'];
1126
- switch ($key) {
1127
- case 's': return 'confirmation';
1128
- case 'c': return 'confirmed';
1129
- case 'u': return 'unsubscription';
1130
- case 'uc': return 'unsubscribed';
1131
- case 'p':
1132
- case 'pe':
1133
- return 'profile';
1134
- default: return $key;
1135
- }
1136
- }
1137
-
1138
- var $privacy_url = false;
1139
-
1140
- /**
1141
- * Generates the privacy URL and cache it.
1142
- *
1143
- * @return string
1144
- */
1145
- function get_privacy_url() {
1146
- if ($this->privacy_url === false) {
1147
- $this->setup_form_options();
1148
- if (!empty($this->form_options['privacy_use_wp_url']) && function_exists('get_privacy_policy_url')) {
1149
- $this->privacy_url = get_privacy_policy_url();
1150
- } else {
1151
- $this->privacy_url = $this->form_options['privacy_url'];
1152
- }
1153
- }
1154
- return $this->privacy_url;
1155
- }
1156
-
1157
- function get_form_javascript() {
1158
-
1159
- }
1160
-
1161
- /**
1162
- * Manages the custom forms made with [newsletter_form] and internal [newsletter_field] shortcodes.
1163
- *
1164
- * @param array $attrs
1165
- * @param string $content
1166
- * @return string
1167
- */
1168
- function get_subscription_form_custom($attrs = [], $content = '') {
1169
- if (!is_array($attrs)) {
1170
- $attrs = [];
1171
- }
1172
-
1173
- $this->setup_form_options();
1174
-
1175
- $attrs = array_merge(['class' => 'tnp-subscription', 'style' => ''], $attrs);
1176
-
1177
- $action = esc_attr($this->build_action_url('s'));
1178
- $class = esc_attr($attrs['class']);
1179
- $style = esc_attr($attrs['style']);
1180
- $buffer = '<form method="post" action="' . $action . '" class="' . $class . '" style="' . $style . '">' . "\n";
1181
-
1182
- $language = $this->get_current_language();
1183
-
1184
- $buffer .= $this->get_form_hidden_fields($attrs);
1185
-
1186
- $buffer .= do_shortcode($content);
1187
-
1188
- if (isset($attrs['button_label'])) {
1189
- $label = $attrs['button_label'];
1190
- } else {
1191
- $label = $this->form_options['subscribe'];
1192
- }
1193
-
1194
- if (!empty($label)) {
1195
- $buffer .= '<div class="tnp-field tnp-field-button">';
1196
- if (strpos($label, 'http') === 0) {
1197
- $buffer .= '<input class="tnp-button-image" type="image" src="' . $label . '">';
1198
- } else {
1199
- $buffer .= '<input class="tnp-button" type="submit" value="' . $label . '">';
1200
- }
1201
- $buffer .= '</div>';
1202
- }
1203
-
1204
- $buffer .= '</form>';
1205
-
1206
- return $buffer;
1207
- }
1208
-
1209
- /** Generates the hidden field for lists which should be implicitely set with a subscription form.
1210
- *
1211
- * @param string $lists Comma separated directly from the shortcode "lists" attribute
1212
- * @param string $language ???
1213
- * @return string
1214
- */
1215
- function get_form_implicit_lists($lists, $language = '') {
1216
- $buffer = '';
1217
-
1218
- $arr = explode(',', $lists);
1219
-
1220
- foreach ($arr as $a) {
1221
- $a = trim($a);
1222
- if (empty($a))
1223
- continue;
1224
-
1225
- $list = $this->get_list($a);
1226
- if (!$list) {
1227
- $buffer .= $this->build_field_admin_notice('List "' . $a . '" added to the form is not configured, skipped.');
1228
- continue;
1229
- }
1230
-
1231
- if ($list->is_private()) {
1232
- $buffer .= $this->build_field_admin_notice('List ' . $a . ' is private cannot be used in a public form.');
1233
- continue;
1234
- }
1235
-
1236
- if ($list->forced) {
1237
- $buffer .= $this->build_field_admin_notice('List ' . $a . ' is already enforced on every subscription there is no need to specify it.');
1238
- continue;
1239
- }
1240
-
1241
- $buffer .= "<input type='hidden' name='nl[]' value='" . esc_attr($a) . "'>\n";
1242
- }
1243
- return $buffer;
1244
- }
1245
-
1246
- /**
1247
- * Builds all the hidden fields of a subscription form. Implicit lists, confirmation url,
1248
- * referrer, language, ...
1249
- *
1250
- * @param array $attrs Attributes of form shortcode
1251
- * @return string HTML with the hidden fields
1252
- */
1253
- function get_form_hidden_fields($attrs) {
1254
- $b = '';
1255
-
1256
- // Compatibility
1257
- if (isset($attrs['list'])) {
1258
- $attrs['lists'] = $attrs['list'];
1259
- }
1260
- if (isset($attrs['lists'])) {
1261
- $b .= $this->get_form_implicit_lists($attrs['lists']);
1262
- }
1263
-
1264
- if (isset($attrs['referrer'])) {
1265
- $b .= '<input type="hidden" name="nr" value="' . esc_attr($attrs['referrer']) . '">';
1266
- }
1267
-
1268
- if (isset($attrs['confirmation_url'])) {
1269
- if ($attrs['confirmation_url'] === '#') {
1270
- $attrs['confirmation_url'] = esc_url_raw($_SERVER['REQUEST_URI']);
1271
- }
1272
-
1273
- $b .= '<input type="hidden" name="ncu" value="' . esc_attr($attrs['confirmation_url']) . '">';
1274
- }
1275
-
1276
- if (isset($attrs['optin'])) {
1277
- $optin = trim(strtolower($attrs['optin']));
1278
- if ($optin !== 'double' && $optin !== 'single') {
1279
- $b .= $this->build_field_admin_notice('The optin is set to an invalid value.');
1280
- } else {
1281
- if ($optin !== 'double' && $this->is_double_optin() && empty($this->options['optin_override'])) {
1282
- $b .= $this->build_field_admin_notice('The optin is specified but cannot be overridden (see the subscription configiraton page).');
1283
- } else {
1284
- $b .= '<input type="hidden" name="optin" value="' . esc_attr($optin) . '">';
1285
- }
1286
- }
1287
- }
1288
-
1289
- $language = $this->get_current_language();
1290
- $b .= '<input type="hidden" name="nlang" value="' . esc_attr($language) . '">';
1291
-
1292
- return $b;
1293
- }
1294
-
1295
- /**
1296
- * Internal use only
1297
- *
1298
- * @param type $name
1299
- * @param type $attrs
1300
- * @param type $suffix
1301
- * @return string
1302
- */
1303
- private function _shortcode_label($name, $attrs, $suffix = null) {
1304
-
1305
- if (!$suffix) {
1306
- $suffix = $name;
1307
- }
1308
- $buffer = '<label for="' . esc_attr($attrs['id']) . '">';
1309
- if (isset($attrs['label'])) {
1310
- if (empty($attrs['label'])) {
1311
- return;
1312
- } else {
1313
- $buffer .= esc_html($attrs['label']);
1314
- }
1315
- } else {
1316
- if (isset($this->form_options[$name])) {
1317
- $buffer .= esc_html($this->form_options[$name]);
1318
- }
1319
- }
1320
- $buffer .= "</label>\n";
1321
- return $buffer;
1322
- }
1323
-
1324
- /**
1325
- * Creates a notices to be displayed near a subscription form field to inform of worng configurations.
1326
- * It is created only if the current user looking at the form is the administrator.
1327
- *
1328
- * @param string $message
1329
- * @return string
1330
- */
1331
- function build_field_admin_notice($message) {
1332
- if (!current_user_can('administrator')) {
1333
- return '';
1334
- }
1335
- return '<p style="background-color: #eee; color: #000; padding: 10px; margin: 10px 0">' . $message . ' <strong>This notice is shown only to administrators to help with configuration.</strong></p>';
1336
- }
1337
-
1338
- function shortcode_newsletter_field($attrs, $content = '') {
1339
- // Counter to create unique ID for checkbox and labels
1340
- static $idx = 0;
1341
-
1342
- $idx++;
1343
- $attrs['id'] = 'tnp-' . $idx;
1344
-
1345
- $this->setup_form_options();
1346
- $language = $this->get_current_language();
1347
-
1348
- $name = $attrs['name'];
1349
-
1350
- $buffer = '';
1351
-
1352
- if ($name == 'email') {
1353
- $buffer .= '<div class="tnp-field tnp-field-email">';
1354
-
1355
- $buffer .= $this->_shortcode_label('email', $attrs);
1356
-
1357
- $buffer .= '<input class="tnp-email" type="email" name="ne" id="' . esc_attr($attrs['id']) . '" value=""';
1358
- if (isset($attrs['placeholder'])) {
1359
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1360
- }
1361
- $buffer .= ' required>';
1362
- if (isset($attrs['button_label'])) {
1363
- $label = $attrs['button_label'];
1364
- if (strpos($label, 'http') === 0) {
1365
- $buffer .= ' <input class="tnp-submit-image" type="image" src="' . esc_attr(esc_url_raw($label)) . '">';
1366
- } else {
1367
- $buffer .= ' <input class="tnp-submit" type="submit" value="' . esc_attr($label) . '" style="width: 29%">';
1368
- }
1369
- }
1370
- $buffer .= "</div>\n";
1371
- return $buffer;
1372
- }
1373
-
1374
- if ($name == 'first_name' || $name == 'name') {
1375
- $buffer .= '<div class="tnp-field tnp-field-firstname">';
1376
- $buffer .= $this->_shortcode_label('name', $attrs);
1377
-
1378
- $buffer .= '<input class="tnp-name" type="text" name="nn" id="' . esc_attr($attrs['id']) . '" value=""';
1379
- if (isset($attrs['placeholder'])) {
1380
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1381
- }
1382
- if ($this->form_options['name_rules'] == 1) {
1383
- $buffer .= ' required';
1384
- }
1385
- $buffer .= '>';
1386
- $buffer .= "</div>\n";
1387
- return $buffer;
1388
- }
1389
-
1390
- if ($name == 'last_name' || $name == 'surname') {
1391
- $buffer .= '<div class="tnp-field tnp-field-surname">';
1392
- $buffer .= $this->_shortcode_label('surname', $attrs);
1393
-
1394
- $buffer .= '<input class="tnp-surname" type="text" name="ns" id="' . esc_attr($attrs['id']) . '" value=""';
1395
- if (isset($attrs['placeholder'])) {
1396
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1397
- }
1398
- if ($this->form_options['surname_rules'] == 1) {
1399
- $buffer .= ' required';
1400
- }
1401
- $buffer .= '>';
1402
- $buffer .= '</div>';
1403
- return $buffer;
1404
- }
1405
-
1406
- // Single list
1407
- if ($name == 'preference' || $name == 'list') {
1408
- if (!isset($attrs['number'])) {
1409
- return $this->build_field_admin_notice('List number not specified.');
1410
- }
1411
- $number = (int) $attrs['number'];
1412
- $list = $this->get_list($number, $language);
1413
- if (!$list) {
1414
- return $this->build_field_admin_notice('List ' . $number . ' is not configured, cannot be shown.');
1415
- }
1416
-
1417
- if ($list->status == 0 || $list->forced) {
1418
- return $this->build_field_admin_notice('List ' . $number . ' is private or enforced cannot be shown.');
1419
- }
1420
-
1421
- if (isset($attrs['hidden'])) {
1422
- return '<input type="hidden" name="nl[]" value="' . esc_attr($list->id) . '">';
1423
- }
1424
-
1425
- $idx++;
1426
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="tnp-' . $idx . '">';
1427
- $buffer .= '<input type="checkbox" id="tnp-' . $idx . '" name="nl[]" value="' . esc_attr($list->id) . '"';
1428
- if (isset($attrs['checked'])) {
1429
- $buffer .= ' checked';
1430
- }
1431
- $buffer .= '>';
1432
- if (isset($attrs['label'])) {
1433
- if ($attrs['label'] != '') {
1434
- $buffer .= '&nbsp;' . esc_html($attrs['label']) . '</label>';
1435
- }
1436
- } else {
1437
- $buffer .= '&nbsp;' . esc_html($list->name) . '</label>';
1438
- }
1439
- $buffer .= "</div>\n";
1440
-
1441
- return $buffer;
1442
- }
1443
-
1444
- // All lists
1445
- if ($name == 'lists' || $name == 'preferences') {
1446
- $lists = $this->get_lists_for_subscription($language);
1447
- if (!empty($lists) && isset($attrs['layout']) && $attrs['layout'] === 'dropdown') {
1448
-
1449
- $buffer .= '<div class="tnp-field tnp-lists">';
1450
- // There is not a default "label" for the block of lists, so it can only be specified in the shortcode attrs as "label"
1451
- $buffer .= $this->_shortcode_label('lists', $attrs);
1452
- $buffer .= '<select class="tnp-lists" name="nl[]" required>';
1453
-
1454
- if (!empty($attrs['first_option_label'])) {
1455
- $buffer .= '<option value="" selected="true" disabled="disabled">' . esc_html($attrs['first_option_label']) . '</option>';
1456
- }
1457
-
1458
- foreach ($lists as $list) {
1459
- $buffer .= '<option value="' . $list->id . '">' . esc_html($list->name) . '</option>';
1460
- }
1461
- $buffer .= '</select>';
1462
- $buffer .= '</div>';
1463
- } else {
1464
-
1465
- foreach ($lists as $list) {
1466
- $idx++;
1467
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="nl' . $idx . '">';
1468
- $buffer .= '<input type="checkbox" id="nl' . $idx . '" name="nl[]" value="' . $list->id . '"';
1469
- if ($list->checked) {
1470
- $buffer .= ' checked';
1471
- }
1472
- $buffer .= '>&nbsp;' . esc_html($list->name) . '</label>';
1473
- $buffer .= "</div>\n";
1474
- }
1475
- }
1476
- return $buffer;
1477
- }
1478
-
1479
- if ($name == 'sex' || $name == 'gender') {
1480
- $buffer .= '<div class="tnp-field tnp-field-gender">';
1481
- $buffer .= $this->_shortcode_label('sex', $attrs);
1482
-
1483
- $buffer .= '<select name="nx" class="tnp-gender" id="tnp-gender"';
1484
- if ($this->form_options['sex_rules']) {
1485
- $buffer .= ' required ';
1486
- }
1487
- $buffer .= '>';
1488
- if ($this->form_options['sex_rules']) {
1489
- $buffer .= '<option value=""></option>';
1490
- }
1491
- $buffer .= '<option value="n">' . esc_html($this->form_options['sex_none']) . '</option>';
1492
- $buffer .= '<option value="f">' . esc_html($this->form_options['sex_female']) . '</option>';
1493
- $buffer .= '<option value="m">' . esc_html($this->form_options['sex_male']) . '</option>';
1494
- $buffer .= '</select>';
1495
- $buffer .= "</div>\n";
1496
- return $buffer;
1497
- }
1498
-
1499
- if ($name == 'profile') {
1500
- if (!isset($attrs['number'])) {
1501
- return $this->build_field_admin_notice('Extra profile number not specified.');
1502
- }
1503
-
1504
- $number = (int) $attrs['number'];
1505
-
1506
- $profile = TNP_Profile_Service::get_profile_by_id($number, $language);
1507
-
1508
- if (!$profile) {
1509
- return $this->build_field_admin_notice('Extra profile ' . $number . ' is not configured, cannot be shown.');
1510
- }
1511
-
1512
- if ($profile->status == 0) {
1513
- return $this->build_field_admin_notice('Extra profile ' . $number . ' is private, cannot be shown.');
1514
- }
1515
-
1516
- $size = isset($attrs['size']) ? $attrs['size'] : '';
1517
- $buffer .= '<div class="tnp-field tnp-field-profile">';
1518
- $buffer .= $this->_shortcode_label('profile_' . $profile->id, $attrs);
1519
-
1520
- $placeholder = isset($attrs['placeholder']) ? $attrs['placeholder'] : $profile->placeholder;
1521
-
1522
- // Text field
1523
- if ($profile->type == TNP_Profile::TYPE_TEXT) {
1524
- $buffer .= '<input class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" type="text" size="' . esc_attr($size) . '" name="np' . $number . '" placeholder="' . esc_attr($placeholder) . '"';
1525
- if ($profile->is_required()) {
1526
- $buffer .= ' required';
1527
- }
1528
- $buffer .= '>';
1529
- }
1530
-
1531
- // Select field
1532
- if ($profile->type == TNP_Profile::TYPE_SELECT) {
1533
- $buffer .= '<select class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" name="np' . $number . '"';
1534
- if ($profile->is_required()) {
1535
- $buffer .= ' required';
1536
- }
1537
- $buffer .= '>';
1538
- if (!empty($placeholder)) {
1539
- $buffer .= '<option value="" selected disabled>' . esc_html($placeholder) . '</option>';
1540
- }
1541
- foreach ($profile->options as $option) {
1542
- $buffer .= '<option>' . esc_html(trim($option)) . '</option>';
1543
- }
1544
- $buffer .= "</select>\n";
1545
- }
1546
-
1547
- $buffer .= "</div>\n";
1548
-
1549
- return $buffer;
1550
- }
1551
-
1552
- if (strpos($name, 'privacy') === 0) {
1553
- if (!isset($attrs['url'])) {
1554
- $attrs['url'] = $this->get_privacy_url();
1555
- }
1556
-
1557
- if (!isset($attrs['label'])) {
1558
- $attrs['label'] = $this->form_options['privacy'];
1559
- }
1560
-
1561
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-privacy">';
1562
-
1563
- $idx++;
1564
- $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy" id="tnp-' . $idx . '"> ';
1565
- $buffer .= '<label for="tnp-' . $idx . '">';
1566
- if (!empty($attrs['url'])) {
1567
- $buffer .= '<a target="_blank" href="' . esc_attr($attrs['url']) . '">';
1568
- }
1569
- $buffer .= $attrs['label'];
1570
- if (!empty($attrs['url'])) {
1571
- $buffer .= '</a>';
1572
- }
1573
- $buffer .= '</label>';
1574
- $buffer .= '</div>';
1575
-
1576
- return $buffer;
1577
- }
1578
- }
1579
-
1580
- /**
1581
- * Builds the privacy field only for completely generated forms.
1582
- *
1583
- * @return string Empty id the privacy filed is not configured
1584
- */
1585
- function get_privacy_field($pre_html = '', $post_html = '') {
1586
- $this->setup_form_options();
1587
- $privacy_status = (int) $this->form_options['privacy_status'];
1588
- if (empty($privacy_status)) {
1589
- return '';
1590
- }
1591
-
1592
- $buffer = '<label>';
1593
- if ($privacy_status === 1) {
1594
- $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy">&nbsp;';
1595
- }
1596
- $url = $this->get_privacy_url();
1597
- if (!empty($url)) {
1598
- $buffer .= '<a target="_blank" href="' . esc_attr($url) . '">';
1599
- $buffer .= esc_attr($this->form_options['privacy']) . '</a>';
1600
- } else {
1601
- $buffer .= esc_html($this->form_options['privacy']);
1602
- }
1603
-
1604
- $buffer .= "</label>";
1605
-
1606
- return $pre_html . $buffer . $post_html;
1607
- }
1608
-
1609
- /**
1610
- * The new standard form.
1611
- *
1612
- * @param string $referrer Deprecated since 6.9.1, use the "referrer" key on $attrs
1613
- * @param string $action
1614
- * @param string $attrs
1615
- * @return string The full HTML form
1616
- */
1617
- function get_subscription_form($referrer = '', $action = null, $attrs = []) {
1618
- $language = $this->get_current_language();
1619
- $options_profile = $this->get_options('profile', $language);
1620
-
1621
- if (!is_array($attrs)) {
1622
- $attrs = [];
1623
- }
1624
-
1625
- // Possible alternative form actions (used by...?)
1626
- if (isset($attrs['action'])) {
1627
- $action = $attrs['action'];
1628
- }
1629
-
1630
- // The referrer parameter is deprecated
1631
- if (!empty($referrer)) {
1632
- $attrs['referrer'] = $referrer;
1633
- }
1634
-
1635
- $buffer = '';
1636
-
1637
- if (empty($action)) {
1638
- $action = $this->build_action_url('s');
1639
- }
1640
-
1641
- if (isset($attrs['before'])) {
1642
- $buffer .= $attrs['before'];
1643
- } else {
1644
- if (isset($attrs['class'])) {
1645
- $buffer .= '<div class="tnp tnp-subscription ' . $attrs['class'] . '">' . "\n";
1646
- } else {
1647
- $buffer .= '<div class="tnp tnp-subscription">' . "\n";
1648
- }
1649
- }
1650
-
1651
- $buffer .= '<form method="post" action="' . esc_attr($action) . '">' . "\n\n";
1652
-
1653
- $buffer .= $this->get_form_hidden_fields($attrs);
1654
-
1655
- if ($options_profile['name_status'] == 2) {
1656
- $buffer .= $this->shortcode_newsletter_field(['name' => 'first_name']);
1657
- }
1658
-
1659
- if ($options_profile['surname_status'] == 2) {
1660
- $buffer .= $this->shortcode_newsletter_field(['name' => 'last_name']);
1661
- }
1662
-
1663
- $buffer .= $this->shortcode_newsletter_field(['name' => 'email']);
1664
-
1665
- if (isset($options_profile['sex_status']) && $options_profile['sex_status'] == 2) {
1666
- $buffer .= $this->shortcode_newsletter_field(['name' => 'gender']);
1667
- }
1668
-
1669
- // Extra profile fields
1670
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1671
- // Not for subscription form
1672
- if ($options_profile['profile_' . $i . '_status'] != 2) {
1673
- continue;
1674
- }
1675
-
1676
- $buffer .= $this->shortcode_newsletter_field(['name' => 'profile', 'number' => $i]);
1677
- }
1678
-
1679
- $tmp = '';
1680
- $lists = $this->get_lists_for_subscription($language);
1681
- if (!empty($attrs['lists_field_layout']) && $attrs['lists_field_layout'] == 'dropdown') {
1682
- if (empty($attrs['lists_field_empty_label'])) {
1683
- $attrs['lists_field_empty_label'] = '';
1684
- }
1685
- if (empty($attrs['lists_field_label'])) {
1686
- $attrs['lists_field_label'] = '';
1687
- }
1688
- $buffer .= $this->shortcode_newsletter_field(['name' => 'lists', 'layout' => 'dropdown', 'first_option_label' => $attrs['lists_field_empty_label'], 'label' => $attrs['lists_field_label']]);
1689
- } else {
1690
- $buffer .= $this->shortcode_newsletter_field(['name' => 'lists']);
1691
- }
1692
-
1693
-
1694
-
1695
- // Deprecated
1696
- $extra = apply_filters('newsletter_subscription_extra', array());
1697
- foreach ($extra as $x) {
1698
- $label = $x['label'];
1699
- if (empty($label)) {
1700
- $label = '&nbsp;';
1701
- }
1702
- $name = '';
1703
- if (!empty($x['name'])) {
1704
- $name = $x['name'];
1705
- }
1706
- $buffer .= '<div class="tnp-field tnp-field-' . $name . '"><label>' . $label . "</label>";
1707
- $buffer .= $x['field'] . "</div>\n";
1708
- }
1709
-
1710
- $buffer .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1711
-
1712
- $buffer .= '<div class="tnp-field tnp-field-button">';
1713
-
1714
- $button_style = '';
1715
- if (!empty($attrs['button_color'])) {
1716
- $button_style = 'style="background-color:' . esc_attr($attrs['button_color']) . '"';
1717
- }
1718
-
1719
- if (strpos($options_profile['subscribe'], 'http') === 0) {
1720
- $buffer .= '<input class="tnp-submit-image" type="image" src="' . esc_attr($options_profile['subscribe']) . '">' . "\n";
1721
- } else {
1722
- $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($options_profile['subscribe']) . '" ' . $button_style . '>' . "\n";
1723
- }
1724
-
1725
- $buffer .= "</div>\n</form>\n";
1726
-
1727
- if (isset($attrs['after'])) {
1728
- $buffer .= $attrs['after'];
1729
- } else {
1730
- $buffer .= "</div>\n";
1731
- }
1732
-
1733
- return $buffer;
1734
- }
1735
-
1736
- function get_form($number) {
1737
- $options = get_option('newsletter_forms');
1738
-
1739
- $form = $options['form_' . $number];
1740
-
1741
- $form = do_shortcode($form);
1742
-
1743
- $action = $this->build_action_url('s');
1744
-
1745
- if (stripos($form, '<form') === false) {
1746
- $form = '<form method="post" action="' . $action . '">' . $form . '</form>';
1747
- }
1748
-
1749
- // For compatibility
1750
- $form = str_replace('{newsletter_url}', $action, $form);
1751
-
1752
- $form = $this->replace_lists($form);
1753
-
1754
- return $form;
1755
- }
1756
-
1757
- /** Replaces on passed text the special tag {lists} that can be used to show the preferences as a list of checkbox.
1758
- * They are called lists but on configuration panel they are named preferences!
1759
- *
1760
- * @param string $buffer
1761
- * @return string
1762
- */
1763
- function replace_lists($buffer) {
1764
- $checkboxes = '';
1765
- $lists = $this->get_lists_for_subscription($this->get_current_language());
1766
- foreach ($lists as $list) {
1767
- $checkboxes .= '<input type="checkbox" name="nl[]" value="' . $list->id . '"/>&nbsp;' . $list->name . '<br />';
1768
- }
1769
- $buffer = str_replace('{lists}', $checkboxes, $buffer);
1770
- $buffer = str_replace('{preferences}', $checkboxes, $buffer);
1771
- return $buffer;
1772
- }
1773
-
1774
- function notify_admin_on_subscription($user) {
1775
-
1776
- if (empty($this->options['notify'])) {
1777
- return;
1778
- }
1779
-
1780
- $message = $this->generate_admin_notification_message($user);
1781
- $email = trim($this->options['notify_email']);
1782
- $subject = $this->generate_admin_notification_subject('New subscription');
1783
-
1784
- Newsletter::instance()->mail($email, $subject, array('html' => $message));
1785
- }
1786
-
1787
- /**
1788
- * Builds the minimal subscription form, with only the email field and inline
1789
- * submit button. If enabled the privacy checkbox is added.
1790
- *
1791
- * @param type $attrs
1792
- * @return string
1793
- */
1794
- function get_subscription_form_minimal($attrs) {
1795
-
1796
- $this->setup_form_options();
1797
-
1798
- if (!is_array($attrs)) {
1799
- $attrs = [];
1800
- }
1801
-
1802
- $attrs = array_merge(array('class' => '', 'referrer' => 'minimal',
1803
- 'button' => $this->form_options['subscribe'], 'button_color' => '',
1804
- 'button_radius' => '', 'placeholder' => $this->form_options['email']), $attrs);
1805
-
1806
- $form = '';
1807
-
1808
- $form .= '<div class="tnp tnp-subscription-minimal ' . $attrs['class'] . '">';
1809
- $form .= '<form action="' . esc_attr($this->build_action_url('s')) . '" method="post">';
1810
-
1811
- $form .= $this->get_form_hidden_fields($attrs);
1812
-
1813
- $form .= '<input class="tnp-email" type="email" required name="ne" value="" placeholder="' . esc_attr($attrs['placeholder']) . '">';
1814
-
1815
- if (isset($attrs['button_label'])) {
1816
- $label = $attrs['button_label'];
1817
- } else if (isset($attrs['button'])) { // Backward compatibility
1818
- $label = $attrs['button'];
1819
- } else {
1820
- $label = $this->form_options['subscribe'];
1821
- }
1822
-
1823
- $form .= '<input class="tnp-submit" type="submit" value="' . esc_attr($attrs['button']) . '"'
1824
- . ' style="background-color:' . esc_attr($attrs['button_color']) . '">';
1825
-
1826
- $form .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1827
-
1828
- $form .= "</form></div>\n";
1829
-
1830
- return $form;
1831
- }
1832
-
1833
- function shortcode_newsletter_form($attrs, $content) {
1834
-
1835
- if (isset($attrs['type']) && $attrs['type'] === 'minimal') {
1836
- return $this->get_subscription_form_minimal($attrs);
1837
- }
1838
-
1839
- // Custom form using the [newsletter_field] shortcodes
1840
- if (!empty($content)) {
1841
- return $this->get_subscription_form_custom($attrs, $content);
1842
- }
1843
-
1844
- // Custom form hand coded and saved in the custom forms option
1845
- if (isset($attrs['form'])) {
1846
- return $this->get_form((int) $attrs['form']);
1847
- }
1848
-
1849
- // Custom hand coded form (as above, new syntax)
1850
- if (isset($attrs['number'])) {
1851
- return $this->get_form((int) $attrs['number']);
1852
- }
1853
-
1854
- return $this->get_subscription_form(null, null, $attrs);
1855
- }
1856
-
1857
- /**
1858
- *
1859
- * @global wpdb $wpdb
1860
- * @param array $attrs
1861
- * @param string $content
1862
- * @return string
1863
- */
1864
- function shortcode_newsletter($attrs, $content) {
1865
- global $wpdb;
1866
-
1867
- $message_key = $this->get_message_key_from_request();
1868
- if ($message_key == 'confirmation') {
1869
- $user = $this->get_user_from_request(false, 'preconfirm');
1870
- } else {
1871
- $user = $this->get_user_from_request();
1872
- }
1873
-
1874
- $message = apply_filters('newsletter_page_text', '', $message_key, $user);
1875
-
1876
- $options = $this->get_options('', $this->get_current_language($user));
1877
-
1878
- if (empty($message)) {
1879
- $message = $options[$message_key . '_text'];
1880
-
1881
- // TODO: the if can be removed
1882
- if ($message_key == 'confirmed') {
1883
- $message .= $options[$message_key . '_tracking'];
1884
- }
1885
- }
1886
-
1887
- // Now check what form must be added
1888
- if ($message_key == 'subscription') {
1889
- if (isset($attrs['show_form']) && $attrs['show_form'] === 'false') {
1890
- //return $this->build_field_admin_notice('The [newsletter] shortcode is configured to not show the subscription form.');
1891
- return;
1892
- }
1893
-
1894
- // Compatibility check
1895
- if (stripos($message, '<form') !== false) {
1896
- $message = str_ireplace('<form', '<form method="post" action="' . esc_attr($this->get_subscribe_url()) . '"', $message);
1897
- } else {
1898
-
1899
- if (strpos($message, '{subscription_form') === false) {
1900
- $message .= '{subscription_form}';
1901
- }
1902
-
1903
- if (isset($attrs['form'])) {
1904
- $message = str_replace('{subscription_form}', $this->get_form($attrs['form']), $message);
1905
- } else {
1906
- $message = str_replace('{subscription_form}', $this->get_subscription_form('page', null, $attrs), $message);
1907
- }
1908
- }
1909
- }
1910
-
1911
- $email = $this->get_email_from_request();
1912
-
1913
- $message = $this->replace($message, $user, $email, 'page');
1914
-
1915
- $message = do_shortcode($message);
1916
-
1917
- if (isset($_REQUEST['alert'])) {
1918
- // slashes are already added by wordpress!
1919
- $message .= '<script>alert("' . esc_js(strip_tags($_REQUEST['alert'])) . '");</script>';
1920
- }
1921
-
1922
- return $message;
1923
- }
1924
-
1925
- }
1926
-
1927
- NewsletterSubscription::instance();
1928
-
1929
- // Compatibility code
1930
-
1931
- /**
1932
- * @deprecated
1933
- * @param int $number
1934
- */
1935
- function newsletter_form($number = null) {
1936
- if ($number != null) {
1937
- echo NewsletterSubscription::instance()->get_form($number);
1938
- } else {
1939
- echo NewsletterSubscription::instance()->get_subscription_form();
1940
- }
1941
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterSubscription extends NewsletterModule {
6
+
7
+ const MESSAGE_CONFIRMED = 'confirmed';
8
+ const OPTIN_DOUBLE = 0;
9
+ const OPTIN_SINGLE = 1;
10
+
11
+ static $instance;
12
+
13
+ /**
14
+ * @var array
15
+ */
16
+ var $options_profile;
17
+
18
+ /**
19
+ * Contains the options for the current language to build a subscription form. Must be initialized with
20
+ * setup_form_options() before use.
21
+ *
22
+ * @var array
23
+ */
24
+ var $form_options = null;
25
+
26
+ /**
27
+ * Contains the antibot/antispam options. Must be initialized with
28
+ * setup_antibot_options() before use.
29
+ *
30
+ * @var array
31
+ */
32
+ var $antibot_options = null;
33
+
34
+ /**
35
+ * @var array
36
+ */
37
+ var $options_lists;
38
+
39
+ /**
40
+ * @return NewsletterSubscription
41
+ */
42
+ static function instance() {
43
+ if (self::$instance == null) {
44
+ self::$instance = new NewsletterSubscription();
45
+ }
46
+ return self::$instance;
47
+ }
48
+
49
+ function __construct() {
50
+
51
+ parent::__construct('subscription', '2.2.7', null, array('lists', 'template', 'profile', 'antibot'));
52
+ $this->options_profile = $this->get_options('profile');
53
+ $this->options_lists = $this->get_options('lists');
54
+
55
+ // Must be called after the Newsletter::hook_init, since some constants are defined
56
+ // there.
57
+ add_action('init', array($this, 'hook_init'), 90);
58
+ }
59
+
60
+ function hook_init() {
61
+ add_action('newsletter_action', array($this, 'hook_newsletter_action'), 10, 3);
62
+ if (is_admin()) {
63
+ add_action('admin_init', array($this, 'hook_admin_init'));
64
+ } else {
65
+ // Shortcode for the Newsletter page
66
+ add_shortcode('newsletter', array($this, 'shortcode_newsletter'));
67
+ add_shortcode('newsletter_form', array($this, 'shortcode_newsletter_form'));
68
+ add_shortcode('newsletter_field', array($this, 'shortcode_newsletter_field'));
69
+ }
70
+ }
71
+
72
+ function hook_admin_init() {
73
+ // So the user can add JS and other code
74
+ if (isset($_GET['page']) && $_GET['page'] === 'newsletter_subscription_forms') {
75
+ header('X-XSS-Protection: 0');
76
+ }
77
+
78
+ if (function_exists('register_block_type')) {
79
+ // Add custom blocks to Gutenberg
80
+ wp_register_script('tnp-blocks', plugins_url('newsletter') . '/includes/tnp-blocks.js', array('wp-block-editor', 'wp-blocks', 'wp-element', 'wp-components'), NEWSLETTER_VERSION);
81
+ register_block_type('tnp/minimal', array('editor_script' => 'tnp-blocks'));
82
+ }
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @global wpdb $wpdb
88
+ * @return mixed
89
+ */
90
+ function hook_newsletter_action($action, $user, $email) {
91
+ global $wpdb;
92
+
93
+ switch ($action) {
94
+ case 'profile-change':
95
+ if ($this->antibot_form_check()) {
96
+
97
+ if (!$user || $user->status != TNP_user::STATUS_CONFIRMED) {
98
+ $this->dienow('Subscriber not found or not confirmed.', 'Even the wrong subscriber token can lead to this error.', 404);
99
+ }
100
+
101
+ if (!$email) {
102
+ $this->dienow('Newsletter not found', 'The newsletter containing the link has been deleted.', 404);
103
+ }
104
+
105
+ if (isset($_REQUEST['list'])) {
106
+ $list_id = (int) $_REQUEST['list'];
107
+
108
+ // Check if the list is public
109
+ $list = $this->get_list($list_id);
110
+ if (!$list || $list->status == TNP_List::STATUS_PRIVATE) {
111
+ $this->dienow('List change not allowed.', 'Please check if the list is marked as "private".', 400);
112
+ }
113
+
114
+ if (empty($_REQUEST['redirect'])) {
115
+ $url = home_url();
116
+ } else {
117
+ $url = esc_url_raw($_REQUEST['redirect']);
118
+ }
119
+ $this->set_user_list($user, $list_id, $_REQUEST['value']);
120
+
121
+ $user = $this->get_user($user->id);
122
+ $this->add_user_log($user, 'cta');
123
+ NewsletterStatistics::instance()->add_click($url, $user->id, $email->id);
124
+ wp_redirect($url);
125
+ die();
126
+ }
127
+ } else {
128
+ $this->request_to_antibot_form('Continue');
129
+ }
130
+
131
+ die();
132
+
133
+ case 'm':
134
+ case 'message':
135
+ include dirname(__FILE__) . '/page.php';
136
+ die();
137
+
138
+ // normal subscription
139
+ case 's':
140
+ case 'subscribe':
141
+
142
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
143
+ $this->dienow('Invalid request', 'The subscription request was not made with a HTTP POST', 400);
144
+ }
145
+
146
+ $options_antibot = $this->get_options('antibot');
147
+
148
+ $captcha = !empty($options_antibot['captcha']);
149
+
150
+ if (!empty($_GET['_wp_amp_action_xhr_converted']) || !empty($options_antibot['disabled']) || $this->antibot_form_check($captcha)) {
151
+
152
+ $subscription = $this->build_subscription();
153
+
154
+ $user = $this->subscribe2($subscription);
155
+
156
+ if (is_wp_error($user)) {
157
+ if ($user->get_error_code() === 'exists') {
158
+ $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
159
+ $options = $this->get_options('', $language);
160
+ $this->dienow($options['error_text'], $user->get_error_message(), 200);
161
+ }
162
+ $this->dienow('Registration failed.', $user->get_error_message(), 400);
163
+ }
164
+
165
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
166
+ $this->show_message('confirmed', $user);
167
+ }
168
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
169
+ $this->show_message('confirmation', $user);
170
+ }
171
+ } else {
172
+ $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
173
+ $options = $this->get_form_options($language);
174
+ $this->request_to_antibot_form($options['subscribe'], $captcha);
175
+ }
176
+ die();
177
+
178
+ // AJAX subscription
179
+ case 'ajaxsub':
180
+
181
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
182
+ $this->dienow('Invalid request');
183
+ }
184
+
185
+ $subscription = $this->build_subscription();
186
+
187
+ $user = $this->subscribe2($subscription);
188
+
189
+ if (is_wp_error($user)) {
190
+ if ($user->get_error_code() === 'exists') {
191
+ echo $this->options['error_text'];
192
+ die();
193
+ } else {
194
+ $this->dienow('Registration failed.', $user->get_error_message(), 400);
195
+ }
196
+ } else {
197
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
198
+ $key = 'confirmed';
199
+ }
200
+
201
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
202
+ $key = 'confirmation';
203
+ }
204
+ }
205
+
206
+ $message = $this->replace($this->options[$key . '_text'], $user);
207
+ if (isset($this->options[$key . '_tracking'])) {
208
+ $message .= $this->options[$key . '_tracking'];
209
+ }
210
+ echo $message;
211
+ die();
212
+
213
+ case 'c':
214
+ case 'confirm':
215
+ if (!$user) {
216
+ $this->dienow(__('Subscriber not found.', 'newsletter'), 'Or it is not present or the secret key does not match.', 404);
217
+ }
218
+
219
+ if ($this->antibot_form_check()) {
220
+ $user = $this->confirm($user);
221
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
222
+ $this->show_message('confirmed', $user);
223
+ } else {
224
+ $this->request_to_antibot_form('Confirm');
225
+ }
226
+ die();
227
+ break;
228
+
229
+ default:
230
+ return;
231
+ }
232
+ }
233
+
234
+ function upgrade() {
235
+ global $wpdb, $charset_collate;
236
+
237
+ parent::upgrade();
238
+
239
+ $newsletter = Newsletter::instance();
240
+ $lists_options = $this->get_options('lists');
241
+ $profile_options = $this->get_options('profile');
242
+
243
+ if (empty($lists_options)) {
244
+ foreach ($profile_options as $key => $value) {
245
+ if (strpos($key, 'list_') === 0) {
246
+ $lists_options[$key] = $value;
247
+ }
248
+ }
249
+ }
250
+
251
+ for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
252
+ // Options migration to the new set
253
+ if (!empty($profile_options['list_' . $i]) && empty($lists_options['list_' . $i])) {
254
+ $lists_options['list_' . $i] = $profile_options['list_' . $i];
255
+ $lists_options['list_' . $i . '_checked'] = $profile_options['list_' . $i . '_checked'];
256
+ $lists_options['list_' . $i . '_forced'] = $profile_options['list_' . $i . '_forced'];
257
+ }
258
+
259
+ if (!isset($profile_options['list_' . $i . '_forced'])) {
260
+ $profile_options['list_' . $i . '_forced'] = empty($this->options['preferences_' . $i]) ? 0 : 1;
261
+ $lists_options['list_' . $i . '_forced'] = empty($this->options['preferences_' . $i]) ? 0 : 1;
262
+ }
263
+ }
264
+
265
+ $this->save_options($profile_options, 'profile');
266
+ $this->save_options($lists_options, 'lists');
267
+
268
+ $default_options = $this->get_default_options();
269
+
270
+ if (empty($this->options['error_text'])) {
271
+ $this->options['error_text'] = $default_options['error_text'];
272
+ $this->save_options($this->options);
273
+ }
274
+
275
+ $this->init_options('template', false);
276
+
277
+ global $wpdb, $charset_collate;
278
+
279
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
280
+
281
+ $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_user_logs` (
282
+ `id` int(11) NOT NULL AUTO_INCREMENT,
283
+ `user_id` int(11) NOT NULL DEFAULT 0,
284
+ `ip` varchar(50) NOT NULL DEFAULT '',
285
+ `source` varchar(50) NOT NULL DEFAULT '',
286
+ `data` longtext,
287
+ `created` int(11) NOT NULL DEFAULT 0,
288
+ PRIMARY KEY (`id`)
289
+ ) $charset_collate;";
290
+
291
+ dbDelta($sql);
292
+
293
+ return true;
294
+ }
295
+
296
+ function first_install() {
297
+
298
+ }
299
+
300
+ function admin_menu() {
301
+ $this->add_menu_page('options', __('List building', 'newsletter'));
302
+ $this->add_admin_page('lists', __('Lists', 'newsletter'));
303
+ $this->add_admin_page('profile', __('Subscription Form', 'newsletter'));
304
+ $this->add_menu_page('antibot', __('Security', 'newsletter'));
305
+ $this->add_admin_page('forms', __('Forms', 'newsletter'));
306
+ $this->add_admin_page('template', __('Template', 'newsletter'));
307
+ }
308
+
309
+ /**
310
+ * This method has been redefined for compatibility with the old options naming. It would
311
+ * be better to change them instead. The subscription options should be named
312
+ * "newsletter_subscription" while the form field options, actually named
313
+ * "newsletter_profile", should be renamed "newsletter_subscription_profile" (since
314
+ * they are retrived with get_options('profile')) or "newsletter_subscription_fields" or
315
+ * "newsletter_subscription_form".
316
+ *
317
+ * @param array $options
318
+ * @param string $sub
319
+ */
320
+ function save_options($options, $sub = '', $autoload = null, $language = '') {
321
+ if (empty($sub) && empty($language)) {
322
+ // For compatibility the options are wrongly named
323
+ return update_option('newsletter', $options, $autoload);
324
+ }
325
+
326
+ if (empty($sub) && !empty($language)) {
327
+ return update_option('newsletter_' . $language, $options, $autoload);
328
+ }
329
+
330
+ if ($sub == 'profile') {
331
+ if (empty($language)) {
332
+ $this->options_profile = $options;
333
+ return update_option('newsletter_profile', $options, $autoload);
334
+ } else {
335
+ return update_option('newsletter_profile_' . $language, $options, $autoload);
336
+ }
337
+ // For compatibility the options are wrongly named
338
+ }
339
+
340
+ if ($sub == 'forms') {
341
+ // For compatibility the options are wrongly named
342
+ return update_option('newsletter_forms', $options, $autoload);
343
+ }
344
+
345
+ if ($sub == 'lists') {
346
+ $this->options_lists = $options;
347
+ }
348
+ return parent::save_options($options, $sub, $autoload, $language);
349
+ }
350
+
351
+ function get_options($sub = '', $language = '') {
352
+ if ($sub == '') {
353
+ // For compatibility the options are wrongly named
354
+ if ($language) {
355
+ $options = get_option('newsletter_' . $language, []);
356
+ $options = array_merge(get_option('newsletter', []), $options);
357
+ } else {
358
+ $options = get_option('newsletter', []);
359
+ }
360
+ if (!is_array($options)) {
361
+ $options = array();
362
+ }
363
+
364
+ return $options;
365
+ }
366
+ if ($sub == 'profile') {
367
+ if ($language) {
368
+ // All that because for unknown reasome, sometime the options are returned as string, maybe a WPML
369
+ // interference...
370
+ $i18n_options = get_option('newsletter_profile_' . $language, []);
371
+ if (!is_array($i18n_options)) {
372
+ $i18n_options = [];
373
+ }
374
+ $options = get_option('newsletter_profile', []);
375
+ if (!is_array($options)) {
376
+ $options = [];
377
+ }
378
+ $options = array_merge($options, array_filter($i18n_options));
379
+ } else {
380
+ $options = get_option('newsletter_profile', []);
381
+ }
382
+ // For compatibility the options are wrongly named
383
+ return $options;
384
+ }
385
+ if ($sub == 'forms') {
386
+ // For compatibility the options are wrongly named
387
+ return get_option('newsletter_forms', []);
388
+ }
389
+ return parent::get_options($sub, $language);
390
+ }
391
+
392
+ /**
393
+ * Prepares the options used to build a subscription form for the current language
394
+ * in internal variable $form_options (for optimization). Can be called many times.
395
+ */
396
+ function setup_form_options() {
397
+ if (empty($this->form_options)) {
398
+ $this->form_options = $this->get_options('profile', $this->get_current_language());
399
+ }
400
+ }
401
+
402
+ function get_form_options($language = '') {
403
+ return $this->get_options('profile', $language);
404
+ }
405
+
406
+ function set_updated($user, $time = 0, $ip = '') {
407
+ global $wpdb;
408
+ if (!$time) {
409
+ $time = time();
410
+ }
411
+
412
+ if (!$ip) {
413
+ $ip = $this->get_remote_ip();
414
+ }
415
+ $ip = $this->process_ip($ip);
416
+
417
+ if (is_object($user)) {
418
+ $id = $user->id;
419
+ } else if (is_array($user)) {
420
+ $id = $user['id'];
421
+ }
422
+
423
+ $id = (int) $id;
424
+
425
+ $wpdb->update(NEWSLETTER_USERS_TABLE, array('updated' => $time, 'ip' => $ip, 'geo' => 0), array('id' => $id));
426
+ }
427
+
428
+ /**
429
+ * Sanitize the subscription data collected before process them. Cleanup the lists, the optin mode, email, first name,
430
+ * last name, adds mandatory lists, get (if not provided) and process the IP.
431
+ *
432
+ * @param TNP_Subscription_Data $data
433
+ */
434
+ private function sanitize($data) {
435
+ $data->email = $this->normalize_email($data->email);
436
+ if (!empty($data->name)) {
437
+ $data->name = $this->normalize_name($data->name);
438
+ }
439
+ if (!empty($data->surname)) {
440
+ $data->surname = $this->normalize_name($data->surname);
441
+ }
442
+
443
+ if (empty($data->ip)) {
444
+ $data->ip = $this->get_remote_ip();
445
+ }
446
+ $data->ip = $this->process_ip($data->ip);
447
+
448
+ if (isset($data->http_referer)) {
449
+ $data->http_referer = mb_substr(strip_tags($data->http_referer), 0, 200);
450
+ }
451
+
452
+ if (isset($data->sex)) {
453
+ $data->sex = $this->normalize_sex($data->sex);
454
+ }
455
+
456
+ if (!isset($data->language)) {
457
+ $data->language = $this->get_current_language();
458
+ } else {
459
+ $data->language = strtolower(strip_tags($data->language));
460
+ }
461
+
462
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
463
+ $key = 'profile_' . $i;
464
+ if (isset($data->$key)) {
465
+ $data->$key = trim($data->$key);
466
+ }
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Builds a default subscription object to be used to collect data and subscription options.
472
+ *
473
+ * @return TNP_Subscription
474
+ */
475
+ function get_default_subscription($language = null) {
476
+ $subscription = new TNP_Subscription();
477
+
478
+ $language = is_null($language) ? $this->get_current_language() : $language;
479
+
480
+ $subscription->data->language = $language;
481
+ $subscription->optin = $this->is_double_optin() ? 'double' : 'single';
482
+
483
+ if (empty($this->options['multiple'])) {
484
+ $subscription->if_exists = TNP_Subscription::EXISTING_ERROR;
485
+ } else if ($this->options['multiple'] == 1) {
486
+ $subscription->if_exists = TNP_Subscription::EXISTING_MERGE;
487
+ } else {
488
+ $subscription->if_exists = TNP_Subscription::EXISTING_SINGLE_OPTIN;
489
+ }
490
+
491
+ $lists = $this->get_lists();
492
+ foreach ($lists as $list) {
493
+ if ($list->forced) {
494
+ $subscription->data->lists['' . $list->id] = 1;
495
+ continue;
496
+ }
497
+ // Enforced by language
498
+ if ($language && in_array($language, $list->languages)) {
499
+ $subscription->data->lists['' . $list->id] = 1;
500
+ }
501
+ }
502
+
503
+ return $subscription;
504
+ }
505
+
506
+ /**
507
+ *
508
+ * @param TNP_Subscription $subscription
509
+ *
510
+ * @return TNP_User|WP_Error
511
+ */
512
+ function subscribe2(TNP_Subscription $subscription) {
513
+
514
+ $this->logger->debug($subscription);
515
+
516
+ $this->sanitize($subscription->data);
517
+
518
+ if (empty($subscription->data->email)) {
519
+ return new WP_Error('email', 'Wrong email address');
520
+ }
521
+
522
+ if (!empty($subscription->data->country) && strlen($subscription->data->country) != 2) {
523
+ return new WP_Error('country', 'Country code length error. ISO 3166-1 alpha-2 format (2 letters)');
524
+ }
525
+
526
+ // Here we should have a clean subscription data
527
+ // Filter?
528
+
529
+ if ($subscription->spamcheck) {
530
+ // TODO: Use autoload
531
+ require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
532
+ $antispam = NewsletterAntispam::instance();
533
+ if ($antispam->is_spam($subscription)) {
534
+ return new WP_Error('spam', 'This looks like a spam subscription');
535
+ }
536
+ }
537
+
538
+ // Exists?
539
+ $user = $this->get_user_by_email($subscription->data->email);
540
+
541
+ $subscription = apply_filters('newsletter_subscription', $subscription, $user);
542
+
543
+ // Do we accept repeated subscriptions?
544
+ if ($user != null && $subscription->if_exists === TNP_Subscription::EXISTING_ERROR) {
545
+ //$this->show_message('error', $user);
546
+ return new WP_Error('exists', 'Email address already registered and Newsletter sets to block repeated registrations. You can change this behavior or the user message above on subscription configuration panel.');
547
+ }
548
+
549
+
550
+ if ($user != null) {
551
+
552
+ $this->logger->info('Subscription of an address with status ' . $user->status);
553
+
554
+ // We cannot communicate with bounced addresses, there is no reason to proceed
555
+ // TODO: Evaluate if the bounce status is very old, possible reset it
556
+ if ($user->status == TNP_User::STATUS_BOUNCED || $user->status == TNP_User::STATUS_COMPLAINED) {
557
+ return new WP_Error('bounced', 'Subscriber present and blocked');
558
+ }
559
+
560
+ if ($user->status == TNP_User::STATUS_UNSUBSCRIBED) {
561
+ // Special behavior?
562
+ }
563
+
564
+ if ($subscription->optin == 'single') {
565
+ $user->status = TNP_User::STATUS_CONFIRMED;
566
+ } else {
567
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
568
+
569
+ set_transient('newsletter_subscription_' . $user->id, $subscription->data, 3600 * 48);
570
+
571
+ // This status is *not* stored it indicate a temporary status to show the correct messages
572
+ $user->status = TNP_User::STATUS_NOT_CONFIRMED;
573
+
574
+ $this->send_message('confirmation', $user);
575
+
576
+ return $user;
577
+ } else {
578
+ $user->status = TNP_User::STATUS_NOT_CONFIRMED;
579
+ }
580
+ }
581
+
582
+ // Can be updated on the fly?
583
+ $subscription->data->merge_in($user);
584
+ } else {
585
+ $this->logger->info('New subscriber');
586
+
587
+ $user = new TNP_User();
588
+ $subscription->data->merge_in($user);
589
+
590
+ $user->token = $this->get_token();
591
+
592
+ $user->status = $subscription->optin == 'single' ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
593
+ $user->updated = time();
594
+ }
595
+
596
+ $user->ip = $this->process_ip($user->ip);
597
+
598
+ $user = apply_filters('newsletter_user_subscribe', $user);
599
+
600
+ $user = $this->save_user($user);
601
+
602
+ $this->add_user_log($user, 'subscribe');
603
+
604
+ // Notification to admin (only for new confirmed subscriptions)
605
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
606
+ do_action('newsletter_user_confirmed', $user);
607
+ $this->notify_admin_on_subscription($user);
608
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
609
+ }
610
+
611
+ if ($subscription->send_emails) {
612
+ $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
613
+ }
614
+
615
+ // Used by Autoresponder (probably)
616
+ do_action('newsletter_user_post_subscribe', $user);
617
+
618
+ return $user;
619
+ }
620
+
621
+ /**
622
+ * Create a subscription using the $_REQUEST data. Does security checks.
623
+ *
624
+ * @deprecated since version 6.9.0
625
+ * @param string $status The status to use for this subscription (confirmed, not confirmed, ...)
626
+ * @param bool $emails If the confirmation/welcome email should be sent or the subscription should be silent
627
+ * @return TNP_User
628
+ */
629
+ function subscribe($status = null, $emails = true) {
630
+
631
+ $this->logger->debug('Subscription start');
632
+
633
+ // Validation
634
+ $ip = $this->get_remote_ip();
635
+ $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
636
+ $first_name = '';
637
+ if (isset($_REQUEST['nn'])) {
638
+ $first_name = $this->normalize_name(stripslashes($_REQUEST['nn']));
639
+ }
640
+
641
+ $last_name = '';
642
+ if (isset($_REQUEST['ns'])) {
643
+ $last_name = $this->normalize_name(stripslashes($_REQUEST['ns']));
644
+ }
645
+
646
+ $opt_in = (int) $this->options['noconfirmation']; // 0 - double, 1 - single
647
+ if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
648
+ switch ($_REQUEST['optin']) {
649
+ case 'single': $opt_in = self::OPTIN_SINGLE;
650
+ break;
651
+ case 'double': $opt_in = self::OPTIN_DOUBLE;
652
+ break;
653
+ }
654
+ }
655
+
656
+ if ($status != null) {
657
+ // If a status is forced and it is requested to be "confirmed" is like a single opt in
658
+ // $status here can only be confirmed or not confirmed
659
+ // TODO: Add a check on status values
660
+ if ($status == TNP_User::STATUS_CONFIRMED) {
661
+ $opt_in = self::OPTIN_SINGLE;
662
+ } else {
663
+ $opt_in = self::OPTIN_DOUBLE;
664
+ }
665
+ }
666
+
667
+ $user = $this->get_user($email);
668
+
669
+ if ($user != null) {
670
+ // Email already registered in our database
671
+ $this->logger->info('Subscription of an address with status ' . $user->status);
672
+
673
+ // Bounced
674
+ // TODO: Manage other cases when added
675
+ if ($user->status == 'B') {
676
+ // Non persistent status to decide which message to show (error)
677
+ $user->status = 'E';
678
+ return $user;
679
+ }
680
+
681
+ // Is there any relevant data change? If so we can proceed otherwise if repeated subscriptions are disabled
682
+ // show an already subscribed message
683
+
684
+ if (empty($this->options['multiple'])) {
685
+ $user->status = 'E';
686
+ return $user;
687
+ }
688
+
689
+ // If the subscriber is confirmed, we cannot change his data in double opt in mode, we need to
690
+ // temporary store and wait for activation
691
+ if ($user->status == TNP_User::STATUS_CONFIRMED && $opt_in == self::OPTIN_DOUBLE) {
692
+
693
+ set_transient($this->get_user_key($user), $_REQUEST, 3600 * 48);
694
+
695
+ // This status is *not* stored it indicate a temporary status to show the correct messages
696
+ $user->status = 'S';
697
+
698
+ $this->send_message('confirmation', $user);
699
+
700
+ return $user;
701
+ }
702
+ }
703
+
704
+ // Here we have a new subscription or we can process the subscription even with a pre-existant user for example
705
+ // because it is not confirmed
706
+ if ($user != null) {
707
+ $this->logger->info("Email address subscribed but not confirmed");
708
+ $user = array('id' => $user->id);
709
+ } else {
710
+ $this->logger->info("New email address");
711
+ $user = array('email' => $email);
712
+ }
713
+
714
+ $user = $this->update_user_from_request($user);
715
+
716
+ $user['token'] = $this->get_token();
717
+ $ip = $this->process_ip($ip);
718
+ $user['ip'] = $ip;
719
+ $user['geo'] = 0;
720
+ $user['status'] = $opt_in == self::OPTIN_SINGLE ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
721
+
722
+ $user['updated'] = time();
723
+
724
+ $user = apply_filters('newsletter_user_subscribe', $user);
725
+
726
+ $user = $this->save_user($user);
727
+
728
+ $this->add_user_log($user, 'subscribe');
729
+
730
+ // Notification to admin (only for new confirmed subscriptions)
731
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
732
+ do_action('newsletter_user_confirmed', $user);
733
+ $this->notify_admin_on_subscription($user);
734
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
735
+ }
736
+
737
+ if ($emails) {
738
+ $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
739
+ }
740
+
741
+ $user = apply_filters('newsletter_user_post_subscribe', $user);
742
+
743
+ return $user;
744
+ }
745
+
746
+ function add_microdata($message) {
747
+ return $message . '<span itemscope itemtype="http://schema.org/EmailMessage"><span itemprop="description" content="Email address confirmation"></span><span itemprop="action" itemscope itemtype="http://schema.org/ConfirmAction"><meta itemprop="name" content="Confirm Subscription"><span itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler"><meta itemprop="url" content="{subscription_confirm_url}"><link itemprop="method" href="http://schema.org/HttpRequestMethod/POST"></span></span></span>';
748
+ }
749
+
750
+ /**
751
+ * Builds a subscription object starting from values in the $_REQUEST
752
+ * global variable. It DOES NOT sanitizie or formally check the values.
753
+ * Usually data comes from a form submission.
754
+ * https://www.thenewsletterplugin.com/documentation/subscription/newsletter-forms/
755
+ *
756
+ * @return TNP_Subscription
757
+ */
758
+ function build_subscription() {
759
+
760
+ $language = '';
761
+ if (!empty($_REQUEST['nlang'])) {
762
+ $language = $_REQUEST['nlang'];
763
+ } else {
764
+ $language = $this->get_current_language();
765
+ }
766
+
767
+ $subscription = $this->get_default_subscription($language);
768
+ $data = $subscription->data;
769
+
770
+ $data->email = $_REQUEST['ne'];
771
+
772
+ if (isset($_REQUEST['nn'])) {
773
+ $data->name = stripslashes($_REQUEST['nn']);
774
+ }
775
+
776
+ if (isset($_REQUEST['ns'])) {
777
+ $data->surname = stripslashes($_REQUEST['ns']);
778
+ }
779
+
780
+ if (!empty($_REQUEST['nx'])) {
781
+ $data->sex = $_REQUEST['nx'][0];
782
+ }
783
+
784
+ if (isset($_REQUEST['nr'])) {
785
+ $data->referrer = $_REQUEST['nr'];
786
+ }
787
+
788
+ // From the antibot form
789
+ if (isset($_REQUEST['nhr'])) {
790
+ $data->http_referer = stripslashes($_REQUEST['nhr']);
791
+ } else if (isset($_SERVER['HTTP_REFERER'])) {
792
+ $data->http_referer = $_SERVER['HTTP_REFERER'];
793
+ }
794
+
795
+ // New profiles
796
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
797
+ // If the profile cannot be set by subscriber, skip it.
798
+ if ($this->options_profile['profile_' . $i . '_status'] == 0) {
799
+ continue;
800
+ }
801
+ if (isset($_REQUEST['np' . $i])) {
802
+ $data->profiles['' . $i] = stripslashes($_REQUEST['np' . $i]);
803
+ }
804
+ }
805
+
806
+ // Lists (field name is nl[] and values the list number so special forms with radio button can work)
807
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
808
+ $this->logger->debug($_REQUEST['nl']);
809
+ foreach ($_REQUEST['nl'] as $list_id) {
810
+ $list = $this->get_list($list_id);
811
+ if (!$list || $list->is_private()) {
812
+ // To administrator show an error to make him aware of the wrong form configuration
813
+ if (current_user_can('administrator')) {
814
+ $this->dienow('Invalid list', 'List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
815
+ }
816
+ // Ignore this list
817
+ continue;
818
+ }
819
+ $data->lists['' . $list_id] = 1;
820
+ }
821
+ } else {
822
+ $this->logger->debug('No lists received');
823
+ }
824
+
825
+ // Opt-in mode
826
+ if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
827
+ switch ($_REQUEST['optin']) {
828
+ case 'single': $subscription->optin = 'single';
829
+ break;
830
+ case 'double': $subscription->optin = 'double';
831
+ break;
832
+ }
833
+ }
834
+
835
+ return $subscription;
836
+ }
837
+
838
+ /**
839
+ * Processes the request and fill in the *array* representing a subscriber with submitted values
840
+ * (filtering when necessary).
841
+ *
842
+ * @deprecated since version 6.9.0
843
+ * @param array $user An array partially filled with subscriber data
844
+ * @return array The filled array representing a subscriber
845
+ */
846
+ function update_user_from_request($user) {
847
+
848
+ if (isset($_REQUEST['nn'])) {
849
+ $user['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
850
+ }
851
+ // TODO: required checking
852
+
853
+ if (isset($_REQUEST['ns'])) {
854
+ $user['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
855
+ }
856
+ // TODO: required checking
857
+
858
+ if (!empty($_REQUEST['nx'])) {
859
+ $user['sex'] = $this->normalize_sex($_REQUEST['nx'][0]);
860
+ }
861
+ // TODO: valid values check
862
+
863
+ if (isset($_REQUEST['nr'])) {
864
+ $user['referrer'] = strip_tags(trim($_REQUEST['nr']));
865
+ }
866
+
867
+ $language = '';
868
+ if (!empty($_REQUEST['nlang'])) {
869
+ $language = strtolower(strip_tags($_REQUEST['nlang']));
870
+ // TODO: Check if it's an allowed language code
871
+ $user['language'] = $language;
872
+ } else {
873
+ $language = $this->get_current_language();
874
+ $user['language'] = $language;
875
+ }
876
+
877
+ // From the antibot form
878
+ if (isset($_REQUEST['nhr'])) {
879
+ $user['http_referer'] = strip_tags(trim($_REQUEST['nhr']));
880
+ } else if (isset($_SERVER['HTTP_REFERER'])) {
881
+ $user['http_referer'] = strip_tags(trim($_SERVER['HTTP_REFERER']));
882
+ }
883
+
884
+ if (strlen($user['http_referer']) > 200) {
885
+ $user['http_referer'] = mb_substr($user['http_referer'], 0, 200);
886
+ }
887
+
888
+ // New profiles
889
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
890
+ // If the profile cannot be set by subscriber, skip it.
891
+ if ($this->options_profile['profile_' . $i . '_status'] == 0) {
892
+ continue;
893
+ }
894
+ if (isset($_REQUEST['np' . $i])) {
895
+ $user['profile_' . $i] = trim(stripslashes($_REQUEST['np' . $i]));
896
+ }
897
+ }
898
+
899
+ // Extra validation to explain the administrator while the submitted data could
900
+ // be interpreted only partially
901
+ if (current_user_can('administrator')) {
902
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
903
+ foreach ($_REQUEST['nl'] as $list_id) {
904
+ $list = $this->get_list($list_id);
905
+ if ($list && $list->status == TNP_List::STATUS_PRIVATE) {
906
+ $this->dienow('Invalid list', '[old] List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
907
+ }
908
+ }
909
+ }
910
+ }
911
+ // Preferences (field names are nl[] and values the list number so special forms with radio button can work)
912
+ // Permetto l'aggiunta solo delle liste pubbliche
913
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
914
+ $lists = $this->get_lists_public();
915
+ //$this->logger->debug($_REQUEST['nl']);
916
+ foreach ($lists as $list) {
917
+ if (in_array('' . $list->id, $_REQUEST['nl'])) {
918
+ $user['list_' . $list->id] = 1;
919
+ }
920
+ }
921
+ } else {
922
+ $this->logger->debug('No lists received');
923
+ }
924
+
925
+ // Forced lists (general or by language)
926
+ // Forzo l'aggiunta delle liste forzate
927
+ $lists = $this->get_lists();
928
+ foreach ($lists as $list) {
929
+ if ($list->forced) {
930
+ $user['list_' . $list->id] = 1;
931
+ }
932
+ if (in_array($language, $list->languages)) {
933
+ $user['list_' . $list->id] = 1;
934
+ }
935
+ }
936
+
937
+ // TODO: should be removed!!!
938
+ if (defined('NEWSLETTER_FEED_VERSION')) {
939
+ $options_feed = get_option('newsletter_feed', array());
940
+ if ($options_feed['add_new'] == 1) {
941
+ $user['feed'] = 1;
942
+ }
943
+ }
944
+ return $user;
945
+ }
946
+
947
+ /**
948
+ * Sends a service message applying the template to the HTML part
949
+ *
950
+ * @param TNP_User $user
951
+ * @param string $subject
952
+ * @param string|array $message If string it is considered HTML, if array it should contains the key "html" and "text"
953
+ * @return type
954
+ */
955
+ function mail($user, $subject, $message) {
956
+ $language = $this->get_user_language($user);
957
+
958
+ $options_template = $this->get_options('template', $language);
959
+
960
+ $template = trim($options_template['template']);
961
+ if (empty($template) || strpos($template, '{message}') === false) {
962
+ $template = '{message}';
963
+ }
964
+
965
+ if (is_array($message)) {
966
+ $message['html'] = str_replace('{message}', $message['html'], $template);
967
+ $message['html'] = $this->replace($message['html'], $user);
968
+ $message['text'] = $this->replace($message['text'], $user);
969
+ } else {
970
+ $message = str_replace('{message}', $message, $template);
971
+ $message = $this->replace($message, $user);
972
+ }
973
+
974
+ $headers = [];
975
+
976
+ // Replaces tags from the template
977
+
978
+ $subject = $this->replace($subject, $user);
979
+
980
+ return Newsletter::instance()->mail($user->email, $subject, $message, $headers);
981
+ }
982
+
983
+ /**
984
+ * Confirms a subscription changing the user status and, possibly, merging the
985
+ * temporary data if present.
986
+ *
987
+ * @param TNP_User $user Optionally it can be null (user search from requests paramaters, but deprecated, or a user id)
988
+ * @return TNP_User
989
+ */
990
+ function confirm($user = null, $emails = true) {
991
+
992
+ // Compatibility with WP Registration Addon
993
+ if (!$user) {
994
+ $user = $this->get_user_from_request(true);
995
+ } else if (is_numeric($user)) {
996
+ $user = $this->get_user($user);
997
+ }
998
+
999
+ if (!$user) {
1000
+ $this->dienow('Subscriber not found', '', 404);
1001
+ }
1002
+ // End compatibility
1003
+ // Should be merged?
1004
+ $data = get_transient('newsletter_subscription_' . $user->id);
1005
+ if ($data !== false) {
1006
+ $data->merge_in($user);
1007
+ //$this->merge($user, $data);
1008
+ $user = $this->save_user($user);
1009
+ $user->status = TNP_User::STATUS_NOT_CONFIRMED;
1010
+ delete_transient('newsletter_subscription_' . $user->id);
1011
+ } else {
1012
+ $new_email = get_transient('newsletter_user_' . $user->id . '_email');
1013
+ if ($new_email) {
1014
+ $data = ['id' => $user->id, 'email' => $new_email];
1015
+ $this->save_user($data);
1016
+ delete_transient('newsletter_user_' . $user->id . '_email');
1017
+ }
1018
+ }
1019
+
1020
+
1021
+ $this->update_user_last_activity($user);
1022
+
1023
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
1024
+
1025
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
1026
+ $this->add_user_log($user, 'activate');
1027
+ do_action('newsletter_user_confirmed', $user);
1028
+ return $user;
1029
+ }
1030
+
1031
+ $this->set_user_status($user, TNP_User::STATUS_CONFIRMED);
1032
+
1033
+ $user = $this->get_user($user);
1034
+
1035
+ $this->add_user_log($user, 'activate');
1036
+
1037
+ do_action('newsletter_user_confirmed', $user);
1038
+ $this->notify_admin_on_subscription($user);
1039
+
1040
+ if ($emails) {
1041
+ $this->send_message('confirmed', $user);
1042
+ }
1043
+
1044
+ return $user;
1045
+ }
1046
+
1047
+ /**
1048
+ * Sends a message (activation, welcome, cancellation, ...) with the correct template
1049
+ * and checking if the message itself is disabled
1050
+ *
1051
+ * @param string $type
1052
+ * @param TNP_User $user
1053
+ */
1054
+ function send_message($type, $user, $force = false) {
1055
+ if (!$force && !empty($this->options[$type . '_disabled'])) {
1056
+ return true;
1057
+ }
1058
+
1059
+ $language = $this->get_user_language($user);
1060
+
1061
+ $options = $this->get_options('', $language);
1062
+ $message = [];
1063
+ $message['html'] = do_shortcode($options[$type . '_message']);
1064
+ $message['text'] = $this->get_text_message($type);
1065
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
1066
+ $message['html'] = $this->add_microdata($message['html']);
1067
+ }
1068
+ $subject = $options[$type . '_subject'];
1069
+
1070
+ return $this->mail($user, $subject, $message);
1071
+ }
1072
+
1073
+ function get_text_message($type) {
1074
+ switch ($type) {
1075
+ case 'confirmation':
1076
+ return __('To confirm your subscription follow the link below.', 'newsletter') . "\n\n{subscription_confirm_url}";
1077
+ case 'confirmed':
1078
+ return __('Your subscription has been confirmed.', 'newsletter');
1079
+ }
1080
+ return '';
1081
+ }
1082
+
1083
+ function is_double_optin() {
1084
+ return $this->options['noconfirmation'] == 0;
1085
+ }
1086
+
1087
+ /**
1088
+ * Sends the activation email without conditions.
1089
+ *
1090
+ * @param stdClass $user
1091
+ * @return bool
1092
+ */
1093
+ function send_activation_email($user) {
1094
+ return $this->send_message('confirmation', $user, true);
1095
+ }
1096
+
1097
+ /**
1098
+ * Finds the right way to show the message identified by $key (welcome, unsubscription, ...) redirecting the user to the
1099
+ * WordPress page or loading the configured url or activating the standard page.
1100
+ */
1101
+ function show_message($key, $user, $alert = '', $email = null) {
1102
+ $url = '';
1103
+
1104
+ if (isset($_REQUEST['ncu'])) {
1105
+ // Custom URL from the form
1106
+ $url = $_REQUEST['ncu'];
1107
+ } else {
1108
+ // Per message custom URL from configuration (language variants could not be supported)
1109
+ $options = $this->get_options('', $this->get_user_language($user));
1110
+ if (!empty($options[$key . '_url'])) {
1111
+ $url = $options[$key . '_url'];
1112
+ }
1113
+ }
1114
+
1115
+ $url = Newsletter::instance()->build_message_url($url, $key, $user, $email, $alert);
1116
+ wp_redirect($url);
1117
+
1118
+ die();
1119
+ }
1120
+
1121
+ function get_message_key_from_request() {
1122
+ if (empty($_GET['nm'])) {
1123
+ return 'subscription';
1124
+ }
1125
+ $key = $_GET['nm'];
1126
+ switch ($key) {
1127
+ case 's': return 'confirmation';
1128
+ case 'c': return 'confirmed';
1129
+ case 'u': return 'unsubscription';
1130
+ case 'uc': return 'unsubscribed';
1131
+ case 'p':
1132
+ case 'pe':
1133
+ return 'profile';
1134
+ default: return $key;
1135
+ }
1136
+ }
1137
+
1138
+ var $privacy_url = false;
1139
+
1140
+ /**
1141
+ * Generates the privacy URL and cache it.
1142
+ *
1143
+ * @return string
1144
+ */
1145
+ function get_privacy_url() {
1146
+ if ($this->privacy_url === false) {
1147
+ $this->setup_form_options();
1148
+ if (!empty($this->form_options['privacy_use_wp_url']) && function_exists('get_privacy_policy_url')) {
1149
+ $this->privacy_url = get_privacy_policy_url();
1150
+ } else {
1151
+ $this->privacy_url = $this->form_options['privacy_url'];
1152
+ }
1153
+ }
1154
+ return $this->privacy_url;
1155
+ }
1156
+
1157
+ function get_form_javascript() {
1158
+
1159
+ }
1160
+
1161
+ /**
1162
+ * Manages the custom forms made with [newsletter_form] and internal [newsletter_field] shortcodes.
1163
+ *
1164
+ * @param array $attrs
1165
+ * @param string $content
1166
+ * @return string
1167
+ */
1168
+ function get_subscription_form_custom($attrs = [], $content = '') {
1169
+ if (!is_array($attrs)) {
1170
+ $attrs = [];
1171
+ }
1172
+
1173
+ $this->setup_form_options();
1174
+
1175
+ $attrs = array_merge(['class' => 'tnp-subscription', 'style' => ''], $attrs);
1176
+
1177
+ $action = esc_attr($this->build_action_url('s'));
1178
+ $class = esc_attr($attrs['class']);
1179
+ $style = esc_attr($attrs['style']);
1180
+ $buffer = '<form method="post" action="' . $action . '" class="' . $class . '" style="' . $style . '">' . "\n";
1181
+
1182
+ $language = $this->get_current_language();
1183
+
1184
+ $buffer .= $this->get_form_hidden_fields($attrs);
1185
+
1186
+ $buffer .= do_shortcode($content);
1187
+
1188
+ if (isset($attrs['button_label'])) {
1189
+ $label = $attrs['button_label'];
1190
+ } else {
1191
+ $label = $this->form_options['subscribe'];
1192
+ }
1193
+
1194
+ if (!empty($label)) {
1195
+ $buffer .= '<div class="tnp-field tnp-field-button">';
1196
+ if (strpos($label, 'http') === 0) {
1197
+ $buffer .= '<input class="tnp-button-image" type="image" src="' . $label . '">';
1198
+ } else {
1199
+ $buffer .= '<input class="tnp-button" type="submit" value="' . $label . '">';
1200
+ }
1201
+ $buffer .= '</div>';
1202
+ }
1203
+
1204
+ $buffer .= '</form>';
1205
+
1206
+ return $buffer;
1207
+ }
1208
+
1209
+ /** Generates the hidden field for lists which should be implicitely set with a subscription form.
1210
+ *
1211
+ * @param string $lists Comma separated directly from the shortcode "lists" attribute
1212
+ * @param string $language ???
1213
+ * @return string
1214
+ */
1215
+ function get_form_implicit_lists($lists, $language = '') {
1216
+ $buffer = '';
1217
+
1218
+ $arr = explode(',', $lists);
1219
+
1220
+ foreach ($arr as $a) {
1221
+ $a = trim($a);
1222
+ if (empty($a))
1223
+ continue;
1224
+
1225
+ $list = $this->get_list($a);
1226
+ if (!$list) {
1227
+ $buffer .= $this->build_field_admin_notice('List "' . $a . '" added to the form is not configured, skipped.');
1228
+ continue;
1229
+ }
1230
+
1231
+ if ($list->is_private()) {
1232
+ $buffer .= $this->build_field_admin_notice('List ' . $a . ' is private cannot be used in a public form.');
1233
+ continue;
1234
+ }
1235
+
1236
+ if ($list->forced) {
1237
+ $buffer .= $this->build_field_admin_notice('List ' . $a . ' is already enforced on every subscription there is no need to specify it.');
1238
+ continue;
1239
+ }
1240
+
1241
+ $buffer .= "<input type='hidden' name='nl[]' value='" . esc_attr($a) . "'>\n";
1242
+ }
1243
+ return $buffer;
1244
+ }
1245
+
1246
+ /**
1247
+ * Builds all the hidden fields of a subscription form. Implicit lists, confirmation url,
1248
+ * referrer, language, ...
1249
+ *
1250
+ * @param array $attrs Attributes of form shortcode
1251
+ * @return string HTML with the hidden fields
1252
+ */
1253
+ function get_form_hidden_fields($attrs) {
1254
+ $b = '';
1255
+
1256
+ // Compatibility
1257
+ if (isset($attrs['list'])) {
1258
+ $attrs['lists'] = $attrs['list'];
1259
+ }
1260
+ if (isset($attrs['lists'])) {
1261
+ $b .= $this->get_form_implicit_lists($attrs['lists']);
1262
+ }
1263
+
1264
+ if (isset($attrs['referrer'])) {
1265
+ $b .= '<input type="hidden" name="nr" value="' . esc_attr($attrs['referrer']) . '">';
1266
+ }
1267
+
1268
+ if (isset($attrs['confirmation_url'])) {
1269
+ if ($attrs['confirmation_url'] === '#') {
1270
+ $attrs['confirmation_url'] = esc_url_raw($_SERVER['REQUEST_URI']);
1271
+ }
1272
+
1273
+ $b .= '<input type="hidden" name="ncu" value="' . esc_attr($attrs['confirmation_url']) . '">';
1274
+ }
1275
+
1276
+ if (isset($attrs['optin'])) {
1277
+ $optin = trim(strtolower($attrs['optin']));
1278
+ if ($optin !== 'double' && $optin !== 'single') {
1279
+ $b .= $this->build_field_admin_notice('The optin is set to an invalid value.');
1280
+ } else {
1281
+ if ($optin !== 'double' && $this->is_double_optin() && empty($this->options['optin_override'])) {
1282
+ $b .= $this->build_field_admin_notice('The optin is specified but cannot be overridden (see the subscription configiraton page).');
1283
+ } else {
1284
+ $b .= '<input type="hidden" name="optin" value="' . esc_attr($optin) . '">';
1285
+ }
1286
+ }
1287
+ }
1288
+
1289
+ $language = $this->get_current_language();
1290
+ $b .= '<input type="hidden" name="nlang" value="' . esc_attr($language) . '">';
1291
+
1292
+ return $b;
1293
+ }
1294
+
1295
+ /**
1296
+ * Internal use only
1297
+ *
1298
+ * @param type $name
1299
+ * @param type $attrs
1300
+ * @param type $suffix
1301
+ * @return string
1302
+ */
1303
+ private function _shortcode_label($name, $attrs, $suffix = null) {
1304
+
1305
+ if (!$suffix) {
1306
+ $suffix = $name;
1307
+ }
1308
+ $buffer = '<label for="' . esc_attr($attrs['id']) . '">';
1309
+ if (isset($attrs['label'])) {
1310
+ if (empty($attrs['label'])) {
1311
+ return;
1312
+ } else {
1313
+ $buffer .= esc_html($attrs['label']);
1314
+ }
1315
+ } else {
1316
+ if (isset($this->form_options[$name])) {
1317
+ $buffer .= esc_html($this->form_options[$name]);
1318
+ }
1319
+ }
1320
+ $buffer .= "</label>\n";
1321
+ return $buffer;
1322
+ }
1323
+
1324
+ /**
1325
+ * Creates a notices to be displayed near a subscription form field to inform of worng configurations.
1326
+ * It is created only if the current user looking at the form is the administrator.
1327
+ *
1328
+ * @param string $message
1329
+ * @return string
1330
+ */
1331
+ function build_field_admin_notice($message) {
1332
+ if (!current_user_can('administrator')) {
1333
+ return '';
1334
+ }
1335
+ return '<p style="background-color: #eee; color: #000; padding: 10px; margin: 10px 0">' . $message . ' <strong>This notice is shown only to administrators to help with configuration.</strong></p>';
1336
+ }
1337
+
1338
+ function shortcode_newsletter_field($attrs, $content = '') {
1339
+ // Counter to create unique ID for checkbox and labels
1340
+ static $idx = 0;
1341
+
1342
+ $idx++;
1343
+ $attrs['id'] = 'tnp-' . $idx;
1344
+
1345
+ $this->setup_form_options();
1346
+ $language = $this->get_current_language();
1347
+
1348
+ $name = $attrs['name'];
1349
+
1350
+ $buffer = '';
1351
+
1352
+ if ($name == 'email') {
1353
+ $buffer .= '<div class="tnp-field tnp-field-email">';
1354
+
1355
+ $buffer .= $this->_shortcode_label('email', $attrs);
1356
+
1357
+ $buffer .= '<input class="tnp-email" type="email" name="ne" id="' . esc_attr($attrs['id']) . '" value=""';
1358
+ if (isset($attrs['placeholder'])) {
1359
+ $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1360
+ }
1361
+ $buffer .= ' required>';
1362
+ if (isset($attrs['button_label'])) {
1363
+ $label = $attrs['button_label'];
1364
+ if (strpos($label, 'http') === 0) {
1365
+ $buffer .= ' <input class="tnp-submit-image" type="image" src="' . esc_attr(esc_url_raw($label)) . '">';
1366
+ } else {
1367
+ $buffer .= ' <input class="tnp-submit" type="submit" value="' . esc_attr($label) . '" style="width: 29%">';
1368
+ }
1369
+ }
1370
+ $buffer .= "</div>\n";
1371
+ return $buffer;
1372
+ }
1373
+
1374
+ if ($name == 'first_name' || $name == 'name') {
1375
+ $buffer .= '<div class="tnp-field tnp-field-firstname">';
1376
+ $buffer .= $this->_shortcode_label('name', $attrs);
1377
+
1378
+ $buffer .= '<input class="tnp-name" type="text" name="nn" id="' . esc_attr($attrs['id']) . '" value=""';
1379
+ if (isset($attrs['placeholder'])) {
1380
+ $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1381
+ }
1382
+ if ($this->form_options['name_rules'] == 1) {
1383
+ $buffer .= ' required';
1384
+ }
1385
+ $buffer .= '>';
1386
+ $buffer .= "</div>\n";
1387
+ return $buffer;
1388
+ }
1389
+
1390
+ if ($name == 'last_name' || $name == 'surname') {
1391
+ $buffer .= '<div class="tnp-field tnp-field-surname">';
1392
+ $buffer .= $this->_shortcode_label('surname', $attrs);
1393
+
1394
+ $buffer .= '<input class="tnp-surname" type="text" name="ns" id="' . esc_attr($attrs['id']) . '" value=""';
1395
+ if (isset($attrs['placeholder'])) {
1396
+ $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1397
+ }
1398
+ if ($this->form_options['surname_rules'] == 1) {
1399
+ $buffer .= ' required';
1400
+ }
1401
+ $buffer .= '>';
1402
+ $buffer .= '</div>';
1403
+ return $buffer;
1404
+ }
1405
+
1406
+ // Single list
1407
+ if ($name == 'preference' || $name == 'list') {
1408
+ if (!isset($attrs['number'])) {
1409
+ return $this->build_field_admin_notice('List number not specified.');
1410
+ }
1411
+ $number = (int) $attrs['number'];
1412
+ $list = $this->get_list($number, $language);
1413
+ if (!$list) {
1414
+ return $this->build_field_admin_notice('List ' . $number . ' is not configured, cannot be shown.');
1415
+ }
1416
+
1417
+ if ($list->status == 0 || $list->forced) {
1418
+ return $this->build_field_admin_notice('List ' . $number . ' is private or enforced cannot be shown.');
1419
+ }
1420
+
1421
+ if (isset($attrs['hidden'])) {
1422
+ return '<input type="hidden" name="nl[]" value="' . esc_attr($list->id) . '">';
1423
+ }
1424
+
1425
+ $idx++;
1426
+ $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="tnp-' . $idx . '">';
1427
+ $buffer .= '<input type="checkbox" id="tnp-' . $idx . '" name="nl[]" value="' . esc_attr($list->id) . '"';
1428
+ if (isset($attrs['checked'])) {
1429
+ $buffer .= ' checked';
1430
+ }
1431
+ $buffer .= '>';
1432
+ if (isset($attrs['label'])) {
1433
+ if ($attrs['label'] != '') {
1434
+ $buffer .= '&nbsp;' . esc_html($attrs['label']) . '</label>';
1435
+ }
1436
+ } else {
1437
+ $buffer .= '&nbsp;' . esc_html($list->name) . '</label>';
1438
+ }
1439
+ $buffer .= "</div>\n";
1440
+
1441
+ return $buffer;
1442
+ }
1443
+
1444
+ // All lists
1445
+ if ($name == 'lists' || $name == 'preferences') {
1446
+ $lists = $this->get_lists_for_subscription($language);
1447
+ if (!empty($lists) && isset($attrs['layout']) && $attrs['layout'] === 'dropdown') {
1448
+
1449
+ $buffer .= '<div class="tnp-field tnp-lists">';
1450
+ // There is not a default "label" for the block of lists, so it can only be specified in the shortcode attrs as "label"
1451
+ $buffer .= $this->_shortcode_label('lists', $attrs);
1452
+ $buffer .= '<select class="tnp-lists" name="nl[]" required>';
1453
+
1454
+ if (!empty($attrs['first_option_label'])) {
1455
+ $buffer .= '<option value="" selected="true" disabled="disabled">' . esc_html($attrs['first_option_label']) . '</option>';
1456
+ }
1457
+
1458
+ foreach ($lists as $list) {
1459
+ $buffer .= '<option value="' . $list->id . '">' . esc_html($list->name) . '</option>';
1460
+ }
1461
+ $buffer .= '</select>';
1462
+ $buffer .= '</div>';
1463
+ } else {
1464
+
1465
+ foreach ($lists as $list) {
1466
+ $idx++;
1467
+ $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="nl' . $idx . '">';
1468
+ $buffer .= '<input type="checkbox" id="nl' . $idx . '" name="nl[]" value="' . $list->id . '"';
1469
+ if ($list->checked) {
1470
+ $buffer .= ' checked';
1471
+ }
1472
+ $buffer .= '>&nbsp;' . esc_html($list->name) . '</label>';
1473
+ $buffer .= "</div>\n";
1474
+ }
1475
+ }
1476
+ return $buffer;
1477
+ }
1478
+
1479
+ if ($name == 'sex' || $name == 'gender') {
1480
+ $buffer .= '<div class="tnp-field tnp-field-gender">';
1481
+ $buffer .= $this->_shortcode_label('sex', $attrs);
1482
+
1483
+ $buffer .= '<select name="nx" class="tnp-gender" id="tnp-gender"';
1484
+ if ($this->form_options['sex_rules']) {
1485
+ $buffer .= ' required ';
1486
+ }
1487
+ $buffer .= '>';
1488
+ if ($this->form_options['sex_rules']) {
1489
+ $buffer .= '<option value=""></option>';
1490
+ }
1491
+ $buffer .= '<option value="n">' . esc_html($this->form_options['sex_none']) . '</option>';
1492
+ $buffer .= '<option value="f">' . esc_html($this->form_options['sex_female']) . '</option>';
1493
+ $buffer .= '<option value="m">' . esc_html($this->form_options['sex_male']) . '</option>';
1494
+ $buffer .= '</select>';
1495
+ $buffer .= "</div>\n";
1496
+ return $buffer;
1497
+ }
1498
+
1499
+ if ($name == 'profile') {
1500
+ if (!isset($attrs['number'])) {
1501
+ return $this->build_field_admin_notice('Extra profile number not specified.');
1502
+ }
1503
+
1504
+ $number = (int) $attrs['number'];
1505
+
1506
+ $profile = TNP_Profile_Service::get_profile_by_id($number, $language);
1507
+
1508
+ if (!$profile) {
1509
+ return $this->build_field_admin_notice('Extra profile ' . $number . ' is not configured, cannot be shown.');
1510
+ }
1511
+
1512
+ if ($profile->status == 0) {
1513
+ return $this->build_field_admin_notice('Extra profile ' . $number . ' is private, cannot be shown.');
1514
+ }
1515
+
1516
+ $size = isset($attrs['size']) ? $attrs['size'] : '';
1517
+ $buffer .= '<div class="tnp-field tnp-field-profile">';
1518
+ $buffer .= $this->_shortcode_label('profile_' . $profile->id, $attrs);
1519
+
1520
+ $placeholder = isset($attrs['placeholder']) ? $attrs['placeholder'] : $profile->placeholder;
1521
+
1522
+ // Text field
1523
+ if ($profile->type == TNP_Profile::TYPE_TEXT) {
1524
+ $buffer .= '<input class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" type="text" size="' . esc_attr($size) . '" name="np' . $number . '" placeholder="' . esc_attr($placeholder) . '"';
1525
+ if ($profile->is_required()) {
1526
+ $buffer .= ' required';
1527
+ }
1528
+ $buffer .= '>';
1529
+ }
1530
+
1531
+ // Select field
1532
+ if ($profile->type == TNP_Profile::TYPE_SELECT) {
1533
+ $buffer .= '<select class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" name="np' . $number . '"';
1534
+ if ($profile->is_required()) {
1535
+ $buffer .= ' required';
1536
+ }
1537
+ $buffer .= '>';
1538
+ if (!empty($placeholder)) {
1539
+ $buffer .= '<option value="" selected disabled>' . esc_html($placeholder) . '</option>';
1540
+ }
1541
+ foreach ($profile->options as $option) {
1542
+ $buffer .= '<option>' . esc_html(trim($option)) . '</option>';
1543
+ }
1544
+ $buffer .= "</select>\n";
1545
+ }
1546
+
1547
+ $buffer .= "</div>\n";
1548
+
1549
+ return $buffer;
1550
+ }
1551
+
1552
+ if (strpos($name, 'privacy') === 0) {
1553
+ if (!isset($attrs['url'])) {
1554
+ $attrs['url'] = $this->get_privacy_url();
1555
+ }
1556
+
1557
+ if (!isset($attrs['label'])) {
1558
+ $attrs['label'] = $this->form_options['privacy'];
1559
+ }
1560
+
1561
+ $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-privacy">';
1562
+
1563
+ $idx++;
1564
+ $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy" id="tnp-' . $idx . '"> ';
1565
+ $buffer .= '<label for="tnp-' . $idx . '">';
1566
+ if (!empty($attrs['url'])) {
1567
+ $buffer .= '<a target="_blank" href="' . esc_attr($attrs['url']) . '">';
1568
+ }
1569
+ $buffer .= $attrs['label'];
1570
+ if (!empty($attrs['url'])) {
1571
+ $buffer .= '</a>';
1572
+ }
1573
+ $buffer .= '</label>';
1574
+ $buffer .= '</div>';
1575
+
1576
+ return $buffer;
1577
+ }
1578
+ }
1579
+
1580
+ /**
1581
+ * Builds the privacy field only for completely generated forms.
1582
+ *
1583
+ * @return string Empty id the privacy filed is not configured
1584
+ */
1585
+ function get_privacy_field($pre_html = '', $post_html = '') {
1586
+ $this->setup_form_options();
1587
+ $privacy_status = (int) $this->form_options['privacy_status'];
1588
+ if (empty($privacy_status)) {
1589
+ return '';
1590
+ }
1591
+
1592
+ $buffer = '<label>';
1593
+ if ($privacy_status === 1) {
1594
+ $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy">&nbsp;';
1595
+ }
1596
+ $url = $this->get_privacy_url();
1597
+ if (!empty($url)) {
1598
+ $buffer .= '<a target="_blank" href="' . esc_attr($url) . '">';
1599
+ $buffer .= esc_attr($this->form_options['privacy']) . '</a>';
1600
+ } else {
1601
+ $buffer .= esc_html($this->form_options['privacy']);
1602
+ }
1603
+
1604
+ $buffer .= "</label>";
1605
+
1606
+ return $pre_html . $buffer . $post_html;
1607
+ }
1608
+
1609
+ /**
1610
+ * The new standard form.
1611
+ *
1612
+ * @param string $referrer Deprecated since 6.9.1, use the "referrer" key on $attrs
1613
+ * @param string $action
1614
+ * @param string $attrs
1615
+ * @return string The full HTML form
1616
+ */
1617
+ function get_subscription_form($referrer = '', $action = null, $attrs = []) {
1618
+ $language = $this->get_current_language();
1619
+ $options_profile = $this->get_options('profile', $language);
1620
+
1621
+ if (!is_array($attrs)) {
1622
+ $attrs = [];
1623
+ }
1624
+
1625
+ // Possible alternative form actions (used by...?)
1626
+ if (isset($attrs['action'])) {
1627
+ $action = $attrs['action'];
1628
+ }
1629
+
1630
+ // The referrer parameter is deprecated
1631
+ if (!empty($referrer)) {
1632
+ $attrs['referrer'] = $referrer;
1633
+ }
1634
+
1635
+ $buffer = '';
1636
+
1637
+ if (empty($action)) {
1638
+ $action = $this->build_action_url('s');
1639
+ }
1640
+
1641
+ if (isset($attrs['before'])) {
1642
+ $buffer .= $attrs['before'];
1643
+ } else {
1644
+ if (isset($attrs['class'])) {
1645
+ $buffer .= '<div class="tnp tnp-subscription ' . $attrs['class'] . '">' . "\n";
1646
+ } else {
1647
+ $buffer .= '<div class="tnp tnp-subscription">' . "\n";
1648
+ }
1649
+ }
1650
+
1651
+ $buffer .= '<form method="post" action="' . esc_attr($action) . '">' . "\n\n";
1652
+
1653
+ $buffer .= $this->get_form_hidden_fields($attrs);
1654
+
1655
+ if ($options_profile['name_status'] == 2) {
1656
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'first_name']);
1657
+ }
1658
+
1659
+ if ($options_profile['surname_status'] == 2) {
1660
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'last_name']);
1661
+ }
1662
+
1663
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'email']);
1664
+
1665
+ if (isset($options_profile['sex_status']) && $options_profile['sex_status'] == 2) {
1666
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'gender']);
1667
+ }
1668
+
1669
+ // Extra profile fields
1670
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1671
+ // Not for subscription form
1672
+ if ($options_profile['profile_' . $i . '_status'] != 2) {
1673
+ continue;
1674
+ }
1675
+
1676
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'profile', 'number' => $i]);
1677
+ }
1678
+
1679
+ $tmp = '';
1680
+ $lists = $this->get_lists_for_subscription($language);
1681
+ if (!empty($attrs['lists_field_layout']) && $attrs['lists_field_layout'] == 'dropdown') {
1682
+ if (empty($attrs['lists_field_empty_label'])) {
1683
+ $attrs['lists_field_empty_label'] = '';
1684
+ }
1685
+ if (empty($attrs['lists_field_label'])) {
1686
+ $attrs['lists_field_label'] = '';
1687
+ }
1688
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'lists', 'layout' => 'dropdown', 'first_option_label' => $attrs['lists_field_empty_label'], 'label' => $attrs['lists_field_label']]);
1689
+ } else {
1690
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'lists']);
1691
+ }
1692
+
1693
+
1694
+
1695
+ // Deprecated
1696
+ $extra = apply_filters('newsletter_subscription_extra', array());
1697
+ foreach ($extra as $x) {
1698
+ $label = $x['label'];
1699
+ if (empty($label)) {
1700
+ $label = '&nbsp;';
1701
+ }
1702
+ $name = '';
1703
+ if (!empty($x['name'])) {
1704
+ $name = $x['name'];
1705
+ }
1706
+ $buffer .= '<div class="tnp-field tnp-field-' . $name . '"><label>' . $label . "</label>";
1707
+ $buffer .= $x['field'] . "</div>\n";
1708
+ }
1709
+
1710
+ $buffer .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1711
+
1712
+ $buffer .= '<div class="tnp-field tnp-field-button">';
1713
+
1714
+ $button_style = '';
1715
+ if (!empty($attrs['button_color'])) {
1716
+ $button_style = 'style="background-color:' . esc_attr($attrs['button_color']) . '"';
1717
+ }
1718
+
1719
+ if (strpos($options_profile['subscribe'], 'http') === 0) {
1720
+ $buffer .= '<input class="tnp-submit-image" type="image" src="' . esc_attr($options_profile['subscribe']) . '">' . "\n";
1721
+ } else {
1722
+ $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($options_profile['subscribe']) . '" ' . $button_style . '>' . "\n";
1723
+ }
1724
+
1725
+ $buffer .= "</div>\n</form>\n";
1726
+
1727
+ if (isset($attrs['after'])) {
1728
+ $buffer .= $attrs['after'];
1729
+ } else {
1730
+ $buffer .= "</div>\n";
1731
+ }
1732
+
1733
+ return $buffer;
1734
+ }
1735
+
1736
+ function get_form($number) {
1737
+ $options = get_option('newsletter_forms');
1738
+
1739
+ $form = $options['form_' . $number];
1740
+
1741
+ $form = do_shortcode($form);
1742
+
1743
+ $action = $this->build_action_url('s');
1744
+
1745
+ if (stripos($form, '<form') === false) {
1746
+ $form = '<form method="post" action="' . $action . '">' . $form . '</form>';
1747
+ }
1748
+
1749
+ // For compatibility
1750
+ $form = str_replace('{newsletter_url}', $action, $form);
1751
+
1752
+ $form = $this->replace_lists($form);
1753
+
1754
+ return $form;
1755
+ }
1756
+
1757
+ /** Replaces on passed text the special tag {lists} that can be used to show the preferences as a list of checkbox.
1758
+ * They are called lists but on configuration panel they are named preferences!
1759
+ *
1760
+ * @param string $buffer
1761
+ * @return string
1762
+ */
1763
+ function replace_lists($buffer) {
1764
+ $checkboxes = '';
1765
+ $lists = $this->get_lists_for_subscription($this->get_current_language());
1766
+ foreach ($lists as $list) {
1767
+ $checkboxes .= '<input type="checkbox" name="nl[]" value="' . $list->id . '"/>&nbsp;' . $list->name . '<br />';
1768
+ }
1769
+ $buffer = str_replace('{lists}', $checkboxes, $buffer);
1770
+ $buffer = str_replace('{preferences}', $checkboxes, $buffer);
1771
+ return $buffer;
1772
+ }
1773
+
1774
+ function notify_admin_on_subscription($user) {
1775
+
1776
+ if (empty($this->options['notify'])) {
1777
+ return;
1778
+ }
1779
+
1780
+ $message = $this->generate_admin_notification_message($user);
1781
+ $email = trim($this->options['notify_email']);
1782
+ $subject = $this->generate_admin_notification_subject('New subscription');
1783
+
1784
+ Newsletter::instance()->mail($email, $subject, array('html' => $message));
1785
+ }
1786
+
1787
+ /**
1788
+ * Builds the minimal subscription form, with only the email field and inline
1789
+ * submit button. If enabled the privacy checkbox is added.
1790
+ *
1791
+ * @param type $attrs
1792
+ * @return string
1793
+ */
1794
+ function get_subscription_form_minimal($attrs) {
1795
+
1796
+ $this->setup_form_options();
1797
+
1798
+ if (!is_array($attrs)) {
1799
+ $attrs = [];
1800
+ }
1801
+
1802
+ $attrs = array_merge(array('class' => '', 'referrer' => 'minimal',
1803
+ 'button' => $this->form_options['subscribe'], 'button_color' => '',
1804
+ 'button_radius' => '', 'placeholder' => $this->form_options['email']), $attrs);
1805
+
1806
+ $form = '';
1807
+
1808
+ $form .= '<div class="tnp tnp-subscription-minimal ' . $attrs['class'] . '">';
1809
+ $form .= '<form action="' . esc_attr($this->build_action_url('s')) . '" method="post">';
1810
+
1811
+ $form .= $this->get_form_hidden_fields($attrs);
1812
+
1813
+ $form .= '<input class="tnp-email" type="email" required name="ne" value="" placeholder="' . esc_attr($attrs['placeholder']) . '">';
1814
+
1815
+ if (isset($attrs['button_label'])) {
1816
+ $label = $attrs['button_label'];
1817
+ } else if (isset($attrs['button'])) { // Backward compatibility
1818
+ $label = $attrs['button'];
1819
+ } else {
1820
+ $label = $this->form_options['subscribe'];
1821
+ }
1822
+
1823
+ $form .= '<input class="tnp-submit" type="submit" value="' . esc_attr($attrs['button']) . '"'
1824
+ . ' style="background-color:' . esc_attr($attrs['button_color']) . '">';
1825
+
1826
+ $form .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1827
+
1828
+ $form .= "</form></div>\n";
1829
+
1830
+ return $form;
1831
+ }
1832
+
1833
+ function shortcode_newsletter_form($attrs, $content) {
1834
+
1835
+ if (isset($attrs['type']) && $attrs['type'] === 'minimal') {
1836
+ return $this->get_subscription_form_minimal($attrs);
1837
+ }
1838
+
1839
+ // Custom form using the [newsletter_field] shortcodes
1840
+ if (!empty($content)) {
1841
+ return $this->get_subscription_form_custom($attrs, $content);
1842
+ }
1843
+
1844
+ // Custom form hand coded and saved in the custom forms option
1845
+ if (isset($attrs['form'])) {
1846
+ return $this->get_form((int) $attrs['form']);
1847
+ }
1848
+
1849
+ // Custom hand coded form (as above, new syntax)
1850
+ if (isset($attrs['number'])) {
1851
+ return $this->get_form((int) $attrs['number']);
1852
+ }
1853
+
1854
+ return $this->get_subscription_form(null, null, $attrs);
1855
+ }
1856
+
1857
+ /**
1858
+ *
1859
+ * @global wpdb $wpdb
1860
+ * @param array $attrs
1861
+ * @param string $content
1862
+ * @return string
1863
+ */
1864
+ function shortcode_newsletter($attrs, $content) {
1865
+ global $wpdb;
1866
+
1867
+ $message_key = $this->get_message_key_from_request();
1868
+ if ($message_key == 'confirmation') {
1869
+ $user = $this->get_user_from_request(false, 'preconfirm');
1870
+ } else {
1871
+ $user = $this->get_user_from_request();
1872
+ }
1873
+
1874
+ $message = apply_filters('newsletter_page_text', '', $message_key, $user);
1875
+
1876
+ $options = $this->get_options('', $this->get_current_language($user));
1877
+
1878
+ if (empty($message)) {
1879
+ $message = $options[$message_key . '_text'];
1880
+
1881
+ // TODO: the if can be removed
1882
+ if ($message_key == 'confirmed') {
1883
+ $message .= $options[$message_key . '_tracking'];
1884
+ }
1885
+ }
1886
+
1887
+ // Now check what form must be added
1888
+ if ($message_key == 'subscription') {
1889
+ if (isset($attrs['show_form']) && $attrs['show_form'] === 'false') {
1890
+ //return $this->build_field_admin_notice('The [newsletter] shortcode is configured to not show the subscription form.');
1891
+ return;
1892
+ }
1893
+
1894
+ // Compatibility check
1895
+ if (stripos($message, '<form') !== false) {
1896
+ $message = str_ireplace('<form', '<form method="post" action="' . esc_attr($this->get_subscribe_url()) . '"', $message);
1897
+ } else {
1898
+
1899
+ if (strpos($message, '{subscription_form') === false) {
1900
+ $message .= '{subscription_form}';
1901
+ }
1902
+
1903
+ if (isset($attrs['form'])) {
1904
+ $message = str_replace('{subscription_form}', $this->get_form($attrs['form']), $message);
1905
+ } else {
1906
+ $message = str_replace('{subscription_form}', $this->get_subscription_form('page', null, $attrs), $message);
1907
+ }
1908
+ }
1909
+ }
1910
+
1911
+ $email = $this->get_email_from_request();
1912
+
1913
+ $message = $this->replace($message, $user, $email, 'page');
1914
+
1915
+ $message = do_shortcode($message);
1916
+
1917
+ if (isset($_REQUEST['alert'])) {
1918
+ // slashes are already added by wordpress!
1919
+ $message .= '<script>alert("' . esc_js(strip_tags($_REQUEST['alert'])) . '");</script>';
1920
+ }
1921
+
1922
+ return $message;
1923
+ }
1924
+
1925
+ }
1926
+
1927
+ NewsletterSubscription::instance();
1928
+
1929
+ // Compatibility code
1930
+
1931
+ /**
1932
+ * @deprecated
1933
+ * @param int $number
1934
+ */
1935
+ function newsletter_form($number = null) {
1936
+ if ($number != null) {
1937
+ echo NewsletterSubscription::instance()->get_form($number);
1938
+ } else {
1939
+ echo NewsletterSubscription::instance()->get_subscription_form();
1940
+ }
1941
+ }
tnp-header.php CHANGED
@@ -1,345 +1,345 @@
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
- </ul>
198
- </li>
199
- <?php } ?>
200
-
201
- <?php
202
- $license_data = Newsletter::instance()->get_license_data();
203
- $premium_url = 'https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=plugin&utm_content=' . urlencode($_GET['page']);
204
- ?>
205
-
206
- <?php if (empty($license_data)) { ?>
207
- <?php if (time() < 1638226799) { ?>
208
- <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">
209
- <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
210
- </li>
211
- <?php } else { ?>
212
- <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
213
- <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
214
- </li>
215
- <?php } ?>
216
- <?php } elseif (is_wp_error($license_data)) { ?>
217
- <li class="tnp-professional-extensions-button-red">
218
- <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('Unable to check', 'newsletter') ?></a>
219
- </li>
220
-
221
- <?php } elseif ($license_data->expire == 0) { ?>
222
- <?php if (time() < 1638226799) { ?>
223
- <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">
224
- <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
225
- </li>
226
- <?php } else { ?>
227
- <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
228
- <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
229
- </li>
230
- <?php } ?>
231
-
232
- <?php } elseif ($license_data->expire < time()) { ?>
233
-
234
- <li class="tnp-professional-extensions-button-red">
235
- <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('License expired', 'newsletter') ?></a>
236
- </li>
237
-
238
- <?php } elseif ($license_data->expire >= time()) { ?>
239
-
240
- <?php $p = class_exists('NewsletterExtensions') ? 'newsletter_extensions_index' : 'newsletter_main_extensions'; ?>
241
- <li class="tnp-professional-extensions-button">
242
- <a href="?page=<?php echo $p ?>"><i class="fas fa-check-square"></i> <?php _e('License active', 'newsletter') ?></a>
243
- </li>
244
-
245
- <?php } ?>
246
- </ul>
247
- </div>
248
-
249
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-shortcode'])) { ?>
250
- <?php
251
- // Check of Newsletter dedicated page
252
- if (!empty(Newsletter::instance()->options['page'])) {
253
- if (get_post_status(Newsletter::instance()->options['page']) === 'publish') {
254
- $content = get_post_field('post_content', Newsletter::instance()->options['page']);
255
- // With and without attributes
256
- if (strpos($content, '[newsletter]') === false && strpos($content, '[newsletter ') === false) {
257
- ?>
258
- <div class="tnp-notice">
259
- <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-shortcode' ?>" class="tnp-dismiss">&times;</a>
260
- The Newsletter dedicated page does not contain the [newsletter] shortcode. If you're using a visual composer it could be ok.
261
- <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>.
262
-
263
- </div>
264
- <?php
265
- }
266
- }
267
- }
268
- ?>
269
- <?php } ?>
270
-
271
- <?php if (isset($_GET['debug']) || !isset($dismissed['rate']) && $user_count > 300) { ?>
272
- <div class="tnp-notice">
273
- <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=rate' ?>" class="tnp-dismiss">&times;</a>
274
-
275
- We never asked before and we're curious: <a href="http://wordpress.org/plugins/newsletter/" target="_blank">would you rate this plugin</a>?
276
- (few seconds required - account on WordPress.org required, every blog owner should have one...). <strong>Really appreciated, The Newsletter Team</strong>.
277
-
278
- </div>
279
- <?php } ?>
280
-
281
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-page']) && empty(Newsletter::instance()->options['page'])) { ?>
282
- <div class="tnp-notice">
283
- <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-page' ?>" class="tnp-dismiss">&times;</a>
284
-
285
- You should create a blog page to show the subscription form and the subscription messages. Go to the
286
- <a href="?page=newsletter_main_main">general settings panel</a> to configure it.
287
-
288
- </div>
289
- <?php } ?>
290
-
291
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-subscribe']) && get_option('newsletter_install_time') && get_option('newsletter_install_time') < time() - 86400 * 15) { ?>
292
- <div class="tnp-notice">
293
- <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-subscribe' ?>" class="tnp-dismiss">&times;</a>
294
- Subscribe to our news, promotions and getting started lessons!
295
- Proceeding you agree to the <a href="https://www.thenewsletterplugin.com/privacy" target="_blank">privacy policy</a>.
296
- <br>
297
- <form action="https://www.thenewsletterplugin.com/?na=s" target="_blank" method="post">
298
- <input type="hidden" value="plugin-header" name="nr">
299
- <input type="hidden" value="3" name="nl[]">
300
- <input type="hidden" value="1" name="nl[]">
301
- <input type="hidden" value="double" name="optin">
302
- <input type="email" name="ne" value="<?php echo esc_attr($current_user_email) ?>">
303
- <input type="submit" value="<?php esc_attr_e('Subscribe', 'newsletter') ?>">
304
- </form>
305
- </div>
306
- <?php } ?>
307
-
308
- <?php
309
- if (!defined('NEWSLETTER_CRON_WARNINGS') || NEWSLETTER_CRON_WARNINGS) {
310
- $x = NewsletterSystem::instance()->get_job_status();
311
- if ($x !== NewsletterSystem::JOB_OK) {
312
- echo '<div class="tnpc-warning">The are issues with the delivery engine. Please <a href="?page=newsletter_system_scheduler">check them here</a>.</div>';
313
- }
314
- }
315
- ?>
316
-
317
- <?php
318
- if ($_GET['page'] !== 'newsletter_emails_edit') {
319
-
320
- $last_failed_newsletters = Newsletter::instance()->get_emails_by_status(TNP_Email::STATUS_ERROR);
321
- if ($last_failed_newsletters) {
322
- $c = new NewsletterControls();
323
- foreach ($last_failed_newsletters as $n) {
324
- echo '<div class="tnpc-error">';
325
- printf(__('Newsletter "%s" stopped by fatal error.', 'newsletter'), esc_html($n->subject));
326
- echo '&nbsp;';
327
-
328
- $c->btn_link('?page=newsletter_emails_edit&id=' . $n->id, __('Check', 'newsletter'));
329
- echo '</div>';
330
- }
331
- }
332
- }
333
- ?>
334
-
335
- <div id="tnp-notification">
336
- <?php
337
- if (isset($controls)) {
338
- $controls->show();
339
- $controls->messages = '';
340
- $controls->errors = '';
341
- }
342
- ?>
343
- </div>
344
-
345
-
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
+ </ul>
198
+ </li>
199
+ <?php } ?>
200
+
201
+ <?php
202
+ $license_data = Newsletter::instance()->get_license_data();
203
+ $premium_url = 'https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=plugin&utm_content=' . urlencode($_GET['page']);
204
+ ?>
205
+
206
+ <?php if (empty($license_data)) { ?>
207
+ <?php if (time() < 1638226799) { ?>
208
+ <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">
209
+ <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
210
+ </li>
211
+ <?php } else { ?>
212
+ <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
213
+ <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
214
+ </li>
215
+ <?php } ?>
216
+ <?php } elseif (is_wp_error($license_data)) { ?>
217
+ <li class="tnp-professional-extensions-button-red">
218
+ <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('Unable to check', 'newsletter') ?></a>
219
+ </li>
220
+
221
+ <?php } elseif ($license_data->expire == 0) { ?>
222
+ <?php if (time() < 1638226799) { ?>
223
+ <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">
224
+ <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
225
+ </li>
226
+ <?php } else { ?>
227
+ <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
228
+ <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
229
+ </li>
230
+ <?php } ?>
231
+
232
+ <?php } elseif ($license_data->expire < time()) { ?>
233
+
234
+ <li class="tnp-professional-extensions-button-red">
235
+ <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('License expired', 'newsletter') ?></a>
236
+ </li>
237
+
238
+ <?php } elseif ($license_data->expire >= time()) { ?>
239
+
240
+ <?php $p = class_exists('NewsletterExtensions') ? 'newsletter_extensions_index' : 'newsletter_main_extensions'; ?>
241
+ <li class="tnp-professional-extensions-button">
242
+ <a href="?page=<?php echo $p ?>"><i class="fas fa-check-square"></i> <?php _e('License active', 'newsletter') ?></a>
243
+ </li>
244
+
245
+ <?php } ?>
246
+ </ul>
247
+ </div>
248
+
249
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-shortcode'])) { ?>
250
+ <?php
251
+ // Check of Newsletter dedicated page
252
+ if (!empty(Newsletter::instance()->options['page'])) {
253
+ if (get_post_status(Newsletter::instance()->options['page']) === 'publish') {
254
+ $content = get_post_field('post_content', Newsletter::instance()->options['page']);
255
+ // With and without attributes
256
+ if (strpos($content, '[newsletter]') === false && strpos($content, '[newsletter ') === false) {
257
+ ?>
258
+ <div class="tnp-notice">
259
+ <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-shortcode' ?>" class="tnp-dismiss">&times;</a>
260
+ The Newsletter dedicated page does not contain the [newsletter] shortcode. If you're using a visual composer it could be ok.
261
+ <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>.
262
+
263
+ </div>
264
+ <?php
265
+ }
266
+ }
267
+ }
268
+ ?>
269
+ <?php } ?>
270
+
271
+ <?php if (isset($_GET['debug']) || !isset($dismissed['rate']) && $user_count > 300) { ?>
272
+ <div class="tnp-notice">
273
+ <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=rate' ?>" class="tnp-dismiss">&times;</a>
274
+
275
+ We never asked before and we're curious: <a href="http://wordpress.org/plugins/newsletter/" target="_blank">would you rate this plugin</a>?
276
+ (few seconds required - account on WordPress.org required, every blog owner should have one...). <strong>Really appreciated, The Newsletter Team</strong>.
277
+
278
+ </div>
279
+ <?php } ?>
280
+
281
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-page']) && empty(Newsletter::instance()->options['page'])) { ?>
282
+ <div class="tnp-notice">
283
+ <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-page' ?>" class="tnp-dismiss">&times;</a>
284
+
285
+ You should create a blog page to show the subscription form and the subscription messages. Go to the
286
+ <a href="?page=newsletter_main_main">general settings panel</a> to configure it.
287
+
288
+ </div>
289
+ <?php } ?>
290
+
291
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-subscribe']) && get_option('newsletter_install_time') && get_option('newsletter_install_time') < time() - 86400 * 15) { ?>
292
+ <div class="tnp-notice">
293
+ <a href="<?php echo esc_attr($_SERVER['REQUEST_URI']) . '&noheader=1&dismiss=newsletter-subscribe' ?>" class="tnp-dismiss">&times;</a>
294
+ Subscribe to our news, promotions and getting started lessons!
295
+ Proceeding you agree to the <a href="https://www.thenewsletterplugin.com/privacy" target="_blank">privacy policy</a>.
296
+ <br>
297
+ <form action="https://www.thenewsletterplugin.com/?na=s" target="_blank" method="post">
298
+ <input type="hidden" value="plugin-header" name="nr">
299
+ <input type="hidden" value="3" name="nl[]">
300
+ <input type="hidden" value="1" name="nl[]">
301
+ <input type="hidden" value="double" name="optin">
302
+ <input type="email" name="ne" value="<?php echo esc_attr($current_user_email) ?>">
303
+ <input type="submit" value="<?php esc_attr_e('Subscribe', 'newsletter') ?>">
304
+ </form>
305
+ </div>
306
+ <?php } ?>
307
+
308
+ <?php
309
+ if (!defined('NEWSLETTER_CRON_WARNINGS') || NEWSLETTER_CRON_WARNINGS) {
310
+ $x = NewsletterSystem::instance()->get_job_status();
311
+ if ($x !== NewsletterSystem::JOB_OK) {
312
+ echo '<div class="tnpc-warning">The are issues with the delivery engine. Please <a href="?page=newsletter_system_scheduler">check them here</a>.</div>';
313
+ }
314
+ }
315
+ ?>
316
+
317
+ <?php
318
+ if ($_GET['page'] !== 'newsletter_emails_edit') {
319
+
320
+ $last_failed_newsletters = Newsletter::instance()->get_emails_by_status(TNP_Email::STATUS_ERROR);
321
+ if ($last_failed_newsletters) {
322
+ $c = new NewsletterControls();
323
+ foreach ($last_failed_newsletters as $n) {
324
+ echo '<div class="tnpc-error">';
325
+ printf(__('Newsletter "%s" stopped by fatal error.', 'newsletter'), esc_html($n->subject));
326
+ echo '&nbsp;';
327
+
328
+ $c->btn_link('?page=newsletter_emails_edit&id=' . $n->id, __('Check', 'newsletter'));
329
+ echo '</div>';
330
+ }
331
+ }
332
+ }
333
+ ?>
334
+
335
+ <div id="tnp-notification">
336
+ <?php
337
+ if (isset($controls)) {
338
+ $controls->show();
339
+ $controls->messages = '';
340
+ $controls->errors = '';
341
+ }
342
+ ?>
343
+ </div>
344
+
345
+