Newsletter - Version 7.4.4

Version Description

  • Changed the dedicated page retrieval to intercept misconfigurations
  • Added option to accept repeated subscription in single opt-in (you should check if it is compatible with your privacy regulation)
  • Posts field selector keep now track of the previous post selected even if no more in list
  • Improved the error management when delivery fails using the WP native mailing function
  • Added title filter on posts block for compatibility with WPGlobus
Download this release

Release Info

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

Code changes from version 7.4.3 to 7.4.4

emails/emails.php CHANGED
@@ -1,1356 +1,1356 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- class NewsletterEmails extends NewsletterModule {
6
-
7
- static $instance;
8
-
9
- const EDITOR_COMPOSER = 2;
10
- const EDITOR_HTML = 1;
11
- const EDITOR_TINYMCE = 0;
12
-
13
- static $PRESETS_LIST;
14
-
15
- const PRESET_EMAIL_TYPE = 'composer_template';
16
-
17
- // Cache
18
- var $blocks = null;
19
-
20
- /**
21
- * @return NewsletterEmails
22
- */
23
- static function instance() {
24
- if (self::$instance == null) {
25
- self::$instance = new NewsletterEmails();
26
- }
27
-
28
- return self::$instance;
29
- }
30
-
31
- function __construct() {
32
- self::$PRESETS_LIST = array("cta", "invite", "announcement", "posts", "sales", "product", "tour", "simple");
33
- $this->themes = new NewsletterThemes('emails');
34
- parent::__construct('emails', '1.1.5');
35
- add_action('newsletter_action', array($this, 'hook_newsletter_action'), 13, 3);
36
- add_action('newsletter_init', [$this, 'hook_newsletter_init']);
37
-
38
- if (is_admin()) {
39
- // Thank you to plugins which add the WP editor on other admin plugin pages...
40
- if (isset($_GET['page']) && $_GET['page'] == 'newsletter_emails_edit') {
41
- global $wp_actions;
42
- $wp_actions['wp_enqueue_editor'] = 1;
43
- }
44
- }
45
- }
46
-
47
- function hook_newsletter_init() {
48
- if (is_admin()) {
49
- if (defined('DOING_AJAX') && DOING_AJAX) {
50
- if (Newsletter::instance()->is_allowed()) {
51
- add_action('wp_ajax_tnpc_render', array($this, 'tnpc_render_callback'));
52
- add_action('wp_ajax_tnpc_preview', array($this, 'tnpc_preview_callback'));
53
- add_action('wp_ajax_tnpc_css', array($this, 'tnpc_css_callback'));
54
- add_action('wp_ajax_tnpc_options', array($this, 'hook_wp_ajax_tnpc_options'));
55
- add_action('wp_ajax_tnpc_get_all_presets', array($this, 'ajax_get_all_presets'));
56
- add_action('wp_ajax_tnpc_get_preset', array($this, 'ajax_get_preset'));
57
- add_action('wp_ajax_tnpc_delete_preset', array($this, 'hook_wp_ajax_tnpc_delete_preset'));
58
- add_action('wp_ajax_tnpc_regenerate_email', array($this, 'hook_wp_ajax_tnpc_regenerate_email'));
59
- }
60
- }
61
- }
62
- }
63
-
64
- function options_decode($options) {
65
- // Old "query string" format
66
- if (is_string($options) && strpos($options, 'options[') !== false) {
67
- $opts = [];
68
- parse_str($options, $opts);
69
- $options = $opts['options'];
70
- }
71
-
72
- if (is_array($options)) {
73
- return $options;
74
- }
75
-
76
- // Json data should be base64 encoded, but for short time it wasn't
77
- $tmp = json_decode($options, true);
78
- if (is_null($tmp)) {
79
- return json_decode(base64_decode($options), true);
80
- } else {
81
- return $tmp;
82
- }
83
- }
84
-
85
- /**
86
- *
87
- * @param array $options Options array
88
- */
89
- function options_encode($options) {
90
- return base64_encode(json_encode($options, JSON_HEX_TAG | JSON_HEX_AMP));
91
- }
92
-
93
- /**
94
- * Builds and returns the HTML with the form fields of a specific block.
95
- *
96
- * @global wpdb $wpdb
97
- */
98
- function hook_wp_ajax_tnpc_options() {
99
- global $wpdb;
100
-
101
- $block = $this->get_block($_REQUEST['id']);
102
- if (!$block) {
103
- die('Block not found with id ' . esc_html($_REQUEST['id']));
104
- }
105
-
106
- if (!class_exists('NewsletterControls')) {
107
- include NEWSLETTER_INCLUDES_DIR . '/controls.php';
108
- }
109
-
110
- $options = $this->options_decode(stripslashes_deep($_REQUEST['options']));
111
- $composer = isset($_POST['composer']) ? $_POST['composer'] : [];
112
-
113
- $context = array('type' => '');
114
- if (isset($_REQUEST['context_type'])) {
115
- $context['type'] = $_REQUEST['context_type'];
116
- }
117
-
118
- $controls = new NewsletterControls($options);
119
- $fields = new NewsletterFields($controls);
120
-
121
- $controls->init();
122
- echo '<input type="hidden" name="action" value="tnpc_render">';
123
- echo '<input type="hidden" name="id" value="' . esc_attr($_REQUEST['id']) . '">';
124
- echo '<input type="hidden" name="context_type" value="' . esc_attr($context['type']) . '">';
125
- $inline_edits = '';
126
- if (isset($controls->data['inline_edits'])) {
127
- $inline_edits = $controls->data['inline_edits'];
128
- }
129
- echo '<input type="hidden" name="options[inline_edits]" value="', esc_attr($this->options_encode($inline_edits)), '">';
130
- echo "<h2>", esc_html($block["name"]), "</h2>";
131
- include $block['dir'] . '/options.php';
132
- wp_die();
133
- }
134
-
135
- /**
136
- * Retrieves the presets list (no id in GET) or a specific preset id in GET)
137
- */
138
- public function ajax_get_all_presets() {
139
- wp_send_json_success($this->get_all_preset());
140
- }
141
-
142
- public function ajax_get_preset() {
143
-
144
- if (empty($_REQUEST['id'])) {
145
- wp_send_json_error([
146
- 'msg' => __('Invalid preset ID')
147
- ]);
148
- }
149
-
150
- $preset_id = $_REQUEST['id'];
151
- $preset_content = $this->get_preset_content($preset_id);
152
- $global_options = $this->get_preset_global_options($preset_id);
153
-
154
- wp_send_json_success([
155
- 'content' => $preset_content,
156
- 'globalOptions' => $global_options,
157
- ]);
158
- }
159
-
160
- private function get_preset_content($preset_id) {
161
-
162
- $content = '';
163
-
164
- if ($this->is_a_tnp_default_preset($preset_id)) {
165
-
166
- // Get preset from file
167
- $preset = $this->get_preset_from_file($preset_id);
168
-
169
- foreach ($preset->blocks as $item) {
170
- ob_start();
171
- $this->render_block($item->block, true, (array) $item->options);
172
- $content .= trim(ob_get_clean());
173
- }
174
- } else {
175
-
176
- // Get preset from db
177
- $preset_email = $this->get_email(intval($preset_id));
178
- $global_options = $this->extract_global_options_from($preset_email);
179
- $content = $this->regenerate_email_blocks($preset_email->message, $global_options);
180
- }
181
-
182
- return $content;
183
- }
184
-
185
- private function get_preset_global_options($preset_id) {
186
-
187
- if ($this->is_a_tnp_default_preset($preset_id)) {
188
- return [];
189
- }
190
-
191
- // Get preset from db
192
- $preset_email = $this->get_email(intval($preset_id));
193
- $global_options = $this->extract_global_options_from($preset_email);
194
-
195
- return $global_options;
196
- }
197
-
198
- private function extract_global_options_from($email) {
199
- $global_options = [];
200
- foreach ($email->options as $global_option_name => $global_option) {
201
- if (strpos($global_option_name, 'composer_') === 0) {
202
- $global_options[str_replace('composer_', '', $global_option_name)] = $global_option;
203
- }
204
- }
205
-
206
- return $global_options;
207
- }
208
-
209
- private function is_a_tnp_default_preset($preset_id) {
210
- return in_array($preset_id, self::$PRESETS_LIST);
211
- }
212
-
213
- private function get_all_preset() {
214
-
215
- $content = "<div class='tnpc-preset-container'>";
216
-
217
- if ($this->is_normal_context_request()) {
218
- $content .= "<div class='tnpc-preset-legacy-themes'><a href='" . $this->get_admin_page_url('theme') . "'>" . __('Looking for legacy themes?', 'newsletter') . "</a></div>";
219
- }
220
-
221
- // LOAD USER PRESETS
222
- $user_preset_list = $this->get_emails(self::PRESET_EMAIL_TYPE);
223
-
224
- foreach ($user_preset_list as $user_preset) {
225
-
226
- $default_icon_url = NEWSLETTER_URL . "/emails/presets/default-icon.png?ver=2";
227
- $preset_name = $user_preset->subject;
228
- $delete_preset_text = __('Delete', 'newsletter');
229
- $edit_preset_text = __('Edit', 'newsletter');
230
-
231
- // esc_js() assumes the string will be in single quote (arghhh!!!)
232
- $onclick_edit = 'tnpc_edit_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
233
- $onclick_delete = 'tnpc_delete_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
234
- $onclick_load = 'tnpc_load_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
235
-
236
- $content .= "<div class='tnpc-preset' onclick='" . esc_attr($onclick_load) . "'>\n";
237
- $content .= "<img src='$default_icon_url' title='" . esc_attr($preset_name) . "' alt='" . esc_attr($preset_name) . "'>\n";
238
- $content .= "<span class='tnpc-preset-label'>" . esc_html($user_preset->subject) . "</span>\n";
239
- $content .= "<span class='tnpc-delete-preset' onclick='" . esc_attr($onclick_delete) . "' title='" . esc_attr($delete_preset_text) . "'><i class='fas fa-times'></i></span>\n";
240
- $content .= "<span class='tnpc-edit-preset' onclick='" . esc_attr($onclick_edit) . "' title='" . esc_attr($edit_preset_text) . "'><i class='fas fa-pencil-alt'></i></span>\n";
241
- $content .= "</div>";
242
- }
243
-
244
- // LOAD TNP PRESETS
245
- foreach (self::$PRESETS_LIST as $id) {
246
- $preset = $this->get_preset_from_file($id);
247
- $preset_name = esc_html($preset->name);
248
- $content .= "<div class='tnpc-preset' onclick='tnpc_load_preset(\"$id\")'>";
249
- $content .= "<img src='$preset->icon' title='$preset_name' alt='$preset_name'/>";
250
- $content .= "<span class='tnpc-preset-label'>$preset_name</span>";
251
- $content .= "</div>";
252
- }
253
-
254
- if ($this->is_normal_context_request()) {
255
- $content .= $this->get_automated_spot_element();
256
- $content .= $this->get_autoresponder_spot_element();
257
- $content .= $this->get_raw_html_preset_element();
258
- }
259
-
260
- return $content;
261
- }
262
-
263
- private function is_normal_context_request() {
264
- return empty($_REQUEST['context_type']);
265
- }
266
-
267
- private function is_automated_context_request() {
268
- return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'automated';
269
- }
270
-
271
- private function is_autoresponder_context_request() {
272
- return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'autoresponder';
273
- }
274
-
275
- private function get_automated_spot_element() {
276
- $result = "<div class='tnpc-preset'>";
277
- if (class_exists('NewsletterAutomated')) {
278
- $result .= "<a href='?page=newsletter_automated_index'>";
279
- } else {
280
- $result .= "<a href='https://www.thenewsletterplugin.com/automated?utm_source=composer&utm_campaign=plugin&utm_medium=automated'>";
281
- }
282
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/automated.png' title='Automated addon' alt='Automated'/>";
283
- $result .= "<span class='tnpc-preset-label'>Daily, weekly and monthly newsletters</span></a>";
284
- $result .= "</div>";
285
-
286
- return $result;
287
- }
288
-
289
- private function get_autoresponder_spot_element() {
290
- $result = "<div class='tnpc-preset'>";
291
- if (class_exists('NewsletterAutoresponder')) {
292
- $result .= "<a href='?page=newsletter_autoresponder_index'>";
293
- } else {
294
- $result .= "<a href='https://www.thenewsletterplugin.com/autoresponder?utm_source=composer&utm_campaign=plugin&utm_medium=autoresponder' target='_blank'>";
295
- }
296
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/autoresponder.png' title='Autoresponder addon' alt='Autoresponder'/>";
297
- $result .= "<span class='tnpc-preset-label'>Autoresponders</span></a>";
298
- $result .= "</div>";
299
-
300
- return $result;
301
- }
302
-
303
- private function get_raw_html_preset_element() {
304
-
305
- $result = "<div class='tnpc-preset tnpc-preset-html' onclick='location.href=\"" . wp_nonce_url('admin.php?page=newsletter_emails_new&id=rawhtml', 'newsletter-new') . "\"'>";
306
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/rawhtml.png' title='RAW HTML' alt='RAW'/>";
307
- $result .= "<span class='tnpc-preset-label'>Raw HTML</span>";
308
- $result .= "</div>";
309
-
310
- $result .= "<div class='clear'></div>";
311
- $result .= "</div>";
312
-
313
- return $result;
314
- }
315
-
316
- function has_dynamic_blocks($theme) {
317
- preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
318
- foreach ($matches[1] as $match) {
319
- $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
320
- $options = $this->options_decode($a);
321
-
322
- $block = $this->get_block($options['block_id']);
323
- if (!$block) {
324
- continue;
325
- }
326
- if ($block['type'] == 'dynamic') {
327
- return true;
328
- }
329
- }
330
- return false;
331
- }
332
-
333
- /**
334
- * Regenerates a saved composed email rendering each block. Regeneration is
335
- * conditioned (possibly) by the context. The context is usually passed to blocks
336
- * so they can act in the right manner.
337
- *
338
- * $context contains a type and, for automated, the last_run.
339
- *
340
- * $email can actually be even a string containing the full newsletter HTML code.
341
- *
342
- * @param TNP_Email $email
343
- * @return string
344
- */
345
- function regenerate($email, $context = []) {
346
-
347
- $context = array_merge(['last_run' => 0, 'type' => ''], $context);
348
-
349
- preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
350
-
351
- $result = '';
352
- $subject = '';
353
-
354
- foreach ($matches[1] as $match) {
355
- $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
356
- $options = $this->options_decode($a);
357
-
358
- $block = $this->get_block($options['block_id']);
359
- if (!$block) {
360
- $this->logger->debug('Unable to load the block ' . $options['block_id']);
361
- //continue;
362
- }
363
-
364
- ob_start();
365
- $out = $this->render_block($options['block_id'], true, $options, $context);
366
- if (is_array($out)) {
367
- if ($out['return_empty_message'] || $out['stop']) {
368
- return false;
369
- }
370
- if ($out['skip']) {
371
- continue;
372
- }
373
- if (empty($subject) && !empty($out['subject'])) {
374
- $subject = $out['subject'];
375
- }
376
- }
377
- $block_html = ob_get_clean();
378
- $result .= $block_html;
379
- }
380
-
381
- $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
382
- $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
383
- $email->subject = $subject;
384
- return true;
385
- }
386
-
387
- function remove_block_data($text) {
388
- // TODO: Lavorare!
389
- return $text;
390
- }
391
-
392
- static function get_outlook_wrapper_open($width = 600) {
393
- return '<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '" style="vertical-align:top;width:' . $width . 'px;"><![endif]-->';
394
- }
395
-
396
- static function get_outlook_wrapper_close() {
397
- return "<!--[if mso | IE]></td></tr></table><![endif]-->";
398
- }
399
-
400
- function hook_safe_style_css($rules) {
401
- $rules[] = 'display';
402
- return $rules;
403
- }
404
-
405
- /**
406
- * Renders a block identified by its id, using the block options and adding a wrapper
407
- * if required (for the first block rendering).
408
- *
409
- * @param string $block_id
410
- * @param boolean $wrapper
411
- * @param array $options
412
- * @param array $context
413
- * @param array $composer
414
- */
415
- function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
416
- static $kses_style_filter = false;
417
- include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
418
-
419
- //Remove 'options_composer_' prefix
420
- $composer_defaults = [];
421
- foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
422
- $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
423
- }
424
- $composer = array_merge($composer_defaults, $composer);
425
-
426
- $width = 600;
427
- $font_family = 'Helvetica, Arial, sans-serif';
428
-
429
- $global_title_font_family = $composer['title_font_family'];
430
- $global_title_font_size = $composer['title_font_size'];
431
- $global_title_font_color = $composer['title_font_color'];
432
- $global_title_font_weight = $composer['title_font_weight'];
433
-
434
- $global_text_font_family = $composer['text_font_family'];
435
- $global_text_font_size = $composer['text_font_size'];
436
- $global_text_font_color = $composer['text_font_color'];
437
- $global_text_font_weight = $composer['text_font_weight'];
438
-
439
- $global_button_font_family = $composer['button_font_family'];
440
- $global_button_font_size = $composer['button_font_size'];
441
- $global_button_font_color = $composer['button_font_color'];
442
- $global_button_font_weight = $composer['button_font_weight'];
443
- $global_button_background_color = $composer['button_background_color'];
444
-
445
- $global_block_background = $composer['block_background'];
446
-
447
- $info = Newsletter::instance()->get_options('info');
448
-
449
- // Just in case...
450
- if (!is_array($options)) {
451
- $options = array();
452
- }
453
-
454
- // This code filters the HTML to remove javascript and unsecure attributes and enable the
455
- // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
456
- add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
457
- $options = wp_kses_post_deep($options);
458
- remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
459
-
460
- $block_options = get_option('newsletter_main');
461
-
462
- $block = $this->get_block($block_id);
463
-
464
- if (!isset($context['type']))
465
- $context['type'] = '';
466
-
467
- // Block not found
468
- if (!$block) {
469
- if ($wrapper) {
470
- echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">';
471
- echo '<tr>';
472
- echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
473
- }
474
- echo $this->get_outlook_wrapper_open($width);
475
-
476
- echo '<p>Ops, this block type is not avalable.</p>';
477
-
478
- echo $this->get_outlook_wrapper_close();
479
-
480
- if ($wrapper) {
481
- echo '</td></tr></table>';
482
- }
483
- return;
484
- }
485
-
486
- $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
487
-
488
- $dir = is_rtl() ? 'rtl' : 'ltr';
489
- $align_left = is_rtl() ? 'right' : 'left';
490
- $align_right = is_rtl() ? 'left' : 'right';
491
-
492
- ob_start();
493
- $logger = $this->logger;
494
- include $block['dir'] . '/block.php';
495
- $content = trim(ob_get_clean());
496
-
497
- if (empty($content)) {
498
- return $out;
499
- }
500
-
501
- $common_defaults = array(
502
- 'block_padding_top' => 0,
503
- 'block_padding_bottom' => 0,
504
- 'block_padding_right' => 0,
505
- 'block_padding_left' => 0,
506
- 'block_background' => '',
507
- 'block_background_2' => '',
508
- 'block_width' => 600,
509
- 'block_align' => 'center'
510
- );
511
-
512
- $options = array_merge($common_defaults, $options);
513
-
514
- // Obsolete
515
- $content = str_replace('{width}', $width, $content);
516
-
517
- $content = $this->inline_css($content, true);
518
-
519
- // CSS driven by the block
520
- // Requited for the server side parsing and rendering
521
- $options['block_id'] = $block_id;
522
-
523
- $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
524
- $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
525
- $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
526
- $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
527
-
528
- $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
529
-
530
- // Internal TD wrapper
531
- $style = 'text-align: center; ';
532
- $style .= 'width: 100% !important; ';
533
- $style .= 'line-height: normal !important; ';
534
- $style .= 'letter-spacing: normal; ';
535
- $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
536
- $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
537
- $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
538
- $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
539
- $style .= 'background-color: ' . $block_background . ';';
540
-
541
- if (isset($options['block_background_gradient'])) {
542
- $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
543
- }
544
-
545
- $data = $this->options_encode($options);
546
- // First time block creation wrapper
547
- if ($wrapper) {
548
- echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">', "\n";
549
- echo "<tr>";
550
- echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
551
- }
552
-
553
- // Container that fixes the width and makes the block responsive
554
- echo $this->get_outlook_wrapper_open($options['block_width']);
555
-
556
- echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $options['block_width'], 'px!important">', "\n";
557
- echo "<tr>";
558
- echo '<td align="', esc_attr($options['block_align']), '" style="', $style, '" bgcolor="', $block_background, '" width="100%">';
559
-
560
- //echo "<!-- block generated content -->\n";
561
- echo trim($content);
562
- //echo "\n<!-- /block generated content -->\n";
563
-
564
- echo "</td></tr></table>";
565
- echo $this->get_outlook_wrapper_close();
566
-
567
- // First time block creation wrapper
568
- if ($wrapper) {
569
- echo "</td></tr></table>";
570
- }
571
-
572
- return $out;
573
- }
574
-
575
- /**
576
- * Ajax call to render a block with a new set of options after the settings popup
577
- * has been saved.
578
- *
579
- * @param type $block_id
580
- * @param type $wrapper
581
- */
582
- function tnpc_render_callback() {
583
- if (!check_ajax_referer('save')) {
584
- $this->dienow('Expired request');
585
- }
586
-
587
- $block_id = $_POST['id'];
588
- $wrapper = isset($_POST['full']);
589
- $options = $this->restore_options_from_request();
590
-
591
- $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
592
- wp_die();
593
- }
594
-
595
- function hook_wp_ajax_tnpc_regenerate_email() {
596
-
597
- $content = stripslashes($_POST['content']);
598
- $global_options = $_POST['composer'];
599
-
600
- $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
601
-
602
- wp_send_json_success([
603
- 'content' => $regenerated_content,
604
- 'message' => __('Successfully updated', 'newsletter')
605
- ]);
606
- }
607
-
608
- private function regenerate_email_blocks($content, $global_options) {
609
-
610
- $raw_block_options = $this->extract_encoded_blocks_options($content);
611
-
612
- $regenerated_content = '';
613
-
614
- foreach ($raw_block_options as $raw_block_option) {
615
-
616
- /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
617
- $block_options = $this->options_decode( $a ); */
618
-
619
- $block_options = $this->options_decode($raw_block_option);
620
-
621
- $block = $this->get_block($block_options['block_id']);
622
- if (!$block) {
623
- $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
624
- }
625
-
626
- ob_start();
627
- $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
628
- $block_html = ob_get_clean();
629
-
630
- $regenerated_content .= $block_html;
631
- }
632
-
633
- return $regenerated_content;
634
- }
635
-
636
- /**
637
- * @param string $html_email_content Email html content
638
- *
639
- * @return string[] Encoded options of email blocks
640
- */
641
- private function extract_encoded_blocks_options($html_email_content) {
642
-
643
- preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
644
-
645
- return $raw_block_options[1];
646
- }
647
-
648
- function tnpc_preview_callback() {
649
- $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
650
-
651
- if (empty($email)) {
652
- echo 'Wrong email identifier';
653
- return;
654
- }
655
-
656
- echo $email['message'];
657
-
658
- wp_die();
659
- }
660
-
661
- function tnpc_css_callback() {
662
- include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
663
- wp_die();
664
- }
665
-
666
- /** Returns the correct admin page to edit the newsletter with the correct editor. */
667
- function get_editor_url($email_id, $editor_type) {
668
- switch ($editor_type) {
669
- case NewsletterEmails::EDITOR_COMPOSER:
670
- return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
671
- case NewsletterEmails::EDITOR_HTML:
672
- return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
673
- case NewsletterEmails::EDITOR_TINYMCE:
674
- return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
675
- }
676
- }
677
-
678
- /**
679
- * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
680
- * or the targeting page (it depends on newsletter status).
681
- *
682
- * @param TNP_Email $email
683
- */
684
- function get_edit_button($email, $only_icon = false) {
685
-
686
- $editor_type = $this->get_editor_type($email);
687
- if ($email->status == 'new') {
688
- $edit_url = $this->get_editor_url($email->id, $editor_type);
689
- } else {
690
- $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
691
- }
692
- switch ($editor_type) {
693
- case NewsletterEmails::EDITOR_COMPOSER:
694
- $icon_class = 'th-large';
695
- break;
696
- case NewsletterEmails::EDITOR_HTML:
697
- $icon_class = 'code';
698
- break;
699
- default:
700
- $icon_class = 'edit';
701
- break;
702
- }
703
- if ($only_icon) {
704
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
705
- '<i class="fas fa-' . $icon_class . '"></i></a>';
706
- } else {
707
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
708
- '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
709
- }
710
- }
711
-
712
- /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
713
- function get_editor_type($email) {
714
- $email = (object) $email;
715
- $editor_type = $email->editor;
716
-
717
- // Backward compatibility
718
- $email_options = maybe_unserialize($email->options);
719
- if (isset($email_options['composer'])) {
720
- $editor_type = NewsletterEmails::EDITOR_COMPOSER;
721
- }
722
- // End backward compatibility
723
-
724
- return $editor_type;
725
- }
726
-
727
- /**
728
- *
729
- * @param type $action
730
- * @param type $user
731
- * @param type $email
732
- * @return type
733
- * @global wpdb $wpdb
734
- */
735
- function hook_newsletter_action($action, $user, $email) {
736
- global $wpdb;
737
-
738
- switch ($action) {
739
- case 'v':
740
- case 'view':
741
- $id = $_GET['id'];
742
- if ($id == 'last') {
743
- $email = $wpdb->get_row("select * from " . NEWSLETTER_EMAILS_TABLE . " where private=0 and type='message' and status='sent' order by send_on desc limit 1");
744
- } else {
745
- $email = $this->get_email($_GET['id']);
746
- }
747
- if (empty($email)) {
748
- header("HTTP/1.0 404 Not Found");
749
- die('Email not found');
750
- }
751
-
752
- if (!Newsletter::instance()->is_allowed()) {
753
-
754
- if ($email->status == 'new') {
755
- header("HTTP/1.0 404 Not Found");
756
- die('Not sent yet');
757
- }
758
-
759
- if ($email->private == 1) {
760
- if (!$user) {
761
- header("HTTP/1.0 404 Not Found");
762
- die('No available for online view');
763
- }
764
- $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
765
- if (!$sent) {
766
- header("HTTP/1.0 404 Not Found");
767
- die('No available for online view');
768
- }
769
- }
770
- }
771
-
772
-
773
- header('Content-Type: text/html;charset=UTF-8');
774
- header('X-Robots-Tag: noindex,nofollow,noarchive');
775
- header('Cache-Control: no-cache,no-store,private');
776
-
777
- echo $this->replace($email->message, $user, $email);
778
-
779
- die();
780
- break;
781
-
782
- case 'emails-css':
783
- $email_id = (int) $_GET['id'];
784
-
785
- $body = Newsletter::instance()->get_email_field($email_id, 'message');
786
-
787
- $x = strpos($body, '<style');
788
- if ($x === false)
789
- return;
790
-
791
- $x = strpos($body, '>', $x);
792
- $y = strpos($body, '</style>');
793
-
794
- header('Content-Type: text/css;charset=UTF-8');
795
-
796
- echo substr($body, $x + 1, $y - $x - 1);
797
-
798
- die();
799
- break;
800
-
801
- case 'emails-composer-css':
802
- header('Cache: no-cache');
803
- header('Content-Type: text/css');
804
- echo $this->get_composer_css();
805
- die();
806
- break;
807
-
808
- case 'emails-preview':
809
- if (!Newsletter::instance()->is_allowed()) {
810
- die('Not enough privileges');
811
- }
812
-
813
- if (!check_admin_referer('view')) {
814
- die();
815
- }
816
-
817
- $theme_id = $_GET['id'];
818
- $theme = $this->themes->get_theme($theme_id);
819
-
820
- // Used by theme code
821
- $theme_options = $this->themes->get_options($theme_id);
822
-
823
- $theme_url = $theme['url'];
824
-
825
- header('Content-Type: text/html;charset=UTF-8');
826
-
827
- include $theme['dir'] . '/theme.php';
828
-
829
- die();
830
- break;
831
-
832
- case 'emails-preview-text':
833
- header('Content-Type: text/plain;charset=UTF-8');
834
- if (!Newsletter::instance()->is_allowed()) {
835
- die('Not enough privileges');
836
- }
837
-
838
- if (!check_admin_referer('view')) {
839
- die();
840
- }
841
-
842
- // Used by theme code
843
- $theme_options = $this->get_current_theme_options();
844
-
845
- $file = include $theme['dir'] . '/theme-text.php';
846
-
847
- if (is_file($file)) {
848
- include $file;
849
- }
850
-
851
- die();
852
- break;
853
-
854
- case 'emails-create':
855
- // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
856
- // excerpt, thumbnail are extracted.
857
- if (!Newsletter::instance()->is_allowed()) {
858
- die('Not enough privileges');
859
- }
860
-
861
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
862
- $controls = new NewsletterControls();
863
-
864
- if (!$controls->is_action('create')) {
865
- die('Wrong call');
866
- }
867
-
868
- $theme_id = $controls->data['id'];
869
- $theme = $this->themes->get_theme($theme_id);
870
-
871
- if (!$theme) {
872
- die('invalid theme');
873
- }
874
-
875
- $this->themes->save_options($theme_id, $controls->data);
876
-
877
- $email = array();
878
- $email['status'] = 'new';
879
- $email['subject'] = ''; //__('Here the email subject', 'newsletter');
880
- $email['track'] = Newsletter::instance()->options['track'];
881
- $email['send_on'] = time();
882
- $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
883
- $email['type'] = 'message';
884
-
885
- $theme_options = $this->themes->get_options($theme_id);
886
-
887
- $theme_url = $theme['url'];
888
- $theme_subject = '';
889
-
890
- ob_start();
891
- include $theme['dir'] . '/theme.php';
892
- $email['message'] = ob_get_clean();
893
-
894
- if (!empty($theme_subject)) {
895
- $email['subject'] = $theme_subject;
896
- }
897
-
898
- if (file_exists($theme['dir'] . '/theme-text.php')) {
899
- ob_start();
900
- include $theme['dir'] . '/theme-text.php';
901
- $email['message_text'] = ob_get_clean();
902
- } else {
903
- $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
904
- }
905
-
906
- $email = $this->save_email($email);
907
-
908
- $edit_url = $this->get_editor_url($email->id, $email->editor);
909
-
910
- header('Location: ' . $edit_url);
911
-
912
- die();
913
- break;
914
- }
915
- }
916
-
917
- function admin_menu() {
918
- $this->add_menu_page('index', 'Newsletters');
919
- $this->add_admin_page('list', 'Email List');
920
- $this->add_admin_page('new', 'Email New');
921
- $this->add_admin_page('edit', 'Email Edit');
922
- $this->add_admin_page('theme', 'Email Themes');
923
- $this->add_admin_page('composer', 'The Composer');
924
- $this->add_admin_page('editorhtml', 'HTML Editor');
925
- $this->add_admin_page('editortinymce', 'TinyMCE Editor');
926
- }
927
-
928
- /**
929
- * Builds a block data structure starting from the folder containing the block
930
- * files.
931
- *
932
- * @param string $dir
933
- * @return array | WP_Error
934
- */
935
- function build_block($dir) {
936
- $dir = realpath($dir);
937
- $dir = wp_normalize_path($dir);
938
- $full_file = $dir . '/block.php';
939
- if (!is_file($full_file)) {
940
- return new WP_Error('1', 'Missing block.php file in ' . $dir);
941
- }
942
-
943
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
944
- $file = basename($dir);
945
-
946
- $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
947
- $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
948
- $data = array_merge($defaults, $data);
949
-
950
- if (is_file($dir . '/icon.png')) {
951
- $data['icon'] = content_url($relative_dir . '/icon.png');
952
- }
953
-
954
- $data['id'] = sanitize_key($file);
955
-
956
- // Absolute path of the block files
957
- $data['dir'] = $dir;
958
- $data['url'] = content_url($relative_dir);
959
-
960
- return $data;
961
- }
962
-
963
- /**
964
- *
965
- * @param type $dir
966
- * @return type
967
- */
968
- function scan_blocks_dir($dir) {
969
- $dir = realpath($dir);
970
- if (!$dir) {
971
- return [];
972
- }
973
- $dir = wp_normalize_path($dir);
974
-
975
- $list = [];
976
- $handle = opendir($dir);
977
- while ($file = readdir($handle)) {
978
-
979
- $data = $this->build_block($dir . '/' . $file);
980
-
981
- if (is_wp_error($data)) {
982
- $this->logger->error($data);
983
- continue;
984
- }
985
- $list[$data['id']] = $data;
986
- }
987
- closedir($handle);
988
- return $list;
989
- }
990
-
991
- /**
992
- * Array of arrays with every registered block and legacy block converted to the new
993
- * format.
994
- *
995
- * @return array
996
- */
997
- function get_blocks() {
998
-
999
- if (!is_null($this->blocks)) {
1000
- return $this->blocks;
1001
- }
1002
-
1003
- $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1004
-
1005
- $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1006
-
1007
- $this->blocks = array_merge($extended, $this->blocks);
1008
-
1009
- $dirs = apply_filters('newsletter_blocks_dir', array());
1010
-
1011
- //$this->logger->debug('Block dirs:');
1012
- //$this->logger->debug($dirs);
1013
-
1014
- foreach ($dirs as $dir) {
1015
- $list = $this->scan_blocks_dir($dir);
1016
- $this->blocks = array_merge($list, $this->blocks);
1017
- }
1018
-
1019
- do_action('newsletter_register_blocks');
1020
-
1021
- foreach (TNP_Composer::$block_dirs as $dir) {
1022
- $block = $this->build_block($dir);
1023
- if (is_wp_error($block)) {
1024
- $this->logger->error($block);
1025
- continue;
1026
- }
1027
- if (!isset($this->blocks[$block['id']])) {
1028
- $this->blocks[$block['id']] = $block;
1029
- } else {
1030
- $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1031
- }
1032
- }
1033
-
1034
- $this->blocks = array_reverse($this->blocks);
1035
- return $this->blocks;
1036
- }
1037
-
1038
- /**
1039
- * Return a single block (associative array) checking for legacy ID as well.
1040
- *
1041
- * @param string $id
1042
- * @return array
1043
- */
1044
- function get_block($id) {
1045
- switch ($id) {
1046
- case 'content-03-text.block':
1047
- $id = 'text';
1048
- break;
1049
- case 'footer-03-social.block':
1050
- $id = 'social';
1051
- break;
1052
- case 'footer-02-canspam.block':
1053
- $id = 'canspam';
1054
- break;
1055
- case 'content-05-image.block':
1056
- $id = 'image';
1057
- break;
1058
- case 'header-01-header.block':
1059
- $id = 'header';
1060
- break;
1061
- case 'footer-01-footer.block':
1062
- $id = 'footer';
1063
- break;
1064
- case 'content-02-heading.block':
1065
- $id = 'heading';
1066
- break;
1067
- case 'content-07-twocols.block':
1068
- case 'content-06-posts.block':
1069
- $id = 'posts';
1070
- break;
1071
- case 'content-04-cta.block':
1072
- $id = 'cta';
1073
- break;
1074
- case 'content-01-hero.block':
1075
- $id = 'hero';
1076
- break;
1077
- // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1078
- // break;
1079
- }
1080
-
1081
- // Conversion for old full path ID
1082
- $id = sanitize_key(basename($id));
1083
-
1084
- // TODO: Correct id for compatibility
1085
- $blocks = $this->get_blocks();
1086
- if (!isset($blocks[$id])) {
1087
- return null;
1088
- }
1089
- return $blocks[$id];
1090
- }
1091
-
1092
- function scan_presets_dir($dir = null) {
1093
-
1094
- if (is_null($dir)) {
1095
- $dir = __DIR__ . '/presets';
1096
- }
1097
-
1098
- if (!is_dir($dir)) {
1099
- return array();
1100
- }
1101
-
1102
- $handle = opendir($dir);
1103
- $list = array();
1104
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1105
- while ($file = readdir($handle)) {
1106
-
1107
- if ($file == '.' || $file == '..')
1108
- continue;
1109
-
1110
- // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1111
- $preset_id = sanitize_key($file);
1112
-
1113
- $full_file = $dir . '/' . $file . '/preset.json';
1114
-
1115
- if (!is_file($full_file)) {
1116
- continue;
1117
- }
1118
-
1119
- $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1120
-
1121
- $list[$preset_id] = $icon;
1122
- }
1123
- closedir($handle);
1124
- return $list;
1125
- }
1126
-
1127
- function get_preset_from_file($id, $dir = null) {
1128
-
1129
- if (is_null($dir)) {
1130
- $dir = __DIR__ . '/presets';
1131
- }
1132
-
1133
- $id = $this->sanitize_file_name($id);
1134
-
1135
- if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1136
- return array();
1137
- }
1138
-
1139
- $json_content = file_get_contents("$dir/$id/preset.json");
1140
- $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1141
- $json = json_decode($json_content);
1142
- $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1143
-
1144
- return $json;
1145
- }
1146
-
1147
- function get_composer_css() {
1148
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1149
- $blocks = $this->get_blocks();
1150
- foreach ($blocks as $block) {
1151
- if (!file_exists($block['dir'] . '/style.css')) {
1152
- continue;
1153
- }
1154
- $css .= "\n\n";
1155
- $css .= "/* " . $block['name'] . " */\n";
1156
- $css .= file_get_contents($block['dir'] . '/style.css');
1157
- }
1158
- return $css;
1159
- }
1160
-
1161
- function get_composer_backend_css() {
1162
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1163
- $css .= "\n\n";
1164
- $css .= $this->get_composer_css();
1165
- return $css;
1166
- }
1167
-
1168
- /**
1169
- * Send an email to the test subscribers.
1170
- *
1171
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1172
- * @param NewsletterControls $controls
1173
- */
1174
- function send_test_email($email, $controls) {
1175
- if (!$email) {
1176
- $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1177
- return;
1178
- }
1179
-
1180
- $original_subject = $email->subject;
1181
- $this->set_test_subject_to($email);
1182
-
1183
- $users = NewsletterUsers::instance()->get_test_users();
1184
- if (count($users) == 0) {
1185
- $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1186
- '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1187
- __('Read more', 'newsletter') . '</strong></a>.';
1188
- } else {
1189
- $r = Newsletter::instance()->send($email, $users, true);
1190
- $emails = array();
1191
- foreach ($users as $user) {
1192
- $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1193
- }
1194
- if (is_wp_error($r)) {
1195
- $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1196
- $controls->errors .= __('Test subscribers:', 'newsletter');
1197
- $controls->errors .= ' ' . implode(', ', $emails);
1198
- $controls->errors .= '<br>';
1199
- $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1200
- $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1201
- } else {
1202
- $controls->messages = __('Test subscribers:', 'newsletter');
1203
-
1204
- $controls->messages .= ' ' . implode(', ', $emails);
1205
- $controls->messages .= '.<br>';
1206
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1207
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1208
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1209
- }
1210
- }
1211
- $email->subject = $original_subject;
1212
- }
1213
-
1214
- /**
1215
- * Send an email to the test subscribers.
1216
- *
1217
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1218
- * @param string $email_address
1219
- *
1220
- * @throws Exception
1221
- */
1222
- function send_test_newsletter_to_email_address($email, $email_address) {
1223
-
1224
- if (!$email) {
1225
- throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1226
- }
1227
-
1228
- $this->set_test_subject_to($email);
1229
-
1230
- $dummy_subscriber = $this->make_dummy_subscriber();
1231
- $dummy_subscriber->email = $email_address;
1232
-
1233
- $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1234
-
1235
- $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1236
-
1237
- if (is_wp_error($result)) {
1238
- $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1239
- $error_message .= __('Test subscribers:', 'newsletter');
1240
- $error_message .= ' ' . $email;
1241
- $error_message .= '<br>';
1242
- $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1243
- $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1244
- throw new Exception($error_message);
1245
- }
1246
-
1247
- $messages = __('Test subscribers:', 'newsletter');
1248
-
1249
- $messages .= ' ' . $email;
1250
- $messages .= '.<br>';
1251
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1252
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1253
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1254
-
1255
- return $messages;
1256
- }
1257
-
1258
- private function set_test_subject_to($email) {
1259
- if ($email->subject == '') {
1260
- $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1261
- } else {
1262
- $email->subject = $email->subject . ' (TEST)';
1263
- }
1264
- }
1265
-
1266
- private function make_dummy_subscriber() {
1267
- $dummy_user = new TNP_User();
1268
- $dummy_user->id = 0;
1269
- $dummy_user->email = 'john.doe@example.org';
1270
- $dummy_user->name = 'John';
1271
- $dummy_user->surname = 'Doe';
1272
- $dummy_user->sex = 'n';
1273
- $dummy_user->language = '';
1274
- $dummy_user->ip = '';
1275
-
1276
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1277
- $profile_key = "profile_$i";
1278
- $dummy_user->$profile_key = '';
1279
- }
1280
-
1281
- return $dummy_user;
1282
- }
1283
-
1284
- function restore_options_from_request() {
1285
-
1286
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1287
- $controls = new NewsletterControls();
1288
- $options = $controls->data;
1289
-
1290
- if (isset($_POST['options']) && is_array($_POST['options'])) {
1291
- // Get all block options
1292
- //$options = stripslashes_deep($_POST['options']);
1293
- // Deserialize inline edits when
1294
- // render is preformed on saving block options
1295
- if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1296
- $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1297
- }
1298
-
1299
- // Restore inline edits from data-json
1300
- // coming from inline editing
1301
- // and merge with current inline edit
1302
- if (isset($_POST['encoded_options'])) {
1303
- $decoded_options = $this->options_decode($_POST['encoded_options']);
1304
-
1305
- $to_merge_inline_edits = [];
1306
-
1307
- if (isset($decoded_options['inline_edits'])) {
1308
- foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1309
- $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1310
- }
1311
- }
1312
-
1313
- //Overwrite with new edited content
1314
- if (isset($options['inline_edits'])) {
1315
- foreach ($options['inline_edits'] as $inline_edit) {
1316
- $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1317
- }
1318
- }
1319
-
1320
- $options['inline_edits'] = array_values($to_merge_inline_edits);
1321
- $options = array_merge($decoded_options, $options);
1322
- }
1323
-
1324
- return $options;
1325
- }
1326
-
1327
- return array();
1328
- }
1329
-
1330
- public function hook_wp_ajax_tnpc_delete_preset() {
1331
-
1332
- if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1333
- wp_send_json_error('Expired request');
1334
- }
1335
-
1336
- $preset_id = (int) $_REQUEST['presetId'];
1337
-
1338
- $newsletter = Newsletter::instance();
1339
-
1340
- if ($preset_id > 0) {
1341
- $preset = $newsletter->get_email($preset_id);
1342
-
1343
- if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1344
- Newsletter::instance()->delete_email($preset_id);
1345
- wp_send_json_success();
1346
- } else {
1347
- wp_send_json_error(__('Is not a preset!', 'newsletter'));
1348
- }
1349
- } else {
1350
- wp_send_json_error();
1351
- }
1352
- }
1353
-
1354
- }
1355
-
1356
- NewsletterEmails::instance();
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterEmails extends NewsletterModule {
6
+
7
+ static $instance;
8
+
9
+ const EDITOR_COMPOSER = 2;
10
+ const EDITOR_HTML = 1;
11
+ const EDITOR_TINYMCE = 0;
12
+
13
+ static $PRESETS_LIST;
14
+
15
+ const PRESET_EMAIL_TYPE = 'composer_template';
16
+
17
+ // Cache
18
+ var $blocks = null;
19
+
20
+ /**
21
+ * @return NewsletterEmails
22
+ */
23
+ static function instance() {
24
+ if (self::$instance == null) {
25
+ self::$instance = new NewsletterEmails();
26
+ }
27
+
28
+ return self::$instance;
29
+ }
30
+
31
+ function __construct() {
32
+ self::$PRESETS_LIST = array("cta", "invite", "announcement", "posts", "sales", "product", "tour", "simple");
33
+ $this->themes = new NewsletterThemes('emails');
34
+ parent::__construct('emails', '1.1.5');
35
+ add_action('newsletter_action', array($this, 'hook_newsletter_action'), 13, 3);
36
+ add_action('newsletter_init', [$this, 'hook_newsletter_init']);
37
+
38
+ if (is_admin()) {
39
+ // Thank you to plugins which add the WP editor on other admin plugin pages...
40
+ if (isset($_GET['page']) && $_GET['page'] == 'newsletter_emails_edit') {
41
+ global $wp_actions;
42
+ $wp_actions['wp_enqueue_editor'] = 1;
43
+ }
44
+ }
45
+ }
46
+
47
+ function hook_newsletter_init() {
48
+ if (is_admin()) {
49
+ if (defined('DOING_AJAX') && DOING_AJAX) {
50
+ if (Newsletter::instance()->is_allowed()) {
51
+ add_action('wp_ajax_tnpc_render', array($this, 'tnpc_render_callback'));
52
+ add_action('wp_ajax_tnpc_preview', array($this, 'tnpc_preview_callback'));
53
+ add_action('wp_ajax_tnpc_css', array($this, 'tnpc_css_callback'));
54
+ add_action('wp_ajax_tnpc_options', array($this, 'hook_wp_ajax_tnpc_options'));
55
+ add_action('wp_ajax_tnpc_get_all_presets', array($this, 'ajax_get_all_presets'));
56
+ add_action('wp_ajax_tnpc_get_preset', array($this, 'ajax_get_preset'));
57
+ add_action('wp_ajax_tnpc_delete_preset', array($this, 'hook_wp_ajax_tnpc_delete_preset'));
58
+ add_action('wp_ajax_tnpc_regenerate_email', array($this, 'hook_wp_ajax_tnpc_regenerate_email'));
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ function options_decode($options) {
65
+ // Old "query string" format
66
+ if (is_string($options) && strpos($options, 'options[') !== false) {
67
+ $opts = [];
68
+ parse_str($options, $opts);
69
+ $options = $opts['options'];
70
+ }
71
+
72
+ if (is_array($options)) {
73
+ return $options;
74
+ }
75
+
76
+ // Json data should be base64 encoded, but for short time it wasn't
77
+ $tmp = json_decode($options, true);
78
+ if (is_null($tmp)) {
79
+ return json_decode(base64_decode($options), true);
80
+ } else {
81
+ return $tmp;
82
+ }
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @param array $options Options array
88
+ */
89
+ function options_encode($options) {
90
+ return base64_encode(json_encode($options, JSON_HEX_TAG | JSON_HEX_AMP));
91
+ }
92
+
93
+ /**
94
+ * Builds and returns the HTML with the form fields of a specific block.
95
+ *
96
+ * @global wpdb $wpdb
97
+ */
98
+ function hook_wp_ajax_tnpc_options() {
99
+ global $wpdb;
100
+
101
+ $block = $this->get_block($_REQUEST['id']);
102
+ if (!$block) {
103
+ die('Block not found with id ' . esc_html($_REQUEST['id']));
104
+ }
105
+
106
+ if (!class_exists('NewsletterControls')) {
107
+ include NEWSLETTER_INCLUDES_DIR . '/controls.php';
108
+ }
109
+
110
+ $options = $this->options_decode(stripslashes_deep($_REQUEST['options']));
111
+ $composer = isset($_POST['composer']) ? $_POST['composer'] : [];
112
+
113
+ $context = array('type' => '');
114
+ if (isset($_REQUEST['context_type'])) {
115
+ $context['type'] = $_REQUEST['context_type'];
116
+ }
117
+
118
+ $controls = new NewsletterControls($options);
119
+ $fields = new NewsletterFields($controls);
120
+
121
+ $controls->init();
122
+ echo '<input type="hidden" name="action" value="tnpc_render">';
123
+ echo '<input type="hidden" name="id" value="' . esc_attr($_REQUEST['id']) . '">';
124
+ echo '<input type="hidden" name="context_type" value="' . esc_attr($context['type']) . '">';
125
+ $inline_edits = '';
126
+ if (isset($controls->data['inline_edits'])) {
127
+ $inline_edits = $controls->data['inline_edits'];
128
+ }
129
+ echo '<input type="hidden" name="options[inline_edits]" value="', esc_attr($this->options_encode($inline_edits)), '">';
130
+ echo "<h2>", esc_html($block["name"]), "</h2>";
131
+ include $block['dir'] . '/options.php';
132
+ wp_die();
133
+ }
134
+
135
+ /**
136
+ * Retrieves the presets list (no id in GET) or a specific preset id in GET)
137
+ */
138
+ public function ajax_get_all_presets() {
139
+ wp_send_json_success($this->get_all_preset());
140
+ }
141
+
142
+ public function ajax_get_preset() {
143
+
144
+ if (empty($_REQUEST['id'])) {
145
+ wp_send_json_error([
146
+ 'msg' => __('Invalid preset ID')
147
+ ]);
148
+ }
149
+
150
+ $preset_id = $_REQUEST['id'];
151
+ $preset_content = $this->get_preset_content($preset_id);
152
+ $global_options = $this->get_preset_global_options($preset_id);
153
+
154
+ wp_send_json_success([
155
+ 'content' => $preset_content,
156
+ 'globalOptions' => $global_options,
157
+ ]);
158
+ }
159
+
160
+ private function get_preset_content($preset_id) {
161
+
162
+ $content = '';
163
+
164
+ if ($this->is_a_tnp_default_preset($preset_id)) {
165
+
166
+ // Get preset from file
167
+ $preset = $this->get_preset_from_file($preset_id);
168
+
169
+ foreach ($preset->blocks as $item) {
170
+ ob_start();
171
+ $this->render_block($item->block, true, (array) $item->options);
172
+ $content .= trim(ob_get_clean());
173
+ }
174
+ } else {
175
+
176
+ // Get preset from db
177
+ $preset_email = $this->get_email(intval($preset_id));
178
+ $global_options = $this->extract_global_options_from($preset_email);
179
+ $content = $this->regenerate_email_blocks($preset_email->message, $global_options);
180
+ }
181
+
182
+ return $content;
183
+ }
184
+
185
+ private function get_preset_global_options($preset_id) {
186
+
187
+ if ($this->is_a_tnp_default_preset($preset_id)) {
188
+ return [];
189
+ }
190
+
191
+ // Get preset from db
192
+ $preset_email = $this->get_email(intval($preset_id));
193
+ $global_options = $this->extract_global_options_from($preset_email);
194
+
195
+ return $global_options;
196
+ }
197
+
198
+ private function extract_global_options_from($email) {
199
+ $global_options = [];
200
+ foreach ($email->options as $global_option_name => $global_option) {
201
+ if (strpos($global_option_name, 'composer_') === 0) {
202
+ $global_options[str_replace('composer_', '', $global_option_name)] = $global_option;
203
+ }
204
+ }
205
+
206
+ return $global_options;
207
+ }
208
+
209
+ private function is_a_tnp_default_preset($preset_id) {
210
+ return in_array($preset_id, self::$PRESETS_LIST);
211
+ }
212
+
213
+ private function get_all_preset() {
214
+
215
+ $content = "<div class='tnpc-preset-container'>";
216
+
217
+ if ($this->is_normal_context_request()) {
218
+ $content .= "<div class='tnpc-preset-legacy-themes'><a href='" . $this->get_admin_page_url('theme') . "'>" . __('Looking for legacy themes?', 'newsletter') . "</a></div>";
219
+ }
220
+
221
+ // LOAD USER PRESETS
222
+ $user_preset_list = $this->get_emails(self::PRESET_EMAIL_TYPE);
223
+
224
+ foreach ($user_preset_list as $user_preset) {
225
+
226
+ $default_icon_url = NEWSLETTER_URL . "/emails/presets/default-icon.png?ver=2";
227
+ $preset_name = $user_preset->subject;
228
+ $delete_preset_text = __('Delete', 'newsletter');
229
+ $edit_preset_text = __('Edit', 'newsletter');
230
+
231
+ // esc_js() assumes the string will be in single quote (arghhh!!!)
232
+ $onclick_edit = 'tnpc_edit_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
233
+ $onclick_delete = 'tnpc_delete_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
234
+ $onclick_load = 'tnpc_load_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
235
+
236
+ $content .= "<div class='tnpc-preset' onclick='" . esc_attr($onclick_load) . "'>\n";
237
+ $content .= "<img src='$default_icon_url' title='" . esc_attr($preset_name) . "' alt='" . esc_attr($preset_name) . "'>\n";
238
+ $content .= "<span class='tnpc-preset-label'>" . esc_html($user_preset->subject) . "</span>\n";
239
+ $content .= "<span class='tnpc-delete-preset' onclick='" . esc_attr($onclick_delete) . "' title='" . esc_attr($delete_preset_text) . "'><i class='fas fa-times'></i></span>\n";
240
+ $content .= "<span class='tnpc-edit-preset' onclick='" . esc_attr($onclick_edit) . "' title='" . esc_attr($edit_preset_text) . "'><i class='fas fa-pencil-alt'></i></span>\n";
241
+ $content .= "</div>";
242
+ }
243
+
244
+ // LOAD TNP PRESETS
245
+ foreach (self::$PRESETS_LIST as $id) {
246
+ $preset = $this->get_preset_from_file($id);
247
+ $preset_name = esc_html($preset->name);
248
+ $content .= "<div class='tnpc-preset' onclick='tnpc_load_preset(\"$id\")'>";
249
+ $content .= "<img src='$preset->icon' title='$preset_name' alt='$preset_name'/>";
250
+ $content .= "<span class='tnpc-preset-label'>$preset_name</span>";
251
+ $content .= "</div>";
252
+ }
253
+
254
+ if ($this->is_normal_context_request()) {
255
+ $content .= $this->get_automated_spot_element();
256
+ $content .= $this->get_autoresponder_spot_element();
257
+ $content .= $this->get_raw_html_preset_element();
258
+ }
259
+
260
+ return $content;
261
+ }
262
+
263
+ private function is_normal_context_request() {
264
+ return empty($_REQUEST['context_type']);
265
+ }
266
+
267
+ private function is_automated_context_request() {
268
+ return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'automated';
269
+ }
270
+
271
+ private function is_autoresponder_context_request() {
272
+ return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'autoresponder';
273
+ }
274
+
275
+ private function get_automated_spot_element() {
276
+ $result = "<div class='tnpc-preset'>";
277
+ if (class_exists('NewsletterAutomated')) {
278
+ $result .= "<a href='?page=newsletter_automated_index'>";
279
+ } else {
280
+ $result .= "<a href='https://www.thenewsletterplugin.com/automated?utm_source=composer&utm_campaign=plugin&utm_medium=automated'>";
281
+ }
282
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/automated.png' title='Automated addon' alt='Automated'/>";
283
+ $result .= "<span class='tnpc-preset-label'>Daily, weekly and monthly newsletters</span></a>";
284
+ $result .= "</div>";
285
+
286
+ return $result;
287
+ }
288
+
289
+ private function get_autoresponder_spot_element() {
290
+ $result = "<div class='tnpc-preset'>";
291
+ if (class_exists('NewsletterAutoresponder')) {
292
+ $result .= "<a href='?page=newsletter_autoresponder_index'>";
293
+ } else {
294
+ $result .= "<a href='https://www.thenewsletterplugin.com/autoresponder?utm_source=composer&utm_campaign=plugin&utm_medium=autoresponder' target='_blank'>";
295
+ }
296
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/autoresponder.png' title='Autoresponder addon' alt='Autoresponder'/>";
297
+ $result .= "<span class='tnpc-preset-label'>Autoresponders</span></a>";
298
+ $result .= "</div>";
299
+
300
+ return $result;
301
+ }
302
+
303
+ private function get_raw_html_preset_element() {
304
+
305
+ $result = "<div class='tnpc-preset tnpc-preset-html' onclick='location.href=\"" . wp_nonce_url('admin.php?page=newsletter_emails_new&id=rawhtml', 'newsletter-new') . "\"'>";
306
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/rawhtml.png' title='RAW HTML' alt='RAW'/>";
307
+ $result .= "<span class='tnpc-preset-label'>Raw HTML</span>";
308
+ $result .= "</div>";
309
+
310
+ $result .= "<div class='clear'></div>";
311
+ $result .= "</div>";
312
+
313
+ return $result;
314
+ }
315
+
316
+ function has_dynamic_blocks($theme) {
317
+ preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
318
+ foreach ($matches[1] as $match) {
319
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
320
+ $options = $this->options_decode($a);
321
+
322
+ $block = $this->get_block($options['block_id']);
323
+ if (!$block) {
324
+ continue;
325
+ }
326
+ if ($block['type'] == 'dynamic') {
327
+ return true;
328
+ }
329
+ }
330
+ return false;
331
+ }
332
+
333
+ /**
334
+ * Regenerates a saved composed email rendering each block. Regeneration is
335
+ * conditioned (possibly) by the context. The context is usually passed to blocks
336
+ * so they can act in the right manner.
337
+ *
338
+ * $context contains a type and, for automated, the last_run.
339
+ *
340
+ * $email can actually be even a string containing the full newsletter HTML code.
341
+ *
342
+ * @param TNP_Email $email
343
+ * @return string
344
+ */
345
+ function regenerate($email, $context = []) {
346
+
347
+ $context = array_merge(['last_run' => 0, 'type' => ''], $context);
348
+
349
+ preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
350
+
351
+ $result = '';
352
+ $subject = '';
353
+
354
+ foreach ($matches[1] as $match) {
355
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
356
+ $options = $this->options_decode($a);
357
+
358
+ $block = $this->get_block($options['block_id']);
359
+ if (!$block) {
360
+ $this->logger->debug('Unable to load the block ' . $options['block_id']);
361
+ //continue;
362
+ }
363
+
364
+ ob_start();
365
+ $out = $this->render_block($options['block_id'], true, $options, $context);
366
+ if (is_array($out)) {
367
+ if ($out['return_empty_message'] || $out['stop']) {
368
+ return false;
369
+ }
370
+ if ($out['skip']) {
371
+ continue;
372
+ }
373
+ if (empty($subject) && !empty($out['subject'])) {
374
+ $subject = $out['subject'];
375
+ }
376
+ }
377
+ $block_html = ob_get_clean();
378
+ $result .= $block_html;
379
+ }
380
+
381
+ $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
382
+ $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
383
+ $email->subject = $subject;
384
+ return true;
385
+ }
386
+
387
+ function remove_block_data($text) {
388
+ // TODO: Lavorare!
389
+ return $text;
390
+ }
391
+
392
+ static function get_outlook_wrapper_open($width = 600) {
393
+ return '<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '" style="vertical-align:top;width:' . $width . 'px;"><![endif]-->';
394
+ }
395
+
396
+ static function get_outlook_wrapper_close() {
397
+ return "<!--[if mso | IE]></td></tr></table><![endif]-->";
398
+ }
399
+
400
+ function hook_safe_style_css($rules) {
401
+ $rules[] = 'display';
402
+ return $rules;
403
+ }
404
+
405
+ /**
406
+ * Renders a block identified by its id, using the block options and adding a wrapper
407
+ * if required (for the first block rendering).
408
+ *
409
+ * @param string $block_id
410
+ * @param boolean $wrapper
411
+ * @param array $options
412
+ * @param array $context
413
+ * @param array $composer
414
+ */
415
+ function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
416
+ static $kses_style_filter = false;
417
+ include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
418
+
419
+ //Remove 'options_composer_' prefix
420
+ $composer_defaults = [];
421
+ foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
422
+ $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
423
+ }
424
+ $composer = array_merge($composer_defaults, $composer);
425
+
426
+ $width = 600;
427
+ $font_family = 'Helvetica, Arial, sans-serif';
428
+
429
+ $global_title_font_family = $composer['title_font_family'];
430
+ $global_title_font_size = $composer['title_font_size'];
431
+ $global_title_font_color = $composer['title_font_color'];
432
+ $global_title_font_weight = $composer['title_font_weight'];
433
+
434
+ $global_text_font_family = $composer['text_font_family'];
435
+ $global_text_font_size = $composer['text_font_size'];
436
+ $global_text_font_color = $composer['text_font_color'];
437
+ $global_text_font_weight = $composer['text_font_weight'];
438
+
439
+ $global_button_font_family = $composer['button_font_family'];
440
+ $global_button_font_size = $composer['button_font_size'];
441
+ $global_button_font_color = $composer['button_font_color'];
442
+ $global_button_font_weight = $composer['button_font_weight'];
443
+ $global_button_background_color = $composer['button_background_color'];
444
+
445
+ $global_block_background = $composer['block_background'];
446
+
447
+ $info = Newsletter::instance()->get_options('info');
448
+
449
+ // Just in case...
450
+ if (!is_array($options)) {
451
+ $options = array();
452
+ }
453
+
454
+ // This code filters the HTML to remove javascript and unsecure attributes and enable the
455
+ // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
456
+ add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
457
+ $options = wp_kses_post_deep($options);
458
+ remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
459
+
460
+ $block_options = get_option('newsletter_main');
461
+
462
+ $block = $this->get_block($block_id);
463
+
464
+ if (!isset($context['type']))
465
+ $context['type'] = '';
466
+
467
+ // Block not found
468
+ if (!$block) {
469
+ if ($wrapper) {
470
+ echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">';
471
+ echo '<tr>';
472
+ echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
473
+ }
474
+ echo $this->get_outlook_wrapper_open($width);
475
+
476
+ echo '<p>Ops, this block type is not avalable.</p>';
477
+
478
+ echo $this->get_outlook_wrapper_close();
479
+
480
+ if ($wrapper) {
481
+ echo '</td></tr></table>';
482
+ }
483
+ return;
484
+ }
485
+
486
+ $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
487
+
488
+ $dir = is_rtl() ? 'rtl' : 'ltr';
489
+ $align_left = is_rtl() ? 'right' : 'left';
490
+ $align_right = is_rtl() ? 'left' : 'right';
491
+
492
+ ob_start();
493
+ $logger = $this->logger;
494
+ include $block['dir'] . '/block.php';
495
+ $content = trim(ob_get_clean());
496
+
497
+ if (empty($content)) {
498
+ return $out;
499
+ }
500
+
501
+ $common_defaults = array(
502
+ 'block_padding_top' => 0,
503
+ 'block_padding_bottom' => 0,
504
+ 'block_padding_right' => 0,
505
+ 'block_padding_left' => 0,
506
+ 'block_background' => '',
507
+ 'block_background_2' => '',
508
+ 'block_width' => 600,
509
+ 'block_align' => 'center'
510
+ );
511
+
512
+ $options = array_merge($common_defaults, $options);
513
+
514
+ // Obsolete
515
+ $content = str_replace('{width}', $width, $content);
516
+
517
+ $content = $this->inline_css($content, true);
518
+
519
+ // CSS driven by the block
520
+ // Requited for the server side parsing and rendering
521
+ $options['block_id'] = $block_id;
522
+
523
+ $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
524
+ $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
525
+ $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
526
+ $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
527
+
528
+ $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
529
+
530
+ // Internal TD wrapper
531
+ $style = 'text-align: center; ';
532
+ $style .= 'width: 100% !important; ';
533
+ $style .= 'line-height: normal !important; ';
534
+ $style .= 'letter-spacing: normal; ';
535
+ $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
536
+ $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
537
+ $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
538
+ $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
539
+ $style .= 'background-color: ' . $block_background . ';';
540
+
541
+ if (isset($options['block_background_gradient'])) {
542
+ $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
543
+ }
544
+
545
+ $data = $this->options_encode($options);
546
+ // First time block creation wrapper
547
+ if ($wrapper) {
548
+ echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">', "\n";
549
+ echo "<tr>";
550
+ echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
551
+ }
552
+
553
+ // Container that fixes the width and makes the block responsive
554
+ echo $this->get_outlook_wrapper_open($options['block_width']);
555
+
556
+ echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $options['block_width'], 'px!important">', "\n";
557
+ echo "<tr>";
558
+ echo '<td align="', esc_attr($options['block_align']), '" style="', $style, '" bgcolor="', $block_background, '" width="100%">';
559
+
560
+ //echo "<!-- block generated content -->\n";
561
+ echo trim($content);
562
+ //echo "\n<!-- /block generated content -->\n";
563
+
564
+ echo "</td></tr></table>";
565
+ echo $this->get_outlook_wrapper_close();
566
+
567
+ // First time block creation wrapper
568
+ if ($wrapper) {
569
+ echo "</td></tr></table>";
570
+ }
571
+
572
+ return $out;
573
+ }
574
+
575
+ /**
576
+ * Ajax call to render a block with a new set of options after the settings popup
577
+ * has been saved.
578
+ *
579
+ * @param type $block_id
580
+ * @param type $wrapper
581
+ */
582
+ function tnpc_render_callback() {
583
+ if (!check_ajax_referer('save')) {
584
+ $this->dienow('Expired request');
585
+ }
586
+
587
+ $block_id = $_POST['id'];
588
+ $wrapper = isset($_POST['full']);
589
+ $options = $this->restore_options_from_request();
590
+
591
+ $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
592
+ wp_die();
593
+ }
594
+
595
+ function hook_wp_ajax_tnpc_regenerate_email() {
596
+
597
+ $content = stripslashes($_POST['content']);
598
+ $global_options = $_POST['composer'];
599
+
600
+ $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
601
+
602
+ wp_send_json_success([
603
+ 'content' => $regenerated_content,
604
+ 'message' => __('Successfully updated', 'newsletter')
605
+ ]);
606
+ }
607
+
608
+ private function regenerate_email_blocks($content, $global_options) {
609
+
610
+ $raw_block_options = $this->extract_encoded_blocks_options($content);
611
+
612
+ $regenerated_content = '';
613
+
614
+ foreach ($raw_block_options as $raw_block_option) {
615
+
616
+ /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
617
+ $block_options = $this->options_decode( $a ); */
618
+
619
+ $block_options = $this->options_decode($raw_block_option);
620
+
621
+ $block = $this->get_block($block_options['block_id']);
622
+ if (!$block) {
623
+ $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
624
+ }
625
+
626
+ ob_start();
627
+ $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
628
+ $block_html = ob_get_clean();
629
+
630
+ $regenerated_content .= $block_html;
631
+ }
632
+
633
+ return $regenerated_content;
634
+ }
635
+
636
+ /**
637
+ * @param string $html_email_content Email html content
638
+ *
639
+ * @return string[] Encoded options of email blocks
640
+ */
641
+ private function extract_encoded_blocks_options($html_email_content) {
642
+
643
+ preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
644
+
645
+ return $raw_block_options[1];
646
+ }
647
+
648
+ function tnpc_preview_callback() {
649
+ $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
650
+
651
+ if (empty($email)) {
652
+ echo 'Wrong email identifier';
653
+ return;
654
+ }
655
+
656
+ echo $email['message'];
657
+
658
+ wp_die();
659
+ }
660
+
661
+ function tnpc_css_callback() {
662
+ include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
663
+ wp_die();
664
+ }
665
+
666
+ /** Returns the correct admin page to edit the newsletter with the correct editor. */
667
+ function get_editor_url($email_id, $editor_type) {
668
+ switch ($editor_type) {
669
+ case NewsletterEmails::EDITOR_COMPOSER:
670
+ return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
671
+ case NewsletterEmails::EDITOR_HTML:
672
+ return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
673
+ case NewsletterEmails::EDITOR_TINYMCE:
674
+ return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
680
+ * or the targeting page (it depends on newsletter status).
681
+ *
682
+ * @param TNP_Email $email
683
+ */
684
+ function get_edit_button($email, $only_icon = false) {
685
+
686
+ $editor_type = $this->get_editor_type($email);
687
+ if ($email->status == 'new') {
688
+ $edit_url = $this->get_editor_url($email->id, $editor_type);
689
+ } else {
690
+ $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
691
+ }
692
+ switch ($editor_type) {
693
+ case NewsletterEmails::EDITOR_COMPOSER:
694
+ $icon_class = 'th-large';
695
+ break;
696
+ case NewsletterEmails::EDITOR_HTML:
697
+ $icon_class = 'code';
698
+ break;
699
+ default:
700
+ $icon_class = 'edit';
701
+ break;
702
+ }
703
+ if ($only_icon) {
704
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
705
+ '<i class="fas fa-' . $icon_class . '"></i></a>';
706
+ } else {
707
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
708
+ '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
709
+ }
710
+ }
711
+
712
+ /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
713
+ function get_editor_type($email) {
714
+ $email = (object) $email;
715
+ $editor_type = $email->editor;
716
+
717
+ // Backward compatibility
718
+ $email_options = maybe_unserialize($email->options);
719
+ if (isset($email_options['composer'])) {
720
+ $editor_type = NewsletterEmails::EDITOR_COMPOSER;
721
+ }
722
+ // End backward compatibility
723
+
724
+ return $editor_type;
725
+ }
726
+
727
+ /**
728
+ *
729
+ * @param type $action
730
+ * @param type $user
731
+ * @param type $email
732
+ * @return type
733
+ * @global wpdb $wpdb
734
+ */
735
+ function hook_newsletter_action($action, $user, $email) {
736
+ global $wpdb;
737
+
738
+ switch ($action) {
739
+ case 'v':
740
+ case 'view':
741
+ $id = $_GET['id'];
742
+ if ($id == 'last') {
743
+ $email = $wpdb->get_row("select * from " . NEWSLETTER_EMAILS_TABLE . " where private=0 and type='message' and status='sent' order by send_on desc limit 1");
744
+ } else {
745
+ $email = $this->get_email($_GET['id']);
746
+ }
747
+ if (empty($email)) {
748
+ header("HTTP/1.0 404 Not Found");
749
+ die('Email not found');
750
+ }
751
+
752
+ if (!Newsletter::instance()->is_allowed()) {
753
+
754
+ if ($email->status == 'new') {
755
+ header("HTTP/1.0 404 Not Found");
756
+ die('Not sent yet');
757
+ }
758
+
759
+ if ($email->private == 1) {
760
+ if (!$user) {
761
+ header("HTTP/1.0 404 Not Found");
762
+ die('No available for online view');
763
+ }
764
+ $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
765
+ if (!$sent) {
766
+ header("HTTP/1.0 404 Not Found");
767
+ die('No available for online view');
768
+ }
769
+ }
770
+ }
771
+
772
+
773
+ header('Content-Type: text/html;charset=UTF-8');
774
+ header('X-Robots-Tag: noindex,nofollow,noarchive');
775
+ header('Cache-Control: no-cache,no-store,private');
776
+
777
+ echo $this->replace($email->message, $user, $email);
778
+
779
+ die();
780
+ break;
781
+
782
+ case 'emails-css':
783
+ $email_id = (int) $_GET['id'];
784
+
785
+ $body = Newsletter::instance()->get_email_field($email_id, 'message');
786
+
787
+ $x = strpos($body, '<style');
788
+ if ($x === false)
789
+ return;
790
+
791
+ $x = strpos($body, '>', $x);
792
+ $y = strpos($body, '</style>');
793
+
794
+ header('Content-Type: text/css;charset=UTF-8');
795
+
796
+ echo substr($body, $x + 1, $y - $x - 1);
797
+
798
+ die();
799
+ break;
800
+
801
+ case 'emails-composer-css':
802
+ header('Cache: no-cache');
803
+ header('Content-Type: text/css');
804
+ echo $this->get_composer_css();
805
+ die();
806
+ break;
807
+
808
+ case 'emails-preview':
809
+ if (!Newsletter::instance()->is_allowed()) {
810
+ die('Not enough privileges');
811
+ }
812
+
813
+ if (!check_admin_referer('view')) {
814
+ die();
815
+ }
816
+
817
+ $theme_id = $_GET['id'];
818
+ $theme = $this->themes->get_theme($theme_id);
819
+
820
+ // Used by theme code
821
+ $theme_options = $this->themes->get_options($theme_id);
822
+
823
+ $theme_url = $theme['url'];
824
+
825
+ header('Content-Type: text/html;charset=UTF-8');
826
+
827
+ include $theme['dir'] . '/theme.php';
828
+
829
+ die();
830
+ break;
831
+
832
+ case 'emails-preview-text':
833
+ header('Content-Type: text/plain;charset=UTF-8');
834
+ if (!Newsletter::instance()->is_allowed()) {
835
+ die('Not enough privileges');
836
+ }
837
+
838
+ if (!check_admin_referer('view')) {
839
+ die();
840
+ }
841
+
842
+ // Used by theme code
843
+ $theme_options = $this->get_current_theme_options();
844
+
845
+ $file = include $theme['dir'] . '/theme-text.php';
846
+
847
+ if (is_file($file)) {
848
+ include $file;
849
+ }
850
+
851
+ die();
852
+ break;
853
+
854
+ case 'emails-create':
855
+ // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
856
+ // excerpt, thumbnail are extracted.
857
+ if (!Newsletter::instance()->is_allowed()) {
858
+ die('Not enough privileges');
859
+ }
860
+
861
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
862
+ $controls = new NewsletterControls();
863
+
864
+ if (!$controls->is_action('create')) {
865
+ die('Wrong call');
866
+ }
867
+
868
+ $theme_id = $controls->data['id'];
869
+ $theme = $this->themes->get_theme($theme_id);
870
+
871
+ if (!$theme) {
872
+ die('invalid theme');
873
+ }
874
+
875
+ $this->themes->save_options($theme_id, $controls->data);
876
+
877
+ $email = array();
878
+ $email['status'] = 'new';
879
+ $email['subject'] = ''; //__('Here the email subject', 'newsletter');
880
+ $email['track'] = Newsletter::instance()->options['track'];
881
+ $email['send_on'] = time();
882
+ $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
883
+ $email['type'] = 'message';
884
+
885
+ $theme_options = $this->themes->get_options($theme_id);
886
+
887
+ $theme_url = $theme['url'];
888
+ $theme_subject = '';
889
+
890
+ ob_start();
891
+ include $theme['dir'] . '/theme.php';
892
+ $email['message'] = ob_get_clean();
893
+
894
+ if (!empty($theme_subject)) {
895
+ $email['subject'] = $theme_subject;
896
+ }
897
+
898
+ if (file_exists($theme['dir'] . '/theme-text.php')) {
899
+ ob_start();
900
+ include $theme['dir'] . '/theme-text.php';
901
+ $email['message_text'] = ob_get_clean();
902
+ } else {
903
+ $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
904
+ }
905
+
906
+ $email = $this->save_email($email);
907
+
908
+ $edit_url = $this->get_editor_url($email->id, $email->editor);
909
+
910
+ header('Location: ' . $edit_url);
911
+
912
+ die();
913
+ break;
914
+ }
915
+ }
916
+
917
+ function admin_menu() {
918
+ $this->add_menu_page('index', 'Newsletters');
919
+ $this->add_admin_page('list', 'Email List');
920
+ $this->add_admin_page('new', 'Email New');
921
+ $this->add_admin_page('edit', 'Email Edit');
922
+ $this->add_admin_page('theme', 'Email Themes');
923
+ $this->add_admin_page('composer', 'The Composer');
924
+ $this->add_admin_page('editorhtml', 'HTML Editor');
925
+ $this->add_admin_page('editortinymce', 'TinyMCE Editor');
926
+ }
927
+
928
+ /**
929
+ * Builds a block data structure starting from the folder containing the block
930
+ * files.
931
+ *
932
+ * @param string $dir
933
+ * @return array | WP_Error
934
+ */
935
+ function build_block($dir) {
936
+ $dir = realpath($dir);
937
+ $dir = wp_normalize_path($dir);
938
+ $full_file = $dir . '/block.php';
939
+ if (!is_file($full_file)) {
940
+ return new WP_Error('1', 'Missing block.php file in ' . $dir);
941
+ }
942
+
943
+ $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
944
+ $file = basename($dir);
945
+
946
+ $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
947
+ $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
948
+ $data = array_merge($defaults, $data);
949
+
950
+ if (is_file($dir . '/icon.png')) {
951
+ $data['icon'] = content_url($relative_dir . '/icon.png');
952
+ }
953
+
954
+ $data['id'] = sanitize_key($file);
955
+
956
+ // Absolute path of the block files
957
+ $data['dir'] = $dir;
958
+ $data['url'] = content_url($relative_dir);
959
+
960
+ return $data;
961
+ }
962
+
963
+ /**
964
+ *
965
+ * @param type $dir
966
+ * @return type
967
+ */
968
+ function scan_blocks_dir($dir) {
969
+ $dir = realpath($dir);
970
+ if (!$dir) {
971
+ return [];
972
+ }
973
+ $dir = wp_normalize_path($dir);
974
+
975
+ $list = [];
976
+ $handle = opendir($dir);
977
+ while ($file = readdir($handle)) {
978
+
979
+ $data = $this->build_block($dir . '/' . $file);
980
+
981
+ if (is_wp_error($data)) {
982
+ $this->logger->error($data);
983
+ continue;
984
+ }
985
+ $list[$data['id']] = $data;
986
+ }
987
+ closedir($handle);
988
+ return $list;
989
+ }
990
+
991
+ /**
992
+ * Array of arrays with every registered block and legacy block converted to the new
993
+ * format.
994
+ *
995
+ * @return array
996
+ */
997
+ function get_blocks() {
998
+
999
+ if (!is_null($this->blocks)) {
1000
+ return $this->blocks;
1001
+ }
1002
+
1003
+ $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1004
+
1005
+ $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1006
+
1007
+ $this->blocks = array_merge($extended, $this->blocks);
1008
+
1009
+ $dirs = apply_filters('newsletter_blocks_dir', array());
1010
+
1011
+ //$this->logger->debug('Block dirs:');
1012
+ //$this->logger->debug($dirs);
1013
+
1014
+ foreach ($dirs as $dir) {
1015
+ $list = $this->scan_blocks_dir($dir);
1016
+ $this->blocks = array_merge($list, $this->blocks);
1017
+ }
1018
+
1019
+ do_action('newsletter_register_blocks');
1020
+
1021
+ foreach (TNP_Composer::$block_dirs as $dir) {
1022
+ $block = $this->build_block($dir);
1023
+ if (is_wp_error($block)) {
1024
+ $this->logger->error($block);
1025
+ continue;
1026
+ }
1027
+ if (!isset($this->blocks[$block['id']])) {
1028
+ $this->blocks[$block['id']] = $block;
1029
+ } else {
1030
+ $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1031
+ }
1032
+ }
1033
+
1034
+ $this->blocks = array_reverse($this->blocks);
1035
+ return $this->blocks;
1036
+ }
1037
+
1038
+ /**
1039
+ * Return a single block (associative array) checking for legacy ID as well.
1040
+ *
1041
+ * @param string $id
1042
+ * @return array
1043
+ */
1044
+ function get_block($id) {
1045
+ switch ($id) {
1046
+ case 'content-03-text.block':
1047
+ $id = 'text';
1048
+ break;
1049
+ case 'footer-03-social.block':
1050
+ $id = 'social';
1051
+ break;
1052
+ case 'footer-02-canspam.block':
1053
+ $id = 'canspam';
1054
+ break;
1055
+ case 'content-05-image.block':
1056
+ $id = 'image';
1057
+ break;
1058
+ case 'header-01-header.block':
1059
+ $id = 'header';
1060
+ break;
1061
+ case 'footer-01-footer.block':
1062
+ $id = 'footer';
1063
+ break;
1064
+ case 'content-02-heading.block':
1065
+ $id = 'heading';
1066
+ break;
1067
+ case 'content-07-twocols.block':
1068
+ case 'content-06-posts.block':
1069
+ $id = 'posts';
1070
+ break;
1071
+ case 'content-04-cta.block':
1072
+ $id = 'cta';
1073
+ break;
1074
+ case 'content-01-hero.block':
1075
+ $id = 'hero';
1076
+ break;
1077
+ // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1078
+ // break;
1079
+ }
1080
+
1081
+ // Conversion for old full path ID
1082
+ $id = sanitize_key(basename($id));
1083
+
1084
+ // TODO: Correct id for compatibility
1085
+ $blocks = $this->get_blocks();
1086
+ if (!isset($blocks[$id])) {
1087
+ return null;
1088
+ }
1089
+ return $blocks[$id];
1090
+ }
1091
+
1092
+ function scan_presets_dir($dir = null) {
1093
+
1094
+ if (is_null($dir)) {
1095
+ $dir = __DIR__ . '/presets';
1096
+ }
1097
+
1098
+ if (!is_dir($dir)) {
1099
+ return array();
1100
+ }
1101
+
1102
+ $handle = opendir($dir);
1103
+ $list = array();
1104
+ $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1105
+ while ($file = readdir($handle)) {
1106
+
1107
+ if ($file == '.' || $file == '..')
1108
+ continue;
1109
+
1110
+ // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1111
+ $preset_id = sanitize_key($file);
1112
+
1113
+ $full_file = $dir . '/' . $file . '/preset.json';
1114
+
1115
+ if (!is_file($full_file)) {
1116
+ continue;
1117
+ }
1118
+
1119
+ $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1120
+
1121
+ $list[$preset_id] = $icon;
1122
+ }
1123
+ closedir($handle);
1124
+ return $list;
1125
+ }
1126
+
1127
+ function get_preset_from_file($id, $dir = null) {
1128
+
1129
+ if (is_null($dir)) {
1130
+ $dir = __DIR__ . '/presets';
1131
+ }
1132
+
1133
+ $id = $this->sanitize_file_name($id);
1134
+
1135
+ if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1136
+ return array();
1137
+ }
1138
+
1139
+ $json_content = file_get_contents("$dir/$id/preset.json");
1140
+ $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1141
+ $json = json_decode($json_content);
1142
+ $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1143
+
1144
+ return $json;
1145
+ }
1146
+
1147
+ function get_composer_css() {
1148
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1149
+ $blocks = $this->get_blocks();
1150
+ foreach ($blocks as $block) {
1151
+ if (!file_exists($block['dir'] . '/style.css')) {
1152
+ continue;
1153
+ }
1154
+ $css .= "\n\n";
1155
+ $css .= "/* " . $block['name'] . " */\n";
1156
+ $css .= file_get_contents($block['dir'] . '/style.css');
1157
+ }
1158
+ return $css;
1159
+ }
1160
+
1161
+ function get_composer_backend_css() {
1162
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1163
+ $css .= "\n\n";
1164
+ $css .= $this->get_composer_css();
1165
+ return $css;
1166
+ }
1167
+
1168
+ /**
1169
+ * Send an email to the test subscribers.
1170
+ *
1171
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1172
+ * @param NewsletterControls $controls
1173
+ */
1174
+ function send_test_email($email, $controls) {
1175
+ if (!$email) {
1176
+ $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1177
+ return;
1178
+ }
1179
+
1180
+ $original_subject = $email->subject;
1181
+ $this->set_test_subject_to($email);
1182
+
1183
+ $users = NewsletterUsers::instance()->get_test_users();
1184
+ if (count($users) == 0) {
1185
+ $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1186
+ '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1187
+ __('Read more', 'newsletter') . '</strong></a>.';
1188
+ } else {
1189
+ $r = Newsletter::instance()->send($email, $users, true);
1190
+ $emails = array();
1191
+ foreach ($users as $user) {
1192
+ $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1193
+ }
1194
+ if (is_wp_error($r)) {
1195
+ $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1196
+ $controls->errors .= __('Test subscribers:', 'newsletter');
1197
+ $controls->errors .= ' ' . implode(', ', $emails);
1198
+ $controls->errors .= '<br>';
1199
+ $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1200
+ $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1201
+ } else {
1202
+ $controls->messages = __('Test subscribers:', 'newsletter');
1203
+
1204
+ $controls->messages .= ' ' . implode(', ', $emails);
1205
+ $controls->messages .= '.<br>';
1206
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1207
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1208
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1209
+ }
1210
+ }
1211
+ $email->subject = $original_subject;
1212
+ }
1213
+
1214
+ /**
1215
+ * Send an email to the test subscribers.
1216
+ *
1217
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1218
+ * @param string $email_address
1219
+ *
1220
+ * @throws Exception
1221
+ */
1222
+ function send_test_newsletter_to_email_address($email, $email_address) {
1223
+
1224
+ if (!$email) {
1225
+ throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1226
+ }
1227
+
1228
+ $this->set_test_subject_to($email);
1229
+
1230
+ $dummy_subscriber = $this->make_dummy_subscriber();
1231
+ $dummy_subscriber->email = $email_address;
1232
+
1233
+ $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1234
+
1235
+ $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1236
+
1237
+ if (is_wp_error($result)) {
1238
+ $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1239
+ $error_message .= __('Test subscribers:', 'newsletter');
1240
+ $error_message .= ' ' . $email;
1241
+ $error_message .= '<br>';
1242
+ $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1243
+ $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1244
+ throw new Exception($error_message);
1245
+ }
1246
+
1247
+ $messages = __('Test subscribers:', 'newsletter');
1248
+
1249
+ $messages .= ' ' . $email;
1250
+ $messages .= '.<br>';
1251
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1252
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1253
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1254
+
1255
+ return $messages;
1256
+ }
1257
+
1258
+ private function set_test_subject_to($email) {
1259
+ if ($email->subject == '') {
1260
+ $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1261
+ } else {
1262
+ $email->subject = $email->subject . ' (TEST)';
1263
+ }
1264
+ }
1265
+
1266
+ private function make_dummy_subscriber() {
1267
+ $dummy_user = new TNP_User();
1268
+ $dummy_user->id = 0;
1269
+ $dummy_user->email = 'john.doe@example.org';
1270
+ $dummy_user->name = 'John';
1271
+ $dummy_user->surname = 'Doe';
1272
+ $dummy_user->sex = 'n';
1273
+ $dummy_user->language = '';
1274
+ $dummy_user->ip = '';
1275
+
1276
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1277
+ $profile_key = "profile_$i";
1278
+ $dummy_user->$profile_key = '';
1279
+ }
1280
+
1281
+ return $dummy_user;
1282
+ }
1283
+
1284
+ function restore_options_from_request() {
1285
+
1286
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1287
+ $controls = new NewsletterControls();
1288
+ $options = $controls->data;
1289
+
1290
+ if (isset($_POST['options']) && is_array($_POST['options'])) {
1291
+ // Get all block options
1292
+ //$options = stripslashes_deep($_POST['options']);
1293
+ // Deserialize inline edits when
1294
+ // render is preformed on saving block options
1295
+ if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1296
+ $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1297
+ }
1298
+
1299
+ // Restore inline edits from data-json
1300
+ // coming from inline editing
1301
+ // and merge with current inline edit
1302
+ if (isset($_POST['encoded_options'])) {
1303
+ $decoded_options = $this->options_decode($_POST['encoded_options']);
1304
+
1305
+ $to_merge_inline_edits = [];
1306
+
1307
+ if (isset($decoded_options['inline_edits'])) {
1308
+ foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1309
+ $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1310
+ }
1311
+ }
1312
+
1313
+ //Overwrite with new edited content
1314
+ if (isset($options['inline_edits'])) {
1315
+ foreach ($options['inline_edits'] as $inline_edit) {
1316
+ $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1317
+ }
1318
+ }
1319
+
1320
+ $options['inline_edits'] = array_values($to_merge_inline_edits);
1321
+ $options = array_merge($decoded_options, $options);
1322
+ }
1323
+
1324
+ return $options;
1325
+ }
1326
+
1327
+ return array();
1328
+ }
1329
+
1330
+ public function hook_wp_ajax_tnpc_delete_preset() {
1331
+
1332
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1333
+ wp_send_json_error('Expired request');
1334
+ }
1335
+
1336
+ $preset_id = (int) $_REQUEST['presetId'];
1337
+
1338
+ $newsletter = Newsletter::instance();
1339
+
1340
+ if ($preset_id > 0) {
1341
+ $preset = $newsletter->get_email($preset_id);
1342
+
1343
+ if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1344
+ Newsletter::instance()->delete_email($preset_id);
1345
+ wp_send_json_success();
1346
+ } else {
1347
+ wp_send_json_error(__('Is not a preset!', 'newsletter'));
1348
+ }
1349
+ } else {
1350
+ wp_send_json_error();
1351
+ }
1352
+ }
1353
+
1354
+ }
1355
+
1356
+ NewsletterEmails::instance();
includes/fields.php CHANGED
@@ -480,6 +480,20 @@ class NewsletterFields {
480
  }
481
 
482
  function posts($name, $label, $count = 20, $args = []) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  $args = array_merge(array('filters' => array(
484
  'posts_per_page' => 5,
485
  'offset' => 0,
@@ -503,7 +517,7 @@ class NewsletterFields {
503
  $args['filters']['posts_per_page'] = $count;
504
 
505
  $posts = get_posts($args['filters']);
506
- $options = array();
507
  if ($args['last_post_option']) {
508
  $options['last'] = 'Most recent post';
509
  }
480
  }
481
 
482
  function posts($name, $label, $count = 20, $args = []) {
483
+ $value = $this->controls->get_value($name, 0);
484
+
485
+ // Post select options
486
+ $options = [];
487
+
488
+ // Retrieve the selected post and add as first element since it could not be part of the
489
+ // latest list anymore
490
+ if (!empty($value)) {
491
+ $post = get_post($value);
492
+ if ($post) {
493
+ $options['' . $post->ID] = $post->post_title;
494
+ }
495
+ }
496
+
497
  $args = array_merge(array('filters' => array(
498
  'posts_per_page' => 5,
499
  'offset' => 0,
517
  $args['filters']['posts_per_page'] = $count;
518
 
519
  $posts = get_posts($args['filters']);
520
+
521
  if ($args['last_post_option']) {
522
  $options['last'] = 'Most recent post';
523
  }
includes/helper.php CHANGED
@@ -90,7 +90,8 @@ function tnp_post_content($post) {
90
  }
91
 
92
  function tnp_post_title($post) {
93
- return $post->post_title;
 
94
  }
95
 
96
  function tnp_post_date($post, $format = null) {
90
  }
91
 
92
  function tnp_post_title($post) {
93
+ //return $post->post_title;
94
+ return get_the_title($post);
95
  }
96
 
97
  function tnp_post_date($post, $format = null) {
includes/mailer.php CHANGED
@@ -214,6 +214,9 @@ class NewsletterMailer {
214
  class NewsletterDefaultMailer extends NewsletterMailer {
215
 
216
  var $filter_active = false;
 
 
 
217
 
218
  /**
219
  * Static to be accessed in the hook: on some installation the object $this is not working, we're still trying to understand why
@@ -223,6 +226,11 @@ class NewsletterDefaultMailer extends NewsletterMailer {
223
 
224
  function __construct() {
225
  parent::__construct('default', Newsletter::instance()->get_options('smtp'));
 
 
 
 
 
226
  }
227
 
228
  function get_description() {
@@ -310,10 +318,29 @@ class NewsletterDefaultMailer extends NewsletterMailer {
310
 
311
  $this->current_message = $message;
312
 
 
313
  $r = wp_mail($message->to, $message->subject, $body, $wp_mail_headers);
314
  $this->current_message = null;
315
 
316
  if (!$r) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  $last_error = error_get_last();
318
  if (is_array($last_error)) {
319
  $message->error = $last_error['message'];
214
  class NewsletterDefaultMailer extends NewsletterMailer {
215
 
216
  var $filter_active = false;
217
+
218
+ /** @var WP_Error */
219
+ var $last_error = null;
220
 
221
  /**
222
  * Static to be accessed in the hook: on some installation the object $this is not working, we're still trying to understand why
226
 
227
  function __construct() {
228
  parent::__construct('default', Newsletter::instance()->get_options('smtp'));
229
+ add_action('wp_mail_failed', [$this, 'hook_wp_mail_failed']);
230
+ }
231
+
232
+ function hook_wp_mail_failed($error) {
233
+ $this->last_error = $error;
234
  }
235
 
236
  function get_description() {
318
 
319
  $this->current_message = $message;
320
 
321
+ $this->last_error = null;
322
  $r = wp_mail($message->to, $message->subject, $body, $wp_mail_headers);
323
  $this->current_message = null;
324
 
325
  if (!$r) {
326
+ if ($this->last_error && is_wp_error($this->last_error)) {
327
+ $error_message = $this->last_error->get_error_message();
328
+
329
+ // Still not used
330
+ $error_data = $this->last_error->get_error_data();
331
+ $error_code = '';
332
+ if (isset($mail_data['phpmailer_exception_code'])) {
333
+ $error_code = $mail_data['phpmailer_exception_code'];
334
+ }
335
+
336
+ if (stripos($error_message, 'Could not instantiate mail function') || stripos($error_message, 'Failed to connect to mailserver')) {
337
+ return new WP_Error(self::ERROR_FATAL, $error_message);
338
+ } else {
339
+ return new WP_Error(self::ERROR_GENERIC, $error_message);
340
+ }
341
+ }
342
+
343
+ // This code should be removed when sure...
344
  $last_error = error_get_last();
345
  if (is_array($last_error)) {
346
  $message->error = $last_error['message'];
includes/module.php CHANGED
@@ -1,2628 +1,2630 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- require_once __DIR__ . '/logger.php';
6
- require_once __DIR__ . '/store.php';
7
- require_once __DIR__ . '/composer.php';
8
- require_once __DIR__ . '/addon.php';
9
- require_once __DIR__ . '/mailer.php';
10
- require_once __DIR__ . '/themes.php';
11
-
12
- class TNP_Media {
13
-
14
- var $id;
15
- var $url;
16
- var $width;
17
- var $height;
18
- var $alt;
19
- var $link;
20
- var $align = 'center';
21
-
22
- /** Sets the width recalculating the height */
23
- public function set_width($width) {
24
- $width = (int) $width;
25
- if (empty($width))
26
- return;
27
- if ($this->width < $width)
28
- return;
29
- $this->height = floor(($width / $this->width) * $this->height);
30
- $this->width = $width;
31
- }
32
-
33
- /** Sets the height recalculating the width */
34
- public function set_height($height) {
35
- $height = (int) $height;
36
- $this->width = floor(($height / $this->height) * $this->width);
37
- $this->height = $height;
38
- }
39
-
40
- }
41
-
42
- /**
43
- * @property int $id The list unique identifier
44
- * @property string $name The list name
45
- * @property bool $forced If the list must be added to every new subscriber
46
- * @property int $status When and how the list is visible to the subscriber - see constants
47
- * @property bool $checked If it must be pre-checked on subscription form
48
- * @property array $languages The list of language used to pre-assign this list
49
- */
50
- class TNP_List {
51
-
52
- const STATUS_PRIVATE = 0;
53
- const STATUS_PUBLIC = 1;
54
- const SUBSCRIPTION_HIDE = 0;
55
- const SUBSCRIPTION_SHOW = 1;
56
- const SUBSCRIPTION_SHOW_CHECKED = 2;
57
- const PROFILE_HIDE = 0;
58
- const PROFILE_SHOW = 1;
59
-
60
- var $id;
61
- var $name;
62
- var $status;
63
- var $forced;
64
- var $checked;
65
- var $show_on_subscription;
66
- var $show_on_profile;
67
-
68
- function is_private() {
69
- return $this->status == self::STATUS_PRIVATE;
70
- }
71
-
72
- }
73
-
74
- /**
75
- * @property int $id The list unique identifier
76
- * @property string $name The list name
77
- * @property int $status When and how the list is visible to the subscriber - see constants
78
- * @property string $type Field type: text or select
79
- * @property array $options Field options (usually the select items)
80
- */
81
- class TNP_Profile {
82
-
83
- const STATUS_PRIVATE = 0;
84
- const STATUS_PUBLIC = 2;
85
- const STATUS_PROFILE_ONLY = 1;
86
- const STATUS_HIDDEN = 3; // Public but never shown (can be set with a hidden form field)
87
- const TYPE_TEXT = 'text';
88
- const TYPE_SELECT = 'select';
89
-
90
- public $id;
91
- public $name;
92
- public $status;
93
- public $type;
94
- public $options;
95
- public $placeholder;
96
- public $rule;
97
-
98
- public function __construct($id, $name, $status, $type, $options, $placeholder, $rule) {
99
- $this->id = $id;
100
- $this->name = $name;
101
- $this->status = $status;
102
- $this->type = $type;
103
- $this->options = $options;
104
- $this->placeholder = $placeholder;
105
- $this->rule = $rule;
106
- }
107
-
108
- function is_select() {
109
- return $this->type == self::TYPE_SELECT;
110
- }
111
-
112
- function is_text() {
113
- return $this->type == self::TYPE_TEXT;
114
- }
115
-
116
- function is_required() {
117
- return $this->rule == 1;
118
- }
119
-
120
- function is_private() {
121
- return $this->status == self::STATUS_PRIVATE;
122
- }
123
-
124
- function show_on_profile() {
125
- return $this->status == self::STATUS_PROFILE_ONLY || $this->status == self::STATUS_PUBLIC;
126
- }
127
-
128
- }
129
-
130
- class TNP_Profile_Service {
131
-
132
- /**
133
- *
134
- * @param string $language
135
- * @param string $type
136
- * @return TNP_Profile[]
137
- */
138
- static function get_profiles($language = '', $type = '') {
139
-
140
- static $profiles = [];
141
- $k = $language . $type;
142
-
143
- if (isset($profiles[$k])) {
144
- return $profiles[$k];
145
- }
146
-
147
- $profiles[$k] = [];
148
- $profile_options = NewsletterSubscription::instance()->get_options('profile', $language);
149
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
150
- if (empty($profile_options['profile_' . $i])) {
151
- continue;
152
- }
153
- $profile = self::create_profile_from_options($profile_options, $i);
154
-
155
- if (empty($type) ||
156
- ( $type == TNP_Profile::TYPE_SELECT && $profile->is_select() ) ||
157
- ( $type == TNP_Profile::TYPE_TEXT && $profile->is_text() )) {
158
- $profiles[$k]['' . $i] = $profile;
159
- }
160
- }
161
-
162
- return $profiles[$k];
163
- }
164
-
165
- static function get_profile_by_id($id, $language = '') {
166
-
167
- $profiles = self::get_profiles($language);
168
- if (isset($profiles[$id]))
169
- return $profiles[$id];
170
- return null;
171
- }
172
-
173
- /**
174
- * @return TNP_Profile
175
- */
176
- private static function create_profile_from_options($options, $id) {
177
- return new TNP_Profile(
178
- $id,
179
- $options['profile_' . $id],
180
- (int) $options['profile_' . $id . '_status'],
181
- $options['profile_' . $id . '_type'],
182
- self::string_db_options_to_array($options['profile_' . $id . '_options']),
183
- $options['profile_' . $id . '_placeholder'],
184
- $options['profile_' . $id . '_rules']
185
- );
186
- }
187
-
188
- /**
189
- * Returns a list of strings which are the items for the select field.
190
- * @return array
191
- */
192
- private static function string_db_options_to_array($string_options) {
193
- $items = array_map('trim', explode(',', $string_options));
194
- $items = array_combine($items, $items);
195
-
196
- return $items;
197
- }
198
-
199
- }
200
-
201
- /**
202
- * Represents the set of data collected by a subscription interface (form, API, ...). Only a valid
203
- * email is mandatory.
204
- */
205
- class TNP_Subscription_Data {
206
-
207
- var $email = null;
208
- var $name = null;
209
- var $surname = null;
210
- var $sex = null;
211
- var $language = null;
212
- var $referrer = null;
213
- var $http_referrer = null;
214
- var $ip = null;
215
- var $country = null;
216
- var $region = null;
217
- var $city = null;
218
-
219
- /**
220
- * Associative array id=>value of lists chosen by the subscriber. A list can be set to
221
- * 0 meaning the subscriber does not want to be in that list.
222
- * The lists must be public: non public lists are filtered.
223
- * @var array
224
- */
225
- var $lists = [];
226
- var $profiles = [];
227
-
228
- function merge_in($subscriber) {
229
- if (!$subscriber)
230
- $subscriber = new TNP_User();
231
- if (!empty($this->email))
232
- $subscriber->email = $this->email;
233
- if (!empty($this->name))
234
- $subscriber->name = $this->name;
235
- if (!empty($this->surname))
236
- $subscriber->surname = $this->surname;
237
- if (!empty($this->sex))
238
- $subscriber->sex = $this->sex;
239
- if (!empty($this->language))
240
- $subscriber->language = $this->language;
241
- if (!empty($this->ip))
242
- $subscriber->ip = $this->ip;
243
- if (!empty($this->referrer))
244
- $subscriber->referrer = $this->referrer;
245
- if (!empty($this->http_referrer))
246
- $subscriber->http_referrer = $this->http_referrer;
247
- if (!empty($this->country))
248
- $subscriber->country = $this->country;
249
- if (!empty($this->region))
250
- $subscriber->region = $this->region;
251
- if (!empty($this->city))
252
- $subscriber->city = $this->city;
253
-
254
-
255
- foreach ($this->lists as $id => $value) {
256
- $key = 'list_' . $id;
257
- $subscriber->$key = $value;
258
- }
259
-
260
- // Profile
261
- foreach ($this->profiles as $id => $value) {
262
- $key = 'profile_' . $id;
263
- $subscriber->$key = $value;
264
- }
265
- }
266
-
267
- /** Sets to active a set of lists. Accepts incorrect data (and ignores it).
268
- *
269
- * @param array $list_ids Array of list IDs
270
- */
271
- function add_lists($list_ids) {
272
- if (empty($list_ids) || !is_array($list_ids))
273
- return;
274
- foreach ($list_ids as $list_id) {
275
- $list_id = (int) $list_id;
276
- if ($list_id < 0 || $list_id > NEWSLETTER_LIST_MAX)
277
- continue;
278
- $this->lists[$list_id] = 1;
279
- }
280
- }
281
-
282
- }
283
-
284
- /**
285
- * Represents a subscription request with the subscriber data and actions to be taken by
286
- * the subscription engine (spam check, notifications, ...).
287
- */
288
- class TNP_Subscription {
289
-
290
- const EXISTING_ERROR = 1;
291
- const EXISTING_MERGE = 0;
292
-
293
- /**
294
- * Subscriber's data following the syntax of the TNP_User
295
- * @var TNP_Subscription_Data
296
- */
297
- var $data;
298
- var $spamcheck = true;
299
- // The optin to use, empty for the plugin default. It's a string to facilitate the use by addons (which have a selector for the desired
300
- // optin as empty (for default), 'single' or 'double'.
301
- var $optin = null;
302
- // What to do with an existing subscriber???
303
- var $if_exists = self::EXISTING_MERGE;
304
-
305
- /**
306
- * Determines if the welcome or activation email should be sent. Note: sometime an activation email is sent disregarding
307
- * this setting.
308
- * @var boolean
309
- */
310
- var $send_emails = true;
311
-
312
- public function __construct() {
313
- $this->data = new TNP_Subscription_Data();
314
- }
315
-
316
- public function is_single_optin() {
317
- return $this->optin == 'single';
318
- }
319
-
320
- public function is_double_optin() {
321
- return $this->optin == 'double';
322
- }
323
-
324
- }
325
-
326
- /**
327
- * @property int $id The subscriber unique identifier
328
- * @property string $email The subscriber email
329
- * @property string $name The subscriber name or first name
330
- * @property string $surname The subscriber last name
331
- * @property string $status The subscriber status
332
- * @property string $language The subscriber language code 2 chars lowercase
333
- * @property string $token The subscriber secret token
334
- * @property string $country The subscriber country code 2 chars uppercase
335
- */
336
- class TNP_User {
337
-
338
- const STATUS_CONFIRMED = 'C';
339
- const STATUS_NOT_CONFIRMED = 'S';
340
- const STATUS_UNSUBSCRIBED = 'U';
341
- const STATUS_BOUNCED = 'B';
342
- const STATUS_COMPLAINED = 'P';
343
-
344
- var $ip = '';
345
-
346
- public static function get_status_label($status) {
347
- switch ($status) {
348
- case self::STATUS_NOT_CONFIRMED: return __('NOT CONFIRMED', 'newsletter');
349
- break;
350
- case self::STATUS_CONFIRMED: return __('CONFIRMED', 'newsletter');
351
- break;
352
- case self::STATUS_UNSUBSCRIBED: return __('UNSUBSCRIBED', 'newsletter');
353
- break;
354
- case self::STATUS_BOUNCED: return __('BOUNCED', 'newsletter');
355
- break;
356
- case self::STATUS_COMPLAINED: return __('COMPLAINED', 'newsletter');
357
- break;
358
- default:
359
- return __('Unknown', 'newsletter');
360
- }
361
- }
362
-
363
- }
364
-
365
- /**
366
- * @property int $id The email unique identifier
367
- * @property string $subject The email subject
368
- * @property string $message The email html message
369
- * @property int $track Check if the email stats should be active
370
- * @property array $options Email options
371
- * @property int $total Total emails to send
372
- * @property int $sent Total sent emails by now
373
- * @property int $open_count Total opened emails
374
- * @property int $click_count Total clicked emails
375
- * */
376
- class TNP_Email {
377
-
378
- const STATUS_DRAFT = 'new';
379
- const STATUS_SENT = 'sent';
380
- const STATUS_SENDING = 'sending';
381
- const STATUS_PAUSED = 'paused';
382
- const STATUS_ERROR = 'error';
383
-
384
- }
385
-
386
- class NewsletterModule {
387
-
388
- /**
389
- * @var NewsletterLogger
390
- */
391
- var $logger;
392
-
393
- /**
394
- * @var NewsletterLogger
395
- */
396
- var $admin_logger;
397
-
398
- /**
399
- * @var NewsletterStore
400
- */
401
- var $store;
402
-
403
- /**
404
- * The main module options
405
- * @var array
406
- */
407
- var $options;
408
-
409
- /**
410
- * @var string The module name
411
- */
412
- var $module;
413
-
414
- /**
415
- * The module version
416
- * @var string
417
- */
418
- var $version;
419
- var $old_version;
420
-
421
- /**
422
- * Prefix for all options stored on WordPress options table.
423
- * @var string
424
- */
425
- var $prefix;
426
-
427
- /**
428
- * @var NewsletterThemes
429
- */
430
- var $themes;
431
- var $components;
432
- static $current_language = '';
433
-
434
- function __construct($module, $version, $module_id = null, $components = array()) {
435
- $this->module = $module;
436
- $this->version = $version;
437
- $this->prefix = 'newsletter_' . $module;
438
- array_unshift($components, '');
439
- $this->components = $components;
440
-
441
- $this->logger = new NewsletterLogger($module);
442
-
443
- $this->options = $this->get_options();
444
- $this->store = NewsletterStore::singleton();
445
-
446
- //$this->logger->debug($module . ' constructed');
447
- // Version check
448
- if (is_admin()) {
449
- $this->admin_logger = new NewsletterLogger($module . '-admin');
450
- $this->old_version = get_option($this->prefix . '_version', '0.0.0');
451
-
452
- if ($this->old_version == '0.0.0') {
453
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
454
- $this->first_install();
455
- update_option($this->prefix . "_first_install_time", time(), FALSE);
456
- }
457
-
458
- if (strcmp($this->old_version, $this->version) != 0) {
459
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
460
- $this->logger->info('Version changed from ' . $this->old_version . ' to ' . $this->version);
461
- // Do all the stuff for this version change
462
- $this->upgrade();
463
- update_option($this->prefix . '_version', $this->version);
464
- }
465
-
466
- add_action('admin_menu', array($this, 'admin_menu'));
467
- }
468
- }
469
-
470
- /**
471
- *
472
- * @global wpdb $wpdb
473
- * @param string $query
474
- */
475
- function query($query) {
476
- global $wpdb;
477
-
478
- $this->logger->debug($query);
479
- //$start = microtime(true);
480
- $r = $wpdb->query($query);
481
- //$this->logger->debug($wpdb->last_query);
482
- //$this->logger->debug('Execution time: ' . (microtime(true)-$start));
483
- //$this->logger->debug('Result: ' . $r);
484
- if ($r === false) {
485
- $this->logger->fatal($query);
486
- $this->logger->fatal($wpdb->last_error);
487
- }
488
- return $r;
489
- }
490
-
491
- function get_results($query) {
492
- global $wpdb;
493
- $r = $wpdb->get_results($query);
494
- if ($r === false) {
495
- $this->logger->fatal($query);
496
- $this->logger->fatal($wpdb->last_error);
497
- }
498
- return $r;
499
- }
500
-
501
- /**
502
- *
503
- * @global wpdb $wpdb
504
- * @param string $table
505
- * @param array $data
506
- */
507
- function insert($table, $data) {
508
- global $wpdb;
509
- $this->logger->debug("inserting into table $table");
510
- $r = $wpdb->insert($table, $data);
511
- if ($r === false) {
512
- $this->logger->fatal($wpdb->last_error);
513
- }
514
- }
515
-
516
- function first_install() {
517
- $this->logger->debug('First install');
518
- }
519
-
520
- /**
521
- * Does a basic upgrade work, checking if the options is already present and if not (first
522
- * installation), recovering the defaults, saving them on database and initializing the
523
- * internal $options.
524
- */
525
- function upgrade() {
526
- foreach ($this->components as $component) {
527
- $this->logger->debug('Upgrading component ' . $component);
528
- $this->init_options($component);
529
- }
530
- }
531
-
532
- function init_options($component = '', $autoload = true) {
533
- global $wpdb;
534
- $default_options = $this->get_default_options($component);
535
- $options = $this->get_options($component);
536
- $options = array_merge($default_options, $options);
537
- $this->save_options($options, $component, $autoload);
538
- }
539
-
540
- function upgrade_query($query) {
541
- global $wpdb, $charset_collate;
542
-
543
- $this->logger->info('upgrade_query> Executing ' . $query);
544
- $suppress_errors = $wpdb->suppress_errors(true);
545
- $wpdb->query($query);
546
- if ($wpdb->last_error) {
547
- $this->logger->debug($wpdb->last_error);
548
- }
549
- $wpdb->suppress_errors($suppress_errors);
550
- }
551
-
552
- /** Returns a prefix to be used for option names and other things which need to be uniquely named. The parameter
553
- * "sub" should be used when a sub name is needed for another set of options or like.
554
- *
555
- * @param string $sub
556
- * @return string The prefix for names
557
- */
558
- function get_prefix($sub = '', $language = '') {
559
- return $this->prefix . (!empty($sub) ? '_' : '') . $sub . (!empty($language) ? '_' : '') . $language;
560
- }
561
-
562
- /**
563
- * Returns the options of a module, if not found an empty array.
564
- */
565
- function get_options($sub = '', $language = '') {
566
- $options = get_option($this->get_prefix($sub, $language), array());
567
- // Protection against scarmled database...
568
- if (!is_array($options)) {
569
- $options = array();
570
- }
571
- if ($language) {
572
- $main_options = get_option($this->get_prefix($sub));
573
- // Protection against scarmled database...
574
- if (!is_array($main_options))
575
- $main_options = array();
576
- //$options = array_merge($main_options, array_filter($options));
577
- $options = array_merge($main_options, $options);
578
- }
579
- return $options;
580
- }
581
-
582
- function get_default_options($sub = '') {
583
- if (!empty($sub)) {
584
- $sub = '-' . $sub;
585
- }
586
- $file = NEWSLETTER_DIR . '/' . $this->module . '/defaults' . $sub . '.php';
587
- if (file_exists($file)) {
588
- @include $file;
589
- }
590
-
591
- if (!isset($options) || !is_array($options)) {
592
- return array();
593
- }
594
- return $options;
595
- }
596
-
597
- function reset_options($sub = '') {
598
- $this->save_options(array_merge($this->get_options($sub), $this->get_default_options($sub)), $sub);
599
- return $this->get_options($sub);
600
- }
601
-
602
- /**
603
- * Saves the module options (or eventually a subset names as per parameter $sub). $options
604
- * should be an array (even if it can work with non array options.
605
- * The internal module options variable IS initialized with those new options only for the main
606
- * options (empty $sub parameter).
607
- * If the options contain a "theme" value, the theme-related options contained are saved as well
608
- * (used by some modules).
609
- *
610
- * @param array $options
611
- * @param string $sub
612
- */
613
- function save_options($options, $sub = '', $autoload = null, $language = '') {
614
- update_option($this->get_prefix($sub, $language), $options, $autoload);
615
- if (empty($sub) && empty($language)) {
616
- $this->options = $options;
617
- if (isset($this->themes) && isset($options['theme'])) {
618
- $this->themes->save_options($options['theme'], $options);
619
- }
620
- }
621
- }
622
-
623
- function delete_options($sub = '') {
624
- delete_option($this->get_prefix($sub));
625
- if (empty($sub)) {
626
- $this->options = array();
627
- }
628
- }
629
-
630
- function merge_options($options, $sub = '', $language = '') {
631
- if (!is_array($options)) {
632
- $options = array();
633
- }
634
- $old_options = $this->get_options($sub, $language);
635
- $this->save_options(array_merge($old_options, $options), $sub, null, $language);
636
- }
637
-
638
- function backup_options($sub) {
639
- $options = $this->get_options($sub);
640
- update_option($this->get_prefix($sub) . '_backup', $options, false);
641
- }
642
-
643
- function get_last_run($sub = '') {
644
- return get_option($this->get_prefix($sub) . '_last_run', 0);
645
- }
646
-
647
- /**
648
- * Save the module last run value. Used to store a timestamp for some modules,
649
- * for example the Feed by Mail module.
650
- *
651
- * @param int $time Unix timestamp (as returned by time() for example)
652
- * @param string $sub Sub module name (default empty)
653
- */
654
- function save_last_run($time, $sub = '') {
655
- update_option($this->get_prefix($sub) . '_last_run', $time);
656
- }
657
-
658
- /**
659
- * Sums $delta seconds to the last run time.
660
- * @param int $delta Seconds
661
- * @param string $sub Sub module name (default empty)
662
- */
663
- function add_to_last_run($delta, $sub = '') {
664
- $time = $this->get_last_run($sub);
665
- $this->save_last_run($time + $delta, $sub);
666
- }
667
-
668
- /**
669
- * Checks if the semaphore of that name (for this module) is still red. If it is active the method
670
- * returns false. If it is not active, it will be activated for $time seconds.
671
- *
672
- * Since this method activate the semaphore when called, it's name is a bit confusing.
673
- *
674
- * @param string $name Sempahore name (local to this module)
675
- * @param int $time Max time in second this semaphore should stay red
676
- * @return boolean False if the semaphore is red and you should not proceed, true is it was not active and has been activated.
677
- */
678
- function check_transient($name, $time) {
679
- if ($time < 60)
680
- $time = 60;
681
- //usleep(rand(0, 1000000));
682
- if (($value = get_transient($this->get_prefix() . '_' . $name)) !== false) {
683
- list($t, $v) = explode(';', $value, 2);
684
- $this->logger->error('Blocked by transient ' . $this->get_prefix() . '_' . $name . ' set ' . (time() - $t) . ' seconds ago by ' . $v);
685
- return false;
686
- }
687
- //$ip = ''; //gethostbyname(gethostname());
688
- $value = time() . ";" . ABSPATH . ';' . gethostname();
689
- set_transient($this->get_prefix() . '_' . $name, $value, $time);
690
- return true;
691
- }
692
-
693
- function delete_transient($name = '') {
694
- delete_transient($this->get_prefix() . '_' . $name);
695
- }
696
-
697
- /** Returns a random token of the specified size (or 10 characters if size is not specified).
698
- *
699
- * @param int $size
700
- * @return string
701
- */
702
- static function get_token($size = 10) {
703
- return substr(md5(rand()), 0, $size);
704
- }
705
-
706
- /**
707
- * Adds query string parameters to an URL checing id there are already other parameters.
708
- *
709
- * @param string $url
710
- * @param string $qs The part of query-string to add (param1=value1&param2=value2...)
711
- * @param boolean $amp If the method must use the &amp; instead of the plain & (default true)
712
- * @return string
713
- */
714
- static function add_qs($url, $qs, $amp = true) {
715
- if (strpos($url, '?') !== false) {
716
- if ($amp)
717
- return $url . '&amp;' . $qs;
718
- else
719
- return $url . '&' . $qs;
720
- } else
721
- return $url . '?' . $qs;
722
- }
723
-
724
- /**
725
- * Returns the email address normalized, lowercase with no spaces. If it's not a valid email
726
- * returns false.
727
- */
728
- static function normalize_email($email) {
729
- if (!is_string($email)) {
730
- return false;
731
- }
732
- $email = strtolower(trim($email));
733
- if (!is_email($email)) {
734
- return false;
735
- }
736
- //$email = apply_filters('newsletter_normalize_email', $email);
737
- return $email;
738
- }
739
-
740
- static function normalize_name($name) {
741
- $name = html_entity_decode($name, ENT_QUOTES);
742
- $name = str_replace(';', ' ', $name);
743
- $name = strip_tags($name);
744
-
745
- return $name;
746
- }
747
-
748
- static function normalize_sex($sex) {
749
- $sex = trim(strtolower($sex));
750
- if ($sex != 'f' && $sex != 'm') {
751
- $sex = 'n';
752
- }
753
- return $sex;
754
- }
755
-
756
- static function is_email($email, $empty_ok = false) {
757
-
758
- if (!is_string($email)) {
759
- return false;
760
- }
761
- $email = strtolower(trim($email));
762
-
763
- if ($email == '') {
764
- return $empty_ok;
765
- }
766
-
767
- if (!is_email($email)) {
768
- return false;
769
- }
770
- return true;
771
- }
772
-
773
- /**
774
- * Converts a GMT date from mysql (see the posts table columns) into a timestamp.
775
- *
776
- * @param string $s GMT date with format yyyy-mm-dd hh:mm:ss
777
- * @return int A timestamp
778
- */
779
- static function m2t($s) {
780
-
781
- // TODO: use the wordpress function I don't remember the name
782
- $s = explode(' ', $s);
783
- $d = explode('-', $s[0]);
784
- $t = explode(':', $s[1]);
785
- return gmmktime((int) $t[0], (int) $t[1], (int) $t[2], (int) $d[1], (int) $d[2], (int) $d[0]);
786
- }
787
-
788
- static function format_date($time) {
789
- if (empty($time)) {
790
- return '-';
791
- }
792
- return gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
793
- }
794
-
795
- static function format_time_delta($delta) {
796
- $days = floor($delta / (3600 * 24));
797
- $hours = floor(($delta % (3600 * 24)) / 3600);
798
- $minutes = floor(($delta % 3600) / 60);
799
- $seconds = floor(($delta % 60));
800
- $buffer = $days . ' days, ' . $hours . ' hours, ' . $minutes . ' minutes, ' . $seconds . ' seconds';
801
- return $buffer;
802
- }
803
-
804
- /**
805
- * Formats a scheduler returned "next execution" time, managing negative or false values. Many times
806
- * used in conjuction with "last run".
807
- *
808
- * @param string $name The scheduler name
809
- * @return string
810
- */
811
- static function format_scheduler_time($name) {
812
- $time = wp_next_scheduled($name);
813
- if ($time === false) {
814
- return 'No next run scheduled';
815
- }
816
- $delta = $time - time();
817
- // If less 10 minutes late it can be a cron problem but now it is working
818
- if ($delta < 0 && $delta > -600) {
819
- return 'Probably running now';
820
- } else if ($delta <= -600) {
821
- return 'It seems the cron system is not working. Reload the page to see if this message change.';
822
- }
823
- return 'Runs in ' . self::format_time_delta($delta);
824
- }
825
-
826
- static function date($time = null, $now = false, $left = false) {
827
- if (is_null($time)) {
828
- $time = time();
829
- }
830
- if ($time == false) {
831
- $buffer = 'none';
832
- } else {
833
- $buffer = gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
834
- }
835
- if ($now) {
836
- $buffer .= ' (now: ' . gmdate(get_option('date_format') . ' ' .
837
- get_option('time_format'), time() + get_option('gmt_offset') * 3600);
838
- $buffer .= ')';
839
- }
840
- if ($left) {
841
- $buffer .= ', ' . gmdate('H:i:s', $time - time()) . ' left';
842
- }
843
- return $buffer;
844
- }
845
-
846
- /**
847
- * Return an array of array with on first element the array of recent post and on second element the array
848
- * of old posts.
849
- *
850
- * @param array $posts
851
- * @param int $time
852
- */
853
- static function split_posts(&$posts, $time = 0) {
854
- if ($time < 0) {
855
- return array_chunk($posts, ceil(count($posts) / 2));
856
- }
857
-
858
- $result = array(array(), array());
859
-
860
- if (empty($posts))
861
- return $result;
862
-
863
- foreach ($posts as &$post) {
864
- if (self::is_post_old($post, $time))
865
- $result[1][] = $post;
866
- else
867
- $result[0][] = $post;
868
- }
869
- return $result;
870
- }
871
-
872
- static function is_post_old(&$post, $time = 0) {
873
- return self::m2t($post->post_date_gmt) <= $time;
874
- }
875
-
876
- static function get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
877
- global $post;
878
-
879
- if (empty($post_id))
880
- $post_id = $post->ID;
881
- if (empty($post_id))
882
- return $alternative;
883
-
884
- $image_id = function_exists('get_post_thumbnail_id') ? get_post_thumbnail_id($post_id) : false;
885
- if ($image_id) {
886
- $image = wp_get_attachment_image_src($image_id, $size);
887
- return $image[0];
888
- } else {
889
- $attachments = get_children(array('post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID'));
890
-
891
- if (empty($attachments)) {
892
- return $alternative;
893
- }
894
-
895
- foreach ($attachments as $id => $attachment) {
896
- $image = wp_get_attachment_image_src($id, $size);
897
- return $image[0];
898
- }
899
- }
900
- }
901
-
902
- /**
903
- * Cleans up a text containing url tags with appended the absolute URL (due to
904
- * the editor behavior) moving back them to the simple form.
905
- */
906
- static function clean_url_tags($text) {
907
- $text = str_replace('%7B', '{', $text);
908
- $text = str_replace('%7D', '}', $text);
909
-
910
- // Only tags which are {*_url}
911
- $text = preg_replace("/[\"']http[^\"']+(\\{[^\\}]+_url\\})[\"']/i", "\"\\1\"", $text);
912
- return $text;
913
- }
914
-
915
- function admin_menu() {
916
-
917
- }
918
-
919
- function add_menu_page($page, $title, $capability = '') {
920
- if (!Newsletter::instance()->is_allowed())
921
- return;
922
- $name = 'newsletter_' . $this->module . '_' . $page;
923
- add_submenu_page('newsletter_main_index', $title, $title, 'exist', $name, array($this, 'menu_page'));
924
- }
925
-
926
- function add_admin_page($page, $title) {
927
- if (!Newsletter::instance()->is_allowed()) {
928
- return;
929
- }
930
- $name = 'newsletter_' . $this->module . '_' . $page;
931
- add_submenu_page(null, $title, $title, 'exist', $name, array($this, 'menu_page'));
932
- }
933
-
934
- function sanitize_file_name($name) {
935
- return preg_replace('/[^a-z_\\-]/i', '', $name);
936
- }
937
-
938
- function menu_page() {
939
- global $plugin_page, $newsletter, $wpdb;
940
-
941
- $parts = explode('_', $plugin_page, 3);
942
- $module = $this->sanitize_file_name($parts[1]);
943
- $page = $this->sanitize_file_name($parts[2]);
944
- $page = str_replace('_', '-', $page);
945
-
946
- $file = NEWSLETTER_DIR . '/' . $module . '/' . $page . '.php';
947
-
948
- require $file;
949
- }
950
-
951
- function get_admin_page_url($page) {
952
- return admin_url('admin.php') . '?page=newsletter_' . $this->module . '_' . $page;
953
- }
954
-
955
- /** Returns all the emails of the give type (message, feed, followup, ...) and in the given format
956
- * (default as objects). Return false on error or at least an empty array. Errors should never
957
- * occur.
958
- *
959
- * @global wpdb $wpdb
960
- * @param string $type
961
- * @return boolean|array
962
- */
963
- function get_emails($type = null, $format = OBJECT) {
964
- global $wpdb;
965
- if ($type == null) {
966
- $list = $wpdb->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " order by id desc", $format);
967
- } else {
968
- $type = (string) $type;
969
- $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where type=%s order by id desc", $type), $format);
970
- }
971
- if ($wpdb->last_error) {
972
- $this->logger->error($wpdb->last_error);
973
- return false;
974
- }
975
- if (empty($list)) {
976
- return [];
977
- }
978
- return $list;
979
- }
980
-
981
- function get_emails_by_status($status) {
982
- global $wpdb;
983
- $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where status=%s order by id desc", $status));
984
-
985
- array_walk($list, function ($email) {
986
- $email->options = maybe_unserialize($email->options);
987
- if (!is_array($email->options)) {
988
- $email->options = [];
989
- }
990
- });
991
- return $list;
992
- }
993
-
994
- /**
995
- * @param string $key
996
- * @param mixed $value
997
- * @return TNP_Email[]
998
- */
999
- // function get_emails_by_field($key, $value) {
1000
- // global $wpdb;
1001
- //
1002
- // $value_placeholder = is_int($value) ? '%d' : '%s';
1003
- //
1004
- // $key = '`' . str_replace('`', '', $key) . '`';
1005
- //
1006
- // $query = $wpdb->prepare("SELECT * FROM " . NEWSLETTER_EMAILS_TABLE . " WHERE $key=$value_placeholder ORDER BY id DESC", $value);
1007
- // //die($query);
1008
- //
1009
- // $email_list = $wpdb->get_results($query);
1010
- //
1011
- // if ($wpdb->last_error) {
1012
- // $this->logger->error($wpdb->last_error);
1013
- //
1014
- // return [];
1015
- // }
1016
- //
1017
- // //Unserialize options
1018
- // array_walk($email_list, function ($email) {
1019
- // $email->options = maybe_unserialize($email->options);
1020
- // if (!is_array($email->options)) {
1021
- // $email->options = [];
1022
- // }
1023
- // });
1024
- //
1025
- // return $email_list;
1026
- // }
1027
-
1028
- /**
1029
- * Retrieves an email from DB and unserialize the options.
1030
- *
1031
- * @param mixed $id
1032
- * @param string $format
1033
- * @return TNP_Email An object with the same fields of TNP_Email, but not actually of that type
1034
- */
1035
- function get_email($id, $format = OBJECT) {
1036
- $email = $this->store->get_single(NEWSLETTER_EMAILS_TABLE, $id, $format);
1037
- if (!$email) {
1038
- return null;
1039
- }
1040
- if ($format == OBJECT) {
1041
- $email->options = maybe_unserialize($email->options);
1042
- if (!is_array($email->options)) {
1043
- $email->options = array();
1044
- }
1045
- if (empty($email->query)) {
1046
- $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1047
- }
1048
- } else if ($format == ARRAY_A) {
1049
- $email['options'] = maybe_unserialize($email['options']);
1050
- if (!is_array($email['options'])) {
1051
- $email['options'] = array();
1052
- }
1053
- if (empty($email['query'])) {
1054
- $email['query'] = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1055
- }
1056
- }
1057
- return $email;
1058
- }
1059
-
1060
- /**
1061
- * Save an email and provide serialization, if needed, of $email['options'].
1062
- * @return TNP_Email
1063
- */
1064
- function save_email($email, $return_format = OBJECT) {
1065
- if (is_object($email)) {
1066
- $email = (array) $email;
1067
- }
1068
-
1069
- if (isset($email['subject'])) {
1070
- if (mb_strlen($email['subject'], 'UTF-8') > 250) {
1071
- $email['subject'] = mb_substr($email['subject'], 0, 250, 'UTF-8');
1072
- }
1073
- }
1074
- if (isset($email['options']) && is_array($email['options'])) {
1075
- $email['options'] = serialize($email['options']);
1076
- }
1077
- $email = $this->store->save(NEWSLETTER_EMAILS_TABLE, $email, $return_format);
1078
- if ($return_format == OBJECT) {
1079
- $email->options = maybe_unserialize($email->options);
1080
- if (!is_array($email->options)) {
1081
- $email->options = [];
1082
- }
1083
- } else if ($return_format == ARRAY_A) {
1084
- $email['options'] = maybe_unserialize($email['options']);
1085
- if (!is_array($email['options'])) {
1086
- $email['options'] = [];
1087
- }
1088
- }
1089
- return $email;
1090
- }
1091
-
1092
- function get_email_from_request() {
1093
-
1094
- if (isset($_REQUEST['nek'])) {
1095
- list($id, $token) = @explode('-', $_REQUEST['nek'], 2);
1096
- } else if (isset($_COOKIE['tnpe'])) {
1097
- list($id, $token) = @explode('-', $_COOKIE['tnpe'], 2);
1098
- } else {
1099
- return null;
1100
- }
1101
-
1102
- $email = $this->get_email($id);
1103
-
1104
- // TODO: Check the token? It's really useful?
1105
-
1106
- return $email;
1107
- }
1108
-
1109
- /**
1110
- * Delete one or more emails identified by ID (single value or array of ID)
1111
- *
1112
- * @global wpdb $wpdb
1113
- * @param int|array $id Single numeric ID or an array of IDs to be deleted
1114
- * @return boolean
1115
- */
1116
- function delete_email($id) {
1117
- global $wpdb;
1118
- $r = $this->store->delete(NEWSLETTER_EMAILS_TABLE, $id);
1119
- if ($r !== false) {
1120
- // $id could be an array if IDs
1121
- $id = (array) $id;
1122
- foreach ($id as $email_id) {
1123
- $wpdb->delete(NEWSLETTER_STATS_TABLE, ['email_id' => $email_id]);
1124
- $wpdb->delete(NEWSLETTER_SENT_TABLE, ['email_id' => $email_id]);
1125
- }
1126
- }
1127
- return $r;
1128
- }
1129
-
1130
- function get_email_field($id, $field_name) {
1131
- return $this->store->get_field(NEWSLETTER_EMAILS_TABLE, $id, $field_name);
1132
- }
1133
-
1134
- function get_email_status_slug($email) {
1135
- $email = (object) $email;
1136
- if ($email->status == 'sending' && $email->send_on > time()) {
1137
- return 'scheduled';
1138
- }
1139
- return $email->status;
1140
- }
1141
-
1142
- function get_email_status_label($email) {
1143
- $email = (object) $email;
1144
- $status = $this->get_email_status_slug($email);
1145
- switch ($status) {
1146
- case 'sending':
1147
- return __('Sending', 'newsletter');
1148
- case 'scheduled':
1149
- return __('Scheduled', 'newsletter');
1150
- case 'sent':
1151
- return __('Sent', 'newsletter');
1152
- case 'paused':
1153
- return __('Paused', 'newsletter');
1154
- case 'new':
1155
- return __('Draft', 'newsletter');
1156
- default:
1157
- return ucfirst($email->status);
1158
- }
1159
- }
1160
-
1161
- function show_email_status_label($email) {
1162
- echo '<span class="tnp-email-status tnp-email-status--', $this->get_email_status_slug($email), '">', esc_html($this->get_email_status_label($email)), '</span>';
1163
- }
1164
-
1165
- function get_email_progress($email, $format = 'percent') {
1166
- return $email->total > 0 ? intval($email->sent / $email->total * 100) : 0;
1167
- }
1168
-
1169
- function show_email_progress_bar($email, $attrs = []) {
1170
-
1171
- $email = (object) $email;
1172
-
1173
- $attrs = array_merge(array('format' => 'percent', 'numbers' => false, 'scheduled' => false), $attrs);
1174
-
1175
- if ($email->status == 'sending' && $email->send_on > time()) {
1176
- if ($attrs['scheduled']) {
1177
- echo '<span class="tnp-progress-date">', $this->format_date($email->send_on), '</span>';
1178
- }
1179
- return;
1180
- } else if ($email->status == 'new') {
1181
- echo '';
1182
- return;
1183
- } else if ($email->status == 'sent') {
1184
- $percent = 100;
1185
- } else {
1186
- $percent = $this->get_email_progress($email);
1187
- }
1188
-
1189
- echo '<div class="tnp-progress tnp-progress--' . $email->status . '">';
1190
- echo '<div class="tnp-progress-bar" role="progressbar" style="width: ', $percent, '%;">&nbsp;', $percent, '%&nbsp;</div>';
1191
- echo '</div>';
1192
- if ($attrs['numbers']) {
1193
- if ($email->status == 'sent') {
1194
- echo '<div class="tnp-progress-numbers">', $email->total, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1195
- } else {
1196
- echo '<div class="tnp-progress-numbers">', $email->sent, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1197
- }
1198
- }
1199
- }
1200
-
1201
- function get_email_type_label($type) {
1202
-
1203
- // Is an email?
1204
- if (is_object($type))
1205
- $type = $type->type;
1206
-
1207
- $label = apply_filters('newsletter_email_type', '', $type);
1208
-
1209
- if (!empty($label))
1210
- return $label;
1211
-
1212
- switch ($type) {
1213
- case 'followup':
1214
- return 'Followup';
1215
- case 'message':
1216
- return 'Standard Newsletter';
1217
- case 'feed':
1218
- return 'Feed by Mail';
1219
- }
1220
-
1221
- if (strpos($type, 'automated') === 0) {
1222
- list($a, $id) = explode('_', $type);
1223
- return 'Automated Channel ' . $id;
1224
- }
1225
-
1226
- return ucfirst($type);
1227
- }
1228
-
1229
- function get_email_progress_label($email) {
1230
- if ($email->status == 'sent' || $email->status == 'sending') {
1231
- return $email->sent . ' ' . __('of', 'newsletter') . ' ' . $email->total;
1232
- }
1233
- return '-';
1234
- }
1235
-
1236
- /**
1237
- * Returns the email unique key
1238
- * @param TNP_User $user
1239
- * @return string
1240
- */
1241
- function get_email_key($email) {
1242
- if (!isset($email->token)) {
1243
- return $email->id . '-';
1244
- }
1245
- return $email->id . '-' . $email->token;
1246
- }
1247
-
1248
- /** Searches for a user using the nk parameter or the ni and nt parameters. Tries even with the newsletter cookie.
1249
- * If found, the user object is returned or null.
1250
- * The user is returned without regards to his status that should be checked by caller.
1251
- *
1252
- * DO NOT REMOVE EVEN IF OLD
1253
- *
1254
- * @return TNP_User
1255
- */
1256
- function check_user($context = '') {
1257
- global $wpdb;
1258
-
1259
- $user = null;
1260
-
1261
- if (isset($_REQUEST['nk'])) {
1262
- list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1263
- } else if (isset($_COOKIE['newsletter'])) {
1264
- list ($id, $token) = @explode('-', $_COOKIE['newsletter'], 2);
1265
- }
1266
-
1267
- if (isset($id)) {
1268
- $user = $this->get_user($id);
1269
- if ($user) {
1270
- if ($context == 'preconfirm') {
1271
- if ($token != md5($user->token)) {
1272
- $user = null;
1273
- }
1274
- } else {
1275
- if ($token != $user->token) {
1276
- $user = null;
1277
- }
1278
- }
1279
- }
1280
- }
1281
-
1282
- if ($user == null && is_user_logged_in()) {
1283
- $user = $this->get_user_by_wp_user_id(get_current_user_id());
1284
- }
1285
- return $user;
1286
- }
1287
-
1288
- /** Returns the user identify by an id or an email. If $id_or_email is an object or an array, it is assumed it contains
1289
- * the "id" attribute or key and that is used to load the user.
1290
- *
1291
- * @global type $wpdb
1292
- * @param string|int|object|array $id_or_email
1293
- * @param string $format
1294
- * @return TNP_User|null
1295
- */
1296
- function get_user($id_or_email, $format = OBJECT) {
1297
- global $wpdb;
1298
-
1299
- if (empty($id_or_email))
1300
- return null;
1301
-
1302
- // To simplify the reaload of a user passing the user it self.
1303
- if (is_object($id_or_email)) {
1304
- $id_or_email = $id_or_email->id;
1305
- } else if (is_array($id_or_email)) {
1306
- $id_or_email = $id_or_email['id'];
1307
- }
1308
-
1309
- $id_or_email = strtolower(trim($id_or_email));
1310
-
1311
- if (is_numeric($id_or_email)) {
1312
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where id=%d limit 1", $id_or_email), $format);
1313
- } else {
1314
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $id_or_email), $format);
1315
- }
1316
-
1317
- if ($wpdb->last_error) {
1318
- $this->logger->error($wpdb->last_error);
1319
- return null;
1320
- }
1321
- return $r;
1322
- }
1323
-
1324
- /**
1325
- *
1326
- * @global wpdb $wpdb
1327
- * @param string $email
1328
- * @return TNP_User
1329
- */
1330
- function get_user_by_email($email) {
1331
- global $wpdb;
1332
-
1333
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $email));
1334
-
1335
- if ($wpdb->last_error) {
1336
- $this->logger->error($wpdb->last_error);
1337
- return null;
1338
- }
1339
- return $r;
1340
- }
1341
-
1342
- /**
1343
- * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
1344
- *
1345
- * @param type $user
1346
- */
1347
- function get_user_edit_url($user) {
1348
- $id = $this->to_int_id($user);
1349
- return admin_url('admin.php') . '?page=newsletter_users_edit&id=' . $id;
1350
- }
1351
-
1352
- /**
1353
- * Returns the user unique key
1354
- * @param TNP_User $user
1355
- * @return string
1356
- */
1357
- function get_user_key($user, $context = '') {
1358
- if (empty($user->token)) {
1359
- $this->refresh_user_token($user);
1360
- }
1361
-
1362
- if ($context == 'preconfirm') {
1363
- return $user->id . '-' . md5($user->token);
1364
- }
1365
- return $user->id . '-' . $user->token;
1366
- }
1367
-
1368
- function get_user_status_label($user, $html = false) {
1369
- if (!$html)
1370
- return TNP_User::get_status_label($user->status);
1371
-
1372
- $label = TNP_User::get_status_label($user->status);
1373
- $class = 'unknown';
1374
- switch ($user->status) {
1375
- case TNP_User::STATUS_NOT_CONFIRMED: $class = 'not-confirmed';
1376
- break;
1377
- case TNP_User::STATUS_CONFIRMED: $class = 'confirmed';
1378
- break;
1379
- case TNP_User::STATUS_UNSUBSCRIBED: $class = 'unsubscribed';
1380
- break;
1381
- case TNP_User::STATUS_BOUNCED: $class = 'bounced';
1382
- break;
1383
- case TNP_User::STATUS_COMPLAINED: $class = 'complained';
1384
- break;
1385
- }
1386
- return '<span class="' . $class . '">' . esc_html($label) . '</span>';
1387
- }
1388
-
1389
- /**
1390
- * Return the user identified by the "nk" parameter (POST or GET).
1391
- * If no user can be found or the token is not matching, returns null.
1392
- * If die_on_fail is true it dies instead of return null.
1393
- *
1394
- * @param bool $die_on_fail
1395
- * @return TNP_User
1396
- */
1397
- function get_user_from_request($die_on_fail = false, $context = '') {
1398
- $id = 0;
1399
- if (isset($_REQUEST['nk'])) {
1400
- list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1401
- }
1402
- $user = $this->get_user($id);
1403
-
1404
- if ($user == null) {
1405
- if ($die_on_fail) {
1406
- die(__('No subscriber found.', 'newsletter'));
1407
- } else {
1408
- return $this->get_user_from_logged_in_user();
1409
- }
1410
- }
1411
-
1412
- if ($context == 'preconfirm') {
1413
- $user_token = md5($user->token);
1414
- } else {
1415
- $user_token = $user->token;
1416
- }
1417
-
1418
- if ($token != $user_token) {
1419
- if ($die_on_fail) {
1420
- die(__('No subscriber found.', 'newsletter'));
1421
- } else {
1422
- return $this->get_user_from_logged_in_user();
1423
- }
1424
- }
1425
- return $user;
1426
- }
1427
-
1428
- function get_user_from_logged_in_user() {
1429
- if (is_user_logged_in()) {
1430
- return $this->get_user_by_wp_user_id(get_current_user_id());
1431
- }
1432
- return null;
1433
- }
1434
-
1435
- function get_user_count($refresh = false) {
1436
- global $wpdb;
1437
- $user_count = get_transient('newsletter_user_count');
1438
- if ($user_count === false || $refresh) {
1439
- $user_count = $wpdb->get_var("select count(*) from " . NEWSLETTER_USERS_TABLE . " where status='C'");
1440
- set_transient('newsletter_user_count', $user_count, DAY_IN_SECONDS);
1441
- }
1442
- return $user_count;
1443
- }
1444
-
1445
- function get_profile($id, $language = '') {
1446
- return TNP_Profile_Service::get_profile_by_id($id, $language);
1447
- }
1448
-
1449
- /**
1450
- * @param string $language The language for the list labels (it does not affect the lists returned)
1451
- * @return TNP_Profile[]
1452
- */
1453
- function get_profiles($language = '') {
1454
- return TNP_Profile_Service::get_profiles($language);
1455
- }
1456
-
1457
- /**
1458
- * Returns a list of TNP_Profile which are public.
1459
- *
1460
- * @staticvar array $profiles
1461
- * @param string $language
1462
- * @return TNP_Profile[]
1463
- */
1464
- function get_profiles_public($language = '') {
1465
- static $profiles = [];
1466
- if (isset($profiles[$language])) {
1467
- return $profiles[$language];
1468
- }
1469
-
1470
- $profiles[$language] = [];
1471
- $all = $this->get_profiles($language);
1472
- foreach ($all as $profile) {
1473
- if ($profile->is_private())
1474
- continue;
1475
-
1476
- $profiles[$language]['' . $profile->id] = $profile;
1477
- }
1478
- return $profiles[$language];
1479
- }
1480
-
1481
- /**
1482
- * Really bad name!
1483
- * @staticvar array $profiles
1484
- * @param type $language
1485
- * @return array
1486
- */
1487
- function get_profiles_for_profile($language = '') {
1488
- static $profiles = [];
1489
- if (isset($profiles[$language])) {
1490
- return $profiles[$language];
1491
- }
1492
-
1493
- $profiles[$language] = [];
1494
- $all = $this->get_profiles($language);
1495
- foreach ($all as $profile) {
1496
- if (!$profile->show_on_profile())
1497
- continue;
1498
-
1499
- $profiles[$language]['' . $profile->id] = $profile;
1500
- }
1501
- return $profiles[$language];
1502
- }
1503
-
1504
- /**
1505
- * @param string $language The language for the list labels (it does not affect the lists returned)
1506
- * @return TNP_List[]
1507
- */
1508
- function get_lists($language = '') {
1509
- static $lists = array();
1510
- if (isset($lists[$language])) {
1511
- return $lists[$language];
1512
- }
1513
-
1514
- $lists[$language] = array();
1515
- $data = NewsletterSubscription::instance()->get_options('lists', $language);
1516
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
1517
- if (empty($data['list_' . $i])) {
1518
- continue;
1519
- }
1520
- $list = $this->create_tnp_list_from_db_lists_array($data, $i);
1521
-
1522
- $lists[$language]['' . $list->id] = $list;
1523
- }
1524
- return $lists[$language];
1525
- }
1526
-
1527
- public function create_tnp_list_from_db_lists_array($db_lists_array, $list_id) {
1528
-
1529
- $list = new TNP_List();
1530
- $list->name = $db_lists_array['list_' . $list_id];
1531
- $list->id = $list_id;
1532
-
1533
- // New format
1534
- if (isset($db_lists_array['list_' . $list_id . '_subscription'])) {
1535
- $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1536
- $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1537
- $list->checked = $db_lists_array['list_' . $list_id . '_subscription'] == 2;
1538
- $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_subscription']) && !$list->forced;
1539
- $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_profile']);
1540
- } else {
1541
- $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1542
- $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1543
- $list->checked = !empty($db_lists_array['list_' . $list_id . '_checked']);
1544
- $list->show_on_subscription = $db_lists_array['list_' . $list_id . '_status'] == 2 && !$list->forced;
1545
- $list->show_on_profile = $db_lists_array['list_' . $list_id . '_status'] == 1 || $db_lists_array['list_' . $list_id . '_status'] == 2;
1546
- }
1547
- if (empty($db_lists_array['list_' . $list_id . '_languages'])) {
1548
- $list->languages = array();
1549
- } else {
1550
- $list->languages = $db_lists_array['list_' . $list_id . '_languages'];
1551
- }
1552
-
1553
- return $list;
1554
- }
1555
-
1556
- /**
1557
- * Returns an array of TNP_List objects of lists that are public.
1558
- * @return TNP_List[]
1559
- */
1560
- function get_lists_public($language = '') {
1561
- static $lists = array();
1562
- if (isset($lists[$language])) {
1563
- return $lists[$language];
1564
- }
1565
-
1566
- $lists[$language] = array();
1567
- $all = $this->get_lists($language);
1568
- foreach ($all as $list) {
1569
- if ($list->status == TNP_List::STATUS_PRIVATE) {
1570
- continue;
1571
- }
1572
- $lists[$language]['' . $list->id] = $list;
1573
- }
1574
- return $lists[$language];
1575
- }
1576
-
1577
- /**
1578
- * Lists to be shown on subscription form.
1579
- *
1580
- * @return TNP_List[]
1581
- */
1582
- function get_lists_for_subscription($language = '') {
1583
- static $lists = array();
1584
- if (isset($lists[$language])) {
1585
- return $lists[$language];
1586
- }
1587
-
1588
- $lists[$language] = array();
1589
- $all = $this->get_lists($language);
1590
- foreach ($all as $list) {
1591
- if (!$list->show_on_subscription) {
1592
- continue;
1593
- }
1594
- $lists[$language]['' . $list->id] = $list;
1595
- }
1596
- return $lists[$language];
1597
- }
1598
-
1599
- /**
1600
- * Returns the lists to be shown in the profile page. The list is associative with
1601
- * the list ID as key.
1602
- *
1603
- * @return TNP_List[]
1604
- */
1605
- function get_lists_for_profile($language = '') {
1606
- static $lists = array();
1607
- if (isset($lists[$language])) {
1608
- return $lists[$language];
1609
- }
1610
-
1611
- $lists[$language] = array();
1612
- $all = $this->get_lists($language);
1613
- foreach ($all as $list) {
1614
- if (!$list->show_on_profile) {
1615
- continue;
1616
- }
1617
- $lists[$language]['' . $list->id] = $list;
1618
- }
1619
- return $lists[$language];
1620
- }
1621
-
1622
- /**
1623
- * Returns the list object or null if not found.
1624
- *
1625
- * @param int $id
1626
- * @return TNP_List
1627
- */
1628
- function get_list($id, $language = '') {
1629
- $lists = $this->get_lists($language);
1630
- if (!isset($lists['' . $id])) {
1631
- return null;
1632
- }
1633
-
1634
- return $lists['' . $id];
1635
- }
1636
-
1637
- /**
1638
- * NEVER CHANGE THIS METHOD SIGNATURE, USER BY THIRD PARTY PLUGINS.
1639
- *
1640
- * Saves a new user on the database. Return false if the email (that must be unique) is already
1641
- * there. For a new users set the token and creation time if not passed.
1642
- *
1643
- * @param array $user
1644
- * @return TNP_User|array|boolean Returns the subscriber reloaded from DB in the specified format. Flase on failure (duplicate email).
1645
- */
1646
- function save_user($user, $return_format = OBJECT) {
1647
- if (is_object($user)) {
1648
- $user = (array) $user;
1649
- }
1650
- if (empty($user['id'])) {
1651
- $existing = $this->get_user($user['email']);
1652
- if ($existing != null) {
1653
- return false;
1654
- }
1655
- if (empty($user['token'])) {
1656
- $user['token'] = NewsletterModule::get_token();
1657
- }
1658
- }
1659
-
1660
- // We still don't know when it happens but under some conditions, matbe external, lists are passed as NULL
1661
- foreach ($user as $key => $value) {
1662
- if (strpos($key, 'list_') !== 0) {
1663
- continue;
1664
- }
1665
- if (is_null($value)) {
1666
- unset($user[$key]);
1667
- } else {
1668
- $user[$key] = (int) $value;
1669
- }
1670
- }
1671
-
1672
- // Due to the unique index on email field, this can fail.
1673
- return $this->store->save(NEWSLETTER_USERS_TABLE, $user, $return_format);
1674
- }
1675
-
1676
- /**
1677
- * Updates the user last activity timestamp.
1678
- *
1679
- * @global wpdb $wpdb
1680
- * @param TNP_User $user
1681
- */
1682
- function update_user_last_activity($user) {
1683
- global $wpdb;
1684
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set last_activity=%d where id=%d limit 1", time(), $user->id));
1685
- }
1686
-
1687
- function update_user_ip($user, $ip) {
1688
- global $wpdb;
1689
- // Only if changed
1690
- $r = $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set ip=%s, geo=0 where ip<>%s and id=%d limit 1", $ip, $ip, $user->id));
1691
- }
1692
-
1693
- /**
1694
- * Finds single style blocks and adds a style attribute to every HTML tag with a class exactly matching the rules in the style
1695
- * block. HTML tags can use the attribute "inline-class" to exact match a style rules if they need a composite class definition.
1696
- *
1697
- * @param string $content
1698
- * @param boolean $strip_style_blocks
1699
- * @return string
1700
- */
1701
- function inline_css($content, $strip_style_blocks = false) {
1702
- $matches = array();
1703
- // "s" skips line breaks
1704
- $styles = preg_match('|<style>(.*?)</style>|s', $content, $matches);
1705
- if (isset($matches[1])) {
1706
- $style = str_replace(array("\n", "\r"), '', $matches[1]);
1707
- $rules = array();
1708
- preg_match_all('|\s*\.(.*?)\{(.*?)\}\s*|s', $style, $rules);
1709
- for ($i = 0; $i < count($rules[1]); $i++) {
1710
- $class = trim($rules[1][$i]);
1711
- $value = trim($rules[2][$i]);
1712
- $value = preg_replace('|\s+|', ' ', $value);
1713
- $content = str_replace(' class="' . $class . '"', ' class="' . $class . '" style="' . $value . '"', $content);
1714
- $content = str_replace(' inline-class="' . $class . '"', ' style="' . $value . '"', $content);
1715
- }
1716
- }
1717
-
1718
- if ($strip_style_blocks) {
1719
- return trim(preg_replace('|<style>.*?</style>|s', '', $content));
1720
- } else {
1721
- return $content;
1722
- }
1723
- }
1724
-
1725
- /**
1726
- * Returns a list of users marked as "test user".
1727
- * @return TNP_User[]
1728
- */
1729
- function get_test_users() {
1730
- return $this->store->get_all(NEWSLETTER_USERS_TABLE, "where test=1 and status in ('C', 'S')");
1731
- }
1732
-
1733
- /**
1734
- * Deletes a subscriber and cleans up all the stats table with his correlated data.
1735
- *
1736
- * @global wpdb $wpdb
1737
- * @param int|id[] $id
1738
- */
1739
- function delete_user($id) {
1740
- global $wpdb;
1741
- $id = (array) $id;
1742
- foreach ($id as $user_id) {
1743
- $user = $this->get_user($user_id);
1744
- if ($user) {
1745
- $r = $this->store->delete(NEWSLETTER_USERS_TABLE, $user_id);
1746
- $wpdb->delete(NEWSLETTER_STATS_TABLE, array('user_id' => $user_id));
1747
- $wpdb->delete(NEWSLETTER_SENT_TABLE, array('user_id' => $user_id));
1748
- do_action('newsletter_user_deleted', $user);
1749
- }
1750
- }
1751
-
1752
- return count($id);
1753
- }
1754
-
1755
- /**
1756
- * Add to a destination URL the parameters to identify the user, the email and to show
1757
- * an alert message, if required. The parameters are then managed by the [newsletter] shortcode.
1758
- *
1759
- * @param string $url If empty the standard newsletter page URL is used (usually it is empty, but sometime a custom URL has been specified)
1760
- * @param string $message_key The message identifier
1761
- * @param TNP_User|int $user
1762
- * @param TNP_Email|int $email
1763
- * @param string $alert An optional alter message to be shown. Does not work with custom URLs
1764
- * @return string The final URL with parameters
1765
- */
1766
- function build_message_url($url = '', $message_key = '', $user = null, $email = null, $alert = '') {
1767
- $params = 'nm=' . urlencode($message_key);
1768
- $language = '';
1769
- if ($user) {
1770
- if (!is_object($user)) {
1771
- $user = $this->get_user($user);
1772
- }
1773
- if ($message_key == 'confirmation') {
1774
- $params .= '&nk=' . urlencode($this->get_user_key($user, 'preconfirm'));
1775
- } else {
1776
- $params .= '&nk=' . urlencode($this->get_user_key($user));
1777
- }
1778
-
1779
- $language = $this->get_user_language($user);
1780
- }
1781
-
1782
- if ($email) {
1783
- if (!is_object($email)) {
1784
- $email = $this->get_email($email);
1785
- }
1786
- $params .= '&nek=' . urlencode($this->get_email_key($email));
1787
- }
1788
-
1789
- if ($alert) {
1790
- $params .= '&alert=' . urlencode($alert);
1791
- }
1792
-
1793
- if (empty($url)) {
1794
- $url = Newsletter::instance()->get_newsletter_page_url($language);
1795
- }
1796
-
1797
- return self::add_qs($url, $params, false);
1798
- }
1799
-
1800
- /**
1801
- * Builds a standard Newsletter action URL for the specified action.
1802
- *
1803
- * @param string $action
1804
- * @param TNP_User $user
1805
- * @param TNP_Email $email
1806
- * @return string
1807
- */
1808
- function build_action_url($action, $user = null, $email = null) {
1809
- $url = $this->add_qs($this->get_home_url(), 'na=' . urlencode($action));
1810
- //$url = $this->add_qs(admin_url('admin-ajax.php'), 'action=newsletter&na=' . urlencode($action));
1811
- if ($user) {
1812
- $url .= '&nk=' . urlencode($this->get_user_key($user));
1813
- }
1814
- if ($email) {
1815
- $url .= '&nek=' . urlencode($this->get_email_key($email));
1816
- }
1817
- return $url;
1818
- }
1819
-
1820
- function get_subscribe_url() {
1821
- return $this->build_action_url('s');
1822
- }
1823
-
1824
- function clean_stats_table() {
1825
- global $wpdb;
1826
- $this->logger->info('Cleaning up stats table');
1827
- $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
1828
- $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
1829
- }
1830
-
1831
- function clean_sent_table() {
1832
- global $wpdb;
1833
- $this->logger->info('Cleaning up sent table');
1834
- $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
1835
- $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
1836
- }
1837
-
1838
- function clean_user_logs_table() {
1839
- //global $wpdb;
1840
- }
1841
-
1842
- function clean_tables() {
1843
- $this->clean_sent_table();
1844
- $this->clean_stats_table();
1845
- $this->clean_user_logs_table();
1846
- }
1847
-
1848
- function anonymize_ip($ip) {
1849
- if (empty($ip)) {
1850
- return $ip;
1851
- }
1852
- $parts = explode('.', $ip);
1853
- array_pop($parts);
1854
- return implode('.', $parts) . '.0';
1855
- }
1856
-
1857
- function process_ip($ip) {
1858
-
1859
- $option = Newsletter::instance()->options['ip'];
1860
- if (empty($option)) {
1861
- return $ip;
1862
- }
1863
- if ($option == 'anonymize') {
1864
- return $this->anonymize_ip($ip);
1865
- }
1866
- return '';
1867
- }
1868
-
1869
- function anonymize_user($id) {
1870
- global $wpdb;
1871
- $user = $this->get_user($id);
1872
- if (!$user) {
1873
- return null;
1874
- }
1875
-
1876
- $user->name = '';
1877
- $user->surname = '';
1878
- $user->ip = $this->anonymize_ip($user->ip);
1879
-
1880
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1881
- $field = 'profile_' . $i;
1882
- $user->$field = '';
1883
- }
1884
-
1885
- // [TODO] Status?
1886
- $user->status = TNP_User::STATUS_UNSUBSCRIBED;
1887
- $user->email = $user->id . '@anonymi.zed';
1888
-
1889
- $user = $this->save_user($user);
1890
-
1891
- return $user;
1892
- }
1893
-
1894
- /**
1895
- * Changes a user status. Accept a user object, user id or user email.
1896
- *
1897
- * @param TNP_User $user
1898
- * @param string $status
1899
- * @return TNP_User
1900
- */
1901
- function set_user_status($user, $status) {
1902
- global $wpdb;
1903
-
1904
- $this->logger->debug('Status change to ' . $status . ' of subscriber ' . $user->id . ' from ' . $_SERVER['REQUEST_URI']);
1905
-
1906
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set status=%s where id=%d limit 1", $status, $user->id));
1907
- $user->status = $status;
1908
- return $this->get_user($user);
1909
- }
1910
-
1911
- /**
1912
- *
1913
- * @global wpdb $wpdb
1914
- * @param TNP_User $user
1915
- * @return TNP_User
1916
- */
1917
- function refresh_user_token($user) {
1918
- global $wpdb;
1919
-
1920
- $token = $this->get_token();
1921
-
1922
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set token=%s where id=%d limit 1", $token, $user->id));
1923
- $user->token = $token;
1924
- }
1925
-
1926
- /**
1927
- * Create a log entry with the meaningful user data.
1928
- *
1929
- * @global wpdb $wpdb
1930
- * @param TNP_User $user
1931
- * @param string $source
1932
- * @return type
1933
- */
1934
- function add_user_log($user, $source = '') {
1935
- global $wpdb;
1936
-
1937
- $lists = $this->get_lists_public();
1938
- foreach ($lists as $list) {
1939
- $field_name = 'list_' . $list->id;
1940
- $data[$field_name] = $user->$field_name;
1941
- }
1942
- $data['status'] = $user->status;
1943
- $ip = $this->get_remote_ip();
1944
- $ip = $this->process_ip($ip);
1945
- $this->store->save($wpdb->prefix . 'newsletter_user_logs', array('ip' => $ip, 'user_id' => $user->id, 'source' => $source, 'created' => time(), 'data' => json_encode($data)));
1946
- }
1947
-
1948
- /**
1949
- *
1950
- * @global wpdb $wpdb
1951
- * @param TNP_User $user
1952
- * @param int $list
1953
- * @param type $value
1954
- */
1955
- function set_user_list($user, $list, $value) {
1956
- global $wpdb;
1957
-
1958
- $list = (int) $list;
1959
- $value = $value ? 1 : 0;
1960
- $r = $wpdb->update(NEWSLETTER_USERS_TABLE, array('list_' . $list => $value), array('id' => $user->id));
1961
- }
1962
-
1963
- function set_user_field($id, $field, $value) {
1964
- $this->store->set_field(NEWSLETTER_USERS_TABLE, $id, $field, $value);
1965
- }
1966
-
1967
- function set_user_wp_user_id($user_id, $wp_user_id) {
1968
- $this->store->set_field(NEWSLETTER_USERS_TABLE, $user_id, 'wp_user_id', $wp_user_id);
1969
- }
1970
-
1971
- /**
1972
- *
1973
- * @param int $wp_user_id
1974
- * @param string $format
1975
- * @return TNP_User
1976
- */
1977
- function get_user_by_wp_user_id($wp_user_id, $format = OBJECT) {
1978
- return $this->store->get_single_by_field(NEWSLETTER_USERS_TABLE, 'wp_user_id', $wp_user_id, $format);
1979
- }
1980
-
1981
- /**
1982
- * Returns the user language IF there is a supported mutilanguage plugin installed.
1983
- * @param TNP_User $user
1984
- * @return string Language code or empty
1985
- */
1986
- function get_user_language($user) {
1987
- if ($user && $this->is_multilanguage()) {
1988
- return $user->language;
1989
- }
1990
- return '';
1991
- }
1992
-
1993
- /**
1994
- * Replaces every possible Newsletter tag ({...}) in a piece of text or HTML.
1995
- *
1996
- * @global wpdb $wpdb
1997
- * @param string $text
1998
- * @param mixed $user Can be an object, associative array or id
1999
- * @param mixed $email Can be an object, associative array or id
2000
- * @param type $referrer
2001
- * @return type
2002
- */
2003
- function replace($text, $user = null, $email = null, $referrer = null) {
2004
- global $wpdb;
2005
-
2006
- if (strpos($text, '<p') !== false) {
2007
- $esc_html = true;
2008
- } else {
2009
- $esc_html = false;
2010
- }
2011
-
2012
- static $home_url = false;
2013
-
2014
- if (!$home_url) {
2015
- $home_url = home_url('/');
2016
- }
2017
-
2018
- //$this->logger->debug('Replace start');
2019
- if ($user !== null && !is_object($user)) {
2020
- if (is_array($user)) {
2021
- $user = (object) $user;
2022
- } else if (is_numeric($user)) {
2023
- $user = $this->get_user($user);
2024
- } else {
2025
- $user = null;
2026
- }
2027
- }
2028
-
2029
- if ($email !== null && !is_object($email)) {
2030
- if (is_array($email)) {
2031
- $email = (object) $email;
2032
- } else if (is_numeric($email)) {
2033
- $email = $this->get_email($email);
2034
- } else {
2035
- $email = null;
2036
- }
2037
- }
2038
-
2039
- $initial_language = $this->get_current_language();
2040
-
2041
- if ($user && $user->language) {
2042
- $this->switch_language($user->language);
2043
- }
2044
-
2045
-
2046
- $text = apply_filters('newsletter_replace', $text, $user, $email, $esc_html);
2047
-
2048
- $text = $this->replace_url($text, 'blog_url', $home_url);
2049
- $text = $this->replace_url($text, 'home_url', $home_url);
2050
-
2051
- $text = str_replace('{blog_title}', html_entity_decode(get_bloginfo('name')), $text);
2052
- $text = str_replace('{blog_description}', get_option('blogdescription'), $text);
2053
-
2054
- $text = $this->replace_date($text);
2055
-
2056
- if ($user) {
2057
- //$this->logger->debug('Replace with user ' . $user->id);
2058
- $nk = $this->get_user_key($user);
2059
- $options_profile = NewsletterSubscription::instance()->get_options('profile', $this->get_user_language($user));
2060
- $text = str_replace('{email}', $user->email, $text);
2061
- $name = apply_filters('newsletter_replace_name', $user->name, $user);
2062
- if (empty($name)) {
2063
- $text = str_replace(' {name}', '', $text);
2064
- $text = str_replace('{name}', '', $text);
2065
- } else {
2066
- $text = str_replace('{name}', esc_html($name), $text);
2067
- }
2068
-
2069
- switch ($user->sex) {
2070
- case 'm': $text = str_replace('{title}', $options_profile['title_male'], $text);
2071
- break;
2072
- case 'f': $text = str_replace('{title}', $options_profile['title_female'], $text);
2073
- break;
2074
- case 'n': $text = str_replace('{title}', $options_profile['title_none'], $text);
2075
- break;
2076
- default:
2077
- $text = str_replace('{title}', '', $text);
2078
- }
2079
-
2080
-
2081
- // Deprecated
2082
- $text = str_replace('{surname}', esc_html($user->surname), $text);
2083
- $text = str_replace('{last_name}', esc_html($user->surname), $text);
2084
-
2085
- $full_name = esc_html(trim($user->name . ' ' . $user->surname));
2086
- if (empty($full_name)) {
2087
- $text = str_replace(' {full_name}', '', $text);
2088
- $text = str_replace('{full_name}', '', $text);
2089
- } else {
2090
- $text = str_replace('{full_name}', $full_name, $text);
2091
- }
2092
-
2093
- $text = str_replace('{token}', $user->token, $text);
2094
- $text = str_replace('%7Btoken%7D', $user->token, $text);
2095
- $text = str_replace('{id}', $user->id, $text);
2096
- $text = str_replace('%7Bid%7D', $user->id, $text);
2097
- $text = str_replace('{ip}', $user->ip, $text);
2098
- $text = str_replace('{key}', $nk, $text);
2099
- $text = str_replace('%7Bkey%7D', $nk, $text);
2100
-
2101
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
2102
- $p = 'profile_' . $i;
2103
- $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
2104
- }
2105
-
2106
- $base = (empty($this->options_main['url']) ? get_option('home') : $this->options_main['url']);
2107
- $id_token = '&amp;ni=' . $user->id . '&amp;nt=' . $user->token;
2108
-
2109
- $text = $this->replace_url($text, 'subscription_confirm_url', $this->build_action_url('c', $user));
2110
- $text = $this->replace_url($text, 'activation_url', $this->build_action_url('c', $user));
2111
-
2112
- // Obsolete.
2113
- $text = $this->replace_url($text, 'FOLLOWUP_SUBSCRIPTION_URL', self::add_qs($base, 'nm=fs' . $id_token));
2114
- $text = $this->replace_url($text, 'FOLLOWUP_UNSUBSCRIPTION_URL', self::add_qs($base, 'nm=fu' . $id_token));
2115
-
2116
- $text = $this->replace_url($text, 'UNLOCK_URL', $this->build_action_url('ul', $user));
2117
- } else {
2118
- //$this->logger->debug('Replace without user');
2119
- $text = $this->replace_url($text, 'subscription_confirm_url', '#');
2120
- $text = $this->replace_url($text, 'activation_url', '#');
2121
- }
2122
-
2123
- if ($email) {
2124
- //$this->logger->debug('Replace with email ' . $email->id);
2125
- $nek = $this->get_email_key($email);
2126
- $text = str_replace('{email_id}', $email->id, $text);
2127
- $text = str_replace('{email_key}', $nek, $text);
2128
- $text = str_replace('{email_subject}', $email->subject, $text);
2129
- // Deprecated
2130
- $text = str_replace('{subject}', $email->subject, $text);
2131
- $text = $this->replace_url($text, 'email_url', $this->build_action_url('v', $user) . '&id=' . $email->id);
2132
- } else {
2133
- //$this->logger->debug('Replace without email');
2134
- $text = $this->replace_url($text, 'email_url', '#');
2135
- }
2136
-
2137
- if (strpos($text, '{subscription_form}') !== false) {
2138
- $text = str_replace('{subscription_form}', NewsletterSubscription::instance()->get_subscription_form($referrer), $text);
2139
- } else {
2140
- for ($i = 1; $i <= 10; $i++) {
2141
- if (strpos($text, "{subscription_form_$i}") !== false) {
2142
- $text = str_replace("{subscription_form_$i}", NewsletterSubscription::instance()->get_form($i), $text);
2143
- break;
2144
- }
2145
- }
2146
- }
2147
-
2148
- // Company info
2149
- // TODO: Move to another module
2150
- $options = Newsletter::instance()->get_options('info');
2151
- $text = str_replace('{company_address}', $options['footer_contact'], $text);
2152
- $text = str_replace('{company_name}', $options['footer_title'], $text);
2153
- $text = str_replace('{company_legal}', $options['footer_legal'], $text);
2154
-
2155
- $this->switch_language($initial_language);
2156
- //$this->logger->debug('Replace end');
2157
- return $text;
2158
- }
2159
-
2160
- function replace_date($text) {
2161
- $text = str_replace('{date}', date_i18n(get_option('date_format')), $text);
2162
-
2163
- // Date processing
2164
- $x = 0;
2165
- while (($x = strpos($text, '{date_', $x)) !== false) {
2166
- $y = strpos($text, '}', $x);
2167
- if ($y === false)
2168
- continue;
2169
- $f = substr($text, $x + 6, $y - $x - 6);
2170
- $text = substr($text, 0, $x) . date_i18n($f) . substr($text, $y + 1);
2171
- }
2172
- return $text;
2173
- }
2174
-
2175
- function replace_url($text, $tag, $url) {
2176
- static $home = false;
2177
- if (!$home) {
2178
- $home = trailingslashit(home_url());
2179
- }
2180
- $tag_lower = strtolower($tag);
2181
- $text = str_replace('http://{' . $tag_lower . '}', $url, $text);
2182
- $text = str_replace('https://{' . $tag_lower . '}', $url, $text);
2183
- $text = str_replace($home . '{' . $tag_lower . '}', $url, $text);
2184
- $text = str_replace($home . '%7B' . $tag_lower . '%7D', $url, $text);
2185
- $text = str_replace('{' . $tag_lower . '}', $url, $text);
2186
- $text = str_replace('%7B' . $tag_lower . '%7D', $url, $text);
2187
-
2188
- $url_encoded = urlencode($url);
2189
- $text = str_replace('%7B' . $tag_lower . '_encoded%7D', $url_encoded, $text);
2190
- $text = str_replace('{' . $tag_lower . '_encoded}', $url_encoded, $text);
2191
-
2192
- // for compatibility
2193
- $text = str_replace($home . $tag, $url, $text);
2194
-
2195
- return $text;
2196
- }
2197
-
2198
- public static function antibot_form_check($captcha = false) {
2199
-
2200
- if (defined('NEWSLETTER_ANTIBOT') && !NEWSLETTER_ANTIBOT) {
2201
- return true;
2202
- }
2203
-
2204
- if (strtolower($_SERVER['REQUEST_METHOD']) != 'post') {
2205
- return false;
2206
- }
2207
-
2208
- if (!isset($_POST['ts']) || time() - $_POST['ts'] > 60) {
2209
- return false;
2210
- }
2211
-
2212
- if ($captcha) {
2213
- $n1 = (int) $_POST['n1'];
2214
- if (empty($n1)) {
2215
- return false;
2216
- }
2217
- $n2 = (int) $_POST['n2'];
2218
- if (empty($n2)) {
2219
- return false;
2220
- }
2221
- $n3 = (int) $_POST['n3'];
2222
- if ($n1 + $n2 != $n3) {
2223
- return false;
2224
- }
2225
- }
2226
-
2227
- return true;
2228
- }
2229
-
2230
- public static function request_to_antibot_form($submit_label = 'Continue...', $captcha = false) {
2231
- header('Content-Type: text/html;charset=UTF-8');
2232
- header('X-Robots-Tag: noindex,nofollow,noarchive');
2233
- header('Cache-Control: no-cache,no-store,private');
2234
- echo "<!DOCTYPE html>\n";
2235
- echo '<html><head>'
2236
- . '<style type="text/css">'
2237
- . '.tnp-captcha {text-align: center; margin: 200px auto 0 auto !important; max-width: 300px !important; padding: 10px !important; font-family: "Open Sans", sans-serif; background: #ECF0F1; border-radius: 5px; padding: 50px !important; border: none !important;}'
2238
- . 'p {text-align: center; padding: 10px; color: #7F8C8D;}'
2239
- . 'input[type=text] {width: 50px; padding: 10px 10px; border: none; border-radius: 2px; margin: 0px 5px;}'
2240
- . 'input[type=submit] {text-align: center; border: none; padding: 10px 15px; font-family: "Open Sans", sans-serif; background-color: #27AE60; color: white; cursor: pointer;}'
2241
- . '</style>'
2242
- . '</head><body>';
2243
- echo '<form method="post" action="https://www.domain.tld" id="form">';
2244
- echo '<div style="width: 1px; height: 1px; overflow: hidden">';
2245
- foreach ($_REQUEST as $name => $value) {
2246
- if ($name == 'submit')
2247
- continue;
2248
- if (is_array($value)) {
2249
- foreach ($value as $element) {
2250
- echo '<input type="text" name="';
2251
- echo esc_attr($name);
2252
- echo '[]" value="';
2253
- echo esc_attr(stripslashes($element));
2254
- echo '">';
2255
- }
2256
- } else {
2257
- echo '<input type="hidden" name="', esc_attr($name), '" value="', esc_attr(stripslashes($value)), '">';
2258
- }
2259
- }
2260
- if (isset($_SERVER['HTTP_REFERER'])) {
2261
- echo '<input type="hidden" name="nhr" value="' . esc_attr($_SERVER['HTTP_REFERER']) . '">';
2262
- }
2263
- echo '<input type="hidden" name="ts" value="' . time() . '">';
2264
- echo '</div>';
2265
-
2266
- if ($captcha) {
2267
- echo '<div class="tnp-captcha">';
2268
- echo '<p>', __('Math question', 'newsletter'), '</p>';
2269
- echo '<input type="text" name="n1" value="', rand(1, 9), '" readonly style="width: 50px">';
2270
- echo '+';
2271
- echo '<input type="text" name="n2" value="', rand(1, 9), '" readonly style="width: 50px">';
2272
- echo '=';
2273
- echo '<input type="text" name="n3" value="?" style="width: 50px">';
2274
- echo '<br><br>';
2275
- echo '<input type="submit" value="', esc_attr($submit_label), '">';
2276
- echo '</div>';
2277
- }
2278
- echo '<noscript><input type="submit" value="';
2279
- echo esc_attr($submit_label);
2280
- echo '"></noscript></form>';
2281
- echo '<script>';
2282
- echo 'document.getElementById("form").action="' . home_url('/') . '";';
2283
- if (!$captcha) {
2284
- echo 'document.getElementById("form").submit();';
2285
- }
2286
- echo '</script>';
2287
- echo '</body></html>';
2288
- die();
2289
- }
2290
-
2291
- static function extract_body($html) {
2292
- $x = stripos($html, '<body');
2293
- if ($x !== false) {
2294
- $x = strpos($html, '>', $x);
2295
- $y = strpos($html, '</body>');
2296
- return substr($html, $x + 1, $y - $x - 1);
2297
- } else {
2298
- return $html;
2299
- }
2300
- }
2301
-
2302
- /** Returns a percentage as string */
2303
- static function percent($value, $total) {
2304
- if ($total == 0)
2305
- return '-';
2306
- return sprintf("%.2f", $value / $total * 100) . '%';
2307
- }
2308
-
2309
- /** Returns a percentage as integer value */
2310
- static function percentValue($value, $total) {
2311
- if ($total == 0)
2312
- return 0;
2313
- return round($value / $total * 100);
2314
- }
2315
-
2316
- /**
2317
- * Takes in a variable and checks if object, array or scalar and return the integer representing
2318
- * a database record id.
2319
- *
2320
- * @param mixed $var
2321
- * @return in
2322
- */
2323
- static function to_int_id($var) {
2324
- if (is_object($var)) {
2325
- return (int) $var->id;
2326
- }
2327
- if (is_array($var)) {
2328
- return (int) $var['id'];
2329
- }
2330
- return (int) $var;
2331
- }
2332
-
2333
- static function to_array($text) {
2334
- $text = trim($text);
2335
- if (empty($text)) {
2336
- return array();
2337
- }
2338
- $text = preg_split("/\\r\\n/", $text);
2339
- $text = array_map('trim', $text);
2340
- $text = array_map('strtolower', $text);
2341
- $text = array_filter($text);
2342
-
2343
- return $text;
2344
- }
2345
-
2346
- static function sanitize_ip($ip) {
2347
- if (empty($ip)) {
2348
- return '';
2349
- }
2350
- $ip = preg_replace('/[^0-9a-fA-F:., ]/', '', trim($ip));
2351
- if (strlen($ip) > 50)
2352
- $ip = substr($ip, 0, 50);
2353
-
2354
- // When more than one IP is present due to firewalls, proxies, and so on. The first one should be the origin.
2355
- if (strpos($ip, ',') !== false) {
2356
- list($ip, $tail) = explode(',', $ip, 2);
2357
- }
2358
- return $ip;
2359
- }
2360
-
2361
- static function get_remote_ip() {
2362
- $ip = '';
2363
- if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2364
- $ip = $_SERVER['HTTP_X_REAL_IP'];
2365
- } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2366
- $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
2367
- } elseif (isset($_SERVER['REMOTE_ADDR'])) {
2368
- $ip = $_SERVER['REMOTE_ADDR'];
2369
- }
2370
- return self::sanitize_ip($ip);
2371
- }
2372
-
2373
- static function get_signature($text) {
2374
- $key = NewsletterStatistics::instance()->options['key'];
2375
- return md5($text . $key);
2376
- }
2377
-
2378
- static function check_signature($text, $signature) {
2379
- if (empty($signature)) {
2380
- return false;
2381
- }
2382
- $key = NewsletterStatistics::instance()->options['key'];
2383
- return md5($text . $key) === $signature;
2384
- }
2385
-
2386
- static function get_home_url() {
2387
- static $url = false;
2388
- if (!$url) {
2389
- $url = home_url('/');
2390
- }
2391
- return $url;
2392
- }
2393
-
2394
- static function clean_eol($text) {
2395
- $text = str_replace("\r\n", "\n", $text);
2396
- $text = str_replace("\r", "\n", $text);
2397
- $text = str_replace("\n", "\r\n", $text);
2398
- return $text;
2399
- }
2400
-
2401
- function set_current_language($language) {
2402
- self::$current_language = $language;
2403
- }
2404
-
2405
- /**
2406
- * Return the current language code. Optionally, if a user is passed and it has a language
2407
- * the user language is returned.
2408
- * If there is no language available, an empty string is returned.
2409
- *
2410
- * @param TNP_User $user
2411
- * @return string The language code
2412
- */
2413
- function get_current_language($user = null) {
2414
-
2415
- if ($user && $user->language) {
2416
- return $user->language;
2417
- }
2418
-
2419
- if (!empty(self::$current_language)) {
2420
- return self::$current_language;
2421
- }
2422
-
2423
- // WPML
2424
- if (class_exists('SitePress')) {
2425
- $current_language = apply_filters('wpml_current_language', '');
2426
- if ($current_language == 'all') {
2427
- $current_language = '';
2428
- }
2429
- return $current_language;
2430
- }
2431
-
2432
- // Polylang
2433
- if (function_exists('pll_current_language')) {
2434
- return pll_current_language();
2435
- }
2436
-
2437
- // Trnslatepress and/or others
2438
- $current_language = apply_filters('newsletter_current_language', '');
2439
-
2440
- return $current_language;
2441
- }
2442
-
2443
- function get_default_language() {
2444
- if (class_exists('SitePress')) {
2445
- return $current_language = apply_filters('wpml_current_language', '');
2446
- } else if (function_exists('pll_default_language')) {
2447
- return pll_default_language();
2448
- } else if (class_exists('TRP_Translate_Press')) {
2449
- // TODO: Find the default language
2450
- }
2451
- return '';
2452
- }
2453
-
2454
- function is_all_languages() {
2455
- return $this->get_current_language() == '';
2456
- }
2457
-
2458
- function is_default_language() {
2459
- return $this->get_current_language() == $this->get_default_language();
2460
- }
2461
-
2462
- /**
2463
- * Returns an array of languages with key the language code and value the language name.
2464
- * An empty array is returned if no language is available.
2465
- */
2466
- function get_languages() {
2467
- $language_options = array();
2468
-
2469
- if (class_exists('SitePress')) {
2470
- $languages = apply_filters('wpml_active_languages', null);
2471
- foreach ($languages as $language) {
2472
- $language_options[$language['language_code']] = $language['translated_name'];
2473
- }
2474
- return $language_options;
2475
- } else if (function_exists('icl_get_languages')) {
2476
- $languages = icl_get_languages();
2477
- foreach ($languages as $code => $language) {
2478
- $language_options[$code] = $language['native_name'];
2479
- }
2480
- return $language_options;
2481
- }
2482
-
2483
- return apply_filters('newsletter_languages', $language_options);
2484
- }
2485
-
2486
- function get_language_label($language) {
2487
- $languages = $this->get_languages();
2488
- if (isset($languages[$language])) {
2489
- return $languages[$language];
2490
- }
2491
- return '';
2492
- }
2493
-
2494
- /**
2495
- * Changes the current language usually before extracting the posts since WPML
2496
- * does not support the language filter in the post query (or at least we didn't
2497
- * find it).
2498
- *
2499
- * @param string $language
2500
- */
2501
- function switch_language($language) {
2502
- if (class_exists('SitePress')) {
2503
- if (empty($language)) {
2504
- $language = 'all';
2505
- }
2506
- do_action('wpml_switch_language', $language);
2507
- return;
2508
- }
2509
- }
2510
-
2511
- function is_multilanguage() {
2512
- return class_exists('SitePress') || function_exists('pll_default_language') || class_exists('TRP_Translate_Press');
2513
- }
2514
-
2515
- function get_posts($filters = array(), $language = '') {
2516
- $current_language = $this->get_current_language();
2517
-
2518
- // Language switch for WPML
2519
- if ($language) {
2520
- if (class_exists('SitePress')) {
2521
- $this->switch_language($language);
2522
- $filters['suppress_filters'] = false;
2523
- }
2524
- if (class_exists('Polylang')) {
2525
- $filters['lang'] = $language;
2526
- }
2527
- }
2528
- $posts = get_posts($filters);
2529
- if ($language) {
2530
- if (class_exists('SitePress')) {
2531
- $this->switch_language($current_language);
2532
- }
2533
- }
2534
- return $posts;
2535
- }
2536
-
2537
- function get_wp_query($filters, $langiage = '') {
2538
- if ($language) {
2539
- if (class_exists('SitePress')) {
2540
- $this->switch_language($language);
2541
- $filters['suppress_filters'] = false;
2542
- }
2543
- if (class_exists('Polylang')) {
2544
- $filters['lang'] = $language;
2545
- }
2546
- }
2547
-
2548
- $posts = new WP_Query($filters);
2549
-
2550
- if ($language) {
2551
- if (class_exists('SitePress')) {
2552
- $this->switch_language($current_language);
2553
- }
2554
- }
2555
-
2556
- return $posts;
2557
- }
2558
-
2559
- protected function generate_admin_notification_message($user) {
2560
-
2561
- $message = file_get_contents(__DIR__ . '/notification.html');
2562
-
2563
- $message = $this->replace($message, $user);
2564
- $message = str_replace('{user_admin_url}', admin_url('admin.php?page=newsletter_users_edit&id=' . $user->id), $message);
2565
-
2566
- return $message;
2567
- }
2568
-
2569
- protected function generate_admin_notification_subject($subject) {
2570
- $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
2571
-
2572
- return '[' . $blogname . '] ' . $subject;
2573
- }
2574
-
2575
- function dienow($message, $admin_message = null, $http_code = 200) {
2576
- if ($admin_message && current_user_can('administrator')) {
2577
- $message .= '<br><br><strong>Text below only visibile to administrators</strong><br>';
2578
- $message .= $admin_message;
2579
- }
2580
- wp_die($message, $http_code);
2581
- }
2582
-
2583
- function dump($var) {
2584
- if (NEWSLETTER_DEBUG) {
2585
- var_dump($var);
2586
- }
2587
- }
2588
-
2589
- function dump_die($var) {
2590
- if (NEWSLETTER_DEBUG) {
2591
- var_dump($var);
2592
- die();
2593
- }
2594
- }
2595
-
2596
- }
2597
-
2598
- /**
2599
- * Kept for compatibility.
2600
- *
2601
- * @param type $post_id
2602
- * @param type $size
2603
- * @param type $alternative
2604
- * @return type
2605
- */
2606
- function nt_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2607
- return NewsletterModule::get_post_image($post_id, $size, $alternative);
2608
- }
2609
-
2610
- function newsletter_get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2611
- echo NewsletterModule::get_post_image($post_id, $size, $alternative);
2612
- }
2613
-
2614
- /**
2615
- * Accepts a post or a post ID.
2616
- *
2617
- * @param WP_Post $post
2618
- */
2619
- function newsletter_the_excerpt($post, $words = 30) {
2620
- $post = get_post($post);
2621
- $excerpt = $post->post_excerpt;
2622
- if (empty($excerpt)) {
2623
- $excerpt = $post->post_content;
2624
- $excerpt = strip_shortcodes($excerpt);
2625
- $excerpt = wp_strip_all_tags($excerpt, true);
2626
- }
2627
- echo '<p>' . wp_trim_words($excerpt, $words) . '</p>';
2628
- }
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ require_once __DIR__ . '/logger.php';
6
+ require_once __DIR__ . '/store.php';
7
+ require_once __DIR__ . '/composer.php';
8
+ require_once __DIR__ . '/addon.php';
9
+ require_once __DIR__ . '/mailer.php';
10
+ require_once __DIR__ . '/themes.php';
11
+
12
+ class TNP_Media {
13
+
14
+ var $id;
15
+ var $url;
16
+ var $width;
17
+ var $height;
18
+ var $alt;
19
+ var $link;
20
+ var $align = 'center';
21
+
22
+ /** Sets the width recalculating the height */
23
+ public function set_width($width) {
24
+ $width = (int) $width;
25
+ if (empty($width))
26
+ return;
27
+ if ($this->width < $width)
28
+ return;
29
+ $this->height = floor(($width / $this->width) * $this->height);
30
+ $this->width = $width;
31
+ }
32
+
33
+ /** Sets the height recalculating the width */
34
+ public function set_height($height) {
35
+ $height = (int) $height;
36
+ $this->width = floor(($height / $this->height) * $this->width);
37
+ $this->height = $height;
38
+ }
39
+
40
+ }
41
+
42
+ /**
43
+ * @property int $id The list unique identifier
44
+ * @property string $name The list name
45
+ * @property bool $forced If the list must be added to every new subscriber
46
+ * @property int $status When and how the list is visible to the subscriber - see constants
47
+ * @property bool $checked If it must be pre-checked on subscription form
48
+ * @property array $languages The list of language used to pre-assign this list
49
+ */
50
+ class TNP_List {
51
+
52
+ const STATUS_PRIVATE = 0;
53
+ const STATUS_PUBLIC = 1;
54
+ const SUBSCRIPTION_HIDE = 0;
55
+ const SUBSCRIPTION_SHOW = 1;
56
+ const SUBSCRIPTION_SHOW_CHECKED = 2;
57
+ const PROFILE_HIDE = 0;
58
+ const PROFILE_SHOW = 1;
59
+
60
+ var $id;
61
+ var $name;
62
+ var $status;
63
+ var $forced;
64
+ var $checked;
65
+ var $show_on_subscription;
66
+ var $show_on_profile;
67
+
68
+ function is_private() {
69
+ return $this->status == self::STATUS_PRIVATE;
70
+ }
71
+
72
+ }
73
+
74
+ /**
75
+ * @property int $id The list unique identifier
76
+ * @property string $name The list name
77
+ * @property int $status When and how the list is visible to the subscriber - see constants
78
+ * @property string $type Field type: text or select
79
+ * @property array $options Field options (usually the select items)
80
+ */
81
+ class TNP_Profile {
82
+
83
+ const STATUS_PRIVATE = 0;
84
+ const STATUS_PUBLIC = 2;
85
+ const STATUS_PROFILE_ONLY = 1;
86
+ const STATUS_HIDDEN = 3; // Public but never shown (can be set with a hidden form field)
87
+ const TYPE_TEXT = 'text';
88
+ const TYPE_SELECT = 'select';
89
+
90
+ public $id;
91
+ public $name;
92
+ public $status;
93
+ public $type;
94
+ public $options;
95
+ public $placeholder;
96
+ public $rule;
97
+
98
+ public function __construct($id, $name, $status, $type, $options, $placeholder, $rule) {
99
+ $this->id = $id;
100
+ $this->name = $name;
101
+ $this->status = $status;
102
+ $this->type = $type;
103
+ $this->options = $options;
104
+ $this->placeholder = $placeholder;
105
+ $this->rule = $rule;
106
+ }
107
+
108
+ function is_select() {
109
+ return $this->type == self::TYPE_SELECT;
110
+ }
111
+
112
+ function is_text() {
113
+ return $this->type == self::TYPE_TEXT;
114
+ }
115
+
116
+ function is_required() {
117
+ return $this->rule == 1;
118
+ }
119
+
120
+ function is_private() {
121
+ return $this->status == self::STATUS_PRIVATE;
122
+ }
123
+
124
+ function show_on_profile() {
125
+ return $this->status == self::STATUS_PROFILE_ONLY || $this->status == self::STATUS_PUBLIC;
126
+ }
127
+
128
+ }
129
+
130
+ class TNP_Profile_Service {
131
+
132
+ /**
133
+ *
134
+ * @param string $language
135
+ * @param string $type
136
+ * @return TNP_Profile[]
137
+ */
138
+ static function get_profiles($language = '', $type = '') {
139
+
140
+ static $profiles = [];
141
+ $k = $language . $type;
142
+
143
+ if (isset($profiles[$k])) {
144
+ return $profiles[$k];
145
+ }
146
+
147
+ $profiles[$k] = [];
148
+ $profile_options = NewsletterSubscription::instance()->get_options('profile', $language);
149
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
150
+ if (empty($profile_options['profile_' . $i])) {
151
+ continue;
152
+ }
153
+ $profile = self::create_profile_from_options($profile_options, $i);
154
+
155
+ if (empty($type) ||
156
+ ( $type == TNP_Profile::TYPE_SELECT && $profile->is_select() ) ||
157
+ ( $type == TNP_Profile::TYPE_TEXT && $profile->is_text() )) {
158
+ $profiles[$k]['' . $i] = $profile;
159
+ }
160
+ }
161
+
162
+ return $profiles[$k];
163
+ }
164
+
165
+ static function get_profile_by_id($id, $language = '') {
166
+
167
+ $profiles = self::get_profiles($language);
168
+ if (isset($profiles[$id]))
169
+ return $profiles[$id];
170
+ return null;
171
+ }
172
+
173
+ /**
174
+ * @return TNP_Profile
175
+ */
176
+ private static function create_profile_from_options($options, $id) {
177
+ return new TNP_Profile(
178
+ $id,
179
+ $options['profile_' . $id],
180
+ (int) $options['profile_' . $id . '_status'],
181
+ $options['profile_' . $id . '_type'],
182
+ self::string_db_options_to_array($options['profile_' . $id . '_options']),
183
+ $options['profile_' . $id . '_placeholder'],
184
+ $options['profile_' . $id . '_rules']
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Returns a list of strings which are the items for the select field.
190
+ * @return array
191
+ */
192
+ private static function string_db_options_to_array($string_options) {
193
+ $items = array_map('trim', explode(',', $string_options));
194
+ $items = array_combine($items, $items);
195
+
196
+ return $items;
197
+ }
198
+
199
+ }
200
+
201
+ /**
202
+ * Represents the set of data collected by a subscription interface (form, API, ...). Only a valid
203
+ * email is mandatory.
204
+ */
205
+ class TNP_Subscription_Data {
206
+
207
+ var $email = null;
208
+ var $name = null;
209
+ var $surname = null;
210
+ var $sex = null;
211
+ var $language = null;
212
+ var $referrer = null;
213
+ var $http_referrer = null;
214
+ var $ip = null;
215
+ var $country = null;
216
+ var $region = null;
217
+ var $city = null;
218
+
219
+ /**
220
+ * Associative array id=>value of lists chosen by the subscriber. A list can be set to
221
+ * 0 meaning the subscriber does not want to be in that list.
222
+ * The lists must be public: non public lists are filtered.
223
+ * @var array
224
+ */
225
+ var $lists = [];
226
+ var $profiles = [];
227
+
228
+ function merge_in($subscriber) {
229
+ if (!$subscriber)
230
+ $subscriber = new TNP_User();
231
+ if (!empty($this->email))
232
+ $subscriber->email = $this->email;
233
+ if (!empty($this->name))
234
+ $subscriber->name = $this->name;
235
+ if (!empty($this->surname))
236
+ $subscriber->surname = $this->surname;
237
+ if (!empty($this->sex))
238
+ $subscriber->sex = $this->sex;
239
+ if (!empty($this->language))
240
+ $subscriber->language = $this->language;
241
+ if (!empty($this->ip))
242
+ $subscriber->ip = $this->ip;
243
+ if (!empty($this->referrer))
244
+ $subscriber->referrer = $this->referrer;
245
+ if (!empty($this->http_referrer))
246
+ $subscriber->http_referrer = $this->http_referrer;
247
+ if (!empty($this->country))
248
+ $subscriber->country = $this->country;
249
+ if (!empty($this->region))
250
+ $subscriber->region = $this->region;
251
+ if (!empty($this->city))
252
+ $subscriber->city = $this->city;
253
+
254
+
255
+ foreach ($this->lists as $id => $value) {
256
+ $key = 'list_' . $id;
257
+ $subscriber->$key = $value;
258
+ }
259
+
260
+ // Profile
261
+ foreach ($this->profiles as $id => $value) {
262
+ $key = 'profile_' . $id;
263
+ $subscriber->$key = $value;
264
+ }
265
+ }
266
+
267
+ /** Sets to active a set of lists. Accepts incorrect data (and ignores it).
268
+ *
269
+ * @param array $list_ids Array of list IDs
270
+ */
271
+ function add_lists($list_ids) {
272
+ if (empty($list_ids) || !is_array($list_ids))
273
+ return;
274
+ foreach ($list_ids as $list_id) {
275
+ $list_id = (int) $list_id;
276
+ if ($list_id < 0 || $list_id > NEWSLETTER_LIST_MAX)
277
+ continue;
278
+ $this->lists[$list_id] = 1;
279
+ }
280
+ }
281
+
282
+ }
283
+
284
+ /**
285
+ * Represents a subscription request with the subscriber data and actions to be taken by
286
+ * the subscription engine (spam check, notifications, ...).
287
+ */
288
+ class TNP_Subscription {
289
+
290
+ const EXISTING_ERROR = 1;
291
+ const EXISTING_MERGE = 0;
292
+ const EXISTING_SINGLE_OPTIN = 2;
293
+
294
+
295
+ /**
296
+ * Subscriber's data following the syntax of the TNP_User
297
+ * @var TNP_Subscription_Data
298
+ */
299
+ var $data;
300
+ var $spamcheck = true;
301
+ // The optin to use, empty for the plugin default. It's a string to facilitate the use by addons (which have a selector for the desired
302
+ // optin as empty (for default), 'single' or 'double'.
303
+ var $optin = null;
304
+ // What to do with an existing subscriber???
305
+ var $if_exists = self::EXISTING_MERGE;
306
+
307
+ /**
308
+ * Determines if the welcome or activation email should be sent. Note: sometime an activation email is sent disregarding
309
+ * this setting.
310
+ * @var boolean
311
+ */
312
+ var $send_emails = true;
313
+
314
+ public function __construct() {
315
+ $this->data = new TNP_Subscription_Data();
316
+ }
317
+
318
+ public function is_single_optin() {
319
+ return $this->optin == 'single';
320
+ }
321
+
322
+ public function is_double_optin() {
323
+ return $this->optin == 'double';
324
+ }
325
+
326
+ }
327
+
328
+ /**
329
+ * @property int $id The subscriber unique identifier
330
+ * @property string $email The subscriber email
331
+ * @property string $name The subscriber name or first name
332
+ * @property string $surname The subscriber last name
333
+ * @property string $status The subscriber status
334
+ * @property string $language The subscriber language code 2 chars lowercase
335
+ * @property string $token The subscriber secret token
336
+ * @property string $country The subscriber country code 2 chars uppercase
337
+ */
338
+ class TNP_User {
339
+
340
+ const STATUS_CONFIRMED = 'C';
341
+ const STATUS_NOT_CONFIRMED = 'S';
342
+ const STATUS_UNSUBSCRIBED = 'U';
343
+ const STATUS_BOUNCED = 'B';
344
+ const STATUS_COMPLAINED = 'P';
345
+
346
+ var $ip = '';
347
+
348
+ public static function get_status_label($status) {
349
+ switch ($status) {
350
+ case self::STATUS_NOT_CONFIRMED: return __('NOT CONFIRMED', 'newsletter');
351
+ break;
352
+ case self::STATUS_CONFIRMED: return __('CONFIRMED', 'newsletter');
353
+ break;
354
+ case self::STATUS_UNSUBSCRIBED: return __('UNSUBSCRIBED', 'newsletter');
355
+ break;
356
+ case self::STATUS_BOUNCED: return __('BOUNCED', 'newsletter');
357
+ break;
358
+ case self::STATUS_COMPLAINED: return __('COMPLAINED', 'newsletter');
359
+ break;
360
+ default:
361
+ return __('Unknown', 'newsletter');
362
+ }
363
+ }
364
+
365
+ }
366
+
367
+ /**
368
+ * @property int $id The email unique identifier
369
+ * @property string $subject The email subject
370
+ * @property string $message The email html message
371
+ * @property int $track Check if the email stats should be active
372
+ * @property array $options Email options
373
+ * @property int $total Total emails to send
374
+ * @property int $sent Total sent emails by now
375
+ * @property int $open_count Total opened emails
376
+ * @property int $click_count Total clicked emails
377
+ * */
378
+ class TNP_Email {
379
+
380
+ const STATUS_DRAFT = 'new';
381
+ const STATUS_SENT = 'sent';
382
+ const STATUS_SENDING = 'sending';
383
+ const STATUS_PAUSED = 'paused';
384
+ const STATUS_ERROR = 'error';
385
+
386
+ }
387
+
388
+ class NewsletterModule {
389
+
390
+ /**
391
+ * @var NewsletterLogger
392
+ */
393
+ var $logger;
394
+
395
+ /**
396
+ * @var NewsletterLogger
397
+ */
398
+ var $admin_logger;
399
+
400
+ /**
401
+ * @var NewsletterStore
402
+ */
403
+ var $store;
404
+
405
+ /**
406
+ * The main module options
407
+ * @var array
408
+ */
409
+ var $options;
410
+
411
+ /**
412
+ * @var string The module name
413
+ */
414
+ var $module;
415
+
416
+ /**
417
+ * The module version
418
+ * @var string
419
+ */
420
+ var $version;
421
+ var $old_version;
422
+
423
+ /**
424
+ * Prefix for all options stored on WordPress options table.
425
+ * @var string
426
+ */
427
+ var $prefix;
428
+
429
+ /**
430
+ * @var NewsletterThemes
431
+ */
432
+ var $themes;
433
+ var $components;
434
+ static $current_language = '';
435
+
436
+ function __construct($module, $version, $module_id = null, $components = array()) {
437
+ $this->module = $module;
438
+ $this->version = $version;
439
+ $this->prefix = 'newsletter_' . $module;
440
+ array_unshift($components, '');
441
+ $this->components = $components;
442
+
443
+ $this->logger = new NewsletterLogger($module);
444
+
445
+ $this->options = $this->get_options();
446
+ $this->store = NewsletterStore::singleton();
447
+
448
+ //$this->logger->debug($module . ' constructed');
449
+ // Version check
450
+ if (is_admin()) {
451
+ $this->admin_logger = new NewsletterLogger($module . '-admin');
452
+ $this->old_version = get_option($this->prefix . '_version', '0.0.0');
453
+
454
+ if ($this->old_version == '0.0.0') {
455
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
456
+ $this->first_install();
457
+ update_option($this->prefix . "_first_install_time", time(), FALSE);
458
+ }
459
+
460
+ if (strcmp($this->old_version, $this->version) != 0) {
461
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
462
+ $this->logger->info('Version changed from ' . $this->old_version . ' to ' . $this->version);
463
+ // Do all the stuff for this version change
464
+ $this->upgrade();
465
+ update_option($this->prefix . '_version', $this->version);
466
+ }
467
+
468
+ add_action('admin_menu', array($this, 'admin_menu'));
469
+ }
470
+ }
471
+
472
+ /**
473
+ *
474
+ * @global wpdb $wpdb
475
+ * @param string $query
476
+ */
477
+ function query($query) {
478
+ global $wpdb;
479
+
480
+ $this->logger->debug($query);
481
+ //$start = microtime(true);
482
+ $r = $wpdb->query($query);
483
+ //$this->logger->debug($wpdb->last_query);
484
+ //$this->logger->debug('Execution time: ' . (microtime(true)-$start));
485
+ //$this->logger->debug('Result: ' . $r);
486
+ if ($r === false) {
487
+ $this->logger->fatal($query);
488
+ $this->logger->fatal($wpdb->last_error);
489
+ }
490
+ return $r;
491
+ }
492
+
493
+ function get_results($query) {
494
+ global $wpdb;
495
+ $r = $wpdb->get_results($query);
496
+ if ($r === false) {
497
+ $this->logger->fatal($query);
498
+ $this->logger->fatal($wpdb->last_error);
499
+ }
500
+ return $r;
501
+ }
502
+
503
+ /**
504
+ *
505
+ * @global wpdb $wpdb
506
+ * @param string $table
507
+ * @param array $data
508
+ */
509
+ function insert($table, $data) {
510
+ global $wpdb;
511
+ $this->logger->debug("inserting into table $table");
512
+ $r = $wpdb->insert($table, $data);
513
+ if ($r === false) {
514
+ $this->logger->fatal($wpdb->last_error);
515
+ }
516
+ }
517
+
518
+ function first_install() {
519
+ $this->logger->debug('First install');
520
+ }
521
+
522
+ /**
523
+ * Does a basic upgrade work, checking if the options is already present and if not (first
524
+ * installation), recovering the defaults, saving them on database and initializing the
525
+ * internal $options.
526
+ */
527
+ function upgrade() {
528
+ foreach ($this->components as $component) {
529
+ $this->logger->debug('Upgrading component ' . $component);
530
+ $this->init_options($component);
531
+ }
532
+ }
533
+
534
+ function init_options($component = '', $autoload = true) {
535
+ global $wpdb;
536
+ $default_options = $this->get_default_options($component);
537
+ $options = $this->get_options($component);
538
+ $options = array_merge($default_options, $options);
539
+ $this->save_options($options, $component, $autoload);
540
+ }
541
+
542
+ function upgrade_query($query) {
543
+ global $wpdb, $charset_collate;
544
+
545
+ $this->logger->info('upgrade_query> Executing ' . $query);
546
+ $suppress_errors = $wpdb->suppress_errors(true);
547
+ $wpdb->query($query);
548
+ if ($wpdb->last_error) {
549
+ $this->logger->debug($wpdb->last_error);
550
+ }
551
+ $wpdb->suppress_errors($suppress_errors);
552
+ }
553
+
554
+ /** Returns a prefix to be used for option names and other things which need to be uniquely named. The parameter
555
+ * "sub" should be used when a sub name is needed for another set of options or like.
556
+ *
557
+ * @param string $sub
558
+ * @return string The prefix for names
559
+ */
560
+ function get_prefix($sub = '', $language = '') {
561
+ return $this->prefix . (!empty($sub) ? '_' : '') . $sub . (!empty($language) ? '_' : '') . $language;
562
+ }
563
+
564
+ /**
565
+ * Returns the options of a module, if not found an empty array.
566
+ */
567
+ function get_options($sub = '', $language = '') {
568
+ $options = get_option($this->get_prefix($sub, $language), array());
569
+ // Protection against scarmled database...
570
+ if (!is_array($options)) {
571
+ $options = array();
572
+ }
573
+ if ($language) {
574
+ $main_options = get_option($this->get_prefix($sub));
575
+ // Protection against scarmled database...
576
+ if (!is_array($main_options))
577
+ $main_options = array();
578
+ //$options = array_merge($main_options, array_filter($options));
579
+ $options = array_merge($main_options, $options);
580
+ }
581
+ return $options;
582
+ }
583
+
584
+ function get_default_options($sub = '') {
585
+ if (!empty($sub)) {
586
+ $sub = '-' . $sub;
587
+ }
588
+ $file = NEWSLETTER_DIR . '/' . $this->module . '/defaults' . $sub . '.php';
589
+ if (file_exists($file)) {
590
+ @include $file;
591
+ }
592
+
593
+ if (!isset($options) || !is_array($options)) {
594
+ return array();
595
+ }
596
+ return $options;
597
+ }
598
+
599
+ function reset_options($sub = '') {
600
+ $this->save_options(array_merge($this->get_options($sub), $this->get_default_options($sub)), $sub);
601
+ return $this->get_options($sub);
602
+ }
603
+
604
+ /**
605
+ * Saves the module options (or eventually a subset names as per parameter $sub). $options
606
+ * should be an array (even if it can work with non array options.
607
+ * The internal module options variable IS initialized with those new options only for the main
608
+ * options (empty $sub parameter).
609
+ * If the options contain a "theme" value, the theme-related options contained are saved as well
610
+ * (used by some modules).
611
+ *
612
+ * @param array $options
613
+ * @param string $sub
614
+ */
615
+ function save_options($options, $sub = '', $autoload = null, $language = '') {
616
+ update_option($this->get_prefix($sub, $language), $options, $autoload);
617
+ if (empty($sub) && empty($language)) {
618
+ $this->options = $options;
619
+ if (isset($this->themes) && isset($options['theme'])) {
620
+ $this->themes->save_options($options['theme'], $options);
621
+ }
622
+ }
623
+ }
624
+
625
+ function delete_options($sub = '') {
626
+ delete_option($this->get_prefix($sub));
627
+ if (empty($sub)) {
628
+ $this->options = array();
629
+ }
630
+ }
631
+
632
+ function merge_options($options, $sub = '', $language = '') {
633
+ if (!is_array($options)) {
634
+ $options = array();
635
+ }
636
+ $old_options = $this->get_options($sub, $language);
637
+ $this->save_options(array_merge($old_options, $options), $sub, null, $language);
638
+ }
639
+
640
+ function backup_options($sub) {
641
+ $options = $this->get_options($sub);
642
+ update_option($this->get_prefix($sub) . '_backup', $options, false);
643
+ }
644
+
645
+ function get_last_run($sub = '') {
646
+ return get_option($this->get_prefix($sub) . '_last_run', 0);
647
+ }
648
+
649
+ /**
650
+ * Save the module last run value. Used to store a timestamp for some modules,
651
+ * for example the Feed by Mail module.
652
+ *
653
+ * @param int $time Unix timestamp (as returned by time() for example)
654
+ * @param string $sub Sub module name (default empty)
655
+ */
656
+ function save_last_run($time, $sub = '') {
657
+ update_option($this->get_prefix($sub) . '_last_run', $time);
658
+ }
659
+
660
+ /**
661
+ * Sums $delta seconds to the last run time.
662
+ * @param int $delta Seconds
663
+ * @param string $sub Sub module name (default empty)
664
+ */
665
+ function add_to_last_run($delta, $sub = '') {
666
+ $time = $this->get_last_run($sub);
667
+ $this->save_last_run($time + $delta, $sub);
668
+ }
669
+
670
+ /**
671
+ * Checks if the semaphore of that name (for this module) is still red. If it is active the method
672
+ * returns false. If it is not active, it will be activated for $time seconds.
673
+ *
674
+ * Since this method activate the semaphore when called, it's name is a bit confusing.
675
+ *
676
+ * @param string $name Sempahore name (local to this module)
677
+ * @param int $time Max time in second this semaphore should stay red
678
+ * @return boolean False if the semaphore is red and you should not proceed, true is it was not active and has been activated.
679
+ */
680
+ function check_transient($name, $time) {
681
+ if ($time < 60)
682
+ $time = 60;
683
+ //usleep(rand(0, 1000000));
684
+ if (($value = get_transient($this->get_prefix() . '_' . $name)) !== false) {
685
+ list($t, $v) = explode(';', $value, 2);
686
+ $this->logger->error('Blocked by transient ' . $this->get_prefix() . '_' . $name . ' set ' . (time() - $t) . ' seconds ago by ' . $v);
687
+ return false;
688
+ }
689
+ //$ip = ''; //gethostbyname(gethostname());
690
+ $value = time() . ";" . ABSPATH . ';' . gethostname();
691
+ set_transient($this->get_prefix() . '_' . $name, $value, $time);
692
+ return true;
693
+ }
694
+
695
+ function delete_transient($name = '') {
696
+ delete_transient($this->get_prefix() . '_' . $name);
697
+ }
698
+
699
+ /** Returns a random token of the specified size (or 10 characters if size is not specified).
700
+ *
701
+ * @param int $size
702
+ * @return string
703
+ */
704
+ static function get_token($size = 10) {
705
+ return substr(md5(rand()), 0, $size);
706
+ }
707
+
708
+ /**
709
+ * Adds query string parameters to an URL checing id there are already other parameters.
710
+ *
711
+ * @param string $url
712
+ * @param string $qs The part of query-string to add (param1=value1&param2=value2...)
713
+ * @param boolean $amp If the method must use the &amp; instead of the plain & (default true)
714
+ * @return string
715
+ */
716
+ static function add_qs($url, $qs, $amp = true) {
717
+ if (strpos($url, '?') !== false) {
718
+ if ($amp)
719
+ return $url . '&amp;' . $qs;
720
+ else
721
+ return $url . '&' . $qs;
722
+ } else
723
+ return $url . '?' . $qs;
724
+ }
725
+
726
+ /**
727
+ * Returns the email address normalized, lowercase with no spaces. If it's not a valid email
728
+ * returns false.
729
+ */
730
+ static function normalize_email($email) {
731
+ if (!is_string($email)) {
732
+ return false;
733
+ }
734
+ $email = strtolower(trim($email));
735
+ if (!is_email($email)) {
736
+ return false;
737
+ }
738
+ //$email = apply_filters('newsletter_normalize_email', $email);
739
+ return $email;
740
+ }
741
+
742
+ static function normalize_name($name) {
743
+ $name = html_entity_decode($name, ENT_QUOTES);
744
+ $name = str_replace(';', ' ', $name);
745
+ $name = strip_tags($name);
746
+
747
+ return $name;
748
+ }
749
+
750
+ static function normalize_sex($sex) {
751
+ $sex = trim(strtolower($sex));
752
+ if ($sex != 'f' && $sex != 'm') {
753
+ $sex = 'n';
754
+ }
755
+ return $sex;
756
+ }
757
+
758
+ static function is_email($email, $empty_ok = false) {
759
+
760
+ if (!is_string($email)) {
761
+ return false;
762
+ }
763
+ $email = strtolower(trim($email));
764
+
765
+ if ($email == '') {
766
+ return $empty_ok;
767
+ }
768
+
769
+ if (!is_email($email)) {
770
+ return false;
771
+ }
772
+ return true;
773
+ }
774
+
775
+ /**
776
+ * Converts a GMT date from mysql (see the posts table columns) into a timestamp.
777
+ *
778
+ * @param string $s GMT date with format yyyy-mm-dd hh:mm:ss
779
+ * @return int A timestamp
780
+ */
781
+ static function m2t($s) {
782
+
783
+ // TODO: use the wordpress function I don't remember the name
784
+ $s = explode(' ', $s);
785
+ $d = explode('-', $s[0]);
786
+ $t = explode(':', $s[1]);
787
+ return gmmktime((int) $t[0], (int) $t[1], (int) $t[2], (int) $d[1], (int) $d[2], (int) $d[0]);
788
+ }
789
+
790
+ static function format_date($time) {
791
+ if (empty($time)) {
792
+ return '-';
793
+ }
794
+ return gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
795
+ }
796
+
797
+ static function format_time_delta($delta) {
798
+ $days = floor($delta / (3600 * 24));
799
+ $hours = floor(($delta % (3600 * 24)) / 3600);
800
+ $minutes = floor(($delta % 3600) / 60);
801
+ $seconds = floor(($delta % 60));
802
+ $buffer = $days . ' days, ' . $hours . ' hours, ' . $minutes . ' minutes, ' . $seconds . ' seconds';
803
+ return $buffer;
804
+ }
805
+
806
+ /**
807
+ * Formats a scheduler returned "next execution" time, managing negative or false values. Many times
808
+ * used in conjuction with "last run".
809
+ *
810
+ * @param string $name The scheduler name
811
+ * @return string
812
+ */
813
+ static function format_scheduler_time($name) {
814
+ $time = wp_next_scheduled($name);
815
+ if ($time === false) {
816
+ return 'No next run scheduled';
817
+ }
818
+ $delta = $time - time();
819
+ // If less 10 minutes late it can be a cron problem but now it is working
820
+ if ($delta < 0 && $delta > -600) {
821
+ return 'Probably running now';
822
+ } else if ($delta <= -600) {
823
+ return 'It seems the cron system is not working. Reload the page to see if this message change.';
824
+ }
825
+ return 'Runs in ' . self::format_time_delta($delta);
826
+ }
827
+
828
+ static function date($time = null, $now = false, $left = false) {
829
+ if (is_null($time)) {
830
+ $time = time();
831
+ }
832
+ if ($time == false) {
833
+ $buffer = 'none';
834
+ } else {
835
+ $buffer = gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
836
+ }
837
+ if ($now) {
838
+ $buffer .= ' (now: ' . gmdate(get_option('date_format') . ' ' .
839
+ get_option('time_format'), time() + get_option('gmt_offset') * 3600);
840
+ $buffer .= ')';
841
+ }
842
+ if ($left) {
843
+ $buffer .= ', ' . gmdate('H:i:s', $time - time()) . ' left';
844
+ }
845
+ return $buffer;
846
+ }
847
+
848
+ /**
849
+ * Return an array of array with on first element the array of recent post and on second element the array
850
+ * of old posts.
851
+ *
852
+ * @param array $posts
853
+ * @param int $time
854
+ */
855
+ static function split_posts(&$posts, $time = 0) {
856
+ if ($time < 0) {
857
+ return array_chunk($posts, ceil(count($posts) / 2));
858
+ }
859
+
860
+ $result = array(array(), array());
861
+
862
+ if (empty($posts))
863
+ return $result;
864
+
865
+ foreach ($posts as &$post) {
866
+ if (self::is_post_old($post, $time))
867
+ $result[1][] = $post;
868
+ else
869
+ $result[0][] = $post;
870
+ }
871
+ return $result;
872
+ }
873
+
874
+ static function is_post_old(&$post, $time = 0) {
875
+ return self::m2t($post->post_date_gmt) <= $time;
876
+ }
877
+
878
+ static function get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
879
+ global $post;
880
+
881
+ if (empty($post_id))
882
+ $post_id = $post->ID;
883
+ if (empty($post_id))
884
+ return $alternative;
885
+
886
+ $image_id = function_exists('get_post_thumbnail_id') ? get_post_thumbnail_id($post_id) : false;
887
+ if ($image_id) {
888
+ $image = wp_get_attachment_image_src($image_id, $size);
889
+ return $image[0];
890
+ } else {
891
+ $attachments = get_children(array('post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID'));
892
+
893
+ if (empty($attachments)) {
894
+ return $alternative;
895
+ }
896
+
897
+ foreach ($attachments as $id => $attachment) {
898
+ $image = wp_get_attachment_image_src($id, $size);
899
+ return $image[0];
900
+ }
901
+ }
902
+ }
903
+
904
+ /**
905
+ * Cleans up a text containing url tags with appended the absolute URL (due to
906
+ * the editor behavior) moving back them to the simple form.
907
+ */
908
+ static function clean_url_tags($text) {
909
+ $text = str_replace('%7B', '{', $text);
910
+ $text = str_replace('%7D', '}', $text);
911
+
912
+ // Only tags which are {*_url}
913
+ $text = preg_replace("/[\"']http[^\"']+(\\{[^\\}]+_url\\})[\"']/i", "\"\\1\"", $text);
914
+ return $text;
915
+ }
916
+
917
+ function admin_menu() {
918
+
919
+ }
920
+
921
+ function add_menu_page($page, $title, $capability = '') {
922
+ if (!Newsletter::instance()->is_allowed())
923
+ return;
924
+ $name = 'newsletter_' . $this->module . '_' . $page;
925
+ add_submenu_page('newsletter_main_index', $title, $title, 'exist', $name, array($this, 'menu_page'));
926
+ }
927
+
928
+ function add_admin_page($page, $title) {
929
+ if (!Newsletter::instance()->is_allowed()) {
930
+ return;
931
+ }
932
+ $name = 'newsletter_' . $this->module . '_' . $page;
933
+ add_submenu_page(null, $title, $title, 'exist', $name, array($this, 'menu_page'));
934
+ }
935
+
936
+ function sanitize_file_name($name) {
937
+ return preg_replace('/[^a-z_\\-]/i', '', $name);
938
+ }
939
+
940
+ function menu_page() {
941
+ global $plugin_page, $newsletter, $wpdb;
942
+
943
+ $parts = explode('_', $plugin_page, 3);
944
+ $module = $this->sanitize_file_name($parts[1]);
945
+ $page = $this->sanitize_file_name($parts[2]);
946
+ $page = str_replace('_', '-', $page);
947
+
948
+ $file = NEWSLETTER_DIR . '/' . $module . '/' . $page . '.php';
949
+
950
+ require $file;
951
+ }
952
+
953
+ function get_admin_page_url($page) {
954
+ return admin_url('admin.php') . '?page=newsletter_' . $this->module . '_' . $page;
955
+ }
956
+
957
+ /** Returns all the emails of the give type (message, feed, followup, ...) and in the given format
958
+ * (default as objects). Return false on error or at least an empty array. Errors should never
959
+ * occur.
960
+ *
961
+ * @global wpdb $wpdb
962
+ * @param string $type
963
+ * @return boolean|array
964
+ */
965
+ function get_emails($type = null, $format = OBJECT) {
966
+ global $wpdb;
967
+ if ($type == null) {
968
+ $list = $wpdb->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " order by id desc", $format);
969
+ } else {
970
+ $type = (string) $type;
971
+ $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where type=%s order by id desc", $type), $format);
972
+ }
973
+ if ($wpdb->last_error) {
974
+ $this->logger->error($wpdb->last_error);
975
+ return false;
976
+ }
977
+ if (empty($list)) {
978
+ return [];
979
+ }
980
+ return $list;
981
+ }
982
+
983
+ function get_emails_by_status($status) {
984
+ global $wpdb;
985
+ $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where status=%s order by id desc", $status));
986
+
987
+ array_walk($list, function ($email) {
988
+ $email->options = maybe_unserialize($email->options);
989
+ if (!is_array($email->options)) {
990
+ $email->options = [];
991
+ }
992
+ });
993
+ return $list;
994
+ }
995
+
996
+ /**
997
+ * @param string $key
998
+ * @param mixed $value
999
+ * @return TNP_Email[]
1000
+ */
1001
+ // function get_emails_by_field($key, $value) {
1002
+ // global $wpdb;
1003
+ //
1004
+ // $value_placeholder = is_int($value) ? '%d' : '%s';
1005
+ //
1006
+ // $key = '`' . str_replace('`', '', $key) . '`';
1007
+ //
1008
+ // $query = $wpdb->prepare("SELECT * FROM " . NEWSLETTER_EMAILS_TABLE . " WHERE $key=$value_placeholder ORDER BY id DESC", $value);
1009
+ // //die($query);
1010
+ //
1011
+ // $email_list = $wpdb->get_results($query);
1012
+ //
1013
+ // if ($wpdb->last_error) {
1014
+ // $this->logger->error($wpdb->last_error);
1015
+ //
1016
+ // return [];
1017
+ // }
1018
+ //
1019
+ // //Unserialize options
1020
+ // array_walk($email_list, function ($email) {
1021
+ // $email->options = maybe_unserialize($email->options);
1022
+ // if (!is_array($email->options)) {
1023
+ // $email->options = [];
1024
+ // }
1025
+ // });
1026
+ //
1027
+ // return $email_list;
1028
+ // }
1029
+
1030
+ /**
1031
+ * Retrieves an email from DB and unserialize the options.
1032
+ *
1033
+ * @param mixed $id
1034
+ * @param string $format
1035
+ * @return TNP_Email An object with the same fields of TNP_Email, but not actually of that type
1036
+ */
1037
+ function get_email($id, $format = OBJECT) {
1038
+ $email = $this->store->get_single(NEWSLETTER_EMAILS_TABLE, $id, $format);
1039
+ if (!$email) {
1040
+ return null;
1041
+ }
1042
+ if ($format == OBJECT) {
1043
+ $email->options = maybe_unserialize($email->options);
1044
+ if (!is_array($email->options)) {
1045
+ $email->options = array();
1046
+ }
1047
+ if (empty($email->query)) {
1048
+ $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1049
+ }
1050
+ } else if ($format == ARRAY_A) {
1051
+ $email['options'] = maybe_unserialize($email['options']);
1052
+ if (!is_array($email['options'])) {
1053
+ $email['options'] = array();
1054
+ }
1055
+ if (empty($email['query'])) {
1056
+ $email['query'] = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1057
+ }
1058
+ }
1059
+ return $email;
1060
+ }
1061
+
1062
+ /**
1063
+ * Save an email and provide serialization, if needed, of $email['options'].
1064
+ * @return TNP_Email
1065
+ */
1066
+ function save_email($email, $return_format = OBJECT) {
1067
+ if (is_object($email)) {
1068
+ $email = (array) $email;
1069
+ }
1070
+
1071
+ if (isset($email['subject'])) {
1072
+ if (mb_strlen($email['subject'], 'UTF-8') > 250) {
1073
+ $email['subject'] = mb_substr($email['subject'], 0, 250, 'UTF-8');
1074
+ }
1075
+ }
1076
+ if (isset($email['options']) && is_array($email['options'])) {
1077
+ $email['options'] = serialize($email['options']);
1078
+ }
1079
+ $email = $this->store->save(NEWSLETTER_EMAILS_TABLE, $email, $return_format);
1080
+ if ($return_format == OBJECT) {
1081
+ $email->options = maybe_unserialize($email->options);
1082
+ if (!is_array($email->options)) {
1083
+ $email->options = [];
1084
+ }
1085
+ } else if ($return_format == ARRAY_A) {
1086
+ $email['options'] = maybe_unserialize($email['options']);
1087
+ if (!is_array($email['options'])) {
1088
+ $email['options'] = [];
1089
+ }
1090
+ }
1091
+ return $email;
1092
+ }
1093
+
1094
+ function get_email_from_request() {
1095
+
1096
+ if (isset($_REQUEST['nek'])) {
1097
+ list($id, $token) = @explode('-', $_REQUEST['nek'], 2);
1098
+ } else if (isset($_COOKIE['tnpe'])) {
1099
+ list($id, $token) = @explode('-', $_COOKIE['tnpe'], 2);
1100
+ } else {
1101
+ return null;
1102
+ }
1103
+
1104
+ $email = $this->get_email($id);
1105
+
1106
+ // TODO: Check the token? It's really useful?
1107
+
1108
+ return $email;
1109
+ }
1110
+
1111
+ /**
1112
+ * Delete one or more emails identified by ID (single value or array of ID)
1113
+ *
1114
+ * @global wpdb $wpdb
1115
+ * @param int|array $id Single numeric ID or an array of IDs to be deleted
1116
+ * @return boolean
1117
+ */
1118
+ function delete_email($id) {
1119
+ global $wpdb;
1120
+ $r = $this->store->delete(NEWSLETTER_EMAILS_TABLE, $id);
1121
+ if ($r !== false) {
1122
+ // $id could be an array if IDs
1123
+ $id = (array) $id;
1124
+ foreach ($id as $email_id) {
1125
+ $wpdb->delete(NEWSLETTER_STATS_TABLE, ['email_id' => $email_id]);
1126
+ $wpdb->delete(NEWSLETTER_SENT_TABLE, ['email_id' => $email_id]);
1127
+ }
1128
+ }
1129
+ return $r;
1130
+ }
1131
+
1132
+ function get_email_field($id, $field_name) {
1133
+ return $this->store->get_field(NEWSLETTER_EMAILS_TABLE, $id, $field_name);
1134
+ }
1135
+
1136
+ function get_email_status_slug($email) {
1137
+ $email = (object) $email;
1138
+ if ($email->status == 'sending' && $email->send_on > time()) {
1139
+ return 'scheduled';
1140
+ }
1141
+ return $email->status;
1142
+ }
1143
+
1144
+ function get_email_status_label($email) {
1145
+ $email = (object) $email;
1146
+ $status = $this->get_email_status_slug($email);
1147
+ switch ($status) {
1148
+ case 'sending':
1149
+ return __('Sending', 'newsletter');
1150
+ case 'scheduled':
1151
+ return __('Scheduled', 'newsletter');
1152
+ case 'sent':
1153
+ return __('Sent', 'newsletter');
1154
+ case 'paused':
1155
+ return __('Paused', 'newsletter');
1156
+ case 'new':
1157
+ return __('Draft', 'newsletter');
1158
+ default:
1159
+ return ucfirst($email->status);
1160
+ }
1161
+ }
1162
+
1163
+ function show_email_status_label($email) {
1164
+ echo '<span class="tnp-email-status tnp-email-status--', $this->get_email_status_slug($email), '">', esc_html($this->get_email_status_label($email)), '</span>';
1165
+ }
1166
+
1167
+ function get_email_progress($email, $format = 'percent') {
1168
+ return $email->total > 0 ? intval($email->sent / $email->total * 100) : 0;
1169
+ }
1170
+
1171
+ function show_email_progress_bar($email, $attrs = []) {
1172
+
1173
+ $email = (object) $email;
1174
+
1175
+ $attrs = array_merge(array('format' => 'percent', 'numbers' => false, 'scheduled' => false), $attrs);
1176
+
1177
+ if ($email->status == 'sending' && $email->send_on > time()) {
1178
+ if ($attrs['scheduled']) {
1179
+ echo '<span class="tnp-progress-date">', $this->format_date($email->send_on), '</span>';
1180
+ }
1181
+ return;
1182
+ } else if ($email->status == 'new') {
1183
+ echo '';
1184
+ return;
1185
+ } else if ($email->status == 'sent') {
1186
+ $percent = 100;
1187
+ } else {
1188
+ $percent = $this->get_email_progress($email);
1189
+ }
1190
+
1191
+ echo '<div class="tnp-progress tnp-progress--' . $email->status . '">';
1192
+ echo '<div class="tnp-progress-bar" role="progressbar" style="width: ', $percent, '%;">&nbsp;', $percent, '%&nbsp;</div>';
1193
+ echo '</div>';
1194
+ if ($attrs['numbers']) {
1195
+ if ($email->status == 'sent') {
1196
+ echo '<div class="tnp-progress-numbers">', $email->total, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1197
+ } else {
1198
+ echo '<div class="tnp-progress-numbers">', $email->sent, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1199
+ }
1200
+ }
1201
+ }
1202
+
1203
+ function get_email_type_label($type) {
1204
+
1205
+ // Is an email?
1206
+ if (is_object($type))
1207
+ $type = $type->type;
1208
+
1209
+ $label = apply_filters('newsletter_email_type', '', $type);
1210
+
1211
+ if (!empty($label))
1212
+ return $label;
1213
+
1214
+ switch ($type) {
1215
+ case 'followup':
1216
+ return 'Followup';
1217
+ case 'message':
1218
+ return 'Standard Newsletter';
1219
+ case 'feed':
1220
+ return 'Feed by Mail';
1221
+ }
1222
+
1223
+ if (strpos($type, 'automated') === 0) {
1224
+ list($a, $id) = explode('_', $type);
1225
+ return 'Automated Channel ' . $id;
1226
+ }
1227
+
1228
+ return ucfirst($type);
1229
+ }
1230
+
1231
+ function get_email_progress_label($email) {
1232
+ if ($email->status == 'sent' || $email->status == 'sending') {
1233
+ return $email->sent . ' ' . __('of', 'newsletter') . ' ' . $email->total;
1234
+ }
1235
+ return '-';
1236
+ }
1237
+
1238
+ /**
1239
+ * Returns the email unique key
1240
+ * @param TNP_User $user
1241
+ * @return string
1242
+ */
1243
+ function get_email_key($email) {
1244
+ if (!isset($email->token)) {
1245
+ return $email->id . '-';
1246
+ }
1247
+ return $email->id . '-' . $email->token;
1248
+ }
1249
+
1250
+ /** Searches for a user using the nk parameter or the ni and nt parameters. Tries even with the newsletter cookie.
1251
+ * If found, the user object is returned or null.
1252
+ * The user is returned without regards to his status that should be checked by caller.
1253
+ *
1254
+ * DO NOT REMOVE EVEN IF OLD
1255
+ *
1256
+ * @return TNP_User
1257
+ */
1258
+ function check_user($context = '') {
1259
+ global $wpdb;
1260
+
1261
+ $user = null;
1262
+
1263
+ if (isset($_REQUEST['nk'])) {
1264
+ list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1265
+ } else if (isset($_COOKIE['newsletter'])) {
1266
+ list ($id, $token) = @explode('-', $_COOKIE['newsletter'], 2);
1267
+ }
1268
+
1269
+ if (isset($id)) {
1270
+ $user = $this->get_user($id);
1271
+ if ($user) {
1272
+ if ($context == 'preconfirm') {
1273
+ if ($token != md5($user->token)) {
1274
+ $user = null;
1275
+ }
1276
+ } else {
1277
+ if ($token != $user->token) {
1278
+ $user = null;
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+
1284
+ if ($user == null && is_user_logged_in()) {
1285
+ $user = $this->get_user_by_wp_user_id(get_current_user_id());
1286
+ }
1287
+ return $user;
1288
+ }
1289
+
1290
+ /** Returns the user identify by an id or an email. If $id_or_email is an object or an array, it is assumed it contains
1291
+ * the "id" attribute or key and that is used to load the user.
1292
+ *
1293
+ * @global type $wpdb
1294
+ * @param string|int|object|array $id_or_email
1295
+ * @param string $format
1296
+ * @return TNP_User|null
1297
+ */
1298
+ function get_user($id_or_email, $format = OBJECT) {
1299
+ global $wpdb;
1300
+
1301
+ if (empty($id_or_email))
1302
+ return null;
1303
+
1304
+ // To simplify the reaload of a user passing the user it self.
1305
+ if (is_object($id_or_email)) {
1306
+ $id_or_email = $id_or_email->id;
1307
+ } else if (is_array($id_or_email)) {
1308
+ $id_or_email = $id_or_email['id'];
1309
+ }
1310
+
1311
+ $id_or_email = strtolower(trim($id_or_email));
1312
+
1313
+ if (is_numeric($id_or_email)) {
1314
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where id=%d limit 1", $id_or_email), $format);
1315
+ } else {
1316
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $id_or_email), $format);
1317
+ }
1318
+
1319
+ if ($wpdb->last_error) {
1320
+ $this->logger->error($wpdb->last_error);
1321
+ return null;
1322
+ }
1323
+ return $r;
1324
+ }
1325
+
1326
+ /**
1327
+ *
1328
+ * @global wpdb $wpdb
1329
+ * @param string $email
1330
+ * @return TNP_User
1331
+ */
1332
+ function get_user_by_email($email) {
1333
+ global $wpdb;
1334
+
1335
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $email));
1336
+
1337
+ if ($wpdb->last_error) {
1338
+ $this->logger->error($wpdb->last_error);
1339
+ return null;
1340
+ }
1341
+ return $r;
1342
+ }
1343
+
1344
+ /**
1345
+ * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
1346
+ *
1347
+ * @param type $user
1348
+ */
1349
+ function get_user_edit_url($user) {
1350
+ $id = $this->to_int_id($user);
1351
+ return admin_url('admin.php') . '?page=newsletter_users_edit&id=' . $id;
1352
+ }
1353
+
1354
+ /**
1355
+ * Returns the user unique key
1356
+ * @param TNP_User $user
1357
+ * @return string
1358
+ */
1359
+ function get_user_key($user, $context = '') {
1360
+ if (empty($user->token)) {
1361
+ $this->refresh_user_token($user);
1362
+ }
1363
+
1364
+ if ($context == 'preconfirm') {
1365
+ return $user->id . '-' . md5($user->token);
1366
+ }
1367
+ return $user->id . '-' . $user->token;
1368
+ }
1369
+
1370
+ function get_user_status_label($user, $html = false) {
1371
+ if (!$html)
1372
+ return TNP_User::get_status_label($user->status);
1373
+
1374
+ $label = TNP_User::get_status_label($user->status);
1375
+ $class = 'unknown';
1376
+ switch ($user->status) {
1377
+ case TNP_User::STATUS_NOT_CONFIRMED: $class = 'not-confirmed';
1378
+ break;
1379
+ case TNP_User::STATUS_CONFIRMED: $class = 'confirmed';
1380
+ break;
1381
+ case TNP_User::STATUS_UNSUBSCRIBED: $class = 'unsubscribed';
1382
+ break;
1383
+ case TNP_User::STATUS_BOUNCED: $class = 'bounced';
1384
+ break;
1385
+ case TNP_User::STATUS_COMPLAINED: $class = 'complained';
1386
+ break;
1387
+ }
1388
+ return '<span class="' . $class . '">' . esc_html($label) . '</span>';
1389
+ }
1390
+
1391
+ /**
1392
+ * Return the user identified by the "nk" parameter (POST or GET).
1393
+ * If no user can be found or the token is not matching, returns null.
1394
+ * If die_on_fail is true it dies instead of return null.
1395
+ *
1396
+ * @param bool $die_on_fail
1397
+ * @return TNP_User
1398
+ */
1399
+ function get_user_from_request($die_on_fail = false, $context = '') {
1400
+ $id = 0;
1401
+ if (isset($_REQUEST['nk'])) {
1402
+ list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1403
+ }
1404
+ $user = $this->get_user($id);
1405
+
1406
+ if ($user == null) {
1407
+ if ($die_on_fail) {
1408
+ die(__('No subscriber found.', 'newsletter'));
1409
+ } else {
1410
+ return $this->get_user_from_logged_in_user();
1411
+ }
1412
+ }
1413
+
1414
+ if ($context == 'preconfirm') {
1415
+ $user_token = md5($user->token);
1416
+ } else {
1417
+ $user_token = $user->token;
1418
+ }
1419
+
1420
+ if ($token != $user_token) {
1421
+ if ($die_on_fail) {
1422
+ die(__('No subscriber found.', 'newsletter'));
1423
+ } else {
1424
+ return $this->get_user_from_logged_in_user();
1425
+ }
1426
+ }
1427
+ return $user;
1428
+ }
1429
+
1430
+ function get_user_from_logged_in_user() {
1431
+ if (is_user_logged_in()) {
1432
+ return $this->get_user_by_wp_user_id(get_current_user_id());
1433
+ }
1434
+ return null;
1435
+ }
1436
+
1437
+ function get_user_count($refresh = false) {
1438
+ global $wpdb;
1439
+ $user_count = get_transient('newsletter_user_count');
1440
+ if ($user_count === false || $refresh) {
1441
+ $user_count = $wpdb->get_var("select count(*) from " . NEWSLETTER_USERS_TABLE . " where status='C'");
1442
+ set_transient('newsletter_user_count', $user_count, DAY_IN_SECONDS);
1443
+ }
1444
+ return $user_count;
1445
+ }
1446
+
1447
+ function get_profile($id, $language = '') {
1448
+ return TNP_Profile_Service::get_profile_by_id($id, $language);
1449
+ }
1450
+
1451
+ /**
1452
+ * @param string $language The language for the list labels (it does not affect the lists returned)
1453
+ * @return TNP_Profile[]
1454
+ */
1455
+ function get_profiles($language = '') {
1456
+ return TNP_Profile_Service::get_profiles($language);
1457
+ }
1458
+
1459
+ /**
1460
+ * Returns a list of TNP_Profile which are public.
1461
+ *
1462
+ * @staticvar array $profiles
1463
+ * @param string $language
1464
+ * @return TNP_Profile[]
1465
+ */
1466
+ function get_profiles_public($language = '') {
1467
+ static $profiles = [];
1468
+ if (isset($profiles[$language])) {
1469
+ return $profiles[$language];
1470
+ }
1471
+
1472
+ $profiles[$language] = [];
1473
+ $all = $this->get_profiles($language);
1474
+ foreach ($all as $profile) {
1475
+ if ($profile->is_private())
1476
+ continue;
1477
+
1478
+ $profiles[$language]['' . $profile->id] = $profile;
1479
+ }
1480
+ return $profiles[$language];
1481
+ }
1482
+
1483
+ /**
1484
+ * Really bad name!
1485
+ * @staticvar array $profiles
1486
+ * @param type $language
1487
+ * @return array
1488
+ */
1489
+ function get_profiles_for_profile($language = '') {
1490
+ static $profiles = [];
1491
+ if (isset($profiles[$language])) {
1492
+ return $profiles[$language];
1493
+ }
1494
+
1495
+ $profiles[$language] = [];
1496
+ $all = $this->get_profiles($language);
1497
+ foreach ($all as $profile) {
1498
+ if (!$profile->show_on_profile())
1499
+ continue;
1500
+
1501
+ $profiles[$language]['' . $profile->id] = $profile;
1502
+ }
1503
+ return $profiles[$language];
1504
+ }
1505
+
1506
+ /**
1507
+ * @param string $language The language for the list labels (it does not affect the lists returned)
1508
+ * @return TNP_List[]
1509
+ */
1510
+ function get_lists($language = '') {
1511
+ static $lists = array();
1512
+ if (isset($lists[$language])) {
1513
+ return $lists[$language];
1514
+ }
1515
+
1516
+ $lists[$language] = array();
1517
+ $data = NewsletterSubscription::instance()->get_options('lists', $language);
1518
+ for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
1519
+ if (empty($data['list_' . $i])) {
1520
+ continue;
1521
+ }
1522
+ $list = $this->create_tnp_list_from_db_lists_array($data, $i);
1523
+
1524
+ $lists[$language]['' . $list->id] = $list;
1525
+ }
1526
+ return $lists[$language];
1527
+ }
1528
+
1529
+ public function create_tnp_list_from_db_lists_array($db_lists_array, $list_id) {
1530
+
1531
+ $list = new TNP_List();
1532
+ $list->name = $db_lists_array['list_' . $list_id];
1533
+ $list->id = $list_id;
1534
+
1535
+ // New format
1536
+ if (isset($db_lists_array['list_' . $list_id . '_subscription'])) {
1537
+ $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1538
+ $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1539
+ $list->checked = $db_lists_array['list_' . $list_id . '_subscription'] == 2;
1540
+ $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_subscription']) && !$list->forced;
1541
+ $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_profile']);
1542
+ } else {
1543
+ $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1544
+ $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1545
+ $list->checked = !empty($db_lists_array['list_' . $list_id . '_checked']);
1546
+ $list->show_on_subscription = $db_lists_array['list_' . $list_id . '_status'] == 2 && !$list->forced;
1547
+ $list->show_on_profile = $db_lists_array['list_' . $list_id . '_status'] == 1 || $db_lists_array['list_' . $list_id . '_status'] == 2;
1548
+ }
1549
+ if (empty($db_lists_array['list_' . $list_id . '_languages'])) {
1550
+ $list->languages = array();
1551
+ } else {
1552
+ $list->languages = $db_lists_array['list_' . $list_id . '_languages'];
1553
+ }
1554
+
1555
+ return $list;
1556
+ }
1557
+
1558
+ /**
1559
+ * Returns an array of TNP_List objects of lists that are public.
1560
+ * @return TNP_List[]
1561
+ */
1562
+ function get_lists_public($language = '') {
1563
+ static $lists = array();
1564
+ if (isset($lists[$language])) {
1565
+ return $lists[$language];
1566
+ }
1567
+
1568
+ $lists[$language] = array();
1569
+ $all = $this->get_lists($language);
1570
+ foreach ($all as $list) {
1571
+ if ($list->status == TNP_List::STATUS_PRIVATE) {
1572
+ continue;
1573
+ }
1574
+ $lists[$language]['' . $list->id] = $list;
1575
+ }
1576
+ return $lists[$language];
1577
+ }
1578
+
1579
+ /**
1580
+ * Lists to be shown on subscription form.
1581
+ *
1582
+ * @return TNP_List[]
1583
+ */
1584
+ function get_lists_for_subscription($language = '') {
1585
+ static $lists = array();
1586
+ if (isset($lists[$language])) {
1587
+ return $lists[$language];
1588
+ }
1589
+
1590
+ $lists[$language] = array();
1591
+ $all = $this->get_lists($language);
1592
+ foreach ($all as $list) {
1593
+ if (!$list->show_on_subscription) {
1594
+ continue;
1595
+ }
1596
+ $lists[$language]['' . $list->id] = $list;
1597
+ }
1598
+ return $lists[$language];
1599
+ }
1600
+
1601
+ /**
1602
+ * Returns the lists to be shown in the profile page. The list is associative with
1603
+ * the list ID as key.
1604
+ *
1605
+ * @return TNP_List[]
1606
+ */
1607
+ function get_lists_for_profile($language = '') {
1608
+ static $lists = array();
1609
+ if (isset($lists[$language])) {
1610
+ return $lists[$language];
1611
+ }
1612
+
1613
+ $lists[$language] = array();
1614
+ $all = $this->get_lists($language);
1615
+ foreach ($all as $list) {
1616
+ if (!$list->show_on_profile) {
1617
+ continue;
1618
+ }
1619
+ $lists[$language]['' . $list->id] = $list;
1620
+ }
1621
+ return $lists[$language];
1622
+ }
1623
+
1624
+ /**
1625
+ * Returns the list object or null if not found.
1626
+ *
1627
+ * @param int $id
1628
+ * @return TNP_List
1629
+ */
1630
+ function get_list($id, $language = '') {
1631
+ $lists = $this->get_lists($language);
1632
+ if (!isset($lists['' . $id])) {
1633
+ return null;
1634
+ }
1635
+
1636
+ return $lists['' . $id];
1637
+ }
1638
+
1639
+ /**
1640
+ * NEVER CHANGE THIS METHOD SIGNATURE, USER BY THIRD PARTY PLUGINS.
1641
+ *
1642
+ * Saves a new user on the database. Return false if the email (that must be unique) is already
1643
+ * there. For a new users set the token and creation time if not passed.
1644
+ *
1645
+ * @param array $user
1646
+ * @return TNP_User|array|boolean Returns the subscriber reloaded from DB in the specified format. Flase on failure (duplicate email).
1647
+ */
1648
+ function save_user($user, $return_format = OBJECT) {
1649
+ if (is_object($user)) {
1650
+ $user = (array) $user;
1651
+ }
1652
+ if (empty($user['id'])) {
1653
+ $existing = $this->get_user($user['email']);
1654
+ if ($existing != null) {
1655
+ return false;
1656
+ }
1657
+ if (empty($user['token'])) {
1658
+ $user['token'] = NewsletterModule::get_token();
1659
+ }
1660
+ }
1661
+
1662
+ // We still don't know when it happens but under some conditions, matbe external, lists are passed as NULL
1663
+ foreach ($user as $key => $value) {
1664
+ if (strpos($key, 'list_') !== 0) {
1665
+ continue;
1666
+ }
1667
+ if (is_null($value)) {
1668
+ unset($user[$key]);
1669
+ } else {
1670
+ $user[$key] = (int) $value;
1671
+ }
1672
+ }
1673
+
1674
+ // Due to the unique index on email field, this can fail.
1675
+ return $this->store->save(NEWSLETTER_USERS_TABLE, $user, $return_format);
1676
+ }
1677
+
1678
+ /**
1679
+ * Updates the user last activity timestamp.
1680
+ *
1681
+ * @global wpdb $wpdb
1682
+ * @param TNP_User $user
1683
+ */
1684
+ function update_user_last_activity($user) {
1685
+ global $wpdb;
1686
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set last_activity=%d where id=%d limit 1", time(), $user->id));
1687
+ }
1688
+
1689
+ function update_user_ip($user, $ip) {
1690
+ global $wpdb;
1691
+ // Only if changed
1692
+ $r = $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set ip=%s, geo=0 where ip<>%s and id=%d limit 1", $ip, $ip, $user->id));
1693
+ }
1694
+
1695
+ /**
1696
+ * Finds single style blocks and adds a style attribute to every HTML tag with a class exactly matching the rules in the style
1697
+ * block. HTML tags can use the attribute "inline-class" to exact match a style rules if they need a composite class definition.
1698
+ *
1699
+ * @param string $content
1700
+ * @param boolean $strip_style_blocks
1701
+ * @return string
1702
+ */
1703
+ function inline_css($content, $strip_style_blocks = false) {
1704
+ $matches = array();
1705
+ // "s" skips line breaks
1706
+ $styles = preg_match('|<style>(.*?)</style>|s', $content, $matches);
1707
+ if (isset($matches[1])) {
1708
+ $style = str_replace(array("\n", "\r"), '', $matches[1]);
1709
+ $rules = array();
1710
+ preg_match_all('|\s*\.(.*?)\{(.*?)\}\s*|s', $style, $rules);
1711
+ for ($i = 0; $i < count($rules[1]); $i++) {
1712
+ $class = trim($rules[1][$i]);
1713
+ $value = trim($rules[2][$i]);
1714
+ $value = preg_replace('|\s+|', ' ', $value);
1715
+ $content = str_replace(' class="' . $class . '"', ' class="' . $class . '" style="' . $value . '"', $content);
1716
+ $content = str_replace(' inline-class="' . $class . '"', ' style="' . $value . '"', $content);
1717
+ }
1718
+ }
1719
+
1720
+ if ($strip_style_blocks) {
1721
+ return trim(preg_replace('|<style>.*?</style>|s', '', $content));
1722
+ } else {
1723
+ return $content;
1724
+ }
1725
+ }
1726
+
1727
+ /**
1728
+ * Returns a list of users marked as "test user".
1729
+ * @return TNP_User[]
1730
+ */
1731
+ function get_test_users() {
1732
+ return $this->store->get_all(NEWSLETTER_USERS_TABLE, "where test=1 and status in ('C', 'S')");
1733
+ }
1734
+
1735
+ /**
1736
+ * Deletes a subscriber and cleans up all the stats table with his correlated data.
1737
+ *
1738
+ * @global wpdb $wpdb
1739
+ * @param int|id[] $id
1740
+ */
1741
+ function delete_user($id) {
1742
+ global $wpdb;
1743
+ $id = (array) $id;
1744
+ foreach ($id as $user_id) {
1745
+ $user = $this->get_user($user_id);
1746
+ if ($user) {
1747
+ $r = $this->store->delete(NEWSLETTER_USERS_TABLE, $user_id);
1748
+ $wpdb->delete(NEWSLETTER_STATS_TABLE, array('user_id' => $user_id));
1749
+ $wpdb->delete(NEWSLETTER_SENT_TABLE, array('user_id' => $user_id));
1750
+ do_action('newsletter_user_deleted', $user);
1751
+ }
1752
+ }
1753
+
1754
+ return count($id);
1755
+ }
1756
+
1757
+ /**
1758
+ * Add to a destination URL the parameters to identify the user, the email and to show
1759
+ * an alert message, if required. The parameters are then managed by the [newsletter] shortcode.
1760
+ *
1761
+ * @param string $url If empty the standard newsletter page URL is used (usually it is empty, but sometime a custom URL has been specified)
1762
+ * @param string $message_key The message identifier
1763
+ * @param TNP_User|int $user
1764
+ * @param TNP_Email|int $email
1765
+ * @param string $alert An optional alter message to be shown. Does not work with custom URLs
1766
+ * @return string The final URL with parameters
1767
+ */
1768
+ function build_message_url($url = '', $message_key = '', $user = null, $email = null, $alert = '') {
1769
+ $params = 'nm=' . urlencode($message_key);
1770
+ $language = '';
1771
+ if ($user) {
1772
+ if (!is_object($user)) {
1773
+ $user = $this->get_user($user);
1774
+ }
1775
+ if ($message_key == 'confirmation') {
1776
+ $params .= '&nk=' . urlencode($this->get_user_key($user, 'preconfirm'));
1777
+ } else {
1778
+ $params .= '&nk=' . urlencode($this->get_user_key($user));
1779
+ }
1780
+
1781
+ $language = $this->get_user_language($user);
1782
+ }
1783
+
1784
+ if ($email) {
1785
+ if (!is_object($email)) {
1786
+ $email = $this->get_email($email);
1787
+ }
1788
+ $params .= '&nek=' . urlencode($this->get_email_key($email));
1789
+ }
1790
+
1791
+ if ($alert) {
1792
+ $params .= '&alert=' . urlencode($alert);
1793
+ }
1794
+
1795
+ if (empty($url)) {
1796
+ $url = Newsletter::instance()->get_newsletter_page_url($language);
1797
+ }
1798
+
1799
+ return self::add_qs($url, $params, false);
1800
+ }
1801
+
1802
+ /**
1803
+ * Builds a standard Newsletter action URL for the specified action.
1804
+ *
1805
+ * @param string $action
1806
+ * @param TNP_User $user
1807
+ * @param TNP_Email $email
1808
+ * @return string
1809
+ */
1810
+ function build_action_url($action, $user = null, $email = null) {
1811
+ $url = $this->add_qs($this->get_home_url(), 'na=' . urlencode($action));
1812
+ //$url = $this->add_qs(admin_url('admin-ajax.php'), 'action=newsletter&na=' . urlencode($action));
1813
+ if ($user) {
1814
+ $url .= '&nk=' . urlencode($this->get_user_key($user));
1815
+ }
1816
+ if ($email) {
1817
+ $url .= '&nek=' . urlencode($this->get_email_key($email));
1818
+ }
1819
+ return $url;
1820
+ }
1821
+
1822
+ function get_subscribe_url() {
1823
+ return $this->build_action_url('s');
1824
+ }
1825
+
1826
+ function clean_stats_table() {
1827
+ global $wpdb;
1828
+ $this->logger->info('Cleaning up stats table');
1829
+ $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
1830
+ $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
1831
+ }
1832
+
1833
+ function clean_sent_table() {
1834
+ global $wpdb;
1835
+ $this->logger->info('Cleaning up sent table');
1836
+ $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
1837
+ $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
1838
+ }
1839
+
1840
+ function clean_user_logs_table() {
1841
+ //global $wpdb;
1842
+ }
1843
+
1844
+ function clean_tables() {
1845
+ $this->clean_sent_table();
1846
+ $this->clean_stats_table();
1847
+ $this->clean_user_logs_table();
1848
+ }
1849
+
1850
+ function anonymize_ip($ip) {
1851
+ if (empty($ip)) {
1852
+ return $ip;
1853
+ }
1854
+ $parts = explode('.', $ip);
1855
+ array_pop($parts);
1856
+ return implode('.', $parts) . '.0';
1857
+ }
1858
+
1859
+ function process_ip($ip) {
1860
+
1861
+ $option = Newsletter::instance()->options['ip'];
1862
+ if (empty($option)) {
1863
+ return $ip;
1864
+ }
1865
+ if ($option == 'anonymize') {
1866
+ return $this->anonymize_ip($ip);
1867
+ }
1868
+ return '';
1869
+ }
1870
+
1871
+ function anonymize_user($id) {
1872
+ global $wpdb;
1873
+ $user = $this->get_user($id);
1874
+ if (!$user) {
1875
+ return null;
1876
+ }
1877
+
1878
+ $user->name = '';
1879
+ $user->surname = '';
1880
+ $user->ip = $this->anonymize_ip($user->ip);
1881
+
1882
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1883
+ $field = 'profile_' . $i;
1884
+ $user->$field = '';
1885
+ }
1886
+
1887
+ // [TODO] Status?
1888
+ $user->status = TNP_User::STATUS_UNSUBSCRIBED;
1889
+ $user->email = $user->id . '@anonymi.zed';
1890
+
1891
+ $user = $this->save_user($user);
1892
+
1893
+ return $user;
1894
+ }
1895
+
1896
+ /**
1897
+ * Changes a user status. Accept a user object, user id or user email.
1898
+ *
1899
+ * @param TNP_User $user
1900
+ * @param string $status
1901
+ * @return TNP_User
1902
+ */
1903
+ function set_user_status($user, $status) {
1904
+ global $wpdb;
1905
+
1906
+ $this->logger->debug('Status change to ' . $status . ' of subscriber ' . $user->id . ' from ' . $_SERVER['REQUEST_URI']);
1907
+
1908
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set status=%s where id=%d limit 1", $status, $user->id));
1909
+ $user->status = $status;
1910
+ return $this->get_user($user);
1911
+ }
1912
+
1913
+ /**
1914
+ *
1915
+ * @global wpdb $wpdb
1916
+ * @param TNP_User $user
1917
+ * @return TNP_User
1918
+ */
1919
+ function refresh_user_token($user) {
1920
+ global $wpdb;
1921
+
1922
+ $token = $this->get_token();
1923
+
1924
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set token=%s where id=%d limit 1", $token, $user->id));
1925
+ $user->token = $token;
1926
+ }
1927
+
1928
+ /**
1929
+ * Create a log entry with the meaningful user data.
1930
+ *
1931
+ * @global wpdb $wpdb
1932
+ * @param TNP_User $user
1933
+ * @param string $source
1934
+ * @return type
1935
+ */
1936
+ function add_user_log($user, $source = '') {
1937
+ global $wpdb;
1938
+
1939
+ $lists = $this->get_lists_public();
1940
+ foreach ($lists as $list) {
1941
+ $field_name = 'list_' . $list->id;
1942
+ $data[$field_name] = $user->$field_name;
1943
+ }
1944
+ $data['status'] = $user->status;
1945
+ $ip = $this->get_remote_ip();
1946
+ $ip = $this->process_ip($ip);
1947
+ $this->store->save($wpdb->prefix . 'newsletter_user_logs', array('ip' => $ip, 'user_id' => $user->id, 'source' => $source, 'created' => time(), 'data' => json_encode($data)));
1948
+ }
1949
+
1950
+ /**
1951
+ *
1952
+ * @global wpdb $wpdb
1953
+ * @param TNP_User $user
1954
+ * @param int $list
1955
+ * @param type $value
1956
+ */
1957
+ function set_user_list($user, $list, $value) {
1958
+ global $wpdb;
1959
+
1960
+ $list = (int) $list;
1961
+ $value = $value ? 1 : 0;
1962
+ $r = $wpdb->update(NEWSLETTER_USERS_TABLE, array('list_' . $list => $value), array('id' => $user->id));
1963
+ }
1964
+
1965
+ function set_user_field($id, $field, $value) {
1966
+ $this->store->set_field(NEWSLETTER_USERS_TABLE, $id, $field, $value);
1967
+ }
1968
+
1969
+ function set_user_wp_user_id($user_id, $wp_user_id) {
1970
+ $this->store->set_field(NEWSLETTER_USERS_TABLE, $user_id, 'wp_user_id', $wp_user_id);
1971
+ }
1972
+
1973
+ /**
1974
+ *
1975
+ * @param int $wp_user_id
1976
+ * @param string $format
1977
+ * @return TNP_User
1978
+ */
1979
+ function get_user_by_wp_user_id($wp_user_id, $format = OBJECT) {
1980
+ return $this->store->get_single_by_field(NEWSLETTER_USERS_TABLE, 'wp_user_id', $wp_user_id, $format);
1981
+ }
1982
+
1983
+ /**
1984
+ * Returns the user language IF there is a supported mutilanguage plugin installed.
1985
+ * @param TNP_User $user
1986
+ * @return string Language code or empty
1987
+ */
1988
+ function get_user_language($user) {
1989
+ if ($user && $this->is_multilanguage()) {
1990
+ return $user->language;
1991
+ }
1992
+ return '';
1993
+ }
1994
+
1995
+ /**
1996
+ * Replaces every possible Newsletter tag ({...}) in a piece of text or HTML.
1997
+ *
1998
+ * @global wpdb $wpdb
1999
+ * @param string $text
2000
+ * @param mixed $user Can be an object, associative array or id
2001
+ * @param mixed $email Can be an object, associative array or id
2002
+ * @param type $referrer
2003
+ * @return type
2004
+ */
2005
+ function replace($text, $user = null, $email = null, $referrer = null) {
2006
+ global $wpdb;
2007
+
2008
+ if (strpos($text, '<p') !== false) {
2009
+ $esc_html = true;
2010
+ } else {
2011
+ $esc_html = false;
2012
+ }
2013
+
2014
+ static $home_url = false;
2015
+
2016
+ if (!$home_url) {
2017
+ $home_url = home_url('/');
2018
+ }
2019
+
2020
+ //$this->logger->debug('Replace start');
2021
+ if ($user !== null && !is_object($user)) {
2022
+ if (is_array($user)) {
2023
+ $user = (object) $user;
2024
+ } else if (is_numeric($user)) {
2025
+ $user = $this->get_user($user);
2026
+ } else {
2027
+ $user = null;
2028
+ }
2029
+ }
2030
+
2031
+ if ($email !== null && !is_object($email)) {
2032
+ if (is_array($email)) {
2033
+ $email = (object) $email;
2034
+ } else if (is_numeric($email)) {
2035
+ $email = $this->get_email($email);
2036
+ } else {
2037
+ $email = null;
2038
+ }
2039
+ }
2040
+
2041
+ $initial_language = $this->get_current_language();
2042
+
2043
+ if ($user && $user->language) {
2044
+ $this->switch_language($user->language);
2045
+ }
2046
+
2047
+
2048
+ $text = apply_filters('newsletter_replace', $text, $user, $email, $esc_html);
2049
+
2050
+ $text = $this->replace_url($text, 'blog_url', $home_url);
2051
+ $text = $this->replace_url($text, 'home_url', $home_url);
2052
+
2053
+ $text = str_replace('{blog_title}', html_entity_decode(get_bloginfo('name')), $text);
2054
+ $text = str_replace('{blog_description}', get_option('blogdescription'), $text);
2055
+
2056
+ $text = $this->replace_date($text);
2057
+
2058
+ if ($user) {
2059
+ //$this->logger->debug('Replace with user ' . $user->id);
2060
+ $nk = $this->get_user_key($user);
2061
+ $options_profile = NewsletterSubscription::instance()->get_options('profile', $this->get_user_language($user));
2062
+ $text = str_replace('{email}', $user->email, $text);
2063
+ $name = apply_filters('newsletter_replace_name', $user->name, $user);
2064
+ if (empty($name)) {
2065
+ $text = str_replace(' {name}', '', $text);
2066
+ $text = str_replace('{name}', '', $text);
2067
+ } else {
2068
+ $text = str_replace('{name}', esc_html($name), $text);
2069
+ }
2070
+
2071
+ switch ($user->sex) {
2072
+ case 'm': $text = str_replace('{title}', $options_profile['title_male'], $text);
2073
+ break;
2074
+ case 'f': $text = str_replace('{title}', $options_profile['title_female'], $text);
2075
+ break;
2076
+ case 'n': $text = str_replace('{title}', $options_profile['title_none'], $text);
2077
+ break;
2078
+ default:
2079
+ $text = str_replace('{title}', '', $text);
2080
+ }
2081
+
2082
+
2083
+ // Deprecated
2084
+ $text = str_replace('{surname}', esc_html($user->surname), $text);
2085
+ $text = str_replace('{last_name}', esc_html($user->surname), $text);
2086
+
2087
+ $full_name = esc_html(trim($user->name . ' ' . $user->surname));
2088
+ if (empty($full_name)) {
2089
+ $text = str_replace(' {full_name}', '', $text);
2090
+ $text = str_replace('{full_name}', '', $text);
2091
+ } else {
2092
+ $text = str_replace('{full_name}', $full_name, $text);
2093
+ }
2094
+
2095
+ $text = str_replace('{token}', $user->token, $text);
2096
+ $text = str_replace('%7Btoken%7D', $user->token, $text);
2097
+ $text = str_replace('{id}', $user->id, $text);
2098
+ $text = str_replace('%7Bid%7D', $user->id, $text);
2099
+ $text = str_replace('{ip}', $user->ip, $text);
2100
+ $text = str_replace('{key}', $nk, $text);
2101
+ $text = str_replace('%7Bkey%7D', $nk, $text);
2102
+
2103
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
2104
+ $p = 'profile_' . $i;
2105
+ $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
2106
+ }
2107
+
2108
+ $base = (empty($this->options_main['url']) ? get_option('home') : $this->options_main['url']);
2109
+ $id_token = '&amp;ni=' . $user->id . '&amp;nt=' . $user->token;
2110
+
2111
+ $text = $this->replace_url($text, 'subscription_confirm_url', $this->build_action_url('c', $user));
2112
+ $text = $this->replace_url($text, 'activation_url', $this->build_action_url('c', $user));
2113
+
2114
+ // Obsolete.
2115
+ $text = $this->replace_url($text, 'FOLLOWUP_SUBSCRIPTION_URL', self::add_qs($base, 'nm=fs' . $id_token));
2116
+ $text = $this->replace_url($text, 'FOLLOWUP_UNSUBSCRIPTION_URL', self::add_qs($base, 'nm=fu' . $id_token));
2117
+
2118
+ $text = $this->replace_url($text, 'UNLOCK_URL', $this->build_action_url('ul', $user));
2119
+ } else {
2120
+ //$this->logger->debug('Replace without user');
2121
+ $text = $this->replace_url($text, 'subscription_confirm_url', '#');
2122
+ $text = $this->replace_url($text, 'activation_url', '#');
2123
+ }
2124
+
2125
+ if ($email) {
2126
+ //$this->logger->debug('Replace with email ' . $email->id);
2127
+ $nek = $this->get_email_key($email);
2128
+ $text = str_replace('{email_id}', $email->id, $text);
2129
+ $text = str_replace('{email_key}', $nek, $text);
2130
+ $text = str_replace('{email_subject}', $email->subject, $text);
2131
+ // Deprecated
2132
+ $text = str_replace('{subject}', $email->subject, $text);
2133
+ $text = $this->replace_url($text, 'email_url', $this->build_action_url('v', $user) . '&id=' . $email->id);
2134
+ } else {
2135
+ //$this->logger->debug('Replace without email');
2136
+ $text = $this->replace_url($text, 'email_url', '#');
2137
+ }
2138
+
2139
+ if (strpos($text, '{subscription_form}') !== false) {
2140
+ $text = str_replace('{subscription_form}', NewsletterSubscription::instance()->get_subscription_form($referrer), $text);
2141
+ } else {
2142
+ for ($i = 1; $i <= 10; $i++) {
2143
+ if (strpos($text, "{subscription_form_$i}") !== false) {
2144
+ $text = str_replace("{subscription_form_$i}", NewsletterSubscription::instance()->get_form($i), $text);
2145
+ break;
2146
+ }
2147
+ }
2148
+ }
2149
+
2150
+ // Company info
2151
+ // TODO: Move to another module
2152
+ $options = Newsletter::instance()->get_options('info');
2153
+ $text = str_replace('{company_address}', $options['footer_contact'], $text);
2154
+ $text = str_replace('{company_name}', $options['footer_title'], $text);
2155
+ $text = str_replace('{company_legal}', $options['footer_legal'], $text);
2156
+
2157
+ $this->switch_language($initial_language);
2158
+ //$this->logger->debug('Replace end');
2159
+ return $text;
2160
+ }
2161
+
2162
+ function replace_date($text) {
2163
+ $text = str_replace('{date}', date_i18n(get_option('date_format')), $text);
2164
+
2165
+ // Date processing
2166
+ $x = 0;
2167
+ while (($x = strpos($text, '{date_', $x)) !== false) {
2168
+ $y = strpos($text, '}', $x);
2169
+ if ($y === false)
2170
+ continue;
2171
+ $f = substr($text, $x + 6, $y - $x - 6);
2172
+ $text = substr($text, 0, $x) . date_i18n($f) . substr($text, $y + 1);
2173
+ }
2174
+ return $text;
2175
+ }
2176
+
2177
+ function replace_url($text, $tag, $url) {
2178
+ static $home = false;
2179
+ if (!$home) {
2180
+ $home = trailingslashit(home_url());
2181
+ }
2182
+ $tag_lower = strtolower($tag);
2183
+ $text = str_replace('http://{' . $tag_lower . '}', $url, $text);
2184
+ $text = str_replace('https://{' . $tag_lower . '}', $url, $text);
2185
+ $text = str_replace($home . '{' . $tag_lower . '}', $url, $text);
2186
+ $text = str_replace($home . '%7B' . $tag_lower . '%7D', $url, $text);
2187
+ $text = str_replace('{' . $tag_lower . '}', $url, $text);
2188
+ $text = str_replace('%7B' . $tag_lower . '%7D', $url, $text);
2189
+
2190
+ $url_encoded = urlencode($url);
2191
+ $text = str_replace('%7B' . $tag_lower . '_encoded%7D', $url_encoded, $text);
2192
+ $text = str_replace('{' . $tag_lower . '_encoded}', $url_encoded, $text);
2193
+
2194
+ // for compatibility
2195
+ $text = str_replace($home . $tag, $url, $text);
2196
+
2197
+ return $text;
2198
+ }
2199
+
2200
+ public static function antibot_form_check($captcha = false) {
2201
+
2202
+ if (defined('NEWSLETTER_ANTIBOT') && !NEWSLETTER_ANTIBOT) {
2203
+ return true;
2204
+ }
2205
+
2206
+ if (strtolower($_SERVER['REQUEST_METHOD']) != 'post') {
2207
+ return false;
2208
+ }
2209
+
2210
+ if (!isset($_POST['ts']) || time() - $_POST['ts'] > 60) {
2211
+ return false;
2212
+ }
2213
+
2214
+ if ($captcha) {
2215
+ $n1 = (int) $_POST['n1'];
2216
+ if (empty($n1)) {
2217
+ return false;
2218
+ }
2219
+ $n2 = (int) $_POST['n2'];
2220
+ if (empty($n2)) {
2221
+ return false;
2222
+ }
2223
+ $n3 = (int) $_POST['n3'];
2224
+ if ($n1 + $n2 != $n3) {
2225
+ return false;
2226
+ }
2227
+ }
2228
+
2229
+ return true;
2230
+ }
2231
+
2232
+ public static function request_to_antibot_form($submit_label = 'Continue...', $captcha = false) {
2233
+ header('Content-Type: text/html;charset=UTF-8');
2234
+ header('X-Robots-Tag: noindex,nofollow,noarchive');
2235
+ header('Cache-Control: no-cache,no-store,private');
2236
+ echo "<!DOCTYPE html>\n";
2237
+ echo '<html><head>'
2238
+ . '<style type="text/css">'
2239
+ . '.tnp-captcha {text-align: center; margin: 200px auto 0 auto !important; max-width: 300px !important; padding: 10px !important; font-family: "Open Sans", sans-serif; background: #ECF0F1; border-radius: 5px; padding: 50px !important; border: none !important;}'
2240
+ . 'p {text-align: center; padding: 10px; color: #7F8C8D;}'
2241
+ . 'input[type=text] {width: 50px; padding: 10px 10px; border: none; border-radius: 2px; margin: 0px 5px;}'
2242
+ . 'input[type=submit] {text-align: center; border: none; padding: 10px 15px; font-family: "Open Sans", sans-serif; background-color: #27AE60; color: white; cursor: pointer;}'
2243
+ . '</style>'
2244
+ . '</head><body>';
2245
+ echo '<form method="post" action="https://www.domain.tld" id="form">';
2246
+ echo '<div style="width: 1px; height: 1px; overflow: hidden">';
2247
+ foreach ($_REQUEST as $name => $value) {
2248
+ if ($name == 'submit')
2249
+ continue;
2250
+ if (is_array($value)) {
2251
+ foreach ($value as $element) {
2252
+ echo '<input type="text" name="';
2253
+ echo esc_attr($name);
2254
+ echo '[]" value="';
2255
+ echo esc_attr(stripslashes($element));
2256
+ echo '">';
2257
+ }
2258
+ } else {
2259
+ echo '<input type="hidden" name="', esc_attr($name), '" value="', esc_attr(stripslashes($value)), '">';
2260
+ }
2261
+ }
2262
+ if (isset($_SERVER['HTTP_REFERER'])) {
2263
+ echo '<input type="hidden" name="nhr" value="' . esc_attr($_SERVER['HTTP_REFERER']) . '">';
2264
+ }
2265
+ echo '<input type="hidden" name="ts" value="' . time() . '">';
2266
+ echo '</div>';
2267
+
2268
+ if ($captcha) {
2269
+ echo '<div class="tnp-captcha">';
2270
+ echo '<p>', __('Math question', 'newsletter'), '</p>';
2271
+ echo '<input type="text" name="n1" value="', rand(1, 9), '" readonly style="width: 50px">';
2272
+ echo '+';
2273
+ echo '<input type="text" name="n2" value="', rand(1, 9), '" readonly style="width: 50px">';
2274
+ echo '=';
2275
+ echo '<input type="text" name="n3" value="?" style="width: 50px">';
2276
+ echo '<br><br>';
2277
+ echo '<input type="submit" value="', esc_attr($submit_label), '">';
2278
+ echo '</div>';
2279
+ }
2280
+ echo '<noscript><input type="submit" value="';
2281
+ echo esc_attr($submit_label);
2282
+ echo '"></noscript></form>';
2283
+ echo '<script>';
2284
+ echo 'document.getElementById("form").action="' . home_url('/') . '";';
2285
+ if (!$captcha) {
2286
+ echo 'document.getElementById("form").submit();';
2287
+ }
2288
+ echo '</script>';
2289
+ echo '</body></html>';
2290
+ die();
2291
+ }
2292
+
2293
+ static function extract_body($html) {
2294
+ $x = stripos($html, '<body');
2295
+ if ($x !== false) {
2296
+ $x = strpos($html, '>', $x);
2297
+ $y = strpos($html, '</body>');
2298
+ return substr($html, $x + 1, $y - $x - 1);
2299
+ } else {
2300
+ return $html;
2301
+ }
2302
+ }
2303
+
2304
+ /** Returns a percentage as string */
2305
+ static function percent($value, $total) {
2306
+ if ($total == 0)
2307
+ return '-';
2308
+ return sprintf("%.2f", $value / $total * 100) . '%';
2309
+ }
2310
+
2311
+ /** Returns a percentage as integer value */
2312
+ static function percentValue($value, $total) {
2313
+ if ($total == 0)
2314
+ return 0;
2315
+ return round($value / $total * 100);
2316
+ }
2317
+
2318
+ /**
2319
+ * Takes in a variable and checks if object, array or scalar and return the integer representing
2320
+ * a database record id.
2321
+ *
2322
+ * @param mixed $var
2323
+ * @return in
2324
+ */
2325
+ static function to_int_id($var) {
2326
+ if (is_object($var)) {
2327
+ return (int) $var->id;
2328
+ }
2329
+ if (is_array($var)) {
2330
+ return (int) $var['id'];
2331
+ }
2332
+ return (int) $var;
2333
+ }
2334
+
2335
+ static function to_array($text) {
2336
+ $text = trim($text);
2337
+ if (empty($text)) {
2338
+ return array();
2339
+ }
2340
+ $text = preg_split("/\\r\\n/", $text);
2341
+ $text = array_map('trim', $text);
2342
+ $text = array_map('strtolower', $text);
2343
+ $text = array_filter($text);
2344
+
2345
+ return $text;
2346
+ }
2347
+
2348
+ static function sanitize_ip($ip) {
2349
+ if (empty($ip)) {
2350
+ return '';
2351
+ }
2352
+ $ip = preg_replace('/[^0-9a-fA-F:., ]/', '', trim($ip));
2353
+ if (strlen($ip) > 50)
2354
+ $ip = substr($ip, 0, 50);
2355
+
2356
+ // When more than one IP is present due to firewalls, proxies, and so on. The first one should be the origin.
2357
+ if (strpos($ip, ',') !== false) {
2358
+ list($ip, $tail) = explode(',', $ip, 2);
2359
+ }
2360
+ return $ip;
2361
+ }
2362
+
2363
+ static function get_remote_ip() {
2364
+ $ip = '';
2365
+ if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2366
+ $ip = $_SERVER['HTTP_X_REAL_IP'];
2367
+ } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2368
+ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
2369
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
2370
+ $ip = $_SERVER['REMOTE_ADDR'];
2371
+ }
2372
+ return self::sanitize_ip($ip);
2373
+ }
2374
+
2375
+ static function get_signature($text) {
2376
+ $key = NewsletterStatistics::instance()->options['key'];
2377
+ return md5($text . $key);
2378
+ }
2379
+
2380
+ static function check_signature($text, $signature) {
2381
+ if (empty($signature)) {
2382
+ return false;
2383
+ }
2384
+ $key = NewsletterStatistics::instance()->options['key'];
2385
+ return md5($text . $key) === $signature;
2386
+ }
2387
+
2388
+ static function get_home_url() {
2389
+ static $url = false;
2390
+ if (!$url) {
2391
+ $url = home_url('/');
2392
+ }
2393
+ return $url;
2394
+ }
2395
+
2396
+ static function clean_eol($text) {
2397
+ $text = str_replace("\r\n", "\n", $text);
2398
+ $text = str_replace("\r", "\n", $text);
2399
+ $text = str_replace("\n", "\r\n", $text);
2400
+ return $text;
2401
+ }
2402
+
2403
+ function set_current_language($language) {
2404
+ self::$current_language = $language;
2405
+ }
2406
+
2407
+ /**
2408
+ * Return the current language code. Optionally, if a user is passed and it has a language
2409
+ * the user language is returned.
2410
+ * If there is no language available, an empty string is returned.
2411
+ *
2412
+ * @param TNP_User $user
2413
+ * @return string The language code
2414
+ */
2415
+ function get_current_language($user = null) {
2416
+
2417
+ if ($user && $user->language) {
2418
+ return $user->language;
2419
+ }
2420
+
2421
+ if (!empty(self::$current_language)) {
2422
+ return self::$current_language;
2423
+ }
2424
+
2425
+ // WPML
2426
+ if (class_exists('SitePress')) {
2427
+ $current_language = apply_filters('wpml_current_language', '');
2428
+ if ($current_language == 'all') {
2429
+ $current_language = '';
2430
+ }
2431
+ return $current_language;
2432
+ }
2433
+
2434
+ // Polylang
2435
+ if (function_exists('pll_current_language')) {
2436
+ return pll_current_language();
2437
+ }
2438
+
2439
+ // Trnslatepress and/or others
2440
+ $current_language = apply_filters('newsletter_current_language', '');
2441
+
2442
+ return $current_language;
2443
+ }
2444
+
2445
+ function get_default_language() {
2446
+ if (class_exists('SitePress')) {
2447
+ return $current_language = apply_filters('wpml_current_language', '');
2448
+ } else if (function_exists('pll_default_language')) {
2449
+ return pll_default_language();
2450
+ } else if (class_exists('TRP_Translate_Press')) {
2451
+ // TODO: Find the default language
2452
+ }
2453
+ return '';
2454
+ }
2455
+
2456
+ function is_all_languages() {
2457
+ return $this->get_current_language() == '';
2458
+ }
2459
+
2460
+ function is_default_language() {
2461
+ return $this->get_current_language() == $this->get_default_language();
2462
+ }
2463
+
2464
+ /**
2465
+ * Returns an array of languages with key the language code and value the language name.
2466
+ * An empty array is returned if no language is available.
2467
+ */
2468
+ function get_languages() {
2469
+ $language_options = array();
2470
+
2471
+ if (class_exists('SitePress')) {
2472
+ $languages = apply_filters('wpml_active_languages', null);
2473
+ foreach ($languages as $language) {
2474
+ $language_options[$language['language_code']] = $language['translated_name'];
2475
+ }
2476
+ return $language_options;
2477
+ } else if (function_exists('icl_get_languages')) {
2478
+ $languages = icl_get_languages();
2479
+ foreach ($languages as $code => $language) {
2480
+ $language_options[$code] = $language['native_name'];
2481
+ }
2482
+ return $language_options;
2483
+ }
2484
+
2485
+ return apply_filters('newsletter_languages', $language_options);
2486
+ }
2487
+
2488
+ function get_language_label($language) {
2489
+ $languages = $this->get_languages();
2490
+ if (isset($languages[$language])) {
2491
+ return $languages[$language];
2492
+ }
2493
+ return '';
2494
+ }
2495
+
2496
+ /**
2497
+ * Changes the current language usually before extracting the posts since WPML
2498
+ * does not support the language filter in the post query (or at least we didn't
2499
+ * find it).
2500
+ *
2501
+ * @param string $language
2502
+ */
2503
+ function switch_language($language) {
2504
+ if (class_exists('SitePress')) {
2505
+ if (empty($language)) {
2506
+ $language = 'all';
2507
+ }
2508
+ do_action('wpml_switch_language', $language);
2509
+ return;
2510
+ }
2511
+ }
2512
+
2513
+ function is_multilanguage() {
2514
+ return class_exists('SitePress') || function_exists('pll_default_language') || class_exists('TRP_Translate_Press');
2515
+ }
2516
+
2517
+ function get_posts($filters = array(), $language = '') {
2518
+ $current_language = $this->get_current_language();
2519
+
2520
+ // Language switch for WPML
2521
+ if ($language) {
2522
+ if (class_exists('SitePress')) {
2523
+ $this->switch_language($language);
2524
+ $filters['suppress_filters'] = false;
2525
+ }
2526
+ if (class_exists('Polylang')) {
2527
+ $filters['lang'] = $language;
2528
+ }
2529
+ }
2530
+ $posts = get_posts($filters);
2531
+ if ($language) {
2532
+ if (class_exists('SitePress')) {
2533
+ $this->switch_language($current_language);
2534
+ }
2535
+ }
2536
+ return $posts;
2537
+ }
2538
+
2539
+ function get_wp_query($filters, $langiage = '') {
2540
+ if ($language) {
2541
+ if (class_exists('SitePress')) {
2542
+ $this->switch_language($language);
2543
+ $filters['suppress_filters'] = false;
2544
+ }
2545
+ if (class_exists('Polylang')) {
2546
+ $filters['lang'] = $language;
2547
+ }
2548
+ }
2549
+
2550
+ $posts = new WP_Query($filters);
2551
+
2552
+ if ($language) {
2553
+ if (class_exists('SitePress')) {
2554
+ $this->switch_language($current_language);
2555
+ }
2556
+ }
2557
+
2558
+ return $posts;
2559
+ }
2560
+
2561
+ protected function generate_admin_notification_message($user) {
2562
+
2563
+ $message = file_get_contents(__DIR__ . '/notification.html');
2564
+
2565
+ $message = $this->replace($message, $user);
2566
+ $message = str_replace('{user_admin_url}', admin_url('admin.php?page=newsletter_users_edit&id=' . $user->id), $message);
2567
+
2568
+ return $message;
2569
+ }
2570
+
2571
+ protected function generate_admin_notification_subject($subject) {
2572
+ $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
2573
+
2574
+ return '[' . $blogname . '] ' . $subject;
2575
+ }
2576
+
2577
+ function dienow($message, $admin_message = null, $http_code = 200) {
2578
+ if ($admin_message && current_user_can('administrator')) {
2579
+ $message .= '<br><br><strong>Text below only visibile to administrators</strong><br>';
2580
+ $message .= $admin_message;
2581
+ }
2582
+ wp_die($message, $http_code);
2583
+ }
2584
+
2585
+ function dump($var) {
2586
+ if (NEWSLETTER_DEBUG) {
2587
+ var_dump($var);
2588
+ }
2589
+ }
2590
+
2591
+ function dump_die($var) {
2592
+ if (NEWSLETTER_DEBUG) {
2593
+ var_dump($var);
2594
+ die();
2595
+ }
2596
+ }
2597
+
2598
+ }
2599
+
2600
+ /**
2601
+ * Kept for compatibility.
2602
+ *
2603
+ * @param type $post_id
2604
+ * @param type $size
2605
+ * @param type $alternative
2606
+ * @return type
2607
+ */
2608
+ function nt_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2609
+ return NewsletterModule::get_post_image($post_id, $size, $alternative);
2610
+ }
2611
+
2612
+ function newsletter_get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2613
+ echo NewsletterModule::get_post_image($post_id, $size, $alternative);
2614
+ }
2615
+
2616
+ /**
2617
+ * Accepts a post or a post ID.
2618
+ *
2619
+ * @param WP_Post $post
2620
+ */
2621
+ function newsletter_the_excerpt($post, $words = 30) {
2622
+ $post = get_post($post);
2623
+ $excerpt = $post->post_excerpt;
2624
+ if (empty($excerpt)) {
2625
+ $excerpt = $post->post_content;
2626
+ $excerpt = strip_shortcodes($excerpt);
2627
+ $excerpt = wp_strip_all_tags($excerpt, true);
2628
+ }
2629
+ echo '<p>' . wp_trim_words($excerpt, $words) . '</p>';
2630
+ }
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.3
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.3');
41
 
42
  global $newsletter, $wpdb;
43
 
@@ -1238,6 +1238,8 @@ class Newsletter extends NewsletterModule {
1238
  * @return WP_Post
1239
  */
1240
  function get_newsletter_page() {
 
 
1241
  return get_post($this->get_newsletter_page_id());
1242
  }
1243
 
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.4
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.4');
41
 
42
  global $newsletter, $wpdb;
43
 
1238
  * @return WP_Post
1239
  */
1240
  function get_newsletter_page() {
1241
+ $page_id = $this->get_newsletter_page_id();
1242
+ if (!$page_id) return false;
1243
  return get_post($this->get_newsletter_page_id());
1244
  }
1245
 
readme.txt CHANGED
@@ -1,12 +1,12 @@
1
- === Newsletter ===
2
- Tags: newsletter, email marketing, welcome email, signup forms, contact, lead generation, marketing automation
3
- Tested up to: 5.9.3
4
- Stable tag: 7.4.3
5
  Contributors: satollo,webagile,michael-travan
6
  License: GPLv2 or later
7
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
8
 
9
- Add a real newsletter system to your blog. For free. With unlimited newsletters and subscribers.
10
 
11
  == Description ==
12
 
@@ -126,6 +126,14 @@ Thank you, The Newsletter Team
126
 
127
  == Changelog ==
128
 
 
 
 
 
 
 
 
 
129
  = 7.4.3 =
130
 
131
  * Removed obsolete note about the newsletter textual part
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.4
5
  Contributors: satollo,webagile,michael-travan
6
  License: GPLv2 or later
7
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
8
 
9
+ An email marketing tool for your blog: subscription forms to create your lists with unlimited subscribers and newsletters.
10
 
11
  == Description ==
12
 
126
 
127
  == Changelog ==
128
 
129
+ = 7.4.4 =
130
+
131
+ * Changed the dedicated page retrieval to intercept misconfigurations
132
+ * Added option to accept repeated subscription in single opt-in (you should check if it is compatible with your privacy regulation)
133
+ * Posts field selector keep now track of the previous post selected even if no more in list
134
+ * Improved the error management when delivery fails using the WP native mailing function
135
+ * Added title filter on posts block for compatibility with WPGlobus
136
+
137
  = 7.4.3 =
138
 
139
  * Removed obsolete note about the newsletter textual part
subscription/options.php CHANGED
@@ -1,285 +1,287 @@
1
- <?php
2
- defined('ABSPATH') || exit;
3
-
4
- include_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
5
- $controls = new NewsletterControls();
6
- $module = NewsletterSubscription::instance();
7
-
8
- $current_language = $module->get_current_language();
9
-
10
- $is_all_languages = $module->is_all_languages();
11
-
12
- $controls->add_language_warning();
13
-
14
- $options = $module->get_options('', $current_language);
15
-
16
- if ($controls->is_action()) {
17
- if ($controls->is_action('save')) {
18
-
19
- $defaults = $module->get_default_options();
20
-
21
- // Without the last curly bracket since there can be a form number apended
22
- if (empty($controls->data['subscription_text'])) {
23
- $controls->data['subscription_text'] = $defaults['subscription_text'];
24
- }
25
-
26
- if (empty($controls->data['confirmation_text'])) {
27
- $controls->data['confirmation_text'] = $defaults['confirmation_text'];
28
- }
29
-
30
- if (empty($controls->data['confirmation_subject'])) {
31
- $controls->data['confirmation_subject'] = $defaults['confirmation_subject'];
32
- }
33
-
34
- if (empty($controls->data['confirmation_message'])) {
35
- $controls->data['confirmation_message'] = $defaults['confirmation_message'];
36
- }
37
-
38
- if (empty($controls->data['confirmed_text'])) {
39
- $controls->data['confirmed_text'] = $defaults['confirmed_text'];
40
- }
41
-
42
- if (empty($controls->data['confirmed_subject'])) {
43
- $controls->data['confirmed_subject'] = $defaults['confirmed_subject'];
44
- }
45
-
46
- if (empty($controls->data['confirmed_message'])) {
47
- $controls->data['confirmed_message'] = $defaults['confirmed_message'];
48
- }
49
-
50
- $controls->data['confirmed_message'] = NewsletterModule::clean_url_tags($controls->data['confirmed_message']);
51
- $controls->data['confirmed_text'] = NewsletterModule::clean_url_tags($controls->data['confirmed_text']);
52
- $controls->data['confirmation_text'] = NewsletterModule::clean_url_tags($controls->data['confirmation_text']);
53
- $controls->data['confirmation_message'] = NewsletterModule::clean_url_tags($controls->data['confirmation_message']);
54
-
55
- $controls->data['confirmed_url'] = trim($controls->data['confirmed_url']);
56
- $controls->data['confirmation_url'] = trim($controls->data['confirmation_url']);
57
-
58
- $module->save_options($controls->data, '', null, $current_language);
59
- $controls->add_message_saved();
60
- }
61
-
62
- if ($controls->is_action('reset')) {
63
- $controls->data = $module->reset_options();
64
- }
65
-
66
- if ($controls->is_action('test-confirmation')) {
67
-
68
- $users = NewsletterUsers::instance()->get_test_users();
69
- if (count($users) == 0) {
70
- $controls->errors = 'There are no test subscribers. Read more about test subscribers <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank">here</a>.';
71
- } else {
72
- $addresses = array();
73
- foreach ($users as &$user) {
74
- $addresses[] = $user->email;
75
- $user->language = $current_language;
76
- $res = $module->send_message('confirmation', $user);
77
- if (!$res) {
78
- $controls->errors = 'The email address ' . $user->email . ' failed.';
79
- break;
80
- }
81
- }
82
- $controls->messages .= 'Test emails sent to ' . count($users) . ' test subscribers: ' .
83
- implode(', ', $addresses) . '. Read more about test subscribers <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank">here</a>.';
84
- $controls->messages .= '<br>If the message is not received, try to change the message text it could trigger some antispam filters.';
85
- }
86
- }
87
-
88
- if ($controls->is_action('test-confirmed')) {
89
-
90
- $users = NewsletterUsers::instance()->get_test_users();
91
- if (count($users) == 0) {
92
- $controls->errors = 'There are no test subscribers. Read more about test subscribers <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank">here</a>.';
93
- } else {
94
- $addresses = array();
95
- foreach ($users as $user) {
96
- $addresses[] = $user->email;
97
- // Force the language to send the message coherently with the current panel view
98
- $user->language = $current_language;
99
- $res = $module->send_message('confirmed', $user);
100
- if (!$res) {
101
- $controls->errors = 'The email address ' . $user->email . ' failed.';
102
- break;
103
- }
104
- }
105
- $controls->messages .= 'Test emails sent to ' . count($users) . ' test subscribers: ' .
106
- implode(', ', $addresses) . '. Read more about test subscribers <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank">here</a>.';
107
- $controls->messages .= '<br>If the message is not received, try to change the message text it could trigger some antispam filters.';
108
- }
109
- }
110
- } else {
111
- $controls->data = $module->get_options('', $current_language);
112
- }
113
-
114
- ?>
115
-
116
- <div class="wrap" id="tnp-wrap">
117
-
118
- <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
119
-
120
- <div id="tnp-heading">
121
-
122
- <h2><?php _e('Subscription Configuration', 'newsletter') ?></h2>
123
- <?php $controls->page_help('https://www.thenewsletterplugin.com/documentation/subscription/subscription/') ?>
124
-
125
- </div>
126
-
127
- <div id="tnp-body">
128
-
129
-
130
- <form method="post" action="">
131
- <?php $controls->init(); ?>
132
- <div id="tabs">
133
- <ul>
134
- <li><a href="#tabs-general"><?php _e('General', 'newsletter') ?></a></li>
135
- <li><a href="#tabs-2"><?php _e('Subscription', 'newsletter') ?></a></li>
136
- <li><a href="#tabs-4"><?php _e('Welcome', 'newsletter') ?></a></li>
137
- <li><a href="#tabs-3"><?php _e('Activation', 'newsletter') ?></a></li>
138
- </ul>
139
-
140
- <div id="tabs-general">
141
- <?php if ($is_all_languages) { ?>
142
- <table class="form-table">
143
-
144
- <tr>
145
- <th><?php $controls->field_label(__('Opt In', 'newsletter'), '/documentation/subscription/subscription/') ?></th>
146
- <td>
147
- <?php $controls->select('noconfirmation', array(0 => __('Double Opt In', 'newsletter'), 1 => __('Single Opt In', 'newsletter'))); ?>
148
- </td>
149
- </tr>
150
- <tr>
151
- <th><?php $controls->field_label(__('Override Opt In', 'newsletter'), '/documentation/subscription/subscription/#advanced') ?></th>
152
- <td>
153
- <?php $controls->yesno('optin_override'); ?>
154
- </td>
155
- </tr>
156
-
157
- <tr>
158
- <th><?php _e('Notifications', 'newsletter') ?></th>
159
- <td>
160
- <?php $controls->yesno('notify'); ?>
161
- <?php $controls->text_email('notify_email'); ?>
162
- </td>
163
- </tr>
164
- </table>
165
- <?php } else { ?>
166
- <?php $controls->switch_to_all_languages_notice(); ?>
167
- <?php } ?>
168
-
169
- </div>
170
-
171
-
172
- <div id="tabs-2">
173
-
174
- <table class="form-table">
175
- <tr>
176
- <th><?php _e('Subscription page', 'newsletter') ?><br><?php echo $controls->help('https://www.thenewsletterplugin.com/documentation/newsletter-tags') ?></th>
177
- <td>
178
- <?php $controls->wp_editor('subscription_text'); ?>
179
- </td>
180
- </tr>
181
-
182
- </table>
183
-
184
- <table class="form-table">
185
- <tr>
186
- <th><?php _e('Repeated subscriptions', 'newsletter')?></th>
187
- <td>
188
- <?php $controls->select('multiple', array('0'=>__('Not allowed', 'newsletter'), '1'=>__('Allowed', 'newsletter'))); ?>
189
- <br><br>
190
- <?php $controls->wp_editor('error_text'); ?>
191
- </td>
192
- </tr>
193
- </table>
194
- </div>
195
-
196
-
197
- <div id="tabs-3">
198
-
199
- <p><?php _e('Only for double opt-in mode.', 'newsletter') ?></p>
200
- <?php $controls->panel_help('https://www.thenewsletterplugin.com/documentation/subscription#activation') ?>
201
-
202
- <table class="form-table">
203
- <tr>
204
- <th><?php _e('Activation message', 'newsletter') ?></th>
205
- <td>
206
- <?php $controls->wp_editor('confirmation_text'); ?>
207
- </td>
208
- </tr>
209
-
210
- <tr>
211
- <th><?php _e('Alternative activation page', 'newsletter'); ?></th>
212
- <td>
213
- <?php $controls->text('confirmation_url', 70, 'https://...'); ?>
214
- </td>
215
- </tr>
216
-
217
-
218
- <!-- CONFIRMATION EMAIL -->
219
- <tr>
220
- <th><?php _e('Activation email', 'newsletter') ?></th>
221
- <td>
222
- <?php $controls->email('confirmation', 'wordpress'); ?>
223
- <br>
224
- <?php $controls->button('test-confirmation', 'Send a test'); ?>
225
- </td>
226
- </tr>
227
- </table>
228
- </div>
229
-
230
-
231
- <div id="tabs-4">
232
- <p>
233
- <?php $controls->panel_help('https://www.thenewsletterplugin.com/documentation/subscription#welcome') ?>
234
- </p>
235
- <table class="form-table">
236
- <tr>
237
- <th><?php _e('Welcome message', 'newsletter') ?></th>
238
- <td>
239
- <?php $controls->wp_editor('confirmed_text'); ?>
240
- </td>
241
- </tr>
242
-
243
- <tr>
244
- <th><?php _e('Alternative welcome page URL', 'newsletter') ?></th>
245
- <td>
246
- <?php $controls->text('confirmed_url', 70, 'https://...'); ?>
247
- </td>
248
- </tr>
249
-
250
- <tr>
251
- <th><?php _e('Conversion tracking code', 'newsletter') ?>
252
- <?php $controls->help('https://www.thenewsletterplugin.com/documentation/subscription#conversion') ?></th>
253
- <td>
254
- <?php $controls->textarea('confirmed_tracking'); ?>
255
- </td>
256
- </tr>
257
-
258
- <!-- WELCOME/CONFIRMED EMAIL -->
259
- <tr>
260
- <th>
261
- <?php _e('Welcome email', 'newsletter') ?>
262
- </th>
263
- <td>
264
- <?php $controls->email('confirmed', 'wordpress', $is_all_languages); ?>
265
- <br>
266
- <?php $controls->button('test-confirmed', 'Send a test'); ?>
267
- </td>
268
- </tr>
269
-
270
- </table>
271
- </div>
272
-
273
- </div>
274
-
275
- <p>
276
- <?php $controls->button_save() ?>
277
- <?php $controls->button_reset() ?>
278
- </p>
279
-
280
- </form>
281
- </div>
282
-
283
- <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
284
-
285
- </div>
 
 
1
+ <?php
2
+ defined('ABSPATH') || exit;
3
+
4
+ include_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
5
+ $controls = new NewsletterControls();
6
+ $module = NewsletterSubscription::instance();
7
+
8
+ $current_language = $module->get_current_language();
9
+
10
+ $is_all_languages = $module->is_all_languages();
11
+
12
+ $controls->add_language_warning();
13
+
14
+ $options = $module->get_options('', $current_language);
15
+
16
+ if ($controls->is_action()) {
17
+ if ($controls->is_action('save')) {
18
+
19
+ $defaults = $module->get_default_options();
20
+
21
+ // Without the last curly bracket since there can be a form number apended
22
+ if (empty($controls->data['subscription_text'])) {
23
+ $controls->data['subscription_text'] = $defaults['subscription_text'];
24
+ }
25
+
26
+ if (empty($controls->data['confirmation_text'])) {
27
+ $controls->data['confirmation_text'] = $defaults['confirmation_text'];
28
+ }
29
+
30
+ if (empty($controls->data['confirmation_subject'])) {
31
+ $controls->data['confirmation_subject'] = $defaults['confirmation_subject'];
32
+ }
33
+
34
+ if (empty($controls->data['confirmation_message'])) {
35
+ $controls->data['confirmation_message'] = $defaults['confirmation_message'];
36
+ }
37
+
38
+ if (empty($controls->data['confirmed_text'])) {
39
+ $controls->data['confirmed_text'] = $defaults['confirmed_text'];
40
+ }
41
+
42
+ if (empty($controls->data['confirmed_subject'])) {
43
+ $controls->data['confirmed_subject'] = $defaults['confirmed_subject'];
44
+ }
45
+
46
+ if (empty($controls->data['confirmed_message'])) {
47
+ $controls->data['confirmed_message'] = $defaults['confirmed_message'];
48
+ }
49
+
50
+ $controls->data['confirmed_message'] = NewsletterModule::clean_url_tags($controls->data['confirmed_message']);
51
+ $controls->data['confirmed_text'] = NewsletterModule::clean_url_tags($controls->data['confirmed_text']);
52
+ $controls->data['confirmation_text'] = NewsletterModule::clean_url_tags($controls->data['confirmation_text']);
53
+ $controls->data['confirmation_message'] = NewsletterModule::clean_url_tags($controls->data['confirmation_message']);
54
+
55
+ $controls->data['confirmed_url'] = trim($controls->data['confirmed_url']);
56
+ $controls->data['confirmation_url'] = trim($controls->data['confirmation_url']);
57
+
58
+ $module->save_options($controls->data, '', null, $current_language);
59
+ $controls->add_message_saved();
60
+ }
61
+
62
+ if ($controls->is_action('reset')) {
63
+ $controls->data = $module->reset_options();
64
+ }
65
+
66
+ if ($controls->is_action('test-confirmation')) {
67
+
68
+ $users = NewsletterUsers::instance()->get_test_users();
69
+ if (count($users) == 0) {
70
+ $controls->errors = 'There are no test subscribers. Read more about test subscribers <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank">here</a>.';
71
+ } else {
72
+ $addresses = array();
73
+ foreach ($users as &$user) {
74
+ $addresses[] = $user->email;
75
+ $user->language = $current_language;
76
+ $res = $module->send_message('confirmation', $user);
77
+ if (!$res) {
78
+ $controls->errors = 'The email address ' . $user->email . ' failed.';
79
+ break;
80
+ }
81
+ }
82
+ $controls->messages .= 'Test emails sent to ' . count($users) . ' test subscribers: ' .
83
+ implode(', ', $addresses) . '. Read more about test subscribers <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank">here</a>.';
84
+ $controls->messages .= '<br>If the message is not received, try to change the message text it could trigger some antispam filters.';
85
+ }
86
+ }
87
+
88
+ if ($controls->is_action('test-confirmed')) {
89
+
90
+ $users = NewsletterUsers::instance()->get_test_users();
91
+ if (count($users) == 0) {
92
+ $controls->errors = 'There are no test subscribers. Read more about test subscribers <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank">here</a>.';
93
+ } else {
94
+ $addresses = array();
95
+ foreach ($users as $user) {
96
+ $addresses[] = $user->email;
97
+ // Force the language to send the message coherently with the current panel view
98
+ $user->language = $current_language;
99
+ $res = $module->send_message('confirmed', $user);
100
+ if (!$res) {
101
+ $controls->errors = 'The email address ' . $user->email . ' failed.';
102
+ break;
103
+ }
104
+ }
105
+ $controls->messages .= 'Test emails sent to ' . count($users) . ' test subscribers: ' .
106
+ implode(', ', $addresses) . '. Read more about test subscribers <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank">here</a>.';
107
+ $controls->messages .= '<br>If the message is not received, try to change the message text it could trigger some antispam filters.';
108
+ }
109
+ }
110
+ } else {
111
+ $controls->data = $module->get_options('', $current_language);
112
+ }
113
+
114
+ ?>
115
+
116
+ <div class="wrap" id="tnp-wrap">
117
+
118
+ <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
119
+
120
+ <div id="tnp-heading">
121
+
122
+ <h2><?php _e('Subscription Configuration', 'newsletter') ?></h2>
123
+ <?php $controls->page_help('https://www.thenewsletterplugin.com/documentation/subscription/subscription/') ?>
124
+
125
+ </div>
126
+
127
+ <div id="tnp-body">
128
+
129
+
130
+ <form method="post" action="">
131
+ <?php $controls->init(); ?>
132
+ <div id="tabs">
133
+ <ul>
134
+ <li><a href="#tabs-general"><?php _e('General', 'newsletter') ?></a></li>
135
+ <li><a href="#tabs-2"><?php _e('Subscription', 'newsletter') ?></a></li>
136
+ <li><a href="#tabs-4"><?php _e('Welcome', 'newsletter') ?></a></li>
137
+ <li><a href="#tabs-3"><?php _e('Activation', 'newsletter') ?></a></li>
138
+ </ul>
139
+
140
+ <div id="tabs-general">
141
+ <?php if ($is_all_languages) { ?>
142
+ <table class="form-table">
143
+
144
+ <tr>
145
+ <th><?php $controls->field_label(__('Opt In', 'newsletter'), '/documentation/subscription/subscription/') ?></th>
146
+ <td>
147
+ <?php $controls->select('noconfirmation', array(0 => __('Double Opt In', 'newsletter'), 1 => __('Single Opt In', 'newsletter'))); ?>
148
+ </td>
149
+ </tr>
150
+ <tr>
151
+ <th><?php $controls->field_label(__('Override Opt In', 'newsletter'), '/documentation/subscription/subscription/#advanced') ?></th>
152
+ <td>
153
+ <?php $controls->yesno('optin_override'); ?>
154
+ </td>
155
+ </tr>
156
+
157
+ <tr>
158
+ <th><?php _e('Notifications', 'newsletter') ?></th>
159
+ <td>
160
+ <?php $controls->yesno('notify'); ?>
161
+ <?php $controls->text_email('notify_email'); ?>
162
+ </td>
163
+ </tr>
164
+ </table>
165
+ <?php } else { ?>
166
+ <?php $controls->switch_to_all_languages_notice(); ?>
167
+ <?php } ?>
168
+
169
+ </div>
170
+
171
+
172
+ <div id="tabs-2">
173
+
174
+ <table class="form-table">
175
+ <tr>
176
+ <th><?php _e('Subscription page', 'newsletter') ?><br><?php echo $controls->help('https://www.thenewsletterplugin.com/documentation/newsletter-tags') ?></th>
177
+ <td>
178
+ <?php $controls->wp_editor('subscription_text'); ?>
179
+ </td>
180
+ </tr>
181
+
182
+ </table>
183
+
184
+ <table class="form-table">
185
+ <tr>
186
+ <th><?php _e('Repeated subscriptions', 'newsletter')?></th>
187
+ <td>
188
+ <?php $controls->select('multiple', ['0'=>__('Not allowed', 'newsletter'), '1'=>__('Allowed', 'newsletter'),
189
+ '1'=>__('Allowed force single opt-in', 'newsletter')]); ?>
190
+ <br><br>
191
+ <?php $controls->wp_editor('error_text'); ?>
192
+ <p class="description">Shown only when "not allowed" is selected<p>
193
+ </td>
194
+ </tr>
195
+ </table>
196
+ </div>
197
+
198
+
199
+ <div id="tabs-3">
200
+
201
+ <p><?php _e('Only for double opt-in mode.', 'newsletter') ?></p>
202
+ <?php $controls->panel_help('https://www.thenewsletterplugin.com/documentation/subscription#activation') ?>
203
+
204
+ <table class="form-table">
205
+ <tr>
206
+ <th><?php _e('Activation message', 'newsletter') ?></th>
207
+ <td>
208
+ <?php $controls->wp_editor('confirmation_text'); ?>
209
+ </td>
210
+ </tr>
211
+
212
+ <tr>
213
+ <th><?php _e('Alternative activation page', 'newsletter'); ?></th>
214
+ <td>
215
+ <?php $controls->text('confirmation_url', 70, 'https://...'); ?>
216
+ </td>
217
+ </tr>
218
+
219
+
220
+ <!-- CONFIRMATION EMAIL -->
221
+ <tr>
222
+ <th><?php _e('Activation email', 'newsletter') ?></th>
223
+ <td>
224
+ <?php $controls->email('confirmation', 'wordpress'); ?>
225
+ <br>
226
+ <?php $controls->button('test-confirmation', 'Send a test'); ?>
227
+ </td>
228
+ </tr>
229
+ </table>
230
+ </div>
231
+
232
+
233
+ <div id="tabs-4">
234
+ <p>
235
+ <?php $controls->panel_help('https://www.thenewsletterplugin.com/documentation/subscription#welcome') ?>
236
+ </p>
237
+ <table class="form-table">
238
+ <tr>
239
+ <th><?php _e('Welcome message', 'newsletter') ?></th>
240
+ <td>
241
+ <?php $controls->wp_editor('confirmed_text'); ?>
242
+ </td>
243
+ </tr>
244
+
245
+ <tr>
246
+ <th><?php _e('Alternative welcome page URL', 'newsletter') ?></th>
247
+ <td>
248
+ <?php $controls->text('confirmed_url', 70, 'https://...'); ?>
249
+ </td>
250
+ </tr>
251
+
252
+ <tr>
253
+ <th><?php _e('Conversion tracking code', 'newsletter') ?>
254
+ <?php $controls->help('https://www.thenewsletterplugin.com/documentation/subscription#conversion') ?></th>
255
+ <td>
256
+ <?php $controls->textarea('confirmed_tracking'); ?>
257
+ </td>
258
+ </tr>
259
+
260
+ <!-- WELCOME/CONFIRMED EMAIL -->
261
+ <tr>
262
+ <th>
263
+ <?php _e('Welcome email', 'newsletter') ?>
264
+ </th>
265
+ <td>
266
+ <?php $controls->email('confirmed', 'wordpress', $is_all_languages); ?>
267
+ <br>
268
+ <?php $controls->button('test-confirmed', 'Send a test'); ?>
269
+ </td>
270
+ </tr>
271
+
272
+ </table>
273
+ </div>
274
+
275
+ </div>
276
+
277
+ <p>
278
+ <?php $controls->button_save() ?>
279
+ <?php $controls->button_reset() ?>
280
+ </p>
281
+
282
+ </form>
283
+ </div>
284
+
285
+ <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
286
+
287
+ </div>
subscription/subscription.php CHANGED
@@ -1,1934 +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
- $subscription->if_exists = empty($this->options['multiple']) ? TNP_Subscription::EXISTING_ERROR : TNP_Subscription::EXISTING_MERGE;
483
-
484
- $lists = $this->get_lists();
485
- foreach ($lists as $list) {
486
- if ($list->forced) {
487
- $subscription->data->lists['' . $list->id] = 1;
488
- continue;
489
- }
490
- // Enforced by language
491
- if ($language && in_array($language, $list->languages)) {
492
- $subscription->data->lists['' . $list->id] = 1;
493
- }
494
- }
495
-
496
- return $subscription;
497
- }
498
-
499
- /**
500
- *
501
- * @param TNP_Subscription $subscription
502
- *
503
- * @return TNP_User|WP_Error
504
- */
505
- function subscribe2(TNP_Subscription $subscription) {
506
-
507
- $this->logger->debug($subscription);
508
-
509
- $this->sanitize($subscription->data);
510
-
511
- if (empty($subscription->data->email)) {
512
- return new WP_Error('email', 'Wrong email address');
513
- }
514
-
515
- if (!empty($subscription->data->country) && strlen($subscription->data->country) != 2) {
516
- return new WP_Error('country', 'Country code length error. ISO 3166-1 alpha-2 format (2 letters)');
517
- }
518
-
519
- // Here we should have a clean subscription data
520
- // Filter?
521
-
522
- if ($subscription->spamcheck) {
523
- // TODO: Use autoload
524
- require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
525
- $antispam = NewsletterAntispam::instance();
526
- if ($antispam->is_spam($subscription)) {
527
- return new WP_Error('spam', 'This looks like a spam subscription');
528
- }
529
- }
530
-
531
- // Exists?
532
- $user = $this->get_user_by_email($subscription->data->email);
533
-
534
- $subscription = apply_filters('newsletter_subscription', $subscription, $user);
535
-
536
- // Do we accept repeated subscriptions?
537
- if ($user != null && $subscription->if_exists === TNP_Subscription::EXISTING_ERROR) {
538
- //$this->show_message('error', $user);
539
- 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.');
540
- }
541
-
542
-
543
- if ($user != null) {
544
-
545
- $this->logger->info('Subscription of an address with status ' . $user->status);
546
-
547
- // We cannot communicate with bounced addresses, there is no reason to proceed
548
- // TODO: Evaluate if the bounce status is very old, possible reset it
549
- if ($user->status == TNP_User::STATUS_BOUNCED || $user->status == TNP_User::STATUS_COMPLAINED) {
550
- return new WP_Error('bounced', 'Subscriber present and blocked');
551
- }
552
-
553
- if ($user->status == TNP_User::STATUS_UNSUBSCRIBED) {
554
- // Special behavior?
555
- }
556
-
557
- if ($subscription->optin == 'single') {
558
- $user->status = TNP_User::STATUS_CONFIRMED;
559
- } else {
560
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
561
-
562
- set_transient('newsletter_subscription_' . $user->id, $subscription->data, 3600 * 48);
563
-
564
- // This status is *not* stored it indicate a temporary status to show the correct messages
565
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
566
-
567
- $this->send_message('confirmation', $user);
568
-
569
- return $user;
570
- } else {
571
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
572
- }
573
- }
574
-
575
- // Can be updated on the fly?
576
- $subscription->data->merge_in($user);
577
- } else {
578
- $this->logger->info('New subscriber');
579
-
580
- $user = new TNP_User();
581
- $subscription->data->merge_in($user);
582
-
583
- $user->token = $this->get_token();
584
-
585
- $user->status = $subscription->optin == 'single' ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
586
- $user->updated = time();
587
- }
588
-
589
- $user->ip = $this->process_ip($user->ip);
590
-
591
- $user = apply_filters('newsletter_user_subscribe', $user);
592
-
593
- $user = $this->save_user($user);
594
-
595
- $this->add_user_log($user, 'subscribe');
596
-
597
- // Notification to admin (only for new confirmed subscriptions)
598
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
599
- do_action('newsletter_user_confirmed', $user);
600
- $this->notify_admin_on_subscription($user);
601
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
602
- }
603
-
604
- if ($subscription->send_emails) {
605
- $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
606
- }
607
-
608
- // Used by Autoresponder (probably)
609
- do_action('newsletter_user_post_subscribe', $user);
610
-
611
- return $user;
612
- }
613
-
614
- /**
615
- * Create a subscription using the $_REQUEST data. Does security checks.
616
- *
617
- * @deprecated since version 6.9.0
618
- * @param string $status The status to use for this subscription (confirmed, not confirmed, ...)
619
- * @param bool $emails If the confirmation/welcome email should be sent or the subscription should be silent
620
- * @return TNP_User
621
- */
622
- function subscribe($status = null, $emails = true) {
623
-
624
- $this->logger->debug('Subscription start');
625
-
626
- // Validation
627
- $ip = $this->get_remote_ip();
628
- $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
629
- $first_name = '';
630
- if (isset($_REQUEST['nn'])) {
631
- $first_name = $this->normalize_name(stripslashes($_REQUEST['nn']));
632
- }
633
-
634
- $last_name = '';
635
- if (isset($_REQUEST['ns'])) {
636
- $last_name = $this->normalize_name(stripslashes($_REQUEST['ns']));
637
- }
638
-
639
- $opt_in = (int) $this->options['noconfirmation']; // 0 - double, 1 - single
640
- if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
641
- switch ($_REQUEST['optin']) {
642
- case 'single': $opt_in = self::OPTIN_SINGLE;
643
- break;
644
- case 'double': $opt_in = self::OPTIN_DOUBLE;
645
- break;
646
- }
647
- }
648
-
649
- if ($status != null) {
650
- // If a status is forced and it is requested to be "confirmed" is like a single opt in
651
- // $status here can only be confirmed or not confirmed
652
- // TODO: Add a check on status values
653
- if ($status == TNP_User::STATUS_CONFIRMED) {
654
- $opt_in = self::OPTIN_SINGLE;
655
- } else {
656
- $opt_in = self::OPTIN_DOUBLE;
657
- }
658
- }
659
-
660
- $user = $this->get_user($email);
661
-
662
- if ($user != null) {
663
- // Email already registered in our database
664
- $this->logger->info('Subscription of an address with status ' . $user->status);
665
-
666
- // Bounced
667
- // TODO: Manage other cases when added
668
- if ($user->status == 'B') {
669
- // Non persistent status to decide which message to show (error)
670
- $user->status = 'E';
671
- return $user;
672
- }
673
-
674
- // Is there any relevant data change? If so we can proceed otherwise if repeated subscriptions are disabled
675
- // show an already subscribed message
676
-
677
- if (empty($this->options['multiple'])) {
678
- $user->status = 'E';
679
- return $user;
680
- }
681
-
682
- // If the subscriber is confirmed, we cannot change his data in double opt in mode, we need to
683
- // temporary store and wait for activation
684
- if ($user->status == TNP_User::STATUS_CONFIRMED && $opt_in == self::OPTIN_DOUBLE) {
685
-
686
- set_transient($this->get_user_key($user), $_REQUEST, 3600 * 48);
687
-
688
- // This status is *not* stored it indicate a temporary status to show the correct messages
689
- $user->status = 'S';
690
-
691
- $this->send_message('confirmation', $user);
692
-
693
- return $user;
694
- }
695
- }
696
-
697
- // Here we have a new subscription or we can process the subscription even with a pre-existant user for example
698
- // because it is not confirmed
699
- if ($user != null) {
700
- $this->logger->info("Email address subscribed but not confirmed");
701
- $user = array('id' => $user->id);
702
- } else {
703
- $this->logger->info("New email address");
704
- $user = array('email' => $email);
705
- }
706
-
707
- $user = $this->update_user_from_request($user);
708
-
709
- $user['token'] = $this->get_token();
710
- $ip = $this->process_ip($ip);
711
- $user['ip'] = $ip;
712
- $user['geo'] = 0;
713
- $user['status'] = $opt_in == self::OPTIN_SINGLE ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
714
-
715
- $user['updated'] = time();
716
-
717
- $user = apply_filters('newsletter_user_subscribe', $user);
718
-
719
- $user = $this->save_user($user);
720
-
721
- $this->add_user_log($user, 'subscribe');
722
-
723
- // Notification to admin (only for new confirmed subscriptions)
724
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
725
- do_action('newsletter_user_confirmed', $user);
726
- $this->notify_admin_on_subscription($user);
727
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
728
- }
729
-
730
- if ($emails) {
731
- $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
732
- }
733
-
734
- $user = apply_filters('newsletter_user_post_subscribe', $user);
735
-
736
- return $user;
737
- }
738
-
739
- function add_microdata($message) {
740
- 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>';
741
- }
742
-
743
- /**
744
- * Builds a subscription object starting from values in the $_REQUEST
745
- * global variable. It DOES NOT sanitizie or formally check the values.
746
- * Usually data comes from a form submission.
747
- * https://www.thenewsletterplugin.com/documentation/subscription/newsletter-forms/
748
- *
749
- * @return TNP_Subscription
750
- */
751
- function build_subscription() {
752
-
753
- $language = '';
754
- if (!empty($_REQUEST['nlang'])) {
755
- $language = $_REQUEST['nlang'];
756
- } else {
757
- $language = $this->get_current_language();
758
- }
759
-
760
- $subscription = $this->get_default_subscription($language);
761
- $data = $subscription->data;
762
-
763
- $data->email = $_REQUEST['ne'];
764
-
765
- if (isset($_REQUEST['nn'])) {
766
- $data->name = stripslashes($_REQUEST['nn']);
767
- }
768
-
769
- if (isset($_REQUEST['ns'])) {
770
- $data->surname = stripslashes($_REQUEST['ns']);
771
- }
772
-
773
- if (!empty($_REQUEST['nx'])) {
774
- $data->sex = $_REQUEST['nx'][0];
775
- }
776
-
777
- if (isset($_REQUEST['nr'])) {
778
- $data->referrer = $_REQUEST['nr'];
779
- }
780
-
781
- // From the antibot form
782
- if (isset($_REQUEST['nhr'])) {
783
- $data->http_referer = stripslashes($_REQUEST['nhr']);
784
- } else if (isset($_SERVER['HTTP_REFERER'])) {
785
- $data->http_referer = $_SERVER['HTTP_REFERER'];
786
- }
787
-
788
- // New profiles
789
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
790
- // If the profile cannot be set by subscriber, skip it.
791
- if ($this->options_profile['profile_' . $i . '_status'] == 0) {
792
- continue;
793
- }
794
- if (isset($_REQUEST['np' . $i])) {
795
- $data->profiles['' . $i] = stripslashes($_REQUEST['np' . $i]);
796
- }
797
- }
798
-
799
- // Lists (field name is nl[] and values the list number so special forms with radio button can work)
800
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
801
- $this->logger->debug($_REQUEST['nl']);
802
- foreach ($_REQUEST['nl'] as $list_id) {
803
- $list = $this->get_list($list_id);
804
- if (!$list || $list->is_private()) {
805
- // To administrator show an error to make him aware of the wrong form configuration
806
- if (current_user_can('administrator')) {
807
- $this->dienow('Invalid list', 'List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
808
- }
809
- // Ignore this list
810
- continue;
811
- }
812
- $data->lists['' . $list_id] = 1;
813
- }
814
- } else {
815
- $this->logger->debug('No lists received');
816
- }
817
-
818
- // Opt-in mode
819
- if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
820
- switch ($_REQUEST['optin']) {
821
- case 'single': $subscription->optin = 'single';
822
- break;
823
- case 'double': $subscription->optin = 'double';
824
- break;
825
- }
826
- }
827
-
828
- return $subscription;
829
- }
830
-
831
- /**
832
- * Processes the request and fill in the *array* representing a subscriber with submitted values
833
- * (filtering when necessary).
834
- *
835
- * @deprecated since version 6.9.0
836
- * @param array $user An array partially filled with subscriber data
837
- * @return array The filled array representing a subscriber
838
- */
839
- function update_user_from_request($user) {
840
-
841
- if (isset($_REQUEST['nn'])) {
842
- $user['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
843
- }
844
- // TODO: required checking
845
-
846
- if (isset($_REQUEST['ns'])) {
847
- $user['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
848
- }
849
- // TODO: required checking
850
-
851
- if (!empty($_REQUEST['nx'])) {
852
- $user['sex'] = $this->normalize_sex($_REQUEST['nx'][0]);
853
- }
854
- // TODO: valid values check
855
-
856
- if (isset($_REQUEST['nr'])) {
857
- $user['referrer'] = strip_tags(trim($_REQUEST['nr']));
858
- }
859
-
860
- $language = '';
861
- if (!empty($_REQUEST['nlang'])) {
862
- $language = strtolower(strip_tags($_REQUEST['nlang']));
863
- // TODO: Check if it's an allowed language code
864
- $user['language'] = $language;
865
- } else {
866
- $language = $this->get_current_language();
867
- $user['language'] = $language;
868
- }
869
-
870
- // From the antibot form
871
- if (isset($_REQUEST['nhr'])) {
872
- $user['http_referer'] = strip_tags(trim($_REQUEST['nhr']));
873
- } else if (isset($_SERVER['HTTP_REFERER'])) {
874
- $user['http_referer'] = strip_tags(trim($_SERVER['HTTP_REFERER']));
875
- }
876
-
877
- if (strlen($user['http_referer']) > 200) {
878
- $user['http_referer'] = mb_substr($user['http_referer'], 0, 200);
879
- }
880
-
881
- // New profiles
882
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
883
- // If the profile cannot be set by subscriber, skip it.
884
- if ($this->options_profile['profile_' . $i . '_status'] == 0) {
885
- continue;
886
- }
887
- if (isset($_REQUEST['np' . $i])) {
888
- $user['profile_' . $i] = trim(stripslashes($_REQUEST['np' . $i]));
889
- }
890
- }
891
-
892
- // Extra validation to explain the administrator while the submitted data could
893
- // be interpreted only partially
894
- if (current_user_can('administrator')) {
895
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
896
- foreach ($_REQUEST['nl'] as $list_id) {
897
- $list = $this->get_list($list_id);
898
- if ($list && $list->status == TNP_List::STATUS_PRIVATE) {
899
- $this->dienow('Invalid list', '[old] List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
900
- }
901
- }
902
- }
903
- }
904
- // Preferences (field names are nl[] and values the list number so special forms with radio button can work)
905
- // Permetto l'aggiunta solo delle liste pubbliche
906
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
907
- $lists = $this->get_lists_public();
908
- //$this->logger->debug($_REQUEST['nl']);
909
- foreach ($lists as $list) {
910
- if (in_array('' . $list->id, $_REQUEST['nl'])) {
911
- $user['list_' . $list->id] = 1;
912
- }
913
- }
914
- } else {
915
- $this->logger->debug('No lists received');
916
- }
917
-
918
- // Forced lists (general or by language)
919
- // Forzo l'aggiunta delle liste forzate
920
- $lists = $this->get_lists();
921
- foreach ($lists as $list) {
922
- if ($list->forced) {
923
- $user['list_' . $list->id] = 1;
924
- }
925
- if (in_array($language, $list->languages)) {
926
- $user['list_' . $list->id] = 1;
927
- }
928
- }
929
-
930
- // TODO: should be removed!!!
931
- if (defined('NEWSLETTER_FEED_VERSION')) {
932
- $options_feed = get_option('newsletter_feed', array());
933
- if ($options_feed['add_new'] == 1) {
934
- $user['feed'] = 1;
935
- }
936
- }
937
- return $user;
938
- }
939
-
940
- /**
941
- * Sends a service message applying the template to the HTML part
942
- *
943
- * @param TNP_User $user
944
- * @param string $subject
945
- * @param string|array $message If string it is considered HTML, if array it should contains the key "html" and "text"
946
- * @return type
947
- */
948
- function mail($user, $subject, $message) {
949
- $language = $this->get_user_language($user);
950
-
951
- $options_template = $this->get_options('template', $language);
952
-
953
- $template = trim($options_template['template']);
954
- if (empty($template) || strpos($template, '{message}') === false) {
955
- $template = '{message}';
956
- }
957
-
958
- if (is_array($message)) {
959
- $message['html'] = str_replace('{message}', $message['html'], $template);
960
- $message['html'] = $this->replace($message['html'], $user);
961
- $message['text'] = $this->replace($message['text'], $user);
962
- } else {
963
- $message = str_replace('{message}', $message, $template);
964
- $message = $this->replace($message, $user);
965
- }
966
-
967
- $headers = [];
968
-
969
- // Replaces tags from the template
970
-
971
- $subject = $this->replace($subject, $user);
972
-
973
- return Newsletter::instance()->mail($user->email, $subject, $message, $headers);
974
- }
975
-
976
- /**
977
- * Confirms a subscription changing the user status and, possibly, merging the
978
- * temporary data if present.
979
- *
980
- * @param TNP_User $user Optionally it can be null (user search from requests paramaters, but deprecated, or a user id)
981
- * @return TNP_User
982
- */
983
- function confirm($user = null, $emails = true) {
984
-
985
- // Compatibility with WP Registration Addon
986
- if (!$user) {
987
- $user = $this->get_user_from_request(true);
988
- } else if (is_numeric($user)) {
989
- $user = $this->get_user($user);
990
- }
991
-
992
- if (!$user) {
993
- $this->dienow('Subscriber not found', '', 404);
994
- }
995
- // End compatibility
996
- // Should be merged?
997
- $data = get_transient('newsletter_subscription_' . $user->id);
998
- if ($data !== false) {
999
- $data->merge_in($user);
1000
- //$this->merge($user, $data);
1001
- $user = $this->save_user($user);
1002
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
1003
- delete_transient('newsletter_subscription_' . $user->id);
1004
- } else {
1005
- $new_email = get_transient('newsletter_user_' . $user->id . '_email');
1006
- if ($new_email) {
1007
- $data = ['id' => $user->id, 'email' => $new_email];
1008
- $this->save_user($data);
1009
- delete_transient('newsletter_user_' . $user->id . '_email');
1010
- }
1011
- }
1012
-
1013
-
1014
- $this->update_user_last_activity($user);
1015
-
1016
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
1017
-
1018
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
1019
- $this->add_user_log($user, 'activate');
1020
- do_action('newsletter_user_confirmed', $user);
1021
- return $user;
1022
- }
1023
-
1024
- $this->set_user_status($user, TNP_User::STATUS_CONFIRMED);
1025
-
1026
- $user = $this->get_user($user);
1027
-
1028
- $this->add_user_log($user, 'activate');
1029
-
1030
- do_action('newsletter_user_confirmed', $user);
1031
- $this->notify_admin_on_subscription($user);
1032
-
1033
- if ($emails) {
1034
- $this->send_message('confirmed', $user);
1035
- }
1036
-
1037
- return $user;
1038
- }
1039
-
1040
- /**
1041
- * Sends a message (activation, welcome, cancellation, ...) with the correct template
1042
- * and checking if the message itself is disabled
1043
- *
1044
- * @param string $type
1045
- * @param TNP_User $user
1046
- */
1047
- function send_message($type, $user, $force = false) {
1048
- if (!$force && !empty($this->options[$type . '_disabled'])) {
1049
- return true;
1050
- }
1051
-
1052
- $language = $this->get_user_language($user);
1053
-
1054
- $options = $this->get_options('', $language);
1055
- $message = [];
1056
- $message['html'] = do_shortcode($options[$type . '_message']);
1057
- $message['text'] = $this->get_text_message($type);
1058
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
1059
- $message['html'] = $this->add_microdata($message['html']);
1060
- }
1061
- $subject = $options[$type . '_subject'];
1062
-
1063
- return $this->mail($user, $subject, $message);
1064
- }
1065
-
1066
- function get_text_message($type) {
1067
- switch ($type) {
1068
- case 'confirmation':
1069
- return __('To confirm your subscription follow the link below.', 'newsletter') . "\n\n{subscription_confirm_url}";
1070
- case 'confirmed':
1071
- return __('Your subscription has been confirmed.', 'newsletter');
1072
- }
1073
- return '';
1074
- }
1075
-
1076
- function is_double_optin() {
1077
- return $this->options['noconfirmation'] == 0;
1078
- }
1079
-
1080
- /**
1081
- * Sends the activation email without conditions.
1082
- *
1083
- * @param stdClass $user
1084
- * @return bool
1085
- */
1086
- function send_activation_email($user) {
1087
- return $this->send_message('confirmation', $user, true);
1088
- }
1089
-
1090
- /**
1091
- * Finds the right way to show the message identified by $key (welcome, unsubscription, ...) redirecting the user to the
1092
- * WordPress page or loading the configured url or activating the standard page.
1093
- */
1094
- function show_message($key, $user, $alert = '', $email = null) {
1095
- $url = '';
1096
-
1097
- if (isset($_REQUEST['ncu'])) {
1098
- // Custom URL from the form
1099
- $url = $_REQUEST['ncu'];
1100
- } else {
1101
- // Per message custom URL from configuration (language variants could not be supported)
1102
- $options = $this->get_options('', $this->get_user_language($user));
1103
- if (!empty($options[$key . '_url'])) {
1104
- $url = $options[$key . '_url'];
1105
- }
1106
- }
1107
-
1108
- $url = Newsletter::instance()->build_message_url($url, $key, $user, $email, $alert);
1109
- wp_redirect($url);
1110
-
1111
- die();
1112
- }
1113
-
1114
- function get_message_key_from_request() {
1115
- if (empty($_GET['nm'])) {
1116
- return 'subscription';
1117
- }
1118
- $key = $_GET['nm'];
1119
- switch ($key) {
1120
- case 's': return 'confirmation';
1121
- case 'c': return 'confirmed';
1122
- case 'u': return 'unsubscription';
1123
- case 'uc': return 'unsubscribed';
1124
- case 'p':
1125
- case 'pe':
1126
- return 'profile';
1127
- default: return $key;
1128
- }
1129
- }
1130
-
1131
- var $privacy_url = false;
1132
-
1133
- /**
1134
- * Generates the privacy URL and cache it.
1135
- *
1136
- * @return string
1137
- */
1138
- function get_privacy_url() {
1139
- if ($this->privacy_url === false) {
1140
- $this->setup_form_options();
1141
- if (!empty($this->form_options['privacy_use_wp_url']) && function_exists('get_privacy_policy_url')) {
1142
- $this->privacy_url = get_privacy_policy_url();
1143
- } else {
1144
- $this->privacy_url = $this->form_options['privacy_url'];
1145
- }
1146
- }
1147
- return $this->privacy_url;
1148
- }
1149
-
1150
- function get_form_javascript() {
1151
-
1152
- }
1153
-
1154
- /**
1155
- * Manages the custom forms made with [newsletter_form] and internal [newsletter_field] shortcodes.
1156
- *
1157
- * @param array $attrs
1158
- * @param string $content
1159
- * @return string
1160
- */
1161
- function get_subscription_form_custom($attrs = [], $content = '') {
1162
- if (!is_array($attrs)) {
1163
- $attrs = [];
1164
- }
1165
-
1166
- $this->setup_form_options();
1167
-
1168
- $attrs = array_merge(['class' => 'tnp-subscription', 'style' => ''], $attrs);
1169
-
1170
- $action = esc_attr($this->build_action_url('s'));
1171
- $class = esc_attr($attrs['class']);
1172
- $style = esc_attr($attrs['style']);
1173
- $buffer = '<form method="post" action="' . $action . '" class="' . $class . '" style="' . $style . '">' . "\n";
1174
-
1175
- $language = $this->get_current_language();
1176
-
1177
- $buffer .= $this->get_form_hidden_fields($attrs);
1178
-
1179
- $buffer .= do_shortcode($content);
1180
-
1181
- if (isset($attrs['button_label'])) {
1182
- $label = $attrs['button_label'];
1183
- } else {
1184
- $label = $this->form_options['subscribe'];
1185
- }
1186
-
1187
- if (!empty($label)) {
1188
- $buffer .= '<div class="tnp-field tnp-field-button">';
1189
- if (strpos($label, 'http') === 0) {
1190
- $buffer .= '<input class="tnp-button-image" type="image" src="' . $label . '">';
1191
- } else {
1192
- $buffer .= '<input class="tnp-button" type="submit" value="' . $label . '">';
1193
- }
1194
- $buffer .= '</div>';
1195
- }
1196
-
1197
- $buffer .= '</form>';
1198
-
1199
- return $buffer;
1200
- }
1201
-
1202
- /** Generates the hidden field for lists which should be implicitely set with a subscription form.
1203
- *
1204
- * @param string $lists Comma separated directly from the shortcode "lists" attribute
1205
- * @param string $language ???
1206
- * @return string
1207
- */
1208
- function get_form_implicit_lists($lists, $language = '') {
1209
- $buffer = '';
1210
-
1211
- $arr = explode(',', $lists);
1212
-
1213
- foreach ($arr as $a) {
1214
- $a = trim($a);
1215
- if (empty($a))
1216
- continue;
1217
-
1218
- $list = $this->get_list($a);
1219
- if (!$list) {
1220
- $buffer .= $this->build_field_admin_notice('List "' . $a . '" added to the form is not configured, skipped.');
1221
- continue;
1222
- }
1223
-
1224
- if ($list->is_private()) {
1225
- $buffer .= $this->build_field_admin_notice('List ' . $a . ' is private cannot be used in a public form.');
1226
- continue;
1227
- }
1228
-
1229
- if ($list->forced) {
1230
- $buffer .= $this->build_field_admin_notice('List ' . $a . ' is already enforced on every subscription there is no need to specify it.');
1231
- continue;
1232
- }
1233
-
1234
- $buffer .= "<input type='hidden' name='nl[]' value='" . esc_attr($a) . "'>\n";
1235
- }
1236
- return $buffer;
1237
- }
1238
-
1239
- /**
1240
- * Builds all the hidden fields of a subscription form. Implicit lists, confirmation url,
1241
- * referrer, language, ...
1242
- *
1243
- * @param array $attrs Attributes of form shortcode
1244
- * @return string HTML with the hidden fields
1245
- */
1246
- function get_form_hidden_fields($attrs) {
1247
- $b = '';
1248
-
1249
- // Compatibility
1250
- if (isset($attrs['list'])) {
1251
- $attrs['lists'] = $attrs['list'];
1252
- }
1253
- if (isset($attrs['lists'])) {
1254
- $b .= $this->get_form_implicit_lists($attrs['lists']);
1255
- }
1256
-
1257
- if (isset($attrs['referrer'])) {
1258
- $b .= '<input type="hidden" name="nr" value="' . esc_attr($attrs['referrer']) . '">';
1259
- }
1260
-
1261
- if (isset($attrs['confirmation_url'])) {
1262
- if ($attrs['confirmation_url'] === '#') {
1263
- $attrs['confirmation_url'] = $_SERVER['REQUEST_URI'];
1264
- }
1265
-
1266
- $b .= '<input type="hidden" name="ncu" value="' . esc_attr($attrs['confirmation_url']) . '">';
1267
- }
1268
-
1269
- if (isset($attrs['optin'])) {
1270
- $optin = trim(strtolower($attrs['optin']));
1271
- if ($optin !== 'double' && $optin !== 'single') {
1272
- $b .= $this->build_field_admin_notice('The optin is set to an invalid value.');
1273
- } else {
1274
- if ($optin !== 'double' && $this->is_double_optin() && empty($this->options['optin_override'])) {
1275
- $b .= $this->build_field_admin_notice('The optin is specified but cannot be overridden (see the subscription configiraton page).');
1276
- } else {
1277
- $b .= '<input type="hidden" name="optin" value="' . esc_attr($optin) . '">';
1278
- }
1279
- }
1280
- }
1281
-
1282
- $language = $this->get_current_language();
1283
- $b .= '<input type="hidden" name="nlang" value="' . esc_attr($language) . '">';
1284
-
1285
- return $b;
1286
- }
1287
-
1288
- /**
1289
- * Internal use only
1290
- *
1291
- * @param type $name
1292
- * @param type $attrs
1293
- * @param type $suffix
1294
- * @return string
1295
- */
1296
- private function _shortcode_label($name, $attrs, $suffix = null) {
1297
-
1298
- if (!$suffix) {
1299
- $suffix = $name;
1300
- }
1301
- $buffer = '<label for="' . esc_attr($attrs['id']) . '">';
1302
- if (isset($attrs['label'])) {
1303
- if (empty($attrs['label'])) {
1304
- return;
1305
- } else {
1306
- $buffer .= esc_html($attrs['label']);
1307
- }
1308
- } else {
1309
- if (isset($this->form_options[$name])) {
1310
- $buffer .= esc_html($this->form_options[$name]);
1311
- }
1312
- }
1313
- $buffer .= "</label>\n";
1314
- return $buffer;
1315
- }
1316
-
1317
- /**
1318
- * Creates a notices to be displayed near a subscription form field to inform of worng configurations.
1319
- * It is created only if the current user looking at the form is the administrator.
1320
- *
1321
- * @param string $message
1322
- * @return string
1323
- */
1324
- function build_field_admin_notice($message) {
1325
- if (!current_user_can('administrator')) {
1326
- return '';
1327
- }
1328
- 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>';
1329
- }
1330
-
1331
- function shortcode_newsletter_field($attrs, $content = '') {
1332
- // Counter to create unique ID for checkbox and labels
1333
- static $idx = 0;
1334
-
1335
- $idx++;
1336
- $attrs['id'] = 'tnp-' . $idx;
1337
-
1338
- $this->setup_form_options();
1339
- $language = $this->get_current_language();
1340
-
1341
- $name = $attrs['name'];
1342
-
1343
- $buffer = '';
1344
-
1345
- if ($name == 'email') {
1346
- $buffer .= '<div class="tnp-field tnp-field-email">';
1347
-
1348
- $buffer .= $this->_shortcode_label('email', $attrs);
1349
-
1350
- $buffer .= '<input class="tnp-email" type="email" name="ne" id="' . esc_attr($attrs['id']) . '" value=""';
1351
- if (isset($attrs['placeholder'])) {
1352
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1353
- }
1354
- $buffer .= ' required>';
1355
- if (isset($attrs['button_label'])) {
1356
- $label = $attrs['button_label'];
1357
- if (strpos($label, 'http') === 0) {
1358
- $buffer .= ' <input class="tnp-submit-image" type="image" src="' . esc_attr(esc_url_raw($label)) . '">';
1359
- } else {
1360
- $buffer .= ' <input class="tnp-submit" type="submit" value="' . esc_attr($label) . '" style="width: 29%">';
1361
- }
1362
- }
1363
- $buffer .= "</div>\n";
1364
- return $buffer;
1365
- }
1366
-
1367
- if ($name == 'first_name' || $name == 'name') {
1368
- $buffer .= '<div class="tnp-field tnp-field-firstname">';
1369
- $buffer .= $this->_shortcode_label('name', $attrs);
1370
-
1371
- $buffer .= '<input class="tnp-name" type="text" name="nn" id="' . esc_attr($attrs['id']) . '" value=""';
1372
- if (isset($attrs['placeholder'])) {
1373
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1374
- }
1375
- if ($this->form_options['name_rules'] == 1) {
1376
- $buffer .= ' required';
1377
- }
1378
- $buffer .= '>';
1379
- $buffer .= "</div>\n";
1380
- return $buffer;
1381
- }
1382
-
1383
- if ($name == 'last_name' || $name == 'surname') {
1384
- $buffer .= '<div class="tnp-field tnp-field-surname">';
1385
- $buffer .= $this->_shortcode_label('surname', $attrs);
1386
-
1387
- $buffer .= '<input class="tnp-surname" type="text" name="ns" id="' . esc_attr($attrs['id']) . '" value=""';
1388
- if (isset($attrs['placeholder'])) {
1389
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1390
- }
1391
- if ($this->form_options['surname_rules'] == 1) {
1392
- $buffer .= ' required';
1393
- }
1394
- $buffer .= '>';
1395
- $buffer .= '</div>';
1396
- return $buffer;
1397
- }
1398
-
1399
- // Single list
1400
- if ($name == 'preference' || $name == 'list') {
1401
- if (!isset($attrs['number'])) {
1402
- return $this->build_field_admin_notice('List number not specified.');
1403
- }
1404
- $number = (int) $attrs['number'];
1405
- $list = $this->get_list($number, $language);
1406
- if (!$list) {
1407
- return $this->build_field_admin_notice('List ' . $number . ' is not configured, cannot be shown.');
1408
- }
1409
-
1410
- if ($list->status == 0 || $list->forced) {
1411
- return $this->build_field_admin_notice('List ' . $number . ' is private or enforced cannot be shown.');
1412
- }
1413
-
1414
- if (isset($attrs['hidden'])) {
1415
- return '<input type="hidden" name="nl[]" value="' . esc_attr($list->id) . '">';
1416
- }
1417
-
1418
- $idx++;
1419
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="tnp-' . $idx . '">';
1420
- $buffer .= '<input type="checkbox" id="tnp-' . $idx . '" name="nl[]" value="' . esc_attr($list->id) . '"';
1421
- if (isset($attrs['checked'])) {
1422
- $buffer .= ' checked';
1423
- }
1424
- $buffer .= '>';
1425
- if (isset($attrs['label'])) {
1426
- if ($attrs['label'] != '') {
1427
- $buffer .= '&nbsp;' . esc_html($attrs['label']) . '</label>';
1428
- }
1429
- } else {
1430
- $buffer .= '&nbsp;' . esc_html($list->name) . '</label>';
1431
- }
1432
- $buffer .= "</div>\n";
1433
-
1434
- return $buffer;
1435
- }
1436
-
1437
- // All lists
1438
- if ($name == 'lists' || $name == 'preferences') {
1439
- $lists = $this->get_lists_for_subscription($language);
1440
- if (!empty($lists) && isset($attrs['layout']) && $attrs['layout'] === 'dropdown') {
1441
-
1442
- $buffer .= '<div class="tnp-field tnp-lists">';
1443
- // There is not a default "label" for the block of lists, so it can only be specified in the shortcode attrs as "label"
1444
- $buffer .= $this->_shortcode_label('lists', $attrs);
1445
- $buffer .= '<select class="tnp-lists" name="nl[]" required>';
1446
-
1447
- if (!empty($attrs['first_option_label'])) {
1448
- $buffer .= '<option value="" selected="true" disabled="disabled">' . esc_html($attrs['first_option_label']) . '</option>';
1449
- }
1450
-
1451
- foreach ($lists as $list) {
1452
- $buffer .= '<option value="' . $list->id . '">' . esc_html($list->name) . '</option>';
1453
- }
1454
- $buffer .= '</select>';
1455
- $buffer .= '</div>';
1456
- } else {
1457
-
1458
- foreach ($lists as $list) {
1459
- $idx++;
1460
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="nl' . $idx . '">';
1461
- $buffer .= '<input type="checkbox" id="nl' . $idx . '" name="nl[]" value="' . $list->id . '"';
1462
- if ($list->checked) {
1463
- $buffer .= ' checked';
1464
- }
1465
- $buffer .= '>&nbsp;' . esc_html($list->name) . '</label>';
1466
- $buffer .= "</div>\n";
1467
- }
1468
- }
1469
- return $buffer;
1470
- }
1471
-
1472
- if ($name == 'sex' || $name == 'gender') {
1473
- $buffer .= '<div class="tnp-field tnp-field-gender">';
1474
- $buffer .= $this->_shortcode_label('sex', $attrs);
1475
-
1476
- $buffer .= '<select name="nx" class="tnp-gender" id="tnp-gender"';
1477
- if ($this->form_options['sex_rules']) {
1478
- $buffer .= ' required ';
1479
- }
1480
- $buffer .= '>';
1481
- if ($this->form_options['sex_rules']) {
1482
- $buffer .= '<option value=""></option>';
1483
- }
1484
- $buffer .= '<option value="n">' . esc_html($this->form_options['sex_none']) . '</option>';
1485
- $buffer .= '<option value="f">' . esc_html($this->form_options['sex_female']) . '</option>';
1486
- $buffer .= '<option value="m">' . esc_html($this->form_options['sex_male']) . '</option>';
1487
- $buffer .= '</select>';
1488
- $buffer .= "</div>\n";
1489
- return $buffer;
1490
- }
1491
-
1492
- if ($name == 'profile') {
1493
- if (!isset($attrs['number'])) {
1494
- return $this->build_field_admin_notice('Extra profile number not specified.');
1495
- }
1496
-
1497
- $number = (int) $attrs['number'];
1498
-
1499
- $profile = TNP_Profile_Service::get_profile_by_id($number, $language);
1500
-
1501
- if (!$profile) {
1502
- return $this->build_field_admin_notice('Extra profile ' . $number . ' is not configured, cannot be shown.');
1503
- }
1504
-
1505
- if ($profile->status == 0) {
1506
- return $this->build_field_admin_notice('Extra profile ' . $number . ' is private, cannot be shown.');
1507
- }
1508
-
1509
- $size = isset($attrs['size']) ? $attrs['size'] : '';
1510
- $buffer .= '<div class="tnp-field tnp-field-profile">';
1511
- $buffer .= $this->_shortcode_label('profile_' . $profile->id, $attrs);
1512
-
1513
- $placeholder = isset($attrs['placeholder']) ? $attrs['placeholder'] : $profile->placeholder;
1514
-
1515
- // Text field
1516
- if ($profile->type == TNP_Profile::TYPE_TEXT) {
1517
- $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) . '"';
1518
- if ($profile->is_required()) {
1519
- $buffer .= ' required';
1520
- }
1521
- $buffer .= '>';
1522
- }
1523
-
1524
- // Select field
1525
- if ($profile->type == TNP_Profile::TYPE_SELECT) {
1526
- $buffer .= '<select class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" name="np' . $number . '"';
1527
- if ($profile->is_required()) {
1528
- $buffer .= ' required';
1529
- }
1530
- $buffer .= '>';
1531
- if (!empty($placeholder)) {
1532
- $buffer .= '<option value="" selected disabled>' . esc_html($placeholder) . '</option>';
1533
- }
1534
- foreach ($profile->options as $option) {
1535
- $buffer .= '<option>' . esc_html(trim($option)) . '</option>';
1536
- }
1537
- $buffer .= "</select>\n";
1538
- }
1539
-
1540
- $buffer .= "</div>\n";
1541
-
1542
- return $buffer;
1543
- }
1544
-
1545
- if (strpos($name, 'privacy') === 0) {
1546
- if (!isset($attrs['url'])) {
1547
- $attrs['url'] = $this->get_privacy_url();
1548
- }
1549
-
1550
- if (!isset($attrs['label'])) {
1551
- $attrs['label'] = $this->form_options['privacy'];
1552
- }
1553
-
1554
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-privacy">';
1555
-
1556
- $idx++;
1557
- $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy" id="tnp-' . $idx . '"> ';
1558
- $buffer .= '<label for="tnp-' . $idx . '">';
1559
- if (!empty($attrs['url'])) {
1560
- $buffer .= '<a target="_blank" href="' . esc_attr($attrs['url']) . '">';
1561
- }
1562
- $buffer .= $attrs['label'];
1563
- if (!empty($attrs['url'])) {
1564
- $buffer .= '</a>';
1565
- }
1566
- $buffer .= '</label>';
1567
- $buffer .= '</div>';
1568
-
1569
- return $buffer;
1570
- }
1571
- }
1572
-
1573
- /**
1574
- * Builds the privacy field only for completely generated forms.
1575
- *
1576
- * @return string Empty id the privacy filed is not configured
1577
- */
1578
- function get_privacy_field($pre_html = '', $post_html = '') {
1579
- $this->setup_form_options();
1580
- $privacy_status = (int) $this->form_options['privacy_status'];
1581
- if (empty($privacy_status)) {
1582
- return '';
1583
- }
1584
-
1585
- $buffer = '<label>';
1586
- if ($privacy_status === 1) {
1587
- $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy">&nbsp;';
1588
- }
1589
- $url = $this->get_privacy_url();
1590
- if (!empty($url)) {
1591
- $buffer .= '<a target="_blank" href="' . esc_attr($url) . '">';
1592
- $buffer .= esc_attr($this->form_options['privacy']) . '</a>';
1593
- } else {
1594
- $buffer .= esc_html($this->form_options['privacy']);
1595
- }
1596
-
1597
- $buffer .= "</label>";
1598
-
1599
- return $pre_html . $buffer . $post_html;
1600
- }
1601
-
1602
- /**
1603
- * The new standard form.
1604
- *
1605
- * @param string $referrer Deprecated since 6.9.1, use the "referrer" key on $attrs
1606
- * @param string $action
1607
- * @param string $attrs
1608
- * @return string The full HTML form
1609
- */
1610
- function get_subscription_form($referrer = '', $action = null, $attrs = []) {
1611
- $language = $this->get_current_language();
1612
- $options_profile = $this->get_options('profile', $language);
1613
-
1614
- if (!is_array($attrs)) {
1615
- $attrs = [];
1616
- }
1617
-
1618
- // Possible alternative form actions (used by...?)
1619
- if (isset($attrs['action'])) {
1620
- $action = $attrs['action'];
1621
- }
1622
-
1623
- // The referrer parameter is deprecated
1624
- if (!empty($referrer)) {
1625
- $attrs['referrer'] = $referrer;
1626
- }
1627
-
1628
- $buffer = '';
1629
-
1630
- if (empty($action)) {
1631
- $action = $this->build_action_url('s');
1632
- }
1633
-
1634
- if (isset($attrs['before'])) {
1635
- $buffer .= $attrs['before'];
1636
- } else {
1637
- if (isset($attrs['class'])) {
1638
- $buffer .= '<div class="tnp tnp-subscription ' . $attrs['class'] . '">' . "\n";
1639
- } else {
1640
- $buffer .= '<div class="tnp tnp-subscription">' . "\n";
1641
- }
1642
- }
1643
-
1644
- $buffer .= '<form method="post" action="' . esc_attr($action) . '">' . "\n\n";
1645
-
1646
- $buffer .= $this->get_form_hidden_fields($attrs);
1647
-
1648
- if ($options_profile['name_status'] == 2) {
1649
- $buffer .= $this->shortcode_newsletter_field(['name' => 'first_name']);
1650
- }
1651
-
1652
- if ($options_profile['surname_status'] == 2) {
1653
- $buffer .= $this->shortcode_newsletter_field(['name' => 'last_name']);
1654
- }
1655
-
1656
- $buffer .= $this->shortcode_newsletter_field(['name' => 'email']);
1657
-
1658
- if (isset($options_profile['sex_status']) && $options_profile['sex_status'] == 2) {
1659
- $buffer .= $this->shortcode_newsletter_field(['name' => 'gender']);
1660
- }
1661
-
1662
- // Extra profile fields
1663
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1664
- // Not for subscription form
1665
- if ($options_profile['profile_' . $i . '_status'] != 2) {
1666
- continue;
1667
- }
1668
-
1669
- $buffer .= $this->shortcode_newsletter_field(['name' => 'profile', 'number' => $i]);
1670
- }
1671
-
1672
- $tmp = '';
1673
- $lists = $this->get_lists_for_subscription($language);
1674
- if (!empty($attrs['lists_field_layout']) && $attrs['lists_field_layout'] == 'dropdown') {
1675
- if (empty($attrs['lists_field_empty_label'])) {
1676
- $attrs['lists_field_empty_label'] = '';
1677
- }
1678
- if (empty($attrs['lists_field_label'])) {
1679
- $attrs['lists_field_label'] = '';
1680
- }
1681
- $buffer .= $this->shortcode_newsletter_field(['name' => 'lists', 'layout' => 'dropdown', 'first_option_label' => $attrs['lists_field_empty_label'], 'label' => $attrs['lists_field_label']]);
1682
- } else {
1683
- $buffer .= $this->shortcode_newsletter_field(['name' => 'lists']);
1684
- }
1685
-
1686
-
1687
-
1688
- // Deprecated
1689
- $extra = apply_filters('newsletter_subscription_extra', array());
1690
- foreach ($extra as $x) {
1691
- $label = $x['label'];
1692
- if (empty($label)) {
1693
- $label = '&nbsp;';
1694
- }
1695
- $name = '';
1696
- if (!empty($x['name'])) {
1697
- $name = $x['name'];
1698
- }
1699
- $buffer .= '<div class="tnp-field tnp-field-' . $name . '"><label>' . $label . "</label>";
1700
- $buffer .= $x['field'] . "</div>\n";
1701
- }
1702
-
1703
- $buffer .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1704
-
1705
- $buffer .= '<div class="tnp-field tnp-field-button">';
1706
-
1707
- $button_style = '';
1708
- if (!empty($attrs['button_color'])) {
1709
- $button_style = 'style="background-color:' . esc_attr($attrs['button_color']) . '"';
1710
- }
1711
-
1712
- if (strpos($options_profile['subscribe'], 'http') === 0) {
1713
- $buffer .= '<input class="tnp-submit-image" type="image" src="' . esc_attr($options_profile['subscribe']) . '">' . "\n";
1714
- } else {
1715
- $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($options_profile['subscribe']) . '" ' . $button_style . '>' . "\n";
1716
- }
1717
-
1718
- $buffer .= "</div>\n</form>\n";
1719
-
1720
- if (isset($attrs['after'])) {
1721
- $buffer .= $attrs['after'];
1722
- } else {
1723
- $buffer .= "</div>\n";
1724
- }
1725
-
1726
- return $buffer;
1727
- }
1728
-
1729
- function get_form($number) {
1730
- $options = get_option('newsletter_forms');
1731
-
1732
- $form = $options['form_' . $number];
1733
-
1734
- $form = do_shortcode($form);
1735
-
1736
- $action = $this->build_action_url('s');
1737
-
1738
- if (stripos($form, '<form') === false) {
1739
- $form = '<form method="post" action="' . $action . '">' . $form . '</form>';
1740
- }
1741
-
1742
- // For compatibility
1743
- $form = str_replace('{newsletter_url}', $action, $form);
1744
-
1745
- $form = $this->replace_lists($form);
1746
-
1747
- return $form;
1748
- }
1749
-
1750
- /** Replaces on passed text the special tag {lists} that can be used to show the preferences as a list of checkbox.
1751
- * They are called lists but on configuration panel they are named preferences!
1752
- *
1753
- * @param string $buffer
1754
- * @return string
1755
- */
1756
- function replace_lists($buffer) {
1757
- $checkboxes = '';
1758
- $lists = $this->get_lists_for_subscription($this->get_current_language());
1759
- foreach ($lists as $list) {
1760
- $checkboxes .= '<input type="checkbox" name="nl[]" value="' . $list->id . '"/>&nbsp;' . $list->name . '<br />';
1761
- }
1762
- $buffer = str_replace('{lists}', $checkboxes, $buffer);
1763
- $buffer = str_replace('{preferences}', $checkboxes, $buffer);
1764
- return $buffer;
1765
- }
1766
-
1767
- function notify_admin_on_subscription($user) {
1768
-
1769
- if (empty($this->options['notify'])) {
1770
- return;
1771
- }
1772
-
1773
- $message = $this->generate_admin_notification_message($user);
1774
- $email = trim($this->options['notify_email']);
1775
- $subject = $this->generate_admin_notification_subject('New subscription');
1776
-
1777
- Newsletter::instance()->mail($email, $subject, array('html' => $message));
1778
- }
1779
-
1780
- /**
1781
- * Builds the minimal subscription form, with only the email field and inline
1782
- * submit button. If enabled the privacy checkbox is added.
1783
- *
1784
- * @param type $attrs
1785
- * @return string
1786
- */
1787
- function get_subscription_form_minimal($attrs) {
1788
-
1789
- $this->setup_form_options();
1790
-
1791
- if (!is_array($attrs)) {
1792
- $attrs = [];
1793
- }
1794
-
1795
- $attrs = array_merge(array('class' => '', 'referrer' => 'minimal',
1796
- 'button' => $this->form_options['subscribe'], 'button_color' => '',
1797
- 'button_radius' => '', 'placeholder' => $this->form_options['email']), $attrs);
1798
-
1799
- $form = '';
1800
-
1801
- $form .= '<div class="tnp tnp-subscription-minimal ' . $attrs['class'] . '">';
1802
- $form .= '<form action="' . esc_attr($this->build_action_url('s')) . '" method="post">';
1803
-
1804
- $form .= $this->get_form_hidden_fields($attrs);
1805
-
1806
- $form .= '<input class="tnp-email" type="email" required name="ne" value="" placeholder="' . esc_attr($attrs['placeholder']) . '">';
1807
-
1808
- if (isset($attrs['button_label'])) {
1809
- $label = $attrs['button_label'];
1810
- } else if (isset($attrs['button'])) { // Backward compatibility
1811
- $label = $attrs['button'];
1812
- } else {
1813
- $label = $this->form_options['subscribe'];
1814
- }
1815
-
1816
- $form .= '<input class="tnp-submit" type="submit" value="' . esc_attr($attrs['button']) . '"'
1817
- . ' style="background-color:' . esc_attr($attrs['button_color']) . '">';
1818
-
1819
- $form .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1820
-
1821
- $form .= "</form></div>\n";
1822
-
1823
- return $form;
1824
- }
1825
-
1826
- function shortcode_newsletter_form($attrs, $content) {
1827
-
1828
- if (isset($attrs['type']) && $attrs['type'] === 'minimal') {
1829
- return $this->get_subscription_form_minimal($attrs);
1830
- }
1831
-
1832
- // Custom form using the [newsletter_field] shortcodes
1833
- if (!empty($content)) {
1834
- return $this->get_subscription_form_custom($attrs, $content);
1835
- }
1836
-
1837
- // Custom form hand coded and saved in the custom forms option
1838
- if (isset($attrs['form'])) {
1839
- return $this->get_form((int) $attrs['form']);
1840
- }
1841
-
1842
- // Custom hand coded form (as above, new syntax)
1843
- if (isset($attrs['number'])) {
1844
- return $this->get_form((int) $attrs['number']);
1845
- }
1846
-
1847
- return $this->get_subscription_form(null, null, $attrs);
1848
- }
1849
-
1850
- /**
1851
- *
1852
- * @global wpdb $wpdb
1853
- * @param array $attrs
1854
- * @param string $content
1855
- * @return string
1856
- */
1857
- function shortcode_newsletter($attrs, $content) {
1858
- global $wpdb;
1859
-
1860
- $message_key = $this->get_message_key_from_request();
1861
- if ($message_key == 'confirmation') {
1862
- $user = $this->get_user_from_request(false, 'preconfirm');
1863
- } else {
1864
- $user = $this->get_user_from_request();
1865
- }
1866
-
1867
- $message = apply_filters('newsletter_page_text', '', $message_key, $user);
1868
-
1869
- $options = $this->get_options('', $this->get_current_language($user));
1870
-
1871
- if (empty($message)) {
1872
- $message = $options[$message_key . '_text'];
1873
-
1874
- // TODO: the if can be removed
1875
- if ($message_key == 'confirmed') {
1876
- $message .= $options[$message_key . '_tracking'];
1877
- }
1878
- }
1879
-
1880
- // Now check what form must be added
1881
- if ($message_key == 'subscription') {
1882
- if (isset($attrs['show_form']) && $attrs['show_form'] === 'false') {
1883
- //return $this->build_field_admin_notice('The [newsletter] shortcode is configured to not show the subscription form.');
1884
- return;
1885
- }
1886
-
1887
- // Compatibility check
1888
- if (stripos($message, '<form') !== false) {
1889
- $message = str_ireplace('<form', '<form method="post" action="' . esc_attr($this->get_subscribe_url()) . '"', $message);
1890
- } else {
1891
-
1892
- if (strpos($message, '{subscription_form') === false) {
1893
- $message .= '{subscription_form}';
1894
- }
1895
-
1896
- if (isset($attrs['form'])) {
1897
- $message = str_replace('{subscription_form}', $this->get_form($attrs['form']), $message);
1898
- } else {
1899
- $message = str_replace('{subscription_form}', $this->get_subscription_form('page', null, $attrs), $message);
1900
- }
1901
- }
1902
- }
1903
-
1904
- $email = $this->get_email_from_request();
1905
-
1906
- $message = $this->replace($message, $user, $email, 'page');
1907
-
1908
- $message = do_shortcode($message);
1909
-
1910
- if (isset($_REQUEST['alert'])) {
1911
- // slashes are already added by wordpress!
1912
- $message .= '<script>alert("' . esc_js(strip_tags($_REQUEST['alert'])) . '");</script>';
1913
- }
1914
-
1915
- return $message;
1916
- }
1917
-
1918
- }
1919
-
1920
- NewsletterSubscription::instance();
1921
-
1922
- // Compatibility code
1923
-
1924
- /**
1925
- * @deprecated
1926
- * @param int $number
1927
- */
1928
- function newsletter_form($number = null) {
1929
- if ($number != null) {
1930
- echo NewsletterSubscription::instance()->get_form($number);
1931
- } else {
1932
- echo NewsletterSubscription::instance()->get_subscription_form();
1933
- }
1934
- }
 
 
 
 
 
 
 
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'] = $_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
+ }
unsubscription/index.php CHANGED
@@ -1,146 +1,146 @@
1
- <?php
2
- /* @var $this NewsletterUnsubscription */
3
- defined('ABSPATH') || exit;
4
-
5
- @include_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
- $controls = new NewsletterControls();
7
-
8
- $current_language = $this->get_current_language();
9
-
10
- $is_all_languages = $this->is_all_languages();
11
-
12
- $controls->add_language_warning();
13
-
14
- if (!$controls->is_action()) {
15
- $controls->data = $this->get_options('', $current_language);
16
- } else {
17
- if ($controls->is_action('save')) {
18
- $this->save_options($controls->data, '', null, $current_language);
19
- $controls->data = $this->get_options('', $current_language);
20
- $controls->add_message_saved();
21
- }
22
-
23
- if ($controls->is_action('reset')) {
24
- // On reset we ignore the current language
25
- $controls->data = $this->reset_options();
26
- }
27
- }
28
- ?>
29
-
30
- <div class="wrap" id="tnp-wrap">
31
-
32
- <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
33
-
34
- <div id="tnp-heading">
35
-
36
- <h2><?php _e('Cancellation', 'newsletter') ?></h2>
37
- <?php $controls->page_help('https://www.thenewsletterplugin.com/documentation/cancellation') ?>
38
-
39
- </div>
40
-
41
- <div id="tnp-body">
42
-
43
- <form method="post" action="">
44
- <?php $controls->init(); ?>
45
- <p>
46
- <?php $controls->button_save() ?>
47
- <?php $controls->button_reset() ?>
48
- </p>
49
- <div id="tabs">
50
-
51
- <ul>
52
- <li><a href="#tabs-cancellation"><?php _e('Cancellation', 'newsletter') ?></a></li>
53
- <li><a href="#tabs-reactivation"><?php _e('Reactivation', 'newsletter') ?></a></li>
54
- <li><a href="#tabs-advanced"><?php _e('Advanced', 'newsletter') ?></a></li>
55
- </ul>
56
-
57
- <div id="tabs-cancellation">
58
- <table class="form-table">
59
- <tr>
60
- <th><?php _e('Cancellation message', 'newsletter') ?></th>
61
- <td>
62
- <?php $controls->wp_editor('unsubscribe_text', array('editor_height' => 250)); ?>
63
- </td>
64
- </tr>
65
-
66
- <tr>
67
- <th><?php _e('Goodbye message', 'newsletter') ?></th>
68
- <td>
69
- <?php $controls->wp_editor('unsubscribed_text', array('editor_height' => 250)); ?>
70
- </td>
71
- </tr>
72
-
73
- <tr>
74
- <th><?php _e('Goodbye email', 'newsletter') ?></th>
75
- <td>
76
- <?php $controls->email('unsubscribed', 'wordpress', $is_all_languages, array('editor_height' => 250)); ?>
77
- </td>
78
- </tr>
79
-
80
- <tr>
81
- <th><?php _e('On error', 'newsletter') ?></th>
82
- <td>
83
- <?php $controls->wp_editor('error_text', array('editor_height' => 150)); ?>
84
- </td>
85
- </tr>
86
- </table>
87
- </div>
88
-
89
- <div id="tabs-reactivation">
90
- <table class="form-table">
91
- <tr>
92
- <th><?php _e('Reactivated message', 'newsletter') ?></th>
93
- <td>
94
- <?php $controls->wp_editor('reactivated_text', array('editor_height' => 250)); ?>
95
- <p class="description">
96
- </p>
97
- </td>
98
- </tr>
99
- </table>
100
- </div>
101
-
102
- <div id="tabs-advanced">
103
- <?php if ($is_all_languages) { ?>
104
- <table class="form-table">
105
- <tr>
106
- <th><?php _e('Cancellation requests via email', 'newsletter') ?></th>
107
- <td>
108
- <?php $controls->text_email('list_unsubscribe_mailto_header'); ?>
109
- <p class="description">
110
- <i class="fas fa-exclamation-triangle"></i> <a href="https://www.thenewsletterplugin.com/documentation/subscribers-and-management/cancellation/#list-unsubscribe" target="_blank"><?php _e('Read more', 'newsletter') ?></a>
111
- </p>
112
- </td>
113
- </tr>
114
- <tr>
115
- <th><?php _e('Disable unsubscribe headers', 'newsletter') ?></th>
116
- <td>
117
- <?php $controls->yesno('disable_unsubscribe_headers'); ?>
118
- <?php $controls->field_help('https://www.thenewsletterplugin.com/documentation/subscribers-and-management/cancellation/#list-unsubscribe') ?>
119
- </td>
120
- </tr>
121
- <tr>
122
- <th><?php _e('Notify admin on cancellation', 'newsletter') ?></th>
123
- <td>
124
- <?php $controls->yesno('notify_admin_on_unsubscription'); ?>
125
- </td>
126
- </tr>
127
- </table>
128
- <?php } else { ?>
129
-
130
- <?php $controls->switch_to_all_languages_notice(); ?>
131
-
132
- <?php } ?>
133
- </div>
134
-
135
- </div>
136
-
137
- <p>
138
- <?php $controls->button_save() ?>
139
- <?php $controls->button_reset() ?>
140
- </p>
141
- </form>
142
- </div>
143
-
144
- <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
145
-
146
- </div>
1
+ <?php
2
+ /* @var $this NewsletterUnsubscription */
3
+ defined('ABSPATH') || exit;
4
+
5
+ @include_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
+ $controls = new NewsletterControls();
7
+
8
+ $current_language = $this->get_current_language();
9
+
10
+ $is_all_languages = $this->is_all_languages();
11
+
12
+ $controls->add_language_warning();
13
+
14
+ if (!$controls->is_action()) {
15
+ $controls->data = $this->get_options('', $current_language);
16
+ } else {
17
+ if ($controls->is_action('save')) {
18
+ $this->save_options($controls->data, '', null, $current_language);
19
+ $controls->data = $this->get_options('', $current_language);
20
+ $controls->add_message_saved();
21
+ }
22
+
23
+ if ($controls->is_action('reset')) {
24
+ // On reset we ignore the current language
25
+ $controls->data = $this->reset_options();
26
+ }
27
+ }
28
+ ?>
29
+
30
+ <div class="wrap" id="tnp-wrap">
31
+
32
+ <?php include NEWSLETTER_DIR . '/tnp-header.php'; ?>
33
+
34
+ <div id="tnp-heading">
35
+
36
+ <h2><?php _e('Cancellation', 'newsletter') ?></h2>
37
+ <?php $controls->page_help('https://www.thenewsletterplugin.com/documentation/cancellation') ?>
38
+
39
+ </div>
40
+
41
+ <div id="tnp-body">
42
+
43
+ <form method="post" action="">
44
+ <?php $controls->init(); ?>
45
+ <p>
46
+ <?php $controls->button_save() ?>
47
+ <?php $controls->button_reset() ?>
48
+ </p>
49
+ <div id="tabs">
50
+
51
+ <ul>
52
+ <li><a href="#tabs-cancellation"><?php _e('Cancellation', 'newsletter') ?></a></li>
53
+ <li><a href="#tabs-reactivation"><?php _e('Reactivation', 'newsletter') ?></a></li>
54
+ <li><a href="#tabs-advanced"><?php _e('Advanced', 'newsletter') ?></a></li>
55
+ </ul>
56
+
57
+ <div id="tabs-cancellation">
58
+ <table class="form-table">
59
+ <tr>
60
+ <th><?php _e('Cancellation message', 'newsletter') ?></th>
61
+ <td>
62
+ <?php $controls->wp_editor('unsubscribe_text', array('editor_height' => 250)); ?>
63
+ </td>
64
+ </tr>
65
+
66
+ <tr>
67
+ <th><?php _e('Goodbye message', 'newsletter') ?></th>
68
+ <td>
69
+ <?php $controls->wp_editor('unsubscribed_text', array('editor_height' => 250)); ?>
70
+ </td>
71
+ </tr>
72
+
73
+ <tr>
74
+ <th><?php _e('Goodbye email', 'newsletter') ?></th>
75
+ <td>
76
+ <?php $controls->email('unsubscribed', 'wordpress', $is_all_languages, array('editor_height' => 250)); ?>
77
+ </td>
78
+ </tr>
79
+
80
+ <tr>
81
+ <th><?php _e('On error', 'newsletter') ?></th>
82
+ <td>
83
+ <?php $controls->wp_editor('error_text', array('editor_height' => 150)); ?>
84
+ </td>
85
+ </tr>
86
+ </table>
87
+ </div>
88
+
89
+ <div id="tabs-reactivation">
90
+ <table class="form-table">
91
+ <tr>
92
+ <th><?php _e('Reactivated message', 'newsletter') ?></th>
93
+ <td>
94
+ <?php $controls->wp_editor('reactivated_text', array('editor_height' => 250)); ?>
95
+ <p class="description">
96
+ </p>
97
+ </td>
98
+ </tr>
99
+ </table>
100
+ </div>
101
+
102
+ <div id="tabs-advanced">
103
+ <?php if ($is_all_languages) { ?>
104
+ <table class="form-table">
105
+ <tr>
106
+ <th><?php _e('Cancellation requests via email', 'newsletter') ?></th>
107
+ <td>
108
+ <?php $controls->text_email('list_unsubscribe_mailto_header'); ?>
109
+ <p class="description">
110
+ <i class="fas fa-exclamation-triangle"></i> <a href="https://www.thenewsletterplugin.com/documentation/subscribers-and-management/cancellation/#list-unsubscribe" target="_blank"><?php _e('Read more', 'newsletter') ?></a>
111
+ </p>
112
+ </td>
113
+ </tr>
114
+ <tr>
115
+ <th><?php _e('Disable unsubscribe headers', 'newsletter') ?></th>
116
+ <td>
117
+ <?php $controls->yesno('disable_unsubscribe_headers'); ?>
118
+ <?php $controls->field_help('https://www.thenewsletterplugin.com/documentation/subscribers-and-management/cancellation/#list-unsubscribe') ?>
119
+ </td>
120
+ </tr>
121
+ <tr>
122
+ <th><?php _e('Notify admin on cancellation', 'newsletter') ?></th>
123
+ <td>
124
+ <?php $controls->yesno('notify_admin_on_unsubscription'); ?>
125
+ </td>
126
+ </tr>
127
+ </table>
128
+ <?php } else { ?>
129
+
130
+ <?php $controls->switch_to_all_languages_notice(); ?>
131
+
132
+ <?php } ?>
133
+ </div>
134
+
135
+ </div>
136
+
137
+ <p>
138
+ <?php $controls->button_save() ?>
139
+ <?php $controls->button_reset() ?>
140
+ </p>
141
+ </form>
142
+ </div>
143
+
144
+ <?php include NEWSLETTER_DIR . '/tnp-footer.php'; ?>
145
+
146
+ </div>