Newsletter - Version 6.9.0

Version Description

  • Improved and optimized the lists field shortcode
  • Added the dropdown option to the lists field shortcode
  • Optimized the standard generated form aligned with the structure of the form generated by shortcodes
  • Added notices only for the administrator on online forms to better discover wrong or unwanted configurations
  • Added support for the next to come API version 2
  • Internally revised the subscription process
  • Fixed empty tag {profile_20}
  • Fix CSS class on mininmal form privacy field
  • Improved error message with notes for administrator on form submission errors
  • Improved error repoting on invalid tracking links (very rare anyway)
Download this release

Release Info

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

Code changes from version 6.8.9 to 6.9.0

admin.css CHANGED
@@ -2456,3 +2456,7 @@ span.tnp-email-status-sent {
2456
  .text-left {
2457
  text-align: left;
2458
  }
 
 
 
 
2456
  .text-left {
2457
  text-align: left;
2458
  }
2459
+
2460
+ .tab-min-height {
2461
+ min-height: 500px;
2462
+ }
includes/TNP.php CHANGED
@@ -231,6 +231,18 @@ class TNP {
231
  $user['sex'] = $newsletter->normalize_sex($params['gender']);
232
  }
233
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i ++) {
235
  if (isset($params['profile_' . $i])) {
236
  $user['profile_' . $i] = trim(stripslashes($params['profile_' . $i]));
231
  $user['sex'] = $newsletter->normalize_sex($params['gender']);
232
  }
233
 
234
+ if (!empty($params['country'])) {
235
+ $user['country'] = sanitize_text_field($params['country']);
236
+ }
237
+
238
+ if (!empty($params['region'])) {
239
+ $user['region'] = sanitize_text_field($params['region']);
240
+ }
241
+
242
+ if (!empty($params['city'])) {
243
+ $user['city'] = sanitize_text_field($params['city']);
244
+ }
245
+
246
  for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i ++) {
247
  if (isset($params['profile_' . $i])) {
248
  $user['profile_' . $i] = trim(stripslashes($params['profile_' . $i]));
includes/antispam.php ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class NewsletterAntispam {
4
+
5
+ var $options;
6
+ var $logger;
7
+
8
+ public function instance() {
9
+ static $instance;
10
+ if (!$instance) {
11
+ $instance = new NewsletterAntispam();
12
+ }
13
+ return $instance;
14
+ }
15
+
16
+ public function __construct() {
17
+ $this->options = NewsletterSubscription::instance()->get_options('antibot');
18
+ $this->logger = new NewsletterLogger('antispam');
19
+ }
20
+
21
+ /**
22
+ * $email must be cleaned using the is_email() function.
23
+ *
24
+ * @param TNP_Subscription $subscription
25
+ */
26
+ function is_spam($subscription) {
27
+
28
+ $email = $subscription->data->email;
29
+ $ip = $subscription->data->ip;
30
+
31
+
32
+ $full_name = $subscription->data->name . ' ' . $subscription->data->surname;
33
+ if ($this->is_spam_text($full_name)) {
34
+ $this->logger->fatal($email . ' - ' . $ip . ' - Name with http: ' . $full_name);
35
+ return true;
36
+ }
37
+
38
+ if ($this->is_ip_blacklisted($ip)) {
39
+ $this->logger->fatal($email . ' - ' . $ip . ' - IP blacklisted');
40
+ return true;
41
+ }
42
+
43
+ if ($this->is_address_blacklisted($email)) {
44
+ $this->logger->fatal($email . ' - ' . $ip . ' - Address blacklisted');
45
+ return true;
46
+ }
47
+
48
+ // Akismet check
49
+ $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
50
+ $referrer = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '';
51
+ if ( $this->is_spam_by_akismet( $email, $full_name, $ip, $user_agent, $referrer ) ) {
52
+ $this->logger->fatal( $email . ' - ' . $ip . ' - Akismet blocked' );
53
+
54
+ return true;
55
+ }
56
+
57
+ // Flood check
58
+ if ($this->is_flood($email, $ip)) {
59
+ $this->logger->fatal($email . ' - ' . $ip . ' - Antiflood triggered');
60
+ return true;
61
+ }
62
+
63
+ // if ($this->is_missing_domain_mx($email)) {
64
+ // $this->logger->fatal($email . ' - ' . $ip . ' - MX check failed');
65
+ // header("HTTP/1.0 404 Not Found");
66
+ // return true;
67
+ // }
68
+
69
+ return false;
70
+ }
71
+
72
+ function is_address_blacklisted($email) {
73
+
74
+ if (empty($this->options['address_blacklist'])) {
75
+ return false;
76
+ }
77
+
78
+ $this->logger->debug('Address blacklist check');
79
+ $rev_email = strrev($email);
80
+ foreach ($this->options['address_blacklist'] as $item) {
81
+ if (strpos($rev_email, strrev($item)) === 0) {
82
+ return true;
83
+ }
84
+ }
85
+ return false;
86
+ }
87
+
88
+ function is_ip_blacklisted($ip) {
89
+
90
+ if (empty($this->options['ip_blacklist'])) {
91
+ return false;
92
+ }
93
+ $this->logger->debug('IP blacklist check');
94
+ foreach ($this->options['ip_blacklist'] as $item) {
95
+ if ($this->ip_match($ip, $item)) {
96
+ return true;
97
+ }
98
+ }
99
+ return false;
100
+ }
101
+
102
+ function is_missing_domain_mx($email) {
103
+ // Actually not fully implemented
104
+ return false;
105
+
106
+ if (empty($this->options['domain_check'])) {
107
+ return false;
108
+ }
109
+
110
+ $this->logger->debug('Domain MX check');
111
+ list($local, $domain) = explode('@', $email);
112
+
113
+ $hosts = array();
114
+ if (!getmxrr($domain, $hosts)) {
115
+ return true;
116
+ }
117
+ return false;
118
+ }
119
+
120
+ function is_flood($email, $ip) {
121
+ global $wpdb;
122
+
123
+ if (empty($this->options['antiflood'])) {
124
+ return false;
125
+ }
126
+
127
+ $this->logger->debug('Antiflood check');
128
+
129
+ $updated = $wpdb->get_var($wpdb->prepare("select updated from " . NEWSLETTER_USERS_TABLE . " where ip=%s or email=%s order by updated desc limit 1", $ip, $email));
130
+
131
+ if ($updated && time() - $updated < $this->options['antiflood']) {
132
+ return true;
133
+ }
134
+
135
+ return false;
136
+ }
137
+
138
+ function is_spam_text($text) {
139
+ if (stripos($text, 'http:') !== false || stripos($text, 'https:') !== false) {
140
+ return true;
141
+ }
142
+ if (stripos($text, 'www.') !== false) {
143
+ return true;
144
+ }
145
+
146
+ return false;
147
+ }
148
+
149
+ function is_spam_by_akismet($email, $name, $ip, $agent, $referrer) {
150
+
151
+ if (!class_exists('Akismet')) {
152
+ return false;
153
+ }
154
+
155
+ if (empty($this->options['akismet'])) {
156
+ return false;
157
+ }
158
+
159
+ $this->logger->debug('Akismet check');
160
+ $request = 'blog=' . urlencode(home_url()) . '&referrer=' . urlencode($referrer) .
161
+ '&user_agent=' . urlencode($agent) .
162
+ '&comment_type=signup' .
163
+ '&comment_author_email=' . urlencode($email) .
164
+ '&user_ip=' . urlencode($ip);
165
+ if (!empty($name)) {
166
+ $request .= '&comment_author=' . urlencode($name);
167
+ }
168
+
169
+ $response = Akismet::http_post($request, 'comment-check');
170
+
171
+ if ($response && $response[1] == 'true') {
172
+ return true;
173
+ }
174
+ return false;
175
+ }
176
+
177
+ function ip_match($ip, $range) {
178
+ if (empty($ip))
179
+ return false;
180
+ if (strpos($range, '/')) {
181
+ list ($subnet, $bits) = explode('/', $range);
182
+ $ip = ip2long($ip);
183
+ $subnet = ip2long($subnet);
184
+ $mask = -1 << (32 - $bits);
185
+ $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
186
+ return ($ip & $mask) == $subnet;
187
+ } else {
188
+ return strpos($range, $ip) === 0;
189
+ }
190
+ }
191
+
192
+ }
includes/module.php CHANGED
@@ -51,6 +51,10 @@ class TNP_List {
51
  var $show_on_subscription;
52
  var $show_on_profile;
53
 
 
 
 
 
54
  }
55
 
56
  /**
@@ -99,12 +103,16 @@ class TNP_Profile {
99
  return $this->rule == 1;
100
  }
101
 
 
 
 
 
102
  }
103
 
104
  class TNP_Profile_Service {
105
 
106
  /**
107
- *
108
  * @param string $language
109
  * @param string $type
110
  * @return TNP_Profile[]
@@ -139,7 +147,8 @@ class TNP_Profile_Service {
139
  static function get_profile_by_id($id, $language = '') {
140
 
141
  $profiles = self::get_profiles($language);
142
- if (isset($profiles[$id])) return $profiles[$id];
 
143
  return null;
144
  }
145
 
@@ -171,6 +180,110 @@ class TNP_Profile_Service {
171
 
172
  }
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  /**
175
  * @property int $id The subscriber unique identifier
176
  * @property string $email The subscriber email
@@ -180,7 +293,7 @@ class TNP_Profile_Service {
180
  * @property string $language The subscriber language code 2 chars lowercase
181
  * @property string $token The subscriber secret token
182
  */
183
- abstract class TNP_User {
184
 
185
  const STATUS_CONFIRMED = 'C';
186
  const STATUS_NOT_CONFIRMED = 'S';
@@ -737,7 +850,7 @@ class NewsletterModule {
737
  }
738
 
739
  function admin_menu() {
740
-
741
  }
742
 
743
  function add_menu_page($page, $title, $capability = '') {
@@ -1099,6 +1212,24 @@ class NewsletterModule {
1099
  return $r;
1100
  }
1101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1102
  /**
1103
  * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
1104
  *
@@ -1219,34 +1350,43 @@ class NewsletterModule {
1219
  if (empty($data['list_' . $i])) {
1220
  continue;
1221
  }
1222
- $list = new TNP_List();
1223
- $list->name = $data['list_' . $i];
1224
- $list->id = $i;
1225
-
1226
- // New format
1227
- if (isset($data['list_' . $i . '_subscription'])) {
1228
- $list->forced = !empty($data['list_' . $i . '_forced']);
1229
- $list->status = empty($data['list_' . $i . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1230
- $list->checked = $data['list_' . $i . '_subscription'] == 2;
1231
- $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && !empty($data['list_' . $i . '_subscription']) && !$list->forced;
1232
- $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && !empty($data['list_' . $i . '_profile']);
1233
- } else {
1234
- $list->forced = !empty($data['list_' . $i . '_forced']);
1235
- $list->status = empty($data['list_' . $i . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1236
- $list->checked = !empty($data['list_' . $i . '_checked']);
1237
- $list->show_on_subscription = $data['list_' . $i . '_status'] == 2 && !$list->forced;
1238
- $list->show_on_profile = $data['list_' . $i . '_status'] == 1 || $data['list_' . $i . '_status'] == 2;
1239
- }
1240
- if (empty($data['list_' . $i . '_languages'])) {
1241
- $list->languages = array();
1242
- } else {
1243
- $list->languages = $data['list_' . $i . '_languages'];
1244
- }
1245
  $lists[$language]['' . $list->id] = $list;
1246
  }
1247
  return $lists[$language];
1248
  }
1249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1250
  /**
1251
  * Returns an array of TNP_List objects of lists that are public.
1252
  * @return TNP_List[]
@@ -1777,7 +1917,7 @@ class NewsletterModule {
1777
  $text = str_replace('{key}', $nk, $text);
1778
  $text = str_replace('%7Bkey%7D', $nk, $text);
1779
 
1780
- for ($i = 1; $i < NEWSLETTER_PROFILE_MAX; $i++) {
1781
  $p = 'profile_' . $i;
1782
  $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
1783
  }
@@ -2204,7 +2344,7 @@ class NewsletterModule {
2204
 
2205
  function dienow($message, $admin_message = null, $http_code = 200) {
2206
  if ($admin_message && current_user_can('administrator')) {
2207
- $message .= '<br><br><strong>Text below only visibile to administrarors</strong><br>';
2208
  $message .= $admin_message;
2209
  }
2210
  wp_die($message, $http_code);
51
  var $show_on_subscription;
52
  var $show_on_profile;
53
 
54
+ function is_private() {
55
+ return $this->status == self::STATUS_PRIVATE;
56
+ }
57
+
58
  }
59
 
60
  /**
103
  return $this->rule == 1;
104
  }
105
 
106
+ function is_private() {
107
+ return $this->status == self::STATUS_PRIVATE;
108
+ }
109
+
110
  }
111
 
112
  class TNP_Profile_Service {
113
 
114
  /**
115
+ *
116
  * @param string $language
117
  * @param string $type
118
  * @return TNP_Profile[]
147
  static function get_profile_by_id($id, $language = '') {
148
 
149
  $profiles = self::get_profiles($language);
150
+ if (isset($profiles[$id]))
151
+ return $profiles[$id];
152
  return null;
153
  }
154
 
180
 
181
  }
182
 
183
+ /**
184
+ * Represents the set of data collected by a subscription interface (form, API, ...). Only a valid
185
+ * email is mandatory.
186
+ */
187
+ class TNP_Subscription_Data {
188
+
189
+ var $email = null;
190
+ var $name = null;
191
+ var $surname = null;
192
+ var $sex = null;
193
+ var $language = null;
194
+ var $referrer = null;
195
+ var $http_referrer = null;
196
+ var $ip = null;
197
+ var $country = null;
198
+ var $region = null;
199
+ var $city = null;
200
+
201
+ /**
202
+ * Associative array id=>value of lists chosen by the subscriber. A list can be set to
203
+ * 0 meaning the subscriber does not want to be in that list.
204
+ * The lists must be public: non public lists are filtered.
205
+ * @var array
206
+ */
207
+ var $lists = [];
208
+ var $profiles = [];
209
+
210
+ function merge_in($subscriber) {
211
+ if (!$subscriber)
212
+ $subscriber = new TNP_User();
213
+ if (!empty($this->email))
214
+ $subscriber->email = $this->email;
215
+ if (!empty($this->name))
216
+ $subscriber->name = $this->name;
217
+ if (!empty($this->surname))
218
+ $subscriber->surname = $this->surname;
219
+ if (!empty($this->sex))
220
+ $subscriber->sex = $this->sex;
221
+ if (!empty($this->language))
222
+ $subscriber->language = $this->language;
223
+ if (!empty($this->ip))
224
+ $subscriber->ip = $this->ip;
225
+ if (!empty($this->referrer))
226
+ $subscriber->referrer = $this->referrer;
227
+ if (!empty($this->http_referrer))
228
+ $subscriber->http_referrer = $this->http_referrer;
229
+ if (!empty($this->country))
230
+ $subscriber->country = $this->country;
231
+ if (!empty($this->region))
232
+ $subscriber->region = $this->region;
233
+ if (!empty($this->city))
234
+ $subscriber->city = $this->city;
235
+
236
+
237
+ foreach ($this->lists as $id => $value) {
238
+ $key = 'list_' . $id;
239
+ $subscriber->$key = $value;
240
+ }
241
+
242
+ // Profile
243
+ foreach ($this->profiles as $id => $value) {
244
+ $key = 'profile_' . $id;
245
+ $subscriber->$key = $value;
246
+ }
247
+ }
248
+
249
+ }
250
+
251
+ /**
252
+ * Represents a subscription request with the subscriber data and actions to be taken by
253
+ * the subscription engine (spam check, notifications, ...).
254
+ */
255
+ class TNP_Subscription {
256
+
257
+ const EXISTING_ERROR = 1;
258
+ const EXISTING_MERGE = 0;
259
+
260
+ /**
261
+ * Subscriber data following the syntax of the TNP_User
262
+ * @var TNP_Subscription_Data
263
+ */
264
+ var $data;
265
+ var $spamcheck = true;
266
+
267
+
268
+ // 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
269
+ // optin as empty (for default), 'single' or 'double'.
270
+ var $optin = null;
271
+ // What to do with an existing subscriber???
272
+ var $if_exists = self::EXISTING_MERGE;
273
+
274
+ /**
275
+ * Determines if the welcome or activation email should be sent. Note: sometime an activation email is sent disregarding
276
+ * this setting.
277
+ * @var boolean
278
+ */
279
+ var $send_emails = true;
280
+
281
+ public function __construct() {
282
+ $this->data = new TNP_Subscription_Data();
283
+ }
284
+
285
+ }
286
+
287
  /**
288
  * @property int $id The subscriber unique identifier
289
  * @property string $email The subscriber email
293
  * @property string $language The subscriber language code 2 chars lowercase
294
  * @property string $token The subscriber secret token
295
  */
296
+ class TNP_User {
297
 
298
  const STATUS_CONFIRMED = 'C';
299
  const STATUS_NOT_CONFIRMED = 'S';
850
  }
851
 
852
  function admin_menu() {
853
+
854
  }
855
 
856
  function add_menu_page($page, $title, $capability = '') {
1212
  return $r;
1213
  }
1214
 
1215
+ /**
1216
+ *
1217
+ * @global wpdb $wpdb
1218
+ * @param string $email
1219
+ * @return TNP_User
1220
+ */
1221
+ function get_user_by_email($email) {
1222
+ global $wpdb;
1223
+
1224
+ $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $email));
1225
+
1226
+ if ($wpdb->last_error) {
1227
+ $this->logger->error($wpdb->last_error);
1228
+ return null;
1229
+ }
1230
+ return $r;
1231
+ }
1232
+
1233
  /**
1234
  * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
1235
  *
1350
  if (empty($data['list_' . $i])) {
1351
  continue;
1352
  }
1353
+ $list = $this->create_tnp_list_from_db_lists_array($data, $i);
1354
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1355
  $lists[$language]['' . $list->id] = $list;
1356
  }
1357
  return $lists[$language];
1358
  }
1359
 
1360
+ public function create_tnp_list_from_db_lists_array( $db_lists_array, $list_id ) {
1361
+
1362
+ $list = new TNP_List();
1363
+ $list->name = $db_lists_array[ 'list_' . $list_id ];
1364
+ $list->id = $list_id;
1365
+
1366
+ // New format
1367
+ if ( isset( $db_lists_array[ 'list_' . $list_id . '_subscription' ] ) ) {
1368
+ $list->forced = ! empty( $db_lists_array[ 'list_' . $list_id . '_forced' ] );
1369
+ $list->status = empty( $db_lists_array[ 'list_' . $list_id . '_status' ] ) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1370
+ $list->checked = $db_lists_array[ 'list_' . $list_id . '_subscription' ] == 2;
1371
+ $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && ! empty( $db_lists_array[ 'list_' . $list_id . '_subscription' ] ) && ! $list->forced;
1372
+ $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && ! empty( $db_lists_array[ 'list_' . $list_id . '_profile' ] );
1373
+ } else {
1374
+ $list->forced = ! empty( $db_lists_array[ 'list_' . $list_id . '_forced' ] );
1375
+ $list->status = empty( $db_lists_array[ 'list_' . $list_id . '_status' ] ) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
1376
+ $list->checked = ! empty( $db_lists_array[ 'list_' . $list_id . '_checked' ] );
1377
+ $list->show_on_subscription = $db_lists_array[ 'list_' . $list_id . '_status' ] == 2 && ! $list->forced;
1378
+ $list->show_on_profile = $db_lists_array[ 'list_' . $list_id . '_status' ] == 1 || $db_lists_array[ 'list_' . $list_id . '_status' ] == 2;
1379
+ }
1380
+ if ( empty( $db_lists_array[ 'list_' . $list_id . '_languages' ] ) ) {
1381
+ $list->languages = array();
1382
+ } else {
1383
+ $list->languages = $db_lists_array[ 'list_' . $list_id . '_languages' ];
1384
+ }
1385
+
1386
+ return $list;
1387
+
1388
+ }
1389
+
1390
  /**
1391
  * Returns an array of TNP_List objects of lists that are public.
1392
  * @return TNP_List[]
1917
  $text = str_replace('{key}', $nk, $text);
1918
  $text = str_replace('%7Bkey%7D', $nk, $text);
1919
 
1920
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1921
  $p = 'profile_' . $i;
1922
  $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
1923
  }
2344
 
2345
  function dienow($message, $admin_message = null, $http_code = 200) {
2346
  if ($admin_message && current_user_can('administrator')) {
2347
+ $message .= '<br><br><strong>Text below only visibile to administrators</strong><br>';
2348
  $message .= $admin_message;
2349
  }
2350
  wp_die($message, $http_code);
plugin.php CHANGED
@@ -4,7 +4,7 @@
4
  Plugin Name: Newsletter
5
  Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
  Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
- Version: 6.8.9
8
  Author: Stefano Lissa & The Newsletter Team
9
  Author URI: https://www.thenewsletterplugin.com
10
  Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
@@ -35,7 +35,7 @@ if (version_compare(phpversion(), '5.6', '<')) {
35
  return;
36
  }
37
 
38
- define('NEWSLETTER_VERSION', '6.8.9');
39
 
40
  global $newsletter, $wpdb;
41
 
@@ -518,11 +518,11 @@ class Newsletter extends NewsletterModule {
518
  wp_enqueue_media();
519
 
520
  wp_enqueue_style('tnp-admin-font', 'https://use.typekit.net/jlj2wjy.css');
521
- wp_enqueue_style('tnp-admin-fontawesome', $newsletter_url . '/vendor/fa/css/all.min.css', [], '6.6.0');
522
- wp_enqueue_style('tnp-admin-jquery-ui', $newsletter_url . '/vendor/jquery-ui/jquery-ui.min.css', [], '6.6.0');
523
- wp_enqueue_style('tnp-admin-dropdown', $newsletter_url . '/css/dropdown.css', [], '6.6.0');
524
- wp_enqueue_style('tnp-admin-fields', $newsletter_url . '/css/fields.css', [], '6.6.0');
525
- wp_enqueue_style('tnp-admin-widgets', $newsletter_url . '/css/widgets.css', [], '6.6.0');
526
  wp_enqueue_style('tnp-admin', $newsletter_url . '/admin.css',
527
  array(
528
  'tnp-admin-font',
@@ -531,9 +531,9 @@ class Newsletter extends NewsletterModule {
531
  'tnp-admin-dropdown',
532
  'tnp-admin-fields',
533
  'tnp-admin-widgets'
534
- ), filemtime(NEWSLETTER_DIR . '/admin.css'));
535
 
536
- wp_enqueue_script('tnp-admin', $newsletter_url . '/admin.js', array('jquery'), time());
537
 
538
  $translations_array = array(
539
  'save_to_update_counter' => __('Save the newsletter to update the counter!', 'newsletter')
@@ -574,12 +574,6 @@ class Newsletter extends NewsletterModule {
574
  delete_option('newsletter_show_welcome');
575
  wp_redirect(admin_url('admin.php?page=newsletter_main_welcome'));
576
  }
577
-
578
- // https://developer.wordpress.org/plugins/privacy/suggesting-text-for-the-site-privacy-policy/
579
- // https://make.wordpress.org/core/2018/05/17/4-9-6-update-guide/
580
- //if (function_exists('wp_add_privacy_policy_content')) {
581
- //wp_add_privacy_policy_content('Newsletter', wp_kses_post( wpautop( $content, false )));
582
- //}
583
  }
584
 
585
  function hook_admin_head() {
@@ -1022,29 +1016,6 @@ class Newsletter extends NewsletterModule {
1022
  wp_clear_scheduled_hook('newsletter');
1023
  }
1024
 
1025
- function shortcode_newsletter_form($attrs, $content) {
1026
- return $this->form($attrs['form']);
1027
- }
1028
-
1029
- function form($number = null) {
1030
- if ($number == null)
1031
- return $this->subscription_form();
1032
- $options = get_option('newsletter_forms');
1033
-
1034
- $form = $options['form_' . $number];
1035
-
1036
- if (stripos($form, '<form') !== false) {
1037
- $form = str_replace('{newsletter_url}', plugins_url('newsletter/do/subscribe.php'), $form);
1038
- } else {
1039
- $form = '<form method="post" action="' . plugins_url('newsletter/do/subscribe.php') . '" onsubmit="return newsletter_check(this)">' .
1040
- $form . '</form>';
1041
- }
1042
-
1043
- $form = $this->replace_lists($form);
1044
-
1045
- return $form;
1046
- }
1047
-
1048
  function find_file($file1, $file2) {
1049
  if (is_file($file1))
1050
  return $file1;
@@ -1065,6 +1036,7 @@ class Newsletter extends NewsletterModule {
1065
  $value->response = array();
1066
  }
1067
 
 
1068
  if ($extra_response) {
1069
  //$this->logger->debug('Already updated');
1070
  $value->response = array_merge($value->response, $extra_response);
@@ -1073,6 +1045,7 @@ class Newsletter extends NewsletterModule {
1073
 
1074
  $extensions = $this->getTnpExtensions();
1075
 
 
1076
  if (!$extensions) {
1077
  return $value;
1078
  }
@@ -1082,6 +1055,7 @@ class Newsletter extends NewsletterModule {
1082
  unset($value->no_update[$extension->wp_slug]);
1083
  }
1084
 
 
1085
  if (!NEWSLETTER_EXTENSION_UPDATE) {
1086
  //$this->logger->info('Updates disabled');
1087
  return $value;
@@ -1089,6 +1063,7 @@ class Newsletter extends NewsletterModule {
1089
 
1090
  include_once(ABSPATH . 'wp-admin/includes/plugin.php');
1091
 
 
1092
  if (!function_exists('get_plugin_data')) {
1093
  //$this->logger->error('No get_plugin_data function available!');
1094
  return $value;
@@ -1096,6 +1071,8 @@ class Newsletter extends NewsletterModule {
1096
 
1097
  $license_key = $this->get_license_key();
1098
 
 
 
1099
  foreach ($extensions as $extension) {
1100
 
1101
  // Patch for names convention
@@ -1142,6 +1119,7 @@ class Newsletter extends NewsletterModule {
1142
  //$this->logger->debug('There is a new version');
1143
  $extra_response[$extension->plugin] = $plugin;
1144
  } else {
 
1145
  //$this->logger->debug('There is NOT a new version');
1146
  $value->no_update[$extension->plugin] = $plugin;
1147
  }
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: 6.9.0
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.
35
  return;
36
  }
37
 
38
+ define('NEWSLETTER_VERSION', '6.9.0');
39
 
40
  global $newsletter, $wpdb;
41
 
518
  wp_enqueue_media();
519
 
520
  wp_enqueue_style('tnp-admin-font', 'https://use.typekit.net/jlj2wjy.css');
521
+ wp_enqueue_style('tnp-admin-fontawesome', $newsletter_url . '/vendor/fa/css/all.min.css', [], NEWSLETTER_VERSION);
522
+ wp_enqueue_style('tnp-admin-jquery-ui', $newsletter_url . '/vendor/jquery-ui/jquery-ui.min.css', [], NEWSLETTER_VERSION);
523
+ wp_enqueue_style('tnp-admin-dropdown', $newsletter_url . '/css/dropdown.css', [], NEWSLETTER_VERSION);
524
+ wp_enqueue_style('tnp-admin-fields', $newsletter_url . '/css/fields.css', [], NEWSLETTER_VERSION);
525
+ wp_enqueue_style('tnp-admin-widgets', $newsletter_url . '/css/widgets.css', [], NEWSLETTER_VERSION);
526
  wp_enqueue_style('tnp-admin', $newsletter_url . '/admin.css',
527
  array(
528
  'tnp-admin-font',
531
  'tnp-admin-dropdown',
532
  'tnp-admin-fields',
533
  'tnp-admin-widgets'
534
+ ), NEWSLETTER_VERSION);
535
 
536
+ wp_enqueue_script('tnp-admin', $newsletter_url . '/admin.js', ['jquery'], NEWSLETTER_VERSION);
537
 
538
  $translations_array = array(
539
  'save_to_update_counter' => __('Save the newsletter to update the counter!', 'newsletter')
574
  delete_option('newsletter_show_welcome');
575
  wp_redirect(admin_url('admin.php?page=newsletter_main_welcome'));
576
  }
 
 
 
 
 
 
577
  }
578
 
579
  function hook_admin_head() {
1016
  wp_clear_scheduled_hook('newsletter');
1017
  }
1018
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1019
  function find_file($file1, $file2) {
1020
  if (is_file($file1))
1021
  return $file1;
1036
  $value->response = array();
1037
  }
1038
 
1039
+ // Already computed? Use it! (this filter is called many times in a single request)
1040
  if ($extra_response) {
1041
  //$this->logger->debug('Already updated');
1042
  $value->response = array_merge($value->response, $extra_response);
1045
 
1046
  $extensions = $this->getTnpExtensions();
1047
 
1048
+ // Ops...
1049
  if (!$extensions) {
1050
  return $value;
1051
  }
1055
  unset($value->no_update[$extension->wp_slug]);
1056
  }
1057
 
1058
+ // Someone doesn't want our addons updated, let respect it (this constant should be defined in wp-config.php)
1059
  if (!NEWSLETTER_EXTENSION_UPDATE) {
1060
  //$this->logger->info('Updates disabled');
1061
  return $value;
1063
 
1064
  include_once(ABSPATH . 'wp-admin/includes/plugin.php');
1065
 
1066
+ // Ok, that is really bad (should we remove it? is there a minimum WP version?)
1067
  if (!function_exists('get_plugin_data')) {
1068
  //$this->logger->error('No get_plugin_data function available!');
1069
  return $value;
1071
 
1072
  $license_key = $this->get_license_key();
1073
 
1074
+ // Here we prepare the update information BUT do not add the link to the package which is privided
1075
+ // by our Addons Manager (due to WP policies)
1076
  foreach ($extensions as $extension) {
1077
 
1078
  // Patch for names convention
1119
  //$this->logger->debug('There is a new version');
1120
  $extra_response[$extension->plugin] = $plugin;
1121
  } else {
1122
+ // Maybe useless...
1123
  //$this->logger->debug('There is NOT a new version');
1124
  $value->no_update[$extension->plugin] = $plugin;
1125
  }
readme.txt CHANGED
@@ -113,6 +113,19 @@ Thank you, The Newsletter Team
113
 
114
  == Changelog ==
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  = 6.8.9 =
117
 
118
  * Welcome and activation messages test sent using the admin language
113
 
114
  == Changelog ==
115
 
116
+ = 6.9.0 =
117
+
118
+ * Improved and optimized the lists field shortcode
119
+ * Added the dropdown option to the lists field shortcode
120
+ * Optimized the standard generated form aligned with the structure of the form generated by shortcodes
121
+ * Added notices **only for the administrator** on online forms to better discover wrong or unwanted configurations
122
+ * Added support for the next to come API version 2
123
+ * Internally revised the subscription process
124
+ * Fixed empty tag {profile_20}
125
+ * Fix CSS class on mininmal form privacy field
126
+ * Improved error message with notes for administrator on form submission errors
127
+ * Improved error repoting on invalid tracking links (very rare anyway)
128
+
129
  = 6.8.9 =
130
 
131
  * Welcome and activation messages test sent using the admin language
statistics/statistics.php CHANGED
@@ -45,8 +45,7 @@ class NewsletterStatistics extends NewsletterModule {
45
  $url = implode(';', $parts);
46
 
47
  if (empty($user_id) || empty($url)) {
48
- header("HTTP/1.0 404 Not Found");
49
- die('Invalid data');
50
  }
51
 
52
  $parts = parse_url($url);
@@ -54,14 +53,12 @@ class NewsletterStatistics extends NewsletterModule {
54
  $verified = $signature == md5($email_id . ';' . $user_id . ';' . $url . ';' . $anchor . $this->options['key']);
55
 
56
  if (!$verified) {
57
- header("HTTP/1.0 404 Not Found");
58
- die('Url not verified');
59
  }
60
 
61
  $user = Newsletter::instance()->get_user($user_id);
62
  if (!$user) {
63
- header("HTTP/1.0 404 Not Found");
64
- die('Invalid subscriber');
65
  }
66
 
67
  // Test emails
@@ -72,8 +69,7 @@ class NewsletterStatistics extends NewsletterModule {
72
 
73
  $email = $this->get_email($email_id);
74
  if (!$email) {
75
- header("HTTP/1.0 404 Not Found");
76
- die('Invalid newsletter');
77
  }
78
 
79
  setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
45
  $url = implode(';', $parts);
46
 
47
  if (empty($user_id) || empty($url)) {
48
+ $this->dienow('Invalid link', 'The tracking link contains invalid data (missing subscriber or original URL)', 404);
 
49
  }
50
 
51
  $parts = parse_url($url);
53
  $verified = $signature == md5($email_id . ';' . $user_id . ';' . $url . ';' . $anchor . $this->options['key']);
54
 
55
  if (!$verified) {
56
+ $this->dienow('Invalid link', 'The link signature (which grants a valid redirection and protects from redirect attacks) is not valid.', 404);
 
57
  }
58
 
59
  $user = Newsletter::instance()->get_user($user_id);
60
  if (!$user) {
61
+ $this->dienow(__('Subscriber not found', 'newsletter'), 'This tracking link contains a reference to a subscriber no more present', 404);
 
62
  }
63
 
64
  // Test emails
69
 
70
  $email = $this->get_email($email_id);
71
  if (!$email) {
72
+ $this->dienow('Invalid newsletter', 'The link originates from a newsletter not found (it could have been deleted)', 404);
 
73
  }
74
 
75
  setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
style.css CHANGED
@@ -7,7 +7,7 @@ CUSTOM CSS RULES.
7
  */
8
 
9
  .tnp-subscription {
10
- font-size: 13px;
11
  display: block;
12
  margin: 15px auto;
13
  max-width: 500px;
@@ -24,7 +24,7 @@ CUSTOM CSS RULES.
24
  .tnp-subscription label {
25
  display: block;
26
  color: inherit;
27
- font-size: 14px;
28
  font-weight: 700;
29
  line-height: normal;
30
  padding: 5px;
7
  */
8
 
9
  .tnp-subscription {
10
+ /*font-size: 13px;*/
11
  display: block;
12
  margin: 15px auto;
13
  max-width: 500px;
24
  .tnp-subscription label {
25
  display: block;
26
  color: inherit;
27
+ /*font-size: 14px;*/
28
  font-weight: 700;
29
  line-height: normal;
30
  padding: 5px;
style.min.css CHANGED
@@ -1 +1 @@
1
- .tnp-subscription{font-size:13px;display:block;margin:15px auto;max-width:500px;width:100%}.tnp-subscription div.tnp-field{margin-bottom:10px;border:0;padding:0}.tnp-subscription label{display:block;color:inherit;font-size:14px;font-weight:700;line-height:normal;padding:5px;margin:0}.tnp-subscription input[type=text],.tnp-subscription input[type=email],.tnp-subscription input[type=submit],.tnp-subscription select{width:100%;height:50px;padding:10px;display:block;border:1px;border-color:#ddd;background-color:#f4f4f4;background-image:none;text-shadow:none;color:#444;font-size:14px;line-height:20px;margin:0;line-height:normal;box-sizing:border-box}.tnp-subscription input[type=checkbox],.tnp-widget input[type=radio]{max-width:20px;display:inline-block}.tnp-subscription select option{margin-right:10px}.tnp-subscription input.tnp-submit{background-color:#444;color:#fff;width:auto;height:auto;margin:0}@media all and (max-width:480px){.tnp-subscription input[type=submit]{width:100%}}.tnp-profile form .tnp-field{margin-bottom:10px;border:0;padding:0}.tnp-profile form .tnp-field label{display:block;color:#333}.tnp-profile form .tnp-field input[type=text],.tnp-profile form .tnp-field input[type=email],.tnp-profile form .tnp-field input[type=submit],.tnp-profile form .tnp-field textarea,.tnp-profile form .tnp-field select{padding:10px;display:block;border:1px;border-color:#ddd;background-color:#f4f4f4;background-image:none;text-shadow:none;color:#444;font-size:14px;margin:0;line-height:normal;box-sizing:border-box;border-radius:0;height:auto;float:none}.tnp-profile form input[type=checkbox],.tnp-profile input[type=radio]{max-width:20px;display:inline-block}.tnp-profile form .tnp-list-label{margin-left:15px}.tnp-profile form select option{margin-right:10px}.tnp-profile form .tnp-field input[type=submit]{background-color:#444;color:#fff;width:auto;height:auto;margin:0}@media all and (max-width:480px){.tnp-profile input[type=submit]{width:100%;margin:0}}.tnp-widget{width:100%;display:block;box-sizing:border-box}.tnp-widget .tnp-field{margin-bottom:10px;border:0;padding:0}.tnp-widget label{display:block;color:inherit;font-size:14px}.tnp-widget input[type=text],.tnp-widget input[type=email],.tnp-widget input[type=submit],.tnp-widget select{width:100%;padding:10px;display:block;border:1px solid #ddd;border-color:#ddd;background-color:#f4f4f4;background-image:none;text-shadow:none;color:#444;font-size:14px;line-height:normal;box-sizing:border-box;height:auto}.tnp-widget input[type=checkbox],.tnp-widget input[type=radio]{width:auto;display:inline-block}.tnp-widget select option{margin-right:10px}.tnp-widget input.tnp-submit{background-color:#444;background-image:none;text-shadow:none;color:#fff;margin:0}.tnp-field input[type="submit"]{position:inherit}.tnp-widget-minimal{width:100%}.tnp-widget-minimal form{margin:0;padding:0;border:0}.tnp-widget-minimal input.tnp-email{width:100%;box-sizing:border-box;padding:10px;display:inline-block;border:1px solid #ddd;background-color:#f4f4f4;color:#444;font-size:14px}.tnp-widget-minimal input.tnp-submit{width:100%;box-sizing:border-box;padding:10px;display:inline-block;border:1px;border-color:#ddd;background-color:#444;background-image:none;text-shadow:none;color:#fff;font-size:14px;line-height:normal;border-radius:0;height:auto;margin:0}.tnp-subscription-minimal{width:100%;box-sizing:border-box}.tnp-subscription-minimal form{margin:0;padding:0;border:0}.tnp-subscription-minimal input.tnp-email{width:70%;max-width:300px;box-sizing:border-box;padding:10px;display:inline-block;border:1px solid #ddd;background-color:#f4f4f4;color:#444;font-size:14px;line-height:20px;border-radius:0}.tnp-subscription-minimal .tnp-privacy-field{margin-top:10px}.tnp-subscription-minimal input.tnp-submit{width:29%;box-sizing:border-box;display:inline-block;padding:10px;border:1px;border-color:#ddd;background-color:#444;background-image:none;text-shadow:none;color:#fff;font-size:14px;line-height:20px;border-radius:0;margin:0}.tnp-comments{clear:both;margin-top:15px;margin-bottom:15px}.tnp-comments label{display:block}.tnp-comments input[type=checkbox]{display:inline-block;width:auto!important}.tnp-lock{clear:both;display:block;box-sizing:border-box;box-shadow:none;margin:20px;padding:15px;background-color:#fff;border:1px solid #ddd}.tnp-nl-checkout{margin-bottom:1em}
1
+ .tnp-subscription{display:block;margin:15px auto;max-width:500px;width:100%}.tnp-subscription div.tnp-field{margin-bottom:10px;border:0;padding:0}.tnp-subscription label{display:block;color:inherit;font-weight:700;line-height:normal;padding:5px;margin:0}.tnp-subscription input[type=text],.tnp-subscription input[type=email],.tnp-subscription input[type=submit],.tnp-subscription select{width:100%;height:50px;padding:10px;display:block;border:1px;border-color:#ddd;background-color:#f4f4f4;background-image:none;text-shadow:none;color:#444;font-size:14px;line-height:20px;margin:0;line-height:normal;box-sizing:border-box}.tnp-subscription input[type=checkbox],.tnp-widget input[type=radio]{max-width:20px;display:inline-block}.tnp-subscription select option{margin-right:10px}.tnp-subscription input.tnp-submit{background-color:#444;color:#fff;width:auto;height:auto;margin:0}@media all and (max-width:480px){.tnp-subscription input[type=submit]{width:100%}}.tnp-profile form .tnp-field{margin-bottom:10px;border:0;padding:0}.tnp-profile form .tnp-field label{display:block;color:#333}.tnp-profile form .tnp-field input[type=text],.tnp-profile form .tnp-field input[type=email],.tnp-profile form .tnp-field input[type=submit],.tnp-profile form .tnp-field textarea,.tnp-profile form .tnp-field select{padding:10px;display:block;border:1px;border-color:#ddd;background-color:#f4f4f4;background-image:none;text-shadow:none;color:#444;font-size:14px;margin:0;line-height:normal;box-sizing:border-box;border-radius:0;height:auto;float:none}.tnp-profile form input[type=checkbox],.tnp-profile input[type=radio]{max-width:20px;display:inline-block}.tnp-profile form .tnp-list-label{margin-left:15px}.tnp-profile form select option{margin-right:10px}.tnp-profile form .tnp-field input[type=submit]{background-color:#444;color:#fff;width:auto;height:auto;margin:0}@media all and (max-width:480px){.tnp-profile input[type=submit]{width:100%;margin:0}}.tnp-widget{width:100%;display:block;box-sizing:border-box}.tnp-widget .tnp-field{margin-bottom:10px;border:0;padding:0}.tnp-widget label{display:block;color:inherit;font-size:14px}.tnp-widget input[type=text],.tnp-widget input[type=email],.tnp-widget input[type=submit],.tnp-widget select{width:100%;padding:10px;display:block;border:1px solid #ddd;border-color:#ddd;background-color:#f4f4f4;background-image:none;text-shadow:none;color:#444;font-size:14px;line-height:normal;box-sizing:border-box;height:auto}.tnp-widget input[type=checkbox],.tnp-widget input[type=radio]{width:auto;display:inline-block}.tnp-widget select option{margin-right:10px}.tnp-widget input.tnp-submit{background-color:#444;background-image:none;text-shadow:none;color:#fff;margin:0}.tnp-field input[type="submit"]{position:inherit}.tnp-widget-minimal{width:100%}.tnp-widget-minimal form{margin:0;padding:0;border:0}.tnp-widget-minimal input.tnp-email{width:100%;box-sizing:border-box;padding:10px;display:inline-block;border:1px solid #ddd;background-color:#f4f4f4;color:#444;font-size:14px}.tnp-widget-minimal input.tnp-submit{width:100%;box-sizing:border-box;padding:10px;display:inline-block;border:1px;border-color:#ddd;background-color:#444;background-image:none;text-shadow:none;color:#fff;font-size:14px;line-height:normal;border-radius:0;height:auto;margin:0}.tnp-subscription-minimal{width:100%;box-sizing:border-box}.tnp-subscription-minimal form{margin:0;padding:0;border:0}.tnp-subscription-minimal input.tnp-email{width:70%;max-width:300px;box-sizing:border-box;padding:10px;display:inline-block;border:1px solid #ddd;background-color:#f4f4f4;color:#444;font-size:14px;line-height:20px;border-radius:0}.tnp-subscription-minimal .tnp-privacy-field{margin-top:10px}.tnp-subscription-minimal input.tnp-submit{width:29%;box-sizing:border-box;display:inline-block;padding:10px;border:1px;border-color:#ddd;background-color:#444;background-image:none;text-shadow:none;color:#fff;font-size:14px;line-height:20px;border-radius:0;margin:0}.tnp-comments{clear:both;margin-top:15px;margin-bottom:15px}.tnp-comments label{display:block}.tnp-comments input[type=checkbox]{display:inline-block;width:auto!important}.tnp-lock{clear:both;display:block;box-sizing:border-box;box-shadow:none;margin:20px;padding:15px;background-color:#fff;border:1px solid #ddd}.tnp-nl-checkout{margin-bottom:1em}
subscription/defaults.php CHANGED
@@ -14,20 +14,11 @@ $options = array();
14
 
15
  $options['noconfirmation'] = 1;
16
 
17
- //$options['antiflood'] = 10;
18
- //$options['ip_blacklist'] = array();
19
- //$options['address_blacklist'] = array();
20
- //$options['domain_check'] = 0;
21
- //$options['akismet'] = 1;
22
- //$options['captcha'] = 1;
23
-
24
  $options['notify_email'] = get_option('admin_email');
25
  $options['multiple'] = 1;
26
  $options['notify'] = 0;
27
 
28
- $options['error_text'] = '<p>' . __('You cannot subscribe with the email address you entered, please contact the site administrator.', 'newsletter') . '</p>';
29
-
30
- //$options['already_confirmed_text'] = '<p>This email address is already subscribed, anyway a welcome email has been sent again. Thank you.</p>';
31
 
32
  // Subscription page introductory text (befor the subscription form)
33
  $options['subscription_text'] = "{subscription_form}";
14
 
15
  $options['noconfirmation'] = 1;
16
 
 
 
 
 
 
 
 
17
  $options['notify_email'] = get_option('admin_email');
18
  $options['multiple'] = 1;
19
  $options['notify'] = 0;
20
 
21
+ $options['error_text'] = '<p>' . __('This email address is already subscribed, please contact the site administrator.', 'newsletter') . '</p>';
 
 
22
 
23
  // Subscription page introductory text (befor the subscription form)
24
  $options['subscription_text'] = "{subscription_form}";
subscription/options.php CHANGED
@@ -153,13 +153,6 @@ if ($controls->is_action()) {
153
  <?php $controls->yesno('optin_override'); ?>
154
  </td>
155
  </tr>
156
- <tr>
157
- <th><?php $controls->field_label(__('Repeated subscriptions', 'newsletter'), '/documentation/subscription/subscription/') ?></th>
158
- <td>
159
- <?php //$controls->select('multiple', array('0'=>__('No', 'newsletter'), '1'=>__('Yes', 'newsletter'), '2'=>__('On new lists added', 'newsletter'))); ?>
160
- <?php $controls->select('multiple', array('0'=>__('No', 'newsletter'), '1'=>__('Yes', 'newsletter'))); ?>
161
- </td>
162
- </tr>
163
 
164
  <tr>
165
  <th><?php _e('Notifications', 'newsletter') ?></th>
@@ -188,12 +181,12 @@ if ($controls->is_action()) {
188
 
189
  </table>
190
 
191
- <h3>Special cases</h3>
192
-
193
  <table class="form-table">
194
  <tr>
195
- <th><?php _e('Error page', 'newsletter') ?></th>
196
  <td>
 
 
197
  <?php $controls->wp_editor('error_text'); ?>
198
  </td>
199
  </tr>
153
  <?php $controls->yesno('optin_override'); ?>
154
  </td>
155
  </tr>
 
 
 
 
 
 
 
156
 
157
  <tr>
158
  <th><?php _e('Notifications', 'newsletter') ?></th>
181
 
182
  </table>
183
 
 
 
184
  <table class="form-table">
185
  <tr>
186
+ <th><?php _e('Repeated subscriptions', 'newsletter')?></th>
187
  <td>
188
+ <?php $controls->select('multiple', array('0'=>__('Not allowed', 'newsletter'), '1'=>__('Allowed', 'newsletter'))); ?>
189
+ <br><br>
190
  <?php $controls->wp_editor('error_text'); ?>
191
  </td>
192
  </tr>
subscription/subscription.php CHANGED
@@ -18,7 +18,7 @@ class NewsletterSubscription extends NewsletterModule {
18
  /**
19
  * Contains the options for the current language to build a subscription form. Must be initialized with
20
  * setup_form_options() before use.
21
- *
22
  * @var array
23
  */
24
  var $form_options = null;
@@ -26,7 +26,7 @@ class NewsletterSubscription extends NewsletterModule {
26
  /**
27
  * Contains the antibot/antispam options. Must be initialized with
28
  * setup_antibot_options() before use.
29
- *
30
  * @var array
31
  */
32
  var $antibot_options = null;
@@ -110,188 +110,6 @@ class NewsletterSubscription extends NewsletterModule {
110
  wp_localize_script('newsletter-subscription', 'newsletter', $data);
111
  }
112
 
113
- function ip_match($ip, $range) {
114
- if (strpos($range, '/')) {
115
- list ($subnet, $bits) = explode('/', $range);
116
- $ip = ip2long($ip);
117
- $subnet = ip2long($subnet);
118
- $mask = -1 << (32 - $bits);
119
- $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
120
- return ($ip & $mask) == $subnet;
121
- } else {
122
- return strpos($range, $ip) === 0;
123
- }
124
- }
125
-
126
- function is_address_blacklisted($email) {
127
- $this->setup_antibot_options();
128
-
129
- if (empty($this->antibot_options['address_blacklist'])) {
130
- return false;
131
- }
132
-
133
- //$this->logger->debug('Address blacklist check');
134
- $rev_email = strrev($email);
135
- foreach ($this->antibot_options['address_blacklist'] as $item) {
136
- if (strpos($rev_email, strrev($item)) === 0) {
137
- return true;
138
- }
139
- }
140
- return false;
141
- }
142
-
143
- function is_ip_blacklisted($ip) {
144
- $this->setup_antibot_options();
145
-
146
- if (empty($this->antibot_options['ip_blacklist'])) {
147
- return false;
148
- }
149
- //$this->logger->debug('IP blacklist check');
150
- foreach ($this->antibot_options['ip_blacklist'] as $item) {
151
- if ($this->ip_match($ip, $item)) {
152
- return true;
153
- }
154
- }
155
- return false;
156
- }
157
-
158
- function is_missing_domain_mx($email) {
159
- // Actually not fully implemented
160
- return false;
161
-
162
- if (empty($this->options['domain_check'])) {
163
- return false;
164
- }
165
-
166
- $this->logger->debug('Domain MX check');
167
- list($local, $domain) = explode('@', $email);
168
-
169
- $hosts = array();
170
- if (!getmxrr($domain, $hosts)) {
171
- return true;
172
- }
173
- return false;
174
- }
175
-
176
- function is_flood($email, $ip) {
177
- global $wpdb;
178
-
179
- $this->setup_antibot_options();
180
-
181
- if (empty($this->antibot_options['antiflood'])) {
182
- return false;
183
- }
184
-
185
- //$this->logger->debug('Antiflood check');
186
-
187
- $updated = $wpdb->get_var($wpdb->prepare("select updated from " . NEWSLETTER_USERS_TABLE . " where ip=%s or email=%s order by updated desc limit 1", $ip, $email));
188
-
189
- if ($updated && time() - $updated < $this->antibot_options['antiflood']) {
190
- return true;
191
- }
192
-
193
- return false;
194
- }
195
-
196
- function is_spam_text($text) {
197
- if (stripos($text, 'http:') !== false || stripos($text, 'https:') !== false) {
198
- return true;
199
- }
200
- if (stripos($text, 'www.') !== false) {
201
- return true;
202
- }
203
-
204
- return false;
205
- }
206
-
207
- function is_spam_by_akismet($email, $name, $ip, $agent, $referrer) {
208
-
209
- if (!class_exists('Akismet')) {
210
- return false;
211
- }
212
-
213
- $this->setup_antibot_options();
214
-
215
- if (empty($this->antibot_options['akismet'])) {
216
- return false;
217
- }
218
-
219
-
220
- $this->logger->debug('Akismet check');
221
- $request = 'blog=' . urlencode(home_url()) . '&referrer=' . urlencode($referrer) .
222
- '&user_agent=' . urlencode($agent) .
223
- '&comment_type=signup' .
224
- '&comment_author_email=' . urlencode($email) .
225
- '&user_ip=' . urlencode($ip);
226
- if (!empty($name)) {
227
- $request .= '&comment_author=' . urlencode($name);
228
- }
229
-
230
- $response = Akismet::http_post($request, 'comment-check');
231
-
232
- if ($response && $response[1] == 'true') {
233
- return true;
234
- }
235
- return false;
236
- }
237
-
238
- /**
239
- * $email must be cleaned using the is_email() function.
240
- *
241
- * @param type $email
242
- * @param type $full_name
243
- * @param type $ip
244
- */
245
- function valid_subscription_or_die($email, $full_name, $ip) {
246
- $antibot_logger = new NewsletterLogger('antibot');
247
-
248
- $antibot_logger->info($_REQUEST);
249
-
250
- if (empty($email)) {
251
- echo 'Wrong email';
252
- header("HTTP/1.0 400 Bad request");
253
- die();
254
- }
255
-
256
- if ($this->is_spam_text($full_name)) {
257
- $antibot_logger->fatal($email . ' - ' . $ip . ' - Name with http: ' . $full_name);
258
- header("HTTP/1.0 404 Not Found");
259
- die();
260
- }
261
-
262
- if ($this->is_missing_domain_mx($email)) {
263
- $antibot_logger->fatal($email . ' - ' . $ip . ' - MX check failed');
264
- header("HTTP/1.0 404 Not Found");
265
- die();
266
- }
267
-
268
- if ($this->is_ip_blacklisted($ip)) {
269
- $antibot_logger->fatal($email . ' - ' . $ip . ' - IP blacklisted');
270
- header("HTTP/1.0 404 Not Found");
271
- die();
272
- }
273
-
274
- if ($this->is_address_blacklisted($email)) {
275
- $antibot_logger->fatal($email . ' - ' . $ip . ' - Address blacklisted');
276
- header("HTTP/1.0 404 Not Found");
277
- die();
278
- }
279
-
280
- // Akismet check
281
- if ($this->is_spam_by_akismet($email, $full_name, $ip, $_SERVER['HTTP_USER_AGENT'], $_SERVER['HTTP_REFERER'])) {
282
- $antibot_logger->fatal($email . ' - ' . $ip . ' - Akismet blocked');
283
- header("HTTP/1.0 404 Not Found");
284
- die();
285
- }
286
-
287
- // Flood check
288
- if ($this->is_flood($email, $ip)) {
289
- $antibot_logger->fatal($email . ' - ' . $ip . ' - Antiflood triggered');
290
- header("HTTP/1.0 404 Not Found");
291
- die('Too quick. Check the antiflood on security page.');
292
- }
293
- }
294
-
295
  /**
296
  *
297
  * @global wpdb $wpdb
@@ -305,11 +123,11 @@ class NewsletterSubscription extends NewsletterModule {
305
  if ($this->antibot_form_check()) {
306
 
307
  if (!$user || $user->status != TNP_user::STATUS_CONFIRMED) {
308
- $this->dienow('Subscriber not found or not confirmed.');
309
  }
310
 
311
  if (!$email) {
312
- $this->dienow('Newsletter not found');
313
  }
314
 
315
  if (isset($_REQUEST['list'])) {
@@ -318,7 +136,7 @@ class NewsletterSubscription extends NewsletterModule {
318
  // Check if the list is public
319
  $list = $this->get_list($list_id);
320
  if (!$list || $list->status == TNP_List::STATUS_PRIVATE) {
321
- $this->dienow('List change not allowed.', 'Please check if the list is marked as "private".');
322
  }
323
 
324
  $url = esc_url_raw($_REQUEST['redirect']);
@@ -347,7 +165,7 @@ class NewsletterSubscription extends NewsletterModule {
347
  case 'subscribe':
348
 
349
  if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
350
- $this->dienow('Invalid request');
351
  }
352
 
353
  $options_antibot = $this->get_options('antibot');
@@ -356,21 +174,24 @@ class NewsletterSubscription extends NewsletterModule {
356
 
357
  if (!empty($options_antibot['disabled']) || $this->antibot_form_check($captcha)) {
358
 
359
- $user = $this->subscribe();
 
 
 
 
 
 
 
 
 
 
 
360
 
361
- if ($user->status == 'E')
362
- $this->show_message('error', $user);
363
- if ($user->status == 'C')
364
  $this->show_message('confirmed', $user);
365
- if ($user->status == 'A')
366
- $this->show_message('already_confirmed', $user);
367
- if ($user->status == 'S')
368
  $this->show_message('confirmation', $user);
369
  } else {
370
- // Temporary store data
371
- //$data_key = wp_generate_password(16, false, false);
372
- //set_transient('newsletter_' . $data_key, $_REQUEST, 60);
373
- //$this->antibot_redirect($data_key);
374
  $this->request_to_antibot_form('Subscribe', $captcha);
375
  }
376
  die();
@@ -383,17 +204,21 @@ class NewsletterSubscription extends NewsletterModule {
383
  $this->dienow('Invalid request');
384
  }
385
 
386
- $user = $this->subscribe();
387
 
388
- if ($user->status == 'E')
389
- $key = 'error';
390
- if ($user->status == 'C')
 
 
 
 
391
  $key = 'confirmed';
392
- if ($user->status == 'A')
393
- $key = 'already_confirmed';
394
- if ($user->status == 'S')
395
- $key = 'confirmation';
396
 
 
 
 
397
 
398
  $message = $this->replace($this->options[$key . '_text'], $user);
399
  if (isset($this->options[$key . '_tracking'])) {
@@ -404,15 +229,14 @@ class NewsletterSubscription extends NewsletterModule {
404
 
405
  case 'c':
406
  case 'confirm':
 
 
 
 
407
  if ($this->antibot_form_check()) {
408
- // TODO: Change to accept $user
409
- $user = $this->confirm();
410
- if ($user->status == 'E') {
411
- $this->show_message('error', $user->id);
412
- } else {
413
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
414
- $this->show_message('confirmed', $user);
415
- }
416
  } else {
417
  $this->request_to_antibot_form('Confirm');
418
  }
@@ -488,7 +312,7 @@ class NewsletterSubscription extends NewsletterModule {
488
  }
489
 
490
  function first_install() {
491
-
492
  }
493
 
494
  function admin_menu() {
@@ -591,12 +415,6 @@ class NewsletterSubscription extends NewsletterModule {
591
  }
592
  }
593
 
594
- function setup_antibot_options() {
595
- if (empty($this->antibot_options)) {
596
- $this->antibot_options = $this->get_options('antibot');
597
- }
598
- }
599
-
600
  function get_form_options($language = '') {
601
  return $this->get_options('profile', $language);
602
  }
@@ -623,26 +441,186 @@ class NewsletterSubscription extends NewsletterModule {
623
  $wpdb->update(NEWSLETTER_USERS_TABLE, array('updated' => $time, 'ip' => $ip, 'geo' => 0), array('id' => $id));
624
  }
625
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
  /**
627
  * Create a subscription using the $_REQUEST data. Does security checks.
628
  *
 
629
  * @param string $status The status to use for this subscription (confirmed, not confirmed, ...)
630
  * @param bool $emails If the confirmation/welcome email should be sent or the subscription should be silent
631
  * @return TNP_User
632
  */
633
  function subscribe($status = null, $emails = true) {
634
 
635
- $options_profile = $this->get_options('profile');
636
- /*
637
- if ($options_profile['name_status'] == 0 && isset($_REQUEST['nn'])) {
638
- // Name injection
639
- die();
640
- }
641
- if ($options_profile['surname_status'] == 0 && isset($_REQUEST['ns'])) {
642
- // Last name injection
643
- die();
644
- }
645
- */
646
 
647
  // Validation
648
  $ip = $this->get_remote_ip();
@@ -657,10 +635,6 @@ class NewsletterSubscription extends NewsletterModule {
657
  $last_name = $this->normalize_name(stripslashes($_REQUEST['ns']));
658
  }
659
 
660
- $full_name = trim($first_name . ' ' . $last_name);
661
-
662
- $this->valid_subscription_or_die($email, $full_name, $ip);
663
-
664
  $opt_in = (int) $this->options['noconfirmation']; // 0 - double, 1 - single
665
  if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
666
  switch ($_REQUEST['optin']) {
@@ -767,10 +741,99 @@ class NewsletterSubscription extends NewsletterModule {
767
  return $message . '<span itemscope itemtype="http://schema.org/EmailMessage"><span itemprop="description" content="Email address confirmation"></span><span itemprop="action" itemscope itemtype="http://schema.org/ConfirmAction"><meta itemprop="name" content="Confirm Subscription"><span itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler"><meta itemprop="url" content="{subscription_confirm_url}"><link itemprop="method" href="http://schema.org/HttpRequestMethod/POST"></span></span></span>';
768
  }
769
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
  /**
771
  * Processes the request and fill in the *array* representing a subscriber with submitted values
772
  * (filtering when necessary).
773
  *
 
774
  * @param array $user An array partially filled with subscriber data
775
  * @return array The filled array representing a subscriber
776
  */
@@ -834,13 +897,13 @@ class NewsletterSubscription extends NewsletterModule {
834
  foreach ($_REQUEST['nl'] as $list_id) {
835
  $list = $this->get_list($list_id);
836
  if ($list && $list->status == TNP_List::STATUS_PRIVATE) {
837
- $this->dienow('Invalid list', 'List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
838
  }
839
  }
840
  }
841
  }
842
-
843
  // Preferences (field names are nl[] and values the list number so special forms with radio button can work)
 
844
  if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
845
  $lists = $this->get_lists_public();
846
  //$this->logger->debug($_REQUEST['nl']);
@@ -854,6 +917,7 @@ class NewsletterSubscription extends NewsletterModule {
854
  }
855
 
856
  // Forced lists (general or by language)
 
857
  $lists = $this->get_lists();
858
  foreach ($lists as $list) {
859
  if ($list->forced) {
@@ -908,13 +972,31 @@ class NewsletterSubscription extends NewsletterModule {
908
  * Confirms a subscription changing the user status and, possibly, merging the
909
  * temporary data if present.
910
  *
911
- * @param int $user_id Optional. If null the user is extracted from the request.
912
  * @return TNP_User
913
  */
914
- function confirm($user_id = null, $emails = true) {
915
 
916
- if ($user_id == null) {
 
917
  $user = $this->get_user_from_request(true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
918
  // Is there any temporary data from a subscription to be confirmed?
919
  $data = get_transient($this->get_user_key($user));
920
  if ($data !== false) {
@@ -926,13 +1008,9 @@ class NewsletterSubscription extends NewsletterModule {
926
  // Forced a fake status so the welcome email is sent
927
  $user->status = Newsletter::STATUS_NOT_CONFIRMED;
928
  }
929
- } else {
930
- $user = $this->get_user($user_id);
931
- if ($user == null) {
932
- die('No subscriber found.');
933
- }
934
  }
935
 
 
936
  $this->update_user_last_activity($user);
937
 
938
  setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
@@ -1042,7 +1120,7 @@ class NewsletterSubscription extends NewsletterModule {
1042
 
1043
  /**
1044
  * Generates the privacy URL and cache it.
1045
- *
1046
  * @return string
1047
  */
1048
  function get_privacy_url() {
@@ -1103,7 +1181,7 @@ class NewsletterSubscription extends NewsletterModule {
1103
 
1104
  /**
1105
  * Manages the custom forms made with [newsletter_form] and internal [newsletter_field] shorcodes.
1106
- *
1107
  * @param array $attrs
1108
  * @param string $content
1109
  * @return string
@@ -1115,7 +1193,7 @@ class NewsletterSubscription extends NewsletterModule {
1115
 
1116
  $this->setup_form_options();
1117
 
1118
- $attrs = array_merge(['class' => 'newsletter', 'style' => ''], $attrs);
1119
 
1120
  $action = esc_attr($this->build_action_url('s'));
1121
  $class = esc_attr($attrs['class']);
@@ -1147,10 +1225,7 @@ class NewsletterSubscription extends NewsletterModule {
1147
  }
1148
 
1149
  if (isset($attrs['lists'])) {
1150
- $arr = explode(',', $attrs['lists']);
1151
- foreach ($arr as $a) {
1152
- $buffer .= "<input type='hidden' name='nl[]' value='" . esc_attr(trim($a)) . "'>\n";
1153
- }
1154
  }
1155
 
1156
  $buffer .= do_shortcode($content);
@@ -1176,6 +1251,38 @@ class NewsletterSubscription extends NewsletterModule {
1176
  return $buffer;
1177
  }
1178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1179
  /**
1180
  * Internal use only
1181
  *
@@ -1198,7 +1305,9 @@ class NewsletterSubscription extends NewsletterModule {
1198
  $buffer .= esc_html($attrs['label']);
1199
  }
1200
  } else {
1201
- $buffer .= esc_html($this->form_options[$name]);
 
 
1202
  }
1203
  $buffer .= "</label>\n";
1204
  return $buffer;
@@ -1211,7 +1320,7 @@ class NewsletterSubscription extends NewsletterModule {
1211
  return '<p style="background-color: #eee; color: #000; padding: 10px; margin: 10px 0">' . $message . ' <strong>This notice is shown only to administrators.</strong></p>';
1212
  }
1213
 
1214
- function shortcode_newsletter_field($attrs, $content) {
1215
  $this->setup_form_options();
1216
  $language = $this->get_current_language();
1217
 
@@ -1224,8 +1333,9 @@ class NewsletterSubscription extends NewsletterModule {
1224
  $buffer .= $this->_shortcode_label('email', $attrs);
1225
 
1226
  $buffer .= '<input class="tnp-email" type="email" name="ne" value=""';
1227
- if (isset($attrs['placeholder']))
1228
  $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
 
1229
  $buffer .= ' required>';
1230
  if (isset($attrs['button_label'])) {
1231
  $label = $attrs['button_label'];
@@ -1297,8 +1407,9 @@ class NewsletterSubscription extends NewsletterModule {
1297
  }
1298
  $buffer .= '>';
1299
  if (isset($attrs['label'])) {
1300
- if ($attrs['label'] != '')
1301
  $buffer .= '&nbsp;' . esc_html($attrs['label']) . '</label>';
 
1302
  } else {
1303
  $buffer .= '&nbsp;' . esc_html($list->name) . '</label>';
1304
  }
@@ -1309,18 +1420,36 @@ class NewsletterSubscription extends NewsletterModule {
1309
 
1310
  // All lists
1311
  if ($name == 'lists' || $name == 'preferences') {
1312
- $tmp = '';
1313
  $lists = $this->get_lists_for_subscription($language);
1314
- foreach ($lists as $list) {
1315
- $tmp .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="nl' . $list->id . '">';
1316
- $tmp .= '<input type="checkbox" id="nl' . $list->id . '" name="nl[]" value="' . $list->id . '"';
1317
- if ($list->checked) {
1318
- $tmp .= ' checked';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1319
  }
1320
- $tmp .= '>&nbsp;' . esc_html($list->name) . '</label>';
1321
- $tmp .= "</div>\n";
1322
  }
1323
- return $tmp;
1324
  }
1325
 
1326
  // TODO: add the "not specified"
@@ -1428,7 +1557,7 @@ class NewsletterSubscription extends NewsletterModule {
1428
 
1429
  /**
1430
  * Builds the privacy field only for completely generated forms.
1431
- *
1432
  * @return string Empty id the privacy filed is not configured
1433
  */
1434
  function get_privacy_field($pre_html = '', $post_html = '') {
@@ -1458,7 +1587,7 @@ class NewsletterSubscription extends NewsletterModule {
1458
  /**
1459
  * Creates the hidden alternative optin field on custom form showing notices if used in the wrong
1460
  * way.
1461
- *
1462
  * @since 6.8.3
1463
  * @param string $optin Could be single or double, lowercase
1464
  * @return string The complete HTML field
@@ -1489,7 +1618,7 @@ class NewsletterSubscription extends NewsletterModule {
1489
  * @param type $attrs
1490
  * @return string
1491
  */
1492
- function get_subscription_form($referrer = null, $action = null, $attrs = array()) {
1493
  $language = $this->get_current_language();
1494
  $options_profile = $this->get_options('profile', $language);
1495
 
@@ -1504,8 +1633,6 @@ class NewsletterSubscription extends NewsletterModule {
1504
 
1505
  $buffer = '';
1506
 
1507
-
1508
-
1509
  if (empty($action)) {
1510
  $action = $this->build_action_url('s');
1511
  }
@@ -1537,86 +1664,33 @@ class NewsletterSubscription extends NewsletterModule {
1537
  $attrs['lists'] = $attrs['list'];
1538
  }
1539
 
1540
- // Hidden lists
1541
  if (isset($attrs['lists'])) {
1542
- $arr = explode(',', $attrs['lists']);
1543
- foreach ($arr as $a) {
1544
- $buffer .= "<input type='hidden' name='nl[]' value='" . ((int) trim($a)) . "'>\n";
1545
- }
1546
  }
1547
 
1548
  if ($options_profile['name_status'] == 2) {
1549
- $buffer .= '<div class="tnp-field tnp-field-firstname"><label>' . esc_html($options_profile['name']) . '</label>';
1550
- $buffer .= '<input class="tnp-firstname" type="text" name="nn" ' . ($options_profile['name_rules'] == 1 ? 'required' : '') . '></div>';
1551
- $buffer .= "\n";
1552
  }
1553
 
1554
  if ($options_profile['surname_status'] == 2) {
1555
- $buffer .= '<div class="tnp-field tnp-field-lastname"><label>' . esc_html($options_profile['surname']) . '</label>';
1556
- $buffer .= '<input class="tnp-lastname" type="text" name="ns" ' . ($options_profile['surname_rules'] == 1 ? 'required' : '') . '></div>';
1557
- $buffer .= "\n";
1558
  }
1559
 
1560
- $buffer .= '<div class="tnp-field tnp-field-email"><label>' . esc_html($options_profile['email']) . '</label>';
1561
- $buffer .= '<input class="tnp-email" type="email" name="ne" required></div>';
1562
- $buffer .= "\n";
1563
 
1564
  if (isset($options_profile['sex_status']) && $options_profile['sex_status'] == 2) {
1565
- $buffer .= '<div class="tnp-field tnp-field-gender"><label>' . esc_html($options_profile['sex']) . '</label>';
1566
- $buffer .= '<select name="nx" class="tnp-gender"';
1567
- if ($options_profile['sex_rules'] == 1) {
1568
- $buffer .= ' required><option value=""></option>';
1569
- } else {
1570
- $buffer .= '><option value="n">' . esc_html($options_profile['sex_none']) . '</option>';
1571
- }
1572
- $buffer .= '<option value="m">' . esc_html($options_profile['sex_male']) . '</option>';
1573
- $buffer .= '<option value="f">' . esc_html($options_profile['sex_female']) . '</option>';
1574
- $buffer .= '</select></div>';
1575
- $buffer .= "\n";
1576
  }
1577
 
1578
  $tmp = '';
1579
  $lists = $this->get_lists_for_subscription($language);
1580
  if (!empty($attrs['lists_field_layout']) && $attrs['lists_field_layout'] == 'dropdown') {
1581
- foreach ($lists as $list) {
1582
-
1583
- $tmp .= '<option value="' . $list->id . '"';
1584
- if ($list->checked) {
1585
- $tmp .= ' selected';
1586
- }
1587
- $tmp .= '>' . esc_html($list->name) . '</option>';
1588
- $tmp .= "\n";
1589
- }
1590
- if (!empty($attrs['lists_field_empty_label'])) {
1591
- $tmp = '<option value="">' . $attrs['lists_field_empty_label'] . '</option>' . $tmp;
1592
- }
1593
- if (!empty($tmp)) {
1594
- $tmp = '<select class="tnp-lists" name="nl[]" required>' . $tmp . '</select>';
1595
- }
1596
- if (!empty($tmp)) {
1597
- $buffer .= '<div class="tnp-field tnp-lists">';
1598
- if (!empty($attrs['lists_field_label'])) {
1599
- $buffer .= '<label>' . $attrs['lists_field_label'] . '</label>';
1600
- }
1601
- $buffer .= $tmp . '</div>';
1602
  }
 
1603
  } else {
1604
-
1605
- foreach ($lists as $list) {
1606
-
1607
- $tmp .= '<div class="tnp-field tnp-field-list"><label><input class="tnp-preference" type="checkbox" name="nl[]" value="' . $list->id . '"';
1608
- if ($list->checked) {
1609
- $tmp .= ' checked';
1610
- }
1611
- $tmp .= '/>&nbsp;' . esc_html($list->name) . '</label></div>';
1612
- }
1613
- if (!empty($tmp)) {
1614
- $buffer .= '<div class="tnp-lists">';
1615
- if (!empty($attrs['lists_field_label'])) {
1616
- $buffer .= '<label>' . $attrs['lists_field_label'] . '</label>';
1617
- }
1618
- $buffer .= $tmp . '</div>';
1619
- }
1620
  }
1621
 
1622
  // Extra profile fields
@@ -1626,26 +1700,7 @@ class NewsletterSubscription extends NewsletterModule {
1626
  continue;
1627
  }
1628
 
1629
-
1630
- $buffer .= '<div class="tnp-field tnp-field-profile"><label>' .
1631
- esc_html($options_profile['profile_' . $i]) . '</label>';
1632
-
1633
- // Text field
1634
- if ($options_profile['profile_' . $i . '_type'] == 'text') {
1635
- $buffer .= '<input class="tnp-profile tnp-profile-' . $i . '" type="text"' . ($options_profile['profile_' . $i . '_rules'] == 1 ? ' required' : '') . ' name="np' . $i . '">';
1636
- }
1637
-
1638
- // Select field
1639
- if ($options_profile['profile_' . $i . '_type'] == 'select') {
1640
- $buffer .= '<select class="tnp-profile tnp-profile-' . $i . '" name="np' . $i . '" ' . ($options_profile['profile_' . $i . '_rules'] == 1 ? ' required' : '') . '>' . "\n";
1641
- $buffer .= "<option></option>\n";
1642
- $opts = explode(',', $options_profile['profile_' . $i . '_options']);
1643
- for ($j = 0; $j < count($opts); $j++) {
1644
- $buffer .= "<option>" . esc_html(trim($opts[$j])) . "</option>\n";
1645
- }
1646
- $buffer .= "</select>\n";
1647
- }
1648
- $buffer .= '</div>';
1649
  }
1650
 
1651
  $extra = apply_filters('newsletter_subscription_extra', array());
@@ -1684,6 +1739,12 @@ class NewsletterSubscription extends NewsletterModule {
1684
  return $buffer;
1685
  }
1686
 
 
 
 
 
 
 
1687
  function get_profile_form($user) {
1688
  return NewsletterProfile::instance()->get_profile_form($user);
1689
  }
@@ -1739,6 +1800,13 @@ class NewsletterSubscription extends NewsletterModule {
1739
  Newsletter::instance()->mail($email, $subject, array('html' => $message));
1740
  }
1741
 
 
 
 
 
 
 
 
1742
  function get_subscription_form_minimal($attrs) {
1743
 
1744
  $language = $this->get_current_language();
@@ -1761,16 +1829,13 @@ class NewsletterSubscription extends NewsletterModule {
1761
  if (isset($attrs['optin'])) {
1762
  $form .= $this->build_optin_field($attrs['optin']);
1763
  }
1764
-
1765
  if (isset($attrs['confirmation_url'])) {
1766
  $form .= "<input type='hidden' name='ncu' value='" . esc_attr($attrs['confirmation_url']) . "'>\n";
1767
- }
1768
 
1769
  if (isset($attrs['lists'])) {
1770
- $arr = explode(',', $attrs['lists']);
1771
- foreach ($arr as $a) {
1772
- $form .= "<input type='hidden' name='nl[]' value='" . ((int) trim($a)) . "'>\n";
1773
- }
1774
  }
1775
  $form .= '<input type="hidden" name="nr" value="' . esc_attr($attrs['referrer']) . '">';
1776
  $form .= '<input type="hidden" name="nlang" value="' . esc_attr($language) . '">' . "\n";
@@ -1778,7 +1843,7 @@ class NewsletterSubscription extends NewsletterModule {
1778
  $form .= '<input class="tnp-submit" type="submit" value="' . esc_attr($attrs['button']) . '"'
1779
  . ' style="background-color:' . esc_attr($attrs['button_color']) . '">';
1780
 
1781
- $form .= $this->get_privacy_field('<div class="tnp_field tnp-privacy-field">', '</div>');
1782
 
1783
  $form .= "</form></div>\n";
1784
 
@@ -1882,7 +1947,7 @@ NewsletterSubscription::instance();
1882
  // Compatibility code
1883
 
1884
  /**
1885
- * @deprecated
1886
  * @param int $number
1887
  */
1888
  function newsletter_form($number = null) {
18
  /**
19
  * Contains the options for the current language to build a subscription form. Must be initialized with
20
  * setup_form_options() before use.
21
+ *
22
  * @var array
23
  */
24
  var $form_options = null;
26
  /**
27
  * Contains the antibot/antispam options. Must be initialized with
28
  * setup_antibot_options() before use.
29
+ *
30
  * @var array
31
  */
32
  var $antibot_options = null;
110
  wp_localize_script('newsletter-subscription', 'newsletter', $data);
111
  }
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  /**
114
  *
115
  * @global wpdb $wpdb
123
  if ($this->antibot_form_check()) {
124
 
125
  if (!$user || $user->status != TNP_user::STATUS_CONFIRMED) {
126
+ $this->dienow('Subscriber not found or not confirmed.', 'Even the wrong subscriber token can lead to this error.', 404);
127
  }
128
 
129
  if (!$email) {
130
+ $this->dienow('Newsletter not found', 'The newsletter containing the link has been deleted.', 404);
131
  }
132
 
133
  if (isset($_REQUEST['list'])) {
136
  // Check if the list is public
137
  $list = $this->get_list($list_id);
138
  if (!$list || $list->status == TNP_List::STATUS_PRIVATE) {
139
+ $this->dienow('List change not allowed.', 'Please check if the list is marked as "private".', 400);
140
  }
141
 
142
  $url = esc_url_raw($_REQUEST['redirect']);
165
  case 'subscribe':
166
 
167
  if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
168
+ $this->dienow('Invalid request', 'The subscription request was not made with a HTTP POST', 400);
169
  }
170
 
171
  $options_antibot = $this->get_options('antibot');
174
 
175
  if (!empty($options_antibot['disabled']) || $this->antibot_form_check($captcha)) {
176
 
177
+ $subscription = $this->build_subscription();
178
+
179
+ $user = $this->subscribe2($subscription);
180
+
181
+ if (is_wp_error($user)) {
182
+ if ($user->get_error_code() === 'exists') {
183
+ $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
184
+ $options = $this->get_options('', $language);
185
+ $this->dienow($options['error_text'], $user->get_error_message(), 200);
186
+ }
187
+ $this->dienow('Registration failed.', $user->get_error_message(), 400);
188
+ }
189
 
190
+ if ($user->status == TNP_User::STATUS_CONFIRMED)
 
 
191
  $this->show_message('confirmed', $user);
192
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED)
 
 
193
  $this->show_message('confirmation', $user);
194
  } else {
 
 
 
 
195
  $this->request_to_antibot_form('Subscribe', $captcha);
196
  }
197
  die();
204
  $this->dienow('Invalid request');
205
  }
206
 
207
+ $subscription = $this->build_subscription();
208
 
209
+ $user = $this->subscribe2($subscription);
210
+
211
+ if (is_wp_error($user)) {
212
+ $this->dienow('Registration failed.', $user->get_error_message(), 400);
213
+ }
214
+
215
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
216
  $key = 'confirmed';
217
+ }
 
 
 
218
 
219
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
220
+ $key = 'confirmation';
221
+ }
222
 
223
  $message = $this->replace($this->options[$key . '_text'], $user);
224
  if (isset($this->options[$key . '_tracking'])) {
229
 
230
  case 'c':
231
  case 'confirm':
232
+ if (!$user) {
233
+ $this->dienow(__('Subscriber not found.', 'newsletter'), 'Or it is not present or the secret key does not match.', 404);
234
+ }
235
+
236
  if ($this->antibot_form_check()) {
237
+ $user = $this->confirm($user);
238
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
239
+ $this->show_message('confirmed', $user);
 
 
 
 
 
240
  } else {
241
  $this->request_to_antibot_form('Confirm');
242
  }
312
  }
313
 
314
  function first_install() {
315
+
316
  }
317
 
318
  function admin_menu() {
415
  }
416
  }
417
 
 
 
 
 
 
 
418
  function get_form_options($language = '') {
419
  return $this->get_options('profile', $language);
420
  }
441
  $wpdb->update(NEWSLETTER_USERS_TABLE, array('updated' => $time, 'ip' => $ip, 'geo' => 0), array('id' => $id));
442
  }
443
 
444
+ /**
445
+ * Sanitize the subscription data collected before process them. Cleanup the lists, the optin mode, email, first name,
446
+ * last name, adds mandatory lists, get (if not provided) and process the IP.
447
+ *
448
+ * @param TNP_Subscription_Data $data
449
+ */
450
+ private function sanitize($data) {
451
+ $data->email = $this->normalize_email($data->email);
452
+ if (!empty($data->name)) {
453
+ $data->name = $this->normalize_name($data->name);
454
+ }
455
+ if (!empty($data->surname)) {
456
+ $data->surname = $this->normalize_name($data->surname);
457
+ }
458
+
459
+ if (empty($data->ip)) {
460
+ $data->ip = $this->get_remote_ip();
461
+ }
462
+ $data->ip = $this->process_ip($data->ip);
463
+
464
+ if (isset($data->http_referer)) {
465
+ $data->http_referer = mb_substr(strip_tags($data->http_referer), 0, 200);
466
+ }
467
+
468
+ if (isset($data->sex)) {
469
+ $data->sex = $this->normalize_sex($data->sex);
470
+ }
471
+
472
+ if (!isset($data->language)) {
473
+ $data->language = $this->get_current_language();
474
+ } else {
475
+ $data->language = strtolower(strip_tags($data->language));
476
+ }
477
+
478
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
479
+ $key = 'profile_' . $i;
480
+ if (isset($data->$key)) {
481
+ $data->$key = trim($data->$key);
482
+ }
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Builds a default subscription object to be used to collect data and subscription options.
488
+ *
489
+ * @return \TNP_Subscription
490
+ */
491
+ function get_default_subscription() {
492
+ $subscription = new TNP_Subscription();
493
+ $subscription->optin = $this->is_double_optin() ? 'double' : 'single';
494
+ $subscription->if_exists = empty($this->options['multiple']) ? TNP_Subscription::EXISTING_ERROR : TNP_Subscription::EXISTING_MERGE;
495
+
496
+ $language = $this->get_current_language();
497
+ $lists = $this->get_lists();
498
+ foreach ($lists as $list) {
499
+ if ($list->forced) {
500
+ $subscription->data->lists['' . $list->id] = 1;
501
+ continue;
502
+ }
503
+ // Enforced by language
504
+ if (in_array($language, $list->languages)) {
505
+ $subscription->data->lists['' . $list->id] = 1;
506
+ }
507
+ }
508
+
509
+ return $subscription;
510
+ }
511
+
512
+ /**
513
+ *
514
+ * @param TNP_Subscription $subscription
515
+ *
516
+ * @return TNP_User|WP_Error
517
+ */
518
+ function subscribe2(TNP_Subscription $subscription) {
519
+
520
+ $this->logger->debug($subscription);
521
+
522
+ $this->sanitize($subscription->data);
523
+
524
+ if (empty($subscription->data->email)) {
525
+ return new WP_Error('email', 'Wrong email address');
526
+ }
527
+
528
+ if (!empty($subscription->data->country) && strlen($subscription->data->country) != 2) {
529
+ return new WP_Error('country', 'Country code length error. ISO 3166-1 alpha-2 format (2 letters)');
530
+ }
531
+
532
+ // Here we should have a clean subscription data
533
+ // Filter?
534
+
535
+ if ($subscription->spamcheck) {
536
+ // TODO: Use autoload
537
+ require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
538
+ $antispam = new NewsletterAntispam();
539
+ if ($antispam->is_spam($subscription)) {
540
+ return new WP_Error('spam', 'This looks like a spam subscription');
541
+ }
542
+ }
543
+
544
+ // Exists?
545
+ $user = $this->get_user_by_email($subscription->data->email);
546
+
547
+ // Do we accept repeated subscriptions?
548
+ if ($user != null && $subscription->if_exists === TNP_Subscription::EXISTING_ERROR) {
549
+ 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.');
550
+ }
551
+
552
+
553
+ if ($user != null) {
554
+
555
+ $this->logger->info('Subscription of an address with status ' . $user->status);
556
+
557
+ // We cannot communicate with bounced addresses, there is no reason to proceed
558
+ // TODO: Evaluate if the bounce status is very old, possible reset it
559
+ if ($user->status == TNP_User::STATUS_BOUNCED) {
560
+ return new WP_Error('bounced', 'Subscriber present and blocked');
561
+ }
562
+
563
+ // If the existing subscriber esists and is already confirmed, park the data until the new subscription is confirmed itself
564
+ if ($user->status == Newsletter::STATUS_CONFIRMED && $subscription->optin == 'double') {
565
+
566
+ set_transient('newsletter_subscription_' . $user->id, $subscription->data, 3600 * 48);
567
+
568
+ // This status is *not* stored it indicate a temporary status to show the correct messages
569
+ $user->status = TNP_User::STATUS_NOT_CONFIRMED;
570
+
571
+ $this->send_message('confirmation', $user);
572
+
573
+ return $user;
574
+ }
575
+
576
+ // Can be updated on the fly?
577
+ $subscription->data->merge_in($user);
578
+ } else {
579
+ $this->logger->info('New subscriber');
580
+
581
+ $user = new TNP_User();
582
+ $subscription->data->merge_in($user);
583
+
584
+ $user->token = $this->get_token();
585
+
586
+ $user->status = $subscription->optin == 'single' ? Newsletter::STATUS_CONFIRMED : Newsletter::STATUS_NOT_CONFIRMED;
587
+ $user->updated = time();
588
+ }
589
+
590
+ $user = apply_filters('newsletter_user_subscribe', $user);
591
+
592
+ $user = $this->save_user($user);
593
+
594
+ $this->add_user_log($user, 'subscribe');
595
+
596
+ // Notification to admin (only for new confirmed subscriptions)
597
+ if ($user->status == Newsletter::STATUS_CONFIRMED) {
598
+ do_action('newsletter_user_confirmed', $user);
599
+ $this->notify_admin_on_subscription($user);
600
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
601
+ }
602
+
603
+ if ($subscription->send_emails) {
604
+ $this->send_message(($user->status == Newsletter::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
605
+ }
606
+
607
+ // Used by Autoresponder (probably)
608
+ do_action('newsletter_user_post_subscribe', $user);
609
+
610
+ return $user;
611
+ }
612
+
613
  /**
614
  * Create a subscription using the $_REQUEST data. Does security checks.
615
  *
616
+ * @deprecated since version 6.9.0
617
  * @param string $status The status to use for this subscription (confirmed, not confirmed, ...)
618
  * @param bool $emails If the confirmation/welcome email should be sent or the subscription should be silent
619
  * @return TNP_User
620
  */
621
  function subscribe($status = null, $emails = true) {
622
 
623
+ $this->logger->debug('Subscription start');
 
 
 
 
 
 
 
 
 
 
624
 
625
  // Validation
626
  $ip = $this->get_remote_ip();
635
  $last_name = $this->normalize_name(stripslashes($_REQUEST['ns']));
636
  }
637
 
 
 
 
 
638
  $opt_in = (int) $this->options['noconfirmation']; // 0 - double, 1 - single
639
  if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
640
  switch ($_REQUEST['optin']) {
741
  return $message . '<span itemscope itemtype="http://schema.org/EmailMessage"><span itemprop="description" content="Email address confirmation"></span><span itemprop="action" itemscope itemtype="http://schema.org/ConfirmAction"><meta itemprop="name" content="Confirm Subscription"><span itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler"><meta itemprop="url" content="{subscription_confirm_url}"><link itemprop="method" href="http://schema.org/HttpRequestMethod/POST"></span></span></span>';
742
  }
743
 
744
+ /**
745
+ * Builds a subscription object starting from values in the $_REQUEST
746
+ * global variable. It DOES NOT sanitizie or formally check the values.
747
+ * Usually data comes from a form submission.
748
+ * https://www.thenewsletterplugin.com/documentation/subscription/newsletter-forms/
749
+ *
750
+ * @return TNP_Subscription
751
+ */
752
+ function build_subscription() {
753
+ $subscription = $this->get_default_subscription();
754
+ $data = $subscription->data;
755
+
756
+ $data->email = $_REQUEST['ne'];
757
+
758
+ if (isset($_REQUEST['nn'])) {
759
+ $data->name = stripslashes($_REQUEST['nn']);
760
+ }
761
+ // TODO: required checking
762
+
763
+ if (isset($_REQUEST['ns'])) {
764
+ $data->surname = stripslashes($_REQUEST['ns']);
765
+ }
766
+ // TODO: required checking
767
+
768
+ if (!empty($_REQUEST['nx'])) {
769
+ $data->sex = $_REQUEST['nx'][0];
770
+ }
771
+ // TODO: valid values check
772
+
773
+ if (isset($_REQUEST['nr'])) {
774
+ $data->referrer = $_REQUEST['nr'];
775
+ }
776
+
777
+ $language = '';
778
+ if (!empty($_REQUEST['nlang'])) {
779
+ $data->language = $_REQUEST['nlang'];
780
+ }
781
+
782
+ // From the antibot form
783
+ if (isset($_REQUEST['nhr'])) {
784
+ $data->http_referer = stripslashes($_REQUEST['nhr']);
785
+ } else if (isset($_SERVER['HTTP_REFERER'])) {
786
+ $data->http_referer = $_SERVER['HTTP_REFERER'];
787
+ }
788
+
789
+ // New profiles
790
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
791
+ // If the profile cannot be set by subscriber, skip it.
792
+ if ($this->options_profile['profile_' . $i . '_status'] == 0) {
793
+ continue;
794
+ }
795
+ if (isset($_REQUEST['np' . $i])) {
796
+ $data->profiles['' . $i] = stripslashes($_REQUEST['np' . $i]);
797
+ }
798
+ }
799
+
800
+ // Lists (field name is nl[] and values the list number so special forms with radio button can work)
801
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
802
+ $this->logger->debug($_REQUEST['nl']);
803
+ foreach ($_REQUEST['nl'] as $list_id) {
804
+ $list = $this->get_list($list_id);
805
+ if (!$list || $list->is_private()) {
806
+ // To administrator show an error to make him aware of the wrong form configuration
807
+ if (current_user_can('administrator')) {
808
+ $this->dienow('Invalid list', 'List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
809
+ }
810
+ // Ignore this list
811
+ continue;
812
+ }
813
+ $data->lists['' . $list_id] = 1;
814
+ }
815
+ } else {
816
+ $this->logger->debug('No lists received');
817
+ }
818
+
819
+ // Opt-in mode
820
+ if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
821
+ switch ($_REQUEST['optin']) {
822
+ case 'single': $subscription->optin = 'single';
823
+ break;
824
+ case 'double': $subscription->optin = 'double';
825
+ break;
826
+ }
827
+ }
828
+
829
+ return $subscription;
830
+ }
831
+
832
  /**
833
  * Processes the request and fill in the *array* representing a subscriber with submitted values
834
  * (filtering when necessary).
835
  *
836
+ * @deprecated since version 6.9.0
837
  * @param array $user An array partially filled with subscriber data
838
  * @return array The filled array representing a subscriber
839
  */
897
  foreach ($_REQUEST['nl'] as $list_id) {
898
  $list = $this->get_list($list_id);
899
  if ($list && $list->status == TNP_List::STATUS_PRIVATE) {
900
+ $this->dienow('Invalid list', '[old] List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
901
  }
902
  }
903
  }
904
  }
 
905
  // Preferences (field names are nl[] and values the list number so special forms with radio button can work)
906
+ // Permetto l'aggiunta solo delle liste pubbliche
907
  if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
908
  $lists = $this->get_lists_public();
909
  //$this->logger->debug($_REQUEST['nl']);
917
  }
918
 
919
  // Forced lists (general or by language)
920
+ // Forzo l'aggiunta delle liste forzate
921
  $lists = $this->get_lists();
922
  foreach ($lists as $list) {
923
  if ($list->forced) {
972
  * Confirms a subscription changing the user status and, possibly, merging the
973
  * temporary data if present.
974
  *
975
+ * @param TNP_User $user Optionally it can be null (user search from requests paramaters, but deprecated, or a user id)
976
  * @return TNP_User
977
  */
978
+ function confirm($user = null, $emails = true) {
979
 
980
+ // Compatibility with WP Registration Addon
981
+ if (!$user) {
982
  $user = $this->get_user_from_request(true);
983
+ } else if (is_numeric($user)) {
984
+ $user = $this->get_user($user);
985
+ }
986
+
987
+ if (!$user) {
988
+ $this->dienow('Subscriber not found', '', 404);
989
+ }
990
+ // End compatibility
991
+ // Should be merged?
992
+ $data = get_transient('newsletter_subscription_' . $user->id);
993
+ if ($data !== false) {
994
+ $data->merge_in($user);
995
+ //$this->merge($user, $data);
996
+ $user = $this->save_user($user);
997
+ $user->status = Newsletter::STATUS_NOT_CONFIRMED;
998
+ delete_transient('newsletter_subscription_' . $user->id);
999
+ } else {
1000
  // Is there any temporary data from a subscription to be confirmed?
1001
  $data = get_transient($this->get_user_key($user));
1002
  if ($data !== false) {
1008
  // Forced a fake status so the welcome email is sent
1009
  $user->status = Newsletter::STATUS_NOT_CONFIRMED;
1010
  }
 
 
 
 
 
1011
  }
1012
 
1013
+
1014
  $this->update_user_last_activity($user);
1015
 
1016
  setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
1120
 
1121
  /**
1122
  * Generates the privacy URL and cache it.
1123
+ *
1124
  * @return string
1125
  */
1126
  function get_privacy_url() {
1181
 
1182
  /**
1183
  * Manages the custom forms made with [newsletter_form] and internal [newsletter_field] shorcodes.
1184
+ *
1185
  * @param array $attrs
1186
  * @param string $content
1187
  * @return string
1193
 
1194
  $this->setup_form_options();
1195
 
1196
+ $attrs = array_merge(['class' => 'tnp-subscription', 'style' => ''], $attrs);
1197
 
1198
  $action = esc_attr($this->build_action_url('s'));
1199
  $class = esc_attr($attrs['class']);
1225
  }
1226
 
1227
  if (isset($attrs['lists'])) {
1228
+ $buffer .= $this->_form_implicit_lists($attrs['lists'], $language);
 
 
 
1229
  }
1230
 
1231
  $buffer .= do_shortcode($content);
1251
  return $buffer;
1252
  }
1253
 
1254
+ /** Generates the hidden field for lists which should be implicitely set with a subscription form.
1255
+ *
1256
+ * @param string $lists Comma separated directly from the shortcode "lists" attribute
1257
+ * @param string $language ???
1258
+ * @return string
1259
+ */
1260
+ function _form_implicit_lists($lists, $language) {
1261
+ $buffer = '';
1262
+ $arr = explode(',', $lists);
1263
+ foreach ($arr as $a) {
1264
+ $a = trim($a);
1265
+ $list = $this->get_list($a, $language);
1266
+ if (!$list) {
1267
+ $buffer .= $this->build_field_admin_notice('List ' . $a . ' added in the form is not configured, skipped.');
1268
+ continue;
1269
+ }
1270
+
1271
+ if ($list->is_private()) {
1272
+ $buffer .= $this->build_field_admin_notice('List ' . $a . ' is private cannot be used in a public form.');
1273
+ continue;
1274
+ }
1275
+
1276
+ if ($list->forced) {
1277
+ $buffer .= $this->build_field_admin_notice('List ' . $a . ' is already enforced on every subscription there is no need to specify it.');
1278
+ continue;
1279
+ }
1280
+
1281
+ $buffer .= "<input type='hidden' name='nl[]' value='" . esc_attr($a) . "'>\n";
1282
+ }
1283
+ return $buffer;
1284
+ }
1285
+
1286
  /**
1287
  * Internal use only
1288
  *
1305
  $buffer .= esc_html($attrs['label']);
1306
  }
1307
  } else {
1308
+ if (isset($this->form_options[$name])) {
1309
+ $buffer .= esc_html($this->form_options[$name]);
1310
+ }
1311
  }
1312
  $buffer .= "</label>\n";
1313
  return $buffer;
1320
  return '<p style="background-color: #eee; color: #000; padding: 10px; margin: 10px 0">' . $message . ' <strong>This notice is shown only to administrators.</strong></p>';
1321
  }
1322
 
1323
+ function shortcode_newsletter_field($attrs, $content = '') {
1324
  $this->setup_form_options();
1325
  $language = $this->get_current_language();
1326
 
1333
  $buffer .= $this->_shortcode_label('email', $attrs);
1334
 
1335
  $buffer .= '<input class="tnp-email" type="email" name="ne" value=""';
1336
+ if (isset($attrs['placeholder'])) {
1337
  $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1338
+ }
1339
  $buffer .= ' required>';
1340
  if (isset($attrs['button_label'])) {
1341
  $label = $attrs['button_label'];
1407
  }
1408
  $buffer .= '>';
1409
  if (isset($attrs['label'])) {
1410
+ if ($attrs['label'] != '') {
1411
  $buffer .= '&nbsp;' . esc_html($attrs['label']) . '</label>';
1412
+ }
1413
  } else {
1414
  $buffer .= '&nbsp;' . esc_html($list->name) . '</label>';
1415
  }
1420
 
1421
  // All lists
1422
  if ($name == 'lists' || $name == 'preferences') {
 
1423
  $lists = $this->get_lists_for_subscription($language);
1424
+ if (isset($attrs['layout']) && $attrs['layout'] === 'dropdown') {
1425
+
1426
+ $buffer .= '<div class="tnp-field tnp-lists">';
1427
+ $buffer .= '<select class="tnp-lists" name="nl[]" required>';
1428
+ // There is not a default "label" for the block of lists, so it can only be specified in the shortcode attrs as "label"
1429
+ $buffer .= $this->_shortcode_label('lists', $attrs);
1430
+
1431
+ if (!empty($attrs['first_option_label'])) {
1432
+ $buffer .= '<option value="">' . esc_html($attrs['first_option_label']) . '</option>';
1433
+ }
1434
+
1435
+ foreach ($lists as $list) {
1436
+ $buffer .= '<option value="' . $list->id . '">' . esc_html($list->name) . '</option>';
1437
+ }
1438
+ $buffer .= '</select>';
1439
+ $buffer .= '</div>';
1440
+ } else {
1441
+
1442
+ foreach ($lists as $list) {
1443
+ $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="nl' . $list->id . '">';
1444
+ $buffer .= '<input type="checkbox" id="nl' . $list->id . '" name="nl[]" value="' . $list->id . '"';
1445
+ if ($list->checked) {
1446
+ $buffer .= ' checked';
1447
+ }
1448
+ $buffer .= '>&nbsp;' . esc_html($list->name) . '</label>';
1449
+ $buffer .= "</div>\n";
1450
  }
 
 
1451
  }
1452
+ return $buffer;
1453
  }
1454
 
1455
  // TODO: add the "not specified"
1557
 
1558
  /**
1559
  * Builds the privacy field only for completely generated forms.
1560
+ *
1561
  * @return string Empty id the privacy filed is not configured
1562
  */
1563
  function get_privacy_field($pre_html = '', $post_html = '') {
1587
  /**
1588
  * Creates the hidden alternative optin field on custom form showing notices if used in the wrong
1589
  * way.
1590
+ *
1591
  * @since 6.8.3
1592
  * @param string $optin Could be single or double, lowercase
1593
  * @return string The complete HTML field
1618
  * @param type $attrs
1619
  * @return string
1620
  */
1621
+ function get_subscription_form($referrer = '', $action = null, $attrs = array()) {
1622
  $language = $this->get_current_language();
1623
  $options_profile = $this->get_options('profile', $language);
1624
 
1633
 
1634
  $buffer = '';
1635
 
 
 
1636
  if (empty($action)) {
1637
  $action = $this->build_action_url('s');
1638
  }
1664
  $attrs['lists'] = $attrs['list'];
1665
  }
1666
 
 
1667
  if (isset($attrs['lists'])) {
1668
+ $buffer .= $this->_form_implicit_lists($attrs['lists'], $language);
 
 
 
1669
  }
1670
 
1671
  if ($options_profile['name_status'] == 2) {
1672
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'first_name']);
 
 
1673
  }
1674
 
1675
  if ($options_profile['surname_status'] == 2) {
1676
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'last_name']);
 
 
1677
  }
1678
 
1679
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'email']);
 
 
1680
 
1681
  if (isset($options_profile['sex_status']) && $options_profile['sex_status'] == 2) {
1682
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'gender']);
 
 
 
 
 
 
 
 
 
 
1683
  }
1684
 
1685
  $tmp = '';
1686
  $lists = $this->get_lists_for_subscription($language);
1687
  if (!empty($attrs['lists_field_layout']) && $attrs['lists_field_layout'] == 'dropdown') {
1688
+ if (empty($attrs['lists_field_empty_label'])) {
1689
+ $attrs['lists_field_empty_label'] = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1690
  }
1691
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'lists', 'layout' => 'dropdown', 'first_option_label' => $attrs['lists_field_empty_label']]);
1692
  } else {
1693
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'lists']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1694
  }
1695
 
1696
  // Extra profile fields
1700
  continue;
1701
  }
1702
 
1703
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'profile', 'number' => $i]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1704
  }
1705
 
1706
  $extra = apply_filters('newsletter_subscription_extra', array());
1739
  return $buffer;
1740
  }
1741
 
1742
+ /**
1743
+ *
1744
+ * @deprecated since version 6.0.0
1745
+ * @param type $user
1746
+ * @return type
1747
+ */
1748
  function get_profile_form($user) {
1749
  return NewsletterProfile::instance()->get_profile_form($user);
1750
  }
1800
  Newsletter::instance()->mail($email, $subject, array('html' => $message));
1801
  }
1802
 
1803
+ /**
1804
+ * Builds the minimal subscription form, with only the email field and inline
1805
+ * submit button. If enabled the privacy checkbox is added.
1806
+ *
1807
+ * @param type $attrs
1808
+ * @return string
1809
+ */
1810
  function get_subscription_form_minimal($attrs) {
1811
 
1812
  $language = $this->get_current_language();
1829
  if (isset($attrs['optin'])) {
1830
  $form .= $this->build_optin_field($attrs['optin']);
1831
  }
1832
+
1833
  if (isset($attrs['confirmation_url'])) {
1834
  $form .= "<input type='hidden' name='ncu' value='" . esc_attr($attrs['confirmation_url']) . "'>\n";
1835
+ }
1836
 
1837
  if (isset($attrs['lists'])) {
1838
+ $form .= $this->_form_implicit_lists($attrs['lists'], $language);
 
 
 
1839
  }
1840
  $form .= '<input type="hidden" name="nr" value="' . esc_attr($attrs['referrer']) . '">';
1841
  $form .= '<input type="hidden" name="nlang" value="' . esc_attr($language) . '">' . "\n";
1843
  $form .= '<input class="tnp-submit" type="submit" value="' . esc_attr($attrs['button']) . '"'
1844
  . ' style="background-color:' . esc_attr($attrs['button_color']) . '">';
1845
 
1846
+ $form .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1847
 
1848
  $form .= "</form></div>\n";
1849
 
1947
  // Compatibility code
1948
 
1949
  /**
1950
+ * @deprecated
1951
  * @param int $number
1952
  */
1953
  function newsletter_form($number = null) {
users/users.php CHANGED
@@ -1,49 +1,50 @@
1
  <?php
2
 
3
- defined('ABSPATH') || exit;
4
 
5
  class NewsletterUsers extends NewsletterModule {
6
 
7
- static $instance;
8
-
9
- /**
10
- * @return NewsletterUsers
11
- */
12
- static function instance() {
13
- if (self::$instance == null) {
14
- self::$instance = new NewsletterUsers();
15
- }
16
- return self::$instance;
17
- }
18
-
19
- function __construct() {
20
- parent::__construct('users', '1.3.0');
21
- if (is_admin()) {
22
- add_action('wp_ajax_newsletter_users_export', array($this, 'hook_wp_ajax_newsletter_users_export'));
23
- }
24
- }
25
-
26
- function hook_wp_ajax_newsletter_users_export() {
27
-
28
- $newsletter = Newsletter::instance();
29
- if ($newsletter->is_allowed()) {
30
- require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
31
- $controls = new NewsletterControls();
32
-
33
- if ($controls->is_action('export')) {
34
- $this->export($controls->data);
35
- }
36
- } else {
37
- die('Not allowed.');
38
- }
39
- }
40
-
41
- function upgrade() {
42
- global $wpdb, $charset_collate;
43
-
44
- parent::upgrade();
45
-
46
- $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter` (
 
47
  `name` varchar(100) NOT NULL DEFAULT '',
48
  `email` varchar(100) NOT NULL DEFAULT '',
49
  `token` varchar(50) NOT NULL DEFAULT '',
@@ -74,128 +75,181 @@ class NewsletterUsers extends NewsletterModule {
74
  `unsub_email_id` int(11) NOT NULL DEFAULT '0',
75
  `unsub_time` int(11) NOT NULL DEFAULT '0',\n";
76
 
77
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
78
- $sql .= "`list_$i` tinyint(4) NOT NULL DEFAULT '0',\n";
79
- }
80
-
81
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
82
- $sql .= "`profile_$i` varchar(255) NOT NULL DEFAULT '',\n";
83
- }
84
- // Leave as last
85
- $sql .= "`test` tinyint(4) NOT NULL DEFAULT '0',\n";
86
- $sql .= "PRIMARY KEY (`id`),\nUNIQUE KEY `email` (`email`),\nKEY `wp_user_id` (`wp_user_id`)\n) $charset_collate;";
87
-
88
- dbDelta($sql);
89
-
90
- if ($this->old_version < '1.2.7') {
91
- $this->query("update " . NEWSLETTER_USERS_TABLE . " set geo=1 where country<>''");
92
-
93
- }
94
- if ($this->old_version > '1.2.5' && $this->old_version < '1.2.9') {
95
- $this->upgrade_query("ALTER TABLE " . NEWSLETTER_USERS_TABLE . " DROP COLUMN last_ip;");
96
- }
97
-
98
- }
99
-
100
- function admin_menu() {
101
- $this->add_menu_page('index', 'Subscribers');
102
- $this->add_admin_page('new', 'New subscriber');
103
- $this->add_admin_page('edit', 'Subscribers Edit');
104
- $this->add_admin_page('massive', 'Massive Management');
105
- $this->add_admin_page('export', 'Export');
106
- $this->add_admin_page('import', 'Import');
107
- $this->add_admin_page('statistics', 'Statistics');
108
- }
109
-
110
- function export($options = null) {
111
- global $wpdb;
112
-
113
- header('Content-Type: application/octet-stream');
114
- header('Content-Disposition: attachment; filename="newsletter-subscribers.csv"');
115
-
116
- // BOM
117
- echo "\xEF\xBB\xBF";
118
-
119
- $sep = ';';
120
- if ($options) {
121
- $sep = $options['separator'];
122
- }
123
- if ($sep == 'tab') {
124
- $sep = "\t";
125
- }
126
-
127
- // CSV header
128
- echo '"Email"' . $sep . '"Name"' . $sep . '"Surname"' . $sep . '"Gender"' . $sep . '"Status"' . $sep . '"Date"' . $sep . '"Token"' . $sep;
129
-
130
- // In table profiles
131
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
132
- echo '"Profile ' . $i . '"' . $sep; // To adjust with field name
133
- }
134
-
135
- // Lists
136
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
137
- echo '"List ' . $i . '"' . $sep;
138
- }
139
-
140
- echo '"Feed by mail"' . $sep . '"Follow up"' . $sep;
141
- echo '"IP"' . $sep . '"Referrer"' . $sep . '"Country"';
142
-
143
- echo "\n";
144
-
145
- $page = 0;
146
- while (true) {
147
- $query = "select * from " . NEWSLETTER_USERS_TABLE . "";
148
- $list = (int) $_POST['options']['list'];
149
- if (!empty($list)) {
150
- $query .= " where list_" . $list . "=1";
151
- }
152
- $recipients = $wpdb->get_results($query . " order by email limit " . $page * 500 . ",500");
153
- for ($i = 0; $i < count($recipients); $i++) {
154
- echo '"' . $recipients[$i]->email . '"' . $sep . '"' . $this->sanitize_csv($recipients[$i]->name) .
155
- '"' . $sep . '"' . $this->sanitize_csv($recipients[$i]->surname) .
156
- '"' . $sep . '"' . $recipients[$i]->sex .
157
- '"' . $sep . '"' . $recipients[$i]->status . '"' . $sep . '"' . $recipients[$i]->created . '"' . $sep . '"' . $recipients[$i]->token . '"' . $sep;
158
-
159
- for ($j = 1; $j <= NEWSLETTER_PROFILE_MAX; $j++) {
160
- $column = 'profile_' . $j;
161
- echo '"' . $this->sanitize_csv($recipients[$i]->$column) . '"' . $sep;
162
- }
163
-
164
- for ($j = 1; $j <= NEWSLETTER_LIST_MAX; $j++) {
165
- $list = 'list_' . $j;
166
- echo '"' . $recipients[$i]->$list . '"' . $sep;
167
- }
168
-
169
- echo '"' . $recipients[$i]->feed . '"' . $sep;
170
- echo '"' . $recipients[$i]->followup . '"' . $sep;
171
- echo '"' . $recipients[$i]->ip . '"' . $sep;
172
- echo '"' . $recipients[$i]->referrer . '"' . $sep;
173
- echo '"' . $recipients[$i]->country . '"' . $sep;
174
-
175
- echo "\n";
176
- flush();
177
- }
178
- if (count($recipients) < 500)
179
- break;
180
- $page++;
181
- }
182
- die();
183
- }
184
-
185
- function sanitize_csv($text) {
186
- $text = str_replace('"', "'", $text);
187
- $text = str_replace("\n", ' ', $text);
188
- $text = str_replace("\r", ' ', $text);
189
- $text = str_replace(";", ' ', $text);
190
-
191
- // Do you wonder? Excel...
192
- $first = substr($text, 0, 1);
193
- if ($first == '=' || $first == '+' || $first == '-' || $first == '@')
194
- {
195
- $text = "'" . $text;
196
- }
197
- return $text;
198
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
  }
201
 
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || exit;
4
 
5
  class NewsletterUsers extends NewsletterModule {
6
 
7
+ static $instance;
8
+
9
+ /**
10
+ * @return NewsletterUsers
11
+ */
12
+ static function instance() {
13
+ if ( self::$instance == null ) {
14
+ self::$instance = new NewsletterUsers();
15
+ }
16
+
17
+ return self::$instance;
18
+ }
19
+
20
+ function __construct() {
21
+ parent::__construct( 'users', '1.3.0' );
22
+ if ( is_admin() ) {
23
+ add_action( 'wp_ajax_newsletter_users_export', array( $this, 'hook_wp_ajax_newsletter_users_export' ) );
24
+ }
25
+ }
26
+
27
+ function hook_wp_ajax_newsletter_users_export() {
28
+
29
+ $newsletter = Newsletter::instance();
30
+ if ( $newsletter->is_allowed() ) {
31
+ require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
32
+ $controls = new NewsletterControls();
33
+
34
+ if ( $controls->is_action( 'export' ) ) {
35
+ $this->export( $controls->data );
36
+ }
37
+ } else {
38
+ die( 'Not allowed.' );
39
+ }
40
+ }
41
+
42
+ function upgrade() {
43
+ global $wpdb, $charset_collate;
44
+
45
+ parent::upgrade();
46
+
47
+ $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter` (
48
  `name` varchar(100) NOT NULL DEFAULT '',
49
  `email` varchar(100) NOT NULL DEFAULT '',
50
  `token` varchar(50) NOT NULL DEFAULT '',
75
  `unsub_email_id` int(11) NOT NULL DEFAULT '0',
76
  `unsub_time` int(11) NOT NULL DEFAULT '0',\n";
77
 
78
+ for ( $i = 1; $i <= NEWSLETTER_LIST_MAX; $i ++ ) {
79
+ $sql .= "`list_$i` tinyint(4) NOT NULL DEFAULT '0',\n";
80
+ }
81
+
82
+ for ( $i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i ++ ) {
83
+ $sql .= "`profile_$i` varchar(255) NOT NULL DEFAULT '',\n";
84
+ }
85
+ // Leave as last
86
+ $sql .= "`test` tinyint(4) NOT NULL DEFAULT '0',\n";
87
+ $sql .= "PRIMARY KEY (`id`),\nUNIQUE KEY `email` (`email`),\nKEY `wp_user_id` (`wp_user_id`)\n) $charset_collate;";
88
+
89
+ dbDelta( $sql );
90
+
91
+ if ( $this->old_version < '1.2.7' ) {
92
+ $this->query( "update " . NEWSLETTER_USERS_TABLE . " set geo=1 where country<>''" );
93
+
94
+ }
95
+ if ( $this->old_version > '1.2.5' && $this->old_version < '1.2.9' ) {
96
+ $this->upgrade_query( "ALTER TABLE " . NEWSLETTER_USERS_TABLE . " DROP COLUMN last_ip;" );
97
+ }
98
+
99
+ }
100
+
101
+ function admin_menu() {
102
+ $this->add_menu_page( 'index', 'Subscribers' );
103
+ $this->add_admin_page( 'new', 'New subscriber' );
104
+ $this->add_admin_page( 'edit', 'Subscribers Edit' );
105
+ $this->add_admin_page( 'massive', 'Massive Management' );
106
+ $this->add_admin_page( 'export', 'Export' );
107
+ $this->add_admin_page( 'import', 'Import' );
108
+ $this->add_admin_page( 'statistics', 'Statistics' );
109
+ }
110
+
111
+ function export( $options = null ) {
112
+ global $wpdb;
113
+
114
+ header( 'Content-Type: application/octet-stream' );
115
+ header( 'Content-Disposition: attachment; filename="newsletter-subscribers.csv"' );
116
+
117
+ // BOM
118
+ echo "\xEF\xBB\xBF";
119
+
120
+ $sep = ';';
121
+ if ( $options ) {
122
+ $sep = $options['separator'];
123
+ }
124
+ if ( $sep == 'tab' ) {
125
+ $sep = "\t";
126
+ }
127
+
128
+ // CSV header
129
+ echo '"Email"' . $sep . '"Name"' . $sep . '"Surname"' . $sep . '"Gender"' . $sep . '"Status"' . $sep . '"Date"' . $sep . '"Token"' . $sep;
130
+
131
+ // In table profiles
132
+ for ( $i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i ++ ) {
133
+ echo '"Profile ' . $i . '"' . $sep; // To adjust with field name
134
+ }
135
+
136
+ // Lists
137
+ for ( $i = 1; $i <= NEWSLETTER_LIST_MAX; $i ++ ) {
138
+ echo '"List ' . $i . '"' . $sep;
139
+ }
140
+
141
+ echo '"Feed by mail"' . $sep . '"Follow up"' . $sep;
142
+ echo '"IP"' . $sep . '"Referrer"' . $sep . '"Country"';
143
+
144
+ echo "\n";
145
+
146
+ $page = 0;
147
+ while ( true ) {
148
+ $query = "select * from " . NEWSLETTER_USERS_TABLE . "";
149
+ $list = (int) $_POST['options']['list'];
150
+ if ( ! empty( $list ) ) {
151
+ $query .= " where list_" . $list . "=1";
152
+ }
153
+ $recipients = $wpdb->get_results( $query . " order by email limit " . $page * 500 . ",500" );
154
+ for ( $i = 0; $i < count( $recipients ); $i ++ ) {
155
+ echo '"' . $recipients[ $i ]->email . '"' . $sep . '"' . $this->sanitize_csv( $recipients[ $i ]->name ) .
156
+ '"' . $sep . '"' . $this->sanitize_csv( $recipients[ $i ]->surname ) .
157
+ '"' . $sep . '"' . $recipients[ $i ]->sex .
158
+ '"' . $sep . '"' . $recipients[ $i ]->status . '"' . $sep . '"' . $recipients[ $i ]->created . '"' . $sep . '"' . $recipients[ $i ]->token . '"' . $sep;
159
+
160
+ for ( $j = 1; $j <= NEWSLETTER_PROFILE_MAX; $j ++ ) {
161
+ $column = 'profile_' . $j;
162
+ echo '"' . $this->sanitize_csv( $recipients[ $i ]->$column ) . '"' . $sep;
163
+ }
164
+
165
+ for ( $j = 1; $j <= NEWSLETTER_LIST_MAX; $j ++ ) {
166
+ $list = 'list_' . $j;
167
+ echo '"' . $recipients[ $i ]->$list . '"' . $sep;
168
+ }
169
+
170
+ echo '"' . $recipients[ $i ]->feed . '"' . $sep;
171
+ echo '"' . $recipients[ $i ]->followup . '"' . $sep;
172
+ echo '"' . $recipients[ $i ]->ip . '"' . $sep;
173
+ echo '"' . $recipients[ $i ]->referrer . '"' . $sep;
174
+ echo '"' . $recipients[ $i ]->country . '"' . $sep;
175
+
176
+ echo "\n";
177
+ flush();
178
+ }
179
+ if ( count( $recipients ) < 500 ) {
180
+ break;
181
+ }
182
+ $page ++;
183
+ }
184
+ die();
185
+ }
186
+
187
+ function sanitize_csv( $text ) {
188
+ $text = str_replace( '"', "'", $text );
189
+ $text = str_replace( "\n", ' ', $text );
190
+ $text = str_replace( "\r", ' ', $text );
191
+ $text = str_replace( ";", ' ', $text );
192
+
193
+ // Do you wonder? Excel...
194
+ $first = substr( $text, 0, 1 );
195
+ if ( $first == '=' || $first == '+' || $first == '-' || $first == '@' ) {
196
+ $text = "'" . $text;
197
+ }
198
+
199
+ return $text;
200
+ }
201
+
202
+ /**
203
+ * @param array $args
204
+ * @param string $format
205
+ *
206
+ * @return array|object|null
207
+ */
208
+ function get_users( $args, $format = OBJECT ) {
209
+ global $wpdb;
210
+
211
+ $default_args = array(
212
+ 'page' => 1,
213
+ 'per_page' => 10
214
+ );
215
+
216
+ $args = array_merge( $default_args, $args );
217
+
218
+ $query = 'SELECT * FROM ' . NEWSLETTER_USERS_TABLE . ' ';
219
+ $query_args = [];
220
+
221
+ $query .= ' LIMIT %d OFFSET %d';
222
+ $query_args[] = (int) $args['per_page'];
223
+ $query_args[] = ( (int) $args['page'] - 1 ) * (int) $args['per_page'];
224
+
225
+
226
+ $records = $wpdb->get_results( $wpdb->prepare( $query, $query_args ), $format );
227
+
228
+ if ( $wpdb->last_error ) {
229
+ $this->logger->error( $wpdb->last_error );
230
+
231
+ return null;
232
+ }
233
+
234
+ return $records;
235
+
236
+ }
237
+
238
+ /**
239
+ * Check if email exists
240
+ *
241
+ * @param string $email
242
+ *
243
+ * @return bool
244
+ */
245
+ function email_exists( $email ) {
246
+
247
+ $email = parent::normalize_email( $email );
248
+ $user = parent::get_user( $email );
249
+
250
+ return $user ? true : false;
251
+
252
+ }
253
 
254
  }
255