Newsletter - Version 7.5.3

Version Description

  • Fixed missing languages on profile page with WPML
Download this release

Release Info

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

Code changes from version 7.5.2 to 7.5.3

Files changed (3) hide show
  1. includes/module.php +2632 -2631
  2. plugin.php +2 -2
  3. readme.txt +5 -1
includes/module.php CHANGED
@@ -1,2631 +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);
2474
- foreach ($languages as $language) {
2475
- $language_options[$language['language_code']] = $language['translated_name'];
2476
- }
2477
- return $language_options;
2478
- } else if (function_exists('icl_get_languages')) {
2479
- $languages = icl_get_languages();
2480
- foreach ($languages as $code => $language) {
2481
- $language_options[$code] = $language['native_name'];
2482
- }
2483
- return $language_options;
2484
- }
2485
-
2486
- return apply_filters('newsletter_languages', $language_options);
2487
- }
2488
-
2489
- function get_language_label($language) {
2490
- $languages = $this->get_languages();
2491
- if (isset($languages[$language])) {
2492
- return $languages[$language];
2493
- }
2494
- return '';
2495
- }
2496
-
2497
- /**
2498
- * Changes the current language usually before extracting the posts since WPML
2499
- * does not support the language filter in the post query (or at least we didn't
2500
- * find it).
2501
- *
2502
- * @param string $language
2503
- */
2504
- function switch_language($language) {
2505
- if (class_exists('SitePress')) {
2506
- if (empty($language)) {
2507
- $language = 'all';
2508
- }
2509
- do_action('wpml_switch_language', $language);
2510
- return;
2511
- }
2512
- }
2513
-
2514
- function is_multilanguage() {
2515
- return class_exists('SitePress') || function_exists('pll_default_language') || class_exists('TRP_Translate_Press');
2516
- }
2517
-
2518
- function get_posts($filters = array(), $language = '') {
2519
- $current_language = $this->get_current_language();
2520
-
2521
- // Language switch for WPML
2522
- if ($language) {
2523
- if (class_exists('SitePress')) {
2524
- $this->switch_language($language);
2525
- $filters['suppress_filters'] = false;
2526
- }
2527
- if (class_exists('Polylang')) {
2528
- $filters['lang'] = $language;
2529
- }
2530
- }
2531
- $posts = get_posts($filters);
2532
- if ($language) {
2533
- if (class_exists('SitePress')) {
2534
- $this->switch_language($current_language);
2535
- }
2536
- }
2537
- return $posts;
2538
- }
2539
-
2540
- function get_wp_query($filters, $langiage = '') {
2541
- if ($language) {
2542
- if (class_exists('SitePress')) {
2543
- $this->switch_language($language);
2544
- $filters['suppress_filters'] = false;
2545
- }
2546
- if (class_exists('Polylang')) {
2547
- $filters['lang'] = $language;
2548
- }
2549
- }
2550
-
2551
- $posts = new WP_Query($filters);
2552
-
2553
- if ($language) {
2554
- if (class_exists('SitePress')) {
2555
- $this->switch_language($current_language);
2556
- }
2557
- }
2558
-
2559
- return $posts;
2560
- }
2561
-
2562
- protected function generate_admin_notification_message($user) {
2563
-
2564
- $message = file_get_contents(__DIR__ . '/notification.html');
2565
-
2566
- $message = $this->replace($message, $user);
2567
- $message = str_replace('{user_admin_url}', admin_url('admin.php?page=newsletter_users_edit&id=' . $user->id), $message);
2568
-
2569
- return $message;
2570
- }
2571
-
2572
- protected function generate_admin_notification_subject($subject) {
2573
- $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
2574
-
2575
- return '[' . $blogname . '] ' . $subject;
2576
- }
2577
-
2578
- function dienow($message, $admin_message = null, $http_code = 200) {
2579
- if ($admin_message && current_user_can('administrator')) {
2580
- $message .= '<br><br><strong>Text below only visibile to administrators</strong><br>';
2581
- $message .= $admin_message;
2582
- }
2583
- wp_die($message, $http_code);
2584
- }
2585
-
2586
- function dump($var) {
2587
- if (NEWSLETTER_DEBUG) {
2588
- var_dump($var);
2589
- }
2590
- }
2591
-
2592
- function dump_die($var) {
2593
- if (NEWSLETTER_DEBUG) {
2594
- var_dump($var);
2595
- die();
2596
- }
2597
- }
2598
-
2599
- }
2600
-
2601
- /**
2602
- * Kept for compatibility.
2603
- *
2604
- * @param type $post_id
2605
- * @param type $size
2606
- * @param type $alternative
2607
- * @return type
2608
- */
2609
- function nt_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2610
- return NewsletterModule::get_post_image($post_id, $size, $alternative);
2611
- }
2612
-
2613
- function newsletter_get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2614
- echo NewsletterModule::get_post_image($post_id, $size, $alternative);
2615
- }
2616
-
2617
- /**
2618
- * Accepts a post or a post ID.
2619
- *
2620
- * @param WP_Post $post
2621
- */
2622
- function newsletter_the_excerpt($post, $words = 30) {
2623
- $post = get_post($post);
2624
- $excerpt = $post->post_excerpt;
2625
- if (empty($excerpt)) {
2626
- $excerpt = $post->post_content;
2627
- $excerpt = strip_shortcodes($excerpt);
2628
- $excerpt = wp_strip_all_tags($excerpt, true);
2629
- }
2630
- echo '<p>' . wp_trim_words($excerpt, $words) . '</p>';
2631
- }
 
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.2
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.2');
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.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
  return;
38
  }
39
 
40
+ define('NEWSLETTER_VERSION', '7.5.3');
41
 
42
  global $newsletter, $wpdb;
43
 
readme.txt CHANGED
@@ -1,7 +1,7 @@
1
  === Newsletter - Send awesome emails from WordPress ===
2
  Tags: newsletter, email marketing, welcome email, signup forms, lead generation, marketing automation
3
  Tested up to: 6.0.1
4
- Stable tag: 7.5.2
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,10 @@ Thank you, The Newsletter Team
126
 
127
  == Changelog ==
128
 
 
 
 
 
129
  = 7.5.2 =
130
 
131
  * Fixed the global newsletter styles not applied when it is regenerated
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
 
127
  == Changelog ==
128
 
129
+ = 7.5.3 =
130
+
131
+ * Fixed missing languages on profile page with WPML
132
+
133
  = 7.5.2 =
134
 
135
  * Fixed the global newsletter styles not applied when it is regenerated