Newsletter - Version 7.3.7

Version Description

  • Fixed unwanted redirects on subscription errors
  • Fixed composer page HTML
  • Minor fixes on PHP, CSS
  • Fixed notice on image block
Download this release

Release Info

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

Code changes from version 7.3.6 to 7.3.7

admin/admin.css CHANGED
@@ -1,4 +1,8 @@
1
  /* WordPress admin main wrapper */
 
 
 
 
2
  #wpwrap {
3
  background-color: #222B36 !important;
4
  }
1
  /* WordPress admin main wrapper */
2
+ :root {
3
+ --tnp-color-green: #27AE60;
4
+ }
5
+
6
  #wpwrap {
7
  background-color: #222B36 !important;
8
  }
admin/controls.css CHANGED
@@ -57,6 +57,7 @@
57
 
58
  #tnp-body .form-table th small {
59
  font-weight: normal;
 
60
  }
61
 
62
  #tnp-body .form-table textarea {
57
 
58
  #tnp-body .form-table th small {
59
  font-weight: normal;
60
+ display: block;
61
  }
62
 
63
  #tnp-body .form-table textarea {
admin/fields.css CHANGED
@@ -1,176 +1,203 @@
 
 
 
1
 
 
 
 
 
 
 
2
 
3
- /* STRUCTURE */
4
- .tnp-field-row {
5
  clear: both;
6
  margin-left: -10px;
7
  margin-right: -10px;
8
  }
9
 
10
- .tnp-field-col-2 {
11
  width: 50%;
12
  box-sizing: border-box;
13
- padding: 10px;
14
  float: left;
15
  }
16
 
17
- .tnp-field-col-3 {
18
  width: 33%;
19
  box-sizing: border-box;
20
- padding: 10px;
21
  float: left;
22
  }
23
 
24
- .tnp-field-col-20 {
25
  width: 20%;
26
  box-sizing: border-box;
27
- padding: 10px;
28
- float: left;
29
- }
30
-
31
- .tnp-field-col-33 {
32
- width: 33%;
33
- box-sizing: border-box;
34
- padding: 10px;
35
  float: left;
36
  }
37
 
38
- .tnp-field-col-66 {
39
  width: 66%;
40
  box-sizing: border-box;
41
- padding: 10px;
42
  float: left;
43
  }
44
 
45
- .tnp-field-col-80 {
46
  width: 80%;
47
  box-sizing: border-box;
48
- padding: 10px;
49
  float: left;
50
  }
51
 
52
- .tnp-field-box {
53
  padding: 10px;
54
  background-color: #eee !important;
55
  border: 1px solid #bbb;
56
  }
57
 
58
- .tnp-section {
59
-
 
 
 
 
60
  }
61
 
62
- .tnp-field.tnp-separator {
63
  border-top: 1px solid #ddd;
64
  line-height: 0;
 
 
 
65
  }
66
 
67
  /* Single field container */
68
- .tnp-field {
69
  display: block;
70
  width: 100%;
71
  margin-bottom: 10px;
72
  }
73
 
74
- .tnp-field select.tnp-small {
 
75
  font-size: .9em;
76
  }
77
 
78
- .tnp-field label.tnp-label, .tnp-field-row label.tnp-row-label {
 
79
  display: block;
80
  font-size: 12px;
81
  font-weight: 300;
82
- border-bottom: 1px solid #fff;
83
- margin: 10px 0px 10px 0px;
84
  font-family: soleil, sans-serif;
85
  font-weight: 300;
86
  padding-bottom: 5px;
87
- color: #868686;
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  }
89
 
90
  /* Compesate the negative row margin */
91
- .tnp-field-row label.tnp-row-label {
92
  margin-left: 10px;
93
  }
94
 
95
- .tnp-field.tnp-checkbox label {
 
 
 
 
 
 
96
  display: inline;
97
  }
98
 
99
  /* Set of inlined field for font, size, color... */
100
- tnp-field.tnp-font {
101
 
102
- }
103
-
104
- .tnp-field:not(.tnp-colorpicker) input {
105
  width: 100%;
106
  }
107
 
108
- .tnp-field input[type=number] {
109
  width: 100px;
110
  }
111
 
112
- .tnp-field.tnp-size input {
113
  width: 60px;
114
  display: inline;
115
  }
116
 
117
- .tnp-field .tnp-padding-fields {
118
  display: inline;
119
  }
120
 
121
  /* Four field for block padding selection */
122
- .tnp-field .tnp-padding-fields input {
123
  width: 40px;
124
  display: inline;
125
  }
126
 
127
- .tnp-field select {
128
  width: auto!important;
129
  color: #34495E;
130
  }
131
 
132
- .tnp-field input[type=url] {
133
  font-family: monospace;
134
  }
135
 
136
- .tnp-field input[type=color] {
137
  width: 40px;
138
  min-height: 30px;
139
  vertical-align: middle;
140
  }
141
 
142
- .tnp-field input[type=submit] {
143
  width: auto;
144
  }
145
 
146
- .tnp-field input[type=checkbox] {
147
  width: auto;
148
  }
149
 
150
- .tnp-field.tnpf-button .tnpf-font-family {
151
  font-size: 13px;
152
  }
153
 
154
- .tnp-field.tnpf-button .tnpf-font-size {
155
  font-size: 13px;
156
  }
157
 
158
- .tnp-field.tnpf-button .tnpf-font-weight {
159
  font-size: 13px;
160
  }
161
 
162
- .tnp-field.tnp-categories {
163
 
164
  }
165
 
166
  /* The label for each category checkbox (should override the generic label appearance) */
167
- .tnp-field.tnp-categories label {
168
 
169
  }
170
 
171
- .tnp-description {
172
- margin-top: 7px;
173
  font-style: italic;
174
  font-weight: normal;
175
  color: #999999;
 
176
  }
1
+ html {
2
+ --tnpf-col-padding: 0 10px;
3
+ }
4
 
5
+ .tnpf-form {
6
+ padding: 15px;
7
+ margin-top: 0;
8
+ color: #444;
9
+ background-color: #ECF0F1 !important;
10
+ }
11
 
12
+ .tnp-field-row, .tnpf-row {
 
13
  clear: both;
14
  margin-left: -10px;
15
  margin-right: -10px;
16
  }
17
 
18
+ .tnp-field-col-2, .tnpf-col-2, .tnpf-col-50 {
19
  width: 50%;
20
  box-sizing: border-box;
21
+ padding: 0 10px;
22
  float: left;
23
  }
24
 
25
+ .tnp-field-col-3, .tnp-field-col-33, .tnpf-col-33 {
26
  width: 33%;
27
  box-sizing: border-box;
28
+ padding: 0 10px;
29
  float: left;
30
  }
31
 
32
+ .tnp-field-col-20, .tnpf-col-20 {
33
  width: 20%;
34
  box-sizing: border-box;
35
+ padding: 0 10px;
 
 
 
 
 
 
 
36
  float: left;
37
  }
38
 
39
+ .tnp-field-col-66, .tnpf-col-66 {
40
  width: 66%;
41
  box-sizing: border-box;
42
+ padding: 0 10px;
43
  float: left;
44
  }
45
 
46
+ .tnp-field-col-80, .tnpf-col-80 {
47
  width: 80%;
48
  box-sizing: border-box;
49
+ padding: 0 10px;
50
  float: left;
51
  }
52
 
53
+ .tnpf-field-box {
54
  padding: 10px;
55
  background-color: #eee !important;
56
  border: 1px solid #bbb;
57
  }
58
 
59
+ .tnpf-section {
60
+ font-size: 1.0rem;
61
+ font-weight: bold;
62
+ color: #333;
63
+ margin-top: 0;
64
+ margin-bottom: 10px;
65
  }
66
 
67
+ .tnpf-separator {
68
  border-top: 1px solid #ddd;
69
  line-height: 0;
70
+ display: block;
71
+ width: 100%;
72
+ margin-bottom: 10px;
73
  }
74
 
75
  /* Single field container */
76
+ .tnpf-field, .tnpf-field {
77
  display: block;
78
  width: 100%;
79
  margin-bottom: 10px;
80
  }
81
 
82
+ .tnpf-field select.tnpf-small,
83
+ .tnpf-field select.tnp-small {
84
  font-size: .9em;
85
  }
86
 
87
+ .tnpf-field label.tnpf-label,
88
+ .tnpf-field label.tnp-label {
89
  display: block;
90
  font-size: 12px;
91
  font-weight: 300;
92
+ border: 0;
93
+ margin: 0px 0px 0px 0px;
94
  font-family: soleil, sans-serif;
95
  font-weight: 300;
96
  padding-bottom: 5px;
97
+ color: #666;
98
+ }
99
+
100
+ .tnpf-row label.tnpf-row-label,
101
+ .tnpf-field-row label.tnp-row-label {
102
+ display: block;
103
+ font-size: 12px;
104
+ font-weight: 300;
105
+ border: 0;
106
+ margin: 0px 0px 0px 10px;
107
+ font-family: soleil, sans-serif;
108
+ font-weight: 300;
109
+ padding-bottom: 5px;
110
+ color: #666;
111
  }
112
 
113
  /* Compesate the negative row margin */
114
+ .tnpf-field-row label.tnp-row-label {
115
  margin-left: 10px;
116
  }
117
 
118
+ .tnpf-row .tnpf-field .tnpf-label,
119
+ .tnpf-row .tnpf-field .tnp-label
120
+ {
121
+ margin-top: 0;
122
+ }
123
+
124
+ .tnpf-field.tnpf-checkbox label {
125
  display: inline;
126
  }
127
 
128
  /* Set of inlined field for font, size, color... */
 
129
 
130
+ .tnpf-field:not(.tnp-colorpicker) input {
 
 
131
  width: 100%;
132
  }
133
 
134
+ .tnpf-field input[type=number] {
135
  width: 100px;
136
  }
137
 
138
+ .tnpf-field.tnpf-size input {
139
  width: 60px;
140
  display: inline;
141
  }
142
 
143
+ .tnpf-field .tnp-padding-fields {
144
  display: inline;
145
  }
146
 
147
  /* Four field for block padding selection */
148
+ .tnpf-field .tnp-padding-fields input {
149
  width: 40px;
150
  display: inline;
151
  }
152
 
153
+ .tnpf-field select {
154
  width: auto!important;
155
  color: #34495E;
156
  }
157
 
158
+ .tnpf-field input[type=url] {
159
  font-family: monospace;
160
  }
161
 
162
+ .tnpf-field input[type=color] {
163
  width: 40px;
164
  min-height: 30px;
165
  vertical-align: middle;
166
  }
167
 
168
+ .tnpf-field input[type=submit] {
169
  width: auto;
170
  }
171
 
172
+ .tnpf-field input[type=checkbox] {
173
  width: auto;
174
  }
175
 
176
+ .tnpf-field.tnpf-button .tnpf-font-family {
177
  font-size: 13px;
178
  }
179
 
180
+ .tnpf-field.tnpf-button .tnpf-font-size {
181
  font-size: 13px;
182
  }
183
 
184
+ .tnpf-field.tnpf-button .tnpf-font-weight {
185
  font-size: 13px;
186
  }
187
 
188
+ .tnpf-field.tnp-categories {
189
 
190
  }
191
 
192
  /* The label for each category checkbox (should override the generic label appearance) */
193
+ .tnpf-field.tnp-categories label {
194
 
195
  }
196
 
197
+ .tnpf-description {
198
+ margin-top: 3px;
199
  font-style: italic;
200
  font-weight: normal;
201
  color: #999999;
202
+ font-size: 12px;
203
  }
emails/blocks/image/block.php CHANGED
@@ -1,67 +1,66 @@
1
- <?php
2
- /*
3
- * Name: Single image
4
- * Section: content
5
- * Description: A single image with link
6
- */
7
-
8
- /* @var $options array */
9
- /* @var $wpdb wpdb */
10
-
11
- $defaults = array(
12
- 'image' => '',
13
- 'url' => '',
14
- 'width' => 0,
15
- 'align' => 'center',
16
- 'block_background' => '',
17
- 'block_padding_left' => 0,
18
- 'block_padding_right' => 0,
19
- 'block_padding_bottom' => 15,
20
- 'block_padding_top' => 15
21
- );
22
-
23
- $options = array_merge($defaults, $options);
24
-
25
- if (empty($options['image']['id'])) {
26
- if (!empty($options['image-url'])) {
27
- $media = new TNP_Media();
28
- $media->url = $options['image-url'];
29
- } else {
30
- $media = new TNP_Media();
31
- // A placeholder can be set by a preset and it is kept indefinitely
32
- if (!empty($options['placeholder'])) {
33
- $media->url = $options['placeholder'];
34
- $media->width = 600;
35
- $media->height = 250;
36
- } else {
37
- $media->url = 'https://source.unsplash.com/1200x500/daily';
38
- $media->width = 600;
39
- $media->height = 250;
40
- }
41
- }
42
- } else {
43
- $media = tnp_resize_2x($options['image']['id'], [600, 0]);
44
- // Should never happen but... it happens
45
- if (!$media) {
46
- echo 'The selected media file cannot be processed';
47
- return;
48
- }
49
- }
50
-
51
- if (!empty($options['width'])) {
52
- $media->set_width($options['width']);
53
- }
54
- $media->link = $options['url'];
55
- if (!empty($options['image-alt'])) {
56
- $media->alt = $options['image-alt'];
57
- }
58
-
59
- ?>
60
-
61
- <table border="0" cellspacing="0" cellpadding="0" width="100%" class="responsive" style="margin: 0;">
62
- <tr>
63
- <td align="<?php echo esc_attr($options['align']) ?>" valign="middle" width="100%">
64
- <?php echo TNP_Composer::image($media); ?>
65
- </td>
66
- </tr>
67
- </table>
1
+ <?php
2
+ /*
3
+ * Name: Single image
4
+ * Section: content
5
+ * Description: A single image with link
6
+ */
7
+
8
+ /* @var $options array */
9
+ /* @var $wpdb wpdb */
10
+
11
+ $defaults = array(
12
+ 'image' => '',
13
+ 'image-alt' => '',
14
+ 'url' => '',
15
+ 'width' => 0,
16
+ 'align' => 'center',
17
+ 'block_background' => '',
18
+ 'block_padding_left' => 0,
19
+ 'block_padding_right' => 0,
20
+ 'block_padding_bottom' => 15,
21
+ 'block_padding_top' => 15
22
+ );
23
+
24
+ $options = array_merge($defaults, $options);
25
+
26
+ if (empty($options['image']['id'])) {
27
+ if (!empty($options['image-url'])) {
28
+ $media = new TNP_Media();
29
+ $media->url = $options['image-url'];
30
+ } else {
31
+ $media = new TNP_Media();
32
+ // A placeholder can be set by a preset and it is kept indefinitely
33
+ if (!empty($options['placeholder'])) {
34
+ $media->url = $options['placeholder'];
35
+ $media->width = 600;
36
+ $media->height = 250;
37
+ } else {
38
+ $media->url = 'https://source.unsplash.com/1200x500/daily';
39
+ $media->width = 600;
40
+ $media->height = 250;
41
+ }
42
+ }
43
+ } else {
44
+ $media = tnp_resize_2x($options['image']['id'], [600, 0]);
45
+ // Should never happen but... it happens
46
+ if (!$media) {
47
+ echo 'The selected media file cannot be processed';
48
+ return;
49
+ }
50
+ }
51
+
52
+ if (!empty($options['width'])) {
53
+ $media->set_width($options['width']);
54
+ }
55
+ $media->link = $options['url'];
56
+ $media->alt = $options['image-alt'];
57
+
58
+ ?>
59
+
60
+ <table border="0" cellspacing="0" cellpadding="0" width="100%" class="responsive" style="margin: 0;">
61
+ <tr>
62
+ <td align="<?php echo esc_attr($options['align']) ?>" valign="middle" width="100%">
63
+ <?php echo TNP_Composer::image($media); ?>
64
+ </td>
65
+ </tr>
66
+ </table>
 
emails/composer.php CHANGED
@@ -1,167 +1,167 @@
1
- <?php
2
- /* @var $this NewsletterEmails */
3
- defined('ABSPATH') || exit;
4
-
5
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
-
7
- $controls = new NewsletterControls();
8
- $module = NewsletterEmails::instance();
9
-
10
- wp_enqueue_style('tnpc-newsletter-style', home_url('/') . '?na=emails-composer-css');
11
-
12
- include NEWSLETTER_INCLUDES_DIR . '/codemirror.php';
13
-
14
- $email = null;
15
-
16
- if ($controls->is_action()) {
17
-
18
- if ($controls->is_action('save_preset')) {
19
- $this->admin_logger->info('Saving new preset: ' . $controls->data['subject']);
20
- // Create new preset email
21
- $email = new stdClass();
22
- TNP_Composer::update_email($email, $controls);
23
- $email->type = NewsletterEmails::PRESET_EMAIL_TYPE;
24
- $email->editor = NewsletterEmails::EDITOR_COMPOSER;
25
- $email->subject = $controls->data['subject'];
26
- $email->message = $controls->data['message'];
27
-
28
- $email = Newsletter::instance()->save_email($email);
29
-
30
- $redirect = $module->get_admin_page_url('composer');
31
- $controls->js_redirect($redirect);
32
-
33
- return;
34
- }
35
-
36
- if ($controls->is_action('update_preset')) {
37
- $this->admin_logger->info('Updating preset ' . $_POST['preset_id']);
38
- $email = Newsletter::instance()->get_email($_POST['preset_id']);
39
- TNP_Composer::update_email($email, $controls);
40
-
41
- $email->subject = $controls->data['subject'];
42
-
43
- // We store only the blocks, after the TNP_Composer::update_email(...) call we have the full HTML
44
- $email->message = $controls->data['message'];
45
-
46
- $email = Newsletter::instance()->save_email($email);
47
-
48
- $redirect = $module->get_admin_page_url('composer');
49
- $controls->js_redirect($redirect);
50
- }
51
-
52
-
53
- if (empty($_GET['id'])) {
54
-
55
- $this->admin_logger->info('Saving new newsletter from composer');
56
-
57
- // Create a new email
58
- $email = new stdClass();
59
- $email->status = 'new';
60
- $email->track = Newsletter::instance()->options['track'];
61
- $email->token = $module->get_token();
62
- $email->message_text = "This email requires a modern e-mail reader but you can view the email online here:\n{email_url}.\nThank you, " . wp_specialchars_decode(get_option('blogname'), ENT_QUOTES) .
63
- "\nTo change your subscription follow: {profile_url}.";
64
- $email->editor = NewsletterEmails::EDITOR_COMPOSER;
65
- $email->type = 'message';
66
- $email->send_on = time();
67
- $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
68
-
69
- TNP_Composer::update_email($email, $controls);
70
-
71
- $email = Newsletter::instance()->save_email($email);
72
- if ($controls->is_action('preview')) {
73
- $redirect = $module->get_admin_page_url('edit');
74
- } else {
75
- $redirect = $module->get_admin_page_url('composer');
76
- }
77
-
78
- $controls->js_redirect($redirect . '&id=' . $email->id);
79
- } else {
80
- $this->admin_logger->info('Saving newsletter ' . $_GET['id'] . ' from composer');
81
- $email = Newsletter::instance()->get_email($_GET['id']);
82
- if ($email->updated != $controls->data['updated']) {
83
- $controls->errors = 'This newsletter has been modified by someone else. Cannot save.';
84
- if (!empty($email->options['sender_email'])) {
85
- $controls->data['sender_email'] = $email->options['sender_email'];
86
- } else {
87
- $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
88
- }
89
-
90
- if (!empty($email->options['sender_name'])) {
91
- $controls->data['sender_name'] = $email->options['sender_name'];
92
- } else {
93
- $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
94
- }
95
- } else {
96
- TNP_Composer::update_email($email, $controls);
97
- $email->updated = time();
98
- $email = Newsletter::instance()->save_email($email);
99
- TNP_Composer::prepare_controls($controls, $email);
100
- if ($controls->is_action('save')) {
101
- $controls->add_message_saved();
102
- }
103
- }
104
- }
105
-
106
- if ($controls->is_action('preview')) {
107
- $redirect = $module->get_admin_page_url('edit');
108
- $controls->js_redirect($redirect . '&id=' . $email->id);
109
- }
110
-
111
- if ($controls->is_action('test')) {
112
- $module->send_test_email($module->get_email($email->id), $controls);
113
- }
114
-
115
- if ($controls->is_action('send-test-to-email-address')) {
116
- $custom_email = sanitize_email($_POST['test_address_email']);
117
- if (!empty($custom_email)) {
118
- try {
119
- $message = $module->send_test_newsletter_to_email_address($module->get_email($email->id), $custom_email);
120
- $controls->messages .= $message;
121
- } catch (Exception $e) {
122
- $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
123
- }
124
- } else {
125
- $controls->errors = __('Empty email address', 'newsletter');
126
- }
127
- }
128
- } else {
129
-
130
- if (!empty($_GET['id'])) {
131
- $email = Newsletter::instance()->get_email((int) $_GET['id']);
132
- }
133
- TNP_Composer::prepare_controls($controls, $email);
134
- }
135
- ?>
136
-
137
- <div id="tnp-notification">
138
- <?php
139
- $controls->show();
140
- $controls->messages = '';
141
- $controls->errors = '';
142
- ?>
143
- </div>
144
-
145
- <div class="wrap tnp-emails-composer" id="tnp-wrap">
146
-
147
- <?php $controls->composer_load_v2(true); ?>
148
-
149
- <div id="tnp-heading" class="tnp-composer-heading">
150
- <div class="tnpc-logo">
151
- <p>The Newsletter Plugin <strong>Composer</strong></p>
152
- </div>
153
- <div class="tnpc-controls">
154
- <form method="post" action="" id="tnpc-form">
155
- <?php $controls->init(); ?>
156
-
157
- <?php $controls->composer_fields_v2(); ?>
158
-
159
- <?php $controls->button('update_preset', __('Update preset', 'newsletter'), 'tnpc_update_preset(this.form)', 'update-preset-button'); ?>
160
- <?php $controls->button('save_preset', __('Save as preset', 'newsletter'), 'tnpc_save_preset(this.form)', 'save-preset-button'); ?>
161
- <?php $controls->button_confirm('reset', __('Back to last save', 'newsletter'), 'Are you sure?'); ?>
162
- <?php $controls->button('save', __('Save', 'newsletter'), 'tnpc_save(this.form); this.form.submit();'); ?>
163
- <?php $controls->button('preview', __('Next', 'newsletter') . ' &raquo;', 'tnpc_save(this.form); this.form.submit();'); ?>
164
- </form>
165
- </div>
166
- </div>
167
- </div>
1
+ <?php
2
+ /* @var $this NewsletterEmails */
3
+ defined('ABSPATH') || exit;
4
+
5
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
6
+
7
+ $controls = new NewsletterControls();
8
+ $module = NewsletterEmails::instance();
9
+
10
+ wp_enqueue_style('tnpc-newsletter-style', home_url('/') . '?na=emails-composer-css');
11
+
12
+ include NEWSLETTER_INCLUDES_DIR . '/codemirror.php';
13
+
14
+ $email = null;
15
+
16
+ if ($controls->is_action()) {
17
+
18
+ if ($controls->is_action('save_preset')) {
19
+ $this->admin_logger->info('Saving new preset: ' . $controls->data['subject']);
20
+ // Create new preset email
21
+ $email = new stdClass();
22
+ TNP_Composer::update_email($email, $controls);
23
+ $email->type = NewsletterEmails::PRESET_EMAIL_TYPE;
24
+ $email->editor = NewsletterEmails::EDITOR_COMPOSER;
25
+ $email->subject = $controls->data['subject'];
26
+ $email->message = $controls->data['message'];
27
+
28
+ $email = Newsletter::instance()->save_email($email);
29
+
30
+ $redirect = $module->get_admin_page_url('composer');
31
+ $controls->js_redirect($redirect);
32
+
33
+ return;
34
+ }
35
+
36
+ if ($controls->is_action('update_preset')) {
37
+ $this->admin_logger->info('Updating preset ' . $_POST['preset_id']);
38
+ $email = Newsletter::instance()->get_email($_POST['preset_id']);
39
+ TNP_Composer::update_email($email, $controls);
40
+
41
+ $email->subject = $controls->data['subject'];
42
+
43
+ // We store only the blocks, after the TNP_Composer::update_email(...) call we have the full HTML
44
+ $email->message = $controls->data['message'];
45
+
46
+ $email = Newsletter::instance()->save_email($email);
47
+
48
+ $redirect = $module->get_admin_page_url('composer');
49
+ $controls->js_redirect($redirect);
50
+ }
51
+
52
+
53
+ if (empty($_GET['id'])) {
54
+
55
+ $this->admin_logger->info('Saving new newsletter from composer');
56
+
57
+ // Create a new email
58
+ $email = new stdClass();
59
+ $email->status = 'new';
60
+ $email->track = Newsletter::instance()->options['track'];
61
+ $email->token = $module->get_token();
62
+ $email->message_text = "This email requires a modern e-mail reader but you can view the email online here:\n{email_url}.\nThank you, " . wp_specialchars_decode(get_option('blogname'), ENT_QUOTES) .
63
+ "\nTo change your subscription follow: {profile_url}.";
64
+ $email->editor = NewsletterEmails::EDITOR_COMPOSER;
65
+ $email->type = 'message';
66
+ $email->send_on = time();
67
+ $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
68
+
69
+ TNP_Composer::update_email($email, $controls);
70
+
71
+ $email = Newsletter::instance()->save_email($email);
72
+ if ($controls->is_action('preview')) {
73
+ $redirect = $module->get_admin_page_url('edit');
74
+ } else {
75
+ $redirect = $module->get_admin_page_url('composer');
76
+ }
77
+
78
+ $controls->js_redirect($redirect . '&id=' . $email->id);
79
+ } else {
80
+ $this->admin_logger->info('Saving newsletter ' . $_GET['id'] . ' from composer');
81
+ $email = Newsletter::instance()->get_email($_GET['id']);
82
+ if ($email->updated != $controls->data['updated']) {
83
+ $controls->errors = 'This newsletter has been modified by someone else. Cannot save.';
84
+ if (!empty($email->options['sender_email'])) {
85
+ $controls->data['sender_email'] = $email->options['sender_email'];
86
+ } else {
87
+ $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
88
+ }
89
+
90
+ if (!empty($email->options['sender_name'])) {
91
+ $controls->data['sender_name'] = $email->options['sender_name'];
92
+ } else {
93
+ $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
94
+ }
95
+ } else {
96
+ TNP_Composer::update_email($email, $controls);
97
+ $email->updated = time();
98
+ $email = Newsletter::instance()->save_email($email);
99
+ TNP_Composer::prepare_controls($controls, $email);
100
+ if ($controls->is_action('save')) {
101
+ $controls->add_message_saved();
102
+ }
103
+ }
104
+ }
105
+
106
+ if ($controls->is_action('preview')) {
107
+ $redirect = $module->get_admin_page_url('edit');
108
+ $controls->js_redirect($redirect . '&id=' . $email->id);
109
+ }
110
+
111
+ if ($controls->is_action('test')) {
112
+ $module->send_test_email($module->get_email($email->id), $controls);
113
+ }
114
+
115
+ if ($controls->is_action('send-test-to-email-address')) {
116
+ $custom_email = sanitize_email($_POST['test_address_email']);
117
+ if (!empty($custom_email)) {
118
+ try {
119
+ $message = $module->send_test_newsletter_to_email_address($module->get_email($email->id), $custom_email);
120
+ $controls->messages .= $message;
121
+ } catch (Exception $e) {
122
+ $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
123
+ }
124
+ } else {
125
+ $controls->errors = __('Empty email address', 'newsletter');
126
+ }
127
+ }
128
+ } else {
129
+
130
+ if (!empty($_GET['id'])) {
131
+ $email = Newsletter::instance()->get_email((int) $_GET['id']);
132
+ }
133
+ TNP_Composer::prepare_controls($controls, $email);
134
+ }
135
+ ?>
136
+
137
+ <div id="tnp-notification">
138
+ <?php
139
+ $controls->show();
140
+ $controls->messages = '';
141
+ $controls->errors = '';
142
+ ?>
143
+ </div>
144
+
145
+ <div class="wrap tnp-emails-composer" id="tnp-wrap">
146
+
147
+ <?php $controls->composer_load_v2(true); ?>
148
+
149
+ <div id="tnp-heading" class="tnp-composer-heading">
150
+ <div class="tnpc-logo">
151
+ <p>The Newsletter Plugin <strong>Composer</strong></p>
152
+ </div>
153
+ <div class="tnpc-controls">
154
+ <form method="post" action="" id="tnpc-form">
155
+ <?php $controls->init(); ?>
156
+
157
+ <?php $controls->composer_fields_v2(); ?>
158
+
159
+ <?php $controls->button('update_preset', __('Update preset', 'newsletter'), 'tnpc_update_preset(this.form)', 'update-preset-button'); ?>
160
+ <?php $controls->button('save_preset', __('Save as preset', 'newsletter'), 'tnpc_save_preset(this.form)', 'save-preset-button'); ?>
161
+ <?php $controls->button_confirm('reset', __('Back to last save', 'newsletter'), 'Are you sure?'); ?>
162
+ <?php $controls->button('save', __('Save', 'newsletter'), 'tnpc_save(this.form); this.form.submit();'); ?>
163
+ <?php $controls->button('preview', __('Next', 'newsletter') . ' &raquo;', 'tnpc_save(this.form); this.form.submit();'); ?>
164
+ </form>
165
+ </div>
166
+ </div>
167
+ </div>
includes/controls.php CHANGED
@@ -1263,6 +1263,7 @@ class NewsletterControls {
1263
  $this->checkbox_group($name, $c->cat_ID, esc_html($c->cat_name));
1264
  }
1265
  echo '<div style="clear: both"></div>';
 
1266
  }
1267
 
1268
  /**
1263
  $this->checkbox_group($name, $c->cat_ID, esc_html($c->cat_name));
1264
  }
1265
  echo '<div style="clear: both"></div>';
1266
+ echo '</div>';
1267
  }
1268
 
1269
  /**
includes/fields.php CHANGED
@@ -10,7 +10,7 @@ class NewsletterFields {
10
  }
11
 
12
  public function _open($subclass = '') {
13
- echo '<div class="tnp-field ', $subclass, '">';
14
  }
15
 
16
  public function _close() {
@@ -22,7 +22,7 @@ class NewsletterFields {
22
  return;
23
  }
24
  // Do not escape, HTML allowed
25
- echo '<label class="tnp-label">', $text, '</label>';
26
  }
27
 
28
  public function _description($attrs) {
@@ -30,7 +30,7 @@ class NewsletterFields {
30
  return;
31
  }
32
  // Do not escape, HTML allowed
33
- echo '<div class="tnp-description">', $attrs['description'], '</div>';
34
  }
35
 
36
  public function _id($name) {
@@ -68,16 +68,16 @@ class NewsletterFields {
68
  */
69
  public function section($title = '') {
70
  // Do not escape, HTML allowed
71
- echo '<h3 class="tnp-section">', $title, '</h3>';
72
  }
73
 
74
  public function separator() {
75
- echo '<div class="tnp-field tnp-separator"></div>';
76
  }
77
 
78
  public function checkbox($name, $label = '', $attrs = []) {
79
  $attrs = $this->_merge_base_attrs($attrs);
80
- $this->_open('tnp-checkbox');
81
  $this->controls->checkbox($name, $label);
82
  $this->_description($attrs);
83
  $this->_close();
@@ -294,7 +294,22 @@ class NewsletterFields {
294
  $attrs = $this->_merge_attrs($attrs);
295
  $this->_open();
296
  $this->_label($label);
297
- $this->controls->yesno($name);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  $this->_description($attrs);
299
  $this->_close();
300
  }
@@ -316,7 +331,7 @@ class NewsletterFields {
316
  */
317
  public function size($name, $label = '', $attrs = []) {
318
  $attrs = $this->_merge_attrs($attrs, ['description' => '', 'placeholder' => '', 'size' => 0, 'label_after' => 'px']);
319
- $this->_open('tnp-size');
320
  $this->_label($label);
321
  $value = $this->controls->get_value($name);
322
  echo '<input id="', $this->_id($name), '" placeholder="', esc_attr($attrs['placeholder']), '" name="', $this->_name($name), '" type="text"';
10
  }
11
 
12
  public function _open($subclass = '') {
13
+ echo '<div class="tnpf-field ', $subclass, '">';
14
  }
15
 
16
  public function _close() {
22
  return;
23
  }
24
  // Do not escape, HTML allowed
25
+ echo '<label class="tnpf-label">', $text, '</label>';
26
  }
27
 
28
  public function _description($attrs) {
30
  return;
31
  }
32
  // Do not escape, HTML allowed
33
+ echo '<div class="tnpf-description">', $attrs['description'], '</div>';
34
  }
35
 
36
  public function _id($name) {
68
  */
69
  public function section($title = '') {
70
  // Do not escape, HTML allowed
71
+ echo '<div class="tnpf-section">', $title, '</div>';
72
  }
73
 
74
  public function separator() {
75
+ echo '<div class="tnpf-separator"></div>';
76
  }
77
 
78
  public function checkbox($name, $label = '', $attrs = []) {
79
  $attrs = $this->_merge_base_attrs($attrs);
80
+ $this->_open('tnpf-checkbox');
81
  $this->controls->checkbox($name, $label);
82
  $this->_description($attrs);
83
  $this->_close();
294
  $attrs = $this->_merge_attrs($attrs);
295
  $this->_open();
296
  $this->_label($label);
297
+
298
+ $value = isset($this->controls->data[$name]) ? (int) $this->controls->data[$name] : 0;
299
+
300
+ echo '<select style="width: 60px" name="options[', esc_attr($name), ']">';
301
+ echo '<option value="0"';
302
+ if ($value == 0) {
303
+ echo ' selected';
304
+ }
305
+ echo '>', __('No', 'newsletter'), '</option>';
306
+ echo '<option value="1"';
307
+ if ($value == 1) {
308
+ echo ' selected';
309
+ }
310
+ echo '>', __('Yes', 'newsletter'), '</option>';
311
+ echo '</select>';
312
+
313
  $this->_description($attrs);
314
  $this->_close();
315
  }
331
  */
332
  public function size($name, $label = '', $attrs = []) {
333
  $attrs = $this->_merge_attrs($attrs, ['description' => '', 'placeholder' => '', 'size' => 0, 'label_after' => 'px']);
334
+ $this->_open('tnpf-size');
335
  $this->_label($label);
336
  $value = $this->controls->get_value($name);
337
  echo '<input id="', $this->_id($name), '" placeholder="', esc_attr($attrs['placeholder']), '" name="', $this->_name($name), '" type="text"';
includes/module.php CHANGED
@@ -1,2608 +1,2608 @@
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)) return;
26
- if ($this->width < $width) return;
27
- $this->height = floor(($width / $this->width) * $this->height);
28
- $this->width = $width;
29
- }
30
-
31
- /** Sets the height recalculating the width */
32
- public function set_height($height) {
33
- $height = (int) $height;
34
- $this->width = floor(($height / $this->height) * $this->width);
35
- $this->height = $height;
36
- }
37
-
38
- }
39
-
40
- /**
41
- * @property int $id The list unique identifier
42
- * @property string $name The list name
43
- * @property bool $forced If the list must be added to every new subscriber
44
- * @property int $status When and how the list is visible to the subscriber - see constants
45
- * @property bool $checked If it must be pre-checked on subscription form
46
- * @property array $languages The list of language used to pre-assign this list
47
- */
48
- class TNP_List {
49
-
50
- const STATUS_PRIVATE = 0;
51
- const STATUS_PUBLIC = 1;
52
- const SUBSCRIPTION_HIDE = 0;
53
- const SUBSCRIPTION_SHOW = 1;
54
- const SUBSCRIPTION_SHOW_CHECKED = 2;
55
- const PROFILE_HIDE = 0;
56
- const PROFILE_SHOW = 1;
57
-
58
- var $id;
59
- var $name;
60
- var $status;
61
- var $forced;
62
- var $checked;
63
- var $show_on_subscription;
64
- var $show_on_profile;
65
-
66
- function is_private() {
67
- return $this->status == self::STATUS_PRIVATE;
68
- }
69
-
70
- }
71
-
72
- /**
73
- * @property int $id The list unique identifier
74
- * @property string $name The list name
75
- * @property int $status When and how the list is visible to the subscriber - see constants
76
- * @property string $type Field type: text or select
77
- * @property array $options Field options (usually the select items)
78
- */
79
- class TNP_Profile {
80
-
81
- const STATUS_PRIVATE = 0;
82
- const STATUS_PUBLIC = 2;
83
- const STATUS_PROFILE_ONLY = 1;
84
- const STATUS_HIDDEN = 3; // Public but never shown (can be set with a hidden form field)
85
- const TYPE_TEXT = 'text';
86
- const TYPE_SELECT = 'select';
87
-
88
- public $id;
89
- public $name;
90
- public $status;
91
- public $type;
92
- public $options;
93
- public $placeholder;
94
- public $rule;
95
-
96
- public function __construct($id, $name, $status, $type, $options, $placeholder, $rule) {
97
- $this->id = $id;
98
- $this->name = $name;
99
- $this->status = $status;
100
- $this->type = $type;
101
- $this->options = $options;
102
- $this->placeholder = $placeholder;
103
- $this->rule = $rule;
104
- }
105
-
106
- function is_select() {
107
- return $this->type == self::TYPE_SELECT;
108
- }
109
-
110
- function is_text() {
111
- return $this->type == self::TYPE_TEXT;
112
- }
113
-
114
- function is_required() {
115
- return $this->rule == 1;
116
- }
117
-
118
- function is_private() {
119
- return $this->status == self::STATUS_PRIVATE;
120
- }
121
-
122
- function show_on_profile() {
123
- return $this->status == self::STATUS_PROFILE_ONLY || $this->status == self::STATUS_PUBLIC;
124
- }
125
-
126
- }
127
-
128
- class TNP_Profile_Service {
129
-
130
- /**
131
- *
132
- * @param string $language
133
- * @param string $type
134
- * @return TNP_Profile[]
135
- */
136
- static function get_profiles($language = '', $type = '') {
137
-
138
- static $profiles = [];
139
- $k = $language . $type;
140
-
141
- if (isset($profiles[$k])) {
142
- return $profiles[$k];
143
- }
144
-
145
- $profiles[$k] = [];
146
- $profile_options = NewsletterSubscription::instance()->get_options('profile', $language);
147
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
148
- if (empty($profile_options['profile_' . $i])) {
149
- continue;
150
- }
151
- $profile = self::create_profile_from_options($profile_options, $i);
152
-
153
- if (empty($type) ||
154
- ( $type == TNP_Profile::TYPE_SELECT && $profile->is_select() ) ||
155
- ( $type == TNP_Profile::TYPE_TEXT && $profile->is_text() )) {
156
- $profiles[$k]['' . $i] = $profile;
157
- }
158
- }
159
-
160
- return $profiles[$k];
161
- }
162
-
163
- static function get_profile_by_id($id, $language = '') {
164
-
165
- $profiles = self::get_profiles($language);
166
- if (isset($profiles[$id]))
167
- return $profiles[$id];
168
- return null;
169
- }
170
-
171
- /**
172
- * @return TNP_Profile
173
- */
174
- private static function create_profile_from_options($options, $id) {
175
- return new TNP_Profile(
176
- $id,
177
- $options['profile_' . $id],
178
- (int) $options['profile_' . $id . '_status'],
179
- $options['profile_' . $id . '_type'],
180
- self::string_db_options_to_array($options['profile_' . $id . '_options']),
181
- $options['profile_' . $id . '_placeholder'],
182
- $options['profile_' . $id . '_rules']
183
- );
184
- }
185
-
186
- /**
187
- * Returns a list of strings which are the items for the select field.
188
- * @return array
189
- */
190
- private static function string_db_options_to_array($string_options) {
191
- $items = array_map('trim', explode(',', $string_options));
192
- $items = array_combine($items, $items);
193
-
194
- return $items;
195
- }
196
-
197
- }
198
-
199
- /**
200
- * Represents the set of data collected by a subscription interface (form, API, ...). Only a valid
201
- * email is mandatory.
202
- */
203
- class TNP_Subscription_Data {
204
-
205
- var $email = null;
206
- var $name = null;
207
- var $surname = null;
208
- var $sex = null;
209
- var $language = null;
210
- var $referrer = null;
211
- var $http_referrer = null;
212
- var $ip = null;
213
- var $country = null;
214
- var $region = null;
215
- var $city = null;
216
-
217
- /**
218
- * Associative array id=>value of lists chosen by the subscriber. A list can be set to
219
- * 0 meaning the subscriber does not want to be in that list.
220
- * The lists must be public: non public lists are filtered.
221
- * @var array
222
- */
223
- var $lists = [];
224
- var $profiles = [];
225
-
226
- function merge_in($subscriber) {
227
- if (!$subscriber)
228
- $subscriber = new TNP_User();
229
- if (!empty($this->email))
230
- $subscriber->email = $this->email;
231
- if (!empty($this->name))
232
- $subscriber->name = $this->name;
233
- if (!empty($this->surname))
234
- $subscriber->surname = $this->surname;
235
- if (!empty($this->sex))
236
- $subscriber->sex = $this->sex;
237
- if (!empty($this->language))
238
- $subscriber->language = $this->language;
239
- if (!empty($this->ip))
240
- $subscriber->ip = $this->ip;
241
- if (!empty($this->referrer))
242
- $subscriber->referrer = $this->referrer;
243
- if (!empty($this->http_referrer))
244
- $subscriber->http_referrer = $this->http_referrer;
245
- if (!empty($this->country))
246
- $subscriber->country = $this->country;
247
- if (!empty($this->region))
248
- $subscriber->region = $this->region;
249
- if (!empty($this->city))
250
- $subscriber->city = $this->city;
251
-
252
-
253
- foreach ($this->lists as $id => $value) {
254
- $key = 'list_' . $id;
255
- $subscriber->$key = $value;
256
- }
257
-
258
- // Profile
259
- foreach ($this->profiles as $id => $value) {
260
- $key = 'profile_' . $id;
261
- $subscriber->$key = $value;
262
- }
263
- }
264
-
265
- /** Sets to active a set of lists. Accepts incorrect data (and ignores it).
266
- *
267
- * @param array $list_ids Array of list IDs
268
- */
269
- function add_lists($list_ids) {
270
- if (empty($list_ids) || !is_array($list_ids))
271
- return;
272
- foreach ($list_ids as $list_id) {
273
- $list_id = (int) $list_id;
274
- if ($list_id < 0 || $list_id > NEWSLETTER_LIST_MAX)
275
- continue;
276
- $this->lists[$list_id] = 1;
277
- }
278
- }
279
-
280
- }
281
-
282
- /**
283
- * Represents a subscription request with the subscriber data and actions to be taken by
284
- * the subscription engine (spam check, notifications, ...).
285
- */
286
- class TNP_Subscription {
287
-
288
- const EXISTING_ERROR = 1;
289
- const EXISTING_MERGE = 0;
290
-
291
- /**
292
- * Subscriber's data following the syntax of the TNP_User
293
- * @var TNP_Subscription_Data
294
- */
295
- var $data;
296
- var $spamcheck = true;
297
- // 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
298
- // optin as empty (for default), 'single' or 'double'.
299
- var $optin = null;
300
- // What to do with an existing subscriber???
301
- var $if_exists = self::EXISTING_MERGE;
302
-
303
- /**
304
- * Determines if the welcome or activation email should be sent. Note: sometime an activation email is sent disregarding
305
- * this setting.
306
- * @var boolean
307
- */
308
- var $send_emails = true;
309
-
310
- public function __construct() {
311
- $this->data = new TNP_Subscription_Data();
312
- }
313
-
314
- public function is_single_optin() {
315
- return $this->optin == 'single';
316
- }
317
-
318
- public function is_double_optin() {
319
- return $this->optin == 'double';
320
- }
321
-
322
- }
323
-
324
- /**
325
- * @property int $id The subscriber unique identifier
326
- * @property string $email The subscriber email
327
- * @property string $name The subscriber name or first name
328
- * @property string $surname The subscriber last name
329
- * @property string $status The subscriber status
330
- * @property string $language The subscriber language code 2 chars lowercase
331
- * @property string $token The subscriber secret token
332
- * @property string $country The subscriber country code 2 chars uppercase
333
- */
334
- class TNP_User {
335
-
336
- const STATUS_CONFIRMED = 'C';
337
- const STATUS_NOT_CONFIRMED = 'S';
338
- const STATUS_UNSUBSCRIBED = 'U';
339
- const STATUS_BOUNCED = 'B';
340
- const STATUS_COMPLAINED = 'P';
341
-
342
- var $ip = '';
343
-
344
- public static function get_status_label($status) {
345
- switch ($status) {
346
- case self::STATUS_NOT_CONFIRMED: return __('NOT CONFIRMED', 'newsletter');
347
- break;
348
- case self::STATUS_CONFIRMED: return __('CONFIRMED', 'newsletter');
349
- break;
350
- case self::STATUS_UNSUBSCRIBED: return __('UNSUBSCRIBED', 'newsletter');
351
- break;
352
- case self::STATUS_BOUNCED: return __('BOUNCED', 'newsletter');
353
- break;
354
- case self::STATUS_COMPLAINED: return __('COMPLAINED', 'newsletter');
355
- break;
356
- default:
357
- return __('Unknown', 'newsletter');
358
- }
359
- }
360
-
361
- }
362
-
363
- /**
364
- * @property int $id The email unique identifier
365
- * @property string $subject The email subject
366
- * @property string $message The email html message
367
- * @property int $track Check if the email stats should be active
368
- * @property array $options Email options
369
- * @property int $total Total emails to send
370
- * @property int $sent Total sent emails by now
371
- * @property int $open_count Total opened emails
372
- * @property int $click_count Total clicked emails
373
- * */
374
- class TNP_Email {
375
-
376
- const STATUS_DRAFT = 'new';
377
- const STATUS_SENT = 'sent';
378
- const STATUS_SENDING = 'sending';
379
- const STATUS_PAUSED = 'paused';
380
- const STATUS_ERROR = 'error';
381
-
382
- }
383
-
384
- class NewsletterModule {
385
-
386
- /**
387
- * @var NewsletterLogger
388
- */
389
- var $logger;
390
-
391
- /**
392
- * @var NewsletterLogger
393
- */
394
- var $admin_logger;
395
-
396
- /**
397
- * @var NewsletterStore
398
- */
399
- var $store;
400
-
401
- /**
402
- * The main module options
403
- * @var array
404
- */
405
- var $options;
406
-
407
- /**
408
- * @var string The module name
409
- */
410
- var $module;
411
-
412
- /**
413
- * The module version
414
- * @var string
415
- */
416
- var $version;
417
- var $old_version;
418
-
419
- /**
420
- * Prefix for all options stored on WordPress options table.
421
- * @var string
422
- */
423
- var $prefix;
424
-
425
- /**
426
- * @var NewsletterThemes
427
- */
428
- var $themes;
429
- var $components;
430
- static $current_language = '';
431
-
432
- function __construct($module, $version, $module_id = null, $components = array()) {
433
- $this->module = $module;
434
- $this->version = $version;
435
- $this->prefix = 'newsletter_' . $module;
436
- array_unshift($components, '');
437
- $this->components = $components;
438
-
439
- $this->logger = new NewsletterLogger($module);
440
-
441
- $this->options = $this->get_options();
442
- $this->store = NewsletterStore::singleton();
443
-
444
- //$this->logger->debug($module . ' constructed');
445
- // Version check
446
- if (is_admin()) {
447
- $this->admin_logger = new NewsletterLogger($module . '-admin');
448
- $this->old_version = get_option($this->prefix . '_version', '0.0.0');
449
-
450
- if ($this->old_version == '0.0.0') {
451
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
452
- $this->first_install();
453
- update_option($this->prefix . "_first_install_time", time(), FALSE);
454
- }
455
-
456
- if (strcmp($this->old_version, $this->version) != 0) {
457
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
458
- $this->logger->info('Version changed from ' . $this->old_version . ' to ' . $this->version);
459
- // Do all the stuff for this version change
460
- $this->upgrade();
461
- update_option($this->prefix . '_version', $this->version);
462
- }
463
-
464
- add_action('admin_menu', array($this, 'admin_menu'));
465
- }
466
- }
467
-
468
- /**
469
- *
470
- * @global wpdb $wpdb
471
- * @param string $query
472
- */
473
- function query($query) {
474
- global $wpdb;
475
-
476
- $this->logger->debug($query);
477
- //$start = microtime(true);
478
- $r = $wpdb->query($query);
479
- //$this->logger->debug($wpdb->last_query);
480
- //$this->logger->debug('Execution time: ' . (microtime(true)-$start));
481
- //$this->logger->debug('Result: ' . $r);
482
- if ($r === false) {
483
- $this->logger->fatal($query);
484
- $this->logger->fatal($wpdb->last_error);
485
- }
486
- return $r;
487
- }
488
-
489
- function get_results($query) {
490
- global $wpdb;
491
- $r = $wpdb->get_results($query);
492
- if ($r === false) {
493
- $this->logger->fatal($query);
494
- $this->logger->fatal($wpdb->last_error);
495
- }
496
- return $r;
497
- }
498
-
499
- /**
500
- *
501
- * @global wpdb $wpdb
502
- * @param string $table
503
- * @param array $data
504
- */
505
- function insert($table, $data) {
506
- global $wpdb;
507
- $this->logger->debug("inserting into table $table");
508
- $r = $wpdb->insert($table, $data);
509
- if ($r === false) {
510
- $this->logger->fatal($wpdb->last_error);
511
- }
512
- }
513
-
514
- function first_install() {
515
- $this->logger->debug('First install');
516
- }
517
-
518
- /**
519
- * Does a basic upgrade work, checking if the options is already present and if not (first
520
- * installation), recovering the defaults, saving them on database and initializing the
521
- * internal $options.
522
- */
523
- function upgrade() {
524
- foreach ($this->components as $component) {
525
- $this->logger->debug('Upgrading component ' . $component);
526
- $this->init_options($component);
527
- }
528
- }
529
-
530
- function init_options($component = '', $autoload = true) {
531
- global $wpdb;
532
- $default_options = $this->get_default_options($component);
533
- $options = $this->get_options($component);
534
- $options = array_merge($default_options, $options);
535
- $this->save_options($options, $component, $autoload);
536
- }
537
-
538
- function upgrade_query($query) {
539
- global $wpdb, $charset_collate;
540
-
541
- $this->logger->info('upgrade_query> Executing ' . $query);
542
- $suppress_errors = $wpdb->suppress_errors(true);
543
- $wpdb->query($query);
544
- if ($wpdb->last_error) {
545
- $this->logger->debug($wpdb->last_error);
546
- }
547
- $wpdb->suppress_errors($suppress_errors);
548
- }
549
-
550
- /** Returns a prefix to be used for option names and other things which need to be uniquely named. The parameter
551
- * "sub" should be used when a sub name is needed for another set of options or like.
552
- *
553
- * @param string $sub
554
- * @return string The prefix for names
555
- */
556
- function get_prefix($sub = '', $language = '') {
557
- return $this->prefix . (!empty($sub) ? '_' : '') . $sub . (!empty($language) ? '_' : '') . $language;
558
- }
559
-
560
- /**
561
- * Returns the options of a module, if not found an empty array.
562
- */
563
- function get_options($sub = '', $language = '') {
564
- $options = get_option($this->get_prefix($sub, $language), array());
565
- // Protection against scarmled database...
566
- if (!is_array($options)) {
567
- $options = array();
568
- }
569
- if ($language) {
570
- $main_options = get_option($this->get_prefix($sub));
571
- // Protection against scarmled database...
572
- if (!is_array($main_options))
573
- $main_options = array();
574
- //$options = array_merge($main_options, array_filter($options));
575
- $options = array_merge($main_options, $options);
576
- }
577
- return $options;
578
- }
579
-
580
- function get_default_options($sub = '') {
581
- if (!empty($sub)) {
582
- $sub = '-' . $sub;
583
- }
584
- $file = NEWSLETTER_DIR . '/' . $this->module . '/defaults' . $sub . '.php';
585
- if (file_exists($file)) {
586
- @include $file;
587
- }
588
-
589
- if (!isset($options) || !is_array($options)) {
590
- return array();
591
- }
592
- return $options;
593
- }
594
-
595
- function reset_options($sub = '') {
596
- $this->save_options(array_merge($this->get_options($sub), $this->get_default_options($sub)), $sub);
597
- return $this->get_options($sub);
598
- }
599
-
600
- /**
601
- * Saves the module options (or eventually a subset names as per parameter $sub). $options
602
- * should be an array (even if it can work with non array options.
603
- * The internal module options variable IS initialized with those new options only for the main
604
- * options (empty $sub parameter).
605
- * If the options contain a "theme" value, the theme-related options contained are saved as well
606
- * (used by some modules).
607
- *
608
- * @param array $options
609
- * @param string $sub
610
- */
611
- function save_options($options, $sub = '', $autoload = null, $language = '') {
612
- update_option($this->get_prefix($sub, $language), $options, $autoload);
613
- if (empty($sub) && empty($language)) {
614
- $this->options = $options;
615
- if (isset($this->themes) && isset($options['theme'])) {
616
- $this->themes->save_options($options['theme'], $options);
617
- }
618
- }
619
- }
620
-
621
- function delete_options($sub = '') {
622
- delete_option($this->get_prefix($sub));
623
- if (empty($sub)) {
624
- $this->options = array();
625
- }
626
- }
627
-
628
- function merge_options($options, $sub = '', $language = '') {
629
- if (!is_array($options)) {
630
- $options = array();
631
- }
632
- $old_options = $this->get_options($sub, $language);
633
- $this->save_options(array_merge($old_options, $options), $sub, null, $language);
634
- }
635
-
636
- function backup_options($sub) {
637
- $options = $this->get_options($sub);
638
- update_option($this->get_prefix($sub) . '_backup', $options, false);
639
- }
640
-
641
- function get_last_run($sub = '') {
642
- return get_option($this->get_prefix($sub) . '_last_run', 0);
643
- }
644
-
645
- /**
646
- * Save the module last run value. Used to store a timestamp for some modules,
647
- * for example the Feed by Mail module.
648
- *
649
- * @param int $time Unix timestamp (as returned by time() for example)
650
- * @param string $sub Sub module name (default empty)
651
- */
652
- function save_last_run($time, $sub = '') {
653
- update_option($this->get_prefix($sub) . '_last_run', $time);
654
- }
655
-
656
- /**
657
- * Sums $delta seconds to the last run time.
658
- * @param int $delta Seconds
659
- * @param string $sub Sub module name (default empty)
660
- */
661
- function add_to_last_run($delta, $sub = '') {
662
- $time = $this->get_last_run($sub);
663
- $this->save_last_run($time + $delta, $sub);
664
- }
665
-
666
- /**
667
- * Checks if the semaphore of that name (for this module) is still red. If it is active the method
668
- * returns false. If it is not active, it will be activated for $time seconds.
669
- *
670
- * Since this method activate the semaphore when called, it's name is a bit confusing.
671
- *
672
- * @param string $name Sempahore name (local to this module)
673
- * @param int $time Max time in second this semaphore should stay red
674
- * @return boolean False if the semaphore is red and you should not proceed, true is it was not active and has been activated.
675
- */
676
- function check_transient($name, $time) {
677
- if ($time < 60)
678
- $time = 60;
679
- //usleep(rand(0, 1000000));
680
- if (($value = get_transient($this->get_prefix() . '_' . $name)) !== false) {
681
- list($t, $v) = explode(';', $value, 2);
682
- $this->logger->error('Blocked by transient ' . $this->get_prefix() . '_' . $name . ' set ' . (time() - $t) . ' seconds ago by ' . $v);
683
- return false;
684
- }
685
- //$ip = ''; //gethostbyname(gethostname());
686
- $value = time() . ";" . ABSPATH . ';' . gethostname();
687
- set_transient($this->get_prefix() . '_' . $name, $value, $time);
688
- return true;
689
- }
690
-
691
- function delete_transient($name = '') {
692
- delete_transient($this->get_prefix() . '_' . $name);
693
- }
694
-
695
- /** Returns a random token of the specified size (or 10 characters if size is not specified).
696
- *
697
- * @param int $size
698
- * @return string
699
- */
700
- static function get_token($size = 10) {
701
- return substr(md5(rand()), 0, $size);
702
- }
703
-
704
- /**
705
- * Adds query string parameters to an URL checing id there are already other parameters.
706
- *
707
- * @param string $url
708
- * @param string $qs The part of query-string to add (param1=value1&param2=value2...)
709
- * @param boolean $amp If the method must use the &amp; instead of the plain & (default true)
710
- * @return string
711
- */
712
- static function add_qs($url, $qs, $amp = true) {
713
- if (strpos($url, '?') !== false) {
714
- if ($amp)
715
- return $url . '&amp;' . $qs;
716
- else
717
- return $url . '&' . $qs;
718
- } else
719
- return $url . '?' . $qs;
720
- }
721
-
722
- /**
723
- * Returns the email address normalized, lowercase with no spaces. If it's not a valid email
724
- * returns false.
725
- */
726
- static function normalize_email($email) {
727
- if (!is_string($email)) {
728
- return false;
729
- }
730
- $email = strtolower(trim($email));
731
- if (!is_email($email)) {
732
- return false;
733
- }
734
- //$email = apply_filters('newsletter_normalize_email', $email);
735
- return $email;
736
- }
737
-
738
- static function normalize_name($name) {
739
- $name = html_entity_decode($name, ENT_QUOTES);
740
- $name = str_replace(';', ' ', $name);
741
- $name = strip_tags($name);
742
-
743
- return $name;
744
- }
745
-
746
- static function normalize_sex($sex) {
747
- $sex = trim(strtolower($sex));
748
- if ($sex != 'f' && $sex != 'm') {
749
- $sex = 'n';
750
- }
751
- return $sex;
752
- }
753
-
754
- static function is_email($email, $empty_ok = false) {
755
-
756
- if (!is_string($email)) {
757
- return false;
758
- }
759
- $email = strtolower(trim($email));
760
-
761
- if ($email == '') {
762
- return $empty_ok;
763
- }
764
-
765
- if (!is_email($email)) {
766
- return false;
767
- }
768
- return true;
769
- }
770
-
771
- /**
772
- * Converts a GMT date from mysql (see the posts table columns) into a timestamp.
773
- *
774
- * @param string $s GMT date with format yyyy-mm-dd hh:mm:ss
775
- * @return int A timestamp
776
- */
777
- static function m2t($s) {
778
-
779
- // TODO: use the wordpress function I don't remember the name
780
- $s = explode(' ', $s);
781
- $d = explode('-', $s[0]);
782
- $t = explode(':', $s[1]);
783
- return gmmktime((int) $t[0], (int) $t[1], (int) $t[2], (int) $d[1], (int) $d[2], (int) $d[0]);
784
- }
785
-
786
- static function format_date($time) {
787
- if (empty($time)) {
788
- return '-';
789
- }
790
- return gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
791
- }
792
-
793
- static function format_time_delta($delta) {
794
- $days = floor($delta / (3600 * 24));
795
- $hours = floor(($delta % (3600 * 24)) / 3600);
796
- $minutes = floor(($delta % 3600) / 60);
797
- $seconds = floor(($delta % 60));
798
- $buffer = $days . ' days, ' . $hours . ' hours, ' . $minutes . ' minutes, ' . $seconds . ' seconds';
799
- return $buffer;
800
- }
801
-
802
- /**
803
- * Formats a scheduler returned "next execution" time, managing negative or false values. Many times
804
- * used in conjuction with "last run".
805
- *
806
- * @param string $name The scheduler name
807
- * @return string
808
- */
809
- static function format_scheduler_time($name) {
810
- $time = wp_next_scheduled($name);
811
- if ($time === false) {
812
- return 'No next run scheduled';
813
- }
814
- $delta = $time - time();
815
- // If less 10 minutes late it can be a cron problem but now it is working
816
- if ($delta < 0 && $delta > -600) {
817
- return 'Probably running now';
818
- } else if ($delta <= -600) {
819
- return 'It seems the cron system is not working. Reload the page to see if this message change.';
820
- }
821
- return 'Runs in ' . self::format_time_delta($delta);
822
- }
823
-
824
- static function date($time = null, $now = false, $left = false) {
825
- if (is_null($time)) {
826
- $time = time();
827
- }
828
- if ($time == false) {
829
- $buffer = 'none';
830
- } else {
831
- $buffer = gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
832
- }
833
- if ($now) {
834
- $buffer .= ' (now: ' . gmdate(get_option('date_format') . ' ' .
835
- get_option('time_format'), time() + get_option('gmt_offset') * 3600);
836
- $buffer .= ')';
837
- }
838
- if ($left) {
839
- $buffer .= ', ' . gmdate('H:i:s', $time - time()) . ' left';
840
- }
841
- return $buffer;
842
- }
843
-
844
- /**
845
- * Return an array of array with on first element the array of recent post and on second element the array
846
- * of old posts.
847
- *
848
- * @param array $posts
849
- * @param int $time
850
- */
851
- static function split_posts(&$posts, $time = 0) {
852
- if ($time < 0) {
853
- return array_chunk($posts, ceil(count($posts) / 2));
854
- }
855
-
856
- $result = array(array(), array());
857
-
858
- if (empty($posts))
859
- return $result;
860
-
861
- foreach ($posts as &$post) {
862
- if (self::is_post_old($post, $time))
863
- $result[1][] = $post;
864
- else
865
- $result[0][] = $post;
866
- }
867
- return $result;
868
- }
869
-
870
- static function is_post_old(&$post, $time = 0) {
871
- return self::m2t($post->post_date_gmt) <= $time;
872
- }
873
-
874
- static function get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
875
- global $post;
876
-
877
- if (empty($post_id))
878
- $post_id = $post->ID;
879
- if (empty($post_id))
880
- return $alternative;
881
-
882
- $image_id = function_exists('get_post_thumbnail_id') ? get_post_thumbnail_id($post_id) : false;
883
- if ($image_id) {
884
- $image = wp_get_attachment_image_src($image_id, $size);
885
- return $image[0];
886
- } else {
887
- $attachments = get_children(array('post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID'));
888
-
889
- if (empty($attachments)) {
890
- return $alternative;
891
- }
892
-
893
- foreach ($attachments as $id => $attachment) {
894
- $image = wp_get_attachment_image_src($id, $size);
895
- return $image[0];
896
- }
897
- }
898
- }
899
-
900
- /**
901
- * Cleans up a text containing url tags with appended the absolute URL (due to
902
- * the editor behavior) moving back them to the simple form.
903
- */
904
- static function clean_url_tags($text) {
905
- $text = str_replace('%7B', '{', $text);
906
- $text = str_replace('%7D', '}', $text);
907
-
908
- // Only tags which are {*_url}
909
- $text = preg_replace("/[\"']http[^\"']+(\\{[^\\}]+_url\\})[\"']/i", "\"\\1\"", $text);
910
- return $text;
911
- }
912
-
913
- function admin_menu() {
914
-
915
- }
916
-
917
- function add_menu_page($page, $title, $capability = '') {
918
- if (!Newsletter::instance()->is_allowed())
919
- return;
920
- $name = 'newsletter_' . $this->module . '_' . $page;
921
- add_submenu_page('newsletter_main_index', $title, $title, 'exist', $name, array($this, 'menu_page'));
922
- }
923
-
924
- function add_admin_page($page, $title) {
925
- if (!Newsletter::instance()->is_allowed()) {
926
- return;
927
- }
928
- $name = 'newsletter_' . $this->module . '_' . $page;
929
- add_submenu_page(null, $title, $title, 'exist', $name, array($this, 'menu_page'));
930
- }
931
-
932
- function sanitize_file_name($name) {
933
- return preg_replace('/[^a-z_\\-]/i', '', $name);
934
- }
935
-
936
- function menu_page() {
937
- global $plugin_page, $newsletter, $wpdb;
938
-
939
- $parts = explode('_', $plugin_page, 3);
940
- $module = $this->sanitize_file_name($parts[1]);
941
- $page = $this->sanitize_file_name($parts[2]);
942
- $page = str_replace('_', '-', $page);
943
-
944
- $file = NEWSLETTER_DIR . '/' . $module . '/' . $page . '.php';
945
-
946
- require $file;
947
- }
948
-
949
- function get_admin_page_url($page) {
950
- return admin_url('admin.php') . '?page=newsletter_' . $this->module . '_' . $page;
951
- }
952
-
953
- /** Returns all the emails of the give type (message, feed, followup, ...) and in the given format
954
- * (default as objects). Return false on error or at least an empty array. Errors should never
955
- * occur.
956
- *
957
- * @global wpdb $wpdb
958
- * @param string $type
959
- * @return boolean|array
960
- */
961
- function get_emails($type = null, $format = OBJECT) {
962
- global $wpdb;
963
- if ($type == null) {
964
- $list = $wpdb->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " order by id desc", $format);
965
- } else {
966
- $type = (string) $type;
967
- $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where type=%s order by id desc", $type), $format);
968
- }
969
- if ($wpdb->last_error) {
970
- $this->logger->error($wpdb->last_error);
971
- return false;
972
- }
973
- if (empty($list)) {
974
- return array();
975
- }
976
- return $list;
977
- }
978
-
979
- /**
980
- * @param string $key
981
- * @param mixed $value
982
- * @return TNP_Email[]
983
- */
984
- function get_emails_by_field($key, $value) {
985
- global $wpdb;
986
-
987
- $value_placeholder = is_int($value) ? '%d' : '%s';
988
-
989
- $query = $wpdb->prepare("SELECT * FROM " . NEWSLETTER_EMAILS_TABLE . " WHERE %1s=$value_placeholder ORDER BY id DESC", $key, $value);
990
-
991
- $email_list = $wpdb->get_results($query);
992
-
993
- if ($wpdb->last_error) {
994
- $this->logger->error($wpdb->last_error);
995
-
996
- return [];
997
- }
998
-
999
- //Unserialize options
1000
- array_walk($email_list, function ($email) {
1001
- $email->options = maybe_unserialize($email->options);
1002
- if (!is_array($email->options)) {
1003
- $email->options = [];
1004
- }
1005
- });
1006
-
1007
- return $email_list;
1008
- }
1009
-
1010
- /**
1011
- * Retrieves an email from DB and unserialize the options.
1012
- *
1013
- * @param mixed $id
1014
- * @param string $format
1015
- * @return TNP_Email An object with the same fields of TNP_Email, but not actually of that type
1016
- */
1017
- function get_email($id, $format = OBJECT) {
1018
- $email = $this->store->get_single(NEWSLETTER_EMAILS_TABLE, $id, $format);
1019
- if (!$email) {
1020
- return null;
1021
- }
1022
- if ($format == OBJECT) {
1023
- $email->options = maybe_unserialize($email->options);
1024
- if (!is_array($email->options)) {
1025
- $email->options = array();
1026
- }
1027
- if (empty($email->query)) {
1028
- $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1029
- }
1030
- } else if ($format == ARRAY_A) {
1031
- $email['options'] = maybe_unserialize($email['options']);
1032
- if (!is_array($email['options'])) {
1033
- $email['options'] = array();
1034
- }
1035
- if (empty($email['query'])) {
1036
- $email['query'] = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1037
- }
1038
- }
1039
- return $email;
1040
- }
1041
-
1042
- /**
1043
- * Save an email and provide serialization, if needed, of $email['options'].
1044
- * @return TNP_Email
1045
- */
1046
- function save_email($email, $return_format = OBJECT) {
1047
- if (is_object($email)) {
1048
- $email = (array) $email;
1049
- }
1050
-
1051
- if (isset($email['subject'])) {
1052
- if (mb_strlen($email['subject'], 'UTF-8') > 250) {
1053
- $email['subject'] = mb_substr($email['subject'], 0, 250, 'UTF-8');
1054
- }
1055
- }
1056
- if (isset($email['options']) && is_array($email['options'])) {
1057
- $email['options'] = serialize($email['options']);
1058
- }
1059
- $email = $this->store->save(NEWSLETTER_EMAILS_TABLE, $email, $return_format);
1060
- if ($return_format == OBJECT) {
1061
- $email->options = maybe_unserialize($email->options);
1062
- if (!is_array($email->options)) {
1063
- $email->options = [];
1064
- }
1065
- } else if ($return_format == ARRAY_A) {
1066
- $email['options'] = maybe_unserialize($email['options']);
1067
- if (!is_array($email['options'])) {
1068
- $email['options'] = [];
1069
- }
1070
- }
1071
- return $email;
1072
- }
1073
-
1074
- function get_email_from_request() {
1075
-
1076
- if (isset($_REQUEST['nek'])) {
1077
- list($id, $token) = @explode('-', $_REQUEST['nek'], 2);
1078
- } else if (isset($_COOKIE['tnpe'])) {
1079
- list($id, $token) = @explode('-', $_COOKIE['tnpe'], 2);
1080
- } else {
1081
- return null;
1082
- }
1083
-
1084
- $email = $this->get_email($id);
1085
-
1086
- // TODO: Check the token? It's really useful?
1087
-
1088
- return $email;
1089
- }
1090
-
1091
- /**
1092
- * Delete one or more emails identified by ID (single value or array of ID)
1093
- *
1094
- * @global wpdb $wpdb
1095
- * @param int|array $id Single numeric ID or an array of IDs to be deleted
1096
- * @return boolean
1097
- */
1098
- function delete_email($id) {
1099
- global $wpdb;
1100
- $r = $this->store->delete(NEWSLETTER_EMAILS_TABLE, $id);
1101
- if ($r !== false) {
1102
- // $id could be an array if IDs
1103
- $id = (array) $id;
1104
- foreach ($id as $email_id) {
1105
- $wpdb->delete(NEWSLETTER_STATS_TABLE, ['email_id' => $email_id]);
1106
- $wpdb->delete(NEWSLETTER_SENT_TABLE, ['email_id' => $email_id]);
1107
- }
1108
- }
1109
- return $r;
1110
- }
1111
-
1112
- function get_email_field($id, $field_name) {
1113
- return $this->store->get_field(NEWSLETTER_EMAILS_TABLE, $id, $field_name);
1114
- }
1115
-
1116
- function get_email_status_slug($email) {
1117
- $email = (object) $email;
1118
- if ($email->status == 'sending' && $email->send_on > time()) {
1119
- return 'scheduled';
1120
- }
1121
- return $email->status;
1122
- }
1123
-
1124
- function get_email_status_label($email) {
1125
- $email = (object) $email;
1126
- $status = $this->get_email_status_slug($email);
1127
- switch ($status) {
1128
- case 'sending':
1129
- return __('Sending', 'newsletter');
1130
- case 'scheduled':
1131
- return __('Scheduled', 'newsletter');
1132
- case 'sent':
1133
- return __('Sent', 'newsletter');
1134
- case 'paused':
1135
- return __('Paused', 'newsletter');
1136
- case 'new':
1137
- return __('Draft', 'newsletter');
1138
- default:
1139
- return ucfirst($email->status);
1140
- }
1141
- }
1142
-
1143
- function show_email_status_label($email) {
1144
- echo '<span class="tnp-email-status tnp-email-status--', $this->get_email_status_slug($email), '">', esc_html($this->get_email_status_label($email)), '</span>';
1145
- }
1146
-
1147
- function get_email_progress($email, $format = 'percent') {
1148
- return $email->total > 0 ? intval($email->sent / $email->total * 100) : 0;
1149
- }
1150
-
1151
- function show_email_progress_bar($email, $attrs = []) {
1152
-
1153
- $email = (object) $email;
1154
-
1155
- $attrs = array_merge(array('format' => 'percent', 'numbers' => false, 'scheduled' => false), $attrs);
1156
-
1157
- if ($email->status == 'sending' && $email->send_on > time()) {
1158
- if ($attrs['scheduled']) {
1159
- echo '<span class="tnp-progress-date">', $this->format_date($email->send_on), '</span>';
1160
- }
1161
- return;
1162
- } else if ($email->status == 'new') {
1163
- echo '';
1164
- return;
1165
- } else if ($email->status == 'sent') {
1166
- $percent = 100;
1167
- } else {
1168
- $percent = $this->get_email_progress($email);
1169
- }
1170
-
1171
- echo '<div class="tnp-progress tnp-progress--' . $email->status . '">';
1172
- echo '<div class="tnp-progress-bar" role="progressbar" style="width: ', $percent, '%;">&nbsp;', $percent, '%&nbsp;</div>';
1173
- echo '</div>';
1174
- if ($attrs['numbers']) {
1175
- if ($email->status == 'sent') {
1176
- echo '<div class="tnp-progress-numbers">', $email->total, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1177
- } else {
1178
- echo '<div class="tnp-progress-numbers">', $email->sent, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1179
- }
1180
- }
1181
- }
1182
-
1183
- function get_email_type_label($type) {
1184
-
1185
- // Is an email?
1186
- if (is_object($type))
1187
- $type = $type->type;
1188
-
1189
- $label = apply_filters('newsletter_email_type', '', $type);
1190
-
1191
- if (!empty($label))
1192
- return $label;
1193
-
1194
- switch ($type) {
1195
- case 'followup':
1196
- return 'Followup';
1197
- case 'message':
1198
- return 'Standard Newsletter';
1199
- case 'feed':
1200
- return 'Feed by Mail';
1201
- }
1202
-
1203
- if (strpos($type, 'automated') === 0) {
1204
- list($a, $id) = explode('_', $type);
1205
- return 'Automated Channel ' . $id;
1206
- }
1207
-
1208
- return ucfirst($type);
1209
- }
1210
-
1211
- function get_email_progress_label($email) {
1212
- if ($email->status == 'sent' || $email->status == 'sending') {
1213
- return $email->sent . ' ' . __('of', 'newsletter') . ' ' . $email->total;
1214
- }
1215
- return '-';
1216
- }
1217
-
1218
- /**
1219
- * Returns the email unique key
1220
- * @param TNP_User $user
1221
- * @return string
1222
- */
1223
- function get_email_key($email) {
1224
- if (!isset($email->token)) {
1225
- return $email->id . '-';
1226
- }
1227
- return $email->id . '-' . $email->token;
1228
- }
1229
-
1230
- /** Searches for a user using the nk parameter or the ni and nt parameters. Tries even with the newsletter cookie.
1231
- * If found, the user object is returned or null.
1232
- * The user is returned without regards to his status that should be checked by caller.
1233
- *
1234
- * DO NOT REMOVE EVEN IF OLD
1235
- *
1236
- * @return TNP_User
1237
- */
1238
- function check_user($context = '') {
1239
- global $wpdb;
1240
-
1241
- $user = null;
1242
-
1243
- if (isset($_REQUEST['nk'])) {
1244
- list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1245
- } else if (isset($_COOKIE['newsletter'])) {
1246
- list ($id, $token) = @explode('-', $_COOKIE['newsletter'], 2);
1247
- }
1248
-
1249
- if (isset($id)) {
1250
- $user = $this->get_user($id);
1251
- if ($user) {
1252
- if ($context == 'preconfirm') {
1253
- if ($token != md5($user->token)) {
1254
- $user = null;
1255
- }
1256
- } else {
1257
- if ($token != $user->token) {
1258
- $user = null;
1259
- }
1260
- }
1261
- }
1262
- }
1263
-
1264
- if ($user == null && is_user_logged_in()) {
1265
- $user = $this->get_user_by_wp_user_id(get_current_user_id());
1266
- }
1267
- return $user;
1268
- }
1269
-
1270
- /** 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
1271
- * the "id" attribute or key and that is used to load the user.
1272
- *
1273
- * @global type $wpdb
1274
- * @param string|int|object|array $id_or_email
1275
- * @param string $format
1276
- * @return TNP_User|null
1277
- */
1278
- function get_user($id_or_email, $format = OBJECT) {
1279
- global $wpdb;
1280
-
1281
- if (empty($id_or_email))
1282
- return null;
1283
-
1284
- // To simplify the reaload of a user passing the user it self.
1285
- if (is_object($id_or_email)) {
1286
- $id_or_email = $id_or_email->id;
1287
- } else if (is_array($id_or_email)) {
1288
- $id_or_email = $id_or_email['id'];
1289
- }
1290
-
1291
- $id_or_email = strtolower(trim($id_or_email));
1292
-
1293
- if (is_numeric($id_or_email)) {
1294
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where id=%d limit 1", $id_or_email), $format);
1295
- } else {
1296
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $id_or_email), $format);
1297
- }
1298
-
1299
- if ($wpdb->last_error) {
1300
- $this->logger->error($wpdb->last_error);
1301
- return null;
1302
- }
1303
- return $r;
1304
- }
1305
-
1306
- /**
1307
- *
1308
- * @global wpdb $wpdb
1309
- * @param string $email
1310
- * @return TNP_User
1311
- */
1312
- function get_user_by_email($email) {
1313
- global $wpdb;
1314
-
1315
- $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $email));
1316
-
1317
- if ($wpdb->last_error) {
1318
- $this->logger->error($wpdb->last_error);
1319
- return null;
1320
- }
1321
- return $r;
1322
- }
1323
-
1324
- /**
1325
- * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
1326
- *
1327
- * @param type $user
1328
- */
1329
- function get_user_edit_url($user) {
1330
- $id = $this->to_int_id($user);
1331
- return admin_url('admin.php') . '?page=newsletter_users_edit&id=' . $id;
1332
- }
1333
-
1334
- /**
1335
- * Returns the user unique key
1336
- * @param TNP_User $user
1337
- * @return string
1338
- */
1339
- function get_user_key($user, $context = '') {
1340
- if (empty($user->token)) {
1341
- $this->refresh_user_token($user);
1342
- }
1343
-
1344
- if ($context == 'preconfirm') {
1345
- return $user->id . '-' . md5($user->token);
1346
- }
1347
- return $user->id . '-' . $user->token;
1348
- }
1349
-
1350
- function get_user_status_label($user, $html = false) {
1351
- if (!$html) return TNP_User::get_status_label($user->status);
1352
-
1353
- $label = TNP_User::get_status_label($user->status);
1354
- $class = 'unknown';
1355
- switch ($user->status) {
1356
- case TNP_User::STATUS_NOT_CONFIRMED: $class = 'not-confirmed';
1357
- break;
1358
- case TNP_User::STATUS_CONFIRMED: $class = 'confirmed';
1359
- break;
1360
- case TNP_User::STATUS_UNSUBSCRIBED: $class = 'unsubscribed';
1361
- break;
1362
- case TNP_User::STATUS_BOUNCED: $class = 'bounced';
1363
- break;
1364
- case TNP_User::STATUS_COMPLAINED: $class = 'complained';
1365
- break;
1366
- }
1367
- return '<span class="' . $class . '">' . esc_html($label) . '</span>';
1368
- }
1369
-
1370
- /**
1371
- * Return the user identified by the "nk" parameter (POST or GET).
1372
- * If no user can be found or the token is not matching, returns null.
1373
- * If die_on_fail is true it dies instead of return null.
1374
- *
1375
- * @param bool $die_on_fail
1376
- * @return TNP_User
1377
- */
1378
- function get_user_from_request($die_on_fail = false, $context = '') {
1379
- $id = 0;
1380
- if (isset($_REQUEST['nk'])) {
1381
- list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1382
- }
1383
- $user = $this->get_user($id);
1384
-
1385
- if ($user == null) {
1386
- if ($die_on_fail) {
1387
- die(__('No subscriber found.', 'newsletter'));
1388
- } else {
1389
- return $this->get_user_from_logged_in_user();
1390
- }
1391
- }
1392
-
1393
- if ($context == 'preconfirm') {
1394
- $user_token = md5($user->token);
1395
- } else {
1396
- $user_token = $user->token;
1397
- }
1398
-
1399
- if ($token != $user_token) {
1400
- if ($die_on_fail) {
1401
- die(__('No subscriber found.', 'newsletter'));
1402
- } else {
1403
- return $this->get_user_from_logged_in_user();
1404
- }
1405
- }
1406
- return $user;
1407
- }
1408
-
1409
- function get_user_from_logged_in_user() {
1410
- if (is_user_logged_in()) {
1411
- return $this->get_user_by_wp_user_id(get_current_user_id());
1412
- }
1413
- return null;
1414
- }
1415
-
1416
- function get_user_count($refresh = false) {
1417
- global $wpdb;
1418
- $user_count = get_transient('newsletter_user_count');
1419
- if ($user_count === false || $refresh) {
1420
- $user_count = $wpdb->get_var("select count(*) from " . NEWSLETTER_USERS_TABLE . " where status='C'");
1421
- set_transient('newsletter_user_count', $user_count, DAY_IN_SECONDS);
1422
- }
1423
- return $user_count;
1424
- }
1425
-
1426
- function get_profile($id, $language = '') {
1427
- return TNP_Profile_Service::get_profile_by_id($id, $language);
1428
- }
1429
-
1430
- /**
1431
- * @param string $language The language for the list labels (it does not affect the lists returned)
1432
- * @return TNP_Profile[]
1433
- */
1434
- function get_profiles($language = '') {
1435
- return TNP_Profile_Service::get_profiles($language);
1436
- }
1437
-
1438
- /**
1439
- * Returns a list of TNP_Profile which are public.
1440
- *
1441
- * @staticvar array $profiles
1442
- * @param string $language
1443
- * @return TNP_Profile[]
1444
- */
1445
- function get_profiles_public($language = '') {
1446
- static $profiles = [];
1447
- if (isset($profiles[$language])) {
1448
- return $profiles[$language];
1449
- }
1450
-
1451
- $profiles[$language] = [];
1452
- $all = $this->get_profiles($language);
1453
- foreach ($all as $profile) {
1454
- if ($profile->is_private())
1455
- continue;
1456
-
1457
- $profiles[$language]['' . $profile->id] = $profile;
1458
- }
1459
- return $profiles[$language];
1460
- }
1461
-
1462
- /**
1463
- * Really bad name!
1464
- * @staticvar array $profiles
1465
- * @param type $language
1466
- * @return array
1467
- */
1468
- function get_profiles_for_profile($language = '') {
1469
- static $profiles = [];
1470
- if (isset($profiles[$language])) {
1471
- return $profiles[$language];
1472
- }
1473
-
1474
- $profiles[$language] = [];
1475
- $all = $this->get_profiles($language);
1476
- foreach ($all as $profile) {
1477
- if (!$profile->show_on_profile())
1478
- continue;
1479
-
1480
- $profiles[$language]['' . $profile->id] = $profile;
1481
- }
1482
- return $profiles[$language];
1483
- }
1484
-
1485
- /**
1486
- * @param string $language The language for the list labels (it does not affect the lists returned)
1487
- * @return TNP_List[]
1488
- */
1489
- function get_lists($language = '') {
1490
- static $lists = array();
1491
- if (isset($lists[$language])) {
1492
- return $lists[$language];
1493
- }
1494
-
1495
- $lists[$language] = array();
1496
- $data = NewsletterSubscription::instance()->get_options('lists', $language);
1497
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
1498
- if (empty($data['list_' . $i])) {
1499
- continue;
1500
- }
1501
- $list = $this->create_tnp_list_from_db_lists_array($data, $i);
1502
-
1503
- $lists[$language]['' . $list->id] = $list;
1504
- }
1505
- return $lists[$language];
1506
- }
1507
-
1508
- public function create_tnp_list_from_db_lists_array($db_lists_array, $list_id) {
1509
-
1510
- $list = new TNP_List();
1511
- $list->name = $db_lists_array['list_' . $list_id];
1512
- $list->id = $list_id;
1513
-
1514
- // New format
1515
- if (isset($db_lists_array['list_' . $list_id . '_subscription'])) {
1516
- $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1517
- $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1518
- $list->checked = $db_lists_array['list_' . $list_id . '_subscription'] == 2;
1519
- $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_subscription']) && !$list->forced;
1520
- $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_profile']);
1521
- } else {
1522
- $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1523
- $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1524
- $list->checked = !empty($db_lists_array['list_' . $list_id . '_checked']);
1525
- $list->show_on_subscription = $db_lists_array['list_' . $list_id . '_status'] == 2 && !$list->forced;
1526
- $list->show_on_profile = $db_lists_array['list_' . $list_id . '_status'] == 1 || $db_lists_array['list_' . $list_id . '_status'] == 2;
1527
- }
1528
- if (empty($db_lists_array['list_' . $list_id . '_languages'])) {
1529
- $list->languages = array();
1530
- } else {
1531
- $list->languages = $db_lists_array['list_' . $list_id . '_languages'];
1532
- }
1533
-
1534
- return $list;
1535
- }
1536
-
1537
- /**
1538
- * Returns an array of TNP_List objects of lists that are public.
1539
- * @return TNP_List[]
1540
- */
1541
- function get_lists_public($language = '') {
1542
- static $lists = array();
1543
- if (isset($lists[$language])) {
1544
- return $lists[$language];
1545
- }
1546
-
1547
- $lists[$language] = array();
1548
- $all = $this->get_lists($language);
1549
- foreach ($all as $list) {
1550
- if ($list->status == TNP_List::STATUS_PRIVATE) {
1551
- continue;
1552
- }
1553
- $lists[$language]['' . $list->id] = $list;
1554
- }
1555
- return $lists[$language];
1556
- }
1557
-
1558
- /**
1559
- * Lists to be shown on subscription form.
1560
- *
1561
- * @return TNP_List[]
1562
- */
1563
- function get_lists_for_subscription($language = '') {
1564
- static $lists = array();
1565
- if (isset($lists[$language])) {
1566
- return $lists[$language];
1567
- }
1568
-
1569
- $lists[$language] = array();
1570
- $all = $this->get_lists($language);
1571
- foreach ($all as $list) {
1572
- if (!$list->show_on_subscription) {
1573
- continue;
1574
- }
1575
- $lists[$language]['' . $list->id] = $list;
1576
- }
1577
- return $lists[$language];
1578
- }
1579
-
1580
- /**
1581
- * Returns the lists to be shown in the profile page. The list is associative with
1582
- * the list ID as key.
1583
- *
1584
- * @return TNP_List[]
1585
- */
1586
- function get_lists_for_profile($language = '') {
1587
- static $lists = array();
1588
- if (isset($lists[$language])) {
1589
- return $lists[$language];
1590
- }
1591
-
1592
- $lists[$language] = array();
1593
- $all = $this->get_lists($language);
1594
- foreach ($all as $list) {
1595
- if (!$list->show_on_profile) {
1596
- continue;
1597
- }
1598
- $lists[$language]['' . $list->id] = $list;
1599
- }
1600
- return $lists[$language];
1601
- }
1602
-
1603
- /**
1604
- * Returns the list object or null if not found.
1605
- *
1606
- * @param int $id
1607
- * @return TNP_List
1608
- */
1609
- function get_list($id, $language = '') {
1610
- $lists = $this->get_lists($language);
1611
- if (!isset($lists['' . $id])) {
1612
- return null;
1613
- }
1614
-
1615
- return $lists['' . $id];
1616
- }
1617
-
1618
- /**
1619
- * NEVER CHANGE THIS METHOD SIGNATURE, USER BY THIRD PARTY PLUGINS.
1620
- *
1621
- * Saves a new user on the database. Return false if the email (that must be unique) is already
1622
- * there. For a new users set the token and creation time if not passed.
1623
- *
1624
- * @param array $user
1625
- * @return TNP_User|array|boolean Returns the subscriber reloaded from DB in the specified format. Flase on failure (duplicate email).
1626
- */
1627
- function save_user($user, $return_format = OBJECT) {
1628
- if (is_object($user)) {
1629
- $user = (array) $user;
1630
- }
1631
- if (empty($user['id'])) {
1632
- $existing = $this->get_user($user['email']);
1633
- if ($existing != null) {
1634
- return false;
1635
- }
1636
- if (empty($user['token'])) {
1637
- $user['token'] = NewsletterModule::get_token();
1638
- }
1639
- }
1640
-
1641
- // We still don't know when it happens but under some conditions, matbe external, lists are passed as NULL
1642
- foreach ($user as $key => $value) {
1643
- if (strpos($key, 'list_') !== 0) {
1644
- continue;
1645
- }
1646
- if (is_null($value)) {
1647
- unset($user[$key]);
1648
- } else {
1649
- $user[$key] = (int) $value;
1650
- }
1651
- }
1652
-
1653
- // Due to the unique index on email field, this can fail.
1654
- return $this->store->save(NEWSLETTER_USERS_TABLE, $user, $return_format);
1655
- }
1656
-
1657
- /**
1658
- * Updates the user last activity timestamp.
1659
- *
1660
- * @global wpdb $wpdb
1661
- * @param TNP_User $user
1662
- */
1663
- function update_user_last_activity($user) {
1664
- global $wpdb;
1665
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set last_activity=%d where id=%d limit 1", time(), $user->id));
1666
- }
1667
-
1668
- function update_user_ip($user, $ip) {
1669
- global $wpdb;
1670
- // Only if changed
1671
- $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));
1672
- }
1673
-
1674
- /**
1675
- * Finds single style blocks and adds a style attribute to every HTML tag with a class exactly matching the rules in the style
1676
- * block. HTML tags can use the attribute "inline-class" to exact match a style rules if they need a composite class definition.
1677
- *
1678
- * @param string $content
1679
- * @param boolean $strip_style_blocks
1680
- * @return string
1681
- */
1682
- function inline_css($content, $strip_style_blocks = false) {
1683
- $matches = array();
1684
- // "s" skips line breaks
1685
- $styles = preg_match('|<style>(.*?)</style>|s', $content, $matches);
1686
- if (isset($matches[1])) {
1687
- $style = str_replace(array("\n", "\r"), '', $matches[1]);
1688
- $rules = array();
1689
- preg_match_all('|\s*\.(.*?)\{(.*?)\}\s*|s', $style, $rules);
1690
- for ($i = 0; $i < count($rules[1]); $i++) {
1691
- $class = trim($rules[1][$i]);
1692
- $value = trim($rules[2][$i]);
1693
- $value = preg_replace('|\s+|', ' ', $value);
1694
- $content = str_replace(' class="' . $class . '"', ' class="' . $class . '" style="' . $value . '"', $content);
1695
- $content = str_replace(' inline-class="' . $class . '"', ' style="' . $value . '"', $content);
1696
- }
1697
- }
1698
-
1699
- if ($strip_style_blocks) {
1700
- return trim(preg_replace('|<style>.*?</style>|s', '', $content));
1701
- } else {
1702
- return $content;
1703
- }
1704
- }
1705
-
1706
- /**
1707
- * Returns a list of users marked as "test user".
1708
- * @return TNP_User[]
1709
- */
1710
- function get_test_users() {
1711
- return $this->store->get_all(NEWSLETTER_USERS_TABLE, "where test=1 and status in ('C', 'S')");
1712
- }
1713
-
1714
- /**
1715
- * Deletes a subscriber and cleans up all the stats table with his correlated data.
1716
- *
1717
- * @global wpdb $wpdb
1718
- * @param int|id[] $id
1719
- */
1720
- function delete_user($id) {
1721
- global $wpdb;
1722
- $id = (array) $id;
1723
- foreach ($id as $user_id) {
1724
- $user = $this->get_user($user_id);
1725
- if ($user) {
1726
- $r = $this->store->delete(NEWSLETTER_USERS_TABLE, $user_id);
1727
- $wpdb->delete(NEWSLETTER_STATS_TABLE, array('user_id' => $user_id));
1728
- $wpdb->delete(NEWSLETTER_SENT_TABLE, array('user_id' => $user_id));
1729
- do_action('newsletter_user_deleted', $user);
1730
- }
1731
- }
1732
-
1733
- return count($id);
1734
- }
1735
-
1736
- /**
1737
- * Add to a destination URL the parameters to identify the user, the email and to show
1738
- * an alert message, if required. The parameters are then managed by the [newsletter] shortcode.
1739
- *
1740
- * @param string $url If empty the standard newsletter page URL is used (usually it is empty, but sometime a custom URL has been specified)
1741
- * @param string $message_key The message identifier
1742
- * @param TNP_User|int $user
1743
- * @param TNP_Email|int $email
1744
- * @param string $alert An optional alter message to be shown. Does not work with custom URLs
1745
- * @return string The final URL with parameters
1746
- */
1747
- function build_message_url($url = '', $message_key = '', $user = null, $email = null, $alert = '') {
1748
- $params = 'nm=' . urlencode($message_key);
1749
- $language = '';
1750
- if ($user) {
1751
- if (!is_object($user)) {
1752
- $user = $this->get_user($user);
1753
- }
1754
- if ($message_key == 'confirmation') {
1755
- $params .= '&nk=' . urlencode($this->get_user_key($user, 'preconfirm'));
1756
- } else {
1757
- $params .= '&nk=' . urlencode($this->get_user_key($user));
1758
- }
1759
-
1760
- $language = $this->get_user_language($user);
1761
- }
1762
-
1763
- if ($email) {
1764
- if (!is_object($email)) {
1765
- $email = $this->get_email($email);
1766
- }
1767
- $params .= '&nek=' . urlencode($this->get_email_key($email));
1768
- }
1769
-
1770
- if ($alert) {
1771
- $params .= '&alert=' . urlencode($alert);
1772
- }
1773
-
1774
- if (empty($url)) {
1775
- $url = Newsletter::instance()->get_newsletter_page_url($language);
1776
- }
1777
-
1778
- return self::add_qs($url, $params, false);
1779
- }
1780
-
1781
- /**
1782
- * Builds a standard Newsletter action URL for the specified action.
1783
- *
1784
- * @param string $action
1785
- * @param TNP_User $user
1786
- * @param TNP_Email $email
1787
- * @return string
1788
- */
1789
- function build_action_url($action, $user = null, $email = null) {
1790
- $url = $this->add_qs($this->get_home_url(), 'na=' . urlencode($action));
1791
- //$url = $this->add_qs(admin_url('admin-ajax.php'), 'action=newsletter&na=' . urlencode($action));
1792
- if ($user) {
1793
- $url .= '&nk=' . urlencode($this->get_user_key($user));
1794
- }
1795
- if ($email) {
1796
- $url .= '&nek=' . urlencode($this->get_email_key($email));
1797
- }
1798
- return $url;
1799
- }
1800
-
1801
- function get_subscribe_url() {
1802
- return $this->build_action_url('s');
1803
- }
1804
-
1805
- function clean_stats_table() {
1806
- global $wpdb;
1807
- $this->logger->info('Cleaning up stats table');
1808
- $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");
1809
- $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");
1810
- }
1811
-
1812
- function clean_sent_table() {
1813
- global $wpdb;
1814
- $this->logger->info('Cleaning up sent table');
1815
- $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");
1816
- $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");
1817
- }
1818
-
1819
- function clean_user_logs_table() {
1820
- //global $wpdb;
1821
- }
1822
-
1823
- function clean_tables() {
1824
- $this->clean_sent_table();
1825
- $this->clean_stats_table();
1826
- $this->clean_user_logs_table();
1827
- }
1828
-
1829
- function anonymize_ip($ip) {
1830
- if (empty($ip)) {
1831
- return $ip;
1832
- }
1833
- $parts = explode('.', $ip);
1834
- array_pop($parts);
1835
- return implode('.', $parts) . '.0';
1836
- }
1837
-
1838
- function process_ip($ip) {
1839
-
1840
- $option = Newsletter::instance()->options['ip'];
1841
- if (empty($option)) {
1842
- return $ip;
1843
- }
1844
- if ($option == 'anonymize') {
1845
- return $this->anonymize_ip($ip);
1846
- }
1847
- return '';
1848
- }
1849
-
1850
- function anonymize_user($id) {
1851
- global $wpdb;
1852
- $user = $this->get_user($id);
1853
- if (!$user) {
1854
- return null;
1855
- }
1856
-
1857
- $user->name = '';
1858
- $user->surname = '';
1859
- $user->ip = $this->anonymize_ip($user->ip);
1860
-
1861
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1862
- $field = 'profile_' . $i;
1863
- $user->$field = '';
1864
- }
1865
-
1866
- // [TODO] Status?
1867
- $user->status = TNP_User::STATUS_UNSUBSCRIBED;
1868
- $user->email = $user->id . '@anonymi.zed';
1869
-
1870
- $user = $this->save_user($user);
1871
-
1872
- return $user;
1873
- }
1874
-
1875
- /**
1876
- * Changes a user status. Accept a user object, user id or user email.
1877
- *
1878
- * @param TNP_User $user
1879
- * @param string $status
1880
- * @return TNP_User
1881
- */
1882
- function set_user_status($user, $status) {
1883
- global $wpdb;
1884
-
1885
- $this->logger->debug('Status change to ' . $status . ' of subscriber ' . $user->id . ' from ' . $_SERVER['REQUEST_URI']);
1886
-
1887
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set status=%s where id=%d limit 1", $status, $user->id));
1888
- $user->status = $status;
1889
- return $this->get_user($user);
1890
- }
1891
-
1892
- /**
1893
- *
1894
- * @global wpdb $wpdb
1895
- * @param TNP_User $user
1896
- * @return TNP_User
1897
- */
1898
- function refresh_user_token($user) {
1899
- global $wpdb;
1900
-
1901
- $token = $this->get_token();
1902
-
1903
- $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set token=%s where id=%d limit 1", $token, $user->id));
1904
- $user->token = $token;
1905
- }
1906
-
1907
- /**
1908
- * Create a log entry with the meaningful user data.
1909
- *
1910
- * @global wpdb $wpdb
1911
- * @param TNP_User $user
1912
- * @param string $source
1913
- * @return type
1914
- */
1915
- function add_user_log($user, $source = '') {
1916
- global $wpdb;
1917
-
1918
- $lists = $this->get_lists_public();
1919
- foreach ($lists as $list) {
1920
- $field_name = 'list_' . $list->id;
1921
- $data[$field_name] = $user->$field_name;
1922
- }
1923
- $data['status'] = $user->status;
1924
- $ip = $this->get_remote_ip();
1925
- $ip = $this->process_ip($ip);
1926
- $this->store->save($wpdb->prefix . 'newsletter_user_logs', array('ip' => $ip, 'user_id' => $user->id, 'source' => $source, 'created' => time(), 'data' => json_encode($data)));
1927
- }
1928
-
1929
- /**
1930
- *
1931
- * @global wpdb $wpdb
1932
- * @param TNP_User $user
1933
- * @param int $list
1934
- * @param type $value
1935
- */
1936
- function set_user_list($user, $list, $value) {
1937
- global $wpdb;
1938
-
1939
- $list = (int) $list;
1940
- $value = $value ? 1 : 0;
1941
- $r = $wpdb->update(NEWSLETTER_USERS_TABLE, array('list_' . $list => $value), array('id' => $user->id));
1942
- }
1943
-
1944
- function set_user_field($id, $field, $value) {
1945
- $this->store->set_field(NEWSLETTER_USERS_TABLE, $id, $field, $value);
1946
- }
1947
-
1948
- function set_user_wp_user_id($user_id, $wp_user_id) {
1949
- $this->store->set_field(NEWSLETTER_USERS_TABLE, $user_id, 'wp_user_id', $wp_user_id);
1950
- }
1951
-
1952
- /**
1953
- *
1954
- * @param int $wp_user_id
1955
- * @param string $format
1956
- * @return TNP_User
1957
- */
1958
- function get_user_by_wp_user_id($wp_user_id, $format = OBJECT) {
1959
- return $this->store->get_single_by_field(NEWSLETTER_USERS_TABLE, 'wp_user_id', $wp_user_id, $format);
1960
- }
1961
-
1962
- /**
1963
- * Returns the user language IF there is a supported mutilanguage plugin installed.
1964
- * @param TNP_User $user
1965
- * @return string Language code or empty
1966
- */
1967
- function get_user_language($user) {
1968
- if ($user && $this->is_multilanguage()) {
1969
- return $user->language;
1970
- }
1971
- return '';
1972
- }
1973
-
1974
- /**
1975
- * Replaces every possible Newsletter tag ({...}) in a piece of text or HTML.
1976
- *
1977
- * @global wpdb $wpdb
1978
- * @param string $text
1979
- * @param mixed $user Can be an object, associative array or id
1980
- * @param mixed $email Can be an object, associative array or id
1981
- * @param type $referrer
1982
- * @return type
1983
- */
1984
- function replace($text, $user = null, $email = null, $referrer = null) {
1985
- global $wpdb;
1986
-
1987
- if (strpos($text, '<p') !== false) {
1988
- $esc_html = true;
1989
- } else {
1990
- $esc_html = false;
1991
- }
1992
-
1993
- static $home_url = false;
1994
-
1995
- if (!$home_url) {
1996
- $home_url = home_url('/');
1997
- }
1998
-
1999
- //$this->logger->debug('Replace start');
2000
- if ($user !== null && !is_object($user)) {
2001
- if (is_array($user)) {
2002
- $user = (object) $user;
2003
- } else if (is_numeric($user)) {
2004
- $user = $this->get_user($user);
2005
- } else {
2006
- $user = null;
2007
- }
2008
- }
2009
-
2010
- if ($email !== null && !is_object($email)) {
2011
- if (is_array($email)) {
2012
- $email = (object) $email;
2013
- } else if (is_numeric($email)) {
2014
- $email = $this->get_email($email);
2015
- } else {
2016
- $email = null;
2017
- }
2018
- }
2019
-
2020
- $initial_language = $this->get_current_language();
2021
-
2022
- if ($user && $user->language) {
2023
- $this->switch_language($user->language);
2024
- }
2025
-
2026
-
2027
- $text = apply_filters('newsletter_replace', $text, $user, $email, $esc_html);
2028
-
2029
- $text = $this->replace_url($text, 'blog_url', $home_url);
2030
- $text = $this->replace_url($text, 'home_url', $home_url);
2031
-
2032
- $text = str_replace('{blog_title}', html_entity_decode(get_bloginfo('name')), $text);
2033
- $text = str_replace('{blog_description}', get_option('blogdescription'), $text);
2034
-
2035
- $text = $this->replace_date($text);
2036
-
2037
- if ($user) {
2038
- //$this->logger->debug('Replace with user ' . $user->id);
2039
- $nk = $this->get_user_key($user);
2040
- $options_profile = NewsletterSubscription::instance()->get_options('profile', $this->get_user_language($user));
2041
- $text = str_replace('{email}', $user->email, $text);
2042
- $name = apply_filters('newsletter_replace_name', $user->name, $user);
2043
- if (empty($name)) {
2044
- $text = str_replace(' {name}', '', $text);
2045
- $text = str_replace('{name}', '', $text);
2046
- } else {
2047
- $text = str_replace('{name}', esc_html($name), $text);
2048
- }
2049
-
2050
- switch ($user->sex) {
2051
- case 'm': $text = str_replace('{title}', $options_profile['title_male'], $text);
2052
- break;
2053
- case 'f': $text = str_replace('{title}', $options_profile['title_female'], $text);
2054
- break;
2055
- case 'n': $text = str_replace('{title}', $options_profile['title_none'], $text);
2056
- break;
2057
- default:
2058
- $text = str_replace('{title}', '', $text);
2059
- }
2060
-
2061
-
2062
- // Deprecated
2063
- $text = str_replace('{surname}', esc_html($user->surname), $text);
2064
- $text = str_replace('{last_name}', esc_html($user->surname), $text);
2065
-
2066
- $full_name = esc_html(trim($user->name . ' ' . $user->surname));
2067
- if (empty($full_name)) {
2068
- $text = str_replace(' {full_name}', '', $text);
2069
- $text = str_replace('{full_name}', '', $text);
2070
- } else {
2071
- $text = str_replace('{full_name}', $full_name, $text);
2072
- }
2073
-
2074
- $text = str_replace('{token}', $user->token, $text);
2075
- $text = str_replace('%7Btoken%7D', $user->token, $text);
2076
- $text = str_replace('{id}', $user->id, $text);
2077
- $text = str_replace('%7Bid%7D', $user->id, $text);
2078
- $text = str_replace('{ip}', $user->ip, $text);
2079
- $text = str_replace('{key}', $nk, $text);
2080
- $text = str_replace('%7Bkey%7D', $nk, $text);
2081
-
2082
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
2083
- $p = 'profile_' . $i;
2084
- $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
2085
- }
2086
-
2087
- $base = (empty($this->options_main['url']) ? get_option('home') : $this->options_main['url']);
2088
- $id_token = '&amp;ni=' . $user->id . '&amp;nt=' . $user->token;
2089
-
2090
- $text = $this->replace_url($text, 'subscription_confirm_url', $this->build_action_url('c', $user));
2091
- $text = $this->replace_url($text, 'activation_url', $this->build_action_url('c', $user));
2092
-
2093
- // Obsolete.
2094
- $text = $this->replace_url($text, 'FOLLOWUP_SUBSCRIPTION_URL', self::add_qs($base, 'nm=fs' . $id_token));
2095
- $text = $this->replace_url($text, 'FOLLOWUP_UNSUBSCRIPTION_URL', self::add_qs($base, 'nm=fu' . $id_token));
2096
-
2097
- $text = $this->replace_url($text, 'UNLOCK_URL', $this->build_action_url('ul', $user));
2098
- } else {
2099
- //$this->logger->debug('Replace without user');
2100
- $text = $this->replace_url($text, 'subscription_confirm_url', '#');
2101
- $text = $this->replace_url($text, 'activation_url', '#');
2102
- }
2103
-
2104
- if ($email) {
2105
- //$this->logger->debug('Replace with email ' . $email->id);
2106
- $nek = $this->get_email_key($email);
2107
- $text = str_replace('{email_id}', $email->id, $text);
2108
- $text = str_replace('{email_key}', $nek, $text);
2109
- $text = str_replace('{email_subject}', $email->subject, $text);
2110
- // Deprecated
2111
- $text = str_replace('{subject}', $email->subject, $text);
2112
- $text = $this->replace_url($text, 'email_url', $this->build_action_url('v', $user) . '&id=' . $email->id);
2113
- } else {
2114
- //$this->logger->debug('Replace without email');
2115
- $text = $this->replace_url($text, 'email_url', '#');
2116
- }
2117
-
2118
- if (strpos($text, '{subscription_form}') !== false) {
2119
- $text = str_replace('{subscription_form}', NewsletterSubscription::instance()->get_subscription_form($referrer), $text);
2120
- } else {
2121
- for ($i = 1; $i <= 10; $i++) {
2122
- if (strpos($text, "{subscription_form_$i}") !== false) {
2123
- $text = str_replace("{subscription_form_$i}", NewsletterSubscription::instance()->get_form($i), $text);
2124
- break;
2125
- }
2126
- }
2127
- }
2128
-
2129
- // Company info
2130
- // TODO: Move to another module
2131
- $options = Newsletter::instance()->get_options('info');
2132
- $text = str_replace('{company_address}', $options['footer_contact'], $text);
2133
- $text = str_replace('{company_name}', $options['footer_title'], $text);
2134
- $text = str_replace('{company_legal}', $options['footer_legal'], $text);
2135
-
2136
- $this->switch_language($initial_language);
2137
- //$this->logger->debug('Replace end');
2138
- return $text;
2139
- }
2140
-
2141
- function replace_date($text) {
2142
- $text = str_replace('{date}', date_i18n(get_option('date_format')), $text);
2143
-
2144
- // Date processing
2145
- $x = 0;
2146
- while (($x = strpos($text, '{date_', $x)) !== false) {
2147
- $y = strpos($text, '}', $x);
2148
- if ($y === false)
2149
- continue;
2150
- $f = substr($text, $x + 6, $y - $x - 6);
2151
- $text = substr($text, 0, $x) . date_i18n($f) . substr($text, $y + 1);
2152
- }
2153
- return $text;
2154
- }
2155
-
2156
- function replace_url($text, $tag, $url) {
2157
- static $home = false;
2158
- if (!$home) {
2159
- $home = trailingslashit(home_url());
2160
- }
2161
- $tag_lower = strtolower($tag);
2162
- $text = str_replace('http://{' . $tag_lower . '}', $url, $text);
2163
- $text = str_replace('https://{' . $tag_lower . '}', $url, $text);
2164
- $text = str_replace($home . '{' . $tag_lower . '}', $url, $text);
2165
- $text = str_replace($home . '%7B' . $tag_lower . '%7D', $url, $text);
2166
- $text = str_replace('{' . $tag_lower . '}', $url, $text);
2167
- $text = str_replace('%7B' . $tag_lower . '%7D', $url, $text);
2168
-
2169
- $url_encoded = urlencode($url);
2170
- $text = str_replace('%7B' . $tag_lower . '_encoded%7D', $url_encoded, $text);
2171
- $text = str_replace('{' . $tag_lower . '_encoded}', $url_encoded, $text);
2172
-
2173
- // for compatibility
2174
- $text = str_replace($home . $tag, $url, $text);
2175
-
2176
- return $text;
2177
- }
2178
-
2179
- public static function antibot_form_check($captcha = false) {
2180
-
2181
- if (defined('NEWSLETTER_ANTIBOT') && !NEWSLETTER_ANTIBOT) {
2182
- return true;
2183
- }
2184
-
2185
- if (strtolower($_SERVER['REQUEST_METHOD']) != 'post') {
2186
- return false;
2187
- }
2188
-
2189
- if (!isset($_POST['ts']) || time() - $_POST['ts'] > 60) {
2190
- return false;
2191
- }
2192
-
2193
- if ($captcha) {
2194
- $n1 = (int) $_POST['n1'];
2195
- if (empty($n1)) {
2196
- return false;
2197
- }
2198
- $n2 = (int) $_POST['n2'];
2199
- if (empty($n2)) {
2200
- return false;
2201
- }
2202
- $n3 = (int) $_POST['n3'];
2203
- if ($n1 + $n2 != $n3) {
2204
- return false;
2205
- }
2206
- }
2207
-
2208
- return true;
2209
- }
2210
-
2211
- public static function request_to_antibot_form($submit_label = 'Continue...', $captcha = false) {
2212
- header('Content-Type: text/html;charset=UTF-8');
2213
- header('X-Robots-Tag: noindex,nofollow,noarchive');
2214
- header('Cache-Control: no-cache,no-store,private');
2215
- echo "<!DOCTYPE html>\n";
2216
- echo '<html><head>'
2217
- . '<style type="text/css">'
2218
- . '.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;}'
2219
- . 'p {text-align: center; padding: 10px; color: #7F8C8D;}'
2220
- . 'input[type=text] {width: 50px; padding: 10px 10px; border: none; border-radius: 2px; margin: 0px 5px;}'
2221
- . 'input[type=submit] {text-align: center; border: none; padding: 10px 15px; font-family: "Open Sans", sans-serif; background-color: #27AE60; color: white; cursor: pointer;}'
2222
- . '</style>'
2223
- . '</head><body>';
2224
- echo '<form method="post" action="https://www.domain.tld" id="form">';
2225
- echo '<div style="width: 1px; height: 1px; overflow: hidden">';
2226
- foreach ($_REQUEST as $name => $value) {
2227
- if ($name == 'submit')
2228
- continue;
2229
- if (is_array($value)) {
2230
- foreach ($value as $element) {
2231
- echo '<input type="text" name="';
2232
- echo esc_attr($name);
2233
- echo '[]" value="';
2234
- echo esc_attr(stripslashes($element));
2235
- echo '">';
2236
- }
2237
- } else {
2238
- echo '<input type="hidden" name="', esc_attr($name), '" value="', esc_attr(stripslashes($value)), '">';
2239
- }
2240
- }
2241
- if (isset($_SERVER['HTTP_REFERER'])) {
2242
- echo '<input type="hidden" name="nhr" value="' . esc_attr($_SERVER['HTTP_REFERER']) . '">';
2243
- }
2244
- echo '<input type="hidden" name="ts" value="' . time() . '">';
2245
- echo '</div>';
2246
-
2247
- if ($captcha) {
2248
- echo '<div class="tnp-captcha">';
2249
- echo '<p>', __('Math question', 'newsletter'), '</p>';
2250
- echo '<input type="text" name="n1" value="', rand(1, 9), '" readonly style="width: 50px">';
2251
- echo '+';
2252
- echo '<input type="text" name="n2" value="', rand(1, 9), '" readonly style="width: 50px">';
2253
- echo '=';
2254
- echo '<input type="text" name="n3" value="?" style="width: 50px">';
2255
- echo '<br><br>';
2256
- echo '<input type="submit" value="', esc_attr($submit_label), '">';
2257
- echo '</div>';
2258
- }
2259
- echo '<noscript><input type="submit" value="';
2260
- echo esc_attr($submit_label);
2261
- echo '"></noscript></form>';
2262
- echo '<script>';
2263
- echo 'document.getElementById("form").action="' . home_url('/') . '";';
2264
- if (!$captcha) {
2265
- echo 'document.getElementById("form").submit();';
2266
- }
2267
- echo '</script>';
2268
- echo '</body></html>';
2269
- die();
2270
- }
2271
-
2272
- static function extract_body($html) {
2273
- $x = stripos($html, '<body');
2274
- if ($x !== false) {
2275
- $x = strpos($html, '>', $x);
2276
- $y = strpos($html, '</body>');
2277
- return substr($html, $x + 1, $y - $x - 1);
2278
- } else {
2279
- return $html;
2280
- }
2281
- }
2282
-
2283
- /** Returns a percentage as string */
2284
- static function percent($value, $total) {
2285
- if ($total == 0)
2286
- return '-';
2287
- return sprintf("%.2f", $value / $total * 100) . '%';
2288
- }
2289
-
2290
- /** Returns a percentage as integer value */
2291
- static function percentValue($value, $total) {
2292
- if ($total == 0)
2293
- return 0;
2294
- return round($value / $total * 100);
2295
- }
2296
-
2297
- /**
2298
- * Takes in a variable and checks if object, array or scalar and return the integer representing
2299
- * a database record id.
2300
- *
2301
- * @param mixed $var
2302
- * @return in
2303
- */
2304
- static function to_int_id($var) {
2305
- if (is_object($var)) {
2306
- return (int) $var->id;
2307
- }
2308
- if (is_array($var)) {
2309
- return (int) $var['id'];
2310
- }
2311
- return (int) $var;
2312
- }
2313
-
2314
- static function to_array($text) {
2315
- $text = trim($text);
2316
- if (empty($text)) {
2317
- return array();
2318
- }
2319
- $text = preg_split("/\\r\\n/", $text);
2320
- $text = array_map('trim', $text);
2321
- $text = array_map('strtolower', $text);
2322
- $text = array_filter($text);
2323
-
2324
- return $text;
2325
- }
2326
-
2327
- static function sanitize_ip($ip) {
2328
- if (empty($ip)) {
2329
- return '';
2330
- }
2331
- $ip = preg_replace('/[^0-9a-fA-F:., ]/', '', trim($ip));
2332
- if (strlen($ip) > 50) $ip = substr($ip, 0, 50);
2333
-
2334
- // When more than one IP is present due to firewalls, proxies, and so on. The first one should be the origin.
2335
- if (strpos($ip, ',') !== false) {
2336
- list($ip, $tail) = explode(',', $ip, 2);
2337
- }
2338
- return $ip;
2339
- }
2340
-
2341
- static function get_remote_ip() {
2342
- $ip = '';
2343
- if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2344
- $ip = $_SERVER['HTTP_X_REAL_IP'];
2345
- } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2346
- $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
2347
- } elseif (isset($_SERVER['REMOTE_ADDR'])) {
2348
- $ip = $_SERVER['REMOTE_ADDR'];
2349
- }
2350
- return self::sanitize_ip($ip);
2351
- }
2352
-
2353
- static function get_signature($text) {
2354
- $key = NewsletterStatistics::instance()->options['key'];
2355
- return md5($text . $key);
2356
- }
2357
-
2358
- static function check_signature($text, $signature) {
2359
- if (empty($signature)) {
2360
- return false;
2361
- }
2362
- $key = NewsletterStatistics::instance()->options['key'];
2363
- return md5($text . $key) === $signature;
2364
- }
2365
-
2366
- static function get_home_url() {
2367
- static $url = false;
2368
- if (!$url) {
2369
- $url = home_url('/');
2370
- }
2371
- return $url;
2372
- }
2373
-
2374
- static function clean_eol($text) {
2375
- $text = str_replace("\r\n", "\n", $text);
2376
- $text = str_replace("\r", "\n", $text);
2377
- $text = str_replace("\n", "\r\n", $text);
2378
- return $text;
2379
- }
2380
-
2381
- function set_current_language($language) {
2382
- self::$current_language = $language;
2383
- }
2384
-
2385
- /**
2386
- * Return the current language code. Optionally, if a user is passed and it has a language
2387
- * the user language is returned.
2388
- * If there is no language available, an empty string is returned.
2389
- *
2390
- * @param TNP_User $user
2391
- * @return string The language code
2392
- */
2393
- function get_current_language($user = null) {
2394
-
2395
- if ($user && $user->language) {
2396
- return $user->language;
2397
- }
2398
-
2399
- if (!empty(self::$current_language)) {
2400
- return self::$current_language;
2401
- }
2402
-
2403
- // WPML
2404
- if (class_exists('SitePress')) {
2405
- $current_language = apply_filters('wpml_current_language', '');
2406
- if ($current_language == 'all') {
2407
- $current_language = '';
2408
- }
2409
- return $current_language;
2410
- }
2411
-
2412
- // Polylang
2413
- if (function_exists('pll_current_language')) {
2414
- return pll_current_language();
2415
- }
2416
-
2417
- // Trnslatepress and/or others
2418
- $current_language = apply_filters('newsletter_current_language', '');
2419
-
2420
- return $current_language;
2421
- }
2422
-
2423
- function get_default_language() {
2424
- if (class_exists('SitePress')) {
2425
- return $current_language = apply_filters('wpml_current_language', '');
2426
- } else if (function_exists('pll_default_language')) {
2427
- return pll_default_language();
2428
- } else if (class_exists('TRP_Translate_Press')) {
2429
- // TODO: Find the default language
2430
- }
2431
- return '';
2432
- }
2433
-
2434
- function is_all_languages() {
2435
- return $this->get_current_language() == '';
2436
- }
2437
-
2438
- function is_default_language() {
2439
- return $this->get_current_language() == $this->get_default_language();
2440
- }
2441
-
2442
- /**
2443
- * Returns an array of languages with key the language code and value the language name.
2444
- * An empty array is returned if no language is available.
2445
- */
2446
- function get_languages() {
2447
- $language_options = array();
2448
-
2449
- if (class_exists('SitePress')) {
2450
- $languages = apply_filters('wpml_active_languages', null);
2451
- foreach ($languages as $language) {
2452
- $language_options[$language['language_code']] = $language['translated_name'];
2453
- }
2454
- return $language_options;
2455
- } else if (function_exists('icl_get_languages')) {
2456
- $languages = icl_get_languages();
2457
- foreach ($languages as $code => $language) {
2458
- $language_options[$code] = $language['native_name'];
2459
- }
2460
- return $language_options;
2461
- }
2462
-
2463
- return apply_filters('newsletter_languages', $language_options);
2464
- }
2465
-
2466
- function get_language_label($language) {
2467
- $languages = $this->get_languages();
2468
- if (isset($languages[$language])) {
2469
- return $languages[$language];
2470
- }
2471
- return '';
2472
- }
2473
-
2474
- /**
2475
- * Changes the current language usually before extracting the posts since WPML
2476
- * does not support the language filter in the post query (or at least we didn't
2477
- * find it).
2478
- *
2479
- * @param string $language
2480
- */
2481
- function switch_language($language) {
2482
- if (class_exists('SitePress')) {
2483
- if (empty($language)) {
2484
- $language = 'all';
2485
- }
2486
- do_action('wpml_switch_language', $language);
2487
- return;
2488
- }
2489
- }
2490
-
2491
- function is_multilanguage() {
2492
- return class_exists('SitePress') || function_exists('pll_default_language') || class_exists('TRP_Translate_Press');
2493
- }
2494
-
2495
- function get_posts($filters = array(), $language = '') {
2496
- $current_language = $this->get_current_language();
2497
-
2498
- // Language switch for WPML
2499
- if ($language) {
2500
- if (class_exists('SitePress')) {
2501
- $this->switch_language($language);
2502
- $filters['suppress_filters'] = false;
2503
- }
2504
- if (class_exists('Polylang')) {
2505
- $filters['lang'] = $language;
2506
- }
2507
- }
2508
- $posts = get_posts($filters);
2509
- if ($language) {
2510
- if (class_exists('SitePress')) {
2511
- $this->switch_language($current_language);
2512
- }
2513
- }
2514
- return $posts;
2515
- }
2516
-
2517
- function get_wp_query($filters, $langiage = '') {
2518
- if ($language) {
2519
- if (class_exists('SitePress')) {
2520
- $this->switch_language($language);
2521
- $filters['suppress_filters'] = false;
2522
- }
2523
- if (class_exists('Polylang')) {
2524
- $filters['lang'] = $language;
2525
- }
2526
- }
2527
-
2528
- $posts = new WP_Query($filters);
2529
-
2530
- if ($language) {
2531
- if (class_exists('SitePress')) {
2532
- $this->switch_language($current_language);
2533
- }
2534
- }
2535
-
2536
- return $posts;
2537
- }
2538
-
2539
- protected function generate_admin_notification_message($user) {
2540
-
2541
- $message = file_get_contents(__DIR__ . '/notification.html');
2542
-
2543
- $message = $this->replace($message, $user);
2544
- $message = str_replace('{user_admin_url}', admin_url('admin.php?page=newsletter_users_edit&id=' . $user->id), $message);
2545
-
2546
- return $message;
2547
- }
2548
-
2549
- protected function generate_admin_notification_subject($subject) {
2550
- $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
2551
-
2552
- return '[' . $blogname . '] ' . $subject;
2553
- }
2554
-
2555
- function dienow($message, $admin_message = null, $http_code = 200) {
2556
- if ($admin_message && current_user_can('administrator')) {
2557
- $message .= '<br><br><strong>Text below only visibile to administrators</strong><br>';
2558
- $message .= $admin_message;
2559
- }
2560
- wp_die($message, $http_code);
2561
- }
2562
-
2563
- function dump($var) {
2564
- if (NEWSLETTER_DEBUG) {
2565
- var_dump($var);
2566
- }
2567
- }
2568
-
2569
- function dump_die($var) {
2570
- if (NEWSLETTER_DEBUG) {
2571
- var_dump($var);
2572
- die();
2573
- }
2574
- }
2575
-
2576
- }
2577
-
2578
- /**
2579
- * Kept for compatibility.
2580
- *
2581
- * @param type $post_id
2582
- * @param type $size
2583
- * @param type $alternative
2584
- * @return type
2585
- */
2586
- function nt_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2587
- return NewsletterModule::get_post_image($post_id, $size, $alternative);
2588
- }
2589
-
2590
- function newsletter_get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2591
- echo NewsletterModule::get_post_image($post_id, $size, $alternative);
2592
- }
2593
-
2594
- /**
2595
- * Accepts a post or a post ID.
2596
- *
2597
- * @param WP_Post $post
2598
- */
2599
- function newsletter_the_excerpt($post, $words = 30) {
2600
- $post = get_post($post);
2601
- $excerpt = $post->post_excerpt;
2602
- if (empty($excerpt)) {
2603
- $excerpt = $post->post_content;
2604
- $excerpt = strip_shortcodes($excerpt);
2605
- $excerpt = wp_strip_all_tags($excerpt, true);
2606
- }
2607
- echo '<p>' . wp_trim_words($excerpt, $words) . '</p>';
2608
- }
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)) return;
26
+ if ($this->width < $width) return;
27
+ $this->height = floor(($width / $this->width) * $this->height);
28
+ $this->width = $width;
29
+ }
30
+
31
+ /** Sets the height recalculating the width */
32
+ public function set_height($height) {
33
+ $height = (int) $height;
34
+ $this->width = floor(($height / $this->height) * $this->width);
35
+ $this->height = $height;
36
+ }
37
+
38
+ }
39
+
40
+ /**
41
+ * @property int $id The list unique identifier
42
+ * @property string $name The list name
43
+ * @property bool $forced If the list must be added to every new subscriber
44
+ * @property int $status When and how the list is visible to the subscriber - see constants
45
+ * @property bool $checked If it must be pre-checked on subscription form
46
+ * @property array $languages The list of language used to pre-assign this list
47
+ */
48
+ class TNP_List {
49
+
50
+ const STATUS_PRIVATE = 0;
51
+ const STATUS_PUBLIC = 1;
52
+ const SUBSCRIPTION_HIDE = 0;
53
+ const SUBSCRIPTION_SHOW = 1;
54
+ const SUBSCRIPTION_SHOW_CHECKED = 2;
55
+ const PROFILE_HIDE = 0;
56
+ const PROFILE_SHOW = 1;
57
+
58
+ var $id;
59
+ var $name;
60
+ var $status;
61
+ var $forced;
62
+ var $checked;
63
+ var $show_on_subscription;
64
+ var $show_on_profile;
65
+
66
+ function is_private() {
67
+ return $this->status == self::STATUS_PRIVATE;
68
+ }
69
+
70
+ }
71
+
72
+ /**
73
+ * @property int $id The list unique identifier
74
+ * @property string $name The list name
75
+ * @property int $status When and how the list is visible to the subscriber - see constants
76
+ * @property string $type Field type: text or select
77
+ * @property array $options Field options (usually the select items)
78
+ */
79
+ class TNP_Profile {
80
+
81
+ const STATUS_PRIVATE = 0;
82
+ const STATUS_PUBLIC = 2;
83
+ const STATUS_PROFILE_ONLY = 1;
84
+ const STATUS_HIDDEN = 3; // Public but never shown (can be set with a hidden form field)
85
+ const TYPE_TEXT = 'text';
86
+ const TYPE_SELECT = 'select';
87
+
88
+ public $id;
89
+ public $name;
90
+ public $status;
91
+ public $type;
92
+ public $options;
93
+ public $placeholder;
94
+ public $rule;
95
+
96
+ public function __construct($id, $name, $status, $type, $options, $placeholder, $rule) {
97
+ $this->id = $id;
98
+ $this->name = $name;
99
+ $this->status = $status;
100
+ $this->type = $type;
101
+ $this->options = $options;
102
+ $this->placeholder = $placeholder;
103
+ $this->rule = $rule;
104
+ }
105
+
106
+ function is_select() {
107
+ return $this->type == self::TYPE_SELECT;
108
+ }
109
+
110
+ function is_text() {
111
+ return $this->type == self::TYPE_TEXT;
112
+ }
113
+
114
+ function is_required() {
115
+ return $this->rule == 1;
116
+ }
117
+
118
+ function is_private() {
119
+ return $this->status == self::STATUS_PRIVATE;
120
+ }
121
+
122
+ function show_on_profile() {
123
+ return $this->status == self::STATUS_PROFILE_ONLY || $this->status == self::STATUS_PUBLIC;
124
+ }
125
+
126
+ }
127
+
128
+ class TNP_Profile_Service {
129
+
130
+ /**
131
+ *
132
+ * @param string $language
133
+ * @param string $type
134
+ * @return TNP_Profile[]
135
+ */
136
+ static function get_profiles($language = '', $type = '') {
137
+
138
+ static $profiles = [];
139
+ $k = $language . $type;
140
+
141
+ if (isset($profiles[$k])) {
142
+ return $profiles[$k];
143
+ }
144
+
145
+ $profiles[$k] = [];
146
+ $profile_options = NewsletterSubscription::instance()->get_options('profile', $language);
147
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
148
+ if (empty($profile_options['profile_' . $i])) {
149
+ continue;
150
+ }
151
+ $profile = self::create_profile_from_options($profile_options, $i);
152
+
153
+ if (empty($type) ||
154
+ ( $type == TNP_Profile::TYPE_SELECT && $profile->is_select() ) ||
155
+ ( $type == TNP_Profile::TYPE_TEXT && $profile->is_text() )) {
156
+ $profiles[$k]['' . $i] = $profile;
157
+ }
158
+ }
159
+
160
+ return $profiles[$k];
161
+ }
162
+
163
+ static function get_profile_by_id($id, $language = '') {
164
+
165
+ $profiles = self::get_profiles($language);
166
+ if (isset($profiles[$id]))
167
+ return $profiles[$id];
168
+ return null;
169
+ }
170
+
171
+ /**
172
+ * @return TNP_Profile
173
+ */
174
+ private static function create_profile_from_options($options, $id) {
175
+ return new TNP_Profile(
176
+ $id,
177
+ $options['profile_' . $id],
178
+ (int) $options['profile_' . $id . '_status'],
179
+ $options['profile_' . $id . '_type'],
180
+ self::string_db_options_to_array($options['profile_' . $id . '_options']),
181
+ $options['profile_' . $id . '_placeholder'],
182
+ $options['profile_' . $id . '_rules']
183
+ );
184
+ }
185
+
186
+ /**
187
+ * Returns a list of strings which are the items for the select field.
188
+ * @return array
189
+ */
190
+ private static function string_db_options_to_array($string_options) {
191
+ $items = array_map('trim', explode(',', $string_options));
192
+ $items = array_combine($items, $items);
193
+
194
+ return $items;
195
+ }
196
+
197
+ }
198
+
199
+ /**
200
+ * Represents the set of data collected by a subscription interface (form, API, ...). Only a valid
201
+ * email is mandatory.
202
+ */
203
+ class TNP_Subscription_Data {
204
+
205
+ var $email = null;
206
+ var $name = null;
207
+ var $surname = null;
208
+ var $sex = null;
209
+ var $language = null;
210
+ var $referrer = null;
211
+ var $http_referrer = null;
212
+ var $ip = null;
213
+ var $country = null;
214
+ var $region = null;
215
+ var $city = null;
216
+
217
+ /**
218
+ * Associative array id=>value of lists chosen by the subscriber. A list can be set to
219
+ * 0 meaning the subscriber does not want to be in that list.
220
+ * The lists must be public: non public lists are filtered.
221
+ * @var array
222
+ */
223
+ var $lists = [];
224
+ var $profiles = [];
225
+
226
+ function merge_in($subscriber) {
227
+ if (!$subscriber)
228
+ $subscriber = new TNP_User();
229
+ if (!empty($this->email))
230
+ $subscriber->email = $this->email;
231
+ if (!empty($this->name))
232
+ $subscriber->name = $this->name;
233
+ if (!empty($this->surname))
234
+ $subscriber->surname = $this->surname;
235
+ if (!empty($this->sex))
236
+ $subscriber->sex = $this->sex;
237
+ if (!empty($this->language))
238
+ $subscriber->language = $this->language;
239
+ if (!empty($this->ip))
240
+ $subscriber->ip = $this->ip;
241
+ if (!empty($this->referrer))
242
+ $subscriber->referrer = $this->referrer;
243
+ if (!empty($this->http_referrer))
244
+ $subscriber->http_referrer = $this->http_referrer;
245
+ if (!empty($this->country))
246
+ $subscriber->country = $this->country;
247
+ if (!empty($this->region))
248
+ $subscriber->region = $this->region;
249
+ if (!empty($this->city))
250
+ $subscriber->city = $this->city;
251
+
252
+
253
+ foreach ($this->lists as $id => $value) {
254
+ $key = 'list_' . $id;
255
+ $subscriber->$key = $value;
256
+ }
257
+
258
+ // Profile
259
+ foreach ($this->profiles as $id => $value) {
260
+ $key = 'profile_' . $id;
261
+ $subscriber->$key = $value;
262
+ }
263
+ }
264
+
265
+ /** Sets to active a set of lists. Accepts incorrect data (and ignores it).
266
+ *
267
+ * @param array $list_ids Array of list IDs
268
+ */
269
+ function add_lists($list_ids) {
270
+ if (empty($list_ids) || !is_array($list_ids))
271
+ return;
272
+ foreach ($list_ids as $list_id) {
273
+ $list_id = (int) $list_id;
274
+ if ($list_id < 0 || $list_id > NEWSLETTER_LIST_MAX)
275
+ continue;
276
+ $this->lists[$list_id] = 1;
277
+ }
278
+ }
279
+
280
+ }
281
+
282
+ /**
283
+ * Represents a subscription request with the subscriber data and actions to be taken by
284
+ * the subscription engine (spam check, notifications, ...).
285
+ */
286
+ class TNP_Subscription {
287
+
288
+ const EXISTING_ERROR = 1;
289
+ const EXISTING_MERGE = 0;
290
+
291
+ /**
292
+ * Subscriber's data following the syntax of the TNP_User
293
+ * @var TNP_Subscription_Data
294
+ */
295
+ var $data;
296
+ var $spamcheck = true;
297
+ // 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
298
+ // optin as empty (for default), 'single' or 'double'.
299
+ var $optin = null;
300
+ // What to do with an existing subscriber???
301
+ var $if_exists = self::EXISTING_MERGE;
302
+
303
+ /**
304
+ * Determines if the welcome or activation email should be sent. Note: sometime an activation email is sent disregarding
305
+ * this setting.
306
+ * @var boolean
307
+ */
308
+ var $send_emails = true;
309
+
310
+ public function __construct() {
311
+ $this->data = new TNP_Subscription_Data();
312
+ }
313
+
314
+ public function is_single_optin() {
315
+ return $this->optin == 'single';
316
+ }
317
+
318
+ public function is_double_optin() {
319
+ return $this->optin == 'double';
320
+ }
321
+
322
+ }
323
+
324
+ /**
325
+ * @property int $id The subscriber unique identifier
326
+ * @property string $email The subscriber email
327
+ * @property string $name The subscriber name or first name
328
+ * @property string $surname The subscriber last name
329
+ * @property string $status The subscriber status
330
+ * @property string $language The subscriber language code 2 chars lowercase
331
+ * @property string $token The subscriber secret token
332
+ * @property string $country The subscriber country code 2 chars uppercase
333
+ */
334
+ class TNP_User {
335
+
336
+ const STATUS_CONFIRMED = 'C';
337
+ const STATUS_NOT_CONFIRMED = 'S';
338
+ const STATUS_UNSUBSCRIBED = 'U';
339
+ const STATUS_BOUNCED = 'B';
340
+ const STATUS_COMPLAINED = 'P';
341
+
342
+ var $ip = '';
343
+
344
+ public static function get_status_label($status) {
345
+ switch ($status) {
346
+ case self::STATUS_NOT_CONFIRMED: return __('NOT CONFIRMED', 'newsletter');
347
+ break;
348
+ case self::STATUS_CONFIRMED: return __('CONFIRMED', 'newsletter');
349
+ break;
350
+ case self::STATUS_UNSUBSCRIBED: return __('UNSUBSCRIBED', 'newsletter');
351
+ break;
352
+ case self::STATUS_BOUNCED: return __('BOUNCED', 'newsletter');
353
+ break;
354
+ case self::STATUS_COMPLAINED: return __('COMPLAINED', 'newsletter');
355
+ break;
356
+ default:
357
+ return __('Unknown', 'newsletter');
358
+ }
359
+ }
360
+
361
+ }
362
+
363
+ /**
364
+ * @property int $id The email unique identifier
365
+ * @property string $subject The email subject
366
+ * @property string $message The email html message
367
+ * @property int $track Check if the email stats should be active
368
+ * @property array $options Email options
369
+ * @property int $total Total emails to send
370
+ * @property int $sent Total sent emails by now
371
+ * @property int $open_count Total opened emails
372
+ * @property int $click_count Total clicked emails
373
+ * */
374
+ class TNP_Email {
375
+
376
+ const STATUS_DRAFT = 'new';
377
+ const STATUS_SENT = 'sent';
378
+ const STATUS_SENDING = 'sending';
379
+ const STATUS_PAUSED = 'paused';
380
+ const STATUS_ERROR = 'error';
381
+
382
+ }
383
+
384
+ class NewsletterModule {
385
+
386
+ /**
387
+ * @var NewsletterLogger
388
+ */
389
+ var $logger;
390
+
391
+ /**
392
+ * @var NewsletterLogger
393
+ */
394
+ var $admin_logger;
395
+
396
+ /**
397
+ * @var NewsletterStore
398
+ */
399
+ var $store;
400
+
401
+ /**
402
+ * The main module options
403
+ * @var array
404
+ */
405
+ var $options;
406
+
407
+ /**
408
+ * @var string The module name
409
+ */
410
+ var $module;
411
+
412
+ /**
413
+ * The module version
414
+ * @var string
415
+ */
416
+ var $version;
417
+ var $old_version;
418
+
419
+ /**
420
+ * Prefix for all options stored on WordPress options table.
421
+ * @var string
422
+ */
423
+ var $prefix;
424
+
425
+ /**
426
+ * @var NewsletterThemes
427
+ */
428
+ var $themes;
429
+ var $components;
430
+ static $current_language = '';
431
+
432
+ function __construct($module, $version, $module_id = null, $components = array()) {
433
+ $this->module = $module;
434
+ $this->version = $version;
435
+ $this->prefix = 'newsletter_' . $module;
436
+ array_unshift($components, '');
437
+ $this->components = $components;
438
+
439
+ $this->logger = new NewsletterLogger($module);
440
+
441
+ $this->options = $this->get_options();
442
+ $this->store = NewsletterStore::singleton();
443
+
444
+ //$this->logger->debug($module . ' constructed');
445
+ // Version check
446
+ if (is_admin()) {
447
+ $this->admin_logger = new NewsletterLogger($module . '-admin');
448
+ $this->old_version = get_option($this->prefix . '_version', '0.0.0');
449
+
450
+ if ($this->old_version == '0.0.0') {
451
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
452
+ $this->first_install();
453
+ update_option($this->prefix . "_first_install_time", time(), FALSE);
454
+ }
455
+
456
+ if (strcmp($this->old_version, $this->version) != 0) {
457
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
458
+ $this->logger->info('Version changed from ' . $this->old_version . ' to ' . $this->version);
459
+ // Do all the stuff for this version change
460
+ $this->upgrade();
461
+ update_option($this->prefix . '_version', $this->version);
462
+ }
463
+
464
+ add_action('admin_menu', array($this, 'admin_menu'));
465
+ }
466
+ }
467
+
468
+ /**
469
+ *
470
+ * @global wpdb $wpdb
471
+ * @param string $query
472
+ */
473
+ function query($query) {
474
+ global $wpdb;
475
+
476
+ $this->logger->debug($query);
477
+ //$start = microtime(true);
478
+ $r = $wpdb->query($query);
479
+ //$this->logger->debug($wpdb->last_query);
480
+ //$this->logger->debug('Execution time: ' . (microtime(true)-$start));
481
+ //$this->logger->debug('Result: ' . $r);
482
+ if ($r === false) {
483
+ $this->logger->fatal($query);
484
+ $this->logger->fatal($wpdb->last_error);
485
+ }
486
+ return $r;
487
+ }
488
+
489
+ function get_results($query) {
490
+ global $wpdb;
491
+ $r = $wpdb->get_results($query);
492
+ if ($r === false) {
493
+ $this->logger->fatal($query);
494
+ $this->logger->fatal($wpdb->last_error);
495
+ }
496
+ return $r;
497
+ }
498
+
499
+ /**
500
+ *
501
+ * @global wpdb $wpdb
502
+ * @param string $table
503
+ * @param array $data
504
+ */
505
+ function insert($table, $data) {
506
+ global $wpdb;
507
+ $this->logger->debug("inserting into table $table");
508
+ $r = $wpdb->insert($table, $data);
509
+ if ($r === false) {
510
+ $this->logger->fatal($wpdb->last_error);
511
+ }
512
+ }
513
+
514
+ function first_install() {
515
+ $this->logger->debug('First install');
516
+ }
517
+
518
+ /**
519
+ * Does a basic upgrade work, checking if the options is already present and if not (first
520
+ * installation), recovering the defaults, saving them on database and initializing the
521
+ * internal $options.
522
+ */
523
+ function upgrade() {
524
+ foreach ($this->components as $component) {
525
+ $this->logger->debug('Upgrading component ' . $component);
526
+ $this->init_options($component);
527
+ }
528
+ }
529
+
530
+ function init_options($component = '', $autoload = true) {
531
+ global $wpdb;
532
+ $default_options = $this->get_default_options($component);
533
+ $options = $this->get_options($component);
534
+ $options = array_merge($default_options, $options);
535
+ $this->save_options($options, $component, $autoload);
536
+ }
537
+
538
+ function upgrade_query($query) {
539
+ global $wpdb, $charset_collate;
540
+
541
+ $this->logger->info('upgrade_query> Executing ' . $query);
542
+ $suppress_errors = $wpdb->suppress_errors(true);
543
+ $wpdb->query($query);
544
+ if ($wpdb->last_error) {
545
+ $this->logger->debug($wpdb->last_error);
546
+ }
547
+ $wpdb->suppress_errors($suppress_errors);
548
+ }
549
+
550
+ /** Returns a prefix to be used for option names and other things which need to be uniquely named. The parameter
551
+ * "sub" should be used when a sub name is needed for another set of options or like.
552
+ *
553
+ * @param string $sub
554
+ * @return string The prefix for names
555
+ */
556
+ function get_prefix($sub = '', $language = '') {
557
+ return $this->prefix . (!empty($sub) ? '_' : '') . $sub . (!empty($language) ? '_' : '') . $language;
558
+ }
559
+
560
+ /**
561
+ * Returns the options of a module, if not found an empty array.
562
+ */
563
+ function get_options($sub = '', $language = '') {
564
+ $options = get_option($this->get_prefix($sub, $language), array());
565
+ // Protection against scarmled database...
566
+ if (!is_array($options)) {
567
+ $options = array();
568
+ }
569
+ if ($language) {
570
+ $main_options = get_option($this->get_prefix($sub));
571
+ // Protection against scarmled database...
572
+ if (!is_array($main_options))
573
+ $main_options = array();
574
+ //$options = array_merge($main_options, array_filter($options));
575
+ $options = array_merge($main_options, $options);
576
+ }
577
+ return $options;
578
+ }
579
+
580
+ function get_default_options($sub = '') {
581
+ if (!empty($sub)) {
582
+ $sub = '-' . $sub;
583
+ }
584
+ $file = NEWSLETTER_DIR . '/' . $this->module . '/defaults' . $sub . '.php';
585
+ if (file_exists($file)) {
586
+ @include $file;
587
+ }
588
+
589
+ if (!isset($options) || !is_array($options)) {
590
+ return array();
591
+ }
592
+ return $options;
593
+ }
594
+
595
+ function reset_options($sub = '') {
596
+ $this->save_options(array_merge($this->get_options($sub), $this->get_default_options($sub)), $sub);
597
+ return $this->get_options($sub);
598
+ }
599
+
600
+ /**
601
+ * Saves the module options (or eventually a subset names as per parameter $sub). $options
602
+ * should be an array (even if it can work with non array options.
603
+ * The internal module options variable IS initialized with those new options only for the main
604
+ * options (empty $sub parameter).
605
+ * If the options contain a "theme" value, the theme-related options contained are saved as well
606
+ * (used by some modules).
607
+ *
608
+ * @param array $options
609
+ * @param string $sub
610
+ */
611
+ function save_options($options, $sub = '', $autoload = null, $language = '') {
612
+ update_option($this->get_prefix($sub, $language), $options, $autoload);
613
+ if (empty($sub) && empty($language)) {
614
+ $this->options = $options;
615
+ if (isset($this->themes) && isset($options['theme'])) {
616
+ $this->themes->save_options($options['theme'], $options);
617
+ }
618
+ }
619
+ }
620
+
621
+ function delete_options($sub = '') {
622
+ delete_option($this->get_prefix($sub));
623
+ if (empty($sub)) {
624
+ $this->options = array();
625
+ }
626
+ }
627
+
628
+ function merge_options($options, $sub = '', $language = '') {
629
+ if (!is_array($options)) {
630
+ $options = array();
631
+ }
632
+ $old_options = $this->get_options($sub, $language);
633
+ $this->save_options(array_merge($old_options, $options), $sub, null, $language);
634
+ }
635
+
636
+ function backup_options($sub) {
637
+ $options = $this->get_options($sub);
638
+ update_option($this->get_prefix($sub) . '_backup', $options, false);
639
+ }
640
+
641
+ function get_last_run($sub = '') {
642
+ return get_option($this->get_prefix($sub) . '_last_run', 0);
643
+ }
644
+
645
+ /**
646
+ * Save the module last run value. Used to store a timestamp for some modules,
647
+ * for example the Feed by Mail module.
648
+ *
649
+ * @param int $time Unix timestamp (as returned by time() for example)
650
+ * @param string $sub Sub module name (default empty)
651
+ */
652
+ function save_last_run($time, $sub = '') {
653
+ update_option($this->get_prefix($sub) . '_last_run', $time);
654
+ }
655
+
656
+ /**
657
+ * Sums $delta seconds to the last run time.
658
+ * @param int $delta Seconds
659
+ * @param string $sub Sub module name (default empty)
660
+ */
661
+ function add_to_last_run($delta, $sub = '') {
662
+ $time = $this->get_last_run($sub);
663
+ $this->save_last_run($time + $delta, $sub);
664
+ }
665
+
666
+ /**
667
+ * Checks if the semaphore of that name (for this module) is still red. If it is active the method
668
+ * returns false. If it is not active, it will be activated for $time seconds.
669
+ *
670
+ * Since this method activate the semaphore when called, it's name is a bit confusing.
671
+ *
672
+ * @param string $name Sempahore name (local to this module)
673
+ * @param int $time Max time in second this semaphore should stay red
674
+ * @return boolean False if the semaphore is red and you should not proceed, true is it was not active and has been activated.
675
+ */
676
+ function check_transient($name, $time) {
677
+ if ($time < 60)
678
+ $time = 60;
679
+ //usleep(rand(0, 1000000));
680
+ if (($value = get_transient($this->get_prefix() . '_' . $name)) !== false) {
681
+ list($t, $v) = explode(';', $value, 2);
682
+ $this->logger->error('Blocked by transient ' . $this->get_prefix() . '_' . $name . ' set ' . (time() - $t) . ' seconds ago by ' . $v);
683
+ return false;
684
+ }
685
+ //$ip = ''; //gethostbyname(gethostname());
686
+ $value = time() . ";" . ABSPATH . ';' . gethostname();
687
+ set_transient($this->get_prefix() . '_' . $name, $value, $time);
688
+ return true;
689
+ }
690
+
691
+ function delete_transient($name = '') {
692
+ delete_transient($this->get_prefix() . '_' . $name);
693
+ }
694
+
695
+ /** Returns a random token of the specified size (or 10 characters if size is not specified).
696
+ *
697
+ * @param int $size
698
+ * @return string
699
+ */
700
+ static function get_token($size = 10) {
701
+ return substr(md5(rand()), 0, $size);
702
+ }
703
+
704
+ /**
705
+ * Adds query string parameters to an URL checing id there are already other parameters.
706
+ *
707
+ * @param string $url
708
+ * @param string $qs The part of query-string to add (param1=value1&param2=value2...)
709
+ * @param boolean $amp If the method must use the &amp; instead of the plain & (default true)
710
+ * @return string
711
+ */
712
+ static function add_qs($url, $qs, $amp = true) {
713
+ if (strpos($url, '?') !== false) {
714
+ if ($amp)
715
+ return $url . '&amp;' . $qs;
716
+ else
717
+ return $url . '&' . $qs;
718
+ } else
719
+ return $url . '?' . $qs;
720
+ }
721
+
722
+ /**
723
+ * Returns the email address normalized, lowercase with no spaces. If it's not a valid email
724
+ * returns false.
725
+ */
726
+ static function normalize_email($email) {
727
+ if (!is_string($email)) {
728
+ return false;
729
+ }
730
+ $email = strtolower(trim($email));
731
+ if (!is_email($email)) {
732
+ return false;
733
+ }
734
+ //$email = apply_filters('newsletter_normalize_email', $email);
735
+ return $email;
736
+ }
737
+
738
+ static function normalize_name($name) {
739
+ $name = html_entity_decode($name, ENT_QUOTES);
740
+ $name = str_replace(';', ' ', $name);
741
+ $name = strip_tags($name);
742
+
743
+ return $name;
744
+ }
745
+
746
+ static function normalize_sex($sex) {
747
+ $sex = trim(strtolower($sex));
748
+ if ($sex != 'f' && $sex != 'm') {
749
+ $sex = 'n';
750
+ }
751
+ return $sex;
752
+ }
753
+
754
+ static function is_email($email, $empty_ok = false) {
755
+
756
+ if (!is_string($email)) {
757
+ return false;
758
+ }
759
+ $email = strtolower(trim($email));
760
+
761
+ if ($email == '') {
762
+ return $empty_ok;
763
+ }
764
+
765
+ if (!is_email($email)) {
766
+ return false;
767
+ }
768
+ return true;
769
+ }
770
+
771
+ /**
772
+ * Converts a GMT date from mysql (see the posts table columns) into a timestamp.
773
+ *
774
+ * @param string $s GMT date with format yyyy-mm-dd hh:mm:ss
775
+ * @return int A timestamp
776
+ */
777
+ static function m2t($s) {
778
+
779
+ // TODO: use the wordpress function I don't remember the name
780
+ $s = explode(' ', $s);
781
+ $d = explode('-', $s[0]);
782
+ $t = explode(':', $s[1]);
783
+ return gmmktime((int) $t[0], (int) $t[1], (int) $t[2], (int) $d[1], (int) $d[2], (int) $d[0]);
784
+ }
785
+
786
+ static function format_date($time) {
787
+ if (empty($time)) {
788
+ return '-';
789
+ }
790
+ return gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
791
+ }
792
+
793
+ static function format_time_delta($delta) {
794
+ $days = floor($delta / (3600 * 24));
795
+ $hours = floor(($delta % (3600 * 24)) / 3600);
796
+ $minutes = floor(($delta % 3600) / 60);
797
+ $seconds = floor(($delta % 60));
798
+ $buffer = $days . ' days, ' . $hours . ' hours, ' . $minutes . ' minutes, ' . $seconds . ' seconds';
799
+ return $buffer;
800
+ }
801
+
802
+ /**
803
+ * Formats a scheduler returned "next execution" time, managing negative or false values. Many times
804
+ * used in conjuction with "last run".
805
+ *
806
+ * @param string $name The scheduler name
807
+ * @return string
808
+ */
809
+ static function format_scheduler_time($name) {
810
+ $time = wp_next_scheduled($name);
811
+ if ($time === false) {
812
+ return 'No next run scheduled';
813
+ }
814
+ $delta = $time - time();
815
+ // If less 10 minutes late it can be a cron problem but now it is working
816
+ if ($delta < 0 && $delta > -600) {
817
+ return 'Probably running now';
818
+ } else if ($delta <= -600) {
819
+ return 'It seems the cron system is not working. Reload the page to see if this message change.';
820
+ }
821
+ return 'Runs in ' . self::format_time_delta($delta);
822
+ }
823
+
824
+ static function date($time = null, $now = false, $left = false) {
825
+ if (is_null($time)) {
826
+ $time = time();
827
+ }
828
+ if ($time == false) {
829
+ $buffer = 'none';
830
+ } else {
831
+ $buffer = gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
832
+ }
833
+ if ($now) {
834
+ $buffer .= ' (now: ' . gmdate(get_option('date_format') . ' ' .
835
+ get_option('time_format'), time() + get_option('gmt_offset') * 3600);
836
+ $buffer .= ')';
837
+ }
838
+ if ($left) {
839
+ $buffer .= ', ' . gmdate('H:i:s', $time - time()) . ' left';
840
+ }
841
+ return $buffer;
842
+ }
843
+
844
+ /**
845
+ * Return an array of array with on first element the array of recent post and on second element the array
846
+ * of old posts.
847
+ *
848
+ * @param array $posts
849
+ * @param int $time
850
+ */
851
+ static function split_posts(&$posts, $time = 0) {
852
+ if ($time < 0) {
853
+ return array_chunk($posts, ceil(count($posts) / 2));
854
+ }
855
+
856
+ $result = array(array(), array());
857
+
858
+ if (empty($posts))
859
+ return $result;
860
+
861
+ foreach ($posts as &$post) {
862
+ if (self::is_post_old($post, $time))
863
+ $result[1][] = $post;
864
+ else
865
+ $result[0][] = $post;
866
+ }
867
+ return $result;
868
+ }
869
+
870
+ static function is_post_old(&$post, $time = 0) {
871
+ return self::m2t($post->post_date_gmt) <= $time;
872
+ }
873
+
874
+ static function get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
875
+ global $post;
876
+
877
+ if (empty($post_id))
878
+ $post_id = $post->ID;
879
+ if (empty($post_id))
880
+ return $alternative;
881
+
882
+ $image_id = function_exists('get_post_thumbnail_id') ? get_post_thumbnail_id($post_id) : false;
883
+ if ($image_id) {
884
+ $image = wp_get_attachment_image_src($image_id, $size);
885
+ return $image[0];
886
+ } else {
887
+ $attachments = get_children(array('post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID'));
888
+
889
+ if (empty($attachments)) {
890
+ return $alternative;
891
+ }
892
+
893
+ foreach ($attachments as $id => $attachment) {
894
+ $image = wp_get_attachment_image_src($id, $size);
895
+ return $image[0];
896
+ }
897
+ }
898
+ }
899
+
900
+ /**
901
+ * Cleans up a text containing url tags with appended the absolute URL (due to
902
+ * the editor behavior) moving back them to the simple form.
903
+ */
904
+ static function clean_url_tags($text) {
905
+ $text = str_replace('%7B', '{', $text);
906
+ $text = str_replace('%7D', '}', $text);
907
+
908
+ // Only tags which are {*_url}
909
+ $text = preg_replace("/[\"']http[^\"']+(\\{[^\\}]+_url\\})[\"']/i", "\"\\1\"", $text);
910
+ return $text;
911
+ }
912
+
913
+ function admin_menu() {
914
+
915
+ }
916
+
917
+ function add_menu_page($page, $title, $capability = '') {
918
+ if (!Newsletter::instance()->is_allowed())
919
+ return;
920
+ $name = 'newsletter_' . $this->module . '_' . $page;
921
+ add_submenu_page('newsletter_main_index', $title, $title, 'exist', $name, array($this, 'menu_page'));
922
+ }
923
+
924
+ function add_admin_page($page, $title) {
925
+ if (!Newsletter::instance()->is_allowed()) {
926
+ return;
927
+ }
928
+ $name = 'newsletter_' . $this->module . '_' . $page;
929
+ add_submenu_page(null, $title, $title, 'exist', $name, array($this, 'menu_page'));
930
+ }
931
+
932
+ function sanitize_file_name($name) {
933
+ return preg_replace('/[^a-z_\\-]/i', '', $name);
934
+ }
935
+
936
+ function menu_page() {
937
+ global $plugin_page, $newsletter, $wpdb;
938
+
939
+ $parts = explode('_', $plugin_page, 3);
940
+ $module = $this->sanitize_file_name($parts[1]);
941
+ $page = $this->sanitize_file_name($parts[2]);
942
+ $page = str_replace('_', '-', $page);
943
+
944
+ $file = NEWSLETTER_DIR . '/' . $module . '/' . $page . '.php';
945
+
946
+ require $file;
947
+ }
948
+
949
+ function get_admin_page_url($page) {
950
+ return admin_url('admin.php') . '?page=newsletter_' . $this->module . '_' . $page;
951
+ }
952
+
953
+ /** Returns all the emails of the give type (message, feed, followup, ...) and in the given format
954
+ * (default as objects). Return false on error or at least an empty array. Errors should never
955
+ * occur.
956
+ *
957
+ * @global wpdb $wpdb
958
+ * @param string $type
959
+ * @return boolean|array
960
+ */
961
+ function get_emails($type = null, $format = OBJECT) {
962
+ global $wpdb;
963
+ if ($type == null) {
964
+ $list = $wpdb->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " order by id desc", $format);
965
+ } else {
966
+ $type = (string) $type;
967
+ $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where type=%s order by id desc", $type), $format);
968
+ }
969
+ if ($wpdb->last_error) {
970
+ $this->logger->error($wpdb->last_error);
971
+ return false;
972
+ }
973
+ if (empty($list)) {
974
+ return array();
975
+ }
976
+ return $list;
977
+ }
978
+
979
+ /**
980
+ * @param string $key
981
+ * @param mixed $value
982
+ * @return TNP_Email[]
983
+ */
984
+ function get_emails_by_field($key, $value) {
985
+ global $wpdb;
986
+
987
+ $value_placeholder = is_int($value) ? '%d' : '%s';
988
+
989
+ $query = $wpdb->prepare("SELECT * FROM " . NEWSLETTER_EMAILS_TABLE . " WHERE %1s=$value_placeholder ORDER BY id DESC", $key, $value);
990
+
991
+ $email_list = $wpdb->get_results($query);
992
+
993
+ if ($wpdb->last_error) {
994
+ $this->logger->error($wpdb->last_error);
995
+
996
+ return [];
997
+ }
998
+
999
+ //Unserialize options
1000
+ array_walk($email_list, function ($email) {
1001
+ $email->options = maybe_unserialize($email->options);
1002
+ if (!is_array($email->options)) {
1003
+ $email->options = [];
1004
+ }
1005
+ });
1006
+
1007
+ return $email_list;
1008
+ }
1009
+
1010
+ /**
1011
+ * Retrieves an email from DB and unserialize the options.
1012
+ *
1013
+ * @param mixed $id
1014
+ * @param string $format
1015
+ * @return TNP_Email An object with the same fields of TNP_Email, but not actually of that type
1016
+ */
1017
+ function get_email($id, $format = OBJECT) {
1018
+ $email = $this->store->get_single(NEWSLETTER_EMAILS_TABLE, $id, $format);
1019
+ if (!$email) {
1020
+ return null;
1021
+ }
1022
+ if ($format == OBJECT) {
1023
+ $email->options = maybe_unserialize($email->options);
1024
+ if (!is_array($email->options)) {
1025
+ $email->options = array();
1026
+ }
1027
+ if (empty($email->query)) {
1028
+ $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1029
+ }
1030
+ } else if ($format == ARRAY_A) {
1031
+ $email['options'] = maybe_unserialize($email['options']);
1032
+ if (!is_array($email['options'])) {
1033
+ $email['options'] = array();
1034
+ }
1035
+ if (empty($email['query'])) {
1036
+ $email['query'] = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
1037
+ }
1038
+ }
1039
+ return $email;
1040
+ }
1041
+
1042
+ /**
1043
+ * Save an email and provide serialization, if needed, of $email['options'].
1044
+ * @return TNP_Email
1045
+ */
1046
+ function save_email($email, $return_format = OBJECT) {
1047
+ if (is_object($email)) {
1048
+ $email = (array) $email;
1049
+ }
1050
+
1051
+ if (isset($email['subject'])) {
1052
+ if (mb_strlen($email['subject'], 'UTF-8') > 250) {
1053
+ $email['subject'] = mb_substr($email['subject'], 0, 250, 'UTF-8');
1054
+ }
1055
+ }
1056
+ if (isset($email['options']) && is_array($email['options'])) {
1057
+ $email['options'] = serialize($email['options']);
1058
+ }
1059
+ $email = $this->store->save(NEWSLETTER_EMAILS_TABLE, $email, $return_format);
1060
+ if ($return_format == OBJECT) {
1061
+ $email->options = maybe_unserialize($email->options);
1062
+ if (!is_array($email->options)) {
1063
+ $email->options = [];
1064
+ }
1065
+ } else if ($return_format == ARRAY_A) {
1066
+ $email['options'] = maybe_unserialize($email['options']);
1067
+ if (!is_array($email['options'])) {
1068
+ $email['options'] = [];
1069
+ }
1070
+ }
1071
+ return $email;
1072
+ }
1073
+
1074
+ function get_email_from_request() {
1075
+
1076
+ if (isset($_REQUEST['nek'])) {
1077
+ list($id, $token) = @explode('-', $_REQUEST['nek'], 2);
1078
+ } else if (isset($_COOKIE['tnpe'])) {
1079
+ list($id, $token) = @explode('-', $_COOKIE['tnpe'], 2);
1080
+ } else {
1081
+ return null;
1082
+ }
1083
+
1084
+ $email = $this->get_email($id);
1085
+
1086
+ // TODO: Check the token? It's really useful?
1087
+
1088
+ return $email;
1089
+ }
1090
+
1091
+ /**
1092
+ * Delete one or more emails identified by ID (single value or array of ID)
1093
+ *
1094
+ * @global wpdb $wpdb
1095
+ * @param int|array $id Single numeric ID or an array of IDs to be deleted
1096
+ * @return boolean
1097
+ */
1098
+ function delete_email($id) {
1099
+ global $wpdb;
1100
+ $r = $this->store->delete(NEWSLETTER_EMAILS_TABLE, $id);
1101
+ if ($r !== false) {
1102
+ // $id could be an array if IDs
1103
+ $id = (array) $id;
1104
+ foreach ($id as $email_id) {
1105
+ $wpdb->delete(NEWSLETTER_STATS_TABLE, ['email_id' => $email_id]);
1106
+ $wpdb->delete(NEWSLETTER_SENT_TABLE, ['email_id' => $email_id]);
1107
+ }
1108
+ }
1109
+ return $r;
1110
+ }
1111
+
1112
+ function get_email_field($id, $field_name) {
1113
+ return $this->store->get_field(NEWSLETTER_EMAILS_TABLE, $id, $field_name);
1114
+ }
1115
+
1116
+ function get_email_status_slug($email) {
1117
+ $email = (object) $email;
1118
+ if ($email->status == 'sending' && $email->send_on > time()) {
1119
+ return 'scheduled';
1120
+ }
1121
+ return $email->status;
1122
+ }
1123
+
1124
+ function get_email_status_label($email) {
1125
+ $email = (object) $email;
1126
+ $status = $this->get_email_status_slug($email);
1127
+ switch ($status) {
1128
+ case 'sending':
1129
+ return __('Sending', 'newsletter');
1130
+ case 'scheduled':
1131
+ return __('Scheduled', 'newsletter');
1132
+ case 'sent':
1133
+ return __('Sent', 'newsletter');
1134
+ case 'paused':
1135
+ return __('Paused', 'newsletter');
1136
+ case 'new':
1137
+ return __('Draft', 'newsletter');
1138
+ default:
1139
+ return ucfirst($email->status);
1140
+ }
1141
+ }
1142
+
1143
+ function show_email_status_label($email) {
1144
+ echo '<span class="tnp-email-status tnp-email-status--', $this->get_email_status_slug($email), '">', esc_html($this->get_email_status_label($email)), '</span>';
1145
+ }
1146
+
1147
+ function get_email_progress($email, $format = 'percent') {
1148
+ return $email->total > 0 ? intval($email->sent / $email->total * 100) : 0;
1149
+ }
1150
+
1151
+ function show_email_progress_bar($email, $attrs = []) {
1152
+
1153
+ $email = (object) $email;
1154
+
1155
+ $attrs = array_merge(array('format' => 'percent', 'numbers' => false, 'scheduled' => false), $attrs);
1156
+
1157
+ if ($email->status == 'sending' && $email->send_on > time()) {
1158
+ if ($attrs['scheduled']) {
1159
+ echo '<span class="tnp-progress-date">', $this->format_date($email->send_on), '</span>';
1160
+ }
1161
+ return;
1162
+ } else if ($email->status == 'new') {
1163
+ echo '';
1164
+ return;
1165
+ } else if ($email->status == 'sent') {
1166
+ $percent = 100;
1167
+ } else {
1168
+ $percent = $this->get_email_progress($email);
1169
+ }
1170
+
1171
+ echo '<div class="tnp-progress tnp-progress--' . $email->status . '">';
1172
+ echo '<div class="tnp-progress-bar" role="progressbar" style="width: ', $percent, '%;">&nbsp;', $percent, '%&nbsp;</div>';
1173
+ echo '</div>';
1174
+ if ($attrs['numbers']) {
1175
+ if ($email->status == 'sent') {
1176
+ echo '<div class="tnp-progress-numbers">', $email->total, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1177
+ } else {
1178
+ echo '<div class="tnp-progress-numbers">', $email->sent, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
1179
+ }
1180
+ }
1181
+ }
1182
+
1183
+ function get_email_type_label($type) {
1184
+
1185
+ // Is an email?
1186
+ if (is_object($type))
1187
+ $type = $type->type;
1188
+
1189
+ $label = apply_filters('newsletter_email_type', '', $type);
1190
+
1191
+ if (!empty($label))
1192
+ return $label;
1193
+
1194
+ switch ($type) {
1195
+ case 'followup':
1196
+ return 'Followup';
1197
+ case 'message':
1198
+ return 'Standard Newsletter';
1199
+ case 'feed':
1200
+ return 'Feed by Mail';
1201
+ }
1202
+
1203
+ if (strpos($type, 'automated') === 0) {
1204
+ list($a, $id) = explode('_', $type);
1205
+ return 'Automated Channel ' . $id;
1206
+ }
1207
+
1208
+ return ucfirst($type);
1209
+ }
1210
+
1211
+ function get_email_progress_label($email) {
1212
+ if ($email->status == 'sent' || $email->status == 'sending') {
1213
+ return $email->sent . ' ' . __('of', 'newsletter') . ' ' . $email->total;
1214
+ }
1215
+ return '-';
1216
+ }
1217
+
1218
+ /**
1219
+ * Returns the email unique key
1220
+ * @param TNP_User $user
1221
+ * @return string
1222
+ */
1223
+ function get_email_key($email) {
1224
+ if (!isset($email->token)) {
1225
+ return $email->id . '-';
1226
+ }
1227
+ return $email->id . '-' . $email->token;
1228
+ }
1229
+
1230
+ /** Searches for a user using the nk parameter or the ni and nt parameters. Tries even with the newsletter cookie.
1231
+ * If found, the user object is returned or null.
1232
+ * The user is returned without regards to his status that should be checked by caller.
1233
+ *
1234
+ * DO NOT REMOVE EVEN IF OLD
1235
+ *
1236
+ * @return TNP_User
1237
+ */
1238
+ function check_user($context = '') {
1239
+ global $wpdb;
1240
+
1241
+ $user = null;
1242
+
1243
+ if (isset($_REQUEST['nk'])) {
1244
+ list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1245
+ } else if (isset($_COOKIE['newsletter'])) {
1246
+ list ($id, $token) = @explode('-', $_COOKIE['newsletter'], 2);
1247
+ }
1248
+
1249
+ if (isset($id)) {
1250
+ $user = $this->get_user($id);
1251
+ if ($user) {
1252
+ if ($context == 'preconfirm') {
1253
+ if ($token != md5($user->token)) {
1254
+ $user = null;
1255
+ }
1256
+ } else {
1257
+ if ($token != $user->token) {
1258
+ $user = null;
1259
+ }
1260
+ }
1261
+ }
1262
+ }
1263
+
1264
+ if ($user == null && is_user_logged_in()) {
1265
+ $user = $this->get_user_by_wp_user_id(get_current_user_id());
1266
+ }
1267
+ return $user;
1268
+ }
1269
+
1270
+ /** 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
1271
+ * the "id" attribute or key and that is used to load the user.
1272
+ *
1273
+ * @global type $wpdb
1274
+ * @param string|int|object|array $id_or_email
1275
+ * @param string $format
1276
+ * @return TNP_User|null
1277
+ */
1278
+ function get_user($id_or_email, $format = OBJECT) {
1279
+ global $wpdb;
1280
+
1281
+ if (empty($id_or_email))
1282
+ return null;
1283
+
1284
+ // To simplify the reaload of a user passing the user it self.
1285
+ if (is_object($id_or_email)) {
1286
+ $id_or_email = $id_or_email->id;
1287
+ } else if (is_array($id_or_email)) {
1288
+ $id_or_email = $id_or_email['id'];
1289
+ }
1290
+
1291
+ $id_or_email = strtolower(trim($id_or_email));
1292
+
1293
+ if (is_numeric($id_or_email)) {
1294
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where id=%d limit 1", $id_or_email), $format);
1295
+ } else {
1296
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $id_or_email), $format);
1297
+ }
1298
+
1299
+ if ($wpdb->last_error) {
1300
+ $this->logger->error($wpdb->last_error);
1301
+ return null;
1302
+ }
1303
+ return $r;
1304
+ }
1305
+
1306
+ /**
1307
+ *
1308
+ * @global wpdb $wpdb
1309
+ * @param string $email
1310
+ * @return TNP_User
1311
+ */
1312
+ function get_user_by_email($email) {
1313
+ global $wpdb;
1314
+
1315
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $email));
1316
+
1317
+ if ($wpdb->last_error) {
1318
+ $this->logger->error($wpdb->last_error);
1319
+ return null;
1320
+ }
1321
+ return $r;
1322
+ }
1323
+
1324
+ /**
1325
+ * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
1326
+ *
1327
+ * @param type $user
1328
+ */
1329
+ function get_user_edit_url($user) {
1330
+ $id = $this->to_int_id($user);
1331
+ return admin_url('admin.php') . '?page=newsletter_users_edit&id=' . $id;
1332
+ }
1333
+
1334
+ /**
1335
+ * Returns the user unique key
1336
+ * @param TNP_User $user
1337
+ * @return string
1338
+ */
1339
+ function get_user_key($user, $context = '') {
1340
+ if (empty($user->token)) {
1341
+ $this->refresh_user_token($user);
1342
+ }
1343
+
1344
+ if ($context == 'preconfirm') {
1345
+ return $user->id . '-' . md5($user->token);
1346
+ }
1347
+ return $user->id . '-' . $user->token;
1348
+ }
1349
+
1350
+ function get_user_status_label($user, $html = false) {
1351
+ if (!$html) return TNP_User::get_status_label($user->status);
1352
+
1353
+ $label = TNP_User::get_status_label($user->status);
1354
+ $class = 'unknown';
1355
+ switch ($user->status) {
1356
+ case TNP_User::STATUS_NOT_CONFIRMED: $class = 'not-confirmed';
1357
+ break;
1358
+ case TNP_User::STATUS_CONFIRMED: $class = 'confirmed';
1359
+ break;
1360
+ case TNP_User::STATUS_UNSUBSCRIBED: $class = 'unsubscribed';
1361
+ break;
1362
+ case TNP_User::STATUS_BOUNCED: $class = 'bounced';
1363
+ break;
1364
+ case TNP_User::STATUS_COMPLAINED: $class = 'complained';
1365
+ break;
1366
+ }
1367
+ return '<span class="' . $class . '">' . esc_html($label) . '</span>';
1368
+ }
1369
+
1370
+ /**
1371
+ * Return the user identified by the "nk" parameter (POST or GET).
1372
+ * If no user can be found or the token is not matching, returns null.
1373
+ * If die_on_fail is true it dies instead of return null.
1374
+ *
1375
+ * @param bool $die_on_fail
1376
+ * @return TNP_User
1377
+ */
1378
+ function get_user_from_request($die_on_fail = false, $context = '') {
1379
+ $id = 0;
1380
+ if (isset($_REQUEST['nk'])) {
1381
+ list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
1382
+ }
1383
+ $user = $this->get_user($id);
1384
+
1385
+ if ($user == null) {
1386
+ if ($die_on_fail) {
1387
+ die(__('No subscriber found.', 'newsletter'));
1388
+ } else {
1389
+ return $this->get_user_from_logged_in_user();
1390
+ }
1391
+ }
1392
+
1393
+ if ($context == 'preconfirm') {
1394
+ $user_token = md5($user->token);
1395
+ } else {
1396
+ $user_token = $user->token;
1397
+ }
1398
+
1399
+ if ($token != $user_token) {
1400
+ if ($die_on_fail) {
1401
+ die(__('No subscriber found.', 'newsletter'));
1402
+ } else {
1403
+ return $this->get_user_from_logged_in_user();
1404
+ }
1405
+ }
1406
+ return $user;
1407
+ }
1408
+
1409
+ function get_user_from_logged_in_user() {
1410
+ if (is_user_logged_in()) {
1411
+ return $this->get_user_by_wp_user_id(get_current_user_id());
1412
+ }
1413
+ return null;
1414
+ }
1415
+
1416
+ function get_user_count($refresh = false) {
1417
+ global $wpdb;
1418
+ $user_count = get_transient('newsletter_user_count');
1419
+ if ($user_count === false || $refresh) {
1420
+ $user_count = $wpdb->get_var("select count(*) from " . NEWSLETTER_USERS_TABLE . " where status='C'");
1421
+ set_transient('newsletter_user_count', $user_count, DAY_IN_SECONDS);
1422
+ }
1423
+ return $user_count;
1424
+ }
1425
+
1426
+ function get_profile($id, $language = '') {
1427
+ return TNP_Profile_Service::get_profile_by_id($id, $language);
1428
+ }
1429
+
1430
+ /**
1431
+ * @param string $language The language for the list labels (it does not affect the lists returned)
1432
+ * @return TNP_Profile[]
1433
+ */
1434
+ function get_profiles($language = '') {
1435
+ return TNP_Profile_Service::get_profiles($language);
1436
+ }
1437
+
1438
+ /**
1439
+ * Returns a list of TNP_Profile which are public.
1440
+ *
1441
+ * @staticvar array $profiles
1442
+ * @param string $language
1443
+ * @return TNP_Profile[]
1444
+ */
1445
+ function get_profiles_public($language = '') {
1446
+ static $profiles = [];
1447
+ if (isset($profiles[$language])) {
1448
+ return $profiles[$language];
1449
+ }
1450
+
1451
+ $profiles[$language] = [];
1452
+ $all = $this->get_profiles($language);
1453
+ foreach ($all as $profile) {
1454
+ if ($profile->is_private())
1455
+ continue;
1456
+
1457
+ $profiles[$language]['' . $profile->id] = $profile;
1458
+ }
1459
+ return $profiles[$language];
1460
+ }
1461
+
1462
+ /**
1463
+ * Really bad name!
1464
+ * @staticvar array $profiles
1465
+ * @param type $language
1466
+ * @return array
1467
+ */
1468
+ function get_profiles_for_profile($language = '') {
1469
+ static $profiles = [];
1470
+ if (isset($profiles[$language])) {
1471
+ return $profiles[$language];
1472
+ }
1473
+
1474
+ $profiles[$language] = [];
1475
+ $all = $this->get_profiles($language);
1476
+ foreach ($all as $profile) {
1477
+ if (!$profile->show_on_profile())
1478
+ continue;
1479
+
1480
+ $profiles[$language]['' . $profile->id] = $profile;
1481
+ }
1482
+ return $profiles[$language];
1483
+ }
1484
+
1485
+ /**
1486
+ * @param string $language The language for the list labels (it does not affect the lists returned)
1487
+ * @return TNP_List[]
1488
+ */
1489
+ function get_lists($language = '') {
1490
+ static $lists = array();
1491
+ if (isset($lists[$language])) {
1492
+ return $lists[$language];
1493
+ }
1494
+
1495
+ $lists[$language] = array();
1496
+ $data = NewsletterSubscription::instance()->get_options('lists', $language);
1497
+ for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
1498
+ if (empty($data['list_' . $i])) {
1499
+ continue;
1500
+ }
1501
+ $list = $this->create_tnp_list_from_db_lists_array($data, $i);
1502
+
1503
+ $lists[$language]['' . $list->id] = $list;
1504
+ }
1505
+ return $lists[$language];
1506
+ }
1507
+
1508
+ public function create_tnp_list_from_db_lists_array($db_lists_array, $list_id) {
1509
+
1510
+ $list = new TNP_List();
1511
+ $list->name = $db_lists_array['list_' . $list_id];
1512
+ $list->id = $list_id;
1513
+
1514
+ // New format
1515
+ if (isset($db_lists_array['list_' . $list_id . '_subscription'])) {
1516
+ $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1517
+ $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1518
+ $list->checked = $db_lists_array['list_' . $list_id . '_subscription'] == 2;
1519
+ $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_subscription']) && !$list->forced;
1520
+ $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && !empty($db_lists_array['list_' . $list_id . '_profile']);
1521
+ } else {
1522
+ $list->forced = !empty($db_lists_array['list_' . $list_id . '_forced']);
1523
+ $list->status = empty($db_lists_array['list_' . $list_id . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1524
+ $list->checked = !empty($db_lists_array['list_' . $list_id . '_checked']);
1525
+ $list->show_on_subscription = $db_lists_array['list_' . $list_id . '_status'] == 2 && !$list->forced;
1526
+ $list->show_on_profile = $db_lists_array['list_' . $list_id . '_status'] == 1 || $db_lists_array['list_' . $list_id . '_status'] == 2;
1527
+ }
1528
+ if (empty($db_lists_array['list_' . $list_id . '_languages'])) {
1529
+ $list->languages = array();
1530
+ } else {
1531
+ $list->languages = $db_lists_array['list_' . $list_id . '_languages'];
1532
+ }
1533
+
1534
+ return $list;
1535
+ }
1536
+
1537
+ /**
1538
+ * Returns an array of TNP_List objects of lists that are public.
1539
+ * @return TNP_List[]
1540
+ */
1541
+ function get_lists_public($language = '') {
1542
+ static $lists = array();
1543
+ if (isset($lists[$language])) {
1544
+ return $lists[$language];
1545
+ }
1546
+
1547
+ $lists[$language] = array();
1548
+ $all = $this->get_lists($language);
1549
+ foreach ($all as $list) {
1550
+ if ($list->status == TNP_List::STATUS_PRIVATE) {
1551
+ continue;
1552
+ }
1553
+ $lists[$language]['' . $list->id] = $list;
1554
+ }
1555
+ return $lists[$language];
1556
+ }
1557
+
1558
+ /**
1559
+ * Lists to be shown on subscription form.
1560
+ *
1561
+ * @return TNP_List[]
1562
+ */
1563
+ function get_lists_for_subscription($language = '') {
1564
+ static $lists = array();
1565
+ if (isset($lists[$language])) {
1566
+ return $lists[$language];
1567
+ }
1568
+
1569
+ $lists[$language] = array();
1570
+ $all = $this->get_lists($language);
1571
+ foreach ($all as $list) {
1572
+ if (!$list->show_on_subscription) {
1573
+ continue;
1574
+ }
1575
+ $lists[$language]['' . $list->id] = $list;
1576
+ }
1577
+ return $lists[$language];
1578
+ }
1579
+
1580
+ /**
1581
+ * Returns the lists to be shown in the profile page. The list is associative with
1582
+ * the list ID as key.
1583
+ *
1584
+ * @return TNP_List[]
1585
+ */
1586
+ function get_lists_for_profile($language = '') {
1587
+ static $lists = array();
1588
+ if (isset($lists[$language])) {
1589
+ return $lists[$language];
1590
+ }
1591
+
1592
+ $lists[$language] = array();
1593
+ $all = $this->get_lists($language);
1594
+ foreach ($all as $list) {
1595
+ if (!$list->show_on_profile) {
1596
+ continue;
1597
+ }
1598
+ $lists[$language]['' . $list->id] = $list;
1599
+ }
1600
+ return $lists[$language];
1601
+ }
1602
+
1603
+ /**
1604
+ * Returns the list object or null if not found.
1605
+ *
1606
+ * @param int $id
1607
+ * @return TNP_List
1608
+ */
1609
+ function get_list($id, $language = '') {
1610
+ $lists = $this->get_lists($language);
1611
+ if (!isset($lists['' . $id])) {
1612
+ return null;
1613
+ }
1614
+
1615
+ return $lists['' . $id];
1616
+ }
1617
+
1618
+ /**
1619
+ * NEVER CHANGE THIS METHOD SIGNATURE, USER BY THIRD PARTY PLUGINS.
1620
+ *
1621
+ * Saves a new user on the database. Return false if the email (that must be unique) is already
1622
+ * there. For a new users set the token and creation time if not passed.
1623
+ *
1624
+ * @param array $user
1625
+ * @return TNP_User|array|boolean Returns the subscriber reloaded from DB in the specified format. Flase on failure (duplicate email).
1626
+ */
1627
+ function save_user($user, $return_format = OBJECT) {
1628
+ if (is_object($user)) {
1629
+ $user = (array) $user;
1630
+ }
1631
+ if (empty($user['id'])) {
1632
+ $existing = $this->get_user($user['email']);
1633
+ if ($existing != null) {
1634
+ return false;
1635
+ }
1636
+ if (empty($user['token'])) {
1637
+ $user['token'] = NewsletterModule::get_token();
1638
+ }
1639
+ }
1640
+
1641
+ // We still don't know when it happens but under some conditions, matbe external, lists are passed as NULL
1642
+ foreach ($user as $key => $value) {
1643
+ if (strpos($key, 'list_') !== 0) {
1644
+ continue;
1645
+ }
1646
+ if (is_null($value)) {
1647
+ unset($user[$key]);
1648
+ } else {
1649
+ $user[$key] = (int) $value;
1650
+ }
1651
+ }
1652
+
1653
+ // Due to the unique index on email field, this can fail.
1654
+ return $this->store->save(NEWSLETTER_USERS_TABLE, $user, $return_format);
1655
+ }
1656
+
1657
+ /**
1658
+ * Updates the user last activity timestamp.
1659
+ *
1660
+ * @global wpdb $wpdb
1661
+ * @param TNP_User $user
1662
+ */
1663
+ function update_user_last_activity($user) {
1664
+ global $wpdb;
1665
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set last_activity=%d where id=%d limit 1", time(), $user->id));
1666
+ }
1667
+
1668
+ function update_user_ip($user, $ip) {
1669
+ global $wpdb;
1670
+ // Only if changed
1671
+ $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));
1672
+ }
1673
+
1674
+ /**
1675
+ * Finds single style blocks and adds a style attribute to every HTML tag with a class exactly matching the rules in the style
1676
+ * block. HTML tags can use the attribute "inline-class" to exact match a style rules if they need a composite class definition.
1677
+ *
1678
+ * @param string $content
1679
+ * @param boolean $strip_style_blocks
1680
+ * @return string
1681
+ */
1682
+ function inline_css($content, $strip_style_blocks = false) {
1683
+ $matches = array();
1684
+ // "s" skips line breaks
1685
+ $styles = preg_match('|<style>(.*?)</style>|s', $content, $matches);
1686
+ if (isset($matches[1])) {
1687
+ $style = str_replace(array("\n", "\r"), '', $matches[1]);
1688
+ $rules = array();
1689
+ preg_match_all('|\s*\.(.*?)\{(.*?)\}\s*|s', $style, $rules);
1690
+ for ($i = 0; $i < count($rules[1]); $i++) {
1691
+ $class = trim($rules[1][$i]);
1692
+ $value = trim($rules[2][$i]);
1693
+ $value = preg_replace('|\s+|', ' ', $value);
1694
+ $content = str_replace(' class="' . $class . '"', ' class="' . $class . '" style="' . $value . '"', $content);
1695
+ $content = str_replace(' inline-class="' . $class . '"', ' style="' . $value . '"', $content);
1696
+ }
1697
+ }
1698
+
1699
+ if ($strip_style_blocks) {
1700
+ return trim(preg_replace('|<style>.*?</style>|s', '', $content));
1701
+ } else {
1702
+ return $content;
1703
+ }
1704
+ }
1705
+
1706
+ /**
1707
+ * Returns a list of users marked as "test user".
1708
+ * @return TNP_User[]
1709
+ */
1710
+ function get_test_users() {
1711
+ return $this->store->get_all(NEWSLETTER_USERS_TABLE, "where test=1 and status in ('C', 'S')");
1712
+ }
1713
+
1714
+ /**
1715
+ * Deletes a subscriber and cleans up all the stats table with his correlated data.
1716
+ *
1717
+ * @global wpdb $wpdb
1718
+ * @param int|id[] $id
1719
+ */
1720
+ function delete_user($id) {
1721
+ global $wpdb;
1722
+ $id = (array) $id;
1723
+ foreach ($id as $user_id) {
1724
+ $user = $this->get_user($user_id);
1725
+ if ($user) {
1726
+ $r = $this->store->delete(NEWSLETTER_USERS_TABLE, $user_id);
1727
+ $wpdb->delete(NEWSLETTER_STATS_TABLE, array('user_id' => $user_id));
1728
+ $wpdb->delete(NEWSLETTER_SENT_TABLE, array('user_id' => $user_id));
1729
+ do_action('newsletter_user_deleted', $user);
1730
+ }
1731
+ }
1732
+
1733
+ return count($id);
1734
+ }
1735
+
1736
+ /**
1737
+ * Add to a destination URL the parameters to identify the user, the email and to show
1738
+ * an alert message, if required. The parameters are then managed by the [newsletter] shortcode.
1739
+ *
1740
+ * @param string $url If empty the standard newsletter page URL is used (usually it is empty, but sometime a custom URL has been specified)
1741
+ * @param string $message_key The message identifier
1742
+ * @param TNP_User|int $user
1743
+ * @param TNP_Email|int $email
1744
+ * @param string $alert An optional alter message to be shown. Does not work with custom URLs
1745
+ * @return string The final URL with parameters
1746
+ */
1747
+ function build_message_url($url = '', $message_key = '', $user = null, $email = null, $alert = '') {
1748
+ $params = 'nm=' . urlencode($message_key);
1749
+ $language = '';
1750
+ if ($user) {
1751
+ if (!is_object($user)) {
1752
+ $user = $this->get_user($user);
1753
+ }
1754
+ if ($message_key == 'confirmation') {
1755
+ $params .= '&nk=' . urlencode($this->get_user_key($user, 'preconfirm'));
1756
+ } else {
1757
+ $params .= '&nk=' . urlencode($this->get_user_key($user));
1758
+ }
1759
+
1760
+ $language = $this->get_user_language($user);
1761
+ }
1762
+
1763
+ if ($email) {
1764
+ if (!is_object($email)) {
1765
+ $email = $this->get_email($email);
1766
+ }
1767
+ $params .= '&nek=' . urlencode($this->get_email_key($email));
1768
+ }
1769
+
1770
+ if ($alert) {
1771
+ $params .= '&alert=' . urlencode($alert);
1772
+ }
1773
+
1774
+ if (empty($url)) {
1775
+ $url = Newsletter::instance()->get_newsletter_page_url($language);
1776
+ }
1777
+
1778
+ return self::add_qs($url, $params, false);
1779
+ }
1780
+
1781
+ /**
1782
+ * Builds a standard Newsletter action URL for the specified action.
1783
+ *
1784
+ * @param string $action
1785
+ * @param TNP_User $user
1786
+ * @param TNP_Email $email
1787
+ * @return string
1788
+ */
1789
+ function build_action_url($action, $user = null, $email = null) {
1790
+ $url = $this->add_qs($this->get_home_url(), 'na=' . urlencode($action));
1791
+ //$url = $this->add_qs(admin_url('admin-ajax.php'), 'action=newsletter&na=' . urlencode($action));
1792
+ if ($user) {
1793
+ $url .= '&nk=' . urlencode($this->get_user_key($user));
1794
+ }
1795
+ if ($email) {
1796
+ $url .= '&nek=' . urlencode($this->get_email_key($email));
1797
+ }
1798
+ return $url;
1799
+ }
1800
+
1801
+ function get_subscribe_url() {
1802
+ return $this->build_action_url('s');
1803
+ }
1804
+
1805
+ function clean_stats_table() {
1806
+ global $wpdb;
1807
+ $this->logger->info('Cleaning up stats table');
1808
+ $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");
1809
+ $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");
1810
+ }
1811
+
1812
+ function clean_sent_table() {
1813
+ global $wpdb;
1814
+ $this->logger->info('Cleaning up sent table');
1815
+ $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");
1816
+ $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");
1817
+ }
1818
+
1819
+ function clean_user_logs_table() {
1820
+ //global $wpdb;
1821
+ }
1822
+
1823
+ function clean_tables() {
1824
+ $this->clean_sent_table();
1825
+ $this->clean_stats_table();
1826
+ $this->clean_user_logs_table();
1827
+ }
1828
+
1829
+ function anonymize_ip($ip) {
1830
+ if (empty($ip)) {
1831
+ return $ip;
1832
+ }
1833
+ $parts = explode('.', $ip);
1834
+ array_pop($parts);
1835
+ return implode('.', $parts) . '.0';
1836
+ }
1837
+
1838
+ function process_ip($ip) {
1839
+
1840
+ $option = Newsletter::instance()->options['ip'];
1841
+ if (empty($option)) {
1842
+ return $ip;
1843
+ }
1844
+ if ($option == 'anonymize') {
1845
+ return $this->anonymize_ip($ip);
1846
+ }
1847
+ return '';
1848
+ }
1849
+
1850
+ function anonymize_user($id) {
1851
+ global $wpdb;
1852
+ $user = $this->get_user($id);
1853
+ if (!$user) {
1854
+ return null;
1855
+ }
1856
+
1857
+ $user->name = '';
1858
+ $user->surname = '';
1859
+ $user->ip = $this->anonymize_ip($user->ip);
1860
+
1861
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1862
+ $field = 'profile_' . $i;
1863
+ $user->$field = '';
1864
+ }
1865
+
1866
+ // [TODO] Status?
1867
+ $user->status = TNP_User::STATUS_UNSUBSCRIBED;
1868
+ $user->email = $user->id . '@anonymi.zed';
1869
+
1870
+ $user = $this->save_user($user);
1871
+
1872
+ return $user;
1873
+ }
1874
+
1875
+ /**
1876
+ * Changes a user status. Accept a user object, user id or user email.
1877
+ *
1878
+ * @param TNP_User $user
1879
+ * @param string $status
1880
+ * @return TNP_User
1881
+ */
1882
+ function set_user_status($user, $status) {
1883
+ global $wpdb;
1884
+
1885
+ $this->logger->debug('Status change to ' . $status . ' of subscriber ' . $user->id . ' from ' . $_SERVER['REQUEST_URI']);
1886
+
1887
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set status=%s where id=%d limit 1", $status, $user->id));
1888
+ $user->status = $status;
1889
+ return $this->get_user($user);
1890
+ }
1891
+
1892
+ /**
1893
+ *
1894
+ * @global wpdb $wpdb
1895
+ * @param TNP_User $user
1896
+ * @return TNP_User
1897
+ */
1898
+ function refresh_user_token($user) {
1899
+ global $wpdb;
1900
+
1901
+ $token = $this->get_token();
1902
+
1903
+ $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set token=%s where id=%d limit 1", $token, $user->id));
1904
+ $user->token = $token;
1905
+ }
1906
+
1907
+ /**
1908
+ * Create a log entry with the meaningful user data.
1909
+ *
1910
+ * @global wpdb $wpdb
1911
+ * @param TNP_User $user
1912
+ * @param string $source
1913
+ * @return type
1914
+ */
1915
+ function add_user_log($user, $source = '') {
1916
+ global $wpdb;
1917
+
1918
+ $lists = $this->get_lists_public();
1919
+ foreach ($lists as $list) {
1920
+ $field_name = 'list_' . $list->id;
1921
+ $data[$field_name] = $user->$field_name;
1922
+ }
1923
+ $data['status'] = $user->status;
1924
+ $ip = $this->get_remote_ip();
1925
+ $ip = $this->process_ip($ip);
1926
+ $this->store->save($wpdb->prefix . 'newsletter_user_logs', array('ip' => $ip, 'user_id' => $user->id, 'source' => $source, 'created' => time(), 'data' => json_encode($data)));
1927
+ }
1928
+
1929
+ /**
1930
+ *
1931
+ * @global wpdb $wpdb
1932
+ * @param TNP_User $user
1933
+ * @param int $list
1934
+ * @param type $value
1935
+ */
1936
+ function set_user_list($user, $list, $value) {
1937
+ global $wpdb;
1938
+
1939
+ $list = (int) $list;
1940
+ $value = $value ? 1 : 0;
1941
+ $r = $wpdb->update(NEWSLETTER_USERS_TABLE, array('list_' . $list => $value), array('id' => $user->id));
1942
+ }
1943
+
1944
+ function set_user_field($id, $field, $value) {
1945
+ $this->store->set_field(NEWSLETTER_USERS_TABLE, $id, $field, $value);
1946
+ }
1947
+
1948
+ function set_user_wp_user_id($user_id, $wp_user_id) {
1949
+ $this->store->set_field(NEWSLETTER_USERS_TABLE, $user_id, 'wp_user_id', $wp_user_id);
1950
+ }
1951
+
1952
+ /**
1953
+ *
1954
+ * @param int $wp_user_id
1955
+ * @param string $format
1956
+ * @return TNP_User
1957
+ */
1958
+ function get_user_by_wp_user_id($wp_user_id, $format = OBJECT) {
1959
+ return $this->store->get_single_by_field(NEWSLETTER_USERS_TABLE, 'wp_user_id', $wp_user_id, $format);
1960
+ }
1961
+
1962
+ /**
1963
+ * Returns the user language IF there is a supported mutilanguage plugin installed.
1964
+ * @param TNP_User $user
1965
+ * @return string Language code or empty
1966
+ */
1967
+ function get_user_language($user) {
1968
+ if ($user && $this->is_multilanguage()) {
1969
+ return $user->language;
1970
+ }
1971
+ return '';
1972
+ }
1973
+
1974
+ /**
1975
+ * Replaces every possible Newsletter tag ({...}) in a piece of text or HTML.
1976
+ *
1977
+ * @global wpdb $wpdb
1978
+ * @param string $text
1979
+ * @param mixed $user Can be an object, associative array or id
1980
+ * @param mixed $email Can be an object, associative array or id
1981
+ * @param type $referrer
1982
+ * @return type
1983
+ */
1984
+ function replace($text, $user = null, $email = null, $referrer = null) {
1985
+ global $wpdb;
1986
+
1987
+ if (strpos($text, '<p') !== false) {
1988
+ $esc_html = true;
1989
+ } else {
1990
+ $esc_html = false;
1991
+ }
1992
+
1993
+ static $home_url = false;
1994
+
1995
+ if (!$home_url) {
1996
+ $home_url = home_url('/');
1997
+ }
1998
+
1999
+ //$this->logger->debug('Replace start');
2000
+ if ($user !== null && !is_object($user)) {
2001
+ if (is_array($user)) {
2002
+ $user = (object) $user;
2003
+ } else if (is_numeric($user)) {
2004
+ $user = $this->get_user($user);
2005
+ } else {
2006
+ $user = null;
2007
+ }
2008
+ }
2009
+
2010
+ if ($email !== null && !is_object($email)) {
2011
+ if (is_array($email)) {
2012
+ $email = (object) $email;
2013
+ } else if (is_numeric($email)) {
2014
+ $email = $this->get_email($email);
2015
+ } else {
2016
+ $email = null;
2017
+ }
2018
+ }
2019
+
2020
+ $initial_language = $this->get_current_language();
2021
+
2022
+ if ($user && $user->language) {
2023
+ $this->switch_language($user->language);
2024
+ }
2025
+
2026
+
2027
+ $text = apply_filters('newsletter_replace', $text, $user, $email, $esc_html);
2028
+
2029
+ $text = $this->replace_url($text, 'blog_url', $home_url);
2030
+ $text = $this->replace_url($text, 'home_url', $home_url);
2031
+
2032
+ $text = str_replace('{blog_title}', html_entity_decode(get_bloginfo('name')), $text);
2033
+ $text = str_replace('{blog_description}', get_option('blogdescription'), $text);
2034
+
2035
+ $text = $this->replace_date($text);
2036
+
2037
+ if ($user) {
2038
+ //$this->logger->debug('Replace with user ' . $user->id);
2039
+ $nk = $this->get_user_key($user);
2040
+ $options_profile = NewsletterSubscription::instance()->get_options('profile', $this->get_user_language($user));
2041
+ $text = str_replace('{email}', $user->email, $text);
2042
+ $name = apply_filters('newsletter_replace_name', $user->name, $user);
2043
+ if (empty($name)) {
2044
+ $text = str_replace(' {name}', '', $text);
2045
+ $text = str_replace('{name}', '', $text);
2046
+ } else {
2047
+ $text = str_replace('{name}', esc_html($name), $text);
2048
+ }
2049
+
2050
+ switch ($user->sex) {
2051
+ case 'm': $text = str_replace('{title}', $options_profile['title_male'], $text);
2052
+ break;
2053
+ case 'f': $text = str_replace('{title}', $options_profile['title_female'], $text);
2054
+ break;
2055
+ case 'n': $text = str_replace('{title}', $options_profile['title_none'], $text);
2056
+ break;
2057
+ default:
2058
+ $text = str_replace('{title}', '', $text);
2059
+ }
2060
+
2061
+
2062
+ // Deprecated
2063
+ $text = str_replace('{surname}', esc_html($user->surname), $text);
2064
+ $text = str_replace('{last_name}', esc_html($user->surname), $text);
2065
+
2066
+ $full_name = esc_html(trim($user->name . ' ' . $user->surname));
2067
+ if (empty($full_name)) {
2068
+ $text = str_replace(' {full_name}', '', $text);
2069
+ $text = str_replace('{full_name}', '', $text);
2070
+ } else {
2071
+ $text = str_replace('{full_name}', $full_name, $text);
2072
+ }
2073
+
2074
+ $text = str_replace('{token}', $user->token, $text);
2075
+ $text = str_replace('%7Btoken%7D', $user->token, $text);
2076
+ $text = str_replace('{id}', $user->id, $text);
2077
+ $text = str_replace('%7Bid%7D', $user->id, $text);
2078
+ $text = str_replace('{ip}', $user->ip, $text);
2079
+ $text = str_replace('{key}', $nk, $text);
2080
+ $text = str_replace('%7Bkey%7D', $nk, $text);
2081
+
2082
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
2083
+ $p = 'profile_' . $i;
2084
+ $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
2085
+ }
2086
+
2087
+ $base = (empty($this->options_main['url']) ? get_option('home') : $this->options_main['url']);
2088
+ $id_token = '&amp;ni=' . $user->id . '&amp;nt=' . $user->token;
2089
+
2090
+ $text = $this->replace_url($text, 'subscription_confirm_url', $this->build_action_url('c', $user));
2091
+ $text = $this->replace_url($text, 'activation_url', $this->build_action_url('c', $user));
2092
+
2093
+ // Obsolete.
2094
+ $text = $this->replace_url($text, 'FOLLOWUP_SUBSCRIPTION_URL', self::add_qs($base, 'nm=fs' . $id_token));
2095
+ $text = $this->replace_url($text, 'FOLLOWUP_UNSUBSCRIPTION_URL', self::add_qs($base, 'nm=fu' . $id_token));
2096
+
2097
+ $text = $this->replace_url($text, 'UNLOCK_URL', $this->build_action_url('ul', $user));
2098
+ } else {
2099
+ //$this->logger->debug('Replace without user');
2100
+ $text = $this->replace_url($text, 'subscription_confirm_url', '#');
2101
+ $text = $this->replace_url($text, 'activation_url', '#');
2102
+ }
2103
+
2104
+ if ($email) {
2105
+ //$this->logger->debug('Replace with email ' . $email->id);
2106
+ $nek = $this->get_email_key($email);
2107
+ $text = str_replace('{email_id}', $email->id, $text);
2108
+ $text = str_replace('{email_key}', $nek, $text);
2109
+ $text = str_replace('{email_subject}', $email->subject, $text);
2110
+ // Deprecated
2111
+ $text = str_replace('{subject}', $email->subject, $text);
2112
+ $text = $this->replace_url($text, 'email_url', $this->build_action_url('v', $user) . '&id=' . $email->id);
2113
+ } else {
2114
+ //$this->logger->debug('Replace without email');
2115
+ $text = $this->replace_url($text, 'email_url', '#');
2116
+ }
2117
+
2118
+ if (strpos($text, '{subscription_form}') !== false) {
2119
+ $text = str_replace('{subscription_form}', NewsletterSubscription::instance()->get_subscription_form($referrer), $text);
2120
+ } else {
2121
+ for ($i = 1; $i <= 10; $i++) {
2122
+ if (strpos($text, "{subscription_form_$i}") !== false) {
2123
+ $text = str_replace("{subscription_form_$i}", NewsletterSubscription::instance()->get_form($i), $text);
2124
+ break;
2125
+ }
2126
+ }
2127
+ }
2128
+
2129
+ // Company info
2130
+ // TODO: Move to another module
2131
+ $options = Newsletter::instance()->get_options('info');
2132
+ $text = str_replace('{company_address}', $options['footer_contact'], $text);
2133
+ $text = str_replace('{company_name}', $options['footer_title'], $text);
2134
+ $text = str_replace('{company_legal}', $options['footer_legal'], $text);
2135
+
2136
+ $this->switch_language($initial_language);
2137
+ //$this->logger->debug('Replace end');
2138
+ return $text;
2139
+ }
2140
+
2141
+ function replace_date($text) {
2142
+ $text = str_replace('{date}', date_i18n(get_option('date_format')), $text);
2143
+
2144
+ // Date processing
2145
+ $x = 0;
2146
+ while (($x = strpos($text, '{date_', $x)) !== false) {
2147
+ $y = strpos($text, '}', $x);
2148
+ if ($y === false)
2149
+ continue;
2150
+ $f = substr($text, $x + 6, $y - $x - 6);
2151
+ $text = substr($text, 0, $x) . date_i18n($f) . substr($text, $y + 1);
2152
+ }
2153
+ return $text;
2154
+ }
2155
+
2156
+ function replace_url($text, $tag, $url) {
2157
+ static $home = false;
2158
+ if (!$home) {
2159
+ $home = trailingslashit(home_url());
2160
+ }
2161
+ $tag_lower = strtolower($tag);
2162
+ $text = str_replace('http://{' . $tag_lower . '}', $url, $text);
2163
+ $text = str_replace('https://{' . $tag_lower . '}', $url, $text);
2164
+ $text = str_replace($home . '{' . $tag_lower . '}', $url, $text);
2165
+ $text = str_replace($home . '%7B' . $tag_lower . '%7D', $url, $text);
2166
+ $text = str_replace('{' . $tag_lower . '}', $url, $text);
2167
+ $text = str_replace('%7B' . $tag_lower . '%7D', $url, $text);
2168
+
2169
+ $url_encoded = urlencode($url);
2170
+ $text = str_replace('%7B' . $tag_lower . '_encoded%7D', $url_encoded, $text);
2171
+ $text = str_replace('{' . $tag_lower . '_encoded}', $url_encoded, $text);
2172
+
2173
+ // for compatibility
2174
+ $text = str_replace($home . $tag, $url, $text);
2175
+
2176
+ return $text;
2177
+ }
2178
+
2179
+ public static function antibot_form_check($captcha = false) {
2180
+
2181
+ if (defined('NEWSLETTER_ANTIBOT') && !NEWSLETTER_ANTIBOT) {
2182
+ return true;
2183
+ }
2184
+
2185
+ if (strtolower($_SERVER['REQUEST_METHOD']) != 'post') {
2186
+ return false;
2187
+ }
2188
+
2189
+ if (!isset($_POST['ts']) || time() - $_POST['ts'] > 60) {
2190
+ return false;
2191
+ }
2192
+
2193
+ if ($captcha) {
2194
+ $n1 = (int) $_POST['n1'];
2195
+ if (empty($n1)) {
2196
+ return false;
2197
+ }
2198
+ $n2 = (int) $_POST['n2'];
2199
+ if (empty($n2)) {
2200
+ return false;
2201
+ }
2202
+ $n3 = (int) $_POST['n3'];
2203
+ if ($n1 + $n2 != $n3) {
2204
+ return false;
2205
+ }
2206
+ }
2207
+
2208
+ return true;
2209
+ }
2210
+
2211
+ public static function request_to_antibot_form($submit_label = 'Continue...', $captcha = false) {
2212
+ header('Content-Type: text/html;charset=UTF-8');
2213
+ header('X-Robots-Tag: noindex,nofollow,noarchive');
2214
+ header('Cache-Control: no-cache,no-store,private');
2215
+ echo "<!DOCTYPE html>\n";
2216
+ echo '<html><head>'
2217
+ . '<style type="text/css">'
2218
+ . '.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;}'
2219
+ . 'p {text-align: center; padding: 10px; color: #7F8C8D;}'
2220
+ . 'input[type=text] {width: 50px; padding: 10px 10px; border: none; border-radius: 2px; margin: 0px 5px;}'
2221
+ . 'input[type=submit] {text-align: center; border: none; padding: 10px 15px; font-family: "Open Sans", sans-serif; background-color: #27AE60; color: white; cursor: pointer;}'
2222
+ . '</style>'
2223
+ . '</head><body>';
2224
+ echo '<form method="post" action="https://www.domain.tld" id="form">';
2225
+ echo '<div style="width: 1px; height: 1px; overflow: hidden">';
2226
+ foreach ($_REQUEST as $name => $value) {
2227
+ if ($name == 'submit')
2228
+ continue;
2229
+ if (is_array($value)) {
2230
+ foreach ($value as $element) {
2231
+ echo '<input type="text" name="';
2232
+ echo esc_attr($name);
2233
+ echo '[]" value="';
2234
+ echo esc_attr(stripslashes($element));
2235
+ echo '">';
2236
+ }
2237
+ } else {
2238
+ echo '<input type="hidden" name="', esc_attr($name), '" value="', esc_attr(stripslashes($value)), '">';
2239
+ }
2240
+ }
2241
+ if (isset($_SERVER['HTTP_REFERER'])) {
2242
+ echo '<input type="hidden" name="nhr" value="' . esc_attr($_SERVER['HTTP_REFERER']) . '">';
2243
+ }
2244
+ echo '<input type="hidden" name="ts" value="' . time() . '">';
2245
+ echo '</div>';
2246
+
2247
+ if ($captcha) {
2248
+ echo '<div class="tnp-captcha">';
2249
+ echo '<p>', __('Math question', 'newsletter'), '</p>';
2250
+ echo '<input type="text" name="n1" value="', rand(1, 9), '" readonly style="width: 50px">';
2251
+ echo '+';
2252
+ echo '<input type="text" name="n2" value="', rand(1, 9), '" readonly style="width: 50px">';
2253
+ echo '=';
2254
+ echo '<input type="text" name="n3" value="?" style="width: 50px">';
2255
+ echo '<br><br>';
2256
+ echo '<input type="submit" value="', esc_attr($submit_label), '">';
2257
+ echo '</div>';
2258
+ }
2259
+ echo '<noscript><input type="submit" value="';
2260
+ echo esc_attr($submit_label);
2261
+ echo '"></noscript></form>';
2262
+ echo '<script>';
2263
+ echo 'document.getElementById("form").action="' . home_url('/') . '";';
2264
+ if (!$captcha) {
2265
+ echo 'document.getElementById("form").submit();';
2266
+ }
2267
+ echo '</script>';
2268
+ echo '</body></html>';
2269
+ die();
2270
+ }
2271
+
2272
+ static function extract_body($html) {
2273
+ $x = stripos($html, '<body');
2274
+ if ($x !== false) {
2275
+ $x = strpos($html, '>', $x);
2276
+ $y = strpos($html, '</body>');
2277
+ return substr($html, $x + 1, $y - $x - 1);
2278
+ } else {
2279
+ return $html;
2280
+ }
2281
+ }
2282
+
2283
+ /** Returns a percentage as string */
2284
+ static function percent($value, $total) {
2285
+ if ($total == 0)
2286
+ return '-';
2287
+ return sprintf("%.2f", $value / $total * 100) . '%';
2288
+ }
2289
+
2290
+ /** Returns a percentage as integer value */
2291
+ static function percentValue($value, $total) {
2292
+ if ($total == 0)
2293
+ return 0;
2294
+ return round($value / $total * 100);
2295
+ }
2296
+
2297
+ /**
2298
+ * Takes in a variable and checks if object, array or scalar and return the integer representing
2299
+ * a database record id.
2300
+ *
2301
+ * @param mixed $var
2302
+ * @return in
2303
+ */
2304
+ static function to_int_id($var) {
2305
+ if (is_object($var)) {
2306
+ return (int) $var->id;
2307
+ }
2308
+ if (is_array($var)) {
2309
+ return (int) $var['id'];
2310
+ }
2311
+ return (int) $var;
2312
+ }
2313
+
2314
+ static function to_array($text) {
2315
+ $text = trim($text);
2316
+ if (empty($text)) {
2317
+ return array();
2318
+ }
2319
+ $text = preg_split("/\\r\\n/", $text);
2320
+ $text = array_map('trim', $text);
2321
+ $text = array_map('strtolower', $text);
2322
+ $text = array_filter($text);
2323
+
2324
+ return $text;
2325
+ }
2326
+
2327
+ static function sanitize_ip($ip) {
2328
+ if (empty($ip)) {
2329
+ return '';
2330
+ }
2331
+ $ip = preg_replace('/[^0-9a-fA-F:., ]/', '', trim($ip));
2332
+ if (strlen($ip) > 50) $ip = substr($ip, 0, 50);
2333
+
2334
+ // When more than one IP is present due to firewalls, proxies, and so on. The first one should be the origin.
2335
+ if (strpos($ip, ',') !== false) {
2336
+ list($ip, $tail) = explode(',', $ip, 2);
2337
+ }
2338
+ return $ip;
2339
+ }
2340
+
2341
+ static function get_remote_ip() {
2342
+ $ip = '';
2343
+ if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2344
+ $ip = $_SERVER['HTTP_X_REAL_IP'];
2345
+ } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2346
+ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
2347
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
2348
+ $ip = $_SERVER['REMOTE_ADDR'];
2349
+ }
2350
+ return self::sanitize_ip($ip);
2351
+ }
2352
+
2353
+ static function get_signature($text) {
2354
+ $key = NewsletterStatistics::instance()->options['key'];
2355
+ return md5($text . $key);
2356
+ }
2357
+
2358
+ static function check_signature($text, $signature) {
2359
+ if (empty($signature)) {
2360
+ return false;
2361
+ }
2362
+ $key = NewsletterStatistics::instance()->options['key'];
2363
+ return md5($text . $key) === $signature;
2364
+ }
2365
+
2366
+ static function get_home_url() {
2367
+ static $url = false;
2368
+ if (!$url) {
2369
+ $url = home_url('/');
2370
+ }
2371
+ return $url;
2372
+ }
2373
+
2374
+ static function clean_eol($text) {
2375
+ $text = str_replace("\r\n", "\n", $text);
2376
+ $text = str_replace("\r", "\n", $text);
2377
+ $text = str_replace("\n", "\r\n", $text);
2378
+ return $text;
2379
+ }
2380
+
2381
+ function set_current_language($language) {
2382
+ self::$current_language = $language;
2383
+ }
2384
+
2385
+ /**
2386
+ * Return the current language code. Optionally, if a user is passed and it has a language
2387
+ * the user language is returned.
2388
+ * If there is no language available, an empty string is returned.
2389
+ *
2390
+ * @param TNP_User $user
2391
+ * @return string The language code
2392
+ */
2393
+ function get_current_language($user = null) {
2394
+
2395
+ if ($user && $user->language) {
2396
+ return $user->language;
2397
+ }
2398
+
2399
+ if (!empty(self::$current_language)) {
2400
+ return self::$current_language;
2401
+ }
2402
+
2403
+ // WPML
2404
+ if (class_exists('SitePress')) {
2405
+ $current_language = apply_filters('wpml_current_language', '');
2406
+ if ($current_language == 'all') {
2407
+ $current_language = '';
2408
+ }
2409
+ return $current_language;
2410
+ }
2411
+
2412
+ // Polylang
2413
+ if (function_exists('pll_current_language')) {
2414
+ return pll_current_language();
2415
+ }
2416
+
2417
+ // Trnslatepress and/or others
2418
+ $current_language = apply_filters('newsletter_current_language', '');
2419
+
2420
+ return $current_language;
2421
+ }
2422
+
2423
+ function get_default_language() {
2424
+ if (class_exists('SitePress')) {
2425
+ return $current_language = apply_filters('wpml_current_language', '');
2426
+ } else if (function_exists('pll_default_language')) {
2427
+ return pll_default_language();
2428
+ } else if (class_exists('TRP_Translate_Press')) {
2429
+ // TODO: Find the default language
2430
+ }
2431
+ return '';
2432
+ }
2433
+
2434
+ function is_all_languages() {
2435
+ return $this->get_current_language() == '';
2436
+ }
2437
+
2438
+ function is_default_language() {
2439
+ return $this->get_current_language() == $this->get_default_language();
2440
+ }
2441
+
2442
+ /**
2443
+ * Returns an array of languages with key the language code and value the language name.
2444
+ * An empty array is returned if no language is available.
2445
+ */
2446
+ function get_languages() {
2447
+ $language_options = array();
2448
+
2449
+ if (class_exists('SitePress')) {
2450
+ $languages = apply_filters('wpml_active_languages', null);
2451
+ foreach ($languages as $language) {
2452
+ $language_options[$language['language_code']] = $language['translated_name'];
2453
+ }
2454
+ return $language_options;
2455
+ } else if (function_exists('icl_get_languages')) {
2456
+ $languages = icl_get_languages();
2457
+ foreach ($languages as $code => $language) {
2458
+ $language_options[$code] = $language['native_name'];
2459
+ }
2460
+ return $language_options;
2461
+ }
2462
+
2463
+ return apply_filters('newsletter_languages', $language_options);
2464
+ }
2465
+
2466
+ function get_language_label($language) {
2467
+ $languages = $this->get_languages();
2468
+ if (isset($languages[$language])) {
2469
+ return $languages[$language];
2470
+ }
2471
+ return '';
2472
+ }
2473
+
2474
+ /**
2475
+ * Changes the current language usually before extracting the posts since WPML
2476
+ * does not support the language filter in the post query (or at least we didn't
2477
+ * find it).
2478
+ *
2479
+ * @param string $language
2480
+ */
2481
+ function switch_language($language) {
2482
+ if (class_exists('SitePress')) {
2483
+ if (empty($language)) {
2484
+ $language = 'all';
2485
+ }
2486
+ do_action('wpml_switch_language', $language);
2487
+ return;
2488
+ }
2489
+ }
2490
+
2491
+ function is_multilanguage() {
2492
+ return class_exists('SitePress') || function_exists('pll_default_language') || class_exists('TRP_Translate_Press');
2493
+ }
2494
+
2495
+ function get_posts($filters = array(), $language = '') {
2496
+ $current_language = $this->get_current_language();
2497
+
2498
+ // Language switch for WPML
2499
+ if ($language) {
2500
+ if (class_exists('SitePress')) {
2501
+ $this->switch_language($language);
2502
+ $filters['suppress_filters'] = false;
2503
+ }
2504
+ if (class_exists('Polylang')) {
2505
+ $filters['lang'] = $language;
2506
+ }
2507
+ }
2508
+ $posts = get_posts($filters);
2509
+ if ($language) {
2510
+ if (class_exists('SitePress')) {
2511
+ $this->switch_language($current_language);
2512
+ }
2513
+ }
2514
+ return $posts;
2515
+ }
2516
+
2517
+ function get_wp_query($filters, $langiage = '') {
2518
+ if ($language) {
2519
+ if (class_exists('SitePress')) {
2520
+ $this->switch_language($language);
2521
+ $filters['suppress_filters'] = false;
2522
+ }
2523
+ if (class_exists('Polylang')) {
2524
+ $filters['lang'] = $language;
2525
+ }
2526
+ }
2527
+
2528
+ $posts = new WP_Query($filters);
2529
+
2530
+ if ($language) {
2531
+ if (class_exists('SitePress')) {
2532
+ $this->switch_language($current_language);
2533
+ }
2534
+ }
2535
+
2536
+ return $posts;
2537
+ }
2538
+
2539
+ protected function generate_admin_notification_message($user) {
2540
+
2541
+ $message = file_get_contents(__DIR__ . '/notification.html');
2542
+
2543
+ $message = $this->replace($message, $user);
2544
+ $message = str_replace('{user_admin_url}', admin_url('admin.php?page=newsletter_users_edit&id=' . $user->id), $message);
2545
+
2546
+ return $message;
2547
+ }
2548
+
2549
+ protected function generate_admin_notification_subject($subject) {
2550
+ $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
2551
+
2552
+ return '[' . $blogname . '] ' . $subject;
2553
+ }
2554
+
2555
+ function dienow($message, $admin_message = null, $http_code = 200) {
2556
+ if ($admin_message && current_user_can('administrator')) {
2557
+ $message .= '<br><br><strong>Text below only visibile to administrators</strong><br>';
2558
+ $message .= $admin_message;
2559
+ }
2560
+ wp_die($message, $http_code);
2561
+ }
2562
+
2563
+ function dump($var) {
2564
+ if (NEWSLETTER_DEBUG) {
2565
+ var_dump($var);
2566
+ }
2567
+ }
2568
+
2569
+ function dump_die($var) {
2570
+ if (NEWSLETTER_DEBUG) {
2571
+ var_dump($var);
2572
+ die();
2573
+ }
2574
+ }
2575
+
2576
+ }
2577
+
2578
+ /**
2579
+ * Kept for compatibility.
2580
+ *
2581
+ * @param type $post_id
2582
+ * @param type $size
2583
+ * @param type $alternative
2584
+ * @return type
2585
+ */
2586
+ function nt_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2587
+ return NewsletterModule::get_post_image($post_id, $size, $alternative);
2588
+ }
2589
+
2590
+ function newsletter_get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
2591
+ echo NewsletterModule::get_post_image($post_id, $size, $alternative);
2592
+ }
2593
+
2594
+ /**
2595
+ * Accepts a post or a post ID.
2596
+ *
2597
+ * @param WP_Post $post
2598
+ */
2599
+ function newsletter_the_excerpt($post, $words = 30) {
2600
+ $post = get_post($post);
2601
+ $excerpt = $post->post_excerpt;
2602
+ if (empty($excerpt)) {
2603
+ $excerpt = $post->post_content;
2604
+ $excerpt = strip_shortcodes($excerpt);
2605
+ $excerpt = wp_strip_all_tags($excerpt, true);
2606
+ }
2607
+ echo '<p>' . wp_trim_words($excerpt, $words) . '</p>';
2608
+ }
plugin.php CHANGED
@@ -1,1418 +1,1418 @@
1
- <?php
2
-
3
- /*
4
- Plugin Name: Newsletter
5
- Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
- Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
- Version: 7.3.6
8
- Author: Stefano Lissa & The Newsletter Team
9
- Author URI: https://www.thenewsletterplugin.com
10
- Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
11
- Text Domain: newsletter
12
- License: GPLv2 or later
13
- Requires at least: 4.6
14
- Requires PHP: 5.6
15
-
16
- Copyright 2009-2022 The Newsletter Team (email: info@thenewsletterplugin.com, web: https://www.thenewsletterplugin.com)
17
-
18
- Newsletter is free software: you can redistribute it and/or modify
19
- it under the terms of the GNU General Public License as published by
20
- the Free Software Foundation, either version 2 of the License, or
21
- any later version.
22
-
23
- Newsletter is distributed in the hope that it will be useful,
24
- but WITHOUT ANY WARRANTY; without even the implied warranty of
25
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
- GNU General Public License for more details.
27
-
28
- You should have received a copy of the GNU General Public License
29
- along with Newsletter. If not, see https://www.gnu.org/licenses/gpl-2.0.html.
30
-
31
- */
32
-
33
- if (version_compare(phpversion(), '5.6', '<')) {
34
- add_action('admin_notices', function () {
35
- echo '<div class="notice notice-error"><p>PHP version 5.6 or greater is required for Newsletter. Ask your provider to upgrade. <a href="https://www.php.net/supported-versions.php" target="_blank">Read more on PHP versions</a></p></div>';
36
- });
37
- return;
38
- }
39
-
40
- define('NEWSLETTER_VERSION', '7.3.6');
41
-
42
- global $newsletter, $wpdb;
43
-
44
- // For acceptance tests, DO NOT CHANGE
45
- if (!defined('NEWSLETTER_DEBUG'))
46
- define('NEWSLETTER_DEBUG', false);
47
-
48
- if (!defined('NEWSLETTER_EXTENSION_UPDATE'))
49
- define('NEWSLETTER_EXTENSION_UPDATE', true);
50
-
51
- if (!defined('NEWSLETTER_EMAILS_TABLE'))
52
- define('NEWSLETTER_EMAILS_TABLE', $wpdb->prefix . 'newsletter_emails');
53
-
54
- if (!defined('NEWSLETTER_USERS_TABLE'))
55
- define('NEWSLETTER_USERS_TABLE', $wpdb->prefix . 'newsletter');
56
-
57
- if (!defined('NEWSLETTER_STATS_TABLE'))
58
- define('NEWSLETTER_STATS_TABLE', $wpdb->prefix . 'newsletter_stats');
59
-
60
- if (!defined('NEWSLETTER_SENT_TABLE'))
61
- define('NEWSLETTER_SENT_TABLE', $wpdb->prefix . 'newsletter_sent');
62
-
63
- define('NEWSLETTER_SLUG', 'newsletter');
64
-
65
- define('NEWSLETTER_DIR', __DIR__);
66
- define('NEWSLETTER_INCLUDES_DIR', __DIR__ . '/includes');
67
-
68
- // Deperacted since plugin can change the plugins_url()
69
- define('NEWSLETTER_URL', WP_PLUGIN_URL . '/newsletter');
70
-
71
- if (!defined('NEWSLETTER_LIST_MAX'))
72
- define('NEWSLETTER_LIST_MAX', 40);
73
-
74
- if (!defined('NEWSLETTER_PROFILE_MAX'))
75
- define('NEWSLETTER_PROFILE_MAX', 20);
76
-
77
- if (!defined('NEWSLETTER_FORMS_MAX'))
78
- define('NEWSLETTER_FORMS_MAX', 10);
79
-
80
- if (!defined('NEWSLETTER_HEADER'))
81
- define('NEWSLETTER_HEADER', true);
82
-
83
- require_once NEWSLETTER_INCLUDES_DIR . '/module.php';
84
- require_once NEWSLETTER_INCLUDES_DIR . '/TNP.php';
85
- require_once NEWSLETTER_INCLUDES_DIR . '/cron.php';
86
-
87
- class Newsletter extends NewsletterModule {
88
-
89
- // Limits to respect to avoid memory, time or provider limits
90
- var $time_start;
91
- var $time_limit = 0;
92
- var $max_emails = null;
93
-
94
- var $mailer = null;
95
-
96
- var $action = '';
97
-
98
- /** @var Newsletter */
99
- static $instance;
100
-
101
- const STATUS_NOT_CONFIRMED = 'S';
102
- const STATUS_CONFIRMED = 'C';
103
-
104
- /**
105
- * @return Newsletter
106
- */
107
- static function instance() {
108
- if (self::$instance == null) {
109
- self::$instance = new Newsletter();
110
- }
111
- return self::$instance;
112
- }
113
-
114
- function __construct() {
115
-
116
- // Grab it before a plugin decides to remove it.
117
- if (isset($_GET['na'])) {
118
- $this->action = $_GET['na'];
119
- }
120
- if (isset($_POST['na'])) {
121
- $this->action = $_POST['na'];
122
- }
123
-
124
- $this->time_start = time();
125
-
126
- parent::__construct('main', '1.6.7', null, ['info', 'smtp']);
127
-
128
- add_action('plugins_loaded', [$this, 'hook_plugins_loaded']);
129
- add_action('init', [$this, 'hook_init'], 1);
130
- add_action('wp_loaded', [$this, 'hook_wp_loaded'], 1);
131
-
132
- add_action('newsletter', [$this, 'hook_newsletter'], 1);
133
-
134
- register_activation_hook(__FILE__, [$this, 'hook_activate']);
135
- register_deactivation_hook(__FILE__, [$this, 'hook_deactivate']);
136
-
137
- add_action('admin_init', [$this, 'hook_admin_init']);
138
-
139
- if (is_admin()) {
140
- add_action('admin_head', [$this, 'hook_admin_head']);
141
-
142
- // Protection against strange schedule removal on some installations
143
- if (!wp_next_scheduled('newsletter') && (!defined('WP_INSTALLING') || !WP_INSTALLING)) {
144
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
145
- }
146
-
147
- add_action('admin_menu', [$this, 'add_extensions_menu'], 90);
148
-
149
- add_filter('display_post_states', [$this, 'add_notice_to_chosen_profile_page_hook'], 10, 2);
150
-
151
- if ($this->is_admin_page()) {
152
- // Remove the emoji replacer to save to database the original emoji characters (see even woocommerce for the same problem)
153
- remove_action('admin_print_scripts', 'print_emoji_detection_script');
154
- add_action('admin_enqueue_scripts', [$this, 'hook_admin_enqueue_scripts']);
155
- }
156
-
157
- }
158
- }
159
-
160
- function hook_plugins_loaded() {
161
- // Used to load dependant modules
162
- do_action('newsletter_loaded', NEWSLETTER_VERSION);
163
-
164
- if (function_exists('load_plugin_textdomain')) {
165
- load_plugin_textdomain('newsletter', false, plugin_basename(__DIR__) . '/languages');
166
- }
167
- }
168
-
169
- function hook_init() {
170
- global $wpdb;
171
-
172
- if (isset($this->options['debug']) && $this->options['debug'] == 1) {
173
- ini_set('log_errors', 1);
174
- ini_set('error_log', WP_CONTENT_DIR . '/logs/newsletter/php-' . date('Y-m') . '-' . get_option('newsletter_logger_secret') . '.txt');
175
- }
176
-
177
- add_shortcode('newsletter_replace', [$this, 'shortcode_newsletter_replace']);
178
-
179
- add_filter('site_transient_update_plugins', [$this, 'hook_site_transient_update_plugins']);
180
-
181
- if (is_admin()) {
182
- if (!class_exists('NewsletterExtensions')) {
183
-
184
- add_filter('plugin_row_meta', function ($plugin_meta, $plugin_file) {
185
-
186
- static $slugs = array();
187
- if (empty($slugs)) {
188
- $addons = $this->getTnpExtensions();
189
- if ($addons) {
190
- foreach ($addons as $addon) {
191
- $slugs[] = $addon->wp_slug;
192
- }
193
- }
194
- }
195
- if (array_search($plugin_file, $slugs) !== false) {
196
-
197
- $plugin_meta[] = '<a href="admin.php?page=newsletter_main_extensions" style="font-weight: bold">Newsletter Addons Manager required</a>';
198
- }
199
- return $plugin_meta;
200
- }, 10, 2);
201
- }
202
-
203
- add_action('in_admin_header', array($this, 'hook_in_admin_header'), 1000);
204
-
205
- if ($this->is_admin_page()) {
206
-
207
- $dismissed = get_option('newsletter_dismissed', []);
208
-
209
- if (isset($_GET['dismiss'])) {
210
- $dismissed[$_GET['dismiss']] = 1;
211
- update_option('newsletter_dismissed', $dismissed);
212
- wp_redirect($_SERVER['HTTP_REFERER']);
213
- exit();
214
- }
215
- }
216
- } else {
217
- add_action('wp_enqueue_scripts', [$this, 'hook_wp_enqueue_scripts']);
218
- }
219
-
220
- do_action('newsletter_init');
221
- }
222
-
223
- function hook_wp_loaded() {
224
- if (empty($this->action)) {
225
- return;
226
- }
227
-
228
- if ($this->action == 'test') {
229
- // This response is tested, do not change it!
230
- echo 'ok';
231
- die();
232
- }
233
-
234
- if ($this->action === 'nul') {
235
- $this->dienow('This link is not active on newsletter preview', 'You can send a test message to test subscriber to have the real working link.');
236
- }
237
-
238
- $user = $this->get_user_from_request();
239
- $email = $this->get_email_from_request();
240
- do_action('newsletter_action', $this->action, $user, $email);
241
- }
242
-
243
- function hook_activate() {
244
- // Ok, why? When the plugin is not active WordPress may remove the scheduled "newsletter" action because
245
- // the every-five-minutes schedule named "newsletter" is not present.
246
- // Since the activation does not forces an upgrade, that schedule must be reactivated here. It is activated on
247
- // the upgrade method as well for the user which upgrade the plugin without deactivte it (many).
248
- if (!wp_next_scheduled('newsletter')) {
249
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
250
- }
251
-
252
- $install_time = get_option('newsletter_install_time');
253
- if (!$install_time) {
254
- update_option('newsletter_install_time', time(), false);
255
- }
256
-
257
- touch(NEWSLETTER_LOG_DIR . '/index.html');
258
-
259
- Newsletter::instance()->upgrade();
260
- NewsletterUsers::instance()->upgrade();
261
- NewsletterEmails::instance()->upgrade();
262
- NewsletterSubscription::instance()->upgrade();
263
- NewsletterStatistics::instance()->upgrade();
264
- NewsletterProfile::instance()->upgrade();
265
- }
266
-
267
- function first_install() {
268
- parent::first_install();
269
- update_option('newsletter_show_welcome', '1', false);
270
- }
271
-
272
- function upgrade() {
273
- global $wpdb, $charset_collate;
274
-
275
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
276
-
277
- parent::upgrade();
278
-
279
- $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_emails` (
280
- `id` int(11) NOT NULL AUTO_INCREMENT,
281
- `language` varchar(10) NOT NULL DEFAULT '',
282
- `subject` varchar(255) NOT NULL DEFAULT '',
283
- `message` longtext,
284
- `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
285
- `status` enum('new','sending','sent','paused','error') NOT NULL DEFAULT 'new',
286
- `total` int(11) NOT NULL DEFAULT '0',
287
- `last_id` int(11) NOT NULL DEFAULT '0',
288
- `sent` int(11) NOT NULL DEFAULT '0',
289
- `track` int(11) NOT NULL DEFAULT '1',
290
- `list` int(11) NOT NULL DEFAULT '0',
291
- `type` varchar(50) NOT NULL DEFAULT '',
292
- `query` longtext,
293
- `editor` tinyint(4) NOT NULL DEFAULT '0',
294
- `sex` varchar(20) NOT NULL DEFAULT '',
295
- `theme` varchar(50) NOT NULL DEFAULT '',
296
- `message_text` longtext,
297
- `preferences` longtext,
298
- `send_on` int(11) NOT NULL DEFAULT '0',
299
- `token` varchar(10) NOT NULL DEFAULT '',
300
- `options` longtext,
301
- `private` tinyint(1) NOT NULL DEFAULT '0',
302
- `click_count` int(10) unsigned NOT NULL DEFAULT '0',
303
- `version` varchar(10) NOT NULL DEFAULT '',
304
- `open_count` int(10) unsigned NOT NULL DEFAULT '0',
305
- `unsub_count` int(10) unsigned NOT NULL DEFAULT '0',
306
- `error_count` int(10) unsigned NOT NULL DEFAULT '0',
307
- `stats_time` int(10) unsigned NOT NULL DEFAULT '0',
308
- `updated` int(10) unsigned NOT NULL DEFAULT '0',
309
- PRIMARY KEY (`id`)
310
- ) $charset_collate;";
311
-
312
- dbDelta($sql);
313
-
314
- // WP does not manage composite primary key when it tries to upgrade a table...
315
- $suppress_errors = $wpdb->suppress_errors(true);
316
-
317
- dbDelta("CREATE TABLE " . $wpdb->prefix . "newsletter_sent (
318
- email_id int(10) unsigned NOT NULL DEFAULT '0',
319
- user_id int(10) unsigned NOT NULL DEFAULT '0',
320
- status tinyint(1) unsigned NOT NULL DEFAULT '0',
321
- open tinyint(1) unsigned NOT NULL DEFAULT '0',
322
- time int(10) unsigned NOT NULL DEFAULT '0',
323
- error varchar(255) NOT NULL DEFAULT '',
324
- ip varchar(100) NOT NULL DEFAULT '',
325
- PRIMARY KEY (email_id,user_id),
326
- KEY user_id (user_id),
327
- KEY email_id (email_id)
328
- ) $charset_collate;");
329
- $wpdb->suppress_errors($suppress_errors);
330
-
331
- // Some setting check to avoid the common support request for mis-configurations
332
- $options = $this->get_options();
333
-
334
- if (empty($options['scheduler_max']) || !is_numeric($options['scheduler_max'])) {
335
- $options['scheduler_max'] = 100;
336
- $this->save_options($options);
337
- }
338
-
339
- wp_clear_scheduled_hook('newsletter');
340
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
341
-
342
- if (!empty($this->options['editor'])) {
343
- if (empty($this->options['roles'])) {
344
- $this->options['roles'] = array('editor');
345
- unset($this->options['editor']);
346
- }
347
- $this->save_options($this->options);
348
- }
349
-
350
- // Clear the addons and license caches
351
- delete_transient('newsletter_license_data');
352
- $this->clear_extensions_cache();
353
-
354
- touch(NEWSLETTER_LOG_DIR . '/index.html');
355
-
356
- return true;
357
- }
358
-
359
- function is_allowed() {
360
- if (current_user_can('administrator')) {
361
- return true;
362
- }
363
- if (!empty($this->options['roles'])) {
364
- foreach ($this->options['roles'] as $role) {
365
- if (current_user_can($role)) {
366
- return true;
367
- }
368
- }
369
- }
370
- return false;
371
- }
372
-
373
- function admin_menu() {
374
- if (!$this->is_allowed()) {
375
- return;
376
- }
377
-
378
- add_menu_page('Newsletter', 'Newsletter', 'exist', 'newsletter_main_index', '', plugins_url('newsletter') . '/admin/images/menu-icon.png', '30.333');
379
-
380
- $this->add_menu_page('index', __('Dashboard', 'newsletter'));
381
- $this->add_admin_page('info', __('Company info', 'newsletter'));
382
-
383
- if (current_user_can('administrator')) {
384
- $this->add_menu_page('welcome', __('Welcome', 'newsletter'));
385
- $this->add_menu_page('main', __('Settings', 'newsletter'));
386
-
387
- // Pages not on menu
388
- $this->add_admin_page('smtp', 'SMTP');
389
- $this->add_admin_page('diagnostic', __('Diagnostic', 'newsletter'));
390
- $this->add_admin_page('test', __('Test', 'newsletter'));
391
- }
392
- }
393
-
394
- function add_extensions_menu() {
395
- if (!class_exists('NewsletterExtensions')) {
396
- $this->add_menu_page('extensions', '<span style="color:#27AE60; font-weight: bold;">' . __('Addons', 'newsletter') . '</span>');
397
- }
398
- }
399
-
400
- function hook_in_admin_header() {
401
- if (!$this->is_admin_page()) {
402
- add_action('admin_notices', array($this, 'hook_admin_notices'));
403
- return;
404
- }
405
- remove_all_actions('admin_notices');
406
- remove_all_actions('all_admin_notices');
407
- add_action('admin_notices', array($this, 'hook_admin_notices'));
408
- }
409
-
410
- function hook_admin_notices() {
411
- // Check of Newsletter dedicated page
412
- if (!empty($this->options['page'])) {
413
- if (get_post_status($this->options['page']) !== 'publish') {
414
- echo '<div class="notice notice-error"><p>The Newsletter dedicated page is not published. <a href="', site_url('/wp-admin/post.php') . '?post=', $this->options['page'], '&action=edit"><strong>Edit the page</strong></a> or <a href="admin.php?page=newsletter_main_main"><strong>review the main settings</strong></a>.</p></div>';
415
- }
416
- }
417
-
418
- if (isset($this->options['debug']) && $this->options['debug'] == 1) {
419
- echo '<div class="notice notice-warning"><p>The Newsletter plugin is in <strong>debug mode</strong>. When done change it on Newsletter <a href="admin.php?page=newsletter_main_main"><strong>main settings</strong></a>. Do not keep the debug mode active on production sites.</p></div>';
420
- }
421
- }
422
-
423
- function hook_wp_enqueue_scripts() {
424
- if (empty($this->options['css_disabled']) && apply_filters('newsletter_enqueue_style', true)) {
425
- wp_enqueue_style('newsletter', plugins_url('newsletter') . '/style.css', [], NEWSLETTER_VERSION);
426
- if (!empty($this->options['css'])) {
427
- wp_add_inline_style('newsletter', $this->options['css']);
428
- }
429
- } else {
430
- if (!empty($this->options['css'])) {
431
- add_action('wp_head', function () {
432
- echo '<style>', $this->options['css'], '</style>';
433
- });
434
- }
435
- }
436
- }
437
-
438
- function hook_admin_enqueue_scripts() {
439
-
440
- $newsletter_url = plugins_url('newsletter');
441
- wp_enqueue_script('jquery-ui-tabs');
442
- wp_enqueue_script('jquery-ui-tooltip');
443
- wp_enqueue_script('jquery-ui-draggable');
444
- wp_enqueue_media();
445
-
446
- wp_enqueue_script('tnp-admin', $newsletter_url . '/admin/admin.js', ['jquery'], NEWSLETTER_VERSION);
447
-
448
- wp_enqueue_style('tnp-select2', $newsletter_url . '/vendor/select2/css/select2.min.css', [], NEWSLETTER_VERSION);
449
- wp_enqueue_script('tnp-select2', $newsletter_url . '/vendor/select2/js/select2.min.js', ['jquery'], NEWSLETTER_VERSION);
450
-
451
- wp_enqueue_style('tnp-modal', $newsletter_url . '/admin/modal.css', [], NEWSLETTER_VERSION);
452
- wp_enqueue_script('tnp-modal', $newsletter_url . '/admin/modal.js', ['jquery'], NEWSLETTER_VERSION, true);
453
-
454
- wp_enqueue_style('tnp-toast', $newsletter_url . '/admin/toast.css', [], NEWSLETTER_VERSION);
455
- wp_enqueue_script('tnp-toast', $newsletter_url . '/admin/toast.js', ['jquery'], NEWSLETTER_VERSION);
456
-
457
- wp_enqueue_style('tnp-admin-font', 'https://use.typekit.net/jlj2wjy.css');
458
- wp_enqueue_style('tnp-admin-fontawesome', $newsletter_url . '/vendor/fa/css/all.min.css', [], NEWSLETTER_VERSION);
459
- wp_enqueue_style('tnp-admin-jquery-ui', $newsletter_url . '/vendor/jquery-ui/jquery-ui.min.css', [], NEWSLETTER_VERSION);
460
- wp_enqueue_style('tnp-admin', $newsletter_url . '/admin/admin.css', [], NEWSLETTER_VERSION);
461
- wp_enqueue_style('tnp-admin-dropdown', $newsletter_url . '/admin/dropdown.css', [], NEWSLETTER_VERSION);
462
- wp_enqueue_style('tnp-admin-tabs', $newsletter_url . '/admin/tabs.css', [], NEWSLETTER_VERSION);
463
- wp_enqueue_style('tnp-admin-controls', $newsletter_url . '/admin/controls.css', [], NEWSLETTER_VERSION);
464
- wp_enqueue_style('tnp-admin-fields', $newsletter_url . '/admin/fields.css', [], NEWSLETTER_VERSION);
465
- wp_enqueue_style('tnp-admin-widgets', $newsletter_url . '/admin/widgets.css', [], NEWSLETTER_VERSION);
466
- wp_enqueue_style('tnp-admin-extensions', $newsletter_url . '/admin/extensions.css', [], NEWSLETTER_VERSION);
467
-
468
- $translations_array = array(
469
- 'save_to_update_counter' => __('Save the newsletter to update the counter!', 'newsletter')
470
- );
471
- wp_localize_script('tnp-admin', 'tnp_translations', $translations_array);
472
-
473
- wp_enqueue_script('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jquery.vmap.min.js', ['jquery'], NEWSLETTER_VERSION);
474
- wp_enqueue_script('tnp-jquery-vmap-world', $newsletter_url . '/vendor/jqvmap/jquery.vmap.world.js', ['tnp-jquery-vmap'], NEWSLETTER_VERSION);
475
- wp_enqueue_style('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jqvmap.min.css', [], NEWSLETTER_VERSION);
476
-
477
- wp_register_script('tnp-chart', $newsletter_url . '/vendor/chartjs/Chart.min.js', ['jquery'], NEWSLETTER_VERSION);
478
-
479
- wp_enqueue_script('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.js', ['jquery']);
480
- wp_enqueue_style('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.css', [], NEWSLETTER_VERSION);
481
- }
482
-
483
- function shortcode_newsletter_replace($attrs, $content) {
484
- $content = do_shortcode($content);
485
- $content = $this->replace($content, $this->get_user_from_request(), $this->get_email_from_request());
486
- return $content;
487
- }
488
-
489
- function is_admin_page() {
490
- if (!isset($_GET['page'])) {
491
- return false;
492
- }
493
- $page = $_GET['page'];
494
- return strpos($page, 'newsletter_') === 0;
495
- }
496
-
497
- function hook_admin_init() {
498
- // Verificare il contesto
499
- if (isset($_GET['page']) && $_GET['page'] === 'newsletter_main_welcome')
500
- return;
501
- if (get_option('newsletter_show_welcome')) {
502
- delete_option('newsletter_show_welcome');
503
- wp_redirect(admin_url('admin.php?page=newsletter_main_welcome'));
504
- }
505
- }
506
-
507
- function hook_admin_head() {
508
- // Small global rule for sidebar menu entries
509
- echo '<style>';
510
- echo '.tnp-side-menu { color: #E67E22!important; }';
511
- echo '</style>';
512
- }
513
-
514
- function relink($text, $email_id, $user_id, $email_token = '') {
515
- return NewsletterStatistics::instance()->relink($text, $email_id, $user_id, $email_token);
516
- }
517
-
518
- /**
519
- * Runs every 5 minutes and look for emails that need to be processed.
520
- */
521
- function hook_newsletter() {
522
-
523
- $this->logger->debug(__METHOD__ . '> Start');
524
-
525
- if (!$this->check_transient('engine', NEWSLETTER_CRON_INTERVAL)) {
526
- $this->logger->debug(__METHOD__ . '> Engine already active, exit');
527
- return;
528
- }
529
-
530
- $emails = $this->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " where status='sending' and send_on<" . time() . " order by id asc");
531
- $this->logger->debug(__METHOD__ . '> Emails found in sending status: ' . count($emails));
532
-
533
- foreach ($emails as $email) {
534
- $this->logger->debug(__METHOD__ . '> Start newsletter ' . $email->id);
535
- $email->options = maybe_unserialize($email->options);
536
- $r = $this->send($email);
537
- $this->logger->debug(__METHOD__ . '> End newsletter ' . $email->id);
538
- if (!$r) {
539
- $this->logger->debug(__METHOD__ . '> Engine returned false, there is no more capacity');
540
- break;
541
- }
542
- }
543
- // Remove the semaphore so the delivery engine can be activated again
544
- $this->delete_transient('engine');
545
-
546
- $this->logger->debug(__METHOD__ . '> End');
547
- }
548
-
549
- function get_send_speed($email = null) {
550
- $this->logger->debug(__METHOD__ . '> Computing delivery speed');
551
- $mailer = $this->get_mailer();
552
- $speed = (int) $mailer->get_speed();
553
- if (!$speed) {
554
- $this->logger->debug(__METHOD__ . '> Speed not set by mailer, use the default');
555
- $speed = (int) $this->options['scheduler_max'];
556
- } else {
557
- $this->logger->debug(__METHOD__ . '> Speed set by mailer');
558
- }
559
-
560
- //$speed = (int) apply_filters('newsletter_send_speed', $speed, $email);
561
- // Fail safe setting
562
- $runs_per_hour = $this->get_runs_per_hour();
563
- if (!$speed || $speed < $runs_per_hour) {
564
- return $runs_per_hour;
565
- }
566
-
567
- $this->logger->debug(__METHOD__ . '> Speed: ' . $speed);
568
- return $speed;
569
- }
570
-
571
- function get_send_delay() {
572
- return 0;
573
- }
574
-
575
- function skip_this_run($email = null) {
576
- return (boolean) apply_filters('newsletter_send_skip', false, $email);
577
- }
578
-
579
- function get_runs_per_hour() {
580
- return (int) (3600 / NEWSLETTER_CRON_INTERVAL);
581
- }
582
-
583
- function get_emails_per_run() {
584
- $speed = $this->get_send_speed();
585
- $max = (int)($speed / $this->get_runs_per_hour());
586
-
587
- return $max;
588
- }
589
-
590
- function get_max_emails($email) {
591
- // Obsolete, here from Speed Control Addon
592
- $max = (int) apply_filters('newsletter_send_max_emails', $this->max_emails, $email);
593
-
594
- return min($max, $this->max_emails);
595
- }
596
-
597
- function fix_email($email) {
598
- if (empty($email->query)) {
599
- $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
600
- }
601
- if (empty($email->id)) {
602
- $email->id = 0;
603
- }
604
- }
605
-
606
- function send_setup() {
607
- $this->logger->debug(__METHOD__ . '> Setup delivery engine');
608
- if (is_null($this->max_emails)) {
609
- $this->max_emails = $this->get_emails_per_run();
610
- $this->logger->debug(__METHOD__ . '> Max emails: ' . $this->max_emails);
611
- ignore_user_abort(true);
612
-
613
- @set_time_limit(NEWSLETTER_CRON_INTERVAL + 30);
614
-
615
- $max_time = (int) (@ini_get('max_execution_time') * 0.95);
616
- if ($max_time == 0 || $max_time > NEWSLETTER_CRON_INTERVAL) {
617
- $max_time = (int) (NEWSLETTER_CRON_INTERVAL * 0.95);
618
- }
619
-
620
- $this->time_limit = $this->time_start + $max_time;
621
-
622
- $this->logger->debug(__METHOD__ . '> Max time set to ' . $max_time);
623
- } else {
624
- $this->logger->debug(__METHOD__ . '> Already setup');
625
- }
626
- }
627
-
628
- function time_exceeded() {
629
- if ($this->time_limit && time() > $this->time_limit) {
630
- $this->logger->info(__METHOD__ . '> Max execution time limit reached');
631
- return true;
632
- }
633
- }
634
-
635
- /**
636
- * Sends an email to targeted users or to given users. If a list of users is given (usually a list of test users)
637
- * the query inside the email to retrieve users is not used.
638
- *
639
- * @global wpdb $wpdb
640
- * @global type $newsletter_feed
641
- * @param TNP_Email $email
642
- * @param array $users
643
- * @return boolean|WP_Error True if the process completed, false if limits was reached. On false the caller should no continue to call it with other emails.
644
- */
645
- function send($email, $users = null, $test = false) {
646
- global $wpdb;
647
-
648
- if (is_array($email)) {
649
- $email = (object) $email;
650
- }
651
-
652
- $this->logger->info(__METHOD__ . '> Send email ' . $email->id);
653
-
654
- $this->send_setup();
655
-
656
- if ($this->max_emails <= 0) {
657
- $this->logger->info(__METHOD__ . '> No more capacity');
658
- return false;
659
- }
660
-
661
- $this->fix_email($email);
662
-
663
- // This stops the update of last_id and sent fields since
664
- // it's not a scheduled delivery but a test or something else (like an autoresponder)
665
- $supplied_users = $users != null;
666
-
667
- if (!$supplied_users) {
668
-
669
- if ($this->skip_this_run($email)) {
670
- return true;
671
- }
672
-
673
- // Speed change for specific email by Speed Control Addon
674
- $max_emails = $this->get_max_emails($email);
675
- if ($max_emails <= 0) {
676
- return true;
677
- }
678
-
679
- $query = $email->query;
680
- $query .= " and id>" . $email->last_id . " order by id limit " . $max_emails;
681
-
682
- $this->logger->debug(__METHOD__ . '> Query: ' . $query);
683
-
684
- //Retrieve subscribers
685
- $users = $this->get_results($query);
686
-
687
- $this->logger->debug(__METHOD__ . '> Loaded subscribers: ' . count($users));
688
-
689
- // If there was a database error, return error
690
- if ($users === false) {
691
- return new WP_Error('1', 'Unable to query subscribers, check the logs');
692
- }
693
-
694
- if (empty($users)) {
695
- $this->logger->info(__METHOD__ . '> No more users, set as sent');
696
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set status='sent', total=sent where id=" . $email->id . " limit 1");
697
- do_action('newsletter_ended_sending_newsletter', $email);
698
- return true;
699
- }
700
- } else {
701
- $this->logger->info(__METHOD__ . '> Subscribers supplied');
702
- }
703
-
704
- $start_time = microtime(true);
705
- $count = 0;
706
- $result = true;
707
-
708
- $mailer = $this->get_mailer();
709
-
710
- $batch_size = $mailer->get_batch_size();
711
-
712
- $this->logger->debug(__METHOD__ . '> Batch size: ' . $batch_size);
713
-
714
- // For batch size == 1 (normal condition) we optimize
715
- if ($batch_size == 1) {
716
-
717
- foreach ($users as $user) {
718
-
719
- $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
720
- $user = apply_filters('newsletter_send_user', $user);
721
- $message = $this->build_message($email, $user);
722
-
723
- // Save even test emails since people wants to see some stats even for test emails. Stats are reset upon the real "send" of a newsletter
724
- $this->save_sent_message($message);
725
-
726
- //Se non è un test incremento il contatore delle email spedite. Perchè incremento prima di spedire??
727
- if (!$test) {
728
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
729
- }
730
-
731
- $r = $mailer->send($message);
732
-
733
- if (!empty($message->error)) {
734
- $this->logger->error($message);
735
- $this->save_sent_message($message);
736
- }
737
-
738
- if (is_wp_error($r)) {
739
- $this->logger->error($r);
740
-
741
- // For fatal error, the newsletter status i changed to error (and the delivery stopped)
742
- if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
743
- $this->set_error_state_of_email($email, $r->get_error_message());
744
- return $r;
745
- }
746
- }
747
-
748
- if (!$supplied_users && !$test && $this->time_exceeded()) {
749
- $result = false;
750
- break;
751
- }
752
- }
753
-
754
- $this->max_emails--;
755
- $count++;
756
- } else {
757
-
758
- $chunks = array_chunk($users, $batch_size);
759
-
760
- foreach ($chunks as $chunk) {
761
-
762
- $messages = [];
763
-
764
- // Peeparing a batch of messages
765
- foreach ($chunk as $user) {
766
- $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
767
- $user = apply_filters('newsletter_send_user', $user);
768
- $message = $this->build_message($email, $user);
769
- $this->save_sent_message($message);
770
- $messages[] = $message;
771
-
772
- if (!$test) {
773
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
774
- }
775
- $this->max_emails--;
776
- $count++;
777
- }
778
-
779
- $r = $mailer->send_batch($messages);
780
-
781
- // Updating the status of the sent messages
782
- foreach ($messages as $message) {
783
- if (!empty($message->error)) {
784
- $this->save_sent_message($message);
785
- }
786
- }
787
-
788
- // The batch went in error
789
- if (is_wp_error($r)) {
790
- $this->logger->error($r);
791
-
792
- if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
793
- $this->set_error_state_of_email($email, $r->get_error_message());
794
- return $r;
795
- }
796
- }
797
-
798
- if (!$supplied_users && !$test && $this->time_exceeded()) {
799
- $result = false;
800
- break;
801
- }
802
- }
803
- }
804
-
805
- $end_time = microtime(true);
806
-
807
- // Stats only for newsletter with enough emails in a batch (we exclude the Autoresponder since it send one email per call)
808
- if (!$test && !$supplied_users && $count > 5) {
809
- $this->update_send_stats($start_time, $end_time, $count, $result);
810
- }
811
-
812
- // Cached general statistics are reset
813
- if (!$test) {
814
- NewsletterStatistics::instance()->reset_stats_time($email->id);
815
- }
816
-
817
- $this->logger->info(__METHOD__ . '> End run for email ' . $email->id);
818
-
819
- return $result;
820
- }
821
-
822
- function update_send_stats($start_time, $end_time, $count, $result) {
823
- $send_calls = get_option('newsletter_diagnostic_send_calls', []);
824
- $send_calls[] = [$start_time, $end_time, $count, $result];
825
-
826
- if (count($send_calls) > 100) {
827
- array_shift($send_calls);
828
- }
829
-
830
- update_option('newsletter_diagnostic_send_calls', $send_calls, false);
831
- }
832
-
833
- /**
834
- * @param TNP_Email $email
835
- */
836
- private function set_error_state_of_email($email, $message = '') {
837
- // Handle only message type at the moment
838
- if ($email->type !== 'message') {
839
- return;
840
- }
841
-
842
- do_action('newsletter_error_on_sending', $email, $message);
843
-
844
- $edited_email = new TNP_Email();
845
- $edited_email->id = $email->id;
846
- $edited_email->status = TNP_Email::STATUS_ERROR;
847
- $edited_email->options = $email->options;
848
- $edited_email->options['error_message'] = $message;
849
-
850
- $this->save_email($edited_email);
851
- }
852
-
853
- /**
854
- *
855
- * @param TNP_Email $email
856
- * @param TNP_User $user
857
- * @return \TNP_Mailer_Message
858
- */
859
- function build_message($email, $user) {
860
-
861
- $message = new TNP_Mailer_Message();
862
-
863
- $message->to = $user->email;
864
-
865
- $message->headers = [];
866
- $message->headers['Precedence'] = 'bulk';
867
- $message->headers['X-Newsletter-Email-Id'] = $email->id;
868
- $message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
869
- $message->headers = apply_filters('newsletter_message_headers', $message->headers, $email, $user);
870
-
871
- $message->body = preg_replace('/data-json=".*?"/is', '', $email->message);
872
- $message->body = preg_replace('/ +/s', ' ', $message->body);
873
- $message->body = $this->replace($message->body, $user, $email);
874
- if ($this->options['do_shortcodes']) {
875
- $message->body = do_shortcode($message->body);
876
- }
877
- $message->body = apply_filters('newsletter_message_html', $message->body, $email, $user);
878
-
879
- $message->body_text = $this->replace($email->message_text, $user, $email);
880
- $message->body_text = apply_filters('newsletter_message_text', $message->body_text, $email, $user);
881
-
882
- if ($email->track == 1) {
883
- $message->body = $this->relink($message->body, $email->id, $user->id, $email->token);
884
- }
885
-
886
- $message->subject = $this->replace($email->subject, $user);
887
- $message->subject = apply_filters('newsletter_message_subject', $message->subject, $email, $user);
888
-
889
- if (!empty($email->options['sender_email'])) {
890
- $message->from = $email->options['sender_email'];
891
- } else {
892
- $message->from = $this->options['sender_email'];
893
- }
894
-
895
- if (!empty($email->options['sender_name'])) {
896
- $message->from_name = $email->options['sender_name'];
897
- } else {
898
- $message->from_name = $this->options['sender_name'];
899
- }
900
-
901
- $message->email_id = $email->id;
902
- $message->user_id = $user->id;
903
-
904
- return apply_filters('newsletter_message', $message, $email, $user);
905
- }
906
-
907
- /**
908
- *
909
- * @param TNP_Mailer_Message $message
910
- * @param int $status
911
- * @param string $error
912
- */
913
- function save_sent_message($message) {
914
- global $wpdb;
915
-
916
- if (!$message->user_id || !$message->email_id) {
917
- return;
918
- }
919
- $status = empty($message->error) ? 0 : 1;
920
-
921
- $error = mb_substr($message->error, 0, 250);
922
-
923
- $this->query($wpdb->prepare("insert into " . $wpdb->prefix . 'newsletter_sent (user_id, email_id, time, status, error) values (%d, %d, %d, %d, %s) on duplicate key update time=%d, status=%d, error=%s', $message->user_id, $message->email_id, time(), $status, $error, time(), $status, $error));
924
- }
925
-
926
- /**
927
- * @deprecated since version 7.3.0
928
- */
929
- function limits_exceeded() {
930
- return false;
931
- }
932
-
933
- /**
934
- * @deprecated since version 6.0.0
935
- */
936
- function register_mail_method($callable) {
937
- }
938
-
939
- function register_mailer($mailer) {
940
- if ($mailer instanceof NewsletterMailer) {
941
- $this->mailer = $mailer;
942
- }
943
- }
944
-
945
- /**
946
- * Returns the current registered mailer which must be used to send emails.
947
- *
948
- * @return NewsletterMailer
949
- */
950
- function get_mailer() {
951
- if ($this->mailer) {
952
- return $this->mailer;
953
- }
954
-
955
- do_action('newsletter_register_mailer');
956
-
957
- if (!$this->mailer) {
958
- // Compatibility
959
- $smtp = $this->get_options('smtp');
960
- if (!empty($smtp['enabled'])) {
961
- $this->mailer = new NewsletterDefaultSMTPMailer($smtp);
962
- } else {
963
- $this->mailer = new NewsletterDefaultMailer();
964
- }
965
- }
966
- return $this->mailer;
967
- }
968
-
969
- /**
970
- *
971
- * @param TNP_Mailer_Message $message
972
- * @return type
973
- */
974
- function deliver($message) {
975
- $mailer = $this->get_mailer();
976
- if (empty($message->from))
977
- $message->from = $this->options['sender_email'];
978
- if (empty($message->from_name))
979
- $mailer->from_name = $this->options['sender_name'];
980
- return $mailer->send($message);
981
- }
982
-
983
- /**
984
- *
985
- * @param type $to
986
- * @param type $subject
987
- * @param string|array $message If string is considered HTML, is array should contains the keys "html" and "text"
988
- * @param type $headers
989
- * @param type $enqueue
990
- * @param type $from
991
- * @return boolean
992
- */
993
- function mail($to, $subject, $message, $headers = array(), $enqueue = false, $from = false) {
994
-
995
- if (empty($subject)) {
996
- $this->logger->error('mail> Subject empty, skipped');
997
- return true;
998
- }
999
-
1000
- $mailer_message = new TNP_Mailer_Message();
1001
- $mailer_message->to = $to;
1002
- $mailer_message->subject = $subject;
1003
- $mailer_message->from = $this->options['sender_email'];
1004
- $mailer_message->from_name = $this->options['sender_name'];
1005
-
1006
- if (!empty($headers)) {
1007
- $mailer_message->headers = $headers;
1008
- }
1009
- $mailer_message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
1010
-
1011
- // Message carrige returns and line feeds clean up
1012
- if (!is_array($message)) {
1013
- $mailer_message->body = $this->clean_eol($message);
1014
- } else {
1015
- if (!empty($message['text'])) {
1016
- $mailer_message->body_text = $this->clean_eol($message['text']);
1017
- }
1018
-
1019
- if (!empty($message['html'])) {
1020
- $mailer_message->body = $this->clean_eol($message['html']);
1021
- }
1022
- }
1023
-
1024
- $this->logger->debug($mailer_message);
1025
-
1026
- $mailer = $this->get_mailer();
1027
-
1028
- $r = $mailer->send($mailer_message);
1029
-
1030
- return !is_wp_error($r);
1031
- }
1032
-
1033
- function hook_deactivate() {
1034
- wp_clear_scheduled_hook('newsletter');
1035
- }
1036
-
1037
- function find_file($file1, $file2) {
1038
- if (is_file($file1))
1039
- return $file1;
1040
- return $file2;
1041
- }
1042
-
1043
- function hook_site_transient_update_plugins($value) {
1044
- static $extra_response = array();
1045
-
1046
- //$this->logger->debug('Update plugins transient called');
1047
-
1048
- if (!$value || !is_object($value)) {
1049
- //$this->logger->info('Empty object');
1050
- return $value;
1051
- }
1052
-
1053
- if (!isset($value->response) || !is_array($value->response)) {
1054
- $value->response = array();
1055
- }
1056
-
1057
- // Already computed? Use it! (this filter is called many times in a single request)
1058
- if ($extra_response) {
1059
- //$this->logger->debug('Already updated');
1060
- $value->response = array_merge($value->response, $extra_response);
1061
- return $value;
1062
- }
1063
-
1064
- $extensions = $this->getTnpExtensions();
1065
-
1066
- // Ops...
1067
- if (!$extensions) {
1068
- return $value;
1069
- }
1070
-
1071
- foreach ($extensions as $extension) {
1072
- unset($value->response[$extension->wp_slug]);
1073
- unset($value->no_update[$extension->wp_slug]);
1074
- }
1075
-
1076
- // Someone doesn't want our addons updated, let respect it (this constant should be defined in wp-config.php)
1077
- if (!NEWSLETTER_EXTENSION_UPDATE) {
1078
- //$this->logger->info('Updates disabled');
1079
- return $value;
1080
- }
1081
-
1082
- include_once(ABSPATH . 'wp-admin/includes/plugin.php');
1083
-
1084
- // Ok, that is really bad (should we remove it? is there a minimum WP version?)
1085
- if (!function_exists('get_plugin_data')) {
1086
- //$this->logger->error('No get_plugin_data function available!');
1087
- return $value;
1088
- }
1089
-
1090
- $license_key = $this->get_license_key();
1091
-
1092
- // Here we prepare the update information BUT do not add the link to the package which is privided
1093
- // by our Addons Manager (due to WP policies)
1094
- foreach ($extensions as $extension) {
1095
-
1096
- // Patch for names convention
1097
- $extension->plugin = $extension->wp_slug;
1098
-
1099
- //$this->logger->debug('Processing ' . $extension->plugin);
1100
- //$this->logger->debug($extension);
1101
-
1102
- $plugin_data = false;
1103
- if (file_exists(WP_PLUGIN_DIR . '/' . $extension->plugin)) {
1104
- $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1105
- } else if (file_exists(WPMU_PLUGIN_DIR . '/' . $extension->plugin)) {
1106
- $plugin_data = get_plugin_data(WPMU_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1107
- }
1108
-
1109
- if (!$plugin_data) {
1110
- //$this->logger->debug('Seems not installed');
1111
- continue;
1112
- }
1113
-
1114
- $plugin = new stdClass();
1115
- $plugin->id = $extension->id;
1116
- $plugin->slug = $extension->slug;
1117
- $plugin->plugin = $extension->plugin;
1118
- $plugin->new_version = $extension->version;
1119
- $plugin->url = $extension->url;
1120
- if (class_exists('NewsletterExtensions')) {
1121
- // NO filters here!
1122
- $plugin->package = NewsletterExtensions::$instance->get_package($extension->id, $license_key);
1123
- } else {
1124
- $plugin->package = '';
1125
- }
1126
- // [banners] => Array
1127
- // (
1128
- // [2x] => https://ps.w.org/wp-rss-aggregator/assets/banner-1544x500.png?rev=2040548
1129
- // [1x] => https://ps.w.org/wp-rss-aggregator/assets/banner-772x250.png?rev=2040548
1130
- // )
1131
- // [icons] => Array
1132
- // (
1133
- // [2x] => https://ps.w.org/advanced-custom-fields/assets/icon-256x256.png?rev=1082746
1134
- // [1x] => https://ps.w.org/advanced-custom-fields/assets/icon-128x128.png?rev=1082746
1135
- // )
1136
- if (version_compare($extension->version, $plugin_data['Version']) > 0) {
1137
- //$this->logger->debug('There is a new version');
1138
- $extra_response[$extension->plugin] = $plugin;
1139
- } else {
1140
- // Maybe useless...
1141
- //$this->logger->debug('There is NOT a new version');
1142
- $value->no_update[$extension->plugin] = $plugin;
1143
- }
1144
- //$this->logger->debug('Added');
1145
- }
1146
-
1147
- $value->response = array_merge($value->response, $extra_response);
1148
-
1149
- return $value;
1150
- }
1151
-
1152
- /**
1153
- * @deprecated since version 6.1.9
1154
- */
1155
- function get_extension_version($extension_id) {
1156
- return null;
1157
- }
1158
-
1159
- /**
1160
- * @deprecated since version 6.1.9
1161
- */
1162
- function set_extension_update_data($value, $extension) {
1163
- return $value;
1164
- }
1165
-
1166
- /**
1167
- * Retrieve the extensions form the tnp site
1168
- * @return array
1169
- */
1170
- function getTnpExtensions() {
1171
-
1172
- $extensions_json = get_transient('tnp_extensions_json');
1173
-
1174
- if (empty($extensions_json)) {
1175
- $url = "http://www.thenewsletterplugin.com/wp-content/extensions.json?ver=" . NEWSLETTER_VERSION;
1176
- $extensions_response = wp_remote_get($url);
1177
-
1178
- if (is_wp_error($extensions_response)) {
1179
- // Cache anyway for blogs which cannot connect outside
1180
- $extensions_json = '[]';
1181
- set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1182
- $this->logger->error($extensions_response);
1183
- } else {
1184
-
1185
- $extensions_json = wp_remote_retrieve_body($extensions_response);
1186
-
1187
- // Not clear cases
1188
- if (empty($extensions_json) || !json_decode($extensions_json)) {
1189
- $this->logger->error('Invalid json from thenewsletterplugin.com: retrying in 72 hours');
1190
- $this->logger->error('JSON: ' . $extensions_json);
1191
- $extensions_json = '[]';
1192
- }
1193
- set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1194
- }
1195
- }
1196
-
1197
- $extensions = json_decode($extensions_json);
1198
-
1199
- return $extensions;
1200
- }
1201
-
1202
- function clear_extensions_cache() {
1203
- delete_transient('tnp_extensions_json');
1204
- }
1205
-
1206
- var $panels = array();
1207
-
1208
- function add_panel($key, $panel) {
1209
- if (!isset($this->panels[$key]))
1210
- $this->panels[$key] = array();
1211
- if (!isset($panel['id']))
1212
- $panel['id'] = sanitize_key($panel['label']);
1213
- $this->panels[$key][] = $panel;
1214
- }
1215
-
1216
- function has_license() {
1217
- return !empty($this->options['contract_key']);
1218
- }
1219
-
1220
- function get_sender_name() {
1221
- return $this->options['sender_name'];
1222
- }
1223
-
1224
- function get_sender_email() {
1225
- return $this->options['sender_email'];
1226
- }
1227
-
1228
- /**
1229
- *
1230
- * @return int
1231
- */
1232
- function get_newsletter_page_id() {
1233
- return (int) $this->options['page'];
1234
- }
1235
-
1236
- /**
1237
- *
1238
- * @return WP_Post
1239
- */
1240
- function get_newsletter_page() {
1241
- return get_post($this->get_newsletter_page_id());
1242
- }
1243
-
1244
- /**
1245
- * Returns the Newsletter dedicated page URL or an alternative URL if that page if not
1246
- * configured or not available.
1247
- *
1248
- * @staticvar string $url
1249
- * @return string
1250
- */
1251
- function get_newsletter_page_url($language = '') {
1252
-
1253
- $page = $this->get_newsletter_page();
1254
-
1255
- if (!$page || $page->post_status !== 'publish') {
1256
- return $this->build_action_url('m');
1257
- }
1258
-
1259
- $newsletter_page_url = get_permalink($page->ID);
1260
- if ($language && $newsletter_page_url) {
1261
- if (class_exists('SitePress')) {
1262
- $newsletter_page_url = apply_filters('wpml_permalink', $newsletter_page_url, $language, true);
1263
- }
1264
- if (function_exists('pll_get_post')) {
1265
- $translated_page = get_permalink(pll_get_post($page->ID, $language));
1266
- if ($translated_page) {
1267
- $newsletter_page_url = $translated_page;
1268
- }
1269
- }
1270
- }
1271
-
1272
- return $newsletter_page_url;
1273
- }
1274
-
1275
- function get_license_key() {
1276
- if (defined('NEWSLETTER_LICENSE_KEY')) {
1277
- return NEWSLETTER_LICENSE_KEY;
1278
- } else {
1279
- if (!empty($this->options['contract_key'])) {
1280
- return trim($this->options['contract_key']);
1281
- }
1282
- }
1283
- return false;
1284
- }
1285
-
1286
- /**
1287
- * Get the data connected to the specified license code on man settings.
1288
- *
1289
- * - false if no license is present
1290
- * - WP_Error if something went wrong if getting the license data
1291
- * - object with expiration and addons list
1292
- *
1293
- * @param boolean $refresh
1294
- * @return \WP_Error|boolean|object
1295
- */
1296
- function get_license_data($refresh = false) {
1297
-
1298
- $this->logger->debug('Getting license data');
1299
-
1300
- $license_key = $this->get_license_key();
1301
- if (empty($license_key)) {
1302
- $this->logger->debug('License was empty');
1303
- delete_transient('newsletter_license_data');
1304
- return false;
1305
- }
1306
-
1307
- if (!$refresh) {
1308
- $license_data = get_transient('newsletter_license_data');
1309
- if ($license_data !== false && is_object($license_data)) {
1310
- $this->logger->debug('License data found on cache');
1311
- return $license_data;
1312
- }
1313
- }
1314
-
1315
- $this->logger->debug('Refreshing the license data');
1316
-
1317
- $license_data_url = 'https://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/get-license-data.php';
1318
-
1319
- $response = wp_remote_post($license_data_url, array(
1320
- 'body' => array('k' => $license_key)
1321
- ));
1322
-
1323
- // Fall back to http...
1324
- if (is_wp_error($response)) {
1325
- $this->logger->error($response);
1326
- $this->logger->error('Falling back to http');
1327
- $license_data_url = str_replace('https', 'http', $license_data_url);
1328
- $response = wp_remote_post($license_data_url, array(
1329
- 'body' => array('k' => $license_key)
1330
- ));
1331
- if (is_wp_error($response)) {
1332
- $this->logger->error($response);
1333
- set_transient('newsletter_license_data', $response, DAY_IN_SECONDS);
1334
- return $response;
1335
- }
1336
- }
1337
-
1338
- $download_message = 'You can download all addons from www.thenewsletterplugin.com if your license is valid.';
1339
-
1340
- if (wp_remote_retrieve_response_code($response) != '200') {
1341
- $this->logger->error('license data error: ' . wp_remote_retrieve_response_code($response));
1342
- $data = new WP_Error(wp_remote_retrieve_response_code($response), 'License validation service error. <br>' . $download_message);
1343
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1344
- return $data;
1345
- }
1346
-
1347
- $json = wp_remote_retrieve_body($response);
1348
- $data = json_decode($json);
1349
-
1350
- if (!is_object($data)) {
1351
- $this->logger->error($json);
1352
- $data = new WP_Error(1, 'License validation service error. <br>' . $download_message);
1353
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1354
- return $data;
1355
- }
1356
-
1357
- if (isset($data->message)) {
1358
- $data = new WP_Error(1, $data->message . ' (check the license on Newsletter main settings)');
1359
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1360
- return $data;
1361
- }
1362
-
1363
- $expiration = WEEK_IN_SECONDS;
1364
- // If the license expires in few days, make the transient live only few days, so it will be refreshed
1365
- if ($data->expire > time() && $data->expire - time() < WEEK_IN_SECONDS) {
1366
- $expiration = $data->expire - time();
1367
- }
1368
- set_transient('newsletter_license_data', $data, $expiration);
1369
-
1370
- return $data;
1371
- }
1372
-
1373
- /**
1374
- * @deprecated
1375
- * @param type $license_key
1376
- * @return \WP_Error
1377
- */
1378
- public static function check_license($license_key) {
1379
- $response = wp_remote_get('http://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/check.php?k=' . urlencode($license_key), array('sslverify' => false));
1380
- if (is_wp_error($response)) {
1381
- /* @var $response WP_Error */
1382
- return new WP_Error(-1, 'It seems that your blog cannot contact the license validator. Ask your provider to unlock the HTTP/HTTPS connections to www.thenewsletterplugin.com<br>'
1383
- . esc_html($response->get_error_code()) . ' - ' . esc_html($response->get_error_message()));
1384
- } else if ($response['response']['code'] != 200) {
1385
- return new WP_Error(-1, '[' . $response['response']['code'] . '] The license seems expired or not valid, please check your <a href="https://www.thenewsletterplugin.com/account">license code and status</a>, thank you.'
1386
- . '<br>You can anyway download the professional extension from https://www.thenewsletterplugin.com.');
1387
- } elseif ($expires = json_decode(wp_remote_retrieve_body($response))) {
1388
- return array('expires' => $expires->expire, 'message' => 'Your license is valid and expires on ' . esc_html(date('Y-m-d', $expires->expire)));
1389
- } else {
1390
- return new WP_Error(-1, 'Unable to detect the license expiration. Debug data to report to the support: <code>' . esc_html(wp_remote_retrieve_body($response)) . '</code>');
1391
- }
1392
- }
1393
-
1394
- function add_notice_to_chosen_profile_page_hook($post_states, $post) {
1395
-
1396
- if ($post->ID == $this->options['page']) {
1397
- $post_states[] = __('Newsletter plugin page, do not delete', 'newsletter');
1398
- }
1399
-
1400
- return $post_states;
1401
- }
1402
-
1403
- }
1404
-
1405
- $newsletter = Newsletter::instance();
1406
-
1407
- if (is_admin()) {
1408
- require_once NEWSLETTER_DIR . '/system/system.php';
1409
- }
1410
-
1411
- require_once NEWSLETTER_DIR . '/subscription/subscription.php';
1412
- require_once NEWSLETTER_DIR . '/unsubscription/unsubscription.php';
1413
- require_once NEWSLETTER_DIR . '/profile/profile.php';
1414
- require_once NEWSLETTER_DIR . '/emails/emails.php';
1415
- require_once NEWSLETTER_DIR . '/users/users.php';
1416
- require_once NEWSLETTER_DIR . '/statistics/statistics.php';
1417
- require_once NEWSLETTER_DIR . '/widget/standard.php';
1418
- require_once NEWSLETTER_DIR . '/widget/minimal.php';
1
+ <?php
2
+
3
+ /*
4
+ Plugin Name: Newsletter
5
+ Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
+ Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
+ Version: 7.3.7
8
+ Author: Stefano Lissa & The Newsletter Team
9
+ Author URI: https://www.thenewsletterplugin.com
10
+ Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
11
+ Text Domain: newsletter
12
+ License: GPLv2 or later
13
+ Requires at least: 4.6
14
+ Requires PHP: 5.6
15
+
16
+ Copyright 2009-2022 The Newsletter Team (email: info@thenewsletterplugin.com, web: https://www.thenewsletterplugin.com)
17
+
18
+ Newsletter is free software: you can redistribute it and/or modify
19
+ it under the terms of the GNU General Public License as published by
20
+ the Free Software Foundation, either version 2 of the License, or
21
+ any later version.
22
+
23
+ Newsletter is distributed in the hope that it will be useful,
24
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
25
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
+ GNU General Public License for more details.
27
+
28
+ You should have received a copy of the GNU General Public License
29
+ along with Newsletter. If not, see https://www.gnu.org/licenses/gpl-2.0.html.
30
+
31
+ */
32
+
33
+ if (version_compare(phpversion(), '5.6', '<')) {
34
+ add_action('admin_notices', function () {
35
+ echo '<div class="notice notice-error"><p>PHP version 5.6 or greater is required for Newsletter. Ask your provider to upgrade. <a href="https://www.php.net/supported-versions.php" target="_blank">Read more on PHP versions</a></p></div>';
36
+ });
37
+ return;
38
+ }
39
+
40
+ define('NEWSLETTER_VERSION', '7.3.7');
41
+
42
+ global $newsletter, $wpdb;
43
+
44
+ // For acceptance tests, DO NOT CHANGE
45
+ if (!defined('NEWSLETTER_DEBUG'))
46
+ define('NEWSLETTER_DEBUG', false);
47
+
48
+ if (!defined('NEWSLETTER_EXTENSION_UPDATE'))
49
+ define('NEWSLETTER_EXTENSION_UPDATE', true);
50
+
51
+ if (!defined('NEWSLETTER_EMAILS_TABLE'))
52
+ define('NEWSLETTER_EMAILS_TABLE', $wpdb->prefix . 'newsletter_emails');
53
+
54
+ if (!defined('NEWSLETTER_USERS_TABLE'))
55
+ define('NEWSLETTER_USERS_TABLE', $wpdb->prefix . 'newsletter');
56
+
57
+ if (!defined('NEWSLETTER_STATS_TABLE'))
58
+ define('NEWSLETTER_STATS_TABLE', $wpdb->prefix . 'newsletter_stats');
59
+
60
+ if (!defined('NEWSLETTER_SENT_TABLE'))
61
+ define('NEWSLETTER_SENT_TABLE', $wpdb->prefix . 'newsletter_sent');
62
+
63
+ define('NEWSLETTER_SLUG', 'newsletter');
64
+
65
+ define('NEWSLETTER_DIR', __DIR__);
66
+ define('NEWSLETTER_INCLUDES_DIR', __DIR__ . '/includes');
67
+
68
+ // Deperacted since plugin can change the plugins_url()
69
+ define('NEWSLETTER_URL', WP_PLUGIN_URL . '/newsletter');
70
+
71
+ if (!defined('NEWSLETTER_LIST_MAX'))
72
+ define('NEWSLETTER_LIST_MAX', 40);
73
+
74
+ if (!defined('NEWSLETTER_PROFILE_MAX'))
75
+ define('NEWSLETTER_PROFILE_MAX', 20);
76
+
77
+ if (!defined('NEWSLETTER_FORMS_MAX'))
78
+ define('NEWSLETTER_FORMS_MAX', 10);
79
+
80
+ if (!defined('NEWSLETTER_HEADER'))
81
+ define('NEWSLETTER_HEADER', true);
82
+
83
+ require_once NEWSLETTER_INCLUDES_DIR . '/module.php';
84
+ require_once NEWSLETTER_INCLUDES_DIR . '/TNP.php';
85
+ require_once NEWSLETTER_INCLUDES_DIR . '/cron.php';
86
+
87
+ class Newsletter extends NewsletterModule {
88
+
89
+ // Limits to respect to avoid memory, time or provider limits
90
+ var $time_start;
91
+ var $time_limit = 0;
92
+ var $max_emails = null;
93
+
94
+ var $mailer = null;
95
+
96
+ var $action = '';
97
+
98
+ /** @var Newsletter */
99
+ static $instance;
100
+
101
+ const STATUS_NOT_CONFIRMED = 'S';
102
+ const STATUS_CONFIRMED = 'C';
103
+
104
+ /**
105
+ * @return Newsletter
106
+ */
107
+ static function instance() {
108
+ if (self::$instance == null) {
109
+ self::$instance = new Newsletter();
110
+ }
111
+ return self::$instance;
112
+ }
113
+
114
+ function __construct() {
115
+
116
+ // Grab it before a plugin decides to remove it.
117
+ if (isset($_GET['na'])) {
118
+ $this->action = $_GET['na'];
119
+ }
120
+ if (isset($_POST['na'])) {
121
+ $this->action = $_POST['na'];
122
+ }
123
+
124
+ $this->time_start = time();
125
+
126
+ parent::__construct('main', '1.6.7', null, ['info', 'smtp']);
127
+
128
+ add_action('plugins_loaded', [$this, 'hook_plugins_loaded']);
129
+ add_action('init', [$this, 'hook_init'], 1);
130
+ add_action('wp_loaded', [$this, 'hook_wp_loaded'], 1);
131
+
132
+ add_action('newsletter', [$this, 'hook_newsletter'], 1);
133
+
134
+ register_activation_hook(__FILE__, [$this, 'hook_activate']);
135
+ register_deactivation_hook(__FILE__, [$this, 'hook_deactivate']);
136
+
137
+ add_action('admin_init', [$this, 'hook_admin_init']);
138
+
139
+ if (is_admin()) {
140
+ add_action('admin_head', [$this, 'hook_admin_head']);
141
+
142
+ // Protection against strange schedule removal on some installations
143
+ if (!wp_next_scheduled('newsletter') && (!defined('WP_INSTALLING') || !WP_INSTALLING)) {
144
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
145
+ }
146
+
147
+ add_action('admin_menu', [$this, 'add_extensions_menu'], 90);
148
+
149
+ add_filter('display_post_states', [$this, 'add_notice_to_chosen_profile_page_hook'], 10, 2);
150
+
151
+ if ($this->is_admin_page()) {
152
+ // Remove the emoji replacer to save to database the original emoji characters (see even woocommerce for the same problem)
153
+ remove_action('admin_print_scripts', 'print_emoji_detection_script');
154
+ add_action('admin_enqueue_scripts', [$this, 'hook_admin_enqueue_scripts']);
155
+ }
156
+
157
+ }
158
+ }
159
+
160
+ function hook_plugins_loaded() {
161
+ // Used to load dependant modules
162
+ do_action('newsletter_loaded', NEWSLETTER_VERSION);
163
+
164
+ if (function_exists('load_plugin_textdomain')) {
165
+ load_plugin_textdomain('newsletter', false, plugin_basename(__DIR__) . '/languages');
166
+ }
167
+ }
168
+
169
+ function hook_init() {
170
+ global $wpdb;
171
+
172
+ if (isset($this->options['debug']) && $this->options['debug'] == 1) {
173
+ ini_set('log_errors', 1);
174
+ ini_set('error_log', WP_CONTENT_DIR . '/logs/newsletter/php-' . date('Y-m') . '-' . get_option('newsletter_logger_secret') . '.txt');
175
+ }
176
+
177
+ add_shortcode('newsletter_replace', [$this, 'shortcode_newsletter_replace']);
178
+
179
+ add_filter('site_transient_update_plugins', [$this, 'hook_site_transient_update_plugins']);
180
+
181
+ if (is_admin()) {
182
+ if (!class_exists('NewsletterExtensions')) {
183
+
184
+ add_filter('plugin_row_meta', function ($plugin_meta, $plugin_file) {
185
+
186
+ static $slugs = array();
187
+ if (empty($slugs)) {
188
+ $addons = $this->getTnpExtensions();
189
+ if ($addons) {
190
+ foreach ($addons as $addon) {
191
+ $slugs[] = $addon->wp_slug;
192
+ }
193
+ }
194
+ }
195
+ if (array_search($plugin_file, $slugs) !== false) {
196
+
197
+ $plugin_meta[] = '<a href="admin.php?page=newsletter_main_extensions" style="font-weight: bold">Newsletter Addons Manager required</a>';
198
+ }
199
+ return $plugin_meta;
200
+ }, 10, 2);
201
+ }
202
+
203
+ add_action('in_admin_header', array($this, 'hook_in_admin_header'), 1000);
204
+
205
+ if ($this->is_admin_page()) {
206
+
207
+ $dismissed = get_option('newsletter_dismissed', []);
208
+
209
+ if (isset($_GET['dismiss'])) {
210
+ $dismissed[$_GET['dismiss']] = 1;
211
+ update_option('newsletter_dismissed', $dismissed);
212
+ wp_redirect($_SERVER['HTTP_REFERER']);
213
+ exit();
214
+ }
215
+ }
216
+ } else {
217
+ add_action('wp_enqueue_scripts', [$this, 'hook_wp_enqueue_scripts']);
218
+ }
219
+
220
+ do_action('newsletter_init');
221
+ }
222
+
223
+ function hook_wp_loaded() {
224
+ if (empty($this->action)) {
225
+ return;
226
+ }
227
+
228
+ if ($this->action == 'test') {
229
+ // This response is tested, do not change it!
230
+ echo 'ok';
231
+ die();
232
+ }
233
+
234
+ if ($this->action === 'nul') {
235
+ $this->dienow('This link is not active on newsletter preview', 'You can send a test message to test subscriber to have the real working link.');
236
+ }
237
+
238
+ $user = $this->get_user_from_request();
239
+ $email = $this->get_email_from_request();
240
+ do_action('newsletter_action', $this->action, $user, $email);
241
+ }
242
+
243
+ function hook_activate() {
244
+ // Ok, why? When the plugin is not active WordPress may remove the scheduled "newsletter" action because
245
+ // the every-five-minutes schedule named "newsletter" is not present.
246
+ // Since the activation does not forces an upgrade, that schedule must be reactivated here. It is activated on
247
+ // the upgrade method as well for the user which upgrade the plugin without deactivte it (many).
248
+ if (!wp_next_scheduled('newsletter')) {
249
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
250
+ }
251
+
252
+ $install_time = get_option('newsletter_install_time');
253
+ if (!$install_time) {
254
+ update_option('newsletter_install_time', time(), false);
255
+ }
256
+
257
+ touch(NEWSLETTER_LOG_DIR . '/index.html');
258
+
259
+ Newsletter::instance()->upgrade();
260
+ NewsletterUsers::instance()->upgrade();
261
+ NewsletterEmails::instance()->upgrade();
262
+ NewsletterSubscription::instance()->upgrade();
263
+ NewsletterStatistics::instance()->upgrade();
264
+ NewsletterProfile::instance()->upgrade();
265
+ }
266
+
267
+ function first_install() {
268
+ parent::first_install();
269
+ update_option('newsletter_show_welcome', '1', false);
270
+ }
271
+
272
+ function upgrade() {
273
+ global $wpdb, $charset_collate;
274
+
275
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
276
+
277
+ parent::upgrade();
278
+
279
+ $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_emails` (
280
+ `id` int(11) NOT NULL AUTO_INCREMENT,
281
+ `language` varchar(10) NOT NULL DEFAULT '',
282
+ `subject` varchar(255) NOT NULL DEFAULT '',
283
+ `message` longtext,
284
+ `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
285
+ `status` enum('new','sending','sent','paused','error') NOT NULL DEFAULT 'new',
286
+ `total` int(11) NOT NULL DEFAULT '0',
287
+ `last_id` int(11) NOT NULL DEFAULT '0',
288
+ `sent` int(11) NOT NULL DEFAULT '0',
289
+ `track` int(11) NOT NULL DEFAULT '1',
290
+ `list` int(11) NOT NULL DEFAULT '0',
291
+ `type` varchar(50) NOT NULL DEFAULT '',
292
+ `query` longtext,
293
+ `editor` tinyint(4) NOT NULL DEFAULT '0',
294
+ `sex` varchar(20) NOT NULL DEFAULT '',
295
+ `theme` varchar(50) NOT NULL DEFAULT '',
296
+ `message_text` longtext,
297
+ `preferences` longtext,
298
+ `send_on` int(11) NOT NULL DEFAULT '0',
299
+ `token` varchar(10) NOT NULL DEFAULT '',
300
+ `options` longtext,
301
+ `private` tinyint(1) NOT NULL DEFAULT '0',
302
+ `click_count` int(10) unsigned NOT NULL DEFAULT '0',
303
+ `version` varchar(10) NOT NULL DEFAULT '',
304
+ `open_count` int(10) unsigned NOT NULL DEFAULT '0',
305
+ `unsub_count` int(10) unsigned NOT NULL DEFAULT '0',
306
+ `error_count` int(10) unsigned NOT NULL DEFAULT '0',
307
+ `stats_time` int(10) unsigned NOT NULL DEFAULT '0',
308
+ `updated` int(10) unsigned NOT NULL DEFAULT '0',
309
+ PRIMARY KEY (`id`)
310
+ ) $charset_collate;";
311
+
312
+ dbDelta($sql);
313
+
314
+ // WP does not manage composite primary key when it tries to upgrade a table...
315
+ $suppress_errors = $wpdb->suppress_errors(true);
316
+
317
+ dbDelta("CREATE TABLE " . $wpdb->prefix . "newsletter_sent (
318
+ email_id int(10) unsigned NOT NULL DEFAULT '0',
319
+ user_id int(10) unsigned NOT NULL DEFAULT '0',
320
+ status tinyint(1) unsigned NOT NULL DEFAULT '0',
321
+ open tinyint(1) unsigned NOT NULL DEFAULT '0',
322
+ time int(10) unsigned NOT NULL DEFAULT '0',
323
+ error varchar(255) NOT NULL DEFAULT '',
324
+ ip varchar(100) NOT NULL DEFAULT '',
325
+ PRIMARY KEY (email_id,user_id),
326
+ KEY user_id (user_id),
327
+ KEY email_id (email_id)
328
+ ) $charset_collate;");
329
+ $wpdb->suppress_errors($suppress_errors);
330
+
331
+ // Some setting check to avoid the common support request for mis-configurations
332
+ $options = $this->get_options();
333
+
334
+ if (empty($options['scheduler_max']) || !is_numeric($options['scheduler_max'])) {
335
+ $options['scheduler_max'] = 100;
336
+ $this->save_options($options);
337
+ }
338
+
339
+ wp_clear_scheduled_hook('newsletter');
340
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
341
+
342
+ if (!empty($this->options['editor'])) {
343
+ if (empty($this->options['roles'])) {
344
+ $this->options['roles'] = array('editor');
345
+ unset($this->options['editor']);
346
+ }
347
+ $this->save_options($this->options);
348
+ }
349
+
350
+ // Clear the addons and license caches
351
+ delete_transient('newsletter_license_data');
352
+ $this->clear_extensions_cache();
353
+
354
+ touch(NEWSLETTER_LOG_DIR . '/index.html');
355
+
356
+ return true;
357
+ }
358
+
359
+ function is_allowed() {
360
+ if (current_user_can('administrator')) {
361
+ return true;
362
+ }
363
+ if (!empty($this->options['roles'])) {
364
+ foreach ($this->options['roles'] as $role) {
365
+ if (current_user_can($role)) {
366
+ return true;
367
+ }
368
+ }
369
+ }
370
+ return false;
371
+ }
372
+
373
+ function admin_menu() {
374
+ if (!$this->is_allowed()) {
375
+ return;
376
+ }
377
+
378
+ add_menu_page('Newsletter', 'Newsletter', 'exist', 'newsletter_main_index', '', plugins_url('newsletter') . '/admin/images/menu-icon.png', '30.333');
379
+
380
+ $this->add_menu_page('index', __('Dashboard', 'newsletter'));
381
+ $this->add_admin_page('info', __('Company info', 'newsletter'));
382
+
383
+ if (current_user_can('administrator')) {
384
+ $this->add_menu_page('welcome', __('Welcome', 'newsletter'));
385
+ $this->add_menu_page('main', __('Settings', 'newsletter'));
386
+
387
+ // Pages not on menu
388
+ $this->add_admin_page('smtp', 'SMTP');
389
+ $this->add_admin_page('diagnostic', __('Diagnostic', 'newsletter'));
390
+ $this->add_admin_page('test', __('Test', 'newsletter'));
391
+ }
392
+ }
393
+
394
+ function add_extensions_menu() {
395
+ if (!class_exists('NewsletterExtensions')) {
396
+ $this->add_menu_page('extensions', '<span style="color:#27AE60; font-weight: bold;">' . __('Addons', 'newsletter') . '</span>');
397
+ }
398
+ }
399
+
400
+ function hook_in_admin_header() {
401
+ if (!$this->is_admin_page()) {
402
+ add_action('admin_notices', array($this, 'hook_admin_notices'));
403
+ return;
404
+ }
405
+ remove_all_actions('admin_notices');
406
+ remove_all_actions('all_admin_notices');
407
+ add_action('admin_notices', array($this, 'hook_admin_notices'));
408
+ }
409
+
410
+ function hook_admin_notices() {
411
+ // Check of Newsletter dedicated page
412
+ if (!empty($this->options['page'])) {
413
+ if (get_post_status($this->options['page']) !== 'publish') {
414
+ echo '<div class="notice notice-error"><p>The Newsletter dedicated page is not published. <a href="', site_url('/wp-admin/post.php') . '?post=', $this->options['page'], '&action=edit"><strong>Edit the page</strong></a> or <a href="admin.php?page=newsletter_main_main"><strong>review the main settings</strong></a>.</p></div>';
415
+ }
416
+ }
417
+
418
+ if (isset($this->options['debug']) && $this->options['debug'] == 1) {
419
+ echo '<div class="notice notice-warning"><p>The Newsletter plugin is in <strong>debug mode</strong>. When done change it on Newsletter <a href="admin.php?page=newsletter_main_main"><strong>main settings</strong></a>. Do not keep the debug mode active on production sites.</p></div>';
420
+ }
421
+ }
422
+
423
+ function hook_wp_enqueue_scripts() {
424
+ if (empty($this->options['css_disabled']) && apply_filters('newsletter_enqueue_style', true)) {
425
+ wp_enqueue_style('newsletter', plugins_url('newsletter') . '/style.css', [], NEWSLETTER_VERSION);
426
+ if (!empty($this->options['css'])) {
427
+ wp_add_inline_style('newsletter', $this->options['css']);
428
+ }
429
+ } else {
430
+ if (!empty($this->options['css'])) {
431
+ add_action('wp_head', function () {
432
+ echo '<style>', $this->options['css'], '</style>';
433
+ });
434
+ }
435
+ }
436
+ }
437
+
438
+ function hook_admin_enqueue_scripts() {
439
+
440
+ $newsletter_url = plugins_url('newsletter');
441
+ wp_enqueue_script('jquery-ui-tabs');
442
+ wp_enqueue_script('jquery-ui-tooltip');
443
+ wp_enqueue_script('jquery-ui-draggable');
444
+ wp_enqueue_media();
445
+
446
+ wp_enqueue_script('tnp-admin', $newsletter_url . '/admin/admin.js', ['jquery'], NEWSLETTER_VERSION);
447
+
448
+ wp_enqueue_style('tnp-select2', $newsletter_url . '/vendor/select2/css/select2.min.css', [], NEWSLETTER_VERSION);
449
+ wp_enqueue_script('tnp-select2', $newsletter_url . '/vendor/select2/js/select2.min.js', ['jquery'], NEWSLETTER_VERSION);
450
+
451
+ wp_enqueue_style('tnp-modal', $newsletter_url . '/admin/modal.css', [], NEWSLETTER_VERSION);
452
+ wp_enqueue_script('tnp-modal', $newsletter_url . '/admin/modal.js', ['jquery'], NEWSLETTER_VERSION, true);
453
+
454
+ wp_enqueue_style('tnp-toast', $newsletter_url . '/admin/toast.css', [], NEWSLETTER_VERSION);
455
+ wp_enqueue_script('tnp-toast', $newsletter_url . '/admin/toast.js', ['jquery'], NEWSLETTER_VERSION);
456
+
457
+ wp_enqueue_style('tnp-admin-font', 'https://use.typekit.net/jlj2wjy.css');
458
+ wp_enqueue_style('tnp-admin-fontawesome', $newsletter_url . '/vendor/fa/css/all.min.css', [], NEWSLETTER_VERSION);
459
+ wp_enqueue_style('tnp-admin-jquery-ui', $newsletter_url . '/vendor/jquery-ui/jquery-ui.min.css', [], NEWSLETTER_VERSION);
460
+ wp_enqueue_style('tnp-admin', $newsletter_url . '/admin/admin.css', [], NEWSLETTER_VERSION);
461
+ wp_enqueue_style('tnp-admin-dropdown', $newsletter_url . '/admin/dropdown.css', [], NEWSLETTER_VERSION);
462
+ wp_enqueue_style('tnp-admin-tabs', $newsletter_url . '/admin/tabs.css', [], NEWSLETTER_VERSION);
463
+ wp_enqueue_style('tnp-admin-controls', $newsletter_url . '/admin/controls.css', [], NEWSLETTER_VERSION);
464
+ wp_enqueue_style('tnp-admin-fields', $newsletter_url . '/admin/fields.css', [], NEWSLETTER_VERSION);
465
+ wp_enqueue_style('tnp-admin-widgets', $newsletter_url . '/admin/widgets.css', [], NEWSLETTER_VERSION);
466
+ wp_enqueue_style('tnp-admin-extensions', $newsletter_url . '/admin/extensions.css', [], NEWSLETTER_VERSION);
467
+
468
+ $translations_array = array(
469
+ 'save_to_update_counter' => __('Save the newsletter to update the counter!', 'newsletter')
470
+ );
471
+ wp_localize_script('tnp-admin', 'tnp_translations', $translations_array);
472
+
473
+ wp_enqueue_script('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jquery.vmap.min.js', ['jquery'], NEWSLETTER_VERSION);
474
+ wp_enqueue_script('tnp-jquery-vmap-world', $newsletter_url . '/vendor/jqvmap/jquery.vmap.world.js', ['tnp-jquery-vmap'], NEWSLETTER_VERSION);
475
+ wp_enqueue_style('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jqvmap.min.css', [], NEWSLETTER_VERSION);
476
+
477
+ wp_register_script('tnp-chart', $newsletter_url . '/vendor/chartjs/Chart.min.js', ['jquery'], NEWSLETTER_VERSION);
478
+
479
+ wp_enqueue_script('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.js', ['jquery']);
480
+ wp_enqueue_style('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.css', [], NEWSLETTER_VERSION);
481
+ }
482
+
483
+ function shortcode_newsletter_replace($attrs, $content) {
484
+ $content = do_shortcode($content);
485
+ $content = $this->replace($content, $this->get_user_from_request(), $this->get_email_from_request());
486
+ return $content;
487
+ }
488
+
489
+ function is_admin_page() {
490
+ if (!isset($_GET['page'])) {
491
+ return false;
492
+ }
493
+ $page = $_GET['page'];
494
+ return strpos($page, 'newsletter_') === 0;
495
+ }
496
+
497
+ function hook_admin_init() {
498
+ // Verificare il contesto
499
+ if (isset($_GET['page']) && $_GET['page'] === 'newsletter_main_welcome')
500
+ return;
501
+ if (get_option('newsletter_show_welcome')) {
502
+ delete_option('newsletter_show_welcome');
503
+ wp_redirect(admin_url('admin.php?page=newsletter_main_welcome'));
504
+ }
505
+ }
506
+
507
+ function hook_admin_head() {
508
+ // Small global rule for sidebar menu entries
509
+ echo '<style>';
510
+ echo '.tnp-side-menu { color: #E67E22!important; }';
511
+ echo '</style>';
512
+ }
513
+
514
+ function relink($text, $email_id, $user_id, $email_token = '') {
515
+ return NewsletterStatistics::instance()->relink($text, $email_id, $user_id, $email_token);
516
+ }
517
+
518
+ /**
519
+ * Runs every 5 minutes and look for emails that need to be processed.
520
+ */
521
+ function hook_newsletter() {
522
+
523
+ $this->logger->debug(__METHOD__ . '> Start');
524
+
525
+ if (!$this->check_transient('engine', NEWSLETTER_CRON_INTERVAL)) {
526
+ $this->logger->debug(__METHOD__ . '> Engine already active, exit');
527
+ return;
528
+ }
529
+
530
+ $emails = $this->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " where status='sending' and send_on<" . time() . " order by id asc");
531
+ $this->logger->debug(__METHOD__ . '> Emails found in sending status: ' . count($emails));
532
+
533
+ foreach ($emails as $email) {
534
+ $this->logger->debug(__METHOD__ . '> Start newsletter ' . $email->id);
535
+ $email->options = maybe_unserialize($email->options);
536
+ $r = $this->send($email);
537
+ $this->logger->debug(__METHOD__ . '> End newsletter ' . $email->id);
538
+ if (!$r) {
539
+ $this->logger->debug(__METHOD__ . '> Engine returned false, there is no more capacity');
540
+ break;
541
+ }
542
+ }
543
+ // Remove the semaphore so the delivery engine can be activated again
544
+ $this->delete_transient('engine');
545
+
546
+ $this->logger->debug(__METHOD__ . '> End');
547
+ }
548
+
549
+ function get_send_speed($email = null) {
550
+ $this->logger->debug(__METHOD__ . '> Computing delivery speed');
551
+ $mailer = $this->get_mailer();
552
+ $speed = (int) $mailer->get_speed();
553
+ if (!$speed) {
554
+ $this->logger->debug(__METHOD__ . '> Speed not set by mailer, use the default');
555
+ $speed = (int) $this->options['scheduler_max'];
556
+ } else {
557
+ $this->logger->debug(__METHOD__ . '> Speed set by mailer');
558
+ }
559
+
560
+ //$speed = (int) apply_filters('newsletter_send_speed', $speed, $email);
561
+ // Fail safe setting
562
+ $runs_per_hour = $this->get_runs_per_hour();
563
+ if (!$speed || $speed < $runs_per_hour) {
564
+ return $runs_per_hour;
565
+ }
566
+
567
+ $this->logger->debug(__METHOD__ . '> Speed: ' . $speed);
568
+ return $speed;
569
+ }
570
+
571
+ function get_send_delay() {
572
+ return 0;
573
+ }
574
+
575
+ function skip_this_run($email = null) {
576
+ return (boolean) apply_filters('newsletter_send_skip', false, $email);
577
+ }
578
+
579
+ function get_runs_per_hour() {
580
+ return (int) (3600 / NEWSLETTER_CRON_INTERVAL);
581
+ }
582
+
583
+ function get_emails_per_run() {
584
+ $speed = $this->get_send_speed();
585
+ $max = (int)($speed / $this->get_runs_per_hour());
586
+
587
+ return $max;
588
+ }
589
+
590
+ function get_max_emails($email) {
591
+ // Obsolete, here from Speed Control Addon
592
+ $max = (int) apply_filters('newsletter_send_max_emails', $this->max_emails, $email);
593
+
594
+ return min($max, $this->max_emails);
595
+ }
596
+
597
+ function fix_email($email) {
598
+ if (empty($email->query)) {
599
+ $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
600
+ }
601
+ if (empty($email->id)) {
602
+ $email->id = 0;
603
+ }
604
+ }
605
+
606
+ function send_setup() {
607
+ $this->logger->debug(__METHOD__ . '> Setup delivery engine');
608
+ if (is_null($this->max_emails)) {
609
+ $this->max_emails = $this->get_emails_per_run();
610
+ $this->logger->debug(__METHOD__ . '> Max emails: ' . $this->max_emails);
611
+ ignore_user_abort(true);
612
+
613
+ @set_time_limit(NEWSLETTER_CRON_INTERVAL + 30);
614
+
615
+ $max_time = (int) (@ini_get('max_execution_time') * 0.95);
616
+ if ($max_time == 0 || $max_time > NEWSLETTER_CRON_INTERVAL) {
617
+ $max_time = (int) (NEWSLETTER_CRON_INTERVAL * 0.95);
618
+ }
619
+
620
+ $this->time_limit = $this->time_start + $max_time;
621
+
622
+ $this->logger->debug(__METHOD__ . '> Max time set to ' . $max_time);
623
+ } else {
624
+ $this->logger->debug(__METHOD__ . '> Already setup');
625
+ }
626
+ }
627
+
628
+ function time_exceeded() {
629
+ if ($this->time_limit && time() > $this->time_limit) {
630
+ $this->logger->info(__METHOD__ . '> Max execution time limit reached');
631
+ return true;
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Sends an email to targeted users or to given users. If a list of users is given (usually a list of test users)
637
+ * the query inside the email to retrieve users is not used.
638
+ *
639
+ * @global wpdb $wpdb
640
+ * @global type $newsletter_feed
641
+ * @param TNP_Email $email
642
+ * @param array $users
643
+ * @return boolean|WP_Error True if the process completed, false if limits was reached. On false the caller should no continue to call it with other emails.
644
+ */
645
+ function send($email, $users = null, $test = false) {
646
+ global $wpdb;
647
+
648
+ if (is_array($email)) {
649
+ $email = (object) $email;
650
+ }
651
+
652
+ $this->logger->info(__METHOD__ . '> Send email ' . $email->id);
653
+
654
+ $this->send_setup();
655
+
656
+ if ($this->max_emails <= 0) {
657
+ $this->logger->info(__METHOD__ . '> No more capacity');
658
+ return false;
659
+ }
660
+
661
+ $this->fix_email($email);
662
+
663
+ // This stops the update of last_id and sent fields since
664
+ // it's not a scheduled delivery but a test or something else (like an autoresponder)
665
+ $supplied_users = $users != null;
666
+
667
+ if (!$supplied_users) {
668
+
669
+ if ($this->skip_this_run($email)) {
670
+ return true;
671
+ }
672
+
673
+ // Speed change for specific email by Speed Control Addon
674
+ $max_emails = $this->get_max_emails($email);
675
+ if ($max_emails <= 0) {
676
+ return true;
677
+ }
678
+
679
+ $query = $email->query;
680
+ $query .= " and id>" . $email->last_id . " order by id limit " . $max_emails;
681
+
682
+ $this->logger->debug(__METHOD__ . '> Query: ' . $query);
683
+
684
+ //Retrieve subscribers
685
+ $users = $this->get_results($query);
686
+
687
+ $this->logger->debug(__METHOD__ . '> Loaded subscribers: ' . count($users));
688
+
689
+ // If there was a database error, return error
690
+ if ($users === false) {
691
+ return new WP_Error('1', 'Unable to query subscribers, check the logs');
692
+ }
693
+
694
+ if (empty($users)) {
695
+ $this->logger->info(__METHOD__ . '> No more users, set as sent');
696
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set status='sent', total=sent where id=" . $email->id . " limit 1");
697
+ do_action('newsletter_ended_sending_newsletter', $email);
698
+ return true;
699
+ }
700
+ } else {
701
+ $this->logger->info(__METHOD__ . '> Subscribers supplied');
702
+ }
703
+
704
+ $start_time = microtime(true);
705
+ $count = 0;
706
+ $result = true;
707
+
708
+ $mailer = $this->get_mailer();
709
+
710
+ $batch_size = $mailer->get_batch_size();
711
+
712
+ $this->logger->debug(__METHOD__ . '> Batch size: ' . $batch_size);
713
+
714
+ // For batch size == 1 (normal condition) we optimize
715
+ if ($batch_size == 1) {
716
+
717
+ foreach ($users as $user) {
718
+
719
+ $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
720
+ $user = apply_filters('newsletter_send_user', $user);
721
+ $message = $this->build_message($email, $user);
722
+
723
+ // Save even test emails since people wants to see some stats even for test emails. Stats are reset upon the real "send" of a newsletter
724
+ $this->save_sent_message($message);
725
+
726
+ //Se non è un test incremento il contatore delle email spedite. Perchè incremento prima di spedire??
727
+ if (!$test) {
728
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
729
+ }
730
+
731
+ $r = $mailer->send($message);
732
+
733
+ if (!empty($message->error)) {
734
+ $this->logger->error($message);
735
+ $this->save_sent_message($message);
736
+ }
737
+
738
+ if (is_wp_error($r)) {
739
+ $this->logger->error($r);
740
+
741
+ // For fatal error, the newsletter status i changed to error (and the delivery stopped)
742
+ if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
743
+ $this->set_error_state_of_email($email, $r->get_error_message());
744
+ return $r;
745
+ }
746
+ }
747
+
748
+ if (!$supplied_users && !$test && $this->time_exceeded()) {
749
+ $result = false;
750
+ break;
751
+ }
752
+ }
753
+
754
+ $this->max_emails--;
755
+ $count++;
756
+ } else {
757
+
758
+ $chunks = array_chunk($users, $batch_size);
759
+
760
+ foreach ($chunks as $chunk) {
761
+
762
+ $messages = [];
763
+
764
+ // Peeparing a batch of messages
765
+ foreach ($chunk as $user) {
766
+ $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
767
+ $user = apply_filters('newsletter_send_user', $user);
768
+ $message = $this->build_message($email, $user);
769
+ $this->save_sent_message($message);
770
+ $messages[] = $message;
771
+
772
+ if (!$test) {
773
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
774
+ }
775
+ $this->max_emails--;
776
+ $count++;
777
+ }
778
+
779
+ $r = $mailer->send_batch($messages);
780
+
781
+ // Updating the status of the sent messages
782
+ foreach ($messages as $message) {
783
+ if (!empty($message->error)) {
784
+ $this->save_sent_message($message);
785
+ }
786
+ }
787
+
788
+ // The batch went in error
789
+ if (is_wp_error($r)) {
790
+ $this->logger->error($r);
791
+
792
+ if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
793
+ $this->set_error_state_of_email($email, $r->get_error_message());
794
+ return $r;
795
+ }
796
+ }
797
+
798
+ if (!$supplied_users && !$test && $this->time_exceeded()) {
799
+ $result = false;
800
+ break;
801
+ }
802
+ }
803
+ }
804
+
805
+ $end_time = microtime(true);
806
+
807
+ // Stats only for newsletter with enough emails in a batch (we exclude the Autoresponder since it send one email per call)
808
+ if (!$test && !$supplied_users && $count > 5) {
809
+ $this->update_send_stats($start_time, $end_time, $count, $result);
810
+ }
811
+
812
+ // Cached general statistics are reset
813
+ if (!$test) {
814
+ NewsletterStatistics::instance()->reset_stats_time($email->id);
815
+ }
816
+
817
+ $this->logger->info(__METHOD__ . '> End run for email ' . $email->id);
818
+
819
+ return $result;
820
+ }
821
+
822
+ function update_send_stats($start_time, $end_time, $count, $result) {
823
+ $send_calls = get_option('newsletter_diagnostic_send_calls', []);
824
+ $send_calls[] = [$start_time, $end_time, $count, $result];
825
+
826
+ if (count($send_calls) > 100) {
827
+ array_shift($send_calls);
828
+ }
829
+
830
+ update_option('newsletter_diagnostic_send_calls', $send_calls, false);
831
+ }
832
+
833
+ /**
834
+ * @param TNP_Email $email
835
+ */
836
+ private function set_error_state_of_email($email, $message = '') {
837
+ // Handle only message type at the moment
838
+ if ($email->type !== 'message') {
839
+ return;
840
+ }
841
+
842
+ do_action('newsletter_error_on_sending', $email, $message);
843
+
844
+ $edited_email = new TNP_Email();
845
+ $edited_email->id = $email->id;
846
+ $edited_email->status = TNP_Email::STATUS_ERROR;
847
+ $edited_email->options = $email->options;
848
+ $edited_email->options['error_message'] = $message;
849
+
850
+ $this->save_email($edited_email);
851
+ }
852
+
853
+ /**
854
+ *
855
+ * @param TNP_Email $email
856
+ * @param TNP_User $user
857
+ * @return \TNP_Mailer_Message
858
+ */
859
+ function build_message($email, $user) {
860
+
861
+ $message = new TNP_Mailer_Message();
862
+
863
+ $message->to = $user->email;
864
+
865
+ $message->headers = [];
866
+ $message->headers['Precedence'] = 'bulk';
867
+ $message->headers['X-Newsletter-Email-Id'] = $email->id;
868
+ $message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
869
+ $message->headers = apply_filters('newsletter_message_headers', $message->headers, $email, $user);
870
+
871
+ $message->body = preg_replace('/data-json=".*?"/is', '', $email->message);
872
+ $message->body = preg_replace('/ +/s', ' ', $message->body);
873
+ $message->body = $this->replace($message->body, $user, $email);
874
+ if ($this->options['do_shortcodes']) {
875
+ $message->body = do_shortcode($message->body);
876
+ }
877
+ $message->body = apply_filters('newsletter_message_html', $message->body, $email, $user);
878
+
879
+ $message->body_text = $this->replace($email->message_text, $user, $email);
880
+ $message->body_text = apply_filters('newsletter_message_text', $message->body_text, $email, $user);
881
+
882
+ if ($email->track == 1) {
883
+ $message->body = $this->relink($message->body, $email->id, $user->id, $email->token);
884
+ }
885
+
886
+ $message->subject = $this->replace($email->subject, $user);
887
+ $message->subject = apply_filters('newsletter_message_subject', $message->subject, $email, $user);
888
+
889
+ if (!empty($email->options['sender_email'])) {
890
+ $message->from = $email->options['sender_email'];
891
+ } else {
892
+ $message->from = $this->options['sender_email'];
893
+ }
894
+
895
+ if (!empty($email->options['sender_name'])) {
896
+ $message->from_name = $email->options['sender_name'];
897
+ } else {
898
+ $message->from_name = $this->options['sender_name'];
899
+ }
900
+
901
+ $message->email_id = $email->id;
902
+ $message->user_id = $user->id;
903
+
904
+ return apply_filters('newsletter_message', $message, $email, $user);
905
+ }
906
+
907
+ /**
908
+ *
909
+ * @param TNP_Mailer_Message $message
910
+ * @param int $status
911
+ * @param string $error
912
+ */
913
+ function save_sent_message($message) {
914
+ global $wpdb;
915
+
916
+ if (!$message->user_id || !$message->email_id) {
917
+ return;
918
+ }
919
+ $status = empty($message->error) ? 0 : 1;
920
+
921
+ $error = mb_substr($message->error, 0, 250);
922
+
923
+ $this->query($wpdb->prepare("insert into " . $wpdb->prefix . 'newsletter_sent (user_id, email_id, time, status, error) values (%d, %d, %d, %d, %s) on duplicate key update time=%d, status=%d, error=%s', $message->user_id, $message->email_id, time(), $status, $error, time(), $status, $error));
924
+ }
925
+
926
+ /**
927
+ * @deprecated since version 7.3.0
928
+ */
929
+ function limits_exceeded() {
930
+ return false;
931
+ }
932
+
933
+ /**
934
+ * @deprecated since version 6.0.0
935
+ */
936
+ function register_mail_method($callable) {
937
+ }
938
+
939
+ function register_mailer($mailer) {
940
+ if ($mailer instanceof NewsletterMailer) {
941
+ $this->mailer = $mailer;
942
+ }
943
+ }
944
+
945
+ /**
946
+ * Returns the current registered mailer which must be used to send emails.
947
+ *
948
+ * @return NewsletterMailer
949
+ */
950
+ function get_mailer() {
951
+ if ($this->mailer) {
952
+ return $this->mailer;
953
+ }
954
+
955
+ do_action('newsletter_register_mailer');
956
+
957
+ if (!$this->mailer) {
958
+ // Compatibility
959
+ $smtp = $this->get_options('smtp');
960
+ if (!empty($smtp['enabled'])) {
961
+ $this->mailer = new NewsletterDefaultSMTPMailer($smtp);
962
+ } else {
963
+ $this->mailer = new NewsletterDefaultMailer();
964
+ }
965
+ }
966
+ return $this->mailer;
967
+ }
968
+
969
+ /**
970
+ *
971
+ * @param TNP_Mailer_Message $message
972
+ * @return type
973
+ */
974
+ function deliver($message) {
975
+ $mailer = $this->get_mailer();
976
+ if (empty($message->from))
977
+ $message->from = $this->options['sender_email'];
978
+ if (empty($message->from_name))
979
+ $mailer->from_name = $this->options['sender_name'];
980
+ return $mailer->send($message);
981
+ }
982
+
983
+ /**
984
+ *
985
+ * @param type $to
986
+ * @param type $subject
987
+ * @param string|array $message If string is considered HTML, is array should contains the keys "html" and "text"
988
+ * @param type $headers
989
+ * @param type $enqueue
990
+ * @param type $from
991
+ * @return boolean
992
+ */
993
+ function mail($to, $subject, $message, $headers = array(), $enqueue = false, $from = false) {
994
+
995
+ if (empty($subject)) {
996
+ $this->logger->error('mail> Subject empty, skipped');
997
+ return true;
998
+ }
999
+
1000
+ $mailer_message = new TNP_Mailer_Message();
1001
+ $mailer_message->to = $to;
1002
+ $mailer_message->subject = $subject;
1003
+ $mailer_message->from = $this->options['sender_email'];
1004
+ $mailer_message->from_name = $this->options['sender_name'];
1005
+
1006
+ if (!empty($headers)) {
1007
+ $mailer_message->headers = $headers;
1008
+ }
1009
+ $mailer_message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
1010
+
1011
+ // Message carrige returns and line feeds clean up
1012
+ if (!is_array($message)) {
1013
+ $mailer_message->body = $this->clean_eol($message);
1014
+ } else {
1015
+ if (!empty($message['text'])) {
1016
+ $mailer_message->body_text = $this->clean_eol($message['text']);
1017
+ }
1018
+
1019
+ if (!empty($message['html'])) {
1020
+ $mailer_message->body = $this->clean_eol($message['html']);
1021
+ }
1022
+ }
1023
+
1024
+ $this->logger->debug($mailer_message);
1025
+
1026
+ $mailer = $this->get_mailer();
1027
+
1028
+ $r = $mailer->send($mailer_message);
1029
+
1030
+ return !is_wp_error($r);
1031
+ }
1032
+
1033
+ function hook_deactivate() {
1034
+ wp_clear_scheduled_hook('newsletter');
1035
+ }
1036
+
1037
+ function find_file($file1, $file2) {
1038
+ if (is_file($file1))
1039
+ return $file1;
1040
+ return $file2;
1041
+ }
1042
+
1043
+ function hook_site_transient_update_plugins($value) {
1044
+ static $extra_response = array();
1045
+
1046
+ //$this->logger->debug('Update plugins transient called');
1047
+
1048
+ if (!$value || !is_object($value)) {
1049
+ //$this->logger->info('Empty object');
1050
+ return $value;
1051
+ }
1052
+
1053
+ if (!isset($value->response) || !is_array($value->response)) {
1054
+ $value->response = array();
1055
+ }
1056
+
1057
+ // Already computed? Use it! (this filter is called many times in a single request)
1058
+ if ($extra_response) {
1059
+ //$this->logger->debug('Already updated');
1060
+ $value->response = array_merge($value->response, $extra_response);
1061
+ return $value;
1062
+ }
1063
+
1064
+ $extensions = $this->getTnpExtensions();
1065
+
1066
+ // Ops...
1067
+ if (!$extensions) {
1068
+ return $value;
1069
+ }
1070
+
1071
+ foreach ($extensions as $extension) {
1072
+ unset($value->response[$extension->wp_slug]);
1073
+ unset($value->no_update[$extension->wp_slug]);
1074
+ }
1075
+
1076
+ // Someone doesn't want our addons updated, let respect it (this constant should be defined in wp-config.php)
1077
+ if (!NEWSLETTER_EXTENSION_UPDATE) {
1078
+ //$this->logger->info('Updates disabled');
1079
+ return $value;
1080
+ }
1081
+
1082
+ include_once(ABSPATH . 'wp-admin/includes/plugin.php');
1083
+
1084
+ // Ok, that is really bad (should we remove it? is there a minimum WP version?)
1085
+ if (!function_exists('get_plugin_data')) {
1086
+ //$this->logger->error('No get_plugin_data function available!');
1087
+ return $value;
1088
+ }
1089
+
1090
+ $license_key = $this->get_license_key();
1091
+
1092
+ // Here we prepare the update information BUT do not add the link to the package which is privided
1093
+ // by our Addons Manager (due to WP policies)
1094
+ foreach ($extensions as $extension) {
1095
+
1096
+ // Patch for names convention
1097
+ $extension->plugin = $extension->wp_slug;
1098
+
1099
+ //$this->logger->debug('Processing ' . $extension->plugin);
1100
+ //$this->logger->debug($extension);
1101
+
1102
+ $plugin_data = false;
1103
+ if (file_exists(WP_PLUGIN_DIR . '/' . $extension->plugin)) {
1104
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1105
+ } else if (file_exists(WPMU_PLUGIN_DIR . '/' . $extension->plugin)) {
1106
+ $plugin_data = get_plugin_data(WPMU_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1107
+ }
1108
+
1109
+ if (!$plugin_data) {
1110
+ //$this->logger->debug('Seems not installed');
1111
+ continue;
1112
+ }
1113
+
1114
+ $plugin = new stdClass();
1115
+ $plugin->id = $extension->id;
1116
+ $plugin->slug = $extension->slug;
1117
+ $plugin->plugin = $extension->plugin;
1118
+ $plugin->new_version = $extension->version;
1119
+ $plugin->url = $extension->url;
1120
+ if (class_exists('NewsletterExtensions')) {
1121
+ // NO filters here!
1122
+ $plugin->package = NewsletterExtensions::$instance->get_package($extension->id, $license_key);
1123
+ } else {
1124
+ $plugin->package = '';
1125
+ }
1126
+ // [banners] => Array
1127
+ // (
1128
+ // [2x] => https://ps.w.org/wp-rss-aggregator/assets/banner-1544x500.png?rev=2040548
1129
+ // [1x] => https://ps.w.org/wp-rss-aggregator/assets/banner-772x250.png?rev=2040548
1130
+ // )
1131
+ // [icons] => Array
1132
+ // (
1133
+ // [2x] => https://ps.w.org/advanced-custom-fields/assets/icon-256x256.png?rev=1082746
1134
+ // [1x] => https://ps.w.org/advanced-custom-fields/assets/icon-128x128.png?rev=1082746
1135
+ // )
1136
+ if (version_compare($extension->version, $plugin_data['Version']) > 0) {
1137
+ //$this->logger->debug('There is a new version');
1138
+ $extra_response[$extension->plugin] = $plugin;
1139
+ } else {
1140
+ // Maybe useless...
1141
+ //$this->logger->debug('There is NOT a new version');
1142
+ $value->no_update[$extension->plugin] = $plugin;
1143
+ }
1144
+ //$this->logger->debug('Added');
1145
+ }
1146
+
1147
+ $value->response = array_merge($value->response, $extra_response);
1148
+
1149
+ return $value;
1150
+ }
1151
+
1152
+ /**
1153
+ * @deprecated since version 6.1.9
1154
+ */
1155
+ function get_extension_version($extension_id) {
1156
+ return null;
1157
+ }
1158
+
1159
+ /**
1160
+ * @deprecated since version 6.1.9
1161
+ */
1162
+ function set_extension_update_data($value, $extension) {
1163
+ return $value;
1164
+ }
1165
+
1166
+ /**
1167
+ * Retrieve the extensions form the tnp site
1168
+ * @return array
1169
+ */
1170
+ function getTnpExtensions() {
1171
+
1172
+ $extensions_json = get_transient('tnp_extensions_json');
1173
+
1174
+ if (empty($extensions_json)) {
1175
+ $url = "http://www.thenewsletterplugin.com/wp-content/extensions.json?ver=" . NEWSLETTER_VERSION;
1176
+ $extensions_response = wp_remote_get($url);
1177
+
1178
+ if (is_wp_error($extensions_response)) {
1179
+ // Cache anyway for blogs which cannot connect outside
1180
+ $extensions_json = '[]';
1181
+ set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1182
+ $this->logger->error($extensions_response);
1183
+ } else {
1184
+
1185
+ $extensions_json = wp_remote_retrieve_body($extensions_response);
1186
+
1187
+ // Not clear cases
1188
+ if (empty($extensions_json) || !json_decode($extensions_json)) {
1189
+ $this->logger->error('Invalid json from thenewsletterplugin.com: retrying in 72 hours');
1190
+ $this->logger->error('JSON: ' . $extensions_json);
1191
+ $extensions_json = '[]';
1192
+ }
1193
+ set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1194
+ }
1195
+ }
1196
+
1197
+ $extensions = json_decode($extensions_json);
1198
+
1199
+ return $extensions;
1200
+ }
1201
+
1202
+ function clear_extensions_cache() {
1203
+ delete_transient('tnp_extensions_json');
1204
+ }
1205
+
1206
+ var $panels = array();
1207
+
1208
+ function add_panel($key, $panel) {
1209
+ if (!isset($this->panels[$key]))
1210
+ $this->panels[$key] = array();
1211
+ if (!isset($panel['id']))
1212
+ $panel['id'] = sanitize_key($panel['label']);
1213
+ $this->panels[$key][] = $panel;
1214
+ }
1215
+
1216
+ function has_license() {
1217
+ return !empty($this->options['contract_key']);
1218
+ }
1219
+
1220
+ function get_sender_name() {
1221
+ return $this->options['sender_name'];
1222
+ }
1223
+
1224
+ function get_sender_email() {
1225
+ return $this->options['sender_email'];
1226
+ }
1227
+
1228
+ /**
1229
+ *
1230
+ * @return int
1231
+ */
1232
+ function get_newsletter_page_id() {
1233
+ return (int) $this->options['page'];
1234
+ }
1235
+
1236
+ /**
1237
+ *
1238
+ * @return WP_Post
1239
+ */
1240
+ function get_newsletter_page() {
1241
+ return get_post($this->get_newsletter_page_id());
1242
+ }
1243
+
1244
+ /**
1245
+ * Returns the Newsletter dedicated page URL or an alternative URL if that page if not
1246
+ * configured or not available.
1247
+ *
1248
+ * @staticvar string $url
1249
+ * @return string
1250
+ */
1251
+ function get_newsletter_page_url($language = '') {
1252
+
1253
+ $page = $this->get_newsletter_page();
1254
+
1255
+ if (!$page || $page->post_status !== 'publish') {
1256
+ return $this->build_action_url('m');
1257
+ }
1258
+
1259
+ $newsletter_page_url = get_permalink($page->ID);
1260
+ if ($language && $newsletter_page_url) {
1261
+ if (class_exists('SitePress')) {
1262
+ $newsletter_page_url = apply_filters('wpml_permalink', $newsletter_page_url, $language, true);
1263
+ }
1264
+ if (function_exists('pll_get_post')) {
1265
+ $translated_page = get_permalink(pll_get_post($page->ID, $language));
1266
+ if ($translated_page) {
1267
+ $newsletter_page_url = $translated_page;
1268
+ }
1269
+ }
1270
+ }
1271
+
1272
+ return $newsletter_page_url;
1273
+ }
1274
+
1275
+ function get_license_key() {
1276
+ if (defined('NEWSLETTER_LICENSE_KEY')) {
1277
+ return NEWSLETTER_LICENSE_KEY;
1278
+ } else {
1279
+ if (!empty($this->options['contract_key'])) {
1280
+ return trim($this->options['contract_key']);
1281
+ }
1282
+ }
1283
+ return false;
1284
+ }
1285
+
1286
+ /**
1287
+ * Get the data connected to the specified license code on man settings.
1288
+ *
1289
+ * - false if no license is present
1290
+ * - WP_Error if something went wrong if getting the license data
1291
+ * - object with expiration and addons list
1292
+ *
1293
+ * @param boolean $refresh
1294
+ * @return \WP_Error|boolean|object
1295
+ */
1296
+ function get_license_data($refresh = false) {
1297
+
1298
+ $this->logger->debug('Getting license data');
1299
+
1300
+ $license_key = $this->get_license_key();
1301
+ if (empty($license_key)) {
1302
+ $this->logger->debug('License was empty');
1303
+ delete_transient('newsletter_license_data');
1304
+ return false;
1305
+ }
1306
+
1307
+ if (!$refresh) {
1308
+ $license_data = get_transient('newsletter_license_data');
1309
+ if ($license_data !== false && is_object($license_data)) {
1310
+ $this->logger->debug('License data found on cache');
1311
+ return $license_data;
1312
+ }
1313
+ }
1314
+
1315
+ $this->logger->debug('Refreshing the license data');
1316
+
1317
+ $license_data_url = 'https://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/get-license-data.php';
1318
+
1319
+ $response = wp_remote_post($license_data_url, array(
1320
+ 'body' => array('k' => $license_key)
1321
+ ));
1322
+
1323
+ // Fall back to http...
1324
+ if (is_wp_error($response)) {
1325
+ $this->logger->error($response);
1326
+ $this->logger->error('Falling back to http');
1327
+ $license_data_url = str_replace('https', 'http', $license_data_url);
1328
+ $response = wp_remote_post($license_data_url, array(
1329
+ 'body' => array('k' => $license_key)
1330
+ ));
1331
+ if (is_wp_error($response)) {
1332
+ $this->logger->error($response);
1333
+ set_transient('newsletter_license_data', $response, DAY_IN_SECONDS);
1334
+ return $response;
1335
+ }
1336
+ }
1337
+
1338
+ $download_message = 'You can download all addons from www.thenewsletterplugin.com if your license is valid.';
1339
+
1340
+ if (wp_remote_retrieve_response_code($response) != '200') {
1341
+ $this->logger->error('license data error: ' . wp_remote_retrieve_response_code($response));
1342
+ $data = new WP_Error(wp_remote_retrieve_response_code($response), 'License validation service error. <br>' . $download_message);
1343
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1344
+ return $data;
1345
+ }
1346
+
1347
+ $json = wp_remote_retrieve_body($response);
1348
+ $data = json_decode($json);
1349
+
1350
+ if (!is_object($data)) {
1351
+ $this->logger->error($json);
1352
+ $data = new WP_Error(1, 'License validation service error. <br>' . $download_message);
1353
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1354
+ return $data;
1355
+ }
1356
+
1357
+ if (isset($data->message)) {
1358
+ $data = new WP_Error(1, $data->message . ' (check the license on Newsletter main settings)');
1359
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1360
+ return $data;
1361
+ }
1362
+
1363
+ $expiration = WEEK_IN_SECONDS;
1364
+ // If the license expires in few days, make the transient live only few days, so it will be refreshed
1365
+ if ($data->expire > time() && $data->expire - time() < WEEK_IN_SECONDS) {
1366
+ $expiration = $data->expire - time();
1367
+ }
1368
+ set_transient('newsletter_license_data', $data, $expiration);
1369
+
1370
+ return $data;
1371
+ }
1372
+
1373
+ /**
1374
+ * @deprecated
1375
+ * @param type $license_key
1376
+ * @return \WP_Error
1377
+ */
1378
+ public static function check_license($license_key) {
1379
+ $response = wp_remote_get('http://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/check.php?k=' . urlencode($license_key), array('sslverify' => false));
1380
+ if (is_wp_error($response)) {
1381
+ /* @var $response WP_Error */
1382
+ return new WP_Error(-1, 'It seems that your blog cannot contact the license validator. Ask your provider to unlock the HTTP/HTTPS connections to www.thenewsletterplugin.com<br>'
1383
+ . esc_html($response->get_error_code()) . ' - ' . esc_html($response->get_error_message()));
1384
+ } else if ($response['response']['code'] != 200) {
1385
+ return new WP_Error(-1, '[' . $response['response']['code'] . '] The license seems expired or not valid, please check your <a href="https://www.thenewsletterplugin.com/account">license code and status</a>, thank you.'
1386
+ . '<br>You can anyway download the professional extension from https://www.thenewsletterplugin.com.');
1387
+ } elseif ($expires = json_decode(wp_remote_retrieve_body($response))) {
1388
+ return array('expires' => $expires->expire, 'message' => 'Your license is valid and expires on ' . esc_html(date('Y-m-d', $expires->expire)));
1389
+ } else {
1390
+ return new WP_Error(-1, 'Unable to detect the license expiration. Debug data to report to the support: <code>' . esc_html(wp_remote_retrieve_body($response)) . '</code>');
1391
+ }
1392
+ }
1393
+
1394
+ function add_notice_to_chosen_profile_page_hook($post_states, $post) {
1395
+
1396
+ if ($post->ID == $this->options['page']) {
1397
+ $post_states[] = __('Newsletter plugin page, do not delete', 'newsletter');
1398
+ }
1399
+
1400
+ return $post_states;
1401
+ }
1402
+
1403
+ }
1404
+
1405
+ $newsletter = Newsletter::instance();
1406
+
1407
+ if (is_admin()) {
1408
+ require_once NEWSLETTER_DIR . '/system/system.php';
1409
+ }
1410
+
1411
+ require_once NEWSLETTER_DIR . '/subscription/subscription.php';
1412
+ require_once NEWSLETTER_DIR . '/unsubscription/unsubscription.php';
1413
+ require_once NEWSLETTER_DIR . '/profile/profile.php';
1414
+ require_once NEWSLETTER_DIR . '/emails/emails.php';
1415
+ require_once NEWSLETTER_DIR . '/users/users.php';
1416
+ require_once NEWSLETTER_DIR . '/statistics/statistics.php';
1417
+ require_once NEWSLETTER_DIR . '/widget/standard.php';
1418
+ require_once NEWSLETTER_DIR . '/widget/minimal.php';
readme.txt CHANGED
@@ -1,385 +1,392 @@
1
- === Newsletter ===
2
- Tags: newsletter, email marketing, welcome email, signup forms, contact, lead generation, marketing automation
3
- Tested up to: 5.9
4
- Stable tag: 7.3.6
5
- Contributors: satollo,webagile,michael-travan
6
- License: GPLv2 or later
7
- License URI: https://www.gnu.org/licenses/gpl-2.0.html
8
-
9
- Add a real newsletter system to your blog. For free. With unlimited newsletters and subscribers.
10
-
11
- == Description ==
12
-
13
- Newsletter is a **real newsletter and email marketing system** for your WordPress blog: perfect for list building, you can easily create, send and track e-mails, headache-free. It just works out of box!
14
-
15
- = Discover a completely rewritten composer =
16
-
17
- We redesigned our drag and drop composer to make your campaign creation even easier. Try it!
18
-
19
- = Main Features =
20
-
21
- * **Easy-to-use Drag and drop composer** to build responsive newsletters
22
- * **Unlimited subscribers** with statistics
23
- * **Unlimited newsletters** with tracking
24
- * **Subscription spam check** with domain/ip black lists, Akismet, captcha
25
- * **Delivery speed** fine control (from 12 emails per hour to as much as your blog can manage)
26
- * [WPML ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Polylang ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Translatepress ready](https://www.thenewsletterplugin.com/documentation/multilanguage)
27
- * All messages are **fully translatable** from administration panels (no .po/.mo file to edit)
28
- * [GDPR ready](https://www.thenewsletterplugin.com/documentation/gdpr-compliancy)
29
- * **Advanced targeting** with lists combinations like all in, at least one, not in and so on
30
- * Customizable **subscription widget**, **page** or **custom form**
31
- * Wordpress Users registration **seamless integration**
32
- * **Single** And **Double Opt-In** plus privacy checkbox for EU laws compliance
33
- * **Subscribers lists** to fine-target your campaigns
34
- * PHP API and REST API for coders and integrations
35
- * SMTP-Ready (with free addon)
36
- * Customizable Themes
37
- * **Status panel** to check your blog mailing capability and configuration
38
- * **Compatible with every SMTP plugin**: Post SMTP (aka Postman), WP Mail SMTP, Easy WP SMTP, Easy SMTP Mail, WP Mail Bank, ...
39
- * **Subscribers import** from file
40
- * Newsletter with Html and Text message versions
41
-
42
- = Find Us =
43
-
44
- Newsletter is a continuously evolving plugin. Stay tuned following us on [Facebook](https://www.facebook.com/thenewsletterplugin/) or [our site](https://www.thenewsletterplugin.com/).
45
-
46
- = Free Addons =
47
-
48
- Improve The Newsletter Plugin with these free addons:
49
-
50
- * [WP Registration Addon](https://www.thenewsletterplugin.com/documentation/wpusers-extension) - connects the WordPress standard and custom registration with Newsletter subscription. Optionally imports all registered users as subscribers.
51
- * [Archive Addon](https://www.thenewsletterplugin.com/documentation/archive-extension) - creates a simple blog page which lists all your sent newsletters
52
- * [Locked Content Addon](https://www.thenewsletterplugin.com/documentation/locked-content-extension) - open up your premium content only after subscription
53
- * [Newsletter REST API Addon](https://www.thenewsletterplugin.com/documentation/developers/newsletter-api-2/) - adds a tier of REST api to integrate with the Newsletter core services
54
- * [Sendinblue Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/sendinblue-extension/) - deliver your newsletters with Sendinblue
55
- * [SMTP Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/smtp-extension/) - deliver your newsletters with external SMTP
56
- * [Advanced Import Addon](https://www.thenewsletterplugin.com/documentation/addons/extended-features/advanced-import/) - import contact from file or copy and paste data with full mapping
57
-
58
- (*easily add them from our [Addons panel](https://www.thenewsletterplugin.com/documentation/install-extensions)*)
59
-
60
- = Addons on WordPress.org =
61
-
62
- * [RSS Composer Block](https://wordpress.org/plugins/newsletter-rss-block/) - (3rd party) a composer block which builds its content from a RSS feed
63
- * [Popup Maker Integration](https://wordpress.org/plugins/newsletter-popupmaker/) - (3rd party) integration of Newsletter forms with Popup Maker plugin
64
- * [BuddyPress integration](https://wordpress.org/plugins/newsletter-buddypress/) - subscription opt-in inside BuddyPress signup form
65
- * [WP User Manager addon for Newsletter](https://wordpress.org/plugins/wpum-newsletter/) - adds the subscription option on registration forms
66
-
67
- = Professional Addons =
68
-
69
- Need *more power*? Feel *something's missing*? The Newsletter Plugin features can be easily extended through our **premium, professional Addons**! Let us introduce just two of them : )
70
-
71
- * [Automated](https://www.thenewsletterplugin.com/automated) - generates and sends your newsletters using your blog last posts, even custom ones like events or products. Just sit and watch!
72
- * [Autoresponder](https://www.thenewsletterplugin.com/autoresponder) - creates email series to follow up your subscribers
73
- * [Extended Composer Blocks](https://www.thenewsletterplugin.com/composer) - adds new blocks to the drag & drop composer
74
- * [WooCommerce Integration](https://www.thenewsletterplugin.com/woocommerce) - subscribe customers to a mailing list and generate product newletters.
75
- * [Reports](https://www.thenewsletterplugin.com/reports) - improves the internal statistics collection system and provides better reports of data collected for each sent email. And retargeting. Neat.
76
- * [Leads](https://www.thenewsletterplugin.com/leads) adds a fancy subscription popup box or a fixed bar to your website that will boost your conversion rate
77
- * [Amazon SES and other mail providers integration](https://www.thenewsletterplugin.com/integrations) - seamlessly integrate Amazon SES and other email service providers with The Newsletter Plugin. Hassle-free.
78
- * [Contact Form 7 Integration](https://www.thenewsletterplugin.com/documentation/contact-form-7-extension) - integrate the subscription on Contact Form 7 forms
79
- * [Ninja Forms Integration](https://www.thenewsletterplugin.com/documentation/ninjaforms-extension) - integrate the subscription on Ninja Forms
80
- * [WP Forms Integration](https://www.thenewsletterplugin.com/documentation/wpforms-extension) - integrate the subscription on WP Forms
81
- * Events Manager and The Events Calendar (By Modern Tribe) integrations - easily add events to your newsletters
82
- * [Google Analytics](https://www.thenewsletterplugin.com/google-analytics) - track newsletter links with Google UTM tracking paramaters
83
- * [Subscribe on Comment](https://www.thenewsletterplugin.com/documentation/comments-extension) - adds the subscription option to your blog comment form
84
- * [Geolocation](https://www.thenewsletterplugin.com/documentation/geolocation-extension) - adds geolocation capability to target subscribers by location
85
-
86
- = GDPR =
87
-
88
- The Newsletter Plugin provides all the technical tools needed to achieve GDPR compliancy and we're continuously working to improve them and to give support even for specific use cases.
89
- The plugin does not collect users' own subscribers data, nor it has any access to those data: hence, we are not a data processor, so a data processing agreement is not needed.
90
- Anyway if you configure the plugin to use external services (usually an external mail delivery service) you should check with that service if some sort of agreement is required.
91
-
92
- = Support =
93
-
94
- We provide support for our plugin on [Wordpress.org forums](https://wordpress.org/support/plugin/newsletter) and through our [official forum](https://www.thenewsletterplugin.com/forums).
95
-
96
- Premium Users with an active license have access to one-to-one support via our [ticketing system](https://www.thenewsletterplugin.com/support-ticket).
97
-
98
- = Follow Us =
99
-
100
- * **Our Official Website** - [https://www.thenewsletterplugin.com/](https://www.thenewsletterplugin.com/)
101
- * **LinkedIn** - [https://www.linkedin.com/company/the-newsletter-plugin](https://www.linkedin.com/company/the-newsletter-plugin)
102
- * **Our Facebook Page** - [https://www.facebook.com/thenewsletterplugin](https://www.facebook.com/thenewsletterplugin)
103
- * **Our Twitter Account** - [https://twitter.com/newsletterwp](https://twitter.com/newsletterwp)
104
-
105
- == Frequently Asked Questions ==
106
-
107
- See the [Newsletter Forum](https://www.thenewsletterplugin.com/forums) to ask for help.
108
-
109
- For documentation start from [Newsletter documentation](https://www.thenewsletterplugin.com/documentation).
110
-
111
- Thank you, The Newsletter Team
112
-
113
- == Screenshots ==
114
-
115
- 1. The responsive email Drag & Drop composer
116
- 2. The plugin dashboard
117
- 3. The Reports extension
118
-
119
- == Changelog ==
120
-
121
- = 7.3.6 =
122
-
123
- * Improved composer reusability in other contexts
124
- * Removed obsolete composer code
125
- * Fixed default tracking for old theme-based neewsletters
126
- * Forced enconding on export (attempt)
127
- * WP 5.9 check
128
-
129
- = 7.3.5 =
130
-
131
- * WP 5.8.3 compatibility check
132
- * Fixed 2021 max year in date picker
133
- * Typos
134
-
135
- = 7.3.4 =
136
-
137
- * Fixed delivery fatal error management
138
- * Fixed link to the schduler dianostica panel
139
-
140
- = 7.3.3 =
141
-
142
- * Added "complained" status to subscriber filters
143
- * Fixed some links bringing to a "not allowed" page
144
- * Fixed a notice on delivery diagnostic page
145
- * Fixed logo width notice on header block
146
-
147
- = 7.3.2 =
148
-
149
- * Fixed the remote ip retrieval and clean up
150
- * Fixed header link to status page
151
- * Fixed database error with too long IPs
152
- * Fixed the subscription of cancelled addresses
153
- * Fixed sender and name customization
154
-
155
- = 7.3.1 =
156
-
157
- * Dropped old mailers support
158
- * Improved sending process and limits checking
159
- * Removed obsolete notifications
160
- * Fixed the wrong report on single email delivery speed
161
- * Added support for custom sending speed by addons
162
- * Improved excerpt generation (but it still depends on plugins and themes...)
163
- * Support for building button option on composer blocks
164
-
165
- = 7.3.0 =
166
-
167
- * Fixed header block layout with (logo only layout)
168
- * Check for conflicts on newsletter saving
169
- * Added the subscriber complained status (actually not managed automatically)
170
-
171
- = 7.2.9 =
172
-
173
- * Fixed generic action button confirmation popup not showing the message
174
- * [SECURITY] Pre parsing of IP addresses on security panel
175
- * [SECURITY] Comments allowed on IP address list on security panel
176
- * [COMPOSER] Fixed preset name/subject
177
- * Improved generated forms for accessibility
178
-
179
- = 7.2.8 =
180
-
181
- * Fixed the print_date() when no time is provided
182
- * Fixed date alignment on posts block
183
- * Folders reorganization
184
- * Social block with a new set of icons
185
- * Boosted performances of the lists management page (for big databases)
186
- * Added a new scheduler diagnostic panel
187
- * Seriously improved the cron statistics and diagnostic panel (check it out!)
188
- * Removed obsolete migration code from ancient versions
189
-
190
- = 7.2.7 =
191
-
192
- * Fixed JS error on composer sometimes preventing the correct initialization
193
-
194
- = 7.2.6 =
195
-
196
- * Fixed links on test emails sent to a free email address
197
- * Added attachment explanation
198
- * Added link to explain the use of the snippet
199
-
200
- = 7.2.5 =
201
-
202
- * Fixed subject not saved under specific circumstance
203
-
204
- = 7.2.4 =
205
-
206
- * Fixed the composer not starting for blog with SSL plugin but still HTTP configured on main WP settings
207
- * Changed labels on subscriber maintenance panel
208
- * Updated requirements for WP version
209
-
210
- = 7.2.3 =
211
-
212
- * [COMPOSER] Added approx. indicators of the subsject visibile part in Apple and Android clients (experimental)
213
- * [COMPOSER] New mobile version view directly while composing (experimental)
214
- * [COMPOSER] New test email to test subscribers or to specific email address
215
- * [COMPOSER] Fixed missing background when creating a new message from a preset
216
- * [COMPOSER] Added media selector to the CTA block
217
- * [COMPOSER] Added reference to the tags on HTML and Text blocks
218
- * [COMPOSER] Move the snippet (preheader) field near the subject
219
- * [COMPOSER] Footer block with three link options: unsubscribe, manage and view online
220
- * [COMPOSER] Improve font coherence between blocks (by default)
221
- * [ANTISPAM] Improved the antispam checks on subscription
222
- * [GENERAL] Removed obsolete folders and code
223
- * [NEWSLETTERS] Refactored subject ideas selector
224
- * [SUBSCRIPTION] Inverted extra profile fields and lists on standard subscription form
225
- * [GENERAL] IP address extracted checking proxy variables
226
- * [GENERAL] Improved sending stats collection and display for the delivery engine (not related to click/open stats)
227
-
228
- = 7.2.2 =
229
-
230
- * [COMPOSER] Posts block excerpt removed when set to 0-length
231
- * [GENERAL]Added special characters on test message
232
- * [GENERAL]Added more specific error for action calls on System/Status panel
233
- * [SUBSCRIPTION] Check for the _wp_amp_action_xhr_converted parameter by AMP plugin
234
-
235
- = 7.2.1 =
236
-
237
- * [GENERAL] Added more detailed admin logging
238
- * [NEWSLETTERS] Fixed scheduled date sometimes reset to 1/1/1970
239
-
240
- = 7.2.0 =
241
-
242
- * [PROFILE] Fixed activation email on profile change
243
- * [NEWSLETTERS] Extended year selection on newsletter scheduling
244
- * [DELIVERY] Breaking change: old enqueue() and flush() methods have been removed
245
- * [GENERAL] Fixed alert message on some buttons
246
- * [SUBSCRIPTION] Fixed error message on multiple subscriptions (when not allowed)
247
- * [GENERAL] Fixed erratic error log line on main log
248
-
249
- = 7.1.9 =
250
-
251
- * [GENERAL] Removed the encodign defatlt to Base 64 when not specified since it seems incompatible with some SMTP plugins
252
- * [GENERAL] Review some controls layout and behavior
253
- * [DEBUG] Improved logging on database errors
254
- * [GENERAL] Added TikTok, Discord and Twitch socials
255
- * [GENERAL] Fixed odd error reported related to the cron call statistics collection
256
- * [DEBUG] Added a delivery diagnostic panel
257
-
258
- = 7.1.8 =
259
-
260
- * [COMPOSER] Fixed alignment of single big image on Outlook Android
261
-
262
- = 7.1.7 =
263
-
264
- * [GENERAL] Fix of permalink onm email with multilanguage plugins
265
-
266
- = 7.1.6 =
267
-
268
- * [COMPOSER] Fixed one column big image Read more links
269
-
270
- = 7.1.5 =
271
-
272
- * [COMPOSER] Improve buttons on posts layout
273
- * [COMPOSER] Improved header layout and logo
274
- * [COMPOSER] Generally improved block spacing
275
-
276
- = 7.1.4 =
277
-
278
- * [COMPOSER] Fixed image block for Outlook
279
- * [GENERAL] Fix undefined values in Gutenberg block
280
-
281
- = 7.1.3 =
282
-
283
- * [COMPOSER] Improvements on blocks layout compatibility
284
- * [COMPOSER] Fixed preset global options saving
285
- * [COMPOSER] Content regeneration on preset selection
286
- * [GENERAL] Added to System menu the Site Health link, a not well known native page of WordPress with system information
287
- * [GENERAL] Added sending statistics reset button on status panel
288
-
289
- = 7.1.2 =
290
-
291
- * [ADDONS] Fixed the addons list
292
-
293
- = 7.1.1 =
294
-
295
- * [GENERAL] Improved profile related functions
296
- * [GENERAL] Fixed check on field names (thanks Peter P.)
297
- * [COMPOSER] Fix on preset selection
298
- * [GENERAL] Fix ajax subscription on error
299
-
300
- = 7.1.0 =
301
-
302
- * [COMPOSER] Added link to tags documentation to inject subscriber's data
303
- * [COMPOSER] Fixed layout of posts block for Outlook 365
304
- * [GENERAL] Improved caching of addons json (even on error)
305
- * [GENERAL] Status menu changed to System/Status and System/Logs
306
- * [API] Fixed the subscriber status management (the API Addon should be updated as well)
307
- * [GENERAL] Fixed management of fatal errors on sending
308
- * [GENERAL] Limited error string per message to 250 chars
309
- * [GENERAL] Fixed check on field names (thanks Peter P.)
310
-
311
-
312
- = 7.0.9 =
313
-
314
- * [CAPTCHA] Fixed button label translation
315
- * [DELIVERY] Mailing fatal error management with newsletter stop and reporting
316
- * [SMTP] Marked obsolete the internal SMTP and made available the free SMTP addon
317
- * [DELIVERY] Better management of delivery fatal errors with a new "error" status for newsletters
318
- * [GENERAL] New log files panel
319
- * [IMPORT] Old import panel replaced by the new (really better) import addon (file, copy and paste, bounced addresses import)
320
- * [NEWSLETTERS] It's now possible to specify the sender name and email per newsletter (thanks to Matthew S.)
321
-
322
- = 7.0.8 =
323
-
324
- * [SUBSCRIBERS] Changed action buttons
325
- * [GENERAL] Reorganization of CSS and removal of unused files
326
- * [DASHBOARD] New window open for links and fix of invalid URLs
327
- * [NEWSLETTERS] New action buttons
328
- * [GENERAL] Minor fixes and optimizations
329
- * [COMPOSER] Fixed rare size error on gif images
330
-
331
- = 7.0.7 =
332
-
333
- * [COMPOSER] Fixed a warning in some inline editable blocks
334
- * [NEWSLETTERS] Fixed style which made list labels badly readable
335
- * [NEWSLETTERS] Fixed style which made bullet lists white (not on delivered newsletters)
336
- * [GENERAL] Added some references to the not working scheduler warning
337
-
338
- = 7.0.6 =
339
-
340
- * [COMPOSER] CTA block not grabbing settings from the "old" blocks
341
- * [COMPOSER] CSS issue on button settings group
342
- * [COMPOSER] Posts block two columns layout (author and padding)
343
- * [IMPORT] Removed the old low-featured import (the free import addon has everything needed!)
344
- * [GENERAL] Compatibility check with WP 5.7
345
-
346
- = 7.0.5 =
347
-
348
- * [COMPOSER] Hero CTA button not working
349
- * Fixed to the hero block button
350
- * Added support for the SMTP addon
351
-
352
- = 7.0.4 =
353
-
354
- * [COMPOSER] Redesigned drag and drop composer
355
- * [COMPOSER] NEW! Save drag and drop composed newsletters as templates to reuse easily
356
- * Redesigned dashboard
357
- * Several bug and fixes
358
-
359
- = 7.0.3 =
360
-
361
- * Option to choose between unsubscription and profile link in the footer block
362
- * Direct image src URL for image block
363
- * New media selector for blocks
364
- * Minor fixes
365
- * Updated codemirror
366
- * Updated default theme
367
- * Fixed tag filter on posts block (when tags have parathesis or like)
368
- * Fixed composer visualization of bullet points
369
-
370
- = 7.0.2 =
371
-
372
- * Fixed media 2x resize
373
-
374
- = 7.0.1 =
375
-
376
- * Fixed enforced lists by language with Polylang
377
-
378
- = 7.0.0 =
379
-
380
- * Added multiple newsletter selection for deletion
381
- * Added text part on welcome and activation email
382
- * Added the attribute "show_form" to "newsletter" shortcode
383
- * Added filter on subscriber saving (from external systems) with wrong list field values
384
- * Added index.html on log folder
385
-
 
 
 
 
 
 
 
1
+ === Newsletter ===
2
+ Tags: newsletter, email marketing, welcome email, signup forms, contact, lead generation, marketing automation
3
+ Tested up to: 5.9
4
+ Stable tag: 7.3.7
5
+ Contributors: satollo,webagile,michael-travan
6
+ License: GPLv2 or later
7
+ License URI: https://www.gnu.org/licenses/gpl-2.0.html
8
+
9
+ Add a real newsletter system to your blog. For free. With unlimited newsletters and subscribers.
10
+
11
+ == Description ==
12
+
13
+ Newsletter is a **real newsletter and email marketing system** for your WordPress blog: perfect for list building, you can easily create, send and track e-mails, headache-free. It just works out of box!
14
+
15
+ = Discover a completely rewritten composer =
16
+
17
+ We redesigned our drag and drop composer to make your campaign creation even easier. Try it!
18
+
19
+ = Main Features =
20
+
21
+ * **Easy-to-use Drag and drop composer** to build responsive newsletters
22
+ * **Unlimited subscribers** with statistics
23
+ * **Unlimited newsletters** with tracking
24
+ * **Subscription spam check** with domain/ip black lists, Akismet, captcha
25
+ * **Delivery speed** fine control (from 12 emails per hour to as much as your blog can manage)
26
+ * [WPML ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Polylang ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Translatepress ready](https://www.thenewsletterplugin.com/documentation/multilanguage)
27
+ * All messages are **fully translatable** from administration panels (no .po/.mo file to edit)
28
+ * [GDPR ready](https://www.thenewsletterplugin.com/documentation/gdpr-compliancy)
29
+ * **Advanced targeting** with lists combinations like all in, at least one, not in and so on
30
+ * Customizable **subscription widget**, **page** or **custom form**
31
+ * Wordpress Users registration **seamless integration**
32
+ * **Single** And **Double Opt-In** plus privacy checkbox for EU laws compliance
33
+ * **Subscribers lists** to fine-target your campaigns
34
+ * PHP API and REST API for coders and integrations
35
+ * SMTP-Ready (with free addon)
36
+ * Customizable Themes
37
+ * **Status panel** to check your blog mailing capability and configuration
38
+ * **Compatible with every SMTP plugin**: Post SMTP (aka Postman), WP Mail SMTP, Easy WP SMTP, Easy SMTP Mail, WP Mail Bank, ...
39
+ * **Subscribers import** from file
40
+ * Newsletter with Html and Text message versions
41
+
42
+ = Find Us =
43
+
44
+ Newsletter is a continuously evolving plugin. Stay tuned following us on [Facebook](https://www.facebook.com/thenewsletterplugin/) or [our site](https://www.thenewsletterplugin.com/).
45
+
46
+ = Free Addons =
47
+
48
+ Improve The Newsletter Plugin with these free addons:
49
+
50
+ * [WP Registration Addon](https://www.thenewsletterplugin.com/documentation/wpusers-extension) - connects the WordPress standard and custom registration with Newsletter subscription. Optionally imports all registered users as subscribers.
51
+ * [Archive Addon](https://www.thenewsletterplugin.com/documentation/archive-extension) - creates a simple blog page which lists all your sent newsletters
52
+ * [Locked Content Addon](https://www.thenewsletterplugin.com/documentation/locked-content-extension) - open up your premium content only after subscription
53
+ * [Newsletter REST API Addon](https://www.thenewsletterplugin.com/documentation/developers/newsletter-api-2/) - adds a tier of REST api to integrate with the Newsletter core services
54
+ * [Sendinblue Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/sendinblue-extension/) - deliver your newsletters with Sendinblue
55
+ * [SMTP Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/smtp-extension/) - deliver your newsletters with external SMTP
56
+ * [Advanced Import Addon](https://www.thenewsletterplugin.com/documentation/addons/extended-features/advanced-import/) - import contact from file or copy and paste data with full mapping
57
+
58
+ (*easily add them from our [Addons panel](https://www.thenewsletterplugin.com/documentation/install-extensions)*)
59
+
60
+ = Addons on WordPress.org =
61
+
62
+ * [RSS Composer Block](https://wordpress.org/plugins/newsletter-rss-block/) - (3rd party) a composer block which builds its content from a RSS feed
63
+ * [Popup Maker Integration](https://wordpress.org/plugins/newsletter-popupmaker/) - (3rd party) integration of Newsletter forms with Popup Maker plugin
64
+ * [BuddyPress integration](https://wordpress.org/plugins/newsletter-buddypress/) - subscription opt-in inside BuddyPress signup form
65
+ * [WP User Manager addon for Newsletter](https://wordpress.org/plugins/wpum-newsletter/) - adds the subscription option on registration forms
66
+
67
+ = Professional Addons =
68
+
69
+ Need *more power*? Feel *something's missing*? The Newsletter Plugin features can be easily extended through our **premium, professional Addons**! Let us introduce just two of them : )
70
+
71
+ * [Automated](https://www.thenewsletterplugin.com/automated) - generates and sends your newsletters using your blog last posts, even custom ones like events or products. Just sit and watch!
72
+ * [Autoresponder](https://www.thenewsletterplugin.com/autoresponder) - creates email series to follow up your subscribers
73
+ * [Extended Composer Blocks](https://www.thenewsletterplugin.com/composer) - adds new blocks to the drag & drop composer
74
+ * [WooCommerce Integration](https://www.thenewsletterplugin.com/woocommerce) - subscribe customers to a mailing list and generate product newletters.
75
+ * [Reports](https://www.thenewsletterplugin.com/reports) - improves the internal statistics collection system and provides better reports of data collected for each sent email. And retargeting. Neat.
76
+ * [Leads](https://www.thenewsletterplugin.com/leads) adds a fancy subscription popup box or a fixed bar to your website that will boost your conversion rate
77
+ * [Amazon SES and other mail providers integration](https://www.thenewsletterplugin.com/integrations) - seamlessly integrate Amazon SES and other email service providers with The Newsletter Plugin. Hassle-free.
78
+ * [Contact Form 7 Integration](https://www.thenewsletterplugin.com/documentation/contact-form-7-extension) - integrate the subscription on Contact Form 7 forms
79
+ * [Ninja Forms Integration](https://www.thenewsletterplugin.com/documentation/ninjaforms-extension) - integrate the subscription on Ninja Forms
80
+ * [WP Forms Integration](https://www.thenewsletterplugin.com/documentation/wpforms-extension) - integrate the subscription on WP Forms
81
+ * Events Manager and The Events Calendar (By Modern Tribe) integrations - easily add events to your newsletters
82
+ * [Google Analytics](https://www.thenewsletterplugin.com/google-analytics) - track newsletter links with Google UTM tracking paramaters
83
+ * [Subscribe on Comment](https://www.thenewsletterplugin.com/documentation/comments-extension) - adds the subscription option to your blog comment form
84
+ * [Geolocation](https://www.thenewsletterplugin.com/documentation/geolocation-extension) - adds geolocation capability to target subscribers by location
85
+
86
+ = GDPR =
87
+
88
+ The Newsletter Plugin provides all the technical tools needed to achieve GDPR compliancy and we're continuously working to improve them and to give support even for specific use cases.
89
+ The plugin does not collect users' own subscribers data, nor it has any access to those data: hence, we are not a data processor, so a data processing agreement is not needed.
90
+ Anyway if you configure the plugin to use external services (usually an external mail delivery service) you should check with that service if some sort of agreement is required.
91
+
92
+ = Support =
93
+
94
+ We provide support for our plugin on [Wordpress.org forums](https://wordpress.org/support/plugin/newsletter) and through our [official forum](https://www.thenewsletterplugin.com/forums).
95
+
96
+ Premium Users with an active license have access to one-to-one support via our [ticketing system](https://www.thenewsletterplugin.com/support-ticket).
97
+
98
+ = Follow Us =
99
+
100
+ * **Our Official Website** - [https://www.thenewsletterplugin.com/](https://www.thenewsletterplugin.com/)
101
+ * **LinkedIn** - [https://www.linkedin.com/company/the-newsletter-plugin](https://www.linkedin.com/company/the-newsletter-plugin)
102
+ * **Our Facebook Page** - [https://www.facebook.com/thenewsletterplugin](https://www.facebook.com/thenewsletterplugin)
103
+ * **Our Twitter Account** - [https://twitter.com/newsletterwp](https://twitter.com/newsletterwp)
104
+
105
+ == Frequently Asked Questions ==
106
+
107
+ See the [Newsletter Forum](https://www.thenewsletterplugin.com/forums) to ask for help.
108
+
109
+ For documentation start from [Newsletter documentation](https://www.thenewsletterplugin.com/documentation).
110
+
111
+ Thank you, The Newsletter Team
112
+
113
+ == Screenshots ==
114
+
115
+ 1. The responsive email Drag & Drop composer
116
+ 2. The plugin dashboard
117
+ 3. The Reports extension
118
+
119
+ == Changelog ==
120
+
121
+ = 7.3.7 =
122
+
123
+ * Fixed unwanted redirects on subscription errors
124
+ * Fixed composer page HTML
125
+ * Minor fixes on PHP, CSS
126
+ * Fixed notice on image block
127
+
128
+ = 7.3.6 =
129
+
130
+ * Improved composer reusability in other contexts
131
+ * Removed obsolete composer code
132
+ * Fixed default tracking for old theme-based neewsletters
133
+ * Forced enconding on export (attempt)
134
+ * WP 5.9 check
135
+
136
+ = 7.3.5 =
137
+
138
+ * WP 5.8.3 compatibility check
139
+ * Fixed 2021 max year in date picker
140
+ * Typos
141
+
142
+ = 7.3.4 =
143
+
144
+ * Fixed delivery fatal error management
145
+ * Fixed link to the schduler dianostica panel
146
+
147
+ = 7.3.3 =
148
+
149
+ * Added "complained" status to subscriber filters
150
+ * Fixed some links bringing to a "not allowed" page
151
+ * Fixed a notice on delivery diagnostic page
152
+ * Fixed logo width notice on header block
153
+
154
+ = 7.3.2 =
155
+
156
+ * Fixed the remote ip retrieval and clean up
157
+ * Fixed header link to status page
158
+ * Fixed database error with too long IPs
159
+ * Fixed the subscription of cancelled addresses
160
+ * Fixed sender and name customization
161
+
162
+ = 7.3.1 =
163
+
164
+ * Dropped old mailers support
165
+ * Improved sending process and limits checking
166
+ * Removed obsolete notifications
167
+ * Fixed the wrong report on single email delivery speed
168
+ * Added support for custom sending speed by addons
169
+ * Improved excerpt generation (but it still depends on plugins and themes...)
170
+ * Support for building button option on composer blocks
171
+
172
+ = 7.3.0 =
173
+
174
+ * Fixed header block layout with (logo only layout)
175
+ * Check for conflicts on newsletter saving
176
+ * Added the subscriber complained status (actually not managed automatically)
177
+
178
+ = 7.2.9 =
179
+
180
+ * Fixed generic action button confirmation popup not showing the message
181
+ * [SECURITY] Pre parsing of IP addresses on security panel
182
+ * [SECURITY] Comments allowed on IP address list on security panel
183
+ * [COMPOSER] Fixed preset name/subject
184
+ * Improved generated forms for accessibility
185
+
186
+ = 7.2.8 =
187
+
188
+ * Fixed the print_date() when no time is provided
189
+ * Fixed date alignment on posts block
190
+ * Folders reorganization
191
+ * Social block with a new set of icons
192
+ * Boosted performances of the lists management page (for big databases)
193
+ * Added a new scheduler diagnostic panel
194
+ * Seriously improved the cron statistics and diagnostic panel (check it out!)
195
+ * Removed obsolete migration code from ancient versions
196
+
197
+ = 7.2.7 =
198
+
199
+ * Fixed JS error on composer sometimes preventing the correct initialization
200
+
201
+ = 7.2.6 =
202
+
203
+ * Fixed links on test emails sent to a free email address
204
+ * Added attachment explanation
205
+ * Added link to explain the use of the snippet
206
+
207
+ = 7.2.5 =
208
+
209
+ * Fixed subject not saved under specific circumstance
210
+
211
+ = 7.2.4 =
212
+
213
+ * Fixed the composer not starting for blog with SSL plugin but still HTTP configured on main WP settings
214
+ * Changed labels on subscriber maintenance panel
215
+ * Updated requirements for WP version
216
+
217
+ = 7.2.3 =
218
+
219
+ * [COMPOSER] Added approx. indicators of the subsject visibile part in Apple and Android clients (experimental)
220
+ * [COMPOSER] New mobile version view directly while composing (experimental)
221
+ * [COMPOSER] New test email to test subscribers or to specific email address
222
+ * [COMPOSER] Fixed missing background when creating a new message from a preset
223
+ * [COMPOSER] Added media selector to the CTA block
224
+ * [COMPOSER] Added reference to the tags on HTML and Text blocks
225
+ * [COMPOSER] Move the snippet (preheader) field near the subject
226
+ * [COMPOSER] Footer block with three link options: unsubscribe, manage and view online
227
+ * [COMPOSER] Improve font coherence between blocks (by default)
228
+ * [ANTISPAM] Improved the antispam checks on subscription
229
+ * [GENERAL] Removed obsolete folders and code
230
+ * [NEWSLETTERS] Refactored subject ideas selector
231
+ * [SUBSCRIPTION] Inverted extra profile fields and lists on standard subscription form
232
+ * [GENERAL] IP address extracted checking proxy variables
233
+ * [GENERAL] Improved sending stats collection and display for the delivery engine (not related to click/open stats)
234
+
235
+ = 7.2.2 =
236
+
237
+ * [COMPOSER] Posts block excerpt removed when set to 0-length
238
+ * [GENERAL]Added special characters on test message
239
+ * [GENERAL]Added more specific error for action calls on System/Status panel
240
+ * [SUBSCRIPTION] Check for the _wp_amp_action_xhr_converted parameter by AMP plugin
241
+
242
+ = 7.2.1 =
243
+
244
+ * [GENERAL] Added more detailed admin logging
245
+ * [NEWSLETTERS] Fixed scheduled date sometimes reset to 1/1/1970
246
+
247
+ = 7.2.0 =
248
+
249
+ * [PROFILE] Fixed activation email on profile change
250
+ * [NEWSLETTERS] Extended year selection on newsletter scheduling
251
+ * [DELIVERY] Breaking change: old enqueue() and flush() methods have been removed
252
+ * [GENERAL] Fixed alert message on some buttons
253
+ * [SUBSCRIPTION] Fixed error message on multiple subscriptions (when not allowed)
254
+ * [GENERAL] Fixed erratic error log line on main log
255
+
256
+ = 7.1.9 =
257
+
258
+ * [GENERAL] Removed the encodign defatlt to Base 64 when not specified since it seems incompatible with some SMTP plugins
259
+ * [GENERAL] Review some controls layout and behavior
260
+ * [DEBUG] Improved logging on database errors
261
+ * [GENERAL] Added TikTok, Discord and Twitch socials
262
+ * [GENERAL] Fixed odd error reported related to the cron call statistics collection
263
+ * [DEBUG] Added a delivery diagnostic panel
264
+
265
+ = 7.1.8 =
266
+
267
+ * [COMPOSER] Fixed alignment of single big image on Outlook Android
268
+
269
+ = 7.1.7 =
270
+
271
+ * [GENERAL] Fix of permalink onm email with multilanguage plugins
272
+
273
+ = 7.1.6 =
274
+
275
+ * [COMPOSER] Fixed one column big image Read more links
276
+
277
+ = 7.1.5 =
278
+
279
+ * [COMPOSER] Improve buttons on posts layout
280
+ * [COMPOSER] Improved header layout and logo
281
+ * [COMPOSER] Generally improved block spacing
282
+
283
+ = 7.1.4 =
284
+
285
+ * [COMPOSER] Fixed image block for Outlook
286
+ * [GENERAL] Fix undefined values in Gutenberg block
287
+
288
+ = 7.1.3 =
289
+
290
+ * [COMPOSER] Improvements on blocks layout compatibility
291
+ * [COMPOSER] Fixed preset global options saving
292
+ * [COMPOSER] Content regeneration on preset selection
293
+ * [GENERAL] Added to System menu the Site Health link, a not well known native page of WordPress with system information
294
+ * [GENERAL] Added sending statistics reset button on status panel
295
+
296
+ = 7.1.2 =
297
+
298
+ * [ADDONS] Fixed the addons list
299
+
300
+ = 7.1.1 =
301
+
302
+ * [GENERAL] Improved profile related functions
303
+ * [GENERAL] Fixed check on field names (thanks Peter P.)
304
+ * [COMPOSER] Fix on preset selection
305
+ * [GENERAL] Fix ajax subscription on error
306
+
307
+ = 7.1.0 =
308
+
309
+ * [COMPOSER] Added link to tags documentation to inject subscriber's data
310
+ * [COMPOSER] Fixed layout of posts block for Outlook 365
311
+ * [GENERAL] Improved caching of addons json (even on error)
312
+ * [GENERAL] Status menu changed to System/Status and System/Logs
313
+ * [API] Fixed the subscriber status management (the API Addon should be updated as well)
314
+ * [GENERAL] Fixed management of fatal errors on sending
315
+ * [GENERAL] Limited error string per message to 250 chars
316
+ * [GENERAL] Fixed check on field names (thanks Peter P.)
317
+
318
+
319
+ = 7.0.9 =
320
+
321
+ * [CAPTCHA] Fixed button label translation
322
+ * [DELIVERY] Mailing fatal error management with newsletter stop and reporting
323
+ * [SMTP] Marked obsolete the internal SMTP and made available the free SMTP addon
324
+ * [DELIVERY] Better management of delivery fatal errors with a new "error" status for newsletters
325
+ * [GENERAL] New log files panel
326
+ * [IMPORT] Old import panel replaced by the new (really better) import addon (file, copy and paste, bounced addresses import)
327
+ * [NEWSLETTERS] It's now possible to specify the sender name and email per newsletter (thanks to Matthew S.)
328
+
329
+ = 7.0.8 =
330
+
331
+ * [SUBSCRIBERS] Changed action buttons
332
+ * [GENERAL] Reorganization of CSS and removal of unused files
333
+ * [DASHBOARD] New window open for links and fix of invalid URLs
334
+ * [NEWSLETTERS] New action buttons
335
+ * [GENERAL] Minor fixes and optimizations
336
+ * [COMPOSER] Fixed rare size error on gif images
337
+
338
+ = 7.0.7 =
339
+
340
+ * [COMPOSER] Fixed a warning in some inline editable blocks
341
+ * [NEWSLETTERS] Fixed style which made list labels badly readable
342
+ * [NEWSLETTERS] Fixed style which made bullet lists white (not on delivered newsletters)
343
+ * [GENERAL] Added some references to the not working scheduler warning
344
+
345
+ = 7.0.6 =
346
+
347
+ * [COMPOSER] CTA block not grabbing settings from the "old" blocks
348
+ * [COMPOSER] CSS issue on button settings group
349
+ * [COMPOSER] Posts block two columns layout (author and padding)
350
+ * [IMPORT] Removed the old low-featured import (the free import addon has everything needed!)
351
+ * [GENERAL] Compatibility check with WP 5.7
352
+
353
+ = 7.0.5 =
354
+
355
+ * [COMPOSER] Hero CTA button not working
356
+ * Fixed to the hero block button
357
+ * Added support for the SMTP addon
358
+
359
+ = 7.0.4 =
360
+
361
+ * [COMPOSER] Redesigned drag and drop composer
362
+ * [COMPOSER] NEW! Save drag and drop composed newsletters as templates to reuse easily
363
+ * Redesigned dashboard
364
+ * Several bug and fixes
365
+
366
+ = 7.0.3 =
367
+
368
+ * Option to choose between unsubscription and profile link in the footer block
369
+ * Direct image src URL for image block
370
+ * New media selector for blocks
371
+ * Minor fixes
372
+ * Updated codemirror
373
+ * Updated default theme
374
+ * Fixed tag filter on posts block (when tags have parathesis or like)
375
+ * Fixed composer visualization of bullet points
376
+
377
+ = 7.0.2 =
378
+
379
+ * Fixed media 2x resize
380
+
381
+ = 7.0.1 =
382
+
383
+ * Fixed enforced lists by language with Polylang
384
+
385
+ = 7.0.0 =
386
+
387
+ * Added multiple newsletter selection for deletion
388
+ * Added text part on welcome and activation email
389
+ * Added the attribute "show_form" to "newsletter" shortcode
390
+ * Added filter on subscriber saving (from external systems) with wrong list field values
391
+ * Added index.html on log folder
392
+
subscription/subscription.php CHANGED
@@ -533,8 +533,8 @@ class NewsletterSubscription extends NewsletterModule {
533
 
534
  // Do we accept repeated subscriptions?
535
  if ($user != null && $subscription->if_exists === TNP_Subscription::EXISTING_ERROR) {
536
- $this->show_message('error', $user);
537
- //return new WP_Error('exists', 'Email address already registered and Newsletter sets to block repeated registrations. You can change this behavior or the user message above on subscription configuration panel.');
538
  }
539
 
540
 
533
 
534
  // Do we accept repeated subscriptions?
535
  if ($user != null && $subscription->if_exists === TNP_Subscription::EXISTING_ERROR) {
536
+ //$this->show_message('error', $user);
537
+ return new WP_Error('exists', 'Email address already registered and Newsletter sets to block repeated registrations. You can change this behavior or the user message above on subscription configuration panel.');
538
  }
539
 
540
 
users/index.php CHANGED
@@ -186,53 +186,27 @@ $controls->data['search_page'] ++;
186
  <?php foreach ($list as $s) { ?>
187
  <tr>
188
  <th scope="row" class="check-column" style="vertical-align: middle"><input class="tnp-selector" type="checkbox" name="ids[]" value="<?php echo $s->id; ?>"/></td>
189
- <td>
190
- <?php echo $s->id; ?>
191
- </td>
192
-
193
- <td>
194
- <?php echo esc_html($s->email); ?>
195
- </td>
196
-
197
- <td>
198
- <?php echo esc_html($s->name); ?> <?php echo esc_html($s->surname); ?>
199
- </td>
200
-
201
- <td>
202
- <small>
203
- <?php echo $this->get_user_status_label($s, true) ?>
204
- </small>
205
- </td>
206
-
207
  <?php if (isset($options['show_preferences']) && $options['show_preferences'] == 1) { ?>
208
- <td>
209
- <small>
210
- <?php
211
  $lists = $this->get_lists();
212
  foreach ($lists as $item) {
213
  $l = 'list_' . $item->id;
214
  if ($s->$l == 1)
215
  echo esc_html($item->name) . '<br>';
216
  }
217
- ?>
218
- </small>
219
- </td>
220
  <?php } ?>
221
-
222
- <td>
223
- <?php $controls->button_icon_edit($this->get_admin_page_url('edit') . '&amp;id=' . $s->id)?>
224
- </td>
225
-
226
- <td style="white-space: nowrap">
227
- <?php $controls->button_icon_delete($s->id); ?>
228
-
229
  <?php if ($s->status == "C") { ?>
230
  <?php $controls->button_icon('resend_welcome', 'fa-redo', __('Resend welcome', 'newsletter'), $s->id, true); ?>
231
  <?php } else { ?>
232
  <?php $controls->button_icon('resend', 'fa-redo', __('Resend activation', 'newsletter'), $s->id, true); ?>
233
- <?php } ?>
234
- </td>
235
-
236
  </tr>
237
  <?php } ?>
238
  </table>
186
  <?php foreach ($list as $s) { ?>
187
  <tr>
188
  <th scope="row" class="check-column" style="vertical-align: middle"><input class="tnp-selector" type="checkbox" name="ids[]" value="<?php echo $s->id; ?>"/></td>
189
+ <td><?php echo $s->id; ?></td>
190
+ <td><?php echo esc_html($s->email); ?></td>
191
+ <td><?php echo esc_html($s->name); ?> <?php echo esc_html($s->surname); ?></td>
192
+ <td><small><?php echo $this->get_user_status_label($s, true) ?></small></td>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  <?php if (isset($options['show_preferences']) && $options['show_preferences'] == 1) { ?>
194
+ <td><small><?php
 
 
195
  $lists = $this->get_lists();
196
  foreach ($lists as $item) {
197
  $l = 'list_' . $item->id;
198
  if ($s->$l == 1)
199
  echo esc_html($item->name) . '<br>';
200
  }
201
+ ?></small></td>
 
 
202
  <?php } ?>
203
+ <td><?php $controls->button_icon_edit($this->get_admin_page_url('edit') . '&amp;id=' . $s->id)?></td>
204
+ <td style="white-space: nowrap"><?php $controls->button_icon_delete($s->id); ?>
 
 
 
 
 
 
205
  <?php if ($s->status == "C") { ?>
206
  <?php $controls->button_icon('resend_welcome', 'fa-redo', __('Resend welcome', 'newsletter'), $s->id, true); ?>
207
  <?php } else { ?>
208
  <?php $controls->button_icon('resend', 'fa-redo', __('Resend activation', 'newsletter'), $s->id, true); ?>
209
+ <?php } ?></td>
 
 
210
  </tr>
211
  <?php } ?>
212
  </table>