Newsletter - Version 7.5.4

Version Description

  • Fixed composer icons path for Amazon AWS+Bitnami installations
  • Fixed profile form on multilanguage installation
Download this release

Release Info

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

Code changes from version 7.5.3 to 7.5.4

Files changed (6) hide show
  1. emails/emails.php +1365 -1363
  2. includes/module.php +2632 -2632
  3. plugin.php +2 -2
  4. profile/profile.php +494 -494
  5. readme.txt +7 -2
  6. tnp-header.php +1 -1
emails/emails.php CHANGED
@@ -1,1363 +1,1365 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- class NewsletterEmails extends NewsletterModule {
6
-
7
- static $instance;
8
-
9
- const EDITOR_COMPOSER = 2;
10
- const EDITOR_HTML = 1;
11
- const EDITOR_TINYMCE = 0;
12
-
13
- static $PRESETS_LIST;
14
-
15
- const PRESET_EMAIL_TYPE = 'composer_template';
16
-
17
- // Cache
18
- var $blocks = null;
19
-
20
- /**
21
- * @return NewsletterEmails
22
- */
23
- static function instance() {
24
- if (self::$instance == null) {
25
- self::$instance = new NewsletterEmails();
26
- }
27
-
28
- return self::$instance;
29
- }
30
-
31
- function __construct() {
32
- self::$PRESETS_LIST = array("cta", "invite", "announcement", "posts", "sales", "product", "tour", "simple");
33
- $this->themes = new NewsletterThemes('emails');
34
- parent::__construct('emails', '1.1.5');
35
- add_action('newsletter_action', array($this, 'hook_newsletter_action'), 13, 3);
36
- add_action('newsletter_init', [$this, 'hook_newsletter_init']);
37
-
38
- if (is_admin()) {
39
- // Thank you to plugins which add the WP editor on other admin plugin pages...
40
- if (isset($_GET['page']) && $_GET['page'] == 'newsletter_emails_edit') {
41
- global $wp_actions;
42
- $wp_actions['wp_enqueue_editor'] = 1;
43
- }
44
- }
45
- }
46
-
47
- function hook_newsletter_init() {
48
- if (is_admin()) {
49
- if (defined('DOING_AJAX') && DOING_AJAX) {
50
- if (Newsletter::instance()->is_allowed()) {
51
- add_action('wp_ajax_tnpc_render', array($this, 'tnpc_render_callback'));
52
- add_action('wp_ajax_tnpc_preview', array($this, 'tnpc_preview_callback'));
53
- add_action('wp_ajax_tnpc_css', array($this, 'tnpc_css_callback'));
54
- add_action('wp_ajax_tnpc_options', array($this, 'hook_wp_ajax_tnpc_options'));
55
- add_action('wp_ajax_tnpc_get_all_presets', array($this, 'ajax_get_all_presets'));
56
- add_action('wp_ajax_tnpc_get_preset', array($this, 'ajax_get_preset'));
57
- add_action('wp_ajax_tnpc_delete_preset', array($this, 'hook_wp_ajax_tnpc_delete_preset'));
58
- add_action('wp_ajax_tnpc_regenerate_email', array($this, 'hook_wp_ajax_tnpc_regenerate_email'));
59
- }
60
- }
61
- }
62
- }
63
-
64
- function options_decode($options) {
65
- // Old "query string" format
66
- if (is_string($options) && strpos($options, 'options[') !== false) {
67
- $opts = [];
68
- parse_str($options, $opts);
69
- $options = $opts['options'];
70
- }
71
-
72
- if (is_array($options)) {
73
- return $options;
74
- }
75
-
76
- // Json data should be base64 encoded, but for short time it wasn't
77
- $tmp = json_decode($options, true);
78
- if (is_null($tmp)) {
79
- return json_decode(base64_decode($options), true);
80
- } else {
81
- return $tmp;
82
- }
83
- }
84
-
85
- /**
86
- *
87
- * @param array $options Options array
88
- */
89
- function options_encode($options) {
90
- return base64_encode(json_encode($options, JSON_HEX_TAG | JSON_HEX_AMP));
91
- }
92
-
93
- /**
94
- * Builds and returns the HTML with the form fields of a specific block.
95
- *
96
- * @global wpdb $wpdb
97
- */
98
- function hook_wp_ajax_tnpc_options() {
99
- global $wpdb;
100
-
101
- $block = $this->get_block($_REQUEST['id']);
102
- if (!$block) {
103
- die('Block not found with id ' . esc_html($_REQUEST['id']));
104
- }
105
-
106
- if (!class_exists('NewsletterControls')) {
107
- include NEWSLETTER_INCLUDES_DIR . '/controls.php';
108
- }
109
-
110
- $options = $this->options_decode(stripslashes_deep($_REQUEST['options']));
111
- $composer = isset($_POST['composer']) ? $_POST['composer'] : [];
112
-
113
- $context = array('type' => '');
114
- if (isset($_REQUEST['context_type'])) {
115
- $context['type'] = $_REQUEST['context_type'];
116
- }
117
-
118
- $controls = new NewsletterControls($options);
119
- $fields = new NewsletterFields($controls);
120
-
121
- $controls->init();
122
- echo '<input type="hidden" name="action" value="tnpc_render">';
123
- echo '<input type="hidden" name="id" value="' . esc_attr($_REQUEST['id']) . '">';
124
- echo '<input type="hidden" name="context_type" value="' . esc_attr($context['type']) . '">';
125
- $inline_edits = '';
126
- if (isset($controls->data['inline_edits'])) {
127
- $inline_edits = $controls->data['inline_edits'];
128
- }
129
- echo '<input type="hidden" name="options[inline_edits]" value="', esc_attr($this->options_encode($inline_edits)), '">';
130
- echo "<h2>", esc_html($block["name"]), "</h2>";
131
- include $block['dir'] . '/options.php';
132
- wp_die();
133
- }
134
-
135
- /**
136
- * Retrieves the presets list (no id in GET) or a specific preset id in GET)
137
- */
138
- public function ajax_get_all_presets() {
139
- wp_send_json_success($this->get_all_preset());
140
- }
141
-
142
- public function ajax_get_preset() {
143
-
144
- if (empty($_REQUEST['id'])) {
145
- wp_send_json_error([
146
- 'msg' => __('Invalid preset ID')
147
- ]);
148
- }
149
-
150
- $preset_id = $_REQUEST['id'];
151
- $preset_content = $this->get_preset_content($preset_id);
152
- $global_options = $this->get_preset_global_options($preset_id);
153
-
154
- wp_send_json_success([
155
- 'content' => $preset_content,
156
- 'globalOptions' => $global_options,
157
- ]);
158
- }
159
-
160
- private function get_preset_content($preset_id) {
161
-
162
- $content = '';
163
-
164
- if ($this->is_a_tnp_default_preset($preset_id)) {
165
-
166
- // Get preset from file
167
- $preset = $this->get_preset_from_file($preset_id);
168
-
169
- foreach ($preset->blocks as $item) {
170
- ob_start();
171
- $this->render_block($item->block, true, (array) $item->options);
172
- $content .= trim(ob_get_clean());
173
- }
174
- } else {
175
-
176
- // Get preset from db
177
- $preset_email = $this->get_email(intval($preset_id));
178
- $global_options = $this->extract_global_options_from($preset_email);
179
- $content = $this->regenerate_email_blocks($preset_email->message, $global_options);
180
- }
181
-
182
- return $content;
183
- }
184
-
185
- private function get_preset_global_options($preset_id) {
186
-
187
- if ($this->is_a_tnp_default_preset($preset_id)) {
188
- return [];
189
- }
190
-
191
- // Get preset from db
192
- $preset_email = $this->get_email(intval($preset_id));
193
- $global_options = $this->extract_global_options_from($preset_email);
194
-
195
- return $global_options;
196
- }
197
-
198
- private function extract_global_options_from($email) {
199
- $global_options = [];
200
- foreach ($email->options as $global_option_name => $global_option) {
201
- if (strpos($global_option_name, 'composer_') === 0) {
202
- $global_options[str_replace('composer_', '', $global_option_name)] = $global_option;
203
- }
204
- }
205
-
206
- return $global_options;
207
- }
208
-
209
- private function is_a_tnp_default_preset($preset_id) {
210
- return in_array($preset_id, self::$PRESETS_LIST);
211
- }
212
-
213
- private function get_all_preset() {
214
-
215
- $content = "<div class='tnpc-preset-container'>";
216
-
217
- if ($this->is_normal_context_request()) {
218
- $content .= "<div class='tnpc-preset-legacy-themes'><a href='" . $this->get_admin_page_url('theme') . "'>" . __('Looking for legacy themes?', 'newsletter') . "</a></div>";
219
- }
220
-
221
- // LOAD USER PRESETS
222
- $user_preset_list = $this->get_emails(self::PRESET_EMAIL_TYPE);
223
-
224
- foreach ($user_preset_list as $user_preset) {
225
-
226
- $default_icon_url = NEWSLETTER_URL . "/emails/presets/default-icon.png?ver=2";
227
- $preset_name = $user_preset->subject;
228
- $delete_preset_text = __('Delete', 'newsletter');
229
- $edit_preset_text = __('Edit', 'newsletter');
230
-
231
- // esc_js() assumes the string will be in single quote (arghhh!!!)
232
- $onclick_edit = 'tnpc_edit_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
233
- $onclick_delete = 'tnpc_delete_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
234
- $onclick_load = 'tnpc_load_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
235
-
236
- $content .= "<div class='tnpc-preset' onclick='" . esc_attr($onclick_load) . "'>\n";
237
- $content .= "<img src='$default_icon_url' title='" . esc_attr($preset_name) . "' alt='" . esc_attr($preset_name) . "'>\n";
238
- $content .= "<span class='tnpc-preset-label'>" . esc_html($user_preset->subject) . "</span>\n";
239
- $content .= "<span class='tnpc-delete-preset' onclick='" . esc_attr($onclick_delete) . "' title='" . esc_attr($delete_preset_text) . "'><i class='fas fa-times'></i></span>\n";
240
- $content .= "<span class='tnpc-edit-preset' onclick='" . esc_attr($onclick_edit) . "' title='" . esc_attr($edit_preset_text) . "'><i class='fas fa-pencil-alt'></i></span>\n";
241
- $content .= "</div>";
242
- }
243
-
244
- // LOAD TNP PRESETS
245
- foreach (self::$PRESETS_LIST as $id) {
246
- $preset = $this->get_preset_from_file($id);
247
- $preset_name = esc_html($preset->name);
248
- $content .= "<div class='tnpc-preset' onclick='tnpc_load_preset(\"$id\")'>";
249
- $content .= "<img src='$preset->icon' title='$preset_name' alt='$preset_name'/>";
250
- $content .= "<span class='tnpc-preset-label'>$preset_name</span>";
251
- $content .= "</div>";
252
- }
253
-
254
- if ($this->is_normal_context_request()) {
255
- $content .= $this->get_automated_spot_element();
256
- $content .= $this->get_autoresponder_spot_element();
257
- $content .= $this->get_raw_html_preset_element();
258
- }
259
-
260
- return $content;
261
- }
262
-
263
- private function is_normal_context_request() {
264
- return empty($_REQUEST['context_type']);
265
- }
266
-
267
- private function is_automated_context_request() {
268
- return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'automated';
269
- }
270
-
271
- private function is_autoresponder_context_request() {
272
- return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'autoresponder';
273
- }
274
-
275
- private function get_automated_spot_element() {
276
- $result = "<div class='tnpc-preset'>";
277
- if (class_exists('NewsletterAutomated')) {
278
- $result .= "<a href='?page=newsletter_automated_index'>";
279
- } else {
280
- $result .= "<a href='https://www.thenewsletterplugin.com/automated?utm_source=composer&utm_campaign=plugin&utm_medium=automated'>";
281
- }
282
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/automated.png' title='Automated addon' alt='Automated'/>";
283
- $result .= "<span class='tnpc-preset-label'>Daily, weekly and monthly newsletters</span></a>";
284
- $result .= "</div>";
285
-
286
- return $result;
287
- }
288
-
289
- private function get_autoresponder_spot_element() {
290
- $result = "<div class='tnpc-preset'>";
291
- if (class_exists('NewsletterAutoresponder')) {
292
- $result .= "<a href='?page=newsletter_autoresponder_index'>";
293
- } else {
294
- $result .= "<a href='https://www.thenewsletterplugin.com/autoresponder?utm_source=composer&utm_campaign=plugin&utm_medium=autoresponder' target='_blank'>";
295
- }
296
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/autoresponder.png' title='Autoresponder addon' alt='Autoresponder'/>";
297
- $result .= "<span class='tnpc-preset-label'>Autoresponders</span></a>";
298
- $result .= "</div>";
299
-
300
- return $result;
301
- }
302
-
303
- private function get_raw_html_preset_element() {
304
-
305
- $result = "<div class='tnpc-preset tnpc-preset-html' onclick='location.href=\"" . wp_nonce_url('admin.php?page=newsletter_emails_new&id=rawhtml', 'newsletter-new') . "\"'>";
306
- $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/rawhtml.png' title='RAW HTML' alt='RAW'/>";
307
- $result .= "<span class='tnpc-preset-label'>Raw HTML</span>";
308
- $result .= "</div>";
309
-
310
- $result .= "<div class='clear'></div>";
311
- $result .= "</div>";
312
-
313
- return $result;
314
- }
315
-
316
- function has_dynamic_blocks($theme) {
317
- preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
318
- foreach ($matches[1] as $match) {
319
- $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
320
- $options = $this->options_decode($a);
321
-
322
- $block = $this->get_block($options['block_id']);
323
- if (!$block) {
324
- continue;
325
- }
326
- if ($block['type'] == 'dynamic') {
327
- return true;
328
- }
329
- }
330
- return false;
331
- }
332
-
333
- /**
334
- * Regenerates a saved composed email rendering each block. Regeneration is
335
- * conditioned (possibly) by the context. The context is usually passed to blocks
336
- * so they can act in the right manner.
337
- *
338
- * $context contains a type and, for automated, the last_run.
339
- *
340
- * $email can actually be even a string containing the full newsletter HTML code.
341
- *
342
- * @param TNP_Email $email
343
- * @return string
344
- */
345
- function regenerate($email, $context = []) {
346
-
347
- $context = array_merge(['last_run' => 0, 'type' => ''], $context);
348
-
349
- $composer = [];
350
- foreach ($email->options as $k=>$v) {
351
- if (strpos($k, 'composer_') !== 0) continue;
352
- $composer[substr($k, 9)] = $v;
353
- }
354
-
355
-
356
- preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
357
-
358
- $result = '';
359
- $subject = '';
360
-
361
- foreach ($matches[1] as $match) {
362
- $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
363
- $options = $this->options_decode($a);
364
-
365
- $block = $this->get_block($options['block_id']);
366
- if (!$block) {
367
- $this->logger->debug('Unable to load the block ' . $options['block_id']);
368
- //continue;
369
- }
370
-
371
- ob_start();
372
- $out = $this->render_block($options['block_id'], true, $options, $context, $composer);
373
- if (is_array($out)) {
374
- if ($out['return_empty_message'] || $out['stop']) {
375
- return false;
376
- }
377
- if ($out['skip']) {
378
- continue;
379
- }
380
- if (empty($subject) && !empty($out['subject'])) {
381
- $subject = $out['subject'];
382
- }
383
- }
384
- $block_html = ob_get_clean();
385
- $result .= $block_html;
386
- }
387
-
388
- $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
389
- $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
390
- $email->subject = $subject;
391
- return true;
392
- }
393
-
394
- function remove_block_data($text) {
395
- // TODO: Lavorare!
396
- return $text;
397
- }
398
-
399
- static function get_outlook_wrapper_open($width = 600) {
400
- return '<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '" style="vertical-align:top;width:' . $width . 'px;"><![endif]-->';
401
- }
402
-
403
- static function get_outlook_wrapper_close() {
404
- return "<!--[if mso | IE]></td></tr></table><![endif]-->";
405
- }
406
-
407
- function hook_safe_style_css($rules) {
408
- $rules[] = 'display';
409
- return $rules;
410
- }
411
-
412
- /**
413
- * Renders a block identified by its id, using the block options and adding a wrapper
414
- * if required (for the first block rendering).
415
- *
416
- * @param string $block_id
417
- * @param boolean $wrapper
418
- * @param array $options
419
- * @param array $context
420
- * @param array $composer
421
- */
422
- function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
423
- static $kses_style_filter = false;
424
- include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
425
-
426
- //Remove 'options_composer_' prefix
427
- $composer_defaults = [];
428
- foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
429
- $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
430
- }
431
- $composer = array_merge($composer_defaults, $composer);
432
-
433
- $width = 600;
434
- $font_family = 'Helvetica, Arial, sans-serif';
435
-
436
- $global_title_font_family = $composer['title_font_family'];
437
- $global_title_font_size = $composer['title_font_size'];
438
- $global_title_font_color = $composer['title_font_color'];
439
- $global_title_font_weight = $composer['title_font_weight'];
440
-
441
- $global_text_font_family = $composer['text_font_family'];
442
- $global_text_font_size = $composer['text_font_size'];
443
- $global_text_font_color = $composer['text_font_color'];
444
- $global_text_font_weight = $composer['text_font_weight'];
445
-
446
- $global_button_font_family = $composer['button_font_family'];
447
- $global_button_font_size = $composer['button_font_size'];
448
- $global_button_font_color = $composer['button_font_color'];
449
- $global_button_font_weight = $composer['button_font_weight'];
450
- $global_button_background_color = $composer['button_background_color'];
451
-
452
- $global_block_background = $composer['block_background'];
453
-
454
- $info = Newsletter::instance()->get_options('info');
455
-
456
- // Just in case...
457
- if (!is_array($options)) {
458
- $options = array();
459
- }
460
-
461
- // This code filters the HTML to remove javascript and unsecure attributes and enable the
462
- // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
463
- add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
464
- $options = wp_kses_post_deep($options);
465
- remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
466
-
467
- $block_options = get_option('newsletter_main');
468
-
469
- $block = $this->get_block($block_id);
470
-
471
- if (!isset($context['type']))
472
- $context['type'] = '';
473
-
474
- // Block not found
475
- if (!$block) {
476
- if ($wrapper) {
477
- echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">';
478
- echo '<tr>';
479
- echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
480
- }
481
- echo $this->get_outlook_wrapper_open($width);
482
-
483
- echo '<p>Ops, this block type is not avalable.</p>';
484
-
485
- echo $this->get_outlook_wrapper_close();
486
-
487
- if ($wrapper) {
488
- echo '</td></tr></table>';
489
- }
490
- return;
491
- }
492
-
493
- $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
494
-
495
- $dir = is_rtl() ? 'rtl' : 'ltr';
496
- $align_left = is_rtl() ? 'right' : 'left';
497
- $align_right = is_rtl() ? 'left' : 'right';
498
-
499
- ob_start();
500
- $logger = $this->logger;
501
- include $block['dir'] . '/block.php';
502
- $content = trim(ob_get_clean());
503
-
504
- if (empty($content)) {
505
- return $out;
506
- }
507
-
508
- $common_defaults = array(
509
- 'block_padding_top' => 0,
510
- 'block_padding_bottom' => 0,
511
- 'block_padding_right' => 0,
512
- 'block_padding_left' => 0,
513
- 'block_background' => '',
514
- 'block_background_2' => '',
515
- 'block_width' => 600,
516
- 'block_align' => 'center'
517
- );
518
-
519
- $options = array_merge($common_defaults, $options);
520
-
521
- // Obsolete
522
- $content = str_replace('{width}', $width, $content);
523
-
524
- $content = $this->inline_css($content, true);
525
-
526
- // CSS driven by the block
527
- // Requited for the server side parsing and rendering
528
- $options['block_id'] = $block_id;
529
-
530
- $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
531
- $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
532
- $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
533
- $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
534
-
535
- $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
536
-
537
- // Internal TD wrapper
538
- $style = 'text-align: center; ';
539
- $style .= 'width: 100% !important; ';
540
- $style .= 'line-height: normal !important; ';
541
- $style .= 'letter-spacing: normal; ';
542
- $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
543
- $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
544
- $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
545
- $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
546
- $style .= 'background-color: ' . $block_background . ';';
547
-
548
- if (isset($options['block_background_gradient'])) {
549
- $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
550
- }
551
-
552
- $data = $this->options_encode($options);
553
- // First time block creation wrapper
554
- if ($wrapper) {
555
- echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">', "\n";
556
- echo "<tr>";
557
- echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
558
- }
559
-
560
- // Container that fixes the width and makes the block responsive
561
- echo $this->get_outlook_wrapper_open($options['block_width']);
562
-
563
- echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $options['block_width'], 'px!important">', "\n";
564
- echo "<tr>";
565
- echo '<td align="', esc_attr($options['block_align']), '" style="', esc_attr($style), '" bgcolor="', esc_attr($block_background), '" width="100%">';
566
-
567
- //echo "<!-- block generated content -->\n";
568
- echo trim($content);
569
- //echo "\n<!-- /block generated content -->\n";
570
-
571
- echo "</td></tr></table>";
572
- echo $this->get_outlook_wrapper_close();
573
-
574
- // First time block creation wrapper
575
- if ($wrapper) {
576
- echo "</td></tr></table>";
577
- }
578
-
579
- return $out;
580
- }
581
-
582
- /**
583
- * Ajax call to render a block with a new set of options after the settings popup
584
- * has been saved.
585
- *
586
- * @param type $block_id
587
- * @param type $wrapper
588
- */
589
- function tnpc_render_callback() {
590
- if (!check_ajax_referer('save')) {
591
- $this->dienow('Expired request');
592
- }
593
-
594
- $block_id = $_POST['id'];
595
- $wrapper = isset($_POST['full']);
596
- $options = $this->restore_options_from_request();
597
-
598
- $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
599
- wp_die();
600
- }
601
-
602
- function hook_wp_ajax_tnpc_regenerate_email() {
603
-
604
- $content = stripslashes($_POST['content']);
605
- $global_options = $_POST['composer'];
606
-
607
- $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
608
-
609
- wp_send_json_success([
610
- 'content' => $regenerated_content,
611
- 'message' => __('Successfully updated', 'newsletter')
612
- ]);
613
- }
614
-
615
- private function regenerate_email_blocks($content, $global_options) {
616
-
617
- $raw_block_options = $this->extract_encoded_blocks_options($content);
618
-
619
- $regenerated_content = '';
620
-
621
- foreach ($raw_block_options as $raw_block_option) {
622
-
623
- /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
624
- $block_options = $this->options_decode( $a ); */
625
-
626
- $block_options = $this->options_decode($raw_block_option);
627
-
628
- $block = $this->get_block($block_options['block_id']);
629
- if (!$block) {
630
- $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
631
- }
632
-
633
- ob_start();
634
- $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
635
- $block_html = ob_get_clean();
636
-
637
- $regenerated_content .= $block_html;
638
- }
639
-
640
- return $regenerated_content;
641
- }
642
-
643
- /**
644
- * @param string $html_email_content Email html content
645
- *
646
- * @return string[] Encoded options of email blocks
647
- */
648
- private function extract_encoded_blocks_options($html_email_content) {
649
-
650
- preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
651
-
652
- return $raw_block_options[1];
653
- }
654
-
655
- function tnpc_preview_callback() {
656
- $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
657
-
658
- if (empty($email)) {
659
- echo 'Wrong email identifier';
660
- return;
661
- }
662
-
663
- echo $email['message'];
664
-
665
- wp_die();
666
- }
667
-
668
- function tnpc_css_callback() {
669
- include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
670
- wp_die();
671
- }
672
-
673
- /** Returns the correct admin page to edit the newsletter with the correct editor. */
674
- function get_editor_url($email_id, $editor_type) {
675
- switch ($editor_type) {
676
- case NewsletterEmails::EDITOR_COMPOSER:
677
- return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
678
- case NewsletterEmails::EDITOR_HTML:
679
- return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
680
- case NewsletterEmails::EDITOR_TINYMCE:
681
- return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
682
- }
683
- }
684
-
685
- /**
686
- * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
687
- * or the targeting page (it depends on newsletter status).
688
- *
689
- * @param TNP_Email $email
690
- */
691
- function get_edit_button($email, $only_icon = false) {
692
-
693
- $editor_type = $this->get_editor_type($email);
694
- if ($email->status == 'new') {
695
- $edit_url = $this->get_editor_url($email->id, $editor_type);
696
- } else {
697
- $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
698
- }
699
- switch ($editor_type) {
700
- case NewsletterEmails::EDITOR_COMPOSER:
701
- $icon_class = 'th-large';
702
- break;
703
- case NewsletterEmails::EDITOR_HTML:
704
- $icon_class = 'code';
705
- break;
706
- default:
707
- $icon_class = 'edit';
708
- break;
709
- }
710
- if ($only_icon) {
711
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
712
- '<i class="fas fa-' . $icon_class . '"></i></a>';
713
- } else {
714
- return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
715
- '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
716
- }
717
- }
718
-
719
- /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
720
- function get_editor_type($email) {
721
- $email = (object) $email;
722
- $editor_type = $email->editor;
723
-
724
- // Backward compatibility
725
- $email_options = maybe_unserialize($email->options);
726
- if (isset($email_options['composer'])) {
727
- $editor_type = NewsletterEmails::EDITOR_COMPOSER;
728
- }
729
- // End backward compatibility
730
-
731
- return $editor_type;
732
- }
733
-
734
- /**
735
- *
736
- * @param type $action
737
- * @param type $user
738
- * @param type $email
739
- * @return type
740
- * @global wpdb $wpdb
741
- */
742
- function hook_newsletter_action($action, $user, $email) {
743
- global $wpdb;
744
-
745
- switch ($action) {
746
- case 'v':
747
- case 'view':
748
- $id = $_GET['id'];
749
- if ($id == 'last') {
750
- $email = $wpdb->get_row("select * from " . NEWSLETTER_EMAILS_TABLE . " where private=0 and type='message' and status='sent' order by send_on desc limit 1");
751
- } else {
752
- $email = $this->get_email($_GET['id']);
753
- }
754
- if (empty($email)) {
755
- header("HTTP/1.0 404 Not Found");
756
- die('Email not found');
757
- }
758
-
759
- if (!Newsletter::instance()->is_allowed()) {
760
-
761
- if ($email->status == 'new') {
762
- header("HTTP/1.0 404 Not Found");
763
- die('Not sent yet');
764
- }
765
-
766
- if ($email->private == 1) {
767
- if (!$user) {
768
- header("HTTP/1.0 404 Not Found");
769
- die('No available for online view');
770
- }
771
- $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
772
- if (!$sent) {
773
- header("HTTP/1.0 404 Not Found");
774
- die('No available for online view');
775
- }
776
- }
777
- }
778
-
779
-
780
- header('Content-Type: text/html;charset=UTF-8');
781
- header('X-Robots-Tag: noindex,nofollow,noarchive');
782
- header('Cache-Control: no-cache,no-store,private');
783
-
784
- echo $this->replace($email->message, $user, $email);
785
-
786
- die();
787
- break;
788
-
789
- case 'emails-css':
790
- $email_id = (int) $_GET['id'];
791
-
792
- $body = Newsletter::instance()->get_email_field($email_id, 'message');
793
-
794
- $x = strpos($body, '<style');
795
- if ($x === false)
796
- return;
797
-
798
- $x = strpos($body, '>', $x);
799
- $y = strpos($body, '</style>');
800
-
801
- header('Content-Type: text/css;charset=UTF-8');
802
-
803
- echo substr($body, $x + 1, $y - $x - 1);
804
-
805
- die();
806
- break;
807
-
808
- case 'emails-composer-css':
809
- header('Cache: no-cache');
810
- header('Content-Type: text/css');
811
- echo $this->get_composer_css();
812
- die();
813
- break;
814
-
815
- case 'emails-preview':
816
- if (!Newsletter::instance()->is_allowed()) {
817
- die('Not enough privileges');
818
- }
819
-
820
- if (!check_admin_referer('view')) {
821
- die();
822
- }
823
-
824
- $theme_id = $_GET['id'];
825
- $theme = $this->themes->get_theme($theme_id);
826
-
827
- // Used by theme code
828
- $theme_options = $this->themes->get_options($theme_id);
829
-
830
- $theme_url = $theme['url'];
831
-
832
- header('Content-Type: text/html;charset=UTF-8');
833
-
834
- include $theme['dir'] . '/theme.php';
835
-
836
- die();
837
- break;
838
-
839
- case 'emails-preview-text':
840
- header('Content-Type: text/plain;charset=UTF-8');
841
- if (!Newsletter::instance()->is_allowed()) {
842
- die('Not enough privileges');
843
- }
844
-
845
- if (!check_admin_referer('view')) {
846
- die();
847
- }
848
-
849
- // Used by theme code
850
- $theme_options = $this->get_current_theme_options();
851
-
852
- $file = include $theme['dir'] . '/theme-text.php';
853
-
854
- if (is_file($file)) {
855
- include $file;
856
- }
857
-
858
- die();
859
- break;
860
-
861
- case 'emails-create':
862
- // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
863
- // excerpt, thumbnail are extracted.
864
- if (!Newsletter::instance()->is_allowed()) {
865
- die('Not enough privileges');
866
- }
867
-
868
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
869
- $controls = new NewsletterControls();
870
-
871
- if (!$controls->is_action('create')) {
872
- die('Wrong call');
873
- }
874
-
875
- $theme_id = $controls->data['id'];
876
- $theme = $this->themes->get_theme($theme_id);
877
-
878
- if (!$theme) {
879
- die('invalid theme');
880
- }
881
-
882
- $this->themes->save_options($theme_id, $controls->data);
883
-
884
- $email = array();
885
- $email['status'] = 'new';
886
- $email['subject'] = ''; //__('Here the email subject', 'newsletter');
887
- $email['track'] = Newsletter::instance()->options['track'];
888
- $email['send_on'] = time();
889
- $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
890
- $email['type'] = 'message';
891
-
892
- $theme_options = $this->themes->get_options($theme_id);
893
-
894
- $theme_url = $theme['url'];
895
- $theme_subject = '';
896
-
897
- ob_start();
898
- include $theme['dir'] . '/theme.php';
899
- $email['message'] = ob_get_clean();
900
-
901
- if (!empty($theme_subject)) {
902
- $email['subject'] = $theme_subject;
903
- }
904
-
905
- if (file_exists($theme['dir'] . '/theme-text.php')) {
906
- ob_start();
907
- include $theme['dir'] . '/theme-text.php';
908
- $email['message_text'] = ob_get_clean();
909
- } else {
910
- $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
911
- }
912
-
913
- $email = $this->save_email($email);
914
-
915
- $edit_url = $this->get_editor_url($email->id, $email->editor);
916
-
917
- header('Location: ' . $edit_url);
918
-
919
- die();
920
- break;
921
- }
922
- }
923
-
924
- function admin_menu() {
925
- $this->add_menu_page('index', 'Newsletters');
926
- $this->add_admin_page('list', 'Email List');
927
- $this->add_admin_page('new', 'Email New');
928
- $this->add_admin_page('edit', 'Email Edit');
929
- $this->add_admin_page('theme', 'Email Themes');
930
- $this->add_admin_page('composer', 'The Composer');
931
- $this->add_admin_page('editorhtml', 'HTML Editor');
932
- $this->add_admin_page('editortinymce', 'TinyMCE Editor');
933
- }
934
-
935
- /**
936
- * Builds a block data structure starting from the folder containing the block
937
- * files.
938
- *
939
- * @param string $dir
940
- * @return array | WP_Error
941
- */
942
- function build_block($dir) {
943
- $dir = realpath($dir);
944
- $dir = wp_normalize_path($dir);
945
- $full_file = $dir . '/block.php';
946
- if (!is_file($full_file)) {
947
- return new WP_Error('1', 'Missing block.php file in ' . $dir);
948
- }
949
-
950
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
951
- $file = basename($dir);
952
-
953
- $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
954
- $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
955
- $data = array_merge($defaults, $data);
956
-
957
- if (is_file($dir . '/icon.png')) {
958
- $data['icon'] = content_url($relative_dir . '/icon.png');
959
- }
960
-
961
- $data['id'] = sanitize_key($file);
962
-
963
- // Absolute path of the block files
964
- $data['dir'] = $dir;
965
- $data['url'] = content_url($relative_dir);
966
-
967
- return $data;
968
- }
969
-
970
- /**
971
- *
972
- * @param type $dir
973
- * @return type
974
- */
975
- function scan_blocks_dir($dir) {
976
- $dir = realpath($dir);
977
- if (!$dir) {
978
- return [];
979
- }
980
- $dir = wp_normalize_path($dir);
981
-
982
- $list = [];
983
- $handle = opendir($dir);
984
- while ($file = readdir($handle)) {
985
-
986
- $data = $this->build_block($dir . '/' . $file);
987
-
988
- if (is_wp_error($data)) {
989
- $this->logger->error($data);
990
- continue;
991
- }
992
- $list[$data['id']] = $data;
993
- }
994
- closedir($handle);
995
- return $list;
996
- }
997
-
998
- /**
999
- * Array of arrays with every registered block and legacy block converted to the new
1000
- * format.
1001
- *
1002
- * @return array
1003
- */
1004
- function get_blocks() {
1005
-
1006
- if (!is_null($this->blocks)) {
1007
- return $this->blocks;
1008
- }
1009
-
1010
- $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1011
-
1012
- $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1013
-
1014
- $this->blocks = array_merge($extended, $this->blocks);
1015
-
1016
- $dirs = apply_filters('newsletter_blocks_dir', array());
1017
-
1018
- //$this->logger->debug('Block dirs:');
1019
- //$this->logger->debug($dirs);
1020
-
1021
- foreach ($dirs as $dir) {
1022
- $list = $this->scan_blocks_dir($dir);
1023
- $this->blocks = array_merge($list, $this->blocks);
1024
- }
1025
-
1026
- do_action('newsletter_register_blocks');
1027
-
1028
- foreach (TNP_Composer::$block_dirs as $dir) {
1029
- $block = $this->build_block($dir);
1030
- if (is_wp_error($block)) {
1031
- $this->logger->error($block);
1032
- continue;
1033
- }
1034
- if (!isset($this->blocks[$block['id']])) {
1035
- $this->blocks[$block['id']] = $block;
1036
- } else {
1037
- $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1038
- }
1039
- }
1040
-
1041
- $this->blocks = array_reverse($this->blocks);
1042
- return $this->blocks;
1043
- }
1044
-
1045
- /**
1046
- * Return a single block (associative array) checking for legacy ID as well.
1047
- *
1048
- * @param string $id
1049
- * @return array
1050
- */
1051
- function get_block($id) {
1052
- switch ($id) {
1053
- case 'content-03-text.block':
1054
- $id = 'text';
1055
- break;
1056
- case 'footer-03-social.block':
1057
- $id = 'social';
1058
- break;
1059
- case 'footer-02-canspam.block':
1060
- $id = 'canspam';
1061
- break;
1062
- case 'content-05-image.block':
1063
- $id = 'image';
1064
- break;
1065
- case 'header-01-header.block':
1066
- $id = 'header';
1067
- break;
1068
- case 'footer-01-footer.block':
1069
- $id = 'footer';
1070
- break;
1071
- case 'content-02-heading.block':
1072
- $id = 'heading';
1073
- break;
1074
- case 'content-07-twocols.block':
1075
- case 'content-06-posts.block':
1076
- $id = 'posts';
1077
- break;
1078
- case 'content-04-cta.block':
1079
- $id = 'cta';
1080
- break;
1081
- case 'content-01-hero.block':
1082
- $id = 'hero';
1083
- break;
1084
- // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1085
- // break;
1086
- }
1087
-
1088
- // Conversion for old full path ID
1089
- $id = sanitize_key(basename($id));
1090
-
1091
- // TODO: Correct id for compatibility
1092
- $blocks = $this->get_blocks();
1093
- if (!isset($blocks[$id])) {
1094
- return null;
1095
- }
1096
- return $blocks[$id];
1097
- }
1098
-
1099
- function scan_presets_dir($dir = null) {
1100
-
1101
- if (is_null($dir)) {
1102
- $dir = __DIR__ . '/presets';
1103
- }
1104
-
1105
- if (!is_dir($dir)) {
1106
- return array();
1107
- }
1108
-
1109
- $handle = opendir($dir);
1110
- $list = array();
1111
- $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1112
- while ($file = readdir($handle)) {
1113
-
1114
- if ($file == '.' || $file == '..')
1115
- continue;
1116
-
1117
- // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1118
- $preset_id = sanitize_key($file);
1119
-
1120
- $full_file = $dir . '/' . $file . '/preset.json';
1121
-
1122
- if (!is_file($full_file)) {
1123
- continue;
1124
- }
1125
-
1126
- $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1127
-
1128
- $list[$preset_id] = $icon;
1129
- }
1130
- closedir($handle);
1131
- return $list;
1132
- }
1133
-
1134
- function get_preset_from_file($id, $dir = null) {
1135
-
1136
- if (is_null($dir)) {
1137
- $dir = __DIR__ . '/presets';
1138
- }
1139
-
1140
- $id = $this->sanitize_file_name($id);
1141
-
1142
- if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1143
- return array();
1144
- }
1145
-
1146
- $json_content = file_get_contents("$dir/$id/preset.json");
1147
- $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1148
- $json = json_decode($json_content);
1149
- $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1150
-
1151
- return $json;
1152
- }
1153
-
1154
- function get_composer_css() {
1155
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1156
- $blocks = $this->get_blocks();
1157
- foreach ($blocks as $block) {
1158
- if (!file_exists($block['dir'] . '/style.css')) {
1159
- continue;
1160
- }
1161
- $css .= "\n\n";
1162
- $css .= "/* " . $block['name'] . " */\n";
1163
- $css .= file_get_contents($block['dir'] . '/style.css');
1164
- }
1165
- return $css;
1166
- }
1167
-
1168
- function get_composer_backend_css() {
1169
- $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1170
- $css .= "\n\n";
1171
- $css .= $this->get_composer_css();
1172
- return $css;
1173
- }
1174
-
1175
- /**
1176
- * Send an email to the test subscribers.
1177
- *
1178
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1179
- * @param NewsletterControls $controls
1180
- */
1181
- function send_test_email($email, $controls) {
1182
- if (!$email) {
1183
- $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1184
- return;
1185
- }
1186
-
1187
- $original_subject = $email->subject;
1188
- $this->set_test_subject_to($email);
1189
-
1190
- $users = NewsletterUsers::instance()->get_test_users();
1191
- if (count($users) == 0) {
1192
- $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1193
- '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1194
- __('Read more', 'newsletter') . '</strong></a>.';
1195
- } else {
1196
- $r = Newsletter::instance()->send($email, $users, true);
1197
- $emails = array();
1198
- foreach ($users as $user) {
1199
- $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1200
- }
1201
- if (is_wp_error($r)) {
1202
- $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1203
- $controls->errors .= __('Test subscribers:', 'newsletter');
1204
- $controls->errors .= ' ' . implode(', ', $emails);
1205
- $controls->errors .= '<br>';
1206
- $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1207
- $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1208
- } else {
1209
- $controls->messages = __('Test subscribers:', 'newsletter');
1210
-
1211
- $controls->messages .= ' ' . implode(', ', $emails);
1212
- $controls->messages .= '.<br>';
1213
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1214
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1215
- $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1216
- }
1217
- }
1218
- $email->subject = $original_subject;
1219
- }
1220
-
1221
- /**
1222
- * Send an email to the test subscribers.
1223
- *
1224
- * @param TNP_Email $email Could be any object with the TNP_Email attributes
1225
- * @param string $email_address
1226
- *
1227
- * @throws Exception
1228
- */
1229
- function send_test_newsletter_to_email_address($email, $email_address) {
1230
-
1231
- if (!$email) {
1232
- throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1233
- }
1234
-
1235
- $this->set_test_subject_to($email);
1236
-
1237
- $dummy_subscriber = $this->make_dummy_subscriber();
1238
- $dummy_subscriber->email = $email_address;
1239
-
1240
- $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1241
-
1242
- $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1243
-
1244
- if (is_wp_error($result)) {
1245
- $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1246
- $error_message .= __('Test subscribers:', 'newsletter');
1247
- $error_message .= ' ' . $email;
1248
- $error_message .= '<br>';
1249
- $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1250
- $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1251
- throw new Exception($error_message);
1252
- }
1253
-
1254
- $messages = __('Test subscribers:', 'newsletter');
1255
-
1256
- $messages .= ' ' . $email;
1257
- $messages .= '.<br>';
1258
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1259
- __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1260
- $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1261
-
1262
- return $messages;
1263
- }
1264
-
1265
- private function set_test_subject_to($email) {
1266
- if ($email->subject == '') {
1267
- $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1268
- } else {
1269
- $email->subject = $email->subject . ' (TEST)';
1270
- }
1271
- }
1272
-
1273
- private function make_dummy_subscriber() {
1274
- $dummy_user = new TNP_User();
1275
- $dummy_user->id = 0;
1276
- $dummy_user->email = 'john.doe@example.org';
1277
- $dummy_user->name = 'John';
1278
- $dummy_user->surname = 'Doe';
1279
- $dummy_user->sex = 'n';
1280
- $dummy_user->language = '';
1281
- $dummy_user->ip = '';
1282
-
1283
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1284
- $profile_key = "profile_$i";
1285
- $dummy_user->$profile_key = '';
1286
- }
1287
-
1288
- return $dummy_user;
1289
- }
1290
-
1291
- function restore_options_from_request() {
1292
-
1293
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1294
- $controls = new NewsletterControls();
1295
- $options = $controls->data;
1296
-
1297
- if (isset($_POST['options']) && is_array($_POST['options'])) {
1298
- // Get all block options
1299
- //$options = stripslashes_deep($_POST['options']);
1300
- // Deserialize inline edits when
1301
- // render is preformed on saving block options
1302
- if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1303
- $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1304
- }
1305
-
1306
- // Restore inline edits from data-json
1307
- // coming from inline editing
1308
- // and merge with current inline edit
1309
- if (isset($_POST['encoded_options'])) {
1310
- $decoded_options = $this->options_decode($_POST['encoded_options']);
1311
-
1312
- $to_merge_inline_edits = [];
1313
-
1314
- if (isset($decoded_options['inline_edits'])) {
1315
- foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1316
- $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1317
- }
1318
- }
1319
-
1320
- //Overwrite with new edited content
1321
- if (isset($options['inline_edits'])) {
1322
- foreach ($options['inline_edits'] as $inline_edit) {
1323
- $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1324
- }
1325
- }
1326
-
1327
- $options['inline_edits'] = array_values($to_merge_inline_edits);
1328
- $options = array_merge($decoded_options, $options);
1329
- }
1330
-
1331
- return $options;
1332
- }
1333
-
1334
- return array();
1335
- }
1336
-
1337
- public function hook_wp_ajax_tnpc_delete_preset() {
1338
-
1339
- if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1340
- wp_send_json_error('Expired request');
1341
- }
1342
-
1343
- $preset_id = (int) $_REQUEST['presetId'];
1344
-
1345
- $newsletter = Newsletter::instance();
1346
-
1347
- if ($preset_id > 0) {
1348
- $preset = $newsletter->get_email($preset_id);
1349
-
1350
- if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1351
- Newsletter::instance()->delete_email($preset_id);
1352
- wp_send_json_success();
1353
- } else {
1354
- wp_send_json_error(__('Is not a preset!', 'newsletter'));
1355
- }
1356
- } else {
1357
- wp_send_json_error();
1358
- }
1359
- }
1360
-
1361
- }
1362
-
1363
- NewsletterEmails::instance();
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterEmails extends NewsletterModule {
6
+
7
+ static $instance;
8
+
9
+ const EDITOR_COMPOSER = 2;
10
+ const EDITOR_HTML = 1;
11
+ const EDITOR_TINYMCE = 0;
12
+
13
+ static $PRESETS_LIST;
14
+
15
+ const PRESET_EMAIL_TYPE = 'composer_template';
16
+
17
+ // Cache
18
+ var $blocks = null;
19
+
20
+ /**
21
+ * @return NewsletterEmails
22
+ */
23
+ static function instance() {
24
+ if (self::$instance == null) {
25
+ self::$instance = new NewsletterEmails();
26
+ }
27
+
28
+ return self::$instance;
29
+ }
30
+
31
+ function __construct() {
32
+ self::$PRESETS_LIST = array("cta", "invite", "announcement", "posts", "sales", "product", "tour", "simple");
33
+ $this->themes = new NewsletterThemes('emails');
34
+ parent::__construct('emails', '1.1.5');
35
+ add_action('newsletter_action', array($this, 'hook_newsletter_action'), 13, 3);
36
+ add_action('newsletter_init', [$this, 'hook_newsletter_init']);
37
+
38
+ if (is_admin()) {
39
+ // Thank you to plugins which add the WP editor on other admin plugin pages...
40
+ if (isset($_GET['page']) && $_GET['page'] == 'newsletter_emails_edit') {
41
+ global $wp_actions;
42
+ $wp_actions['wp_enqueue_editor'] = 1;
43
+ }
44
+ }
45
+ }
46
+
47
+ function hook_newsletter_init() {
48
+ if (is_admin()) {
49
+ if (defined('DOING_AJAX') && DOING_AJAX) {
50
+ if (Newsletter::instance()->is_allowed()) {
51
+ add_action('wp_ajax_tnpc_render', array($this, 'tnpc_render_callback'));
52
+ add_action('wp_ajax_tnpc_preview', array($this, 'tnpc_preview_callback'));
53
+ add_action('wp_ajax_tnpc_css', array($this, 'tnpc_css_callback'));
54
+ add_action('wp_ajax_tnpc_options', array($this, 'hook_wp_ajax_tnpc_options'));
55
+ add_action('wp_ajax_tnpc_get_all_presets', array($this, 'ajax_get_all_presets'));
56
+ add_action('wp_ajax_tnpc_get_preset', array($this, 'ajax_get_preset'));
57
+ add_action('wp_ajax_tnpc_delete_preset', array($this, 'hook_wp_ajax_tnpc_delete_preset'));
58
+ add_action('wp_ajax_tnpc_regenerate_email', array($this, 'hook_wp_ajax_tnpc_regenerate_email'));
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ function options_decode($options) {
65
+ // Old "query string" format
66
+ if (is_string($options) && strpos($options, 'options[') !== false) {
67
+ $opts = [];
68
+ parse_str($options, $opts);
69
+ $options = $opts['options'];
70
+ }
71
+
72
+ if (is_array($options)) {
73
+ return $options;
74
+ }
75
+
76
+ // Json data should be base64 encoded, but for short time it wasn't
77
+ $tmp = json_decode($options, true);
78
+ if (is_null($tmp)) {
79
+ return json_decode(base64_decode($options), true);
80
+ } else {
81
+ return $tmp;
82
+ }
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @param array $options Options array
88
+ */
89
+ function options_encode($options) {
90
+ return base64_encode(json_encode($options, JSON_HEX_TAG | JSON_HEX_AMP));
91
+ }
92
+
93
+ /**
94
+ * Builds and returns the HTML with the form fields of a specific block.
95
+ *
96
+ * @global wpdb $wpdb
97
+ */
98
+ function hook_wp_ajax_tnpc_options() {
99
+ global $wpdb;
100
+
101
+ $block = $this->get_block($_REQUEST['id']);
102
+ if (!$block) {
103
+ die('Block not found with id ' . esc_html($_REQUEST['id']));
104
+ }
105
+
106
+ if (!class_exists('NewsletterControls')) {
107
+ include NEWSLETTER_INCLUDES_DIR . '/controls.php';
108
+ }
109
+
110
+ $options = $this->options_decode(stripslashes_deep($_REQUEST['options']));
111
+ $composer = isset($_POST['composer']) ? $_POST['composer'] : [];
112
+
113
+ $context = array('type' => '');
114
+ if (isset($_REQUEST['context_type'])) {
115
+ $context['type'] = $_REQUEST['context_type'];
116
+ }
117
+
118
+ $controls = new NewsletterControls($options);
119
+ $fields = new NewsletterFields($controls);
120
+
121
+ $controls->init();
122
+ echo '<input type="hidden" name="action" value="tnpc_render">';
123
+ echo '<input type="hidden" name="id" value="' . esc_attr($_REQUEST['id']) . '">';
124
+ echo '<input type="hidden" name="context_type" value="' . esc_attr($context['type']) . '">';
125
+ $inline_edits = '';
126
+ if (isset($controls->data['inline_edits'])) {
127
+ $inline_edits = $controls->data['inline_edits'];
128
+ }
129
+ echo '<input type="hidden" name="options[inline_edits]" value="', esc_attr($this->options_encode($inline_edits)), '">';
130
+ echo "<h2>", esc_html($block["name"]), "</h2>";
131
+ include $block['dir'] . '/options.php';
132
+ wp_die();
133
+ }
134
+
135
+ /**
136
+ * Retrieves the presets list (no id in GET) or a specific preset id in GET)
137
+ */
138
+ public function ajax_get_all_presets() {
139
+ wp_send_json_success($this->get_all_preset());
140
+ }
141
+
142
+ public function ajax_get_preset() {
143
+
144
+ if (empty($_REQUEST['id'])) {
145
+ wp_send_json_error([
146
+ 'msg' => __('Invalid preset ID')
147
+ ]);
148
+ }
149
+
150
+ $preset_id = $_REQUEST['id'];
151
+ $preset_content = $this->get_preset_content($preset_id);
152
+ $global_options = $this->get_preset_global_options($preset_id);
153
+
154
+ wp_send_json_success([
155
+ 'content' => $preset_content,
156
+ 'globalOptions' => $global_options,
157
+ ]);
158
+ }
159
+
160
+ private function get_preset_content($preset_id) {
161
+
162
+ $content = '';
163
+
164
+ if ($this->is_a_tnp_default_preset($preset_id)) {
165
+
166
+ // Get preset from file
167
+ $preset = $this->get_preset_from_file($preset_id);
168
+
169
+ foreach ($preset->blocks as $item) {
170
+ ob_start();
171
+ $this->render_block($item->block, true, (array) $item->options);
172
+ $content .= trim(ob_get_clean());
173
+ }
174
+ } else {
175
+
176
+ // Get preset from db
177
+ $preset_email = $this->get_email(intval($preset_id));
178
+ $global_options = $this->extract_global_options_from($preset_email);
179
+ $content = $this->regenerate_email_blocks($preset_email->message, $global_options);
180
+ }
181
+
182
+ return $content;
183
+ }
184
+
185
+ private function get_preset_global_options($preset_id) {
186
+
187
+ if ($this->is_a_tnp_default_preset($preset_id)) {
188
+ return [];
189
+ }
190
+
191
+ // Get preset from db
192
+ $preset_email = $this->get_email(intval($preset_id));
193
+ $global_options = $this->extract_global_options_from($preset_email);
194
+
195
+ return $global_options;
196
+ }
197
+
198
+ private function extract_global_options_from($email) {
199
+ $global_options = [];
200
+ foreach ($email->options as $global_option_name => $global_option) {
201
+ if (strpos($global_option_name, 'composer_') === 0) {
202
+ $global_options[str_replace('composer_', '', $global_option_name)] = $global_option;
203
+ }
204
+ }
205
+
206
+ return $global_options;
207
+ }
208
+
209
+ private function is_a_tnp_default_preset($preset_id) {
210
+ return in_array($preset_id, self::$PRESETS_LIST);
211
+ }
212
+
213
+ private function get_all_preset() {
214
+
215
+ $content = "<div class='tnpc-preset-container'>";
216
+
217
+ if ($this->is_normal_context_request()) {
218
+ $content .= "<div class='tnpc-preset-legacy-themes'><a href='" . $this->get_admin_page_url('theme') . "'>" . __('Looking for legacy themes?', 'newsletter') . "</a></div>";
219
+ }
220
+
221
+ // LOAD USER PRESETS
222
+ $user_preset_list = $this->get_emails(self::PRESET_EMAIL_TYPE);
223
+
224
+ foreach ($user_preset_list as $user_preset) {
225
+
226
+ $default_icon_url = NEWSLETTER_URL . "/emails/presets/default-icon.png?ver=2";
227
+ $preset_name = $user_preset->subject;
228
+ $delete_preset_text = __('Delete', 'newsletter');
229
+ $edit_preset_text = __('Edit', 'newsletter');
230
+
231
+ // esc_js() assumes the string will be in single quote (arghhh!!!)
232
+ $onclick_edit = 'tnpc_edit_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
233
+ $onclick_delete = 'tnpc_delete_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
234
+ $onclick_load = 'tnpc_load_preset(' . ((int) $user_preset->id) . ', \'' . esc_js($preset_name) . '\', event)';
235
+
236
+ $content .= "<div class='tnpc-preset' onclick='" . esc_attr($onclick_load) . "'>\n";
237
+ $content .= "<img src='$default_icon_url' title='" . esc_attr($preset_name) . "' alt='" . esc_attr($preset_name) . "'>\n";
238
+ $content .= "<span class='tnpc-preset-label'>" . esc_html($user_preset->subject) . "</span>\n";
239
+ $content .= "<span class='tnpc-delete-preset' onclick='" . esc_attr($onclick_delete) . "' title='" . esc_attr($delete_preset_text) . "'><i class='fas fa-times'></i></span>\n";
240
+ $content .= "<span class='tnpc-edit-preset' onclick='" . esc_attr($onclick_edit) . "' title='" . esc_attr($edit_preset_text) . "'><i class='fas fa-pencil-alt'></i></span>\n";
241
+ $content .= "</div>";
242
+ }
243
+
244
+ // LOAD TNP PRESETS
245
+ foreach (self::$PRESETS_LIST as $id) {
246
+ $preset = $this->get_preset_from_file($id);
247
+ $preset_name = esc_html($preset->name);
248
+ $content .= "<div class='tnpc-preset' onclick='tnpc_load_preset(\"$id\")'>";
249
+ $content .= "<img src='$preset->icon' title='$preset_name' alt='$preset_name'/>";
250
+ $content .= "<span class='tnpc-preset-label'>$preset_name</span>";
251
+ $content .= "</div>";
252
+ }
253
+
254
+ if ($this->is_normal_context_request()) {
255
+ $content .= $this->get_automated_spot_element();
256
+ $content .= $this->get_autoresponder_spot_element();
257
+ $content .= $this->get_raw_html_preset_element();
258
+ }
259
+
260
+ return $content;
261
+ }
262
+
263
+ private function is_normal_context_request() {
264
+ return empty($_REQUEST['context_type']);
265
+ }
266
+
267
+ private function is_automated_context_request() {
268
+ return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'automated';
269
+ }
270
+
271
+ private function is_autoresponder_context_request() {
272
+ return isset($_REQUEST['context_type']) && $_REQUEST['context_type'] === 'autoresponder';
273
+ }
274
+
275
+ private function get_automated_spot_element() {
276
+ $result = "<div class='tnpc-preset'>";
277
+ if (class_exists('NewsletterAutomated')) {
278
+ $result .= "<a href='?page=newsletter_automated_index'>";
279
+ } else {
280
+ $result .= "<a href='https://www.thenewsletterplugin.com/automated?utm_source=composer&utm_campaign=plugin&utm_medium=automated'>";
281
+ }
282
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/automated.png' title='Automated addon' alt='Automated'/>";
283
+ $result .= "<span class='tnpc-preset-label'>Daily, weekly and monthly newsletters</span></a>";
284
+ $result .= "</div>";
285
+
286
+ return $result;
287
+ }
288
+
289
+ private function get_autoresponder_spot_element() {
290
+ $result = "<div class='tnpc-preset'>";
291
+ if (class_exists('NewsletterAutoresponder')) {
292
+ $result .= "<a href='?page=newsletter_autoresponder_index'>";
293
+ } else {
294
+ $result .= "<a href='https://www.thenewsletterplugin.com/autoresponder?utm_source=composer&utm_campaign=plugin&utm_medium=autoresponder' target='_blank'>";
295
+ }
296
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/autoresponder.png' title='Autoresponder addon' alt='Autoresponder'/>";
297
+ $result .= "<span class='tnpc-preset-label'>Autoresponders</span></a>";
298
+ $result .= "</div>";
299
+
300
+ return $result;
301
+ }
302
+
303
+ private function get_raw_html_preset_element() {
304
+
305
+ $result = "<div class='tnpc-preset tnpc-preset-html' onclick='location.href=\"" . wp_nonce_url('admin.php?page=newsletter_emails_new&id=rawhtml', 'newsletter-new') . "\"'>";
306
+ $result .= "<img src='" . plugins_url('newsletter') . "/emails/images/rawhtml.png' title='RAW HTML' alt='RAW'/>";
307
+ $result .= "<span class='tnpc-preset-label'>Raw HTML</span>";
308
+ $result .= "</div>";
309
+
310
+ $result .= "<div class='clear'></div>";
311
+ $result .= "</div>";
312
+
313
+ return $result;
314
+ }
315
+
316
+ function has_dynamic_blocks($theme) {
317
+ preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
318
+ foreach ($matches[1] as $match) {
319
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
320
+ $options = $this->options_decode($a);
321
+
322
+ $block = $this->get_block($options['block_id']);
323
+ if (!$block) {
324
+ continue;
325
+ }
326
+ if ($block['type'] == 'dynamic') {
327
+ return true;
328
+ }
329
+ }
330
+ return false;
331
+ }
332
+
333
+ /**
334
+ * Regenerates a saved composed email rendering each block. Regeneration is
335
+ * conditioned (possibly) by the context. The context is usually passed to blocks
336
+ * so they can act in the right manner.
337
+ *
338
+ * $context contains a type and, for automated, the last_run.
339
+ *
340
+ * $email can actually be even a string containing the full newsletter HTML code.
341
+ *
342
+ * @param TNP_Email $email
343
+ * @return string
344
+ */
345
+ function regenerate($email, $context = []) {
346
+
347
+ $context = array_merge(['last_run' => 0, 'type' => ''], $context);
348
+
349
+ $composer = [];
350
+ foreach ($email->options as $k=>$v) {
351
+ if (strpos($k, 'composer_') !== 0) continue;
352
+ $composer[substr($k, 9)] = $v;
353
+ }
354
+
355
+
356
+ preg_match_all('/data-json="(.*?)"/m', $email->message, $matches, PREG_PATTERN_ORDER);
357
+
358
+ $result = '';
359
+ $subject = '';
360
+
361
+ foreach ($matches[1] as $match) {
362
+ $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
363
+ $options = $this->options_decode($a);
364
+
365
+ $block = $this->get_block($options['block_id']);
366
+ if (!$block) {
367
+ $this->logger->debug('Unable to load the block ' . $options['block_id']);
368
+ //continue;
369
+ }
370
+
371
+ ob_start();
372
+ $out = $this->render_block($options['block_id'], true, $options, $context, $composer);
373
+ if (is_array($out)) {
374
+ if ($out['return_empty_message'] || $out['stop']) {
375
+ return false;
376
+ }
377
+ if ($out['skip']) {
378
+ continue;
379
+ }
380
+ if (empty($subject) && !empty($out['subject'])) {
381
+ $subject = $out['subject'];
382
+ }
383
+ }
384
+ $block_html = ob_get_clean();
385
+ $result .= $block_html;
386
+ }
387
+
388
+ $email->message = TNP_Composer::get_html_open($email) . TNP_Composer::get_main_wrapper_open($email) .
389
+ $result . TNP_Composer::get_main_wrapper_close($email) . TNP_Composer::get_html_close($email);
390
+ $email->subject = $subject;
391
+ return true;
392
+ }
393
+
394
+ function remove_block_data($text) {
395
+ // TODO: Lavorare!
396
+ return $text;
397
+ }
398
+
399
+ static function get_outlook_wrapper_open($width = 600) {
400
+ return '<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '" style="vertical-align:top;width:' . $width . 'px;"><![endif]-->';
401
+ }
402
+
403
+ static function get_outlook_wrapper_close() {
404
+ return "<!--[if mso | IE]></td></tr></table><![endif]-->";
405
+ }
406
+
407
+ function hook_safe_style_css($rules) {
408
+ $rules[] = 'display';
409
+ return $rules;
410
+ }
411
+
412
+ /**
413
+ * Renders a block identified by its id, using the block options and adding a wrapper
414
+ * if required (for the first block rendering).
415
+ *
416
+ * @param string $block_id
417
+ * @param boolean $wrapper
418
+ * @param array $options
419
+ * @param array $context
420
+ * @param array $composer
421
+ */
422
+ function render_block($block_id = null, $wrapper = false, $options = [], $context = [], $composer = []) {
423
+ static $kses_style_filter = false;
424
+ include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
425
+
426
+ //Remove 'options_composer_' prefix
427
+ $composer_defaults = [];
428
+ foreach (TNP_Composer::get_global_style_defaults() as $global_option_name => $global_option) {
429
+ $composer_defaults[str_replace('options_composer_', '', $global_option_name)] = $global_option;
430
+ }
431
+ $composer = array_merge($composer_defaults, $composer);
432
+
433
+ $width = 600;
434
+ $font_family = 'Helvetica, Arial, sans-serif';
435
+
436
+ $global_title_font_family = $composer['title_font_family'];
437
+ $global_title_font_size = $composer['title_font_size'];
438
+ $global_title_font_color = $composer['title_font_color'];
439
+ $global_title_font_weight = $composer['title_font_weight'];
440
+
441
+ $global_text_font_family = $composer['text_font_family'];
442
+ $global_text_font_size = $composer['text_font_size'];
443
+ $global_text_font_color = $composer['text_font_color'];
444
+ $global_text_font_weight = $composer['text_font_weight'];
445
+
446
+ $global_button_font_family = $composer['button_font_family'];
447
+ $global_button_font_size = $composer['button_font_size'];
448
+ $global_button_font_color = $composer['button_font_color'];
449
+ $global_button_font_weight = $composer['button_font_weight'];
450
+ $global_button_background_color = $composer['button_background_color'];
451
+
452
+ $global_block_background = $composer['block_background'];
453
+
454
+ $info = Newsletter::instance()->get_options('info');
455
+
456
+ // Just in case...
457
+ if (!is_array($options)) {
458
+ $options = array();
459
+ }
460
+
461
+ // This code filters the HTML to remove javascript and unsecure attributes and enable the
462
+ // "display" rule for CSS which is needed in blocks to force specific "block" or "inline" or "table".
463
+ add_filter('safe_style_css', [$this, 'hook_safe_style_css'], 9999);
464
+ $options = wp_kses_post_deep($options);
465
+ remove_filter('safe_style_css', [$this, 'hook_safe_style_css']);
466
+
467
+ $block_options = get_option('newsletter_main');
468
+
469
+ $block = $this->get_block($block_id);
470
+
471
+ if (!isset($context['type']))
472
+ $context['type'] = '';
473
+
474
+ // Block not found
475
+ if (!$block) {
476
+ if ($wrapper) {
477
+ echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">';
478
+ echo '<tr>';
479
+ echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
480
+ }
481
+ echo $this->get_outlook_wrapper_open($width);
482
+
483
+ echo '<p>Ops, this block type is not avalable.</p>';
484
+
485
+ echo $this->get_outlook_wrapper_close();
486
+
487
+ if ($wrapper) {
488
+ echo '</td></tr></table>';
489
+ }
490
+ return;
491
+ }
492
+
493
+ $out = ['subject' => '', 'return_empty_message' => false, 'stop' => false, 'skip' => false];
494
+
495
+ $dir = is_rtl() ? 'rtl' : 'ltr';
496
+ $align_left = is_rtl() ? 'right' : 'left';
497
+ $align_right = is_rtl() ? 'left' : 'right';
498
+
499
+ ob_start();
500
+ $logger = $this->logger;
501
+ include $block['dir'] . '/block.php';
502
+ $content = trim(ob_get_clean());
503
+
504
+ if (empty($content)) {
505
+ return $out;
506
+ }
507
+
508
+ $common_defaults = array(
509
+ 'block_padding_top' => 0,
510
+ 'block_padding_bottom' => 0,
511
+ 'block_padding_right' => 0,
512
+ 'block_padding_left' => 0,
513
+ 'block_background' => '',
514
+ 'block_background_2' => '',
515
+ 'block_width' => 600,
516
+ 'block_align' => 'center'
517
+ );
518
+
519
+ $options = array_merge($common_defaults, $options);
520
+
521
+ // Obsolete
522
+ $content = str_replace('{width}', $width, $content);
523
+
524
+ $content = $this->inline_css($content, true);
525
+
526
+ // CSS driven by the block
527
+ // Requited for the server side parsing and rendering
528
+ $options['block_id'] = $block_id;
529
+
530
+ $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
531
+ $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
532
+ $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
533
+ $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
534
+
535
+ $block_background = empty($options['block_background']) ? $global_block_background : $options['block_background'];
536
+
537
+ // Internal TD wrapper
538
+ $style = 'text-align: center; ';
539
+ $style .= 'width: 100% !important; ';
540
+ $style .= 'line-height: normal !important; ';
541
+ $style .= 'letter-spacing: normal; ';
542
+ $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
543
+ $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
544
+ $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
545
+ $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
546
+ $style .= 'background-color: ' . $block_background . ';';
547
+
548
+ if (isset($options['block_background_gradient'])) {
549
+ $style .= 'background: linear-gradient(180deg, ' . $block_background . ' 0%, ' . $options['block_background_2'] . ' 100%);';
550
+ }
551
+
552
+ $data = $this->options_encode($options);
553
+ // First time block creation wrapper
554
+ if ($wrapper) {
555
+ echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">', "\n";
556
+ echo "<tr>";
557
+ echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
558
+ }
559
+
560
+ // Container that fixes the width and makes the block responsive
561
+ echo $this->get_outlook_wrapper_open($options['block_width']);
562
+
563
+ echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $options['block_width'], 'px!important">', "\n";
564
+ echo "<tr>";
565
+ echo '<td align="', esc_attr($options['block_align']), '" style="', esc_attr($style), '" bgcolor="', esc_attr($block_background), '" width="100%">';
566
+
567
+ //echo "<!-- block generated content -->\n";
568
+ echo trim($content);
569
+ //echo "\n<!-- /block generated content -->\n";
570
+
571
+ echo "</td></tr></table>";
572
+ echo $this->get_outlook_wrapper_close();
573
+
574
+ // First time block creation wrapper
575
+ if ($wrapper) {
576
+ echo "</td></tr></table>";
577
+ }
578
+
579
+ return $out;
580
+ }
581
+
582
+ /**
583
+ * Ajax call to render a block with a new set of options after the settings popup
584
+ * has been saved.
585
+ *
586
+ * @param type $block_id
587
+ * @param type $wrapper
588
+ */
589
+ function tnpc_render_callback() {
590
+ if (!check_ajax_referer('save')) {
591
+ $this->dienow('Expired request');
592
+ }
593
+
594
+ $block_id = $_POST['id'];
595
+ $wrapper = isset($_POST['full']);
596
+ $options = $this->restore_options_from_request();
597
+
598
+ $this->render_block($block_id, $wrapper, $options, [], $_POST['composer']);
599
+ wp_die();
600
+ }
601
+
602
+ function hook_wp_ajax_tnpc_regenerate_email() {
603
+
604
+ $content = stripslashes($_POST['content']);
605
+ $global_options = $_POST['composer'];
606
+
607
+ $regenerated_content = $this->regenerate_email_blocks($content, $global_options);
608
+
609
+ wp_send_json_success([
610
+ 'content' => $regenerated_content,
611
+ 'message' => __('Successfully updated', 'newsletter')
612
+ ]);
613
+ }
614
+
615
+ private function regenerate_email_blocks($content, $global_options) {
616
+
617
+ $raw_block_options = $this->extract_encoded_blocks_options($content);
618
+
619
+ $regenerated_content = '';
620
+
621
+ foreach ($raw_block_options as $raw_block_option) {
622
+
623
+ /* $a = html_entity_decode( $raw_block_option, ENT_QUOTES, 'UTF-8' );
624
+ $block_options = $this->options_decode( $a ); */
625
+
626
+ $block_options = $this->options_decode($raw_block_option);
627
+
628
+ $block = $this->get_block($block_options['block_id']);
629
+ if (!$block) {
630
+ $this->logger->debug('Unable to load the block ' . $block_options['block_id']);
631
+ }
632
+
633
+ ob_start();
634
+ $this->render_block($block_options['block_id'], true, $block_options, [], $global_options);
635
+ $block_html = ob_get_clean();
636
+
637
+ $regenerated_content .= $block_html;
638
+ }
639
+
640
+ return $regenerated_content;
641
+ }
642
+
643
+ /**
644
+ * @param string $html_email_content Email html content
645
+ *
646
+ * @return string[] Encoded options of email blocks
647
+ */
648
+ private function extract_encoded_blocks_options($html_email_content) {
649
+
650
+ preg_match_all('/data-json="(.*?)"/m', $html_email_content, $raw_block_options, PREG_PATTERN_ORDER);
651
+
652
+ return $raw_block_options[1];
653
+ }
654
+
655
+ function tnpc_preview_callback() {
656
+ $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
657
+
658
+ if (empty($email)) {
659
+ echo 'Wrong email identifier';
660
+ return;
661
+ }
662
+
663
+ echo $email['message'];
664
+
665
+ wp_die();
666
+ }
667
+
668
+ function tnpc_css_callback() {
669
+ include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
670
+ wp_die();
671
+ }
672
+
673
+ /** Returns the correct admin page to edit the newsletter with the correct editor. */
674
+ function get_editor_url($email_id, $editor_type) {
675
+ switch ($editor_type) {
676
+ case NewsletterEmails::EDITOR_COMPOSER:
677
+ return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
678
+ case NewsletterEmails::EDITOR_HTML:
679
+ return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
680
+ case NewsletterEmails::EDITOR_TINYMCE:
681
+ return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
687
+ * or the targeting page (it depends on newsletter status).
688
+ *
689
+ * @param TNP_Email $email
690
+ */
691
+ function get_edit_button($email, $only_icon = false) {
692
+
693
+ $editor_type = $this->get_editor_type($email);
694
+ if ($email->status == 'new') {
695
+ $edit_url = $this->get_editor_url($email->id, $editor_type);
696
+ } else {
697
+ $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
698
+ }
699
+ switch ($editor_type) {
700
+ case NewsletterEmails::EDITOR_COMPOSER:
701
+ $icon_class = 'th-large';
702
+ break;
703
+ case NewsletterEmails::EDITOR_HTML:
704
+ $icon_class = 'code';
705
+ break;
706
+ default:
707
+ $icon_class = 'edit';
708
+ break;
709
+ }
710
+ if ($only_icon) {
711
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
712
+ '<i class="fas fa-' . $icon_class . '"></i></a>';
713
+ } else {
714
+ return '<a class="button-primary" href="' . $edit_url . '" title="' . esc_attr__('Edit', 'newsletter') . '">' .
715
+ '<i class="fas fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
716
+ }
717
+ }
718
+
719
+ /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
720
+ function get_editor_type($email) {
721
+ $email = (object) $email;
722
+ $editor_type = $email->editor;
723
+
724
+ // Backward compatibility
725
+ $email_options = maybe_unserialize($email->options);
726
+ if (isset($email_options['composer'])) {
727
+ $editor_type = NewsletterEmails::EDITOR_COMPOSER;
728
+ }
729
+ // End backward compatibility
730
+
731
+ return $editor_type;
732
+ }
733
+
734
+ /**
735
+ *
736
+ * @param type $action
737
+ * @param type $user
738
+ * @param type $email
739
+ * @return type
740
+ * @global wpdb $wpdb
741
+ */
742
+ function hook_newsletter_action($action, $user, $email) {
743
+ global $wpdb;
744
+
745
+ switch ($action) {
746
+ case 'v':
747
+ case 'view':
748
+ $id = $_GET['id'];
749
+ if ($id == 'last') {
750
+ $email = $wpdb->get_row("select * from " . NEWSLETTER_EMAILS_TABLE . " where private=0 and type='message' and status='sent' order by send_on desc limit 1");
751
+ } else {
752
+ $email = $this->get_email($_GET['id']);
753
+ }
754
+ if (empty($email)) {
755
+ header("HTTP/1.0 404 Not Found");
756
+ die('Email not found');
757
+ }
758
+
759
+ if (!Newsletter::instance()->is_allowed()) {
760
+
761
+ if ($email->status == 'new') {
762
+ header("HTTP/1.0 404 Not Found");
763
+ die('Not sent yet');
764
+ }
765
+
766
+ if ($email->private == 1) {
767
+ if (!$user) {
768
+ header("HTTP/1.0 404 Not Found");
769
+ die('No available for online view');
770
+ }
771
+ $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
772
+ if (!$sent) {
773
+ header("HTTP/1.0 404 Not Found");
774
+ die('No available for online view');
775
+ }
776
+ }
777
+ }
778
+
779
+
780
+ header('Content-Type: text/html;charset=UTF-8');
781
+ header('X-Robots-Tag: noindex,nofollow,noarchive');
782
+ header('Cache-Control: no-cache,no-store,private');
783
+
784
+ echo $this->replace($email->message, $user, $email);
785
+
786
+ die();
787
+ break;
788
+
789
+ case 'emails-css':
790
+ $email_id = (int) $_GET['id'];
791
+
792
+ $body = Newsletter::instance()->get_email_field($email_id, 'message');
793
+
794
+ $x = strpos($body, '<style');
795
+ if ($x === false)
796
+ return;
797
+
798
+ $x = strpos($body, '>', $x);
799
+ $y = strpos($body, '</style>');
800
+
801
+ header('Content-Type: text/css;charset=UTF-8');
802
+
803
+ echo substr($body, $x + 1, $y - $x - 1);
804
+
805
+ die();
806
+ break;
807
+
808
+ case 'emails-composer-css':
809
+ header('Cache: no-cache');
810
+ header('Content-Type: text/css');
811
+ echo $this->get_composer_css();
812
+ die();
813
+ break;
814
+
815
+ case 'emails-preview':
816
+ if (!Newsletter::instance()->is_allowed()) {
817
+ die('Not enough privileges');
818
+ }
819
+
820
+ if (!check_admin_referer('view')) {
821
+ die();
822
+ }
823
+
824
+ $theme_id = $_GET['id'];
825
+ $theme = $this->themes->get_theme($theme_id);
826
+
827
+ // Used by theme code
828
+ $theme_options = $this->themes->get_options($theme_id);
829
+
830
+ $theme_url = $theme['url'];
831
+
832
+ header('Content-Type: text/html;charset=UTF-8');
833
+
834
+ include $theme['dir'] . '/theme.php';
835
+
836
+ die();
837
+ break;
838
+
839
+ case 'emails-preview-text':
840
+ header('Content-Type: text/plain;charset=UTF-8');
841
+ if (!Newsletter::instance()->is_allowed()) {
842
+ die('Not enough privileges');
843
+ }
844
+
845
+ if (!check_admin_referer('view')) {
846
+ die();
847
+ }
848
+
849
+ // Used by theme code
850
+ $theme_options = $this->get_current_theme_options();
851
+
852
+ $file = include $theme['dir'] . '/theme-text.php';
853
+
854
+ if (is_file($file)) {
855
+ include $file;
856
+ }
857
+
858
+ die();
859
+ break;
860
+
861
+ case 'emails-create':
862
+ // Newsletter from themes are created on frontend context because sometime WP themes change the way the content,
863
+ // excerpt, thumbnail are extracted.
864
+ if (!Newsletter::instance()->is_allowed()) {
865
+ die('Not enough privileges');
866
+ }
867
+
868
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
869
+ $controls = new NewsletterControls();
870
+
871
+ if (!$controls->is_action('create')) {
872
+ die('Wrong call');
873
+ }
874
+
875
+ $theme_id = $controls->data['id'];
876
+ $theme = $this->themes->get_theme($theme_id);
877
+
878
+ if (!$theme) {
879
+ die('invalid theme');
880
+ }
881
+
882
+ $this->themes->save_options($theme_id, $controls->data);
883
+
884
+ $email = array();
885
+ $email['status'] = 'new';
886
+ $email['subject'] = ''; //__('Here the email subject', 'newsletter');
887
+ $email['track'] = Newsletter::instance()->options['track'];
888
+ $email['send_on'] = time();
889
+ $email['editor'] = NewsletterEmails::EDITOR_TINYMCE;
890
+ $email['type'] = 'message';
891
+
892
+ $theme_options = $this->themes->get_options($theme_id);
893
+
894
+ $theme_url = $theme['url'];
895
+ $theme_subject = '';
896
+
897
+ ob_start();
898
+ include $theme['dir'] . '/theme.php';
899
+ $email['message'] = ob_get_clean();
900
+
901
+ if (!empty($theme_subject)) {
902
+ $email['subject'] = $theme_subject;
903
+ }
904
+
905
+ if (file_exists($theme['dir'] . '/theme-text.php')) {
906
+ ob_start();
907
+ include $theme['dir'] . '/theme-text.php';
908
+ $email['message_text'] = ob_get_clean();
909
+ } else {
910
+ $email['message_text'] = 'You need a modern email client to read this email. Read it online: {email_url}.';
911
+ }
912
+
913
+ $email = $this->save_email($email);
914
+
915
+ $edit_url = $this->get_editor_url($email->id, $email->editor);
916
+
917
+ header('Location: ' . $edit_url);
918
+
919
+ die();
920
+ break;
921
+ }
922
+ }
923
+
924
+ function admin_menu() {
925
+ $this->add_menu_page('index', 'Newsletters');
926
+ $this->add_admin_page('list', 'Email List');
927
+ $this->add_admin_page('new', 'Email New');
928
+ $this->add_admin_page('edit', 'Email Edit');
929
+ $this->add_admin_page('theme', 'Email Themes');
930
+ $this->add_admin_page('composer', 'The Composer');
931
+ $this->add_admin_page('editorhtml', 'HTML Editor');
932
+ $this->add_admin_page('editortinymce', 'TinyMCE Editor');
933
+ }
934
+
935
+ /**
936
+ * Builds a block data structure starting from the folder containing the block
937
+ * files.
938
+ *
939
+ * @param string $dir
940
+ * @return array | WP_Error
941
+ */
942
+ function build_block($dir) {
943
+ $dir = realpath($dir);
944
+ $dir = wp_normalize_path($dir);
945
+ $full_file = $dir . '/block.php';
946
+ if (!is_file($full_file)) {
947
+ return new WP_Error('1', 'Missing block.php file in ' . $dir);
948
+ }
949
+
950
+ $wp_content_dir = wp_normalize_path(realpath(WP_CONTENT_DIR));
951
+
952
+ $relative_dir = substr($dir, strlen($wp_content_dir));
953
+ $file = basename($dir);
954
+
955
+ $data = get_file_data($full_file, ['name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type']);
956
+ $defaults = ['section' => 'content', 'name' => ucfirst($file), 'descritpion' => '', 'icon' => plugins_url('newsletter') . '/admin/images/block-icon.png'];
957
+ $data = array_merge($defaults, $data);
958
+
959
+ if (is_file($dir . '/icon.png')) {
960
+ $data['icon'] = content_url($relative_dir . '/icon.png');
961
+ }
962
+
963
+ $data['id'] = sanitize_key($file);
964
+
965
+ // Absolute path of the block files
966
+ $data['dir'] = $dir;
967
+ $data['url'] = content_url($relative_dir);
968
+
969
+ return $data;
970
+ }
971
+
972
+ /**
973
+ *
974
+ * @param type $dir
975
+ * @return type
976
+ */
977
+ function scan_blocks_dir($dir) {
978
+ $dir = realpath($dir);
979
+ if (!$dir) {
980
+ return [];
981
+ }
982
+ $dir = wp_normalize_path($dir);
983
+
984
+ $list = [];
985
+ $handle = opendir($dir);
986
+ while ($file = readdir($handle)) {
987
+
988
+ $data = $this->build_block($dir . '/' . $file);
989
+
990
+ if (is_wp_error($data)) {
991
+ $this->logger->error($data);
992
+ continue;
993
+ }
994
+ $list[$data['id']] = $data;
995
+ }
996
+ closedir($handle);
997
+ return $list;
998
+ }
999
+
1000
+ /**
1001
+ * Array of arrays with every registered block and legacy block converted to the new
1002
+ * format.
1003
+ *
1004
+ * @return array
1005
+ */
1006
+ function get_blocks() {
1007
+
1008
+ if (!is_null($this->blocks)) {
1009
+ return $this->blocks;
1010
+ }
1011
+
1012
+ $this->blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
1013
+
1014
+ $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
1015
+
1016
+ $this->blocks = array_merge($extended, $this->blocks);
1017
+
1018
+ $dirs = apply_filters('newsletter_blocks_dir', array());
1019
+
1020
+ //$this->logger->debug('Block dirs:');
1021
+ //$this->logger->debug($dirs);
1022
+
1023
+ foreach ($dirs as $dir) {
1024
+ $list = $this->scan_blocks_dir($dir);
1025
+ $this->blocks = array_merge($list, $this->blocks);
1026
+ }
1027
+
1028
+ do_action('newsletter_register_blocks');
1029
+
1030
+ foreach (TNP_Composer::$block_dirs as $dir) {
1031
+ $block = $this->build_block($dir);
1032
+ if (is_wp_error($block)) {
1033
+ $this->logger->error($block);
1034
+ continue;
1035
+ }
1036
+ if (!isset($this->blocks[$block['id']])) {
1037
+ $this->blocks[$block['id']] = $block;
1038
+ } else {
1039
+ $this->logger->error('The block "' . $block['id'] . '" has already been registered');
1040
+ }
1041
+ }
1042
+
1043
+ $this->blocks = array_reverse($this->blocks);
1044
+ return $this->blocks;
1045
+ }
1046
+
1047
+ /**
1048
+ * Return a single block (associative array) checking for legacy ID as well.
1049
+ *
1050
+ * @param string $id
1051
+ * @return array
1052
+ */
1053
+ function get_block($id) {
1054
+ switch ($id) {
1055
+ case 'content-03-text.block':
1056
+ $id = 'text';
1057
+ break;
1058
+ case 'footer-03-social.block':
1059
+ $id = 'social';
1060
+ break;
1061
+ case 'footer-02-canspam.block':
1062
+ $id = 'canspam';
1063
+ break;
1064
+ case 'content-05-image.block':
1065
+ $id = 'image';
1066
+ break;
1067
+ case 'header-01-header.block':
1068
+ $id = 'header';
1069
+ break;
1070
+ case 'footer-01-footer.block':
1071
+ $id = 'footer';
1072
+ break;
1073
+ case 'content-02-heading.block':
1074
+ $id = 'heading';
1075
+ break;
1076
+ case 'content-07-twocols.block':
1077
+ case 'content-06-posts.block':
1078
+ $id = 'posts';
1079
+ break;
1080
+ case 'content-04-cta.block':
1081
+ $id = 'cta';
1082
+ break;
1083
+ case 'content-01-hero.block':
1084
+ $id = 'hero';
1085
+ break;
1086
+ // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
1087
+ // break;
1088
+ }
1089
+
1090
+ // Conversion for old full path ID
1091
+ $id = sanitize_key(basename($id));
1092
+
1093
+ // TODO: Correct id for compatibility
1094
+ $blocks = $this->get_blocks();
1095
+ if (!isset($blocks[$id])) {
1096
+ return null;
1097
+ }
1098
+ return $blocks[$id];
1099
+ }
1100
+
1101
+ function scan_presets_dir($dir = null) {
1102
+
1103
+ if (is_null($dir)) {
1104
+ $dir = __DIR__ . '/presets';
1105
+ }
1106
+
1107
+ if (!is_dir($dir)) {
1108
+ return array();
1109
+ }
1110
+
1111
+ $handle = opendir($dir);
1112
+ $list = array();
1113
+ $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
1114
+ while ($file = readdir($handle)) {
1115
+
1116
+ if ($file == '.' || $file == '..')
1117
+ continue;
1118
+
1119
+ // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
1120
+ $preset_id = sanitize_key($file);
1121
+
1122
+ $full_file = $dir . '/' . $file . '/preset.json';
1123
+
1124
+ if (!is_file($full_file)) {
1125
+ continue;
1126
+ }
1127
+
1128
+ $icon = content_url($relative_dir . '/' . $file . '/icon.png');
1129
+
1130
+ $list[$preset_id] = $icon;
1131
+ }
1132
+ closedir($handle);
1133
+ return $list;
1134
+ }
1135
+
1136
+ function get_preset_from_file($id, $dir = null) {
1137
+
1138
+ if (is_null($dir)) {
1139
+ $dir = __DIR__ . '/presets';
1140
+ }
1141
+
1142
+ $id = $this->sanitize_file_name($id);
1143
+
1144
+ if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
1145
+ return array();
1146
+ }
1147
+
1148
+ $json_content = file_get_contents("$dir/$id/preset.json");
1149
+ $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
1150
+ $json = json_decode($json_content);
1151
+ $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png?ver=2";
1152
+
1153
+ return $json;
1154
+ }
1155
+
1156
+ function get_composer_css() {
1157
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
1158
+ $blocks = $this->get_blocks();
1159
+ foreach ($blocks as $block) {
1160
+ if (!file_exists($block['dir'] . '/style.css')) {
1161
+ continue;
1162
+ }
1163
+ $css .= "\n\n";
1164
+ $css .= "/* " . $block['name'] . " */\n";
1165
+ $css .= file_get_contents($block['dir'] . '/style.css');
1166
+ }
1167
+ return $css;
1168
+ }
1169
+
1170
+ function get_composer_backend_css() {
1171
+ $css = file_get_contents(__DIR__ . '/tnp-composer/css/backend.css');
1172
+ $css .= "\n\n";
1173
+ $css .= $this->get_composer_css();
1174
+ return $css;
1175
+ }
1176
+
1177
+ /**
1178
+ * Send an email to the test subscribers.
1179
+ *
1180
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1181
+ * @param NewsletterControls $controls
1182
+ */
1183
+ function send_test_email($email, $controls) {
1184
+ if (!$email) {
1185
+ $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
1186
+ return;
1187
+ }
1188
+
1189
+ $original_subject = $email->subject;
1190
+ $this->set_test_subject_to($email);
1191
+
1192
+ $users = NewsletterUsers::instance()->get_test_users();
1193
+ if (count($users) == 0) {
1194
+ $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
1195
+ '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
1196
+ __('Read more', 'newsletter') . '</strong></a>.';
1197
+ } else {
1198
+ $r = Newsletter::instance()->send($email, $users, true);
1199
+ $emails = array();
1200
+ foreach ($users as $user) {
1201
+ $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
1202
+ }
1203
+ if (is_wp_error($r)) {
1204
+ $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
1205
+ $controls->errors .= __('Test subscribers:', 'newsletter');
1206
+ $controls->errors .= ' ' . implode(', ', $emails);
1207
+ $controls->errors .= '<br>';
1208
+ $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
1209
+ $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1210
+ } else {
1211
+ $controls->messages = __('Test subscribers:', 'newsletter');
1212
+
1213
+ $controls->messages .= ' ' . implode(', ', $emails);
1214
+ $controls->messages .= '.<br>';
1215
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1216
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1217
+ $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1218
+ }
1219
+ }
1220
+ $email->subject = $original_subject;
1221
+ }
1222
+
1223
+ /**
1224
+ * Send an email to the test subscribers.
1225
+ *
1226
+ * @param TNP_Email $email Could be any object with the TNP_Email attributes
1227
+ * @param string $email_address
1228
+ *
1229
+ * @throws Exception
1230
+ */
1231
+ function send_test_newsletter_to_email_address($email, $email_address) {
1232
+
1233
+ if (!$email) {
1234
+ throw new Exception(__('Newsletter should be saved before send a test', 'newsletter'));
1235
+ }
1236
+
1237
+ $this->set_test_subject_to($email);
1238
+
1239
+ $dummy_subscriber = $this->make_dummy_subscriber();
1240
+ $dummy_subscriber->email = $email_address;
1241
+
1242
+ $result = Newsletter::instance()->send($email, [$dummy_subscriber], true);
1243
+
1244
+ $email = '<a href="admin.php?page=newsletter_users_edit&id=' . $dummy_subscriber->id . '" target="_blank">' . $dummy_subscriber->email . '</a>';
1245
+
1246
+ if (is_wp_error($result)) {
1247
+ $error_message = 'Something went wrong. Check the error logs on status page.<br>';
1248
+ $error_message .= __('Test subscribers:', 'newsletter');
1249
+ $error_message .= ' ' . $email;
1250
+ $error_message .= '<br>';
1251
+ $error_message .= '<strong>' . esc_html($result->get_error_message()) . '</strong><br>';
1252
+ $error_message .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1253
+ throw new Exception($error_message);
1254
+ }
1255
+
1256
+ $messages = __('Test subscribers:', 'newsletter');
1257
+
1258
+ $messages .= ' ' . $email;
1259
+ $messages .= '.<br>';
1260
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
1261
+ __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
1262
+ $messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
1263
+
1264
+ return $messages;
1265
+ }
1266
+
1267
+ private function set_test_subject_to($email) {
1268
+ if ($email->subject == '') {
1269
+ $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
1270
+ } else {
1271
+ $email->subject = $email->subject . ' (TEST)';
1272
+ }
1273
+ }
1274
+
1275
+ private function make_dummy_subscriber() {
1276
+ $dummy_user = new TNP_User();
1277
+ $dummy_user->id = 0;
1278
+ $dummy_user->email = 'john.doe@example.org';
1279
+ $dummy_user->name = 'John';
1280
+ $dummy_user->surname = 'Doe';
1281
+ $dummy_user->sex = 'n';
1282
+ $dummy_user->language = '';
1283
+ $dummy_user->ip = '';
1284
+
1285
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1286
+ $profile_key = "profile_$i";
1287
+ $dummy_user->$profile_key = '';
1288
+ }
1289
+
1290
+ return $dummy_user;
1291
+ }
1292
+
1293
+ function restore_options_from_request() {
1294
+
1295
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
1296
+ $controls = new NewsletterControls();
1297
+ $options = $controls->data;
1298
+
1299
+ if (isset($_POST['options']) && is_array($_POST['options'])) {
1300
+ // Get all block options
1301
+ //$options = stripslashes_deep($_POST['options']);
1302
+ // Deserialize inline edits when
1303
+ // render is preformed on saving block options
1304
+ if (isset($options['inline_edits']) && !is_array($options['inline_edits'])) {
1305
+ $options['inline_edits'] = $this->options_decode($options['inline_edits']);
1306
+ }
1307
+
1308
+ // Restore inline edits from data-json
1309
+ // coming from inline editing
1310
+ // and merge with current inline edit
1311
+ if (isset($_POST['encoded_options'])) {
1312
+ $decoded_options = $this->options_decode($_POST['encoded_options']);
1313
+
1314
+ $to_merge_inline_edits = [];
1315
+
1316
+ if (isset($decoded_options['inline_edits'])) {
1317
+ foreach ($decoded_options['inline_edits'] as $decoded_inline_edit) {
1318
+ $to_merge_inline_edits[$decoded_inline_edit['post_id'] . $decoded_inline_edit['type']] = $decoded_inline_edit;
1319
+ }
1320
+ }
1321
+
1322
+ //Overwrite with new edited content
1323
+ if (isset($options['inline_edits'])) {
1324
+ foreach ($options['inline_edits'] as $inline_edit) {
1325
+ $to_merge_inline_edits[$inline_edit['post_id'] . $inline_edit['type']] = $inline_edit;
1326
+ }
1327
+ }
1328
+
1329
+ $options['inline_edits'] = array_values($to_merge_inline_edits);
1330
+ $options = array_merge($decoded_options, $options);
1331
+ }
1332
+
1333
+ return $options;
1334
+ }
1335
+
1336
+ return array();
1337
+ }
1338
+
1339
+ public function hook_wp_ajax_tnpc_delete_preset() {
1340
+
1341
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'preset')) {
1342
+ wp_send_json_error('Expired request');
1343
+ }
1344
+
1345
+ $preset_id = (int) $_REQUEST['presetId'];
1346
+
1347
+ $newsletter = Newsletter::instance();
1348
+
1349
+ if ($preset_id > 0) {
1350
+ $preset = $newsletter->get_email($preset_id);
1351
+
1352
+ if ($preset && $preset->type === self::PRESET_EMAIL_TYPE) {
1353
+ Newsletter::instance()->delete_email($preset_id);
1354
+ wp_send_json_success();
1355
+ } else {
1356
+ wp_send_json_error(__('Is not a preset!', 'newsletter'));
1357
+ }
1358
+ } else {
1359
+ wp_send_json_error();
1360
+ }
1361
+ }
1362
+
1363
+ }
1364
+
1365
+ NewsletterEmails::instance();
includes/module.php CHANGED
@@ -1,2632 +1,2632 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- require_once __DIR__ . '/logger.php';
6
- require_once __DIR__ . '/store.php';
7
- require_once __DIR__ . '/composer.php';
8
- require_once __DIR__ . '/addon.php';
9
- require_once __DIR__ . '/mailer.php';
10
- require_once __DIR__ . '/themes.php';
11
-
12
- class TNP_Media {
13
-
14
- var $id;
15
- var $url;
16
- var $width;
17
- var $height;
18
- var $alt;
19
- var $link;
20
- var $align = 'center';
21
-
22
- /** Sets the width recalculating the height */
23
- public function set_width($width) {
24
- $width = (int) $width;
25
- if (empty($width))
26
- return;
27
- if ($this->width < $width)
28
- return;
29
- $this->height = floor(($width / $this->width) * $this->height);
30
- $this->width = $width;
31
- }
32
-
33
- /** Sets the height recalculating the width */
34
- public function set_height($height) {
35
- $height = (int) $height;
36
- $this->width = floor(($height / $this->height) * $this->width);
37
- $this->height = $height;
38
- }
39
-
40
- }
41
-
42
- /**
43
- * @property int $id The list unique identifier
44
- * @property string $name The list name
45
- * @property bool $forced If the list must be added to every new subscriber
46
- * @property int $status When and how the list is visible to the subscriber - see constants
47
- * @property bool $checked If it must be pre-checked on subscription form
48
- * @property array $languages The list of language used to pre-assign this list
49
- */
50
- class TNP_List {
51
-
52
- const STATUS_PRIVATE = 0;
53
- const STATUS_PUBLIC = 1;
54
- const SUBSCRIPTION_HIDE = 0;
55
- const SUBSCRIPTION_SHOW = 1;
56
- const SUBSCRIPTION_SHOW_CHECKED = 2;
57
- const PROFILE_HIDE = 0;
58
- const PROFILE_SHOW = 1;
59
-
60
- var $id;
61
- var $name;
62
- var $status;
63
- var $forced;
64
- var $checked;
65
- var $show_on_subscription;
66
- var $show_on_profile;
67
-
68
- function is_private() {
69
- return $this->status == self::STATUS_PRIVATE;
70
- }
71
-
72
- }
73
-
74
- /**
75
- * @property int $id The list unique identifier
76
- * @property string $name The list name
77
- * @property int $status When and how the list is visible to the subscriber - see constants
78
- * @property string $type Field type: text or select
79
- * @property array $options Field options (usually the select items)
80
- */
81
- class TNP_Profile {
82
-
83
- const STATUS_PRIVATE = 0;
84
- const STATUS_PUBLIC = 2;
85
- const STATUS_PROFILE_ONLY = 1;
86
- const STATUS_HIDDEN = 3; // Public but never shown (can be set with a hidden form field)
87
- const TYPE_TEXT = 'text';
88
- const TYPE_SELECT = 'select';
89
-
90
- public $id;
91
- public $name;
92
- public $status;
93
- public $type;
94
- public $options;
95
- public $placeholder;
96
- public $rule;
97
-
98
- public function __construct($id, $name, $status, $type, $options, $placeholder, $rule) {
99
- $this->id = $id;
100
- $this->name = $name;
101
- $this->status = $status;
102
- $this->type = $type;
103
- $this->options = $options;
104
- $this->placeholder = $placeholder;
105
- $this->rule = $rule;
106
- }
107
-
108
- function is_select() {
109
- return $this->type == self::TYPE_SELECT;
110
- }
111
-
112
- function is_text() {
113
- return $this->type == self::TYPE_TEXT;
114
- }
115
-
116
- function is_required() {
117
- return $this->rule == 1;
118
- }
119
-
120
- function is_private() {
121
- return $this->status == self::STATUS_PRIVATE;
122
- }
123
-
124
- function show_on_profile() {
125
- return $this->status == self::STATUS_PROFILE_ONLY || $this->status == self::STATUS_PUBLIC;
126
- }
127
-
128
- }
129
-
130
- class TNP_Profile_Service {
131
-
132
- /**
133
- *
134
- * @param string $language
135
- * @param string $type
136
- * @return TNP_Profile[]
137
- */
138
- static function get_profiles($language = '', $type = '') {
139
-
140
- static $profiles = [];
141
- $k = $language . $type;
142
-
143
- if (isset($profiles[$k])) {
144
- return $profiles[$k];
145
- }
146
-
147
- $profiles[$k] = [];
148
- $profile_options = NewsletterSubscription::instance()->get_options('profile', $language);
149
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
150
- if (empty($profile_options['profile_' . $i])) {
151
- continue;
152
- }
153
- $profile = self::create_profile_from_options($profile_options, $i);
154
-
155
- if (empty($type) ||
156
- ( $type == TNP_Profile::TYPE_SELECT && $profile->is_select() ) ||
157
- ( $type == TNP_Profile::TYPE_TEXT && $profile->is_text() )) {
158
- $profiles[$k]['' . $i] = $profile;
159
- }
160
- }
161
-
162
- return $profiles[$k];
163
- }
164
-
165
- static function get_profile_by_id($id, $language = '') {
166
-
167
- $profiles = self::get_profiles($language);
168
- if (isset($profiles[$id]))
169
- return $profiles[$id];
170
- return null;
171
- }
172
-
173
- /**
174
- * @return TNP_Profile
175
- */
176
- private static function create_profile_from_options($options, $id) {
177
- return new TNP_Profile(
178
- $id,
179
- $options['profile_' . $id],
180
- (int) $options['profile_' . $id . '_status'],
181
- $options['profile_' . $id . '_type'],
182
- self::string_db_options_to_array($options['profile_' . $id . '_options']),
183
- $options['profile_' . $id . '_placeholder'],
184
- $options['profile_' . $id . '_rules']
185
- );
186
- }
187
-
188
- /**
189
- * Returns a list of strings which are the items for the select field.
190
- * @return array
191
- */
192
- private static function string_db_options_to_array($string_options) {
193
- $items = array_map('trim', explode(',', $string_options));
194
- $items = array_combine($items, $items);
195
-
196
- return $items;
197
- }
198
-
199
- }
200
-
201
- /**
202
- * Represents the set of data collected by a subscription interface (form, API, ...). Only a valid
203
- * email is mandatory.
204
- */
205
- class TNP_Subscription_Data {
206
-
207
- var $email = null;
208
- var $name = null;
209
- var $surname = null;
210
- var $sex = null;
211
- var $language = null;
212
- var $referrer = null;
213
- var $http_referrer = null;
214
- var $ip = null;
215
- var $country = null;
216
- var $region = null;
217
- var $city = null;
218
-
219
- /**
220
- * Associative array id=>value of lists chosen by the subscriber. A list can be set to
221
- * 0 meaning the subscriber does not want to be in that list.
222
- * The lists must be public: non public lists are filtered.
223
- * @var array
224
- */
225
- var $lists = [];
226
- var $profiles = [];
227
-
228
- function merge_in($subscriber) {
229
- if (!$subscriber)
230
- $subscriber = new TNP_User();
231
- if (!empty($this->email))
232
- $subscriber->email = $this->email;
233
- if (!empty($this->name))
234
- $subscriber->name = $this->name;
235
- if (!empty($this->surname))
236
- $subscriber->surname = $this->surname;
237
- if (!empty($this->sex))
238
- $subscriber->sex = $this->sex;
239
- if (!empty($this->language))
240
- $subscriber->language = $this->language;
241
- if (!empty($this->ip))
242
- $subscriber->ip = $this->ip;
243
- if (!empty($this->referrer))
244
- $subscriber->referrer = $this->referrer;
245
- if (!empty($this->http_referrer))
246
- $subscriber->http_referrer = $this->http_referrer;
247
- if (!empty($this->country))
248
- $subscriber->country = $this->country;
249
- if (!empty($this->region))
250
- $subscriber->region = $this->region;
251
- if (!empty($this->city))
252
- $subscriber->city = $this->city;
253
-
254
-
255
- foreach ($this->lists as $id => $value) {
256
- $key = 'list_' . $id;
257
- $subscriber->$key = $value;
258
- }
259
-
260
- // Profile
261
- foreach ($this->profiles as $id => $value) {
262
- $key = 'profile_' . $id;
263
- $subscriber->$key = $value;
264
- }
265
- }
266
-
267
- /** Sets to active a set of lists. Accepts incorrect data (and ignores it).
268
- *
269
- * @param array $list_ids Array of list IDs
270
- */
271
- function add_lists($list_ids) {
272
- if (empty($list_ids) || !is_array($list_ids))
273
- return;
274
- foreach ($list_ids as $list_id) {
275
- $list_id = (int) $list_id;
276
- if ($list_id < 0 || $list_id > NEWSLETTER_LIST_MAX)
277
- continue;
278
- $this->lists[$list_id] = 1;
279
- }
280
- }
281
-
282
- }
283
-
284
- /**
285
- * Represents a subscription request with the subscriber data and actions to be taken by
286
- * the subscription engine (spam check, notifications, ...).
287
- */
288
- class TNP_Subscription {
289
-
290
- const EXISTING_ERROR = 1;
291
- const EXISTING_MERGE = 0;
292
- const EXISTING_SINGLE_OPTIN = 2;
293
-
294
-
295
- /**
296
- * Subscriber's data following the syntax of the TNP_User
297
- * @var TNP_Subscription_Data
298
- */
299
- var $data;
300
- var $spamcheck = true;
301
- // The optin to use, empty for the plugin default. It's a string to facilitate the use by addons (which have a selector for the desired
302
- // optin as empty (for default), 'single' or 'double'.
303
- var $optin = null;
304
- // What to do with an existing subscriber???
305
- var $if_exists = self::EXISTING_MERGE;
306
-
307
- /**
308
- * Determines if the welcome or activation email should be sent. Note: sometime an activation email is sent disregarding
309
- * this setting.
310
- * @var boolean
311
- */
312
- var $send_emails = true;
313
-
314
- public function __construct() {
315
- $this->data = new TNP_Subscription_Data();
316
- }
317
-
318
- public function is_single_optin() {
319
- return $this->optin == 'single';
320
- }
321
-
322
- public function is_double_optin() {
323
- return $this->optin == 'double';
324
- }
325
-
326
- }
327
-
328
- /**
329
- * @property int $id The subscriber unique identifier
330
- * @property string $email The subscriber email
331
- * @property string $name The subscriber name or first name
332
- * @property string $surname The subscriber last name
333
- * @property string $status The subscriber status
334
- * @property string $language The subscriber language code 2 chars lowercase
335
- * @property string $token The subscriber secret token
336
- * @property string $country The subscriber country code 2 chars uppercase
337
- */
338
- class TNP_User {
339
-
340
- const STATUS_CONFIRMED = 'C';
341
- const STATUS_NOT_CONFIRMED = 'S';
342
- const STATUS_UNSUBSCRIBED = 'U';
343
- const STATUS_BOUNCED = 'B';
344
- const STATUS_COMPLAINED = 'P';
345
-
346
- var $ip = '';
347
-
348
- public static function get_status_label($status) {
349
- switch ($status) {
350
- case self::STATUS_NOT_CONFIRMED: return __('NOT CONFIRMED', 'newsletter');
351
- break;
352
- case self::STATUS_CONFIRMED: return __('CONFIRMED', 'newsletter');
353
- break;
354
- case self::STATUS_UNSUBSCRIBED: return __('UNSUBSCRIBED', 'newsletter');
355
- break;
356
- case self::STATUS_BOUNCED: return __('BOUNCED', 'newsletter');
357
- break;
358
- case self::STATUS_COMPLAINED: return __('COMPLAINED', 'newsletter');
359
- break;
360
- default:
361
- return __('Unknown', 'newsletter');
362
- }
363
- }
364
-
365
- }
366
-
367
- /**
368
- * @property int $id The email unique identifier
369
- * @property string $subject The email subject
370
- * @property string $message The email html message
371
- * @property int $track Check if the email stats should be active
372
- * @property array $options Email options
373
- * @property int $total Total emails to send
374
- * @property int $sent Total sent emails by now
375
- * @property int $open_count Total opened emails
376
- * @property int $click_count Total clicked emails
377
- * */
378
- class TNP_Email {
379
-
380
- const STATUS_DRAFT = 'new';
381
- const STATUS_SENT = 'sent';
382
- const STATUS_SENDING = 'sending';
383
- const STATUS_PAUSED = 'paused';
384
- const STATUS_ERROR = 'error';
385
-
386
- }
387
-
388
- class NewsletterModule {
389
-
390
- /**
391
- * @var NewsletterLogger
392
- */
393
- var $logger;
394
-
395
- /**
396
- * @var NewsletterLogger
397
- */
398
- var $admin_logger;
399
-
400
- /**
401
- * @var NewsletterStore
402
- */
403
- var $store;
404
-
405
- /**
406
- * The main module options
407
- * @var array
408
- */
409
- var $options;
410
-
411
- /**
412
- * @var string The module name
413
- */
414
- var $module;
415
-
416
- /**
417
- * The module version
418
- * @var string
419
- */
420
- var $version;
421
- var $old_version;
422
-
423
- /**
424
- * Prefix for all options stored on WordPress options table.
425
- * @var string
426
- */
427
- var $prefix;
428
-
429
- /**
430
- * @var NewsletterThemes
431
- */
432
- var $themes;
433
- var $components;
434
- static $current_language = '';
435
-
436
- function __construct($module, $version, $module_id = null, $components = array()) {
437
- $this->module = $module;
438
- $this->version = $version;
439
- $this->prefix = 'newsletter_' . $module;
440
- array_unshift($components, '');
441
- $this->components = $components;
442
-
443
- $this->logger = new NewsletterLogger($module);
444
-
445
- $this->options = $this->get_options();
446
- $this->store = NewsletterStore::singleton();
447
-
448
- //$this->logger->debug($module . ' constructed');
449
- // Version check
450
- if (is_admin()) {
451
- $this->admin_logger = new NewsletterLogger($module . '-admin');
452
- $this->old_version = get_option($this->prefix . '_version', '0.0.0');
453
-
454
- if ($this->old_version == '0.0.0') {
455
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
456
- $this->first_install();
457
- update_option($this->prefix . "_first_install_time", time(), FALSE);
458
- }
459
-
460
- if (strcmp($this->old_version, $this->version) != 0) {
461
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
462
- $this->logger->info('Version changed from ' . $this->old_version . ' to ' . $this->version);
463
- // Do all the stuff for this version change
464
- $this->upgrade();
465
- update_option($this->prefix . '_version', $this->version);
466
- }
467
-
468
- add_action('admin_menu', array($this, 'admin_menu'));
469
- }
470
- }
471
-
472
- /**
473
- *
474
- * @global wpdb $wpdb
475
- * @param string $query
476
- */
477
- function query($query) {
478
- global $wpdb;
479
-
480
- $this->logger->debug($query);
481
- //$start = microtime(true);
482
- $r = $wpdb->query($query);
483
- //$this->logger->debug($wpdb->last_query);
484
- //$this->logger->debug('Execution time: ' . (microtime(true)-$start));
485
- //$this->logger->debug('Result: ' . $r);
486
- if ($r === false) {
487
- $this->logger->fatal($query);
488
- $this->logger->fatal($wpdb->last_error);
489
- }
490
- return $r;
491
- }
492
-
493
- function get_results($query) {
494
- global $wpdb;
495
- $r = $wpdb->get_results($query);
496
- if ($r === false) {
497
- $this->logger->fatal($query);
498
- $this->logger->fatal($wpdb->last_error);
499
- }
500
- return $r;
501
- }
502
-
503
- /**
504
- *
505
- * @global wpdb $wpdb
506
- * @param string $table
507
- * @param array $data
508
- */
509
- function insert($table, $data) {
510
- global $wpdb;
511
- $this->logger->debug("inserting into table $table");
512
- $r = $wpdb->insert($table, $data);
513
- if ($r === false) {
514
- $this->logger->fatal($wpdb->last_error);
515
- }
516
- }
517
-
518
- function first_install() {
519
- $this->logger->debug('First install');
520
- }
521
-
522
- /**
523
- * Does a basic upgrade work, checking if the options is already present and if not (first
524
- * installation), recovering the defaults, saving them on database and initializing the
525
- * internal $options.
526
- */
527
- function upgrade() {
528
- foreach ($this->components as $component) {
529
- $this->logger->debug('Upgrading component ' . $component);
530
- $this->init_options($component);
531
- }
532
- }
533
-
534
- function init_options($component = '', $autoload = true) {
535
- global $wpdb;
536
- $default_options = $this->get_default_options($component);
537
- $options = $this->get_options($component);
538
- $options = array_merge($default_options, $options);
539
- $this->save_options($options, $component, $autoload);
540
- }
541
-
542
- function upgrade_query($query) {
543
- global $wpdb, $charset_collate;
544
-
545
- $this->logger->info('upgrade_query> Executing ' . $query);
546
- $suppress_errors = $wpdb->suppress_errors(true);
547
- $wpdb->query($query);
548
- if ($wpdb->last_error) {
549
- $this->logger->debug($wpdb->last_error);
550
- }
551
- $wpdb->suppress_errors($suppress_errors);
552
- }
553
-
554
- /** Returns a prefix to be used for option names and other things which need to be uniquely named. The parameter
555
- * "sub" should be used when a sub name is needed for another set of options or like.
556
- *
557
- * @param string $sub
558
- * @return string The prefix for names
559
- */
560
- function get_prefix($sub = '', $language = '') {
561
- return $this->prefix . (!empty($sub) ? '_' : '') . $sub . (!empty($language) ? '_' : '') . $language;
562
- }
563
-
564
- /**
565
- * Returns the options of a module, if not found an empty array.
566
- */
567
- function get_options($sub = '', $language = '') {
568
- $options = get_option($this->get_prefix($sub, $language), array());
569
- // Protection against scarmled database...
570
- if (!is_array($options)) {
571
- $options = array();
572
- }
573
- if ($language) {
574
- $main_options = get_option($this->get_prefix($sub));
575
- // Protection against scarmled database...
576
- if (!is_array($main_options))
577
- $main_options = array();
578
- //$options = array_merge($main_options, array_filter($options));
579
- $options = array_merge($main_options, $options);
580
- }
581
- return $options;
582
- }
583
-
584
- function get_default_options($sub = '') {
585
- if (!empty($sub)) {
586
- $sub = '-' . $sub;
587
- }
588
- $file = NEWSLETTER_DIR . '/' . $this->module . '/defaults' . $sub . '.php';
589
- if (file_exists($file)) {
590
- @include $file;
591
- }
592
-
593
- if (!isset($options) || !is_array($options)) {
594
- return array();
595
- }
596
- return $options;
597
- }
598
-
599
- function reset_options($sub = '') {
600
- $this->save_options(array_merge($this->get_options($sub), $this->get_default_options($sub)), $sub);
601
- return $this->get_options($sub);
602
- }
603
-
604
- /**
605
- * Saves the module options (or eventually a subset names as per parameter $sub). $options
606
- * should be an array (even if it can work with non array options.
607
- * The internal module options variable IS initialized with those new options only for the main
608
- * options (empty $sub parameter).
609
- * If the options contain a "theme" value, the theme-related options contained are saved as well
610
- * (used by some modules).
611
- *
612
- * @param array $options
613
- * @param string $sub
614
- */
615
- function save_options($options, $sub = '', $autoload = null, $language = '') {
616
- update_option($this->get_prefix($sub, $language), $options, $autoload);
617
- if (empty($sub) && empty($language)) {
618
- $this->options = $options;
619
- if (isset($this->themes) && isset($options['theme'])) {
620
- $this->themes->save_options($options['theme'], $options);
621
- }
622
- }
623
- }
624
-
625
- function delete_options($sub = '') {
626
- delete_option($this->get_prefix($sub));
627
- if (empty($sub)) {
628
- $this->options = array();
629
- }
630
- }
631
-
632
- function merge_options($options, $sub = '', $language = '') {
633
- if (!is_array($options)) {
634
- $options = array();
635
- }
636
- $old_options = $this->get_options($sub, $language);
637
- $this->save_options(array_merge($old_options, $options), $sub, null, $language);
638
- }
639
-
640
- function backup_options($sub) {
641
- $options = $this->get_options($sub);
642
- update_option($this->get_prefix($sub) . '_backup', $options, false);
643
- }
644
-
645
- function get_last_run($sub = '') {
646
- return get_option($this->get_prefix($sub) . '_last_run', 0);
647
- }
648
-
649
- /**
650
- * Save the module last run value. Used to store a timestamp for some modules,
651
- * for example the Feed by Mail module.
652
- *
653
- * @param int $time Unix timestamp (as returned by time() for example)
654
- * @param string $sub Sub module name (default empty)
655
- */
656
- function save_last_run($time, $sub = '') {
657
- update_option($this->get_prefix($sub) . '_last_run', $time);
658
- }
659
-
660
- /**
661
- * Sums $delta seconds to the last run time.
662
- * @param int $delta Seconds
663
- * @param string $sub Sub module name (default empty)
664
- */
665
- function add_to_last_run($delta, $sub = '') {
666
- $time = $this->get_last_run($sub);
667
- $this->save_last_run($time + $delta, $sub);
668
- }
669
-
670
- /**
671
- * Checks if the semaphore of that name (for this module) is still red. If it is active the method
672
- * returns false. If it is not active, it will be activated for $time seconds.
673
- *
674
- * Since this method activate the semaphore when called, it's name is a bit confusing.
675
- *
676
- * @param string $name Sempahore name (local to this module)
677
- * @param int $time Max time in second this semaphore should stay red
678
- * @return boolean False if the semaphore is red and you should not proceed, true is it was not active and has been activated.
679
- */
680
- function check_transient($name, $time) {
681
- if ($time < 60)
682
- $time = 60;
683
- //usleep(rand(0, 1000000));
684
- if (($value = get_transient($this->get_prefix() . '_' . $name)) !== false) {
685
- list($t, $v) = explode(';', $value, 2);
686
- $this->logger->error('Blocked by transient ' . $this->get_prefix() . '_' . $name . ' set ' . (time() - $t) . ' seconds ago by ' . $v);
687
- return false;
688
- }
689
- //$ip = ''; //gethostbyname(gethostname());
690
- $value = time() . ";" . ABSPATH . ';' . gethostname();
691
- set_transient($this->get_prefix() . '_' . $name, $value, $time);
692
- return true;
693
- }
694
-
695
- function delete_transient($name = '') {
696
- delete_transient($this->get_prefix() . '_' . $name);
697
- }
698
-
699
- /** Returns a random token of the specified size (or 10 characters if size is not specified).
700
- *
701
- * @param int $size
702
- * @return string
703
- */
704
- static function get_token($size = 10) {
705
- return substr(md5(rand()), 0, $size);
706
- }
707
-
708
- /**
709
- * Adds query string parameters to an URL checing id there are already other parameters.
710
- *
711
- * @param string $url
712
- * @param string $qs The part of query-string to add (param1=value1&param2=value2...)
713
- * @param boolean $amp If the method must use the &amp; instead of the plain & (default true)
714
- * @return string
715
- */
716
- static function add_qs($url, $qs, $amp = true) {
717
- if (strpos($url, '?') !== false) {
718
- if ($amp)
719
- return $url . '&amp;' . $qs;
720
- else
721
- return $url . '&' . $qs;
722
- } else
723
- return $url . '?' . $qs;
724
- }
725
-
726
- /**
727
- * Returns the email address normalized, lowercase with no spaces. If it's not a valid email
728
- * returns false.
729
- */
730
- static function normalize_email($email) {
731
- if (!is_string($email)) {
732
- return false;
733
- }
734
- $email = strtolower(trim($email));
735
- if (!is_email($email)) {
736
- return false;
737
- }
738
- //$email = apply_filters('newsletter_normalize_email', $email);
739
- return $email;
740
- }
741
-
742
- static function normalize_name($name) {
743
- $name = html_entity_decode($name, ENT_QUOTES);
744
- $name = str_replace(';', ' ', $name);
745
- $name = strip_tags($name);
746
-
747
- return $name;
748
- }
749
-
750
- static function normalize_sex($sex) {
751
- $sex = trim(strtolower($sex));
752
- if ($sex != 'f' && $sex != 'm') {
753
- $sex = 'n';
754
- }
755
- return $sex;
756
- }
757
-
758
- static function is_email($email, $empty_ok = false) {
759
-
760
- if (!is_string($email)) {
761
- return false;
762
- }
763
- $email = strtolower(trim($email));
764
-
765
- if ($email == '') {
766
- return $empty_ok;
767
- }
768
-
769
- if (!is_email($email)) {
770
- return false;
771
- }
772
- return true;
773
- }
774
-
775
- /**
776
- * Converts a GMT date from mysql (see the posts table columns) into a timestamp.
777
- *
778
- * @param string $s GMT date with format yyyy-mm-dd hh:mm:ss
779
- * @return int A timestamp
780
- */
781
- static function m2t($s) {
782
-
783
- // TODO: use the wordpress function I don't remember the name
784
- $s = explode(' ', $s);
785
- $d = explode('-', $s[0]);
786
- $t = explode(':', $s[1]);
787
- return gmmktime((int) $t[0], (int) $t[1], (int) $t[2], (int) $d[1], (int) $d[2], (int) $d[0]);
788
- }
789
-
790
- static function format_date($time) {
791
- if (empty($time)) {
792
- return '-';
793
- }
794
- return gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
795
- }
796
-
797
- static function format_time_delta($delta) {
798
- $days = floor($delta / (3600 * 24));
799
- $hours = floor(($delta % (3600 * 24)) / 3600);
800
- $minutes = floor(($delta % 3600) / 60);
801
- $seconds = floor(($delta % 60));
802
- $buffer = $days . ' days, ' . $hours . ' hours, ' . $minutes . ' minutes, ' . $seconds . ' seconds';
803
- return $buffer;
804
- }
805
-
806
- /**
807
- * Formats a scheduler returned "next execution" time, managing negative or false values. Many times
808
- * used in conjuction with "last run".
809
- *
810
- * @param string $name The scheduler name
811
- * @return string
812
- */
813
- static function format_scheduler_time($name) {
814
- $time = wp_next_scheduled($name);
815
- if ($time === false) {
816
- return 'No next run scheduled';
817
- }
818
- $delta = $time - time();
819
- // If less 10 minutes late it can be a cron problem but now it is working
820
- if ($delta < 0 && $delta > -600) {
821
- return 'Probably running now';
822
- } else if ($delta <= -600) {
823
- return 'It seems the cron system is not working. Reload the page to see if this message change.';
824
- }
825
- return 'Runs in ' . self::format_time_delta($delta);
826
- }
827
-
828
- static function date($time = null, $now = false, $left = false) {
829
- if (is_null($time)) {
830
- $time = time();
831
- }
832
- if ($time == false) {
833
- $buffer = 'none';
834
- } else {
835
- $buffer = gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
836
- }
837
- if ($now) {
838
- $buffer .= ' (now: ' . gmdate(get_option('date_format') . ' ' .
839
- get_option('time_format'), time() + get_option('gmt_offset') * 3600);
840
- $buffer .= ')';
841
- }
842
- if ($left) {
843
- $buffer .= ', ' . gmdate('H:i:s', $time - time()) . ' left';
844
- }
845
- return $buffer;
846
- }
847
-
848
- /**
849
- * Return an array of array with on first element the array of recent post and on second element the array
850
- * of old posts.
851
- *
852
- * @param array $posts
853
- * @param int $time
854
- */
855
- static function split_posts(&$posts, $time = 0) {
856
- if ($time < 0) {
857
- return array_chunk($posts, ceil(count($posts) / 2));
858
- }
859
-
860
- $result = array(array(), array());
861
-
862
- if (empty($posts))
863
- return $result;
864
-
865
- foreach ($posts as &$post) {
866
- if (self::is_post_old($post, $time))
867
- $result[1][] = $post;
868
- else
869
- $result[0][] = $post;
870
- }
871
- return $result;
872
- }
873
-
874
- static function is_post_old(&$post, $time = 0) {
875
- return self::m2t($post->post_date_gmt) <= $time;
876
- }
877
-
878
- static function get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
879
- global $post;
880
-
881
- if (empty($post_id))
882
- $post_id = $post->ID;
883
- if (empty($post_id))
884
- return $alternative;
885
-
886
- $image_id = function_exists('get_post_thumbnail_id') ? get_post_thumbnail_id($post_id) : false;
887
- if ($image_id) {
888
- $image = wp_get_attachment_image_src($image_id, $size);
889
- return $image[0];
890
- } else {
891
- $attachments = get_children(array('post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID'));
892
-
893
- if (empty($attachments)) {
894
- return $alternative;
895
- }
896
-
897
- foreach ($attachments as $id => $attachment) {
898
- $image = wp_get_attachment_image_src($id, $size);
899
- return $image[0];
900
- }
901
- }
902
- }
903
-
904
- /**
905
- * Cleans up a text containing url tags with appended the absolute URL (due to
906
- * the editor behavior) moving back them to the simple form.
907
- */
908
- static function clean_url_tags($text) {
909
- $text = str_replace('%7B', '{', $text);
910
- $text = str_replace('%7D', '}', $text);
911
-
912
- // Only tags which are {*_url}
913
- $text = preg_replace("/[\"']http[^\"']+(\\{[^\\}]+_url\\})[\"']/i", "\"\\1\"", $text);
914
- return $text;
915
- }
916
-
917
- function admin_menu() {
918
-
919
- }
920
-
921
- function add_menu_page($page, $title, $capability = '') {
922
- if (!Newsletter::instance()->is_allowed())
923
- return;
924
- $name = 'newsletter_' . $this->module . '_' . $page;
925
- add_submenu_page('newsletter_main_index', $title, $title, 'exist', $name, array($this, 'menu_page'));
926
- }
927
-
928
- function add_admin_page($page, $title) {
929
- if (!Newsletter::instance()->is_allowed()) {
930
- return;
931
- }
932
- $name = 'newsletter_' . $this->module . '_' . $page;
933
- add_submenu_page(null, $title, $title, 'exist', $name, array($this, 'menu_page'));
934
- }
935
-
936
- function sanitize_file_name($name) {
937
- return preg_replace('/[^a-z_\\-]/i', '', $name);
938
- }
939
-
940
- function menu_page() {
941
- global $plugin_page, $newsletter, $wpdb;
942
-
943
- $parts = explode('_', $plugin_page, 3);
944
- $module = $this->sanitize_file_name($parts[1]);
945
- $page = $this->sanitize_file_name($parts[2]);
946
- $page = str_replace('_', '-', $page);
947
-
948
- $file = NEWSLETTER_DIR . '/' . $module . '/' . $page . '.php';
949
-
950
- require $file;
951
- }
952
-
953
- function get_admin_page_url($page) {
954
- return admin_url('admin.php') . '?page=newsletter_' . $this->module . '_' . $page;
955
- }
956
-
957
- /** Returns all the emails of the give type (message, feed, followup, ...) and in the given format
958
- * (default as objects). Return false on error or at least an empty array. Errors should never
959
- * occur.
960
- *
961
- * @global wpdb $wpdb
962
- * @param string $type
963
- * @return boolean|array
964
- */
965
- function get_emails($type = null, $format = OBJECT) {
966
- global $wpdb;
967
- if ($type == null) {
968
- $list = $wpdb->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " order by id desc", $format);
969
- } else {
970
- $type = (string) $type;
971
- $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where type=%s order by id desc", $type), $format);
972
- }
973
- if ($wpdb->last_error) {
974
- $this->logger->error($wpdb->last_error);
975
- return false;
976
- }
977
- if (empty($list)) {
978
- return [];
979
- }
980
- return $list;
981
- }
982
-
983
- function get_emails_by_status($status) {
984
- global $wpdb;
985
- $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where status=%s order by id desc", $status));
986
-
987
- array_walk($list, function ($email) {
988
- $email->options = maybe_unserialize($email->options);
989
- if (!is_array($email->options)) {
990
- $email->options = [];
991
- }
992
- });
993
- return $list;
994
- }
995
-
996
- /**
997
- * @param string $key
998
- * @param mixed $value
999
- * @return TNP_Email[]
1000
- */
1001
- // function get_emails_by_field($key, $value) {
1002
- // global $wpdb;
1003
- //
1004
- // $value_placeholder = is_int($value) ? '%d' : '%s';
1005
- //
1006
- // $key = '`' . str_replace('`', '', $key) . '`';
1007
- //
1008
- // $query = $wpdb->prepare("SELECT * FROM " . NEWSLETTER_EMAILS_TABLE . " WHERE $key=$value_placeholder ORDER BY id DESC", $value);
1009
- // //die($query);
1010
- //
1011
- // $email_list = $wpdb->get_results($query);
1012
- //
1013
- // if ($wpdb->last_error) {
1014
- // $this->logger->error($wpdb->last_error);
1015
- //
1016
- // return [];
1017
- // }
1018
- //
1019
- // //Unserialize options
1020
- // array_walk($email_list, function ($email) {
1021
- // $email->options = maybe_unserialize($email->options);
1022
- // if (!is_array($email->options)) {
1023
- // $email->options = [];
1024
- // }
1025
- // });
1026
- //
1027
- // return $email_list;
1028
- // }
1029
-
1030
- /**
1031
- * Retrieves an email from DB and unserialize the options.
1032
- *
1033
- * @param mixed $id
1034
- * @param string $format
1035
- * @return TNP_Email An object with the same fields of TNP_Email, but not actually of that type
1036
- */
1037
- function get_email($id, $format = OBJECT) {
1038
- $email = $this->store->get_single(NEWSLETTER_EMAILS_TABLE, $id, $format);
1039
- if (!$email) {
1040
- return null;
1041
- }
1042
- if ($format == OBJECT) {
1043
- $email->options = maybe_unserialize($email->options);
1044
- if (!is_array($email->options)) {
1045
- $email->options = array();
1046
- }
1047
- if (empty($email->query)) {
1048
- $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1049
- }
1050
- } else if ($format == ARRAY_A) {
1051
- $email['options'] = maybe_unserialize($email['options']);
1052
- if (!is_array($email['options'])) {
1053
- $email['options'] = array();
1054
- }
1055
- if (empty($email['query'])) {
1056
- $email['query'] = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1057
- }
1058
- }
1059
- return $email;
1060
- }
1061
-
1062
- /**
1063
- * Save an email and provide serialization, if needed, of $email['options'].
1064
- * @return TNP_Email
1065
- */
1066
- function save_email($email, $return_format = OBJECT) {
1067
- if (is_object($email)) {
1068
- $email = (array) $email;
1069
- }
1070
-
1071
- if (isset($email['subject'])) {
1072
- if (mb_strlen($email['subject'], 'UTF-8') > 250) {
1073
- $email['subject'] = mb_substr($email['subject'], 0, 250, 'UTF-8');
1074
- }
1075
- }
1076
- if (isset($email['options']) && is_array($email['options'])) {
1077
- $email['options'] = serialize($email['options']);
1078
- }
1079
- $email = $this->store->save(NEWSLETTER_EMAILS_TABLE, $email, $return_format);
1080
- if ($return_format == OBJECT) {
1081
- $email->options = maybe_unserialize($email->options);
1082
- if (!is_array($email->options)) {
1083
- $email->options = [];
1084
- }
1085
- } else if ($return_format == ARRAY_A) {
1086
- $email['options'] = maybe_unserialize($email['options']);
1087
- if (!is_array($email['options'])) {
1088
- $email['options'] = [];
1089
- }
1090
- }
1091
- return $email;
1092
- }
1093
-
1094
- function get_email_from_request() {
1095
-
1096
- if (isset($_REQUEST['nek'])) {
1097
- list($id, $token) = @explode('-', $_REQUEST['nek'], 2);
1098
- } else if (isset($_COOKIE['tnpe'])) {
1099
- list($id, $token) = @explode('-', $_COOKIE['tnpe'], 2);
1100
- } else {
1101
- return null;
1102
- }
1103
-
1104
- $email = $this->get_email($id);
1105
-
1106
- // TODO: Check the token? It's really useful?
1107
-
1108
- return $email;
1109
- }
1110
-
1111
- /**
1112
- * Delete one or more emails identified by ID (single value or array of ID)
1113
- *
1114
- * @global wpdb $wpdb
1115
- * @param int|array $id Single numeric ID or an array of IDs to be deleted
1116
- * @return boolean
1117
- */
1118
- function delete_email($id) {
1119
- global $wpdb;
1120
- $r = $this->store->delete(NEWSLETTER_EMAILS_TABLE, $id);
1121
- if ($r !== false) {
1122
- // $id could be an array if IDs
1123
- $id = (array) $id;
1124
- foreach ($id as $email_id) {
1125
- $wpdb->delete(NEWSLETTER_STATS_TABLE, ['email_id' => $email_id]);
1126
- $wpdb->delete(NEWSLETTER_SENT_TABLE, ['email_id' => $email_id]);
1127
- }
1128
- }
1129
- return $r;
1130
- }
1131
-
1132
- function get_email_field($id, $field_name) {
1133
- return $this->store->get_field(NEWSLETTER_EMAILS_TABLE, $id, $field_name);
1134
- }
1135
-
1136
- function get_email_status_slug($email) {
1137
- $email = (object) $email;
1138
- if ($email->status == 'sending' && $email->send_on > time()) {
1139
- return 'scheduled';
1140
- }
1141
- return $email->status;
1142
- }
1143
-
1144
- function get_email_status_label($email) {
1145
- $email = (object) $email;
1146
- $status = $this->get_email_status_slug($email);
1147
- switch ($status) {
1148
- case 'sending':
1149
- return __('Sending', 'newsletter');
1150
- case 'scheduled':
1151
- return __('Scheduled', 'newsletter');
1152
- case 'sent':
1153
- return __('Sent', 'newsletter');
1154
- case 'paused':
1155
- return __('Paused', 'newsletter');
1156
- case 'new':
1157
- return __('Draft', 'newsletter');
1158
- default:
1159
- return ucfirst($email->status);
1160
- }
1161
- }
1162
-
1163
- function show_email_status_label($email) {
1164
- echo '<span class="tnp-email-status tnp-email-status--', $this->get_email_status_slug($email), '">', esc_html($this->get_email_status_label($email)), '</span>';
1165
- }
1166
-
1167
- function get_email_progress($email, $format = 'percent') {
1168
- return $email->total > 0 ? intval($email->sent / $email->total * 100) : 0;
1169
- }
1170
-
1171
- function show_email_progress_bar($email, $attrs = []) {
1172
-
1173
- $email = (object) $email;
1174
-
1175
- $attrs = array_merge(array('format' => 'percent', 'numbers' => false, 'scheduled' => false), $attrs);
1176
-
1177
- if ($email->status == 'sending' && $email->send_on > time()) {
1178
- if ($attrs['scheduled']) {
1179
- echo '<span class="tnp-progress-date">', $this->format_date($email->send_on), '</span>';
1180
- }
1181
- return;
1182
- } else if ($email->status == 'new') {
1183
- echo '';
1184
- return;
1185
- } else if ($email->status == 'sent') {
1186
- $percent = 100;
1187
- } else {
1188
- $percent = $this->get_email_progress($email);
1189
- }
1190
-
1191
- echo '<div class="tnp-progress tnp-progress--' . $email->status . '">';
1192
- echo '<div class="tnp-progress-bar" role="progressbar" style="width: ', $percent, '%;">&nbsp;', $percent, '%&nbsp;</div>';
1193
- echo '</div>';
1194
- if ($attrs['numbers']) {
1195
- if ($email->status == 'sent') {
1196
- echo '<div class="tnp-progress-numbers">', $email->total, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1197
- } else {
1198
- echo '<div class="tnp-progress-numbers">', $email->sent, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1199
- }
1200
- }
1201
- }
1202
-
1203
- function get_email_type_label($type) {
1204
-
1205
- // Is an email?
1206
- if (is_object($type))
1207
- $type = $type->type;
1208
-
1209
- $label = apply_filters('newsletter_email_type', '', $type);
1210
-
1211
- if (!empty($label))
1212
- return $label;
1213
-
1214
- switch ($type) {
1215
- case 'followup':
1216
- return 'Followup';
1217
- case 'message':
1218
- return 'Standard Newsletter';
1219
- case 'feed':
1220
- return 'Feed by Mail';
1221
- }
1222
-
1223
- if (strpos($type, 'automated') === 0) {
1224
- list($a, $id) = explode('_', $type);
1225
- return 'Automated Channel ' . $id;
1226
- }
1227
-
1228
- return ucfirst($type);
1229
- }
1230
-
1231
- function get_email_progress_label($email) {
1232
- if ($email->status == 'sent' || $email->status == 'sending') {
1233
- return $email->sent . ' ' . __('of', 'newsletter') . ' ' . $email->total;
1234
- }
1235
- return '-';
1236
- }
1237
-
1238
- /**
1239
- * Returns the email unique key
1240
- * @param TNP_User $user
1241
- * @return string
1242
- */
1243
- function get_email_key($email) {
1244
- if (!isset($email->token)) {
1245
- return $email->id . '-';
1246
- }
1247
- return $email->id . '-' . $email->token;
1248
- }
1249
-
1250
- /** Searches for a user using the nk parameter or the ni and nt parameters. Tries even with the newsletter cookie.
1251
- * If found, the user object is returned or null.
1252
- * The user is returned without regards to his status that should be checked by caller.
1253
- *
1254
- * DO NOT REMOVE EVEN IF OLD
1255
- *
1256
- * @return TNP_User
1257
- */
1258
- function check_user($context = '') {
1259
- global $wpdb;
1260
-
1261
- $user = null;
1262
-
1263
- if (isset($_REQUEST['nk'])) {
1264
- list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1265
- } else if (isset($_COOKIE['newsletter'])) {
1266
- list ($id, $token) = @explode('-', $_COOKIE['newsletter'], 2);
1267
- }
1268
-
1269
- if (isset($id)) {
1270
- $user = $this->get_user($id);
1271
- if ($user) {
1272
- if ($context == 'preconfirm') {
1273
- if ($token != md5($user->token)) {
1274
- $user = null;
1275
- }
1276
- } else {
1277
- if ($token != $user->token) {
1278
- $user = null;
1279
- }
1280
- }
1281
- }
1282
- }
1283
-
1284
- if ($user == null && is_user_logged_in()) {
1285
- $user = $this->get_user_by_wp_user_id(get_current_user_id());
1286
- }
1287
- return $user;
1288
- }
1289
-
1290
- /** Returns the user identify by an id or an email. If $id_or_email is an object or an array, it is assumed it contains
1291
- * the "id" attribute or key and that is used to load the user.
1292
- *
1293
- * @global type $wpdb
1294
- * @param string|int|object|array $id_or_email
1295
- * @param string $format
1296
- * @return TNP_User|null
1297
- */
1298
- function get_user($id_or_email, $format = OBJECT) {
1299
- global $wpdb;
1300
-
1301
- if (empty($id_or_email))
1302
- return null;
1303
-
1304
- // To simplify the reaload of a user passing the user it self.
1305
- if (is_object($id_or_email)) {
1306
- $id_or_email = $id_or_email->id;
1307
- } else if (is_array($id_or_email)) {
1308
- $id_or_email = $id_or_email['id'];
1309
- }
1310
-
1311
- $id_or_email = strtolower(trim($id_or_email));
1312
-
1313
- if (is_numeric($id_or_email)) {
1314
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where id=%d limit 1", $id_or_email), $format);
1315
- } else {
1316
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $id_or_email), $format);
1317
- }
1318
-
1319
- if ($wpdb->last_error) {
1320
- $this->logger->error($wpdb->last_error);
1321
- return null;
1322
- }
1323
- return $r;
1324
- }
1325
-
1326
- /**
1327
- *
1328
- * @global wpdb $wpdb
1329
- * @param string $email
1330
- * @return TNP_User
1331
- */
1332
- function get_user_by_email($email) {
1333
- global $wpdb;
1334
-
1335
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $email));
1336
-
1337
- if ($wpdb->last_error) {
1338
- $this->logger->error($wpdb->last_error);
1339
- return null;
1340
- }
1341
- return $r;
1342
- }
1343
-
1344
- /**
1345
- * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
1346
- *
1347
- * @param type $user
1348
- */
1349
- function get_user_edit_url($user) {
1350
- $id = $this->to_int_id($user);
1351
- return admin_url('admin.php') . '?page=newsletter_users_edit&id=' . $id;
1352
- }
1353
-
1354
- /**
1355
- * Returns the user unique key
1356
- * @param TNP_User $user
1357
- * @return string
1358
- */
1359
- function get_user_key($user, $context = '') {
1360
- if (empty($user->token)) {
1361
- $this->refresh_user_token($user);
1362
- }
1363
-
1364
- if ($context == 'preconfirm') {
1365
- return $user->id . '-' . md5($user->token);
1366
- }
1367
- return $user->id . '-' . $user->token;
1368
- }
1369
-
1370
- function get_user_status_label($user, $html = false) {
1371
- if (!$html)
1372
- return TNP_User::get_status_label($user->status);
1373
-
1374
- $label = TNP_User::get_status_label($user->status);
1375
- $class = 'unknown';
1376
- switch ($user->status) {
1377
- case TNP_User::STATUS_NOT_CONFIRMED: $class = 'not-confirmed';
1378
- break;
1379
- case TNP_User::STATUS_CONFIRMED: $class = 'confirmed';
1380
- break;
1381
- case TNP_User::STATUS_UNSUBSCRIBED: $class = 'unsubscribed';
1382
- break;
1383
- case TNP_User::STATUS_BOUNCED: $class = 'bounced';
1384
- break;
1385
- case TNP_User::STATUS_COMPLAINED: $class = 'complained';
1386
- break;
1387
- }
1388
- return '<span class="' . $class . '">' . esc_html($label) . '</span>';
1389
- }
1390
-
1391
- /**
1392
- * Return the user identified by the "nk" parameter (POST or GET).
1393
- * If no user can be found or the token is not matching, returns null.
1394
- * If die_on_fail is true it dies instead of return null.
1395
- *
1396
- * @param bool $die_on_fail
1397
- * @return TNP_User
1398
- */
1399
- function get_user_from_request($die_on_fail = false, $context = '') {
1400
- $id = 0;
1401
- if (isset($_REQUEST['nk'])) {
1402
- list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1403
- }
1404
- $user = $this->get_user($id);
1405
-
1406
- if ($user == null) {
1407
- if ($die_on_fail) {
1408
- die(__('No subscriber found.', 'newsletter'));
1409
- } else {
1410
- return $this->get_user_from_logged_in_user();
1411
- }
1412
- }
1413
-
1414
- if ($context == 'preconfirm') {
1415
- $user_token = md5($user->token);
1416
- } else {
1417
- $user_token = $user->token;
1418
- }
1419
-
1420
- if ($token != $user_token) {
1421
- if ($die_on_fail) {
1422
- die(__('No subscriber found.', 'newsletter'));
1423
- } else {
1424
- return $this->get_user_from_logged_in_user();
1425
- }
1426
- }
1427
- return $user;
1428
- }
1429
-
1430
- function get_user_from_logged_in_user() {
1431
- if (is_user_logged_in()) {
1432
- return $this->get_user_by_wp_user_id(get_current_user_id());
1433
- }
1434
- return null;
1435
- }
1436
-
1437
- function get_user_count($refresh = false) {
1438
- global $wpdb;
1439
- $user_count = get_transient('newsletter_user_count');
1440
- if ($user_count === false || $refresh) {
1441
- $user_count = $wpdb->get_var("select count(*) from " . NEWSLETTER_USERS_TABLE . " where status='C'");
1442
- set_transient('newsletter_user_count', $user_count, DAY_IN_SECONDS);
1443
- }
1444
- return $user_count;
1445
- }
1446
-
1447
- function get_profile($id, $language = '') {
1448
- return TNP_Profile_Service::get_profile_by_id($id, $language);
1449
- }
1450
-
1451
- /**
1452
- * @param string $language The language for the list labels (it does not affect the lists returned)
1453
- * @return TNP_Profile[]
1454
- */
1455
- function get_profiles($language = '') {
1456
- return TNP_Profile_Service::get_profiles($language);
1457
- }
1458
-
1459
- /**
1460
- * Returns a list of TNP_Profile which are public.
1461
- *
1462
- * @staticvar array $profiles
1463
- * @param string $language
1464
- * @return TNP_Profile[]
1465
- */
1466
- function get_profiles_public($language = '') {
1467
- static $profiles = [];
1468
- if (isset($profiles[$language])) {
1469
- return $profiles[$language];
1470
- }
1471
-
1472
- $profiles[$language] = [];
1473
- $all = $this->get_profiles($language);
1474
- foreach ($all as $profile) {
1475
- if ($profile->is_private())
1476
- continue;
1477
-
1478
- $profiles[$language]['' . $profile->id] = $profile;
1479
- }
1480
- return $profiles[$language];
1481
- }
1482
-
1483
- /**
1484
- * Really bad name!
1485
- * @staticvar array $profiles
1486
- * @param type $language
1487
- * @return array
1488
- */
1489
- function get_profiles_for_profile($language = '') {
1490
- static $profiles = [];
1491
- if (isset($profiles[$language])) {
1492
- return $profiles[$language];
1493
- }
1494
-
1495
- $profiles[$language] = [];
1496
- $all = $this->get_profiles($language);
1497
- foreach ($all as $profile) {
1498
- if (!$profile->show_on_profile())
1499
- continue;
1500
-
1501
- $profiles[$language]['' . $profile->id] = $profile;
1502
- }
1503
- return $profiles[$language];
1504
- }
1505
-
1506
- /**
1507
- * @param string $language The language for the list labels (it does not affect the lists returned)
1508
- * @return TNP_List[]
1509
- */
1510
- function get_lists($language = '') {
1511
- static $lists = array();
1512
- if (isset($lists[$language])) {
1513
- return $lists[$language];
1514
- }
1515
-
1516
- $lists[$language] = array();
1517
- $data = NewsletterSubscription::instance()->get_options('lists', $language);
1518
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
1519
- if (empty($data['list_' . $i])) {
1520
- continue;
1521
- }
1522
- $list = $this->create_tnp_list_from_db_lists_array($data, $i);
1523
-
1524
- $lists[$language]['' . $list->id] = $list;
1525
- }
1526
- return $lists[$language];
1527
- }
1528
-
1529
- public function create_tnp_list_from_db_lists_array($db_lists_array, $list_id) {
1530
-
1531
- $list = new TNP_List();
1532
- $list->name = $db_lists_array['list_' . $list_id];
1533
- $list->id = $list_id;
1534
-
1535
- // New format
1536
- if (isset($db_lists_array['list_' . $list_id . '_subscription'])) {
1537
- $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1538
- $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1539
- $list->checked = $db_lists_array['list_' . $list_id . '_subscription'] == 2;
1540
- $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_subscription']) && !$list->forced;
1541
- $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_profile']);
1542
- } else {
1543
- $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1544
- $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1545
- $list->checked = !empty($db_lists_array['list_' . $list_id . '_checked']);
1546
- $list->show_on_subscription = $db_lists_array['list_' . $list_id . '_status'] == 2 && !$list->forced;
1547
- $list->show_on_profile = $db_lists_array['list_' . $list_id . '_status'] == 1 || $db_lists_array['list_' . $list_id . '_status'] == 2;
1548
- }
1549
- if (empty($db_lists_array['list_' . $list_id . '_languages'])) {
1550
- $list->languages = array();
1551
- } else {
1552
- $list->languages = $db_lists_array['list_' . $list_id . '_languages'];
1553
- }
1554
-
1555
- return $list;
1556
- }
1557
-
1558
- /**
1559
- * Returns an array of TNP_List objects of lists that are public.
1560
- * @return TNP_List[]
1561
- */
1562
- function get_lists_public($language = '') {
1563
- static $lists = array();
1564
- if (isset($lists[$language])) {
1565
- return $lists[$language];
1566
- }
1567
-
1568
- $lists[$language] = array();
1569
- $all = $this->get_lists($language);
1570
- foreach ($all as $list) {
1571
- if ($list->status == TNP_List::STATUS_PRIVATE) {
1572
- continue;
1573
- }
1574
- $lists[$language]['' . $list->id] = $list;
1575
- }
1576
- return $lists[$language];
1577
- }
1578
-
1579
- /**
1580
- * Lists to be shown on subscription form.
1581
- *
1582
- * @return TNP_List[]
1583
- */
1584
- function get_lists_for_subscription($language = '') {
1585
- static $lists = array();
1586
- if (isset($lists[$language])) {
1587
- return $lists[$language];
1588
- }
1589
-
1590
- $lists[$language] = array();
1591
- $all = $this->get_lists($language);
1592
- foreach ($all as $list) {
1593
- if (!$list->show_on_subscription) {
1594
- continue;
1595
- }
1596
- $lists[$language]['' . $list->id] = $list;
1597
- }
1598
- return $lists[$language];
1599
- }
1600
-
1601
- /**
1602
- * Returns the lists to be shown in the profile page. The list is associative with
1603
- * the list ID as key.
1604
- *
1605
- * @return TNP_List[]
1606
- */
1607
- function get_lists_for_profile($language = '') {
1608
- static $lists = array();
1609
- if (isset($lists[$language])) {
1610
- return $lists[$language];
1611
- }
1612
-
1613
- $lists[$language] = array();
1614
- $all = $this->get_lists($language);
1615
- foreach ($all as $list) {
1616
- if (!$list->show_on_profile) {
1617
- continue;
1618
- }
1619
- $lists[$language]['' . $list->id] = $list;
1620
- }
1621
- return $lists[$language];
1622
- }
1623
-
1624
- /**
1625
- * Returns the list object or null if not found.
1626
- *
1627
- * @param int $id
1628
- * @return TNP_List
1629
- */
1630
- function get_list($id, $language = '') {
1631
- $lists = $this->get_lists($language);
1632
- if (!isset($lists['' . $id])) {
1633
- return null;
1634
- }
1635
-
1636
- return $lists['' . $id];
1637
- }
1638
-
1639
- /**
1640
- * NEVER CHANGE THIS METHOD SIGNATURE, USER BY THIRD PARTY PLUGINS.
1641
- *
1642
- * Saves a new user on the database. Return false if the email (that must be unique) is already
1643
- * there. For a new users set the token and creation time if not passed.
1644
- *
1645
- * @param array $user
1646
- * @return TNP_User|array|boolean Returns the subscriber reloaded from DB in the specified format. Flase on failure (duplicate email).
1647
- */
1648
- function save_user($user, $return_format = OBJECT) {
1649
- if (is_object($user)) {
1650
- $user = (array) $user;
1651
- }
1652
- if (empty($user['id'])) {
1653
- $existing = $this->get_user($user['email']);
1654
- if ($existing != null) {
1655
- return false;
1656
- }
1657
- if (empty($user['token'])) {
1658
- $user['token'] = NewsletterModule::get_token();
1659
- }
1660
- }
1661
-
1662
- // We still don't know when it happens but under some conditions, matbe external, lists are passed as NULL
1663
- foreach ($user as $key => $value) {
1664
- if (strpos($key, 'list_') !== 0) {
1665
- continue;
1666
- }
1667
- if (is_null($value)) {
1668
- unset($user[$key]);
1669
- } else {
1670
- $user[$key] = (int) $value;
1671
- }
1672
- }
1673
-
1674
- // Due to the unique index on email field, this can fail.
1675
- return $this->store->save(NEWSLETTER_USERS_TABLE, $user, $return_format);
1676
- }
1677
-
1678
- /**
1679
- * Updates the user last activity timestamp.
1680
- *
1681
- * @global wpdb $wpdb
1682
- * @param TNP_User $user
1683
- */
1684
- function update_user_last_activity($user) {
1685
- global $wpdb;
1686
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set last_activity=%d where id=%d limit 1", time(), $user->id));
1687
- }
1688
-
1689
- function update_user_ip($user, $ip) {
1690
- global $wpdb;
1691
- // Only if changed
1692
- $r = $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set ip=%s, geo=0 where ip<>%s and id=%d limit 1", $ip, $ip, $user->id));
1693
- }
1694
-
1695
- /**
1696
- * Finds single style blocks and adds a style attribute to every HTML tag with a class exactly matching the rules in the style
1697
- * block. HTML tags can use the attribute "inline-class" to exact match a style rules if they need a composite class definition.
1698
- *
1699
- * @param string $content
1700
- * @param boolean $strip_style_blocks
1701
- * @return string
1702
- */
1703
- function inline_css($content, $strip_style_blocks = false) {
1704
- $matches = array();
1705
- // "s" skips line breaks
1706
- $styles = preg_match('|<style>(.*?)</style>|s', $content, $matches);
1707
- if (isset($matches[1])) {
1708
- $style = str_replace(array("\n", "\r"), '', $matches[1]);
1709
- $rules = array();
1710
- preg_match_all('|\s*\.(.*?)\{(.*?)\}\s*|s', $style, $rules);
1711
- for ($i = 0; $i < count($rules[1]); $i++) {
1712
- $class = trim($rules[1][$i]);
1713
- $value = trim($rules[2][$i]);
1714
- $value = preg_replace('|\s+|', ' ', $value);
1715
- $content = str_replace(' class="' . $class . '"', ' class="' . $class . '" style="' . $value . '"', $content);
1716
- $content = str_replace(' inline-class="' . $class . '"', ' style="' . $value . '"', $content);
1717
- }
1718
- }
1719
-
1720
- if ($strip_style_blocks) {
1721
- return trim(preg_replace('|<style>.*?</style>|s', '', $content));
1722
- } else {
1723
- return $content;
1724
- }
1725
- }
1726
-
1727
- /**
1728
- * Returns a list of users marked as "test user".
1729
- * @return TNP_User[]
1730
- */
1731
- function get_test_users() {
1732
- return $this->store->get_all(NEWSLETTER_USERS_TABLE, "where test=1 and status in ('C', 'S')");
1733
- }
1734
-
1735
- /**
1736
- * Deletes a subscriber and cleans up all the stats table with his correlated data.
1737
- *
1738
- * @global wpdb $wpdb
1739
- * @param int|id[] $id
1740
- */
1741
- function delete_user($id) {
1742
- global $wpdb;
1743
- $id = (array) $id;
1744
- foreach ($id as $user_id) {
1745
- $user = $this->get_user($user_id);
1746
- if ($user) {
1747
- $r = $this->store->delete(NEWSLETTER_USERS_TABLE, $user_id);
1748
- $wpdb->delete(NEWSLETTER_STATS_TABLE, array('user_id' => $user_id));
1749
- $wpdb->delete(NEWSLETTER_SENT_TABLE, array('user_id' => $user_id));
1750
- do_action('newsletter_user_deleted', $user);
1751
- }
1752
- }
1753
-
1754
- return count($id);
1755
- }
1756
-
1757
- /**
1758
- * Add to a destination URL the parameters to identify the user, the email and to show
1759
- * an alert message, if required. The parameters are then managed by the [newsletter] shortcode.
1760
- *
1761
- * @param string $url If empty the standard newsletter page URL is used (usually it is empty, but sometime a custom URL has been specified)
1762
- * @param string $message_key The message identifier
1763
- * @param TNP_User|int $user
1764
- * @param TNP_Email|int $email
1765
- * @param string $alert An optional alter message to be shown. Does not work with custom URLs
1766
- * @return string The final URL with parameters
1767
- */
1768
- function build_message_url($url = '', $message_key = '', $user = null, $email = null, $alert = '') {
1769
- $params = 'nm=' . urlencode($message_key);
1770
- $language = '';
1771
- if ($user) {
1772
- if (!is_object($user)) {
1773
- $user = $this->get_user($user);
1774
- }
1775
- if ($message_key == 'confirmation') {
1776
- $params .= '&nk=' . urlencode($this->get_user_key($user, 'preconfirm'));
1777
- } else {
1778
- $params .= '&nk=' . urlencode($this->get_user_key($user));
1779
- }
1780
-
1781
- $language = $this->get_user_language($user);
1782
- }
1783
-
1784
- if ($email) {
1785
- if (!is_object($email)) {
1786
- $email = $this->get_email($email);
1787
- }
1788
- $params .= '&nek=' . urlencode($this->get_email_key($email));
1789
- }
1790
-
1791
- if ($alert) {
1792
- $params .= '&alert=' . urlencode($alert);
1793
- }
1794
-
1795
- if (empty($url)) {
1796
- $url = Newsletter::instance()->get_newsletter_page_url($language);
1797
- }
1798
-
1799
- return self::add_qs($url, $params, false);
1800
- }
1801
-
1802
- /**
1803
- * Builds a standard Newsletter action URL for the specified action.
1804
- *
1805
- * @param string $action
1806
- * @param TNP_User $user
1807
- * @param TNP_Email $email
1808
- * @return string
1809
- */
1810
- function build_action_url($action, $user = null, $email = null) {
1811
- $url = $this->add_qs($this->get_home_url(), 'na=' . urlencode($action));
1812
- //$url = $this->add_qs(admin_url('admin-ajax.php'), 'action=newsletter&na=' . urlencode($action));
1813
- if ($user) {
1814
- $url .= '&nk=' . urlencode($this->get_user_key($user));
1815
- }
1816
- if ($email) {
1817
- $url .= '&nek=' . urlencode($this->get_email_key($email));
1818
- }
1819
- return $url;
1820
- }
1821
-
1822
- function get_subscribe_url() {
1823
- return $this->build_action_url('s');
1824
- }
1825
-
1826
- function clean_stats_table() {
1827
- global $wpdb;
1828
- $this->logger->info('Cleaning up stats table');
1829
- $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
1830
- $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
1831
- }
1832
-
1833
- function clean_sent_table() {
1834
- global $wpdb;
1835
- $this->logger->info('Cleaning up sent table');
1836
- $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
1837
- $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
1838
- }
1839
-
1840
- function clean_user_logs_table() {
1841
- //global $wpdb;
1842
- }
1843
-
1844
- function clean_tables() {
1845
- $this->clean_sent_table();
1846
- $this->clean_stats_table();
1847
- $this->clean_user_logs_table();
1848
- }
1849
-
1850
- function anonymize_ip($ip) {
1851
- if (empty($ip)) {
1852
- return $ip;
1853
- }
1854
- $parts = explode('.', $ip);
1855
- array_pop($parts);
1856
- return implode('.', $parts) . '.0';
1857
- }
1858
-
1859
- function process_ip($ip) {
1860
-
1861
- $option = Newsletter::instance()->options['ip'];
1862
- if (empty($option)) {
1863
- return $ip;
1864
- }
1865
- if ($option == 'anonymize') {
1866
- return $this->anonymize_ip($ip);
1867
- }
1868
- return '';
1869
- }
1870
-
1871
- function anonymize_user($id) {
1872
- global $wpdb;
1873
- $user = $this->get_user($id);
1874
- if (!$user) {
1875
- return null;
1876
- }
1877
-
1878
- $user->name = '';
1879
- $user->surname = '';
1880
- $user->ip = $this->anonymize_ip($user->ip);
1881
-
1882
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1883
- $field = 'profile_' . $i;
1884
- $user->$field = '';
1885
- }
1886
-
1887
- // [TODO] Status?
1888
- $user->status = TNP_User::STATUS_UNSUBSCRIBED;
1889
- $user->email = $user->id . '@anonymi.zed';
1890
-
1891
- $user = $this->save_user($user);
1892
-
1893
- return $user;
1894
- }
1895
-
1896
- /**
1897
- * Changes a user status. Accept a user object, user id or user email.
1898
- *
1899
- * @param TNP_User $user
1900
- * @param string $status
1901
- * @return TNP_User
1902
- */
1903
- function set_user_status($user, $status) {
1904
- global $wpdb;
1905
-
1906
- $this->logger->debug('Status change to ' . $status . ' of subscriber ' . $user->id . ' from ' . $_SERVER['REQUEST_URI']);
1907
-
1908
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set status=%s where id=%d limit 1", $status, $user->id));
1909
- $user->status = $status;
1910
- return $this->get_user($user);
1911
- }
1912
-
1913
- /**
1914
- *
1915
- * @global wpdb $wpdb
1916
- * @param TNP_User $user
1917
- * @return TNP_User
1918
- */
1919
- function refresh_user_token($user) {
1920
- global $wpdb;
1921
-
1922
- $token = $this->get_token();
1923
-
1924
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set token=%s where id=%d limit 1", $token, $user->id));
1925
- $user->token = $token;
1926
- }
1927
-
1928
- /**
1929
- * Create a log entry with the meaningful user data.
1930
- *
1931
- * @global wpdb $wpdb
1932
- * @param TNP_User $user
1933
- * @param string $source
1934
- * @return type
1935
- */
1936
- function add_user_log($user, $source = '') {
1937
- global $wpdb;
1938
-
1939
- $lists = $this->get_lists_public();
1940
- foreach ($lists as $list) {
1941
- $field_name = 'list_' . $list->id;
1942
- $data[$field_name] = $user->$field_name;
1943
- }
1944
- $data['status'] = $user->status;
1945
- $ip = $this->get_remote_ip();
1946
- $ip = $this->process_ip($ip);
1947
- $this->store->save($wpdb->prefix . 'newsletter_user_logs', array('ip' => $ip, 'user_id' => $user->id, 'source' => $source, 'created' => time(), 'data' => json_encode($data)));
1948
- }
1949
-
1950
- /**
1951
- *
1952
- * @global wpdb $wpdb
1953
- * @param TNP_User $user
1954
- * @param int $list
1955
- * @param type $value
1956
- */
1957
- function set_user_list($user, $list, $value) {
1958
- global $wpdb;
1959
-
1960
- $list = (int) $list;
1961
- $value = $value ? 1 : 0;
1962
- $r = $wpdb->update(NEWSLETTER_USERS_TABLE, array('list_' . $list => $value), array('id' => $user->id));
1963
- }
1964
-
1965
- function set_user_field($id, $field, $value) {
1966
- $this->store->set_field(NEWSLETTER_USERS_TABLE, $id, $field, $value);
1967
- }
1968
-
1969
- function set_user_wp_user_id($user_id, $wp_user_id) {
1970
- $this->store->set_field(NEWSLETTER_USERS_TABLE, $user_id, 'wp_user_id', $wp_user_id);
1971
- }
1972
-
1973
- /**
1974
- *
1975
- * @param int $wp_user_id
1976
- * @param string $format
1977
- * @return TNP_User
1978
- */
1979
- function get_user_by_wp_user_id($wp_user_id, $format = OBJECT) {
1980
- return $this->store->get_single_by_field(NEWSLETTER_USERS_TABLE, 'wp_user_id', $wp_user_id, $format);
1981
- }
1982
-
1983
- /**
1984
- * Returns the user language IF there is a supported mutilanguage plugin installed.
1985
- * @param TNP_User $user
1986
- * @return string Language code or empty
1987
- */
1988
- function get_user_language($user) {
1989
- if ($user && $this->is_multilanguage()) {
1990
- return $user->language;
1991
- }
1992
- return '';
1993
- }
1994
-
1995
- /**
1996
- * Replaces every possible Newsletter tag ({...}) in a piece of text or HTML.
1997
- *
1998
- * @global wpdb $wpdb
1999
- * @param string $text
2000
- * @param mixed $user Can be an object, associative array or id
2001
- * @param mixed $email Can be an object, associative array or id
2002
- * @param type $referrer
2003
- * @return type
2004
- */
2005
- function replace($text, $user = null, $email = null, $referrer = null) {
2006
- global $wpdb;
2007
-
2008
- if (strpos($text, '<p') !== false) {
2009
- $esc_html = true;
2010
- } else {
2011
- $esc_html = false;
2012
- }
2013
-
2014
- static $home_url = false;
2015
-
2016
- if (!$home_url) {
2017
- $home_url = home_url('/');
2018
- }
2019
-
2020
- //$this->logger->debug('Replace start');
2021
- if ($user !== null && !is_object($user)) {
2022
- if (is_array($user)) {
2023
- $user = (object) $user;
2024
- } else if (is_numeric($user)) {
2025
- $user = $this->get_user($user);
2026
- } else {
2027
- $user = null;
2028
- }
2029
- }
2030
-
2031
- if ($email !== null && !is_object($email)) {
2032
- if (is_array($email)) {
2033
- $email = (object) $email;
2034
- } else if (is_numeric($email)) {
2035
- $email = $this->get_email($email);
2036
- } else {
2037
- $email = null;
2038
- }
2039
- }
2040
-
2041
- $initial_language = $this->get_current_language();
2042
-
2043
- if ($user && $user->language) {
2044
- $this->switch_language($user->language);
2045
- }
2046
-
2047
-
2048
- $text = apply_filters('newsletter_replace', $text, $user, $email, $esc_html);
2049
-
2050
- $text = $this->replace_url($text, 'blog_url', $home_url);
2051
- $text = $this->replace_url($text, 'home_url', $home_url);
2052
-
2053
- $text = str_replace('{blog_title}', html_entity_decode(get_bloginfo('name')), $text);
2054
- $text = str_replace('{blog_description}', get_option('blogdescription'), $text);
2055
-
2056
- $text = $this->replace_date($text);
2057
-
2058
- if ($user) {
2059
- //$this->logger->debug('Replace with user ' . $user->id);
2060
- $nk = $this->get_user_key($user);
2061
- $options_profile = NewsletterSubscription::instance()->get_options('profile', $this->get_user_language($user));
2062
- $text = str_replace('{email}', $user->email, $text);
2063
- $name = apply_filters('newsletter_replace_name', $user->name, $user);
2064
- if (empty($name)) {
2065
- $text = str_replace(' {name}', '', $text);
2066
- $text = str_replace('{name}', '', $text);
2067
- } else {
2068
- $text = str_replace('{name}', esc_html($name), $text);
2069
- }
2070
-
2071
- switch ($user->sex) {
2072
- case 'm': $text = str_replace('{title}', $options_profile['title_male'], $text);
2073
- break;
2074
- case 'f': $text = str_replace('{title}', $options_profile['title_female'], $text);
2075
- break;
2076
- //case 'n': $text = str_replace('{title}', $options_profile['title_none'], $text);
2077
- // break;
2078
- default:
2079
- $text = str_replace('{title}', $options_profile['title_none'], $text);
2080
- //$text = str_replace('{title}', '', $text);
2081
- }
2082
-
2083
-
2084
- // Deprecated
2085
- $text = str_replace('{surname}', esc_html($user->surname), $text);
2086
- $text = str_replace('{last_name}', esc_html($user->surname), $text);
2087
-
2088
- $full_name = esc_html(trim($user->name . ' ' . $user->surname));
2089
- if (empty($full_name)) {
2090
- $text = str_replace(' {full_name}', '', $text);
2091
- $text = str_replace('{full_name}', '', $text);
2092
- } else {
2093
- $text = str_replace('{full_name}', $full_name, $text);
2094
- }
2095
-
2096
- $text = str_replace('{token}', $user->token, $text);
2097
- $text = str_replace('%7Btoken%7D', $user->token, $text);
2098
- $text = str_replace('{id}', $user->id, $text);
2099
- $text = str_replace('%7Bid%7D', $user->id, $text);
2100
- $text = str_replace('{ip}', $user->ip, $text);
2101
- $text = str_replace('{key}', $nk, $text);
2102
- $text = str_replace('%7Bkey%7D', $nk, $text);
2103
-
2104
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
2105
- $p = 'profile_' . $i;
2106
- $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
2107
- }
2108
-
2109
- $base = (empty($this->options_main['url']) ? get_option('home') : $this->options_main['url']);
2110
- $id_token = '&amp;ni=' . $user->id . '&amp;nt=' . $user->token;
2111
-
2112
- $text = $this->replace_url($text, 'subscription_confirm_url', $this->build_action_url('c', $user));
2113
- $text = $this->replace_url($text, 'activation_url', $this->build_action_url('c', $user));
2114
-
2115
- // Obsolete.
2116
- $text = $this->replace_url($text, 'FOLLOWUP_SUBSCRIPTION_URL', self::add_qs($base, 'nm=fs' . $id_token));
2117
- $text = $this->replace_url($text, 'FOLLOWUP_UNSUBSCRIPTION_URL', self::add_qs($base, 'nm=fu' . $id_token));
2118
-
2119
- $text = $this->replace_url($text, 'UNLOCK_URL', $this->build_action_url('ul', $user));
2120
- } else {
2121
- //$this->logger->debug('Replace without user');
2122
- $text = $this->replace_url($text, 'subscription_confirm_url', '#');
2123
- $text = $this->replace_url($text, 'activation_url', '#');
2124
- }
2125
-
2126
- if ($email) {
2127
- //$this->logger->debug('Replace with email ' . $email->id);
2128
- $nek = $this->get_email_key($email);
2129
- $text = str_replace('{email_id}', $email->id, $text);
2130
- $text = str_replace('{email_key}', $nek, $text);
2131
- $text = str_replace('{email_subject}', $email->subject, $text);
2132
- // Deprecated
2133
- $text = str_replace('{subject}', $email->subject, $text);
2134
- $text = $this->replace_url($text, 'email_url', $this->build_action_url('v', $user) . '&id=' . $email->id);
2135
- } else {
2136
- //$this->logger->debug('Replace without email');
2137
- $text = $this->replace_url($text, 'email_url', '#');
2138
- }
2139
-
2140
- if (strpos($text, '{subscription_form}') !== false) {
2141
- $text = str_replace('{subscription_form}', NewsletterSubscription::instance()->get_subscription_form($referrer), $text);
2142
- } else {
2143
- for ($i = 1; $i <= 10; $i++) {
2144
- if (strpos($text, "{subscription_form_$i}") !== false) {
2145
- $text = str_replace("{subscription_form_$i}", NewsletterSubscription::instance()->get_form($i), $text);
2146
- break;
2147
- }
2148
- }
2149
- }
2150
-
2151
- // Company info
2152
- // TODO: Move to another module
2153
- $options = Newsletter::instance()->get_options('info');
2154
- $text = str_replace('{company_address}', $options['footer_contact'], $text);
2155
- $text = str_replace('{company_name}', $options['footer_title'], $text);
2156
- $text = str_replace('{company_legal}', $options['footer_legal'], $text);
2157
-
2158
- $this->switch_language($initial_language);
2159
- //$this->logger->debug('Replace end');
2160
- return $text;
2161
- }
2162
-
2163
- function replace_date($text) {
2164
- $text = str_replace('{date}', date_i18n(get_option('date_format')), $text);
2165
-
2166
- // Date processing
2167
- $x = 0;
2168
- while (($x = strpos($text, '{date_', $x)) !== false) {
2169
- $y = strpos($text, '}', $x);
2170
- if ($y === false)
2171
- continue;
2172
- $f = substr($text, $x + 6, $y - $x - 6);
2173
- $text = substr($text, 0, $x) . date_i18n($f) . substr($text, $y + 1);
2174
- }
2175
- return $text;
2176
- }
2177
-
2178
- function replace_url($text, $tag, $url) {
2179
- static $home = false;
2180
- if (!$home) {
2181
- $home = trailingslashit(home_url());
2182
- }
2183
- $tag_lower = strtolower($tag);
2184
- $text = str_replace('http://{' . $tag_lower . '}', $url, $text);
2185
- $text = str_replace('https://{' . $tag_lower . '}', $url, $text);
2186
- $text = str_replace($home . '{' . $tag_lower . '}', $url, $text);
2187
- $text = str_replace($home . '%7B' . $tag_lower . '%7D', $url, $text);
2188
- $text = str_replace('{' . $tag_lower . '}', $url, $text);
2189
- $text = str_replace('%7B' . $tag_lower . '%7D', $url, $text);
2190
-
2191
- $url_encoded = urlencode($url);
2192
- $text = str_replace('%7B' . $tag_lower . '_encoded%7D', $url_encoded, $text);
2193
- $text = str_replace('{' . $tag_lower . '_encoded}', $url_encoded, $text);
2194
-
2195
- // for compatibility
2196
- $text = str_replace($home . $tag, $url, $text);
2197
-
2198
- return $text;
2199
- }
2200
-
2201
- public static function antibot_form_check($captcha = false) {
2202
-
2203
- if (defined('NEWSLETTER_ANTIBOT') && !NEWSLETTER_ANTIBOT) {
2204
- return true;
2205
- }
2206
-
2207
- if (strtolower($_SERVER['REQUEST_METHOD']) != 'post') {
2208
- return false;
2209
- }
2210
-
2211
- if (!isset($_POST['ts']) || time() - $_POST['ts'] > 60) {
2212
- return false;
2213
- }
2214
-
2215
- if ($captcha) {
2216
- $n1 = (int) $_POST['n1'];
2217
- if (empty($n1)) {
2218
- return false;
2219
- }
2220
- $n2 = (int) $_POST['n2'];
2221
- if (empty($n2)) {
2222
- return false;
2223
- }
2224
- $n3 = (int) $_POST['n3'];
2225
- if ($n1 + $n2 != $n3) {
2226
- return false;
2227
- }
2228
- }
2229
-
2230
- return true;
2231
- }
2232
-
2233
- public static function request_to_antibot_form($submit_label = 'Continue...', $captcha = false) {
2234
- header('Content-Type: text/html;charset=UTF-8');
2235
- header('X-Robots-Tag: noindex,nofollow,noarchive');
2236
- header('Cache-Control: no-cache,no-store,private');
2237
- echo "<!DOCTYPE html>\n";
2238
- echo '<html><head>'
2239
- . '<style type="text/css">'
2240
- . '.tnp-captcha {text-align: center; margin: 200px auto 0 auto !important; max-width: 300px !important; padding: 10px !important; font-family: "Open Sans", sans-serif; background: #ECF0F1; border-radius: 5px; padding: 50px !important; border: none !important;}'
2241
- . 'p {text-align: center; padding: 10px; color: #7F8C8D;}'
2242
- . 'input[type=text] {width: 50px; padding: 10px 10px; border: none; border-radius: 2px; margin: 0px 5px;}'
2243
- . 'input[type=submit] {text-align: center; border: none; padding: 10px 15px; font-family: "Open Sans", sans-serif; background-color: #27AE60; color: white; cursor: pointer;}'
2244
- . '</style>'
2245
- . '</head><body>';
2246
- echo '<form method="post" action="https://www.domain.tld" id="form">';
2247
- echo '<div style="width: 1px; height: 1px; overflow: hidden">';
2248
- foreach ($_REQUEST as $name => $value) {
2249
- if ($name == 'submit')
2250
- continue;
2251
- if (is_array($value)) {
2252
- foreach ($value as $element) {
2253
- echo '<input type="text" name="';
2254
- echo esc_attr($name);
2255
- echo '[]" value="';
2256
- echo esc_attr(stripslashes($element));
2257
- echo '">';
2258
- }
2259
- } else {
2260
- echo '<input type="hidden" name="', esc_attr($name), '" value="', esc_attr(stripslashes($value)), '">';
2261
- }
2262
- }
2263
- if (isset($_SERVER['HTTP_REFERER'])) {
2264
- echo '<input type="hidden" name="nhr" value="' . esc_attr($_SERVER['HTTP_REFERER']) . '">';
2265
- }
2266
- echo '<input type="hidden" name="ts" value="' . time() . '">';
2267
- echo '</div>';
2268
-
2269
- if ($captcha) {
2270
- echo '<div class="tnp-captcha">';
2271
- echo '<p>', __('Math question', 'newsletter'), '</p>';
2272
- echo '<input type="text" name="n1" value="', rand(1, 9), '" readonly style="width: 50px">';
2273
- echo '+';
2274
- echo '<input type="text" name="n2" value="', rand(1, 9), '" readonly style="width: 50px">';
2275
- echo '=';
2276
- echo '<input type="text" name="n3" value="?" style="width: 50px">';
2277
- echo '<br><br>';
2278
- echo '<input type="submit" value="', esc_attr($submit_label), '">';
2279
- echo '</div>';
2280
- }
2281
- echo '<noscript><input type="submit" value="';
2282
- echo esc_attr($submit_label);
2283
- echo '"></noscript></form>';
2284
- echo '<script>';
2285
- echo 'document.getElementById("form").action="' . home_url('/') . '";';
2286
- if (!$captcha) {
2287
- echo 'document.getElementById("form").submit();';
2288
- }
2289
- echo '</script>';
2290
- echo '</body></html>';
2291
- die();
2292
- }
2293
-
2294
- static function extract_body($html) {
2295
- $x = stripos($html, '<body');
2296
- if ($x !== false) {
2297
- $x = strpos($html, '>', $x);
2298
- $y = strpos($html, '</body>');
2299
- return substr($html, $x + 1, $y - $x - 1);
2300
- } else {
2301
- return $html;
2302
- }
2303
- }
2304
-
2305
- /** Returns a percentage as string */
2306
- static function percent($value, $total) {
2307
- if ($total == 0)
2308
- return '-';
2309
- return sprintf("%.2f", $value / $total * 100) . '%';
2310
- }
2311
-
2312
- /** Returns a percentage as integer value */
2313
- static function percentValue($value, $total) {
2314
- if ($total == 0)
2315
- return 0;
2316
- return round($value / $total * 100);
2317
- }
2318
-
2319
- /**
2320
- * Takes in a variable and checks if object, array or scalar and return the integer representing
2321
- * a database record id.
2322
- *
2323
- * @param mixed $var
2324
- * @return in
2325
- */
2326
- static function to_int_id($var) {
2327
- if (is_object($var)) {
2328
- return (int) $var->id;
2329
- }
2330
- if (is_array($var)) {
2331
- return (int) $var['id'];
2332
- }
2333
- return (int) $var;
2334
- }
2335
-
2336
- static function to_array($text) {
2337
- $text = trim($text);
2338
- if (empty($text)) {
2339
- return array();
2340
- }
2341
- $text = preg_split("/\\r\\n/", $text);
2342
- $text = array_map('trim', $text);
2343
- $text = array_map('strtolower', $text);
2344
- $text = array_filter($text);
2345
-
2346
- return $text;
2347
- }
2348
-
2349
- static function sanitize_ip($ip) {
2350
- if (empty($ip)) {
2351
- return '';
2352
- }
2353
- $ip = preg_replace('/[^0-9a-fA-F:., ]/', '', trim($ip));
2354
- if (strlen($ip) > 50)
2355
- $ip = substr($ip, 0, 50);
2356
-
2357
- // When more than one IP is present due to firewalls, proxies, and so on. The first one should be the origin.
2358
- if (strpos($ip, ',') !== false) {
2359
- list($ip, $tail) = explode(',', $ip, 2);
2360
- }
2361
- return $ip;
2362
- }
2363
-
2364
- static function get_remote_ip() {
2365
- $ip = '';
2366
- if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2367
- $ip = $_SERVER['HTTP_X_REAL_IP'];
2368
- } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2369
- $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
2370
- } elseif (isset($_SERVER['REMOTE_ADDR'])) {
2371
- $ip = $_SERVER['REMOTE_ADDR'];
2372
- }
2373
- return self::sanitize_ip($ip);
2374
- }
2375
-
2376
- static function get_signature($text) {
2377
- $key = NewsletterStatistics::instance()->options['key'];
2378
- return md5($text . $key);
2379
- }
2380
-
2381
- static function check_signature($text, $signature) {
2382
- if (empty($signature)) {
2383
- return false;
2384
- }
2385
- $key = NewsletterStatistics::instance()->options['key'];
2386
- return md5($text . $key) === $signature;
2387
- }
2388
-
2389
- static function get_home_url() {
2390
- static $url = false;
2391
- if (!$url) {
2392
- $url = home_url('/');
2393
- }
2394
- return $url;
2395
- }
2396
-
2397
- static function clean_eol($text) {
2398
- $text = str_replace("\r\n", "\n", $text);
2399
- $text = str_replace("\r", "\n", $text);
2400
- $text = str_replace("\n", "\r\n", $text);
2401
- return $text;
2402
- }
2403
-
2404
- function set_current_language($language) {
2405
- self::$current_language = $language;
2406
- }
2407
-
2408
- /**
2409
- * Return the current language code. Optionally, if a user is passed and it has a language
2410
- * the user language is returned.
2411
- * If there is no language available, an empty string is returned.
2412
- *
2413
- * @param TNP_User $user
2414
- * @return string The language code
2415
- */
2416
- function get_current_language($user = null) {
2417
-
2418
- if ($user && $user->language) {
2419
- return $user->language;
2420
- }
2421
-
2422
- if (!empty(self::$current_language)) {
2423
- return self::$current_language;
2424
- }
2425
-
2426
- // WPML
2427
- if (class_exists('SitePress')) {
2428
- $current_language = apply_filters('wpml_current_language', '');
2429
- if ($current_language == 'all') {
2430
- $current_language = '';
2431
- }
2432
- return $current_language;
2433
- }
2434
-
2435
- // Polylang
2436
- if (function_exists('pll_current_language')) {
2437
- return pll_current_language();
2438
- }
2439
-
2440
- // Trnslatepress and/or others
2441
- $current_language = apply_filters('newsletter_current_language', '');
2442
-
2443
- return $current_language;
2444
- }
2445
-
2446
- function get_default_language() {
2447
- if (class_exists('SitePress')) {
2448
- return $current_language = apply_filters('wpml_current_language', '');
2449
- } else if (function_exists('pll_default_language')) {
2450
- return pll_default_language();
2451
- } else if (class_exists('TRP_Translate_Press')) {
2452
- // TODO: Find the default language
2453
- }
2454
- return '';
2455
- }
2456
-
2457
- function is_all_languages() {
2458
- return $this->get_current_language() == '';
2459
- }
2460
-
2461
- function is_default_language() {
2462
- return $this->get_current_language() == $this->get_default_language();
2463
- }
2464
-
2465
- /**
2466
- * Returns an array of languages with key the language code and value the language name.
2467
- * An empty array is returned if no language is available.
2468
- */
2469
- function get_languages() {
2470
- $language_options = array();
2471
-
2472
- if (class_exists('SitePress')) {
2473
- $languages = apply_filters('wpml_active_languages', null, ['skip_missing' => 0]);
2474
- foreach ($languages as $language) {
2475
- $language_options[$language['language_code']] = $language['translated_name'];
2476
- }
2477
-
2478
- return $language_options;
2479
- } else if (function_exists('icl_get_languages')) {
2480
- $languages = icl_get_languages();
2481
- foreach ($languages as $code => $language) {
2482
- $language_options[$code] = $language['native_name'];
2483
- }
2484
- return $language_options;
2485
- }
2486
-
2487
- return apply_filters('newsletter_languages', $language_options);
2488
- }
2489
-
2490
- function get_language_label($language) {
2491
- $languages = $this->get_languages();
2492
- if (isset($languages[$language])) {
2493
- return $languages[$language];
2494
- }
2495
- return '';
2496
- }
2497
-
2498
- /**
2499
- * Changes the current language usually before extracting the posts since WPML
2500
- * does not support the language filter in the post query (or at least we didn't
2501
- * find it).
2502
- *
2503
- * @param string $language
2504
- */
2505
- function switch_language($language) {
2506
- if (class_exists('SitePress')) {
2507
- if (empty($language)) {
2508
- $language = 'all';
2509
- }
2510
- do_action('wpml_switch_language', $language);
2511
- return;
2512
- }
2513
- }
2514
-
2515
- function is_multilanguage() {
2516
- return class_exists('SitePress') || function_exists('pll_default_language') || class_exists('TRP_Translate_Press');
2517
- }
2518
-
2519
- function get_posts($filters = array(), $language = '') {
2520
- $current_language = $this->get_current_language();
2521
-
2522
- // Language switch for WPML
2523
- if ($language) {
2524
- if (class_exists('SitePress')) {
2525
- $this->switch_language($language);
2526
- $filters['suppress_filters'] = false;
2527
- }
2528
- if (class_exists('Polylang')) {
2529
- $filters['lang'] = $language;
2530
- }
2531
- }
2532
- $posts = get_posts($filters);
2533
- if ($language) {
2534
- if (class_exists('SitePress')) {
2535
- $this->switch_language($current_language);
2536
- }
2537
- }
2538
- return $posts;
2539
- }
2540
-
2541
- function get_wp_query($filters, $langiage = '') {
2542
- if ($language) {
2543
- if (class_exists('SitePress')) {
2544
- $this->switch_language($language);
2545
- $filters['suppress_filters'] = false;
2546
- }
2547
- if (class_exists('Polylang')) {
2548
- $filters['lang'] = $language;
2549
- }
2550
- }
2551
-
2552
- $posts = new WP_Query($filters);
2553
-
2554
- if ($language) {
2555
- if (class_exists('SitePress')) {
2556
- $this->switch_language($current_language);
2557
- }
2558
- }
2559
-
2560
- return $posts;
2561
- }
2562
-
2563
- protected function generate_admin_notification_message($user) {
2564
-
2565
- $message = file_get_contents(__DIR__ . '/notification.html');
2566
-
2567
- $message = $this->replace($message, $user);
2568
- $message = str_replace('{user_admin_url}', admin_url('admin.php?page=newsletter_users_edit&id=' . $user->id), $message);
2569
-
2570
- return $message;
2571
- }
2572
-
2573
- protected function generate_admin_notification_subject($subject) {
2574
- $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
2575
-
2576
- return '[' . $blogname . '] ' . $subject;
2577
- }
2578
-
2579
- function dienow($message, $admin_message = null, $http_code = 200) {
2580
- if ($admin_message && current_user_can('administrator')) {
2581
- $message .= '<br><br><strong>Text below only visibile to administrators</strong><br>';
2582
- $message .= $admin_message;
2583
- }
2584
- wp_die($message, $http_code);
2585
- }
2586
-
2587
- function dump($var) {
2588
- if (NEWSLETTER_DEBUG) {
2589
- var_dump($var);
2590
- }
2591
- }
2592
-
2593
- function dump_die($var) {
2594
- if (NEWSLETTER_DEBUG) {
2595
- var_dump($var);
2596
- die();
2597
- }
2598
- }
2599
-
2600
- }
2601
-
2602
- /**
2603
- * Kept for compatibility.
2604
- *
2605
- * @param type $post_id
2606
- * @param type $size
2607
- * @param type $alternative
2608
- * @return type
2609
- */
2610
- function nt_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2611
- return NewsletterModule::get_post_image($post_id, $size, $alternative);
2612
- }
2613
-
2614
- function newsletter_get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2615
- echo NewsletterModule::get_post_image($post_id, $size, $alternative);
2616
- }
2617
-
2618
- /**
2619
- * Accepts a post or a post ID.
2620
- *
2621
- * @param WP_Post $post
2622
- */
2623
- function newsletter_the_excerpt($post, $words = 30) {
2624
- $post = get_post($post);
2625
- $excerpt = $post->post_excerpt;
2626
- if (empty($excerpt)) {
2627
- $excerpt = $post->post_content;
2628
- $excerpt = strip_shortcodes($excerpt);
2629
- $excerpt = wp_strip_all_tags($excerpt, true);
2630
- }
2631
- echo '<p>' . wp_trim_words($excerpt, $words) . '</p>';
2632
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ require_once __DIR__ . '/logger.php';
6
+ require_once __DIR__ . '/store.php';
7
+ require_once __DIR__ . '/composer.php';
8
+ require_once __DIR__ . '/addon.php';
9
+ require_once __DIR__ . '/mailer.php';
10
+ require_once __DIR__ . '/themes.php';
11
+
12
+ class TNP_Media {
13
+
14
+ var $id;
15
+ var $url;
16
+ var $width;
17
+ var $height;
18
+ var $alt;
19
+ var $link;
20
+ var $align = 'center';
21
+
22
+ /** Sets the width recalculating the height */
23
+ public function set_width($width) {
24
+ $width = (int) $width;
25
+ if (empty($width))
26
+ return;
27
+ if ($this->width < $width)
28
+ return;
29
+ $this->height = floor(($width / $this->width) * $this->height);
30
+ $this->width = $width;
31
+ }
32
+
33
+ /** Sets the height recalculating the width */
34
+ public function set_height($height) {
35
+ $height = (int) $height;
36
+ $this->width = floor(($height / $this->height) * $this->width);
37
+ $this->height = $height;
38
+ }
39
+
40
+ }
41
+
42
+ /**
43
+ * @property int $id The list unique identifier
44
+ * @property string $name The list name
45
+ * @property bool $forced If the list must be added to every new subscriber
46
+ * @property int $status When and how the list is visible to the subscriber - see constants
47
+ * @property bool $checked If it must be pre-checked on subscription form
48
+ * @property array $languages The list of language used to pre-assign this list
49
+ */
50
+ class TNP_List {
51
+
52
+ const STATUS_PRIVATE = 0;
53
+ const STATUS_PUBLIC = 1;
54
+ const SUBSCRIPTION_HIDE = 0;
55
+ const SUBSCRIPTION_SHOW = 1;
56
+ const SUBSCRIPTION_SHOW_CHECKED = 2;
57
+ const PROFILE_HIDE = 0;
58
+ const PROFILE_SHOW = 1;
59
+
60
+ var $id;
61
+ var $name;
62
+ var $status;
63
+ var $forced;
64
+ var $checked;
65
+ var $show_on_subscription;
66
+ var $show_on_profile;
67
+
68
+ function is_private() {
69
+ return $this->status == self::STATUS_PRIVATE;
70
+ }
71
+
72
+ }
73
+
74
+ /**
75
+ * @property int $id The list unique identifier
76
+ * @property string $name The list name
77
+ * @property int $status When and how the list is visible to the subscriber - see constants
78
+ * @property string $type Field type: text or select
79
+ * @property array $options Field options (usually the select items)
80
+ */
81
+ class TNP_Profile {
82
+
83
+ const STATUS_PRIVATE = 0;
84
+ const STATUS_PUBLIC = 2;
85
+ const STATUS_PROFILE_ONLY = 1;
86
+ const STATUS_HIDDEN = 3; // Public but never shown (can be set with a hidden form field)
87
+ const TYPE_TEXT = 'text';
88
+ const TYPE_SELECT = 'select';
89
+
90
+ public $id;
91
+ public $name;
92
+ public $status;
93
+ public $type;
94
+ public $options;
95
+ public $placeholder;
96
+ public $rule;
97
+
98
+ public function __construct($id, $name, $status, $type, $options, $placeholder, $rule) {
99
+ $this->id = $id;
100
+ $this->name = $name;
101
+ $this->status = $status;
102
+ $this->type = $type;
103
+ $this->options = $options;
104
+ $this->placeholder = $placeholder;
105
+ $this->rule = $rule;
106
+ }
107
+
108
+ function is_select() {
109
+ return $this->type == self::TYPE_SELECT;
110
+ }
111
+
112
+ function is_text() {
113
+ return $this->type == self::TYPE_TEXT;
114
+ }
115
+
116
+ function is_required() {
117
+ return $this->rule == 1;
118
+ }
119
+
120
+ function is_private() {
121
+ return $this->status == self::STATUS_PRIVATE;
122
+ }
123
+
124
+ function show_on_profile() {
125
+ return $this->status == self::STATUS_PROFILE_ONLY || $this->status == self::STATUS_PUBLIC;
126
+ }
127
+
128
+ }
129
+
130
+ class TNP_Profile_Service {
131
+
132
+ /**
133
+ *
134
+ * @param string $language
135
+ * @param string $type
136
+ * @return TNP_Profile[]
137
+ */
138
+ static function get_profiles($language = '', $type = '') {
139
+
140
+ static $profiles = [];
141
+ $k = $language . $type;
142
+
143
+ if (isset($profiles[$k])) {
144
+ return $profiles[$k];
145
+ }
146
+
147
+ $profiles[$k] = [];
148
+ $profile_options = NewsletterSubscription::instance()->get_options('profile', $language);
149
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
150
+ if (empty($profile_options['profile_' . $i])) {
151
+ continue;
152
+ }
153
+ $profile = self::create_profile_from_options($profile_options, $i);
154
+
155
+ if (empty($type) ||
156
+ ( $type == TNP_Profile::TYPE_SELECT && $profile->is_select() ) ||
157
+ ( $type == TNP_Profile::TYPE_TEXT && $profile->is_text() )) {
158
+ $profiles[$k]['' . $i] = $profile;
159
+ }
160
+ }
161
+
162
+ return $profiles[$k];
163
+ }
164
+
165
+ static function get_profile_by_id($id, $language = '') {
166
+
167
+ $profiles = self::get_profiles($language);
168
+ if (isset($profiles[$id]))
169
+ return $profiles[$id];
170
+ return null;
171
+ }
172
+
173
+ /**
174
+ * @return TNP_Profile
175
+ */
176
+ private static function create_profile_from_options($options, $id) {
177
+ return new TNP_Profile(
178
+ $id,
179
+ $options['profile_' . $id],
180
+ (int) $options['profile_' . $id . '_status'],
181
+ $options['profile_' . $id . '_type'],
182
+ self::string_db_options_to_array($options['profile_' . $id . '_options']),
183
+ $options['profile_' . $id . '_placeholder'],
184
+ $options['profile_' . $id . '_rules']
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Returns a list of strings which are the items for the select field.
190
+ * @return array
191
+ */
192
+ private static function string_db_options_to_array($string_options) {
193
+ $items = array_map('trim', explode(',', $string_options));
194
+ $items = array_combine($items, $items);
195
+
196
+ return $items;
197
+ }
198
+
199
+ }
200
+
201
+ /**
202
+ * Represents the set of data collected by a subscription interface (form, API, ...). Only a valid
203
+ * email is mandatory.
204
+ */
205
+ class TNP_Subscription_Data {
206
+
207
+ var $email = null;
208
+ var $name = null;
209
+ var $surname = null;
210
+ var $sex = null;
211
+ var $language = null;
212
+ var $referrer = null;
213
+ var $http_referrer = null;
214
+ var $ip = null;
215
+ var $country = null;
216
+ var $region = null;
217
+ var $city = null;
218
+
219
+ /**
220
+ * Associative array id=>value of lists chosen by the subscriber. A list can be set to
221
+ * 0 meaning the subscriber does not want to be in that list.
222
+ * The lists must be public: non public lists are filtered.
223
+ * @var array
224
+ */
225
+ var $lists = [];
226
+ var $profiles = [];
227
+
228
+ function merge_in($subscriber) {
229
+ if (!$subscriber)
230
+ $subscriber = new TNP_User();
231
+ if (!empty($this->email))
232
+ $subscriber->email = $this->email;
233
+ if (!empty($this->name))
234
+ $subscriber->name = $this->name;
235
+ if (!empty($this->surname))
236
+ $subscriber->surname = $this->surname;
237
+ if (!empty($this->sex))
238
+ $subscriber->sex = $this->sex;
239
+ if (!empty($this->language))
240
+ $subscriber->language = $this->language;
241
+ if (!empty($this->ip))
242
+ $subscriber->ip = $this->ip;
243
+ if (!empty($this->referrer))
244
+ $subscriber->referrer = $this->referrer;
245
+ if (!empty($this->http_referrer))
246
+ $subscriber->http_referrer = $this->http_referrer;
247
+ if (!empty($this->country))
248
+ $subscriber->country = $this->country;
249
+ if (!empty($this->region))
250
+ $subscriber->region = $this->region;
251
+ if (!empty($this->city))
252
+ $subscriber->city = $this->city;
253
+
254
+
255
+ foreach ($this->lists as $id => $value) {
256
+ $key = 'list_' . $id;
257
+ $subscriber->$key = $value;
258
+ }
259
+
260
+ // Profile
261
+ foreach ($this->profiles as $id => $value) {
262
+ $key = 'profile_' . $id;
263
+ $subscriber->$key = $value;
264
+ }
265
+ }
266
+
267
+ /** Sets to active a set of lists. Accepts incorrect data (and ignores it).
268
+ *
269
+ * @param array $list_ids Array of list IDs
270
+ */
271
+ function add_lists($list_ids) {
272
+ if (empty($list_ids) || !is_array($list_ids))
273
+ return;
274
+ foreach ($list_ids as $list_id) {
275
+ $list_id = (int) $list_id;
276
+ if ($list_id < 0 || $list_id > NEWSLETTER_LIST_MAX)
277
+ continue;
278
+ $this->lists[$list_id] = 1;
279
+ }
280
+ }
281
+
282
+ }
283
+
284
+ /**
285
+ * Represents a subscription request with the subscriber data and actions to be taken by
286
+ * the subscription engine (spam check, notifications, ...).
287
+ */
288
+ class TNP_Subscription {
289
+
290
+ const EXISTING_ERROR = 1;
291
+ const EXISTING_MERGE = 0;
292
+ const EXISTING_SINGLE_OPTIN = 2;
293
+
294
+
295
+ /**
296
+ * Subscriber's data following the syntax of the TNP_User
297
+ * @var TNP_Subscription_Data
298
+ */
299
+ var $data;
300
+ var $spamcheck = true;
301
+ // The optin to use, empty for the plugin default. It's a string to facilitate the use by addons (which have a selector for the desired
302
+ // optin as empty (for default), 'single' or 'double'.
303
+ var $optin = null;
304
+ // What to do with an existing subscriber???
305
+ var $if_exists = self::EXISTING_MERGE;
306
+
307
+ /**
308
+ * Determines if the welcome or activation email should be sent. Note: sometime an activation email is sent disregarding
309
+ * this setting.
310
+ * @var boolean
311
+ */
312
+ var $send_emails = true;
313
+
314
+ public function __construct() {
315
+ $this->data = new TNP_Subscription_Data();
316
+ }
317
+
318
+ public function is_single_optin() {
319
+ return $this->optin == 'single';
320
+ }
321
+
322
+ public function is_double_optin() {
323
+ return $this->optin == 'double';
324
+ }
325
+
326
+ }
327
+
328
+ /**
329
+ * @property int $id The subscriber unique identifier
330
+ * @property string $email The subscriber email
331
+ * @property string $name The subscriber name or first name
332
+ * @property string $surname The subscriber last name
333
+ * @property string $status The subscriber status
334
+ * @property string $language The subscriber language code 2 chars lowercase
335
+ * @property string $token The subscriber secret token
336
+ * @property string $country The subscriber country code 2 chars uppercase
337
+ */
338
+ class TNP_User {
339
+
340
+ const STATUS_CONFIRMED = 'C';
341
+ const STATUS_NOT_CONFIRMED = 'S';
342
+ const STATUS_UNSUBSCRIBED = 'U';
343
+ const STATUS_BOUNCED = 'B';
344
+ const STATUS_COMPLAINED = 'P';
345
+
346
+ var $ip = '';
347
+
348
+ public static function get_status_label($status) {
349
+ switch ($status) {
350
+ case self::STATUS_NOT_CONFIRMED: return __('NOT CONFIRMED', 'newsletter');
351
+ break;
352
+ case self::STATUS_CONFIRMED: return __('CONFIRMED', 'newsletter');
353
+ break;
354
+ case self::STATUS_UNSUBSCRIBED: return __('UNSUBSCRIBED', 'newsletter');
355
+ break;
356
+ case self::STATUS_BOUNCED: return __('BOUNCED', 'newsletter');
357
+ break;
358
+ case self::STATUS_COMPLAINED: return __('COMPLAINED', 'newsletter');
359
+ break;
360
+ default:
361
+ return __('Unknown', 'newsletter');
362
+ }
363
+ }
364
+
365
+ }
366
+
367
+ /**
368
+ * @property int $id The email unique identifier
369
+ * @property string $subject The email subject
370
+ * @property string $message The email html message
371
+ * @property int $track Check if the email stats should be active
372
+ * @property array $options Email options
373
+ * @property int $total Total emails to send
374
+ * @property int $sent Total sent emails by now
375
+ * @property int $open_count Total opened emails
376
+ * @property int $click_count Total clicked emails
377
+ * */
378
+ class TNP_Email {
379
+
380
+ const STATUS_DRAFT = 'new';
381
+ const STATUS_SENT = 'sent';
382
+ const STATUS_SENDING = 'sending';
383
+ const STATUS_PAUSED = 'paused';
384
+ const STATUS_ERROR = 'error';
385
+
386
+ }
387
+
388
+ class NewsletterModule {
389
+
390
+ /**
391
+ * @var NewsletterLogger
392
+ */
393
+ var $logger;
394
+
395
+ /**
396
+ * @var NewsletterLogger
397
+ */
398
+ var $admin_logger;
399
+
400
+ /**
401
+ * @var NewsletterStore
402
+ */
403
+ var $store;
404
+
405
+ /**
406
+ * The main module options
407
+ * @var array
408
+ */
409
+ var $options;
410
+
411
+ /**
412
+ * @var string The module name
413
+ */
414
+ var $module;
415
+
416
+ /**
417
+ * The module version
418
+ * @var string
419
+ */
420
+ var $version;
421
+ var $old_version;
422
+
423
+ /**
424
+ * Prefix for all options stored on WordPress options table.
425
+ * @var string
426
+ */
427
+ var $prefix;
428
+
429
+ /**
430
+ * @var NewsletterThemes
431
+ */
432
+ var $themes;
433
+ var $components;
434
+ static $current_language = '';
435
+
436
+ function __construct($module, $version, $module_id = null, $components = array()) {
437
+ $this->module = $module;
438
+ $this->version = $version;
439
+ $this->prefix = 'newsletter_' . $module;
440
+ array_unshift($components, '');
441
+ $this->components = $components;
442
+
443
+ $this->logger = new NewsletterLogger($module);
444
+
445
+ $this->options = $this->get_options();
446
+ $this->store = NewsletterStore::singleton();
447
+
448
+ //$this->logger->debug($module . ' constructed');
449
+ // Version check
450
+ if (is_admin()) {
451
+ $this->admin_logger = new NewsletterLogger($module . '-admin');
452
+ $this->old_version = get_option($this->prefix . '_version', '0.0.0');
453
+
454
+ if ($this->old_version == '0.0.0') {
455
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
456
+ $this->first_install();
457
+ update_option($this->prefix . "_first_install_time", time(), FALSE);
458
+ }
459
+
460
+ if (strcmp($this->old_version, $this->version) != 0) {
461
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
462
+ $this->logger->info('Version changed from ' . $this->old_version . ' to ' . $this->version);
463
+ // Do all the stuff for this version change
464
+ $this->upgrade();
465
+ update_option($this->prefix . '_version', $this->version);
466
+ }
467
+
468
+ add_action('admin_menu', array($this, 'admin_menu'));
469
+ }
470
+ }
471
+
472
+ /**
473
+ *
474
+ * @global wpdb $wpdb
475
+ * @param string $query
476
+ */
477
+ function query($query) {
478
+ global $wpdb;
479
+
480
+ $this->logger->debug($query);
481
+ //$start = microtime(true);
482
+ $r = $wpdb->query($query);
483
+ //$this->logger->debug($wpdb->last_query);
484
+ //$this->logger->debug('Execution time: ' . (microtime(true)-$start));
485
+ //$this->logger->debug('Result: ' . $r);
486
+ if ($r === false) {
487
+ $this->logger->fatal($query);
488
+ $this->logger->fatal($wpdb->last_error);
489
+ }
490
+ return $r;
491
+ }
492
+
493
+ function get_results($query) {
494
+ global $wpdb;
495
+ $r = $wpdb->get_results($query);
496
+ if ($r === false) {
497
+ $this->logger->fatal($query);
498
+ $this->logger->fatal($wpdb->last_error);
499
+ }
500
+ return $r;
501
+ }
502
+
503
+ /**
504
+ *
505
+ * @global wpdb $wpdb
506
+ * @param string $table
507
+ * @param array $data
508
+ */
509
+ function insert($table, $data) {
510
+ global $wpdb;
511
+ $this->logger->debug("inserting into table $table");
512
+ $r = $wpdb->insert($table, $data);
513
+ if ($r === false) {
514
+ $this->logger->fatal($wpdb->last_error);
515
+ }
516
+ }
517
+
518
+ function first_install() {
519
+ $this->logger->debug('First install');
520
+ }
521
+
522
+ /**
523
+ * Does a basic upgrade work, checking if the options is already present and if not (first
524
+ * installation), recovering the defaults, saving them on database and initializing the
525
+ * internal $options.
526
+ */
527
+ function upgrade() {
528
+ foreach ($this->components as $component) {
529
+ $this->logger->debug('Upgrading component ' . $component);
530
+ $this->init_options($component);
531
+ }
532
+ }
533
+
534
+ function init_options($component = '', $autoload = true) {
535
+ global $wpdb;
536
+ $default_options = $this->get_default_options($component);
537
+ $options = $this->get_options($component);
538
+ $options = array_merge($default_options, $options);
539
+ $this->save_options($options, $component, $autoload);
540
+ }
541
+
542
+ function upgrade_query($query) {
543
+ global $wpdb, $charset_collate;
544
+
545
+ $this->logger->info('upgrade_query> Executing ' . $query);
546
+ $suppress_errors = $wpdb->suppress_errors(true);
547
+ $wpdb->query($query);
548
+ if ($wpdb->last_error) {
549
+ $this->logger->debug($wpdb->last_error);
550
+ }
551
+ $wpdb->suppress_errors($suppress_errors);
552
+ }
553
+
554
+ /** Returns a prefix to be used for option names and other things which need to be uniquely named. The parameter
555
+ * "sub" should be used when a sub name is needed for another set of options or like.
556
+ *
557
+ * @param string $sub
558
+ * @return string The prefix for names
559
+ */
560
+ function get_prefix($sub = '', $language = '') {
561
+ return $this->prefix . (!empty($sub) ? '_' : '') . $sub . (!empty($language) ? '_' : '') . $language;
562
+ }
563
+
564
+ /**
565
+ * Returns the options of a module, if not found an empty array.
566
+ */
567
+ function get_options($sub = '', $language = '') {
568
+ $options = get_option($this->get_prefix($sub, $language), array());
569
+ // Protection against scarmled database...
570
+ if (!is_array($options)) {
571
+ $options = array();
572
+ }
573
+ if ($language) {
574
+ $main_options = get_option($this->get_prefix($sub));
575
+ // Protection against scarmled database...
576
+ if (!is_array($main_options))
577
+ $main_options = array();
578
+ //$options = array_merge($main_options, array_filter($options));
579
+ $options = array_merge($main_options, $options);
580
+ }
581
+ return $options;
582
+ }
583
+
584
+ function get_default_options($sub = '') {
585
+ if (!empty($sub)) {
586
+ $sub = '-' . $sub;
587
+ }
588
+ $file = NEWSLETTER_DIR . '/' . $this->module . '/defaults' . $sub . '.php';
589
+ if (file_exists($file)) {
590
+ @include $file;
591
+ }
592
+
593
+ if (!isset($options) || !is_array($options)) {
594
+ return array();
595
+ }
596
+ return $options;
597
+ }
598
+
599
+ function reset_options($sub = '') {
600
+ $this->save_options(array_merge($this->get_options($sub), $this->get_default_options($sub)), $sub);
601
+ return $this->get_options($sub);
602
+ }
603
+
604
+ /**
605
+ * Saves the module options (or eventually a subset names as per parameter $sub). $options
606
+ * should be an array (even if it can work with non array options.
607
+ * The internal module options variable IS initialized with those new options only for the main
608
+ * options (empty $sub parameter).
609
+ * If the options contain a "theme" value, the theme-related options contained are saved as well
610
+ * (used by some modules).
611
+ *
612
+ * @param array $options
613
+ * @param string $sub
614
+ */
615
+ function save_options($options, $sub = '', $autoload = null, $language = '') {
616
+ update_option($this->get_prefix($sub, $language), $options, $autoload);
617
+ if (empty($sub) && empty($language)) {
618
+ $this->options = $options;
619
+ if (isset($this->themes) && isset($options['theme'])) {
620
+ $this->themes->save_options($options['theme'], $options);
621
+ }
622
+ }
623
+ }
624
+
625
+ function delete_options($sub = '') {
626
+ delete_option($this->get_prefix($sub));
627
+ if (empty($sub)) {
628
+ $this->options = array();
629
+ }
630
+ }
631
+
632
+ function merge_options($options, $sub = '', $language = '') {
633
+ if (!is_array($options)) {
634
+ $options = array();
635
+ }
636
+ $old_options = $this->get_options($sub, $language);
637
+ $this->save_options(array_merge($old_options, $options), $sub, null, $language);
638
+ }
639
+
640
+ function backup_options($sub) {
641
+ $options = $this->get_options($sub);
642
+ update_option($this->get_prefix($sub) . '_backup', $options, false);
643
+ }
644
+
645
+ function get_last_run($sub = '') {
646
+ return get_option($this->get_prefix($sub) . '_last_run', 0);
647
+ }
648
+
649
+ /**
650
+ * Save the module last run value. Used to store a timestamp for some modules,
651
+ * for example the Feed by Mail module.
652
+ *
653
+ * @param int $time Unix timestamp (as returned by time() for example)
654
+ * @param string $sub Sub module name (default empty)
655
+ */
656
+ function save_last_run($time, $sub = '') {
657
+ update_option($this->get_prefix($sub) . '_last_run', $time);
658
+ }
659
+
660
+ /**
661
+ * Sums $delta seconds to the last run time.
662
+ * @param int $delta Seconds
663
+ * @param string $sub Sub module name (default empty)
664
+ */
665
+ function add_to_last_run($delta, $sub = '') {
666
+ $time = $this->get_last_run($sub);
667
+ $this->save_last_run($time + $delta, $sub);
668
+ }
669
+
670
+ /**
671
+ * Checks if the semaphore of that name (for this module) is still red. If it is active the method
672
+ * returns false. If it is not active, it will be activated for $time seconds.
673
+ *
674
+ * Since this method activate the semaphore when called, it's name is a bit confusing.
675
+ *
676
+ * @param string $name Sempahore name (local to this module)
677
+ * @param int $time Max time in second this semaphore should stay red
678
+ * @return boolean False if the semaphore is red and you should not proceed, true is it was not active and has been activated.
679
+ */
680
+ function check_transient($name, $time) {
681
+ if ($time < 60)
682
+ $time = 60;
683
+ //usleep(rand(0, 1000000));
684
+ if (($value = get_transient($this->get_prefix() . '_' . $name)) !== false) {
685
+ list($t, $v) = explode(';', $value, 2);
686
+ $this->logger->error('Blocked by transient ' . $this->get_prefix() . '_' . $name . ' set ' . (time() - $t) . ' seconds ago by ' . $v);
687
+ return false;
688
+ }
689
+ //$ip = ''; //gethostbyname(gethostname());
690
+ $value = time() . ";" . ABSPATH . ';' . gethostname();
691
+ set_transient($this->get_prefix() . '_' . $name, $value, $time);
692
+ return true;
693
+ }
694
+
695
+ function delete_transient($name = '') {
696
+ delete_transient($this->get_prefix() . '_' . $name);
697
+ }
698
+
699
+ /** Returns a random token of the specified size (or 10 characters if size is not specified).
700
+ *
701
+ * @param int $size
702
+ * @return string
703
+ */
704
+ static function get_token($size = 10) {
705
+ return substr(md5(rand()), 0, $size);
706
+ }
707
+
708
+ /**
709
+ * Adds query string parameters to an URL checing id there are already other parameters.
710
+ *
711
+ * @param string $url
712
+ * @param string $qs The part of query-string to add (param1=value1&param2=value2...)
713
+ * @param boolean $amp If the method must use the &amp; instead of the plain & (default true)
714
+ * @return string
715
+ */
716
+ static function add_qs($url, $qs, $amp = true) {
717
+ if (strpos($url, '?') !== false) {
718
+ if ($amp)
719
+ return $url . '&amp;' . $qs;
720
+ else
721
+ return $url . '&' . $qs;
722
+ } else
723
+ return $url . '?' . $qs;
724
+ }
725
+
726
+ /**
727
+ * Returns the email address normalized, lowercase with no spaces. If it's not a valid email
728
+ * returns false.
729
+ */
730
+ static function normalize_email($email) {
731
+ if (!is_string($email)) {
732
+ return false;
733
+ }
734
+ $email = strtolower(trim($email));
735
+ if (!is_email($email)) {
736
+ return false;
737
+ }
738
+ //$email = apply_filters('newsletter_normalize_email', $email);
739
+ return $email;
740
+ }
741
+
742
+ static function normalize_name($name) {
743
+ $name = html_entity_decode($name, ENT_QUOTES);
744
+ $name = str_replace(';', ' ', $name);
745
+ $name = strip_tags($name);
746
+
747
+ return $name;
748
+ }
749
+
750
+ static function normalize_sex($sex) {
751
+ $sex = trim(strtolower($sex));
752
+ if ($sex != 'f' && $sex != 'm') {
753
+ $sex = 'n';
754
+ }
755
+ return $sex;
756
+ }
757
+
758
+ static function is_email($email, $empty_ok = false) {
759
+
760
+ if (!is_string($email)) {
761
+ return false;
762
+ }
763
+ $email = strtolower(trim($email));
764
+
765
+ if ($email == '') {
766
+ return $empty_ok;
767
+ }
768
+
769
+ if (!is_email($email)) {
770
+ return false;
771
+ }
772
+ return true;
773
+ }
774
+
775
+ /**
776
+ * Converts a GMT date from mysql (see the posts table columns) into a timestamp.
777
+ *
778
+ * @param string $s GMT date with format yyyy-mm-dd hh:mm:ss
779
+ * @return int A timestamp
780
+ */
781
+ static function m2t($s) {
782
+
783
+ // TODO: use the wordpress function I don't remember the name
784
+ $s = explode(' ', $s);
785
+ $d = explode('-', $s[0]);
786
+ $t = explode(':', $s[1]);
787
+ return gmmktime((int) $t[0], (int) $t[1], (int) $t[2], (int) $d[1], (int) $d[2], (int) $d[0]);
788
+ }
789
+
790
+ static function format_date($time) {
791
+ if (empty($time)) {
792
+ return '-';
793
+ }
794
+ return gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
795
+ }
796
+
797
+ static function format_time_delta($delta) {
798
+ $days = floor($delta / (3600 * 24));
799
+ $hours = floor(($delta % (3600 * 24)) / 3600);
800
+ $minutes = floor(($delta % 3600) / 60);
801
+ $seconds = floor(($delta % 60));
802
+ $buffer = $days . ' days, ' . $hours . ' hours, ' . $minutes . ' minutes, ' . $seconds . ' seconds';
803
+ return $buffer;
804
+ }
805
+
806
+ /**
807
+ * Formats a scheduler returned "next execution" time, managing negative or false values. Many times
808
+ * used in conjuction with "last run".
809
+ *
810
+ * @param string $name The scheduler name
811
+ * @return string
812
+ */
813
+ static function format_scheduler_time($name) {
814
+ $time = wp_next_scheduled($name);
815
+ if ($time === false) {
816
+ return 'No next run scheduled';
817
+ }
818
+ $delta = $time - time();
819
+ // If less 10 minutes late it can be a cron problem but now it is working
820
+ if ($delta < 0 && $delta > -600) {
821
+ return 'Probably running now';
822
+ } else if ($delta <= -600) {
823
+ return 'It seems the cron system is not working. Reload the page to see if this message change.';
824
+ }
825
+ return 'Runs in ' . self::format_time_delta($delta);
826
+ }
827
+
828
+ static function date($time = null, $now = false, $left = false) {
829
+ if (is_null($time)) {
830
+ $time = time();
831
+ }
832
+ if ($time == false) {
833
+ $buffer = 'none';
834
+ } else {
835
+ $buffer = gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
836
+ }
837
+ if ($now) {
838
+ $buffer .= ' (now: ' . gmdate(get_option('date_format') . ' ' .
839
+ get_option('time_format'), time() + get_option('gmt_offset') * 3600);
840
+ $buffer .= ')';
841
+ }
842
+ if ($left) {
843
+ $buffer .= ', ' . gmdate('H:i:s', $time - time()) . ' left';
844
+ }
845
+ return $buffer;
846
+ }
847
+
848
+ /**
849
+ * Return an array of array with on first element the array of recent post and on second element the array
850
+ * of old posts.
851
+ *
852
+ * @param array $posts
853
+ * @param int $time
854
+ */
855
+ static function split_posts(&$posts, $time = 0) {
856
+ if ($time < 0) {
857
+ return array_chunk($posts, ceil(count($posts) / 2));
858
+ }
859
+
860
+ $result = array(array(), array());
861
+
862
+ if (empty($posts))
863
+ return $result;
864
+
865
+ foreach ($posts as &$post) {
866
+ if (self::is_post_old($post, $time))
867
+ $result[1][] = $post;
868
+ else
869
+ $result[0][] = $post;
870
+ }
871
+ return $result;
872
+ }
873
+
874
+ static function is_post_old(&$post, $time = 0) {
875
+ return self::m2t($post->post_date_gmt) <= $time;
876
+ }
877
+
878
+ static function get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
879
+ global $post;
880
+
881
+ if (empty($post_id))
882
+ $post_id = $post->ID;
883
+ if (empty($post_id))
884
+ return $alternative;
885
+
886
+ $image_id = function_exists('get_post_thumbnail_id') ? get_post_thumbnail_id($post_id) : false;
887
+ if ($image_id) {
888
+ $image = wp_get_attachment_image_src($image_id, $size);
889
+ return $image[0];
890
+ } else {
891
+ $attachments = get_children(array('post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID'));
892
+
893
+ if (empty($attachments)) {
894
+ return $alternative;
895
+ }
896
+
897
+ foreach ($attachments as $id => $attachment) {
898
+ $image = wp_get_attachment_image_src($id, $size);
899
+ return $image[0];
900
+ }
901
+ }
902
+ }
903
+
904
+ /**
905
+ * Cleans up a text containing url tags with appended the absolute URL (due to
906
+ * the editor behavior) moving back them to the simple form.
907
+ */
908
+ static function clean_url_tags($text) {
909
+ $text = str_replace('%7B', '{', $text);
910
+ $text = str_replace('%7D', '}', $text);
911
+
912
+ // Only tags which are {*_url}
913
+ $text = preg_replace("/[\"']http[^\"']+(\\{[^\\}]+_url\\})[\"']/i", "\"\\1\"", $text);
914
+ return $text;
915
+ }
916
+
917
+ function admin_menu() {
918
+
919
+ }
920
+
921
+ function add_menu_page($page, $title, $capability = '') {
922
+ if (!Newsletter::instance()->is_allowed())
923
+ return;
924
+ $name = 'newsletter_' . $this->module . '_' . $page;
925
+ add_submenu_page('newsletter_main_index', $title, $title, 'exist', $name, array($this, 'menu_page'));
926
+ }
927
+
928
+ function add_admin_page($page, $title) {
929
+ if (!Newsletter::instance()->is_allowed()) {
930
+ return;
931
+ }
932
+ $name = 'newsletter_' . $this->module . '_' . $page;
933
+ add_submenu_page(null, $title, $title, 'exist', $name, array($this, 'menu_page'));
934
+ }
935
+
936
+ function sanitize_file_name($name) {
937
+ return preg_replace('/[^a-z_\\-]/i', '', $name);
938
+ }
939
+
940
+ function menu_page() {
941
+ global $plugin_page, $newsletter, $wpdb;
942
+
943
+ $parts = explode('_', $plugin_page, 3);
944
+ $module = $this->sanitize_file_name($parts[1]);
945
+ $page = $this->sanitize_file_name($parts[2]);
946
+ $page = str_replace('_', '-', $page);
947
+
948
+ $file = NEWSLETTER_DIR . '/' . $module . '/' . $page . '.php';
949
+
950
+ require $file;
951
+ }
952
+
953
+ function get_admin_page_url($page) {
954
+ return admin_url('admin.php') . '?page=newsletter_' . $this->module . '_' . $page;
955
+ }
956
+
957
+ /** Returns all the emails of the give type (message, feed, followup, ...) and in the given format
958
+ * (default as objects). Return false on error or at least an empty array. Errors should never
959
+ * occur.
960
+ *
961
+ * @global wpdb $wpdb
962
+ * @param string $type
963
+ * @return boolean|array
964
+ */
965
+ function get_emails($type = null, $format = OBJECT) {
966
+ global $wpdb;
967
+ if ($type == null) {
968
+ $list = $wpdb->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " order by id desc", $format);
969
+ } else {
970
+ $type = (string) $type;
971
+ $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where type=%s order by id desc", $type), $format);
972
+ }
973
+ if ($wpdb->last_error) {
974
+ $this->logger->error($wpdb->last_error);
975
+ return false;
976
+ }
977
+ if (empty($list)) {
978
+ return [];
979
+ }
980
+ return $list;
981
+ }
982
+
983
+ function get_emails_by_status($status) {
984
+ global $wpdb;
985
+ $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where status=%s order by id desc", $status));
986
+
987
+ array_walk($list, function ($email) {
988
+ $email->options = maybe_unserialize($email->options);
989
+ if (!is_array($email->options)) {
990
+ $email->options = [];
991
+ }
992
+ });
993
+ return $list;
994
+ }
995
+
996
+ /**
997
+ * @param string $key
998
+ * @param mixed $value
999
+ * @return TNP_Email[]
1000
+ */
1001
+ // function get_emails_by_field($key, $value) {
1002
+ // global $wpdb;
1003
+ //
1004
+ // $value_placeholder = is_int($value) ? '%d' : '%s';
1005
+ //
1006
+ // $key = '`' . str_replace('`', '', $key) . '`';
1007
+ //
1008
+ // $query = $wpdb->prepare("SELECT * FROM " . NEWSLETTER_EMAILS_TABLE . " WHERE $key=$value_placeholder ORDER BY id DESC", $value);
1009
+ // //die($query);
1010
+ //
1011
+ // $email_list = $wpdb->get_results($query);
1012
+ //
1013
+ // if ($wpdb->last_error) {
1014
+ // $this->logger->error($wpdb->last_error);
1015
+ //
1016
+ // return [];
1017
+ // }
1018
+ //
1019
+ // //Unserialize options
1020
+ // array_walk($email_list, function ($email) {
1021
+ // $email->options = maybe_unserialize($email->options);
1022
+ // if (!is_array($email->options)) {
1023
+ // $email->options = [];
1024
+ // }
1025
+ // });
1026
+ //
1027
+ // return $email_list;
1028
+ // }
1029
+
1030
+ /**
1031
+ * Retrieves an email from DB and unserialize the options.
1032
+ *
1033
+ * @param mixed $id
1034
+ * @param string $format
1035
+ * @return TNP_Email An object with the same fields of TNP_Email, but not actually of that type
1036
+ */
1037
+ function get_email($id, $format = OBJECT) {
1038
+ $email = $this->store->get_single(NEWSLETTER_EMAILS_TABLE, $id, $format);
1039
+ if (!$email) {
1040
+ return null;
1041
+ }
1042
+ if ($format == OBJECT) {
1043
+ $email->options = maybe_unserialize($email->options);
1044
+ if (!is_array($email->options)) {
1045
+ $email->options = array();
1046
+ }
1047
+ if (empty($email->query)) {
1048
+ $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1049
+ }
1050
+ } else if ($format == ARRAY_A) {
1051
+ $email['options'] = maybe_unserialize($email['options']);
1052
+ if (!is_array($email['options'])) {
1053
+ $email['options'] = array();
1054
+ }
1055
+ if (empty($email['query'])) {
1056
+ $email['query'] = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1057
+ }
1058
+ }
1059
+ return $email;
1060
+ }
1061
+
1062
+ /**
1063
+ * Save an email and provide serialization, if needed, of $email['options'].
1064
+ * @return TNP_Email
1065
+ */
1066
+ function save_email($email, $return_format = OBJECT) {
1067
+ if (is_object($email)) {
1068
+ $email = (array) $email;
1069
+ }
1070
+
1071
+ if (isset($email['subject'])) {
1072
+ if (mb_strlen($email['subject'], 'UTF-8') > 250) {
1073
+ $email['subject'] = mb_substr($email['subject'], 0, 250, 'UTF-8');
1074
+ }
1075
+ }
1076
+ if (isset($email['options']) && is_array($email['options'])) {
1077
+ $email['options'] = serialize($email['options']);
1078
+ }
1079
+ $email = $this->store->save(NEWSLETTER_EMAILS_TABLE, $email, $return_format);
1080
+ if ($return_format == OBJECT) {
1081
+ $email->options = maybe_unserialize($email->options);
1082
+ if (!is_array($email->options)) {
1083
+ $email->options = [];
1084
+ }
1085
+ } else if ($return_format == ARRAY_A) {
1086
+ $email['options'] = maybe_unserialize($email['options']);
1087
+ if (!is_array($email['options'])) {
1088
+ $email['options'] = [];
1089
+ }
1090
+ }
1091
+ return $email;
1092
+ }
1093
+
1094
+ function get_email_from_request() {
1095
+
1096
+ if (isset($_REQUEST['nek'])) {
1097
+ list($id, $token) = @explode('-', $_REQUEST['nek'], 2);
1098
+ } else if (isset($_COOKIE['tnpe'])) {
1099
+ list($id, $token) = @explode('-', $_COOKIE['tnpe'], 2);
1100
+ } else {
1101
+ return null;
1102
+ }
1103
+
1104
+ $email = $this->get_email($id);
1105
+
1106
+ // TODO: Check the token? It's really useful?
1107
+
1108
+ return $email;
1109
+ }
1110
+
1111
+ /**
1112
+ * Delete one or more emails identified by ID (single value or array of ID)
1113
+ *
1114
+ * @global wpdb $wpdb
1115
+ * @param int|array $id Single numeric ID or an array of IDs to be deleted
1116
+ * @return boolean
1117
+ */
1118
+ function delete_email($id) {
1119
+ global $wpdb;
1120
+ $r = $this->store->delete(NEWSLETTER_EMAILS_TABLE, $id);
1121
+ if ($r !== false) {
1122
+ // $id could be an array if IDs
1123
+ $id = (array) $id;
1124
+ foreach ($id as $email_id) {
1125
+ $wpdb->delete(NEWSLETTER_STATS_TABLE, ['email_id' => $email_id]);
1126
+ $wpdb->delete(NEWSLETTER_SENT_TABLE, ['email_id' => $email_id]);
1127
+ }
1128
+ }
1129
+ return $r;
1130
+ }
1131
+
1132
+ function get_email_field($id, $field_name) {
1133
+ return $this->store->get_field(NEWSLETTER_EMAILS_TABLE, $id, $field_name);
1134
+ }
1135
+
1136
+ function get_email_status_slug($email) {
1137
+ $email = (object) $email;
1138
+ if ($email->status == 'sending' && $email->send_on > time()) {
1139
+ return 'scheduled';
1140
+ }
1141
+ return $email->status;
1142
+ }
1143
+
1144
+ function get_email_status_label($email) {
1145
+ $email = (object) $email;
1146
+ $status = $this->get_email_status_slug($email);
1147
+ switch ($status) {
1148
+ case 'sending':
1149
+ return __('Sending', 'newsletter');
1150
+ case 'scheduled':
1151
+ return __('Scheduled', 'newsletter');
1152
+ case 'sent':
1153
+ return __('Sent', 'newsletter');
1154
+ case 'paused':
1155
+ return __('Paused', 'newsletter');
1156
+ case 'new':
1157
+ return __('Draft', 'newsletter');
1158
+ default:
1159
+ return ucfirst($email->status);
1160
+ }
1161
+ }
1162
+
1163
+ function show_email_status_label($email) {
1164
+ echo '<span class="tnp-email-status tnp-email-status--', $this->get_email_status_slug($email), '">', esc_html($this->get_email_status_label($email)), '</span>';
1165
+ }
1166
+
1167
+ function get_email_progress($email, $format = 'percent') {
1168
+ return $email->total > 0 ? intval($email->sent / $email->total * 100) : 0;
1169
+ }
1170
+
1171
+ function show_email_progress_bar($email, $attrs = []) {
1172
+
1173
+ $email = (object) $email;
1174
+
1175
+ $attrs = array_merge(array('format' => 'percent', 'numbers' => false, 'scheduled' => false), $attrs);
1176
+
1177
+ if ($email->status == 'sending' && $email->send_on > time()) {
1178
+ if ($attrs['scheduled']) {
1179
+ echo '<span class="tnp-progress-date">', $this->format_date($email->send_on), '</span>';
1180
+ }
1181
+ return;
1182
+ } else if ($email->status == 'new') {
1183
+ echo '';
1184
+ return;
1185
+ } else if ($email->status == 'sent') {
1186
+ $percent = 100;
1187
+ } else {
1188
+ $percent = $this->get_email_progress($email);
1189
+ }
1190
+
1191
+ echo '<div class="tnp-progress tnp-progress--' . $email->status . '">';
1192
+ echo '<div class="tnp-progress-bar" role="progressbar" style="width: ', $percent, '%;">&nbsp;', $percent, '%&nbsp;</div>';
1193
+ echo '</div>';
1194
+ if ($attrs['numbers']) {
1195
+ if ($email->status == 'sent') {
1196
+ echo '<div class="tnp-progress-numbers">', $email->total, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1197
+ } else {
1198
+ echo '<div class="tnp-progress-numbers">', $email->sent, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1199
+ }
1200
+ }
1201
+ }
1202
+
1203
+ function get_email_type_label($type) {
1204
+
1205
+ // Is an email?
1206
+ if (is_object($type))
1207
+ $type = $type->type;
1208
+
1209
+ $label = apply_filters('newsletter_email_type', '', $type);
1210
+
1211
+ if (!empty($label))
1212
+ return $label;
1213
+
1214
+ switch ($type) {
1215
+ case 'followup':
1216
+ return 'Followup';
1217
+ case 'message':
1218
+ return 'Standard Newsletter';
1219
+ case 'feed':
1220
+ return 'Feed by Mail';
1221
+ }
1222
+
1223
+ if (strpos($type, 'automated') === 0) {
1224
+ list($a, $id) = explode('_', $type);
1225
+ return 'Automated Channel ' . $id;
1226
+ }
1227
+
1228
+ return ucfirst($type);
1229
+ }
1230
+
1231
+ function get_email_progress_label($email) {
1232
+ if ($email->status == 'sent' || $email->status == 'sending') {
1233
+ return $email->sent . ' ' . __('of', 'newsletter') . ' ' . $email->total;
1234
+ }
1235
+ return '-';
1236
+ }
1237
+
1238
+ /**
1239
+ * Returns the email unique key
1240
+ * @param TNP_User $user
1241
+ * @return string
1242
+ */
1243
+ function get_email_key($email) {
1244
+ if (!isset($email->token)) {
1245
+ return $email->id . '-';
1246
+ }
1247
+ return $email->id . '-' . $email->token;
1248
+ }
1249
+
1250
+ /** Searches for a user using the nk parameter or the ni and nt parameters. Tries even with the newsletter cookie.
1251
+ * If found, the user object is returned or null.
1252
+ * The user is returned without regards to his status that should be checked by caller.
1253
+ *
1254
+ * DO NOT REMOVE EVEN IF OLD
1255
+ *
1256
+ * @return TNP_User
1257
+ */
1258
+ function check_user($context = '') {
1259
+ global $wpdb;
1260
+
1261
+ $user = null;
1262
+
1263
+ if (isset($_REQUEST['nk'])) {
1264
+ list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1265
+ } else if (isset($_COOKIE['newsletter'])) {
1266
+ list ($id, $token) = @explode('-', $_COOKIE['newsletter'], 2);
1267
+ }
1268
+
1269
+ if (isset($id)) {
1270
+ $user = $this->get_user($id);
1271
+ if ($user) {
1272
+ if ($context == 'preconfirm') {
1273
+ if ($token != md5($user->token)) {
1274
+ $user = null;
1275
+ }
1276
+ } else {
1277
+ if ($token != $user->token) {
1278
+ $user = null;
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+
1284
+ if ($user == null && is_user_logged_in()) {
1285
+ $user = $this->get_user_by_wp_user_id(get_current_user_id());
1286
+ }
1287
+ return $user;
1288
+ }
1289
+
1290
+ /** Returns the user identify by an id or an email. If $id_or_email is an object or an array, it is assumed it contains
1291
+ * the "id" attribute or key and that is used to load the user.
1292
+ *
1293
+ * @global type $wpdb
1294
+ * @param string|int|object|array $id_or_email
1295
+ * @param string $format
1296
+ * @return TNP_User|null
1297
+ */
1298
+ function get_user($id_or_email, $format = OBJECT) {
1299
+ global $wpdb;
1300
+
1301
+ if (empty($id_or_email))
1302
+ return null;
1303
+
1304
+ // To simplify the reaload of a user passing the user it self.
1305
+ if (is_object($id_or_email)) {
1306
+ $id_or_email = $id_or_email->id;
1307
+ } else if (is_array($id_or_email)) {
1308
+ $id_or_email = $id_or_email['id'];
1309
+ }
1310
+
1311
+ $id_or_email = strtolower(trim($id_or_email));
1312
+
1313
+ if (is_numeric($id_or_email)) {
1314
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where id=%d limit 1", $id_or_email), $format);
1315
+ } else {
1316
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $id_or_email), $format);
1317
+ }
1318
+
1319
+ if ($wpdb->last_error) {
1320
+ $this->logger->error($wpdb->last_error);
1321
+ return null;
1322
+ }
1323
+ return $r;
1324
+ }
1325
+
1326
+ /**
1327
+ *
1328
+ * @global wpdb $wpdb
1329
+ * @param string $email
1330
+ * @return TNP_User
1331
+ */
1332
+ function get_user_by_email($email) {
1333
+ global $wpdb;
1334
+
1335
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $email));
1336
+
1337
+ if ($wpdb->last_error) {
1338
+ $this->logger->error($wpdb->last_error);
1339
+ return null;
1340
+ }
1341
+ return $r;
1342
+ }
1343
+
1344
+ /**
1345
+ * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
1346
+ *
1347
+ * @param type $user
1348
+ */
1349
+ function get_user_edit_url($user) {
1350
+ $id = $this->to_int_id($user);
1351
+ return admin_url('admin.php') . '?page=newsletter_users_edit&id=' . $id;
1352
+ }
1353
+
1354
+ /**
1355
+ * Returns the user unique key
1356
+ * @param TNP_User $user
1357
+ * @return string
1358
+ */
1359
+ function get_user_key($user, $context = '') {
1360
+ if (empty($user->token)) {
1361
+ $this->refresh_user_token($user);
1362
+ }
1363
+
1364
+ if ($context == 'preconfirm') {
1365
+ return $user->id . '-' . md5($user->token);
1366
+ }
1367
+ return $user->id . '-' . $user->token;
1368
+ }
1369
+
1370
+ function get_user_status_label($user, $html = false) {
1371
+ if (!$html)
1372
+ return TNP_User::get_status_label($user->status);
1373
+
1374
+ $label = TNP_User::get_status_label($user->status);
1375
+ $class = 'unknown';
1376
+ switch ($user->status) {
1377
+ case TNP_User::STATUS_NOT_CONFIRMED: $class = 'not-confirmed';
1378
+ break;
1379
+ case TNP_User::STATUS_CONFIRMED: $class = 'confirmed';
1380
+ break;
1381
+ case TNP_User::STATUS_UNSUBSCRIBED: $class = 'unsubscribed';
1382
+ break;
1383
+ case TNP_User::STATUS_BOUNCED: $class = 'bounced';
1384
+ break;
1385
+ case TNP_User::STATUS_COMPLAINED: $class = 'complained';
1386
+ break;
1387
+ }
1388
+ return '<span class="' . $class . '">' . esc_html($label) . '</span>';
1389
+ }
1390
+
1391
+ /**
1392
+ * Return the user identified by the "nk" parameter (POST or GET).
1393
+ * If no user can be found or the token is not matching, returns null.
1394
+ * If die_on_fail is true it dies instead of return null.
1395
+ *
1396
+ * @param bool $die_on_fail
1397
+ * @return TNP_User
1398
+ */
1399
+ function get_user_from_request($die_on_fail = false, $context = '') {
1400
+ $id = 0;
1401
+ if (isset($_REQUEST['nk'])) {
1402
+ list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1403
+ }
1404
+ $user = $this->get_user($id);
1405
+
1406
+ if ($user == null) {
1407
+ if ($die_on_fail) {
1408
+ die(__('No subscriber found.', 'newsletter'));
1409
+ } else {
1410
+ return $this->get_user_from_logged_in_user();
1411
+ }
1412
+ }
1413
+
1414
+ if ($context == 'preconfirm') {
1415
+ $user_token = md5($user->token);
1416
+ } else {
1417
+ $user_token = $user->token;
1418
+ }
1419
+
1420
+ if ($token != $user_token) {
1421
+ if ($die_on_fail) {
1422
+ die(__('No subscriber found.', 'newsletter'));
1423
+ } else {
1424
+ return $this->get_user_from_logged_in_user();
1425
+ }
1426
+ }
1427
+ return $user;
1428
+ }
1429
+
1430
+ function get_user_from_logged_in_user() {
1431
+ if (is_user_logged_in()) {
1432
+ return $this->get_user_by_wp_user_id(get_current_user_id());
1433
+ }
1434
+ return null;
1435
+ }
1436
+
1437
+ function get_user_count($refresh = false) {
1438
+ global $wpdb;
1439
+ $user_count = get_transient('newsletter_user_count');
1440
+ if ($user_count === false || $refresh) {
1441
+ $user_count = $wpdb->get_var("select count(*) from " . NEWSLETTER_USERS_TABLE . " where status='C'");
1442
+ set_transient('newsletter_user_count', $user_count, DAY_IN_SECONDS);
1443
+ }
1444
+ return $user_count;
1445
+ }
1446
+
1447
+ function get_profile($id, $language = '') {
1448
+ return TNP_Profile_Service::get_profile_by_id($id, $language);
1449
+ }
1450
+
1451
+ /**
1452
+ * @param string $language The language for the list labels (it does not affect the lists returned)
1453
+ * @return TNP_Profile[]
1454
+ */
1455
+ function get_profiles($language = '') {
1456
+ return TNP_Profile_Service::get_profiles($language);
1457
+ }
1458
+
1459
+ /**
1460
+ * Returns a list of TNP_Profile which are public.
1461
+ *
1462
+ * @staticvar array $profiles
1463
+ * @param string $language
1464
+ * @return TNP_Profile[]
1465
+ */
1466
+ function get_profiles_public($language = '') {
1467
+ static $profiles = [];
1468
+ if (isset($profiles[$language])) {
1469
+ return $profiles[$language];
1470
+ }
1471
+
1472
+ $profiles[$language] = [];
1473
+ $all = $this->get_profiles($language);
1474
+ foreach ($all as $profile) {
1475
+ if ($profile->is_private())
1476
+ continue;
1477
+
1478
+ $profiles[$language]['' . $profile->id] = $profile;
1479
+ }
1480
+ return $profiles[$language];
1481
+ }
1482
+
1483
+ /**
1484
+ * Really bad name!
1485
+ * @staticvar array $profiles
1486
+ * @param type $language
1487
+ * @return array
1488
+ */
1489
+ function get_profiles_for_profile($language = '') {
1490
+ static $profiles = [];
1491
+ if (isset($profiles[$language])) {
1492
+ return $profiles[$language];
1493
+ }
1494
+
1495
+ $profiles[$language] = [];
1496
+ $all = $this->get_profiles($language);
1497
+ foreach ($all as $profile) {
1498
+ if (!$profile->show_on_profile())
1499
+ continue;
1500
+
1501
+ $profiles[$language]['' . $profile->id] = $profile;
1502
+ }
1503
+ return $profiles[$language];
1504
+ }
1505
+
1506
+ /**
1507
+ * @param string $language The language for the list labels (it does not affect the lists returned)
1508
+ * @return TNP_List[]
1509
+ */
1510
+ function get_lists($language = '') {
1511
+ static $lists = array();
1512
+ if (isset($lists[$language])) {
1513
+ return $lists[$language];
1514
+ }
1515
+
1516
+ $lists[$language] = array();
1517
+ $data = NewsletterSubscription::instance()->get_options('lists', $language);
1518
+ for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
1519
+ if (empty($data['list_' . $i])) {
1520
+ continue;
1521
+ }
1522
+ $list = $this->create_tnp_list_from_db_lists_array($data, $i);
1523
+
1524
+ $lists[$language]['' . $list->id] = $list;
1525
+ }
1526
+ return $lists[$language];
1527
+ }
1528
+
1529
+ public function create_tnp_list_from_db_lists_array($db_lists_array, $list_id) {
1530
+
1531
+ $list = new TNP_List();
1532
+ $list->name = $db_lists_array['list_' . $list_id];
1533
+ $list->id = $list_id;
1534
+
1535
+ // New format
1536
+ if (isset($db_lists_array['list_' . $list_id . '_subscription'])) {
1537
+ $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1538
+ $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1539
+ $list->checked = $db_lists_array['list_' . $list_id . '_subscription'] == 2;
1540
+ $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_subscription']) && !$list->forced;
1541
+ $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_profile']);
1542
+ } else {
1543
+ $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1544
+ $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1545
+ $list->checked = !empty($db_lists_array['list_' . $list_id . '_checked']);
1546
+ $list->show_on_subscription = $db_lists_array['list_' . $list_id . '_status'] == 2 && !$list->forced;
1547
+ $list->show_on_profile = $db_lists_array['list_' . $list_id . '_status'] == 1 || $db_lists_array['list_' . $list_id . '_status'] == 2;
1548
+ }
1549
+ if (empty($db_lists_array['list_' . $list_id . '_languages'])) {
1550
+ $list->languages = array();
1551
+ } else {
1552
+ $list->languages = $db_lists_array['list_' . $list_id . '_languages'];
1553
+ }
1554
+
1555
+ return $list;
1556
+ }
1557
+
1558
+ /**
1559
+ * Returns an array of TNP_List objects of lists that are public.
1560
+ * @return TNP_List[]
1561
+ */
1562
+ function get_lists_public($language = '') {
1563
+ static $lists = array();
1564
+ if (isset($lists[$language])) {
1565
+ return $lists[$language];
1566
+ }
1567
+
1568
+ $lists[$language] = array();
1569
+ $all = $this->get_lists($language);
1570
+ foreach ($all as $list) {
1571
+ if ($list->status == TNP_List::STATUS_PRIVATE) {
1572
+ continue;
1573
+ }
1574
+ $lists[$language]['' . $list->id] = $list;
1575
+ }
1576
+ return $lists[$language];
1577
+ }
1578
+
1579
+ /**
1580
+ * Lists to be shown on subscription form.
1581
+ *
1582
+ * @return TNP_List[]
1583
+ */
1584
+ function get_lists_for_subscription($language = '') {
1585
+ static $lists = array();
1586
+ if (isset($lists[$language])) {
1587
+ return $lists[$language];
1588
+ }
1589
+
1590
+ $lists[$language] = array();
1591
+ $all = $this->get_lists($language);
1592
+ foreach ($all as $list) {
1593
+ if (!$list->show_on_subscription) {
1594
+ continue;
1595
+ }
1596
+ $lists[$language]['' . $list->id] = $list;
1597
+ }
1598
+ return $lists[$language];
1599
+ }
1600
+
1601
+ /**
1602
+ * Returns the lists to be shown in the profile page. The list is associative with
1603
+ * the list ID as key.
1604
+ *
1605
+ * @return TNP_List[]
1606
+ */
1607
+ function get_lists_for_profile($language = '') {
1608
+ static $lists = array();
1609
+ if (isset($lists[$language])) {
1610
+ return $lists[$language];
1611
+ }
1612
+
1613
+ $lists[$language] = array();
1614
+ $all = $this->get_lists($language);
1615
+ foreach ($all as $list) {
1616
+ if (!$list->show_on_profile) {
1617
+ continue;
1618
+ }
1619
+ $lists[$language]['' . $list->id] = $list;
1620
+ }
1621
+ return $lists[$language];
1622
+ }
1623
+
1624
+ /**
1625
+ * Returns the list object or null if not found.
1626
+ *
1627
+ * @param int $id
1628
+ * @return TNP_List
1629
+ */
1630
+ function get_list($id, $language = '') {
1631
+ $lists = $this->get_lists($language);
1632
+ if (!isset($lists['' . $id])) {
1633
+ return null;
1634
+ }
1635
+
1636
+ return $lists['' . $id];
1637
+ }
1638
+
1639
+ /**
1640
+ * NEVER CHANGE THIS METHOD SIGNATURE, USER BY THIRD PARTY PLUGINS.
1641
+ *
1642
+ * Saves a new user on the database. Return false if the email (that must be unique) is already
1643
+ * there. For a new users set the token and creation time if not passed.
1644
+ *
1645
+ * @param array $user
1646
+ * @return TNP_User|array|boolean Returns the subscriber reloaded from DB in the specified format. Flase on failure (duplicate email).
1647
+ */
1648
+ function save_user($user, $return_format = OBJECT) {
1649
+ if (is_object($user)) {
1650
+ $user = (array) $user;
1651
+ }
1652
+ if (empty($user['id'])) {
1653
+ $existing = $this->get_user($user['email']);
1654
+ if ($existing != null) {
1655
+ return false;
1656
+ }
1657
+ if (empty($user['token'])) {
1658
+ $user['token'] = NewsletterModule::get_token();
1659
+ }
1660
+ }
1661
+
1662
+ // We still don't know when it happens but under some conditions, matbe external, lists are passed as NULL
1663
+ foreach ($user as $key => $value) {
1664
+ if (strpos($key, 'list_') !== 0) {
1665
+ continue;
1666
+ }
1667
+ if (is_null($value)) {
1668
+ unset($user[$key]);
1669
+ } else {
1670
+ $user[$key] = (int) $value;
1671
+ }
1672
+ }
1673
+
1674
+ // Due to the unique index on email field, this can fail.
1675
+ return $this->store->save(NEWSLETTER_USERS_TABLE, $user, $return_format);
1676
+ }
1677
+
1678
+ /**
1679
+ * Updates the user last activity timestamp.
1680
+ *
1681
+ * @global wpdb $wpdb
1682
+ * @param TNP_User $user
1683
+ */
1684
+ function update_user_last_activity($user) {
1685
+ global $wpdb;
1686
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set last_activity=%d where id=%d limit 1", time(), $user->id));
1687
+ }
1688
+
1689
+ function update_user_ip($user, $ip) {
1690
+ global $wpdb;
1691
+ // Only if changed
1692
+ $r = $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set ip=%s, geo=0 where ip<>%s and id=%d limit 1", $ip, $ip, $user->id));
1693
+ }
1694
+
1695
+ /**
1696
+ * Finds single style blocks and adds a style attribute to every HTML tag with a class exactly matching the rules in the style
1697
+ * block. HTML tags can use the attribute "inline-class" to exact match a style rules if they need a composite class definition.
1698
+ *
1699
+ * @param string $content
1700
+ * @param boolean $strip_style_blocks
1701
+ * @return string
1702
+ */
1703
+ function inline_css($content, $strip_style_blocks = false) {
1704
+ $matches = array();
1705
+ // "s" skips line breaks
1706
+ $styles = preg_match('|<style>(.*?)</style>|s', $content, $matches);
1707
+ if (isset($matches[1])) {
1708
+ $style = str_replace(array("\n", "\r"), '', $matches[1]);
1709
+ $rules = array();
1710
+ preg_match_all('|\s*\.(.*?)\{(.*?)\}\s*|s', $style, $rules);
1711
+ for ($i = 0; $i < count($rules[1]); $i++) {
1712
+ $class = trim($rules[1][$i]);
1713
+ $value = trim($rules[2][$i]);
1714
+ $value = preg_replace('|\s+|', ' ', $value);
1715
+ $content = str_replace(' class="' . $class . '"', ' class="' . $class . '" style="' . $value . '"', $content);
1716
+ $content = str_replace(' inline-class="' . $class . '"', ' style="' . $value . '"', $content);
1717
+ }
1718
+ }
1719
+
1720
+ if ($strip_style_blocks) {
1721
+ return trim(preg_replace('|<style>.*?</style>|s', '', $content));
1722
+ } else {
1723
+ return $content;
1724
+ }
1725
+ }
1726
+
1727
+ /**
1728
+ * Returns a list of users marked as "test user".
1729
+ * @return TNP_User[]
1730
+ */
1731
+ function get_test_users() {
1732
+ return $this->store->get_all(NEWSLETTER_USERS_TABLE, "where test=1 and status in ('C', 'S')");
1733
+ }
1734
+
1735
+ /**
1736
+ * Deletes a subscriber and cleans up all the stats table with his correlated data.
1737
+ *
1738
+ * @global wpdb $wpdb
1739
+ * @param int|id[] $id
1740
+ */
1741
+ function delete_user($id) {
1742
+ global $wpdb;
1743
+ $id = (array) $id;
1744
+ foreach ($id as $user_id) {
1745
+ $user = $this->get_user($user_id);
1746
+ if ($user) {
1747
+ $r = $this->store->delete(NEWSLETTER_USERS_TABLE, $user_id);
1748
+ $wpdb->delete(NEWSLETTER_STATS_TABLE, array('user_id' => $user_id));
1749
+ $wpdb->delete(NEWSLETTER_SENT_TABLE, array('user_id' => $user_id));
1750
+ do_action('newsletter_user_deleted', $user);
1751
+ }
1752
+ }
1753
+
1754
+ return count($id);
1755
+ }
1756
+
1757
+ /**
1758
+ * Add to a destination URL the parameters to identify the user, the email and to show
1759
+ * an alert message, if required. The parameters are then managed by the [newsletter] shortcode.
1760
+ *
1761
+ * @param string $url If empty the standard newsletter page URL is used (usually it is empty, but sometime a custom URL has been specified)
1762
+ * @param string $message_key The message identifier
1763
+ * @param TNP_User|int $user
1764
+ * @param TNP_Email|int $email
1765
+ * @param string $alert An optional alter message to be shown. Does not work with custom URLs
1766
+ * @return string The final URL with parameters
1767
+ */
1768
+ function build_message_url($url = '', $message_key = '', $user = null, $email = null, $alert = '') {
1769
+ $params = 'nm=' . urlencode($message_key);
1770
+ $language = '';
1771
+ if ($user) {
1772
+ if (!is_object($user)) {
1773
+ $user = $this->get_user($user);
1774
+ }
1775
+ if ($message_key == 'confirmation') {
1776
+ $params .= '&nk=' . urlencode($this->get_user_key($user, 'preconfirm'));
1777
+ } else {
1778
+ $params .= '&nk=' . urlencode($this->get_user_key($user));
1779
+ }
1780
+
1781
+ $language = $this->get_user_language($user);
1782
+ }
1783
+
1784
+ if ($email) {
1785
+ if (!is_object($email)) {
1786
+ $email = $this->get_email($email);
1787
+ }
1788
+ $params .= '&nek=' . urlencode($this->get_email_key($email));
1789
+ }
1790
+
1791
+ if ($alert) {
1792
+ $params .= '&alert=' . urlencode($alert);
1793
+ }
1794
+
1795
+ if (empty($url)) {
1796
+ $url = Newsletter::instance()->get_newsletter_page_url($language);
1797
+ }
1798
+
1799
+ return self::add_qs($url, $params, false);
1800
+ }
1801
+
1802
+ /**
1803
+ * Builds a standard Newsletter action URL for the specified action.
1804
+ *
1805
+ * @param string $action
1806
+ * @param TNP_User $user
1807
+ * @param TNP_Email $email
1808
+ * @return string
1809
+ */
1810
+ function build_action_url($action, $user = null, $email = null) {
1811
+ $url = $this->add_qs($this->get_home_url(), 'na=' . urlencode($action));
1812
+ //$url = $this->add_qs(admin_url('admin-ajax.php'), 'action=newsletter&na=' . urlencode($action));
1813
+ if ($user) {
1814
+ $url .= '&nk=' . urlencode($this->get_user_key($user));
1815
+ }
1816
+ if ($email) {
1817
+ $url .= '&nek=' . urlencode($this->get_email_key($email));
1818
+ }
1819
+ return $url;
1820
+ }
1821
+
1822
+ function get_subscribe_url() {
1823
+ return $this->build_action_url('s');
1824
+ }
1825
+
1826
+ function clean_stats_table() {
1827
+ global $wpdb;
1828
+ $this->logger->info('Cleaning up stats table');
1829
+ $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
1830
+ $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
1831
+ }
1832
+
1833
+ function clean_sent_table() {
1834
+ global $wpdb;
1835
+ $this->logger->info('Cleaning up sent table');
1836
+ $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
1837
+ $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
1838
+ }
1839
+
1840
+ function clean_user_logs_table() {
1841
+ //global $wpdb;
1842
+ }
1843
+
1844
+ function clean_tables() {
1845
+ $this->clean_sent_table();
1846
+ $this->clean_stats_table();
1847
+ $this->clean_user_logs_table();
1848
+ }
1849
+
1850
+ function anonymize_ip($ip) {
1851
+ if (empty($ip)) {
1852
+ return $ip;
1853
+ }
1854
+ $parts = explode('.', $ip);
1855
+ array_pop($parts);
1856
+ return implode('.', $parts) . '.0';
1857
+ }
1858
+
1859
+ function process_ip($ip) {
1860
+
1861
+ $option = Newsletter::instance()->options['ip'];
1862
+ if (empty($option)) {
1863
+ return $ip;
1864
+ }
1865
+ if ($option == 'anonymize') {
1866
+ return $this->anonymize_ip($ip);
1867
+ }
1868
+ return '';
1869
+ }
1870
+
1871
+ function anonymize_user($id) {
1872
+ global $wpdb;
1873
+ $user = $this->get_user($id);
1874
+ if (!$user) {
1875
+ return null;
1876
+ }
1877
+
1878
+ $user->name = '';
1879
+ $user->surname = '';
1880
+ $user->ip = $this->anonymize_ip($user->ip);
1881
+
1882
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1883
+ $field = 'profile_' . $i;
1884
+ $user->$field = '';
1885
+ }
1886
+
1887
+ // [TODO] Status?
1888
+ $user->status = TNP_User::STATUS_UNSUBSCRIBED;
1889
+ $user->email = $user->id . '@anonymi.zed';
1890
+
1891
+ $user = $this->save_user($user);
1892
+
1893
+ return $user;
1894
+ }
1895
+
1896
+ /**
1897
+ * Changes a user status. Accept a user object, user id or user email.
1898
+ *
1899
+ * @param TNP_User $user
1900
+ * @param string $status
1901
+ * @return TNP_User
1902
+ */
1903
+ function set_user_status($user, $status) {
1904
+ global $wpdb;
1905
+
1906
+ $this->logger->debug('Status change to ' . $status . ' of subscriber ' . $user->id . ' from ' . $_SERVER['REQUEST_URI']);
1907
+
1908
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set status=%s where id=%d limit 1", $status, $user->id));
1909
+ $user->status = $status;
1910
+ return $this->get_user($user);
1911
+ }
1912
+
1913
+ /**
1914
+ *
1915
+ * @global wpdb $wpdb
1916
+ * @param TNP_User $user
1917
+ * @return TNP_User
1918
+ */
1919
+ function refresh_user_token($user) {
1920
+ global $wpdb;
1921
+
1922
+ $token = $this->get_token();
1923
+
1924
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set token=%s where id=%d limit 1", $token, $user->id));
1925
+ $user->token = $token;
1926
+ }
1927
+
1928
+ /**
1929
+ * Create a log entry with the meaningful user data.
1930
+ *
1931
+ * @global wpdb $wpdb
1932
+ * @param TNP_User $user
1933
+ * @param string $source
1934
+ * @return type
1935
+ */
1936
+ function add_user_log($user, $source = '') {
1937
+ global $wpdb;
1938
+
1939
+ $lists = $this->get_lists_public();
1940
+ foreach ($lists as $list) {
1941
+ $field_name = 'list_' . $list->id;
1942
+ $data[$field_name] = $user->$field_name;
1943
+ }
1944
+ $data['status'] = $user->status;
1945
+ $ip = $this->get_remote_ip();
1946
+ $ip = $this->process_ip($ip);
1947
+ $this->store->save($wpdb->prefix . 'newsletter_user_logs', array('ip' => $ip, 'user_id' => $user->id, 'source' => $source, 'created' => time(), 'data' => json_encode($data)));
1948
+ }
1949
+
1950
+ /**
1951
+ *
1952
+ * @global wpdb $wpdb
1953
+ * @param TNP_User $user
1954
+ * @param int $list
1955
+ * @param type $value
1956
+ */
1957
+ function set_user_list($user, $list, $value) {
1958
+ global $wpdb;
1959
+
1960
+ $list = (int) $list;
1961
+ $value = $value ? 1 : 0;
1962
+ $r = $wpdb->update(NEWSLETTER_USERS_TABLE, array('list_' . $list => $value), array('id' => $user->id));
1963
+ }
1964
+
1965
+ function set_user_field($id, $field, $value) {
1966
+ $this->store->set_field(NEWSLETTER_USERS_TABLE, $id, $field, $value);
1967
+ }
1968
+
1969
+ function set_user_wp_user_id($user_id, $wp_user_id) {
1970
+ $this->store->set_field(NEWSLETTER_USERS_TABLE, $user_id, 'wp_user_id', $wp_user_id);
1971
+ }
1972
+
1973
+ /**
1974
+ *
1975
+ * @param int $wp_user_id
1976
+ * @param string $format
1977
+ * @return TNP_User
1978
+ */
1979
+ function get_user_by_wp_user_id($wp_user_id, $format = OBJECT) {
1980
+ return $this->store->get_single_by_field(NEWSLETTER_USERS_TABLE, 'wp_user_id', $wp_user_id, $format);
1981
+ }
1982
+
1983
+ /**
1984
+ * Returns the user language IF there is a supported mutilanguage plugin installed.
1985
+ * @param TNP_User $user
1986
+ * @return string Language code or empty
1987
+ */
1988
+ function get_user_language($user) {
1989
+ if ($user && $this->is_multilanguage()) {
1990
+ return $user->language;
1991
+ }
1992
+ return '';
1993
+ }
1994
+
1995
+ /**
1996
+ * Replaces every possible Newsletter tag ({...}) in a piece of text or HTML.
1997
+ *
1998
+ * @global wpdb $wpdb
1999
+ * @param string $text
2000
+ * @param mixed $user Can be an object, associative array or id
2001
+ * @param mixed $email Can be an object, associative array or id
2002
+ * @param type $referrer
2003
+ * @return type
2004
+ */
2005
+ function replace($text, $user = null, $email = null, $referrer = null) {
2006
+ global $wpdb;
2007
+
2008
+ if (strpos($text, '<p') !== false) {
2009
+ $esc_html = true;
2010
+ } else {
2011
+ $esc_html = false;
2012
+ }
2013
+
2014
+ static $home_url = false;
2015
+
2016
+ if (!$home_url) {
2017
+ $home_url = home_url('/');
2018
+ }
2019
+
2020
+ //$this->logger->debug('Replace start');
2021
+ if ($user !== null && !is_object($user)) {
2022
+ if (is_array($user)) {
2023
+ $user = (object) $user;
2024
+ } else if (is_numeric($user)) {
2025
+ $user = $this->get_user($user);
2026
+ } else {
2027
+ $user = null;
2028
+ }
2029
+ }
2030
+
2031
+ if ($email !== null && !is_object($email)) {
2032
+ if (is_array($email)) {
2033
+ $email = (object) $email;
2034
+ } else if (is_numeric($email)) {
2035
+ $email = $this->get_email($email);
2036
+ } else {
2037
+ $email = null;
2038
+ }
2039
+ }
2040
+
2041
+ $initial_language = $this->get_current_language();
2042
+
2043
+ if ($user && $user->language) {
2044
+ $this->switch_language($user->language);
2045
+ }
2046
+
2047
+
2048
+ $text = apply_filters('newsletter_replace', $text, $user, $email, $esc_html);
2049
+
2050
+ $text = $this->replace_url($text, 'blog_url', $home_url);
2051
+ $text = $this->replace_url($text, 'home_url', $home_url);
2052
+
2053
+ $text = str_replace('{blog_title}', html_entity_decode(get_bloginfo('name')), $text);
2054
+ $text = str_replace('{blog_description}', get_option('blogdescription'), $text);
2055
+
2056
+ $text = $this->replace_date($text);
2057
+
2058
+ if ($user) {
2059
+ //$this->logger->debug('Replace with user ' . $user->id);
2060
+ $nk = $this->get_user_key($user);
2061
+ $options_profile = NewsletterSubscription::instance()->get_options('profile', $this->get_user_language($user));
2062
+ $text = str_replace('{email}', $user->email, $text);
2063
+ $name = apply_filters('newsletter_replace_name', $user->name, $user);
2064
+ if (empty($name)) {
2065
+ $text = str_replace(' {name}', '', $text);
2066
+ $text = str_replace('{name}', '', $text);
2067
+ } else {
2068
+ $text = str_replace('{name}', esc_html($name), $text);
2069
+ }
2070
+
2071
+ switch ($user->sex) {
2072
+ case 'm': $text = str_replace('{title}', $options_profile['title_male'], $text);
2073
+ break;
2074
+ case 'f': $text = str_replace('{title}', $options_profile['title_female'], $text);
2075
+ break;
2076
+ //case 'n': $text = str_replace('{title}', $options_profile['title_none'], $text);
2077
+ // break;
2078
+ default:
2079
+ $text = str_replace('{title}', $options_profile['title_none'], $text);
2080
+ //$text = str_replace('{title}', '', $text);
2081
+ }
2082
+
2083
+
2084
+ // Deprecated
2085
+ $text = str_replace('{surname}', esc_html($user->surname), $text);
2086
+ $text = str_replace('{last_name}', esc_html($user->surname), $text);
2087
+
2088
+ $full_name = esc_html(trim($user->name . ' ' . $user->surname));
2089
+ if (empty($full_name)) {
2090
+ $text = str_replace(' {full_name}', '', $text);
2091
+ $text = str_replace('{full_name}', '', $text);
2092
+ } else {
2093
+ $text = str_replace('{full_name}', $full_name, $text);
2094
+ }
2095
+
2096
+ $text = str_replace('{token}', $user->token, $text);
2097
+ $text = str_replace('%7Btoken%7D', $user->token, $text);
2098
+ $text = str_replace('{id}', $user->id, $text);
2099
+ $text = str_replace('%7Bid%7D', $user->id, $text);
2100
+ $text = str_replace('{ip}', $user->ip, $text);
2101
+ $text = str_replace('{key}', $nk, $text);
2102
+ $text = str_replace('%7Bkey%7D', $nk, $text);
2103
+
2104
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
2105
+ $p = 'profile_' . $i;
2106
+ $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
2107
+ }
2108
+
2109
+ $base = (empty($this->options_main['url']) ? get_option('home') : $this->options_main['url']);
2110
+ $id_token = '&amp;ni=' . $user->id . '&amp;nt=' . $user->token;
2111
+
2112
+ $text = $this->replace_url($text, 'subscription_confirm_url', $this->build_action_url('c', $user));
2113
+ $text = $this->replace_url($text, 'activation_url', $this->build_action_url('c', $user));
2114
+
2115
+ // Obsolete.
2116
+ $text = $this->replace_url($text, 'FOLLOWUP_SUBSCRIPTION_URL', self::add_qs($base, 'nm=fs' . $id_token));
2117
+ $text = $this->replace_url($text, 'FOLLOWUP_UNSUBSCRIPTION_URL', self::add_qs($base, 'nm=fu' . $id_token));
2118
+
2119
+ $text = $this->replace_url($text, 'UNLOCK_URL', $this->build_action_url('ul', $user));
2120
+ } else {
2121
+ //$this->logger->debug('Replace without user');
2122
+ $text = $this->replace_url($text, 'subscription_confirm_url', '#');
2123
+ $text = $this->replace_url($text, 'activation_url', '#');
2124
+ }
2125
+
2126
+ if ($email) {
2127
+ //$this->logger->debug('Replace with email ' . $email->id);
2128
+ $nek = $this->get_email_key($email);
2129
+ $text = str_replace('{email_id}', $email->id, $text);
2130
+ $text = str_replace('{email_key}', $nek, $text);
2131
+ $text = str_replace('{email_subject}', $email->subject, $text);
2132
+ // Deprecated
2133
+ $text = str_replace('{subject}', $email->subject, $text);
2134
+ $text = $this->replace_url($text, 'email_url', $this->build_action_url('v', $user) . '&id=' . $email->id);
2135
+ } else {
2136
+ //$this->logger->debug('Replace without email');
2137
+ $text = $this->replace_url($text, 'email_url', '#');
2138
+ }
2139
+
2140
+ if (strpos($text, '{subscription_form}') !== false) {
2141
+ $text = str_replace('{subscription_form}', NewsletterSubscription::instance()->get_subscription_form($referrer), $text);
2142
+ } else {
2143
+ for ($i = 1; $i <= 10; $i++) {
2144
+ if (strpos($text, "{subscription_form_$i}") !== false) {
2145
+ $text = str_replace("{subscription_form_$i}", NewsletterSubscription::instance()->get_form($i), $text);
2146
+ break;
2147
+ }
2148
+ }
2149
+ }
2150
+
2151
+ // Company info
2152
+ // TODO: Move to another module
2153
+ $options = Newsletter::instance()->get_options('info');
2154
+ $text = str_replace('{company_address}', $options['footer_contact'], $text);
2155
+ $text = str_replace('{company_name}', $options['footer_title'], $text);
2156
+ $text = str_replace('{company_legal}', $options['footer_legal'], $text);
2157
+
2158
+ $this->switch_language($initial_language);
2159
+ //$this->logger->debug('Replace end');
2160
+ return $text;
2161
+ }
2162
+
2163
+ function replace_date($text) {
2164
+ $text = str_replace('{date}', date_i18n(get_option('date_format')), $text);
2165
+
2166
+ // Date processing
2167
+ $x = 0;
2168
+ while (($x = strpos($text, '{date_', $x)) !== false) {
2169
+ $y = strpos($text, '}', $x);
2170
+ if ($y === false)
2171
+ continue;
2172
+ $f = substr($text, $x + 6, $y - $x - 6);
2173
+ $text = substr($text, 0, $x) . date_i18n($f) . substr($text, $y + 1);
2174
+ }
2175
+ return $text;
2176
+ }
2177
+
2178
+ function replace_url($text, $tag, $url) {
2179
+ static $home = false;
2180
+ if (!$home) {
2181
+ $home = trailingslashit(home_url());
2182
+ }
2183
+ $tag_lower = strtolower($tag);
2184
+ $text = str_replace('http://{' . $tag_lower . '}', $url, $text);
2185
+ $text = str_replace('https://{' . $tag_lower . '}', $url, $text);
2186
+ $text = str_replace($home . '{' . $tag_lower . '}', $url, $text);
2187
+ $text = str_replace($home . '%7B' . $tag_lower . '%7D', $url, $text);
2188
+ $text = str_replace('{' . $tag_lower . '}', $url, $text);
2189
+ $text = str_replace('%7B' . $tag_lower . '%7D', $url, $text);
2190
+
2191
+ $url_encoded = urlencode($url);
2192
+ $text = str_replace('%7B' . $tag_lower . '_encoded%7D', $url_encoded, $text);
2193
+ $text = str_replace('{' . $tag_lower . '_encoded}', $url_encoded, $text);
2194
+
2195
+ // for compatibility
2196
+ $text = str_replace($home . $tag, $url, $text);
2197
+
2198
+ return $text;
2199
+ }
2200
+
2201
+ public static function antibot_form_check($captcha = false) {
2202
+
2203
+ if (defined('NEWSLETTER_ANTIBOT') && !NEWSLETTER_ANTIBOT) {
2204
+ return true;
2205
+ }
2206
+
2207
+ if (strtolower($_SERVER['REQUEST_METHOD']) != 'post') {
2208
+ return false;
2209
+ }
2210
+
2211
+ if (!isset($_POST['ts']) || time() - $_POST['ts'] > 60) {
2212
+ return false;
2213
+ }
2214
+
2215
+ if ($captcha) {
2216
+ $n1 = (int) $_POST['n1'];
2217
+ if (empty($n1)) {
2218
+ return false;
2219
+ }
2220
+ $n2 = (int) $_POST['n2'];
2221
+ if (empty($n2)) {
2222
+ return false;
2223
+ }
2224
+ $n3 = (int) $_POST['n3'];
2225
+ if ($n1 + $n2 != $n3) {
2226
+ return false;
2227
+ }
2228
+ }
2229
+
2230
+ return true;
2231
+ }
2232
+
2233
+ public static function request_to_antibot_form($submit_label = 'Continue...', $captcha = false) {
2234
+ header('Content-Type: text/html;charset=UTF-8');
2235
+ header('X-Robots-Tag: noindex,nofollow,noarchive');
2236
+ header('Cache-Control: no-cache,no-store,private');
2237
+ echo "<!DOCTYPE html>\n";
2238
+ echo '<html><head>'
2239
+ . '<style type="text/css">'
2240
+ . '.tnp-captcha {text-align: center; margin: 200px auto 0 auto !important; max-width: 300px !important; padding: 10px !important; font-family: "Open Sans", sans-serif; background: #ECF0F1; border-radius: 5px; padding: 50px !important; border: none !important;}'
2241
+ . 'p {text-align: center; padding: 10px; color: #7F8C8D;}'
2242
+ . 'input[type=text] {width: 50px; padding: 10px 10px; border: none; border-radius: 2px; margin: 0px 5px;}'
2243
+ . 'input[type=submit] {text-align: center; border: none; padding: 10px 15px; font-family: "Open Sans", sans-serif; background-color: #27AE60; color: white; cursor: pointer;}'
2244
+ . '</style>'
2245
+ . '</head><body>';
2246
+ echo '<form method="post" action="https://www.domain.tld" id="form">';
2247
+ echo '<div style="width: 1px; height: 1px; overflow: hidden">';
2248
+ foreach ($_REQUEST as $name => $value) {
2249
+ if ($name == 'submit')
2250
+ continue;
2251
+ if (is_array($value)) {
2252
+ foreach ($value as $element) {
2253
+ echo '<input type="text" name="';
2254
+ echo esc_attr($name);
2255
+ echo '[]" value="';
2256
+ echo esc_attr(stripslashes($element));
2257
+ echo '">';
2258
+ }
2259
+ } else {
2260
+ echo '<input type="hidden" name="', esc_attr($name), '" value="', esc_attr(stripslashes($value)), '">';
2261
+ }
2262
+ }
2263
+ if (isset($_SERVER['HTTP_REFERER'])) {
2264
+ echo '<input type="hidden" name="nhr" value="' . esc_attr($_SERVER['HTTP_REFERER']) . '">';
2265
+ }
2266
+ echo '<input type="hidden" name="ts" value="' . time() . '">';
2267
+ echo '</div>';
2268
+
2269
+ if ($captcha) {
2270
+ echo '<div class="tnp-captcha">';
2271
+ echo '<p>', __('Math question', 'newsletter'), '</p>';
2272
+ echo '<input type="text" name="n1" value="', rand(1, 9), '" readonly style="width: 50px">';
2273
+ echo '+';
2274
+ echo '<input type="text" name="n2" value="', rand(1, 9), '" readonly style="width: 50px">';
2275
+ echo '=';
2276
+ echo '<input type="text" name="n3" value="?" style="width: 50px">';
2277
+ echo '<br><br>';
2278
+ echo '<input type="submit" value="', esc_attr($submit_label), '">';
2279
+ echo '</div>';
2280
+ }
2281
+ echo '<noscript><input type="submit" value="';
2282
+ echo esc_attr($submit_label);
2283
+ echo '"></noscript></form>';
2284
+ echo '<script>';
2285
+ echo 'document.getElementById("form").action="' . home_url('/') . '";';
2286
+ if (!$captcha) {
2287
+ echo 'document.getElementById("form").submit();';
2288
+ }
2289
+ echo '</script>';
2290
+ echo '</body></html>';
2291
+ die();
2292
+ }
2293
+
2294
+ static function extract_body($html) {
2295
+ $x = stripos($html, '<body');
2296
+ if ($x !== false) {
2297
+ $x = strpos($html, '>', $x);
2298
+ $y = strpos($html, '</body>');
2299
+ return substr($html, $x + 1, $y - $x - 1);
2300
+ } else {
2301
+ return $html;
2302
+ }
2303
+ }
2304
+
2305
+ /** Returns a percentage as string */
2306
+ static function percent($value, $total) {
2307
+ if ($total == 0)
2308
+ return '-';
2309
+ return sprintf("%.2f", $value / $total * 100) . '%';
2310
+ }
2311
+
2312
+ /** Returns a percentage as integer value */
2313
+ static function percentValue($value, $total) {
2314
+ if ($total == 0)
2315
+ return 0;
2316
+ return round($value / $total * 100);
2317
+ }
2318
+
2319
+ /**
2320
+ * Takes in a variable and checks if object, array or scalar and return the integer representing
2321
+ * a database record id.
2322
+ *
2323
+ * @param mixed $var
2324
+ * @return in
2325
+ */
2326
+ static function to_int_id($var) {
2327
+ if (is_object($var)) {
2328
+ return (int) $var->id;
2329
+ }
2330
+ if (is_array($var)) {
2331
+ return (int) $var['id'];
2332
+ }
2333
+ return (int) $var;
2334
+ }
2335
+
2336
+ static function to_array($text) {
2337
+ $text = trim($text);
2338
+ if (empty($text)) {
2339
+ return array();
2340
+ }
2341
+ $text = preg_split("/\\r\\n/", $text);
2342
+ $text = array_map('trim', $text);
2343
+ $text = array_map('strtolower', $text);
2344
+ $text = array_filter($text);
2345
+
2346
+ return $text;
2347
+ }
2348
+
2349
+ static function sanitize_ip($ip) {
2350
+ if (empty($ip)) {
2351
+ return '';
2352
+ }
2353
+ $ip = preg_replace('/[^0-9a-fA-F:., ]/', '', trim($ip));
2354
+ if (strlen($ip) > 50)
2355
+ $ip = substr($ip, 0, 50);
2356
+
2357
+ // When more than one IP is present due to firewalls, proxies, and so on. The first one should be the origin.
2358
+ if (strpos($ip, ',') !== false) {
2359
+ list($ip, $tail) = explode(',', $ip, 2);
2360
+ }
2361
+ return $ip;
2362
+ }
2363
+
2364
+ static function get_remote_ip() {
2365
+ $ip = '';
2366
+ if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2367
+ $ip = $_SERVER['HTTP_X_REAL_IP'];
2368
+ } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2369
+ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
2370
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
2371
+ $ip = $_SERVER['REMOTE_ADDR'];
2372
+ }
2373
+ return self::sanitize_ip($ip);
2374
+ }
2375
+
2376
+ static function get_signature($text) {
2377
+ $key = NewsletterStatistics::instance()->options['key'];
2378
+ return md5($text . $key);
2379
+ }
2380
+
2381
+ static function check_signature($text, $signature) {
2382
+ if (empty($signature)) {
2383
+ return false;
2384
+ }
2385
+ $key = NewsletterStatistics::instance()->options['key'];
2386
+ return md5($text . $key) === $signature;
2387
+ }
2388
+
2389
+ static function get_home_url() {
2390
+ static $url = false;
2391
+ if (!$url) {
2392
+ $url = home_url('/');
2393
+ }
2394
+ return $url;
2395
+ }
2396
+
2397
+ static function clean_eol($text) {
2398
+ $text = str_replace("\r\n", "\n", $text);
2399
+ $text = str_replace("\r", "\n", $text);
2400
+ $text = str_replace("\n", "\r\n", $text);
2401
+ return $text;
2402
+ }
2403
+
2404
+ function set_current_language($language) {
2405
+ self::$current_language = $language;
2406
+ }
2407
+
2408
+ /**
2409
+ * Return the current language code. Optionally, if a user is passed and it has a language
2410
+ * the user language is returned.
2411
+ * If there is no language available, an empty string is returned.
2412
+ *
2413
+ * @param TNP_User $user
2414
+ * @return string The language code
2415
+ */
2416
+ function get_current_language($user = null) {
2417
+
2418
+ if ($user && $user->language) {
2419
+ return $user->language;
2420
+ }
2421
+
2422
+ if (!empty(self::$current_language)) {
2423
+ return self::$current_language;
2424
+ }
2425
+
2426
+ // WPML
2427
+ if (class_exists('SitePress')) {
2428
+ $current_language = apply_filters('wpml_current_language', '');
2429
+ if ($current_language == 'all') {
2430
+ $current_language = '';
2431
+ }
2432
+ return $current_language;
2433
+ }
2434
+
2435
+ // Polylang
2436
+ if (function_exists('pll_current_language')) {
2437
+ return pll_current_language();
2438
+ }
2439
+
2440
+ // Trnslatepress and/or others
2441
+ $current_language = apply_filters('newsletter_current_language', '');
2442
+
2443
+ return $current_language;
2444
+ }
2445
+
2446
+ function get_default_language() {
2447
+ if (class_exists('SitePress')) {
2448
+ return $current_language = apply_filters('wpml_current_language', '');
2449
+ } else if (function_exists('pll_default_language')) {
2450
+ return pll_default_language();
2451
+ } else if (class_exists('TRP_Translate_Press')) {
2452
+ // TODO: Find the default language
2453
+ }
2454
+ return '';
2455
+ }
2456
+
2457
+ function is_all_languages() {
2458
+ return $this->get_current_language() == '';
2459
+ }
2460
+
2461
+ function is_default_language() {
2462
+ return $this->get_current_language() == $this->get_default_language();
2463
+ }
2464
+
2465
+ /**
2466
+ * Returns an array of languages with key the language code and value the language name.
2467
+ * An empty array is returned if no language is available.
2468
+ */
2469
+ function get_languages() {
2470
+ $language_options = array();
2471
+
2472
+ if (class_exists('SitePress')) {
2473
+ $languages = apply_filters('wpml_active_languages', null, ['skip_missing' => 0]);
2474
+ foreach ($languages as $language) {
2475
+ $language_options[$language['language_code']] = $language['translated_name'];
2476
+ }
2477
+
2478
+ return $language_options;
2479
+ } else if (function_exists('icl_get_languages')) {
2480
+ $languages = icl_get_languages();
2481
+ foreach ($languages as $code => $language) {
2482
+ $language_options[$code] = $language['native_name'];
2483
+ }
2484
+ return $language_options;
2485
+ }
2486
+
2487
+ return apply_filters('newsletter_languages', $language_options);
2488
+ }
2489
+
2490
+ function get_language_label($language) {
2491
+ $languages = $this->get_languages();
2492
+ if (isset($languages[$language])) {
2493
+ return $languages[$language];
2494
+ }
2495
+ return '';
2496
+ }
2497
+
2498
+ /**
2499
+ * Changes the current language usually before extracting the posts since WPML
2500
+ * does not support the language filter in the post query (or at least we didn't
2501
+ * find it).
2502
+ *
2503
+ * @param string $language
2504
+ */
2505
+ function switch_language($language) {
2506
+ if (class_exists('SitePress')) {
2507
+ if (empty($language)) {
2508
+ $language = 'all';
2509
+ }
2510
+ do_action('wpml_switch_language', $language);
2511
+ return;
2512
+ }
2513
+ }
2514
+
2515
+ function is_multilanguage() {
2516
+ return class_exists('SitePress') || function_exists('pll_default_language') || class_exists('TRP_Translate_Press');
2517
+ }
2518
+
2519
+ function get_posts($filters = array(), $language = '') {
2520
+ $current_language = $this->get_current_language();
2521
+
2522
+ // Language switch for WPML
2523
+ if ($language) {
2524
+ if (class_exists('SitePress')) {
2525
+ $this->switch_language($language);
2526
+ $filters['suppress_filters'] = false;
2527
+ }
2528
+ if (class_exists('Polylang')) {
2529
+ $filters['lang'] = $language;
2530
+ }
2531
+ }
2532
+ $posts = get_posts($filters);
2533
+ if ($language) {
2534
+ if (class_exists('SitePress')) {
2535
+ $this->switch_language($current_language);
2536
+ }
2537
+ }
2538
+ return $posts;
2539
+ }
2540
+
2541
+ function get_wp_query($filters, $langiage = '') {
2542
+ if ($language) {
2543
+ if (class_exists('SitePress')) {
2544
+ $this->switch_language($language);
2545
+ $filters['suppress_filters'] = false;
2546
+ }
2547
+ if (class_exists('Polylang')) {
2548
+ $filters['lang'] = $language;
2549
+ }
2550
+ }
2551
+
2552
+ $posts = new WP_Query($filters);
2553
+
2554
+ if ($language) {
2555
+ if (class_exists('SitePress')) {
2556
+ $this->switch_language($current_language);
2557
+ }
2558
+ }
2559
+
2560
+ return $posts;
2561
+ }
2562
+
2563
+ protected function generate_admin_notification_message($user) {
2564
+
2565
+ $message = file_get_contents(__DIR__ . '/notification.html');
2566
+
2567
+ $message = $this->replace($message, $user);
2568
+ $message = str_replace('{user_admin_url}', admin_url('admin.php?page=newsletter_users_edit&id=' . $user->id), $message);
2569
+
2570
+ return $message;
2571
+ }
2572
+
2573
+ protected function generate_admin_notification_subject($subject) {
2574
+ $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
2575
+
2576
+ return '[' . $blogname . '] ' . $subject;
2577
+ }
2578
+
2579
+ function dienow($message, $admin_message = null, $http_code = 200) {
2580
+ if ($admin_message && current_user_can('administrator')) {
2581
+ $message .= '<br><br><strong>Text below only visibile to administrators</strong><br>';
2582
+ $message .= $admin_message;
2583
+ }
2584
+ wp_die($message, $http_code);
2585
+ }
2586
+
2587
+ function dump($var) {
2588
+ if (NEWSLETTER_DEBUG) {
2589
+ var_dump($var);
2590
+ }
2591
+ }
2592
+
2593
+ function dump_die($var) {
2594
+ if (NEWSLETTER_DEBUG) {
2595
+ var_dump($var);
2596
+ die();
2597
+ }
2598
+ }
2599
+
2600
+ }
2601
+
2602
+ /**
2603
+ * Kept for compatibility.
2604
+ *
2605
+ * @param type $post_id
2606
+ * @param type $size
2607
+ * @param type $alternative
2608
+ * @return type
2609
+ */
2610
+ function nt_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2611
+ return NewsletterModule::get_post_image($post_id, $size, $alternative);
2612
+ }
2613
+
2614
+ function newsletter_get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2615
+ echo NewsletterModule::get_post_image($post_id, $size, $alternative);
2616
+ }
2617
+
2618
+ /**
2619
+ * Accepts a post or a post ID.
2620
+ *
2621
+ * @param WP_Post $post
2622
+ */
2623
+ function newsletter_the_excerpt($post, $words = 30) {
2624
+ $post = get_post($post);
2625
+ $excerpt = $post->post_excerpt;
2626
+ if (empty($excerpt)) {
2627
+ $excerpt = $post->post_content;
2628
+ $excerpt = strip_shortcodes($excerpt);
2629
+ $excerpt = wp_strip_all_tags($excerpt, true);
2630
+ }
2631
+ echo '<p>' . wp_trim_words($excerpt, $words) . '</p>';
2632
+ }
plugin.php CHANGED
@@ -4,7 +4,7 @@
4
  Plugin Name: Newsletter
5
  Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
  Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
- Version: 7.5.3
8
  Author: Stefano Lissa & The Newsletter Team
9
  Author URI: https://www.thenewsletterplugin.com
10
  Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
@@ -37,7 +37,7 @@ if (version_compare(phpversion(), '5.6', '<')) {
37
  return;
38
  }
39
 
40
- define('NEWSLETTER_VERSION', '7.5.3');
41
 
42
  global $newsletter, $wpdb;
43
 
4
  Plugin Name: Newsletter
5
  Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
  Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
+ Version: 7.5.4
8
  Author: Stefano Lissa & The Newsletter Team
9
  Author URI: https://www.thenewsletterplugin.com
10
  Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
37
  return;
38
  }
39
 
40
+ define('NEWSLETTER_VERSION', '7.5.4');
41
 
42
  global $newsletter, $wpdb;
43
 
profile/profile.php CHANGED
@@ -1,494 +1,494 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- class NewsletterProfile extends NewsletterModule {
6
-
7
- static $instance;
8
-
9
- /**
10
- * @return NewsletterProfile
11
- */
12
- static function instance() {
13
- if (self::$instance == null) {
14
- self::$instance = new NewsletterProfile();
15
- }
16
- return self::$instance;
17
- }
18
-
19
- function __construct() {
20
- parent::__construct('profile', '1.1.0');
21
- add_shortcode('newsletter_profile', array($this, 'shortcode_newsletter_profile'));
22
- add_filter('newsletter_replace', array($this, 'hook_newsletter_replace'), 10, 4);
23
- add_filter('newsletter_page_text', array($this, 'hook_newsletter_page_text'), 10, 3);
24
- add_action('newsletter_action', array($this, 'hook_newsletter_action'), 12, 3);
25
- }
26
-
27
- function hook_newsletter_action($action, $user, $email) {
28
-
29
- if (in_array($action, ['p', 'profile', 'pe', 'profile-save', 'profile_export', 'ps'])) {
30
- if (!$user || $user->status != TNP_User::STATUS_CONFIRMED) {
31
-
32
- $this->dienow(__('The subscriber was not found or is not confirmed.', 'newsletter'), '', 404);
33
- }
34
- }
35
-
36
- switch ($action) {
37
- case 'profile':
38
- case 'p':
39
- case 'pe':
40
-
41
- $profile_url = $this->build_message_url($this->options['url'], 'profile', $user, $email);
42
- $profile_url = apply_filters('newsletter_profile_url', $profile_url, $user);
43
-
44
- wp_redirect($profile_url);
45
- die();
46
-
47
- break;
48
-
49
- case 'profile-save':
50
- case 'ps':
51
- $res = $this->save_profile($user);
52
- if (is_wp_error($res)) {
53
- wp_redirect($this->build_message_url($this->options['url'], 'profile', $user, $email, $res->get_error_message()));
54
- die();
55
- }
56
-
57
- wp_redirect($this->build_message_url($this->options['url'], 'profile', $user, $email, $res));
58
- die();
59
- break;
60
-
61
- case 'profile_export':
62
- header('Content-Type: application/json;charset=UTF-8');
63
- echo $this->to_json($user);
64
- die();
65
- }
66
- }
67
-
68
- /**
69
- *
70
- * @param stdClass $user
71
- */
72
- function get_profile_export_url($user) {
73
- return $this->build_action_url('profile_export', $user);
74
- }
75
-
76
- /**
77
- * URL to the subscriber profile edit action. This URL MUST NEVER be changed by
78
- * 3rd party plugins. Plugins can change the final URL after the action has been executed using the
79
- * <code>newsletter_profile_url</code> filter.
80
- *
81
- * @param stdClass $user
82
- */
83
- function get_profile_url($user, $email = null) {
84
- return $this->build_action_url('profile', $user, $email);
85
- }
86
-
87
- function hook_newsletter_replace($text, $user, $email, $html = true) {
88
- if (!$user) {
89
- $text = $this->replace_url($text, 'PROFILE_URL', $this->build_action_url('nul'));
90
- return $text;
91
- }
92
-
93
- // Profile edit page URL and link
94
- $url = $this->get_profile_url($user, $email);
95
- $text = $this->replace_url($text, 'profile_url', $url);
96
- // Profile export URL and link
97
- $url = $this->get_profile_export_url($user);
98
- $text = $this->replace_url($text, 'profile_export_url', $url);
99
-
100
- if (strpos($text, '{profile_form}') !== false) {
101
- $text = str_replace('{profile_form}', $this->get_profile_form($user), $text);
102
- }
103
- return $text;
104
- }
105
-
106
- /**
107
- *
108
- * @param type $text
109
- * @param type $key
110
- * @param TNP_User $user
111
- * @return string
112
- */
113
- function hook_newsletter_page_text($text, $key, $user) {
114
- if ($key == 'profile') {
115
- if (!$user || $user->status == TNP_User::STATUS_UNSUBSCRIBED) {
116
- return 'Subscriber not found.';
117
- }
118
- $options = $this->get_options('main', $this->get_current_language($user));
119
- return $options['text'];
120
- }
121
- return $text;
122
- }
123
-
124
- function shortcode_newsletter_profile($attrs, $content) {
125
- $user = $this->check_user();
126
-
127
- if (empty($user)) {
128
- if (empty($content)) {
129
- return __('Subscriber not found.', 'newsletter');
130
- } else {
131
- return $content;
132
- }
133
- }
134
-
135
- return $this->get_profile_form($user);
136
- }
137
-
138
- function to_json($user) {
139
- global $wpdb;
140
-
141
-
142
- $fields = array('name', 'surname', 'sex', 'created', 'ip', 'email');
143
- $data = array(
144
- 'email' => $user->email,
145
- 'name' => $user->name,
146
- 'last_name' => $user->surname,
147
- 'gender' => $user->sex,
148
- 'created' => $user->created,
149
- 'ip' => $user->ip,
150
- );
151
-
152
- // Lists
153
- $data['lists'] = array();
154
-
155
- $lists = $this->get_lists_public();
156
- foreach ($lists as $list) {
157
- $field = 'list_' . $list->id;
158
- if ($user->$field == 1) {
159
- $data['lists'][] = $list->name;
160
- }
161
- }
162
-
163
- // Profile
164
- $options_profile = get_option('newsletter_profile', array());
165
- $data['profiles'] = array();
166
- for ($i = 1; $i < NEWSLETTER_PROFILE_MAX; $i++) {
167
- $field = 'profile_' . $i;
168
- if ($options_profile[$field . '_status'] != 1 && $options_profile[$field . '_status'] != 2) {
169
- continue;
170
- }
171
- $data['profiles'][] = array('name' => $options_profile[$field], 'value' => $user->$field);
172
- }
173
-
174
- // Newsletters
175
- if ($this->options['export_newsletters']) {
176
- $sent = $wpdb->get_results($wpdb->prepare("select * from {$wpdb->prefix}newsletter_sent where user_id=%d order by email_id asc", $user->id));
177
- $newsletters = array();
178
- foreach ($sent as $item) {
179
- $action = 'none';
180
- if ($item->open == 1) {
181
- $action = 'read';
182
- } else if ($item->open == 2) {
183
- $action = 'click';
184
- }
185
-
186
- $email = $this->get_email($item->email_id);
187
- if (!$email) {
188
- continue;
189
- }
190
- // 'id'=>$item->email_id,
191
- $newsletters[] = array('subject' => $email->subject, 'action' => $action, 'sent' => date('Y-m-d h:i:s', $email->send_on));
192
- }
193
-
194
- $data['newsletters'] = $newsletters;
195
- }
196
-
197
- $extra = apply_filters('newsletter_profile_export_extra', array());
198
-
199
- $data = array_merge($extra, $data);
200
-
201
- return json_encode($data, JSON_PRETTY_PRINT);
202
- }
203
-
204
- /**
205
- * Build the profile editing form for the specified subscriber.
206
- *
207
- * @param TNP_User $user
208
- * @return string
209
- */
210
- function get_profile_form($user) {
211
- // Do not pay attention to option name here, it's a compatibility problem
212
-
213
- $language = $this->get_user_language($user);
214
- $options = NewsletterSubscription::instance()->get_options('profile', $language);
215
-
216
- $buffer = '';
217
-
218
- $buffer .= '<div class="tnp tnp-profile">';
219
- $buffer .= '<form action="' . $this->build_action_url('ps') . '" method="post">';
220
- $buffer .= '<input type="hidden" name="nk" value="' . esc_attr($user->id . '-' . $user->token) . '">';
221
-
222
- $buffer .= '<div class="tnp-field tnp-field-email">';
223
- $buffer .= '<label>' . esc_html($options['email']) . '</label>';
224
- $buffer .= '<input class="tnp-email" type="text" name="ne" required value="' . esc_attr($user->email) . '">';
225
- $buffer .= "</div>\n";
226
-
227
-
228
- if ($options['name_status'] >= 1) {
229
- $buffer .= '<div class="tnp-field tnp-field-firstname">';
230
- $buffer .= '<label>' . esc_html($options['name']) . '</label>';
231
- $buffer .= '<input class="tnp-firstname" type="text" name="nn" value="' . esc_attr($user->name) . '"' . ($options['name_rules'] == 1 ? ' required' : '') . '>';
232
- $buffer .= "</div>\n";
233
- }
234
-
235
- if ($options['surname_status'] >= 1) {
236
- $buffer .= '<div class="tnp-field tnp-field-lastname">';
237
- $buffer .= '<label>' . esc_html($options['surname']) . '</label>';
238
- $buffer .= '<input class="tnp-lastname" type="text" name="ns" value="' . esc_attr($user->surname) . '"' . ($options['surname_rules'] == 1 ? ' required' : '') . '>';
239
- $buffer .= "</div>\n";
240
- }
241
-
242
- if ($options['sex_status'] >= 1) {
243
- $buffer .= '<div class="tnp-field tnp-field-gender">';
244
- $buffer .= '<label>' . esc_html($options['sex']) . '</label>';
245
- $buffer .= '<select name="nx" class="tnp-gender"';
246
- if ($options['sex_rules']) {
247
- $buffer .= ' required ';
248
- }
249
- $buffer .= '>';
250
- if ($options['sex_rules']) {
251
- $buffer .= '<option value=""></option>';
252
- }
253
- $buffer .= '<option value="n"' . ($user->sex == 'n' ? ' selected' : '') . '>' . esc_html($options['sex_none']) . '</option>';
254
- $buffer .= '<option value="f"' . ($user->sex == 'f' ? ' selected' : '') . '>' . esc_html($options['sex_female']) . '</option>';
255
- $buffer .= '<option value="m"' . ($user->sex == 'm' ? ' selected' : '') . '>' . esc_html($options['sex_male']) . '</option>';
256
- $buffer .= '</select>';
257
- $buffer .= "</div>\n";
258
- }
259
-
260
- if ($this->is_multilanguage()) {
261
-
262
- $languages = $this->get_languages();
263
-
264
- $buffer .= '<div class="tnp-field tnp-field-language">';
265
- $buffer .= '<label>' . __('Language', 'newsletter') . '</label>';
266
- $buffer .= '<select name="nlng" class="tnp-language">';
267
-
268
- $buffer .= '<option value="" disabled ' . ( empty($user->language) ? ' selected' : '' ) . '>' . __('Select language', 'newsletter') . '</option>';
269
- foreach ($languages as $key => $language) {
270
- $buffer .= '<option value="' . $key . '"' . ( $user->language == $key ? ' selected' : '' ) . '>' . esc_html($language) . '</option>';
271
- }
272
-
273
- $buffer .= '</select>';
274
- $buffer .= "</div>\n";
275
- }
276
-
277
- // Profile
278
- $profiles = NewsletterSubscription::instance()->get_profiles_for_profile($user->language);
279
- foreach ($profiles as $profile) {
280
- $i = $profile->id; // I'm lazy
281
-
282
- $buffer .= '<div class="tnp-field tnp-field-profile">';
283
- $buffer .= '<label>' . esc_html($profile->name) . '</label>';
284
-
285
- $field = 'profile_' . $i;
286
-
287
- if ($profile->is_text()) {
288
- $buffer .= '<input class="tnp-profile tnp-profile-' . $i . '" type="text" name="np' . $i . '" value="' . esc_attr($user->$field) . '"' .
289
- ($profile->is_required() ? ' required' : '') . '>';
290
- }
291
-
292
- if ($profile->is_select()) {
293
- $buffer .= '<select class="tnp-profile tnp-profile-' . $i . '" name="np' . $i . '"' . ($profile->is_required() ? ' required' : '') . '>';
294
- foreach ($profile->options as $option) {
295
- $buffer .= '<option';
296
- if ($option == $user->$field) {
297
- $buffer .= ' selected';
298
- }
299
- $buffer .= '>' . esc_html($option) . '</option>';
300
- }
301
- $buffer .= '</select>';
302
- }
303
-
304
- $buffer .= "</div>\n";
305
- }
306
-
307
- // Lists
308
- $lists = $this->get_lists_for_profile($language);
309
- $tmp = '';
310
- foreach ($lists as $list) {
311
-
312
- $tmp .= '<div class="tnp-field tnp-field-list">';
313
- $tmp .= '<label><input class="tnp-list tnp-list-' . $list->id . '" type="checkbox" name="nl[]" value="' . $list->id . '"';
314
- $field = 'list_' . $list->id;
315
- if ($user->$field == 1) {
316
- $tmp .= ' checked';
317
- }
318
- $tmp .= '><span class="tnp-list-label">' . esc_html($list->name) . '</span></label>';
319
- $tmp .= "</div>\n";
320
- }
321
-
322
- if (!empty($tmp)) {
323
- $buffer .= '<div class="tnp-lists">' . "\n" . $tmp . "\n" . '</div>';
324
- }
325
-
326
- // Obsolete
327
- $extra = apply_filters('newsletter_profile_extra', array(), $user);
328
- foreach ($extra as $x) {
329
- $buffer .= '<div class="tnp-field">';
330
- $buffer .= '<label>' . $x['label'] . "</label>";
331
- $buffer .= $x['field'];
332
- $buffer .= "</div>\n";
333
- }
334
-
335
- $local_options = $this->get_options('', $language);
336
-
337
- // Privacy
338
- $privacy_url = NewsletterSubscription::instance()->get_privacy_url();
339
- if (!empty($local_options['privacy_label']) && !empty($privacy_url)) {
340
- $buffer .= '<div class="tnp-field tnp-field-privacy">';
341
- if ($privacy_url) {
342
- $buffer .= '<a href="' . $privacy_url . '" target="_blank">';
343
- }
344
-
345
- $buffer .= $local_options['privacy_label'];
346
-
347
- if ($privacy_url) {
348
- $buffer .= '</a>';
349
- }
350
- $buffer .= "</div>\n";
351
- }
352
-
353
- $buffer .= '<div class="tnp-field tnp-field-button">';
354
- $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($local_options['save_label']) . '">';
355
- $buffer .= "</div>\n";
356
-
357
- $buffer .= "</form>\n</div>\n";
358
-
359
- return $buffer;
360
- }
361
-
362
- /**
363
- * Saves the subscriber data extracting them from the $_REQUEST and for the
364
- * subscriber identified by the <code>$user</code> object.
365
- *
366
- * @return string|WP_Error If not an error the string represent the message to show
367
- */
368
- function save_profile($user) {
369
- global $wpdb;
370
-
371
- // Conatains the cleaned up user data to be saved
372
- $data = array();
373
- $data['id'] = $user->id;
374
-
375
- $options = $this->get_options('', $this->get_current_language($user));
376
- $options_profile = get_option('newsletter_profile', array());
377
- $options_main = get_option('newsletter_main', array());
378
-
379
- // Not an elegant interaction between modules but...
380
- $subscription_module = NewsletterSubscription::instance();
381
-
382
- require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
383
-
384
- $antispam = NewsletterAntispam::instance();
385
-
386
- $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
387
-
388
- if ($antispam->is_address_blacklisted($email)) {
389
- return new WP_Error('spam', 'That email address is not accepted');
390
- }
391
-
392
- if (!$email) {
393
- return new WP_Error('email', $options['error']);
394
- }
395
-
396
- $email_changed = ($email != $user->email);
397
-
398
- // If the email has been changed, check if it is available
399
- if ($email_changed) {
400
- $tmp = $this->get_user($email);
401
- if ($tmp != null && $tmp->id != $user->id) {
402
- return new WP_Error('inuse', $options['error']);
403
- }
404
- }
405
-
406
- if ($email_changed && $subscription_module->is_double_optin()) {
407
- set_transient('newsletter_user_' . $user->id . '_email', $email, DAY_IN_SECONDS);
408
- } else {
409
- $data['email'] = $email;
410
- }
411
-
412
- if (isset($_REQUEST['nn'])) {
413
- $data['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
414
- if ($antispam->is_spam_text($data['name'])) {
415
- return new WP_Error('spam', 'That name/surname');
416
- }
417
- }
418
- if (isset($_REQUEST['ns'])) {
419
- $data['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
420
- if ($antispam->is_spam_text($data['surname'])) {
421
- return new WP_Error('spam', 'That name/surname');
422
- }
423
- }
424
- if ($options_profile['sex_status'] >= 1) {
425
- $data['sex'] = $_REQUEST['nx'][0];
426
- // Wrong data injection check
427
- if ($data['sex'] != 'm' && $data['sex'] != 'f' && $data['sex'] != 'n') {
428
- die('Wrong sex field');
429
- }
430
- }
431
- if (isset($_REQUEST['nlng'])) {
432
- $languages = $this->get_languages();
433
- if (isset($languages[$_REQUEST['nlng']])) {
434
- $data['language'] = $_REQUEST['nlng'];
435
- }
436
- }
437
-
438
- // Lists. If not list is present or there is no list to choose or all are unchecked.
439
- $nl = array();
440
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
441
- $nl = $_REQUEST['nl'];
442
- }
443
-
444
- // Every possible list shown in the profile must be processed
445
- $lists = $this->get_lists_for_profile();
446
- foreach ($lists as $list) {
447
- $field_name = 'list_' . $list->id;
448
- $data[$field_name] = in_array($list->id, $nl) ? 1 : 0;
449
- }
450
-
451
- // Profile
452
- $profiles = $this->get_profiles_public();
453
- foreach ($profiles as $profile) {
454
- if (isset($_REQUEST['np' . $profile->id])) {
455
- $data['profile_' . $profile->id] = stripslashes($_REQUEST['np' . $profile->id]);
456
- }
457
- }
458
-
459
- // Feed by Mail service is saved here
460
- $data = apply_filters('newsletter_profile_save', $data);
461
-
462
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
463
- $data['status'] = TNP_User::STATUS_CONFIRMED;
464
- }
465
-
466
- $user = $this->save_user($data);
467
- $this->add_user_log($user, 'profile');
468
-
469
- // Send the activation again only if we use double opt-in, otherwise it has no meaning
470
- if ($email_changed && $subscription_module->is_double_optin()) {
471
- $user->email = $email;
472
- $subscription_module->send_activation_email($user);
473
- return $options['email_changed'];
474
- }
475
-
476
- return $options['saved'];
477
- }
478
-
479
- function admin_menu() {
480
- $this->add_admin_page('index', 'Profile');
481
- }
482
-
483
- // Patch to avoid conflicts with the "newsletter_profile" option of the subscription module
484
- // TODO: Fix it
485
- public function get_prefix($sub = '', $language = '') {
486
- if (empty($sub)) {
487
- $sub = 'main';
488
- }
489
- return parent::get_prefix($sub, $language);
490
- }
491
-
492
- }
493
-
494
- NewsletterProfile::instance();
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterProfile extends NewsletterModule {
6
+
7
+ static $instance;
8
+
9
+ /**
10
+ * @return NewsletterProfile
11
+ */
12
+ static function instance() {
13
+ if (self::$instance == null) {
14
+ self::$instance = new NewsletterProfile();
15
+ }
16
+ return self::$instance;
17
+ }
18
+
19
+ function __construct() {
20
+ parent::__construct('profile', '1.1.0');
21
+ add_shortcode('newsletter_profile', array($this, 'shortcode_newsletter_profile'));
22
+ add_filter('newsletter_replace', array($this, 'hook_newsletter_replace'), 10, 4);
23
+ add_filter('newsletter_page_text', array($this, 'hook_newsletter_page_text'), 10, 3);
24
+ add_action('newsletter_action', array($this, 'hook_newsletter_action'), 12, 3);
25
+ }
26
+
27
+ function hook_newsletter_action($action, $user, $email) {
28
+
29
+ if (in_array($action, ['p', 'profile', 'pe', 'profile-save', 'profile_export', 'ps'])) {
30
+ if (!$user || $user->status != TNP_User::STATUS_CONFIRMED) {
31
+
32
+ $this->dienow(__('The subscriber was not found or is not confirmed.', 'newsletter'), '', 404);
33
+ }
34
+ }
35
+
36
+ switch ($action) {
37
+ case 'profile':
38
+ case 'p':
39
+ case 'pe':
40
+
41
+ $profile_url = $this->build_message_url($this->options['url'], 'profile', $user, $email);
42
+ $profile_url = apply_filters('newsletter_profile_url', $profile_url, $user);
43
+
44
+ wp_redirect($profile_url);
45
+ die();
46
+
47
+ break;
48
+
49
+ case 'profile-save':
50
+ case 'ps':
51
+ $res = $this->save_profile($user);
52
+ if (is_wp_error($res)) {
53
+ wp_redirect($this->build_message_url($this->options['url'], 'profile', $user, $email, $res->get_error_message()));
54
+ die();
55
+ }
56
+
57
+ wp_redirect($this->build_message_url($this->options['url'], 'profile', $user, $email, $res));
58
+ die();
59
+ break;
60
+
61
+ case 'profile_export':
62
+ header('Content-Type: application/json;charset=UTF-8');
63
+ echo $this->to_json($user);
64
+ die();
65
+ }
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param stdClass $user
71
+ */
72
+ function get_profile_export_url($user) {
73
+ return $this->build_action_url('profile_export', $user);
74
+ }
75
+
76
+ /**
77
+ * URL to the subscriber profile edit action. This URL MUST NEVER be changed by
78
+ * 3rd party plugins. Plugins can change the final URL after the action has been executed using the
79
+ * <code>newsletter_profile_url</code> filter.
80
+ *
81
+ * @param stdClass $user
82
+ */
83
+ function get_profile_url($user, $email = null) {
84
+ return $this->build_action_url('profile', $user, $email);
85
+ }
86
+
87
+ function hook_newsletter_replace($text, $user, $email, $html = true) {
88
+ if (!$user) {
89
+ $text = $this->replace_url($text, 'PROFILE_URL', $this->build_action_url('nul'));
90
+ return $text;
91
+ }
92
+
93
+ // Profile edit page URL and link
94
+ $url = $this->get_profile_url($user, $email);
95
+ $text = $this->replace_url($text, 'profile_url', $url);
96
+ // Profile export URL and link
97
+ $url = $this->get_profile_export_url($user);
98
+ $text = $this->replace_url($text, 'profile_export_url', $url);
99
+
100
+ if (strpos($text, '{profile_form}') !== false) {
101
+ $text = str_replace('{profile_form}', $this->get_profile_form($user), $text);
102
+ }
103
+ return $text;
104
+ }
105
+
106
+ /**
107
+ *
108
+ * @param type $text
109
+ * @param type $key
110
+ * @param TNP_User $user
111
+ * @return string
112
+ */
113
+ function hook_newsletter_page_text($text, $key, $user) {
114
+ if ($key == 'profile') {
115
+ if (!$user || $user->status == TNP_User::STATUS_UNSUBSCRIBED) {
116
+ return 'Subscriber not found.';
117
+ }
118
+ $options = $this->get_options('main', $this->get_current_language($user));
119
+ return $options['text'];
120
+ }
121
+ return $text;
122
+ }
123
+
124
+ function shortcode_newsletter_profile($attrs, $content) {
125
+ $user = $this->check_user();
126
+
127
+ if (empty($user)) {
128
+ if (empty($content)) {
129
+ return __('Subscriber not found.', 'newsletter');
130
+ } else {
131
+ return $content;
132
+ }
133
+ }
134
+
135
+ return $this->get_profile_form($user);
136
+ }
137
+
138
+ function to_json($user) {
139
+ global $wpdb;
140
+
141
+
142
+ $fields = array('name', 'surname', 'sex', 'created', 'ip', 'email');
143
+ $data = array(
144
+ 'email' => $user->email,
145
+ 'name' => $user->name,
146
+ 'last_name' => $user->surname,
147
+ 'gender' => $user->sex,
148
+ 'created' => $user->created,
149
+ 'ip' => $user->ip,
150
+ );
151
+
152
+ // Lists
153
+ $data['lists'] = array();
154
+
155
+ $lists = $this->get_lists_public();
156
+ foreach ($lists as $list) {
157
+ $field = 'list_' . $list->id;
158
+ if ($user->$field == 1) {
159
+ $data['lists'][] = $list->name;
160
+ }
161
+ }
162
+
163
+ // Profile
164
+ $options_profile = get_option('newsletter_profile', array());
165
+ $data['profiles'] = array();
166
+ for ($i = 1; $i < NEWSLETTER_PROFILE_MAX; $i++) {
167
+ $field = 'profile_' . $i;
168
+ if ($options_profile[$field . '_status'] != 1 && $options_profile[$field . '_status'] != 2) {
169
+ continue;
170
+ }
171
+ $data['profiles'][] = array('name' => $options_profile[$field], 'value' => $user->$field);
172
+ }
173
+
174
+ // Newsletters
175
+ if ($this->options['export_newsletters']) {
176
+ $sent = $wpdb->get_results($wpdb->prepare("select * from {$wpdb->prefix}newsletter_sent where user_id=%d order by email_id asc", $user->id));
177
+ $newsletters = array();
178
+ foreach ($sent as $item) {
179
+ $action = 'none';
180
+ if ($item->open == 1) {
181
+ $action = 'read';
182
+ } else if ($item->open == 2) {
183
+ $action = 'click';
184
+ }
185
+
186
+ $email = $this->get_email($item->email_id);
187
+ if (!$email) {
188
+ continue;
189
+ }
190
+ // 'id'=>$item->email_id,
191
+ $newsletters[] = array('subject' => $email->subject, 'action' => $action, 'sent' => date('Y-m-d h:i:s', $email->send_on));
192
+ }
193
+
194
+ $data['newsletters'] = $newsletters;
195
+ }
196
+
197
+ $extra = apply_filters('newsletter_profile_export_extra', array());
198
+
199
+ $data = array_merge($extra, $data);
200
+
201
+ return json_encode($data, JSON_PRETTY_PRINT);
202
+ }
203
+
204
+ /**
205
+ * Build the profile editing form for the specified subscriber.
206
+ *
207
+ * @param TNP_User $user
208
+ * @return string
209
+ */
210
+ function get_profile_form($user) {
211
+ // Do not pay attention to option name here, it's a compatibility problem
212
+
213
+ $language = $this->get_user_language($user);
214
+ $options = NewsletterSubscription::instance()->get_options('profile', $language);
215
+
216
+ $buffer = '';
217
+
218
+ $buffer .= '<div class="tnp tnp-profile">';
219
+ $buffer .= '<form action="' . $this->build_action_url('ps') . '" method="post">';
220
+ $buffer .= '<input type="hidden" name="nk" value="' . esc_attr($user->id . '-' . $user->token) . '">';
221
+
222
+ $buffer .= '<div class="tnp-field tnp-field-email">';
223
+ $buffer .= '<label>' . esc_html($options['email']) . '</label>';
224
+ $buffer .= '<input class="tnp-email" type="text" name="ne" required value="' . esc_attr($user->email) . '">';
225
+ $buffer .= "</div>\n";
226
+
227
+
228
+ if ($options['name_status'] >= 1) {
229
+ $buffer .= '<div class="tnp-field tnp-field-firstname">';
230
+ $buffer .= '<label>' . esc_html($options['name']) . '</label>';
231
+ $buffer .= '<input class="tnp-firstname" type="text" name="nn" value="' . esc_attr($user->name) . '"' . ($options['name_rules'] == 1 ? ' required' : '') . '>';
232
+ $buffer .= "</div>\n";
233
+ }
234
+
235
+ if ($options['surname_status'] >= 1) {
236
+ $buffer .= '<div class="tnp-field tnp-field-lastname">';
237
+ $buffer .= '<label>' . esc_html($options['surname']) . '</label>';
238
+ $buffer .= '<input class="tnp-lastname" type="text" name="ns" value="' . esc_attr($user->surname) . '"' . ($options['surname_rules'] == 1 ? ' required' : '') . '>';
239
+ $buffer .= "</div>\n";
240
+ }
241
+
242
+ if ($options['sex_status'] >= 1) {
243
+ $buffer .= '<div class="tnp-field tnp-field-gender">';
244
+ $buffer .= '<label>' . esc_html($options['sex']) . '</label>';
245
+ $buffer .= '<select name="nx" class="tnp-gender"';
246
+ if ($options['sex_rules']) {
247
+ $buffer .= ' required ';
248
+ }
249
+ $buffer .= '>';
250
+ if ($options['sex_rules']) {
251
+ $buffer .= '<option value=""></option>';
252
+ }
253
+ $buffer .= '<option value="n"' . ($user->sex == 'n' ? ' selected' : '') . '>' . esc_html($options['sex_none']) . '</option>';
254
+ $buffer .= '<option value="f"' . ($user->sex == 'f' ? ' selected' : '') . '>' . esc_html($options['sex_female']) . '</option>';
255
+ $buffer .= '<option value="m"' . ($user->sex == 'm' ? ' selected' : '') . '>' . esc_html($options['sex_male']) . '</option>';
256
+ $buffer .= '</select>';
257
+ $buffer .= "</div>\n";
258
+ }
259
+
260
+ if ($this->is_multilanguage()) {
261
+
262
+ $languages = $this->get_languages();
263
+
264
+ $buffer .= '<div class="tnp-field tnp-field-language">';
265
+ $buffer .= '<label>' . __('Language', 'newsletter') . '</label>';
266
+ $buffer .= '<select name="nlng" class="tnp-language">';
267
+
268
+ $buffer .= '<option value="" disabled ' . ( empty($user->language) ? ' selected' : '' ) . '>' . __('Select language', 'newsletter') . '</option>';
269
+ foreach ($languages as $key => $l) {
270
+ $buffer .= '<option value="' . $key . '"' . ( $user->language == $key ? ' selected' : '' ) . '>' . esc_html($l) . '</option>';
271
+ }
272
+
273
+ $buffer .= '</select>';
274
+ $buffer .= "</div>\n";
275
+ }
276
+
277
+ // Profile
278
+ $profiles = NewsletterSubscription::instance()->get_profiles_for_profile($user->language);
279
+ foreach ($profiles as $profile) {
280
+ $i = $profile->id; // I'm lazy
281
+
282
+ $buffer .= '<div class="tnp-field tnp-field-profile">';
283
+ $buffer .= '<label>' . esc_html($profile->name) . '</label>';
284
+
285
+ $field = 'profile_' . $i;
286
+
287
+ if ($profile->is_text()) {
288
+ $buffer .= '<input class="tnp-profile tnp-profile-' . $i . '" type="text" name="np' . $i . '" value="' . esc_attr($user->$field) . '"' .
289
+ ($profile->is_required() ? ' required' : '') . '>';
290
+ }
291
+
292
+ if ($profile->is_select()) {
293
+ $buffer .= '<select class="tnp-profile tnp-profile-' . $i . '" name="np' . $i . '"' . ($profile->is_required() ? ' required' : '') . '>';
294
+ foreach ($profile->options as $option) {
295
+ $buffer .= '<option';
296
+ if ($option == $user->$field) {
297
+ $buffer .= ' selected';
298
+ }
299
+ $buffer .= '>' . esc_html($option) . '</option>';
300
+ }
301
+ $buffer .= '</select>';
302
+ }
303
+
304
+ $buffer .= "</div>\n";
305
+ }
306
+
307
+ // Lists
308
+ $lists = $this->get_lists_for_profile($language);
309
+ $tmp = '';
310
+ foreach ($lists as $list) {
311
+
312
+ $tmp .= '<div class="tnp-field tnp-field-list">';
313
+ $tmp .= '<label><input class="tnp-list tnp-list-' . $list->id . '" type="checkbox" name="nl[]" value="' . $list->id . '"';
314
+ $field = 'list_' . $list->id;
315
+ if ($user->$field == 1) {
316
+ $tmp .= ' checked';
317
+ }
318
+ $tmp .= '><span class="tnp-list-label">' . esc_html($list->name) . '</span></label>';
319
+ $tmp .= "</div>\n";
320
+ }
321
+
322
+ if (!empty($tmp)) {
323
+ $buffer .= '<div class="tnp-lists">' . "\n" . $tmp . "\n" . '</div>';
324
+ }
325
+
326
+ // Obsolete
327
+ $extra = apply_filters('newsletter_profile_extra', array(), $user);
328
+ foreach ($extra as $x) {
329
+ $buffer .= '<div class="tnp-field">';
330
+ $buffer .= '<label>' . $x['label'] . "</label>";
331
+ $buffer .= $x['field'];
332
+ $buffer .= "</div>\n";
333
+ }
334
+
335
+ $local_options = $this->get_options('', $language);
336
+
337
+ // Privacy
338
+ $privacy_url = NewsletterSubscription::instance()->get_privacy_url();
339
+ if (!empty($local_options['privacy_label']) && !empty($privacy_url)) {
340
+ $buffer .= '<div class="tnp-field tnp-field-privacy">';
341
+ if ($privacy_url) {
342
+ $buffer .= '<a href="' . $privacy_url . '" target="_blank">';
343
+ }
344
+
345
+ $buffer .= $local_options['privacy_label'];
346
+
347
+ if ($privacy_url) {
348
+ $buffer .= '</a>';
349
+ }
350
+ $buffer .= "</div>\n";
351
+ }
352
+
353
+ $buffer .= '<div class="tnp-field tnp-field-button">';
354
+ $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($local_options['save_label']) . '">';
355
+ $buffer .= "</div>\n";
356
+
357
+ $buffer .= "</form>\n</div>\n";
358
+
359
+ return $buffer;
360
+ }
361
+
362
+ /**
363
+ * Saves the subscriber data extracting them from the $_REQUEST and for the
364
+ * subscriber identified by the <code>$user</code> object.
365
+ *
366
+ * @return string|WP_Error If not an error the string represent the message to show
367
+ */
368
+ function save_profile($user) {
369
+ global $wpdb;
370
+
371
+ // Conatains the cleaned up user data to be saved
372
+ $data = array();
373
+ $data['id'] = $user->id;
374
+
375
+ $options = $this->get_options('', $this->get_current_language($user));
376
+ $options_profile = get_option('newsletter_profile', array());
377
+ $options_main = get_option('newsletter_main', array());
378
+
379
+ // Not an elegant interaction between modules but...
380
+ $subscription_module = NewsletterSubscription::instance();
381
+
382
+ require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
383
+
384
+ $antispam = NewsletterAntispam::instance();
385
+
386
+ $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
387
+
388
+ if ($antispam->is_address_blacklisted($email)) {
389
+ return new WP_Error('spam', 'That email address is not accepted');
390
+ }
391
+
392
+ if (!$email) {
393
+ return new WP_Error('email', $options['error']);
394
+ }
395
+
396
+ $email_changed = ($email != $user->email);
397
+
398
+ // If the email has been changed, check if it is available
399
+ if ($email_changed) {
400
+ $tmp = $this->get_user($email);
401
+ if ($tmp != null && $tmp->id != $user->id) {
402
+ return new WP_Error('inuse', $options['error']);
403
+ }
404
+ }
405
+
406
+ if ($email_changed && $subscription_module->is_double_optin()) {
407
+ set_transient('newsletter_user_' . $user->id . '_email', $email, DAY_IN_SECONDS);
408
+ } else {
409
+ $data['email'] = $email;
410
+ }
411
+
412
+ if (isset($_REQUEST['nn'])) {
413
+ $data['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
414
+ if ($antispam->is_spam_text($data['name'])) {
415
+ return new WP_Error('spam', 'That name/surname');
416
+ }
417
+ }
418
+ if (isset($_REQUEST['ns'])) {
419
+ $data['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
420
+ if ($antispam->is_spam_text($data['surname'])) {
421
+ return new WP_Error('spam', 'That name/surname');
422
+ }
423
+ }
424
+ if ($options_profile['sex_status'] >= 1) {
425
+ $data['sex'] = $_REQUEST['nx'][0];
426
+ // Wrong data injection check
427
+ if ($data['sex'] != 'm' && $data['sex'] != 'f' && $data['sex'] != 'n') {
428
+ die('Wrong sex field');
429
+ }
430
+ }
431
+ if (isset($_REQUEST['nlng'])) {
432
+ $languages = $this->get_languages();
433
+ if (isset($languages[$_REQUEST['nlng']])) {
434
+ $data['language'] = $_REQUEST['nlng'];
435
+ }
436
+ }
437
+
438
+ // Lists. If not list is present or there is no list to choose or all are unchecked.
439
+ $nl = array();
440
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
441
+ $nl = $_REQUEST['nl'];
442
+ }
443
+
444
+ // Every possible list shown in the profile must be processed
445
+ $lists = $this->get_lists_for_profile();
446
+ foreach ($lists as $list) {
447
+ $field_name = 'list_' . $list->id;
448
+ $data[$field_name] = in_array($list->id, $nl) ? 1 : 0;
449
+ }
450
+
451
+ // Profile
452
+ $profiles = $this->get_profiles_public();
453
+ foreach ($profiles as $profile) {
454
+ if (isset($_REQUEST['np' . $profile->id])) {
455
+ $data['profile_' . $profile->id] = stripslashes($_REQUEST['np' . $profile->id]);
456
+ }
457
+ }
458
+
459
+ // Feed by Mail service is saved here
460
+ $data = apply_filters('newsletter_profile_save', $data);
461
+
462
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
463
+ $data['status'] = TNP_User::STATUS_CONFIRMED;
464
+ }
465
+
466
+ $user = $this->save_user($data);
467
+ $this->add_user_log($user, 'profile');
468
+
469
+ // Send the activation again only if we use double opt-in, otherwise it has no meaning
470
+ if ($email_changed && $subscription_module->is_double_optin()) {
471
+ $user->email = $email;
472
+ $subscription_module->send_activation_email($user);
473
+ return $options['email_changed'];
474
+ }
475
+
476
+ return $options['saved'];
477
+ }
478
+
479
+ function admin_menu() {
480
+ $this->add_admin_page('index', 'Profile');
481
+ }
482
+
483
+ // Patch to avoid conflicts with the "newsletter_profile" option of the subscription module
484
+ // TODO: Fix it
485
+ public function get_prefix($sub = '', $language = '') {
486
+ if (empty($sub)) {
487
+ $sub = 'main';
488
+ }
489
+ return parent::get_prefix($sub, $language);
490
+ }
491
+
492
+ }
493
+
494
+ NewsletterProfile::instance();
readme.txt CHANGED
@@ -1,7 +1,7 @@
1
  === Newsletter - Send awesome emails from WordPress ===
2
  Tags: newsletter, email marketing, welcome email, signup forms, lead generation, marketing automation
3
- Tested up to: 6.0.1
4
- Stable tag: 7.5.3
5
  Contributors: satollo,webagile,michael-travan
6
  License: GPLv2 or later
7
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -126,6 +126,11 @@ Thank you, The Newsletter Team
126
 
127
  == Changelog ==
128
 
 
 
 
 
 
129
  = 7.5.3 =
130
 
131
  * Fixed missing languages on profile page with WPML
1
  === Newsletter - Send awesome emails from WordPress ===
2
  Tags: newsletter, email marketing, welcome email, signup forms, lead generation, marketing automation
3
+ Tested up to: 6.1
4
+ Stable tag: 7.5.4
5
  Contributors: satollo,webagile,michael-travan
6
  License: GPLv2 or later
7
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
126
 
127
  == Changelog ==
128
 
129
+ = 7.5.4 =
130
+
131
+ * Fixed composer icons path for Amazon AWS+Bitnami installations
132
+ * Fixed profile form on multilanguage installation
133
+
134
  = 7.5.3 =
135
 
136
  * Fixed missing languages on profile page with WPML
tnp-header.php CHANGED
@@ -309,7 +309,7 @@ $current_user_email = ''; //$current_user->user_email;
309
  if (!defined('NEWSLETTER_CRON_WARNINGS') || NEWSLETTER_CRON_WARNINGS) {
310
  $x = NewsletterSystem::instance()->get_job_status();
311
  if ($x !== NewsletterSystem::JOB_OK) {
312
- echo '<div class="tnpc-warning">The are issues with the delivery engine. Please <a href="?page=newsletter_system_scheduler">check them here</a>.</div>';
313
  }
314
  }
315
  ?>
309
  if (!defined('NEWSLETTER_CRON_WARNINGS') || NEWSLETTER_CRON_WARNINGS) {
310
  $x = NewsletterSystem::instance()->get_job_status();
311
  if ($x !== NewsletterSystem::JOB_OK) {
312
+ echo '<div class="tnpc-warning">There are issues with the delivery engine. Please <a href="?page=newsletter_system_scheduler">check them here</a>.</div>';
313
  }
314
  }
315
  ?>