Newsletter - Version 7.4.9

Version Description

  • Composer fix not updating new newsletters
Download this release

Release Info

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

Code changes from version 7.4.8 to 7.4.9

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;">', "\n";
551
- }
552
-
553
- // Container that fixes the width and makes the block responsive
554
- echo $this->get_outlook_wrapper_open($options['block_width']);
555
-
556
- echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $options['block_width'], 'px!important">', "\n";
557
- echo "<tr>";
558
- echo '<td align="', esc_attr($options['block_align']), '" style="', esc_attr($style), '" bgcolor="', esc_attr($block_background), '" width="100%">';
559
-
560
- //echo "<!-- block generated content -->\n";
561
- echo trim($content);
562
- //echo "\n<!-- /block generated content -->\n";
563
-
564
- echo "</td></tr></table>";
565
- echo $this->get_outlook_wrapper_close();
566
-
567
- // First time block creation wrapper
568
- if ($wrapper) {
569
- echo "</td></tr></table>";
570
- }
571
-
572
- return $out;
573
- }
574
-
575
- /**
576
- * Ajax call to render a block with a new set of options after the settings popup
577
- * has been saved.
578
- *
579
- * @param type $block_id
580
- * @param type $wrapper
581
- */
582
- function tnpc_render_callback() {
583
- if (!check_ajax_referer('save')) {
584
- $this->dienow('Expired request');
585
- }
586
-
587
- $block_id = $_POST['id'];
588
- $wrapper = isset($_POST['full']);
589
- $options = $this->restore_options_from_request();
590
-
591
- $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
592
- wp_die();
593
- }
594
-
595
- function hook_wp_ajax_tnpc_regenerate_email() {
596
-
597
- $content = stripslashes($_POST['content']);
598
- $global_options = $_POST['composer'];
599
-
600
- $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
601
-
602
- wp_send_json_success([
603
- 'content' => $regenerated_content,
604
- 'message' => __('Successfully updated', 'newsletter')
605
- ]);
606
- }
607
-
608
- private function regenerate_email_blocks($content, $global_options) {
609
-
610
- $raw_block_options = $this->extract_encoded_blocks_options($content);
611
-
612
- $regenerated_content = '';
613
-
614
- foreach ($raw_block_options as $raw_block_option) {
615
-
616
- /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
617
- $block_options = $this->options_decode( $a ); */
618
-
619
- $block_options = $this->options_decode($raw_block_option);
620
-
621
- $block = $this->get_block($block_options['block_id']);
622
- if (!$block) {
623
- $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
624
- }
625
-
626
- ob_start();
627
- $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
628
- $block_html = ob_get_clean();
629
-
630
- $regenerated_content .= $block_html;
631
- }
632
-
633
- return $regenerated_content;
634
- }
635
-
636
- /**
637
- * @param string $html_email_content Email html content
638
- *
639
- * @return string[] Encoded options of email blocks
640
- */
641
- private function extract_encoded_blocks_options($html_email_content) {
642
-
643
- preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
644
-
645
- return $raw_block_options[1];
646
- }
647
-
648
- function tnpc_preview_callback() {
649
- $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
650
-
651
- if (empty($email)) {
652
- echo 'Wrong email identifier';
653
- return;
654
- }
655
-
656
- echo $email['message'];
657
-
658
- wp_die();
659
- }
660
-
661
- function tnpc_css_callback() {
662
- include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
663
- wp_die();
664
- }
665
-
666
- /** Returns the correct admin page to edit the newsletter with the correct editor. */
667
- function get_editor_url($email_id, $editor_type) {
668
- switch ($editor_type) {
669
- case NewsletterEmails::EDITOR_COMPOSER:
670
- return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
671
- case NewsletterEmails::EDITOR_HTML:
672
- return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
673
- case NewsletterEmails::EDITOR_TINYMCE:
674
- return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
675
- }
676
- }
677
-
678
- /**
679
- * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
680
- * or the targeting page (it depends on newsletter status).
681
- *
682
- * @param TNP_Email $email
683
- */
684
- function get_edit_button($email, $only_icon = false) {
685
-
686
- $editor_type = $this->get_editor_type($email);
687
- if ($email->status == 'new') {
688
- $edit_url = $this->get_editor_url($email->id, $editor_type);
689
- } else {
690
- $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
691
- }
692
- switch ($editor_type) {
693
- case NewsletterEmails::EDITOR_COMPOSER:
694
- $icon_class = 'th-large';
695
- break;
696
- case NewsletterEmails::EDITOR_HTML:
697
- $icon_class = 'code';
698
- break;
699
- default:
700
- $icon_class = 'edit';
701
- break;
702
- }
703
- if ($only_icon) {
704
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
705
- '<i class="fas fa-' . $icon_class . '"></i></a>';
706
- } else {
707
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
708
- '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
709
- }
710
- }
711
-
712
- /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
713
- function get_editor_type($email) {
714
- $email = (object) $email;
715
- $editor_type = $email->editor;
716
-
717
- // Backward compatibility
718
- $email_options = maybe_unserialize($email->options);
719
- if (isset($email_options['composer'])) {
720
- $editor_type = NewsletterEmails::EDITOR_COMPOSER;
721
- }
722
- // End backward compatibility
723
-
724
- return $editor_type;
725
- }
726
-
727
- /**
728
- *
729
- * @param type $action
730
- * @param type $user
731
- * @param type $email
732
- * @return type
733
- * @global wpdb $wpdb
734
- */
735
- function hook_newsletter_action($action, $user, $email) {
736
- global $wpdb;
737
-
738
- switch ($action) {
739
- case 'v':
740
- case 'view':
741
- $id = $_GET['id'];
742
- if ($id == 'last') {
743
- $email = $wpdb->get_row("select * from " . NEWSLETTER_EMAILS_TABLE . " where private=0 and type='message' and status='sent' order by send_on desc limit 1");
744
- } else {
745
- $email = $this->get_email($_GET['id']);
746
- }
747
- if (empty($email)) {
748
- header("HTTP/1.0 404 Not Found");
749
- die('Email not found');
750
- }
751
-
752
- if (!Newsletter::instance()->is_allowed()) {
753
-
754
- if ($email->status == 'new') {
755
- header("HTTP/1.0 404 Not Found");
756
- die('Not sent yet');
757
- }
758
-
759
- if ($email->private == 1) {
760
- if (!$user) {
761
- header("HTTP/1.0 404 Not Found");
762
- die('No available for online view');
763
- }
764
- $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
765
- if (!$sent) {
766
- header("HTTP/1.0 404 Not Found");
767
- die('No available for online view');
768
- }
769
- }
770
- }
771
-
772
-
773
- header('Content-Type: text/html;charset=UTF-8');
774
- header('X-Robots-Tag: noindex,nofollow,noarchive');
775
- header('Cache-Control: no-cache,no-store,private');
776
-
777
- echo $this->replace($email->message, $user, $email);
778
-
779
- die();
780
- break;
781
-
782
- case 'emails-css':
783
- $email_id = (int) $_GET['id'];
784
-
785
- $body = Newsletter::instance()->get_email_field($email_id, 'message');
786
-
787
- $x = strpos($body, '<style');
788
- if ($x === false)
789
- return;
790
-
791
- $x = strpos($body, '>', $x);
792
- $y = strpos($body, '</style>');
793
-
794
- header('Content-Type: text/css;charset=UTF-8');
795
-
796
- echo substr($body, $x + 1, $y - $x - 1);
797
-
798
- die();
799
- break;
800
-
801
- case 'emails-composer-css':
802
- header('Cache: no-cache');
803
- header('Content-Type: text/css');
804
- echo $this->get_composer_css();
805
- die();
806
- break;
807
-
808
- case 'emails-preview':
809
- if (!Newsletter::instance()->is_allowed()) {
810
- die('Not enough privileges');
811
- }
812
-
813
- if (!check_admin_referer('view')) {
814
- die();
815
- }
816
-
817
- $theme_id = $_GET['id'];
818
- $theme = $this->themes->get_theme($theme_id);
819
-
820
- // Used by theme code
821
- $theme_options = $this->themes->get_options($theme_id);
822
-
823
- $theme_url = $theme['url'];
824
-
825
- header('Content-Type: text/html;charset=UTF-8');
826
-
827
- include $theme['dir'] . '/theme.php';
828
-
829
- die();
830
- break;
831
-
832
- case 'emails-preview-text':
833
- header('Content-Type: text/plain;charset=UTF-8');
834
- if (!Newsletter::instance()->is_allowed()) {
835
- die('Not enough privileges');
836
- }
837
-
838
- if (!check_admin_referer('view')) {
839
- die();
840
- }
841
-
842
- // Used by theme code
843
- $theme_options = $this->get_current_theme_options();
844
-
845
- $file = include $theme['dir'] . '/theme-text.php';
846
-
847
- if (is_file($file)) {
848
- include $file;
849
- }
850
-
851
- die();
852
- break;
853
-
854
- case 'emails-create':
855
- // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
856
- // excerpt, thumbnail are extracted.
857
- if (!Newsletter::instance()->is_allowed()) {
858
- die('Not enough privileges');
859
- }
860
-
861
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
862
- $controls = new NewsletterControls();
863
-
864
- if (!$controls->is_action('create')) {
865
- die('Wrong call');
866
- }
867
-
868
- $theme_id = $controls->data['id'];
869
- $theme = $this->themes->get_theme($theme_id);
870
-
871
- if (!$theme) {
872
- die('invalid theme');
873
- }
874
-
875
- $this->themes->save_options($theme_id, $controls->data);
876
-
877
- $email = array();
878
- $email['status'] = 'new';
879
- $email['subject'] = ''; //__('Here the email subject', 'newsletter');
880
- $email['track'] = Newsletter::instance()->options['track'];
881
- $email['send_on'] = time();
882
- $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
883
- $email['type'] = 'message';
884
-
885
- $theme_options = $this->themes->get_options($theme_id);
886
-
887
- $theme_url = $theme['url'];
888
- $theme_subject = '';
889
-
890
- ob_start();
891
- include $theme['dir'] . '/theme.php';
892
- $email['message'] = ob_get_clean();
893
-
894
- if (!empty($theme_subject)) {
895
- $email['subject'] = $theme_subject;
896
- }
897
-
898
- if (file_exists($theme['dir'] . '/theme-text.php')) {
899
- ob_start();
900
- include $theme['dir'] . '/theme-text.php';
901
- $email['message_text'] = ob_get_clean();
902
- } else {
903
- $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
904
- }
905
-
906
- $email = $this->save_email($email);
907
-
908
- $edit_url = $this->get_editor_url($email->id, $email->editor);
909
-
910
- header('Location: ' . $edit_url);
911
-
912
- die();
913
- break;
914
- }
915
- }
916
-
917
- function admin_menu() {
918
- $this->add_menu_page('index', 'Newsletters');
919
- $this->add_admin_page('list', 'Email List');
920
- $this->add_admin_page('new', 'Email New');
921
- $this->add_admin_page('edit', 'Email Edit');
922
- $this->add_admin_page('theme', 'Email Themes');
923
- $this->add_admin_page('composer', 'The Composer');
924
- $this->add_admin_page('editorhtml', 'HTML Editor');
925
- $this->add_admin_page('editortinymce', 'TinyMCE Editor');
926
- }
927
-
928
- /**
929
- * Builds a block data structure starting from the folder containing the block
930
- * files.
931
- *
932
- * @param string $dir
933
- * @return array | WP_Error
934
- */
935
- function build_block($dir) {
936
- $dir = realpath($dir);
937
- $dir = wp_normalize_path($dir);
938
- $full_file = $dir . '/block.php';
939
- if (!is_file($full_file)) {
940
- return new WP_Error('1', 'Missing block.php file in ' . $dir);
941
- }
942
-
943
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
944
- $file = basename($dir);
945
-
946
- $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
947
- $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
948
- $data = array_merge($defaults, $data);
949
-
950
- if (is_file($dir . '/icon.png')) {
951
- $data['icon'] = content_url($relative_dir . '/icon.png');
952
- }
953
-
954
- $data['id'] = sanitize_key($file);
955
-
956
- // Absolute path of the block files
957
- $data['dir'] = $dir;
958
- $data['url'] = content_url($relative_dir);
959
-
960
- return $data;
961
- }
962
-
963
- /**
964
- *
965
- * @param type $dir
966
- * @return type
967
- */
968
- function scan_blocks_dir($dir) {
969
- $dir = realpath($dir);
970
- if (!$dir) {
971
- return [];
972
- }
973
- $dir = wp_normalize_path($dir);
974
-
975
- $list = [];
976
- $handle = opendir($dir);
977
- while ($file = readdir($handle)) {
978
-
979
- $data = $this->build_block($dir . '/' . $file);
980
-
981
- if (is_wp_error($data)) {
982
- $this->logger->error($data);
983
- continue;
984
- }
985
- $list[$data['id']] = $data;
986
- }
987
- closedir($handle);
988
- return $list;
989
- }
990
-
991
- /**
992
- * Array of arrays with every registered block and legacy block converted to the new
993
- * format.
994
- *
995
- * @return array
996
- */
997
- function get_blocks() {
998
-
999
- if (!is_null($this->blocks)) {
1000
- return $this->blocks;
1001
- }
1002
-
1003
- $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1004
-
1005
- $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1006
-
1007
- $this->blocks = array_merge($extended, $this->blocks);
1008
-
1009
- $dirs = apply_filters('newsletter_blocks_dir', array());
1010
-
1011
- //$this->logger->debug('Block dirs:');
1012
- //$this->logger->debug($dirs);
1013
-
1014
- foreach ($dirs as $dir) {
1015
- $list = $this->scan_blocks_dir($dir);
1016
- $this->blocks = array_merge($list, $this->blocks);
1017
- }
1018
-
1019
- do_action('newsletter_register_blocks');
1020
-
1021
- foreach (TNP_Composer::$block_dirs as $dir) {
1022
- $block = $this->build_block($dir);
1023
- if (is_wp_error($block)) {
1024
- $this->logger->error($block);
1025
- continue;
1026
- }
1027
- if (!isset($this->blocks[$block['id']])) {
1028
- $this->blocks[$block['id']] = $block;
1029
- } else {
1030
- $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1031
- }
1032
- }
1033
-
1034
- $this->blocks = array_reverse($this->blocks);
1035
- return $this->blocks;
1036
- }
1037
-
1038
- /**
1039
- * Return a single block (associative array) checking for legacy ID as well.
1040
- *
1041
- * @param string $id
1042
- * @return array
1043
- */
1044
- function get_block($id) {
1045
- switch ($id) {
1046
- case 'content-03-text.block':
1047
- $id = 'text';
1048
- break;
1049
- case 'footer-03-social.block':
1050
- $id = 'social';
1051
- break;
1052
- case 'footer-02-canspam.block':
1053
- $id = 'canspam';
1054
- break;
1055
- case 'content-05-image.block':
1056
- $id = 'image';
1057
- break;
1058
- case 'header-01-header.block':
1059
- $id = 'header';
1060
- break;
1061
- case 'footer-01-footer.block':
1062
- $id = 'footer';
1063
- break;
1064
- case 'content-02-heading.block':
1065
- $id = 'heading';
1066
- break;
1067
- case 'content-07-twocols.block':
1068
- case 'content-06-posts.block':
1069
- $id = 'posts';
1070
- break;
1071
- case 'content-04-cta.block':
1072
- $id = 'cta';
1073
- break;
1074
- case 'content-01-hero.block':
1075
- $id = 'hero';
1076
- break;
1077
- // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1078
- // break;
1079
- }
1080
-
1081
- // Conversion for old full path ID
1082
- $id = sanitize_key(basename($id));
1083
-
1084
- // TODO: Correct id for compatibility
1085
- $blocks = $this->get_blocks();
1086
- if (!isset($blocks[$id])) {
1087
- return null;
1088
- }
1089
- return $blocks[$id];
1090
- }
1091
-
1092
- function scan_presets_dir($dir = null) {
1093
-
1094
- if (is_null($dir)) {
1095
- $dir = __DIR__ . '/presets';
1096
- }
1097
-
1098
- if (!is_dir($dir)) {
1099
- return array();
1100
- }
1101
-
1102
- $handle = opendir($dir);
1103
- $list = array();
1104
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1105
- while ($file = readdir($handle)) {
1106
-
1107
- if ($file == '.' || $file == '..')
1108
- continue;
1109
-
1110
- // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1111
- $preset_id = sanitize_key($file);
1112
-
1113
- $full_file = $dir . '/' . $file . '/preset.json';
1114
-
1115
- if (!is_file($full_file)) {
1116
- continue;
1117
- }
1118
-
1119
- $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1120
-
1121
- $list[$preset_id] = $icon;
1122
- }
1123
- closedir($handle);
1124
- return $list;
1125
- }
1126
-
1127
- function get_preset_from_file($id, $dir = null) {
1128
-
1129
- if (is_null($dir)) {
1130
- $dir = __DIR__ . '/presets';
1131
- }
1132
-
1133
- $id = $this->sanitize_file_name($id);
1134
-
1135
- if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1136
- return array();
1137
- }
1138
-
1139
- $json_content = file_get_contents("$dir/$id/preset.json");
1140
- $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1141
- $json = json_decode($json_content);
1142
- $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1143
-
1144
- return $json;
1145
- }
1146
-
1147
- function get_composer_css() {
1148
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1149
- $blocks = $this->get_blocks();
1150
- foreach ($blocks as $block) {
1151
- if (!file_exists($block['dir'] . '/style.css')) {
1152
- continue;
1153
- }
1154
- $css .= "\n\n";
1155
- $css .= "/* " . $block['name'] . " */\n";
1156
- $css .= file_get_contents($block['dir'] . '/style.css');
1157
- }
1158
- return $css;
1159
- }
1160
-
1161
- function get_composer_backend_css() {
1162
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1163
- $css .= "\n\n";
1164
- $css .= $this->get_composer_css();
1165
- return $css;
1166
- }
1167
-
1168
- /**
1169
- * Send an email to the test subscribers.
1170
- *
1171
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1172
- * @param NewsletterControls $controls
1173
- */
1174
- function send_test_email($email, $controls) {
1175
- if (!$email) {
1176
- $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1177
- return;
1178
- }
1179
-
1180
- $original_subject = $email->subject;
1181
- $this->set_test_subject_to($email);
1182
-
1183
- $users = NewsletterUsers::instance()->get_test_users();
1184
- if (count($users) == 0) {
1185
- $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1186
- '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1187
- __('Read more', 'newsletter') . '</strong></a>.';
1188
- } else {
1189
- $r = Newsletter::instance()->send($email, $users, true);
1190
- $emails = array();
1191
- foreach ($users as $user) {
1192
- $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1193
- }
1194
- if (is_wp_error($r)) {
1195
- $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1196
- $controls->errors .= __('Test subscribers:', 'newsletter');
1197
- $controls->errors .= ' ' . implode(', ', $emails);
1198
- $controls->errors .= '<br>';
1199
- $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1200
- $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1201
- } else {
1202
- $controls->messages = __('Test subscribers:', 'newsletter');
1203
-
1204
- $controls->messages .= ' ' . implode(', ', $emails);
1205
- $controls->messages .= '.<br>';
1206
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1207
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1208
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1209
- }
1210
- }
1211
- $email->subject = $original_subject;
1212
- }
1213
-
1214
- /**
1215
- * Send an email to the test subscribers.
1216
- *
1217
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1218
- * @param string $email_address
1219
- *
1220
- * @throws Exception
1221
- */
1222
- function send_test_newsletter_to_email_address($email, $email_address) {
1223
-
1224
- if (!$email) {
1225
- throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1226
- }
1227
-
1228
- $this->set_test_subject_to($email);
1229
-
1230
- $dummy_subscriber = $this->make_dummy_subscriber();
1231
- $dummy_subscriber->email = $email_address;
1232
-
1233
- $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1234
-
1235
- $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1236
-
1237
- if (is_wp_error($result)) {
1238
- $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1239
- $error_message .= __('Test subscribers:', 'newsletter');
1240
- $error_message .= ' ' . $email;
1241
- $error_message .= '<br>';
1242
- $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1243
- $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1244
- throw new Exception($error_message);
1245
- }
1246
-
1247
- $messages = __('Test subscribers:', 'newsletter');
1248
-
1249
- $messages .= ' ' . $email;
1250
- $messages .= '.<br>';
1251
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1252
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1253
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1254
-
1255
- return $messages;
1256
- }
1257
-
1258
- private function set_test_subject_to($email) {
1259
- if ($email->subject == '') {
1260
- $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1261
- } else {
1262
- $email->subject = $email->subject . ' (TEST)';
1263
- }
1264
- }
1265
-
1266
- private function make_dummy_subscriber() {
1267
- $dummy_user = new TNP_User();
1268
- $dummy_user->id = 0;
1269
- $dummy_user->email = 'john.doe@example.org';
1270
- $dummy_user->name = 'John';
1271
- $dummy_user->surname = 'Doe';
1272
- $dummy_user->sex = 'n';
1273
- $dummy_user->language = '';
1274
- $dummy_user->ip = '';
1275
-
1276
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1277
- $profile_key = "profile_$i";
1278
- $dummy_user->$profile_key = '';
1279
- }
1280
-
1281
- return $dummy_user;
1282
- }
1283
-
1284
- function restore_options_from_request() {
1285
-
1286
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1287
- $controls = new NewsletterControls();
1288
- $options = $controls->data;
1289
-
1290
- if (isset($_POST['options']) && is_array($_POST['options'])) {
1291
- // Get all block options
1292
- //$options = stripslashes_deep($_POST['options']);
1293
- // Deserialize inline edits when
1294
- // render is preformed on saving block options
1295
- if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1296
- $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1297
- }
1298
-
1299
- // Restore inline edits from data-json
1300
- // coming from inline editing
1301
- // and merge with current inline edit
1302
- if (isset($_POST['encoded_options'])) {
1303
- $decoded_options = $this->options_decode($_POST['encoded_options']);
1304
-
1305
- $to_merge_inline_edits = [];
1306
-
1307
- if (isset($decoded_options['inline_edits'])) {
1308
- foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1309
- $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1310
- }
1311
- }
1312
-
1313
- //Overwrite with new edited content
1314
- if (isset($options['inline_edits'])) {
1315
- foreach ($options['inline_edits'] as $inline_edit) {
1316
- $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1317
- }
1318
- }
1319
-
1320
- $options['inline_edits'] = array_values($to_merge_inline_edits);
1321
- $options = array_merge($decoded_options, $options);
1322
- }
1323
-
1324
- return $options;
1325
- }
1326
-
1327
- return array();
1328
- }
1329
-
1330
- public function hook_wp_ajax_tnpc_delete_preset() {
1331
-
1332
- if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1333
- wp_send_json_error('Expired request');
1334
- }
1335
-
1336
- $preset_id = (int) $_REQUEST['presetId'];
1337
-
1338
- $newsletter = Newsletter::instance();
1339
-
1340
- if ($preset_id > 0) {
1341
- $preset = $newsletter->get_email($preset_id);
1342
-
1343
- if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1344
- Newsletter::instance()->delete_email($preset_id);
1345
- wp_send_json_success();
1346
- } else {
1347
- wp_send_json_error(__('Is not a preset!', 'newsletter'));
1348
- }
1349
- } else {
1350
- wp_send_json_error();
1351
- }
1352
- }
1353
-
1354
- }
1355
-
1356
- NewsletterEmails::instance();
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterEmails extends NewsletterModule {
6
+
7
+ static $instance;
8
+
9
+ const EDITOR_COMPOSER = 2;
10
+ const EDITOR_HTML = 1;
11
+ const EDITOR_TINYMCE = 0;
12
+
13
+ static $PRESETS_LIST;
14
+
15
+ const PRESET_EMAIL_TYPE = 'composer_template';
16
+
17
+ // Cache
18
+ var $blocks = null;
19
+
20
+ /**
21
+ * @return NewsletterEmails
22
+ */
23
+ static function instance() {
24
+ if (self::$instance == null) {
25
+ self::$instance = new NewsletterEmails();
26
+ }
27
+
28
+ return self::$instance;
29
+ }
30
+
31
+ function __construct() {
32
+ self::$PRESETS_LIST = array("cta", "invite", "announcement", "posts", "sales", "product", "tour", "simple");
33
+ $this->themes = new NewsletterThemes('emails');
34
+ parent::__construct('emails', '1.1.5');
35
+ add_action('newsletter_action', array($this, 'hook_newsletter_action'), 13, 3);
36
+ add_action('newsletter_init', [$this, 'hook_newsletter_init']);
37
+
38
+ if (is_admin()) {
39
+ // Thank you to plugins which add the WP editor on other admin plugin pages...
40
+ if (isset($_GET['page']) && $_GET['page'] == 'newsletter_emails_edit') {
41
+ global $wp_actions;
42
+ $wp_actions['wp_enqueue_editor'] = 1;
43
+ }
44
+ }
45
+ }
46
+
47
+ function hook_newsletter_init() {
48
+ if (is_admin()) {
49
+ if (defined('DOING_AJAX') && DOING_AJAX) {
50
+ if (Newsletter::instance()->is_allowed()) {
51
+ add_action('wp_ajax_tnpc_render', array($this, 'tnpc_render_callback'));
52
+ add_action('wp_ajax_tnpc_preview', array($this, 'tnpc_preview_callback'));
53
+ add_action('wp_ajax_tnpc_css', array($this, 'tnpc_css_callback'));
54
+ add_action('wp_ajax_tnpc_options', array($this, 'hook_wp_ajax_tnpc_options'));
55
+ add_action('wp_ajax_tnpc_get_all_presets', array($this, 'ajax_get_all_presets'));
56
+ add_action('wp_ajax_tnpc_get_preset', array($this, 'ajax_get_preset'));
57
+ add_action('wp_ajax_tnpc_delete_preset', array($this, 'hook_wp_ajax_tnpc_delete_preset'));
58
+ add_action('wp_ajax_tnpc_regenerate_email', array($this, 'hook_wp_ajax_tnpc_regenerate_email'));
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ function options_decode($options) {
65
+ // Old "query string" format
66
+ if (is_string($options) && strpos($options, 'options[') !== false) {
67
+ $opts = [];
68
+ parse_str($options, $opts);
69
+ $options = $opts['options'];
70
+ }
71
+
72
+ if (is_array($options)) {
73
+ return $options;
74
+ }
75
+
76
+ // Json data should be base64 encoded, but for short time it wasn't
77
+ $tmp = json_decode($options, true);
78
+ if (is_null($tmp)) {
79
+ return json_decode(base64_decode($options), true);
80
+ } else {
81
+ return $tmp;
82
+ }
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @param array $options Options array
88
+ */
89
+ function options_encode($options) {
90
+ return base64_encode(json_encode($options, JSON_HEX_TAG | JSON_HEX_AMP));
91
+ }
92
+
93
+ /**
94
+ * Builds and returns the HTML with the form fields of a specific block.
95
+ *
96
+ * @global wpdb $wpdb
97
+ */
98
+ function hook_wp_ajax_tnpc_options() {
99
+ global $wpdb;
100
+
101
+ $block = $this->get_block($_REQUEST['id']);
102
+ if (!$block) {
103
+ die('Block not found with id ' . esc_html($_REQUEST['id']));
104
+ }
105
+
106
+ if (!class_exists('NewsletterControls')) {
107
+ include NEWSLETTER_INCLUDES_DIR . '/controls.php';
108
+ }
109
+
110
+ $options = $this->options_decode(stripslashes_deep($_REQUEST['options']));
111
+ $composer = isset($_POST['composer']) ? $_POST['composer'] : [];
112
+
113
+ $context = array('type' => '');
114
+ if (isset($_REQUEST['context_type'])) {
115
+ $context['type'] = $_REQUEST['context_type'];
116
+ }
117
+
118
+ $controls = new NewsletterControls($options);
119
+ $fields = new NewsletterFields($controls);
120
+
121
+ $controls->init();
122
+ echo '<input type="hidden" name="action" value="tnpc_render">';
123
+ echo '<input type="hidden" name="id" value="' . esc_attr($_REQUEST['id']) . '">';
124
+ echo '<input type="hidden" name="context_type" value="' . esc_attr($context['type']) . '">';
125
+ $inline_edits = '';
126
+ if (isset($controls->data['inline_edits'])) {
127
+ $inline_edits = $controls->data['inline_edits'];
128
+ }
129
+ echo '<input type="hidden" name="options[inline_edits]" value="', esc_attr($this->options_encode($inline_edits)), '">';
130
+ echo "<h2>", esc_html($block["name"]), "</h2>";
131
+ include $block['dir'] . '/options.php';
132
+ wp_die();
133
+ }
134
+
135
+ /**
136
+ * Retrieves the presets list (no id in GET) or a specific preset id in GET)
137
+ */
138
+ public function ajax_get_all_presets() {
139
+ wp_send_json_success($this->get_all_preset());
140
+ }
141
+
142
+ public function ajax_get_preset() {
143
+
144
+ if (empty($_REQUEST['id'])) {
145
+ wp_send_json_error([
146
+ 'msg' => __('Invalid preset ID')
147
+ ]);
148
+ }
149
+
150
+ $preset_id = $_REQUEST['id'];
151
+ $preset_content = $this->get_preset_content($preset_id);
152
+ $global_options = $this->get_preset_global_options($preset_id);
153
+
154
+ wp_send_json_success([
155
+ 'content' => $preset_content,
156
+ 'globalOptions' => $global_options,
157
+ ]);
158
+ }
159
+
160
+ private function get_preset_content($preset_id) {
161
+
162
+ $content = '';
163
+
164
+ if ($this->is_a_tnp_default_preset($preset_id)) {
165
+
166
+ // Get preset from file
167
+ $preset = $this->get_preset_from_file($preset_id);
168
+
169
+ foreach ($preset->blocks as $item) {
170
+ ob_start();
171
+ $this->render_block($item->block, true, (array) $item->options);
172
+ $content .= trim(ob_get_clean());
173
+ }
174
+ } else {
175
+
176
+ // Get preset from db
177
+ $preset_email = $this->get_email(intval($preset_id));
178
+ $global_options = $this->extract_global_options_from($preset_email);
179
+ $content = $this->regenerate_email_blocks($preset_email->message, $global_options);
180
+ }
181
+
182
+ return $content;
183
+ }
184
+
185
+ private function get_preset_global_options($preset_id) {
186
+
187
+ if ($this->is_a_tnp_default_preset($preset_id)) {
188
+ return [];
189
+ }
190
+
191
+ // Get preset from db
192
+ $preset_email = $this->get_email(intval($preset_id));
193
+ $global_options = $this->extract_global_options_from($preset_email);
194
+
195
+ return $global_options;
196
+ }
197
+
198
+ private function extract_global_options_from($email) {
199
+ $global_options = [];
200
+ foreach ($email->options as $global_option_name => $global_option) {
201
+ if (strpos($global_option_name, 'composer_') === 0) {
202
+ $global_options[str_replace('composer_', '', $global_option_name)] = $global_option;
203
+ }
204
+ }
205
+
206
+ return $global_options;
207
+ }
208
+
209
+ private function is_a_tnp_default_preset($preset_id) {
210
+ return in_array($preset_id, self::$PRESETS_LIST);
211
+ }
212
+
213
+ private function get_all_preset() {
214
+
215
+ $content = "<div class='tnpc-preset-container'>";
216
+
217
+ if ($this->is_normal_context_request()) {
218
+ $content .= "<div class='tnpc-preset-legacy-themes'><a href='" . $this->get_admin_page_url('theme') . "'>" . __('Looking for legacy themes?', 'newsletter') . "</a></div>";
219
+ }
220
+
221
+ // LOAD USER PRESETS
222
+ $user_preset_list = $this->get_emails(self::PRESET_EMAIL_TYPE);
223
+
224
+ foreach ($user_preset_list as $user_preset) {
225
+
226
+ $default_icon_url = NEWSLETTER_URL . "/emails/presets/default-icon.png?ver=2";
227
+ $preset_name = $user_preset->subject;
228
+ $delete_preset_text = __('Delete', 'newsletter');
229
+ $edit_preset_text = __('Edit', 'newsletter');
230
+
231
+ // esc_js() assumes the string will be in single quote (arghhh!!!)
232
+ $onclick_edit = 'tnpc_edit_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
233
+ $onclick_delete = 'tnpc_delete_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
234
+ $onclick_load = 'tnpc_load_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
235
+
236
+ $content .= "<div class='tnpc-preset' onclick='" . esc_attr($onclick_load) . "'>\n";
237
+ $content .= "<img src='$default_icon_url' title='" . esc_attr($preset_name) . "' alt='" . esc_attr($preset_name) . "'>\n";
238
+ $content .= "<span class='tnpc-preset-label'>" . esc_html($user_preset->subject) . "</span>\n";
239
+ $content .= "<span class='tnpc-delete-preset' onclick='" . esc_attr($onclick_delete) . "' title='" . esc_attr($delete_preset_text) . "'><i class='fas fa-times'></i></span>\n";
240
+ $content .= "<span class='tnpc-edit-preset' onclick='" . esc_attr($onclick_edit) . "' title='" . esc_attr($edit_preset_text) . "'><i class='fas fa-pencil-alt'></i></span>\n";
241
+ $content .= "</div>";
242
+ }
243
+
244
+ // LOAD TNP PRESETS
245
+ foreach (self::$PRESETS_LIST as $id) {
246
+ $preset = $this->get_preset_from_file($id);
247
+ $preset_name = esc_html($preset->name);
248
+ $content .= "<div class='tnpc-preset' onclick='tnpc_load_preset(\"$id\")'>";
249
+ $content .= "<img src='$preset->icon' title='$preset_name' alt='$preset_name'/>";
250
+ $content .= "<span class='tnpc-preset-label'>$preset_name</span>";
251
+ $content .= "</div>";
252
+ }
253
+
254
+ if ($this->is_normal_context_request()) {
255
+ $content .= $this->get_automated_spot_element();
256
+ $content .= $this->get_autoresponder_spot_element();
257
+ $content .= $this->get_raw_html_preset_element();
258
+ }
259
+
260
+ return $content;
261
+ }
262
+
263
+ private function is_normal_context_request() {
264
+ return empty($_REQUEST['context_type']);
265
+ }
266
+
267
+ private function is_automated_context_request() {
268
+ return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'automated';
269
+ }
270
+
271
+ private function is_autoresponder_context_request() {
272
+ return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'autoresponder';
273
+ }
274
+
275
+ private function get_automated_spot_element() {
276
+ $result = "<div class='tnpc-preset'>";
277
+ if (class_exists('NewsletterAutomated')) {
278
+ $result .= "<a href='?page=newsletter_automated_index'>";
279
+ } else {
280
+ $result .= "<a href='https://www.thenewsletterplugin.com/automated?utm_source=composer&utm_campaign=plugin&utm_medium=automated'>";
281
+ }
282
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/automated.png' title='Automated addon' alt='Automated'/>";
283
+ $result .= "<span class='tnpc-preset-label'>Daily, weekly and monthly newsletters</span></a>";
284
+ $result .= "</div>";
285
+
286
+ return $result;
287
+ }
288
+
289
+ private function get_autoresponder_spot_element() {
290
+ $result = "<div class='tnpc-preset'>";
291
+ if (class_exists('NewsletterAutoresponder')) {
292
+ $result .= "<a href='?page=newsletter_autoresponder_index'>";
293
+ } else {
294
+ $result .= "<a href='https://www.thenewsletterplugin.com/autoresponder?utm_source=composer&utm_campaign=plugin&utm_medium=autoresponder' target='_blank'>";
295
+ }
296
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/autoresponder.png' title='Autoresponder addon' alt='Autoresponder'/>";
297
+ $result .= "<span class='tnpc-preset-label'>Autoresponders</span></a>";
298
+ $result .= "</div>";
299
+
300
+ return $result;
301
+ }
302
+
303
+ private function get_raw_html_preset_element() {
304
+
305
+ $result = "<div class='tnpc-preset tnpc-preset-html' onclick='location.href=\"" . wp_nonce_url('admin.php?page=newsletter_emails_new&id=rawhtml', 'newsletter-new') . "\"'>";
306
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/rawhtml.png' title='RAW HTML' alt='RAW'/>";
307
+ $result .= "<span class='tnpc-preset-label'>Raw HTML</span>";
308
+ $result .= "</div>";
309
+
310
+ $result .= "<div class='clear'></div>";
311
+ $result .= "</div>";
312
+
313
+ return $result;
314
+ }
315
+
316
+ function has_dynamic_blocks($theme) {
317
+ preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
318
+ foreach ($matches[1] as $match) {
319
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
320
+ $options = $this->options_decode($a);
321
+
322
+ $block = $this->get_block($options['block_id']);
323
+ if (!$block) {
324
+ continue;
325
+ }
326
+ if ($block['type'] == 'dynamic') {
327
+ return true;
328
+ }
329
+ }
330
+ return false;
331
+ }
332
+
333
+ /**
334
+ * Regenerates a saved composed email rendering each block. Regeneration is
335
+ * conditioned (possibly) by the context. The context is usually passed to blocks
336
+ * so they can act in the right manner.
337
+ *
338
+ * $context contains a type and, for automated, the last_run.
339
+ *
340
+ * $email can actually be even a string containing the full newsletter HTML code.
341
+ *
342
+ * @param TNP_Email $email
343
+ * @return string
344
+ */
345
+ function regenerate($email, $context = []) {
346
+
347
+ $context = array_merge(['last_run' => 0, 'type' => ''], $context);
348
+
349
+ preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
350
+
351
+ $result = '';
352
+ $subject = '';
353
+
354
+ foreach ($matches[1] as $match) {
355
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
356
+ $options = $this->options_decode($a);
357
+
358
+ $block = $this->get_block($options['block_id']);
359
+ if (!$block) {
360
+ $this->logger->debug('Unable to load the block ' . $options['block_id']);
361
+ //continue;
362
+ }
363
+
364
+ ob_start();
365
+ $out = $this->render_block($options['block_id'], true, $options, $context);
366
+ if (is_array($out)) {
367
+ if ($out['return_empty_message'] || $out['stop']) {
368
+ return false;
369
+ }
370
+ if ($out['skip']) {
371
+ continue;
372
+ }
373
+ if (empty($subject) && !empty($out['subject'])) {
374
+ $subject = $out['subject'];
375
+ }
376
+ }
377
+ $block_html = ob_get_clean();
378
+ $result .= $block_html;
379
+ }
380
+
381
+ $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
382
+ $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
383
+ $email->subject = $subject;
384
+ return true;
385
+ }
386
+
387
+ function remove_block_data($text) {
388
+ // TODO: Lavorare!
389
+ return $text;
390
+ }
391
+
392
+ static function get_outlook_wrapper_open($width = 600) {
393
+ return '<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '" style="vertical-align:top;width:' . $width . 'px;"><![endif]-->';
394
+ }
395
+
396
+ static function get_outlook_wrapper_close() {
397
+ return "<!--[if mso | IE]></td></tr></table><![endif]-->";
398
+ }
399
+
400
+ function hook_safe_style_css($rules) {
401
+ $rules[] = 'display';
402
+ return $rules;
403
+ }
404
+
405
+ /**
406
+ * Renders a block identified by its id, using the block options and adding a wrapper
407
+ * if required (for the first block rendering).
408
+ *
409
+ * @param string $block_id
410
+ * @param boolean $wrapper
411
+ * @param array $options
412
+ * @param array $context
413
+ * @param array $composer
414
+ */
415
+ function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
416
+ static $kses_style_filter = false;
417
+ include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
418
+
419
+ //Remove 'options_composer_' prefix
420
+ $composer_defaults = [];
421
+ foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
422
+ $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
423
+ }
424
+ $composer = array_merge($composer_defaults, $composer);
425
+
426
+ $width = 600;
427
+ $font_family = 'Helvetica, Arial, sans-serif';
428
+
429
+ $global_title_font_family = $composer['title_font_family'];
430
+ $global_title_font_size = $composer['title_font_size'];
431
+ $global_title_font_color = $composer['title_font_color'];
432
+ $global_title_font_weight = $composer['title_font_weight'];
433
+
434
+ $global_text_font_family = $composer['text_font_family'];
435
+ $global_text_font_size = $composer['text_font_size'];
436
+ $global_text_font_color = $composer['text_font_color'];
437
+ $global_text_font_weight = $composer['text_font_weight'];
438
+
439
+ $global_button_font_family = $composer['button_font_family'];
440
+ $global_button_font_size = $composer['button_font_size'];
441
+ $global_button_font_color = $composer['button_font_color'];
442
+ $global_button_font_weight = $composer['button_font_weight'];
443
+ $global_button_background_color = $composer['button_background_color'];
444
+
445
+ $global_block_background = $composer['block_background'];
446
+
447
+ $info = Newsletter::instance()->get_options('info');
448
+
449
+ // Just in case...
450
+ if (!is_array($options)) {
451
+ $options = array();
452
+ }
453
+
454
+ // This code filters the HTML to remove javascript and unsecure attributes and enable the
455
+ // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
456
+ add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
457
+ $options = wp_kses_post_deep($options);
458
+ remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
459
+
460
+ $block_options = get_option('newsletter_main');
461
+
462
+ $block = $this->get_block($block_id);
463
+
464
+ if (!isset($context['type']))
465
+ $context['type'] = '';
466
+
467
+ // Block not found
468
+ if (!$block) {
469
+ if ($wrapper) {
470
+ echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">';
471
+ echo '<tr>';
472
+ echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
473
+ }
474
+ echo $this->get_outlook_wrapper_open($width);
475
+
476
+ echo '<p>Ops, this block type is not avalable.</p>';
477
+
478
+ echo $this->get_outlook_wrapper_close();
479
+
480
+ if ($wrapper) {
481
+ echo '</td></tr></table>';
482
+ }
483
+ return;
484
+ }
485
+
486
+ $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
487
+
488
+ $dir = is_rtl() ? 'rtl' : 'ltr';
489
+ $align_left = is_rtl() ? 'right' : 'left';
490
+ $align_right = is_rtl() ? 'left' : 'right';
491
+
492
+ ob_start();
493
+ $logger = $this->logger;
494
+ include $block['dir'] . '/block.php';
495
+ $content = trim(ob_get_clean());
496
+
497
+ if (empty($content)) {
498
+ return $out;
499
+ }
500
+
501
+ $common_defaults = array(
502
+ 'block_padding_top' => 0,
503
+ 'block_padding_bottom' => 0,
504
+ 'block_padding_right' => 0,
505
+ 'block_padding_left' => 0,
506
+ 'block_background' => '',
507
+ 'block_background_2' => '',
508
+ 'block_width' => 600,
509
+ 'block_align' => 'center'
510
+ );
511
+
512
+ $options = array_merge($common_defaults, $options);
513
+
514
+ // Obsolete
515
+ $content = str_replace('{width}', $width, $content);
516
+
517
+ $content = $this->inline_css($content, true);
518
+
519
+ // CSS driven by the block
520
+ // Requited for the server side parsing and rendering
521
+ $options['block_id'] = $block_id;
522
+
523
+ $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
524
+ $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
525
+ $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
526
+ $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
527
+
528
+ $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
529
+
530
+ // Internal TD wrapper
531
+ $style = 'text-align: center; ';
532
+ $style .= 'width: 100% !important; ';
533
+ $style .= 'line-height: normal !important; ';
534
+ $style .= 'letter-spacing: normal; ';
535
+ $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
536
+ $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
537
+ $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
538
+ $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
539
+ $style .= 'background-color: ' . $block_background . ';';
540
+
541
+ if (isset($options['block_background_gradient'])) {
542
+ $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
543
+ }
544
+
545
+ $data = $this->options_encode($options);
546
+ // First time block creation wrapper
547
+ if ($wrapper) {
548
+ echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">', "\n";
549
+ echo "<tr>";
550
+ echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
551
+ }
552
+
553
+ // Container that fixes the width and makes the block responsive
554
+ echo $this->get_outlook_wrapper_open($options['block_width']);
555
+
556
+ echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $options['block_width'], 'px!important">', "\n";
557
+ echo "<tr>";
558
+ echo '<td align="', esc_attr($options['block_align']), '" style="', esc_attr($style), '" bgcolor="', esc_attr($block_background), '" width="100%">';
559
+
560
+ //echo "<!-- block generated content -->\n";
561
+ echo trim($content);
562
+ //echo "\n<!-- /block generated content -->\n";
563
+
564
+ echo "</td></tr></table>";
565
+ echo $this->get_outlook_wrapper_close();
566
+
567
+ // First time block creation wrapper
568
+ if ($wrapper) {
569
+ echo "</td></tr></table>";
570
+ }
571
+
572
+ return $out;
573
+ }
574
+
575
+ /**
576
+ * Ajax call to render a block with a new set of options after the settings popup
577
+ * has been saved.
578
+ *
579
+ * @param type $block_id
580
+ * @param type $wrapper
581
+ */
582
+ function tnpc_render_callback() {
583
+ if (!check_ajax_referer('save')) {
584
+ $this->dienow('Expired request');
585
+ }
586
+
587
+ $block_id = $_POST['id'];
588
+ $wrapper = isset($_POST['full']);
589
+ $options = $this->restore_options_from_request();
590
+
591
+ $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
592
+ wp_die();
593
+ }
594
+
595
+ function hook_wp_ajax_tnpc_regenerate_email() {
596
+
597
+ $content = stripslashes($_POST['content']);
598
+ $global_options = $_POST['composer'];
599
+
600
+ $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
601
+
602
+ wp_send_json_success([
603
+ 'content' => $regenerated_content,
604
+ 'message' => __('Successfully updated', 'newsletter')
605
+ ]);
606
+ }
607
+
608
+ private function regenerate_email_blocks($content, $global_options) {
609
+
610
+ $raw_block_options = $this->extract_encoded_blocks_options($content);
611
+
612
+ $regenerated_content = '';
613
+
614
+ foreach ($raw_block_options as $raw_block_option) {
615
+
616
+ /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
617
+ $block_options = $this->options_decode( $a ); */
618
+
619
+ $block_options = $this->options_decode($raw_block_option);
620
+
621
+ $block = $this->get_block($block_options['block_id']);
622
+ if (!$block) {
623
+ $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
624
+ }
625
+
626
+ ob_start();
627
+ $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
628
+ $block_html = ob_get_clean();
629
+
630
+ $regenerated_content .= $block_html;
631
+ }
632
+
633
+ return $regenerated_content;
634
+ }
635
+
636
+ /**
637
+ * @param string $html_email_content Email html content
638
+ *
639
+ * @return string[] Encoded options of email blocks
640
+ */
641
+ private function extract_encoded_blocks_options($html_email_content) {
642
+
643
+ preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
644
+
645
+ return $raw_block_options[1];
646
+ }
647
+
648
+ function tnpc_preview_callback() {
649
+ $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
650
+
651
+ if (empty($email)) {
652
+ echo 'Wrong email identifier';
653
+ return;
654
+ }
655
+
656
+ echo $email['message'];
657
+
658
+ wp_die();
659
+ }
660
+
661
+ function tnpc_css_callback() {
662
+ include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
663
+ wp_die();
664
+ }
665
+
666
+ /** Returns the correct admin page to edit the newsletter with the correct editor. */
667
+ function get_editor_url($email_id, $editor_type) {
668
+ switch ($editor_type) {
669
+ case NewsletterEmails::EDITOR_COMPOSER:
670
+ return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
671
+ case NewsletterEmails::EDITOR_HTML:
672
+ return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
673
+ case NewsletterEmails::EDITOR_TINYMCE:
674
+ return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
680
+ * or the targeting page (it depends on newsletter status).
681
+ *
682
+ * @param TNP_Email $email
683
+ */
684
+ function get_edit_button($email, $only_icon = false) {
685
+
686
+ $editor_type = $this->get_editor_type($email);
687
+ if ($email->status == 'new') {
688
+ $edit_url = $this->get_editor_url($email->id, $editor_type);
689
+ } else {
690
+ $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
691
+ }
692
+ switch ($editor_type) {
693
+ case NewsletterEmails::EDITOR_COMPOSER:
694
+ $icon_class = 'th-large';
695
+ break;
696
+ case NewsletterEmails::EDITOR_HTML:
697
+ $icon_class = 'code';
698
+ break;
699
+ default:
700
+ $icon_class = 'edit';
701
+ break;
702
+ }
703
+ if ($only_icon) {
704
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
705
+ '<i class="fas fa-' . $icon_class . '"></i></a>';
706
+ } else {
707
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
708
+ '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
709
+ }
710
+ }
711
+
712
+ /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
713
+ function get_editor_type($email) {
714
+ $email = (object) $email;
715
+ $editor_type = $email->editor;
716
+
717
+ // Backward compatibility
718
+ $email_options = maybe_unserialize($email->options);
719
+ if (isset($email_options['composer'])) {
720
+ $editor_type = NewsletterEmails::EDITOR_COMPOSER;
721
+ }
722
+ // End backward compatibility
723
+
724
+ return $editor_type;
725
+ }
726
+
727
+ /**
728
+ *
729
+ * @param type $action
730
+ * @param type $user
731
+ * @param type $email
732
+ * @return type
733
+ * @global wpdb $wpdb
734
+ */
735
+ function hook_newsletter_action($action, $user, $email) {
736
+ global $wpdb;
737
+
738
+ switch ($action) {
739
+ case 'v':
740
+ case 'view':
741
+ $id = $_GET['id'];
742
+ if ($id == 'last') {
743
+ $email = $wpdb->get_row("select * from " . NEWSLETTER_EMAILS_TABLE . " where private=0 and type='message' and status='sent' order by send_on desc limit 1");
744
+ } else {
745
+ $email = $this->get_email($_GET['id']);
746
+ }
747
+ if (empty($email)) {
748
+ header("HTTP/1.0 404 Not Found");
749
+ die('Email not found');
750
+ }
751
+
752
+ if (!Newsletter::instance()->is_allowed()) {
753
+
754
+ if ($email->status == 'new') {
755
+ header("HTTP/1.0 404 Not Found");
756
+ die('Not sent yet');
757
+ }
758
+
759
+ if ($email->private == 1) {
760
+ if (!$user) {
761
+ header("HTTP/1.0 404 Not Found");
762
+ die('No available for online view');
763
+ }
764
+ $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
765
+ if (!$sent) {
766
+ header("HTTP/1.0 404 Not Found");
767
+ die('No available for online view');
768
+ }
769
+ }
770
+ }
771
+
772
+
773
+ header('Content-Type: text/html;charset=UTF-8');
774
+ header('X-Robots-Tag: noindex,nofollow,noarchive');
775
+ header('Cache-Control: no-cache,no-store,private');
776
+
777
+ echo $this->replace($email->message, $user, $email);
778
+
779
+ die();
780
+ break;
781
+
782
+ case 'emails-css':
783
+ $email_id = (int) $_GET['id'];
784
+
785
+ $body = Newsletter::instance()->get_email_field($email_id, 'message');
786
+
787
+ $x = strpos($body, '<style');
788
+ if ($x === false)
789
+ return;
790
+
791
+ $x = strpos($body, '>', $x);
792
+ $y = strpos($body, '</style>');
793
+
794
+ header('Content-Type: text/css;charset=UTF-8');
795
+
796
+ echo substr($body, $x + 1, $y - $x - 1);
797
+
798
+ die();
799
+ break;
800
+
801
+ case 'emails-composer-css':
802
+ header('Cache: no-cache');
803
+ header('Content-Type: text/css');
804
+ echo $this->get_composer_css();
805
+ die();
806
+ break;
807
+
808
+ case 'emails-preview':
809
+ if (!Newsletter::instance()->is_allowed()) {
810
+ die('Not enough privileges');
811
+ }
812
+
813
+ if (!check_admin_referer('view')) {
814
+ die();
815
+ }
816
+
817
+ $theme_id = $_GET['id'];
818
+ $theme = $this->themes->get_theme($theme_id);
819
+
820
+ // Used by theme code
821
+ $theme_options = $this->themes->get_options($theme_id);
822
+
823
+ $theme_url = $theme['url'];
824
+
825
+ header('Content-Type: text/html;charset=UTF-8');
826
+
827
+ include $theme['dir'] . '/theme.php';
828
+
829
+ die();
830
+ break;
831
+
832
+ case 'emails-preview-text':
833
+ header('Content-Type: text/plain;charset=UTF-8');
834
+ if (!Newsletter::instance()->is_allowed()) {
835
+ die('Not enough privileges');
836
+ }
837
+
838
+ if (!check_admin_referer('view')) {
839
+ die();
840
+ }
841
+
842
+ // Used by theme code
843
+ $theme_options = $this->get_current_theme_options();
844
+
845
+ $file = include $theme['dir'] . '/theme-text.php';
846
+
847
+ if (is_file($file)) {
848
+ include $file;
849
+ }
850
+
851
+ die();
852
+ break;
853
+
854
+ case 'emails-create':
855
+ // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
856
+ // excerpt, thumbnail are extracted.
857
+ if (!Newsletter::instance()->is_allowed()) {
858
+ die('Not enough privileges');
859
+ }
860
+
861
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
862
+ $controls = new NewsletterControls();
863
+
864
+ if (!$controls->is_action('create')) {
865
+ die('Wrong call');
866
+ }
867
+
868
+ $theme_id = $controls->data['id'];
869
+ $theme = $this->themes->get_theme($theme_id);
870
+
871
+ if (!$theme) {
872
+ die('invalid theme');
873
+ }
874
+
875
+ $this->themes->save_options($theme_id, $controls->data);
876
+
877
+ $email = array();
878
+ $email['status'] = 'new';
879
+ $email['subject'] = ''; //__('Here the email subject', 'newsletter');
880
+ $email['track'] = Newsletter::instance()->options['track'];
881
+ $email['send_on'] = time();
882
+ $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
883
+ $email['type'] = 'message';
884
+
885
+ $theme_options = $this->themes->get_options($theme_id);
886
+
887
+ $theme_url = $theme['url'];
888
+ $theme_subject = '';
889
+
890
+ ob_start();
891
+ include $theme['dir'] . '/theme.php';
892
+ $email['message'] = ob_get_clean();
893
+
894
+ if (!empty($theme_subject)) {
895
+ $email['subject'] = $theme_subject;
896
+ }
897
+
898
+ if (file_exists($theme['dir'] . '/theme-text.php')) {
899
+ ob_start();
900
+ include $theme['dir'] . '/theme-text.php';
901
+ $email['message_text'] = ob_get_clean();
902
+ } else {
903
+ $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
904
+ }
905
+
906
+ $email = $this->save_email($email);
907
+
908
+ $edit_url = $this->get_editor_url($email->id, $email->editor);
909
+
910
+ header('Location: ' . $edit_url);
911
+
912
+ die();
913
+ break;
914
+ }
915
+ }
916
+
917
+ function admin_menu() {
918
+ $this->add_menu_page('index', 'Newsletters');
919
+ $this->add_admin_page('list', 'Email List');
920
+ $this->add_admin_page('new', 'Email New');
921
+ $this->add_admin_page('edit', 'Email Edit');
922
+ $this->add_admin_page('theme', 'Email Themes');
923
+ $this->add_admin_page('composer', 'The Composer');
924
+ $this->add_admin_page('editorhtml', 'HTML Editor');
925
+ $this->add_admin_page('editortinymce', 'TinyMCE Editor');
926
+ }
927
+
928
+ /**
929
+ * Builds a block data structure starting from the folder containing the block
930
+ * files.
931
+ *
932
+ * @param string $dir
933
+ * @return array | WP_Error
934
+ */
935
+ function build_block($dir) {
936
+ $dir = realpath($dir);
937
+ $dir = wp_normalize_path($dir);
938
+ $full_file = $dir . '/block.php';
939
+ if (!is_file($full_file)) {
940
+ return new WP_Error('1', 'Missing block.php file in ' . $dir);
941
+ }
942
+
943
+ $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
944
+ $file = basename($dir);
945
+
946
+ $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
947
+ $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
948
+ $data = array_merge($defaults, $data);
949
+
950
+ if (is_file($dir . '/icon.png')) {
951
+ $data['icon'] = content_url($relative_dir . '/icon.png');
952
+ }
953
+
954
+ $data['id'] = sanitize_key($file);
955
+
956
+ // Absolute path of the block files
957
+ $data['dir'] = $dir;
958
+ $data['url'] = content_url($relative_dir);
959
+
960
+ return $data;
961
+ }
962
+
963
+ /**
964
+ *
965
+ * @param type $dir
966
+ * @return type
967
+ */
968
+ function scan_blocks_dir($dir) {
969
+ $dir = realpath($dir);
970
+ if (!$dir) {
971
+ return [];
972
+ }
973
+ $dir = wp_normalize_path($dir);
974
+
975
+ $list = [];
976
+ $handle = opendir($dir);
977
+ while ($file = readdir($handle)) {
978
+
979
+ $data = $this->build_block($dir . '/' . $file);
980
+
981
+ if (is_wp_error($data)) {
982
+ $this->logger->error($data);
983
+ continue;
984
+ }
985
+ $list[$data['id']] = $data;
986
+ }
987
+ closedir($handle);
988
+ return $list;
989
+ }
990
+
991
+ /**
992
+ * Array of arrays with every registered block and legacy block converted to the new
993
+ * format.
994
+ *
995
+ * @return array
996
+ */
997
+ function get_blocks() {
998
+
999
+ if (!is_null($this->blocks)) {
1000
+ return $this->blocks;
1001
+ }
1002
+
1003
+ $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1004
+
1005
+ $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1006
+
1007
+ $this->blocks = array_merge($extended, $this->blocks);
1008
+
1009
+ $dirs = apply_filters('newsletter_blocks_dir', array());
1010
+
1011
+ //$this->logger->debug('Block dirs:');
1012
+ //$this->logger->debug($dirs);
1013
+
1014
+ foreach ($dirs as $dir) {
1015
+ $list = $this->scan_blocks_dir($dir);
1016
+ $this->blocks = array_merge($list, $this->blocks);
1017
+ }
1018
+
1019
+ do_action('newsletter_register_blocks');
1020
+
1021
+ foreach (TNP_Composer::$block_dirs as $dir) {
1022
+ $block = $this->build_block($dir);
1023
+ if (is_wp_error($block)) {
1024
+ $this->logger->error($block);
1025
+ continue;
1026
+ }
1027
+ if (!isset($this->blocks[$block['id']])) {
1028
+ $this->blocks[$block['id']] = $block;
1029
+ } else {
1030
+ $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1031
+ }
1032
+ }
1033
+
1034
+ $this->blocks = array_reverse($this->blocks);
1035
+ return $this->blocks;
1036
+ }
1037
+
1038
+ /**
1039
+ * Return a single block (associative array) checking for legacy ID as well.
1040
+ *
1041
+ * @param string $id
1042
+ * @return array
1043
+ */
1044
+ function get_block($id) {
1045
+ switch ($id) {
1046
+ case 'content-03-text.block':
1047
+ $id = 'text';
1048
+ break;
1049
+ case 'footer-03-social.block':
1050
+ $id = 'social';
1051
+ break;
1052
+ case 'footer-02-canspam.block':
1053
+ $id = 'canspam';
1054
+ break;
1055
+ case 'content-05-image.block':
1056
+ $id = 'image';
1057
+ break;
1058
+ case 'header-01-header.block':
1059
+ $id = 'header';
1060
+ break;
1061
+ case 'footer-01-footer.block':
1062
+ $id = 'footer';
1063
+ break;
1064
+ case 'content-02-heading.block':
1065
+ $id = 'heading';
1066
+ break;
1067
+ case 'content-07-twocols.block':
1068
+ case 'content-06-posts.block':
1069
+ $id = 'posts';
1070
+ break;
1071
+ case 'content-04-cta.block':
1072
+ $id = 'cta';
1073
+ break;
1074
+ case 'content-01-hero.block':
1075
+ $id = 'hero';
1076
+ break;
1077
+ // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1078
+ // break;
1079
+ }
1080
+
1081
+ // Conversion for old full path ID
1082
+ $id = sanitize_key(basename($id));
1083
+
1084
+ // TODO: Correct id for compatibility
1085
+ $blocks = $this->get_blocks();
1086
+ if (!isset($blocks[$id])) {
1087
+ return null;
1088
+ }
1089
+ return $blocks[$id];
1090
+ }
1091
+
1092
+ function scan_presets_dir($dir = null) {
1093
+
1094
+ if (is_null($dir)) {
1095
+ $dir = __DIR__ . '/presets';
1096
+ }
1097
+
1098
+ if (!is_dir($dir)) {
1099
+ return array();
1100
+ }
1101
+
1102
+ $handle = opendir($dir);
1103
+ $list = array();
1104
+ $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1105
+ while ($file = readdir($handle)) {
1106
+
1107
+ if ($file == '.' || $file == '..')
1108
+ continue;
1109
+
1110
+ // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1111
+ $preset_id = sanitize_key($file);
1112
+
1113
+ $full_file = $dir . '/' . $file . '/preset.json';
1114
+
1115
+ if (!is_file($full_file)) {
1116
+ continue;
1117
+ }
1118
+
1119
+ $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1120
+
1121
+ $list[$preset_id] = $icon;
1122
+ }
1123
+ closedir($handle);
1124
+ return $list;
1125
+ }
1126
+
1127
+ function get_preset_from_file($id, $dir = null) {
1128
+
1129
+ if (is_null($dir)) {
1130
+ $dir = __DIR__ . '/presets';
1131
+ }
1132
+
1133
+ $id = $this->sanitize_file_name($id);
1134
+
1135
+ if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1136
+ return array();
1137
+ }
1138
+
1139
+ $json_content = file_get_contents("$dir/$id/preset.json");
1140
+ $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1141
+ $json = json_decode($json_content);
1142
+ $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1143
+
1144
+ return $json;
1145
+ }
1146
+
1147
+ function get_composer_css() {
1148
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1149
+ $blocks = $this->get_blocks();
1150
+ foreach ($blocks as $block) {
1151
+ if (!file_exists($block['dir'] . '/style.css')) {
1152
+ continue;
1153
+ }
1154
+ $css .= "\n\n";
1155
+ $css .= "/* " . $block['name'] . " */\n";
1156
+ $css .= file_get_contents($block['dir'] . '/style.css');
1157
+ }
1158
+ return $css;
1159
+ }
1160
+
1161
+ function get_composer_backend_css() {
1162
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1163
+ $css .= "\n\n";
1164
+ $css .= $this->get_composer_css();
1165
+ return $css;
1166
+ }
1167
+
1168
+ /**
1169
+ * Send an email to the test subscribers.
1170
+ *
1171
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1172
+ * @param NewsletterControls $controls
1173
+ */
1174
+ function send_test_email($email, $controls) {
1175
+ if (!$email) {
1176
+ $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1177
+ return;
1178
+ }
1179
+
1180
+ $original_subject = $email->subject;
1181
+ $this->set_test_subject_to($email);
1182
+
1183
+ $users = NewsletterUsers::instance()->get_test_users();
1184
+ if (count($users) == 0) {
1185
+ $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1186
+ '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1187
+ __('Read more', 'newsletter') . '</strong></a>.';
1188
+ } else {
1189
+ $r = Newsletter::instance()->send($email, $users, true);
1190
+ $emails = array();
1191
+ foreach ($users as $user) {
1192
+ $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1193
+ }
1194
+ if (is_wp_error($r)) {
1195
+ $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1196
+ $controls->errors .= __('Test subscribers:', 'newsletter');
1197
+ $controls->errors .= ' ' . implode(', ', $emails);
1198
+ $controls->errors .= '<br>';
1199
+ $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1200
+ $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1201
+ } else {
1202
+ $controls->messages = __('Test subscribers:', 'newsletter');
1203
+
1204
+ $controls->messages .= ' ' . implode(', ', $emails);
1205
+ $controls->messages .= '.<br>';
1206
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1207
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1208
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1209
+ }
1210
+ }
1211
+ $email->subject = $original_subject;
1212
+ }
1213
+
1214
+ /**
1215
+ * Send an email to the test subscribers.
1216
+ *
1217
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1218
+ * @param string $email_address
1219
+ *
1220
+ * @throws Exception
1221
+ */
1222
+ function send_test_newsletter_to_email_address($email, $email_address) {
1223
+
1224
+ if (!$email) {
1225
+ throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1226
+ }
1227
+
1228
+ $this->set_test_subject_to($email);
1229
+
1230
+ $dummy_subscriber = $this->make_dummy_subscriber();
1231
+ $dummy_subscriber->email = $email_address;
1232
+
1233
+ $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1234
+
1235
+ $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1236
+
1237
+ if (is_wp_error($result)) {
1238
+ $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1239
+ $error_message .= __('Test subscribers:', 'newsletter');
1240
+ $error_message .= ' ' . $email;
1241
+ $error_message .= '<br>';
1242
+ $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1243
+ $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1244
+ throw new Exception($error_message);
1245
+ }
1246
+
1247
+ $messages = __('Test subscribers:', 'newsletter');
1248
+
1249
+ $messages .= ' ' . $email;
1250
+ $messages .= '.<br>';
1251
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1252
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1253
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1254
+
1255
+ return $messages;
1256
+ }
1257
+
1258
+ private function set_test_subject_to($email) {
1259
+ if ($email->subject == '') {
1260
+ $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1261
+ } else {
1262
+ $email->subject = $email->subject . ' (TEST)';
1263
+ }
1264
+ }
1265
+
1266
+ private function make_dummy_subscriber() {
1267
+ $dummy_user = new TNP_User();
1268
+ $dummy_user->id = 0;
1269
+ $dummy_user->email = 'john.doe@example.org';
1270
+ $dummy_user->name = 'John';
1271
+ $dummy_user->surname = 'Doe';
1272
+ $dummy_user->sex = 'n';
1273
+ $dummy_user->language = '';
1274
+ $dummy_user->ip = '';
1275
+
1276
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1277
+ $profile_key = "profile_$i";
1278
+ $dummy_user->$profile_key = '';
1279
+ }
1280
+
1281
+ return $dummy_user;
1282
+ }
1283
+
1284
+ function restore_options_from_request() {
1285
+
1286
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1287
+ $controls = new NewsletterControls();
1288
+ $options = $controls->data;
1289
+
1290
+ if (isset($_POST['options']) && is_array($_POST['options'])) {
1291
+ // Get all block options
1292
+ //$options = stripslashes_deep($_POST['options']);
1293
+ // Deserialize inline edits when
1294
+ // render is preformed on saving block options
1295
+ if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1296
+ $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1297
+ }
1298
+
1299
+ // Restore inline edits from data-json
1300
+ // coming from inline editing
1301
+ // and merge with current inline edit
1302
+ if (isset($_POST['encoded_options'])) {
1303
+ $decoded_options = $this->options_decode($_POST['encoded_options']);
1304
+
1305
+ $to_merge_inline_edits = [];
1306
+
1307
+ if (isset($decoded_options['inline_edits'])) {
1308
+ foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1309
+ $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1310
+ }
1311
+ }
1312
+
1313
+ //Overwrite with new edited content
1314
+ if (isset($options['inline_edits'])) {
1315
+ foreach ($options['inline_edits'] as $inline_edit) {
1316
+ $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1317
+ }
1318
+ }
1319
+
1320
+ $options['inline_edits'] = array_values($to_merge_inline_edits);
1321
+ $options = array_merge($decoded_options, $options);
1322
+ }
1323
+
1324
+ return $options;
1325
+ }
1326
+
1327
+ return array();
1328
+ }
1329
+
1330
+ public function hook_wp_ajax_tnpc_delete_preset() {
1331
+
1332
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1333
+ wp_send_json_error('Expired request');
1334
+ }
1335
+
1336
+ $preset_id = (int) $_REQUEST['presetId'];
1337
+
1338
+ $newsletter = Newsletter::instance();
1339
+
1340
+ if ($preset_id > 0) {
1341
+ $preset = $newsletter->get_email($preset_id);
1342
+
1343
+ if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1344
+ Newsletter::instance()->delete_email($preset_id);
1345
+ wp_send_json_success();
1346
+ } else {
1347
+ wp_send_json_error(__('Is not a preset!', 'newsletter'));
1348
+ }
1349
+ } else {
1350
+ wp_send_json_error();
1351
+ }
1352
+ }
1353
+
1354
+ }
1355
+
1356
+ NewsletterEmails::instance();
emails/tnp-composer/_scripts/newsletter-builder-v2.js CHANGED
@@ -1,953 +1,952 @@
1
- // add delete buttons
2
- jQuery.fn.add_delete = function () {
3
- this.append('<div class="tnpc-row-delete" title="Delete"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/delete.png" width="32"></div>');
4
- this.find('.tnpc-row-delete').perform_delete();
5
- };
6
-
7
- // delete row
8
- jQuery.fn.perform_delete = function () {
9
- this.click(function () {
10
- tnpc_hide_block_options();
11
- // remove block
12
- jQuery(this).parent().remove();
13
- tnpc_mobile_preview();
14
- });
15
- }
16
-
17
- // add edit button
18
- jQuery.fn.add_block_edit = function () {
19
- this.append('<div class="tnpc-row-edit-block" title="Edit"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/edit.png" width="32"></div>');
20
- this.find('.tnpc-row-edit-block').perform_block_edit();
21
- }
22
-
23
- // edit block
24
- jQuery.fn.perform_block_edit = function () {
25
-
26
- jQuery(".tnpc-row-edit-block").click(function (e) {
27
- e.preventDefault()
28
- });
29
-
30
- this.click(function (e) {
31
-
32
- e.preventDefault();
33
-
34
- target = jQuery(this).parent().find('.edit-block');
35
-
36
- // The row container which is a global variable and used later after the options save
37
- container = jQuery(this).closest("table");
38
-
39
- if (container.hasClass('tnpc-row-block')) {
40
-
41
- tnpc_show_block_options();
42
-
43
- var options = container.find(".tnpc-block-content").attr("data-json");
44
-
45
- // Compatibility
46
- if (!options) {
47
- options = target.attr("data-options");
48
- }
49
-
50
- var data = {
51
- action: "tnpc_options",
52
- id: container.data("id"),
53
- context_type: tnp_context_type,
54
- options: options
55
- };
56
-
57
- tnpc_add_global_options(data);
58
-
59
- builderAreaHelper.lock();
60
- jQuery("#tnpc-block-options-form").load(ajaxurl, data, function () {
61
- console.log('Block form options loaded');
62
- start_options = jQuery("#tnpc-block-options-form").serializeArray();
63
- tnpc_add_global_options(start_options);
64
- builderAreaHelper.unlock();
65
- });
66
-
67
- } else {
68
- alert("This is deprecated block version and cannot be edited. Please replace it with a new one.");
69
- }
70
-
71
- });
72
-
73
- };
74
-
75
- // add clone button
76
- jQuery.fn.add_block_clone = function () {
77
- this.append('<div class="tnpc-row-clone" title="Clone"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/copy.png" width="32"></div>');
78
- this.find('.tnpc-row-clone').perform_clone();
79
- }
80
-
81
- // clone block
82
- jQuery.fn.perform_clone = function () {
83
-
84
- jQuery(".tnpc-row-clone").click(function (e) {
85
- e.preventDefault()
86
- });
87
-
88
- this.click(function (e) {
89
-
90
- e.preventDefault();
91
-
92
- // hide block edit form
93
- tnpc_hide_block_options();
94
-
95
- // find the row
96
- let row = jQuery(this).closest('.tnpc-row');
97
-
98
- // clone the block
99
- let new_row = row.clone();
100
- new_row.find(".tnpc-row-delete").remove();
101
- new_row.find(".tnpc-row-edit-block").remove();
102
- new_row.find(".tnpc-row-clone").remove();
103
-
104
- new_row.add_delete();
105
- new_row.add_block_edit();
106
- new_row.add_block_clone();
107
- // if (new_row.hasClass('tnpc-row-block')) {
108
- // new_row.find(".tnpc-row-edit-block i").click();
109
- // }
110
- new_row.insertAfter(row);
111
- tnpc_mobile_preview();
112
- });
113
- };
114
-
115
- let start_options = null;
116
- let container = null;
117
-
118
- jQuery(function () {
119
-
120
- // open blocks tab
121
- document.getElementById("defaultOpen").click();
122
-
123
- // preload content from a body named input
124
- var preloadedContent = jQuery('input[name="message"]').val();
125
- if (!preloadedContent) {
126
- preloadedContent = jQuery('input[name="options[message]"]').val();
127
- }
128
-
129
- if (!preloadedContent) {
130
- tnpc_show_presets_modal();
131
- } else {
132
- jQuery('#newsletter-builder-area-center-frame-content').html(preloadedContent);
133
- start_composer();
134
- }
135
-
136
- // subject management
137
- jQuery('#options-subject').val(jQuery('#tnpc-form input[name="options[subject]"]').val());
138
-
139
- // preheader management
140
- jQuery('#options-preheader').val(jQuery('#tnpc-form input[name="options[options_preheader]"]').val());
141
-
142
- // ======================== //
143
- // == BACKGROUND COLOR == //
144
- // ======================== //
145
- _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
146
-
147
- function _setBuilderAreaBackgroundColor(color) {
148
- jQuery('#newsletter-builder-area-center-frame-content').css('background-color', color);
149
- }
150
-
151
- window._setBuilderAreaBackgroundColor = _setBuilderAreaBackgroundColor; //BAD STUFF!!!
152
-
153
- // ======================== //
154
- // == BACKGROUND COLOR == //
155
- // ======================== //
156
-
157
- });
158
-
159
- function BuilderAreaHelper() {
160
-
161
- var _builderAreaEl = document.querySelector('#newsletter-builder-area');
162
- var _overlayEl = document.createElement('div');
163
- _overlayEl.style.zIndex = 99999;
164
- _overlayEl.style.position = 'absolute';
165
- _overlayEl.style.top = 0;
166
- _overlayEl.style.left = 0;
167
- _overlayEl.style.width = '100%';
168
- _overlayEl.style.height = '100%';
169
-
170
- this.lock = function () {
171
- console.log('Lock builder area');
172
- _builderAreaEl.appendChild(_overlayEl);
173
- }
174
-
175
- this.unlock = function () {
176
- console.log('Unlock builder area');
177
- _builderAreaEl.removeChild(_overlayEl);
178
- }
179
-
180
- }
181
-
182
- let builderAreaHelper = new BuilderAreaHelper();
183
-
184
- function init_builder_area() {
185
-
186
- //Drag & Drop
187
- jQuery("#newsletter-builder-area-center-frame-content").sortable({
188
- revert: false,
189
- placeholder: "placeholder",
190
- forcePlaceholderSize: true,
191
- opacity: 0.6,
192
- tolerance: "pointer",
193
- helper: function (e) {
194
- var helper = jQuery(document.getElementById("sortable-helper")).clone();
195
- return helper;
196
- },
197
- update: function (event, ui) {
198
- if (ui.item.attr("id") == "draggable-helper") {
199
- loading_row = jQuery('<div style="text-align: center; padding: 20px; background-color: #d4d5d6; color: #52BE7F;"><i class="fa fa-cog fa-2x fa-spin" /></div>');
200
- ui.item.before(loading_row);
201
- ui.item.remove();
202
- var data = new Array(
203
- {"name": 'action', "value": 'tnpc_render'},
204
- {"name": 'id', "value": ui.item.data("id")},
205
- {"name": 'b', "value": ui.item.data("id")},
206
- {"name": 'full', "value": 1},
207
- {"name": '_wpnonce', "value": tnp_nonce}
208
- );
209
-
210
- tnpc_add_global_options(data);
211
-
212
- jQuery.post(ajaxurl, data, function (response) {
213
-
214
- var new_row = jQuery(response);
215
- // ui.item.before(new_row);
216
- // ui.item.remove();
217
- loading_row.before(new_row);
218
- loading_row.remove();
219
- new_row.add_delete();
220
- new_row.add_block_edit();
221
- new_row.add_block_clone();
222
- // new_row.find(".tnpc-row-edit").hover_edit();
223
- if (new_row.hasClass('tnpc-row-block')) {
224
- new_row.find(".tnpc-row-edit-block").click();
225
- }
226
- tnpc_mobile_preview();
227
- }).fail(function () {
228
- alert("Block rendering failed.");
229
- loading_row.remove();
230
- });
231
- } else {
232
- tnpc_mobile_preview();
233
- }
234
- }
235
- });
236
-
237
- jQuery(".newsletter-sidebar-buttons-content-tab").draggable({
238
- connectToSortable: "#newsletter-builder-area-center-frame-content",
239
-
240
- // Build the helper for dragging
241
- helper: function (e) {
242
- var helper = jQuery(document.getElementById("draggable-helper")).clone();
243
- // Do not uset .data() with jQuery
244
- helper.attr("data-id", e.currentTarget.dataset.id);
245
- helper.html(e.currentTarget.dataset.name);
246
- return helper;
247
- },
248
- revert: false,
249
- start: function () {
250
- if (jQuery('.tnpc-row').length) {
251
- } else {
252
- jQuery('#newsletter-builder-area-center-frame-content').append('<div class="tnpc-drop-here">Drag&Drop blocks here!</div>');
253
- }
254
- },
255
- stop: function (event, ui) {
256
- jQuery('.tnpc-drop-here').remove();
257
- }
258
- });
259
-
260
- jQuery(".tnpc-row").add_delete();
261
- jQuery(".tnpc-row").add_block_edit();
262
- jQuery(".tnpc-row").add_block_clone();
263
-
264
- }
265
-
266
- function start_composer() {
267
-
268
- init_builder_area();
269
-
270
- // Closes the block options layer (without saving)
271
- jQuery("#tnpc-block-options-cancel").click(function () {
272
-
273
- tnpc_hide_block_options();
274
-
275
- var _target = target;
276
-
277
- jQuery.post(ajaxurl, start_options, function (response) {
278
- _target.html(response);
279
- jQuery("#tnpc-block-options-form").html("");
280
- });
281
- });
282
-
283
- // Fires the save event for block options
284
- jQuery("#tnpc-block-options-save").click(function (e) {
285
- e.preventDefault();
286
-
287
- var _target = target;
288
-
289
- // fix for Codemirror
290
- if (typeof templateEditor !== 'undefined') {
291
- templateEditor.save();
292
- }
293
-
294
- if (window.tinymce)
295
- window.tinymce.triggerSave();
296
-
297
- var data = jQuery("#tnpc-block-options-form").serializeArray();
298
-
299
- tnpc_add_global_options(data);
300
-
301
- tnpc_hide_block_options();
302
-
303
- jQuery.post(ajaxurl, data, function (response) {
304
- _target.html(response);
305
- tnpc_mobile_preview();
306
- jQuery("#tnpc-block-options-form").html("");
307
- });
308
- });
309
-
310
- // live preview from block options *** EXPERIMENTAL ***
311
- jQuery('#tnpc-block-options-form').change(function (event) {
312
- var data = jQuery("#tnpc-block-options-form").serializeArray();
313
-
314
- var _container = container;
315
- var _target = target;
316
-
317
- tnpc_add_global_options(data);
318
-
319
- jQuery.post(ajaxurl, data, function (response) {
320
- _target.html(response);
321
- if (event.target.dataset.afterRendering === 'reload') {
322
- _container.find(".tnpc-row-edit-block").click();
323
- }
324
- }).fail(function () {
325
- alert("Block rendering failed");
326
- });
327
-
328
- });
329
-
330
- tnpc_mobile_preview();
331
-
332
- }
333
-
334
- function tnpc_show_block_options() {
335
-
336
- const animationDuration = 500;
337
-
338
- //jQuery("#tnpc-blocks").fadeOut(animationDuration);
339
- //jQuery("#tnpc-global-styles").fadeOut(animationDuration);
340
- //jQuery("#tnpc-mobile-tab").fadeOut(animationDuration);
341
- //jQuery("#tnpc-test-tab").fadeOut(animationDuration);
342
-
343
- jQuery("#tnpc-block-options").fadeIn(animationDuration);
344
- jQuery("#tnpc-block-options").css('display', 'flex');
345
-
346
- }
347
-
348
- function tnpc_hide_block_options() {
349
-
350
- const animationDuration = 500;
351
-
352
- jQuery("#tnpc-block-options").fadeOut(animationDuration);
353
-
354
- //var $activeTab = jQuery(".tnpc-tabs .tablinks.active");
355
- //jQuery('#' + $activeTab.data('tabId')).fadeIn(animationDuration);
356
-
357
- jQuery("#tnpc-block-options-form").html('');
358
-
359
- }
360
-
361
- function tnpc_mobile_preview() {
362
-
363
- return;
364
-
365
- }
366
-
367
- function tnpc_save(form) {
368
-
369
- form.elements["options[message]"].value = tnpc_get_email_content_from_builder_area();
370
-
371
- // When the composer is not showing the subject field (for example in Automated)
372
- if (document.getElementById("options-preheader")) {
373
- form.elements["options[options_preheader]"].value = jQuery('#options-preheader').val();
374
- } else {
375
- form.elements["options[options_preheader]"].value = "";
376
- }
377
- if (document.getElementById("options-subject")) {
378
- form.elements["options[subject]"].value = jQuery('#options-subject-subject').val();
379
- } else {
380
- form.elements["options[subject]"].value = "";
381
- }
382
-
383
- var global_form = document.getElementById("tnpc-global-styles-form");
384
- //Copy "Global styles" form inputs into main form
385
- tnpc_copy_form(global_form, form);
386
-
387
- }
388
-
389
- function tnpc_get_email_content_from_builder_area() {
390
-
391
- var $elMessage = jQuery("#newsletter-builder-area-center-frame-content").clone();
392
-
393
- $elMessage.find('.tnpc-row-delete').remove();
394
- $elMessage.find('.tnpc-row-edit-block').remove();
395
- $elMessage.find('.tnpc-row-clone').remove();
396
- $elMessage.find('.tnpc-row').removeClass('ui-draggable');
397
- $elMessage.find('#sortable-helper').remove();
398
-
399
- return $elMessage.html();
400
-
401
- }
402
-
403
- function tnpc_copy_form(source, dest) {
404
- for (var i = 0; i < source.elements.length; i++) {
405
- var field = document.createElement("input");
406
- field.type = "hidden";
407
- field.name = source.elements[i].name;
408
- field.value = source.elements[i].value;
409
-
410
- // Non clona le select!
411
- //var clonedEl = source.elements[i].cloneNode();
412
- //clonedEl.style.display = 'none';
413
- dest.appendChild(field);
414
- }
415
- }
416
-
417
- function tnpc_test() {
418
- let form = document.getElementById("tnpc-form");
419
- tnpc_save(form);
420
- form.act.value = "test";
421
- form.submit();
422
- }
423
-
424
- function openTab(evt, tabName) {
425
- evt.preventDefault();
426
- // Declare all variables
427
- var i, tabcontent, tablinks;
428
-
429
- // Get all elements with class="tabcontent" and hide them
430
- tabcontent = document.getElementsByClassName("tabcontent");
431
- for (i = 0; i < tabcontent.length; i++) {
432
- tabcontent[i].style.display = "none";
433
- }
434
-
435
- // Get all elements with class="tablinks" and remove the class "active"
436
- tablinks = document.getElementsByClassName("tablinks");
437
- for (i = 0; i < tablinks.length; i++) {
438
- tablinks[i].className = tablinks[i].className.replace(" active", "");
439
- }
440
-
441
- // Show the current tab, and add an "active" class to the button that opened the tab
442
- document.getElementById(tabName).style.display = "block";
443
- evt.currentTarget.className += " active";
444
- }
445
-
446
- function tnpc_scratch() {
447
-
448
- jQuery('#newsletter-builder-area-center-frame-content').html(" ");
449
- init_builder_area();
450
-
451
- }
452
-
453
- function tnpc_reload_options(e) {
454
- e.preventDefault();
455
- let options = jQuery("#tnpc-block-options-form").serializeArray();
456
- for (let i = 0; i < options.length; i++) {
457
- if (options[i].name === 'action') {
458
- options[i].value = 'tnpc_options';
459
- }
460
- }
461
-
462
- jQuery("#tnpc-block-options-form").load(ajaxurl, options);
463
- }
464
-
465
- function tnpc_add_global_options(data) {
466
- let globalOptions = jQuery("#tnpc-global-styles-form").serializeArray();
467
- for (let i = 0; i < globalOptions.length; i++) {
468
- globalOptions[i].name = globalOptions[i].name.replace("[options_", "[").replace("options[", "composer[").replace("composer_", "");
469
- if (Array.isArray(data)) {
470
- data.push(globalOptions[i]);
471
- } else {
472
- //Inline edit data format is object not array
473
- data[globalOptions[i].name] = globalOptions[i].value;
474
- }
475
- }
476
- }
477
-
478
- // ==================================================== //
479
- // ================= PRESET ===================== //
480
- // ==================================================== //
481
-
482
- //TODO non va bene tenere nel global space variabili che altri potrebbero accidentalmente modificare/usare
483
- // ma questo è un test
484
- const toastBottom = new TnpToast({duration: 5000, position: 'bottom right', wrapperPadding: '70px 20px'});
485
-
486
- //TODO - spostare gestione dei preset in contesto privato ma aggiungendo comunque a window le funzioni triggerate da html (load_preset, delete_preset,...) per mantenere compatibilità?
487
- const presetListModal = new TNPModal({
488
- closeWhenClickOutside: true,
489
- showClose: true,
490
- style: {
491
- backgroundColor: '#ECF0F1',
492
- height: '400px',
493
- width: '740px',
494
- },
495
- onClose: function () {
496
- start_composer();
497
- //Enable buttons
498
- jQuery('.tnpc-controls input[type=button]').attr('disabled', false);
499
- }
500
- });
501
-
502
- function tnpc_show_presets_modal() {
503
-
504
- jQuery('.tnpc-controls input[type=button]').attr('disabled', true);
505
-
506
- const elModalContent = presetListModal.open();
507
-
508
- jQuery.ajax({
509
- type: "POST",
510
- url: ajaxurl,
511
- data: {
512
- action: "tnpc_get_all_presets",
513
- context_type: tnp_context_type,
514
- },
515
- success: function (res) {
516
- jQuery(elModalContent).html(res.data);
517
- },
518
- });
519
-
520
- }
521
-
522
- function tnpc_load_preset(id, subject, isEditMode) {
523
-
524
- presetListModal.close();
525
-
526
- jQuery.ajax({
527
- type: "POST",
528
- url: ajaxurl,
529
- data: {
530
- action: "tnpc_get_preset",
531
- id: id
532
- },
533
- success: function (res) {
534
- jQuery('#newsletter-builder-area-center-frame-content').html(res.data.content);
535
- _restore_global_options(res.data.globalOptions);
536
-
537
- start_composer();
538
-
539
- if (!isEditMode) {
540
- //Enable buttons
541
- jQuery('.tnpc-controls input[type=button]').attr('disabled', false);
542
- }
543
-
544
- if (subject && subject.length > 0) {
545
- jQuery('#options-subject-subject').val(tnpc_remove_double_quotes_escape_from(subject));
546
- }
547
- },
548
- });
549
-
550
- function _restore_global_options(options) {
551
- jQuery.each(options, function (name, value) {
552
- var el = jQuery(`#tnpc-global-styles-form #options-options_composer_${name}`);
553
- if (el.length) {
554
- el.val(value);
555
- }
556
- });
557
-
558
- tnp_controls_init();
559
- _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
560
- }
561
-
562
- }
563
-
564
- function tnpc_save_preset(form) {
565
- const presetName = document.getElementById('options-subject-subject').value.replace('"', '');
566
-
567
- const presetNameModal = new TNPModal({
568
- title: 'Choose a preset name',
569
- content: '<input type="text" id="preset_name" style="width: 100%" placeholder="Preset name" value="' + presetName + '"/>',
570
- showConfirm: true,
571
- clickConfirmOnPressEnter: true,
572
- onConfirm: function () {
573
- const inputEl = document.querySelector('#preset_name');
574
- document.querySelector('#options-subject-subject').value = inputEl.value;
575
- tnpc_save(form);
576
- form.submit();
577
- }
578
- });
579
-
580
- presetNameModal.open();
581
-
582
- }
583
-
584
- function tnpc_delete_preset(presetId, name, event) {
585
- event.stopPropagation();
586
-
587
- const presetDeleteModal = new TNPModal({
588
- title: `Are you sure to delete "${name}" preset?`,
589
- confirmText: 'DELETE PRESET',
590
- confirmClassName: 'button-secondary button-danger',
591
- showConfirm: true,
592
- onConfirm: function () {
593
-
594
- const wrapperPresetEl = event.target.closest(".tnpc-preset");
595
-
596
- jQuery.ajax({
597
- type: 'POST',
598
- dataType: 'json',
599
- url: ajaxurl,
600
- data: {
601
- action: 'tnpc_delete_preset',
602
- _wpnonce: tnp_preset_nonce,
603
- presetId: presetId
604
- },
605
- success: function (response) {
606
- if (response.success) {
607
- wrapperPresetEl.parentNode.removeChild(wrapperPresetEl);
608
- toastBottom.success('Preset successfully deleted!');
609
- }
610
- }
611
- });
612
-
613
- }
614
- });
615
-
616
- presetDeleteModal.open();
617
-
618
- }
619
-
620
- function tnpc_edit_preset(presetId, name, event) {
621
- event.stopPropagation();
622
- tnpc_load_preset(presetId, name, true);
623
-
624
- const composerForm = document.querySelector('#tnpc-form');
625
-
626
- jQuery('#save-preset-button').hide();
627
- jQuery('#update-preset-button').show();
628
-
629
- //Add preset id hidden field
630
- const presetIdfield = document.createElement("input");
631
- presetIdfield.type = "hidden";
632
- presetIdfield.name = "preset_id";
633
- presetIdfield.value = presetId;
634
- composerForm.appendChild(presetIdfield);
635
-
636
- }
637
-
638
- function tnpc_remove_double_quotes_escape_from(str) {
639
- return str.replace(/\\"/g, '"');
640
- }
641
-
642
- function tnpc_remove_double_quotes_from(str) {
643
- return str.replace(/['"]+/g, '');
644
- }
645
-
646
- function tnpc_update_preset(form) {
647
- tnpc_save(form);
648
- form.submit();
649
- }
650
-
651
- // ========================================================= //
652
- // ================= PRESET FINE ===================== //
653
- // ========================================================= //
654
-
655
- jQuery(document).ready(function () {
656
- 'use strict'
657
-
658
- var TNPInlineEditor = (function () {
659
-
660
- var className = 'tnpc-inline-editable';
661
- var newInputName = 'new_name';
662
- var activeInlineElements = [];
663
-
664
- function init() {
665
- // find all inline editable elements
666
- jQuery('#newsletter-builder-area-center-frame-content').on('click', '.' + className, function (e) {
667
- e.preventDefault();
668
- removeAllActiveElements();
669
-
670
- var originalEl = jQuery(this).hide();
671
- var newEl = jQuery(getEditableComponent(this.innerText.trim(), this.dataset.id, this.dataset.type, originalEl)).insertAfter(this);
672
-
673
- activeInlineElements.push({'originalEl': originalEl, 'newEl': newEl});
674
-
675
- //Add submit event listener for newly created block
676
- jQuery('.tnpc-inline-editable-form-' + this.dataset.type + this.dataset.id).on('submit', function (e) {
677
- submit(e, newEl, jQuery(originalEl));
678
- });
679
-
680
- //Add close event listener for newly created block
681
- jQuery('.tnpc-inline-editable-form-actions .tnpc-dismiss-' + this.dataset.type + this.dataset.id).on('click', function (e) {
682
- removeAllActiveElements();
683
- });
684
-
685
- });
686
-
687
- // Close all created elements if clicked outside
688
- jQuery('#newsletter-builder-area-center-frame-content').on('click', function (e) {
689
- if (activeInlineElements.length > 0
690
- && !jQuery(e.target).hasClass(className)
691
- && jQuery(e.target).closest('.tnpc-inline-editable-container').length === 0) {
692
- removeAllActiveElements();
693
- }
694
- });
695
-
696
- }
697
-
698
- function removeAllActiveElements() {
699
- activeInlineElements.forEach(function (obj) {
700
- obj.originalEl.show();
701
-
702
- obj.newEl.off();
703
- obj.newEl.remove();
704
- });
705
-
706
- activeInlineElements = []
707
- }
708
-
709
- function getEditableComponent(value, id, type, originalEl) {
710
-
711
- var element = '';
712
-
713
- //COPY FONT STYLE FROM ORIGINAL ELEMENT
714
- var fontFamily = originalEl.css('font-family');
715
- var fontSize = originalEl.css('font-size');
716
- var styleAttr = "style='font-family:" + fontFamily + ";font-size:" + fontSize + ";'";
717
-
718
- switch (type) {
719
- case 'text':
720
- {
721
- element = "<textarea name='" + newInputName + "' class='" + className + "-textarea' rows='5' " + styleAttr + ">" + value + "</textarea>";
722
- break;
723
- }
724
- case 'title':
725
- {
726
- element = "<textarea name='" + newInputName + "' class='" + className + "-textarea' rows='2'" + styleAttr + ">" + value + "</textarea>";
727
- break;
728
- }
729
- }
730
-
731
- var component = "<td>";
732
- component += "<form class='tnpc-inline-editable-form tnpc-inline-editable-form-" + type + id + "'>";
733
- component += "<input type='hidden' name='id' value='" + id + "'>";
734
- component += "<input type='hidden' name='type' value='" + type + "'>";
735
- component += "<input type='hidden' name='old_value' value='" + value + "'>";
736
- component += "<div class='tnpc-inline-editable-container'>";
737
- component += element;
738
- component += "<div class='tnpc-inline-editable-form-actions'>";
739
- component += "<button type='submit'><span class='dashicons dashicons-yes-alt' title='save'></span></button>";
740
- component += "<span class='dashicons dashicons-dismiss tnpc-dismiss-" + type + id + "' title='close'></span>";
741
- component += "</div>";
742
- component += "</div>";
743
- component += "</form>";
744
- component += "</td>";
745
- return component;
746
- }
747
-
748
- function submit(e, elementToDeleteAfterSubmit, elementToShow) {
749
- e.preventDefault();
750
-
751
- var id = elementToDeleteAfterSubmit.find('form input[name=id]').val();
752
- var type = elementToDeleteAfterSubmit.find('form input[name=type]').val();
753
- var newValue = elementToDeleteAfterSubmit.find('form [name="' + newInputName + '"]').val();
754
-
755
- ajax_render_block(elementToShow, type, id, newValue);
756
-
757
- elementToDeleteAfterSubmit.remove();
758
- elementToShow.show();
759
-
760
- }
761
-
762
- function ajax_render_block(inlineElement, type, postId, newContent) {
763
-
764
- var target = inlineElement.closest('.edit-block');
765
- var container = target.closest('table');
766
- var blockContent = target.children('.tnpc-block-content');
767
-
768
- if (container.hasClass('tnpc-row-block')) {
769
- var data = {
770
- 'action': 'tnpc_render',
771
- 'id': container.data('id'),
772
- 'b': container.data('id'),
773
- 'full': 1,
774
- '_wpnonce': tnp_nonce,
775
- 'options': {
776
- 'inline_edits': [{
777
- 'type': type,
778
- 'post_id': postId,
779
- 'content': newContent
780
- }]
781
- },
782
- 'encoded_options': blockContent.data('json')
783
- };
784
-
785
- tnpc_add_global_options(data);
786
-
787
- jQuery.post(ajaxurl, data, function (response) {
788
- var new_row = jQuery(response);
789
-
790
- container.before(new_row);
791
- container.remove();
792
-
793
- new_row.add_delete();
794
- new_row.add_block_edit();
795
- new_row.add_block_clone();
796
-
797
- //Force reload options
798
- if (new_row.hasClass('tnpc-row-block')) {
799
- new_row.find(".tnpc-row-edit-block").click();
800
- }
801
-
802
- tnpc_mobile_preview();
803
-
804
- }).fail(function () {
805
- alert("Block rendering failed.");
806
- });
807
-
808
- }
809
-
810
- }
811
-
812
- return {init};
813
- })();
814
-
815
- TNPInlineEditor.init();
816
-
817
- });
818
-
819
- // =================================================== //
820
- // =============== GLOBAL STYLE ================== //
821
- // =================================================== //
822
-
823
- (function globalStyleIIFE() {
824
-
825
- var _elTrigger = document.querySelector('#tnpc-global-styles-form [name="apply"]');
826
-
827
- _elTrigger.addEventListener('click', function (e) {
828
- e.preventDefault();
829
-
830
- var data = {
831
- 'action': 'tnpc_regenerate_email',
832
- 'content': tnpc_get_email_content_from_builder_area(),
833
- '_wpnonce': tnp_nonce,
834
- };
835
-
836
- tnpc_add_global_options(data);
837
-
838
- jQuery.post(ajaxurl, data, function (response) {
839
- if (response && response.success) {
840
- jQuery('#newsletter-builder-area-center-frame-content').html(response.data.content);
841
- //Change background color of builder area
842
- _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
843
- init_builder_area();
844
- tnpc_mobile_preview();
845
-
846
- toastBottom.success(response.data.message);
847
- } else {
848
- toastBottom.error(response.data.message);
849
- }
850
- });
851
-
852
- });
853
-
854
- })();
855
-
856
- // ========================================================= //
857
- // ================= SEND A TEST ===================== //
858
- // ========================================================= //
859
-
860
- (function sendATestIIFE($) {
861
-
862
- var testNewsletterWithEmailFormId = '#test-newsletter-form';
863
- var testNewsletterWithEmailForm = document.querySelector(testNewsletterWithEmailFormId);
864
- testNewsletterWithEmailForm.addEventListener('submit', function (e) {
865
- e.preventDefault();
866
- var testEmail = testNewsletterWithEmailForm.querySelector('input[name="email"]').value;
867
-
868
- let form = document.getElementById("tnpc-form");
869
- tnpc_save(form);
870
-
871
- form.act.value = "send-test-to-email-address";
872
- var input = document.createElement("input");
873
- input.setAttribute("type", "hidden");
874
- input.setAttribute("name", "test_address_email");
875
- input.setAttribute("value", testEmail);
876
- form.appendChild(input);
877
-
878
- form.submit();
879
- });
880
-
881
- })(jQuery);
882
-
883
- // ================================================================== //
884
- // ================= SUBJECT LENGTH ICONS ===================== //
885
- // ================================================================== //
886
-
887
- (function subjectLengthIconsIIFE($) {
888
- var $subjectContainer = $('#tnpc-subject');
889
- var $subjectInput = $('#tnpc-subject input');
890
- var subjectCharCounterEl = null;
891
-
892
- $subjectInput.on('focusin', function (e) {
893
- $subjectContainer.find('img').fadeTo(400, 1);
894
- });
895
-
896
- $subjectInput.on('keyup', function (e) {
897
- setSubjectCharactersLenght(this.value.length);
898
- });
899
-
900
- $subjectInput.on('focusout', function (e) {
901
- $subjectContainer.find('img').fadeTo(300, 0);
902
- });
903
-
904
- function setSubjectCharactersLenght(length = 0) {
905
-
906
- if (length === 0 && subjectCharCounterEl !== null) {
907
- subjectCharCounterEl.remove();
908
- subjectCharCounterEl = null;
909
- return;
910
- }
911
-
912
- if (!subjectCharCounterEl) {
913
- subjectCharCounterEl = document.createElement("span");
914
- subjectCharCounterEl.style.position = 'absolute';
915
- subjectCharCounterEl.style.top = '-18px';
916
- subjectCharCounterEl.style.right = $subjectContainer[0].getBoundingClientRect().width - $subjectInput[0].getBoundingClientRect().width + 'px';
917
- subjectCharCounterEl.style.color = '#999';
918
- subjectCharCounterEl.style.fontSize = '0.8rem';
919
- $subjectContainer.find('div')[0].appendChild(subjectCharCounterEl);
920
- }
921
-
922
- const word = length === 1 ? 'character' : 'characters';
923
- subjectCharCounterEl.innerHTML = `${length} ${word}`;
924
- }
925
-
926
- })(jQuery);
927
-
928
- // ======================================================================= //
929
- // ================= COMPOSER MODE VIEW SWITCH ===================== //
930
- // ======================================================================= //
931
-
932
- (function composerModeViewIIFE($) {
933
- const activeClass = 'composer-view-mode__item--active';
934
- var status = 'desktop';
935
-
936
- $('.composer-view-mode__item[data-view-mode="' + status + '"]').addClass(activeClass);
937
-
938
- $('.composer-view-mode__item').on('click', function () {
939
- var $el = $(this);
940
-
941
- if ($el.data('viewMode') === 'desktop') {
942
- status = 'desktop';
943
- $('.composer-view-mode__item[data-view-mode="desktop"]').addClass(activeClass);
944
- $('.composer-view-mode__item[data-view-mode="mobile"]').removeClass(activeClass);
945
- } else if ($el.data('viewMode') === 'mobile') {
946
- status = 'mobile';
947
- $('.composer-view-mode__item[data-view-mode="desktop"]').removeClass(activeClass);
948
- $('.composer-view-mode__item[data-view-mode="mobile"]').addClass(activeClass);
949
- }
950
-
951
- tnp_view(status);
952
- });
953
- })(jQuery);
1
+ // add delete buttons
2
+ jQuery.fn.add_delete = function () {
3
+ this.append('<div class="tnpc-row-delete" title="Delete"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/delete.png" width="32"></div>');
4
+ this.find('.tnpc-row-delete').perform_delete();
5
+ };
6
+
7
+ // delete row
8
+ jQuery.fn.perform_delete = function () {
9
+ this.click(function () {
10
+ tnpc_hide_block_options();
11
+ // remove block
12
+ jQuery(this).parent().remove();
13
+ tnpc_mobile_preview();
14
+ });
15
+ }
16
+
17
+ // add edit button
18
+ jQuery.fn.add_block_edit = function () {
19
+ this.append('<div class="tnpc-row-edit-block" title="Edit"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/edit.png" width="32"></div>');
20
+ this.find('.tnpc-row-edit-block').perform_block_edit();
21
+ }
22
+
23
+ // edit block
24
+ jQuery.fn.perform_block_edit = function () {
25
+
26
+ jQuery(".tnpc-row-edit-block").click(function (e) {
27
+ e.preventDefault()
28
+ });
29
+
30
+ this.click(function (e) {
31
+
32
+ e.preventDefault();
33
+
34
+ target = jQuery(this).parent().find('.edit-block');
35
+
36
+ // The row container which is a global variable and used later after the options save
37
+ container = jQuery(this).closest("table");
38
+
39
+ if (container.hasClass('tnpc-row-block')) {
40
+
41
+ tnpc_show_block_options();
42
+
43
+ var options = container.find(".tnpc-block-content").attr("data-json");
44
+
45
+ // Compatibility
46
+ if (!options) {
47
+ options = target.attr("data-options");
48
+ }
49
+
50
+ var data = {
51
+ action: "tnpc_options",
52
+ id: container.data("id"),
53
+ context_type: tnp_context_type,
54
+ options: options
55
+ };
56
+
57
+ tnpc_add_global_options(data);
58
+
59
+ builderAreaHelper.lock();
60
+ jQuery("#tnpc-block-options-form").load(ajaxurl, data, function () {
61
+ console.log('Block form options loaded');
62
+ start_options = jQuery("#tnpc-block-options-form").serializeArray();
63
+ tnpc_add_global_options(start_options);
64
+ builderAreaHelper.unlock();
65
+ });
66
+
67
+ } else {
68
+ alert("This is deprecated block version and cannot be edited. Please replace it with a new one.");
69
+ }
70
+
71
+ });
72
+
73
+ };
74
+
75
+ // add clone button
76
+ jQuery.fn.add_block_clone = function () {
77
+ this.append('<div class="tnpc-row-clone" title="Clone"><img src="' + TNP_PLUGIN_URL + '/emails/tnp-composer/_assets/copy.png" width="32"></div>');
78
+ this.find('.tnpc-row-clone').perform_clone();
79
+ }
80
+
81
+ // clone block
82
+ jQuery.fn.perform_clone = function () {
83
+
84
+ jQuery(".tnpc-row-clone").click(function (e) {
85
+ e.preventDefault()
86
+ });
87
+
88
+ this.click(function (e) {
89
+
90
+ e.preventDefault();
91
+
92
+ // hide block edit form
93
+ tnpc_hide_block_options();
94
+
95
+ // find the row
96
+ let row = jQuery(this).closest('.tnpc-row');
97
+
98
+ // clone the block
99
+ let new_row = row.clone();
100
+ new_row.find(".tnpc-row-delete").remove();
101
+ new_row.find(".tnpc-row-edit-block").remove();
102
+ new_row.find(".tnpc-row-clone").remove();
103
+
104
+ new_row.add_delete();
105
+ new_row.add_block_edit();
106
+ new_row.add_block_clone();
107
+ // if (new_row.hasClass('tnpc-row-block')) {
108
+ // new_row.find(".tnpc-row-edit-block i").click();
109
+ // }
110
+ new_row.insertAfter(row);
111
+ tnpc_mobile_preview();
112
+ });
113
+ };
114
+
115
+ let start_options = null;
116
+ let container = null;
117
+
118
+ jQuery(function () {
119
+
120
+ // open blocks tab
121
+ document.getElementById("defaultOpen").click();
122
+
123
+ // preload content from a body named input
124
+ var preloadedContent = jQuery('input[name="message"]').val();
125
+ if (!preloadedContent) {
126
+ preloadedContent = jQuery('input[name="options[message]"]').val();
127
+ }
128
+
129
+ if (!preloadedContent) {
130
+ tnpc_show_presets_modal();
131
+ } else {
132
+ jQuery('#newsletter-builder-area-center-frame-content').html(preloadedContent);
133
+ start_composer();
134
+ }
135
+
136
+ // subject management
137
+ jQuery('#options-subject').val(jQuery('#tnpc-form input[name="options[subject]"]').val());
138
+
139
+ // preheader management
140
+ jQuery('#options-preheader').val(jQuery('#tnpc-form input[name="options[options_preheader]"]').val());
141
+
142
+ // ======================== //
143
+ // == BACKGROUND COLOR == //
144
+ // ======================== //
145
+ _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
146
+
147
+ function _setBuilderAreaBackgroundColor(color) {
148
+ jQuery('#newsletter-builder-area-center-frame-content').css('background-color', color);
149
+ }
150
+
151
+ window._setBuilderAreaBackgroundColor = _setBuilderAreaBackgroundColor; //BAD STUFF!!!
152
+
153
+ // ======================== //
154
+ // == BACKGROUND COLOR == //
155
+ // ======================== //
156
+
157
+ });
158
+
159
+ function BuilderAreaHelper() {
160
+
161
+ var _builderAreaEl = document.querySelector('#newsletter-builder-area');
162
+ var _overlayEl = document.createElement('div');
163
+ _overlayEl.style.zIndex = 99999;
164
+ _overlayEl.style.position = 'absolute';
165
+ _overlayEl.style.top = 0;
166
+ _overlayEl.style.left = 0;
167
+ _overlayEl.style.width = '100%';
168
+ _overlayEl.style.height = '100%';
169
+
170
+ this.lock = function () {
171
+ console.log('Lock builder area');
172
+ _builderAreaEl.appendChild(_overlayEl);
173
+ }
174
+
175
+ this.unlock = function () {
176
+ console.log('Unlock builder area');
177
+ _builderAreaEl.removeChild(_overlayEl);
178
+ }
179
+
180
+ }
181
+
182
+ let builderAreaHelper = new BuilderAreaHelper();
183
+
184
+ function init_builder_area() {
185
+
186
+ //Drag & Drop
187
+ jQuery("#newsletter-builder-area-center-frame-content").sortable({
188
+ revert: false,
189
+ placeholder: "placeholder",
190
+ forcePlaceholderSize: true,
191
+ opacity: 0.6,
192
+ tolerance: "pointer",
193
+ helper: function (e) {
194
+ var helper = jQuery(document.getElementById("sortable-helper")).clone();
195
+ return helper;
196
+ },
197
+ update: function (event, ui) {
198
+ if (ui.item.attr("id") == "draggable-helper") {
199
+ loading_row = jQuery('<div style="text-align: center; padding: 20px; background-color: #d4d5d6; color: #52BE7F;"><i class="fa fa-cog fa-2x fa-spin" /></div>');
200
+ ui.item.before(loading_row);
201
+ ui.item.remove();
202
+ var data = new Array(
203
+ {"name": 'action', "value": 'tnpc_render'},
204
+ {"name": 'id', "value": ui.item.data("id")},
205
+ {"name": 'b', "value": ui.item.data("id")},
206
+ {"name": 'full', "value": 1},
207
+ {"name": '_wpnonce', "value": tnp_nonce}
208
+ );
209
+
210
+ tnpc_add_global_options(data);
211
+
212
+ jQuery.post(ajaxurl, data, function (response) {
213
+
214
+ var new_row = jQuery(response);
215
+ // ui.item.before(new_row);
216
+ // ui.item.remove();
217
+ loading_row.before(new_row);
218
+ loading_row.remove();
219
+ new_row.add_delete();
220
+ new_row.add_block_edit();
221
+ new_row.add_block_clone();
222
+ // new_row.find(".tnpc-row-edit").hover_edit();
223
+ if (new_row.hasClass('tnpc-row-block')) {
224
+ new_row.find(".tnpc-row-edit-block").click();
225
+ }
226
+ tnpc_mobile_preview();
227
+ }).fail(function () {
228
+ alert("Block rendering failed.");
229
+ loading_row.remove();
230
+ });
231
+ } else {
232
+ tnpc_mobile_preview();
233
+ }
234
+ }
235
+ });
236
+
237
+ jQuery(".newsletter-sidebar-buttons-content-tab").draggable({
238
+ connectToSortable: "#newsletter-builder-area-center-frame-content",
239
+
240
+ // Build the helper for dragging
241
+ helper: function (e) {
242
+ var helper = jQuery(document.getElementById("draggable-helper")).clone();
243
+ // Do not uset .data() with jQuery
244
+ helper.attr("data-id", e.currentTarget.dataset.id);
245
+ helper.html(e.currentTarget.dataset.name);
246
+ return helper;
247
+ },
248
+ revert: false,
249
+ start: function () {
250
+ if (jQuery('.tnpc-row').length) {
251
+ } else {
252
+ jQuery('#newsletter-builder-area-center-frame-content').append('<div class="tnpc-drop-here">Drag&Drop blocks here!</div>');
253
+ }
254
+ },
255
+ stop: function (event, ui) {
256
+ jQuery('.tnpc-drop-here').remove();
257
+ }
258
+ });
259
+
260
+ jQuery(".tnpc-row").add_delete();
261
+ jQuery(".tnpc-row").add_block_edit();
262
+ jQuery(".tnpc-row").add_block_clone();
263
+
264
+ }
265
+
266
+ function start_composer() {
267
+
268
+ init_builder_area();
269
+
270
+ // Closes the block options layer (without saving)
271
+ jQuery("#tnpc-block-options-cancel").click(function () {
272
+
273
+ tnpc_hide_block_options();
274
+
275
+ var _target = target;
276
+
277
+ jQuery.post(ajaxurl, start_options, function (response) {
278
+ _target.html(response);
279
+ jQuery("#tnpc-block-options-form").html("");
280
+ });
281
+ });
282
+
283
+ // Fires the save event for block options
284
+ jQuery("#tnpc-block-options-save").click(function (e) {
285
+ e.preventDefault();
286
+
287
+ var _target = target;
288
+
289
+ // fix for Codemirror
290
+ if (typeof templateEditor !== 'undefined') {
291
+ templateEditor.save();
292
+ }
293
+
294
+ if (window.tinymce)
295
+ window.tinymce.triggerSave();
296
+
297
+ var data = jQuery("#tnpc-block-options-form").serializeArray();
298
+
299
+ tnpc_add_global_options(data);
300
+
301
+ tnpc_hide_block_options();
302
+
303
+ jQuery.post(ajaxurl, data, function (response) {
304
+ _target.html(response);
305
+ tnpc_mobile_preview();
306
+ jQuery("#tnpc-block-options-form").html("");
307
+ });
308
+ });
309
+
310
+ jQuery('#tnpc-block-options-form').change(function (event) {
311
+ var data = jQuery("#tnpc-block-options-form").serializeArray();
312
+
313
+ var _container = container;
314
+ var _target = target;
315
+
316
+ tnpc_add_global_options(data);
317
+
318
+ jQuery.post(ajaxurl, data, function (response) {
319
+ _target.html(response);
320
+ if (event.target.dataset.afterRendering === 'reload') {
321
+ _container.find(".tnpc-row-edit-block").click();
322
+ }
323
+ }).fail(function () {
324
+ alert("Block rendering failed");
325
+ });
326
+
327
+ });
328
+
329
+ tnpc_mobile_preview();
330
+
331
+ }
332
+
333
+ function tnpc_show_block_options() {
334
+
335
+ const animationDuration = 500;
336
+
337
+ //jQuery("#tnpc-blocks").fadeOut(animationDuration);
338
+ //jQuery("#tnpc-global-styles").fadeOut(animationDuration);
339
+ //jQuery("#tnpc-mobile-tab").fadeOut(animationDuration);
340
+ //jQuery("#tnpc-test-tab").fadeOut(animationDuration);
341
+
342
+ jQuery("#tnpc-block-options").fadeIn(animationDuration);
343
+ jQuery("#tnpc-block-options").css('display', 'flex');
344
+
345
+ }
346
+
347
+ function tnpc_hide_block_options() {
348
+
349
+ const animationDuration = 500;
350
+
351
+ jQuery("#tnpc-block-options").fadeOut(animationDuration);
352
+
353
+ //var $activeTab = jQuery(".tnpc-tabs .tablinks.active");
354
+ //jQuery('#' + $activeTab.data('tabId')).fadeIn(animationDuration);
355
+
356
+ jQuery("#tnpc-block-options-form").html('');
357
+
358
+ }
359
+
360
+ function tnpc_mobile_preview() {
361
+
362
+ return;
363
+
364
+ }
365
+
366
+ function tnpc_save(form) {
367
+
368
+ form.elements["options[message]"].value = tnpc_get_email_content_from_builder_area();
369
+
370
+ // When the composer is not showing the subject field (for example in Automated)
371
+ if (document.getElementById("options-preheader")) {
372
+ form.elements["options[options_preheader]"].value = jQuery('#options-preheader').val();
373
+ } else {
374
+ form.elements["options[options_preheader]"].value = "";
375
+ }
376
+ if (document.getElementById("options-subject")) {
377
+ form.elements["options[subject]"].value = jQuery('#options-subject-subject').val();
378
+ } else {
379
+ form.elements["options[subject]"].value = "";
380
+ }
381
+
382
+ var global_form = document.getElementById("tnpc-global-styles-form");
383
+ //Copy "Global styles" form inputs into main form
384
+ tnpc_copy_form(global_form, form);
385
+
386
+ }
387
+
388
+ function tnpc_get_email_content_from_builder_area() {
389
+
390
+ var $elMessage = jQuery("#newsletter-builder-area-center-frame-content").clone();
391
+
392
+ $elMessage.find('.tnpc-row-delete').remove();
393
+ $elMessage.find('.tnpc-row-edit-block').remove();
394
+ $elMessage.find('.tnpc-row-clone').remove();
395
+ $elMessage.find('.tnpc-row').removeClass('ui-draggable');
396
+ $elMessage.find('#sortable-helper').remove();
397
+
398
+ return $elMessage.html();
399
+
400
+ }
401
+
402
+ function tnpc_copy_form(source, dest) {
403
+ for (var i = 0; i < source.elements.length; i++) {
404
+ var field = document.createElement("input");
405
+ field.type = "hidden";
406
+ field.name = source.elements[i].name;
407
+ field.value = source.elements[i].value;
408
+
409
+ // Non clona le select!
410
+ //var clonedEl = source.elements[i].cloneNode();
411
+ //clonedEl.style.display = 'none';
412
+ dest.appendChild(field);
413
+ }
414
+ }
415
+
416
+ function tnpc_test() {
417
+ let form = document.getElementById("tnpc-form");
418
+ tnpc_save(form);
419
+ form.act.value = "test";
420
+ form.submit();
421
+ }
422
+
423
+ function openTab(evt, tabName) {
424
+ evt.preventDefault();
425
+ // Declare all variables
426
+ var i, tabcontent, tablinks;
427
+
428
+ // Get all elements with class="tabcontent" and hide them
429
+ tabcontent = document.getElementsByClassName("tabcontent");
430
+ for (i = 0; i < tabcontent.length; i++) {
431
+ tabcontent[i].style.display = "none";
432
+ }
433
+
434
+ // Get all elements with class="tablinks" and remove the class "active"
435
+ tablinks = document.getElementsByClassName("tablinks");
436
+ for (i = 0; i < tablinks.length; i++) {
437
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
438
+ }
439
+
440
+ // Show the current tab, and add an "active" class to the button that opened the tab
441
+ document.getElementById(tabName).style.display = "block";
442
+ evt.currentTarget.className += " active";
443
+ }
444
+
445
+ function tnpc_scratch() {
446
+
447
+ jQuery('#newsletter-builder-area-center-frame-content').html(" ");
448
+ init_builder_area();
449
+
450
+ }
451
+
452
+ function tnpc_reload_options(e) {
453
+ e.preventDefault();
454
+ let options = jQuery("#tnpc-block-options-form").serializeArray();
455
+ for (let i = 0; i < options.length; i++) {
456
+ if (options[i].name === 'action') {
457
+ options[i].value = 'tnpc_options';
458
+ }
459
+ }
460
+
461
+ jQuery("#tnpc-block-options-form").load(ajaxurl, options);
462
+ }
463
+
464
+ function tnpc_add_global_options(data) {
465
+ let globalOptions = jQuery("#tnpc-global-styles-form").serializeArray();
466
+ for (let i = 0; i < globalOptions.length; i++) {
467
+ globalOptions[i].name = globalOptions[i].name.replace("[options_", "[").replace("options[", "composer[").replace("composer_", "");
468
+ if (Array.isArray(data)) {
469
+ data.push(globalOptions[i]);
470
+ } else {
471
+ //Inline edit data format is object not array
472
+ data[globalOptions[i].name] = globalOptions[i].value;
473
+ }
474
+ }
475
+ }
476
+
477
+ // ==================================================== //
478
+ // ================= PRESET ===================== //
479
+ // ==================================================== //
480
+
481
+ //TODO non va bene tenere nel global space variabili che altri potrebbero accidentalmente modificare/usare
482
+ // ma questo è un test
483
+ const toastBottom = new TnpToast({duration: 5000, position: 'bottom right', wrapperPadding: '70px 20px'});
484
+
485
+ //TODO - spostare gestione dei preset in contesto privato ma aggiungendo comunque a window le funzioni triggerate da html (load_preset, delete_preset,...) per mantenere compatibilità?
486
+ const presetListModal = new TNPModal({
487
+ closeWhenClickOutside: true,
488
+ showClose: true,
489
+ style: {
490
+ backgroundColor: '#ECF0F1',
491
+ height: '400px',
492
+ width: '740px',
493
+ },
494
+ onClose: function () {
495
+ start_composer();
496
+ //Enable buttons
497
+ jQuery('.tnpc-controls input[type=button]').attr('disabled', false);
498
+ }
499
+ });
500
+
501
+ function tnpc_show_presets_modal() {
502
+
503
+ jQuery('.tnpc-controls input[type=button]').attr('disabled', true);
504
+
505
+ const elModalContent = presetListModal.open();
506
+
507
+ jQuery.ajax({
508
+ type: "POST",
509
+ url: ajaxurl,
510
+ data: {
511
+ action: "tnpc_get_all_presets",
512
+ context_type: tnp_context_type,
513
+ },
514
+ success: function (res) {
515
+ jQuery(elModalContent).html(res.data);
516
+ },
517
+ });
518
+
519
+ }
520
+
521
+ function tnpc_load_preset(id, subject, isEditMode) {
522
+
523
+ presetListModal.close();
524
+
525
+ jQuery.ajax({
526
+ type: "POST",
527
+ url: ajaxurl,
528
+ data: {
529
+ action: "tnpc_get_preset",
530
+ id: id
531
+ },
532
+ success: function (res) {
533
+ jQuery('#newsletter-builder-area-center-frame-content').html(res.data.content);
534
+ _restore_global_options(res.data.globalOptions);
535
+
536
+ start_composer();
537
+
538
+ if (!isEditMode) {
539
+ //Enable buttons
540
+ jQuery('.tnpc-controls input[type=button]').attr('disabled', false);
541
+ }
542
+
543
+ if (subject && subject.length > 0) {
544
+ jQuery('#options-subject-subject').val(tnpc_remove_double_quotes_escape_from(subject));
545
+ }
546
+ },
547
+ });
548
+
549
+ function _restore_global_options(options) {
550
+ jQuery.each(options, function (name, value) {
551
+ var el = jQuery(`#tnpc-global-styles-form #options-options_composer_${name}`);
552
+ if (el.length) {
553
+ el.val(value);
554
+ }
555
+ });
556
+
557
+ tnp_controls_init();
558
+ _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
559
+ }
560
+
561
+ }
562
+
563
+ function tnpc_save_preset(form) {
564
+ const presetName = document.getElementById('options-subject-subject').value.replace('"', '');
565
+
566
+ const presetNameModal = new TNPModal({
567
+ title: 'Choose a preset name',
568
+ content: '<input type="text" id="preset_name" style="width: 100%" placeholder="Preset name" value="' + presetName + '"/>',
569
+ showConfirm: true,
570
+ clickConfirmOnPressEnter: true,
571
+ onConfirm: function () {
572
+ const inputEl = document.querySelector('#preset_name');
573
+ document.querySelector('#options-subject-subject').value = inputEl.value;
574
+ tnpc_save(form);
575
+ form.submit();
576
+ }
577
+ });
578
+
579
+ presetNameModal.open();
580
+
581
+ }
582
+
583
+ function tnpc_delete_preset(presetId, name, event) {
584
+ event.stopPropagation();
585
+
586
+ const presetDeleteModal = new TNPModal({
587
+ title: `Are you sure to delete "${name}" preset?`,
588
+ confirmText: 'DELETE PRESET',
589
+ confirmClassName: 'button-secondary button-danger',
590
+ showConfirm: true,
591
+ onConfirm: function () {
592
+
593
+ const wrapperPresetEl = event.target.closest(".tnpc-preset");
594
+
595
+ jQuery.ajax({
596
+ type: 'POST',
597
+ dataType: 'json',
598
+ url: ajaxurl,
599
+ data: {
600
+ action: 'tnpc_delete_preset',
601
+ _wpnonce: tnp_preset_nonce,
602
+ presetId: presetId
603
+ },
604
+ success: function (response) {
605
+ if (response.success) {
606
+ wrapperPresetEl.parentNode.removeChild(wrapperPresetEl);
607
+ toastBottom.success('Preset successfully deleted!');
608
+ }
609
+ }
610
+ });
611
+
612
+ }
613
+ });
614
+
615
+ presetDeleteModal.open();
616
+
617
+ }
618
+
619
+ function tnpc_edit_preset(presetId, name, event) {
620
+ event.stopPropagation();
621
+ tnpc_load_preset(presetId, name, true);
622
+
623
+ const composerForm = document.querySelector('#tnpc-form');
624
+
625
+ jQuery('#save-preset-button').hide();
626
+ jQuery('#update-preset-button').show();
627
+
628
+ //Add preset id hidden field
629
+ const presetIdfield = document.createElement("input");
630
+ presetIdfield.type = "hidden";
631
+ presetIdfield.name = "preset_id";
632
+ presetIdfield.value = presetId;
633
+ composerForm.appendChild(presetIdfield);
634
+
635
+ }
636
+
637
+ function tnpc_remove_double_quotes_escape_from(str) {
638
+ return str.replace(/\\"/g, '"');
639
+ }
640
+
641
+ function tnpc_remove_double_quotes_from(str) {
642
+ return str.replace(/['"]+/g, '');
643
+ }
644
+
645
+ function tnpc_update_preset(form) {
646
+ tnpc_save(form);
647
+ form.submit();
648
+ }
649
+
650
+ // ========================================================= //
651
+ // ================= PRESET FINE ===================== //
652
+ // ========================================================= //
653
+
654
+ jQuery(document).ready(function () {
655
+ 'use strict'
656
+
657
+ var TNPInlineEditor = (function () {
658
+
659
+ var className = 'tnpc-inline-editable';
660
+ var newInputName = 'new_name';
661
+ var activeInlineElements = [];
662
+
663
+ function init() {
664
+ // find all inline editable elements
665
+ jQuery('#newsletter-builder-area-center-frame-content').on('click', '.' + className, function (e) {
666
+ e.preventDefault();
667
+ removeAllActiveElements();
668
+
669
+ var originalEl = jQuery(this).hide();
670
+ var newEl = jQuery(getEditableComponent(this.innerText.trim(), this.dataset.id, this.dataset.type, originalEl)).insertAfter(this);
671
+
672
+ activeInlineElements.push({'originalEl': originalEl, 'newEl': newEl});
673
+
674
+ //Add submit event listener for newly created block
675
+ jQuery('.tnpc-inline-editable-form-' + this.dataset.type + this.dataset.id).on('submit', function (e) {
676
+ submit(e, newEl, jQuery(originalEl));
677
+ });
678
+
679
+ //Add close event listener for newly created block
680
+ jQuery('.tnpc-inline-editable-form-actions .tnpc-dismiss-' + this.dataset.type + this.dataset.id).on('click', function (e) {
681
+ removeAllActiveElements();
682
+ });
683
+
684
+ });
685
+
686
+ // Close all created elements if clicked outside
687
+ jQuery('#newsletter-builder-area-center-frame-content').on('click', function (e) {
688
+ if (activeInlineElements.length > 0
689
+ && !jQuery(e.target).hasClass(className)
690
+ && jQuery(e.target).closest('.tnpc-inline-editable-container').length === 0) {
691
+ removeAllActiveElements();
692
+ }
693
+ });
694
+
695
+ }
696
+
697
+ function removeAllActiveElements() {
698
+ activeInlineElements.forEach(function (obj) {
699
+ obj.originalEl.show();
700
+
701
+ obj.newEl.off();
702
+ obj.newEl.remove();
703
+ });
704
+
705
+ activeInlineElements = []
706
+ }
707
+
708
+ function getEditableComponent(value, id, type, originalEl) {
709
+
710
+ var element = '';
711
+
712
+ //COPY FONT STYLE FROM ORIGINAL ELEMENT
713
+ var fontFamily = originalEl.css('font-family');
714
+ var fontSize = originalEl.css('font-size');
715
+ var styleAttr = "style='font-family:" + fontFamily + ";font-size:" + fontSize + ";'";
716
+
717
+ switch (type) {
718
+ case 'text':
719
+ {
720
+ element = "<textarea name='" + newInputName + "' class='" + className + "-textarea' rows='5' " + styleAttr + ">" + value + "</textarea>";
721
+ break;
722
+ }
723
+ case 'title':
724
+ {
725
+ element = "<textarea name='" + newInputName + "' class='" + className + "-textarea' rows='2'" + styleAttr + ">" + value + "</textarea>";
726
+ break;
727
+ }
728
+ }
729
+
730
+ var component = "<td>";
731
+ component += "<form class='tnpc-inline-editable-form tnpc-inline-editable-form-" + type + id + "'>";
732
+ component += "<input type='hidden' name='id' value='" + id + "'>";
733
+ component += "<input type='hidden' name='type' value='" + type + "'>";
734
+ component += "<input type='hidden' name='old_value' value='" + value + "'>";
735
+ component += "<div class='tnpc-inline-editable-container'>";
736
+ component += element;
737
+ component += "<div class='tnpc-inline-editable-form-actions'>";
738
+ component += "<button type='submit'><span class='dashicons dashicons-yes-alt' title='save'></span></button>";
739
+ component += "<span class='dashicons dashicons-dismiss tnpc-dismiss-" + type + id + "' title='close'></span>";
740
+ component += "</div>";
741
+ component += "</div>";
742
+ component += "</form>";
743
+ component += "</td>";
744
+ return component;
745
+ }
746
+
747
+ function submit(e, elementToDeleteAfterSubmit, elementToShow) {
748
+ e.preventDefault();
749
+
750
+ var id = elementToDeleteAfterSubmit.find('form input[name=id]').val();
751
+ var type = elementToDeleteAfterSubmit.find('form input[name=type]').val();
752
+ var newValue = elementToDeleteAfterSubmit.find('form [name="' + newInputName + '"]').val();
753
+
754
+ ajax_render_block(elementToShow, type, id, newValue);
755
+
756
+ elementToDeleteAfterSubmit.remove();
757
+ elementToShow.show();
758
+
759
+ }
760
+
761
+ function ajax_render_block(inlineElement, type, postId, newContent) {
762
+
763
+ var target = inlineElement.closest('.edit-block');
764
+ var container = target.closest('table');
765
+ var blockContent = target.children('.tnpc-block-content');
766
+
767
+ if (container.hasClass('tnpc-row-block')) {
768
+ var data = {
769
+ 'action': 'tnpc_render',
770
+ 'id': container.data('id'),
771
+ 'b': container.data('id'),
772
+ 'full': 1,
773
+ '_wpnonce': tnp_nonce,
774
+ 'options': {
775
+ 'inline_edits': [{
776
+ 'type': type,
777
+ 'post_id': postId,
778
+ 'content': newContent
779
+ }]
780
+ },
781
+ 'encoded_options': blockContent.data('json')
782
+ };
783
+
784
+ tnpc_add_global_options(data);
785
+
786
+ jQuery.post(ajaxurl, data, function (response) {
787
+ var new_row = jQuery(response);
788
+
789
+ container.before(new_row);
790
+ container.remove();
791
+
792
+ new_row.add_delete();
793
+ new_row.add_block_edit();
794
+ new_row.add_block_clone();
795
+
796
+ //Force reload options
797
+ if (new_row.hasClass('tnpc-row-block')) {
798
+ new_row.find(".tnpc-row-edit-block").click();
799
+ }
800
+
801
+ tnpc_mobile_preview();
802
+
803
+ }).fail(function () {
804
+ alert("Block rendering failed.");
805
+ });
806
+
807
+ }
808
+
809
+ }
810
+
811
+ return {init};
812
+ })();
813
+
814
+ TNPInlineEditor.init();
815
+
816
+ });
817
+
818
+ // =================================================== //
819
+ // =============== GLOBAL STYLE ================== //
820
+ // =================================================== //
821
+
822
+ (function globalStyleIIFE() {
823
+
824
+ var _elTrigger = document.querySelector('#tnpc-global-styles-form [name="apply"]');
825
+
826
+ _elTrigger.addEventListener('click', function (e) {
827
+ e.preventDefault();
828
+
829
+ var data = {
830
+ 'action': 'tnpc_regenerate_email',
831
+ 'content': tnpc_get_email_content_from_builder_area(),
832
+ '_wpnonce': tnp_nonce,
833
+ };
834
+
835
+ tnpc_add_global_options(data);
836
+
837
+ jQuery.post(ajaxurl, data, function (response) {
838
+ if (response && response.success) {
839
+ jQuery('#newsletter-builder-area-center-frame-content').html(response.data.content);
840
+ //Change background color of builder area
841
+ _setBuilderAreaBackgroundColor(document.getElementById('options-options_composer_background').value);
842
+ init_builder_area();
843
+ tnpc_mobile_preview();
844
+
845
+ toastBottom.success(response.data.message);
846
+ } else {
847
+ toastBottom.error(response.data.message);
848
+ }
849
+ });
850
+
851
+ });
852
+
853
+ })();
854
+
855
+ // ========================================================= //
856
+ // ================= SEND A TEST ===================== //
857
+ // ========================================================= //
858
+
859
+ (function sendATestIIFE($) {
860
+
861
+ var testNewsletterWithEmailFormId = '#test-newsletter-form';
862
+ var testNewsletterWithEmailForm = document.querySelector(testNewsletterWithEmailFormId);
863
+ testNewsletterWithEmailForm.addEventListener('submit', function (e) {
864
+ e.preventDefault();
865
+ var testEmail = testNewsletterWithEmailForm.querySelector('input[name="email"]').value;
866
+
867
+ let form = document.getElementById("tnpc-form");
868
+ tnpc_save(form);
869
+
870
+ form.act.value = "send-test-to-email-address";
871
+ var input = document.createElement("input");
872
+ input.setAttribute("type", "hidden");
873
+ input.setAttribute("name", "test_address_email");
874
+ input.setAttribute("value", testEmail);
875
+ form.appendChild(input);
876
+
877
+ form.submit();
878
+ });
879
+
880
+ })(jQuery);
881
+
882
+ // ================================================================== //
883
+ // ================= SUBJECT LENGTH ICONS ===================== //
884
+ // ================================================================== //
885
+
886
+ (function subjectLengthIconsIIFE($) {
887
+ var $subjectContainer = $('#tnpc-subject');
888
+ var $subjectInput = $('#tnpc-subject input');
889
+ var subjectCharCounterEl = null;
890
+
891
+ $subjectInput.on('focusin', function (e) {
892
+ $subjectContainer.find('img').fadeTo(400, 1);
893
+ });
894
+
895
+ $subjectInput.on('keyup', function (e) {
896
+ setSubjectCharactersLenght(this.value.length);
897
+ });
898
+
899
+ $subjectInput.on('focusout', function (e) {
900
+ $subjectContainer.find('img').fadeTo(300, 0);
901
+ });
902
+
903
+ function setSubjectCharactersLenght(length = 0) {
904
+
905
+ if (length === 0 && subjectCharCounterEl !== null) {
906
+ subjectCharCounterEl.remove();
907
+ subjectCharCounterEl = null;
908
+ return;
909
+ }
910
+
911
+ if (!subjectCharCounterEl) {
912
+ subjectCharCounterEl = document.createElement("span");
913
+ subjectCharCounterEl.style.position = 'absolute';
914
+ subjectCharCounterEl.style.top = '-18px';
915
+ subjectCharCounterEl.style.right = $subjectContainer[0].getBoundingClientRect().width - $subjectInput[0].getBoundingClientRect().width + 'px';
916
+ subjectCharCounterEl.style.color = '#999';
917
+ subjectCharCounterEl.style.fontSize = '0.8rem';
918
+ $subjectContainer.find('div')[0].appendChild(subjectCharCounterEl);
919
+ }
920
+
921
+ const word = length === 1 ? 'character' : 'characters';
922
+ subjectCharCounterEl.innerHTML = `${length} ${word}`;
923
+ }
924
+
925
+ })(jQuery);
926
+
927
+ // ======================================================================= //
928
+ // ================= COMPOSER MODE VIEW SWITCH ===================== //
929
+ // ======================================================================= //
930
+
931
+ (function composerModeViewIIFE($) {
932
+ const activeClass = 'composer-view-mode__item--active';
933
+ var status = 'desktop';
934
+
935
+ $('.composer-view-mode__item[data-view-mode="' + status + '"]').addClass(activeClass);
936
+
937
+ $('.composer-view-mode__item').on('click', function () {
938
+ var $el = $(this);
939
+
940
+ if ($el.data('viewMode') === 'desktop') {
941
+ status = 'desktop';
942
+ $('.composer-view-mode__item[data-view-mode="desktop"]').addClass(activeClass);
943
+ $('.composer-view-mode__item[data-view-mode="mobile"]').removeClass(activeClass);
944
+ } else if ($el.data('viewMode') === 'mobile') {
945
+ status = 'mobile';
946
+ $('.composer-view-mode__item[data-view-mode="desktop"]').removeClass(activeClass);
947
+ $('.composer-view-mode__item[data-view-mode="mobile"]').addClass(activeClass);
948
+ }
949
+
950
+ tnp_view(status);
951
+ });
952
+ })(jQuery);
 
emails/tnp-composer/index-v2.php CHANGED
@@ -1,206 +1,205 @@
1
- <?php
2
- /**
3
- * This file is included by NewsletterControls to create the composer.
4
- */
5
- /* @var $this NewsletterControls */
6
-
7
- defined('ABSPATH') || exit;
8
-
9
- $list = NewsletterEmails::instance()->get_blocks();
10
-
11
- $blocks = array();
12
- foreach ($list as $key => $data) {
13
- if (!isset($blocks[$data['section']])) {
14
- $blocks[$data['section']] = array();
15
- }
16
- $blocks[$data['section']][$key]['name'] = $data['name'];
17
- $blocks[$data['section']][$key]['filename'] = $key;
18
- $blocks[$data['section']][$key]['icon'] = $data['icon'];
19
- }
20
-
21
- // order the sections
22
- $blocks = array_merge(array_flip(array('header', 'content', 'footer')), $blocks);
23
-
24
- // prepare the options for the default blocks
25
- $block_options = get_option('newsletter_main');
26
-
27
- $fields = new NewsletterFields($controls);
28
-
29
- $dir = is_rtl() ? 'rtl' : 'ltr';
30
- $rev_dir = is_rtl() ? 'ltr' : 'rlt';
31
- ?>
32
- <script type="text/javascript">
33
- if (window.innerWidth < 1550) {
34
- document.body.classList.add('folded');
35
- }
36
-
37
- function tnp_view(type) {
38
- if (type === 'mobile') {
39
- jQuery('#newsletter-builder-area-center-frame-content').addClass('tnp-view-mobile');
40
- } else {
41
- jQuery('#newsletter-builder-area-center-frame-content').removeClass('tnp-view-mobile');
42
- }
43
- }
44
- </script>
45
-
46
-
47
- <style>
48
- <?php echo NewsletterEmails::instance()->get_composer_backend_css(); ?>
49
- </style>
50
- <div id="newsletter-builder" dir="ltr">
51
-
52
- <div id="newsletter-builder-area" class="tnp-builder-column">
53
-
54
- <?php if ($tnpc_show_subject) { ?>
55
- <div id="tnpc-subject-wrap" dir="<?php echo $dir ?>">
56
- <table role="presentation" style="width: 100%">
57
- <?php if (!empty($controls->data['sender_email'])) { ?>
58
- <tr>
59
- <th dir="<?php echo $dir ?>"><?php _e('From', 'newsletter') ?></th>
60
- <td dir="<?php echo $dir ?>"><?php echo esc_html($controls->data['sender_email']) ?></td>
61
- </tr>
62
- <?php } ?>
63
- <tr>
64
- <th dir="<?php echo $dir ?>">
65
- <?php _e('Subject', 'newsletter') ?>
66
- <?php if ($context_type === 'automated') { ?>
67
- <?php $this->field_help('https://www.thenewsletterplugin.com/documentation/addons/extended-features/automated-extension/#subject') ?>
68
- <?php } ?>
69
- </th>
70
- <td dir="<?php echo $dir ?>">
71
- <div id="tnpc-subject">
72
- <?php $this->subject('subject'); ?>
73
- </div>
74
- </td>
75
- </tr>
76
- <tr>
77
- <th dir="<?php echo $dir ?>"><span title="<?php esc_attr_e('Shown by some email clients as excerpt', 'newsletter') ?>"><?php _e('Snippet', 'newsletter') ?></span>
78
- <?php $this->field_help('https://www.thenewsletterplugin.com/documentation/newsletters/composer/#subject') ?>
79
- </th>
80
- <td dir="<?php echo $dir ?>"><?php $this->text('preheader') ?></td>
81
- </tr>
82
- </table>
83
-
84
- <div style="text-align: left; margin-left: 1em;">
85
- <a href="https://www.thenewsletterplugin.com/documentation/newsletters/newsletter-tags/"
86
- target="_blank">You can use tags to inject subscriber fields</a>. Even on subject.
87
-
88
-
89
- </div>
90
-
91
- <div class="composer-actions">
92
-
93
- <div id="attachment-newsletter-button" class="button-primary" data-tnp-modal-target="#attachment-modal">
94
- <i class="fas fa-paperclip"></i>
95
- </div>
96
- <?php if ($show_test) { ?>
97
- <div id="test-newsletter-button" class="button-primary" data-tnp-modal-target="#test-newsletter-modal">
98
- <i class="fas fa-paper-plane"></i> <?php _e('Test', 'newsletter') ?>
99
- </div>
100
- <?php } ?>
101
-
102
- <div class="composer-view-mode">
103
- <span class="composer-view-mode__item" data-view-mode="desktop">
104
- <i class="fas fa-desktop"></i>
105
- </span>
106
-
107
- <span class="composer-view-mode__item" data-view-mode="mobile">
108
- <i class="fas fa-mobile"></i>
109
- <span>
110
- </div>
111
-
112
- </div>
113
-
114
- <?php include NEWSLETTER_DIR . '/emails/tnp-composer/modal/test-newsletter.php' ?>
115
- <?php include NEWSLETTER_DIR . '/emails/tnp-composer/modal/attachment.php' ?>
116
-
117
- </div>
118
- <?php } ?>
119
-
120
-
121
- <div id="newsletter-builder-area-center-frame-content" dir="<?php echo $dir ?>">
122
-
123
- <!-- Composer content -->
124
-
125
- </div>
126
- </div>
127
-
128
- <div id="newsletter-builder-sidebar" dir="<?php echo is_rtl() ? 'rtl' : 'ltr' ?>">
129
-
130
- <div class="tnpc-tabs">
131
- <button class="tablinks" onclick="openTab(event, 'tnpc-blocks')" data-tab-id='tnpc-blocks' id="defaultOpen"><?php _e('Blocks', 'newsletter') ?></button>
132
- <button class="tablinks" onclick="openTab(event, 'tnpc-global-styles')" data-tab-id='tnpc-global-styles'><?php _e('Settings', 'newsletter') ?></button>
133
- </div>
134
-
135
- <div id="tnpc-blocks" class="tabcontent">
136
- <?php foreach ($blocks as $k => $section) { ?>
137
- <div class="newsletter-sidebar-add-buttons" id="sidebar-add-<?php echo $k ?>">
138
- <!--<h4><span><?php echo ucfirst($k) ?></span></h4>-->
139
- <?php foreach ($section AS $key => $block) { ?>
140
- <div class="newsletter-sidebar-buttons-content-tab" data-id="<?php echo $key ?>" data-name="<?php echo esc_attr($block['name']) ?>">
141
- <img src="<?php echo $block['icon'] ?>" title="<?php echo esc_attr($block['name']) ?>">
142
- </div>
143
- <?php } ?>
144
- </div>
145
- <?php } ?>
146
- </div>
147
-
148
- <div id="tnpc-global-styles" class="tabcontent">
149
-
150
- <form id="tnpc-global-styles-form">
151
-
152
- <div class="tnp-field-row">
153
- <div class="tnp-field-col-2">
154
- <?php $fields->color('options_composer_background', __('Main background', 'newsletter')) ?>
155
- </div>
156
- <div class="tnp-field-col-2">
157
- <?php $fields->color('options_composer_block_background', 'Blocks background') ?>
158
- </div>
159
- </div>
160
-
161
- <?php $fields->font('options_composer_title_font', __('Titles font', 'newsletter')) ?>
162
- <?php $fields->font('options_composer_text_font', __('Text font', 'newsletter')) ?>
163
- <?php $fields->button_style('options_composer_button', __('Button style', 'newsletter')); ?>
164
-
165
- <button class="button-secondary" name="apply"><?php _e("Apply", 'newsletter') ?></button>
166
-
167
- </form>
168
-
169
- </div>
170
-
171
- <!-- Block options container (dynamically loaded -->
172
- <div id="tnpc-block-options">
173
- <div id="tnpc-block-options-buttons">
174
- <span id="tnpc-block-options-cancel" class="button-secondary"><?php _e("Cancel", "newsletter") ?></span>
175
- <span id="tnpc-block-options-save" class="button-primary"><?php _e("Apply", "newsletter") ?></span>
176
- </div>
177
- <form id="tnpc-block-options-form" onsubmit="return false;"></form>
178
- </div>
179
-
180
- </div>
181
-
182
- <div style="clear: both"></div>
183
-
184
- </div>
185
-
186
- <div style="display: none">
187
- <div id="newsletter-preloaded-export"></div>
188
- <!-- Block placeholder used by jQuery UI -->
189
- <div id="draggable-helper"></div>
190
- <div id="sortable-helper"></div>
191
- </div>
192
-
193
- <script type="text/javascript">
194
- TNP_PLUGIN_URL = "<?php echo esc_js(NEWSLETTER_URL) ?>";
195
- TNP_HOME_URL = "<?php echo esc_js(home_url('/', is_ssl() ? 'https' : 'http')) ?>";
196
- tnp_context_type = "<?php echo esc_js($context_type) ?>";
197
- tnp_nonce = '<?php echo esc_js(wp_create_nonce('save')) ?>';
198
- tnp_preset_nonce = '<?php echo esc_js(wp_create_nonce('preset')) ?>';
199
- </script>
200
- <?php
201
- wp_enqueue_script('tnp-composer', plugins_url('newsletter') . '/emails/tnp-composer/_scripts/newsletter-builder-v2.js', ['tnp-modal', 'tnp-toast'], NEWSLETTER_VERSION);
202
- ?>
203
-
204
- <?php include NEWSLETTER_DIR . '/emails/subjects.php'; ?>
205
-
206
- <?php if (function_exists('wp_enqueue_editor')) wp_enqueue_editor(); ?>
1
+ <?php
2
+ /**
3
+ * This file is included by NewsletterControls to create the composer.
4
+ */
5
+ /* @var $this NewsletterControls */
6
+
7
+ defined('ABSPATH') || exit;
8
+
9
+ $list = NewsletterEmails::instance()->get_blocks();
10
+
11
+ $blocks = array();
12
+ foreach ($list as $key => $data) {
13
+ if (!isset($blocks[$data['section']])) {
14
+ $blocks[$data['section']] = array();
15
+ }
16
+ $blocks[$data['section']][$key]['name'] = $data['name'];
17
+ $blocks[$data['section']][$key]['filename'] = $key;
18
+ $blocks[$data['section']][$key]['icon'] = $data['icon'];
19
+ }
20
+
21
+ // order the sections
22
+ $blocks = array_merge(array_flip(array('header', 'content', 'footer')), $blocks);
23
+
24
+ // prepare the options for the default blocks
25
+ $block_options = get_option('newsletter_main');
26
+
27
+ $fields = new NewsletterFields($controls);
28
+
29
+ $dir = is_rtl() ? 'rtl' : 'ltr';
30
+ $rev_dir = is_rtl() ? 'ltr' : 'rlt';
31
+ ?>
32
+ <script type="text/javascript">
33
+ if (window.innerWidth < 1550) {
34
+ document.body.classList.add('folded');
35
+ }
36
+
37
+ function tnp_view(type) {
38
+ if (type === 'mobile') {
39
+ jQuery('#newsletter-builder-area-center-frame-content').addClass('tnp-view-mobile');
40
+ } else {
41
+ jQuery('#newsletter-builder-area-center-frame-content').removeClass('tnp-view-mobile');
42
+ }
43
+ }
44
+ </script>
45
+
46
+
47
+ <style>
48
+ <?php echo NewsletterEmails::instance()->get_composer_backend_css(); ?>
49
+ </style>
50
+
51
+ <div id="newsletter-builder" dir="ltr">
52
+
53
+ <div id="newsletter-builder-area" class="tnp-builder-column">
54
+
55
+ <?php if ($tnpc_show_subject) { ?>
56
+ <div id="tnpc-subject-wrap" dir="<?php echo $dir ?>">
57
+ <table role="presentation" style="width: 100%">
58
+ <?php if (!empty($controls->data['sender_email'])) { ?>
59
+ <tr>
60
+ <th dir="<?php echo $dir ?>"><?php _e('From', 'newsletter') ?></th>
61
+ <td dir="<?php echo $dir ?>"><?php echo esc_html($controls->data['sender_email']) ?></td>
62
+ </tr>
63
+ <?php } ?>
64
+ <tr>
65
+ <th dir="<?php echo $dir ?>">
66
+ <?php _e('Subject', 'newsletter') ?>
67
+ <?php if ($context_type === 'automated') { ?>
68
+ <?php $this->field_help('https://www.thenewsletterplugin.com/documentation/addons/extended-features/automated-extension/#subject') ?>
69
+ <?php } ?>
70
+ </th>
71
+ <td dir="<?php echo $dir ?>">
72
+ <div id="tnpc-subject">
73
+ <?php $this->subject('subject'); ?>
74
+ </div>
75
+ </td>
76
+ </tr>
77
+ <tr>
78
+ <th dir="<?php echo $dir ?>"><span title="<?php esc_attr_e('Shown by some email clients as excerpt', 'newsletter') ?>"><?php _e('Snippet', 'newsletter') ?></span>
79
+ <?php $this->field_help('https://www.thenewsletterplugin.com/documentation/newsletters/composer/#subject') ?>
80
+ </th>
81
+ <td dir="<?php echo $dir ?>"><?php $this->text('preheader') ?></td>
82
+ </tr>
83
+ </table>
84
+
85
+ <div style="text-align: left; margin-left: 1em;">
86
+ <a href="https://www.thenewsletterplugin.com/documentation/newsletters/newsletter-tags/"
87
+ target="_blank">You can use tags to inject subscriber fields</a>. Even on subject.
88
+ </div>
89
+
90
+ <div class="composer-actions">
91
+
92
+ <div id="attachment-newsletter-button" class="button-primary" data-tnp-modal-target="#attachment-modal">
93
+ <i class="fas fa-paperclip"></i>
94
+ </div>
95
+
96
+ <?php if ($show_test) { ?>
97
+ <div id="test-newsletter-button" class="button-primary" data-tnp-modal-target="#test-newsletter-modal">
98
+ <i class="fas fa-paper-plane"></i> <?php _e('Test', 'newsletter') ?>
99
+ </div>
100
+ <?php } ?>
101
+
102
+ <div class="composer-view-mode">
103
+
104
+ <span class="composer-view-mode__item" data-view-mode="desktop"><i class="fas fa-desktop"></i></span>
105
+
106
+ <span class="composer-view-mode__item" data-view-mode="mobile"><i class="fas fa-mobile"></i></span>
107
+ </div>
108
+
109
+ </div>
110
+
111
+ <?php include NEWSLETTER_DIR . '/emails/tnp-composer/modal/test-newsletter.php' ?>
112
+ <?php include NEWSLETTER_DIR . '/emails/tnp-composer/modal/attachment.php' ?>
113
+
114
+ </div>
115
+ <?php } ?>
116
+
117
+
118
+ <div id="newsletter-builder-area-center-frame-content" dir="<?php echo $dir ?>">
119
+
120
+ <!-- Composer content -->
121
+
122
+ </div>
123
+ </div>
124
+
125
+
126
+
127
+ <div id="newsletter-builder-sidebar" dir="<?php echo $dir ?>">
128
+
129
+ <div class="tnpc-tabs">
130
+ <button class="tablinks" onclick="openTab(event, 'tnpc-blocks')" data-tab-id='tnpc-blocks' id="defaultOpen"><?php _e('Blocks', 'newsletter') ?></button>
131
+ <button class="tablinks" onclick="openTab(event, 'tnpc-global-styles')" data-tab-id='tnpc-global-styles'><?php _e('Settings', 'newsletter') ?></button>
132
+ </div>
133
+
134
+ <div id="tnpc-blocks" class="tabcontent">
135
+ <?php foreach ($blocks as $k => $section) { ?>
136
+ <div class="newsletter-sidebar-add-buttons" id="sidebar-add-<?php echo $k ?>">
137
+ <!--<h4><span><?php echo ucfirst($k) ?></span></h4>-->
138
+ <?php foreach ($section AS $key => $block) { ?>
139
+ <div class="newsletter-sidebar-buttons-content-tab" data-id="<?php echo $key ?>" data-name="<?php echo esc_attr($block['name']) ?>">
140
+ <img src="<?php echo $block['icon'] ?>" title="<?php echo esc_attr($block['name']) ?>">
141
+ </div>
142
+ <?php } ?>
143
+ </div>
144
+ <?php } ?>
145
+ </div>
146
+
147
+ <div id="tnpc-global-styles" class="tabcontent">
148
+
149
+ <form id="tnpc-global-styles-form">
150
+
151
+ <div class="tnp-field-row">
152
+ <div class="tnp-field-col-2">
153
+ <?php $fields->color('options_composer_background', __('Main background', 'newsletter')) ?>
154
+ </div>
155
+ <div class="tnp-field-col-2">
156
+ <?php $fields->color('options_composer_block_background', 'Blocks background') ?>
157
+ </div>
158
+ </div>
159
+
160
+ <?php $fields->font('options_composer_title_font', __('Titles font', 'newsletter')) ?>
161
+ <?php $fields->font('options_composer_text_font', __('Text font', 'newsletter')) ?>
162
+ <?php $fields->button_style('options_composer_button', __('Button style', 'newsletter')); ?>
163
+
164
+ <button class="button-secondary" name="apply"><?php _e("Apply", 'newsletter') ?></button>
165
+
166
+ </form>
167
+
168
+ </div>
169
+
170
+ <!-- Block options container (dynamically loaded -->
171
+ <div id="tnpc-block-options">
172
+ <div id="tnpc-block-options-buttons">
173
+ <span id="tnpc-block-options-cancel" class="button-secondary"><?php _e("Cancel", "newsletter") ?></span>
174
+ <span id="tnpc-block-options-save" class="button-primary"><?php _e("Apply", "newsletter") ?></span>
175
+ </div>
176
+ <form id="tnpc-block-options-form" onsubmit="return false;"></form>
177
+ </div>
178
+
179
+ </div>
180
+
181
+ <div style="clear: both"></div>
182
+
183
+ </div>
184
+
185
+ <div style="display: none">
186
+ <div id="newsletter-preloaded-export"></div>
187
+ <!-- Block placeholder used by jQuery UI -->
188
+ <div id="draggable-helper"></div>
189
+ <div id="sortable-helper"></div>
190
+ </div>
191
+
192
+ <script type="text/javascript">
193
+ TNP_PLUGIN_URL = "<?php echo esc_js(NEWSLETTER_URL) ?>";
194
+ TNP_HOME_URL = "<?php echo esc_js(home_url('/', is_ssl() ? 'https' : 'http')) ?>";
195
+ tnp_context_type = "<?php echo esc_js($context_type) ?>";
196
+ tnp_nonce = '<?php echo esc_js(wp_create_nonce('save')) ?>';
197
+ tnp_preset_nonce = '<?php echo esc_js(wp_create_nonce('preset')) ?>';
198
+ </script>
199
+ <?php
200
+ wp_enqueue_script('tnp-composer', plugins_url('newsletter') . '/emails/tnp-composer/_scripts/newsletter-builder-v2.js', ['tnp-modal', 'tnp-toast'], NEWSLETTER_VERSION);
201
+ ?>
202
+
203
+ <?php include NEWSLETTER_DIR . '/emails/subjects.php'; ?>
204
+
205
+ <?php if (function_exists('wp_enqueue_editor')) wp_enqueue_editor(); ?>
 
includes/composer.php CHANGED
@@ -1,987 +1,987 @@
1
- <?php
2
-
3
- /** For old style coders */
4
- function tnp_register_block($dir) {
5
- return TNP_Composer::register_block($dir);
6
- }
7
-
8
- /**
9
- * Generates and HTML button for email using the values found on $options and
10
- * prefixed by $prefix, with the standard syntax of NewsletterFields::button().
11
- *
12
- * @param array $options
13
- * @param string $prefix
14
- * @return string
15
- */
16
- function tnpc_button($options, $prefix = 'button') {
17
- return TNP_Composer::button($options, $prefix);
18
- }
19
-
20
- class TNP_Composer {
21
-
22
- static $block_dirs = array();
23
-
24
- static function register_block($dir) {
25
- // Checks
26
- $dir = realpath($dir);
27
- if (!$dir) {
28
- $error = new WP_Error('1', 'Seems not a valid path: ' . $dir);
29
- NewsletterEmails::instance()->logger->error($error);
30
- return $error;
31
- }
32
-
33
- $dir = wp_normalize_path($dir);
34
-
35
- if (!file_exists($dir . '/block.php')) {
36
- $error = new WP_Error('1', 'block.php missing on folder ' . $dir);
37
- NewsletterEmails::instance()->logger->error($error);
38
- return $error;
39
- }
40
-
41
- self::$block_dirs[] = $dir;
42
- return true;
43
- }
44
-
45
- /**
46
- * @param string $open
47
- * @param string $inner
48
- * @param string $close
49
- * @param string[] $markers
50
- *
51
- * @return string
52
- */
53
- static function wrap_html_element($open, $inner, $close, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
54
-
55
- return $open . $markers[0] . $inner . $markers[1] . $close;
56
- }
57
-
58
- /**
59
- * @param string $block
60
- * @param string[] $markers
61
- *
62
- * @return string
63
- */
64
- static function unwrap_html_element($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
65
- if (self::_has_markers($block, $markers)) {
66
- self::_escape_markers($markers);
67
- $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
68
-
69
- $matches = array();
70
- preg_match($pattern, $block, $matches);
71
-
72
- return $matches[1];
73
- }
74
-
75
- return $block;
76
- }
77
-
78
- /**
79
- * @param string $block
80
- * @param string[] $markers
81
- *
82
- * @return bool
83
- */
84
- private static function _has_markers($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
85
-
86
- self::_escape_markers($markers);
87
-
88
- $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
89
-
90
- return preg_match($pattern, $block);
91
- }
92
-
93
- /**
94
- * Sources:
95
- * - https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
96
- *
97
- * @param type $email
98
- * @return type
99
- */
100
- static function get_html_open($email) {
101
- $open = '<!DOCTYPE html>' . "\n";
102
- $open .= '<html xmlns="https://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">' . "\n";
103
- $open .= '<head>' . "\n";
104
- $open .= '<title>{email_subject}</title>' . "\n";
105
- $open .= '<meta charset="utf-8">' . "\n";
106
- $open .= '<meta name="viewport" content="width=device-width, initial-scale=1">' . "\n";
107
- $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge">' . "\n";
108
- $open .= '<meta name="format-detection" content="address=no">' . "\n";
109
- $open .= '<meta name="format-detection" content="telephone=no">' . "\n";
110
- $open .= '<meta name="format-detection" content="email=no">' . "\n";
111
- $open .= '<meta name="x-apple-disable-message-reformatting">' . "\n";
112
-
113
- // $open .= '<!--[if !mso]><!-->' . "\n";
114
- // $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
115
- // $open .= '<!--<![endif]-->' . "\n";
116
-
117
- // $open .= '<!--[if mso]>' . "\n";
118
-
119
- $open .= '<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->' . "\n";
120
-
121
- // $open .= '<style type="text/css">';
122
- // $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
123
- // $open .= 'div, td {padding:0;}';
124
- // $open .= 'div {margin:0 !important;}';
125
- // $open .= '</style>';
126
- // $open .= "\n";
127
- // $open .= '<noscript>';
128
- // $open .= '<xml>';
129
- // $open .= '<o:OfficeDocumentSettings>';
130
- // $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
131
- // $open .= '</o:OfficeDocumentSettings>';
132
- // $open .= '</xml>';
133
- // $open .= '</noscript>';
134
- // $open .= "\n";
135
- // $open .= '<![endif]-->';
136
- // $open .= "\n";
137
- $open .= '<style type="text/css">' . "\n";
138
- $open .= NewsletterEmails::instance()->get_composer_css();
139
- $open .= "\n</style>\n";
140
- $open .= "</head>\n";
141
- $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
142
- $open .= "\n";
143
- $open .= self::get_html_preheader($email);
144
-
145
- return $open;
146
- }
147
-
148
- static private function get_html_preheader($email) {
149
-
150
- if (empty($email->options['preheader'])) {
151
- return "";
152
- }
153
-
154
- $preheader_text = esc_html($email->options['preheader']);
155
- $html = "<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">$preheader_text</div>";
156
- $html .= "\n";
157
-
158
- return $html;
159
- }
160
-
161
- static function get_html_close($email) {
162
- return "</body>\n</html>";
163
- }
164
-
165
- /**
166
- *
167
- * @param TNP_Email $email
168
- * @return string
169
- */
170
- static function get_main_wrapper_open($email) {
171
- if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
172
- $bgcolor = '';
173
- } else {
174
- $bgcolor = $email->options['composer_background'];
175
- }
176
-
177
- return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
178
- "<tr>\n" .
179
- "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
180
- }
181
-
182
- /**
183
- *
184
- * @param TNP_Email $email
185
- * @return string
186
- */
187
- static function get_main_wrapper_close($email) {
188
- return "\n<!-- /tnp -->\n" .
189
- "</td>\n" .
190
- "</tr>\n" .
191
- "</table>\n\n";
192
- }
193
-
194
- /**
195
- * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
196
- *
197
- * @param string $html_email
198
- *
199
- * @return string
200
- */
201
- static function unwrap_email($html_email) {
202
-
203
- if (self::_has_markers($html_email)) {
204
- $html_email = self::unwrap_html_element($html_email);
205
- } else {
206
- //KEEP FOR OLD EMAIL COMPATIBILITY
207
- // Extracts only the body part
208
- $x = strpos($html_email, '<body');
209
- if ($x) {
210
- $x = strpos($html_email, '>', $x);
211
- $y = strpos($html_email, '</body>');
212
- $html_email = substr($html_email, $x + 1, $y - $x - 1);
213
- }
214
-
215
- /* Cleans up uncorrectly stored newsletter bodies */
216
- $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
217
- $html_email = preg_replace('/<meta.*?>/', '', $html_email);
218
- $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
219
- $html_email = trim($html_email);
220
- }
221
-
222
- // Required since esc_html DOES NOT escape the HTML entities (apparently)
223
- $html_email = str_replace('&', '&amp;', $html_email);
224
- $html_email = str_replace('"', '&quot;', $html_email);
225
- $html_email = str_replace('<', '&lt;', $html_email);
226
- $html_email = str_replace('>', '&gt;', $html_email);
227
-
228
- return $html_email;
229
- }
230
-
231
- private static function _escape_markers(&$markers) {
232
- $markers[0] = str_replace('/', '\/', $markers[0]);
233
- $markers[1] = str_replace('/', '\/', $markers[1]);
234
- }
235
-
236
- /**
237
- * Using the data collected inside $controls (and submitted by a form containing the
238
- * composer fields), updates the email. The message body is completed with doctype,
239
- * head, style and the main wrapper.
240
- *
241
- * @param TNP_Email $email
242
- * @param NewsletterControls $controls
243
- */
244
- static function update_email($email, $controls) {
245
- if (isset($controls->data['subject'])) {
246
- $email->subject = $controls->data['subject'];
247
- }
248
-
249
- // They should be only composer options
250
- foreach ($controls->data as $name => $value) {
251
- if (strpos($name, 'options_') === 0) {
252
- $email->options[substr($name, 8)] = $value;
253
- }
254
- }
255
-
256
- //if (isset($controls->data['preheader'])) {
257
- // $email->options['preheader'] = $controls->data['preheader'];
258
- //}
259
-
260
- $email->editor = NewsletterEmails::EDITOR_COMPOSER;
261
-
262
- $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
263
- $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
264
- }
265
-
266
- /**
267
- * Prepares a controls object injecting the relevant fields from an email
268
- * which cannot be directly used by controls. If $email is null or missing,
269
- * $controls is prepared with default values.
270
- *
271
- * @param NewsletterControls $controls
272
- * @param TNP_Email $email
273
- */
274
- static function prepare_controls($controls, $email = null) {
275
-
276
- // Controls for a new email (which actually does not exist yet
277
- if (!empty($email)) {
278
-
279
- foreach ($email->options as $name => $value) {
280
- $controls->data['options_' . $name] = $value;
281
- }
282
-
283
- $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
284
- $controls->data['subject'] = $email->subject;
285
- $controls->data['updated'] = $email->updated;
286
- }
287
-
288
- if (!empty($email->options['sender_email'])) {
289
- $controls->data['sender_email'] = $email->options['sender_email'];
290
- } else {
291
- $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
292
- }
293
-
294
- if (!empty($email->options['sender_name'])) {
295
- $controls->data['sender_name'] = $email->options['sender_name'];
296
- } else {
297
- $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
298
- }
299
-
300
- $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
301
- }
302
-
303
- /**
304
- * Extract inline edited post field from inline_edit_list[]
305
- *
306
- * @param array $inline_edit_list
307
- * @param string $field_type
308
- * @param int $post_id
309
- *
310
- * @return string
311
- */
312
- static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
313
-
314
- foreach ($inline_edit_list as $edit) {
315
- if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
316
- return $edit['content'];
317
- }
318
- }
319
-
320
- return '';
321
- }
322
-
323
- /**
324
- * Check if inline_edit_list[] have inline edit field for specific post
325
- *
326
- * @param array $inline_edit_list
327
- * @param string $field_type
328
- * @param int $post_id
329
- *
330
- * @return bool
331
- */
332
- static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
333
- if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
334
- return false;
335
- }
336
- foreach ($inline_edit_list as $edit) {
337
- if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
338
- return true;
339
- }
340
- }
341
-
342
- return false;
343
- }
344
-
345
- /**
346
- * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
347
- *
348
- * - [prefix]_url The button URL
349
- * - [prefix]_font_family
350
- * - [prefix]_font_size
351
- * - [prefix]_font_weight
352
- * - [prefix]_label
353
- * - [prefix]_font_color The label color
354
- * - [prefix]_background The button color
355
- *
356
- * TODO: Add radius and possiblt the alignment
357
- *
358
- * @param array $options
359
- * @param string $prefix
360
- * @return string
361
- */
362
- static function button($options, $prefix = 'button') {
363
-
364
- if (empty($options[$prefix . '_label'])) {
365
- return;
366
- }
367
- $defaults = [
368
- $prefix . '_url' => '#',
369
- $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
370
- $prefix . '_font_color' => '#ffffff',
371
- $prefix . '_font_weight' => 'bold',
372
- $prefix . '_font_size' => 20,
373
- $prefix . '_background' => '#256F9C',
374
- $prefix . '_align' => 'center'
375
- ];
376
-
377
- $options = array_merge($defaults, array_filter($options));
378
-
379
- $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
380
- if (!empty($options[$prefix . '_align'])) {
381
- $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
382
- }
383
- if (!empty($options[$prefix . '_width'])) {
384
- $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
385
- }
386
- $b .= '>';
387
- $b .= '<tr>';
388
- $b .= '<td align="center" bgcolor="' . $options[$prefix . '_background'] . '" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:' . $options[$prefix . '_background'] . '" valign="middle">';
389
- $b .= '<a href="' . $options[$prefix . '_url'] . '"';
390
- $b .= ' style="display:inline-block;background:' . $options[$prefix . '_background'] . ';color:' . $options[$prefix . '_font_color'] . ';font-family:' . $options[$prefix . '_font_family'] . ';font-size:' . $options[$prefix . '_font_size'] . 'px;font-weight:' . $options[$prefix . '_font_weight'] . ';line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"';
391
- $b .= ' target="_blank">';
392
- $b .= $options[$prefix . '_label'];
393
- $b .= '</a>';
394
- $b .= '</td></tr></table>';
395
- return $b;
396
- }
397
-
398
- /**
399
- * Generates an IMG tag, linked if the media has an URL.
400
- *
401
- * @param TNP_Media $media
402
- * @param string $style
403
- * @return string
404
- */
405
- static function image($media, $attr = []) {
406
-
407
- $default_attrs = [
408
- 'style' => 'max-width: 100%; height: auto; display: inline-block',
409
- 'class' => '',
410
- 'link-style' => 'text-decoration: none; display: inline-block',
411
- 'link-class' => null,
412
- ];
413
-
414
- $attr = array_merge($default_attrs, $attr);
415
-
416
- //Class and style attribute are mutually exclusive.
417
- //Class take priority to style because classes will transform to inline style inside block rendering operation
418
- if (!empty($attr['inline-class'])) {
419
- $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
420
- } else {
421
- $styling = ' style="' . esc_attr($attr['style']) . '" ';
422
- }
423
-
424
- if (!empty($attr['class'])) {
425
- $styling .= ' class="' . esc_attr($attr['class']) . '" ';
426
- }
427
-
428
- //Class and style attribute are mutually exclusive.
429
- //Class take priority to style because classes will transform to inline style inside block rendering operation
430
- if (!empty($attr['link-class'])) {
431
- $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
432
- } else {
433
- $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
434
- }
435
-
436
- $b = '';
437
- if ($media->link) {
438
- $b .= '<a href="' . esc_attr($media->link) . '" target="_blank" rel="noopener nofollow" style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
439
- } else {
440
- // The span grants images are not upscaled when fluid (two columns posts block)
441
- $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
442
- }
443
- if ($media) {
444
- $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
445
- if ($media->height) {
446
- $b .= ' height="' . esc_attr($media->height) . '"';
447
- }
448
- $b .= ' alt="' . esc_attr($media->alt) . '"'
449
- . ' border="0"'
450
- . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0; font-size: 12px"'
451
- . ' class="' . esc_attr($attr['class']) . '" '
452
- . '>';
453
- }
454
-
455
- if ($media->link) {
456
- $b .= '</a>';
457
- } else {
458
- $b .= '</span>';
459
- }
460
-
461
- return $b;
462
- }
463
-
464
- /**
465
- * Returns a WP media ID for the specified post (or false if nothing can be found)
466
- * looking for the featured image or, if missing, taking the first media in the gallery and
467
- * if again missing, searching the first reference to a media in the post content.
468
- *
469
- * The media ID is not checked for real existance of the associated attachment.
470
- *
471
- * @param int $post_id
472
- * @return int
473
- */
474
- static function get_post_thumbnail_id($post_id) {
475
- if (is_object($post_id)) {
476
- $post_id = $post_id->ID;
477
- }
478
-
479
- // Find a media id to be used as featured image
480
- $media_id = get_post_thumbnail_id($post_id);
481
- if (!empty($media_id)) {
482
- return $media_id;
483
- }
484
-
485
- $attachments = get_children(array('numberpost' => 1, 'post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order'));
486
- if (!empty($attachments)) {
487
- foreach ($attachments as $id => &$attachment) {
488
- return $id;
489
- }
490
- }
491
-
492
- $post = get_post($post_id);
493
-
494
- $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
495
- if ($matches) {
496
- return (int) $matches[1];
497
- }
498
-
499
- return false;
500
- }
501
-
502
- /**
503
- * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
504
- * media has a size which best match the one requested (this is the standard WP behavior, plugins
505
- * could change it).
506
- *
507
- * @param int $media_id
508
- * @param array $size
509
- * @return \TNP_Media
510
- */
511
- function get_media($media_id, $size) {
512
- $src = wp_get_attachment_image_src($media_id, $size);
513
- if (!$src) {
514
- return null;
515
- }
516
- $media = new TNP_Media();
517
- $media->id = $media_id;
518
- $media->url = $src[0];
519
- $media->width = $src[1];
520
- $media->height = $src[2];
521
- return $media;
522
- }
523
-
524
- static function post_content($post) {
525
- $content = $post->post_content;
526
- $content = wpautop($content);
527
- if (true || $options['enable shortcodes']) {
528
- remove_shortcode('gallery');
529
- add_shortcode('gallery', 'tnp_gallery_shortcode');
530
- $content = do_shortcode($content);
531
- }
532
- $content = str_replace('<p>', '<p class="paragraph">', $content);
533
-
534
- $selected_images = array();
535
- if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
536
- foreach ($matches[0] as $image) {
537
- if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
538
- $selected_images[$image] = $attachment_id;
539
- }
540
- }
541
- }
542
-
543
- foreach ($selected_images as $image => $attachment_id) {
544
- $src = tnp_media_resize($attachment_id, array(600, 0));
545
- if (is_wp_error($src)) {
546
- continue;
547
- }
548
- $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
549
- }
550
-
551
- return $content;
552
- }
553
-
554
- static function get_global_style_defaults() {
555
- return [
556
- 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
557
- 'options_composer_title_font_size' => 32,
558
- 'options_composer_title_font_weight' => 'normal',
559
- 'options_composer_title_font_color' => '#222222',
560
- 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
561
- 'options_composer_text_font_size' => 16,
562
- 'options_composer_text_font_weight' => 'normal',
563
- 'options_composer_text_font_color' => '#222222',
564
- 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
565
- 'options_composer_button_font_size' => 16,
566
- 'options_composer_button_font_weight' => 'normal',
567
- 'options_composer_button_font_color' => '#FFFFFF',
568
- 'options_composer_button_background_color' => '#256F9C',
569
- 'options_composer_background' => '#FFFFFF',
570
- 'options_composer_block_background' => '#FFFFFF',
571
- ];
572
- }
573
-
574
- /**
575
- * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
576
- *
577
- * Attributes:
578
- * - columns: number of columns [2]
579
- * - padding: cells padding [10]
580
- * - responsive: il on mobile the cell should stack up [true]
581
- * - width: the whole row width, it should reduced by the external row padding [600]
582
- *
583
- * @param string[] $items
584
- * @param array $attrs
585
- * @return string
586
- */
587
- static function grid($items = [], $attrs = []) {
588
- $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
589
- $width = (int) $attrs['width'];
590
- $columns = (int) $attrs['columns'];
591
- $padding = (int) $attrs['padding'];
592
- $column_width = $width / $columns;
593
- $td_width = 100 / $columns;
594
- $chunks = array_chunk($items, $columns);
595
-
596
- if ($attrs['responsive']) {
597
-
598
- $e = '';
599
- foreach ($chunks as &$chunk) {
600
- $e .= '<div style="text-align:center;font-size:0;">';
601
- $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
602
- foreach ($chunk as &$item) {
603
- $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
604
-
605
- $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
606
-
607
- // This element to add padding without deal with border-box not well supported
608
- $e .= '<div style="padding:' . $padding . 'px;">';
609
- $e .= $item;
610
- $e .= '</div>';
611
- $e .= '</div>';
612
-
613
- $e .= '<!--[if mso]></td><![endif]-->';
614
- }
615
- $e .= '<!--[if mso]></tr></table><![endif]-->';
616
- $e .= '</div>';
617
- }
618
-
619
- return $e;
620
- } else {
621
- $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
622
- foreach ($chunks as &$chunk) {
623
- $e .= '<tr>';
624
- foreach ($chunk as &$item) {
625
- $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
626
- $e .= $item;
627
- $e .= '</td>';
628
- }
629
- $e .= '</tr>';
630
- }
631
- $e .= '</table>';
632
- return $e;
633
- }
634
- }
635
-
636
- static function get_text_style($options, $prefix, $composer, $attrs = []) {
637
- return self::get_style($options, $prefix, $composer, 'text', $attrs);
638
- }
639
-
640
- static function get_title_style($options, $prefix, $composer, $attrs = []) {
641
- return self::get_style($options, $prefix, $composer, 'title', $attrs);
642
- }
643
-
644
- static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
645
- $style = new TNP_Style();
646
- $scale = 1.0;
647
- if (!empty($attrs['scale'])) {
648
- $scale = (float) $attrs['scale'];
649
- }
650
- if (!empty($prefix))
651
- $prefix .= '_';
652
-
653
- $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
654
- $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
655
- $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
656
- $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
657
- if ($type === 'button') {
658
- $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
659
- }
660
- return $style;
661
- }
662
-
663
- static function get_button_options($options, $prefix, $composer) {
664
- $button_options = [];
665
- $scale = 1;
666
- $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
667
- $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
668
- $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
669
- $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
670
- $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
671
- $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
672
- $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
673
- $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
674
- $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
675
-
676
- return $button_options;
677
- }
678
-
679
- static function convert_to_text($html) {
680
- if (!class_exists('DOMDocument')) {
681
- return '';
682
- }
683
- // Replace '&' with '&amp;' in URLs to avoid warnings about inavlid entities from loadHTML()
684
- // Todo: make this more general using a regular expression
685
- //$logger = PlaintextNewsletterAddon::$instance->get_logger();
686
- //$logger->debug('html="' . $html . '"');
687
- $html = str_replace(
688
- array('&nk=', '&nek=', '&id='),
689
- array('&amp;nk=', '&amp;nek=', '&amp;id='),
690
- $html);
691
- //$logger->debug('new html="' . $html . '"');
692
- //
693
- $output = '';
694
-
695
- // Prevents warnings for problems with the HTML
696
- if (function_exists('libxml_use_internal_errors')) {
697
- libxml_use_internal_errors(true);
698
- }
699
- $dom = new DOMDocument();
700
- $r = $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html);
701
- if (!$r) {
702
- return '';
703
- }
704
- $bodylist = $dom->getElementsByTagName('body');
705
- // Of course it should be a single element
706
- foreach ($bodylist as $body) {
707
- self::process_dom_element($body, $output);
708
- }
709
- return $output;
710
- }
711
-
712
- static function process_dom_element(DOMElement $parent, &$output) {
713
- foreach ($parent->childNodes as $node) {
714
- if (is_a($node, 'DOMElement') && ($node->tagName != 'style')) {
715
- if ($node->tagName== 'br') {
716
- $output .= "\n";
717
- continue;
718
- }
719
- self::process_dom_element($node, $output);
720
-
721
- // If the containing tag was a block level tag, we add a couple of line ending
722
- if ($node->tagName == 'p' || $node->tagName == 'div' || $node->tagName == 'td') {
723
- // Avoid more than one blank line between elements
724
- if ((strlen($output) >= 2) && (substr($output, -2) != "\n\n")) {
725
- $output .= "\n\n";
726
- }
727
- }
728
-
729
- if ($node->tagName == 'a') {
730
- $output .= ' (' . $node->getAttribute('href') . ') ';
731
- continue;
732
- }
733
- elseif ($node->tagName == 'img') {
734
- $output .= $node->getAttribute('alt');
735
- }
736
- }
737
- elseif (is_a($node, 'DOMText')) {
738
- $decoded = utf8_decode($node->wholeText);
739
- if (ctype_space($decoded)) {
740
- // Append blank only if last character output is not blank.
741
- if ((strlen($output) > 0) && !ctype_space(substr($output, -1))) {
742
- $output .= ' ';
743
- }
744
- } else {
745
- $output .= trim($node->wholeText);
746
- }
747
- }
748
- }
749
- }
750
- }
751
-
752
-
753
-
754
- class TNP_Style {
755
-
756
- var $font_family;
757
- var $font_size;
758
- var $font_weight;
759
- var $font_color;
760
- var $background;
761
-
762
- function echo_css($scale = 1.0) {
763
- echo 'font-size: ', round($this->font_size * $scale), 'px;';
764
- echo 'font-family: ', $this->font_family, ';';
765
- echo 'font-weight: ', $this->font_weight, ';';
766
- echo 'color: ', $this->font_color, ';';
767
- }
768
-
769
- }
770
-
771
- /**
772
- * Generate multicolumn and responsive html template for email.
773
- * Initialize class with max columns per row and start to add cells.
774
- */
775
- class TNP_Composer_Grid_System {
776
-
777
- /**
778
- * @var TNP_Composer_Grid_Row[]
779
- */
780
- private $rows;
781
-
782
- /**
783
- * @var int
784
- */
785
- private $cells_per_row;
786
-
787
- /**
788
- * @var int
789
- */
790
- private $cells_counter;
791
-
792
- /**
793
- * TNP_Composer_Grid_System constructor.
794
- *
795
- * @param int $columns_per_row Max columns per row
796
- */
797
- public function __construct($columns_per_row) {
798
- $this->cells_per_row = $columns_per_row;
799
- $this->cells_counter = 0;
800
- $this->rows = [];
801
- }
802
-
803
- public function __toString() {
804
- return $this->render();
805
- }
806
-
807
- /**
808
- * Add cell to grid
809
- *
810
- * @param TNP_Composer_Grid_Cell $cell
811
- */
812
- public function add_cell($cell) {
813
-
814
- if ($this->cells_counter % $this->cells_per_row === 0) {
815
- $this->add_row(new TNP_Composer_Grid_Row());
816
- }
817
-
818
- $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
819
- $this->rows[$row_idx]->add_cell($cell);
820
- $this->cells_counter++;
821
- }
822
-
823
- private function add_row($row) {
824
- $this->rows[] = $row;
825
- }
826
-
827
- public function render() {
828
-
829
- $str = '';
830
- foreach ($this->rows as $row) {
831
- $str .= $row->render();
832
- }
833
-
834
- return $str;
835
- }
836
-
837
- }
838
-
839
- /**
840
- * Class TNP_Composer_Grid_Row
841
- */
842
- class TNP_Composer_Grid_Row {
843
-
844
- /**
845
- * @var TNP_Composer_Grid_Cell[]
846
- */
847
- private $cells;
848
-
849
- public function __construct(...$cells) {
850
- if (!empty($cells)) {
851
- foreach ($cells as $cell) {
852
- $this->add_cell($cell);
853
- }
854
- }
855
- }
856
-
857
- /**
858
- * @param TNP_Composer_Grid_Cell $cell
859
- */
860
- public function add_cell($cell) {
861
- $this->cells[] = $cell;
862
- }
863
-
864
- public function render() {
865
- $rendered_cells = '';
866
- $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
867
- foreach ($this->cells as $cell) {
868
- $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
869
- }
870
-
871
- $row_template = $this->get_template();
872
-
873
- return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
874
- }
875
-
876
- private function cells_count() {
877
- return count($this->cells);
878
- }
879
-
880
- private function get_template() {
881
- return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
882
- }
883
-
884
- }
885
-
886
- /**
887
- * Class TNP_Composer_Grid_Cell
888
- */
889
- class TNP_Composer_Grid_Cell {
890
-
891
- /**
892
- * @var string
893
- */
894
- private $content;
895
-
896
- /**
897
- * @var array
898
- */
899
- public $args;
900
-
901
- public function __construct($content = null, $args = []) {
902
- $default_args = [
903
- 'width' => '100%',
904
- 'class' => '',
905
- 'align' => 'left',
906
- 'valign' => 'top'
907
- ];
908
-
909
- $this->args = array_merge($default_args, $args);
910
-
911
- $this->content = $content ? $content : '';
912
- }
913
-
914
- public function add_content($content) {
915
- $this->content .= $content;
916
- }
917
-
918
- public function render($args) {
919
- $this->args = array_merge($this->args, $args);
920
-
921
- $column_template = $this->get_template();
922
- $column = str_replace(
923
- [
924
- 'TNP_ALIGN_PH',
925
- 'TNP_VALIGN_PH',
926
- 'TNP_WIDTH_PH',
927
- 'TNP_CLASS_PH',
928
- 'TNP_COLUMN_CONTENT_PH'
929
- ], [
930
- $this->args['align'],
931
- $this->args['valign'],
932
- $this->args['width'],
933
- $this->args['class'],
934
- $this->content
935
- ], $column_template);
936
-
937
- return $column;
938
- }
939
-
940
- private function get_template() {
941
- return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
942
- <tbody>
943
- <tr>
944
- <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
945
- TNP_COLUMN_CONTENT_PH
946
- </td>
947
- </tr>
948
- </tbody>
949
- </table>";
950
- }
951
-
952
- }
953
-
954
- class TNP_Composer_Component_Factory {
955
-
956
- private $options;
957
-
958
- /**
959
- * TNP_Composer_Component_Factory constructor.
960
- *
961
- * @param Controller$controller
962
- */
963
- public function __construct($controller) {
964
-
965
- }
966
-
967
- function heading() {
968
-
969
- }
970
-
971
- function paragraph() {
972
-
973
- }
974
-
975
- function link() {
976
-
977
- }
978
-
979
- function button() {
980
-
981
- }
982
-
983
- function image() {
984
-
985
- }
986
-
987
- }
1
+ <?php
2
+
3
+ /** For old style coders */
4
+ function tnp_register_block($dir) {
5
+ return TNP_Composer::register_block($dir);
6
+ }
7
+
8
+ /**
9
+ * Generates and HTML button for email using the values found on $options and
10
+ * prefixed by $prefix, with the standard syntax of NewsletterFields::button().
11
+ *
12
+ * @param array $options
13
+ * @param string $prefix
14
+ * @return string
15
+ */
16
+ function tnpc_button($options, $prefix = 'button') {
17
+ return TNP_Composer::button($options, $prefix);
18
+ }
19
+
20
+ class TNP_Composer {
21
+
22
+ static $block_dirs = array();
23
+
24
+ static function register_block($dir) {
25
+ // Checks
26
+ $dir = realpath($dir);
27
+ if (!$dir) {
28
+ $error = new WP_Error('1', 'Seems not a valid path: ' . $dir);
29
+ NewsletterEmails::instance()->logger->error($error);
30
+ return $error;
31
+ }
32
+
33
+ $dir = wp_normalize_path($dir);
34
+
35
+ if (!file_exists($dir . '/block.php')) {
36
+ $error = new WP_Error('1', 'block.php missing on folder ' . $dir);
37
+ NewsletterEmails::instance()->logger->error($error);
38
+ return $error;
39
+ }
40
+
41
+ self::$block_dirs[] = $dir;
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * @param string $open
47
+ * @param string $inner
48
+ * @param string $close
49
+ * @param string[] $markers
50
+ *
51
+ * @return string
52
+ */
53
+ static function wrap_html_element($open, $inner, $close, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
54
+
55
+ return $open . $markers[0] . $inner . $markers[1] . $close;
56
+ }
57
+
58
+ /**
59
+ * @param string $block
60
+ * @param string[] $markers
61
+ *
62
+ * @return string
63
+ */
64
+ static function unwrap_html_element($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
65
+ if (self::_has_markers($block, $markers)) {
66
+ self::_escape_markers($markers);
67
+ $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
68
+
69
+ $matches = array();
70
+ preg_match($pattern, $block, $matches);
71
+
72
+ return $matches[1];
73
+ }
74
+
75
+ return $block;
76
+ }
77
+
78
+ /**
79
+ * @param string $block
80
+ * @param string[] $markers
81
+ *
82
+ * @return bool
83
+ */
84
+ private static function _has_markers($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
85
+
86
+ self::_escape_markers($markers);
87
+
88
+ $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
89
+
90
+ return preg_match($pattern, $block);
91
+ }
92
+
93
+ /**
94
+ * Sources:
95
+ * - https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
96
+ *
97
+ * @param type $email
98
+ * @return type
99
+ */
100
+ static function get_html_open($email) {
101
+ $open = '<!DOCTYPE html>' . "\n";
102
+ $open .= '<html xmlns="https://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">' . "\n";
103
+ $open .= '<head>' . "\n";
104
+ $open .= '<title>{email_subject}</title>' . "\n";
105
+ $open .= '<meta charset="utf-8">' . "\n";
106
+ $open .= '<meta name="viewport" content="width=device-width, initial-scale=1">' . "\n";
107
+ $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge">' . "\n";
108
+ $open .= '<meta name="format-detection" content="address=no">' . "\n";
109
+ $open .= '<meta name="format-detection" content="telephone=no">' . "\n";
110
+ $open .= '<meta name="format-detection" content="email=no">' . "\n";
111
+ $open .= '<meta name="x-apple-disable-message-reformatting">' . "\n";
112
+
113
+ // $open .= '<!--[if !mso]><!-->' . "\n";
114
+ // $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
115
+ // $open .= '<!--<![endif]-->' . "\n";
116
+
117
+ // $open .= '<!--[if mso]>' . "\n";
118
+
119
+ $open .= '<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->' . "\n";
120
+
121
+ // $open .= '<style type="text/css">';
122
+ // $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
123
+ // $open .= 'div, td {padding:0;}';
124
+ // $open .= 'div {margin:0 !important;}';
125
+ // $open .= '</style>';
126
+ // $open .= "\n";
127
+ // $open .= '<noscript>';
128
+ // $open .= '<xml>';
129
+ // $open .= '<o:OfficeDocumentSettings>';
130
+ // $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
131
+ // $open .= '</o:OfficeDocumentSettings>';
132
+ // $open .= '</xml>';
133
+ // $open .= '</noscript>';
134
+ // $open .= "\n";
135
+ // $open .= '<![endif]-->';
136
+ // $open .= "\n";
137
+ $open .= '<style type="text/css">' . "\n";
138
+ $open .= NewsletterEmails::instance()->get_composer_css();
139
+ $open .= "\n</style>\n";
140
+ $open .= "</head>\n";
141
+ $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
142
+ $open .= "\n";
143
+ $open .= self::get_html_preheader($email);
144
+
145
+ return $open;
146
+ }
147
+
148
+ static private function get_html_preheader($email) {
149
+
150
+ if (empty($email->options['preheader'])) {
151
+ return "";
152
+ }
153
+
154
+ $preheader_text = esc_html($email->options['preheader']);
155
+ $html = "<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">$preheader_text</div>";
156
+ $html .= "\n";
157
+
158
+ return $html;
159
+ }
160
+
161
+ static function get_html_close($email) {
162
+ return "</body>\n</html>";
163
+ }
164
+
165
+ /**
166
+ *
167
+ * @param TNP_Email $email
168
+ * @return string
169
+ */
170
+ static function get_main_wrapper_open($email) {
171
+ if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
172
+ $bgcolor = '';
173
+ } else {
174
+ $bgcolor = $email->options['composer_background'];
175
+ }
176
+
177
+ return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
178
+ "<tr>\n" .
179
+ "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
180
+ }
181
+
182
+ /**
183
+ *
184
+ * @param TNP_Email $email
185
+ * @return string
186
+ */
187
+ static function get_main_wrapper_close($email) {
188
+ return "\n<!-- /tnp -->\n" .
189
+ "</td>\n" .
190
+ "</tr>\n" .
191
+ "</table>\n\n";
192
+ }
193
+
194
+ /**
195
+ * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
196
+ *
197
+ * @param string $html_email
198
+ *
199
+ * @return string
200
+ */
201
+ static function unwrap_email($html_email) {
202
+
203
+ if (self::_has_markers($html_email)) {
204
+ $html_email = self::unwrap_html_element($html_email);
205
+ } else {
206
+ //KEEP FOR OLD EMAIL COMPATIBILITY
207
+ // Extracts only the body part
208
+ $x = strpos($html_email, '<body');
209
+ if ($x) {
210
+ $x = strpos($html_email, '>', $x);
211
+ $y = strpos($html_email, '</body>');
212
+ $html_email = substr($html_email, $x + 1, $y - $x - 1);
213
+ }
214
+
215
+ /* Cleans up uncorrectly stored newsletter bodies */
216
+ $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
217
+ $html_email = preg_replace('/<meta.*?>/', '', $html_email);
218
+ $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
219
+ $html_email = trim($html_email);
220
+ }
221
+
222
+ // Required since esc_html DOES NOT escape the HTML entities (apparently)
223
+ $html_email = str_replace('&', '&amp;', $html_email);
224
+ $html_email = str_replace('"', '&quot;', $html_email);
225
+ $html_email = str_replace('<', '&lt;', $html_email);
226
+ $html_email = str_replace('>', '&gt;', $html_email);
227
+
228
+ return $html_email;
229
+ }
230
+
231
+ private static function _escape_markers(&$markers) {
232
+ $markers[0] = str_replace('/', '\/', $markers[0]);
233
+ $markers[1] = str_replace('/', '\/', $markers[1]);
234
+ }
235
+
236
+ /**
237
+ * Using the data collected inside $controls (and submitted by a form containing the
238
+ * composer fields), updates the email. The message body is completed with doctype,
239
+ * head, style and the main wrapper.
240
+ *
241
+ * @param TNP_Email $email
242
+ * @param NewsletterControls $controls
243
+ */
244
+ static function update_email($email, $controls) {
245
+ if (isset($controls->data['subject'])) {
246
+ $email->subject = $controls->data['subject'];
247
+ }
248
+
249
+ // They should be only composer options
250
+ foreach ($controls->data as $name => $value) {
251
+ if (strpos($name, 'options_') === 0) {
252
+ $email->options[substr($name, 8)] = $value;
253
+ }
254
+ }
255
+
256
+ //if (isset($controls->data['preheader'])) {
257
+ // $email->options['preheader'] = $controls->data['preheader'];
258
+ //}
259
+
260
+ $email->editor = NewsletterEmails::EDITOR_COMPOSER;
261
+
262
+ $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
263
+ $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
264
+ }
265
+
266
+ /**
267
+ * Prepares a controls object injecting the relevant fields from an email
268
+ * which cannot be directly used by controls. If $email is null or missing,
269
+ * $controls is prepared with default values.
270
+ *
271
+ * @param NewsletterControls $controls
272
+ * @param TNP_Email $email
273
+ */
274
+ static function prepare_controls($controls, $email = null) {
275
+
276
+ // Controls for a new email (which actually does not exist yet
277
+ if (!empty($email)) {
278
+
279
+ foreach ($email->options as $name => $value) {
280
+ $controls->data['options_' . $name] = $value;
281
+ }
282
+
283
+ $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
284
+ $controls->data['subject'] = $email->subject;
285
+ $controls->data['updated'] = $email->updated;
286
+ }
287
+
288
+ if (!empty($email->options['sender_email'])) {
289
+ $controls->data['sender_email'] = $email->options['sender_email'];
290
+ } else {
291
+ $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
292
+ }
293
+
294
+ if (!empty($email->options['sender_name'])) {
295
+ $controls->data['sender_name'] = $email->options['sender_name'];
296
+ } else {
297
+ $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
298
+ }
299
+
300
+ $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
301
+ }
302
+
303
+ /**
304
+ * Extract inline edited post field from inline_edit_list[]
305
+ *
306
+ * @param array $inline_edit_list
307
+ * @param string $field_type
308
+ * @param int $post_id
309
+ *
310
+ * @return string
311
+ */
312
+ static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
313
+
314
+ foreach ($inline_edit_list as $edit) {
315
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
316
+ return $edit['content'];
317
+ }
318
+ }
319
+
320
+ return '';
321
+ }
322
+
323
+ /**
324
+ * Check if inline_edit_list[] have inline edit field for specific post
325
+ *
326
+ * @param array $inline_edit_list
327
+ * @param string $field_type
328
+ * @param int $post_id
329
+ *
330
+ * @return bool
331
+ */
332
+ static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
333
+ if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
334
+ return false;
335
+ }
336
+ foreach ($inline_edit_list as $edit) {
337
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
338
+ return true;
339
+ }
340
+ }
341
+
342
+ return false;
343
+ }
344
+
345
+ /**
346
+ * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
347
+ *
348
+ * - [prefix]_url The button URL
349
+ * - [prefix]_font_family
350
+ * - [prefix]_font_size
351
+ * - [prefix]_font_weight
352
+ * - [prefix]_label
353
+ * - [prefix]_font_color The label color
354
+ * - [prefix]_background The button color
355
+ *
356
+ * TODO: Add radius and possiblt the alignment
357
+ *
358
+ * @param array $options
359
+ * @param string $prefix
360
+ * @return string
361
+ */
362
+ static function button($options, $prefix = 'button') {
363
+
364
+ if (empty($options[$prefix . '_label'])) {
365
+ return;
366
+ }
367
+ $defaults = [
368
+ $prefix . '_url' => '#',
369
+ $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
370
+ $prefix . '_font_color' => '#ffffff',
371
+ $prefix . '_font_weight' => 'bold',
372
+ $prefix . '_font_size' => 20,
373
+ $prefix . '_background' => '#256F9C',
374
+ $prefix . '_align' => 'center'
375
+ ];
376
+
377
+ $options = array_merge($defaults, array_filter($options));
378
+
379
+ $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
380
+ if (!empty($options[$prefix . '_align'])) {
381
+ $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
382
+ }
383
+ if (!empty($options[$prefix . '_width'])) {
384
+ $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
385
+ }
386
+ $b .= '>';
387
+ $b .= '<tr>';
388
+ $b .= '<td align="center" bgcolor="' . $options[$prefix . '_background'] . '" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:' . $options[$prefix . '_background'] . '" valign="middle">';
389
+ $b .= '<a href="' . $options[$prefix . '_url'] . '"';
390
+ $b .= ' style="display:inline-block;background:' . $options[$prefix . '_background'] . ';color:' . $options[$prefix . '_font_color'] . ';font-family:' . $options[$prefix . '_font_family'] . ';font-size:' . $options[$prefix . '_font_size'] . 'px;font-weight:' . $options[$prefix . '_font_weight'] . ';line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"';
391
+ $b .= ' target="_blank">';
392
+ $b .= $options[$prefix . '_label'];
393
+ $b .= '</a>';
394
+ $b .= '</td></tr></table>';
395
+ return $b;
396
+ }
397
+
398
+ /**
399
+ * Generates an IMG tag, linked if the media has an URL.
400
+ *
401
+ * @param TNP_Media $media
402
+ * @param string $style
403
+ * @return string
404
+ */
405
+ static function image($media, $attr = []) {
406
+
407
+ $default_attrs = [
408
+ 'style' => 'max-width: 100%; height: auto; display: inline-block',
409
+ 'class' => '',
410
+ 'link-style' => 'text-decoration: none; display: inline-block',
411
+ 'link-class' => null,
412
+ ];
413
+
414
+ $attr = array_merge($default_attrs, $attr);
415
+
416
+ //Class and style attribute are mutually exclusive.
417
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
418
+ if (!empty($attr['inline-class'])) {
419
+ $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
420
+ } else {
421
+ $styling = ' style="' . esc_attr($attr['style']) . '" ';
422
+ }
423
+
424
+ if (!empty($attr['class'])) {
425
+ $styling .= ' class="' . esc_attr($attr['class']) . '" ';
426
+ }
427
+
428
+ //Class and style attribute are mutually exclusive.
429
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
430
+ if (!empty($attr['link-class'])) {
431
+ $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
432
+ } else {
433
+ $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
434
+ }
435
+
436
+ $b = '';
437
+ if ($media->link) {
438
+ $b .= '<a href="' . esc_attr($media->link) . '" target="_blank" rel="noopener nofollow" style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
439
+ } else {
440
+ // The span grants images are not upscaled when fluid (two columns posts block)
441
+ $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
442
+ }
443
+ if ($media) {
444
+ $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
445
+ if ($media->height) {
446
+ $b .= ' height="' . esc_attr($media->height) . '"';
447
+ }
448
+ $b .= ' alt="' . esc_attr($media->alt) . '"'
449
+ . ' border="0"'
450
+ . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0; font-size: 12px"'
451
+ . ' class="' . esc_attr($attr['class']) . '" '
452
+ . '>';
453
+ }
454
+
455
+ if ($media->link) {
456
+ $b .= '</a>';
457
+ } else {
458
+ $b .= '</span>';
459
+ }
460
+
461
+ return $b;
462
+ }
463
+
464
+ /**
465
+ * Returns a WP media ID for the specified post (or false if nothing can be found)
466
+ * looking for the featured image or, if missing, taking the first media in the gallery and
467
+ * if again missing, searching the first reference to a media in the post content.
468
+ *
469
+ * The media ID is not checked for real existance of the associated attachment.
470
+ *
471
+ * @param int $post_id
472
+ * @return int
473
+ */
474
+ static function get_post_thumbnail_id($post_id) {
475
+ if (is_object($post_id)) {
476
+ $post_id = $post_id->ID;
477
+ }
478
+
479
+ // Find a media id to be used as featured image
480
+ $media_id = get_post_thumbnail_id($post_id);
481
+ if (!empty($media_id)) {
482
+ return $media_id;
483
+ }
484
+
485
+ $attachments = get_children(array('numberpost' => 1, 'post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order'));
486
+ if (!empty($attachments)) {
487
+ foreach ($attachments as $id => &$attachment) {
488
+ return $id;
489
+ }
490
+ }
491
+
492
+ $post = get_post($post_id);
493
+
494
+ $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
495
+ if ($matches) {
496
+ return (int) $matches[1];
497
+ }
498
+
499
+ return false;
500
+ }
501
+
502
+ /**
503
+ * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
504
+ * media has a size which best match the one requested (this is the standard WP behavior, plugins
505
+ * could change it).
506
+ *
507
+ * @param int $media_id
508
+ * @param array $size
509
+ * @return \TNP_Media
510
+ */
511
+ function get_media($media_id, $size) {
512
+ $src = wp_get_attachment_image_src($media_id, $size);
513
+ if (!$src) {
514
+ return null;
515
+ }
516
+ $media = new TNP_Media();
517
+ $media->id = $media_id;
518
+ $media->url = $src[0];
519
+ $media->width = $src[1];
520
+ $media->height = $src[2];
521
+ return $media;
522
+ }
523
+
524
+ static function post_content($post) {
525
+ $content = $post->post_content;
526
+ $content = wpautop($content);
527
+ if (true || $options['enable shortcodes']) {
528
+ remove_shortcode('gallery');
529
+ add_shortcode('gallery', 'tnp_gallery_shortcode');
530
+ $content = do_shortcode($content);
531
+ }
532
+ $content = str_replace('<p>', '<p class="paragraph">', $content);
533
+
534
+ $selected_images = array();
535
+ if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
536
+ foreach ($matches[0] as $image) {
537
+ if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
538
+ $selected_images[$image] = $attachment_id;
539
+ }
540
+ }
541
+ }
542
+
543
+ foreach ($selected_images as $image => $attachment_id) {
544
+ $src = tnp_media_resize($attachment_id, array(600, 0));
545
+ if (is_wp_error($src)) {
546
+ continue;
547
+ }
548
+ $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
549
+ }
550
+
551
+ return $content;
552
+ }
553
+
554
+ static function get_global_style_defaults() {
555
+ return [
556
+ 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
557
+ 'options_composer_title_font_size' => 32,
558
+ 'options_composer_title_font_weight' => 'normal',
559
+ 'options_composer_title_font_color' => '#222222',
560
+ 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
561
+ 'options_composer_text_font_size' => 16,
562
+ 'options_composer_text_font_weight' => 'normal',
563
+ 'options_composer_text_font_color' => '#222222',
564
+ 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
565
+ 'options_composer_button_font_size' => 16,
566
+ 'options_composer_button_font_weight' => 'normal',
567
+ 'options_composer_button_font_color' => '#FFFFFF',
568
+ 'options_composer_button_background_color' => '#256F9C',
569
+ 'options_composer_background' => '#FFFFFF',
570
+ 'options_composer_block_background' => '#FFFFFF',
571
+ ];
572
+ }
573
+
574
+ /**
575
+ * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
576
+ *
577
+ * Attributes:
578
+ * - columns: number of columns [2]
579
+ * - padding: cells padding [10]
580
+ * - responsive: il on mobile the cell should stack up [true]
581
+ * - width: the whole row width, it should reduced by the external row padding [600]
582
+ *
583
+ * @param string[] $items
584
+ * @param array $attrs
585
+ * @return string
586
+ */
587
+ static function grid($items = [], $attrs = []) {
588
+ $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
589
+ $width = (int) $attrs['width'];
590
+ $columns = (int) $attrs['columns'];
591
+ $padding = (int) $attrs['padding'];
592
+ $column_width = $width / $columns;
593
+ $td_width = 100 / $columns;
594
+ $chunks = array_chunk($items, $columns);
595
+
596
+ if ($attrs['responsive']) {
597
+
598
+ $e = '';
599
+ foreach ($chunks as &$chunk) {
600
+ $e .= '<div style="text-align:center;font-size:0;">';
601
+ $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
602
+ foreach ($chunk as &$item) {
603
+ $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
604
+
605
+ $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
606
+
607
+ // This element to add padding without deal with border-box not well supported
608
+ $e .= '<div style="padding:' . $padding . 'px;">';
609
+ $e .= $item;
610
+ $e .= '</div>';
611
+ $e .= '</div>';
612
+
613
+ $e .= '<!--[if mso]></td><![endif]-->';
614
+ }
615
+ $e .= '<!--[if mso]></tr></table><![endif]-->';
616
+ $e .= '</div>';
617
+ }
618
+
619
+ return $e;
620
+ } else {
621
+ $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
622
+ foreach ($chunks as &$chunk) {
623
+ $e .= '<tr>';
624
+ foreach ($chunk as &$item) {
625
+ $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
626
+ $e .= $item;
627
+ $e .= '</td>';
628
+ }
629
+ $e .= '</tr>';
630
+ }
631
+ $e .= '</table>';
632
+ return $e;
633
+ }
634
+ }
635
+
636
+ static function get_text_style($options, $prefix, $composer, $attrs = []) {
637
+ return self::get_style($options, $prefix, $composer, 'text', $attrs);
638
+ }
639
+
640
+ static function get_title_style($options, $prefix, $composer, $attrs = []) {
641
+ return self::get_style($options, $prefix, $composer, 'title', $attrs);
642
+ }
643
+
644
+ static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
645
+ $style = new TNP_Style();
646
+ $scale = 1.0;
647
+ if (!empty($attrs['scale'])) {
648
+ $scale = (float) $attrs['scale'];
649
+ }
650
+ if (!empty($prefix))
651
+ $prefix .= '_';
652
+
653
+ $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
654
+ $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
655
+ $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
656
+ $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
657
+ if ($type === 'button') {
658
+ $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
659
+ }
660
+ return $style;
661
+ }
662
+
663
+ static function get_button_options($options, $prefix, $composer) {
664
+ $button_options = [];
665
+ $scale = 1;
666
+ $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
667
+ $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
668
+ $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
669
+ $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
670
+ $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
671
+ $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
672
+ $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
673
+ $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
674
+ $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
675
+
676
+ return $button_options;
677
+ }
678
+
679
+ static function convert_to_text($html) {
680
+ if (!class_exists('DOMDocument')) {
681
+ return '';
682
+ }
683
+ // Replace '&' with '&amp;' in URLs to avoid warnings about inavlid entities from loadHTML()
684
+ // Todo: make this more general using a regular expression
685
+ //$logger = PlaintextNewsletterAddon::$instance->get_logger();
686
+ //$logger->debug('html="' . $html . '"');
687
+ $html = str_replace(
688
+ array('&nk=', '&nek=', '&id='),
689
+ array('&amp;nk=', '&amp;nek=', '&amp;id='),
690
+ $html);
691
+ //$logger->debug('new html="' . $html . '"');
692
+ //
693
+ $output = '';
694
+
695
+ // Prevents warnings for problems with the HTML
696
+ if (function_exists('libxml_use_internal_errors')) {
697
+ libxml_use_internal_errors(true);
698
+ }
699
+ $dom = new DOMDocument();
700
+ $r = $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html);
701
+ if (!$r) {
702
+ return '';
703
+ }
704
+ $bodylist = $dom->getElementsByTagName('body');
705
+ // Of course it should be a single element
706
+ foreach ($bodylist as $body) {
707
+ self::process_dom_element($body, $output);
708
+ }
709
+ return $output;
710
+ }
711
+
712
+ static function process_dom_element(DOMElement $parent, &$output) {
713
+ foreach ($parent->childNodes as $node) {
714
+ if (is_a($node, 'DOMElement') && ($node->tagName != 'style')) {
715
+ if ($node->tagName== 'br') {
716
+ $output .= "\n";
717
+ continue;
718
+ }
719
+ self::process_dom_element($node, $output);
720
+
721
+ // If the containing tag was a block level tag, we add a couple of line ending
722
+ if ($node->tagName == 'p' || $node->tagName == 'div' || $node->tagName == 'td') {
723
+ // Avoid more than one blank line between elements
724
+ if ((strlen($output) >= 2) && (substr($output, -2) != "\n\n")) {
725
+ $output .= "\n\n";
726
+ }
727
+ }
728
+
729
+ if ($node->tagName == 'a') {
730
+ $output .= ' (' . $node->getAttribute('href') . ') ';
731
+ continue;
732
+ }
733
+ elseif ($node->tagName == 'img') {
734
+ $output .= $node->getAttribute('alt');
735
+ }
736
+ }
737
+ elseif (is_a($node, 'DOMText')) {
738
+ $decoded = utf8_decode($node->wholeText);
739
+ if (ctype_space($decoded)) {
740
+ // Append blank only if last character output is not blank.
741
+ if ((strlen($output) > 0) && !ctype_space(substr($output, -1))) {
742
+ $output .= ' ';
743
+ }
744
+ } else {
745
+ $output .= trim($node->wholeText);
746
+ }
747
+ }
748
+ }
749
+ }
750
+ }
751
+
752
+
753
+
754
+ class TNP_Style {
755
+
756
+ var $font_family;
757
+ var $font_size;
758
+ var $font_weight;
759
+ var $font_color;
760
+ var $background;
761
+
762
+ function echo_css($scale = 1.0) {
763
+ echo 'font-size: ', round($this->font_size * $scale), 'px;';
764
+ echo 'font-family: ', $this->font_family, ';';
765
+ echo 'font-weight: ', $this->font_weight, ';';
766
+ echo 'color: ', $this->font_color, ';';
767
+ }
768
+
769
+ }
770
+
771
+ /**
772
+ * Generate multicolumn and responsive html template for email.
773
+ * Initialize class with max columns per row and start to add cells.
774
+ */
775
+ class TNP_Composer_Grid_System {
776
+
777
+ /**
778
+ * @var TNP_Composer_Grid_Row[]
779
+ */
780
+ private $rows;
781
+
782
+ /**
783
+ * @var int
784
+ */
785
+ private $cells_per_row;
786
+
787
+ /**
788
+ * @var int
789
+ */
790
+ private $cells_counter;
791
+
792
+ /**
793
+ * TNP_Composer_Grid_System constructor.
794
+ *
795
+ * @param int $columns_per_row Max columns per row
796
+ */
797
+ public function __construct($columns_per_row) {
798
+ $this->cells_per_row = $columns_per_row;
799
+ $this->cells_counter = 0;
800
+ $this->rows = [];
801
+ }
802
+
803
+ public function __toString() {
804
+ return $this->render();
805
+ }
806
+
807
+ /**
808
+ * Add cell to grid
809
+ *
810
+ * @param TNP_Composer_Grid_Cell $cell
811
+ */
812
+ public function add_cell($cell) {
813
+
814
+ if ($this->cells_counter % $this->cells_per_row === 0) {
815
+ $this->add_row(new TNP_Composer_Grid_Row());
816
+ }
817
+
818
+ $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
819
+ $this->rows[$row_idx]->add_cell($cell);
820
+ $this->cells_counter++;
821
+ }
822
+
823
+ private function add_row($row) {
824
+ $this->rows[] = $row;
825
+ }
826
+
827
+ public function render() {
828
+
829
+ $str = '';
830
+ foreach ($this->rows as $row) {
831
+ $str .= $row->render();
832
+ }
833
+
834
+ return $str;
835
+ }
836
+
837
+ }
838
+
839
+ /**
840
+ * Class TNP_Composer_Grid_Row
841
+ */
842
+ class TNP_Composer_Grid_Row {
843
+
844
+ /**
845
+ * @var TNP_Composer_Grid_Cell[]
846
+ */
847
+ private $cells;
848
+
849
+ public function __construct(...$cells) {
850
+ if (!empty($cells)) {
851
+ foreach ($cells as $cell) {
852
+ $this->add_cell($cell);
853
+ }
854
+ }
855
+ }
856
+
857
+ /**
858
+ * @param TNP_Composer_Grid_Cell $cell
859
+ */
860
+ public function add_cell($cell) {
861
+ $this->cells[] = $cell;
862
+ }
863
+
864
+ public function render() {
865
+ $rendered_cells = '';
866
+ $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
867
+ foreach ($this->cells as $cell) {
868
+ $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
869
+ }
870
+
871
+ $row_template = $this->get_template();
872
+
873
+ return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
874
+ }
875
+
876
+ private function cells_count() {
877
+ return count($this->cells);
878
+ }
879
+
880
+ private function get_template() {
881
+ return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
882
+ }
883
+
884
+ }
885
+
886
+ /**
887
+ * Class TNP_Composer_Grid_Cell
888
+ */
889
+ class TNP_Composer_Grid_Cell {
890
+
891
+ /**
892
+ * @var string
893
+ */
894
+ private $content;
895
+
896
+ /**
897
+ * @var array
898
+ */
899
+ public $args;
900
+
901
+ public function __construct($content = null, $args = []) {
902
+ $default_args = [
903
+ 'width' => '100%',
904
+ 'class' => '',
905
+ 'align' => 'left',
906
+ 'valign' => 'top'
907
+ ];
908
+
909
+ $this->args = array_merge($default_args, $args);
910
+
911
+ $this->content = $content ? $content : '';
912
+ }
913
+
914
+ public function add_content($content) {
915
+ $this->content .= $content;
916
+ }
917
+
918
+ public function render($args) {
919
+ $this->args = array_merge($this->args, $args);
920
+
921
+ $column_template = $this->get_template();
922
+ $column = str_replace(
923
+ [
924
+ 'TNP_ALIGN_PH',
925
+ 'TNP_VALIGN_PH',
926
+ 'TNP_WIDTH_PH',
927
+ 'TNP_CLASS_PH',
928
+ 'TNP_COLUMN_CONTENT_PH'
929
+ ], [
930
+ $this->args['align'],
931
+ $this->args['valign'],
932
+ $this->args['width'],
933
+ $this->args['class'],
934
+ $this->content
935
+ ], $column_template);
936
+
937
+ return $column;
938
+ }
939
+
940
+ private function get_template() {
941
+ return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
942
+ <tbody>
943
+ <tr>
944
+ <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
945
+ TNP_COLUMN_CONTENT_PH
946
+ </td>
947
+ </tr>
948
+ </tbody>
949
+ </table>";
950
+ }
951
+
952
+ }
953
+
954
+ class TNP_Composer_Component_Factory {
955
+
956
+ private $options;
957
+
958
+ /**
959
+ * TNP_Composer_Component_Factory constructor.
960
+ *
961
+ * @param Controller$controller
962
+ */
963
+ public function __construct($controller) {
964
+
965
+ }
966
+
967
+ function heading() {
968
+
969
+ }
970
+
971
+ function paragraph() {
972
+
973
+ }
974
+
975
+ function link() {
976
+
977
+ }
978
+
979
+ function button() {
980
+
981
+ }
982
+
983
+ function image() {
984
+
985
+ }
986
+
987
+ }
plugin.php CHANGED
@@ -1,1422 +1,1422 @@
1
- <?php
2
-
3
- /*
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.8
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.
11
- Text Domain: newsletter
12
- License: GPLv2 or later
13
- Requires at least: 4.6
14
- Requires PHP: 5.6
15
-
16
- Copyright 2009-2022 The Newsletter Team (email: info@thenewsletterplugin.com, web: https://www.thenewsletterplugin.com)
17
-
18
- Newsletter is free software: you can redistribute it and/or modify
19
- it under the terms of the GNU General Public License as published by
20
- the Free Software Foundation, either version 2 of the License, or
21
- any later version.
22
-
23
- Newsletter is distributed in the hope that it will be useful,
24
- but WITHOUT ANY WARRANTY; without even the implied warranty of
25
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
- GNU General Public License for more details.
27
-
28
- You should have received a copy of the GNU General Public License
29
- along with Newsletter. If not, see https://www.gnu.org/licenses/gpl-2.0.html.
30
-
31
- */
32
-
33
- if (version_compare(phpversion(), '5.6', '<')) {
34
- add_action('admin_notices', function () {
35
- echo '<div class="notice notice-error"><p>PHP version 5.6 or greater is required for Newsletter. Ask your provider to upgrade. <a href="https://www.php.net/supported-versions.php" target="_blank">Read more on PHP versions</a></p></div>';
36
- });
37
- return;
38
- }
39
-
40
- define('NEWSLETTER_VERSION', '7.4.8');
41
-
42
- global $newsletter, $wpdb;
43
-
44
- // For acceptance tests, DO NOT CHANGE
45
- if (!defined('NEWSLETTER_DEBUG'))
46
- define('NEWSLETTER_DEBUG', false);
47
-
48
- if (!defined('NEWSLETTER_EXTENSION_UPDATE'))
49
- define('NEWSLETTER_EXTENSION_UPDATE', true);
50
-
51
- if (!defined('NEWSLETTER_EMAILS_TABLE'))
52
- define('NEWSLETTER_EMAILS_TABLE', $wpdb->prefix . 'newsletter_emails');
53
-
54
- if (!defined('NEWSLETTER_USERS_TABLE'))
55
- define('NEWSLETTER_USERS_TABLE', $wpdb->prefix . 'newsletter');
56
-
57
- if (!defined('NEWSLETTER_STATS_TABLE'))
58
- define('NEWSLETTER_STATS_TABLE', $wpdb->prefix . 'newsletter_stats');
59
-
60
- if (!defined('NEWSLETTER_SENT_TABLE'))
61
- define('NEWSLETTER_SENT_TABLE', $wpdb->prefix . 'newsletter_sent');
62
-
63
- define('NEWSLETTER_SLUG', 'newsletter');
64
-
65
- define('NEWSLETTER_DIR', __DIR__);
66
- define('NEWSLETTER_INCLUDES_DIR', __DIR__ . '/includes');
67
-
68
- // Deperacted since plugin can change the plugins_url()
69
- define('NEWSLETTER_URL', WP_PLUGIN_URL . '/newsletter');
70
-
71
- if (!defined('NEWSLETTER_LIST_MAX'))
72
- define('NEWSLETTER_LIST_MAX', 40);
73
-
74
- if (!defined('NEWSLETTER_PROFILE_MAX'))
75
- define('NEWSLETTER_PROFILE_MAX', 20);
76
-
77
- if (!defined('NEWSLETTER_FORMS_MAX'))
78
- define('NEWSLETTER_FORMS_MAX', 10);
79
-
80
- if (!defined('NEWSLETTER_HEADER'))
81
- define('NEWSLETTER_HEADER', true);
82
-
83
- require_once NEWSLETTER_INCLUDES_DIR . '/module.php';
84
- require_once NEWSLETTER_INCLUDES_DIR . '/TNP.php';
85
- require_once NEWSLETTER_INCLUDES_DIR . '/cron.php';
86
-
87
- class Newsletter extends NewsletterModule {
88
-
89
- // Limits to respect to avoid memory, time or provider limits
90
- var $time_start;
91
- var $time_limit = 0;
92
- var $max_emails = null;
93
- var $mailer = null;
94
- var $action = '';
95
-
96
- /** @var Newsletter */
97
- static $instance;
98
-
99
- const STATUS_NOT_CONFIRMED = 'S';
100
- const STATUS_CONFIRMED = 'C';
101
-
102
- /**
103
- * @return Newsletter
104
- */
105
- static function instance() {
106
- if (self::$instance == null) {
107
- self::$instance = new Newsletter();
108
- }
109
- return self::$instance;
110
- }
111
-
112
- function __construct() {
113
-
114
- // Grab it before a plugin decides to remove it.
115
- if (isset($_GET['na'])) {
116
- $this->action = $_GET['na'];
117
- }
118
- if (isset($_POST['na'])) {
119
- $this->action = $_POST['na'];
120
- }
121
-
122
- $this->time_start = time();
123
-
124
- parent::__construct('main', '1.6.7', null, ['info', 'smtp']);
125
-
126
- add_action('plugins_loaded', [$this, 'hook_plugins_loaded']);
127
- add_action('init', [$this, 'hook_init'], 1);
128
- add_action('wp_loaded', [$this, 'hook_wp_loaded'], 1);
129
-
130
- add_action('newsletter', [$this, 'hook_newsletter'], 1);
131
-
132
- register_activation_hook(__FILE__, [$this, 'hook_activate']);
133
- register_deactivation_hook(__FILE__, [$this, 'hook_deactivate']);
134
-
135
- add_action('admin_init', [$this, 'hook_admin_init']);
136
-
137
- if (is_admin()) {
138
- add_action('admin_head', [$this, 'hook_admin_head']);
139
-
140
- // Protection against strange schedule removal on some installations
141
- if (!wp_next_scheduled('newsletter') && (!defined('WP_INSTALLING') || !WP_INSTALLING)) {
142
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
143
- }
144
-
145
- add_action('admin_menu', [$this, 'add_extensions_menu'], 90);
146
-
147
- add_filter('display_post_states', [$this, 'add_notice_to_chosen_profile_page_hook'], 10, 2);
148
-
149
- if ($this->is_admin_page()) {
150
- add_action('admin_enqueue_scripts', [$this, 'hook_admin_enqueue_scripts']);
151
- }
152
- }
153
- }
154
-
155
- function hook_plugins_loaded() {
156
- // Used to load dependant modules
157
- do_action('newsletter_loaded', NEWSLETTER_VERSION);
158
-
159
- if (function_exists('load_plugin_textdomain')) {
160
- load_plugin_textdomain('newsletter', false, plugin_basename(__DIR__) . '/languages');
161
- }
162
- }
163
-
164
- function hook_init() {
165
- global $wpdb;
166
-
167
- if (isset($this->options['debug']) && $this->options['debug'] == 1) {
168
- ini_set('log_errors', 1);
169
- ini_set('error_log', WP_CONTENT_DIR . '/logs/newsletter/php-' . date('Y-m') . '-' . get_option('newsletter_logger_secret') . '.txt');
170
- }
171
-
172
- add_shortcode('newsletter_replace', [$this, 'shortcode_newsletter_replace']);
173
-
174
- add_filter('site_transient_update_plugins', [$this, 'hook_site_transient_update_plugins']);
175
-
176
- if (is_admin()) {
177
- if (!class_exists('NewsletterExtensions')) {
178
-
179
- add_filter('plugin_row_meta', function ($plugin_meta, $plugin_file) {
180
-
181
- static $slugs = array();
182
- if (empty($slugs)) {
183
- $addons = $this->getTnpExtensions();
184
- if ($addons) {
185
- foreach ($addons as $addon) {
186
- $slugs[] = $addon->wp_slug;
187
- }
188
- }
189
- }
190
- if (array_search($plugin_file, $slugs) !== false) {
191
-
192
- $plugin_meta[] = '<a href="admin.php?page=newsletter_main_extensions" style="font-weight: bold">Newsletter Addons Manager required</a>';
193
- }
194
- return $plugin_meta;
195
- }, 10, 2);
196
- }
197
-
198
- add_action('in_admin_header', array($this, 'hook_in_admin_header'), 1000);
199
-
200
- if ($this->is_admin_page()) {
201
-
202
- $dismissed = get_option('newsletter_dismissed', []);
203
-
204
- if (isset($_GET['dismiss'])) {
205
- $dismissed[$_GET['dismiss']] = 1;
206
- update_option('newsletter_dismissed', $dismissed);
207
- wp_redirect($_SERVER['HTTP_REFERER']);
208
- exit();
209
- }
210
- }
211
- } else {
212
- add_action('wp_enqueue_scripts', [$this, 'hook_wp_enqueue_scripts']);
213
- }
214
-
215
- do_action('newsletter_init');
216
- }
217
-
218
- function hook_wp_loaded() {
219
- if (empty($this->action)) {
220
- return;
221
- }
222
-
223
- if ($this->action == 'test') {
224
- // This response is tested, do not change it!
225
- echo 'ok';
226
- die();
227
- }
228
-
229
- if ($this->action === 'nul') {
230
- $this->dienow('This link is not active on newsletter preview', 'You can send a test message to test subscriber to have the real working link.');
231
- }
232
-
233
- $user = $this->get_user_from_request();
234
- $email = $this->get_email_from_request();
235
- do_action('newsletter_action', $this->action, $user, $email);
236
- }
237
-
238
- function hook_activate() {
239
- // Ok, why? When the plugin is not active WordPress may remove the scheduled "newsletter" action because
240
- // the every-five-minutes schedule named "newsletter" is not present.
241
- // Since the activation does not forces an upgrade, that schedule must be reactivated here. It is activated on
242
- // the upgrade method as well for the user which upgrade the plugin without deactivte it (many).
243
- if (!wp_next_scheduled('newsletter')) {
244
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
245
- }
246
-
247
- $install_time = get_option('newsletter_install_time');
248
- if (!$install_time) {
249
- update_option('newsletter_install_time', time(), false);
250
- }
251
-
252
- touch(NEWSLETTER_LOG_DIR . '/index.html');
253
-
254
- Newsletter::instance()->upgrade();
255
- NewsletterUsers::instance()->upgrade();
256
- NewsletterEmails::instance()->upgrade();
257
- NewsletterSubscription::instance()->upgrade();
258
- NewsletterStatistics::instance()->upgrade();
259
- NewsletterProfile::instance()->upgrade();
260
- }
261
-
262
- function first_install() {
263
- parent::first_install();
264
- update_option('newsletter_show_welcome', '1', false);
265
- }
266
-
267
- function upgrade() {
268
- global $wpdb, $charset_collate;
269
-
270
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
271
-
272
- parent::upgrade();
273
-
274
- $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_emails` (
275
- `id` int(11) NOT NULL AUTO_INCREMENT,
276
- `language` varchar(10) NOT NULL DEFAULT '',
277
- `subject` varchar(255) NOT NULL DEFAULT '',
278
- `message` longtext,
279
- `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
280
- `status` enum('new','sending','sent','paused','error') NOT NULL DEFAULT 'new',
281
- `total` int(11) NOT NULL DEFAULT '0',
282
- `last_id` int(11) NOT NULL DEFAULT '0',
283
- `sent` int(11) NOT NULL DEFAULT '0',
284
- `track` int(11) NOT NULL DEFAULT '1',
285
- `list` int(11) NOT NULL DEFAULT '0',
286
- `type` varchar(50) NOT NULL DEFAULT '',
287
- `query` longtext,
288
- `editor` tinyint(4) NOT NULL DEFAULT '0',
289
- `sex` varchar(20) NOT NULL DEFAULT '',
290
- `theme` varchar(50) NOT NULL DEFAULT '',
291
- `message_text` longtext,
292
- `preferences` longtext,
293
- `send_on` int(11) NOT NULL DEFAULT '0',
294
- `token` varchar(10) NOT NULL DEFAULT '',
295
- `options` longtext,
296
- `private` tinyint(1) NOT NULL DEFAULT '0',
297
- `click_count` int(10) unsigned NOT NULL DEFAULT '0',
298
- `version` varchar(10) NOT NULL DEFAULT '',
299
- `open_count` int(10) unsigned NOT NULL DEFAULT '0',
300
- `unsub_count` int(10) unsigned NOT NULL DEFAULT '0',
301
- `error_count` int(10) unsigned NOT NULL DEFAULT '0',
302
- `stats_time` int(10) unsigned NOT NULL DEFAULT '0',
303
- `updated` int(10) unsigned NOT NULL DEFAULT '0',
304
- PRIMARY KEY (`id`)
305
- ) $charset_collate;";
306
-
307
- dbDelta($sql);
308
-
309
- // WP does not manage composite primary key when it tries to upgrade a table...
310
- $suppress_errors = $wpdb->suppress_errors(true);
311
-
312
- dbDelta("CREATE TABLE " . $wpdb->prefix . "newsletter_sent (
313
- email_id int(10) unsigned NOT NULL DEFAULT '0',
314
- user_id int(10) unsigned NOT NULL DEFAULT '0',
315
- status tinyint(1) unsigned NOT NULL DEFAULT '0',
316
- open tinyint(1) unsigned NOT NULL DEFAULT '0',
317
- time int(10) unsigned NOT NULL DEFAULT '0',
318
- error varchar(255) NOT NULL DEFAULT '',
319
- ip varchar(100) NOT NULL DEFAULT '',
320
- PRIMARY KEY (email_id,user_id),
321
- KEY user_id (user_id),
322
- KEY email_id (email_id)
323
- ) $charset_collate;");
324
- $wpdb->suppress_errors($suppress_errors);
325
-
326
- // Some setting check to avoid the common support request for mis-configurations
327
- $options = $this->get_options();
328
-
329
- if (empty($options['scheduler_max']) || !is_numeric($options['scheduler_max'])) {
330
- $options['scheduler_max'] = 100;
331
- $this->save_options($options);
332
- }
333
-
334
- wp_clear_scheduled_hook('newsletter');
335
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
336
-
337
- if (!empty($this->options['editor'])) {
338
- if (empty($this->options['roles'])) {
339
- $this->options['roles'] = array('editor');
340
- unset($this->options['editor']);
341
- }
342
- $this->save_options($this->options);
343
- }
344
-
345
- // Clear the addons and license caches
346
- delete_transient('newsletter_license_data');
347
- $this->clear_extensions_cache();
348
-
349
- touch(NEWSLETTER_LOG_DIR . '/index.html');
350
-
351
- return true;
352
- }
353
-
354
- function is_allowed() {
355
- if (current_user_can('administrator')) {
356
- return true;
357
- }
358
- if (!empty($this->options['roles'])) {
359
- foreach ($this->options['roles'] as $role) {
360
- if (current_user_can($role)) {
361
- return true;
362
- }
363
- }
364
- }
365
- return false;
366
- }
367
-
368
- function admin_menu() {
369
- if (!$this->is_allowed()) {
370
- return;
371
- }
372
-
373
- add_menu_page('Newsletter', 'Newsletter', 'exist', 'newsletter_main_index', '', plugins_url('newsletter') . '/admin/images/menu-icon.png', '30.333');
374
-
375
- $this->add_menu_page('index', __('Dashboard', 'newsletter'));
376
- $this->add_admin_page('info', __('Company info', 'newsletter'));
377
-
378
- if (current_user_can('administrator')) {
379
- $this->add_menu_page('welcome', __('Welcome', 'newsletter'));
380
- $this->add_menu_page('main', __('Settings', 'newsletter'));
381
-
382
- // Pages not on menu
383
- $this->add_admin_page('smtp', 'SMTP');
384
- $this->add_admin_page('diagnostic', __('Diagnostic', 'newsletter'));
385
- $this->add_admin_page('test', __('Test', 'newsletter'));
386
- }
387
- }
388
-
389
- function add_extensions_menu() {
390
- if (!class_exists('NewsletterExtensions')) {
391
- $this->add_menu_page('extensions', '<span style="color:#27AE60; font-weight: bold;">' . __('Addons', 'newsletter') . '</span>');
392
- }
393
- }
394
-
395
- function hook_in_admin_header() {
396
- if (!$this->is_admin_page()) {
397
- add_action('admin_notices', array($this, 'hook_admin_notices'));
398
- return;
399
- }
400
- remove_all_actions('admin_notices');
401
- remove_all_actions('all_admin_notices');
402
- add_action('admin_notices', array($this, 'hook_admin_notices'));
403
- }
404
-
405
- function hook_admin_notices() {
406
- // Check of Newsletter dedicated page
407
- if (!empty($this->options['page'])) {
408
- if (get_post_status($this->options['page']) !== 'publish') {
409
- echo '<div class="notice notice-error"><p>The Newsletter dedicated page is not published. <a href="', site_url('/wp-admin/post.php') . '?post=', $this->options['page'], '&action=edit"><strong>Edit the page</strong></a> or <a href="admin.php?page=newsletter_main_main"><strong>review the main settings</strong></a>.</p></div>';
410
- }
411
- }
412
-
413
- if (isset($this->options['debug']) && $this->options['debug'] == 1) {
414
- echo '<div class="notice notice-warning"><p>The Newsletter plugin is in <strong>debug mode</strong>. When done change it on Newsletter <a href="admin.php?page=newsletter_main_main"><strong>main settings</strong></a>. Do not keep the debug mode active on production sites.</p></div>';
415
- }
416
- }
417
-
418
- function hook_wp_enqueue_scripts() {
419
- if (empty($this->options['css_disabled']) && apply_filters('newsletter_enqueue_style', true)) {
420
- wp_enqueue_style('newsletter', plugins_url('newsletter') . '/style.css', [], NEWSLETTER_VERSION);
421
- if (!empty($this->options['css'])) {
422
- wp_add_inline_style('newsletter', $this->options['css']);
423
- }
424
- } else {
425
- if (!empty($this->options['css'])) {
426
- add_action('wp_head', function () {
427
- echo '<style>', $this->options['css'], '</style>';
428
- });
429
- }
430
- }
431
- }
432
-
433
- function hook_admin_enqueue_scripts() {
434
-
435
- $newsletter_url = plugins_url('newsletter');
436
- wp_enqueue_script('jquery-ui-tabs');
437
- wp_enqueue_script('jquery-ui-tooltip');
438
- wp_enqueue_script('jquery-ui-draggable');
439
- wp_enqueue_media();
440
-
441
- wp_enqueue_script('tnp-admin', $newsletter_url . '/admin/admin.js', ['jquery'], NEWSLETTER_VERSION);
442
-
443
- wp_enqueue_style('tnp-select2', $newsletter_url . '/vendor/select2/css/select2.min.css', [], NEWSLETTER_VERSION);
444
- wp_enqueue_script('tnp-select2', $newsletter_url . '/vendor/select2/js/select2.min.js', ['jquery'], NEWSLETTER_VERSION);
445
-
446
- wp_enqueue_style('tnp-modal', $newsletter_url . '/admin/modal.css', [], NEWSLETTER_VERSION);
447
- wp_enqueue_script('tnp-modal', $newsletter_url . '/admin/modal.js', ['jquery'], NEWSLETTER_VERSION, true);
448
-
449
- wp_enqueue_style('tnp-toast', $newsletter_url . '/admin/toast.css', [], NEWSLETTER_VERSION);
450
- wp_enqueue_script('tnp-toast', $newsletter_url . '/admin/toast.js', ['jquery'], NEWSLETTER_VERSION);
451
-
452
- wp_enqueue_style('tnp-admin-font', 'https://use.typekit.net/jlj2wjy.css');
453
- wp_enqueue_style('tnp-admin-fontawesome', $newsletter_url . '/vendor/fa/css/all.min.css', [], NEWSLETTER_VERSION);
454
- wp_enqueue_style('tnp-admin-jquery-ui', $newsletter_url . '/vendor/jquery-ui/jquery-ui.min.css', [], NEWSLETTER_VERSION);
455
- wp_enqueue_style('tnp-admin', $newsletter_url . '/admin/admin.css', [], NEWSLETTER_VERSION);
456
- wp_enqueue_style('tnp-admin-dropdown', $newsletter_url . '/admin/dropdown.css', [], NEWSLETTER_VERSION);
457
- wp_enqueue_style('tnp-admin-tabs', $newsletter_url . '/admin/tabs.css', [], NEWSLETTER_VERSION);
458
- wp_enqueue_style('tnp-admin-controls', $newsletter_url . '/admin/controls.css', [], NEWSLETTER_VERSION);
459
- wp_enqueue_style('tnp-admin-fields', $newsletter_url . '/admin/fields.css', [], NEWSLETTER_VERSION);
460
- wp_enqueue_style('tnp-admin-widgets', $newsletter_url . '/admin/widgets.css', [], NEWSLETTER_VERSION);
461
- wp_enqueue_style('tnp-admin-extensions', $newsletter_url . '/admin/extensions.css', [], NEWSLETTER_VERSION);
462
-
463
- $translations_array = array(
464
- 'save_to_update_counter' => __('Save the newsletter to update the counter!', 'newsletter')
465
- );
466
- wp_localize_script('tnp-admin', 'tnp_translations', $translations_array);
467
-
468
- wp_enqueue_script('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jquery.vmap.min.js', ['jquery'], NEWSLETTER_VERSION);
469
- wp_enqueue_script('tnp-jquery-vmap-world', $newsletter_url . '/vendor/jqvmap/jquery.vmap.world.js', ['tnp-jquery-vmap'], NEWSLETTER_VERSION);
470
- wp_enqueue_style('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jqvmap.min.css', [], NEWSLETTER_VERSION);
471
-
472
- wp_register_script('tnp-chart', $newsletter_url . '/vendor/chartjs/Chart.min.js', ['jquery'], NEWSLETTER_VERSION);
473
-
474
- wp_enqueue_script('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.js', ['jquery']);
475
- wp_enqueue_style('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.css', [], NEWSLETTER_VERSION);
476
- }
477
-
478
- function shortcode_newsletter_replace($attrs, $content) {
479
- $content = do_shortcode($content);
480
- $content = $this->replace($content, $this->get_user_from_request(), $this->get_email_from_request());
481
- return $content;
482
- }
483
-
484
- function is_admin_page() {
485
- if (!isset($_GET['page'])) {
486
- return false;
487
- }
488
- $page = $_GET['page'];
489
- return strpos($page, 'newsletter_') === 0;
490
- }
491
-
492
- function hook_admin_init() {
493
- // Verificare il contesto
494
- if (isset($_GET['page']) && $_GET['page'] === 'newsletter_main_welcome')
495
- return;
496
- if (get_option('newsletter_show_welcome')) {
497
- delete_option('newsletter_show_welcome');
498
- wp_redirect(admin_url('admin.php?page=newsletter_main_welcome'));
499
- }
500
-
501
- if ($this->is_admin_page()) {
502
- // Remove the emoji replacer to save to database the original emoji characters (see even woocommerce for the same problem)
503
- remove_action('admin_print_scripts', 'print_emoji_detection_script');
504
- }
505
- }
506
-
507
- function hook_admin_head() {
508
- // Small global rule for sidebar menu entries
509
- echo '<style>';
510
- echo '.tnp-side-menu { color: #E67E22!important; }';
511
- echo '</style>';
512
- }
513
-
514
- function relink($text, $email_id, $user_id, $email_token = '') {
515
- return NewsletterStatistics::instance()->relink($text, $email_id, $user_id, $email_token);
516
- }
517
-
518
- /**
519
- * Runs every 5 minutes and look for emails that need to be processed.
520
- */
521
- function hook_newsletter() {
522
-
523
- $this->logger->debug(__METHOD__ . '> Start');
524
-
525
- if (!$this->check_transient('engine', NEWSLETTER_CRON_INTERVAL)) {
526
- $this->logger->debug(__METHOD__ . '> Engine already active, exit');
527
- return;
528
- }
529
-
530
- $emails = $this->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " where status='sending' and send_on<" . time() . " order by id asc");
531
- $this->logger->debug(__METHOD__ . '> Emails found in sending status: ' . count($emails));
532
-
533
- foreach ($emails as $email) {
534
- $this->logger->debug(__METHOD__ . '> Start newsletter ' . $email->id);
535
- $email->options = maybe_unserialize($email->options);
536
- $r = $this->send($email);
537
- $this->logger->debug(__METHOD__ . '> End newsletter ' . $email->id);
538
- if (!$r) {
539
- $this->logger->debug(__METHOD__ . '> Engine returned false, there is no more capacity');
540
- break;
541
- }
542
- }
543
- // Remove the semaphore so the delivery engine can be activated again
544
- $this->delete_transient('engine');
545
-
546
- $this->logger->debug(__METHOD__ . '> End');
547
- }
548
-
549
- function get_send_speed($email = null) {
550
- $this->logger->debug(__METHOD__ . '> Computing delivery speed');
551
- $mailer = $this->get_mailer();
552
- $speed = (int) $mailer->get_speed();
553
- if (!$speed) {
554
- $this->logger->debug(__METHOD__ . '> Speed not set by mailer, use the default');
555
- $speed = (int) $this->options['scheduler_max'];
556
- } else {
557
- $this->logger->debug(__METHOD__ . '> Speed set by mailer');
558
- }
559
-
560
- //$speed = (int) apply_filters('newsletter_send_speed', $speed, $email);
561
- // Fail safe setting
562
- $runs_per_hour = $this->get_runs_per_hour();
563
- if (!$speed || $speed < $runs_per_hour) {
564
- return $runs_per_hour;
565
- }
566
-
567
- $this->logger->debug(__METHOD__ . '> Speed: ' . $speed);
568
- return $speed;
569
- }
570
-
571
- function get_send_delay() {
572
- return 0;
573
- }
574
-
575
- function skip_this_run($email = null) {
576
- return (boolean) apply_filters('newsletter_send_skip', false, $email);
577
- }
578
-
579
- function get_runs_per_hour() {
580
- return (int) (3600 / NEWSLETTER_CRON_INTERVAL);
581
- }
582
-
583
- function get_emails_per_run() {
584
- $speed = $this->get_send_speed();
585
- $max = (int) ($speed / $this->get_runs_per_hour());
586
-
587
- return $max;
588
- }
589
-
590
- function get_max_emails($email) {
591
- // Obsolete, here from Speed Control Addon
592
- $max = (int) apply_filters('newsletter_send_max_emails', $this->max_emails, $email);
593
-
594
- return min($max, $this->max_emails);
595
- }
596
-
597
- function fix_email($email) {
598
- if (empty($email->query)) {
599
- $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
600
- }
601
- if (empty($email->id)) {
602
- $email->id = 0;
603
- }
604
- }
605
-
606
- function send_setup() {
607
- $this->logger->debug(__METHOD__ . '> Setup delivery engine');
608
- if (is_null($this->max_emails)) {
609
- $this->max_emails = $this->get_emails_per_run();
610
- $this->logger->debug(__METHOD__ . '> Max emails: ' . $this->max_emails);
611
- ignore_user_abort(true);
612
-
613
- @set_time_limit(NEWSLETTER_CRON_INTERVAL + 30);
614
-
615
- $max_time = (int) (@ini_get('max_execution_time') * 0.95);
616
- if ($max_time == 0 || $max_time > NEWSLETTER_CRON_INTERVAL) {
617
- $max_time = (int) (NEWSLETTER_CRON_INTERVAL * 0.95);
618
- }
619
-
620
- $this->time_limit = $this->time_start + $max_time;
621
-
622
- $this->logger->debug(__METHOD__ . '> Max time set to ' . $max_time);
623
- } else {
624
- $this->logger->debug(__METHOD__ . '> Already setup');
625
- }
626
- }
627
-
628
- function time_exceeded() {
629
- if ($this->time_limit && time() > $this->time_limit) {
630
- $this->logger->info(__METHOD__ . '> Max execution time limit reached');
631
- return true;
632
- }
633
- }
634
-
635
- /**
636
- * Sends an email to targeted users or to given users. If a list of users is given (usually a list of test users)
637
- * the query inside the email to retrieve users is not used.
638
- *
639
- * @global wpdb $wpdb
640
- * @global type $newsletter_feed
641
- * @param TNP_Email $email
642
- * @param array $users
643
- * @return boolean|WP_Error True if the process completed, false if limits was reached. On false the caller should no continue to call it with other emails.
644
- */
645
- function send($email, $users = null, $test = false) {
646
- global $wpdb;
647
-
648
- if (is_array($email)) {
649
- $email = (object) $email;
650
- }
651
-
652
- $this->logger->info(__METHOD__ . '> Send email ' . $email->id);
653
-
654
- $this->send_setup();
655
-
656
- if ($this->max_emails <= 0) {
657
- $this->logger->info(__METHOD__ . '> No more capacity');
658
- return false;
659
- }
660
-
661
- $this->fix_email($email);
662
-
663
- // This stops the update of last_id and sent fields since
664
- // it's not a scheduled delivery but a test or something else (like an autoresponder)
665
- $supplied_users = $users != null;
666
-
667
- if (!$supplied_users) {
668
-
669
- if ($this->skip_this_run($email)) {
670
- return true;
671
- }
672
-
673
- // Speed change for specific email by Speed Control Addon
674
- $max_emails = $this->get_max_emails($email);
675
- if ($max_emails <= 0) {
676
- return true;
677
- }
678
-
679
- $query = $email->query;
680
- $query .= " and id>" . $email->last_id . " order by id limit " . $max_emails;
681
-
682
- $this->logger->debug(__METHOD__ . '> Query: ' . $query);
683
-
684
- //Retrieve subscribers
685
- $users = $this->get_results($query);
686
-
687
- $this->logger->debug(__METHOD__ . '> Loaded subscribers: ' . count($users));
688
-
689
- // If there was a database error, return error
690
- if ($users === false) {
691
- return new WP_Error('1', 'Unable to query subscribers, check the logs');
692
- }
693
-
694
- if (empty($users)) {
695
- $this->logger->info(__METHOD__ . '> No more users, set as sent');
696
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set status='sent', total=sent where id=" . $email->id . " limit 1");
697
- do_action('newsletter_ended_sending_newsletter', $email);
698
- return true;
699
- }
700
- } else {
701
- $this->logger->info(__METHOD__ . '> Subscribers supplied');
702
- }
703
-
704
- $start_time = microtime(true);
705
- $count = 0;
706
- $result = true;
707
-
708
- $mailer = $this->get_mailer();
709
-
710
- $batch_size = $mailer->get_batch_size();
711
-
712
- $this->logger->debug(__METHOD__ . '> Batch size: ' . $batch_size);
713
-
714
- // For batch size == 1 (normal condition) we optimize
715
- if ($batch_size == 1) {
716
-
717
- foreach ($users as $user) {
718
-
719
- $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
720
- $user = apply_filters('newsletter_send_user', $user);
721
- $message = $this->build_message($email, $user);
722
-
723
- // Save even test emails since people wants to see some stats even for test emails. Stats are reset upon the real "send" of a newsletter
724
- $this->save_sent_message($message);
725
-
726
- //Se non è un test incremento il contatore delle email spedite. Perchè incremento prima di spedire??
727
- if (!$test) {
728
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
729
- }
730
-
731
- $r = $mailer->send($message);
732
-
733
- if (!empty($message->error)) {
734
- $this->logger->error($message);
735
- $this->save_sent_message($message);
736
- }
737
-
738
- if (is_wp_error($r)) {
739
- $this->logger->error($r);
740
-
741
- // For fatal error, the newsletter status i changed to error (and the delivery stopped)
742
- if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
743
- $this->set_error_state_of_email($email, $r->get_error_message());
744
- return $r;
745
- }
746
- }
747
-
748
- if (!$supplied_users && !$test && $this->time_exceeded()) {
749
- $result = false;
750
- break;
751
- }
752
- }
753
-
754
- $this->max_emails--;
755
- $count++;
756
- } else {
757
-
758
- $chunks = array_chunk($users, $batch_size);
759
-
760
- foreach ($chunks as $chunk) {
761
-
762
- $messages = [];
763
-
764
- // Peeparing a batch of messages
765
- foreach ($chunk as $user) {
766
- $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
767
- $user = apply_filters('newsletter_send_user', $user);
768
- $message = $this->build_message($email, $user);
769
- $this->save_sent_message($message);
770
- $messages[] = $message;
771
-
772
- if (!$test) {
773
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
774
- }
775
- $this->max_emails--;
776
- $count++;
777
- }
778
-
779
- $r = $mailer->send_batch($messages);
780
-
781
- // Updating the status of the sent messages
782
- foreach ($messages as $message) {
783
- if (!empty($message->error)) {
784
- $this->save_sent_message($message);
785
- }
786
- }
787
-
788
- // The batch went in error
789
- if (is_wp_error($r)) {
790
- $this->logger->error($r);
791
-
792
- if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
793
- $this->set_error_state_of_email($email, $r->get_error_message());
794
- return $r;
795
- }
796
- }
797
-
798
- if (!$supplied_users && !$test && $this->time_exceeded()) {
799
- $result = false;
800
- break;
801
- }
802
- }
803
- }
804
-
805
- $end_time = microtime(true);
806
-
807
- // Stats only for newsletter with enough emails in a batch (we exclude the Autoresponder since it send one email per call)
808
- if (!$test && !$supplied_users && $count > 5) {
809
- $this->update_send_stats($start_time, $end_time, $count, $result);
810
- }
811
-
812
- // Cached general statistics are reset
813
- if (!$test) {
814
- NewsletterStatistics::instance()->reset_stats_time($email->id);
815
- }
816
-
817
- $this->logger->info(__METHOD__ . '> End run for email ' . $email->id);
818
-
819
- return $result;
820
- }
821
-
822
- function update_send_stats($start_time, $end_time, $count, $result) {
823
- $send_calls = get_option('newsletter_diagnostic_send_calls', []);
824
- $send_calls[] = [$start_time, $end_time, $count, $result];
825
-
826
- if (count($send_calls) > 100) {
827
- array_shift($send_calls);
828
- }
829
-
830
- update_option('newsletter_diagnostic_send_calls', $send_calls, false);
831
- }
832
-
833
- /**
834
- * @param TNP_Email $email
835
- */
836
- private function set_error_state_of_email($email, $message = '') {
837
- // Handle only message type at the moment
838
- if ($email->type !== 'message') {
839
- return;
840
- }
841
-
842
- do_action('newsletter_error_on_sending', $email, $message);
843
-
844
- $edited_email = new TNP_Email();
845
- $edited_email->id = $email->id;
846
- $edited_email->status = TNP_Email::STATUS_ERROR;
847
- $edited_email->options = $email->options;
848
- $edited_email->options['error_message'] = $message;
849
-
850
- $this->save_email($edited_email);
851
- }
852
-
853
- /**
854
- *
855
- * @param TNP_Email $email
856
- * @param TNP_User $user
857
- * @return \TNP_Mailer_Message
858
- */
859
- function build_message($email, $user) {
860
-
861
- $message = new TNP_Mailer_Message();
862
-
863
- $message->to = $user->email;
864
-
865
- $message->headers = [];
866
- $message->headers['Precedence'] = 'bulk';
867
- $message->headers['X-Newsletter-Email-Id'] = $email->id;
868
- $message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
869
- $message->headers = apply_filters('newsletter_message_headers', $message->headers, $email, $user);
870
-
871
- $message->body = preg_replace('/data-json=".*?"/is', '', $email->message);
872
- $message->body = preg_replace('/ +/s', ' ', $message->body);
873
- $message->body = $this->replace($message->body, $user, $email);
874
- if ($this->options['do_shortcodes']) {
875
- $message->body = do_shortcode($message->body);
876
- }
877
- $message->body = apply_filters('newsletter_message_html', $message->body, $email, $user);
878
-
879
- $message->body_text = $this->replace($email->message_text, $user, $email);
880
- $message->body_text = apply_filters('newsletter_message_text', $message->body_text, $email, $user);
881
-
882
- if ($email->track == 1) {
883
- $message->body = $this->relink($message->body, $email->id, $user->id, $email->token);
884
- }
885
-
886
- $message->subject = $this->replace($email->subject, $user);
887
- $message->subject = apply_filters('newsletter_message_subject', $message->subject, $email, $user);
888
-
889
- if (!empty($email->options['sender_email'])) {
890
- $message->from = $email->options['sender_email'];
891
- } else {
892
- $message->from = $this->options['sender_email'];
893
- }
894
-
895
- if (!empty($email->options['sender_name'])) {
896
- $message->from_name = $email->options['sender_name'];
897
- } else {
898
- $message->from_name = $this->options['sender_name'];
899
- }
900
-
901
- $message->email_id = $email->id;
902
- $message->user_id = $user->id;
903
-
904
- return apply_filters('newsletter_message', $message, $email, $user);
905
- }
906
-
907
- /**
908
- *
909
- * @param TNP_Mailer_Message $message
910
- * @param int $status
911
- * @param string $error
912
- */
913
- function save_sent_message($message) {
914
- global $wpdb;
915
-
916
- if (!$message->user_id || !$message->email_id) {
917
- return;
918
- }
919
- $status = empty($message->error) ? 0 : 1;
920
-
921
- $error = mb_substr($message->error, 0, 250);
922
-
923
- $this->query($wpdb->prepare("insert into " . $wpdb->prefix . 'newsletter_sent (user_id, email_id, time, status, error) values (%d, %d, %d, %d, %s) on duplicate key update time=%d, status=%d, error=%s', $message->user_id, $message->email_id, time(), $status, $error, time(), $status, $error));
924
- }
925
-
926
- /**
927
- * @deprecated since version 7.3.0
928
- */
929
- function limits_exceeded() {
930
- return false;
931
- }
932
-
933
- /**
934
- * @deprecated since version 6.0.0
935
- */
936
- function register_mail_method($callable) {
937
-
938
- }
939
-
940
- function register_mailer($mailer) {
941
- if ($mailer instanceof NewsletterMailer) {
942
- $this->mailer = $mailer;
943
- }
944
- }
945
-
946
- /**
947
- * Returns the current registered mailer which must be used to send emails.
948
- *
949
- * @return NewsletterMailer
950
- */
951
- function get_mailer() {
952
- if ($this->mailer) {
953
- return $this->mailer;
954
- }
955
-
956
- do_action('newsletter_register_mailer');
957
-
958
- if (!$this->mailer) {
959
- // Compatibility
960
- $smtp = $this->get_options('smtp');
961
- if (!empty($smtp['enabled'])) {
962
- $this->mailer = new NewsletterDefaultSMTPMailer($smtp);
963
- } else {
964
- $this->mailer = new NewsletterDefaultMailer();
965
- }
966
- }
967
- return $this->mailer;
968
- }
969
-
970
- /**
971
- *
972
- * @param TNP_Mailer_Message $message
973
- * @return type
974
- */
975
- function deliver($message) {
976
- $mailer = $this->get_mailer();
977
- if (empty($message->from))
978
- $message->from = $this->options['sender_email'];
979
- if (empty($message->from_name))
980
- $mailer->from_name = $this->options['sender_name'];
981
- return $mailer->send($message);
982
- }
983
-
984
- /**
985
- *
986
- * @param type $to
987
- * @param type $subject
988
- * @param string|array $message If string is considered HTML, is array should contains the keys "html" and "text"
989
- * @param type $headers
990
- * @param type $enqueue
991
- * @param type $from
992
- * @return boolean
993
- */
994
- function mail($to, $subject, $message, $headers = array(), $enqueue = false, $from = false) {
995
-
996
- if (empty($subject)) {
997
- $this->logger->error('mail> Subject empty, skipped');
998
- return true;
999
- }
1000
-
1001
- $mailer_message = new TNP_Mailer_Message();
1002
- $mailer_message->to = $to;
1003
- $mailer_message->subject = $subject;
1004
- $mailer_message->from = $this->options['sender_email'];
1005
- $mailer_message->from_name = $this->options['sender_name'];
1006
-
1007
- if (!empty($headers)) {
1008
- $mailer_message->headers = $headers;
1009
- }
1010
- $mailer_message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
1011
-
1012
- // Message carrige returns and line feeds clean up
1013
- if (!is_array($message)) {
1014
- $mailer_message->body = $this->clean_eol($message);
1015
- } else {
1016
- if (!empty($message['text'])) {
1017
- $mailer_message->body_text = $this->clean_eol($message['text']);
1018
- }
1019
-
1020
- if (!empty($message['html'])) {
1021
- $mailer_message->body = $this->clean_eol($message['html']);
1022
- }
1023
- }
1024
-
1025
- $this->logger->debug($mailer_message);
1026
-
1027
- $mailer = $this->get_mailer();
1028
-
1029
- $r = $mailer->send($mailer_message);
1030
-
1031
- return !is_wp_error($r);
1032
- }
1033
-
1034
- function hook_deactivate() {
1035
- wp_clear_scheduled_hook('newsletter');
1036
- }
1037
-
1038
- function find_file($file1, $file2) {
1039
- if (is_file($file1))
1040
- return $file1;
1041
- return $file2;
1042
- }
1043
-
1044
- function hook_site_transient_update_plugins($value) {
1045
- static $extra_response = array();
1046
-
1047
- //$this->logger->debug('Update plugins transient called');
1048
-
1049
- if (!$value || !is_object($value)) {
1050
- //$this->logger->info('Empty object');
1051
- return $value;
1052
- }
1053
-
1054
- if (!isset($value->response) || !is_array($value->response)) {
1055
- $value->response = array();
1056
- }
1057
-
1058
- // Already computed? Use it! (this filter is called many times in a single request)
1059
- if ($extra_response) {
1060
- //$this->logger->debug('Already updated');
1061
- $value->response = array_merge($value->response, $extra_response);
1062
- return $value;
1063
- }
1064
-
1065
- $extensions = $this->getTnpExtensions();
1066
-
1067
- // Ops...
1068
- if (!$extensions) {
1069
- return $value;
1070
- }
1071
-
1072
- foreach ($extensions as $extension) {
1073
- unset($value->response[$extension->wp_slug]);
1074
- unset($value->no_update[$extension->wp_slug]);
1075
- }
1076
-
1077
- // Someone doesn't want our addons updated, let respect it (this constant should be defined in wp-config.php)
1078
- if (!NEWSLETTER_EXTENSION_UPDATE) {
1079
- //$this->logger->info('Updates disabled');
1080
- return $value;
1081
- }
1082
-
1083
- include_once(ABSPATH . 'wp-admin/includes/plugin.php');
1084
-
1085
- // Ok, that is really bad (should we remove it? is there a minimum WP version?)
1086
- if (!function_exists('get_plugin_data')) {
1087
- //$this->logger->error('No get_plugin_data function available!');
1088
- return $value;
1089
- }
1090
-
1091
- $license_key = $this->get_license_key();
1092
-
1093
- // Here we prepare the update information BUT do not add the link to the package which is privided
1094
- // by our Addons Manager (due to WP policies)
1095
- foreach ($extensions as $extension) {
1096
-
1097
- // Patch for names convention
1098
- $extension->plugin = $extension->wp_slug;
1099
-
1100
- //$this->logger->debug('Processing ' . $extension->plugin);
1101
- //$this->logger->debug($extension);
1102
-
1103
- $plugin_data = false;
1104
- if (file_exists(WP_PLUGIN_DIR . '/' . $extension->plugin)) {
1105
- $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1106
- } else if (file_exists(WPMU_PLUGIN_DIR . '/' . $extension->plugin)) {
1107
- $plugin_data = get_plugin_data(WPMU_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1108
- }
1109
-
1110
- if (!$plugin_data) {
1111
- //$this->logger->debug('Seems not installed');
1112
- continue;
1113
- }
1114
-
1115
- $plugin = new stdClass();
1116
- $plugin->id = $extension->id;
1117
- $plugin->slug = $extension->slug;
1118
- $plugin->plugin = $extension->plugin;
1119
- $plugin->new_version = $extension->version;
1120
- $plugin->url = $extension->url;
1121
- if (class_exists('NewsletterExtensions')) {
1122
- // NO filters here!
1123
- $plugin->package = NewsletterExtensions::$instance->get_package($extension->id, $license_key);
1124
- } else {
1125
- $plugin->package = '';
1126
- }
1127
- // [banners] => Array
1128
- // (
1129
- // [2x] => https://ps.w.org/wp-rss-aggregator/assets/banner-1544x500.png?rev=2040548
1130
- // [1x] => https://ps.w.org/wp-rss-aggregator/assets/banner-772x250.png?rev=2040548
1131
- // )
1132
- // [icons] => Array
1133
- // (
1134
- // [2x] => https://ps.w.org/advanced-custom-fields/assets/icon-256x256.png?rev=1082746
1135
- // [1x] => https://ps.w.org/advanced-custom-fields/assets/icon-128x128.png?rev=1082746
1136
- // )
1137
- if (version_compare($extension->version, $plugin_data['Version']) > 0) {
1138
- //$this->logger->debug('There is a new version');
1139
- $extra_response[$extension->plugin] = $plugin;
1140
- } else {
1141
- // Maybe useless...
1142
- //$this->logger->debug('There is NOT a new version');
1143
- $value->no_update[$extension->plugin] = $plugin;
1144
- }
1145
- //$this->logger->debug('Added');
1146
- }
1147
-
1148
- $value->response = array_merge($value->response, $extra_response);
1149
-
1150
- return $value;
1151
- }
1152
-
1153
- /**
1154
- * @deprecated since version 6.1.9
1155
- */
1156
- function get_extension_version($extension_id) {
1157
- return null;
1158
- }
1159
-
1160
- /**
1161
- * @deprecated since version 6.1.9
1162
- */
1163
- function set_extension_update_data($value, $extension) {
1164
- return $value;
1165
- }
1166
-
1167
- /**
1168
- * Retrieve the extensions form the tnp site
1169
- * @return array
1170
- */
1171
- function getTnpExtensions() {
1172
-
1173
- $extensions_json = get_transient('tnp_extensions_json');
1174
-
1175
- if (empty($extensions_json)) {
1176
- $url = "http://www.thenewsletterplugin.com/wp-content/extensions.json?ver=" . NEWSLETTER_VERSION;
1177
- $extensions_response = wp_remote_get($url);
1178
-
1179
- if (is_wp_error($extensions_response)) {
1180
- // Cache anyway for blogs which cannot connect outside
1181
- $extensions_json = '[]';
1182
- set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1183
- $this->logger->error($extensions_response);
1184
- } else {
1185
-
1186
- $extensions_json = wp_remote_retrieve_body($extensions_response);
1187
-
1188
- // Not clear cases
1189
- if (empty($extensions_json) || !json_decode($extensions_json)) {
1190
- $this->logger->error('Invalid json from thenewsletterplugin.com: retrying in 72 hours');
1191
- $this->logger->error('JSON: ' . $extensions_json);
1192
- $extensions_json = '[]';
1193
- }
1194
- set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1195
- }
1196
- }
1197
-
1198
- $extensions = json_decode($extensions_json);
1199
-
1200
- return $extensions;
1201
- }
1202
-
1203
- function clear_extensions_cache() {
1204
- delete_transient('tnp_extensions_json');
1205
- }
1206
-
1207
- var $panels = array();
1208
-
1209
- function add_panel($key, $panel) {
1210
- if (!isset($this->panels[$key]))
1211
- $this->panels[$key] = array();
1212
- if (!isset($panel['id']))
1213
- $panel['id'] = sanitize_key($panel['label']);
1214
- $this->panels[$key][] = $panel;
1215
- }
1216
-
1217
- function has_license() {
1218
- return !empty($this->options['contract_key']);
1219
- }
1220
-
1221
- function get_sender_name() {
1222
- return $this->options['sender_name'];
1223
- }
1224
-
1225
- function get_sender_email() {
1226
- return $this->options['sender_email'];
1227
- }
1228
-
1229
- /**
1230
- *
1231
- * @return int
1232
- */
1233
- function get_newsletter_page_id() {
1234
- return (int) $this->options['page'];
1235
- }
1236
-
1237
- /**
1238
- *
1239
- * @return WP_Post
1240
- */
1241
- function get_newsletter_page() {
1242
- $page_id = $this->get_newsletter_page_id();
1243
- if (!$page_id)
1244
- return false;
1245
- return get_post($this->get_newsletter_page_id());
1246
- }
1247
-
1248
- /**
1249
- * Returns the Newsletter dedicated page URL or an alternative URL if that page if not
1250
- * configured or not available.
1251
- *
1252
- * @staticvar string $url
1253
- * @return string
1254
- */
1255
- function get_newsletter_page_url($language = '') {
1256
-
1257
- $page = $this->get_newsletter_page();
1258
-
1259
- if (!$page || $page->post_status !== 'publish') {
1260
- return $this->build_action_url('m');
1261
- }
1262
-
1263
- $newsletter_page_url = get_permalink($page->ID);
1264
- if ($language && $newsletter_page_url) {
1265
- if (class_exists('SitePress')) {
1266
- $newsletter_page_url = apply_filters('wpml_permalink', $newsletter_page_url, $language, true);
1267
- }
1268
- if (function_exists('pll_get_post')) {
1269
- $translated_page = get_permalink(pll_get_post($page->ID, $language));
1270
- if ($translated_page) {
1271
- $newsletter_page_url = $translated_page;
1272
- }
1273
- }
1274
- }
1275
-
1276
- return $newsletter_page_url;
1277
- }
1278
-
1279
- function get_license_key() {
1280
- if (defined('NEWSLETTER_LICENSE_KEY')) {
1281
- return NEWSLETTER_LICENSE_KEY;
1282
- } else {
1283
- if (!empty($this->options['contract_key'])) {
1284
- return trim($this->options['contract_key']);
1285
- }
1286
- }
1287
- return false;
1288
- }
1289
-
1290
- /**
1291
- * Get the data connected to the specified license code on man settings.
1292
- *
1293
- * - false if no license is present
1294
- * - WP_Error if something went wrong if getting the license data
1295
- * - object with expiration and addons list
1296
- *
1297
- * @param boolean $refresh
1298
- * @return \WP_Error|boolean|object
1299
- */
1300
- function get_license_data($refresh = false) {
1301
-
1302
- $this->logger->debug('Getting license data');
1303
-
1304
- $license_key = $this->get_license_key();
1305
- if (empty($license_key)) {
1306
- $this->logger->debug('License was empty');
1307
- delete_transient('newsletter_license_data');
1308
- return false;
1309
- }
1310
-
1311
- if (!$refresh) {
1312
- $license_data = get_transient('newsletter_license_data');
1313
- if ($license_data !== false && is_object($license_data)) {
1314
- $this->logger->debug('License data found on cache');
1315
- return $license_data;
1316
- }
1317
- }
1318
-
1319
- $this->logger->debug('Refreshing the license data');
1320
-
1321
- $license_data_url = 'https://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/get-license-data.php';
1322
-
1323
- $response = wp_remote_post($license_data_url, array(
1324
- 'body' => array('k' => $license_key)
1325
- ));
1326
-
1327
- // Fall back to http...
1328
- if (is_wp_error($response)) {
1329
- $this->logger->error($response);
1330
- $this->logger->error('Falling back to http');
1331
- $license_data_url = str_replace('https', 'http', $license_data_url);
1332
- $response = wp_remote_post($license_data_url, array(
1333
- 'body' => array('k' => $license_key)
1334
- ));
1335
- if (is_wp_error($response)) {
1336
- $this->logger->error($response);
1337
- set_transient('newsletter_license_data', $response, DAY_IN_SECONDS);
1338
- return $response;
1339
- }
1340
- }
1341
-
1342
- $download_message = 'You can download all addons from www.thenewsletterplugin.com if your license is valid.';
1343
-
1344
- if (wp_remote_retrieve_response_code($response) != '200') {
1345
- $this->logger->error('license data error: ' . wp_remote_retrieve_response_code($response));
1346
- $data = new WP_Error(wp_remote_retrieve_response_code($response), 'License validation service error. <br>' . $download_message);
1347
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1348
- return $data;
1349
- }
1350
-
1351
- $json = wp_remote_retrieve_body($response);
1352
- $data = json_decode($json);
1353
-
1354
- if (!is_object($data)) {
1355
- $this->logger->error($json);
1356
- $data = new WP_Error(1, 'License validation service error. <br>' . $download_message);
1357
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1358
- return $data;
1359
- }
1360
-
1361
- if (isset($data->message)) {
1362
- $data = new WP_Error(1, $data->message . ' (check the license on Newsletter main settings)');
1363
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1364
- return $data;
1365
- }
1366
-
1367
- $expiration = WEEK_IN_SECONDS;
1368
- // If the license expires in few days, make the transient live only few days, so it will be refreshed
1369
- if ($data->expire > time() && $data->expire - time() < WEEK_IN_SECONDS) {
1370
- $expiration = $data->expire - time();
1371
- }
1372
- set_transient('newsletter_license_data', $data, $expiration);
1373
-
1374
- return $data;
1375
- }
1376
-
1377
- /**
1378
- * @deprecated
1379
- * @param type $license_key
1380
- * @return \WP_Error
1381
- */
1382
- public static function check_license($license_key) {
1383
- $response = wp_remote_get('http://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/check.php?k=' . urlencode($license_key), array('sslverify' => false));
1384
- if (is_wp_error($response)) {
1385
- /* @var $response WP_Error */
1386
- return new WP_Error(-1, 'It seems that your blog cannot contact the license validator. Ask your provider to unlock the HTTP/HTTPS connections to www.thenewsletterplugin.com<br>'
1387
- . esc_html($response->get_error_code()) . ' - ' . esc_html($response->get_error_message()));
1388
- } else if ($response['response']['code'] != 200) {
1389
- return new WP_Error(-1, '[' . $response['response']['code'] . '] The license seems expired or not valid, please check your <a href="https://www.thenewsletterplugin.com/account">license code and status</a>, thank you.'
1390
- . '<br>You can anyway download the professional extension from https://www.thenewsletterplugin.com.');
1391
- } elseif ($expires = json_decode(wp_remote_retrieve_body($response))) {
1392
- return array('expires' => $expires->expire, 'message' => 'Your license is valid and expires on ' . esc_html(date('Y-m-d', $expires->expire)));
1393
- } else {
1394
- return new WP_Error(-1, 'Unable to detect the license expiration. Debug data to report to the support: <code>' . esc_html(wp_remote_retrieve_body($response)) . '</code>');
1395
- }
1396
- }
1397
-
1398
- function add_notice_to_chosen_profile_page_hook($post_states, $post) {
1399
-
1400
- if ($post->ID == $this->options['page']) {
1401
- $post_states[] = __('Newsletter plugin page, do not delete', 'newsletter');
1402
- }
1403
-
1404
- return $post_states;
1405
- }
1406
-
1407
- }
1408
-
1409
- $newsletter = Newsletter::instance();
1410
-
1411
- if (is_admin()) {
1412
- require_once NEWSLETTER_DIR . '/system/system.php';
1413
- }
1414
-
1415
- require_once NEWSLETTER_DIR . '/subscription/subscription.php';
1416
- require_once NEWSLETTER_DIR . '/unsubscription/unsubscription.php';
1417
- require_once NEWSLETTER_DIR . '/profile/profile.php';
1418
- require_once NEWSLETTER_DIR . '/emails/emails.php';
1419
- require_once NEWSLETTER_DIR . '/users/users.php';
1420
- require_once NEWSLETTER_DIR . '/statistics/statistics.php';
1421
- require_once NEWSLETTER_DIR . '/widget/standard.php';
1422
- require_once NEWSLETTER_DIR . '/widget/minimal.php';
1
+ <?php
2
+
3
+ /*
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.9
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.
11
+ Text Domain: newsletter
12
+ License: GPLv2 or later
13
+ Requires at least: 4.6
14
+ Requires PHP: 5.6
15
+
16
+ Copyright 2009-2022 The Newsletter Team (email: info@thenewsletterplugin.com, web: https://www.thenewsletterplugin.com)
17
+
18
+ Newsletter is free software: you can redistribute it and/or modify
19
+ it under the terms of the GNU General Public License as published by
20
+ the Free Software Foundation, either version 2 of the License, or
21
+ any later version.
22
+
23
+ Newsletter is distributed in the hope that it will be useful,
24
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
25
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
+ GNU General Public License for more details.
27
+
28
+ You should have received a copy of the GNU General Public License
29
+ along with Newsletter. If not, see https://www.gnu.org/licenses/gpl-2.0.html.
30
+
31
+ */
32
+
33
+ if (version_compare(phpversion(), '5.6', '<')) {
34
+ add_action('admin_notices', function () {
35
+ echo '<div class="notice notice-error"><p>PHP version 5.6 or greater is required for Newsletter. Ask your provider to upgrade. <a href="https://www.php.net/supported-versions.php" target="_blank">Read more on PHP versions</a></p></div>';
36
+ });
37
+ return;
38
+ }
39
+
40
+ define('NEWSLETTER_VERSION', '7.4.9');
41
+
42
+ global $newsletter, $wpdb;
43
+
44
+ // For acceptance tests, DO NOT CHANGE
45
+ if (!defined('NEWSLETTER_DEBUG'))
46
+ define('NEWSLETTER_DEBUG', false);
47
+
48
+ if (!defined('NEWSLETTER_EXTENSION_UPDATE'))
49
+ define('NEWSLETTER_EXTENSION_UPDATE', true);
50
+
51
+ if (!defined('NEWSLETTER_EMAILS_TABLE'))
52
+ define('NEWSLETTER_EMAILS_TABLE', $wpdb->prefix . 'newsletter_emails');
53
+
54
+ if (!defined('NEWSLETTER_USERS_TABLE'))
55
+ define('NEWSLETTER_USERS_TABLE', $wpdb->prefix . 'newsletter');
56
+
57
+ if (!defined('NEWSLETTER_STATS_TABLE'))
58
+ define('NEWSLETTER_STATS_TABLE', $wpdb->prefix . 'newsletter_stats');
59
+
60
+ if (!defined('NEWSLETTER_SENT_TABLE'))
61
+ define('NEWSLETTER_SENT_TABLE', $wpdb->prefix . 'newsletter_sent');
62
+
63
+ define('NEWSLETTER_SLUG', 'newsletter');
64
+
65
+ define('NEWSLETTER_DIR', __DIR__);
66
+ define('NEWSLETTER_INCLUDES_DIR', __DIR__ . '/includes');
67
+
68
+ // Deperacted since plugin can change the plugins_url()
69
+ define('NEWSLETTER_URL', WP_PLUGIN_URL . '/newsletter');
70
+
71
+ if (!defined('NEWSLETTER_LIST_MAX'))
72
+ define('NEWSLETTER_LIST_MAX', 40);
73
+
74
+ if (!defined('NEWSLETTER_PROFILE_MAX'))
75
+ define('NEWSLETTER_PROFILE_MAX', 20);
76
+
77
+ if (!defined('NEWSLETTER_FORMS_MAX'))
78
+ define('NEWSLETTER_FORMS_MAX', 10);
79
+
80
+ if (!defined('NEWSLETTER_HEADER'))
81
+ define('NEWSLETTER_HEADER', true);
82
+
83
+ require_once NEWSLETTER_INCLUDES_DIR . '/module.php';
84
+ require_once NEWSLETTER_INCLUDES_DIR . '/TNP.php';
85
+ require_once NEWSLETTER_INCLUDES_DIR . '/cron.php';
86
+
87
+ class Newsletter extends NewsletterModule {
88
+
89
+ // Limits to respect to avoid memory, time or provider limits
90
+ var $time_start;
91
+ var $time_limit = 0;
92
+ var $max_emails = null;
93
+ var $mailer = null;
94
+ var $action = '';
95
+
96
+ /** @var Newsletter */
97
+ static $instance;
98
+
99
+ const STATUS_NOT_CONFIRMED = 'S';
100
+ const STATUS_CONFIRMED = 'C';
101
+
102
+ /**
103
+ * @return Newsletter
104
+ */
105
+ static function instance() {
106
+ if (self::$instance == null) {
107
+ self::$instance = new Newsletter();
108
+ }
109
+ return self::$instance;
110
+ }
111
+
112
+ function __construct() {
113
+
114
+ // Grab it before a plugin decides to remove it.
115
+ if (isset($_GET['na'])) {
116
+ $this->action = $_GET['na'];
117
+ }
118
+ if (isset($_POST['na'])) {
119
+ $this->action = $_POST['na'];
120
+ }
121
+
122
+ $this->time_start = time();
123
+
124
+ parent::__construct('main', '1.6.7', null, ['info', 'smtp']);
125
+
126
+ add_action('plugins_loaded', [$this, 'hook_plugins_loaded']);
127
+ add_action('init', [$this, 'hook_init'], 1);
128
+ add_action('wp_loaded', [$this, 'hook_wp_loaded'], 1);
129
+
130
+ add_action('newsletter', [$this, 'hook_newsletter'], 1);
131
+
132
+ register_activation_hook(__FILE__, [$this, 'hook_activate']);
133
+ register_deactivation_hook(__FILE__, [$this, 'hook_deactivate']);
134
+
135
+ add_action('admin_init', [$this, 'hook_admin_init']);
136
+
137
+ if (is_admin()) {
138
+ add_action('admin_head', [$this, 'hook_admin_head']);
139
+
140
+ // Protection against strange schedule removal on some installations
141
+ if (!wp_next_scheduled('newsletter') && (!defined('WP_INSTALLING') || !WP_INSTALLING)) {
142
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
143
+ }
144
+
145
+ add_action('admin_menu', [$this, 'add_extensions_menu'], 90);
146
+
147
+ add_filter('display_post_states', [$this, 'add_notice_to_chosen_profile_page_hook'], 10, 2);
148
+
149
+ if ($this->is_admin_page()) {
150
+ add_action('admin_enqueue_scripts', [$this, 'hook_admin_enqueue_scripts']);
151
+ }
152
+ }
153
+ }
154
+
155
+ function hook_plugins_loaded() {
156
+ // Used to load dependant modules
157
+ do_action('newsletter_loaded', NEWSLETTER_VERSION);
158
+
159
+ if (function_exists('load_plugin_textdomain')) {
160
+ load_plugin_textdomain('newsletter', false, plugin_basename(__DIR__) . '/languages');
161
+ }
162
+ }
163
+
164
+ function hook_init() {
165
+ global $wpdb;
166
+
167
+ if (isset($this->options['debug']) && $this->options['debug'] == 1) {
168
+ ini_set('log_errors', 1);
169
+ ini_set('error_log', WP_CONTENT_DIR . '/logs/newsletter/php-' . date('Y-m') . '-' . get_option('newsletter_logger_secret') . '.txt');
170
+ }
171
+
172
+ add_shortcode('newsletter_replace', [$this, 'shortcode_newsletter_replace']);
173
+
174
+ add_filter('site_transient_update_plugins', [$this, 'hook_site_transient_update_plugins']);
175
+
176
+ if (is_admin()) {
177
+ if (!class_exists('NewsletterExtensions')) {
178
+
179
+ add_filter('plugin_row_meta', function ($plugin_meta, $plugin_file) {
180
+
181
+ static $slugs = array();
182
+ if (empty($slugs)) {
183
+ $addons = $this->getTnpExtensions();
184
+ if ($addons) {
185
+ foreach ($addons as $addon) {
186
+ $slugs[] = $addon->wp_slug;
187
+ }
188
+ }
189
+ }
190
+ if (array_search($plugin_file, $slugs) !== false) {
191
+
192
+ $plugin_meta[] = '<a href="admin.php?page=newsletter_main_extensions" style="font-weight: bold">Newsletter Addons Manager required</a>';
193
+ }
194
+ return $plugin_meta;
195
+ }, 10, 2);
196
+ }
197
+
198
+ add_action('in_admin_header', array($this, 'hook_in_admin_header'), 1000);
199
+
200
+ if ($this->is_admin_page()) {
201
+
202
+ $dismissed = get_option('newsletter_dismissed', []);
203
+
204
+ if (isset($_GET['dismiss'])) {
205
+ $dismissed[$_GET['dismiss']] = 1;
206
+ update_option('newsletter_dismissed', $dismissed);
207
+ wp_redirect($_SERVER['HTTP_REFERER']);
208
+ exit();
209
+ }
210
+ }
211
+ } else {
212
+ add_action('wp_enqueue_scripts', [$this, 'hook_wp_enqueue_scripts']);
213
+ }
214
+
215
+ do_action('newsletter_init');
216
+ }
217
+
218
+ function hook_wp_loaded() {
219
+ if (empty($this->action)) {
220
+ return;
221
+ }
222
+
223
+ if ($this->action == 'test') {
224
+ // This response is tested, do not change it!
225
+ echo 'ok';
226
+ die();
227
+ }
228
+
229
+ if ($this->action === 'nul') {
230
+ $this->dienow('This link is not active on newsletter preview', 'You can send a test message to test subscriber to have the real working link.');
231
+ }
232
+
233
+ $user = $this->get_user_from_request();
234
+ $email = $this->get_email_from_request();
235
+ do_action('newsletter_action', $this->action, $user, $email);
236
+ }
237
+
238
+ function hook_activate() {
239
+ // Ok, why? When the plugin is not active WordPress may remove the scheduled "newsletter" action because
240
+ // the every-five-minutes schedule named "newsletter" is not present.
241
+ // Since the activation does not forces an upgrade, that schedule must be reactivated here. It is activated on
242
+ // the upgrade method as well for the user which upgrade the plugin without deactivte it (many).
243
+ if (!wp_next_scheduled('newsletter')) {
244
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
245
+ }
246
+
247
+ $install_time = get_option('newsletter_install_time');
248
+ if (!$install_time) {
249
+ update_option('newsletter_install_time', time(), false);
250
+ }
251
+
252
+ touch(NEWSLETTER_LOG_DIR . '/index.html');
253
+
254
+ Newsletter::instance()->upgrade();
255
+ NewsletterUsers::instance()->upgrade();
256
+ NewsletterEmails::instance()->upgrade();
257
+ NewsletterSubscription::instance()->upgrade();
258
+ NewsletterStatistics::instance()->upgrade();
259
+ NewsletterProfile::instance()->upgrade();
260
+ }
261
+
262
+ function first_install() {
263
+ parent::first_install();
264
+ update_option('newsletter_show_welcome', '1', false);
265
+ }
266
+
267
+ function upgrade() {
268
+ global $wpdb, $charset_collate;
269
+
270
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
271
+
272
+ parent::upgrade();
273
+
274
+ $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_emails` (
275
+ `id` int(11) NOT NULL AUTO_INCREMENT,
276
+ `language` varchar(10) NOT NULL DEFAULT '',
277
+ `subject` varchar(255) NOT NULL DEFAULT '',
278
+ `message` longtext,
279
+ `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
280
+ `status` enum('new','sending','sent','paused','error') NOT NULL DEFAULT 'new',
281
+ `total` int(11) NOT NULL DEFAULT '0',
282
+ `last_id` int(11) NOT NULL DEFAULT '0',
283
+ `sent` int(11) NOT NULL DEFAULT '0',
284
+ `track` int(11) NOT NULL DEFAULT '1',
285
+ `list` int(11) NOT NULL DEFAULT '0',
286
+ `type` varchar(50) NOT NULL DEFAULT '',
287
+ `query` longtext,
288
+ `editor` tinyint(4) NOT NULL DEFAULT '0',
289
+ `sex` varchar(20) NOT NULL DEFAULT '',
290
+ `theme` varchar(50) NOT NULL DEFAULT '',
291
+ `message_text` longtext,
292
+ `preferences` longtext,
293
+ `send_on` int(11) NOT NULL DEFAULT '0',
294
+ `token` varchar(10) NOT NULL DEFAULT '',
295
+ `options` longtext,
296
+ `private` tinyint(1) NOT NULL DEFAULT '0',
297
+ `click_count` int(10) unsigned NOT NULL DEFAULT '0',
298
+ `version` varchar(10) NOT NULL DEFAULT '',
299
+ `open_count` int(10) unsigned NOT NULL DEFAULT '0',
300
+ `unsub_count` int(10) unsigned NOT NULL DEFAULT '0',
301
+ `error_count` int(10) unsigned NOT NULL DEFAULT '0',
302
+ `stats_time` int(10) unsigned NOT NULL DEFAULT '0',
303
+ `updated` int(10) unsigned NOT NULL DEFAULT '0',
304
+ PRIMARY KEY (`id`)
305
+ ) $charset_collate;";
306
+
307
+ dbDelta($sql);
308
+
309
+ // WP does not manage composite primary key when it tries to upgrade a table...
310
+ $suppress_errors = $wpdb->suppress_errors(true);
311
+
312
+ dbDelta("CREATE TABLE " . $wpdb->prefix . "newsletter_sent (
313
+ email_id int(10) unsigned NOT NULL DEFAULT '0',
314
+ user_id int(10) unsigned NOT NULL DEFAULT '0',
315
+ status tinyint(1) unsigned NOT NULL DEFAULT '0',
316
+ open tinyint(1) unsigned NOT NULL DEFAULT '0',
317
+ time int(10) unsigned NOT NULL DEFAULT '0',
318
+ error varchar(255) NOT NULL DEFAULT '',
319
+ ip varchar(100) NOT NULL DEFAULT '',
320
+ PRIMARY KEY (email_id,user_id),
321
+ KEY user_id (user_id),
322
+ KEY email_id (email_id)
323
+ ) $charset_collate;");
324
+ $wpdb->suppress_errors($suppress_errors);
325
+
326
+ // Some setting check to avoid the common support request for mis-configurations
327
+ $options = $this->get_options();
328
+
329
+ if (empty($options['scheduler_max']) || !is_numeric($options['scheduler_max'])) {
330
+ $options['scheduler_max'] = 100;
331
+ $this->save_options($options);
332
+ }
333
+
334
+ wp_clear_scheduled_hook('newsletter');
335
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
336
+
337
+ if (!empty($this->options['editor'])) {
338
+ if (empty($this->options['roles'])) {
339
+ $this->options['roles'] = array('editor');
340
+ unset($this->options['editor']);
341
+ }
342
+ $this->save_options($this->options);
343
+ }
344
+
345
+ // Clear the addons and license caches
346
+ delete_transient('newsletter_license_data');
347
+ $this->clear_extensions_cache();
348
+
349
+ touch(NEWSLETTER_LOG_DIR . '/index.html');
350
+
351
+ return true;
352
+ }
353
+
354
+ function is_allowed() {
355
+ if (current_user_can('administrator')) {
356
+ return true;
357
+ }
358
+ if (!empty($this->options['roles'])) {
359
+ foreach ($this->options['roles'] as $role) {
360
+ if (current_user_can($role)) {
361
+ return true;
362
+ }
363
+ }
364
+ }
365
+ return false;
366
+ }
367
+
368
+ function admin_menu() {
369
+ if (!$this->is_allowed()) {
370
+ return;
371
+ }
372
+
373
+ add_menu_page('Newsletter', 'Newsletter', 'exist', 'newsletter_main_index', '', plugins_url('newsletter') . '/admin/images/menu-icon.png', '30.333');
374
+
375
+ $this->add_menu_page('index', __('Dashboard', 'newsletter'));
376
+ $this->add_admin_page('info', __('Company info', 'newsletter'));
377
+
378
+ if (current_user_can('administrator')) {
379
+ $this->add_menu_page('welcome', __('Welcome', 'newsletter'));
380
+ $this->add_menu_page('main', __('Settings', 'newsletter'));
381
+
382
+ // Pages not on menu
383
+ $this->add_admin_page('smtp', 'SMTP');
384
+ $this->add_admin_page('diagnostic', __('Diagnostic', 'newsletter'));
385
+ $this->add_admin_page('test', __('Test', 'newsletter'));
386
+ }
387
+ }
388
+
389
+ function add_extensions_menu() {
390
+ if (!class_exists('NewsletterExtensions')) {
391
+ $this->add_menu_page('extensions', '<span style="color:#27AE60; font-weight: bold;">' . __('Addons', 'newsletter') . '</span>');
392
+ }
393
+ }
394
+
395
+ function hook_in_admin_header() {
396
+ if (!$this->is_admin_page()) {
397
+ add_action('admin_notices', array($this, 'hook_admin_notices'));
398
+ return;
399
+ }
400
+ remove_all_actions('admin_notices');
401
+ remove_all_actions('all_admin_notices');
402
+ add_action('admin_notices', array($this, 'hook_admin_notices'));
403
+ }
404
+
405
+ function hook_admin_notices() {
406
+ // Check of Newsletter dedicated page
407
+ if (!empty($this->options['page'])) {
408
+ if (get_post_status($this->options['page']) !== 'publish') {
409
+ echo '<div class="notice notice-error"><p>The Newsletter dedicated page is not published. <a href="', site_url('/wp-admin/post.php') . '?post=', $this->options['page'], '&action=edit"><strong>Edit the page</strong></a> or <a href="admin.php?page=newsletter_main_main"><strong>review the main settings</strong></a>.</p></div>';
410
+ }
411
+ }
412
+
413
+ if (isset($this->options['debug']) && $this->options['debug'] == 1) {
414
+ echo '<div class="notice notice-warning"><p>The Newsletter plugin is in <strong>debug mode</strong>. When done change it on Newsletter <a href="admin.php?page=newsletter_main_main"><strong>main settings</strong></a>. Do not keep the debug mode active on production sites.</p></div>';
415
+ }
416
+ }
417
+
418
+ function hook_wp_enqueue_scripts() {
419
+ if (empty($this->options['css_disabled']) && apply_filters('newsletter_enqueue_style', true)) {
420
+ wp_enqueue_style('newsletter', plugins_url('newsletter') . '/style.css', [], NEWSLETTER_VERSION);
421
+ if (!empty($this->options['css'])) {
422
+ wp_add_inline_style('newsletter', $this->options['css']);
423
+ }
424
+ } else {
425
+ if (!empty($this->options['css'])) {
426
+ add_action('wp_head', function () {
427
+ echo '<style>', $this->options['css'], '</style>';
428
+ });
429
+ }
430
+ }
431
+ }
432
+
433
+ function hook_admin_enqueue_scripts() {
434
+
435
+ $newsletter_url = plugins_url('newsletter');
436
+ wp_enqueue_script('jquery-ui-tabs');
437
+ wp_enqueue_script('jquery-ui-tooltip');
438
+ wp_enqueue_script('jquery-ui-draggable');
439
+ wp_enqueue_media();
440
+
441
+ wp_enqueue_script('tnp-admin', $newsletter_url . '/admin/admin.js', ['jquery'], NEWSLETTER_VERSION);
442
+
443
+ wp_enqueue_style('tnp-select2', $newsletter_url . '/vendor/select2/css/select2.min.css', [], NEWSLETTER_VERSION);
444
+ wp_enqueue_script('tnp-select2', $newsletter_url . '/vendor/select2/js/select2.min.js', ['jquery'], NEWSLETTER_VERSION);
445
+
446
+ wp_enqueue_style('tnp-modal', $newsletter_url . '/admin/modal.css', [], NEWSLETTER_VERSION);
447
+ wp_enqueue_script('tnp-modal', $newsletter_url . '/admin/modal.js', ['jquery'], NEWSLETTER_VERSION, true);
448
+
449
+ wp_enqueue_style('tnp-toast', $newsletter_url . '/admin/toast.css', [], NEWSLETTER_VERSION);
450
+ wp_enqueue_script('tnp-toast', $newsletter_url . '/admin/toast.js', ['jquery'], NEWSLETTER_VERSION);
451
+
452
+ wp_enqueue_style('tnp-admin-font', 'https://use.typekit.net/jlj2wjy.css');
453
+ wp_enqueue_style('tnp-admin-fontawesome', $newsletter_url . '/vendor/fa/css/all.min.css', [], NEWSLETTER_VERSION);
454
+ wp_enqueue_style('tnp-admin-jquery-ui', $newsletter_url . '/vendor/jquery-ui/jquery-ui.min.css', [], NEWSLETTER_VERSION);
455
+ wp_enqueue_style('tnp-admin', $newsletter_url . '/admin/admin.css', [], NEWSLETTER_VERSION);
456
+ wp_enqueue_style('tnp-admin-dropdown', $newsletter_url . '/admin/dropdown.css', [], NEWSLETTER_VERSION);
457
+ wp_enqueue_style('tnp-admin-tabs', $newsletter_url . '/admin/tabs.css', [], NEWSLETTER_VERSION);
458
+ wp_enqueue_style('tnp-admin-controls', $newsletter_url . '/admin/controls.css', [], NEWSLETTER_VERSION);
459
+ wp_enqueue_style('tnp-admin-fields', $newsletter_url . '/admin/fields.css', [], NEWSLETTER_VERSION);
460
+ wp_enqueue_style('tnp-admin-widgets', $newsletter_url . '/admin/widgets.css', [], NEWSLETTER_VERSION);
461
+ wp_enqueue_style('tnp-admin-extensions', $newsletter_url . '/admin/extensions.css', [], NEWSLETTER_VERSION);
462
+
463
+ $translations_array = array(
464
+ 'save_to_update_counter' => __('Save the newsletter to update the counter!', 'newsletter')
465
+ );
466
+ wp_localize_script('tnp-admin', 'tnp_translations', $translations_array);
467
+
468
+ wp_enqueue_script('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jquery.vmap.min.js', ['jquery'], NEWSLETTER_VERSION);
469
+ wp_enqueue_script('tnp-jquery-vmap-world', $newsletter_url . '/vendor/jqvmap/jquery.vmap.world.js', ['tnp-jquery-vmap'], NEWSLETTER_VERSION);
470
+ wp_enqueue_style('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jqvmap.min.css', [], NEWSLETTER_VERSION);
471
+
472
+ wp_register_script('tnp-chart', $newsletter_url . '/vendor/chartjs/Chart.min.js', ['jquery'], NEWSLETTER_VERSION);
473
+
474
+ wp_enqueue_script('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.js', ['jquery']);
475
+ wp_enqueue_style('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.css', [], NEWSLETTER_VERSION);
476
+ }
477
+
478
+ function shortcode_newsletter_replace($attrs, $content) {
479
+ $content = do_shortcode($content);
480
+ $content = $this->replace($content, $this->get_user_from_request(), $this->get_email_from_request());
481
+ return $content;
482
+ }
483
+
484
+ function is_admin_page() {
485
+ if (!isset($_GET['page'])) {
486
+ return false;
487
+ }
488
+ $page = $_GET['page'];
489
+ return strpos($page, 'newsletter_') === 0;
490
+ }
491
+
492
+ function hook_admin_init() {
493
+ // Verificare il contesto
494
+ if (isset($_GET['page']) && $_GET['page'] === 'newsletter_main_welcome')
495
+ return;
496
+ if (get_option('newsletter_show_welcome')) {
497
+ delete_option('newsletter_show_welcome');
498
+ wp_redirect(admin_url('admin.php?page=newsletter_main_welcome'));
499
+ }
500
+
501
+ if ($this->is_admin_page()) {
502
+ // Remove the emoji replacer to save to database the original emoji characters (see even woocommerce for the same problem)
503
+ remove_action('admin_print_scripts', 'print_emoji_detection_script');
504
+ }
505
+ }
506
+
507
+ function hook_admin_head() {
508
+ // Small global rule for sidebar menu entries
509
+ echo '<style>';
510
+ echo '.tnp-side-menu { color: #E67E22!important; }';
511
+ echo '</style>';
512
+ }
513
+
514
+ function relink($text, $email_id, $user_id, $email_token = '') {
515
+ return NewsletterStatistics::instance()->relink($text, $email_id, $user_id, $email_token);
516
+ }
517
+
518
+ /**
519
+ * Runs every 5 minutes and look for emails that need to be processed.
520
+ */
521
+ function hook_newsletter() {
522
+
523
+ $this->logger->debug(__METHOD__ . '> Start');
524
+
525
+ if (!$this->check_transient('engine', NEWSLETTER_CRON_INTERVAL)) {
526
+ $this->logger->debug(__METHOD__ . '> Engine already active, exit');
527
+ return;
528
+ }
529
+
530
+ $emails = $this->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " where status='sending' and send_on<" . time() . " order by id asc");
531
+ $this->logger->debug(__METHOD__ . '> Emails found in sending status: ' . count($emails));
532
+
533
+ foreach ($emails as $email) {
534
+ $this->logger->debug(__METHOD__ . '> Start newsletter ' . $email->id);
535
+ $email->options = maybe_unserialize($email->options);
536
+ $r = $this->send($email);
537
+ $this->logger->debug(__METHOD__ . '> End newsletter ' . $email->id);
538
+ if (!$r) {
539
+ $this->logger->debug(__METHOD__ . '> Engine returned false, there is no more capacity');
540
+ break;
541
+ }
542
+ }
543
+ // Remove the semaphore so the delivery engine can be activated again
544
+ $this->delete_transient('engine');
545
+
546
+ $this->logger->debug(__METHOD__ . '> End');
547
+ }
548
+
549
+ function get_send_speed($email = null) {
550
+ $this->logger->debug(__METHOD__ . '> Computing delivery speed');
551
+ $mailer = $this->get_mailer();
552
+ $speed = (int) $mailer->get_speed();
553
+ if (!$speed) {
554
+ $this->logger->debug(__METHOD__ . '> Speed not set by mailer, use the default');
555
+ $speed = (int) $this->options['scheduler_max'];
556
+ } else {
557
+ $this->logger->debug(__METHOD__ . '> Speed set by mailer');
558
+ }
559
+
560
+ //$speed = (int) apply_filters('newsletter_send_speed', $speed, $email);
561
+ // Fail safe setting
562
+ $runs_per_hour = $this->get_runs_per_hour();
563
+ if (!$speed || $speed < $runs_per_hour) {
564
+ return $runs_per_hour;
565
+ }
566
+
567
+ $this->logger->debug(__METHOD__ . '> Speed: ' . $speed);
568
+ return $speed;
569
+ }
570
+
571
+ function get_send_delay() {
572
+ return 0;
573
+ }
574
+
575
+ function skip_this_run($email = null) {
576
+ return (boolean) apply_filters('newsletter_send_skip', false, $email);
577
+ }
578
+
579
+ function get_runs_per_hour() {
580
+ return (int) (3600 / NEWSLETTER_CRON_INTERVAL);
581
+ }
582
+
583
+ function get_emails_per_run() {
584
+ $speed = $this->get_send_speed();
585
+ $max = (int) ($speed / $this->get_runs_per_hour());
586
+
587
+ return $max;
588
+ }
589
+
590
+ function get_max_emails($email) {
591
+ // Obsolete, here from Speed Control Addon
592
+ $max = (int) apply_filters('newsletter_send_max_emails', $this->max_emails, $email);
593
+
594
+ return min($max, $this->max_emails);
595
+ }
596
+
597
+ function fix_email($email) {
598
+ if (empty($email->query)) {
599
+ $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
600
+ }
601
+ if (empty($email->id)) {
602
+ $email->id = 0;
603
+ }
604
+ }
605
+
606
+ function send_setup() {
607
+ $this->logger->debug(__METHOD__ . '> Setup delivery engine');
608
+ if (is_null($this->max_emails)) {
609
+ $this->max_emails = $this->get_emails_per_run();
610
+ $this->logger->debug(__METHOD__ . '> Max emails: ' . $this->max_emails);
611
+ ignore_user_abort(true);
612
+
613
+ @set_time_limit(NEWSLETTER_CRON_INTERVAL + 30);
614
+
615
+ $max_time = (int) (@ini_get('max_execution_time') * 0.95);
616
+ if ($max_time == 0 || $max_time > NEWSLETTER_CRON_INTERVAL) {
617
+ $max_time = (int) (NEWSLETTER_CRON_INTERVAL * 0.95);
618
+ }
619
+
620
+ $this->time_limit = $this->time_start + $max_time;
621
+
622
+ $this->logger->debug(__METHOD__ . '> Max time set to ' . $max_time);
623
+ } else {
624
+ $this->logger->debug(__METHOD__ . '> Already setup');
625
+ }
626
+ }
627
+
628
+ function time_exceeded() {
629
+ if ($this->time_limit && time() > $this->time_limit) {
630
+ $this->logger->info(__METHOD__ . '> Max execution time limit reached');
631
+ return true;
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Sends an email to targeted users or to given users. If a list of users is given (usually a list of test users)
637
+ * the query inside the email to retrieve users is not used.
638
+ *
639
+ * @global wpdb $wpdb
640
+ * @global type $newsletter_feed
641
+ * @param TNP_Email $email
642
+ * @param array $users
643
+ * @return boolean|WP_Error True if the process completed, false if limits was reached. On false the caller should no continue to call it with other emails.
644
+ */
645
+ function send($email, $users = null, $test = false) {
646
+ global $wpdb;
647
+
648
+ if (is_array($email)) {
649
+ $email = (object) $email;
650
+ }
651
+
652
+ $this->logger->info(__METHOD__ . '> Send email ' . $email->id);
653
+
654
+ $this->send_setup();
655
+
656
+ if ($this->max_emails <= 0) {
657
+ $this->logger->info(__METHOD__ . '> No more capacity');
658
+ return false;
659
+ }
660
+
661
+ $this->fix_email($email);
662
+
663
+ // This stops the update of last_id and sent fields since
664
+ // it's not a scheduled delivery but a test or something else (like an autoresponder)
665
+ $supplied_users = $users != null;
666
+
667
+ if (!$supplied_users) {
668
+
669
+ if ($this->skip_this_run($email)) {
670
+ return true;
671
+ }
672
+
673
+ // Speed change for specific email by Speed Control Addon
674
+ $max_emails = $this->get_max_emails($email);
675
+ if ($max_emails <= 0) {
676
+ return true;
677
+ }
678
+
679
+ $query = $email->query;
680
+ $query .= " and id>" . $email->last_id . " order by id limit " . $max_emails;
681
+
682
+ $this->logger->debug(__METHOD__ . '> Query: ' . $query);
683
+
684
+ //Retrieve subscribers
685
+ $users = $this->get_results($query);
686
+
687
+ $this->logger->debug(__METHOD__ . '> Loaded subscribers: ' . count($users));
688
+
689
+ // If there was a database error, return error
690
+ if ($users === false) {
691
+ return new WP_Error('1', 'Unable to query subscribers, check the logs');
692
+ }
693
+
694
+ if (empty($users)) {
695
+ $this->logger->info(__METHOD__ . '> No more users, set as sent');
696
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set status='sent', total=sent where id=" . $email->id . " limit 1");
697
+ do_action('newsletter_ended_sending_newsletter', $email);
698
+ return true;
699
+ }
700
+ } else {
701
+ $this->logger->info(__METHOD__ . '> Subscribers supplied');
702
+ }
703
+
704
+ $start_time = microtime(true);
705
+ $count = 0;
706
+ $result = true;
707
+
708
+ $mailer = $this->get_mailer();
709
+
710
+ $batch_size = $mailer->get_batch_size();
711
+
712
+ $this->logger->debug(__METHOD__ . '> Batch size: ' . $batch_size);
713
+
714
+ // For batch size == 1 (normal condition) we optimize
715
+ if ($batch_size == 1) {
716
+
717
+ foreach ($users as $user) {
718
+
719
+ $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
720
+ $user = apply_filters('newsletter_send_user', $user);
721
+ $message = $this->build_message($email, $user);
722
+
723
+ // Save even test emails since people wants to see some stats even for test emails. Stats are reset upon the real "send" of a newsletter
724
+ $this->save_sent_message($message);
725
+
726
+ //Se non è un test incremento il contatore delle email spedite. Perchè incremento prima di spedire??
727
+ if (!$test) {
728
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
729
+ }
730
+
731
+ $r = $mailer->send($message);
732
+
733
+ if (!empty($message->error)) {
734
+ $this->logger->error($message);
735
+ $this->save_sent_message($message);
736
+ }
737
+
738
+ if (is_wp_error($r)) {
739
+ $this->logger->error($r);
740
+
741
+ // For fatal error, the newsletter status i changed to error (and the delivery stopped)
742
+ if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
743
+ $this->set_error_state_of_email($email, $r->get_error_message());
744
+ return $r;
745
+ }
746
+ }
747
+
748
+ if (!$supplied_users && !$test && $this->time_exceeded()) {
749
+ $result = false;
750
+ break;
751
+ }
752
+ }
753
+
754
+ $this->max_emails--;
755
+ $count++;
756
+ } else {
757
+
758
+ $chunks = array_chunk($users, $batch_size);
759
+
760
+ foreach ($chunks as $chunk) {
761
+
762
+ $messages = [];
763
+
764
+ // Peeparing a batch of messages
765
+ foreach ($chunk as $user) {
766
+ $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
767
+ $user = apply_filters('newsletter_send_user', $user);
768
+ $message = $this->build_message($email, $user);
769
+ $this->save_sent_message($message);
770
+ $messages[] = $message;
771
+
772
+ if (!$test) {
773
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
774
+ }
775
+ $this->max_emails--;
776
+ $count++;
777
+ }
778
+
779
+ $r = $mailer->send_batch($messages);
780
+
781
+ // Updating the status of the sent messages
782
+ foreach ($messages as $message) {
783
+ if (!empty($message->error)) {
784
+ $this->save_sent_message($message);
785
+ }
786
+ }
787
+
788
+ // The batch went in error
789
+ if (is_wp_error($r)) {
790
+ $this->logger->error($r);
791
+
792
+ if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
793
+ $this->set_error_state_of_email($email, $r->get_error_message());
794
+ return $r;
795
+ }
796
+ }
797
+
798
+ if (!$supplied_users && !$test && $this->time_exceeded()) {
799
+ $result = false;
800
+ break;
801
+ }
802
+ }
803
+ }
804
+
805
+ $end_time = microtime(true);
806
+
807
+ // Stats only for newsletter with enough emails in a batch (we exclude the Autoresponder since it send one email per call)
808
+ if (!$test && !$supplied_users && $count > 5) {
809
+ $this->update_send_stats($start_time, $end_time, $count, $result);
810
+ }
811
+
812
+ // Cached general statistics are reset
813
+ if (!$test) {
814
+ NewsletterStatistics::instance()->reset_stats_time($email->id);
815
+ }
816
+
817
+ $this->logger->info(__METHOD__ . '> End run for email ' . $email->id);
818
+
819
+ return $result;
820
+ }
821
+
822
+ function update_send_stats($start_time, $end_time, $count, $result) {
823
+ $send_calls = get_option('newsletter_diagnostic_send_calls', []);
824
+ $send_calls[] = [$start_time, $end_time, $count, $result];
825
+
826
+ if (count($send_calls) > 100) {
827
+ array_shift($send_calls);
828
+ }
829
+
830
+ update_option('newsletter_diagnostic_send_calls', $send_calls, false);
831
+ }
832
+
833
+ /**
834
+ * @param TNP_Email $email
835
+ */
836
+ private function set_error_state_of_email($email, $message = '') {
837
+ // Handle only message type at the moment
838
+ if ($email->type !== 'message') {
839
+ return;
840
+ }
841
+
842
+ do_action('newsletter_error_on_sending', $email, $message);
843
+
844
+ $edited_email = new TNP_Email();
845
+ $edited_email->id = $email->id;
846
+ $edited_email->status = TNP_Email::STATUS_ERROR;
847
+ $edited_email->options = $email->options;
848
+ $edited_email->options['error_message'] = $message;
849
+
850
+ $this->save_email($edited_email);
851
+ }
852
+
853
+ /**
854
+ *
855
+ * @param TNP_Email $email
856
+ * @param TNP_User $user
857
+ * @return \TNP_Mailer_Message
858
+ */
859
+ function build_message($email, $user) {
860
+
861
+ $message = new TNP_Mailer_Message();
862
+
863
+ $message->to = $user->email;
864
+
865
+ $message->headers = [];
866
+ $message->headers['Precedence'] = 'bulk';
867
+ $message->headers['X-Newsletter-Email-Id'] = $email->id;
868
+ $message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
869
+ $message->headers = apply_filters('newsletter_message_headers', $message->headers, $email, $user);
870
+
871
+ $message->body = preg_replace('/data-json=".*?"/is', '', $email->message);
872
+ $message->body = preg_replace('/ +/s', ' ', $message->body);
873
+ $message->body = $this->replace($message->body, $user, $email);
874
+ if ($this->options['do_shortcodes']) {
875
+ $message->body = do_shortcode($message->body);
876
+ }
877
+ $message->body = apply_filters('newsletter_message_html', $message->body, $email, $user);
878
+
879
+ $message->body_text = $this->replace($email->message_text, $user, $email);
880
+ $message->body_text = apply_filters('newsletter_message_text', $message->body_text, $email, $user);
881
+
882
+ if ($email->track == 1) {
883
+ $message->body = $this->relink($message->body, $email->id, $user->id, $email->token);
884
+ }
885
+
886
+ $message->subject = $this->replace($email->subject, $user);
887
+ $message->subject = apply_filters('newsletter_message_subject', $message->subject, $email, $user);
888
+
889
+ if (!empty($email->options['sender_email'])) {
890
+ $message->from = $email->options['sender_email'];
891
+ } else {
892
+ $message->from = $this->options['sender_email'];
893
+ }
894
+
895
+ if (!empty($email->options['sender_name'])) {
896
+ $message->from_name = $email->options['sender_name'];
897
+ } else {
898
+ $message->from_name = $this->options['sender_name'];
899
+ }
900
+
901
+ $message->email_id = $email->id;
902
+ $message->user_id = $user->id;
903
+
904
+ return apply_filters('newsletter_message', $message, $email, $user);
905
+ }
906
+
907
+ /**
908
+ *
909
+ * @param TNP_Mailer_Message $message
910
+ * @param int $status
911
+ * @param string $error
912
+ */
913
+ function save_sent_message($message) {
914
+ global $wpdb;
915
+
916
+ if (!$message->user_id || !$message->email_id) {
917
+ return;
918
+ }
919
+ $status = empty($message->error) ? 0 : 1;
920
+
921
+ $error = mb_substr($message->error, 0, 250);
922
+
923
+ $this->query($wpdb->prepare("insert into " . $wpdb->prefix . 'newsletter_sent (user_id, email_id, time, status, error) values (%d, %d, %d, %d, %s) on duplicate key update time=%d, status=%d, error=%s', $message->user_id, $message->email_id, time(), $status, $error, time(), $status, $error));
924
+ }
925
+
926
+ /**
927
+ * @deprecated since version 7.3.0
928
+ */
929
+ function limits_exceeded() {
930
+ return false;
931
+ }
932
+
933
+ /**
934
+ * @deprecated since version 6.0.0
935
+ */
936
+ function register_mail_method($callable) {
937
+
938
+ }
939
+
940
+ function register_mailer($mailer) {
941
+ if ($mailer instanceof NewsletterMailer) {
942
+ $this->mailer = $mailer;
943
+ }
944
+ }
945
+
946
+ /**
947
+ * Returns the current registered mailer which must be used to send emails.
948
+ *
949
+ * @return NewsletterMailer
950
+ */
951
+ function get_mailer() {
952
+ if ($this->mailer) {
953
+ return $this->mailer;
954
+ }
955
+
956
+ do_action('newsletter_register_mailer');
957
+
958
+ if (!$this->mailer) {
959
+ // Compatibility
960
+ $smtp = $this->get_options('smtp');
961
+ if (!empty($smtp['enabled'])) {
962
+ $this->mailer = new NewsletterDefaultSMTPMailer($smtp);
963
+ } else {
964
+ $this->mailer = new NewsletterDefaultMailer();
965
+ }
966
+ }
967
+ return $this->mailer;
968
+ }
969
+
970
+ /**
971
+ *
972
+ * @param TNP_Mailer_Message $message
973
+ * @return type
974
+ */
975
+ function deliver($message) {
976
+ $mailer = $this->get_mailer();
977
+ if (empty($message->from))
978
+ $message->from = $this->options['sender_email'];
979
+ if (empty($message->from_name))
980
+ $mailer->from_name = $this->options['sender_name'];
981
+ return $mailer->send($message);
982
+ }
983
+
984
+ /**
985
+ *
986
+ * @param type $to
987
+ * @param type $subject
988
+ * @param string|array $message If string is considered HTML, is array should contains the keys "html" and "text"
989
+ * @param type $headers
990
+ * @param type $enqueue
991
+ * @param type $from
992
+ * @return boolean
993
+ */
994
+ function mail($to, $subject, $message, $headers = array(), $enqueue = false, $from = false) {
995
+
996
+ if (empty($subject)) {
997
+ $this->logger->error('mail> Subject empty, skipped');
998
+ return true;
999
+ }
1000
+
1001
+ $mailer_message = new TNP_Mailer_Message();
1002
+ $mailer_message->to = $to;
1003
+ $mailer_message->subject = $subject;
1004
+ $mailer_message->from = $this->options['sender_email'];
1005
+ $mailer_message->from_name = $this->options['sender_name'];
1006
+
1007
+ if (!empty($headers)) {
1008
+ $mailer_message->headers = $headers;
1009
+ }
1010
+ $mailer_message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
1011
+
1012
+ // Message carrige returns and line feeds clean up
1013
+ if (!is_array($message)) {
1014
+ $mailer_message->body = $this->clean_eol($message);
1015
+ } else {
1016
+ if (!empty($message['text'])) {
1017
+ $mailer_message->body_text = $this->clean_eol($message['text']);
1018
+ }
1019
+
1020
+ if (!empty($message['html'])) {
1021
+ $mailer_message->body = $this->clean_eol($message['html']);
1022
+ }
1023
+ }
1024
+
1025
+ $this->logger->debug($mailer_message);
1026
+
1027
+ $mailer = $this->get_mailer();
1028
+
1029
+ $r = $mailer->send($mailer_message);
1030
+
1031
+ return !is_wp_error($r);
1032
+ }
1033
+
1034
+ function hook_deactivate() {
1035
+ wp_clear_scheduled_hook('newsletter');
1036
+ }
1037
+
1038
+ function find_file($file1, $file2) {
1039
+ if (is_file($file1))
1040
+ return $file1;
1041
+ return $file2;
1042
+ }
1043
+
1044
+ function hook_site_transient_update_plugins($value) {
1045
+ static $extra_response = array();
1046
+
1047
+ //$this->logger->debug('Update plugins transient called');
1048
+
1049
+ if (!$value || !is_object($value)) {
1050
+ //$this->logger->info('Empty object');
1051
+ return $value;
1052
+ }
1053
+
1054
+ if (!isset($value->response) || !is_array($value->response)) {
1055
+ $value->response = array();
1056
+ }
1057
+
1058
+ // Already computed? Use it! (this filter is called many times in a single request)
1059
+ if ($extra_response) {
1060
+ //$this->logger->debug('Already updated');
1061
+ $value->response = array_merge($value->response, $extra_response);
1062
+ return $value;
1063
+ }
1064
+
1065
+ $extensions = $this->getTnpExtensions();
1066
+
1067
+ // Ops...
1068
+ if (!$extensions) {
1069
+ return $value;
1070
+ }
1071
+
1072
+ foreach ($extensions as $extension) {
1073
+ unset($value->response[$extension->wp_slug]);
1074
+ unset($value->no_update[$extension->wp_slug]);
1075
+ }
1076
+
1077
+ // Someone doesn't want our addons updated, let respect it (this constant should be defined in wp-config.php)
1078
+ if (!NEWSLETTER_EXTENSION_UPDATE) {
1079
+ //$this->logger->info('Updates disabled');
1080
+ return $value;
1081
+ }
1082
+
1083
+ include_once(ABSPATH . 'wp-admin/includes/plugin.php');
1084
+
1085
+ // Ok, that is really bad (should we remove it? is there a minimum WP version?)
1086
+ if (!function_exists('get_plugin_data')) {
1087
+ //$this->logger->error('No get_plugin_data function available!');
1088
+ return $value;
1089
+ }
1090
+
1091
+ $license_key = $this->get_license_key();
1092
+
1093
+ // Here we prepare the update information BUT do not add the link to the package which is privided
1094
+ // by our Addons Manager (due to WP policies)
1095
+ foreach ($extensions as $extension) {
1096
+
1097
+ // Patch for names convention
1098
+ $extension->plugin = $extension->wp_slug;
1099
+
1100
+ //$this->logger->debug('Processing ' . $extension->plugin);
1101
+ //$this->logger->debug($extension);
1102
+
1103
+ $plugin_data = false;
1104
+ if (file_exists(WP_PLUGIN_DIR . '/' . $extension->plugin)) {
1105
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1106
+ } else if (file_exists(WPMU_PLUGIN_DIR . '/' . $extension->plugin)) {
1107
+ $plugin_data = get_plugin_data(WPMU_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1108
+ }
1109
+
1110
+ if (!$plugin_data) {
1111
+ //$this->logger->debug('Seems not installed');
1112
+ continue;
1113
+ }
1114
+
1115
+ $plugin = new stdClass();
1116
+ $plugin->id = $extension->id;
1117
+ $plugin->slug = $extension->slug;
1118
+ $plugin->plugin = $extension->plugin;
1119
+ $plugin->new_version = $extension->version;
1120
+ $plugin->url = $extension->url;
1121
+ if (class_exists('NewsletterExtensions')) {
1122
+ // NO filters here!
1123
+ $plugin->package = NewsletterExtensions::$instance->get_package($extension->id, $license_key);
1124
+ } else {
1125
+ $plugin->package = '';
1126
+ }
1127
+ // [banners] => Array
1128
+ // (
1129
+ // [2x] => https://ps.w.org/wp-rss-aggregator/assets/banner-1544x500.png?rev=2040548
1130
+ // [1x] => https://ps.w.org/wp-rss-aggregator/assets/banner-772x250.png?rev=2040548
1131
+ // )
1132
+ // [icons] => Array
1133
+ // (
1134
+ // [2x] => https://ps.w.org/advanced-custom-fields/assets/icon-256x256.png?rev=1082746
1135
+ // [1x] => https://ps.w.org/advanced-custom-fields/assets/icon-128x128.png?rev=1082746
1136
+ // )
1137
+ if (version_compare($extension->version, $plugin_data['Version']) > 0) {
1138
+ //$this->logger->debug('There is a new version');
1139
+ $extra_response[$extension->plugin] = $plugin;
1140
+ } else {
1141
+ // Maybe useless...
1142
+ //$this->logger->debug('There is NOT a new version');
1143
+ $value->no_update[$extension->plugin] = $plugin;
1144
+ }
1145
+ //$this->logger->debug('Added');
1146
+ }
1147
+
1148
+ $value->response = array_merge($value->response, $extra_response);
1149
+
1150
+ return $value;
1151
+ }
1152
+
1153
+ /**
1154
+ * @deprecated since version 6.1.9
1155
+ */
1156
+ function get_extension_version($extension_id) {
1157
+ return null;
1158
+ }
1159
+
1160
+ /**
1161
+ * @deprecated since version 6.1.9
1162
+ */
1163
+ function set_extension_update_data($value, $extension) {
1164
+ return $value;
1165
+ }
1166
+
1167
+ /**
1168
+ * Retrieve the extensions form the tnp site
1169
+ * @return array
1170
+ */
1171
+ function getTnpExtensions() {
1172
+
1173
+ $extensions_json = get_transient('tnp_extensions_json');
1174
+
1175
+ if (empty($extensions_json)) {
1176
+ $url = "http://www.thenewsletterplugin.com/wp-content/extensions.json?ver=" . NEWSLETTER_VERSION;
1177
+ $extensions_response = wp_remote_get($url);
1178
+
1179
+ if (is_wp_error($extensions_response)) {
1180
+ // Cache anyway for blogs which cannot connect outside
1181
+ $extensions_json = '[]';
1182
+ set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1183
+ $this->logger->error($extensions_response);
1184
+ } else {
1185
+
1186
+ $extensions_json = wp_remote_retrieve_body($extensions_response);
1187
+
1188
+ // Not clear cases
1189
+ if (empty($extensions_json) || !json_decode($extensions_json)) {
1190
+ $this->logger->error('Invalid json from thenewsletterplugin.com: retrying in 72 hours');
1191
+ $this->logger->error('JSON: ' . $extensions_json);
1192
+ $extensions_json = '[]';
1193
+ }
1194
+ set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1195
+ }
1196
+ }
1197
+
1198
+ $extensions = json_decode($extensions_json);
1199
+
1200
+ return $extensions;
1201
+ }
1202
+
1203
+ function clear_extensions_cache() {
1204
+ delete_transient('tnp_extensions_json');
1205
+ }
1206
+
1207
+ var $panels = array();
1208
+
1209
+ function add_panel($key, $panel) {
1210
+ if (!isset($this->panels[$key]))
1211
+ $this->panels[$key] = array();
1212
+ if (!isset($panel['id']))
1213
+ $panel['id'] = sanitize_key($panel['label']);
1214
+ $this->panels[$key][] = $panel;
1215
+ }
1216
+
1217
+ function has_license() {
1218
+ return !empty($this->options['contract_key']);
1219
+ }
1220
+
1221
+ function get_sender_name() {
1222
+ return $this->options['sender_name'];
1223
+ }
1224
+
1225
+ function get_sender_email() {
1226
+ return $this->options['sender_email'];
1227
+ }
1228
+
1229
+ /**
1230
+ *
1231
+ * @return int
1232
+ */
1233
+ function get_newsletter_page_id() {
1234
+ return (int) $this->options['page'];
1235
+ }
1236
+
1237
+ /**
1238
+ *
1239
+ * @return WP_Post
1240
+ */
1241
+ function get_newsletter_page() {
1242
+ $page_id = $this->get_newsletter_page_id();
1243
+ if (!$page_id)
1244
+ return false;
1245
+ return get_post($this->get_newsletter_page_id());
1246
+ }
1247
+
1248
+ /**
1249
+ * Returns the Newsletter dedicated page URL or an alternative URL if that page if not
1250
+ * configured or not available.
1251
+ *
1252
+ * @staticvar string $url
1253
+ * @return string
1254
+ */
1255
+ function get_newsletter_page_url($language = '') {
1256
+
1257
+ $page = $this->get_newsletter_page();
1258
+
1259
+ if (!$page || $page->post_status !== 'publish') {
1260
+ return $this->build_action_url('m');
1261
+ }
1262
+
1263
+ $newsletter_page_url = get_permalink($page->ID);
1264
+ if ($language && $newsletter_page_url) {
1265
+ if (class_exists('SitePress')) {
1266
+ $newsletter_page_url = apply_filters('wpml_permalink', $newsletter_page_url, $language, true);
1267
+ }
1268
+ if (function_exists('pll_get_post')) {
1269
+ $translated_page = get_permalink(pll_get_post($page->ID, $language));
1270
+ if ($translated_page) {
1271
+ $newsletter_page_url = $translated_page;
1272
+ }
1273
+ }
1274
+ }
1275
+
1276
+ return $newsletter_page_url;
1277
+ }
1278
+
1279
+ function get_license_key() {
1280
+ if (defined('NEWSLETTER_LICENSE_KEY')) {
1281
+ return NEWSLETTER_LICENSE_KEY;
1282
+ } else {
1283
+ if (!empty($this->options['contract_key'])) {
1284
+ return trim($this->options['contract_key']);
1285
+ }
1286
+ }
1287
+ return false;
1288
+ }
1289
+
1290
+ /**
1291
+ * Get the data connected to the specified license code on man settings.
1292
+ *
1293
+ * - false if no license is present
1294
+ * - WP_Error if something went wrong if getting the license data
1295
+ * - object with expiration and addons list
1296
+ *
1297
+ * @param boolean $refresh
1298
+ * @return \WP_Error|boolean|object
1299
+ */
1300
+ function get_license_data($refresh = false) {
1301
+
1302
+ $this->logger->debug('Getting license data');
1303
+
1304
+ $license_key = $this->get_license_key();
1305
+ if (empty($license_key)) {
1306
+ $this->logger->debug('License was empty');
1307
+ delete_transient('newsletter_license_data');
1308
+ return false;
1309
+ }
1310
+
1311
+ if (!$refresh) {
1312
+ $license_data = get_transient('newsletter_license_data');
1313
+ if ($license_data !== false && is_object($license_data)) {
1314
+ $this->logger->debug('License data found on cache');
1315
+ return $license_data;
1316
+ }
1317
+ }
1318
+
1319
+ $this->logger->debug('Refreshing the license data');
1320
+
1321
+ $license_data_url = 'https://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/get-license-data.php';
1322
+
1323
+ $response = wp_remote_post($license_data_url, array(
1324
+ 'body' => array('k' => $license_key)
1325
+ ));
1326
+
1327
+ // Fall back to http...
1328
+ if (is_wp_error($response)) {
1329
+ $this->logger->error($response);
1330
+ $this->logger->error('Falling back to http');
1331
+ $license_data_url = str_replace('https', 'http', $license_data_url);
1332
+ $response = wp_remote_post($license_data_url, array(
1333
+ 'body' => array('k' => $license_key)
1334
+ ));
1335
+ if (is_wp_error($response)) {
1336
+ $this->logger->error($response);
1337
+ set_transient('newsletter_license_data', $response, DAY_IN_SECONDS);
1338
+ return $response;
1339
+ }
1340
+ }
1341
+
1342
+ $download_message = 'You can download all addons from www.thenewsletterplugin.com if your license is valid.';
1343
+
1344
+ if (wp_remote_retrieve_response_code($response) != '200') {
1345
+ $this->logger->error('license data error: ' . wp_remote_retrieve_response_code($response));
1346
+ $data = new WP_Error(wp_remote_retrieve_response_code($response), 'License validation service error. <br>' . $download_message);
1347
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1348
+ return $data;
1349
+ }
1350
+
1351
+ $json = wp_remote_retrieve_body($response);
1352
+ $data = json_decode($json);
1353
+
1354
+ if (!is_object($data)) {
1355
+ $this->logger->error($json);
1356
+ $data = new WP_Error(1, 'License validation service error. <br>' . $download_message);
1357
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1358
+ return $data;
1359
+ }
1360
+
1361
+ if (isset($data->message)) {
1362
+ $data = new WP_Error(1, $data->message . ' (check the license on Newsletter main settings)');
1363
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1364
+ return $data;
1365
+ }
1366
+
1367
+ $expiration = WEEK_IN_SECONDS;
1368
+ // If the license expires in few days, make the transient live only few days, so it will be refreshed
1369
+ if ($data->expire > time() && $data->expire - time() < WEEK_IN_SECONDS) {
1370
+ $expiration = $data->expire - time();
1371
+ }
1372
+ set_transient('newsletter_license_data', $data, $expiration);
1373
+
1374
+ return $data;
1375
+ }
1376
+
1377
+ /**
1378
+ * @deprecated
1379
+ * @param type $license_key
1380
+ * @return \WP_Error
1381
+ */
1382
+ public static function check_license($license_key) {
1383
+ $response = wp_remote_get('http://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/check.php?k=' . urlencode($license_key), array('sslverify' => false));
1384
+ if (is_wp_error($response)) {
1385
+ /* @var $response WP_Error */
1386
+ return new WP_Error(-1, 'It seems that your blog cannot contact the license validator. Ask your provider to unlock the HTTP/HTTPS connections to www.thenewsletterplugin.com<br>'
1387
+ . esc_html($response->get_error_code()) . ' - ' . esc_html($response->get_error_message()));
1388
+ } else if ($response['response']['code'] != 200) {
1389
+ return new WP_Error(-1, '[' . $response['response']['code'] . '] The license seems expired or not valid, please check your <a href="https://www.thenewsletterplugin.com/account">license code and status</a>, thank you.'
1390
+ . '<br>You can anyway download the professional extension from https://www.thenewsletterplugin.com.');
1391
+ } elseif ($expires = json_decode(wp_remote_retrieve_body($response))) {
1392
+ return array('expires' => $expires->expire, 'message' => 'Your license is valid and expires on ' . esc_html(date('Y-m-d', $expires->expire)));
1393
+ } else {
1394
+ return new WP_Error(-1, 'Unable to detect the license expiration. Debug data to report to the support: <code>' . esc_html(wp_remote_retrieve_body($response)) . '</code>');
1395
+ }
1396
+ }
1397
+
1398
+ function add_notice_to_chosen_profile_page_hook($post_states, $post) {
1399
+
1400
+ if ($post->ID == $this->options['page']) {
1401
+ $post_states[] = __('Newsletter plugin page, do not delete', 'newsletter');
1402
+ }
1403
+
1404
+ return $post_states;
1405
+ }
1406
+
1407
+ }
1408
+
1409
+ $newsletter = Newsletter::instance();
1410
+
1411
+ if (is_admin()) {
1412
+ require_once NEWSLETTER_DIR . '/system/system.php';
1413
+ }
1414
+
1415
+ require_once NEWSLETTER_DIR . '/subscription/subscription.php';
1416
+ require_once NEWSLETTER_DIR . '/unsubscription/unsubscription.php';
1417
+ require_once NEWSLETTER_DIR . '/profile/profile.php';
1418
+ require_once NEWSLETTER_DIR . '/emails/emails.php';
1419
+ require_once NEWSLETTER_DIR . '/users/users.php';
1420
+ require_once NEWSLETTER_DIR . '/statistics/statistics.php';
1421
+ require_once NEWSLETTER_DIR . '/widget/standard.php';
1422
+ require_once NEWSLETTER_DIR . '/widget/minimal.php';
readme.txt CHANGED
@@ -1,470 +1,474 @@
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.8
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
-
13
- Newsletter is a **real newsletter and email marketing system** for your WordPress blog: perfect for list building, you can easily create, send and track e-mails, headache-free. It just works out of box!
14
-
15
- = Discover a completely rewritten composer =
16
-
17
- We redesigned our drag and drop composer to make your campaign creation even easier. Try it!
18
-
19
- = Main Features =
20
-
21
- * **Easy-to-use Drag and drop composer** to build responsive newsletters
22
- * **Unlimited subscribers** with statistics
23
- * **Unlimited newsletters** with tracking
24
- * **Subscription spam check** with domain/ip black lists, Akismet, captcha
25
- * **Delivery speed** fine control (from 12 emails per hour to as much as your blog can manage)
26
- * [WPML ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Polylang ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Translatepress ready](https://www.thenewsletterplugin.com/documentation/multilanguage)
27
- * All messages are **fully translatable** from administration panels (no .po/.mo file to edit)
28
- * [GDPR ready](https://www.thenewsletterplugin.com/documentation/gdpr-compliancy)
29
- * **Advanced targeting** with lists combinations like all in, at least one, not in and so on
30
- * Customizable **subscription widget**, **page** or **custom form**
31
- * Wordpress Users registration **seamless integration**
32
- * **Single** And **Double Opt-In** plus privacy checkbox for EU laws compliance
33
- * **Subscribers lists** to fine-target your campaigns
34
- * PHP API and REST API for coders and integrations
35
- * SMTP-Ready (with free addon)
36
- * Customizable Themes
37
- * **Status panel** to check your blog mailing capability and configuration
38
- * **Compatible with every SMTP plugin**: Post SMTP (aka Postman), WP Mail SMTP, Easy WP SMTP, Easy SMTP Mail, WP Mail Bank, ...
39
- * **Subscribers import** from file
40
- * Newsletter with Html and Text message versions
41
-
42
- = Find Us =
43
-
44
- Newsletter is a continuously evolving plugin. Stay tuned following us on [Facebook](https://www.facebook.com/thenewsletterplugin/) or [our site](https://www.thenewsletterplugin.com/).
45
-
46
- = Free Addons =
47
-
48
- Improve The Newsletter Plugin with these free addons:
49
-
50
- * [WP Registration Addon](https://www.thenewsletterplugin.com/documentation/wpusers-extension) - connects the WordPress standard and custom registration with Newsletter subscription. Optionally imports all registered users as subscribers.
51
- * [Archive Addon](https://www.thenewsletterplugin.com/documentation/archive-extension) - creates a simple blog page which lists all your sent newsletters
52
- * [Locked Content Addon](https://www.thenewsletterplugin.com/documentation/locked-content-extension) - open up your premium content only after subscription
53
- * [Newsletter REST API Addon](https://www.thenewsletterplugin.com/documentation/developers/newsletter-api-2/) - adds a tier of REST api to integrate with the Newsletter core services
54
- * [Sendinblue Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/sendinblue-extension/) - deliver your newsletters with Sendinblue
55
- * [SMTP Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/smtp-extension/) - deliver your newsletters with external SMTP
56
- * [Advanced Import Addon](https://www.thenewsletterplugin.com/documentation/addons/extended-features/advanced-import/) - import contact from file or copy and paste data with full mapping
57
-
58
- (*easily add them from our [Addons panel](https://www.thenewsletterplugin.com/documentation/install-extensions)*)
59
-
60
- = Addons on WordPress.org =
61
-
62
- * [RSS Composer Block](https://wordpress.org/plugins/newsletter-rss-block/) - (3rd party) a composer block which builds its content from a RSS feed
63
- * [Popup Maker Integration](https://wordpress.org/plugins/newsletter-popupmaker/) - (3rd party) integration of Newsletter forms with Popup Maker plugin
64
- * [BuddyPress integration](https://wordpress.org/plugins/newsletter-buddypress/) - subscription opt-in inside BuddyPress signup form
65
- * [WP User Manager addon for Newsletter](https://wordpress.org/plugins/wpum-newsletter/) - adds the subscription option on registration forms
66
- * [Plaintext Generator](https://wordpress.org/plugins/plaintext-newsletter/) - generates the plaintext version from an HTML newsletter
67
-
68
- = Professional Addons =
69
-
70
- Need *more power*? Feel *something's missing*? The Newsletter Plugin features can be easily extended through our **premium, professional Addons**! Let us introduce just two of them : )
71
-
72
- * [Automated](https://www.thenewsletterplugin.com/automated) - generates and sends your newsletters using your blog last posts, even custom ones like events or products. Just sit and watch!
73
- * [Autoresponder](https://www.thenewsletterplugin.com/autoresponder) - creates email series to follow up your subscribers
74
- * [Extended Composer Blocks](https://www.thenewsletterplugin.com/composer) - adds new blocks to the drag & drop composer
75
- * [WooCommerce Integration](https://www.thenewsletterplugin.com/woocommerce) - subscribe customers to a mailing list and generate product newletters.
76
- * [Reports](https://www.thenewsletterplugin.com/reports) - improves the internal statistics collection system and provides better reports of data collected for each sent email. And retargeting. Neat.
77
- * [Leads](https://www.thenewsletterplugin.com/leads) adds a fancy subscription popup box or a fixed bar to your website that will boost your conversion rate
78
- * [Amazon SES and other mail providers integration](https://www.thenewsletterplugin.com/integrations) - seamlessly integrate Amazon SES and other email service providers with The Newsletter Plugin. Hassle-free.
79
- * [Contact Form 7 Integration](https://www.thenewsletterplugin.com/documentation/contact-form-7-extension) - integrate the subscription on Contact Form 7 forms
80
- * [Ninja Forms Integration](https://www.thenewsletterplugin.com/documentation/ninjaforms-extension) - integrate the subscription on Ninja Forms
81
- * [WP Forms Integration](https://www.thenewsletterplugin.com/documentation/wpforms-extension) - integrate the subscription on WP Forms
82
- * Events Manager and The Events Calendar (By Modern Tribe) integrations - easily add events to your newsletters
83
- * [Google Analytics](https://www.thenewsletterplugin.com/google-analytics) - track newsletter links with Google UTM tracking paramaters
84
- * [Subscribe on Comment](https://www.thenewsletterplugin.com/documentation/comments-extension) - adds the subscription option to your blog comment form
85
- * [Geolocation](https://www.thenewsletterplugin.com/documentation/geolocation-extension) - adds geolocation capability to target subscribers by location
86
-
87
- = GDPR =
88
-
89
- The Newsletter Plugin provides all the technical tools needed to achieve GDPR compliancy and we're continuously working to improve them and to give support even for specific use cases.
90
- The plugin does not collect users' own subscribers data, nor it has any access to those data: hence, we are not a data processor, so a data processing agreement is not needed.
91
- Anyway if you configure the plugin to use external services (usually an external mail delivery service) you should check with that service if some sort of agreement is required.
92
-
93
- = Support =
94
-
95
- We provide support for our plugin on [Wordpress.org forums](https://wordpress.org/support/plugin/newsletter) and through our [official forum](https://www.thenewsletterplugin.com/forums).
96
-
97
- Premium Users with an active license have access to one-to-one support via our [ticketing system](https://www.thenewsletterplugin.com/support-ticket).
98
-
99
- = Developers =
100
-
101
- We have a [documentation section](https://www.thenewsletterplugin.com/documentation/developers/) dedicated to who want to develop with Newsletter.
102
-
103
- You can find us on [GitHub](https://github.com/TheNewsletterPlugin) with some examples of addons.
104
-
105
- = Follow Us =
106
-
107
- * **Our Official Website** - [https://www.thenewsletterplugin.com/](https://www.thenewsletterplugin.com/)
108
- * **GitHub** - [https://github.com/TheNewsletterPlugin](https://github.com/TheNewsletterPlugin)
109
- * **LinkedIn** - [https://www.linkedin.com/company/the-newsletter-plugin](https://www.linkedin.com/company/the-newsletter-plugin)
110
- * **Our Facebook Page** - [https://www.facebook.com/thenewsletterplugin](https://www.facebook.com/thenewsletterplugin)
111
- * **Our Twitter Account** - [https://twitter.com/newsletterwp](https://twitter.com/newsletterwp)
112
-
113
- == Frequently Asked Questions ==
114
-
115
- See the [Newsletter Forum](https://www.thenewsletterplugin.com/forums) to ask for help.
116
-
117
- For documentation start from [Newsletter documentation](https://www.thenewsletterplugin.com/documentation).
118
-
119
- Thank you, The Newsletter Team
120
-
121
- == Screenshots ==
122
-
123
- 1. The responsive email Drag & Drop composer
124
- 2. The plugin dashboard
125
- 3. The Reports extension
126
-
127
- == Changelog ==
128
-
129
- = 7.4.8 =
130
-
131
- * Changed the image block for Gmail
132
- * Removed the WP emojis staticizer on emails sent by Newsletter
133
-
134
- = 7.4.7 =
135
-
136
- * Changed removal order of the emojis admin script breaking newsletters' body
137
- * Disabled DOM warnings
138
-
139
- = 7.4.6 =
140
-
141
- * XSS security fix
142
-
143
- = 7.4.5 =
144
-
145
- * Fixed image block font-size to show the alt text (not for Outlook, it never shows the alt text)
146
- * XSS security fix
147
-
148
- = 7.4.4 =
149
-
150
- * Changed the dedicated page retrieval to intercept misconfigurations
151
- * Added option to accept repeated subscription in single opt-in (you should check if it is compatible with your privacy regulation)
152
- * Posts field selector keep now track of the previous post selected even if no more in list
153
- * Improved the error management when delivery fails using the WP native mailing function
154
- * Added title filter on posts block for compatibility with WPGlobus
155
-
156
- = 7.4.3 =
157
-
158
- * Removed obsolete note about the newsletter textual part
159
- * Introduced text part generator right now only for the Composer (with permission of Frank M.)
160
- * Added check on System>Status for images with relative URL
161
- * Addec check for Freesoul Deactivate Plugins
162
- * Minimal for layout now consider the button_label attribute
163
-
164
- = 7.4.2 =
165
-
166
- * Fixed the post date (regression)
167
- * Added link to the automatic plaintext generator plugin by franciscus
168
- * Possible fix for our Gutenberg block (sometimes) not working
169
- * Added uoloads dir and url on System>Status panel
170
- * Fixed Status panel error when a newsletter is in "error" status
171
- * Added default width to the logo on header block
172
-
173
- = 7.4.1 =
174
-
175
- * Added check on invalid media on two columns post type
176
- * WP 5.9.2 compatibility check
177
-
178
- = 7.4.0 =
179
-
180
- * Added privacy links
181
- * Added filter "newsletter_subscription" (documentation is under writing)
182
-
183
- = 7.3.9 =
184
-
185
- * Fixed grid layout not showing correctly on gmail
186
- * Fixed notice on widget page about the wrong editor enqueued
187
- * Removed wrong top bar warning
188
- * Fixed block options on posts block
189
- * Fixed padding on two column posts block
190
-
191
- = 7.3.8 =
192
-
193
- * Fixed graph scale on System/Scheduler panel
194
- * Fixed untraslated labels on subscriber management panels
195
- * Fixed the "toggle" private/public status on sent newsletter
196
- * Removed the "action call test" from the status panel since it does not work with some providers but does not affect the correct working of Newsletter
197
- * Added developer information on this readme
198
-
199
- = 7.3.7 =
200
-
201
- * Fixed unwanted redirects on subscription errors
202
- * Fixed composer page HTML
203
- * Minor fixes on PHP, CSS
204
- * Fixed notice on image block
205
-
206
- = 7.3.6 =
207
-
208
- * Improved composer reusability in other contexts
209
- * Removed obsolete composer code
210
- * Fixed default tracking for old theme-based neewsletters
211
- * Forced enconding on export (attempt)
212
- * WP 5.9 check
213
-
214
- = 7.3.5 =
215
-
216
- * WP 5.8.3 compatibility check
217
- * Fixed 2021 max year in date picker
218
- * Typos
219
-
220
- = 7.3.4 =
221
-
222
- * Fixed delivery fatal error management
223
- * Fixed link to the schduler dianostica panel
224
-
225
- = 7.3.3 =
226
-
227
- * Added "complained" status to subscriber filters
228
- * Fixed some links bringing to a "not allowed" page
229
- * Fixed a notice on delivery diagnostic page
230
- * Fixed logo width notice on header block
231
-
232
- = 7.3.2 =
233
-
234
- * Fixed the remote ip retrieval and clean up
235
- * Fixed header link to status page
236
- * Fixed database error with too long IPs
237
- * Fixed the subscription of cancelled addresses
238
- * Fixed sender and name customization
239
-
240
- = 7.3.1 =
241
-
242
- * Dropped old mailers support
243
- * Improved sending process and limits checking
244
- * Removed obsolete notifications
245
- * Fixed the wrong report on single email delivery speed
246
- * Added support for custom sending speed by addons
247
- * Improved excerpt generation (but it still depends on plugins and themes...)
248
- * Support for building button option on composer blocks
249
-
250
- = 7.3.0 =
251
-
252
- * Fixed header block layout with (logo only layout)
253
- * Check for conflicts on newsletter saving
254
- * Added the subscriber complained status (actually not managed automatically)
255
-
256
- = 7.2.9 =
257
-
258
- * Fixed generic action button confirmation popup not showing the message
259
- * [SECURITY] Pre parsing of IP addresses on security panel
260
- * [SECURITY] Comments allowed on IP address list on security panel
261
- * [COMPOSER] Fixed preset name/subject
262
- * Improved generated forms for accessibility
263
-
264
- = 7.2.8 =
265
-
266
- * Fixed the print_date() when no time is provided
267
- * Fixed date alignment on posts block
268
- * Folders reorganization
269
- * Social block with a new set of icons
270
- * Boosted performances of the lists management page (for big databases)
271
- * Added a new scheduler diagnostic panel
272
- * Seriously improved the cron statistics and diagnostic panel (check it out!)
273
- * Removed obsolete migration code from ancient versions
274
-
275
- = 7.2.7 =
276
-
277
- * Fixed JS error on composer sometimes preventing the correct initialization
278
-
279
- = 7.2.6 =
280
-
281
- * Fixed links on test emails sent to a free email address
282
- * Added attachment explanation
283
- * Added link to explain the use of the snippet
284
-
285
- = 7.2.5 =
286
-
287
- * Fixed subject not saved under specific circumstance
288
-
289
- = 7.2.4 =
290
-
291
- * Fixed the composer not starting for blog with SSL plugin but still HTTP configured on main WP settings
292
- * Changed labels on subscriber maintenance panel
293
- * Updated requirements for WP version
294
-
295
- = 7.2.3 =
296
-
297
- * [COMPOSER] Added approx. indicators of the subsject visibile part in Apple and Android clients (experimental)
298
- * [COMPOSER] New mobile version view directly while composing (experimental)
299
- * [COMPOSER] New test email to test subscribers or to specific email address
300
- * [COMPOSER] Fixed missing background when creating a new message from a preset
301
- * [COMPOSER] Added media selector to the CTA block
302
- * [COMPOSER] Added reference to the tags on HTML and Text blocks
303
- * [COMPOSER] Move the snippet (preheader) field near the subject
304
- * [COMPOSER] Footer block with three link options: unsubscribe, manage and view online
305
- * [COMPOSER] Improve font coherence between blocks (by default)
306
- * [ANTISPAM] Improved the antispam checks on subscription
307
- * [GENERAL] Removed obsolete folders and code
308
- * [NEWSLETTERS] Refactored subject ideas selector
309
- * [SUBSCRIPTION] Inverted extra profile fields and lists on standard subscription form
310
- * [GENERAL] IP address extracted checking proxy variables
311
- * [GENERAL] Improved sending stats collection and display for the delivery engine (not related to click/open stats)
312
-
313
- = 7.2.2 =
314
-
315
- * [COMPOSER] Posts block excerpt removed when set to 0-length
316
- * [GENERAL]Added special characters on test message
317
- * [GENERAL]Added more specific error for action calls on System/Status panel
318
- * [SUBSCRIPTION] Check for the _wp_amp_action_xhr_converted parameter by AMP plugin
319
-
320
- = 7.2.1 =
321
-
322
- * [GENERAL] Added more detailed admin logging
323
- * [NEWSLETTERS] Fixed scheduled date sometimes reset to 1/1/1970
324
-
325
- = 7.2.0 =
326
-
327
- * [PROFILE] Fixed activation email on profile change
328
- * [NEWSLETTERS] Extended year selection on newsletter scheduling
329
- * [DELIVERY] Breaking change: old enqueue() and flush() methods have been removed
330
- * [GENERAL] Fixed alert message on some buttons
331
- * [SUBSCRIPTION] Fixed error message on multiple subscriptions (when not allowed)
332
- * [GENERAL] Fixed erratic error log line on main log
333
-
334
- = 7.1.9 =
335
-
336
- * [GENERAL] Removed the encodign defatlt to Base 64 when not specified since it seems incompatible with some SMTP plugins
337
- * [GENERAL] Review some controls layout and behavior
338
- * [DEBUG] Improved logging on database errors
339
- * [GENERAL] Added TikTok, Discord and Twitch socials
340
- * [GENERAL] Fixed odd error reported related to the cron call statistics collection
341
- * [DEBUG] Added a delivery diagnostic panel
342
-
343
- = 7.1.8 =
344
-
345
- * [COMPOSER] Fixed alignment of single big image on Outlook Android
346
-
347
- = 7.1.7 =
348
-
349
- * [GENERAL] Fix of permalink onm email with multilanguage plugins
350
-
351
- = 7.1.6 =
352
-
353
- * [COMPOSER] Fixed one column big image Read more links
354
-
355
- = 7.1.5 =
356
-
357
- * [COMPOSER] Improve buttons on posts layout
358
- * [COMPOSER] Improved header layout and logo
359
- * [COMPOSER] Generally improved block spacing
360
-
361
- = 7.1.4 =
362
-
363
- * [COMPOSER] Fixed image block for Outlook
364
- * [GENERAL] Fix undefined values in Gutenberg block
365
-
366
- = 7.1.3 =
367
-
368
- * [COMPOSER] Improvements on blocks layout compatibility
369
- * [COMPOSER] Fixed preset global options saving
370
- * [COMPOSER] Content regeneration on preset selection
371
- * [GENERAL] Added to System menu the Site Health link, a not well known native page of WordPress with system information
372
- * [GENERAL] Added sending statistics reset button on status panel
373
-
374
- = 7.1.2 =
375
-
376
- * [ADDONS] Fixed the addons list
377
-
378
- = 7.1.1 =
379
-
380
- * [GENERAL] Improved profile related functions
381
- * [GENERAL] Fixed check on field names (thanks Peter P.)
382
- * [COMPOSER] Fix on preset selection
383
- * [GENERAL] Fix ajax subscription on error
384
-
385
- = 7.1.0 =
386
-
387
- * [COMPOSER] Added link to tags documentation to inject subscriber's data
388
- * [COMPOSER] Fixed layout of posts block for Outlook 365
389
- * [GENERAL] Improved caching of addons json (even on error)
390
- * [GENERAL] Status menu changed to System/Status and System/Logs
391
- * [API] Fixed the subscriber status management (the API Addon should be updated as well)
392
- * [GENERAL] Fixed management of fatal errors on sending
393
- * [GENERAL] Limited error string per message to 250 chars
394
- * [GENERAL] Fixed check on field names (thanks Peter P.)
395
-
396
-
397
- = 7.0.9 =
398
-
399
- * [CAPTCHA] Fixed button label translation
400
- * [DELIVERY] Mailing fatal error management with newsletter stop and reporting
401
- * [SMTP] Marked obsolete the internal SMTP and made available the free SMTP addon
402
- * [DELIVERY] Better management of delivery fatal errors with a new "error" status for newsletters
403
- * [GENERAL] New log files panel
404
- * [IMPORT] Old import panel replaced by the new (really better) import addon (file, copy and paste, bounced addresses import)
405
- * [NEWSLETTERS] It's now possible to specify the sender name and email per newsletter (thanks to Matthew S.)
406
-
407
- = 7.0.8 =
408
-
409
- * [SUBSCRIBERS] Changed action buttons
410
- * [GENERAL] Reorganization of CSS and removal of unused files
411
- * [DASHBOARD] New window open for links and fix of invalid URLs
412
- * [NEWSLETTERS] New action buttons
413
- * [GENERAL] Minor fixes and optimizations
414
- * [COMPOSER] Fixed rare size error on gif images
415
-
416
- = 7.0.7 =
417
-
418
- * [COMPOSER] Fixed a warning in some inline editable blocks
419
- * [NEWSLETTERS] Fixed style which made list labels badly readable
420
- * [NEWSLETTERS] Fixed style which made bullet lists white (not on delivered newsletters)
421
- * [GENERAL] Added some references to the not working scheduler warning
422
-
423
- = 7.0.6 =
424
-
425
- * [COMPOSER] CTA block not grabbing settings from the "old" blocks
426
- * [COMPOSER] CSS issue on button settings group
427
- * [COMPOSER] Posts block two columns layout (author and padding)
428
- * [IMPORT] Removed the old low-featured import (the free import addon has everything needed!)
429
- * [GENERAL] Compatibility check with WP 5.7
430
-
431
- = 7.0.5 =
432
-
433
- * [COMPOSER] Hero CTA button not working
434
- * Fixed to the hero block button
435
- * Added support for the SMTP addon
436
-
437
- = 7.0.4 =
438
-
439
- * [COMPOSER] Redesigned drag and drop composer
440
- * [COMPOSER] NEW! Save drag and drop composed newsletters as templates to reuse easily
441
- * Redesigned dashboard
442
- * Several bug and fixes
443
-
444
- = 7.0.3 =
445
-
446
- * Option to choose between unsubscription and profile link in the footer block
447
- * Direct image src URL for image block
448
- * New media selector for blocks
449
- * Minor fixes
450
- * Updated codemirror
451
- * Updated default theme
452
- * Fixed tag filter on posts block (when tags have parathesis or like)
453
- * Fixed composer visualization of bullet points
454
-
455
- = 7.0.2 =
456
-
457
- * Fixed media 2x resize
458
-
459
- = 7.0.1 =
460
-
461
- * Fixed enforced lists by language with Polylang
462
-
463
- = 7.0.0 =
464
-
465
- * Added multiple newsletter selection for deletion
466
- * Added text part on welcome and activation email
467
- * Added the attribute "show_form" to "newsletter" shortcode
468
- * Added filter on subscriber saving (from external systems) with wrong list field values
469
- * Added index.html on log folder
470
-
 
 
 
 
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.9
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
+
13
+ Newsletter is a **real newsletter and email marketing system** for your WordPress blog: perfect for list building, you can easily create, send and track e-mails, headache-free. It just works out of box!
14
+
15
+ = Discover a completely rewritten composer =
16
+
17
+ We redesigned our drag and drop composer to make your campaign creation even easier. Try it!
18
+
19
+ = Main Features =
20
+
21
+ * **Easy-to-use Drag and drop composer** to build responsive newsletters
22
+ * **Unlimited subscribers** with statistics
23
+ * **Unlimited newsletters** with tracking
24
+ * **Subscription spam check** with domain/ip black lists, Akismet, captcha
25
+ * **Delivery speed** fine control (from 12 emails per hour to as much as your blog can manage)
26
+ * [WPML ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Polylang ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Translatepress ready](https://www.thenewsletterplugin.com/documentation/multilanguage)
27
+ * All messages are **fully translatable** from administration panels (no .po/.mo file to edit)
28
+ * [GDPR ready](https://www.thenewsletterplugin.com/documentation/gdpr-compliancy)
29
+ * **Advanced targeting** with lists combinations like all in, at least one, not in and so on
30
+ * Customizable **subscription widget**, **page** or **custom form**
31
+ * Wordpress Users registration **seamless integration**
32
+ * **Single** And **Double Opt-In** plus privacy checkbox for EU laws compliance
33
+ * **Subscribers lists** to fine-target your campaigns
34
+ * PHP API and REST API for coders and integrations
35
+ * SMTP-Ready (with free addon)
36
+ * Customizable Themes
37
+ * **Status panel** to check your blog mailing capability and configuration
38
+ * **Compatible with every SMTP plugin**: Post SMTP (aka Postman), WP Mail SMTP, Easy WP SMTP, Easy SMTP Mail, WP Mail Bank, ...
39
+ * **Subscribers import** from file
40
+ * Newsletter with Html and Text message versions
41
+
42
+ = Find Us =
43
+
44
+ Newsletter is a continuously evolving plugin. Stay tuned following us on [Facebook](https://www.facebook.com/thenewsletterplugin/) or [our site](https://www.thenewsletterplugin.com/).
45
+
46
+ = Free Addons =
47
+
48
+ Improve The Newsletter Plugin with these free addons:
49
+
50
+ * [WP Registration Addon](https://www.thenewsletterplugin.com/documentation/wpusers-extension) - connects the WordPress standard and custom registration with Newsletter subscription. Optionally imports all registered users as subscribers.
51
+ * [Archive Addon](https://www.thenewsletterplugin.com/documentation/archive-extension) - creates a simple blog page which lists all your sent newsletters
52
+ * [Locked Content Addon](https://www.thenewsletterplugin.com/documentation/locked-content-extension) - open up your premium content only after subscription
53
+ * [Newsletter REST API Addon](https://www.thenewsletterplugin.com/documentation/developers/newsletter-api-2/) - adds a tier of REST api to integrate with the Newsletter core services
54
+ * [Sendinblue Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/sendinblue-extension/) - deliver your newsletters with Sendinblue
55
+ * [SMTP Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/smtp-extension/) - deliver your newsletters with external SMTP
56
+ * [Advanced Import Addon](https://www.thenewsletterplugin.com/documentation/addons/extended-features/advanced-import/) - import contact from file or copy and paste data with full mapping
57
+
58
+ (*easily add them from our [Addons panel](https://www.thenewsletterplugin.com/documentation/install-extensions)*)
59
+
60
+ = Addons on WordPress.org =
61
+
62
+ * [RSS Composer Block](https://wordpress.org/plugins/newsletter-rss-block/) - (3rd party) a composer block which builds its content from a RSS feed
63
+ * [Popup Maker Integration](https://wordpress.org/plugins/newsletter-popupmaker/) - (3rd party) integration of Newsletter forms with Popup Maker plugin
64
+ * [BuddyPress integration](https://wordpress.org/plugins/newsletter-buddypress/) - subscription opt-in inside BuddyPress signup form
65
+ * [WP User Manager addon for Newsletter](https://wordpress.org/plugins/wpum-newsletter/) - adds the subscription option on registration forms
66
+ * [Plaintext Generator](https://wordpress.org/plugins/plaintext-newsletter/) - generates the plaintext version from an HTML newsletter
67
+
68
+ = Professional Addons =
69
+
70
+ Need *more power*? Feel *something's missing*? The Newsletter Plugin features can be easily extended through our **premium, professional Addons**! Let us introduce just two of them : )
71
+
72
+ * [Automated](https://www.thenewsletterplugin.com/automated) - generates and sends your newsletters using your blog last posts, even custom ones like events or products. Just sit and watch!
73
+ * [Autoresponder](https://www.thenewsletterplugin.com/autoresponder) - creates email series to follow up your subscribers
74
+ * [Extended Composer Blocks](https://www.thenewsletterplugin.com/composer) - adds new blocks to the drag & drop composer
75
+ * [WooCommerce Integration](https://www.thenewsletterplugin.com/woocommerce) - subscribe customers to a mailing list and generate product newletters.
76
+ * [Reports](https://www.thenewsletterplugin.com/reports) - improves the internal statistics collection system and provides better reports of data collected for each sent email. And retargeting. Neat.
77
+ * [Leads](https://www.thenewsletterplugin.com/leads) adds a fancy subscription popup box or a fixed bar to your website that will boost your conversion rate
78
+ * [Amazon SES and other mail providers integration](https://www.thenewsletterplugin.com/integrations) - seamlessly integrate Amazon SES and other email service providers with The Newsletter Plugin. Hassle-free.
79
+ * [Contact Form 7 Integration](https://www.thenewsletterplugin.com/documentation/contact-form-7-extension) - integrate the subscription on Contact Form 7 forms
80
+ * [Ninja Forms Integration](https://www.thenewsletterplugin.com/documentation/ninjaforms-extension) - integrate the subscription on Ninja Forms
81
+ * [WP Forms Integration](https://www.thenewsletterplugin.com/documentation/wpforms-extension) - integrate the subscription on WP Forms
82
+ * Events Manager and The Events Calendar (By Modern Tribe) integrations - easily add events to your newsletters
83
+ * [Google Analytics](https://www.thenewsletterplugin.com/google-analytics) - track newsletter links with Google UTM tracking paramaters
84
+ * [Subscribe on Comment](https://www.thenewsletterplugin.com/documentation/comments-extension) - adds the subscription option to your blog comment form
85
+ * [Geolocation](https://www.thenewsletterplugin.com/documentation/geolocation-extension) - adds geolocation capability to target subscribers by location
86
+
87
+ = GDPR =
88
+
89
+ The Newsletter Plugin provides all the technical tools needed to achieve GDPR compliancy and we're continuously working to improve them and to give support even for specific use cases.
90
+ The plugin does not collect users' own subscribers data, nor it has any access to those data: hence, we are not a data processor, so a data processing agreement is not needed.
91
+ Anyway if you configure the plugin to use external services (usually an external mail delivery service) you should check with that service if some sort of agreement is required.
92
+
93
+ = Support =
94
+
95
+ We provide support for our plugin on [Wordpress.org forums](https://wordpress.org/support/plugin/newsletter) and through our [official forum](https://www.thenewsletterplugin.com/forums).
96
+
97
+ Premium Users with an active license have access to one-to-one support via our [ticketing system](https://www.thenewsletterplugin.com/support-ticket).
98
+
99
+ = Developers =
100
+
101
+ We have a [documentation section](https://www.thenewsletterplugin.com/documentation/developers/) dedicated to who want to develop with Newsletter.
102
+
103
+ You can find us on [GitHub](https://github.com/TheNewsletterPlugin) with some examples of addons.
104
+
105
+ = Follow Us =
106
+
107
+ * **Our Official Website** - [https://www.thenewsletterplugin.com/](https://www.thenewsletterplugin.com/)
108
+ * **GitHub** - [https://github.com/TheNewsletterPlugin](https://github.com/TheNewsletterPlugin)
109
+ * **LinkedIn** - [https://www.linkedin.com/company/the-newsletter-plugin](https://www.linkedin.com/company/the-newsletter-plugin)
110
+ * **Our Facebook Page** - [https://www.facebook.com/thenewsletterplugin](https://www.facebook.com/thenewsletterplugin)
111
+ * **Our Twitter Account** - [https://twitter.com/newsletterwp](https://twitter.com/newsletterwp)
112
+
113
+ == Frequently Asked Questions ==
114
+
115
+ See the [Newsletter Forum](https://www.thenewsletterplugin.com/forums) to ask for help.
116
+
117
+ For documentation start from [Newsletter documentation](https://www.thenewsletterplugin.com/documentation).
118
+
119
+ Thank you, The Newsletter Team
120
+
121
+ == Screenshots ==
122
+
123
+ 1. The responsive email Drag & Drop composer
124
+ 2. The plugin dashboard
125
+ 3. The Reports extension
126
+
127
+ == Changelog ==
128
+
129
+ = 7.4.9 =
130
+
131
+ * Composer fix not updating new newsletters
132
+
133
+ = 7.4.8 =
134
+
135
+ * Changed the image block for Gmail
136
+ * Removed the WP emojis staticizer on emails sent by Newsletter
137
+
138
+ = 7.4.7 =
139
+
140
+ * Changed removal order of the emojis admin script breaking newsletters' body
141
+ * Disabled DOM warnings
142
+
143
+ = 7.4.6 =
144
+
145
+ * XSS security fix
146
+
147
+ = 7.4.5 =
148
+
149
+ * Fixed image block font-size to show the alt text (not for Outlook, it never shows the alt text)
150
+ * XSS security fix
151
+
152
+ = 7.4.4 =
153
+
154
+ * Changed the dedicated page retrieval to intercept misconfigurations
155
+ * Added option to accept repeated subscription in single opt-in (you should check if it is compatible with your privacy regulation)
156
+ * Posts field selector keep now track of the previous post selected even if no more in list
157
+ * Improved the error management when delivery fails using the WP native mailing function
158
+ * Added title filter on posts block for compatibility with WPGlobus
159
+
160
+ = 7.4.3 =
161
+
162
+ * Removed obsolete note about the newsletter textual part
163
+ * Introduced text part generator right now only for the Composer (with permission of Frank M.)
164
+ * Added check on System>Status for images with relative URL
165
+ * Addec check for Freesoul Deactivate Plugins
166
+ * Minimal for layout now consider the button_label attribute
167
+
168
+ = 7.4.2 =
169
+
170
+ * Fixed the post date (regression)
171
+ * Added link to the automatic plaintext generator plugin by franciscus
172
+ * Possible fix for our Gutenberg block (sometimes) not working
173
+ * Added uoloads dir and url on System>Status panel
174
+ * Fixed Status panel error when a newsletter is in "error" status
175
+ * Added default width to the logo on header block
176
+
177
+ = 7.4.1 =
178
+
179
+ * Added check on invalid media on two columns post type
180
+ * WP 5.9.2 compatibility check
181
+
182
+ = 7.4.0 =
183
+
184
+ * Added privacy links
185
+ * Added filter "newsletter_subscription" (documentation is under writing)
186
+
187
+ = 7.3.9 =
188
+
189
+ * Fixed grid layout not showing correctly on gmail
190
+ * Fixed notice on widget page about the wrong editor enqueued
191
+ * Removed wrong top bar warning
192
+ * Fixed block options on posts block
193
+ * Fixed padding on two column posts block
194
+
195
+ = 7.3.8 =
196
+
197
+ * Fixed graph scale on System/Scheduler panel
198
+ * Fixed untraslated labels on subscriber management panels
199
+ * Fixed the "toggle" private/public status on sent newsletter
200
+ * Removed the "action call test" from the status panel since it does not work with some providers but does not affect the correct working of Newsletter
201
+ * Added developer information on this readme
202
+
203
+ = 7.3.7 =
204
+
205
+ * Fixed unwanted redirects on subscription errors
206
+ * Fixed composer page HTML
207
+ * Minor fixes on PHP, CSS
208
+ * Fixed notice on image block
209
+
210
+ = 7.3.6 =
211
+
212
+ * Improved composer reusability in other contexts
213
+ * Removed obsolete composer code
214
+ * Fixed default tracking for old theme-based neewsletters
215
+ * Forced enconding on export (attempt)
216
+ * WP 5.9 check
217
+
218
+ = 7.3.5 =
219
+
220
+ * WP 5.8.3 compatibility check
221
+ * Fixed 2021 max year in date picker
222
+ * Typos
223
+
224
+ = 7.3.4 =
225
+
226
+ * Fixed delivery fatal error management
227
+ * Fixed link to the schduler dianostica panel
228
+
229
+ = 7.3.3 =
230
+
231
+ * Added "complained" status to subscriber filters
232
+ * Fixed some links bringing to a "not allowed" page
233
+ * Fixed a notice on delivery diagnostic page
234
+ * Fixed logo width notice on header block
235
+
236
+ = 7.3.2 =
237
+
238
+ * Fixed the remote ip retrieval and clean up
239
+ * Fixed header link to status page
240
+ * Fixed database error with too long IPs
241
+ * Fixed the subscription of cancelled addresses
242
+ * Fixed sender and name customization
243
+
244
+ = 7.3.1 =
245
+
246
+ * Dropped old mailers support
247
+ * Improved sending process and limits checking
248
+ * Removed obsolete notifications
249
+ * Fixed the wrong report on single email delivery speed
250
+ * Added support for custom sending speed by addons
251
+ * Improved excerpt generation (but it still depends on plugins and themes...)
252
+ * Support for building button option on composer blocks
253
+
254
+ = 7.3.0 =
255
+
256
+ * Fixed header block layout with (logo only layout)
257
+ * Check for conflicts on newsletter saving
258
+ * Added the subscriber complained status (actually not managed automatically)
259
+
260
+ = 7.2.9 =
261
+
262
+ * Fixed generic action button confirmation popup not showing the message
263
+ * [SECURITY] Pre parsing of IP addresses on security panel
264
+ * [SECURITY] Comments allowed on IP address list on security panel
265
+ * [COMPOSER] Fixed preset name/subject
266
+ * Improved generated forms for accessibility
267
+
268
+ = 7.2.8 =
269
+
270
+ * Fixed the print_date() when no time is provided
271
+ * Fixed date alignment on posts block
272
+ * Folders reorganization
273
+ * Social block with a new set of icons
274
+ * Boosted performances of the lists management page (for big databases)
275
+ * Added a new scheduler diagnostic panel
276
+ * Seriously improved the cron statistics and diagnostic panel (check it out!)
277
+ * Removed obsolete migration code from ancient versions
278
+
279
+ = 7.2.7 =
280
+
281
+ * Fixed JS error on composer sometimes preventing the correct initialization
282
+
283
+ = 7.2.6 =
284
+
285
+ * Fixed links on test emails sent to a free email address
286
+ * Added attachment explanation
287
+ * Added link to explain the use of the snippet
288
+
289
+ = 7.2.5 =
290
+
291
+ * Fixed subject not saved under specific circumstance
292
+
293
+ = 7.2.4 =
294
+
295
+ * Fixed the composer not starting for blog with SSL plugin but still HTTP configured on main WP settings
296
+ * Changed labels on subscriber maintenance panel
297
+ * Updated requirements for WP version
298
+
299
+ = 7.2.3 =
300
+
301
+ * [COMPOSER] Added approx. indicators of the subsject visibile part in Apple and Android clients (experimental)
302
+ * [COMPOSER] New mobile version view directly while composing (experimental)
303
+ * [COMPOSER] New test email to test subscribers or to specific email address
304
+ * [COMPOSER] Fixed missing background when creating a new message from a preset
305
+ * [COMPOSER] Added media selector to the CTA block
306
+ * [COMPOSER] Added reference to the tags on HTML and Text blocks
307
+ * [COMPOSER] Move the snippet (preheader) field near the subject
308
+ * [COMPOSER] Footer block with three link options: unsubscribe, manage and view online
309
+ * [COMPOSER] Improve font coherence between blocks (by default)
310
+ * [ANTISPAM] Improved the antispam checks on subscription
311
+ * [GENERAL] Removed obsolete folders and code
312
+ * [NEWSLETTERS] Refactored subject ideas selector
313
+ * [SUBSCRIPTION] Inverted extra profile fields and lists on standard subscription form
314
+ * [GENERAL] IP address extracted checking proxy variables
315
+ * [GENERAL] Improved sending stats collection and display for the delivery engine (not related to click/open stats)
316
+
317
+ = 7.2.2 =
318
+
319
+ * [COMPOSER] Posts block excerpt removed when set to 0-length
320
+ * [GENERAL]Added special characters on test message
321
+ * [GENERAL]Added more specific error for action calls on System/Status panel
322
+ * [SUBSCRIPTION] Check for the _wp_amp_action_xhr_converted parameter by AMP plugin
323
+
324
+ = 7.2.1 =
325
+
326
+ * [GENERAL] Added more detailed admin logging
327
+ * [NEWSLETTERS] Fixed scheduled date sometimes reset to 1/1/1970
328
+
329
+ = 7.2.0 =
330
+
331
+ * [PROFILE] Fixed activation email on profile change
332
+ * [NEWSLETTERS] Extended year selection on newsletter scheduling
333
+ * [DELIVERY] Breaking change: old enqueue() and flush() methods have been removed
334
+ * [GENERAL] Fixed alert message on some buttons
335
+ * [SUBSCRIPTION] Fixed error message on multiple subscriptions (when not allowed)
336
+ * [GENERAL] Fixed erratic error log line on main log
337
+
338
+ = 7.1.9 =
339
+
340
+ * [GENERAL] Removed the encodign defatlt to Base 64 when not specified since it seems incompatible with some SMTP plugins
341
+ * [GENERAL] Review some controls layout and behavior
342
+ * [DEBUG] Improved logging on database errors
343
+ * [GENERAL] Added TikTok, Discord and Twitch socials
344
+ * [GENERAL] Fixed odd error reported related to the cron call statistics collection
345
+ * [DEBUG] Added a delivery diagnostic panel
346
+
347
+ = 7.1.8 =
348
+
349
+ * [COMPOSER] Fixed alignment of single big image on Outlook Android
350
+
351
+ = 7.1.7 =
352
+
353
+ * [GENERAL] Fix of permalink onm email with multilanguage plugins
354
+
355
+ = 7.1.6 =
356
+
357
+ * [COMPOSER] Fixed one column big image Read more links
358
+
359
+ = 7.1.5 =
360
+
361
+ * [COMPOSER] Improve buttons on posts layout
362
+ * [COMPOSER] Improved header layout and logo
363
+ * [COMPOSER] Generally improved block spacing
364
+
365
+ = 7.1.4 =
366
+
367
+ * [COMPOSER] Fixed image block for Outlook
368
+ * [GENERAL] Fix undefined values in Gutenberg block
369
+
370
+ = 7.1.3 =
371
+
372
+ * [COMPOSER] Improvements on blocks layout compatibility
373
+ * [COMPOSER] Fixed preset global options saving
374
+ * [COMPOSER] Content regeneration on preset selection
375
+ * [GENERAL] Added to System menu the Site Health link, a not well known native page of WordPress with system information
376
+ * [GENERAL] Added sending statistics reset button on status panel
377
+
378
+ = 7.1.2 =
379
+
380
+ * [ADDONS] Fixed the addons list
381
+
382
+ = 7.1.1 =
383
+
384
+ * [GENERAL] Improved profile related functions
385
+ * [GENERAL] Fixed check on field names (thanks Peter P.)
386
+ * [COMPOSER] Fix on preset selection
387
+ * [GENERAL] Fix ajax subscription on error
388
+
389
+ = 7.1.0 =
390
+
391
+ * [COMPOSER] Added link to tags documentation to inject subscriber's data
392
+ * [COMPOSER] Fixed layout of posts block for Outlook 365
393
+ * [GENERAL] Improved caching of addons json (even on error)
394
+ * [GENERAL] Status menu changed to System/Status and System/Logs
395
+ * [API] Fixed the subscriber status management (the API Addon should be updated as well)
396
+ * [GENERAL] Fixed management of fatal errors on sending
397
+ * [GENERAL] Limited error string per message to 250 chars
398
+ * [GENERAL] Fixed check on field names (thanks Peter P.)
399
+
400
+
401
+ = 7.0.9 =
402
+
403
+ * [CAPTCHA] Fixed button label translation
404
+ * [DELIVERY] Mailing fatal error management with newsletter stop and reporting
405
+ * [SMTP] Marked obsolete the internal SMTP and made available the free SMTP addon
406
+ * [DELIVERY] Better management of delivery fatal errors with a new "error" status for newsletters
407
+ * [GENERAL] New log files panel
408
+ * [IMPORT] Old import panel replaced by the new (really better) import addon (file, copy and paste, bounced addresses import)
409
+ * [NEWSLETTERS] It's now possible to specify the sender name and email per newsletter (thanks to Matthew S.)
410
+
411
+ = 7.0.8 =
412
+
413
+ * [SUBSCRIBERS] Changed action buttons
414
+ * [GENERAL] Reorganization of CSS and removal of unused files
415
+ * [DASHBOARD] New window open for links and fix of invalid URLs
416
+ * [NEWSLETTERS] New action buttons
417
+ * [GENERAL] Minor fixes and optimizations
418
+ * [COMPOSER] Fixed rare size error on gif images
419
+
420
+ = 7.0.7 =
421
+
422
+ * [COMPOSER] Fixed a warning in some inline editable blocks
423
+ * [NEWSLETTERS] Fixed style which made list labels badly readable
424
+ * [NEWSLETTERS] Fixed style which made bullet lists white (not on delivered newsletters)
425
+ * [GENERAL] Added some references to the not working scheduler warning
426
+
427
+ = 7.0.6 =
428
+
429
+ * [COMPOSER] CTA block not grabbing settings from the "old" blocks
430
+ * [COMPOSER] CSS issue on button settings group
431
+ * [COMPOSER] Posts block two columns layout (author and padding)
432
+ * [IMPORT] Removed the old low-featured import (the free import addon has everything needed!)
433
+ * [GENERAL] Compatibility check with WP 5.7
434
+
435
+ = 7.0.5 =
436
+
437
+ * [COMPOSER] Hero CTA button not working
438
+ * Fixed to the hero block button
439
+ * Added support for the SMTP addon
440
+
441
+ = 7.0.4 =
442
+
443
+ * [COMPOSER] Redesigned drag and drop composer
444
+ * [COMPOSER] NEW! Save drag and drop composed newsletters as templates to reuse easily
445
+ * Redesigned dashboard
446
+ * Several bug and fixes
447
+
448
+ = 7.0.3 =
449
+
450
+ * Option to choose between unsubscription and profile link in the footer block
451
+ * Direct image src URL for image block
452
+ * New media selector for blocks
453
+ * Minor fixes
454
+ * Updated codemirror
455
+ * Updated default theme
456
+ * Fixed tag filter on posts block (when tags have parathesis or like)
457
+ * Fixed composer visualization of bullet points
458
+
459
+ = 7.0.2 =
460
+
461
+ * Fixed media 2x resize
462
+
463
+ = 7.0.1 =
464
+
465
+ * Fixed enforced lists by language with Polylang
466
+
467
+ = 7.0.0 =
468
+
469
+ * Added multiple newsletter selection for deletion
470
+ * Added text part on welcome and activation email
471
+ * Added the attribute "show_form" to "newsletter" shortcode
472
+ * Added filter on subscriber saving (from external systems) with wrong list field values
473
+ * Added index.html on log folder
474
+