Newsletter - Version 7.3.9

Version Description

  • Fixed grid layout not showing correctly on gmail
  • Fixed notice on widget page about the wrong editor enqueued
  • Removed wrong top bar warning
  • Fixed block options on posts block
  • Fixed padding on two column posts block
Download this release

Release Info

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

Code changes from version 7.3.8 to 7.3.9

admin/fields.css CHANGED
@@ -1,203 +1,203 @@
1
- html {
2
- --tnpf-col-padding: 0 10px;
3
- }
4
-
5
- .tnpf-form {
6
- padding: 15px;
7
- margin-top: 0;
8
- color: #444;
9
- background-color: #ECF0F1 !important;
10
- }
11
-
12
- .tnp-field-row, .tnpf-row {
13
- clear: both;
14
- margin-left: -10px;
15
- margin-right: -10px;
16
- }
17
-
18
- .tnp-field-col-2, .tnpf-col-2, .tnpf-col-50 {
19
- width: 50%;
20
- box-sizing: border-box;
21
- padding: 0 10px;
22
- float: left;
23
- }
24
-
25
- .tnp-field-col-3, .tnp-field-col-33, .tnpf-col-33 {
26
- width: 33%;
27
- box-sizing: border-box;
28
- padding: 0 10px;
29
- float: left;
30
- }
31
-
32
- .tnp-field-col-20, .tnpf-col-20 {
33
- width: 20%;
34
- box-sizing: border-box;
35
- padding: 0 10px;
36
- float: left;
37
- }
38
-
39
- .tnp-field-col-66, .tnpf-col-66 {
40
- width: 66%;
41
- box-sizing: border-box;
42
- padding: 0 10px;
43
- float: left;
44
- }
45
-
46
- .tnp-field-col-80, .tnpf-col-80 {
47
- width: 80%;
48
- box-sizing: border-box;
49
- padding: 0 10px;
50
- float: left;
51
- }
52
-
53
- .tnpf-field-box {
54
- padding: 10px;
55
- background-color: #eee !important;
56
- border: 1px solid #bbb;
57
- }
58
-
59
- .tnpf-section {
60
- font-size: 1.0rem;
61
- font-weight: bold;
62
- color: #333;
63
- margin-top: 0;
64
- margin-bottom: 10px;
65
- }
66
-
67
- .tnpf-separator {
68
- border-top: 1px solid #ddd;
69
- line-height: 0;
70
- display: block;
71
- width: 100%;
72
- margin-bottom: 10px;
73
- }
74
-
75
- /* Single field container */
76
- .tnpf-field, .tnpf-field {
77
- display: block;
78
- width: 100%;
79
- margin-bottom: 10px;
80
- }
81
-
82
- .tnpf-field select.tnpf-small,
83
- .tnpf-field select.tnp-small {
84
- font-size: .9em;
85
- }
86
-
87
- .tnpf-field label.tnpf-label,
88
- .tnpf-field label.tnp-label {
89
- display: block;
90
- font-size: 12px;
91
- font-weight: 300;
92
- border: 0;
93
- margin: 0px 0px 0px 0px;
94
- font-family: soleil, sans-serif;
95
- font-weight: 300;
96
- padding-bottom: 5px;
97
- color: #666;
98
- }
99
-
100
- .tnpf-row label.tnpf-row-label,
101
- .tnpf-field-row label.tnp-row-label {
102
- display: block;
103
- font-size: 12px;
104
- font-weight: 300;
105
- border: 0;
106
- margin: 0px 0px 0px 10px;
107
- font-family: soleil, sans-serif;
108
- font-weight: 300;
109
- padding-bottom: 5px;
110
- color: #666;
111
- }
112
-
113
- /* Compesate the negative row margin */
114
- .tnpf-field-row label.tnp-row-label {
115
- margin-left: 10px;
116
- }
117
-
118
- .tnpf-row .tnpf-field .tnpf-label,
119
- .tnpf-row .tnpf-field .tnp-label
120
- {
121
- margin-top: 0;
122
- }
123
-
124
- .tnpf-field.tnpf-checkbox label {
125
- display: inline;
126
- }
127
-
128
- /* Set of inlined field for font, size, color... */
129
-
130
- .tnpf-field:not(.tnp-colorpicker) input {
131
- width: 100%;
132
- }
133
-
134
- .tnpf-field input[type=number] {
135
- width: 100px;
136
- }
137
-
138
- .tnpf-field.tnpf-size input {
139
- width: 60px;
140
- display: inline;
141
- }
142
-
143
- .tnpf-field .tnp-padding-fields {
144
- display: inline;
145
- }
146
-
147
- /* Four field for block padding selection */
148
- .tnpf-field .tnp-padding-fields input {
149
- width: 40px;
150
- display: inline;
151
- }
152
-
153
- .tnpf-field select {
154
- width: auto!important;
155
- color: #34495E;
156
- }
157
-
158
- .tnpf-field input[type=url] {
159
- font-family: monospace;
160
- }
161
-
162
- .tnpf-field input[type=color] {
163
- width: 40px;
164
- min-height: 30px;
165
- vertical-align: middle;
166
- }
167
-
168
- .tnpf-field input[type=submit] {
169
- width: auto;
170
- }
171
-
172
- .tnpf-field input[type=checkbox] {
173
- width: auto;
174
- }
175
-
176
- .tnpf-field.tnpf-button .tnpf-font-family {
177
- font-size: 13px;
178
- }
179
-
180
- .tnpf-field.tnpf-button .tnpf-font-size {
181
- font-size: 13px;
182
- }
183
-
184
- .tnpf-field.tnpf-button .tnpf-font-weight {
185
- font-size: 13px;
186
- }
187
-
188
- .tnpf-field.tnp-categories {
189
-
190
- }
191
-
192
- /* The label for each category checkbox (should override the generic label appearance) */
193
- .tnpf-field.tnp-categories label {
194
-
195
- }
196
-
197
- .tnpf-description {
198
- margin-top: 3px;
199
- font-style: italic;
200
- font-weight: normal;
201
- color: #999999;
202
- font-size: 12px;
203
- }
1
+ html {
2
+ --tnpf-col-padding: 0 10px;
3
+ }
4
+
5
+ .tnpf-form {
6
+ padding: 15px;
7
+ margin-top: 0;
8
+ color: #444;
9
+ background-color: #ECF0F1 !important;
10
+ }
11
+
12
+ .tnp-field-row, .tnpf-row {
13
+ clear: both;
14
+ margin-left: -10px;
15
+ margin-right: -10px;
16
+ }
17
+
18
+ .tnp-field-col-2, .tnpf-col-2, .tnpf-col-50 {
19
+ width: 50%;
20
+ box-sizing: border-box;
21
+ padding: 0 10px;
22
+ float: left;
23
+ }
24
+
25
+ .tnp-field-col-3, .tnp-field-col-33, .tnpf-col-33 {
26
+ width: 33%;
27
+ box-sizing: border-box;
28
+ padding: 0 10px;
29
+ float: left;
30
+ }
31
+
32
+ .tnp-field-col-20, .tnpf-col-20 {
33
+ width: 20%;
34
+ box-sizing: border-box;
35
+ padding: 0 10px;
36
+ float: left;
37
+ }
38
+
39
+ .tnp-field-col-66, .tnpf-col-66 {
40
+ width: 66%;
41
+ box-sizing: border-box;
42
+ padding: 0 10px;
43
+ float: left;
44
+ }
45
+
46
+ .tnp-field-col-80, .tnpf-col-80 {
47
+ width: 80%;
48
+ box-sizing: border-box;
49
+ padding: 0 10px;
50
+ float: left;
51
+ }
52
+
53
+ .tnpf-field-box {
54
+ padding: 10px;
55
+ background-color: #eee !important;
56
+ border: 1px solid #bbb;
57
+ }
58
+
59
+ .tnpf-section {
60
+ font-size: 1.0rem;
61
+ font-weight: bold;
62
+ color: #333;
63
+ margin-top: 0;
64
+ margin-bottom: 10px;
65
+ }
66
+
67
+ .tnpf-separator {
68
+ border-top: 1px solid #ddd;
69
+ line-height: 0;
70
+ display: block;
71
+ width: 100%;
72
+ margin-bottom: 10px;
73
+ }
74
+
75
+ /* Single field container */
76
+ .tnpf-field, .tnpf-field {
77
+ display: block;
78
+ width: 100%;
79
+ margin-bottom: 10px;
80
+ }
81
+
82
+ .tnpf-field select.tnpf-small,
83
+ .tnpf-field select.tnp-small {
84
+ font-size: .9em;
85
+ }
86
+
87
+ .tnpf-field label.tnpf-label,
88
+ .tnpf-field label.tnp-label {
89
+ display: block;
90
+ font-size: 12px;
91
+ font-weight: 300;
92
+ border: 0;
93
+ margin: 0px 0px 0px 0px;
94
+ font-family: soleil, sans-serif;
95
+ font-weight: 300;
96
+ padding-bottom: 5px;
97
+ color: #666;
98
+ }
99
+
100
+ .tnpf-row label.tnpf-row-label,
101
+ .tnp-field-row label.tnp-row-label {
102
+ display: block;
103
+ font-size: 12px;
104
+ font-weight: 300;
105
+ border: 0;
106
+ margin: 0px 0px 0px 10px;
107
+ font-family: soleil, sans-serif;
108
+ font-weight: 300;
109
+ padding-bottom: 5px;
110
+ color: #666;
111
+ }
112
+
113
+ /* Compesate the negative row margin */
114
+ .tnp-field-row label.tnp-row-label {
115
+ margin-left: 10px;
116
+ }
117
+
118
+ .tnpf-row .tnpf-field .tnpf-label,
119
+ .tnpf-row .tnpf-field .tnp-label
120
+ {
121
+ margin-top: 0;
122
+ }
123
+
124
+ .tnpf-field.tnpf-checkbox label {
125
+ display: inline;
126
+ }
127
+
128
+ /* Set of inlined field for font, size, color... */
129
+
130
+ .tnpf-field:not(.tnp-colorpicker) input {
131
+ width: 100%;
132
+ }
133
+
134
+ .tnpf-field input[type=number] {
135
+ width: 100px;
136
+ }
137
+
138
+ .tnpf-field.tnpf-size input {
139
+ width: 60px;
140
+ display: inline;
141
+ }
142
+
143
+ .tnpf-field .tnp-padding-fields {
144
+ display: inline;
145
+ }
146
+
147
+ /* Four field for block padding selection */
148
+ .tnpf-field .tnp-padding-fields input {
149
+ width: 40px;
150
+ display: inline;
151
+ }
152
+
153
+ .tnpf-field select {
154
+ width: auto!important;
155
+ color: #34495E;
156
+ }
157
+
158
+ .tnpf-field input[type=url] {
159
+ font-family: monospace;
160
+ }
161
+
162
+ .tnpf-field input[type=color] {
163
+ width: 40px;
164
+ min-height: 30px;
165
+ vertical-align: middle;
166
+ }
167
+
168
+ .tnpf-field input[type=submit] {
169
+ width: auto;
170
+ }
171
+
172
+ .tnpf-field input[type=checkbox] {
173
+ width: auto;
174
+ }
175
+
176
+ .tnpf-field.tnpf-button .tnpf-font-family {
177
+ font-size: 13px;
178
+ }
179
+
180
+ .tnpf-field.tnpf-button .tnpf-font-size {
181
+ font-size: 13px;
182
+ }
183
+
184
+ .tnpf-field.tnpf-button .tnpf-font-weight {
185
+ font-size: 13px;
186
+ }
187
+
188
+ .tnpf-field.tnp-categories {
189
+
190
+ }
191
+
192
+ /* The label for each category checkbox (should override the generic label appearance) */
193
+ .tnpf-field.tnp-categories label {
194
+
195
+ }
196
+
197
+ .tnpf-description {
198
+ margin-top: 3px;
199
+ font-style: italic;
200
+ font-weight: normal;
201
+ color: #999999;
202
+ font-size: 12px;
203
+ }
emails/blocks/posts/layout-two.php CHANGED
@@ -1,137 +1,137 @@
1
- <?php
2
- $size = array('width' => 600, 'height' => 400, "crop" => true);
3
- $total_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
- $column_width = $total_width / 2 - 20;
5
-
6
- $title_style = TNP_Composer::get_style($options, 'title', $composer, 'title', ['scale' => .8]);
7
- $text_style = TNP_Composer::get_style($options, '', $composer, 'text');
8
-
9
- $items = [];
10
- ?>
11
- <style>
12
- .title {
13
- font-family: <?php echo $title_style->font_family ?>;
14
- font-size: <?php echo $title_style->font_size ?>px;
15
- font-weight: <?php echo $title_style->font_weight ?>;
16
- color: <?php echo $title_style->font_color ?>;
17
- line-height: 1.3em;
18
- padding: 15px 0 0 0;
19
- }
20
-
21
- .excerpt {
22
- font-family: <?php echo $text_style->font_family ?>;
23
- font-size: <?php echo $text_style->font_size ?>px;
24
- font-weight: <?php echo $text_style->font_weight ?>;
25
- color: <?php echo $text_style->font_color ?>;
26
- line-height: 1.4em;
27
- padding: 5px 0 0 0;
28
- }
29
-
30
- .meta {
31
- font-family: <?php echo $text_style->font_family ?>;
32
- color: <?php echo $text_style->font_color ?>;
33
- font-size: <?php echo round($text_style->font_size * 0.9) ?>px;
34
- font-weight: <?php echo $text_style->font_weight ?>;
35
- padding: 10px 0 0 0;
36
- font-style: italic;
37
- line-height: normal !important;
38
- }
39
- .button {
40
- padding: 15px 0;
41
- }
42
- .column-left {
43
- padding-right: 10px;
44
- padding-bottom: 20px;
45
- }
46
- .column-right {
47
- padding-left: 10px;
48
- padding-bottom: 20px;
49
- }
50
-
51
- </style>
52
-
53
-
54
- <?php foreach ($posts AS $p) { ?>
55
- <?php
56
- $media = null;
57
- if ($show_image) {
58
- $media = tnp_composer_block_posts_get_media($p, $size, $image_placeholder_url);
59
- $media->link = tnp_post_permalink($p);
60
- $media->set_width($column_width);
61
- }
62
-
63
- $meta = [];
64
-
65
- if ($show_date) {
66
- $meta[] = tnp_post_date($p);
67
- }
68
-
69
- if ($show_author) {
70
- $author_object = get_user_by('id', $p->post_author);
71
- if ($author_object) {
72
- $meta[] = $author_object->display_name;
73
- }
74
- }
75
-
76
- $button_options['button_url'] = tnp_post_permalink($p);
77
- ob_start();
78
- ?>
79
-
80
-
81
-
82
- <table cellpadding="0" cellspacing="0" border="0" width="100%">
83
- <?php if ($media) { ?>
84
- <tr>
85
- <td align="center" valign="middle">
86
- <?php echo TNP_Composer::image($media, ['class' => 'fluid']) ?>
87
- </td>
88
- </tr>
89
- <?php } ?>
90
- <tr>
91
- <td align="center" inline-class="title" class="title tnpc-row-edit tnpc-inline-editable" data-type="title" data-id="<?php echo $p->ID ?>">
92
- <?php
93
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $p->ID) ?
94
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $p->ID) :
95
- tnp_post_title($p)
96
- ?>
97
- </td>
98
- </tr>
99
- <?php if ($meta) { ?>
100
- <tr>
101
- <td align="center" inline-class="meta" class="meta">
102
- <?php echo esc_html(implode(' - ', $meta)) ?>
103
- </td>
104
- </tr>
105
- <?php } ?>
106
-
107
-
108
- <?php if ($excerpt_length) { ?>
109
- <tr>
110
- <td align="center" inline-class="excerpt" class="title tnpc-row-edit tnpc-inline-editable" data-type="text" data-id="<?php echo $p->ID ?>">
111
- <?php
112
- echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $p->ID) ?
113
- TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $p->ID) :
114
- tnp_post_excerpt($p, $excerpt_length)
115
- ?>
116
- </td>
117
- </tr>
118
- <?php } ?>
119
-
120
- <?php if ($show_read_more_button) { ?>
121
- <tr>
122
- <td align="center" inline-class="button">
123
- <?php echo TNP_Composer::button($button_options) ?>
124
- </td>
125
- </tr>
126
- <?php } ?>
127
- </table>
128
- <?php
129
- $items[] = ob_get_clean();
130
-
131
- } ?>
132
-
133
-
134
- <?php echo TNP_Composer::grid($items, ['width'=>$total_width, 'responsive'=>true, 'padding'=>0])?>
135
-
136
-
137
-
1
+ <?php
2
+ $size = array('width' => 600, 'height' => 400, "crop" => true);
3
+ $total_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
4
+ $column_width = $total_width / 2 - 20;
5
+
6
+ $title_style = TNP_Composer::get_style($options, 'title', $composer, 'title', ['scale' => .8]);
7
+ $text_style = TNP_Composer::get_style($options, '', $composer, 'text');
8
+
9
+ $items = [];
10
+ ?>
11
+ <style>
12
+ .title {
13
+ font-family: <?php echo $title_style->font_family ?>;
14
+ font-size: <?php echo $title_style->font_size ?>px;
15
+ font-weight: <?php echo $title_style->font_weight ?>;
16
+ color: <?php echo $title_style->font_color ?>;
17
+ line-height: 1.3em;
18
+ padding: 15px 0 0 0;
19
+ }
20
+
21
+ .excerpt {
22
+ font-family: <?php echo $text_style->font_family ?>;
23
+ font-size: <?php echo $text_style->font_size ?>px;
24
+ font-weight: <?php echo $text_style->font_weight ?>;
25
+ color: <?php echo $text_style->font_color ?>;
26
+ line-height: 1.4em;
27
+ padding: 5px 0 0 0;
28
+ }
29
+
30
+ .meta {
31
+ font-family: <?php echo $text_style->font_family ?>;
32
+ color: <?php echo $text_style->font_color ?>;
33
+ font-size: <?php echo round($text_style->font_size * 0.9) ?>px;
34
+ font-weight: <?php echo $text_style->font_weight ?>;
35
+ padding: 10px 0 0 0;
36
+ font-style: italic;
37
+ line-height: normal !important;
38
+ }
39
+ .button {
40
+ padding: 15px 0;
41
+ }
42
+ .column-left {
43
+ padding-right: 10px;
44
+ padding-bottom: 20px;
45
+ }
46
+ .column-right {
47
+ padding-left: 10px;
48
+ padding-bottom: 20px;
49
+ }
50
+
51
+ </style>
52
+
53
+
54
+ <?php foreach ($posts AS $p) { ?>
55
+ <?php
56
+ $media = null;
57
+ if ($show_image) {
58
+ $media = tnp_composer_block_posts_get_media($p, $size, $image_placeholder_url);
59
+ $media->link = tnp_post_permalink($p);
60
+ $media->set_width($column_width);
61
+ }
62
+
63
+ $meta = [];
64
+
65
+ if ($show_date) {
66
+ $meta[] = tnp_post_date($p);
67
+ }
68
+
69
+ if ($show_author) {
70
+ $author_object = get_user_by('id', $p->post_author);
71
+ if ($author_object) {
72
+ $meta[] = $author_object->display_name;
73
+ }
74
+ }
75
+
76
+ $button_options['button_url'] = tnp_post_permalink($p);
77
+ ob_start();
78
+ ?>
79
+
80
+
81
+
82
+ <table cellpadding="0" cellspacing="0" border="0" width="100%">
83
+ <?php if ($media) { ?>
84
+ <tr>
85
+ <td align="center" valign="middle">
86
+ <?php echo TNP_Composer::image($media, ['class' => 'fluid']) ?>
87
+ </td>
88
+ </tr>
89
+ <?php } ?>
90
+ <tr>
91
+ <td align="center" inline-class="title" class="title tnpc-row-edit tnpc-inline-editable" data-type="title" data-id="<?php echo $p->ID ?>">
92
+ <?php
93
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'title', $p->ID) ?
94
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'title', $p->ID) :
95
+ tnp_post_title($p)
96
+ ?>
97
+ </td>
98
+ </tr>
99
+ <?php if ($meta) { ?>
100
+ <tr>
101
+ <td align="center" inline-class="meta" class="meta">
102
+ <?php echo esc_html(implode(' - ', $meta)) ?>
103
+ </td>
104
+ </tr>
105
+ <?php } ?>
106
+
107
+
108
+ <?php if ($excerpt_length) { ?>
109
+ <tr>
110
+ <td align="center" inline-class="excerpt" class="title tnpc-row-edit tnpc-inline-editable" data-type="text" data-id="<?php echo $p->ID ?>">
111
+ <?php
112
+ echo TNP_Composer::is_post_field_edited_inline($options['inline_edits'], 'text', $p->ID) ?
113
+ TNP_Composer::get_edited_inline_post_field($options['inline_edits'], 'text', $p->ID) :
114
+ tnp_post_excerpt($p, $excerpt_length)
115
+ ?>
116
+ </td>
117
+ </tr>
118
+ <?php } ?>
119
+
120
+ <?php if ($show_read_more_button) { ?>
121
+ <tr>
122
+ <td align="center" inline-class="button">
123
+ <?php echo TNP_Composer::button($button_options) ?>
124
+ </td>
125
+ </tr>
126
+ <?php } ?>
127
+ </table>
128
+ <?php
129
+ $items[] = ob_get_clean();
130
+
131
+ } ?>
132
+
133
+
134
+ <?php echo TNP_Composer::grid($items, ['width'=>$total_width, 'responsive'=>true, 'padding'=>5])?>
135
+
136
+
137
+
emails/blocks/posts/options.php CHANGED
@@ -1,118 +1,118 @@
1
- <?php
2
- /* @var $options array contains all the options the current block we're ediging contains */
3
- /* @var $controls NewsletterControls */
4
- /* @var $fields NewsletterFields */
5
-
6
- $extensions_url = '?page=newsletter_main_extension';
7
- if (class_exists('NewsletterExtensions')) {
8
- $extensions_url = '?page=newsletter_extensions_index';
9
- }
10
- ?>
11
- <p>
12
- Custom post types can be added using our <a href="<?php echo $extensions_url ?>" target="_blank">Advanced Composer Blocks Addon</a>.
13
- </p>
14
-
15
- <?php if ($context['type'] == 'automated') { ?>
16
-
17
- <div class="tnp-field-box">
18
- <p>
19
- <strong>AUTOMATED</strong><br>
20
- While composing all posts are shown while on sending posts are extrated following the rules below.
21
- <a href="https://www.thenewsletterplugin.com/documentation/addons/extended-features/automated-extension/#regeneration" target="_blank">Read more</a>.
22
- </p>
23
- <?php $fields->select('automated_disabled', '', ['' => 'Use the last newsletter date and...', '1' => 'Do not consider the last newsletter']) ?>
24
-
25
- <div class="tnp-field-row">
26
- <div class="tnp-field-col-2">
27
- <?php
28
- $fields->select('automated_include', __('If there are new posts', 'newsletter'),
29
- [
30
- 'new' => __('Include only new posts', 'newsletter'),
31
- 'max' => __('Include specified max posts', 'newsletter')
32
- ],
33
- ['description' => '', 'class' => 'tnp-small'])
34
- ?>
35
- </div>
36
- <div class="tnp-field-col-2">
37
- <?php
38
- $fields->select('automated', __('If there are not new posts', 'newsletter'),
39
- [
40
- '' => 'Show the message below',
41
- '1' => 'Do not send the newsletter',
42
- '2' => 'Remove this block'
43
- ],
44
- ['description' => '', 'class' => 'tnp-small'])
45
- ?>
46
- <?php $fields->text('automated_no_contents', null, ['placeholder' => 'No new posts message']) ?>
47
- </div>
48
- </div>
49
- <div style="clear: both"></div>
50
- </div>
51
- <?php } ?>
52
-
53
-
54
- <?php
55
- $fields->select('layout', __('Layout', 'newsletter'),
56
- [
57
- 'one' => __('One column', 'newsletter'),
58
- 'one-2' => __('One column variant', 'newsletter'),
59
- 'two' => __('Two columns', 'newsletter'),
60
- 'big-image' => __('One column, big image', 'newsletter'),
61
- 'full-post' => __('Full post', 'newsletter')
62
- ])
63
- ?>
64
-
65
-
66
- <div class="tnp-field-row">
67
- <label class="tnp-row-label"><?php _e('Post info', 'newsletter') ?></label>
68
- <div class="tnp-field-col-3">
69
- <?php $fields->checkbox('show_date', __('Show date', 'newsletter')) ?>
70
- </div>
71
- <div class="tnp-field-col-3">
72
- <?php $fields->checkbox('show_author', __('Show author', 'newsletter')) ?>
73
- </div>
74
- <div class="tnp-field-col-3">
75
- <?php $fields->checkbox('show_image', __('Show image', 'newsletter')) ?>
76
- </div>
77
- <div style="clear: both"></div>
78
- </div>
79
-
80
- <div class="tnp-field-row">
81
- <div class="tnp-field-col-2">
82
- <?php $fields->select_number('max', __('Max posts', 'newsletter'), 1, 40); ?>
83
- </div>
84
- <div class="tnp-field-col-2">
85
- <?php $fields->select_number('post_offset', __('Posts offset', 'newsletter'), 0, 20); ?>
86
- </div>
87
- </div>
88
-
89
- <div class="tnp-field-row">
90
- <div class="tnp-field-col-2">
91
- <?php $fields->number('excerpt_length', __('Excerpt words', 'newsletter'), array('min' => 0)); ?>
92
- </div>
93
- <div class="tnp-field-col-2">
94
- <?php $fields->yesno('show_read_more_button', 'Show read more button') ?>
95
- </div>
96
- <div style="clear: both"></div>
97
- </div>
98
-
99
- <?php $fields->language(); ?>
100
-
101
- <?php $fields->section(__('Filters', 'newsletter')) ?>
102
- <?php $fields->categories(); ?>
103
- <?php $fields->text('tags', __('Tags', 'newsletter'), ['description' => __('Comma separated')]); ?>
104
-
105
- <?php $fields->section(__('Styles', 'newsletter')) ?>
106
- <?php $fields->font('title_font', __('Title font', 'newsletter'), ['family_default' => true, 'size_default' => true, 'weight_default' => true]) ?>
107
- <?php $fields->font('font', __('Excerpt font', 'newsletter'), ['family_default' => true, 'size_default' => true, 'weight_default' => true]) ?>
108
- <?php
109
- $fields->button('button', __('Read more button', 'newsletter'), [
110
- 'url' => false,
111
- 'family_default' => true,
112
- 'size_default' => true,
113
- 'weight_default' => true
114
- ])
115
- ?>
116
-
117
- <?php $fields->block_commons() ?>
118
-
1
+ <?php
2
+ /* @var $options array contains all the options the current block we're ediging contains */
3
+ /* @var $controls NewsletterControls */
4
+ /* @var $fields NewsletterFields */
5
+
6
+ $extensions_url = '?page=newsletter_main_extension';
7
+ if (class_exists('NewsletterExtensions')) {
8
+ $extensions_url = '?page=newsletter_extensions_index';
9
+ }
10
+ ?>
11
+ <p>
12
+ Custom post types can be added using our <a href="<?php echo $extensions_url ?>" target="_blank">Advanced Composer Blocks Addon</a>.
13
+ </p>
14
+
15
+ <?php if ($context['type'] == 'automated') { ?>
16
+
17
+ <div class="tnp-field-box">
18
+ <p>
19
+ <strong>AUTOMATED</strong><br>
20
+ While composing all posts are shown while on sending posts are extrated following the rules below.
21
+ <a href="https://www.thenewsletterplugin.com/documentation/addons/extended-features/automated-extension/#regeneration" target="_blank">Read more</a>.
22
+ </p>
23
+ <?php $fields->select('automated_disabled', '', ['' => 'Use the last newsletter date and...', '1' => 'Do not consider the last newsletter']) ?>
24
+
25
+ <div class="tnp-field-row">
26
+ <div class="tnp-field-col-2">
27
+ <?php
28
+ $fields->select('automated_include', __('If there are new posts', 'newsletter'),
29
+ [
30
+ 'new' => __('Include only new posts', 'newsletter'),
31
+ 'max' => __('Include specified max posts', 'newsletter')
32
+ ],
33
+ ['description' => '', 'class' => 'tnp-small'])
34
+ ?>
35
+ </div>
36
+ <div class="tnp-field-col-2">
37
+ <?php
38
+ $fields->select('automated', __('If there are not new posts', 'newsletter'),
39
+ [
40
+ '' => 'Show the message below',
41
+ '1' => 'Do not send the newsletter',
42
+ '2' => 'Remove this block'
43
+ ],
44
+ ['description' => '', 'class' => 'tnp-small'])
45
+ ?>
46
+ <?php $fields->text('automated_no_contents', null, ['placeholder' => 'No new posts message']) ?>
47
+ </div>
48
+ </div>
49
+ <div style="clear: both"></div>
50
+ </div>
51
+ <?php } ?>
52
+
53
+
54
+ <?php
55
+ $fields->select('layout', __('Layout', 'newsletter'),
56
+ [
57
+ 'one' => __('One column', 'newsletter'),
58
+ 'one-2' => __('One column variant', 'newsletter'),
59
+ 'two' => __('Two columns', 'newsletter'),
60
+ 'big-image' => __('One column, big image', 'newsletter'),
61
+ 'full-post' => __('Full post', 'newsletter')
62
+ ])
63
+ ?>
64
+
65
+
66
+ <div class="tnp-field-row">
67
+ <label class="tnp-row-label"><?php _e('Post info', 'newsletter') ?></label>
68
+ <div class="tnp-field-col-3">
69
+ <?php $fields->checkbox('show_date', __('Show date', 'newsletter')) ?>
70
+ </div>
71
+ <div class="tnp-field-col-3">
72
+ <?php $fields->checkbox('show_author', __('Show author', 'newsletter')) ?>
73
+ </div>
74
+ <div class="tnp-field-col-3">
75
+ <?php $fields->checkbox('show_image', __('Show image', 'newsletter')) ?>
76
+ </div>
77
+ <div style="clear: both"></div>
78
+ </div>
79
+
80
+ <div class="tnp-field-row">
81
+ <div class="tnp-field-col-2">
82
+ <?php $fields->select_number('max', __('Max posts', 'newsletter'), 1, 40); ?>
83
+ </div>
84
+ <div class="tnp-field-col-2">
85
+ <?php $fields->select_number('post_offset', __('Posts offset', 'newsletter'), 0, 20); ?>
86
+ </div>
87
+ </div>
88
+
89
+ <div class="tnp-field-row">
90
+ <div class="tnp-field-col-2">
91
+ <?php $fields->number('excerpt_length', __('Excerpt words', 'newsletter'), array('min' => 0)); ?>
92
+ </div>
93
+ <div class="tnp-field-col-2">
94
+ <?php $fields->yesno('show_read_more_button', 'Show read more button') ?>
95
+ </div>
96
+ <div style="clear: both"></div>
97
+ </div>
98
+
99
+ <?php $fields->language(); ?>
100
+
101
+ <?php $fields->section(__('Filters', 'newsletter')) ?>
102
+ <?php $fields->categories(); ?>
103
+ <?php $fields->text('tags', __('Tags', 'newsletter'), ['description' => __('Comma separated')]); ?>
104
+
105
+ <?php $fields->section(__('Styles', 'newsletter')) ?>
106
+ <?php $fields->font('title_font', __('Title font', 'newsletter'), ['family_default' => true, 'size_default' => true, 'weight_default' => true]) ?>
107
+ <?php $fields->font('font', __('Excerpt font', 'newsletter'), ['family_default' => true, 'size_default' => true, 'weight_default' => true]) ?>
108
+ <?php
109
+ $fields->button('button', __('Read more button', 'newsletter'), [
110
+ 'url' => false,
111
+ 'family_default' => true,
112
+ 'size_default' => true,
113
+ 'weight_default' => true
114
+ ])
115
+ ?>
116
+
117
+ <?php $fields->block_commons() ?>
118
+
includes/composer.php CHANGED
@@ -1,904 +1,904 @@
1
- <?php
2
-
3
- /** For old style coders */
4
- function tnp_register_block($dir) {
5
- return TNP_Composer::register_block($dir);
6
- }
7
-
8
- /**
9
- * Generates and HTML button for email using the values found on $options and
10
- * prefixed by $prefix, with the standard syntax of NewsletterFields::button().
11
- *
12
- * @param array $options
13
- * @param string $prefix
14
- * @return string
15
- */
16
- function tnpc_button($options, $prefix = 'button') {
17
- return TNP_Composer::button($options, $prefix);
18
- }
19
-
20
- class TNP_Composer {
21
-
22
- static $block_dirs = array();
23
-
24
- static function register_block($dir) {
25
- // Checks
26
- $dir = realpath($dir);
27
- if (!$dir) {
28
- $error = new WP_Error('1', 'Seems not a valid path: ' . $dir);
29
- NewsletterEmails::instance()->logger->error($error);
30
- return $error;
31
- }
32
-
33
- $dir = wp_normalize_path($dir);
34
-
35
- if (!file_exists($dir . '/block.php')) {
36
- $error = new WP_Error('1', 'block.php missing on folder ' . $dir);
37
- NewsletterEmails::instance()->logger->error($error);
38
- return $error;
39
- }
40
-
41
- self::$block_dirs[] = $dir;
42
- return true;
43
- }
44
-
45
- /**
46
- * @param string $open
47
- * @param string $inner
48
- * @param string $close
49
- * @param string[] $markers
50
- *
51
- * @return string
52
- */
53
- static function wrap_html_element($open, $inner, $close, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
54
-
55
- return $open . $markers[0] . $inner . $markers[1] . $close;
56
- }
57
-
58
- /**
59
- * @param string $block
60
- * @param string[] $markers
61
- *
62
- * @return string
63
- */
64
- static function unwrap_html_element($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
65
- if (self::_has_markers($block, $markers)) {
66
- self::_escape_markers($markers);
67
- $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
68
-
69
- $matches = array();
70
- preg_match($pattern, $block, $matches);
71
-
72
- return $matches[1];
73
- }
74
-
75
- return $block;
76
- }
77
-
78
- /**
79
- * @param string $block
80
- * @param string[] $markers
81
- *
82
- * @return bool
83
- */
84
- private static function _has_markers($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
85
-
86
- self::_escape_markers($markers);
87
-
88
- $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
89
-
90
- return preg_match($pattern, $block);
91
- }
92
-
93
- /**
94
- * Sources:
95
- * - https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
96
- *
97
- * @param type $email
98
- * @return type
99
- */
100
- static function get_html_open($email) {
101
- $open = "<!DOCTYPE html>\n";
102
- $open .= "<html xmlns=\"https://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">\n<head>\n<title>{email_subject}</title>\n";
103
- $open .= "<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n";
104
-
105
- $open .= '<!--[if !mso]><!-->' . "\n";
106
- $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
107
- $open .= '<!--<![endif]-->' . "\n";
108
-
109
- $open .= '<!--[if mso]>' . "\n";
110
- ;
111
- $open .= '<style type="text/css">';
112
- $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
113
- $open .= 'div, td {padding:0;}';
114
- $open .= 'div {margin:0 !important;}';
115
- $open .= '</style>';
116
- $open .= "\n";
117
- $open .= '<noscript>';
118
- $open .= '<xml>';
119
- $open .= '<o:OfficeDocumentSettings>';
120
- $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
121
- $open .= '</o:OfficeDocumentSettings>';
122
- $open .= '</xml>';
123
- $open .= '</noscript>';
124
- $open .= "\n";
125
- $open .= '<![endif]-->';
126
- $open .= "\n";
127
- $open .= "<style type=\"text/css\">\n";
128
- $open .= NewsletterEmails::instance()->get_composer_css();
129
- $open .= "\n</style>\n";
130
- $open .= "</head>\n";
131
- $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
132
- $open .= "\n";
133
- $open .= self::get_html_preheader($email);
134
-
135
- return $open;
136
- }
137
-
138
- static private function get_html_preheader($email) {
139
-
140
- if (empty($email->options['preheader'])) {
141
- return "";
142
- }
143
-
144
- $preheader_text = $email->options['preheader'];
145
- $html = "<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">$preheader_text</div>";
146
- $html .= "\n";
147
-
148
- return $html;
149
- }
150
-
151
- static function get_html_close($email) {
152
- return "</body>\n</html>";
153
- }
154
-
155
- /**
156
- *
157
- * @param TNP_Email $email
158
- * @return string
159
- */
160
- static function get_main_wrapper_open($email) {
161
- if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
162
- $bgcolor = '';
163
- } else {
164
- $bgcolor = $email->options['composer_background'];
165
- }
166
-
167
- return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
168
- "<tr>\n" .
169
- "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
170
- }
171
-
172
- /**
173
- *
174
- * @param TNP_Email $email
175
- * @return string
176
- */
177
- static function get_main_wrapper_close($email) {
178
- return "\n<!-- /tnp -->\n" .
179
- "</td>\n" .
180
- "</tr>\n" .
181
- "</table>\n\n";
182
- }
183
-
184
- /**
185
- * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
186
- *
187
- * @param string $html_email
188
- *
189
- * @return string
190
- */
191
- static function unwrap_email($html_email) {
192
-
193
- if (self::_has_markers($html_email)) {
194
- $html_email = self::unwrap_html_element($html_email);
195
- } else {
196
- //KEEP FOR OLD EMAIL COMPATIBILITY
197
- // Extracts only the body part
198
- $x = strpos($html_email, '<body');
199
- if ($x) {
200
- $x = strpos($html_email, '>', $x);
201
- $y = strpos($html_email, '</body>');
202
- $html_email = substr($html_email, $x + 1, $y - $x - 1);
203
- }
204
-
205
- /* Cleans up uncorrectly stored newsletter bodies */
206
- $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
207
- $html_email = preg_replace('/<meta.*?>/', '', $html_email);
208
- $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
209
- $html_email = trim($html_email);
210
- }
211
-
212
- // Required since esc_html DOES NOT escape the HTML entities (apparently)
213
- $html_email = str_replace('&', '&amp;', $html_email);
214
- $html_email = str_replace('"', '&quot;', $html_email);
215
- $html_email = str_replace('<', '&lt;', $html_email);
216
- $html_email = str_replace('>', '&gt;', $html_email);
217
-
218
- return $html_email;
219
- }
220
-
221
- private static function _escape_markers(&$markers) {
222
- $markers[0] = str_replace('/', '\/', $markers[0]);
223
- $markers[1] = str_replace('/', '\/', $markers[1]);
224
- }
225
-
226
- /**
227
- * Using the data collected inside $controls (and submitted by a form containing the
228
- * composer fields), updates the email. The message body is completed with doctype,
229
- * head, style and the main wrapper.
230
- *
231
- * @param TNP_Email $email
232
- * @param NewsletterControls $controls
233
- */
234
- static function update_email($email, $controls) {
235
- if (isset($controls->data['subject'])) {
236
- $email->subject = $controls->data['subject'];
237
- }
238
-
239
- // They should be only composer options
240
- foreach ($controls->data as $name => $value) {
241
- if (strpos($name, 'options_') === 0) {
242
- $email->options[substr($name, 8)] = $value;
243
- }
244
- }
245
-
246
- //if (isset($controls->data['preheader'])) {
247
- // $email->options['preheader'] = $controls->data['preheader'];
248
- //}
249
-
250
- $email->editor = NewsletterEmails::EDITOR_COMPOSER;
251
-
252
- $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
253
- $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
254
- }
255
-
256
- /**
257
- * Prepares a controls object injecting the relevant fields from an email
258
- * which cannot be directly used by controls. If $email is null or missing,
259
- * $controls is prepared with default values.
260
- *
261
- * @param NewsletterControls $controls
262
- * @param TNP_Email $email
263
- */
264
- static function prepare_controls($controls, $email = null) {
265
-
266
- // Controls for a new email (which actually does not exist yet
267
- if (!empty($email)) {
268
-
269
- foreach ($email->options as $name => $value) {
270
- $controls->data['options_' . $name] = $value;
271
- }
272
-
273
- $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
274
- $controls->data['subject'] = $email->subject;
275
- $controls->data['updated'] = $email->updated;
276
-
277
- }
278
-
279
- if (!empty($email->options['sender_email'])) {
280
- $controls->data['sender_email'] = $email->options['sender_email'];
281
- } else {
282
- $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
283
- }
284
-
285
- if (!empty($email->options['sender_name'])) {
286
- $controls->data['sender_name'] = $email->options['sender_name'];
287
- } else {
288
- $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
289
- }
290
-
291
- $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
292
- }
293
-
294
- /**
295
- * Extract inline edited post field from inline_edit_list[]
296
- *
297
- * @param array $inline_edit_list
298
- * @param string $field_type
299
- * @param int $post_id
300
- *
301
- * @return string
302
- */
303
- static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
304
-
305
- foreach ($inline_edit_list as $edit) {
306
- if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
307
- return $edit['content'];
308
- }
309
- }
310
-
311
- return '';
312
- }
313
-
314
- /**
315
- * Check if inline_edit_list[] have inline edit field for specific post
316
- *
317
- * @param array $inline_edit_list
318
- * @param string $field_type
319
- * @param int $post_id
320
- *
321
- * @return bool
322
- */
323
- static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
324
- if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
325
- return false;
326
- }
327
- foreach ($inline_edit_list as $edit) {
328
- if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
329
- return true;
330
- }
331
- }
332
-
333
- return false;
334
- }
335
-
336
- /**
337
- * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
338
- *
339
- * - [prefix]_url The button URL
340
- * - [prefix]_font_family
341
- * - [prefix]_font_size
342
- * - [prefix]_font_weight
343
- * - [prefix]_label
344
- * - [prefix]_font_color The label color
345
- * - [prefix]_background The button color
346
- *
347
- * TODO: Add radius and possiblt the alignment
348
- *
349
- * @param array $options
350
- * @param string $prefix
351
- * @return string
352
- */
353
- static function button($options, $prefix = 'button') {
354
-
355
- if (empty($options[$prefix . '_label'])) {
356
- return;
357
- }
358
- $defaults = [
359
- $prefix . '_url' => '#',
360
- $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
361
- $prefix . '_font_color' => '#ffffff',
362
- $prefix . '_font_weight' => 'bold',
363
- $prefix . '_font_size' => 20,
364
- $prefix . '_background' => '#256F9C',
365
- $prefix . '_align' => 'center'
366
- ];
367
-
368
- $options = array_merge($defaults, array_filter($options));
369
-
370
- $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
371
- if (!empty($options[$prefix . '_align'])) {
372
- $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
373
- }
374
- if (!empty($options[$prefix . '_width'])) {
375
- $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
376
- }
377
- $b .= '>';
378
- $b .= '<tr>';
379
- $b .= '<td align="center" bgcolor="' . $options[$prefix . '_background'] . '" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:' . $options[$prefix . '_background'] . '" valign="middle">';
380
- $b .= '<a href="' . $options[$prefix . '_url'] . '"';
381
- $b .= ' style="display:inline-block;background:' . $options[$prefix . '_background'] . ';color:' . $options[$prefix . '_font_color'] . ';font-family:' . $options[$prefix . '_font_family'] . ';font-size:' . $options[$prefix . '_font_size'] . 'px;font-weight:' . $options[$prefix . '_font_weight'] . ';line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"';
382
- $b .= ' target="_blank">';
383
- $b .= $options[$prefix . '_label'];
384
- $b .= '</a>';
385
- $b .= '</td></tr></table>';
386
- return $b;
387
- }
388
-
389
- /**
390
- * Generates an IMG tag, linked if the media has an URL.
391
- *
392
- * @param TNP_Media $media
393
- * @param string $style
394
- * @return string
395
- */
396
- static function image($media, $attr = []) {
397
-
398
- $default_attrs = [
399
- 'style' => 'max-width: 100%; height: auto; display: inline-block',
400
- 'class' => '',
401
- 'link-style' => 'text-decoration: none; display: inline-block',
402
- 'link-class' => null,
403
- ];
404
-
405
- $attr = array_merge($default_attrs, $attr);
406
-
407
- //Class and style attribute are mutually exclusive.
408
- //Class take priority to style because classes will transform to inline style inside block rendering operation
409
- if (!empty($attr['inline-class'])) {
410
- $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
411
- } else {
412
- $styling = ' style="' . esc_attr($attr['style']) . '" ';
413
- }
414
-
415
- if (!empty($attr['class'])) {
416
- $styling .= ' class="' . esc_attr($attr['class']) . '" ';
417
- }
418
-
419
- //Class and style attribute are mutually exclusive.
420
- //Class take priority to style because classes will transform to inline style inside block rendering operation
421
- if (!empty($attr['link-class'])) {
422
- $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
423
- } else {
424
- $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
425
- }
426
-
427
- $b = '';
428
- if ($media->link) {
429
- $b .= '<a href="' . esc_attr($media->link) . '" target="_blank" rel="noopener nofollow" style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
430
- } else {
431
- // The span grants images are not upscaled when fluid (two columns posts block)
432
- $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
433
- }
434
- if ($media) {
435
- $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
436
- if ($media->height) {
437
- $b .= ' height="' . esc_attr($media->height) . '"';
438
- }
439
- $b .= ' alt="' . esc_attr($media->alt) . '"'
440
- . ' border="0"'
441
- . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0;"'
442
- . ' class="' . esc_attr($attr['class']) . '" '
443
- . '>';
444
- }
445
-
446
- if ($media->link) {
447
- $b .= '</a>';
448
- } else {
449
- $b .= '</span>';
450
- }
451
-
452
- return $b;
453
- }
454
-
455
- /**
456
- * Returns a WP media ID for the specified post (or false if nothing can be found)
457
- * looking for the featured image or, if missing, taking the first media in the gallery and
458
- * if again missing, searching the first reference to a media in the post content.
459
- *
460
- * The media ID is not checked for real existance of the associated attachment.
461
- *
462
- * @param int $post_id
463
- * @return int
464
- */
465
- static function get_post_thumbnail_id($post_id) {
466
- if (is_object($post_id)) {
467
- $post_id = $post_id->ID;
468
- }
469
-
470
- // Find a media id to be used as featured image
471
- $media_id = get_post_thumbnail_id($post_id);
472
- if (!empty($media_id)) {
473
- return $media_id;
474
- }
475
-
476
- $attachments = get_children(array('numberpost' => 1, 'post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order'));
477
- if (!empty($attachments)) {
478
- foreach ($attachments as $id => &$attachment) {
479
- return $id;
480
- }
481
- }
482
-
483
- $post = get_post($post_id);
484
-
485
- $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
486
- if ($matches) {
487
- return (int) $matches[1];
488
- }
489
-
490
- return false;
491
- }
492
-
493
- /**
494
- * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
495
- * media has a size which best match the one requested (this is the standard WP behavior, plugins
496
- * could change it).
497
- *
498
- * @param int $media_id
499
- * @param array $size
500
- * @return \TNP_Media
501
- */
502
- function get_media($media_id, $size) {
503
- $src = wp_get_attachment_image_src($media_id, $size);
504
- if (!$src) {
505
- return null;
506
- }
507
- $media = new TNP_Media();
508
- $media->id = $media_id;
509
- $media->url = $src[0];
510
- $media->width = $src[1];
511
- $media->height = $src[2];
512
- return $media;
513
- }
514
-
515
- static function post_content($post) {
516
- $content = $post->post_content;
517
- $content = wpautop($content);
518
- if (true || $options['enable shortcodes']) {
519
- remove_shortcode('gallery');
520
- add_shortcode('gallery', 'tnp_gallery_shortcode');
521
- $content = do_shortcode($content);
522
- }
523
- $content = str_replace('<p>', '<p class="paragraph">', $content);
524
-
525
- $selected_images = array();
526
- if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
527
- foreach ($matches[0] as $image) {
528
- if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
529
- $selected_images[$image] = $attachment_id;
530
- }
531
- }
532
- }
533
-
534
- foreach ($selected_images as $image => $attachment_id) {
535
- $src = tnp_media_resize($attachment_id, array(600, 0));
536
- if (is_wp_error($src)) {
537
- continue;
538
- }
539
- $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
540
- }
541
-
542
- return $content;
543
- }
544
-
545
- static function get_global_style_defaults() {
546
- return [
547
- 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
548
- 'options_composer_title_font_size' => 32,
549
- 'options_composer_title_font_weight' => 'normal',
550
- 'options_composer_title_font_color' => '#222222',
551
- 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
552
- 'options_composer_text_font_size' => 16,
553
- 'options_composer_text_font_weight' => 'normal',
554
- 'options_composer_text_font_color' => '#222222',
555
- 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
556
- 'options_composer_button_font_size' => 16,
557
- 'options_composer_button_font_weight' => 'normal',
558
- 'options_composer_button_font_color' => '#FFFFFF',
559
- 'options_composer_button_background_color' => '#256F9C',
560
- 'options_composer_background' => '#FFFFFF',
561
- 'options_composer_block_background' => '#FFFFFF',
562
- ];
563
- }
564
-
565
- /**
566
- * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
567
- *
568
- * Attributes:
569
- * - columns: number of columns [2]
570
- * - padding: cells padding [10]
571
- * - responsive: il on mobile the cell should stack up [true]
572
- * - width: the whole row width, it should reduced by the external row padding [600]
573
- *
574
- * @param string[] $items
575
- * @param array $attrs
576
- * @return string
577
- */
578
- static function grid($items = [], $attrs = []) {
579
- $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
580
- $width = (int) $attrs['width'];
581
- $columns = (int) $attrs['columns'];
582
- $padding = (int) $attrs['padding'];
583
- $column_width = $width / $columns;
584
- $td_width = 100 / $columns;
585
- $chunks = array_chunk($items, $columns);
586
-
587
- if ($attrs['responsive']) {
588
-
589
- $e = '<div style="text-align:center;font-size:0;">';
590
- foreach ($chunks as &$chunk) {
591
-
592
- $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
593
- foreach ($chunk as &$item) {
594
- $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
595
-
596
- $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
597
-
598
- // This element to add padding without deal with border-box not well supported
599
- $e .= '<div style="padding:' . $padding . 'px;">';
600
- $e .= $item;
601
- $e .= '</div>';
602
- $e .= '</div>';
603
-
604
- $e .= '<!--[if mso]></td><![endif]-->';
605
- }
606
- $e .= '<!--[if mso]></tr></table><![endif]-->';
607
- $e .= '</div>';
608
- }
609
- return $e;
610
- } else {
611
- $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
612
- foreach ($chunks as &$chunk) {
613
- $e .= '<tr>';
614
- foreach ($chunk as &$item) {
615
- $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
616
- $e .= $item;
617
- $e .= '</td>';
618
- }
619
- $e .= '</tr>';
620
- }
621
- $e .= '</table>';
622
- return $e;
623
- }
624
- }
625
-
626
- static function get_text_style($options, $prefix, $composer, $attrs = []) {
627
- return self::get_style($options, $prefix, $composer, 'text', $attrs);
628
- }
629
-
630
- static function get_title_style($options, $prefix, $composer, $attrs = []) {
631
- return self::get_style($options, $prefix, $composer, 'title', $attrs);
632
- }
633
-
634
- static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
635
- $style = new TNP_Style();
636
- $scale = 1.0;
637
- if (!empty($attrs['scale'])) {
638
- $scale = (float) $attrs['scale'];
639
- }
640
- if (!empty($prefix))
641
- $prefix .= '_';
642
-
643
- $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
644
- $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
645
- $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
646
- $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
647
- if ($type === 'button') {
648
- $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
649
- }
650
- return $style;
651
- }
652
-
653
- static function get_button_options($options, $prefix, $composer) {
654
- $button_options = [];
655
- $scale = 1;
656
- $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
657
- $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
658
- $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
659
- $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
660
- $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
661
- $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
662
- $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
663
- $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
664
- $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
665
-
666
- return $button_options;
667
- }
668
-
669
- }
670
-
671
- class TNP_Style {
672
-
673
- var $font_family;
674
- var $font_size;
675
- var $font_weight;
676
- var $font_color;
677
- var $background;
678
-
679
- function echo_css($scale = 1.0) {
680
- echo 'font-size: ', round($this->font_size * $scale), 'px;';
681
- echo 'font-family: ', $this->font_family, ';';
682
- echo 'font-weight: ', $this->font_weight, ';';
683
- echo 'color: ', $this->font_color, ';';
684
- }
685
-
686
- }
687
-
688
- /**
689
- * Generate multicolumn and responsive html template for email.
690
- * Initialize class with max columns per row and start to add cells.
691
- */
692
- class TNP_Composer_Grid_System {
693
-
694
- /**
695
- * @var TNP_Composer_Grid_Row[]
696
- */
697
- private $rows;
698
-
699
- /**
700
- * @var int
701
- */
702
- private $cells_per_row;
703
-
704
- /**
705
- * @var int
706
- */
707
- private $cells_counter;
708
-
709
- /**
710
- * TNP_Composer_Grid_System constructor.
711
- *
712
- * @param int $columns_per_row Max columns per row
713
- */
714
- public function __construct($columns_per_row) {
715
- $this->cells_per_row = $columns_per_row;
716
- $this->cells_counter = 0;
717
- $this->rows = [];
718
- }
719
-
720
- public function __toString() {
721
- return $this->render();
722
- }
723
-
724
- /**
725
- * Add cell to grid
726
- *
727
- * @param TNP_Composer_Grid_Cell $cell
728
- */
729
- public function add_cell($cell) {
730
-
731
- if ($this->cells_counter % $this->cells_per_row === 0) {
732
- $this->add_row(new TNP_Composer_Grid_Row());
733
- }
734
-
735
- $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
736
- $this->rows[$row_idx]->add_cell($cell);
737
- $this->cells_counter++;
738
- }
739
-
740
- private function add_row($row) {
741
- $this->rows[] = $row;
742
- }
743
-
744
- public function render() {
745
-
746
- $str = '';
747
- foreach ($this->rows as $row) {
748
- $str .= $row->render();
749
- }
750
-
751
- return $str;
752
- }
753
-
754
- }
755
-
756
- /**
757
- * Class TNP_Composer_Grid_Row
758
- */
759
- class TNP_Composer_Grid_Row {
760
-
761
- /**
762
- * @var TNP_Composer_Grid_Cell[]
763
- */
764
- private $cells;
765
-
766
- public function __construct(...$cells) {
767
- if (!empty($cells)) {
768
- foreach ($cells as $cell) {
769
- $this->add_cell($cell);
770
- }
771
- }
772
- }
773
-
774
- /**
775
- * @param TNP_Composer_Grid_Cell $cell
776
- */
777
- public function add_cell($cell) {
778
- $this->cells[] = $cell;
779
- }
780
-
781
- public function render() {
782
- $rendered_cells = '';
783
- $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
784
- foreach ($this->cells as $cell) {
785
- $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
786
- }
787
-
788
- $row_template = $this->get_template();
789
-
790
- return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
791
- }
792
-
793
- private function cells_count() {
794
- return count($this->cells);
795
- }
796
-
797
- private function get_template() {
798
- return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
799
- }
800
-
801
- }
802
-
803
- /**
804
- * Class TNP_Composer_Grid_Cell
805
- */
806
- class TNP_Composer_Grid_Cell {
807
-
808
- /**
809
- * @var string
810
- */
811
- private $content;
812
-
813
- /**
814
- * @var array
815
- */
816
- public $args;
817
-
818
- public function __construct($content = null, $args = []) {
819
- $default_args = [
820
- 'width' => '100%',
821
- 'class' => '',
822
- 'align' => 'left',
823
- 'valign' => 'top'
824
- ];
825
-
826
- $this->args = array_merge($default_args, $args);
827
-
828
- $this->content = $content ? $content : '';
829
- }
830
-
831
- public function add_content($content) {
832
- $this->content .= $content;
833
- }
834
-
835
- public function render($args) {
836
- $this->args = array_merge($this->args, $args);
837
-
838
- $column_template = $this->get_template();
839
- $column = str_replace(
840
- [
841
- 'TNP_ALIGN_PH',
842
- 'TNP_VALIGN_PH',
843
- 'TNP_WIDTH_PH',
844
- 'TNP_CLASS_PH',
845
- 'TNP_COLUMN_CONTENT_PH'
846
- ], [
847
- $this->args['align'],
848
- $this->args['valign'],
849
- $this->args['width'],
850
- $this->args['class'],
851
- $this->content
852
- ], $column_template);
853
-
854
- return $column;
855
- }
856
-
857
- private function get_template() {
858
- return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
859
- <tbody>
860
- <tr>
861
- <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
862
- TNP_COLUMN_CONTENT_PH
863
- </td>
864
- </tr>
865
- </tbody>
866
- </table>";
867
- }
868
-
869
- }
870
-
871
- class TNP_Composer_Component_Factory {
872
-
873
- private $options;
874
-
875
- /**
876
- * TNP_Composer_Component_Factory constructor.
877
- *
878
- * @param Controller$controller
879
- */
880
- public function __construct($controller) {
881
-
882
- }
883
-
884
- function heading() {
885
-
886
- }
887
-
888
- function paragraph() {
889
-
890
- }
891
-
892
- function link() {
893
-
894
- }
895
-
896
- function button() {
897
-
898
- }
899
-
900
- function image() {
901
-
902
- }
903
-
904
- }
1
+ <?php
2
+
3
+ /** For old style coders */
4
+ function tnp_register_block($dir) {
5
+ return TNP_Composer::register_block($dir);
6
+ }
7
+
8
+ /**
9
+ * Generates and HTML button for email using the values found on $options and
10
+ * prefixed by $prefix, with the standard syntax of NewsletterFields::button().
11
+ *
12
+ * @param array $options
13
+ * @param string $prefix
14
+ * @return string
15
+ */
16
+ function tnpc_button($options, $prefix = 'button') {
17
+ return TNP_Composer::button($options, $prefix);
18
+ }
19
+
20
+ class TNP_Composer {
21
+
22
+ static $block_dirs = array();
23
+
24
+ static function register_block($dir) {
25
+ // Checks
26
+ $dir = realpath($dir);
27
+ if (!$dir) {
28
+ $error = new WP_Error('1', 'Seems not a valid path: ' . $dir);
29
+ NewsletterEmails::instance()->logger->error($error);
30
+ return $error;
31
+ }
32
+
33
+ $dir = wp_normalize_path($dir);
34
+
35
+ if (!file_exists($dir . '/block.php')) {
36
+ $error = new WP_Error('1', 'block.php missing on folder ' . $dir);
37
+ NewsletterEmails::instance()->logger->error($error);
38
+ return $error;
39
+ }
40
+
41
+ self::$block_dirs[] = $dir;
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * @param string $open
47
+ * @param string $inner
48
+ * @param string $close
49
+ * @param string[] $markers
50
+ *
51
+ * @return string
52
+ */
53
+ static function wrap_html_element($open, $inner, $close, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
54
+
55
+ return $open . $markers[0] . $inner . $markers[1] . $close;
56
+ }
57
+
58
+ /**
59
+ * @param string $block
60
+ * @param string[] $markers
61
+ *
62
+ * @return string
63
+ */
64
+ static function unwrap_html_element($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
65
+ if (self::_has_markers($block, $markers)) {
66
+ self::_escape_markers($markers);
67
+ $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
68
+
69
+ $matches = array();
70
+ preg_match($pattern, $block, $matches);
71
+
72
+ return $matches[1];
73
+ }
74
+
75
+ return $block;
76
+ }
77
+
78
+ /**
79
+ * @param string $block
80
+ * @param string[] $markers
81
+ *
82
+ * @return bool
83
+ */
84
+ private static function _has_markers($block, $markers = array('<!-- tnp -->', '<!-- /tnp -->')) {
85
+
86
+ self::_escape_markers($markers);
87
+
88
+ $pattern = sprintf('/%s(.*?)%s/s', $markers[0], $markers[1]);
89
+
90
+ return preg_match($pattern, $block);
91
+ }
92
+
93
+ /**
94
+ * Sources:
95
+ * - https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
96
+ *
97
+ * @param type $email
98
+ * @return type
99
+ */
100
+ static function get_html_open($email) {
101
+ $open = "<!DOCTYPE html>\n";
102
+ $open .= "<html xmlns=\"https://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">\n<head>\n<title>{email_subject}</title>\n";
103
+ $open .= "<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n";
104
+
105
+ $open .= '<!--[if !mso]><!-->' . "\n";
106
+ $open .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
107
+ $open .= '<!--<![endif]-->' . "\n";
108
+
109
+ $open .= '<!--[if mso]>' . "\n";
110
+ ;
111
+ $open .= '<style type="text/css">';
112
+ $open .= 'table {border-collapse:collapse;border-spacing:0;margin:0;}';
113
+ $open .= 'div, td {padding:0;}';
114
+ $open .= 'div {margin:0 !important;}';
115
+ $open .= '</style>';
116
+ $open .= "\n";
117
+ $open .= '<noscript>';
118
+ $open .= '<xml>';
119
+ $open .= '<o:OfficeDocumentSettings>';
120
+ $open .= '<o:PixelsPerInch>96</o:PixelsPerInch>';
121
+ $open .= '</o:OfficeDocumentSettings>';
122
+ $open .= '</xml>';
123
+ $open .= '</noscript>';
124
+ $open .= "\n";
125
+ $open .= '<![endif]-->';
126
+ $open .= "\n";
127
+ $open .= "<style type=\"text/css\">\n";
128
+ $open .= NewsletterEmails::instance()->get_composer_css();
129
+ $open .= "\n</style>\n";
130
+ $open .= "</head>\n";
131
+ $open .= '<body style="margin: 0; padding: 0; line-height: normal; word-spacing: normal;" dir="' . (is_rtl() ? 'rtl' : 'ltr') . '">';
132
+ $open .= "\n";
133
+ $open .= self::get_html_preheader($email);
134
+
135
+ return $open;
136
+ }
137
+
138
+ static private function get_html_preheader($email) {
139
+
140
+ if (empty($email->options['preheader'])) {
141
+ return "";
142
+ }
143
+
144
+ $preheader_text = $email->options['preheader'];
145
+ $html = "<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">$preheader_text</div>";
146
+ $html .= "\n";
147
+
148
+ return $html;
149
+ }
150
+
151
+ static function get_html_close($email) {
152
+ return "</body>\n</html>";
153
+ }
154
+
155
+ /**
156
+ *
157
+ * @param TNP_Email $email
158
+ * @return string
159
+ */
160
+ static function get_main_wrapper_open($email) {
161
+ if (!isset($email->options['composer_background']) || $email->options['composer_background'] == 'inherit') {
162
+ $bgcolor = '';
163
+ } else {
164
+ $bgcolor = $email->options['composer_background'];
165
+ }
166
+
167
+ return "\n<table cellpadding='0' cellspacing='0' border='0' width='100%'>\n" .
168
+ "<tr>\n" .
169
+ "<td bgcolor='$bgcolor' valign='top'><!-- tnp -->";
170
+ }
171
+
172
+ /**
173
+ *
174
+ * @param TNP_Email $email
175
+ * @return string
176
+ */
177
+ static function get_main_wrapper_close($email) {
178
+ return "\n<!-- /tnp -->\n" .
179
+ "</td>\n" .
180
+ "</tr>\n" .
181
+ "</table>\n\n";
182
+ }
183
+
184
+ /**
185
+ * Remove <doctype>, <body> and unnecessary envelopes for editing with composer
186
+ *
187
+ * @param string $html_email
188
+ *
189
+ * @return string
190
+ */
191
+ static function unwrap_email($html_email) {
192
+
193
+ if (self::_has_markers($html_email)) {
194
+ $html_email = self::unwrap_html_element($html_email);
195
+ } else {
196
+ //KEEP FOR OLD EMAIL COMPATIBILITY
197
+ // Extracts only the body part
198
+ $x = strpos($html_email, '<body');
199
+ if ($x) {
200
+ $x = strpos($html_email, '>', $x);
201
+ $y = strpos($html_email, '</body>');
202
+ $html_email = substr($html_email, $x + 1, $y - $x - 1);
203
+ }
204
+
205
+ /* Cleans up uncorrectly stored newsletter bodies */
206
+ $html_email = preg_replace('/<style\s+.*?>.*?<\\/style>/is', '', $html_email);
207
+ $html_email = preg_replace('/<meta.*?>/', '', $html_email);
208
+ $html_email = preg_replace('/<title\s+.*?>.*?<\\/title>/i', '', $html_email);
209
+ $html_email = trim($html_email);
210
+ }
211
+
212
+ // Required since esc_html DOES NOT escape the HTML entities (apparently)
213
+ $html_email = str_replace('&', '&amp;', $html_email);
214
+ $html_email = str_replace('"', '&quot;', $html_email);
215
+ $html_email = str_replace('<', '&lt;', $html_email);
216
+ $html_email = str_replace('>', '&gt;', $html_email);
217
+
218
+ return $html_email;
219
+ }
220
+
221
+ private static function _escape_markers(&$markers) {
222
+ $markers[0] = str_replace('/', '\/', $markers[0]);
223
+ $markers[1] = str_replace('/', '\/', $markers[1]);
224
+ }
225
+
226
+ /**
227
+ * Using the data collected inside $controls (and submitted by a form containing the
228
+ * composer fields), updates the email. The message body is completed with doctype,
229
+ * head, style and the main wrapper.
230
+ *
231
+ * @param TNP_Email $email
232
+ * @param NewsletterControls $controls
233
+ */
234
+ static function update_email($email, $controls) {
235
+ if (isset($controls->data['subject'])) {
236
+ $email->subject = $controls->data['subject'];
237
+ }
238
+
239
+ // They should be only composer options
240
+ foreach ($controls->data as $name => $value) {
241
+ if (strpos($name, 'options_') === 0) {
242
+ $email->options[substr($name, 8)] = $value;
243
+ }
244
+ }
245
+
246
+ //if (isset($controls->data['preheader'])) {
247
+ // $email->options['preheader'] = $controls->data['preheader'];
248
+ //}
249
+
250
+ $email->editor = NewsletterEmails::EDITOR_COMPOSER;
251
+
252
+ $email->message = self::get_html_open($email) . self::get_main_wrapper_open($email) .
253
+ $controls->data['message'] . self::get_main_wrapper_close($email) . self::get_html_close($email);
254
+ }
255
+
256
+ /**
257
+ * Prepares a controls object injecting the relevant fields from an email
258
+ * which cannot be directly used by controls. If $email is null or missing,
259
+ * $controls is prepared with default values.
260
+ *
261
+ * @param NewsletterControls $controls
262
+ * @param TNP_Email $email
263
+ */
264
+ static function prepare_controls($controls, $email = null) {
265
+
266
+ // Controls for a new email (which actually does not exist yet
267
+ if (!empty($email)) {
268
+
269
+ foreach ($email->options as $name => $value) {
270
+ $controls->data['options_' . $name] = $value;
271
+ }
272
+
273
+ $controls->data['message'] = TNP_Composer::unwrap_email($email->message);
274
+ $controls->data['subject'] = $email->subject;
275
+ $controls->data['updated'] = $email->updated;
276
+ }
277
+
278
+ if (!empty($email->options['sender_email'])) {
279
+ $controls->data['sender_email'] = $email->options['sender_email'];
280
+ } else {
281
+ $controls->data['sender_email'] = Newsletter::instance()->options['sender_email'];
282
+ }
283
+
284
+ if (!empty($email->options['sender_name'])) {
285
+ $controls->data['sender_name'] = $email->options['sender_name'];
286
+ } else {
287
+ $controls->data['sender_name'] = Newsletter::instance()->options['sender_name'];
288
+ }
289
+
290
+ $controls->data = array_merge(TNP_Composer::get_global_style_defaults(), $controls->data);
291
+ }
292
+
293
+ /**
294
+ * Extract inline edited post field from inline_edit_list[]
295
+ *
296
+ * @param array $inline_edit_list
297
+ * @param string $field_type
298
+ * @param int $post_id
299
+ *
300
+ * @return string
301
+ */
302
+ static function get_edited_inline_post_field($inline_edit_list, $field_type, $post_id) {
303
+
304
+ foreach ($inline_edit_list as $edit) {
305
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
306
+ return $edit['content'];
307
+ }
308
+ }
309
+
310
+ return '';
311
+ }
312
+
313
+ /**
314
+ * Check if inline_edit_list[] have inline edit field for specific post
315
+ *
316
+ * @param array $inline_edit_list
317
+ * @param string $field_type
318
+ * @param int $post_id
319
+ *
320
+ * @return bool
321
+ */
322
+ static function is_post_field_edited_inline($inline_edit_list, $field_type, $post_id) {
323
+ if (empty($inline_edit_list) || !is_array($inline_edit_list)) {
324
+ return false;
325
+ }
326
+ foreach ($inline_edit_list as $edit) {
327
+ if ($edit['type'] == $field_type && $edit['post_id'] == $post_id) {
328
+ return true;
329
+ }
330
+ }
331
+
332
+ return false;
333
+ }
334
+
335
+ /**
336
+ * Creates the HTML for a button extrating from the options, with the provided prefix, the button attributes:
337
+ *
338
+ * - [prefix]_url The button URL
339
+ * - [prefix]_font_family
340
+ * - [prefix]_font_size
341
+ * - [prefix]_font_weight
342
+ * - [prefix]_label
343
+ * - [prefix]_font_color The label color
344
+ * - [prefix]_background The button color
345
+ *
346
+ * TODO: Add radius and possiblt the alignment
347
+ *
348
+ * @param array $options
349
+ * @param string $prefix
350
+ * @return string
351
+ */
352
+ static function button($options, $prefix = 'button') {
353
+
354
+ if (empty($options[$prefix . '_label'])) {
355
+ return;
356
+ }
357
+ $defaults = [
358
+ $prefix . '_url' => '#',
359
+ $prefix . '_font_family' => 'Helvetica, Arial, sans-serif',
360
+ $prefix . '_font_color' => '#ffffff',
361
+ $prefix . '_font_weight' => 'bold',
362
+ $prefix . '_font_size' => 20,
363
+ $prefix . '_background' => '#256F9C',
364
+ $prefix . '_align' => 'center'
365
+ ];
366
+
367
+ $options = array_merge($defaults, array_filter($options));
368
+
369
+ $b = '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin: 0 auto"';
370
+ if (!empty($options[$prefix . '_align'])) {
371
+ $b .= ' align="' . esc_attr($options[$prefix . '_align']) . '"';
372
+ }
373
+ if (!empty($options[$prefix . '_width'])) {
374
+ $b .= ' width="' . esc_attr($options[$prefix . '_width']) . '"';
375
+ }
376
+ $b .= '>';
377
+ $b .= '<tr>';
378
+ $b .= '<td align="center" bgcolor="' . $options[$prefix . '_background'] . '" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:' . $options[$prefix . '_background'] . '" valign="middle">';
379
+ $b .= '<a href="' . $options[$prefix . '_url'] . '"';
380
+ $b .= ' style="display:inline-block;background:' . $options[$prefix . '_background'] . ';color:' . $options[$prefix . '_font_color'] . ';font-family:' . $options[$prefix . '_font_family'] . ';font-size:' . $options[$prefix . '_font_size'] . 'px;font-weight:' . $options[$prefix . '_font_weight'] . ';line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"';
381
+ $b .= ' target="_blank">';
382
+ $b .= $options[$prefix . '_label'];
383
+ $b .= '</a>';
384
+ $b .= '</td></tr></table>';
385
+ return $b;
386
+ }
387
+
388
+ /**
389
+ * Generates an IMG tag, linked if the media has an URL.
390
+ *
391
+ * @param TNP_Media $media
392
+ * @param string $style
393
+ * @return string
394
+ */
395
+ static function image($media, $attr = []) {
396
+
397
+ $default_attrs = [
398
+ 'style' => 'max-width: 100%; height: auto; display: inline-block',
399
+ 'class' => '',
400
+ 'link-style' => 'text-decoration: none; display: inline-block',
401
+ 'link-class' => null,
402
+ ];
403
+
404
+ $attr = array_merge($default_attrs, $attr);
405
+
406
+ //Class and style attribute are mutually exclusive.
407
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
408
+ if (!empty($attr['inline-class'])) {
409
+ $styling = ' inline-class="' . esc_attr($attr['inline-class']) . '" ';
410
+ } else {
411
+ $styling = ' style="' . esc_attr($attr['style']) . '" ';
412
+ }
413
+
414
+ if (!empty($attr['class'])) {
415
+ $styling .= ' class="' . esc_attr($attr['class']) . '" ';
416
+ }
417
+
418
+ //Class and style attribute are mutually exclusive.
419
+ //Class take priority to style because classes will transform to inline style inside block rendering operation
420
+ if (!empty($attr['link-class'])) {
421
+ $link_styling = ' inline-class="' . esc_attr($attr['link-class']) . '" ';
422
+ } else {
423
+ $link_styling = ' style="' . esc_attr($attr['link-style']) . '" ';
424
+ }
425
+
426
+ $b = '';
427
+ if ($media->link) {
428
+ $b .= '<a href="' . esc_attr($media->link) . '" target="_blank" rel="noopener nofollow" style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
429
+ } else {
430
+ // The span grants images are not upscaled when fluid (two columns posts block)
431
+ $b .= '<span style="display: inline-block; font-size: 0; text-decoration: none; line-height: normal!important">';
432
+ }
433
+ if ($media) {
434
+ $b .= '<img src="' . esc_attr($media->url) . '" width="' . esc_attr($media->width) . '"';
435
+ if ($media->height) {
436
+ $b .= ' height="' . esc_attr($media->height) . '"';
437
+ }
438
+ $b .= ' alt="' . esc_attr($media->alt) . '"'
439
+ . ' border="0"'
440
+ . ' style="display: inline-block; max-width: 100%!important; padding: 0; border: 0;"'
441
+ . ' class="' . esc_attr($attr['class']) . '" '
442
+ . '>';
443
+ }
444
+
445
+ if ($media->link) {
446
+ $b .= '</a>';
447
+ } else {
448
+ $b .= '</span>';
449
+ }
450
+
451
+ return $b;
452
+ }
453
+
454
+ /**
455
+ * Returns a WP media ID for the specified post (or false if nothing can be found)
456
+ * looking for the featured image or, if missing, taking the first media in the gallery and
457
+ * if again missing, searching the first reference to a media in the post content.
458
+ *
459
+ * The media ID is not checked for real existance of the associated attachment.
460
+ *
461
+ * @param int $post_id
462
+ * @return int
463
+ */
464
+ static function get_post_thumbnail_id($post_id) {
465
+ if (is_object($post_id)) {
466
+ $post_id = $post_id->ID;
467
+ }
468
+
469
+ // Find a media id to be used as featured image
470
+ $media_id = get_post_thumbnail_id($post_id);
471
+ if (!empty($media_id)) {
472
+ return $media_id;
473
+ }
474
+
475
+ $attachments = get_children(array('numberpost' => 1, 'post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order'));
476
+ if (!empty($attachments)) {
477
+ foreach ($attachments as $id => &$attachment) {
478
+ return $id;
479
+ }
480
+ }
481
+
482
+ $post = get_post($post_id);
483
+
484
+ $r = preg_match('/wp-image-(\d+)/', $post->post_content, $matches);
485
+ if ($matches) {
486
+ return (int) $matches[1];
487
+ }
488
+
489
+ return false;
490
+ }
491
+
492
+ /**
493
+ * Builds a TNP_Media object to be used in newsletters from a WP media/attachement ID. The returned
494
+ * media has a size which best match the one requested (this is the standard WP behavior, plugins
495
+ * could change it).
496
+ *
497
+ * @param int $media_id
498
+ * @param array $size
499
+ * @return \TNP_Media
500
+ */
501
+ function get_media($media_id, $size) {
502
+ $src = wp_get_attachment_image_src($media_id, $size);
503
+ if (!$src) {
504
+ return null;
505
+ }
506
+ $media = new TNP_Media();
507
+ $media->id = $media_id;
508
+ $media->url = $src[0];
509
+ $media->width = $src[1];
510
+ $media->height = $src[2];
511
+ return $media;
512
+ }
513
+
514
+ static function post_content($post) {
515
+ $content = $post->post_content;
516
+ $content = wpautop($content);
517
+ if (true || $options['enable shortcodes']) {
518
+ remove_shortcode('gallery');
519
+ add_shortcode('gallery', 'tnp_gallery_shortcode');
520
+ $content = do_shortcode($content);
521
+ }
522
+ $content = str_replace('<p>', '<p class="paragraph">', $content);
523
+
524
+ $selected_images = array();
525
+ if (preg_match_all('/<img [^>]+>/', $content, $matches)) {
526
+ foreach ($matches[0] as $image) {
527
+ if (preg_match('/wp-image-([0-9]+)/i', $image, $class_id) && ( $attachment_id = absint($class_id[1]) )) {
528
+ $selected_images[$image] = $attachment_id;
529
+ }
530
+ }
531
+ }
532
+
533
+ foreach ($selected_images as $image => $attachment_id) {
534
+ $src = tnp_media_resize($attachment_id, array(600, 0));
535
+ if (is_wp_error($src)) {
536
+ continue;
537
+ }
538
+ $content = str_replace($image, '<img src="' . $src . '" width="600" style="max-width: 100%">', $content);
539
+ }
540
+
541
+ return $content;
542
+ }
543
+
544
+ static function get_global_style_defaults() {
545
+ return [
546
+ 'options_composer_title_font_family' => 'Verdana, Geneva, sans-serif',
547
+ 'options_composer_title_font_size' => 32,
548
+ 'options_composer_title_font_weight' => 'normal',
549
+ 'options_composer_title_font_color' => '#222222',
550
+ 'options_composer_text_font_family' => 'Verdana, Geneva, sans-serif',
551
+ 'options_composer_text_font_size' => 16,
552
+ 'options_composer_text_font_weight' => 'normal',
553
+ 'options_composer_text_font_color' => '#222222',
554
+ 'options_composer_button_font_family' => 'Verdana, Geneva, sans-serif',
555
+ 'options_composer_button_font_size' => 16,
556
+ 'options_composer_button_font_weight' => 'normal',
557
+ 'options_composer_button_font_color' => '#FFFFFF',
558
+ 'options_composer_button_background_color' => '#256F9C',
559
+ 'options_composer_background' => '#FFFFFF',
560
+ 'options_composer_block_background' => '#FFFFFF',
561
+ ];
562
+ }
563
+
564
+ /**
565
+ * Inspired by: https://webdesign.tutsplus.com/tutorials/creating-a-future-proof-responsive-email-without-media-queries--cms-23919
566
+ *
567
+ * Attributes:
568
+ * - columns: number of columns [2]
569
+ * - padding: cells padding [10]
570
+ * - responsive: il on mobile the cell should stack up [true]
571
+ * - width: the whole row width, it should reduced by the external row padding [600]
572
+ *
573
+ * @param string[] $items
574
+ * @param array $attrs
575
+ * @return string
576
+ */
577
+ static function grid($items = [], $attrs = []) {
578
+ $attrs = wp_parse_args($attrs, ['width' => 600, 'columns' => 2, 'padding' => 10, 'responsive' => true]);
579
+ $width = (int) $attrs['width'];
580
+ $columns = (int) $attrs['columns'];
581
+ $padding = (int) $attrs['padding'];
582
+ $column_width = $width / $columns;
583
+ $td_width = 100 / $columns;
584
+ $chunks = array_chunk($items, $columns);
585
+
586
+ if ($attrs['responsive']) {
587
+
588
+ $e = '';
589
+ foreach ($chunks as &$chunk) {
590
+ $e .= '<div style="text-align:center;font-size:0;">';
591
+ $e .= '<!--[if mso]><table role="presentation" width="100%"><tr><![endif]-->';
592
+ foreach ($chunk as &$item) {
593
+ $e .= '<!--[if mso]><td width="' . $td_width . '%" style="width:' . $td_width . '%;padding:' . $padding . 'px" valign="top"><![endif]-->';
594
+
595
+ $e .= '<div class="max-width-100" style="width:100%;max-width:' . $column_width . 'px;display:inline-block;vertical-align: top;box-sizing: border-box;">';
596
+
597
+ // This element to add padding without deal with border-box not well supported
598
+ $e .= '<div style="padding:' . $padding . 'px;">';
599
+ $e .= $item;
600
+ $e .= '</div>';
601
+ $e .= '</div>';
602
+
603
+ $e .= '<!--[if mso]></td><![endif]-->';
604
+ }
605
+ $e .= '<!--[if mso]></tr></table><![endif]-->';
606
+ $e .= '</div>';
607
+ }
608
+
609
+ return $e;
610
+ } else {
611
+ $e = '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 100%!important">';
612
+ foreach ($chunks as &$chunk) {
613
+ $e .= '<tr>';
614
+ foreach ($chunk as &$item) {
615
+ $e .= '<td width="' . $td_width . '%" style="width:' . $td_width . '%; padding:' . $padding . 'px" valign="top">';
616
+ $e .= $item;
617
+ $e .= '</td>';
618
+ }
619
+ $e .= '</tr>';
620
+ }
621
+ $e .= '</table>';
622
+ return $e;
623
+ }
624
+ }
625
+
626
+ static function get_text_style($options, $prefix, $composer, $attrs = []) {
627
+ return self::get_style($options, $prefix, $composer, 'text', $attrs);
628
+ }
629
+
630
+ static function get_title_style($options, $prefix, $composer, $attrs = []) {
631
+ return self::get_style($options, $prefix, $composer, 'title', $attrs);
632
+ }
633
+
634
+ static function get_style($options, $prefix, $composer, $type = 'text', $attrs = []) {
635
+ $style = new TNP_Style();
636
+ $scale = 1.0;
637
+ if (!empty($attrs['scale'])) {
638
+ $scale = (float) $attrs['scale'];
639
+ }
640
+ if (!empty($prefix))
641
+ $prefix .= '_';
642
+
643
+ $style->font_family = empty($options[$prefix . 'font_family']) ? $composer[$type . '_font_family'] : $options[$prefix . 'font_family'];
644
+ $style->font_size = empty($options[$prefix . 'font_size']) ? round($composer[$type . '_font_size'] * $scale) : $options[$prefix . 'font_size'];
645
+ $style->font_color = empty($options[$prefix . 'font_color']) ? $composer[$type . '_font_color'] : $options[$prefix . 'font_color'];
646
+ $style->font_weight = empty($options[$prefix . 'font_weight']) ? $composer[$type . '_font_weight'] : $options[$prefix . 'font_weight'];
647
+ if ($type === 'button') {
648
+ $style->background = empty($options[$prefix . 'background']) ? $composer[$type . '_background_color'] : $options[$prefix . 'background'];
649
+ }
650
+ return $style;
651
+ }
652
+
653
+ static function get_button_options($options, $prefix, $composer) {
654
+ $button_options = [];
655
+ $scale = 1;
656
+ $button_options['button_font_family'] = empty($options[$prefix . '_font_family']) ? $composer['button_font_family'] : $options[$prefix . '_font_family'];
657
+ $button_options['button_font_size'] = empty($options[$prefix . '_font_size']) ? round($composer['button_font_size'] * $scale) : $options[$prefix . '_font_size'];
658
+ $button_options['button_font_color'] = empty($options[$prefix . '_font_color']) ? $composer['button_font_color'] : $options[$prefix . '_font_color'];
659
+ $button_options['button_font_weight'] = empty($options[$prefix . '_font_weight']) ? $composer['button_font_weight'] : $options[$prefix . '_font_weight'];
660
+ $button_options['button_background'] = empty($options[$prefix . '_background']) ? $composer['button_background_color'] : $options[$prefix . '_background'];
661
+ $button_options['button_align'] = empty($options[$prefix . '_align']) ? 'center' : $options[$prefix . '_align'];
662
+ $button_options['button_width'] = empty($options[$prefix . '_width']) ? 'center' : $options[$prefix . '_width'];
663
+ $button_options['button_url'] = empty($options[$prefix . '_url']) ? '#' : $options[$prefix . '_url'];
664
+ $button_options['button_label'] = empty($options[$prefix . '_label']) ? '' : $options[$prefix . '_label'];
665
+
666
+ return $button_options;
667
+ }
668
+
669
+ }
670
+
671
+ class TNP_Style {
672
+
673
+ var $font_family;
674
+ var $font_size;
675
+ var $font_weight;
676
+ var $font_color;
677
+ var $background;
678
+
679
+ function echo_css($scale = 1.0) {
680
+ echo 'font-size: ', round($this->font_size * $scale), 'px;';
681
+ echo 'font-family: ', $this->font_family, ';';
682
+ echo 'font-weight: ', $this->font_weight, ';';
683
+ echo 'color: ', $this->font_color, ';';
684
+ }
685
+
686
+ }
687
+
688
+ /**
689
+ * Generate multicolumn and responsive html template for email.
690
+ * Initialize class with max columns per row and start to add cells.
691
+ */
692
+ class TNP_Composer_Grid_System {
693
+
694
+ /**
695
+ * @var TNP_Composer_Grid_Row[]
696
+ */
697
+ private $rows;
698
+
699
+ /**
700
+ * @var int
701
+ */
702
+ private $cells_per_row;
703
+
704
+ /**
705
+ * @var int
706
+ */
707
+ private $cells_counter;
708
+
709
+ /**
710
+ * TNP_Composer_Grid_System constructor.
711
+ *
712
+ * @param int $columns_per_row Max columns per row
713
+ */
714
+ public function __construct($columns_per_row) {
715
+ $this->cells_per_row = $columns_per_row;
716
+ $this->cells_counter = 0;
717
+ $this->rows = [];
718
+ }
719
+
720
+ public function __toString() {
721
+ return $this->render();
722
+ }
723
+
724
+ /**
725
+ * Add cell to grid
726
+ *
727
+ * @param TNP_Composer_Grid_Cell $cell
728
+ */
729
+ public function add_cell($cell) {
730
+
731
+ if ($this->cells_counter % $this->cells_per_row === 0) {
732
+ $this->add_row(new TNP_Composer_Grid_Row());
733
+ }
734
+
735
+ $row_idx = (int) floor($this->cells_counter / $this->cells_per_row);
736
+ $this->rows[$row_idx]->add_cell($cell);
737
+ $this->cells_counter++;
738
+ }
739
+
740
+ private function add_row($row) {
741
+ $this->rows[] = $row;
742
+ }
743
+
744
+ public function render() {
745
+
746
+ $str = '';
747
+ foreach ($this->rows as $row) {
748
+ $str .= $row->render();
749
+ }
750
+
751
+ return $str;
752
+ }
753
+
754
+ }
755
+
756
+ /**
757
+ * Class TNP_Composer_Grid_Row
758
+ */
759
+ class TNP_Composer_Grid_Row {
760
+
761
+ /**
762
+ * @var TNP_Composer_Grid_Cell[]
763
+ */
764
+ private $cells;
765
+
766
+ public function __construct(...$cells) {
767
+ if (!empty($cells)) {
768
+ foreach ($cells as $cell) {
769
+ $this->add_cell($cell);
770
+ }
771
+ }
772
+ }
773
+
774
+ /**
775
+ * @param TNP_Composer_Grid_Cell $cell
776
+ */
777
+ public function add_cell($cell) {
778
+ $this->cells[] = $cell;
779
+ }
780
+
781
+ public function render() {
782
+ $rendered_cells = '';
783
+ $column_percentage_width = round(100 / $this->cells_count(), 0, PHP_ROUND_HALF_DOWN) . '%';
784
+ foreach ($this->cells as $cell) {
785
+ $rendered_cells .= $cell->render(['width' => $column_percentage_width]);
786
+ }
787
+
788
+ $row_template = $this->get_template();
789
+
790
+ return str_replace('TNP_ROW_CONTENT_PH', $rendered_cells, $row_template);
791
+ }
792
+
793
+ private function cells_count() {
794
+ return count($this->cells);
795
+ }
796
+
797
+ private function get_template() {
798
+ return "<table border='0' cellpadding='0' cellspacing='0' width='100%'><tbody><tr><td>TNP_ROW_CONTENT_PH</td></tr></tbody></table>";
799
+ }
800
+
801
+ }
802
+
803
+ /**
804
+ * Class TNP_Composer_Grid_Cell
805
+ */
806
+ class TNP_Composer_Grid_Cell {
807
+
808
+ /**
809
+ * @var string
810
+ */
811
+ private $content;
812
+
813
+ /**
814
+ * @var array
815
+ */
816
+ public $args;
817
+
818
+ public function __construct($content = null, $args = []) {
819
+ $default_args = [
820
+ 'width' => '100%',
821
+ 'class' => '',
822
+ 'align' => 'left',
823
+ 'valign' => 'top'
824
+ ];
825
+
826
+ $this->args = array_merge($default_args, $args);
827
+
828
+ $this->content = $content ? $content : '';
829
+ }
830
+
831
+ public function add_content($content) {
832
+ $this->content .= $content;
833
+ }
834
+
835
+ public function render($args) {
836
+ $this->args = array_merge($this->args, $args);
837
+
838
+ $column_template = $this->get_template();
839
+ $column = str_replace(
840
+ [
841
+ 'TNP_ALIGN_PH',
842
+ 'TNP_VALIGN_PH',
843
+ 'TNP_WIDTH_PH',
844
+ 'TNP_CLASS_PH',
845
+ 'TNP_COLUMN_CONTENT_PH'
846
+ ], [
847
+ $this->args['align'],
848
+ $this->args['valign'],
849
+ $this->args['width'],
850
+ $this->args['class'],
851
+ $this->content
852
+ ], $column_template);
853
+
854
+ return $column;
855
+ }
856
+
857
+ private function get_template() {
858
+ return "<table border='0' cellpadding='0' cellspacing='0' width='TNP_WIDTH_PH' align='left' style='table-layout: fixed;' class='responsive'>
859
+ <tbody>
860
+ <tr>
861
+ <td border='0' style='padding: 20px 10px 40px;' align='TNP_ALIGN_PH' valign='TNP_VALIGN_PH' class='TNP_CLASS_PH'>
862
+ TNP_COLUMN_CONTENT_PH
863
+ </td>
864
+ </tr>
865
+ </tbody>
866
+ </table>";
867
+ }
868
+
869
+ }
870
+
871
+ class TNP_Composer_Component_Factory {
872
+
873
+ private $options;
874
+
875
+ /**
876
+ * TNP_Composer_Component_Factory constructor.
877
+ *
878
+ * @param Controller$controller
879
+ */
880
+ public function __construct($controller) {
881
+
882
+ }
883
+
884
+ function heading() {
885
+
886
+ }
887
+
888
+ function paragraph() {
889
+
890
+ }
891
+
892
+ function link() {
893
+
894
+ }
895
+
896
+ function button() {
897
+
898
+ }
899
+
900
+ function image() {
901
+
902
+ }
903
+
904
+ }
includes/tnp-blocks.js CHANGED
@@ -1,252 +1,252 @@
1
- (function (blocks, editor, element, components) {
2
-
3
- const el = element.createElement;
4
- const {registerBlockType} = blocks;
5
- const { RichText, InspectorControls, withColors, PanelColorSettings, getColorClassName, AlignmentToolbar, BlockControls } = editor;
6
- const { Fragment } = element;
7
- const { TextControl, RadioControl, Panel, PanelBody, PanelRow, SelectControl, RangeControl } = components;
8
- const colorSamples = [
9
- {
10
- name: 'GREEN SEA',
11
- slug: 'GREENSEA',
12
- color: '#16A085'
13
- },
14
- {
15
- name: 'NEPHRITIS',
16
- slug: 'NEPHRITIS',
17
- color: '#27AE60'
18
- },
19
- {
20
- name: 'BELIZE HOLE',
21
- slug: 'BELIZEHOLE',
22
- color: '#2980B9'
23
- },
24
- {
25
- name: 'WISTERIA',
26
- slug: 'WISTERIA',
27
- color: '#8E44AD'
28
- },
29
- {
30
- name: 'MIDNIGHT BLUE',
31
- slug: 'MIDNIGHTBLUE',
32
- color: '#2C3E50'
33
- },
34
- {
35
- name: 'ORANGE',
36
- slug: 'ORANGE',
37
- color: '#F39C12'
38
- },
39
- {
40
- name: 'ALIZARIN',
41
- slug: 'ALIZARIN',
42
- color: '#E74C3C'
43
- },
44
- {
45
- name: 'WHITE',
46
- slug: 'WHITE',
47
- color: '#FFFFFF'
48
- },
49
- {
50
- name: 'CLOUDS',
51
- slug: 'CLOUDS',
52
- color: '#ECF0F1'
53
- },
54
- {
55
- name: 'ASBESTOS',
56
- slug: 'ASBESTOS',
57
- color: '#7F8C8D'
58
- }
59
- ];
60
-
61
- registerBlockType('tnp/minimal', {
62
- title: 'Newsletter subscription form',
63
- icon: 'email',
64
- category: 'common',
65
- keywords: ['newsletter', 'subscription', 'form'],
66
- attributes: {
67
- formtype: {type: 'string', default: 'minimal'},
68
- content: { type: 'array', source: 'children', selector: 'p', default: 'Subscribe to our newsletter!'},
69
- list_ids: { type: 'string' },
70
- rowColor: { type: 'string'},
71
- customRowColor: { type: 'string'},
72
- textColor: { type: 'string'},
73
- customTextColor: { type: 'string'},
74
- buttonColor: { type: 'string'},
75
- customButtonColor: { type: 'string'},
76
- padding: {type: 'integer', default: 20},
77
- alignment: { type: 'string'}
78
- },
79
-
80
- edit: withColors('rowColor', 'textColor', 'buttonColor')(function (props) {
81
-
82
- function onChangeContent( newContent ) {
83
- props.setAttributes( { content: newContent } );
84
- }
85
-
86
- function onChangeAlignment( newAlignment ) {
87
- props.setAttributes( { alignment: newAlignment } );
88
- }
89
-
90
- return el( Fragment, {},
91
- el( InspectorControls, {},
92
-
93
- // 1st Panel - Form Settings
94
- el( PanelBody, { title: 'Form Settings', initialOpen: true },
95
-
96
- /* Form type */
97
- el( RadioControl,
98
- {
99
- label: 'Form type',
100
- options : [
101
- { label: 'Minimal', value: 'minimal' },
102
- { label: 'Full', value: 'full' },
103
- ],
104
- onChange: ( value ) => {
105
- props.setAttributes( { formtype: value } );
106
- },
107
- selected: props.attributes.formtype
108
- }
109
- ),
110
-
111
- /* Lists field */
112
- el( PanelRow, {},
113
- el( TextControl,
114
- {
115
- label: 'Lists IDs (comma separated)',
116
- onChange: ( value ) => {
117
- props.setAttributes( { list_ids: value } );
118
- },
119
- value: props.attributes.list_ids
120
- }
121
- )
122
- )
123
- ),
124
-
125
- /* Style */
126
- el( PanelColorSettings, {
127
- title: 'Style',
128
- colorSettings: [
129
- {
130
- colors: colorSamples, // here you can pass custom colors
131
- value: props.rowColor.color,
132
- label: 'Background color',
133
- onChange: props.setRowColor,
134
- },
135
- {
136
- colors: colorSamples, // here you can pass custom colors
137
- value: props.textColor.color,
138
- label: 'Text color',
139
- onChange: props.setTextColor,
140
- },
141
- {
142
- colors: colorSamples, // here you can pass custom colors
143
- value: props.buttonColor.color,
144
- label: 'Button color',
145
- onChange: props.setButtonColor,
146
- }
147
- ]
148
- }),
149
-
150
- el( RangeControl,
151
- {
152
- label: 'Padding',
153
- min: 0,
154
- max: 100,
155
- onChange: ( value ) => {
156
- props.setAttributes( { padding: value } );
157
- },
158
- value: props.attributes.padding
159
- }
160
- )
161
-
162
- ),
163
-
164
- el(
165
- "div",
166
- {style: {backgroundColor: props.rowColor.color, color: props.textColor.color, padding: props.attributes.padding, textAlign: props.attributes.alignment}},
167
- el(
168
- BlockControls,
169
- { key: 'controls' },
170
- el(
171
- AlignmentToolbar,
172
- {
173
- value: props.attributes.alignment,
174
- onChange: onChangeAlignment
175
- }
176
- )
177
- ),
178
- el(RichText,
179
- {
180
- tagName: 'p',
181
- format: 'string',
182
- onChange: onChangeContent,
183
- value: props.attributes.content,
184
- // formattingControls: [ 'bold' ]
185
- }),
186
- el('div',
187
- {style: {backgroundColor: 'lightGrey', margin: '20px', padding: '5px',
188
- fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif'}},
189
- el('svg',
190
- {
191
- width: 20,
192
- height: 20
193
- },
194
- wp.element.createElement( 'path',
195
- {
196
- d: "M6 14H4V6h2V4H2v12h4M7.1 17h2.1l3.7-14h-2.1M14 4v2h2v8h-2v2h4V4"
197
- }
198
- )
199
- ),
200
- ' Newsletter Form'
201
- ),
202
- ))
203
- }),
204
- save: function (props) {
205
-
206
- var rowClass = getColorClassName( 'row-color', props.attributes.rowColor );
207
- var textClass = getColorClassName( 'text-color', props.attributes.textColor );
208
- var buttonClass = getColorClassName( 'button-color', props.attributes.buttonColor );
209
-
210
- formtype_attr = "";
211
- if (props.attributes.formtype != "full") {
212
- formtype_attr = " type=\"minimal\"";
213
- }
214
-
215
- lists_attr = "";
216
- if (props.attributes.list_ids) {
217
- lists_attr = " lists=\"" + props.attributes.list_ids + "\"";
218
- }
219
-
220
- button_color_attr = "";
221
- button_color = buttonClass ? undefined : props.attributes.customButtonColor;
222
- if (button_color) {
223
- button_color_attr = " button_color=\"" + button_color + "\"";
224
- }
225
-
226
- var formStyles = {
227
- backgroundColor: rowClass ? undefined : props.attributes.customRowColor,
228
- color: textClass ? undefined : props.attributes.customTextColor,
229
- padding: props.attributes.padding,
230
- textAlign: props.attributes.alignment
231
- };
232
-
233
- return (
234
- el('div', {style: formStyles},
235
- el( RichText.Content, {
236
- tagName: 'p',
237
- value: props.attributes.content
238
- }),
239
- el(
240
- "div",
241
- {},
242
- "[newsletter_form" + formtype_attr + lists_attr + button_color_attr + "]"
243
- )));
244
- }
245
- });
246
-
247
- })(
248
- window.wp.blocks,
249
- window.wp.blockEditor,
250
- window.wp.element,
251
- window.wp.components,
252
- );
1
+ (function (blocks, editor, element, components) {
2
+
3
+ const el = element.createElement;
4
+ const {registerBlockType} = blocks;
5
+ const { RichText, InspectorControls, withColors, PanelColorSettings, getColorClassName, AlignmentToolbar, BlockControls } = editor;
6
+ const { Fragment } = element;
7
+ const { TextControl, RadioControl, Panel, PanelBody, PanelRow, SelectControl, RangeControl } = components;
8
+ const colorSamples = [
9
+ {
10
+ name: 'GREEN SEA',
11
+ slug: 'GREENSEA',
12
+ color: '#16A085'
13
+ },
14
+ {
15
+ name: 'NEPHRITIS',
16
+ slug: 'NEPHRITIS',
17
+ color: '#27AE60'
18
+ },
19
+ {
20
+ name: 'BELIZE HOLE',
21
+ slug: 'BELIZEHOLE',
22
+ color: '#2980B9'
23
+ },
24
+ {
25
+ name: 'WISTERIA',
26
+ slug: 'WISTERIA',
27
+ color: '#8E44AD'
28
+ },
29
+ {
30
+ name: 'MIDNIGHT BLUE',
31
+ slug: 'MIDNIGHTBLUE',
32
+ color: '#2C3E50'
33
+ },
34
+ {
35
+ name: 'ORANGE',
36
+ slug: 'ORANGE',
37
+ color: '#F39C12'
38
+ },
39
+ {
40
+ name: 'ALIZARIN',
41
+ slug: 'ALIZARIN',
42
+ color: '#E74C3C'
43
+ },
44
+ {
45
+ name: 'WHITE',
46
+ slug: 'WHITE',
47
+ color: '#FFFFFF'
48
+ },
49
+ {
50
+ name: 'CLOUDS',
51
+ slug: 'CLOUDS',
52
+ color: '#ECF0F1'
53
+ },
54
+ {
55
+ name: 'ASBESTOS',
56
+ slug: 'ASBESTOS',
57
+ color: '#7F8C8D'
58
+ }
59
+ ];
60
+
61
+ registerBlockType('tnp/minimal', {
62
+ title: 'Newsletter subscription form',
63
+ icon: 'email',
64
+ category: 'common',
65
+ keywords: ['newsletter', 'subscription', 'form'],
66
+ attributes: {
67
+ formtype: {type: 'string', default: 'minimal'},
68
+ content: { type: 'array', source: 'children', selector: 'p', default: 'Subscribe to our newsletter!'},
69
+ list_ids: { type: 'string' },
70
+ rowColor: { type: 'string'},
71
+ customRowColor: { type: 'string'},
72
+ textColor: { type: 'string'},
73
+ customTextColor: { type: 'string'},
74
+ buttonColor: { type: 'string'},
75
+ customButtonColor: { type: 'string'},
76
+ padding: {type: 'integer', default: 20},
77
+ alignment: { type: 'string'}
78
+ },
79
+
80
+ edit: withColors('rowColor', 'textColor', 'buttonColor')(function (props) {
81
+
82
+ function onChangeContent( newContent ) {
83
+ props.setAttributes( { content: newContent } );
84
+ }
85
+
86
+ function onChangeAlignment( newAlignment ) {
87
+ props.setAttributes( { alignment: newAlignment } );
88
+ }
89
+
90
+ return el( Fragment, {},
91
+ el( InspectorControls, {},
92
+
93
+ // 1st Panel - Form Settings
94
+ el( PanelBody, { title: 'Form Settings', initialOpen: true },
95
+
96
+ /* Form type */
97
+ el( RadioControl,
98
+ {
99
+ label: 'Form type',
100
+ options : [
101
+ { label: 'Minimal', value: 'minimal' },
102
+ { label: 'Full', value: 'full' },
103
+ ],
104
+ onChange: ( value ) => {
105
+ props.setAttributes( { formtype: value } );
106
+ },
107
+ selected: props.attributes.formtype
108
+ }
109
+ ),
110
+
111
+ /* Lists field */
112
+ el( PanelRow, {},
113
+ el( TextControl,
114
+ {
115
+ label: 'Lists IDs (comma separated)',
116
+ onChange: ( value ) => {
117
+ props.setAttributes( { list_ids: value } );
118
+ },
119
+ value: props.attributes.list_ids
120
+ }
121
+ )
122
+ )
123
+ ),
124
+
125
+ /* Style */
126
+ el( PanelColorSettings, {
127
+ title: 'Style',
128
+ colorSettings: [
129
+ {
130
+ colors: colorSamples, // here you can pass custom colors
131
+ value: props.rowColor.color,
132
+ label: 'Background color',
133
+ onChange: props.setRowColor,
134
+ },
135
+ {
136
+ colors: colorSamples, // here you can pass custom colors
137
+ value: props.textColor.color,
138
+ label: 'Text color',
139
+ onChange: props.setTextColor,
140
+ },
141
+ {
142
+ colors: colorSamples, // here you can pass custom colors
143
+ value: props.buttonColor.color,
144
+ label: 'Button color',
145
+ onChange: props.setButtonColor,
146
+ }
147
+ ]
148
+ }),
149
+
150
+ el( RangeControl,
151
+ {
152
+ label: 'Padding',
153
+ min: 0,
154
+ max: 100,
155
+ onChange: ( value ) => {
156
+ props.setAttributes( { padding: value } );
157
+ },
158
+ value: props.attributes.padding
159
+ }
160
+ )
161
+
162
+ ),
163
+
164
+ el(
165
+ "div",
166
+ {style: {backgroundColor: props.rowColor.color, color: props.textColor.color, padding: props.attributes.padding, textAlign: props.attributes.alignment}},
167
+ el(
168
+ BlockControls,
169
+ { key: 'controls' },
170
+ el(
171
+ AlignmentToolbar,
172
+ {
173
+ value: props.attributes.alignment,
174
+ onChange: onChangeAlignment
175
+ }
176
+ )
177
+ ),
178
+ el(RichText,
179
+ {
180
+ tagName: 'p',
181
+ format: 'string',
182
+ onChange: onChangeContent,
183
+ value: props.attributes.content,
184
+ // formattingControls: [ 'bold' ]
185
+ }),
186
+ el('div',
187
+ {style: {backgroundColor: 'lightGrey', margin: '20px', padding: '5px',
188
+ fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif'}},
189
+ el('svg',
190
+ {
191
+ width: 20,
192
+ height: 20
193
+ },
194
+ wp.element.createElement( 'path',
195
+ {
196
+ d: "M6 14H4V6h2V4H2v12h4M7.1 17h2.1l3.7-14h-2.1M14 4v2h2v8h-2v2h4V4"
197
+ }
198
+ )
199
+ ),
200
+ ' Newsletter Form'
201
+ ),
202
+ ))
203
+ }),
204
+ save: function (props) {
205
+
206
+ var rowClass = getColorClassName( 'row-color', props.attributes.rowColor );
207
+ var textClass = getColorClassName( 'text-color', props.attributes.textColor );
208
+ var buttonClass = getColorClassName( 'button-color', props.attributes.buttonColor );
209
+
210
+ formtype_attr = "";
211
+ if (props.attributes.formtype != "full") {
212
+ formtype_attr = " type=\"minimal\"";
213
+ }
214
+
215
+ lists_attr = "";
216
+ if (props.attributes.list_ids) {
217
+ lists_attr = " lists=\"" + props.attributes.list_ids + "\"";
218
+ }
219
+
220
+ button_color_attr = "";
221
+ button_color = buttonClass ? undefined : props.attributes.customButtonColor;
222
+ if (button_color) {
223
+ button_color_attr = " button_color=\"" + button_color + "\"";
224
+ }
225
+
226
+ var formStyles = {
227
+ backgroundColor: rowClass ? undefined : props.attributes.customRowColor,
228
+ color: textClass ? undefined : props.attributes.customTextColor,
229
+ padding: props.attributes.padding,
230
+ textAlign: props.attributes.alignment
231
+ };
232
+
233
+ return (
234
+ el('div', {style: formStyles},
235
+ el( RichText.Content, {
236
+ tagName: 'p',
237
+ value: props.attributes.content
238
+ }),
239
+ el(
240
+ "div",
241
+ {},
242
+ "[newsletter_form" + formtype_attr + lists_attr + button_color_attr + "]"
243
+ )));
244
+ }
245
+ });
246
+
247
+ })(
248
+ window.wp.blocks,
249
+ window.wp.blockEditor,
250
+ window.wp.element,
251
+ window.wp.components,
252
+ );
plugin.php CHANGED
@@ -1,1418 +1,1418 @@
1
- <?php
2
-
3
- /*
4
- Plugin Name: Newsletter
5
- Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
- Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
- Version: 7.3.8
8
- Author: Stefano Lissa & The Newsletter Team
9
- Author URI: https://www.thenewsletterplugin.com
10
- Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
11
- Text Domain: newsletter
12
- License: GPLv2 or later
13
- Requires at least: 4.6
14
- Requires PHP: 5.6
15
-
16
- Copyright 2009-2022 The Newsletter Team (email: info@thenewsletterplugin.com, web: https://www.thenewsletterplugin.com)
17
-
18
- Newsletter is free software: you can redistribute it and/or modify
19
- it under the terms of the GNU General Public License as published by
20
- the Free Software Foundation, either version 2 of the License, or
21
- any later version.
22
-
23
- Newsletter is distributed in the hope that it will be useful,
24
- but WITHOUT ANY WARRANTY; without even the implied warranty of
25
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
- GNU General Public License for more details.
27
-
28
- You should have received a copy of the GNU General Public License
29
- along with Newsletter. If not, see https://www.gnu.org/licenses/gpl-2.0.html.
30
-
31
- */
32
-
33
- if (version_compare(phpversion(), '5.6', '<')) {
34
- add_action('admin_notices', function () {
35
- echo '<div class="notice notice-error"><p>PHP version 5.6 or greater is required for Newsletter. Ask your provider to upgrade. <a href="https://www.php.net/supported-versions.php" target="_blank">Read more on PHP versions</a></p></div>';
36
- });
37
- return;
38
- }
39
-
40
- define('NEWSLETTER_VERSION', '7.3.8');
41
-
42
- global $newsletter, $wpdb;
43
-
44
- // For acceptance tests, DO NOT CHANGE
45
- if (!defined('NEWSLETTER_DEBUG'))
46
- define('NEWSLETTER_DEBUG', false);
47
-
48
- if (!defined('NEWSLETTER_EXTENSION_UPDATE'))
49
- define('NEWSLETTER_EXTENSION_UPDATE', true);
50
-
51
- if (!defined('NEWSLETTER_EMAILS_TABLE'))
52
- define('NEWSLETTER_EMAILS_TABLE', $wpdb->prefix . 'newsletter_emails');
53
-
54
- if (!defined('NEWSLETTER_USERS_TABLE'))
55
- define('NEWSLETTER_USERS_TABLE', $wpdb->prefix . 'newsletter');
56
-
57
- if (!defined('NEWSLETTER_STATS_TABLE'))
58
- define('NEWSLETTER_STATS_TABLE', $wpdb->prefix . 'newsletter_stats');
59
-
60
- if (!defined('NEWSLETTER_SENT_TABLE'))
61
- define('NEWSLETTER_SENT_TABLE', $wpdb->prefix . 'newsletter_sent');
62
-
63
- define('NEWSLETTER_SLUG', 'newsletter');
64
-
65
- define('NEWSLETTER_DIR', __DIR__);
66
- define('NEWSLETTER_INCLUDES_DIR', __DIR__ . '/includes');
67
-
68
- // Deperacted since plugin can change the plugins_url()
69
- define('NEWSLETTER_URL', WP_PLUGIN_URL . '/newsletter');
70
-
71
- if (!defined('NEWSLETTER_LIST_MAX'))
72
- define('NEWSLETTER_LIST_MAX', 40);
73
-
74
- if (!defined('NEWSLETTER_PROFILE_MAX'))
75
- define('NEWSLETTER_PROFILE_MAX', 20);
76
-
77
- if (!defined('NEWSLETTER_FORMS_MAX'))
78
- define('NEWSLETTER_FORMS_MAX', 10);
79
-
80
- if (!defined('NEWSLETTER_HEADER'))
81
- define('NEWSLETTER_HEADER', true);
82
-
83
- require_once NEWSLETTER_INCLUDES_DIR . '/module.php';
84
- require_once NEWSLETTER_INCLUDES_DIR . '/TNP.php';
85
- require_once NEWSLETTER_INCLUDES_DIR . '/cron.php';
86
-
87
- class Newsletter extends NewsletterModule {
88
-
89
- // Limits to respect to avoid memory, time or provider limits
90
- var $time_start;
91
- var $time_limit = 0;
92
- var $max_emails = null;
93
-
94
- var $mailer = null;
95
-
96
- var $action = '';
97
-
98
- /** @var Newsletter */
99
- static $instance;
100
-
101
- const STATUS_NOT_CONFIRMED = 'S';
102
- const STATUS_CONFIRMED = 'C';
103
-
104
- /**
105
- * @return Newsletter
106
- */
107
- static function instance() {
108
- if (self::$instance == null) {
109
- self::$instance = new Newsletter();
110
- }
111
- return self::$instance;
112
- }
113
-
114
- function __construct() {
115
-
116
- // Grab it before a plugin decides to remove it.
117
- if (isset($_GET['na'])) {
118
- $this->action = $_GET['na'];
119
- }
120
- if (isset($_POST['na'])) {
121
- $this->action = $_POST['na'];
122
- }
123
-
124
- $this->time_start = time();
125
-
126
- parent::__construct('main', '1.6.7', null, ['info', 'smtp']);
127
-
128
- add_action('plugins_loaded', [$this, 'hook_plugins_loaded']);
129
- add_action('init', [$this, 'hook_init'], 1);
130
- add_action('wp_loaded', [$this, 'hook_wp_loaded'], 1);
131
-
132
- add_action('newsletter', [$this, 'hook_newsletter'], 1);
133
-
134
- register_activation_hook(__FILE__, [$this, 'hook_activate']);
135
- register_deactivation_hook(__FILE__, [$this, 'hook_deactivate']);
136
-
137
- add_action('admin_init', [$this, 'hook_admin_init']);
138
-
139
- if (is_admin()) {
140
- add_action('admin_head', [$this, 'hook_admin_head']);
141
-
142
- // Protection against strange schedule removal on some installations
143
- if (!wp_next_scheduled('newsletter') && (!defined('WP_INSTALLING') || !WP_INSTALLING)) {
144
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
145
- }
146
-
147
- add_action('admin_menu', [$this, 'add_extensions_menu'], 90);
148
-
149
- add_filter('display_post_states', [$this, 'add_notice_to_chosen_profile_page_hook'], 10, 2);
150
-
151
- if ($this->is_admin_page()) {
152
- // Remove the emoji replacer to save to database the original emoji characters (see even woocommerce for the same problem)
153
- remove_action('admin_print_scripts', 'print_emoji_detection_script');
154
- add_action('admin_enqueue_scripts', [$this, 'hook_admin_enqueue_scripts']);
155
- }
156
-
157
- }
158
- }
159
-
160
- function hook_plugins_loaded() {
161
- // Used to load dependant modules
162
- do_action('newsletter_loaded', NEWSLETTER_VERSION);
163
-
164
- if (function_exists('load_plugin_textdomain')) {
165
- load_plugin_textdomain('newsletter', false, plugin_basename(__DIR__) . '/languages');
166
- }
167
- }
168
-
169
- function hook_init() {
170
- global $wpdb;
171
-
172
- if (isset($this->options['debug']) && $this->options['debug'] == 1) {
173
- ini_set('log_errors', 1);
174
- ini_set('error_log', WP_CONTENT_DIR . '/logs/newsletter/php-' . date('Y-m') . '-' . get_option('newsletter_logger_secret') . '.txt');
175
- }
176
-
177
- add_shortcode('newsletter_replace', [$this, 'shortcode_newsletter_replace']);
178
-
179
- add_filter('site_transient_update_plugins', [$this, 'hook_site_transient_update_plugins']);
180
-
181
- if (is_admin()) {
182
- if (!class_exists('NewsletterExtensions')) {
183
-
184
- add_filter('plugin_row_meta', function ($plugin_meta, $plugin_file) {
185
-
186
- static $slugs = array();
187
- if (empty($slugs)) {
188
- $addons = $this->getTnpExtensions();
189
- if ($addons) {
190
- foreach ($addons as $addon) {
191
- $slugs[] = $addon->wp_slug;
192
- }
193
- }
194
- }
195
- if (array_search($plugin_file, $slugs) !== false) {
196
-
197
- $plugin_meta[] = '<a href="admin.php?page=newsletter_main_extensions" style="font-weight: bold">Newsletter Addons Manager required</a>';
198
- }
199
- return $plugin_meta;
200
- }, 10, 2);
201
- }
202
-
203
- add_action('in_admin_header', array($this, 'hook_in_admin_header'), 1000);
204
-
205
- if ($this->is_admin_page()) {
206
-
207
- $dismissed = get_option('newsletter_dismissed', []);
208
-
209
- if (isset($_GET['dismiss'])) {
210
- $dismissed[$_GET['dismiss']] = 1;
211
- update_option('newsletter_dismissed', $dismissed);
212
- wp_redirect($_SERVER['HTTP_REFERER']);
213
- exit();
214
- }
215
- }
216
- } else {
217
- add_action('wp_enqueue_scripts', [$this, 'hook_wp_enqueue_scripts']);
218
- }
219
-
220
- do_action('newsletter_init');
221
- }
222
-
223
- function hook_wp_loaded() {
224
- if (empty($this->action)) {
225
- return;
226
- }
227
-
228
- if ($this->action == 'test') {
229
- // This response is tested, do not change it!
230
- echo 'ok';
231
- die();
232
- }
233
-
234
- if ($this->action === 'nul') {
235
- $this->dienow('This link is not active on newsletter preview', 'You can send a test message to test subscriber to have the real working link.');
236
- }
237
-
238
- $user = $this->get_user_from_request();
239
- $email = $this->get_email_from_request();
240
- do_action('newsletter_action', $this->action, $user, $email);
241
- }
242
-
243
- function hook_activate() {
244
- // Ok, why? When the plugin is not active WordPress may remove the scheduled "newsletter" action because
245
- // the every-five-minutes schedule named "newsletter" is not present.
246
- // Since the activation does not forces an upgrade, that schedule must be reactivated here. It is activated on
247
- // the upgrade method as well for the user which upgrade the plugin without deactivte it (many).
248
- if (!wp_next_scheduled('newsletter')) {
249
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
250
- }
251
-
252
- $install_time = get_option('newsletter_install_time');
253
- if (!$install_time) {
254
- update_option('newsletter_install_time', time(), false);
255
- }
256
-
257
- touch(NEWSLETTER_LOG_DIR . '/index.html');
258
-
259
- Newsletter::instance()->upgrade();
260
- NewsletterUsers::instance()->upgrade();
261
- NewsletterEmails::instance()->upgrade();
262
- NewsletterSubscription::instance()->upgrade();
263
- NewsletterStatistics::instance()->upgrade();
264
- NewsletterProfile::instance()->upgrade();
265
- }
266
-
267
- function first_install() {
268
- parent::first_install();
269
- update_option('newsletter_show_welcome', '1', false);
270
- }
271
-
272
- function upgrade() {
273
- global $wpdb, $charset_collate;
274
-
275
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
276
-
277
- parent::upgrade();
278
-
279
- $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_emails` (
280
- `id` int(11) NOT NULL AUTO_INCREMENT,
281
- `language` varchar(10) NOT NULL DEFAULT '',
282
- `subject` varchar(255) NOT NULL DEFAULT '',
283
- `message` longtext,
284
- `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
285
- `status` enum('new','sending','sent','paused','error') NOT NULL DEFAULT 'new',
286
- `total` int(11) NOT NULL DEFAULT '0',
287
- `last_id` int(11) NOT NULL DEFAULT '0',
288
- `sent` int(11) NOT NULL DEFAULT '0',
289
- `track` int(11) NOT NULL DEFAULT '1',
290
- `list` int(11) NOT NULL DEFAULT '0',
291
- `type` varchar(50) NOT NULL DEFAULT '',
292
- `query` longtext,
293
- `editor` tinyint(4) NOT NULL DEFAULT '0',
294
- `sex` varchar(20) NOT NULL DEFAULT '',
295
- `theme` varchar(50) NOT NULL DEFAULT '',
296
- `message_text` longtext,
297
- `preferences` longtext,
298
- `send_on` int(11) NOT NULL DEFAULT '0',
299
- `token` varchar(10) NOT NULL DEFAULT '',
300
- `options` longtext,
301
- `private` tinyint(1) NOT NULL DEFAULT '0',
302
- `click_count` int(10) unsigned NOT NULL DEFAULT '0',
303
- `version` varchar(10) NOT NULL DEFAULT '',
304
- `open_count` int(10) unsigned NOT NULL DEFAULT '0',
305
- `unsub_count` int(10) unsigned NOT NULL DEFAULT '0',
306
- `error_count` int(10) unsigned NOT NULL DEFAULT '0',
307
- `stats_time` int(10) unsigned NOT NULL DEFAULT '0',
308
- `updated` int(10) unsigned NOT NULL DEFAULT '0',
309
- PRIMARY KEY (`id`)
310
- ) $charset_collate;";
311
-
312
- dbDelta($sql);
313
-
314
- // WP does not manage composite primary key when it tries to upgrade a table...
315
- $suppress_errors = $wpdb->suppress_errors(true);
316
-
317
- dbDelta("CREATE TABLE " . $wpdb->prefix . "newsletter_sent (
318
- email_id int(10) unsigned NOT NULL DEFAULT '0',
319
- user_id int(10) unsigned NOT NULL DEFAULT '0',
320
- status tinyint(1) unsigned NOT NULL DEFAULT '0',
321
- open tinyint(1) unsigned NOT NULL DEFAULT '0',
322
- time int(10) unsigned NOT NULL DEFAULT '0',
323
- error varchar(255) NOT NULL DEFAULT '',
324
- ip varchar(100) NOT NULL DEFAULT '',
325
- PRIMARY KEY (email_id,user_id),
326
- KEY user_id (user_id),
327
- KEY email_id (email_id)
328
- ) $charset_collate;");
329
- $wpdb->suppress_errors($suppress_errors);
330
-
331
- // Some setting check to avoid the common support request for mis-configurations
332
- $options = $this->get_options();
333
-
334
- if (empty($options['scheduler_max']) || !is_numeric($options['scheduler_max'])) {
335
- $options['scheduler_max'] = 100;
336
- $this->save_options($options);
337
- }
338
-
339
- wp_clear_scheduled_hook('newsletter');
340
- wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
341
-
342
- if (!empty($this->options['editor'])) {
343
- if (empty($this->options['roles'])) {
344
- $this->options['roles'] = array('editor');
345
- unset($this->options['editor']);
346
- }
347
- $this->save_options($this->options);
348
- }
349
-
350
- // Clear the addons and license caches
351
- delete_transient('newsletter_license_data');
352
- $this->clear_extensions_cache();
353
-
354
- touch(NEWSLETTER_LOG_DIR . '/index.html');
355
-
356
- return true;
357
- }
358
-
359
- function is_allowed() {
360
- if (current_user_can('administrator')) {
361
- return true;
362
- }
363
- if (!empty($this->options['roles'])) {
364
- foreach ($this->options['roles'] as $role) {
365
- if (current_user_can($role)) {
366
- return true;
367
- }
368
- }
369
- }
370
- return false;
371
- }
372
-
373
- function admin_menu() {
374
- if (!$this->is_allowed()) {
375
- return;
376
- }
377
-
378
- add_menu_page('Newsletter', 'Newsletter', 'exist', 'newsletter_main_index', '', plugins_url('newsletter') . '/admin/images/menu-icon.png', '30.333');
379
-
380
- $this->add_menu_page('index', __('Dashboard', 'newsletter'));
381
- $this->add_admin_page('info', __('Company info', 'newsletter'));
382
-
383
- if (current_user_can('administrator')) {
384
- $this->add_menu_page('welcome', __('Welcome', 'newsletter'));
385
- $this->add_menu_page('main', __('Settings', 'newsletter'));
386
-
387
- // Pages not on menu
388
- $this->add_admin_page('smtp', 'SMTP');
389
- $this->add_admin_page('diagnostic', __('Diagnostic', 'newsletter'));
390
- $this->add_admin_page('test', __('Test', 'newsletter'));
391
- }
392
- }
393
-
394
- function add_extensions_menu() {
395
- if (!class_exists('NewsletterExtensions')) {
396
- $this->add_menu_page('extensions', '<span style="color:#27AE60; font-weight: bold;">' . __('Addons', 'newsletter') . '</span>');
397
- }
398
- }
399
-
400
- function hook_in_admin_header() {
401
- if (!$this->is_admin_page()) {
402
- add_action('admin_notices', array($this, 'hook_admin_notices'));
403
- return;
404
- }
405
- remove_all_actions('admin_notices');
406
- remove_all_actions('all_admin_notices');
407
- add_action('admin_notices', array($this, 'hook_admin_notices'));
408
- }
409
-
410
- function hook_admin_notices() {
411
- // Check of Newsletter dedicated page
412
- if (!empty($this->options['page'])) {
413
- if (get_post_status($this->options['page']) !== 'publish') {
414
- echo '<div class="notice notice-error"><p>The Newsletter dedicated page is not published. <a href="', site_url('/wp-admin/post.php') . '?post=', $this->options['page'], '&action=edit"><strong>Edit the page</strong></a> or <a href="admin.php?page=newsletter_main_main"><strong>review the main settings</strong></a>.</p></div>';
415
- }
416
- }
417
-
418
- if (isset($this->options['debug']) && $this->options['debug'] == 1) {
419
- echo '<div class="notice notice-warning"><p>The Newsletter plugin is in <strong>debug mode</strong>. When done change it on Newsletter <a href="admin.php?page=newsletter_main_main"><strong>main settings</strong></a>. Do not keep the debug mode active on production sites.</p></div>';
420
- }
421
- }
422
-
423
- function hook_wp_enqueue_scripts() {
424
- if (empty($this->options['css_disabled']) && apply_filters('newsletter_enqueue_style', true)) {
425
- wp_enqueue_style('newsletter', plugins_url('newsletter') . '/style.css', [], NEWSLETTER_VERSION);
426
- if (!empty($this->options['css'])) {
427
- wp_add_inline_style('newsletter', $this->options['css']);
428
- }
429
- } else {
430
- if (!empty($this->options['css'])) {
431
- add_action('wp_head', function () {
432
- echo '<style>', $this->options['css'], '</style>';
433
- });
434
- }
435
- }
436
- }
437
-
438
- function hook_admin_enqueue_scripts() {
439
-
440
- $newsletter_url = plugins_url('newsletter');
441
- wp_enqueue_script('jquery-ui-tabs');
442
- wp_enqueue_script('jquery-ui-tooltip');
443
- wp_enqueue_script('jquery-ui-draggable');
444
- wp_enqueue_media();
445
-
446
- wp_enqueue_script('tnp-admin', $newsletter_url . '/admin/admin.js', ['jquery'], NEWSLETTER_VERSION);
447
-
448
- wp_enqueue_style('tnp-select2', $newsletter_url . '/vendor/select2/css/select2.min.css', [], NEWSLETTER_VERSION);
449
- wp_enqueue_script('tnp-select2', $newsletter_url . '/vendor/select2/js/select2.min.js', ['jquery'], NEWSLETTER_VERSION);
450
-
451
- wp_enqueue_style('tnp-modal', $newsletter_url . '/admin/modal.css', [], NEWSLETTER_VERSION);
452
- wp_enqueue_script('tnp-modal', $newsletter_url . '/admin/modal.js', ['jquery'], NEWSLETTER_VERSION, true);
453
-
454
- wp_enqueue_style('tnp-toast', $newsletter_url . '/admin/toast.css', [], NEWSLETTER_VERSION);
455
- wp_enqueue_script('tnp-toast', $newsletter_url . '/admin/toast.js', ['jquery'], NEWSLETTER_VERSION);
456
-
457
- wp_enqueue_style('tnp-admin-font', 'https://use.typekit.net/jlj2wjy.css');
458
- wp_enqueue_style('tnp-admin-fontawesome', $newsletter_url . '/vendor/fa/css/all.min.css', [], NEWSLETTER_VERSION);
459
- wp_enqueue_style('tnp-admin-jquery-ui', $newsletter_url . '/vendor/jquery-ui/jquery-ui.min.css', [], NEWSLETTER_VERSION);
460
- wp_enqueue_style('tnp-admin', $newsletter_url . '/admin/admin.css', [], NEWSLETTER_VERSION);
461
- wp_enqueue_style('tnp-admin-dropdown', $newsletter_url . '/admin/dropdown.css', [], NEWSLETTER_VERSION);
462
- wp_enqueue_style('tnp-admin-tabs', $newsletter_url . '/admin/tabs.css', [], NEWSLETTER_VERSION);
463
- wp_enqueue_style('tnp-admin-controls', $newsletter_url . '/admin/controls.css', [], NEWSLETTER_VERSION);
464
- wp_enqueue_style('tnp-admin-fields', $newsletter_url . '/admin/fields.css', [], NEWSLETTER_VERSION);
465
- wp_enqueue_style('tnp-admin-widgets', $newsletter_url . '/admin/widgets.css', [], NEWSLETTER_VERSION);
466
- wp_enqueue_style('tnp-admin-extensions', $newsletter_url . '/admin/extensions.css', [], NEWSLETTER_VERSION);
467
-
468
- $translations_array = array(
469
- 'save_to_update_counter' => __('Save the newsletter to update the counter!', 'newsletter')
470
- );
471
- wp_localize_script('tnp-admin', 'tnp_translations', $translations_array);
472
-
473
- wp_enqueue_script('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jquery.vmap.min.js', ['jquery'], NEWSLETTER_VERSION);
474
- wp_enqueue_script('tnp-jquery-vmap-world', $newsletter_url . '/vendor/jqvmap/jquery.vmap.world.js', ['tnp-jquery-vmap'], NEWSLETTER_VERSION);
475
- wp_enqueue_style('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jqvmap.min.css', [], NEWSLETTER_VERSION);
476
-
477
- wp_register_script('tnp-chart', $newsletter_url . '/vendor/chartjs/Chart.min.js', ['jquery'], NEWSLETTER_VERSION);
478
-
479
- wp_enqueue_script('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.js', ['jquery']);
480
- wp_enqueue_style('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.css', [], NEWSLETTER_VERSION);
481
- }
482
-
483
- function shortcode_newsletter_replace($attrs, $content) {
484
- $content = do_shortcode($content);
485
- $content = $this->replace($content, $this->get_user_from_request(), $this->get_email_from_request());
486
- return $content;
487
- }
488
-
489
- function is_admin_page() {
490
- if (!isset($_GET['page'])) {
491
- return false;
492
- }
493
- $page = $_GET['page'];
494
- return strpos($page, 'newsletter_') === 0;
495
- }
496
-
497
- function hook_admin_init() {
498
- // Verificare il contesto
499
- if (isset($_GET['page']) && $_GET['page'] === 'newsletter_main_welcome')
500
- return;
501
- if (get_option('newsletter_show_welcome')) {
502
- delete_option('newsletter_show_welcome');
503
- wp_redirect(admin_url('admin.php?page=newsletter_main_welcome'));
504
- }
505
- }
506
-
507
- function hook_admin_head() {
508
- // Small global rule for sidebar menu entries
509
- echo '<style>';
510
- echo '.tnp-side-menu { color: #E67E22!important; }';
511
- echo '</style>';
512
- }
513
-
514
- function relink($text, $email_id, $user_id, $email_token = '') {
515
- return NewsletterStatistics::instance()->relink($text, $email_id, $user_id, $email_token);
516
- }
517
-
518
- /**
519
- * Runs every 5 minutes and look for emails that need to be processed.
520
- */
521
- function hook_newsletter() {
522
-
523
- $this->logger->debug(__METHOD__ . '> Start');
524
-
525
- if (!$this->check_transient('engine', NEWSLETTER_CRON_INTERVAL)) {
526
- $this->logger->debug(__METHOD__ . '> Engine already active, exit');
527
- return;
528
- }
529
-
530
- $emails = $this->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " where status='sending' and send_on<" . time() . " order by id asc");
531
- $this->logger->debug(__METHOD__ . '> Emails found in sending status: ' . count($emails));
532
-
533
- foreach ($emails as $email) {
534
- $this->logger->debug(__METHOD__ . '> Start newsletter ' . $email->id);
535
- $email->options = maybe_unserialize($email->options);
536
- $r = $this->send($email);
537
- $this->logger->debug(__METHOD__ . '> End newsletter ' . $email->id);
538
- if (!$r) {
539
- $this->logger->debug(__METHOD__ . '> Engine returned false, there is no more capacity');
540
- break;
541
- }
542
- }
543
- // Remove the semaphore so the delivery engine can be activated again
544
- $this->delete_transient('engine');
545
-
546
- $this->logger->debug(__METHOD__ . '> End');
547
- }
548
-
549
- function get_send_speed($email = null) {
550
- $this->logger->debug(__METHOD__ . '> Computing delivery speed');
551
- $mailer = $this->get_mailer();
552
- $speed = (int) $mailer->get_speed();
553
- if (!$speed) {
554
- $this->logger->debug(__METHOD__ . '> Speed not set by mailer, use the default');
555
- $speed = (int) $this->options['scheduler_max'];
556
- } else {
557
- $this->logger->debug(__METHOD__ . '> Speed set by mailer');
558
- }
559
-
560
- //$speed = (int) apply_filters('newsletter_send_speed', $speed, $email);
561
- // Fail safe setting
562
- $runs_per_hour = $this->get_runs_per_hour();
563
- if (!$speed || $speed < $runs_per_hour) {
564
- return $runs_per_hour;
565
- }
566
-
567
- $this->logger->debug(__METHOD__ . '> Speed: ' . $speed);
568
- return $speed;
569
- }
570
-
571
- function get_send_delay() {
572
- return 0;
573
- }
574
-
575
- function skip_this_run($email = null) {
576
- return (boolean) apply_filters('newsletter_send_skip', false, $email);
577
- }
578
-
579
- function get_runs_per_hour() {
580
- return (int) (3600 / NEWSLETTER_CRON_INTERVAL);
581
- }
582
-
583
- function get_emails_per_run() {
584
- $speed = $this->get_send_speed();
585
- $max = (int)($speed / $this->get_runs_per_hour());
586
-
587
- return $max;
588
- }
589
-
590
- function get_max_emails($email) {
591
- // Obsolete, here from Speed Control Addon
592
- $max = (int) apply_filters('newsletter_send_max_emails', $this->max_emails, $email);
593
-
594
- return min($max, $this->max_emails);
595
- }
596
-
597
- function fix_email($email) {
598
- if (empty($email->query)) {
599
- $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
600
- }
601
- if (empty($email->id)) {
602
- $email->id = 0;
603
- }
604
- }
605
-
606
- function send_setup() {
607
- $this->logger->debug(__METHOD__ . '> Setup delivery engine');
608
- if (is_null($this->max_emails)) {
609
- $this->max_emails = $this->get_emails_per_run();
610
- $this->logger->debug(__METHOD__ . '> Max emails: ' . $this->max_emails);
611
- ignore_user_abort(true);
612
-
613
- @set_time_limit(NEWSLETTER_CRON_INTERVAL + 30);
614
-
615
- $max_time = (int) (@ini_get('max_execution_time') * 0.95);
616
- if ($max_time == 0 || $max_time > NEWSLETTER_CRON_INTERVAL) {
617
- $max_time = (int) (NEWSLETTER_CRON_INTERVAL * 0.95);
618
- }
619
-
620
- $this->time_limit = $this->time_start + $max_time;
621
-
622
- $this->logger->debug(__METHOD__ . '> Max time set to ' . $max_time);
623
- } else {
624
- $this->logger->debug(__METHOD__ . '> Already setup');
625
- }
626
- }
627
-
628
- function time_exceeded() {
629
- if ($this->time_limit && time() > $this->time_limit) {
630
- $this->logger->info(__METHOD__ . '> Max execution time limit reached');
631
- return true;
632
- }
633
- }
634
-
635
- /**
636
- * Sends an email to targeted users or to given users. If a list of users is given (usually a list of test users)
637
- * the query inside the email to retrieve users is not used.
638
- *
639
- * @global wpdb $wpdb
640
- * @global type $newsletter_feed
641
- * @param TNP_Email $email
642
- * @param array $users
643
- * @return boolean|WP_Error True if the process completed, false if limits was reached. On false the caller should no continue to call it with other emails.
644
- */
645
- function send($email, $users = null, $test = false) {
646
- global $wpdb;
647
-
648
- if (is_array($email)) {
649
- $email = (object) $email;
650
- }
651
-
652
- $this->logger->info(__METHOD__ . '> Send email ' . $email->id);
653
-
654
- $this->send_setup();
655
-
656
- if ($this->max_emails <= 0) {
657
- $this->logger->info(__METHOD__ . '> No more capacity');
658
- return false;
659
- }
660
-
661
- $this->fix_email($email);
662
-
663
- // This stops the update of last_id and sent fields since
664
- // it's not a scheduled delivery but a test or something else (like an autoresponder)
665
- $supplied_users = $users != null;
666
-
667
- if (!$supplied_users) {
668
-
669
- if ($this->skip_this_run($email)) {
670
- return true;
671
- }
672
-
673
- // Speed change for specific email by Speed Control Addon
674
- $max_emails = $this->get_max_emails($email);
675
- if ($max_emails <= 0) {
676
- return true;
677
- }
678
-
679
- $query = $email->query;
680
- $query .= " and id>" . $email->last_id . " order by id limit " . $max_emails;
681
-
682
- $this->logger->debug(__METHOD__ . '> Query: ' . $query);
683
-
684
- //Retrieve subscribers
685
- $users = $this->get_results($query);
686
-
687
- $this->logger->debug(__METHOD__ . '> Loaded subscribers: ' . count($users));
688
-
689
- // If there was a database error, return error
690
- if ($users === false) {
691
- return new WP_Error('1', 'Unable to query subscribers, check the logs');
692
- }
693
-
694
- if (empty($users)) {
695
- $this->logger->info(__METHOD__ . '> No more users, set as sent');
696
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set status='sent', total=sent where id=" . $email->id . " limit 1");
697
- do_action('newsletter_ended_sending_newsletter', $email);
698
- return true;
699
- }
700
- } else {
701
- $this->logger->info(__METHOD__ . '> Subscribers supplied');
702
- }
703
-
704
- $start_time = microtime(true);
705
- $count = 0;
706
- $result = true;
707
-
708
- $mailer = $this->get_mailer();
709
-
710
- $batch_size = $mailer->get_batch_size();
711
-
712
- $this->logger->debug(__METHOD__ . '> Batch size: ' . $batch_size);
713
-
714
- // For batch size == 1 (normal condition) we optimize
715
- if ($batch_size == 1) {
716
-
717
- foreach ($users as $user) {
718
-
719
- $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
720
- $user = apply_filters('newsletter_send_user', $user);
721
- $message = $this->build_message($email, $user);
722
-
723
- // Save even test emails since people wants to see some stats even for test emails. Stats are reset upon the real "send" of a newsletter
724
- $this->save_sent_message($message);
725
-
726
- //Se non è un test incremento il contatore delle email spedite. Perchè incremento prima di spedire??
727
- if (!$test) {
728
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
729
- }
730
-
731
- $r = $mailer->send($message);
732
-
733
- if (!empty($message->error)) {
734
- $this->logger->error($message);
735
- $this->save_sent_message($message);
736
- }
737
-
738
- if (is_wp_error($r)) {
739
- $this->logger->error($r);
740
-
741
- // For fatal error, the newsletter status i changed to error (and the delivery stopped)
742
- if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
743
- $this->set_error_state_of_email($email, $r->get_error_message());
744
- return $r;
745
- }
746
- }
747
-
748
- if (!$supplied_users && !$test && $this->time_exceeded()) {
749
- $result = false;
750
- break;
751
- }
752
- }
753
-
754
- $this->max_emails--;
755
- $count++;
756
- } else {
757
-
758
- $chunks = array_chunk($users, $batch_size);
759
-
760
- foreach ($chunks as $chunk) {
761
-
762
- $messages = [];
763
-
764
- // Peeparing a batch of messages
765
- foreach ($chunk as $user) {
766
- $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
767
- $user = apply_filters('newsletter_send_user', $user);
768
- $message = $this->build_message($email, $user);
769
- $this->save_sent_message($message);
770
- $messages[] = $message;
771
-
772
- if (!$test) {
773
- $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
774
- }
775
- $this->max_emails--;
776
- $count++;
777
- }
778
-
779
- $r = $mailer->send_batch($messages);
780
-
781
- // Updating the status of the sent messages
782
- foreach ($messages as $message) {
783
- if (!empty($message->error)) {
784
- $this->save_sent_message($message);
785
- }
786
- }
787
-
788
- // The batch went in error
789
- if (is_wp_error($r)) {
790
- $this->logger->error($r);
791
-
792
- if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
793
- $this->set_error_state_of_email($email, $r->get_error_message());
794
- return $r;
795
- }
796
- }
797
-
798
- if (!$supplied_users && !$test && $this->time_exceeded()) {
799
- $result = false;
800
- break;
801
- }
802
- }
803
- }
804
-
805
- $end_time = microtime(true);
806
-
807
- // Stats only for newsletter with enough emails in a batch (we exclude the Autoresponder since it send one email per call)
808
- if (!$test && !$supplied_users && $count > 5) {
809
- $this->update_send_stats($start_time, $end_time, $count, $result);
810
- }
811
-
812
- // Cached general statistics are reset
813
- if (!$test) {
814
- NewsletterStatistics::instance()->reset_stats_time($email->id);
815
- }
816
-
817
- $this->logger->info(__METHOD__ . '> End run for email ' . $email->id);
818
-
819
- return $result;
820
- }
821
-
822
- function update_send_stats($start_time, $end_time, $count, $result) {
823
- $send_calls = get_option('newsletter_diagnostic_send_calls', []);
824
- $send_calls[] = [$start_time, $end_time, $count, $result];
825
-
826
- if (count($send_calls) > 100) {
827
- array_shift($send_calls);
828
- }
829
-
830
- update_option('newsletter_diagnostic_send_calls', $send_calls, false);
831
- }
832
-
833
- /**
834
- * @param TNP_Email $email
835
- */
836
- private function set_error_state_of_email($email, $message = '') {
837
- // Handle only message type at the moment
838
- if ($email->type !== 'message') {
839
- return;
840
- }
841
-
842
- do_action('newsletter_error_on_sending', $email, $message);
843
-
844
- $edited_email = new TNP_Email();
845
- $edited_email->id = $email->id;
846
- $edited_email->status = TNP_Email::STATUS_ERROR;
847
- $edited_email->options = $email->options;
848
- $edited_email->options['error_message'] = $message;
849
-
850
- $this->save_email($edited_email);
851
- }
852
-
853
- /**
854
- *
855
- * @param TNP_Email $email
856
- * @param TNP_User $user
857
- * @return \TNP_Mailer_Message
858
- */
859
- function build_message($email, $user) {
860
-
861
- $message = new TNP_Mailer_Message();
862
-
863
- $message->to = $user->email;
864
-
865
- $message->headers = [];
866
- $message->headers['Precedence'] = 'bulk';
867
- $message->headers['X-Newsletter-Email-Id'] = $email->id;
868
- $message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
869
- $message->headers = apply_filters('newsletter_message_headers', $message->headers, $email, $user);
870
-
871
- $message->body = preg_replace('/data-json=".*?"/is', '', $email->message);
872
- $message->body = preg_replace('/ +/s', ' ', $message->body);
873
- $message->body = $this->replace($message->body, $user, $email);
874
- if ($this->options['do_shortcodes']) {
875
- $message->body = do_shortcode($message->body);
876
- }
877
- $message->body = apply_filters('newsletter_message_html', $message->body, $email, $user);
878
-
879
- $message->body_text = $this->replace($email->message_text, $user, $email);
880
- $message->body_text = apply_filters('newsletter_message_text', $message->body_text, $email, $user);
881
-
882
- if ($email->track == 1) {
883
- $message->body = $this->relink($message->body, $email->id, $user->id, $email->token);
884
- }
885
-
886
- $message->subject = $this->replace($email->subject, $user);
887
- $message->subject = apply_filters('newsletter_message_subject', $message->subject, $email, $user);
888
-
889
- if (!empty($email->options['sender_email'])) {
890
- $message->from = $email->options['sender_email'];
891
- } else {
892
- $message->from = $this->options['sender_email'];
893
- }
894
-
895
- if (!empty($email->options['sender_name'])) {
896
- $message->from_name = $email->options['sender_name'];
897
- } else {
898
- $message->from_name = $this->options['sender_name'];
899
- }
900
-
901
- $message->email_id = $email->id;
902
- $message->user_id = $user->id;
903
-
904
- return apply_filters('newsletter_message', $message, $email, $user);
905
- }
906
-
907
- /**
908
- *
909
- * @param TNP_Mailer_Message $message
910
- * @param int $status
911
- * @param string $error
912
- */
913
- function save_sent_message($message) {
914
- global $wpdb;
915
-
916
- if (!$message->user_id || !$message->email_id) {
917
- return;
918
- }
919
- $status = empty($message->error) ? 0 : 1;
920
-
921
- $error = mb_substr($message->error, 0, 250);
922
-
923
- $this->query($wpdb->prepare("insert into " . $wpdb->prefix . 'newsletter_sent (user_id, email_id, time, status, error) values (%d, %d, %d, %d, %s) on duplicate key update time=%d, status=%d, error=%s', $message->user_id, $message->email_id, time(), $status, $error, time(), $status, $error));
924
- }
925
-
926
- /**
927
- * @deprecated since version 7.3.0
928
- */
929
- function limits_exceeded() {
930
- return false;
931
- }
932
-
933
- /**
934
- * @deprecated since version 6.0.0
935
- */
936
- function register_mail_method($callable) {
937
- }
938
-
939
- function register_mailer($mailer) {
940
- if ($mailer instanceof NewsletterMailer) {
941
- $this->mailer = $mailer;
942
- }
943
- }
944
-
945
- /**
946
- * Returns the current registered mailer which must be used to send emails.
947
- *
948
- * @return NewsletterMailer
949
- */
950
- function get_mailer() {
951
- if ($this->mailer) {
952
- return $this->mailer;
953
- }
954
-
955
- do_action('newsletter_register_mailer');
956
-
957
- if (!$this->mailer) {
958
- // Compatibility
959
- $smtp = $this->get_options('smtp');
960
- if (!empty($smtp['enabled'])) {
961
- $this->mailer = new NewsletterDefaultSMTPMailer($smtp);
962
- } else {
963
- $this->mailer = new NewsletterDefaultMailer();
964
- }
965
- }
966
- return $this->mailer;
967
- }
968
-
969
- /**
970
- *
971
- * @param TNP_Mailer_Message $message
972
- * @return type
973
- */
974
- function deliver($message) {
975
- $mailer = $this->get_mailer();
976
- if (empty($message->from))
977
- $message->from = $this->options['sender_email'];
978
- if (empty($message->from_name))
979
- $mailer->from_name = $this->options['sender_name'];
980
- return $mailer->send($message);
981
- }
982
-
983
- /**
984
- *
985
- * @param type $to
986
- * @param type $subject
987
- * @param string|array $message If string is considered HTML, is array should contains the keys "html" and "text"
988
- * @param type $headers
989
- * @param type $enqueue
990
- * @param type $from
991
- * @return boolean
992
- */
993
- function mail($to, $subject, $message, $headers = array(), $enqueue = false, $from = false) {
994
-
995
- if (empty($subject)) {
996
- $this->logger->error('mail> Subject empty, skipped');
997
- return true;
998
- }
999
-
1000
- $mailer_message = new TNP_Mailer_Message();
1001
- $mailer_message->to = $to;
1002
- $mailer_message->subject = $subject;
1003
- $mailer_message->from = $this->options['sender_email'];
1004
- $mailer_message->from_name = $this->options['sender_name'];
1005
-
1006
- if (!empty($headers)) {
1007
- $mailer_message->headers = $headers;
1008
- }
1009
- $mailer_message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
1010
-
1011
- // Message carrige returns and line feeds clean up
1012
- if (!is_array($message)) {
1013
- $mailer_message->body = $this->clean_eol($message);
1014
- } else {
1015
- if (!empty($message['text'])) {
1016
- $mailer_message->body_text = $this->clean_eol($message['text']);
1017
- }
1018
-
1019
- if (!empty($message['html'])) {
1020
- $mailer_message->body = $this->clean_eol($message['html']);
1021
- }
1022
- }
1023
-
1024
- $this->logger->debug($mailer_message);
1025
-
1026
- $mailer = $this->get_mailer();
1027
-
1028
- $r = $mailer->send($mailer_message);
1029
-
1030
- return !is_wp_error($r);
1031
- }
1032
-
1033
- function hook_deactivate() {
1034
- wp_clear_scheduled_hook('newsletter');
1035
- }
1036
-
1037
- function find_file($file1, $file2) {
1038
- if (is_file($file1))
1039
- return $file1;
1040
- return $file2;
1041
- }
1042
-
1043
- function hook_site_transient_update_plugins($value) {
1044
- static $extra_response = array();
1045
-
1046
- //$this->logger->debug('Update plugins transient called');
1047
-
1048
- if (!$value || !is_object($value)) {
1049
- //$this->logger->info('Empty object');
1050
- return $value;
1051
- }
1052
-
1053
- if (!isset($value->response) || !is_array($value->response)) {
1054
- $value->response = array();
1055
- }
1056
-
1057
- // Already computed? Use it! (this filter is called many times in a single request)
1058
- if ($extra_response) {
1059
- //$this->logger->debug('Already updated');
1060
- $value->response = array_merge($value->response, $extra_response);
1061
- return $value;
1062
- }
1063
-
1064
- $extensions = $this->getTnpExtensions();
1065
-
1066
- // Ops...
1067
- if (!$extensions) {
1068
- return $value;
1069
- }
1070
-
1071
- foreach ($extensions as $extension) {
1072
- unset($value->response[$extension->wp_slug]);
1073
- unset($value->no_update[$extension->wp_slug]);
1074
- }
1075
-
1076
- // Someone doesn't want our addons updated, let respect it (this constant should be defined in wp-config.php)
1077
- if (!NEWSLETTER_EXTENSION_UPDATE) {
1078
- //$this->logger->info('Updates disabled');
1079
- return $value;
1080
- }
1081
-
1082
- include_once(ABSPATH . 'wp-admin/includes/plugin.php');
1083
-
1084
- // Ok, that is really bad (should we remove it? is there a minimum WP version?)
1085
- if (!function_exists('get_plugin_data')) {
1086
- //$this->logger->error('No get_plugin_data function available!');
1087
- return $value;
1088
- }
1089
-
1090
- $license_key = $this->get_license_key();
1091
-
1092
- // Here we prepare the update information BUT do not add the link to the package which is privided
1093
- // by our Addons Manager (due to WP policies)
1094
- foreach ($extensions as $extension) {
1095
-
1096
- // Patch for names convention
1097
- $extension->plugin = $extension->wp_slug;
1098
-
1099
- //$this->logger->debug('Processing ' . $extension->plugin);
1100
- //$this->logger->debug($extension);
1101
-
1102
- $plugin_data = false;
1103
- if (file_exists(WP_PLUGIN_DIR . '/' . $extension->plugin)) {
1104
- $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1105
- } else if (file_exists(WPMU_PLUGIN_DIR . '/' . $extension->plugin)) {
1106
- $plugin_data = get_plugin_data(WPMU_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1107
- }
1108
-
1109
- if (!$plugin_data) {
1110
- //$this->logger->debug('Seems not installed');
1111
- continue;
1112
- }
1113
-
1114
- $plugin = new stdClass();
1115
- $plugin->id = $extension->id;
1116
- $plugin->slug = $extension->slug;
1117
- $plugin->plugin = $extension->plugin;
1118
- $plugin->new_version = $extension->version;
1119
- $plugin->url = $extension->url;
1120
- if (class_exists('NewsletterExtensions')) {
1121
- // NO filters here!
1122
- $plugin->package = NewsletterExtensions::$instance->get_package($extension->id, $license_key);
1123
- } else {
1124
- $plugin->package = '';
1125
- }
1126
- // [banners] => Array
1127
- // (
1128
- // [2x] => https://ps.w.org/wp-rss-aggregator/assets/banner-1544x500.png?rev=2040548
1129
- // [1x] => https://ps.w.org/wp-rss-aggregator/assets/banner-772x250.png?rev=2040548
1130
- // )
1131
- // [icons] => Array
1132
- // (
1133
- // [2x] => https://ps.w.org/advanced-custom-fields/assets/icon-256x256.png?rev=1082746
1134
- // [1x] => https://ps.w.org/advanced-custom-fields/assets/icon-128x128.png?rev=1082746
1135
- // )
1136
- if (version_compare($extension->version, $plugin_data['Version']) > 0) {
1137
- //$this->logger->debug('There is a new version');
1138
- $extra_response[$extension->plugin] = $plugin;
1139
- } else {
1140
- // Maybe useless...
1141
- //$this->logger->debug('There is NOT a new version');
1142
- $value->no_update[$extension->plugin] = $plugin;
1143
- }
1144
- //$this->logger->debug('Added');
1145
- }
1146
-
1147
- $value->response = array_merge($value->response, $extra_response);
1148
-
1149
- return $value;
1150
- }
1151
-
1152
- /**
1153
- * @deprecated since version 6.1.9
1154
- */
1155
- function get_extension_version($extension_id) {
1156
- return null;
1157
- }
1158
-
1159
- /**
1160
- * @deprecated since version 6.1.9
1161
- */
1162
- function set_extension_update_data($value, $extension) {
1163
- return $value;
1164
- }
1165
-
1166
- /**
1167
- * Retrieve the extensions form the tnp site
1168
- * @return array
1169
- */
1170
- function getTnpExtensions() {
1171
-
1172
- $extensions_json = get_transient('tnp_extensions_json');
1173
-
1174
- if (empty($extensions_json)) {
1175
- $url = "http://www.thenewsletterplugin.com/wp-content/extensions.json?ver=" . NEWSLETTER_VERSION;
1176
- $extensions_response = wp_remote_get($url);
1177
-
1178
- if (is_wp_error($extensions_response)) {
1179
- // Cache anyway for blogs which cannot connect outside
1180
- $extensions_json = '[]';
1181
- set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1182
- $this->logger->error($extensions_response);
1183
- } else {
1184
-
1185
- $extensions_json = wp_remote_retrieve_body($extensions_response);
1186
-
1187
- // Not clear cases
1188
- if (empty($extensions_json) || !json_decode($extensions_json)) {
1189
- $this->logger->error('Invalid json from thenewsletterplugin.com: retrying in 72 hours');
1190
- $this->logger->error('JSON: ' . $extensions_json);
1191
- $extensions_json = '[]';
1192
- }
1193
- set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1194
- }
1195
- }
1196
-
1197
- $extensions = json_decode($extensions_json);
1198
-
1199
- return $extensions;
1200
- }
1201
-
1202
- function clear_extensions_cache() {
1203
- delete_transient('tnp_extensions_json');
1204
- }
1205
-
1206
- var $panels = array();
1207
-
1208
- function add_panel($key, $panel) {
1209
- if (!isset($this->panels[$key]))
1210
- $this->panels[$key] = array();
1211
- if (!isset($panel['id']))
1212
- $panel['id'] = sanitize_key($panel['label']);
1213
- $this->panels[$key][] = $panel;
1214
- }
1215
-
1216
- function has_license() {
1217
- return !empty($this->options['contract_key']);
1218
- }
1219
-
1220
- function get_sender_name() {
1221
- return $this->options['sender_name'];
1222
- }
1223
-
1224
- function get_sender_email() {
1225
- return $this->options['sender_email'];
1226
- }
1227
-
1228
- /**
1229
- *
1230
- * @return int
1231
- */
1232
- function get_newsletter_page_id() {
1233
- return (int) $this->options['page'];
1234
- }
1235
-
1236
- /**
1237
- *
1238
- * @return WP_Post
1239
- */
1240
- function get_newsletter_page() {
1241
- return get_post($this->get_newsletter_page_id());
1242
- }
1243
-
1244
- /**
1245
- * Returns the Newsletter dedicated page URL or an alternative URL if that page if not
1246
- * configured or not available.
1247
- *
1248
- * @staticvar string $url
1249
- * @return string
1250
- */
1251
- function get_newsletter_page_url($language = '') {
1252
-
1253
- $page = $this->get_newsletter_page();
1254
-
1255
- if (!$page || $page->post_status !== 'publish') {
1256
- return $this->build_action_url('m');
1257
- }
1258
-
1259
- $newsletter_page_url = get_permalink($page->ID);
1260
- if ($language && $newsletter_page_url) {
1261
- if (class_exists('SitePress')) {
1262
- $newsletter_page_url = apply_filters('wpml_permalink', $newsletter_page_url, $language, true);
1263
- }
1264
- if (function_exists('pll_get_post')) {
1265
- $translated_page = get_permalink(pll_get_post($page->ID, $language));
1266
- if ($translated_page) {
1267
- $newsletter_page_url = $translated_page;
1268
- }
1269
- }
1270
- }
1271
-
1272
- return $newsletter_page_url;
1273
- }
1274
-
1275
- function get_license_key() {
1276
- if (defined('NEWSLETTER_LICENSE_KEY')) {
1277
- return NEWSLETTER_LICENSE_KEY;
1278
- } else {
1279
- if (!empty($this->options['contract_key'])) {
1280
- return trim($this->options['contract_key']);
1281
- }
1282
- }
1283
- return false;
1284
- }
1285
-
1286
- /**
1287
- * Get the data connected to the specified license code on man settings.
1288
- *
1289
- * - false if no license is present
1290
- * - WP_Error if something went wrong if getting the license data
1291
- * - object with expiration and addons list
1292
- *
1293
- * @param boolean $refresh
1294
- * @return \WP_Error|boolean|object
1295
- */
1296
- function get_license_data($refresh = false) {
1297
-
1298
- $this->logger->debug('Getting license data');
1299
-
1300
- $license_key = $this->get_license_key();
1301
- if (empty($license_key)) {
1302
- $this->logger->debug('License was empty');
1303
- delete_transient('newsletter_license_data');
1304
- return false;
1305
- }
1306
-
1307
- if (!$refresh) {
1308
- $license_data = get_transient('newsletter_license_data');
1309
- if ($license_data !== false && is_object($license_data)) {
1310
- $this->logger->debug('License data found on cache');
1311
- return $license_data;
1312
- }
1313
- }
1314
-
1315
- $this->logger->debug('Refreshing the license data');
1316
-
1317
- $license_data_url = 'https://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/get-license-data.php';
1318
-
1319
- $response = wp_remote_post($license_data_url, array(
1320
- 'body' => array('k' => $license_key)
1321
- ));
1322
-
1323
- // Fall back to http...
1324
- if (is_wp_error($response)) {
1325
- $this->logger->error($response);
1326
- $this->logger->error('Falling back to http');
1327
- $license_data_url = str_replace('https', 'http', $license_data_url);
1328
- $response = wp_remote_post($license_data_url, array(
1329
- 'body' => array('k' => $license_key)
1330
- ));
1331
- if (is_wp_error($response)) {
1332
- $this->logger->error($response);
1333
- set_transient('newsletter_license_data', $response, DAY_IN_SECONDS);
1334
- return $response;
1335
- }
1336
- }
1337
-
1338
- $download_message = 'You can download all addons from www.thenewsletterplugin.com if your license is valid.';
1339
-
1340
- if (wp_remote_retrieve_response_code($response) != '200') {
1341
- $this->logger->error('license data error: ' . wp_remote_retrieve_response_code($response));
1342
- $data = new WP_Error(wp_remote_retrieve_response_code($response), 'License validation service error. <br>' . $download_message);
1343
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1344
- return $data;
1345
- }
1346
-
1347
- $json = wp_remote_retrieve_body($response);
1348
- $data = json_decode($json);
1349
-
1350
- if (!is_object($data)) {
1351
- $this->logger->error($json);
1352
- $data = new WP_Error(1, 'License validation service error. <br>' . $download_message);
1353
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1354
- return $data;
1355
- }
1356
-
1357
- if (isset($data->message)) {
1358
- $data = new WP_Error(1, $data->message . ' (check the license on Newsletter main settings)');
1359
- set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1360
- return $data;
1361
- }
1362
-
1363
- $expiration = WEEK_IN_SECONDS;
1364
- // If the license expires in few days, make the transient live only few days, so it will be refreshed
1365
- if ($data->expire > time() && $data->expire - time() < WEEK_IN_SECONDS) {
1366
- $expiration = $data->expire - time();
1367
- }
1368
- set_transient('newsletter_license_data', $data, $expiration);
1369
-
1370
- return $data;
1371
- }
1372
-
1373
- /**
1374
- * @deprecated
1375
- * @param type $license_key
1376
- * @return \WP_Error
1377
- */
1378
- public static function check_license($license_key) {
1379
- $response = wp_remote_get('http://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/check.php?k=' . urlencode($license_key), array('sslverify' => false));
1380
- if (is_wp_error($response)) {
1381
- /* @var $response WP_Error */
1382
- return new WP_Error(-1, 'It seems that your blog cannot contact the license validator. Ask your provider to unlock the HTTP/HTTPS connections to www.thenewsletterplugin.com<br>'
1383
- . esc_html($response->get_error_code()) . ' - ' . esc_html($response->get_error_message()));
1384
- } else if ($response['response']['code'] != 200) {
1385
- return new WP_Error(-1, '[' . $response['response']['code'] . '] The license seems expired or not valid, please check your <a href="https://www.thenewsletterplugin.com/account">license code and status</a>, thank you.'
1386
- . '<br>You can anyway download the professional extension from https://www.thenewsletterplugin.com.');
1387
- } elseif ($expires = json_decode(wp_remote_retrieve_body($response))) {
1388
- return array('expires' => $expires->expire, 'message' => 'Your license is valid and expires on ' . esc_html(date('Y-m-d', $expires->expire)));
1389
- } else {
1390
- return new WP_Error(-1, 'Unable to detect the license expiration. Debug data to report to the support: <code>' . esc_html(wp_remote_retrieve_body($response)) . '</code>');
1391
- }
1392
- }
1393
-
1394
- function add_notice_to_chosen_profile_page_hook($post_states, $post) {
1395
-
1396
- if ($post->ID == $this->options['page']) {
1397
- $post_states[] = __('Newsletter plugin page, do not delete', 'newsletter');
1398
- }
1399
-
1400
- return $post_states;
1401
- }
1402
-
1403
- }
1404
-
1405
- $newsletter = Newsletter::instance();
1406
-
1407
- if (is_admin()) {
1408
- require_once NEWSLETTER_DIR . '/system/system.php';
1409
- }
1410
-
1411
- require_once NEWSLETTER_DIR . '/subscription/subscription.php';
1412
- require_once NEWSLETTER_DIR . '/unsubscription/unsubscription.php';
1413
- require_once NEWSLETTER_DIR . '/profile/profile.php';
1414
- require_once NEWSLETTER_DIR . '/emails/emails.php';
1415
- require_once NEWSLETTER_DIR . '/users/users.php';
1416
- require_once NEWSLETTER_DIR . '/statistics/statistics.php';
1417
- require_once NEWSLETTER_DIR . '/widget/standard.php';
1418
- require_once NEWSLETTER_DIR . '/widget/minimal.php';
1
+ <?php
2
+
3
+ /*
4
+ Plugin Name: Newsletter
5
+ Plugin URI: https://www.thenewsletterplugin.com/plugins/newsletter
6
+ Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
7
+ Version: 7.3.9
8
+ Author: Stefano Lissa & The Newsletter Team
9
+ Author URI: https://www.thenewsletterplugin.com
10
+ Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
11
+ Text Domain: newsletter
12
+ License: GPLv2 or later
13
+ Requires at least: 4.6
14
+ Requires PHP: 5.6
15
+
16
+ Copyright 2009-2022 The Newsletter Team (email: info@thenewsletterplugin.com, web: https://www.thenewsletterplugin.com)
17
+
18
+ Newsletter is free software: you can redistribute it and/or modify
19
+ it under the terms of the GNU General Public License as published by
20
+ the Free Software Foundation, either version 2 of the License, or
21
+ any later version.
22
+
23
+ Newsletter is distributed in the hope that it will be useful,
24
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
25
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
+ GNU General Public License for more details.
27
+
28
+ You should have received a copy of the GNU General Public License
29
+ along with Newsletter. If not, see https://www.gnu.org/licenses/gpl-2.0.html.
30
+
31
+ */
32
+
33
+ if (version_compare(phpversion(), '5.6', '<')) {
34
+ add_action('admin_notices', function () {
35
+ echo '<div class="notice notice-error"><p>PHP version 5.6 or greater is required for Newsletter. Ask your provider to upgrade. <a href="https://www.php.net/supported-versions.php" target="_blank">Read more on PHP versions</a></p></div>';
36
+ });
37
+ return;
38
+ }
39
+
40
+ define('NEWSLETTER_VERSION', '7.3.9');
41
+
42
+ global $newsletter, $wpdb;
43
+
44
+ // For acceptance tests, DO NOT CHANGE
45
+ if (!defined('NEWSLETTER_DEBUG'))
46
+ define('NEWSLETTER_DEBUG', false);
47
+
48
+ if (!defined('NEWSLETTER_EXTENSION_UPDATE'))
49
+ define('NEWSLETTER_EXTENSION_UPDATE', true);
50
+
51
+ if (!defined('NEWSLETTER_EMAILS_TABLE'))
52
+ define('NEWSLETTER_EMAILS_TABLE', $wpdb->prefix . 'newsletter_emails');
53
+
54
+ if (!defined('NEWSLETTER_USERS_TABLE'))
55
+ define('NEWSLETTER_USERS_TABLE', $wpdb->prefix . 'newsletter');
56
+
57
+ if (!defined('NEWSLETTER_STATS_TABLE'))
58
+ define('NEWSLETTER_STATS_TABLE', $wpdb->prefix . 'newsletter_stats');
59
+
60
+ if (!defined('NEWSLETTER_SENT_TABLE'))
61
+ define('NEWSLETTER_SENT_TABLE', $wpdb->prefix . 'newsletter_sent');
62
+
63
+ define('NEWSLETTER_SLUG', 'newsletter');
64
+
65
+ define('NEWSLETTER_DIR', __DIR__);
66
+ define('NEWSLETTER_INCLUDES_DIR', __DIR__ . '/includes');
67
+
68
+ // Deperacted since plugin can change the plugins_url()
69
+ define('NEWSLETTER_URL', WP_PLUGIN_URL . '/newsletter');
70
+
71
+ if (!defined('NEWSLETTER_LIST_MAX'))
72
+ define('NEWSLETTER_LIST_MAX', 40);
73
+
74
+ if (!defined('NEWSLETTER_PROFILE_MAX'))
75
+ define('NEWSLETTER_PROFILE_MAX', 20);
76
+
77
+ if (!defined('NEWSLETTER_FORMS_MAX'))
78
+ define('NEWSLETTER_FORMS_MAX', 10);
79
+
80
+ if (!defined('NEWSLETTER_HEADER'))
81
+ define('NEWSLETTER_HEADER', true);
82
+
83
+ require_once NEWSLETTER_INCLUDES_DIR . '/module.php';
84
+ require_once NEWSLETTER_INCLUDES_DIR . '/TNP.php';
85
+ require_once NEWSLETTER_INCLUDES_DIR . '/cron.php';
86
+
87
+ class Newsletter extends NewsletterModule {
88
+
89
+ // Limits to respect to avoid memory, time or provider limits
90
+ var $time_start;
91
+ var $time_limit = 0;
92
+ var $max_emails = null;
93
+
94
+ var $mailer = null;
95
+
96
+ var $action = '';
97
+
98
+ /** @var Newsletter */
99
+ static $instance;
100
+
101
+ const STATUS_NOT_CONFIRMED = 'S';
102
+ const STATUS_CONFIRMED = 'C';
103
+
104
+ /**
105
+ * @return Newsletter
106
+ */
107
+ static function instance() {
108
+ if (self::$instance == null) {
109
+ self::$instance = new Newsletter();
110
+ }
111
+ return self::$instance;
112
+ }
113
+
114
+ function __construct() {
115
+
116
+ // Grab it before a plugin decides to remove it.
117
+ if (isset($_GET['na'])) {
118
+ $this->action = $_GET['na'];
119
+ }
120
+ if (isset($_POST['na'])) {
121
+ $this->action = $_POST['na'];
122
+ }
123
+
124
+ $this->time_start = time();
125
+
126
+ parent::__construct('main', '1.6.7', null, ['info', 'smtp']);
127
+
128
+ add_action('plugins_loaded', [$this, 'hook_plugins_loaded']);
129
+ add_action('init', [$this, 'hook_init'], 1);
130
+ add_action('wp_loaded', [$this, 'hook_wp_loaded'], 1);
131
+
132
+ add_action('newsletter', [$this, 'hook_newsletter'], 1);
133
+
134
+ register_activation_hook(__FILE__, [$this, 'hook_activate']);
135
+ register_deactivation_hook(__FILE__, [$this, 'hook_deactivate']);
136
+
137
+ add_action('admin_init', [$this, 'hook_admin_init']);
138
+
139
+ if (is_admin()) {
140
+ add_action('admin_head', [$this, 'hook_admin_head']);
141
+
142
+ // Protection against strange schedule removal on some installations
143
+ if (!wp_next_scheduled('newsletter') && (!defined('WP_INSTALLING') || !WP_INSTALLING)) {
144
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
145
+ }
146
+
147
+ add_action('admin_menu', [$this, 'add_extensions_menu'], 90);
148
+
149
+ add_filter('display_post_states', [$this, 'add_notice_to_chosen_profile_page_hook'], 10, 2);
150
+
151
+ if ($this->is_admin_page()) {
152
+ // Remove the emoji replacer to save to database the original emoji characters (see even woocommerce for the same problem)
153
+ remove_action('admin_print_scripts', 'print_emoji_detection_script');
154
+ add_action('admin_enqueue_scripts', [$this, 'hook_admin_enqueue_scripts']);
155
+ }
156
+
157
+ }
158
+ }
159
+
160
+ function hook_plugins_loaded() {
161
+ // Used to load dependant modules
162
+ do_action('newsletter_loaded', NEWSLETTER_VERSION);
163
+
164
+ if (function_exists('load_plugin_textdomain')) {
165
+ load_plugin_textdomain('newsletter', false, plugin_basename(__DIR__) . '/languages');
166
+ }
167
+ }
168
+
169
+ function hook_init() {
170
+ global $wpdb;
171
+
172
+ if (isset($this->options['debug']) && $this->options['debug'] == 1) {
173
+ ini_set('log_errors', 1);
174
+ ini_set('error_log', WP_CONTENT_DIR . '/logs/newsletter/php-' . date('Y-m') . '-' . get_option('newsletter_logger_secret') . '.txt');
175
+ }
176
+
177
+ add_shortcode('newsletter_replace', [$this, 'shortcode_newsletter_replace']);
178
+
179
+ add_filter('site_transient_update_plugins', [$this, 'hook_site_transient_update_plugins']);
180
+
181
+ if (is_admin()) {
182
+ if (!class_exists('NewsletterExtensions')) {
183
+
184
+ add_filter('plugin_row_meta', function ($plugin_meta, $plugin_file) {
185
+
186
+ static $slugs = array();
187
+ if (empty($slugs)) {
188
+ $addons = $this->getTnpExtensions();
189
+ if ($addons) {
190
+ foreach ($addons as $addon) {
191
+ $slugs[] = $addon->wp_slug;
192
+ }
193
+ }
194
+ }
195
+ if (array_search($plugin_file, $slugs) !== false) {
196
+
197
+ $plugin_meta[] = '<a href="admin.php?page=newsletter_main_extensions" style="font-weight: bold">Newsletter Addons Manager required</a>';
198
+ }
199
+ return $plugin_meta;
200
+ }, 10, 2);
201
+ }
202
+
203
+ add_action('in_admin_header', array($this, 'hook_in_admin_header'), 1000);
204
+
205
+ if ($this->is_admin_page()) {
206
+
207
+ $dismissed = get_option('newsletter_dismissed', []);
208
+
209
+ if (isset($_GET['dismiss'])) {
210
+ $dismissed[$_GET['dismiss']] = 1;
211
+ update_option('newsletter_dismissed', $dismissed);
212
+ wp_redirect($_SERVER['HTTP_REFERER']);
213
+ exit();
214
+ }
215
+ }
216
+ } else {
217
+ add_action('wp_enqueue_scripts', [$this, 'hook_wp_enqueue_scripts']);
218
+ }
219
+
220
+ do_action('newsletter_init');
221
+ }
222
+
223
+ function hook_wp_loaded() {
224
+ if (empty($this->action)) {
225
+ return;
226
+ }
227
+
228
+ if ($this->action == 'test') {
229
+ // This response is tested, do not change it!
230
+ echo 'ok';
231
+ die();
232
+ }
233
+
234
+ if ($this->action === 'nul') {
235
+ $this->dienow('This link is not active on newsletter preview', 'You can send a test message to test subscriber to have the real working link.');
236
+ }
237
+
238
+ $user = $this->get_user_from_request();
239
+ $email = $this->get_email_from_request();
240
+ do_action('newsletter_action', $this->action, $user, $email);
241
+ }
242
+
243
+ function hook_activate() {
244
+ // Ok, why? When the plugin is not active WordPress may remove the scheduled "newsletter" action because
245
+ // the every-five-minutes schedule named "newsletter" is not present.
246
+ // Since the activation does not forces an upgrade, that schedule must be reactivated here. It is activated on
247
+ // the upgrade method as well for the user which upgrade the plugin without deactivte it (many).
248
+ if (!wp_next_scheduled('newsletter')) {
249
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
250
+ }
251
+
252
+ $install_time = get_option('newsletter_install_time');
253
+ if (!$install_time) {
254
+ update_option('newsletter_install_time', time(), false);
255
+ }
256
+
257
+ touch(NEWSLETTER_LOG_DIR . '/index.html');
258
+
259
+ Newsletter::instance()->upgrade();
260
+ NewsletterUsers::instance()->upgrade();
261
+ NewsletterEmails::instance()->upgrade();
262
+ NewsletterSubscription::instance()->upgrade();
263
+ NewsletterStatistics::instance()->upgrade();
264
+ NewsletterProfile::instance()->upgrade();
265
+ }
266
+
267
+ function first_install() {
268
+ parent::first_install();
269
+ update_option('newsletter_show_welcome', '1', false);
270
+ }
271
+
272
+ function upgrade() {
273
+ global $wpdb, $charset_collate;
274
+
275
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
276
+
277
+ parent::upgrade();
278
+
279
+ $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_emails` (
280
+ `id` int(11) NOT NULL AUTO_INCREMENT,
281
+ `language` varchar(10) NOT NULL DEFAULT '',
282
+ `subject` varchar(255) NOT NULL DEFAULT '',
283
+ `message` longtext,
284
+ `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
285
+ `status` enum('new','sending','sent','paused','error') NOT NULL DEFAULT 'new',
286
+ `total` int(11) NOT NULL DEFAULT '0',
287
+ `last_id` int(11) NOT NULL DEFAULT '0',
288
+ `sent` int(11) NOT NULL DEFAULT '0',
289
+ `track` int(11) NOT NULL DEFAULT '1',
290
+ `list` int(11) NOT NULL DEFAULT '0',
291
+ `type` varchar(50) NOT NULL DEFAULT '',
292
+ `query` longtext,
293
+ `editor` tinyint(4) NOT NULL DEFAULT '0',
294
+ `sex` varchar(20) NOT NULL DEFAULT '',
295
+ `theme` varchar(50) NOT NULL DEFAULT '',
296
+ `message_text` longtext,
297
+ `preferences` longtext,
298
+ `send_on` int(11) NOT NULL DEFAULT '0',
299
+ `token` varchar(10) NOT NULL DEFAULT '',
300
+ `options` longtext,
301
+ `private` tinyint(1) NOT NULL DEFAULT '0',
302
+ `click_count` int(10) unsigned NOT NULL DEFAULT '0',
303
+ `version` varchar(10) NOT NULL DEFAULT '',
304
+ `open_count` int(10) unsigned NOT NULL DEFAULT '0',
305
+ `unsub_count` int(10) unsigned NOT NULL DEFAULT '0',
306
+ `error_count` int(10) unsigned NOT NULL DEFAULT '0',
307
+ `stats_time` int(10) unsigned NOT NULL DEFAULT '0',
308
+ `updated` int(10) unsigned NOT NULL DEFAULT '0',
309
+ PRIMARY KEY (`id`)
310
+ ) $charset_collate;";
311
+
312
+ dbDelta($sql);
313
+
314
+ // WP does not manage composite primary key when it tries to upgrade a table...
315
+ $suppress_errors = $wpdb->suppress_errors(true);
316
+
317
+ dbDelta("CREATE TABLE " . $wpdb->prefix . "newsletter_sent (
318
+ email_id int(10) unsigned NOT NULL DEFAULT '0',
319
+ user_id int(10) unsigned NOT NULL DEFAULT '0',
320
+ status tinyint(1) unsigned NOT NULL DEFAULT '0',
321
+ open tinyint(1) unsigned NOT NULL DEFAULT '0',
322
+ time int(10) unsigned NOT NULL DEFAULT '0',
323
+ error varchar(255) NOT NULL DEFAULT '',
324
+ ip varchar(100) NOT NULL DEFAULT '',
325
+ PRIMARY KEY (email_id,user_id),
326
+ KEY user_id (user_id),
327
+ KEY email_id (email_id)
328
+ ) $charset_collate;");
329
+ $wpdb->suppress_errors($suppress_errors);
330
+
331
+ // Some setting check to avoid the common support request for mis-configurations
332
+ $options = $this->get_options();
333
+
334
+ if (empty($options['scheduler_max']) || !is_numeric($options['scheduler_max'])) {
335
+ $options['scheduler_max'] = 100;
336
+ $this->save_options($options);
337
+ }
338
+
339
+ wp_clear_scheduled_hook('newsletter');
340
+ wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
341
+
342
+ if (!empty($this->options['editor'])) {
343
+ if (empty($this->options['roles'])) {
344
+ $this->options['roles'] = array('editor');
345
+ unset($this->options['editor']);
346
+ }
347
+ $this->save_options($this->options);
348
+ }
349
+
350
+ // Clear the addons and license caches
351
+ delete_transient('newsletter_license_data');
352
+ $this->clear_extensions_cache();
353
+
354
+ touch(NEWSLETTER_LOG_DIR . '/index.html');
355
+
356
+ return true;
357
+ }
358
+
359
+ function is_allowed() {
360
+ if (current_user_can('administrator')) {
361
+ return true;
362
+ }
363
+ if (!empty($this->options['roles'])) {
364
+ foreach ($this->options['roles'] as $role) {
365
+ if (current_user_can($role)) {
366
+ return true;
367
+ }
368
+ }
369
+ }
370
+ return false;
371
+ }
372
+
373
+ function admin_menu() {
374
+ if (!$this->is_allowed()) {
375
+ return;
376
+ }
377
+
378
+ add_menu_page('Newsletter', 'Newsletter', 'exist', 'newsletter_main_index', '', plugins_url('newsletter') . '/admin/images/menu-icon.png', '30.333');
379
+
380
+ $this->add_menu_page('index', __('Dashboard', 'newsletter'));
381
+ $this->add_admin_page('info', __('Company info', 'newsletter'));
382
+
383
+ if (current_user_can('administrator')) {
384
+ $this->add_menu_page('welcome', __('Welcome', 'newsletter'));
385
+ $this->add_menu_page('main', __('Settings', 'newsletter'));
386
+
387
+ // Pages not on menu
388
+ $this->add_admin_page('smtp', 'SMTP');
389
+ $this->add_admin_page('diagnostic', __('Diagnostic', 'newsletter'));
390
+ $this->add_admin_page('test', __('Test', 'newsletter'));
391
+ }
392
+ }
393
+
394
+ function add_extensions_menu() {
395
+ if (!class_exists('NewsletterExtensions')) {
396
+ $this->add_menu_page('extensions', '<span style="color:#27AE60; font-weight: bold;">' . __('Addons', 'newsletter') . '</span>');
397
+ }
398
+ }
399
+
400
+ function hook_in_admin_header() {
401
+ if (!$this->is_admin_page()) {
402
+ add_action('admin_notices', array($this, 'hook_admin_notices'));
403
+ return;
404
+ }
405
+ remove_all_actions('admin_notices');
406
+ remove_all_actions('all_admin_notices');
407
+ add_action('admin_notices', array($this, 'hook_admin_notices'));
408
+ }
409
+
410
+ function hook_admin_notices() {
411
+ // Check of Newsletter dedicated page
412
+ if (!empty($this->options['page'])) {
413
+ if (get_post_status($this->options['page']) !== 'publish') {
414
+ echo '<div class="notice notice-error"><p>The Newsletter dedicated page is not published. <a href="', site_url('/wp-admin/post.php') . '?post=', $this->options['page'], '&action=edit"><strong>Edit the page</strong></a> or <a href="admin.php?page=newsletter_main_main"><strong>review the main settings</strong></a>.</p></div>';
415
+ }
416
+ }
417
+
418
+ if (isset($this->options['debug']) && $this->options['debug'] == 1) {
419
+ echo '<div class="notice notice-warning"><p>The Newsletter plugin is in <strong>debug mode</strong>. When done change it on Newsletter <a href="admin.php?page=newsletter_main_main"><strong>main settings</strong></a>. Do not keep the debug mode active on production sites.</p></div>';
420
+ }
421
+ }
422
+
423
+ function hook_wp_enqueue_scripts() {
424
+ if (empty($this->options['css_disabled']) && apply_filters('newsletter_enqueue_style', true)) {
425
+ wp_enqueue_style('newsletter', plugins_url('newsletter') . '/style.css', [], NEWSLETTER_VERSION);
426
+ if (!empty($this->options['css'])) {
427
+ wp_add_inline_style('newsletter', $this->options['css']);
428
+ }
429
+ } else {
430
+ if (!empty($this->options['css'])) {
431
+ add_action('wp_head', function () {
432
+ echo '<style>', $this->options['css'], '</style>';
433
+ });
434
+ }
435
+ }
436
+ }
437
+
438
+ function hook_admin_enqueue_scripts() {
439
+
440
+ $newsletter_url = plugins_url('newsletter');
441
+ wp_enqueue_script('jquery-ui-tabs');
442
+ wp_enqueue_script('jquery-ui-tooltip');
443
+ wp_enqueue_script('jquery-ui-draggable');
444
+ wp_enqueue_media();
445
+
446
+ wp_enqueue_script('tnp-admin', $newsletter_url . '/admin/admin.js', ['jquery'], NEWSLETTER_VERSION);
447
+
448
+ wp_enqueue_style('tnp-select2', $newsletter_url . '/vendor/select2/css/select2.min.css', [], NEWSLETTER_VERSION);
449
+ wp_enqueue_script('tnp-select2', $newsletter_url . '/vendor/select2/js/select2.min.js', ['jquery'], NEWSLETTER_VERSION);
450
+
451
+ wp_enqueue_style('tnp-modal', $newsletter_url . '/admin/modal.css', [], NEWSLETTER_VERSION);
452
+ wp_enqueue_script('tnp-modal', $newsletter_url . '/admin/modal.js', ['jquery'], NEWSLETTER_VERSION, true);
453
+
454
+ wp_enqueue_style('tnp-toast', $newsletter_url . '/admin/toast.css', [], NEWSLETTER_VERSION);
455
+ wp_enqueue_script('tnp-toast', $newsletter_url . '/admin/toast.js', ['jquery'], NEWSLETTER_VERSION);
456
+
457
+ wp_enqueue_style('tnp-admin-font', 'https://use.typekit.net/jlj2wjy.css');
458
+ wp_enqueue_style('tnp-admin-fontawesome', $newsletter_url . '/vendor/fa/css/all.min.css', [], NEWSLETTER_VERSION);
459
+ wp_enqueue_style('tnp-admin-jquery-ui', $newsletter_url . '/vendor/jquery-ui/jquery-ui.min.css', [], NEWSLETTER_VERSION);
460
+ wp_enqueue_style('tnp-admin', $newsletter_url . '/admin/admin.css', [], NEWSLETTER_VERSION);
461
+ wp_enqueue_style('tnp-admin-dropdown', $newsletter_url . '/admin/dropdown.css', [], NEWSLETTER_VERSION);
462
+ wp_enqueue_style('tnp-admin-tabs', $newsletter_url . '/admin/tabs.css', [], NEWSLETTER_VERSION);
463
+ wp_enqueue_style('tnp-admin-controls', $newsletter_url . '/admin/controls.css', [], NEWSLETTER_VERSION);
464
+ wp_enqueue_style('tnp-admin-fields', $newsletter_url . '/admin/fields.css', [], NEWSLETTER_VERSION);
465
+ wp_enqueue_style('tnp-admin-widgets', $newsletter_url . '/admin/widgets.css', [], NEWSLETTER_VERSION);
466
+ wp_enqueue_style('tnp-admin-extensions', $newsletter_url . '/admin/extensions.css', [], NEWSLETTER_VERSION);
467
+
468
+ $translations_array = array(
469
+ 'save_to_update_counter' => __('Save the newsletter to update the counter!', 'newsletter')
470
+ );
471
+ wp_localize_script('tnp-admin', 'tnp_translations', $translations_array);
472
+
473
+ wp_enqueue_script('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jquery.vmap.min.js', ['jquery'], NEWSLETTER_VERSION);
474
+ wp_enqueue_script('tnp-jquery-vmap-world', $newsletter_url . '/vendor/jqvmap/jquery.vmap.world.js', ['tnp-jquery-vmap'], NEWSLETTER_VERSION);
475
+ wp_enqueue_style('tnp-jquery-vmap', $newsletter_url . '/vendor/jqvmap/jqvmap.min.css', [], NEWSLETTER_VERSION);
476
+
477
+ wp_register_script('tnp-chart', $newsletter_url . '/vendor/chartjs/Chart.min.js', ['jquery'], NEWSLETTER_VERSION);
478
+
479
+ wp_enqueue_script('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.js', ['jquery']);
480
+ wp_enqueue_style('tnp-color-picker', $newsletter_url . '/vendor/spectrum/spectrum.min.css', [], NEWSLETTER_VERSION);
481
+ }
482
+
483
+ function shortcode_newsletter_replace($attrs, $content) {
484
+ $content = do_shortcode($content);
485
+ $content = $this->replace($content, $this->get_user_from_request(), $this->get_email_from_request());
486
+ return $content;
487
+ }
488
+
489
+ function is_admin_page() {
490
+ if (!isset($_GET['page'])) {
491
+ return false;
492
+ }
493
+ $page = $_GET['page'];
494
+ return strpos($page, 'newsletter_') === 0;
495
+ }
496
+
497
+ function hook_admin_init() {
498
+ // Verificare il contesto
499
+ if (isset($_GET['page']) && $_GET['page'] === 'newsletter_main_welcome')
500
+ return;
501
+ if (get_option('newsletter_show_welcome')) {
502
+ delete_option('newsletter_show_welcome');
503
+ wp_redirect(admin_url('admin.php?page=newsletter_main_welcome'));
504
+ }
505
+ }
506
+
507
+ function hook_admin_head() {
508
+ // Small global rule for sidebar menu entries
509
+ echo '<style>';
510
+ echo '.tnp-side-menu { color: #E67E22!important; }';
511
+ echo '</style>';
512
+ }
513
+
514
+ function relink($text, $email_id, $user_id, $email_token = '') {
515
+ return NewsletterStatistics::instance()->relink($text, $email_id, $user_id, $email_token);
516
+ }
517
+
518
+ /**
519
+ * Runs every 5 minutes and look for emails that need to be processed.
520
+ */
521
+ function hook_newsletter() {
522
+
523
+ $this->logger->debug(__METHOD__ . '> Start');
524
+
525
+ if (!$this->check_transient('engine', NEWSLETTER_CRON_INTERVAL)) {
526
+ $this->logger->debug(__METHOD__ . '> Engine already active, exit');
527
+ return;
528
+ }
529
+
530
+ $emails = $this->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " where status='sending' and send_on<" . time() . " order by id asc");
531
+ $this->logger->debug(__METHOD__ . '> Emails found in sending status: ' . count($emails));
532
+
533
+ foreach ($emails as $email) {
534
+ $this->logger->debug(__METHOD__ . '> Start newsletter ' . $email->id);
535
+ $email->options = maybe_unserialize($email->options);
536
+ $r = $this->send($email);
537
+ $this->logger->debug(__METHOD__ . '> End newsletter ' . $email->id);
538
+ if (!$r) {
539
+ $this->logger->debug(__METHOD__ . '> Engine returned false, there is no more capacity');
540
+ break;
541
+ }
542
+ }
543
+ // Remove the semaphore so the delivery engine can be activated again
544
+ $this->delete_transient('engine');
545
+
546
+ $this->logger->debug(__METHOD__ . '> End');
547
+ }
548
+
549
+ function get_send_speed($email = null) {
550
+ $this->logger->debug(__METHOD__ . '> Computing delivery speed');
551
+ $mailer = $this->get_mailer();
552
+ $speed = (int) $mailer->get_speed();
553
+ if (!$speed) {
554
+ $this->logger->debug(__METHOD__ . '> Speed not set by mailer, use the default');
555
+ $speed = (int) $this->options['scheduler_max'];
556
+ } else {
557
+ $this->logger->debug(__METHOD__ . '> Speed set by mailer');
558
+ }
559
+
560
+ //$speed = (int) apply_filters('newsletter_send_speed', $speed, $email);
561
+ // Fail safe setting
562
+ $runs_per_hour = $this->get_runs_per_hour();
563
+ if (!$speed || $speed < $runs_per_hour) {
564
+ return $runs_per_hour;
565
+ }
566
+
567
+ $this->logger->debug(__METHOD__ . '> Speed: ' . $speed);
568
+ return $speed;
569
+ }
570
+
571
+ function get_send_delay() {
572
+ return 0;
573
+ }
574
+
575
+ function skip_this_run($email = null) {
576
+ return (boolean) apply_filters('newsletter_send_skip', false, $email);
577
+ }
578
+
579
+ function get_runs_per_hour() {
580
+ return (int) (3600 / NEWSLETTER_CRON_INTERVAL);
581
+ }
582
+
583
+ function get_emails_per_run() {
584
+ $speed = $this->get_send_speed();
585
+ $max = (int)($speed / $this->get_runs_per_hour());
586
+
587
+ return $max;
588
+ }
589
+
590
+ function get_max_emails($email) {
591
+ // Obsolete, here from Speed Control Addon
592
+ $max = (int) apply_filters('newsletter_send_max_emails', $this->max_emails, $email);
593
+
594
+ return min($max, $this->max_emails);
595
+ }
596
+
597
+ function fix_email($email) {
598
+ if (empty($email->query)) {
599
+ $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
600
+ }
601
+ if (empty($email->id)) {
602
+ $email->id = 0;
603
+ }
604
+ }
605
+
606
+ function send_setup() {
607
+ $this->logger->debug(__METHOD__ . '> Setup delivery engine');
608
+ if (is_null($this->max_emails)) {
609
+ $this->max_emails = $this->get_emails_per_run();
610
+ $this->logger->debug(__METHOD__ . '> Max emails: ' . $this->max_emails);
611
+ ignore_user_abort(true);
612
+
613
+ @set_time_limit(NEWSLETTER_CRON_INTERVAL + 30);
614
+
615
+ $max_time = (int) (@ini_get('max_execution_time') * 0.95);
616
+ if ($max_time == 0 || $max_time > NEWSLETTER_CRON_INTERVAL) {
617
+ $max_time = (int) (NEWSLETTER_CRON_INTERVAL * 0.95);
618
+ }
619
+
620
+ $this->time_limit = $this->time_start + $max_time;
621
+
622
+ $this->logger->debug(__METHOD__ . '> Max time set to ' . $max_time);
623
+ } else {
624
+ $this->logger->debug(__METHOD__ . '> Already setup');
625
+ }
626
+ }
627
+
628
+ function time_exceeded() {
629
+ if ($this->time_limit && time() > $this->time_limit) {
630
+ $this->logger->info(__METHOD__ . '> Max execution time limit reached');
631
+ return true;
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Sends an email to targeted users or to given users. If a list of users is given (usually a list of test users)
637
+ * the query inside the email to retrieve users is not used.
638
+ *
639
+ * @global wpdb $wpdb
640
+ * @global type $newsletter_feed
641
+ * @param TNP_Email $email
642
+ * @param array $users
643
+ * @return boolean|WP_Error True if the process completed, false if limits was reached. On false the caller should no continue to call it with other emails.
644
+ */
645
+ function send($email, $users = null, $test = false) {
646
+ global $wpdb;
647
+
648
+ if (is_array($email)) {
649
+ $email = (object) $email;
650
+ }
651
+
652
+ $this->logger->info(__METHOD__ . '> Send email ' . $email->id);
653
+
654
+ $this->send_setup();
655
+
656
+ if ($this->max_emails <= 0) {
657
+ $this->logger->info(__METHOD__ . '> No more capacity');
658
+ return false;
659
+ }
660
+
661
+ $this->fix_email($email);
662
+
663
+ // This stops the update of last_id and sent fields since
664
+ // it's not a scheduled delivery but a test or something else (like an autoresponder)
665
+ $supplied_users = $users != null;
666
+
667
+ if (!$supplied_users) {
668
+
669
+ if ($this->skip_this_run($email)) {
670
+ return true;
671
+ }
672
+
673
+ // Speed change for specific email by Speed Control Addon
674
+ $max_emails = $this->get_max_emails($email);
675
+ if ($max_emails <= 0) {
676
+ return true;
677
+ }
678
+
679
+ $query = $email->query;
680
+ $query .= " and id>" . $email->last_id . " order by id limit " . $max_emails;
681
+
682
+ $this->logger->debug(__METHOD__ . '> Query: ' . $query);
683
+
684
+ //Retrieve subscribers
685
+ $users = $this->get_results($query);
686
+
687
+ $this->logger->debug(__METHOD__ . '> Loaded subscribers: ' . count($users));
688
+
689
+ // If there was a database error, return error
690
+ if ($users === false) {
691
+ return new WP_Error('1', 'Unable to query subscribers, check the logs');
692
+ }
693
+
694
+ if (empty($users)) {
695
+ $this->logger->info(__METHOD__ . '> No more users, set as sent');
696
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set status='sent', total=sent where id=" . $email->id . " limit 1");
697
+ do_action('newsletter_ended_sending_newsletter', $email);
698
+ return true;
699
+ }
700
+ } else {
701
+ $this->logger->info(__METHOD__ . '> Subscribers supplied');
702
+ }
703
+
704
+ $start_time = microtime(true);
705
+ $count = 0;
706
+ $result = true;
707
+
708
+ $mailer = $this->get_mailer();
709
+
710
+ $batch_size = $mailer->get_batch_size();
711
+
712
+ $this->logger->debug(__METHOD__ . '> Batch size: ' . $batch_size);
713
+
714
+ // For batch size == 1 (normal condition) we optimize
715
+ if ($batch_size == 1) {
716
+
717
+ foreach ($users as $user) {
718
+
719
+ $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
720
+ $user = apply_filters('newsletter_send_user', $user);
721
+ $message = $this->build_message($email, $user);
722
+
723
+ // Save even test emails since people wants to see some stats even for test emails. Stats are reset upon the real "send" of a newsletter
724
+ $this->save_sent_message($message);
725
+
726
+ //Se non è un test incremento il contatore delle email spedite. Perchè incremento prima di spedire??
727
+ if (!$test) {
728
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
729
+ }
730
+
731
+ $r = $mailer->send($message);
732
+
733
+ if (!empty($message->error)) {
734
+ $this->logger->error($message);
735
+ $this->save_sent_message($message);
736
+ }
737
+
738
+ if (is_wp_error($r)) {
739
+ $this->logger->error($r);
740
+
741
+ // For fatal error, the newsletter status i changed to error (and the delivery stopped)
742
+ if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
743
+ $this->set_error_state_of_email($email, $r->get_error_message());
744
+ return $r;
745
+ }
746
+ }
747
+
748
+ if (!$supplied_users && !$test && $this->time_exceeded()) {
749
+ $result = false;
750
+ break;
751
+ }
752
+ }
753
+
754
+ $this->max_emails--;
755
+ $count++;
756
+ } else {
757
+
758
+ $chunks = array_chunk($users, $batch_size);
759
+
760
+ foreach ($chunks as $chunk) {
761
+
762
+ $messages = [];
763
+
764
+ // Peeparing a batch of messages
765
+ foreach ($chunk as $user) {
766
+ $this->logger->debug(__METHOD__ . '> Processing user ID: ' . $user->id);
767
+ $user = apply_filters('newsletter_send_user', $user);
768
+ $message = $this->build_message($email, $user);
769
+ $this->save_sent_message($message);
770
+ $messages[] = $message;
771
+
772
+ if (!$test) {
773
+ $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set sent=sent+1, last_id=" . $user->id . " where id=" . $email->id . " limit 1");
774
+ }
775
+ $this->max_emails--;
776
+ $count++;
777
+ }
778
+
779
+ $r = $mailer->send_batch($messages);
780
+
781
+ // Updating the status of the sent messages
782
+ foreach ($messages as $message) {
783
+ if (!empty($message->error)) {
784
+ $this->save_sent_message($message);
785
+ }
786
+ }
787
+
788
+ // The batch went in error
789
+ if (is_wp_error($r)) {
790
+ $this->logger->error($r);
791
+
792
+ if (!$test && $r->get_error_code() == NewsletterMailer::ERROR_FATAL) {
793
+ $this->set_error_state_of_email($email, $r->get_error_message());
794
+ return $r;
795
+ }
796
+ }
797
+
798
+ if (!$supplied_users && !$test && $this->time_exceeded()) {
799
+ $result = false;
800
+ break;
801
+ }
802
+ }
803
+ }
804
+
805
+ $end_time = microtime(true);
806
+
807
+ // Stats only for newsletter with enough emails in a batch (we exclude the Autoresponder since it send one email per call)
808
+ if (!$test && !$supplied_users && $count > 5) {
809
+ $this->update_send_stats($start_time, $end_time, $count, $result);
810
+ }
811
+
812
+ // Cached general statistics are reset
813
+ if (!$test) {
814
+ NewsletterStatistics::instance()->reset_stats_time($email->id);
815
+ }
816
+
817
+ $this->logger->info(__METHOD__ . '> End run for email ' . $email->id);
818
+
819
+ return $result;
820
+ }
821
+
822
+ function update_send_stats($start_time, $end_time, $count, $result) {
823
+ $send_calls = get_option('newsletter_diagnostic_send_calls', []);
824
+ $send_calls[] = [$start_time, $end_time, $count, $result];
825
+
826
+ if (count($send_calls) > 100) {
827
+ array_shift($send_calls);
828
+ }
829
+
830
+ update_option('newsletter_diagnostic_send_calls', $send_calls, false);
831
+ }
832
+
833
+ /**
834
+ * @param TNP_Email $email
835
+ */
836
+ private function set_error_state_of_email($email, $message = '') {
837
+ // Handle only message type at the moment
838
+ if ($email->type !== 'message') {
839
+ return;
840
+ }
841
+
842
+ do_action('newsletter_error_on_sending', $email, $message);
843
+
844
+ $edited_email = new TNP_Email();
845
+ $edited_email->id = $email->id;
846
+ $edited_email->status = TNP_Email::STATUS_ERROR;
847
+ $edited_email->options = $email->options;
848
+ $edited_email->options['error_message'] = $message;
849
+
850
+ $this->save_email($edited_email);
851
+ }
852
+
853
+ /**
854
+ *
855
+ * @param TNP_Email $email
856
+ * @param TNP_User $user
857
+ * @return \TNP_Mailer_Message
858
+ */
859
+ function build_message($email, $user) {
860
+
861
+ $message = new TNP_Mailer_Message();
862
+
863
+ $message->to = $user->email;
864
+
865
+ $message->headers = [];
866
+ $message->headers['Precedence'] = 'bulk';
867
+ $message->headers['X-Newsletter-Email-Id'] = $email->id;
868
+ $message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
869
+ $message->headers = apply_filters('newsletter_message_headers', $message->headers, $email, $user);
870
+
871
+ $message->body = preg_replace('/data-json=".*?"/is', '', $email->message);
872
+ $message->body = preg_replace('/ +/s', ' ', $message->body);
873
+ $message->body = $this->replace($message->body, $user, $email);
874
+ if ($this->options['do_shortcodes']) {
875
+ $message->body = do_shortcode($message->body);
876
+ }
877
+ $message->body = apply_filters('newsletter_message_html', $message->body, $email, $user);
878
+
879
+ $message->body_text = $this->replace($email->message_text, $user, $email);
880
+ $message->body_text = apply_filters('newsletter_message_text', $message->body_text, $email, $user);
881
+
882
+ if ($email->track == 1) {
883
+ $message->body = $this->relink($message->body, $email->id, $user->id, $email->token);
884
+ }
885
+
886
+ $message->subject = $this->replace($email->subject, $user);
887
+ $message->subject = apply_filters('newsletter_message_subject', $message->subject, $email, $user);
888
+
889
+ if (!empty($email->options['sender_email'])) {
890
+ $message->from = $email->options['sender_email'];
891
+ } else {
892
+ $message->from = $this->options['sender_email'];
893
+ }
894
+
895
+ if (!empty($email->options['sender_name'])) {
896
+ $message->from_name = $email->options['sender_name'];
897
+ } else {
898
+ $message->from_name = $this->options['sender_name'];
899
+ }
900
+
901
+ $message->email_id = $email->id;
902
+ $message->user_id = $user->id;
903
+
904
+ return apply_filters('newsletter_message', $message, $email, $user);
905
+ }
906
+
907
+ /**
908
+ *
909
+ * @param TNP_Mailer_Message $message
910
+ * @param int $status
911
+ * @param string $error
912
+ */
913
+ function save_sent_message($message) {
914
+ global $wpdb;
915
+
916
+ if (!$message->user_id || !$message->email_id) {
917
+ return;
918
+ }
919
+ $status = empty($message->error) ? 0 : 1;
920
+
921
+ $error = mb_substr($message->error, 0, 250);
922
+
923
+ $this->query($wpdb->prepare("insert into " . $wpdb->prefix . 'newsletter_sent (user_id, email_id, time, status, error) values (%d, %d, %d, %d, %s) on duplicate key update time=%d, status=%d, error=%s', $message->user_id, $message->email_id, time(), $status, $error, time(), $status, $error));
924
+ }
925
+
926
+ /**
927
+ * @deprecated since version 7.3.0
928
+ */
929
+ function limits_exceeded() {
930
+ return false;
931
+ }
932
+
933
+ /**
934
+ * @deprecated since version 6.0.0
935
+ */
936
+ function register_mail_method($callable) {
937
+ }
938
+
939
+ function register_mailer($mailer) {
940
+ if ($mailer instanceof NewsletterMailer) {
941
+ $this->mailer = $mailer;
942
+ }
943
+ }
944
+
945
+ /**
946
+ * Returns the current registered mailer which must be used to send emails.
947
+ *
948
+ * @return NewsletterMailer
949
+ */
950
+ function get_mailer() {
951
+ if ($this->mailer) {
952
+ return $this->mailer;
953
+ }
954
+
955
+ do_action('newsletter_register_mailer');
956
+
957
+ if (!$this->mailer) {
958
+ // Compatibility
959
+ $smtp = $this->get_options('smtp');
960
+ if (!empty($smtp['enabled'])) {
961
+ $this->mailer = new NewsletterDefaultSMTPMailer($smtp);
962
+ } else {
963
+ $this->mailer = new NewsletterDefaultMailer();
964
+ }
965
+ }
966
+ return $this->mailer;
967
+ }
968
+
969
+ /**
970
+ *
971
+ * @param TNP_Mailer_Message $message
972
+ * @return type
973
+ */
974
+ function deliver($message) {
975
+ $mailer = $this->get_mailer();
976
+ if (empty($message->from))
977
+ $message->from = $this->options['sender_email'];
978
+ if (empty($message->from_name))
979
+ $mailer->from_name = $this->options['sender_name'];
980
+ return $mailer->send($message);
981
+ }
982
+
983
+ /**
984
+ *
985
+ * @param type $to
986
+ * @param type $subject
987
+ * @param string|array $message If string is considered HTML, is array should contains the keys "html" and "text"
988
+ * @param type $headers
989
+ * @param type $enqueue
990
+ * @param type $from
991
+ * @return boolean
992
+ */
993
+ function mail($to, $subject, $message, $headers = array(), $enqueue = false, $from = false) {
994
+
995
+ if (empty($subject)) {
996
+ $this->logger->error('mail> Subject empty, skipped');
997
+ return true;
998
+ }
999
+
1000
+ $mailer_message = new TNP_Mailer_Message();
1001
+ $mailer_message->to = $to;
1002
+ $mailer_message->subject = $subject;
1003
+ $mailer_message->from = $this->options['sender_email'];
1004
+ $mailer_message->from_name = $this->options['sender_name'];
1005
+
1006
+ if (!empty($headers)) {
1007
+ $mailer_message->headers = $headers;
1008
+ }
1009
+ $mailer_message->headers['X-Auto-Response-Suppress'] = 'OOF, AutoReply';
1010
+
1011
+ // Message carrige returns and line feeds clean up
1012
+ if (!is_array($message)) {
1013
+ $mailer_message->body = $this->clean_eol($message);
1014
+ } else {
1015
+ if (!empty($message['text'])) {
1016
+ $mailer_message->body_text = $this->clean_eol($message['text']);
1017
+ }
1018
+
1019
+ if (!empty($message['html'])) {
1020
+ $mailer_message->body = $this->clean_eol($message['html']);
1021
+ }
1022
+ }
1023
+
1024
+ $this->logger->debug($mailer_message);
1025
+
1026
+ $mailer = $this->get_mailer();
1027
+
1028
+ $r = $mailer->send($mailer_message);
1029
+
1030
+ return !is_wp_error($r);
1031
+ }
1032
+
1033
+ function hook_deactivate() {
1034
+ wp_clear_scheduled_hook('newsletter');
1035
+ }
1036
+
1037
+ function find_file($file1, $file2) {
1038
+ if (is_file($file1))
1039
+ return $file1;
1040
+ return $file2;
1041
+ }
1042
+
1043
+ function hook_site_transient_update_plugins($value) {
1044
+ static $extra_response = array();
1045
+
1046
+ //$this->logger->debug('Update plugins transient called');
1047
+
1048
+ if (!$value || !is_object($value)) {
1049
+ //$this->logger->info('Empty object');
1050
+ return $value;
1051
+ }
1052
+
1053
+ if (!isset($value->response) || !is_array($value->response)) {
1054
+ $value->response = array();
1055
+ }
1056
+
1057
+ // Already computed? Use it! (this filter is called many times in a single request)
1058
+ if ($extra_response) {
1059
+ //$this->logger->debug('Already updated');
1060
+ $value->response = array_merge($value->response, $extra_response);
1061
+ return $value;
1062
+ }
1063
+
1064
+ $extensions = $this->getTnpExtensions();
1065
+
1066
+ // Ops...
1067
+ if (!$extensions) {
1068
+ return $value;
1069
+ }
1070
+
1071
+ foreach ($extensions as $extension) {
1072
+ unset($value->response[$extension->wp_slug]);
1073
+ unset($value->no_update[$extension->wp_slug]);
1074
+ }
1075
+
1076
+ // Someone doesn't want our addons updated, let respect it (this constant should be defined in wp-config.php)
1077
+ if (!NEWSLETTER_EXTENSION_UPDATE) {
1078
+ //$this->logger->info('Updates disabled');
1079
+ return $value;
1080
+ }
1081
+
1082
+ include_once(ABSPATH . 'wp-admin/includes/plugin.php');
1083
+
1084
+ // Ok, that is really bad (should we remove it? is there a minimum WP version?)
1085
+ if (!function_exists('get_plugin_data')) {
1086
+ //$this->logger->error('No get_plugin_data function available!');
1087
+ return $value;
1088
+ }
1089
+
1090
+ $license_key = $this->get_license_key();
1091
+
1092
+ // Here we prepare the update information BUT do not add the link to the package which is privided
1093
+ // by our Addons Manager (due to WP policies)
1094
+ foreach ($extensions as $extension) {
1095
+
1096
+ // Patch for names convention
1097
+ $extension->plugin = $extension->wp_slug;
1098
+
1099
+ //$this->logger->debug('Processing ' . $extension->plugin);
1100
+ //$this->logger->debug($extension);
1101
+
1102
+ $plugin_data = false;
1103
+ if (file_exists(WP_PLUGIN_DIR . '/' . $extension->plugin)) {
1104
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1105
+ } else if (file_exists(WPMU_PLUGIN_DIR . '/' . $extension->plugin)) {
1106
+ $plugin_data = get_plugin_data(WPMU_PLUGIN_DIR . '/' . $extension->plugin, false, false);
1107
+ }
1108
+
1109
+ if (!$plugin_data) {
1110
+ //$this->logger->debug('Seems not installed');
1111
+ continue;
1112
+ }
1113
+
1114
+ $plugin = new stdClass();
1115
+ $plugin->id = $extension->id;
1116
+ $plugin->slug = $extension->slug;
1117
+ $plugin->plugin = $extension->plugin;
1118
+ $plugin->new_version = $extension->version;
1119
+ $plugin->url = $extension->url;
1120
+ if (class_exists('NewsletterExtensions')) {
1121
+ // NO filters here!
1122
+ $plugin->package = NewsletterExtensions::$instance->get_package($extension->id, $license_key);
1123
+ } else {
1124
+ $plugin->package = '';
1125
+ }
1126
+ // [banners] => Array
1127
+ // (
1128
+ // [2x] => https://ps.w.org/wp-rss-aggregator/assets/banner-1544x500.png?rev=2040548
1129
+ // [1x] => https://ps.w.org/wp-rss-aggregator/assets/banner-772x250.png?rev=2040548
1130
+ // )
1131
+ // [icons] => Array
1132
+ // (
1133
+ // [2x] => https://ps.w.org/advanced-custom-fields/assets/icon-256x256.png?rev=1082746
1134
+ // [1x] => https://ps.w.org/advanced-custom-fields/assets/icon-128x128.png?rev=1082746
1135
+ // )
1136
+ if (version_compare($extension->version, $plugin_data['Version']) > 0) {
1137
+ //$this->logger->debug('There is a new version');
1138
+ $extra_response[$extension->plugin] = $plugin;
1139
+ } else {
1140
+ // Maybe useless...
1141
+ //$this->logger->debug('There is NOT a new version');
1142
+ $value->no_update[$extension->plugin] = $plugin;
1143
+ }
1144
+ //$this->logger->debug('Added');
1145
+ }
1146
+
1147
+ $value->response = array_merge($value->response, $extra_response);
1148
+
1149
+ return $value;
1150
+ }
1151
+
1152
+ /**
1153
+ * @deprecated since version 6.1.9
1154
+ */
1155
+ function get_extension_version($extension_id) {
1156
+ return null;
1157
+ }
1158
+
1159
+ /**
1160
+ * @deprecated since version 6.1.9
1161
+ */
1162
+ function set_extension_update_data($value, $extension) {
1163
+ return $value;
1164
+ }
1165
+
1166
+ /**
1167
+ * Retrieve the extensions form the tnp site
1168
+ * @return array
1169
+ */
1170
+ function getTnpExtensions() {
1171
+
1172
+ $extensions_json = get_transient('tnp_extensions_json');
1173
+
1174
+ if (empty($extensions_json)) {
1175
+ $url = "http://www.thenewsletterplugin.com/wp-content/extensions.json?ver=" . NEWSLETTER_VERSION;
1176
+ $extensions_response = wp_remote_get($url);
1177
+
1178
+ if (is_wp_error($extensions_response)) {
1179
+ // Cache anyway for blogs which cannot connect outside
1180
+ $extensions_json = '[]';
1181
+ set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1182
+ $this->logger->error($extensions_response);
1183
+ } else {
1184
+
1185
+ $extensions_json = wp_remote_retrieve_body($extensions_response);
1186
+
1187
+ // Not clear cases
1188
+ if (empty($extensions_json) || !json_decode($extensions_json)) {
1189
+ $this->logger->error('Invalid json from thenewsletterplugin.com: retrying in 72 hours');
1190
+ $this->logger->error('JSON: ' . $extensions_json);
1191
+ $extensions_json = '[]';
1192
+ }
1193
+ set_transient('tnp_extensions_json', $extensions_json, 72 * 60 * 60);
1194
+ }
1195
+ }
1196
+
1197
+ $extensions = json_decode($extensions_json);
1198
+
1199
+ return $extensions;
1200
+ }
1201
+
1202
+ function clear_extensions_cache() {
1203
+ delete_transient('tnp_extensions_json');
1204
+ }
1205
+
1206
+ var $panels = array();
1207
+
1208
+ function add_panel($key, $panel) {
1209
+ if (!isset($this->panels[$key]))
1210
+ $this->panels[$key] = array();
1211
+ if (!isset($panel['id']))
1212
+ $panel['id'] = sanitize_key($panel['label']);
1213
+ $this->panels[$key][] = $panel;
1214
+ }
1215
+
1216
+ function has_license() {
1217
+ return !empty($this->options['contract_key']);
1218
+ }
1219
+
1220
+ function get_sender_name() {
1221
+ return $this->options['sender_name'];
1222
+ }
1223
+
1224
+ function get_sender_email() {
1225
+ return $this->options['sender_email'];
1226
+ }
1227
+
1228
+ /**
1229
+ *
1230
+ * @return int
1231
+ */
1232
+ function get_newsletter_page_id() {
1233
+ return (int) $this->options['page'];
1234
+ }
1235
+
1236
+ /**
1237
+ *
1238
+ * @return WP_Post
1239
+ */
1240
+ function get_newsletter_page() {
1241
+ return get_post($this->get_newsletter_page_id());
1242
+ }
1243
+
1244
+ /**
1245
+ * Returns the Newsletter dedicated page URL or an alternative URL if that page if not
1246
+ * configured or not available.
1247
+ *
1248
+ * @staticvar string $url
1249
+ * @return string
1250
+ */
1251
+ function get_newsletter_page_url($language = '') {
1252
+
1253
+ $page = $this->get_newsletter_page();
1254
+
1255
+ if (!$page || $page->post_status !== 'publish') {
1256
+ return $this->build_action_url('m');
1257
+ }
1258
+
1259
+ $newsletter_page_url = get_permalink($page->ID);
1260
+ if ($language && $newsletter_page_url) {
1261
+ if (class_exists('SitePress')) {
1262
+ $newsletter_page_url = apply_filters('wpml_permalink', $newsletter_page_url, $language, true);
1263
+ }
1264
+ if (function_exists('pll_get_post')) {
1265
+ $translated_page = get_permalink(pll_get_post($page->ID, $language));
1266
+ if ($translated_page) {
1267
+ $newsletter_page_url = $translated_page;
1268
+ }
1269
+ }
1270
+ }
1271
+
1272
+ return $newsletter_page_url;
1273
+ }
1274
+
1275
+ function get_license_key() {
1276
+ if (defined('NEWSLETTER_LICENSE_KEY')) {
1277
+ return NEWSLETTER_LICENSE_KEY;
1278
+ } else {
1279
+ if (!empty($this->options['contract_key'])) {
1280
+ return trim($this->options['contract_key']);
1281
+ }
1282
+ }
1283
+ return false;
1284
+ }
1285
+
1286
+ /**
1287
+ * Get the data connected to the specified license code on man settings.
1288
+ *
1289
+ * - false if no license is present
1290
+ * - WP_Error if something went wrong if getting the license data
1291
+ * - object with expiration and addons list
1292
+ *
1293
+ * @param boolean $refresh
1294
+ * @return \WP_Error|boolean|object
1295
+ */
1296
+ function get_license_data($refresh = false) {
1297
+
1298
+ $this->logger->debug('Getting license data');
1299
+
1300
+ $license_key = $this->get_license_key();
1301
+ if (empty($license_key)) {
1302
+ $this->logger->debug('License was empty');
1303
+ delete_transient('newsletter_license_data');
1304
+ return false;
1305
+ }
1306
+
1307
+ if (!$refresh) {
1308
+ $license_data = get_transient('newsletter_license_data');
1309
+ if ($license_data !== false && is_object($license_data)) {
1310
+ $this->logger->debug('License data found on cache');
1311
+ return $license_data;
1312
+ }
1313
+ }
1314
+
1315
+ $this->logger->debug('Refreshing the license data');
1316
+
1317
+ $license_data_url = 'https://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/get-license-data.php';
1318
+
1319
+ $response = wp_remote_post($license_data_url, array(
1320
+ 'body' => array('k' => $license_key)
1321
+ ));
1322
+
1323
+ // Fall back to http...
1324
+ if (is_wp_error($response)) {
1325
+ $this->logger->error($response);
1326
+ $this->logger->error('Falling back to http');
1327
+ $license_data_url = str_replace('https', 'http', $license_data_url);
1328
+ $response = wp_remote_post($license_data_url, array(
1329
+ 'body' => array('k' => $license_key)
1330
+ ));
1331
+ if (is_wp_error($response)) {
1332
+ $this->logger->error($response);
1333
+ set_transient('newsletter_license_data', $response, DAY_IN_SECONDS);
1334
+ return $response;
1335
+ }
1336
+ }
1337
+
1338
+ $download_message = 'You can download all addons from www.thenewsletterplugin.com if your license is valid.';
1339
+
1340
+ if (wp_remote_retrieve_response_code($response) != '200') {
1341
+ $this->logger->error('license data error: ' . wp_remote_retrieve_response_code($response));
1342
+ $data = new WP_Error(wp_remote_retrieve_response_code($response), 'License validation service error. <br>' . $download_message);
1343
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1344
+ return $data;
1345
+ }
1346
+
1347
+ $json = wp_remote_retrieve_body($response);
1348
+ $data = json_decode($json);
1349
+
1350
+ if (!is_object($data)) {
1351
+ $this->logger->error($json);
1352
+ $data = new WP_Error(1, 'License validation service error. <br>' . $download_message);
1353
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1354
+ return $data;
1355
+ }
1356
+
1357
+ if (isset($data->message)) {
1358
+ $data = new WP_Error(1, $data->message . ' (check the license on Newsletter main settings)');
1359
+ set_transient('newsletter_license_data', $data, DAY_IN_SECONDS);
1360
+ return $data;
1361
+ }
1362
+
1363
+ $expiration = WEEK_IN_SECONDS;
1364
+ // If the license expires in few days, make the transient live only few days, so it will be refreshed
1365
+ if ($data->expire > time() && $data->expire - time() < WEEK_IN_SECONDS) {
1366
+ $expiration = $data->expire - time();
1367
+ }
1368
+ set_transient('newsletter_license_data', $data, $expiration);
1369
+
1370
+ return $data;
1371
+ }
1372
+
1373
+ /**
1374
+ * @deprecated
1375
+ * @param type $license_key
1376
+ * @return \WP_Error
1377
+ */
1378
+ public static function check_license($license_key) {
1379
+ $response = wp_remote_get('http://www.thenewsletterplugin.com/wp-content/plugins/file-commerce-pro/check.php?k=' . urlencode($license_key), array('sslverify' => false));
1380
+ if (is_wp_error($response)) {
1381
+ /* @var $response WP_Error */
1382
+ return new WP_Error(-1, 'It seems that your blog cannot contact the license validator. Ask your provider to unlock the HTTP/HTTPS connections to www.thenewsletterplugin.com<br>'
1383
+ . esc_html($response->get_error_code()) . ' - ' . esc_html($response->get_error_message()));
1384
+ } else if ($response['response']['code'] != 200) {
1385
+ return new WP_Error(-1, '[' . $response['response']['code'] . '] The license seems expired or not valid, please check your <a href="https://www.thenewsletterplugin.com/account">license code and status</a>, thank you.'
1386
+ . '<br>You can anyway download the professional extension from https://www.thenewsletterplugin.com.');
1387
+ } elseif ($expires = json_decode(wp_remote_retrieve_body($response))) {
1388
+ return array('expires' => $expires->expire, 'message' => 'Your license is valid and expires on ' . esc_html(date('Y-m-d', $expires->expire)));
1389
+ } else {
1390
+ return new WP_Error(-1, 'Unable to detect the license expiration. Debug data to report to the support: <code>' . esc_html(wp_remote_retrieve_body($response)) . '</code>');
1391
+ }
1392
+ }
1393
+
1394
+ function add_notice_to_chosen_profile_page_hook($post_states, $post) {
1395
+
1396
+ if ($post->ID == $this->options['page']) {
1397
+ $post_states[] = __('Newsletter plugin page, do not delete', 'newsletter');
1398
+ }
1399
+
1400
+ return $post_states;
1401
+ }
1402
+
1403
+ }
1404
+
1405
+ $newsletter = Newsletter::instance();
1406
+
1407
+ if (is_admin()) {
1408
+ require_once NEWSLETTER_DIR . '/system/system.php';
1409
+ }
1410
+
1411
+ require_once NEWSLETTER_DIR . '/subscription/subscription.php';
1412
+ require_once NEWSLETTER_DIR . '/unsubscription/unsubscription.php';
1413
+ require_once NEWSLETTER_DIR . '/profile/profile.php';
1414
+ require_once NEWSLETTER_DIR . '/emails/emails.php';
1415
+ require_once NEWSLETTER_DIR . '/users/users.php';
1416
+ require_once NEWSLETTER_DIR . '/statistics/statistics.php';
1417
+ require_once NEWSLETTER_DIR . '/widget/standard.php';
1418
+ require_once NEWSLETTER_DIR . '/widget/minimal.php';
readme.txt CHANGED
@@ -1,407 +1,415 @@
1
- === Newsletter ===
2
- Tags: newsletter, email marketing, welcome email, signup forms, contact, lead generation, marketing automation
3
- Tested up to: 5.9
4
- Stable tag: 7.3.8
5
- Contributors: satollo,webagile,michael-travan
6
- License: GPLv2 or later
7
- License URI: https://www.gnu.org/licenses/gpl-2.0.html
8
-
9
- Add a real newsletter system to your blog. For free. With unlimited newsletters and subscribers.
10
-
11
- == Description ==
12
-
13
- Newsletter is a **real newsletter and email marketing system** for your WordPress blog: perfect for list building, you can easily create, send and track e-mails, headache-free. It just works out of box!
14
-
15
- = Discover a completely rewritten composer =
16
-
17
- We redesigned our drag and drop composer to make your campaign creation even easier. Try it!
18
-
19
- = Main Features =
20
-
21
- * **Easy-to-use Drag and drop composer** to build responsive newsletters
22
- * **Unlimited subscribers** with statistics
23
- * **Unlimited newsletters** with tracking
24
- * **Subscription spam check** with domain/ip black lists, Akismet, captcha
25
- * **Delivery speed** fine control (from 12 emails per hour to as much as your blog can manage)
26
- * [WPML ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Polylang ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Translatepress ready](https://www.thenewsletterplugin.com/documentation/multilanguage)
27
- * All messages are **fully translatable** from administration panels (no .po/.mo file to edit)
28
- * [GDPR ready](https://www.thenewsletterplugin.com/documentation/gdpr-compliancy)
29
- * **Advanced targeting** with lists combinations like all in, at least one, not in and so on
30
- * Customizable **subscription widget**, **page** or **custom form**
31
- * Wordpress Users registration **seamless integration**
32
- * **Single** And **Double Opt-In** plus privacy checkbox for EU laws compliance
33
- * **Subscribers lists** to fine-target your campaigns
34
- * PHP API and REST API for coders and integrations
35
- * SMTP-Ready (with free addon)
36
- * Customizable Themes
37
- * **Status panel** to check your blog mailing capability and configuration
38
- * **Compatible with every SMTP plugin**: Post SMTP (aka Postman), WP Mail SMTP, Easy WP SMTP, Easy SMTP Mail, WP Mail Bank, ...
39
- * **Subscribers import** from file
40
- * Newsletter with Html and Text message versions
41
-
42
- = Find Us =
43
-
44
- Newsletter is a continuously evolving plugin. Stay tuned following us on [Facebook](https://www.facebook.com/thenewsletterplugin/) or [our site](https://www.thenewsletterplugin.com/).
45
-
46
- = Free Addons =
47
-
48
- Improve The Newsletter Plugin with these free addons:
49
-
50
- * [WP Registration Addon](https://www.thenewsletterplugin.com/documentation/wpusers-extension) - connects the WordPress standard and custom registration with Newsletter subscription. Optionally imports all registered users as subscribers.
51
- * [Archive Addon](https://www.thenewsletterplugin.com/documentation/archive-extension) - creates a simple blog page which lists all your sent newsletters
52
- * [Locked Content Addon](https://www.thenewsletterplugin.com/documentation/locked-content-extension) - open up your premium content only after subscription
53
- * [Newsletter REST API Addon](https://www.thenewsletterplugin.com/documentation/developers/newsletter-api-2/) - adds a tier of REST api to integrate with the Newsletter core services
54
- * [Sendinblue Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/sendinblue-extension/) - deliver your newsletters with Sendinblue
55
- * [SMTP Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/smtp-extension/) - deliver your newsletters with external SMTP
56
- * [Advanced Import Addon](https://www.thenewsletterplugin.com/documentation/addons/extended-features/advanced-import/) - import contact from file or copy and paste data with full mapping
57
-
58
- (*easily add them from our [Addons panel](https://www.thenewsletterplugin.com/documentation/install-extensions)*)
59
-
60
- = Addons on WordPress.org =
61
-
62
- * [RSS Composer Block](https://wordpress.org/plugins/newsletter-rss-block/) - (3rd party) a composer block which builds its content from a RSS feed
63
- * [Popup Maker Integration](https://wordpress.org/plugins/newsletter-popupmaker/) - (3rd party) integration of Newsletter forms with Popup Maker plugin
64
- * [BuddyPress integration](https://wordpress.org/plugins/newsletter-buddypress/) - subscription opt-in inside BuddyPress signup form
65
- * [WP User Manager addon for Newsletter](https://wordpress.org/plugins/wpum-newsletter/) - adds the subscription option on registration forms
66
-
67
- = Professional Addons =
68
-
69
- Need *more power*? Feel *something's missing*? The Newsletter Plugin features can be easily extended through our **premium, professional Addons**! Let us introduce just two of them : )
70
-
71
- * [Automated](https://www.thenewsletterplugin.com/automated) - generates and sends your newsletters using your blog last posts, even custom ones like events or products. Just sit and watch!
72
- * [Autoresponder](https://www.thenewsletterplugin.com/autoresponder) - creates email series to follow up your subscribers
73
- * [Extended Composer Blocks](https://www.thenewsletterplugin.com/composer) - adds new blocks to the drag & drop composer
74
- * [WooCommerce Integration](https://www.thenewsletterplugin.com/woocommerce) - subscribe customers to a mailing list and generate product newletters.
75
- * [Reports](https://www.thenewsletterplugin.com/reports) - improves the internal statistics collection system and provides better reports of data collected for each sent email. And retargeting. Neat.
76
- * [Leads](https://www.thenewsletterplugin.com/leads) adds a fancy subscription popup box or a fixed bar to your website that will boost your conversion rate
77
- * [Amazon SES and other mail providers integration](https://www.thenewsletterplugin.com/integrations) - seamlessly integrate Amazon SES and other email service providers with The Newsletter Plugin. Hassle-free.
78
- * [Contact Form 7 Integration](https://www.thenewsletterplugin.com/documentation/contact-form-7-extension) - integrate the subscription on Contact Form 7 forms
79
- * [Ninja Forms Integration](https://www.thenewsletterplugin.com/documentation/ninjaforms-extension) - integrate the subscription on Ninja Forms
80
- * [WP Forms Integration](https://www.thenewsletterplugin.com/documentation/wpforms-extension) - integrate the subscription on WP Forms
81
- * Events Manager and The Events Calendar (By Modern Tribe) integrations - easily add events to your newsletters
82
- * [Google Analytics](https://www.thenewsletterplugin.com/google-analytics) - track newsletter links with Google UTM tracking paramaters
83
- * [Subscribe on Comment](https://www.thenewsletterplugin.com/documentation/comments-extension) - adds the subscription option to your blog comment form
84
- * [Geolocation](https://www.thenewsletterplugin.com/documentation/geolocation-extension) - adds geolocation capability to target subscribers by location
85
-
86
- = GDPR =
87
-
88
- The Newsletter Plugin provides all the technical tools needed to achieve GDPR compliancy and we're continuously working to improve them and to give support even for specific use cases.
89
- The plugin does not collect users' own subscribers data, nor it has any access to those data: hence, we are not a data processor, so a data processing agreement is not needed.
90
- Anyway if you configure the plugin to use external services (usually an external mail delivery service) you should check with that service if some sort of agreement is required.
91
-
92
- = Support =
93
-
94
- We provide support for our plugin on [Wordpress.org forums](https://wordpress.org/support/plugin/newsletter) and through our [official forum](https://www.thenewsletterplugin.com/forums).
95
-
96
- Premium Users with an active license have access to one-to-one support via our [ticketing system](https://www.thenewsletterplugin.com/support-ticket).
97
-
98
- = Developers =
99
-
100
- We have a [documentation section](https://www.thenewsletterplugin.com/documentation/developers/) dedicated to who want to develop with Newsletter.
101
-
102
- You can find us on [GitHub](https://github.com/TheNewsletterPlugin) with some examples of addons.
103
-
104
- = Follow Us =
105
-
106
- * **Our Official Website** - [https://www.thenewsletterplugin.com/](https://www.thenewsletterplugin.com/)
107
- * **GitHub** - [https://github.com/TheNewsletterPlugin](https://github.com/TheNewsletterPlugin)
108
- * **LinkedIn** - [https://www.linkedin.com/company/the-newsletter-plugin](https://www.linkedin.com/company/the-newsletter-plugin)
109
- * **Our Facebook Page** - [https://www.facebook.com/thenewsletterplugin](https://www.facebook.com/thenewsletterplugin)
110
- * **Our Twitter Account** - [https://twitter.com/newsletterwp](https://twitter.com/newsletterwp)
111
-
112
- == Frequently Asked Questions ==
113
-
114
- See the [Newsletter Forum](https://www.thenewsletterplugin.com/forums) to ask for help.
115
-
116
- For documentation start from [Newsletter documentation](https://www.thenewsletterplugin.com/documentation).
117
-
118
- Thank you, The Newsletter Team
119
-
120
- == Screenshots ==
121
-
122
- 1. The responsive email Drag & Drop composer
123
- 2. The plugin dashboard
124
- 3. The Reports extension
125
-
126
- == Changelog ==
127
-
128
- = 7.3.8 =
129
-
130
- * Fixed graph scale on System/Scheduler panel
131
- * Fixed untraslated labels on subscriber management panels
132
- * Fixed the "toggle" private/public status on sent newsletter
133
- * Removed the "action call test" from the status panel since it does not work with some providers but does not affect the correct working of Newsletter
134
- * Added developer information on this readme
135
-
136
- = 7.3.7 =
137
-
138
- * Fixed unwanted redirects on subscription errors
139
- * Fixed composer page HTML
140
- * Minor fixes on PHP, CSS
141
- * Fixed notice on image block
142
-
143
- = 7.3.6 =
144
-
145
- * Improved composer reusability in other contexts
146
- * Removed obsolete composer code
147
- * Fixed default tracking for old theme-based neewsletters
148
- * Forced enconding on export (attempt)
149
- * WP 5.9 check
150
-
151
- = 7.3.5 =
152
-
153
- * WP 5.8.3 compatibility check
154
- * Fixed 2021 max year in date picker
155
- * Typos
156
-
157
- = 7.3.4 =
158
-
159
- * Fixed delivery fatal error management
160
- * Fixed link to the schduler dianostica panel
161
-
162
- = 7.3.3 =
163
-
164
- * Added "complained" status to subscriber filters
165
- * Fixed some links bringing to a "not allowed" page
166
- * Fixed a notice on delivery diagnostic page
167
- * Fixed logo width notice on header block
168
-
169
- = 7.3.2 =
170
-
171
- * Fixed the remote ip retrieval and clean up
172
- * Fixed header link to status page
173
- * Fixed database error with too long IPs
174
- * Fixed the subscription of cancelled addresses
175
- * Fixed sender and name customization
176
-
177
- = 7.3.1 =
178
-
179
- * Dropped old mailers support
180
- * Improved sending process and limits checking
181
- * Removed obsolete notifications
182
- * Fixed the wrong report on single email delivery speed
183
- * Added support for custom sending speed by addons
184
- * Improved excerpt generation (but it still depends on plugins and themes...)
185
- * Support for building button option on composer blocks
186
-
187
- = 7.3.0 =
188
-
189
- * Fixed header block layout with (logo only layout)
190
- * Check for conflicts on newsletter saving
191
- * Added the subscriber complained status (actually not managed automatically)
192
-
193
- = 7.2.9 =
194
-
195
- * Fixed generic action button confirmation popup not showing the message
196
- * [SECURITY] Pre parsing of IP addresses on security panel
197
- * [SECURITY] Comments allowed on IP address list on security panel
198
- * [COMPOSER] Fixed preset name/subject
199
- * Improved generated forms for accessibility
200
-
201
- = 7.2.8 =
202
-
203
- * Fixed the print_date() when no time is provided
204
- * Fixed date alignment on posts block
205
- * Folders reorganization
206
- * Social block with a new set of icons
207
- * Boosted performances of the lists management page (for big databases)
208
- * Added a new scheduler diagnostic panel
209
- * Seriously improved the cron statistics and diagnostic panel (check it out!)
210
- * Removed obsolete migration code from ancient versions
211
-
212
- = 7.2.7 =
213
-
214
- * Fixed JS error on composer sometimes preventing the correct initialization
215
-
216
- = 7.2.6 =
217
-
218
- * Fixed links on test emails sent to a free email address
219
- * Added attachment explanation
220
- * Added link to explain the use of the snippet
221
-
222
- = 7.2.5 =
223
-
224
- * Fixed subject not saved under specific circumstance
225
-
226
- = 7.2.4 =
227
-
228
- * Fixed the composer not starting for blog with SSL plugin but still HTTP configured on main WP settings
229
- * Changed labels on subscriber maintenance panel
230
- * Updated requirements for WP version
231
-
232
- = 7.2.3 =
233
-
234
- * [COMPOSER] Added approx. indicators of the subsject visibile part in Apple and Android clients (experimental)
235
- * [COMPOSER] New mobile version view directly while composing (experimental)
236
- * [COMPOSER] New test email to test subscribers or to specific email address
237
- * [COMPOSER] Fixed missing background when creating a new message from a preset
238
- * [COMPOSER] Added media selector to the CTA block
239
- * [COMPOSER] Added reference to the tags on HTML and Text blocks
240
- * [COMPOSER] Move the snippet (preheader) field near the subject
241
- * [COMPOSER] Footer block with three link options: unsubscribe, manage and view online
242
- * [COMPOSER] Improve font coherence between blocks (by default)
243
- * [ANTISPAM] Improved the antispam checks on subscription
244
- * [GENERAL] Removed obsolete folders and code
245
- * [NEWSLETTERS] Refactored subject ideas selector
246
- * [SUBSCRIPTION] Inverted extra profile fields and lists on standard subscription form
247
- * [GENERAL] IP address extracted checking proxy variables
248
- * [GENERAL] Improved sending stats collection and display for the delivery engine (not related to click/open stats)
249
-
250
- = 7.2.2 =
251
-
252
- * [COMPOSER] Posts block excerpt removed when set to 0-length
253
- * [GENERAL]Added special characters on test message
254
- * [GENERAL]Added more specific error for action calls on System/Status panel
255
- * [SUBSCRIPTION] Check for the _wp_amp_action_xhr_converted parameter by AMP plugin
256
-
257
- = 7.2.1 =
258
-
259
- * [GENERAL] Added more detailed admin logging
260
- * [NEWSLETTERS] Fixed scheduled date sometimes reset to 1/1/1970
261
-
262
- = 7.2.0 =
263
-
264
- * [PROFILE] Fixed activation email on profile change
265
- * [NEWSLETTERS] Extended year selection on newsletter scheduling
266
- * [DELIVERY] Breaking change: old enqueue() and flush() methods have been removed
267
- * [GENERAL] Fixed alert message on some buttons
268
- * [SUBSCRIPTION] Fixed error message on multiple subscriptions (when not allowed)
269
- * [GENERAL] Fixed erratic error log line on main log
270
-
271
- = 7.1.9 =
272
-
273
- * [GENERAL] Removed the encodign defatlt to Base 64 when not specified since it seems incompatible with some SMTP plugins
274
- * [GENERAL] Review some controls layout and behavior
275
- * [DEBUG] Improved logging on database errors
276
- * [GENERAL] Added TikTok, Discord and Twitch socials
277
- * [GENERAL] Fixed odd error reported related to the cron call statistics collection
278
- * [DEBUG] Added a delivery diagnostic panel
279
-
280
- = 7.1.8 =
281
-
282
- * [COMPOSER] Fixed alignment of single big image on Outlook Android
283
-
284
- = 7.1.7 =
285
-
286
- * [GENERAL] Fix of permalink onm email with multilanguage plugins
287
-
288
- = 7.1.6 =
289
-
290
- * [COMPOSER] Fixed one column big image Read more links
291
-
292
- = 7.1.5 =
293
-
294
- * [COMPOSER] Improve buttons on posts layout
295
- * [COMPOSER] Improved header layout and logo
296
- * [COMPOSER] Generally improved block spacing
297
-
298
- = 7.1.4 =
299
-
300
- * [COMPOSER] Fixed image block for Outlook
301
- * [GENERAL] Fix undefined values in Gutenberg block
302
-
303
- = 7.1.3 =
304
-
305
- * [COMPOSER] Improvements on blocks layout compatibility
306
- * [COMPOSER] Fixed preset global options saving
307
- * [COMPOSER] Content regeneration on preset selection
308
- * [GENERAL] Added to System menu the Site Health link, a not well known native page of WordPress with system information
309
- * [GENERAL] Added sending statistics reset button on status panel
310
-
311
- = 7.1.2 =
312
-
313
- * [ADDONS] Fixed the addons list
314
-
315
- = 7.1.1 =
316
-
317
- * [GENERAL] Improved profile related functions
318
- * [GENERAL] Fixed check on field names (thanks Peter P.)
319
- * [COMPOSER] Fix on preset selection
320
- * [GENERAL] Fix ajax subscription on error
321
-
322
- = 7.1.0 =
323
-
324
- * [COMPOSER] Added link to tags documentation to inject subscriber's data
325
- * [COMPOSER] Fixed layout of posts block for Outlook 365
326
- * [GENERAL] Improved caching of addons json (even on error)
327
- * [GENERAL] Status menu changed to System/Status and System/Logs
328
- * [API] Fixed the subscriber status management (the API Addon should be updated as well)
329
- * [GENERAL] Fixed management of fatal errors on sending
330
- * [GENERAL] Limited error string per message to 250 chars
331
- * [GENERAL] Fixed check on field names (thanks Peter P.)
332
-
333
-
334
- = 7.0.9 =
335
-
336
- * [CAPTCHA] Fixed button label translation
337
- * [DELIVERY] Mailing fatal error management with newsletter stop and reporting
338
- * [SMTP] Marked obsolete the internal SMTP and made available the free SMTP addon
339
- * [DELIVERY] Better management of delivery fatal errors with a new "error" status for newsletters
340
- * [GENERAL] New log files panel
341
- * [IMPORT] Old import panel replaced by the new (really better) import addon (file, copy and paste, bounced addresses import)
342
- * [NEWSLETTERS] It's now possible to specify the sender name and email per newsletter (thanks to Matthew S.)
343
-
344
- = 7.0.8 =
345
-
346
- * [SUBSCRIBERS] Changed action buttons
347
- * [GENERAL] Reorganization of CSS and removal of unused files
348
- * [DASHBOARD] New window open for links and fix of invalid URLs
349
- * [NEWSLETTERS] New action buttons
350
- * [GENERAL] Minor fixes and optimizations
351
- * [COMPOSER] Fixed rare size error on gif images
352
-
353
- = 7.0.7 =
354
-
355
- * [COMPOSER] Fixed a warning in some inline editable blocks
356
- * [NEWSLETTERS] Fixed style which made list labels badly readable
357
- * [NEWSLETTERS] Fixed style which made bullet lists white (not on delivered newsletters)
358
- * [GENERAL] Added some references to the not working scheduler warning
359
-
360
- = 7.0.6 =
361
-
362
- * [COMPOSER] CTA block not grabbing settings from the "old" blocks
363
- * [COMPOSER] CSS issue on button settings group
364
- * [COMPOSER] Posts block two columns layout (author and padding)
365
- * [IMPORT] Removed the old low-featured import (the free import addon has everything needed!)
366
- * [GENERAL] Compatibility check with WP 5.7
367
-
368
- = 7.0.5 =
369
-
370
- * [COMPOSER] Hero CTA button not working
371
- * Fixed to the hero block button
372
- * Added support for the SMTP addon
373
-
374
- = 7.0.4 =
375
-
376
- * [COMPOSER] Redesigned drag and drop composer
377
- * [COMPOSER] NEW! Save drag and drop composed newsletters as templates to reuse easily
378
- * Redesigned dashboard
379
- * Several bug and fixes
380
-
381
- = 7.0.3 =
382
-
383
- * Option to choose between unsubscription and profile link in the footer block
384
- * Direct image src URL for image block
385
- * New media selector for blocks
386
- * Minor fixes
387
- * Updated codemirror
388
- * Updated default theme
389
- * Fixed tag filter on posts block (when tags have parathesis or like)
390
- * Fixed composer visualization of bullet points
391
-
392
- = 7.0.2 =
393
-
394
- * Fixed media 2x resize
395
-
396
- = 7.0.1 =
397
-
398
- * Fixed enforced lists by language with Polylang
399
-
400
- = 7.0.0 =
401
-
402
- * Added multiple newsletter selection for deletion
403
- * Added text part on welcome and activation email
404
- * Added the attribute "show_form" to "newsletter" shortcode
405
- * Added filter on subscriber saving (from external systems) with wrong list field values
406
- * Added index.html on log folder
407
-
 
 
 
 
 
 
 
 
1
+ === Newsletter ===
2
+ Tags: newsletter, email marketing, welcome email, signup forms, contact, lead generation, marketing automation
3
+ Tested up to: 5.9
4
+ Stable tag: 7.3.9
5
+ Contributors: satollo,webagile,michael-travan
6
+ License: GPLv2 or later
7
+ License URI: https://www.gnu.org/licenses/gpl-2.0.html
8
+
9
+ Add a real newsletter system to your blog. For free. With unlimited newsletters and subscribers.
10
+
11
+ == Description ==
12
+
13
+ Newsletter is a **real newsletter and email marketing system** for your WordPress blog: perfect for list building, you can easily create, send and track e-mails, headache-free. It just works out of box!
14
+
15
+ = Discover a completely rewritten composer =
16
+
17
+ We redesigned our drag and drop composer to make your campaign creation even easier. Try it!
18
+
19
+ = Main Features =
20
+
21
+ * **Easy-to-use Drag and drop composer** to build responsive newsletters
22
+ * **Unlimited subscribers** with statistics
23
+ * **Unlimited newsletters** with tracking
24
+ * **Subscription spam check** with domain/ip black lists, Akismet, captcha
25
+ * **Delivery speed** fine control (from 12 emails per hour to as much as your blog can manage)
26
+ * [WPML ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Polylang ready](https://www.thenewsletterplugin.com/documentation/multilanguage), [Translatepress ready](https://www.thenewsletterplugin.com/documentation/multilanguage)
27
+ * All messages are **fully translatable** from administration panels (no .po/.mo file to edit)
28
+ * [GDPR ready](https://www.thenewsletterplugin.com/documentation/gdpr-compliancy)
29
+ * **Advanced targeting** with lists combinations like all in, at least one, not in and so on
30
+ * Customizable **subscription widget**, **page** or **custom form**
31
+ * Wordpress Users registration **seamless integration**
32
+ * **Single** And **Double Opt-In** plus privacy checkbox for EU laws compliance
33
+ * **Subscribers lists** to fine-target your campaigns
34
+ * PHP API and REST API for coders and integrations
35
+ * SMTP-Ready (with free addon)
36
+ * Customizable Themes
37
+ * **Status panel** to check your blog mailing capability and configuration
38
+ * **Compatible with every SMTP plugin**: Post SMTP (aka Postman), WP Mail SMTP, Easy WP SMTP, Easy SMTP Mail, WP Mail Bank, ...
39
+ * **Subscribers import** from file
40
+ * Newsletter with Html and Text message versions
41
+
42
+ = Find Us =
43
+
44
+ Newsletter is a continuously evolving plugin. Stay tuned following us on [Facebook](https://www.facebook.com/thenewsletterplugin/) or [our site](https://www.thenewsletterplugin.com/).
45
+
46
+ = Free Addons =
47
+
48
+ Improve The Newsletter Plugin with these free addons:
49
+
50
+ * [WP Registration Addon](https://www.thenewsletterplugin.com/documentation/wpusers-extension) - connects the WordPress standard and custom registration with Newsletter subscription. Optionally imports all registered users as subscribers.
51
+ * [Archive Addon](https://www.thenewsletterplugin.com/documentation/archive-extension) - creates a simple blog page which lists all your sent newsletters
52
+ * [Locked Content Addon](https://www.thenewsletterplugin.com/documentation/locked-content-extension) - open up your premium content only after subscription
53
+ * [Newsletter REST API Addon](https://www.thenewsletterplugin.com/documentation/developers/newsletter-api-2/) - adds a tier of REST api to integrate with the Newsletter core services
54
+ * [Sendinblue Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/sendinblue-extension/) - deliver your newsletters with Sendinblue
55
+ * [SMTP Addon](https://www.thenewsletterplugin.com/documentation/addons/delivery-addons/smtp-extension/) - deliver your newsletters with external SMTP
56
+ * [Advanced Import Addon](https://www.thenewsletterplugin.com/documentation/addons/extended-features/advanced-import/) - import contact from file or copy and paste data with full mapping
57
+
58
+ (*easily add them from our [Addons panel](https://www.thenewsletterplugin.com/documentation/install-extensions)*)
59
+
60
+ = Addons on WordPress.org =
61
+
62
+ * [RSS Composer Block](https://wordpress.org/plugins/newsletter-rss-block/) - (3rd party) a composer block which builds its content from a RSS feed
63
+ * [Popup Maker Integration](https://wordpress.org/plugins/newsletter-popupmaker/) - (3rd party) integration of Newsletter forms with Popup Maker plugin
64
+ * [BuddyPress integration](https://wordpress.org/plugins/newsletter-buddypress/) - subscription opt-in inside BuddyPress signup form
65
+ * [WP User Manager addon for Newsletter](https://wordpress.org/plugins/wpum-newsletter/) - adds the subscription option on registration forms
66
+
67
+ = Professional Addons =
68
+
69
+ Need *more power*? Feel *something's missing*? The Newsletter Plugin features can be easily extended through our **premium, professional Addons**! Let us introduce just two of them : )
70
+
71
+ * [Automated](https://www.thenewsletterplugin.com/automated) - generates and sends your newsletters using your blog last posts, even custom ones like events or products. Just sit and watch!
72
+ * [Autoresponder](https://www.thenewsletterplugin.com/autoresponder) - creates email series to follow up your subscribers
73
+ * [Extended Composer Blocks](https://www.thenewsletterplugin.com/composer) - adds new blocks to the drag & drop composer
74
+ * [WooCommerce Integration](https://www.thenewsletterplugin.com/woocommerce) - subscribe customers to a mailing list and generate product newletters.
75
+ * [Reports](https://www.thenewsletterplugin.com/reports) - improves the internal statistics collection system and provides better reports of data collected for each sent email. And retargeting. Neat.
76
+ * [Leads](https://www.thenewsletterplugin.com/leads) adds a fancy subscription popup box or a fixed bar to your website that will boost your conversion rate
77
+ * [Amazon SES and other mail providers integration](https://www.thenewsletterplugin.com/integrations) - seamlessly integrate Amazon SES and other email service providers with The Newsletter Plugin. Hassle-free.
78
+ * [Contact Form 7 Integration](https://www.thenewsletterplugin.com/documentation/contact-form-7-extension) - integrate the subscription on Contact Form 7 forms
79
+ * [Ninja Forms Integration](https://www.thenewsletterplugin.com/documentation/ninjaforms-extension) - integrate the subscription on Ninja Forms
80
+ * [WP Forms Integration](https://www.thenewsletterplugin.com/documentation/wpforms-extension) - integrate the subscription on WP Forms
81
+ * Events Manager and The Events Calendar (By Modern Tribe) integrations - easily add events to your newsletters
82
+ * [Google Analytics](https://www.thenewsletterplugin.com/google-analytics) - track newsletter links with Google UTM tracking paramaters
83
+ * [Subscribe on Comment](https://www.thenewsletterplugin.com/documentation/comments-extension) - adds the subscription option to your blog comment form
84
+ * [Geolocation](https://www.thenewsletterplugin.com/documentation/geolocation-extension) - adds geolocation capability to target subscribers by location
85
+
86
+ = GDPR =
87
+
88
+ The Newsletter Plugin provides all the technical tools needed to achieve GDPR compliancy and we're continuously working to improve them and to give support even for specific use cases.
89
+ The plugin does not collect users' own subscribers data, nor it has any access to those data: hence, we are not a data processor, so a data processing agreement is not needed.
90
+ Anyway if you configure the plugin to use external services (usually an external mail delivery service) you should check with that service if some sort of agreement is required.
91
+
92
+ = Support =
93
+
94
+ We provide support for our plugin on [Wordpress.org forums](https://wordpress.org/support/plugin/newsletter) and through our [official forum](https://www.thenewsletterplugin.com/forums).
95
+
96
+ Premium Users with an active license have access to one-to-one support via our [ticketing system](https://www.thenewsletterplugin.com/support-ticket).
97
+
98
+ = Developers =
99
+
100
+ We have a [documentation section](https://www.thenewsletterplugin.com/documentation/developers/) dedicated to who want to develop with Newsletter.
101
+
102
+ You can find us on [GitHub](https://github.com/TheNewsletterPlugin) with some examples of addons.
103
+
104
+ = Follow Us =
105
+
106
+ * **Our Official Website** - [https://www.thenewsletterplugin.com/](https://www.thenewsletterplugin.com/)
107
+ * **GitHub** - [https://github.com/TheNewsletterPlugin](https://github.com/TheNewsletterPlugin)
108
+ * **LinkedIn** - [https://www.linkedin.com/company/the-newsletter-plugin](https://www.linkedin.com/company/the-newsletter-plugin)
109
+ * **Our Facebook Page** - [https://www.facebook.com/thenewsletterplugin](https://www.facebook.com/thenewsletterplugin)
110
+ * **Our Twitter Account** - [https://twitter.com/newsletterwp](https://twitter.com/newsletterwp)
111
+
112
+ == Frequently Asked Questions ==
113
+
114
+ See the [Newsletter Forum](https://www.thenewsletterplugin.com/forums) to ask for help.
115
+
116
+ For documentation start from [Newsletter documentation](https://www.thenewsletterplugin.com/documentation).
117
+
118
+ Thank you, The Newsletter Team
119
+
120
+ == Screenshots ==
121
+
122
+ 1. The responsive email Drag & Drop composer
123
+ 2. The plugin dashboard
124
+ 3. The Reports extension
125
+
126
+ == Changelog ==
127
+
128
+ = 7.3.9 =
129
+
130
+ * Fixed grid layout not showing correctly on gmail
131
+ * Fixed notice on widget page about the wrong editor enqueued
132
+ * Removed wrong top bar warning
133
+ * Fixed block options on posts block
134
+ * Fixed padding on two column posts block
135
+
136
+ = 7.3.8 =
137
+
138
+ * Fixed graph scale on System/Scheduler panel
139
+ * Fixed untraslated labels on subscriber management panels
140
+ * Fixed the "toggle" private/public status on sent newsletter
141
+ * Removed the "action call test" from the status panel since it does not work with some providers but does not affect the correct working of Newsletter
142
+ * Added developer information on this readme
143
+
144
+ = 7.3.7 =
145
+
146
+ * Fixed unwanted redirects on subscription errors
147
+ * Fixed composer page HTML
148
+ * Minor fixes on PHP, CSS
149
+ * Fixed notice on image block
150
+
151
+ = 7.3.6 =
152
+
153
+ * Improved composer reusability in other contexts
154
+ * Removed obsolete composer code
155
+ * Fixed default tracking for old theme-based neewsletters
156
+ * Forced enconding on export (attempt)
157
+ * WP 5.9 check
158
+
159
+ = 7.3.5 =
160
+
161
+ * WP 5.8.3 compatibility check
162
+ * Fixed 2021 max year in date picker
163
+ * Typos
164
+
165
+ = 7.3.4 =
166
+
167
+ * Fixed delivery fatal error management
168
+ * Fixed link to the schduler dianostica panel
169
+
170
+ = 7.3.3 =
171
+
172
+ * Added "complained" status to subscriber filters
173
+ * Fixed some links bringing to a "not allowed" page
174
+ * Fixed a notice on delivery diagnostic page
175
+ * Fixed logo width notice on header block
176
+
177
+ = 7.3.2 =
178
+
179
+ * Fixed the remote ip retrieval and clean up
180
+ * Fixed header link to status page
181
+ * Fixed database error with too long IPs
182
+ * Fixed the subscription of cancelled addresses
183
+ * Fixed sender and name customization
184
+
185
+ = 7.3.1 =
186
+
187
+ * Dropped old mailers support
188
+ * Improved sending process and limits checking
189
+ * Removed obsolete notifications
190
+ * Fixed the wrong report on single email delivery speed
191
+ * Added support for custom sending speed by addons
192
+ * Improved excerpt generation (but it still depends on plugins and themes...)
193
+ * Support for building button option on composer blocks
194
+
195
+ = 7.3.0 =
196
+
197
+ * Fixed header block layout with (logo only layout)
198
+ * Check for conflicts on newsletter saving
199
+ * Added the subscriber complained status (actually not managed automatically)
200
+
201
+ = 7.2.9 =
202
+
203
+ * Fixed generic action button confirmation popup not showing the message
204
+ * [SECURITY] Pre parsing of IP addresses on security panel
205
+ * [SECURITY] Comments allowed on IP address list on security panel
206
+ * [COMPOSER] Fixed preset name/subject
207
+ * Improved generated forms for accessibility
208
+
209
+ = 7.2.8 =
210
+
211
+ * Fixed the print_date() when no time is provided
212
+ * Fixed date alignment on posts block
213
+ * Folders reorganization
214
+ * Social block with a new set of icons
215
+ * Boosted performances of the lists management page (for big databases)
216
+ * Added a new scheduler diagnostic panel
217
+ * Seriously improved the cron statistics and diagnostic panel (check it out!)
218
+ * Removed obsolete migration code from ancient versions
219
+
220
+ = 7.2.7 =
221
+
222
+ * Fixed JS error on composer sometimes preventing the correct initialization
223
+
224
+ = 7.2.6 =
225
+
226
+ * Fixed links on test emails sent to a free email address
227
+ * Added attachment explanation
228
+ * Added link to explain the use of the snippet
229
+
230
+ = 7.2.5 =
231
+
232
+ * Fixed subject not saved under specific circumstance
233
+
234
+ = 7.2.4 =
235
+
236
+ * Fixed the composer not starting for blog with SSL plugin but still HTTP configured on main WP settings
237
+ * Changed labels on subscriber maintenance panel
238
+ * Updated requirements for WP version
239
+
240
+ = 7.2.3 =
241
+
242
+ * [COMPOSER] Added approx. indicators of the subsject visibile part in Apple and Android clients (experimental)
243
+ * [COMPOSER] New mobile version view directly while composing (experimental)
244
+ * [COMPOSER] New test email to test subscribers or to specific email address
245
+ * [COMPOSER] Fixed missing background when creating a new message from a preset
246
+ * [COMPOSER] Added media selector to the CTA block
247
+ * [COMPOSER] Added reference to the tags on HTML and Text blocks
248
+ * [COMPOSER] Move the snippet (preheader) field near the subject
249
+ * [COMPOSER] Footer block with three link options: unsubscribe, manage and view online
250
+ * [COMPOSER] Improve font coherence between blocks (by default)
251
+ * [ANTISPAM] Improved the antispam checks on subscription
252
+ * [GENERAL] Removed obsolete folders and code
253
+ * [NEWSLETTERS] Refactored subject ideas selector
254
+ * [SUBSCRIPTION] Inverted extra profile fields and lists on standard subscription form
255
+ * [GENERAL] IP address extracted checking proxy variables
256
+ * [GENERAL] Improved sending stats collection and display for the delivery engine (not related to click/open stats)
257
+
258
+ = 7.2.2 =
259
+
260
+ * [COMPOSER] Posts block excerpt removed when set to 0-length
261
+ * [GENERAL]Added special characters on test message
262
+ * [GENERAL]Added more specific error for action calls on System/Status panel
263
+ * [SUBSCRIPTION] Check for the _wp_amp_action_xhr_converted parameter by AMP plugin
264
+
265
+ = 7.2.1 =
266
+
267
+ * [GENERAL] Added more detailed admin logging
268
+ * [NEWSLETTERS] Fixed scheduled date sometimes reset to 1/1/1970
269
+
270
+ = 7.2.0 =
271
+
272
+ * [PROFILE] Fixed activation email on profile change
273
+ * [NEWSLETTERS] Extended year selection on newsletter scheduling
274
+ * [DELIVERY] Breaking change: old enqueue() and flush() methods have been removed
275
+ * [GENERAL] Fixed alert message on some buttons
276
+ * [SUBSCRIPTION] Fixed error message on multiple subscriptions (when not allowed)
277
+ * [GENERAL] Fixed erratic error log line on main log
278
+
279
+ = 7.1.9 =
280
+
281
+ * [GENERAL] Removed the encodign defatlt to Base 64 when not specified since it seems incompatible with some SMTP plugins
282
+ * [GENERAL] Review some controls layout and behavior
283
+ * [DEBUG] Improved logging on database errors
284
+ * [GENERAL] Added TikTok, Discord and Twitch socials
285
+ * [GENERAL] Fixed odd error reported related to the cron call statistics collection
286
+ * [DEBUG] Added a delivery diagnostic panel
287
+
288
+ = 7.1.8 =
289
+
290
+ * [COMPOSER] Fixed alignment of single big image on Outlook Android
291
+
292
+ = 7.1.7 =
293
+
294
+ * [GENERAL] Fix of permalink onm email with multilanguage plugins
295
+
296
+ = 7.1.6 =
297
+
298
+ * [COMPOSER] Fixed one column big image Read more links
299
+
300
+ = 7.1.5 =
301
+
302
+ * [COMPOSER] Improve buttons on posts layout
303
+ * [COMPOSER] Improved header layout and logo
304
+ * [COMPOSER] Generally improved block spacing
305
+
306
+ = 7.1.4 =
307
+
308
+ * [COMPOSER] Fixed image block for Outlook
309
+ * [GENERAL] Fix undefined values in Gutenberg block
310
+
311
+ = 7.1.3 =
312
+
313
+ * [COMPOSER] Improvements on blocks layout compatibility
314
+ * [COMPOSER] Fixed preset global options saving
315
+ * [COMPOSER] Content regeneration on preset selection
316
+ * [GENERAL] Added to System menu the Site Health link, a not well known native page of WordPress with system information
317
+ * [GENERAL] Added sending statistics reset button on status panel
318
+
319
+ = 7.1.2 =
320
+
321
+ * [ADDONS] Fixed the addons list
322
+
323
+ = 7.1.1 =
324
+
325
+ * [GENERAL] Improved profile related functions
326
+ * [GENERAL] Fixed check on field names (thanks Peter P.)
327
+ * [COMPOSER] Fix on preset selection
328
+ * [GENERAL] Fix ajax subscription on error
329
+
330
+ = 7.1.0 =
331
+
332
+ * [COMPOSER] Added link to tags documentation to inject subscriber's data
333
+ * [COMPOSER] Fixed layout of posts block for Outlook 365
334
+ * [GENERAL] Improved caching of addons json (even on error)
335
+ * [GENERAL] Status menu changed to System/Status and System/Logs
336
+ * [API] Fixed the subscriber status management (the API Addon should be updated as well)
337
+ * [GENERAL] Fixed management of fatal errors on sending
338
+ * [GENERAL] Limited error string per message to 250 chars
339
+ * [GENERAL] Fixed check on field names (thanks Peter P.)
340
+
341
+
342
+ = 7.0.9 =
343
+
344
+ * [CAPTCHA] Fixed button label translation
345
+ * [DELIVERY] Mailing fatal error management with newsletter stop and reporting
346
+ * [SMTP] Marked obsolete the internal SMTP and made available the free SMTP addon
347
+ * [DELIVERY] Better management of delivery fatal errors with a new "error" status for newsletters
348
+ * [GENERAL] New log files panel
349
+ * [IMPORT] Old import panel replaced by the new (really better) import addon (file, copy and paste, bounced addresses import)
350
+ * [NEWSLETTERS] It's now possible to specify the sender name and email per newsletter (thanks to Matthew S.)
351
+
352
+ = 7.0.8 =
353
+
354
+ * [SUBSCRIBERS] Changed action buttons
355
+ * [GENERAL] Reorganization of CSS and removal of unused files
356
+ * [DASHBOARD] New window open for links and fix of invalid URLs
357
+ * [NEWSLETTERS] New action buttons
358
+ * [GENERAL] Minor fixes and optimizations
359
+ * [COMPOSER] Fixed rare size error on gif images
360
+
361
+ = 7.0.7 =
362
+
363
+ * [COMPOSER] Fixed a warning in some inline editable blocks
364
+ * [NEWSLETTERS] Fixed style which made list labels badly readable
365
+ * [NEWSLETTERS] Fixed style which made bullet lists white (not on delivered newsletters)
366
+ * [GENERAL] Added some references to the not working scheduler warning
367
+
368
+ = 7.0.6 =
369
+
370
+ * [COMPOSER] CTA block not grabbing settings from the "old" blocks
371
+ * [COMPOSER] CSS issue on button settings group
372
+ * [COMPOSER] Posts block two columns layout (author and padding)
373
+ * [IMPORT] Removed the old low-featured import (the free import addon has everything needed!)
374
+ * [GENERAL] Compatibility check with WP 5.7
375
+
376
+ = 7.0.5 =
377
+
378
+ * [COMPOSER] Hero CTA button not working
379
+ * Fixed to the hero block button
380
+ * Added support for the SMTP addon
381
+
382
+ = 7.0.4 =
383
+
384
+ * [COMPOSER] Redesigned drag and drop composer
385
+ * [COMPOSER] NEW! Save drag and drop composed newsletters as templates to reuse easily
386
+ * Redesigned dashboard
387
+ * Several bug and fixes
388
+
389
+ = 7.0.3 =
390
+
391
+ * Option to choose between unsubscription and profile link in the footer block
392
+ * Direct image src URL for image block
393
+ * New media selector for blocks
394
+ * Minor fixes
395
+ * Updated codemirror
396
+ * Updated default theme
397
+ * Fixed tag filter on posts block (when tags have parathesis or like)
398
+ * Fixed composer visualization of bullet points
399
+
400
+ = 7.0.2 =
401
+
402
+ * Fixed media 2x resize
403
+
404
+ = 7.0.1 =
405
+
406
+ * Fixed enforced lists by language with Polylang
407
+
408
+ = 7.0.0 =
409
+
410
+ * Added multiple newsletter selection for deletion
411
+ * Added text part on welcome and activation email
412
+ * Added the attribute "show_form" to "newsletter" shortcode
413
+ * Added filter on subscriber saving (from external systems) with wrong list field values
414
+ * Added index.html on log folder
415
+
subscription/subscription.php CHANGED
@@ -1,1923 +1,1923 @@
1
- <?php
2
-
3
- defined('ABSPATH') || exit;
4
-
5
- class NewsletterSubscription extends NewsletterModule {
6
-
7
- const MESSAGE_CONFIRMED = 'confirmed';
8
- const OPTIN_DOUBLE = 0;
9
- const OPTIN_SINGLE = 1;
10
-
11
- static $instance;
12
-
13
- /**
14
- * @var array
15
- */
16
- var $options_profile;
17
-
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;
25
-
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;
33
-
34
- /**
35
- * @var array
36
- */
37
- var $options_lists;
38
-
39
- /**
40
- * @return NewsletterSubscription
41
- */
42
- static function instance() {
43
- if (self::$instance == null) {
44
- self::$instance = new NewsletterSubscription();
45
- }
46
- return self::$instance;
47
- }
48
-
49
- function __construct() {
50
-
51
- parent::__construct('subscription', '2.2.7', null, array('lists', 'template', 'profile', 'antibot'));
52
- $this->options_profile = $this->get_options('profile');
53
- $this->options_lists = $this->get_options('lists');
54
-
55
- // Must be called after the Newsletter::hook_init, since some constants are defined
56
- // there.
57
- add_action('init', array($this, 'hook_init'), 90);
58
- }
59
-
60
- function hook_init() {
61
- add_action('newsletter_action', array($this, 'hook_newsletter_action'), 10, 3);
62
- if (is_admin()) {
63
- add_action('admin_init', array($this, 'hook_admin_init'));
64
- } else {
65
- // Shortcode for the Newsletter page
66
- add_shortcode('newsletter', array($this, 'shortcode_newsletter'));
67
- add_shortcode('newsletter_form', array($this, 'shortcode_newsletter_form'));
68
- add_shortcode('newsletter_field', array($this, 'shortcode_newsletter_field'));
69
- }
70
- }
71
-
72
- function hook_admin_init() {
73
- // So the user can add JS and other code
74
- if (isset($_GET['page']) && $_GET['page'] === 'newsletter_subscription_forms') {
75
- header('X-XSS-Protection: 0');
76
- }
77
-
78
- if (function_exists('register_block_type')) {
79
- // Add custom blocks to Gutenberg
80
- wp_register_script('tnp-blocks', plugins_url('newsletter') . '/includes/tnp-blocks.js', array('wp-blocks', 'wp-element', 'wp-editor'), NEWSLETTER_VERSION);
81
- register_block_type('tnp/minimal', array('editor_script' => 'tnp-blocks'));
82
- }
83
- }
84
-
85
- /**
86
- *
87
- * @global wpdb $wpdb
88
- * @return mixed
89
- */
90
- function hook_newsletter_action($action, $user, $email) {
91
- global $wpdb;
92
-
93
- switch ($action) {
94
- case 'profile-change':
95
- if ($this->antibot_form_check()) {
96
-
97
- if (!$user || $user->status != TNP_user::STATUS_CONFIRMED) {
98
- $this->dienow('Subscriber not found or not confirmed.', 'Even the wrong subscriber token can lead to this error.', 404);
99
- }
100
-
101
- if (!$email) {
102
- $this->dienow('Newsletter not found', 'The newsletter containing the link has been deleted.', 404);
103
- }
104
-
105
- if (isset($_REQUEST['list'])) {
106
- $list_id = (int) $_REQUEST['list'];
107
-
108
- // Check if the list is public
109
- $list = $this->get_list($list_id);
110
- if (!$list || $list->status == TNP_List::STATUS_PRIVATE) {
111
- $this->dienow('List change not allowed.', 'Please check if the list is marked as "private".', 400);
112
- }
113
-
114
- if (empty($_REQUEST['redirect'])) {
115
- $url = home_url();
116
- } else {
117
- $url = esc_url_raw($_REQUEST['redirect']);
118
- }
119
- $this->set_user_list($user, $list_id, $_REQUEST['value']);
120
-
121
- $user = $this->get_user($user->id);
122
- $this->add_user_log($user, 'cta');
123
- NewsletterStatistics::instance()->add_click($url, $user->id, $email->id);
124
- wp_redirect($url);
125
- die();
126
- }
127
- } else {
128
- $this->request_to_antibot_form('Continue');
129
- }
130
-
131
- die();
132
-
133
- case 'm':
134
- case 'message':
135
- include dirname(__FILE__) . '/page.php';
136
- die();
137
-
138
- // normal subscription
139
- case 's':
140
- case 'subscribe':
141
-
142
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
143
- $this->dienow('Invalid request', 'The subscription request was not made with a HTTP POST', 400);
144
- }
145
-
146
- $options_antibot = $this->get_options('antibot');
147
-
148
- $captcha = !empty($options_antibot['captcha']);
149
-
150
- if (!empty($_GET['_wp_amp_action_xhr_converted']) || !empty($options_antibot['disabled']) || $this->antibot_form_check($captcha)) {
151
-
152
- $subscription = $this->build_subscription();
153
-
154
- $user = $this->subscribe2($subscription);
155
-
156
- if (is_wp_error($user)) {
157
- if ($user->get_error_code() === 'exists') {
158
- $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
159
- $options = $this->get_options('', $language);
160
- $this->dienow($options['error_text'], $user->get_error_message(), 200);
161
- }
162
- $this->dienow('Registration failed.', $user->get_error_message(), 400);
163
- }
164
-
165
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
166
- $this->show_message('confirmed', $user);
167
- }
168
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
169
- $this->show_message('confirmation', $user);
170
- }
171
- } else {
172
- $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
173
- $options = $this->get_form_options($language);
174
- $this->request_to_antibot_form($options['subscribe'], $captcha);
175
- }
176
- die();
177
-
178
- // AJAX subscription
179
- case 'ajaxsub':
180
-
181
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
182
- $this->dienow('Invalid request');
183
- }
184
-
185
- $subscription = $this->build_subscription();
186
-
187
- $user = $this->subscribe2($subscription);
188
-
189
- if (is_wp_error($user)) {
190
- if ($user->get_error_code() === 'exists') {
191
- echo $this->options['error_text'];
192
- die();
193
- } else {
194
- $this->dienow('Registration failed.', $user->get_error_message(), 400);
195
- }
196
- } else {
197
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
198
- $key = 'confirmed';
199
- }
200
-
201
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
202
- $key = 'confirmation';
203
- }
204
- }
205
-
206
- $message = $this->replace($this->options[$key . '_text'], $user);
207
- if (isset($this->options[$key . '_tracking'])) {
208
- $message .= $this->options[$key . '_tracking'];
209
- }
210
- echo $message;
211
- die();
212
-
213
- case 'c':
214
- case 'confirm':
215
- if (!$user) {
216
- $this->dienow(__('Subscriber not found.', 'newsletter'), 'Or it is not present or the secret key does not match.', 404);
217
- }
218
-
219
- if ($this->antibot_form_check()) {
220
- $user = $this->confirm($user);
221
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
222
- $this->show_message('confirmed', $user);
223
- } else {
224
- $this->request_to_antibot_form('Confirm');
225
- }
226
- die();
227
- break;
228
-
229
- default:
230
- return;
231
- }
232
- }
233
-
234
- function upgrade() {
235
- global $wpdb, $charset_collate;
236
-
237
- parent::upgrade();
238
-
239
- $newsletter = Newsletter::instance();
240
- $lists_options = $this->get_options('lists');
241
- $profile_options = $this->get_options('profile');
242
-
243
- if (empty($lists_options)) {
244
- foreach ($profile_options as $key => $value) {
245
- if (strpos($key, 'list_') === 0) {
246
- $lists_options[$key] = $value;
247
- }
248
- }
249
- }
250
-
251
- for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
252
- // Options migration to the new set
253
- if (!empty($profile_options['list_' . $i]) && empty($lists_options['list_' . $i])) {
254
- $lists_options['list_' . $i] = $profile_options['list_' . $i];
255
- $lists_options['list_' . $i . '_checked'] = $profile_options['list_' . $i . '_checked'];
256
- $lists_options['list_' . $i . '_forced'] = $profile_options['list_' . $i . '_forced'];
257
- }
258
-
259
- if (!isset($profile_options['list_' . $i . '_forced'])) {
260
- $profile_options['list_' . $i . '_forced'] = empty($this->options['preferences_' . $i]) ? 0 : 1;
261
- $lists_options['list_' . $i . '_forced'] = empty($this->options['preferences_' . $i]) ? 0 : 1;
262
- }
263
- }
264
-
265
- $this->save_options($profile_options, 'profile');
266
- $this->save_options($lists_options, 'lists');
267
-
268
- $default_options = $this->get_default_options();
269
-
270
- if (empty($this->options['error_text'])) {
271
- $this->options['error_text'] = $default_options['error_text'];
272
- $this->save_options($this->options);
273
- }
274
-
275
- $this->init_options('template', false);
276
-
277
- global $wpdb, $charset_collate;
278
-
279
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
280
-
281
- $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_user_logs` (
282
- `id` int(11) NOT NULL AUTO_INCREMENT,
283
- `user_id` int(11) NOT NULL DEFAULT 0,
284
- `ip` varchar(50) NOT NULL DEFAULT '',
285
- `source` varchar(50) NOT NULL DEFAULT '',
286
- `data` longtext,
287
- `created` int(11) NOT NULL DEFAULT 0,
288
- PRIMARY KEY (`id`)
289
- ) $charset_collate;";
290
-
291
- dbDelta($sql);
292
-
293
- return true;
294
- }
295
-
296
- function first_install() {
297
-
298
- }
299
-
300
- function admin_menu() {
301
- $this->add_menu_page('options', __('List building', 'newsletter'));
302
- $this->add_admin_page('lists', __('Lists', 'newsletter'));
303
- $this->add_admin_page('profile', __('Subscription Form', 'newsletter'));
304
- $this->add_menu_page('antibot', __('Security', 'newsletter'));
305
- $this->add_admin_page('forms', __('Forms', 'newsletter'));
306
- $this->add_admin_page('template', __('Template', 'newsletter'));
307
- }
308
-
309
- /**
310
- * This method has been redefined for compatibility with the old options naming. It would
311
- * be better to change them instead. The subscription options should be named
312
- * "newsletter_subscription" while the form field options, actually named
313
- * "newsletter_profile", should be renamed "newsletter_subscription_profile" (since
314
- * they are retrived with get_options('profile')) or "newsletter_subscription_fields" or
315
- * "newsletter_subscription_form".
316
- *
317
- * @param array $options
318
- * @param string $sub
319
- */
320
- function save_options($options, $sub = '', $autoload = null, $language = '') {
321
- if (empty($sub) && empty($language)) {
322
- // For compatibility the options are wrongly named
323
- return update_option('newsletter', $options, $autoload);
324
- }
325
-
326
- if (empty($sub) && !empty($language)) {
327
- return update_option('newsletter_' . $language, $options, $autoload);
328
- }
329
-
330
- if ($sub == 'profile') {
331
- if (empty($language)) {
332
- $this->options_profile = $options;
333
- return update_option('newsletter_profile', $options, $autoload);
334
- } else {
335
- return update_option('newsletter_profile_' . $language, $options, $autoload);
336
- }
337
- // For compatibility the options are wrongly named
338
- }
339
-
340
- if ($sub == 'forms') {
341
- // For compatibility the options are wrongly named
342
- return update_option('newsletter_forms', $options, $autoload);
343
- }
344
-
345
- if ($sub == 'lists') {
346
- $this->options_lists = $options;
347
- }
348
- return parent::save_options($options, $sub, $autoload, $language);
349
- }
350
-
351
- function get_options($sub = '', $language = '') {
352
- if ($sub == '') {
353
- // For compatibility the options are wrongly named
354
- if ($language) {
355
- $options = get_option('newsletter_' . $language, []);
356
- $options = array_merge(get_option('newsletter', []), $options);
357
- } else {
358
- $options = get_option('newsletter', []);
359
- }
360
- if (!is_array($options)) {
361
- $options = array();
362
- }
363
-
364
- return $options;
365
- }
366
- if ($sub == 'profile') {
367
- if ($language) {
368
- // All that because for unknown reasome, sometime the options are returned as string, maybe a WPML
369
- // interference...
370
- $i18n_options = get_option('newsletter_profile_' . $language, []);
371
- if (!is_array($i18n_options)) {
372
- $i18n_options = [];
373
- }
374
- $options = get_option('newsletter_profile', []);
375
- if (!is_array($options)) {
376
- $options = [];
377
- }
378
- $options = array_merge($options, array_filter($i18n_options));
379
- } else {
380
- $options = get_option('newsletter_profile', []);
381
- }
382
- // For compatibility the options are wrongly named
383
- return $options;
384
- }
385
- if ($sub == 'forms') {
386
- // For compatibility the options are wrongly named
387
- return get_option('newsletter_forms', []);
388
- }
389
- return parent::get_options($sub, $language);
390
- }
391
-
392
- /**
393
- * Prepares the options used to build a subscription form for the current language
394
- * in internal variable $form_options (for optimization). Can be called many times.
395
- */
396
- function setup_form_options() {
397
- if (empty($this->form_options)) {
398
- $this->form_options = $this->get_options('profile', $this->get_current_language());
399
- }
400
- }
401
-
402
- function get_form_options($language = '') {
403
- return $this->get_options('profile', $language);
404
- }
405
-
406
- function set_updated($user, $time = 0, $ip = '') {
407
- global $wpdb;
408
- if (!$time) {
409
- $time = time();
410
- }
411
-
412
- if (!$ip) {
413
- $ip = $this->get_remote_ip();
414
- }
415
- $ip = $this->process_ip($ip);
416
-
417
- if (is_object($user)) {
418
- $id = $user->id;
419
- } else if (is_array($user)) {
420
- $id = $user['id'];
421
- }
422
-
423
- $id = (int) $id;
424
-
425
- $wpdb->update(NEWSLETTER_USERS_TABLE, array('updated' => $time, 'ip' => $ip, 'geo' => 0), array('id' => $id));
426
- }
427
-
428
- /**
429
- * Sanitize the subscription data collected before process them. Cleanup the lists, the optin mode, email, first name,
430
- * last name, adds mandatory lists, get (if not provided) and process the IP.
431
- *
432
- * @param TNP_Subscription_Data $data
433
- */
434
- private function sanitize($data) {
435
- $data->email = $this->normalize_email($data->email);
436
- if (!empty($data->name)) {
437
- $data->name = $this->normalize_name($data->name);
438
- }
439
- if (!empty($data->surname)) {
440
- $data->surname = $this->normalize_name($data->surname);
441
- }
442
-
443
- if (empty($data->ip)) {
444
- $data->ip = $this->get_remote_ip();
445
- }
446
- $data->ip = $this->process_ip($data->ip);
447
-
448
- if (isset($data->http_referer)) {
449
- $data->http_referer = mb_substr(strip_tags($data->http_referer), 0, 200);
450
- }
451
-
452
- if (isset($data->sex)) {
453
- $data->sex = $this->normalize_sex($data->sex);
454
- }
455
-
456
- if (!isset($data->language)) {
457
- $data->language = $this->get_current_language();
458
- } else {
459
- $data->language = strtolower(strip_tags($data->language));
460
- }
461
-
462
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
463
- $key = 'profile_' . $i;
464
- if (isset($data->$key)) {
465
- $data->$key = trim($data->$key);
466
- }
467
- }
468
- }
469
-
470
- /**
471
- * Builds a default subscription object to be used to collect data and subscription options.
472
- *
473
- * @return TNP_Subscription
474
- */
475
- function get_default_subscription($language = null) {
476
- $subscription = new TNP_Subscription();
477
-
478
- $language = is_null($language) ? $this->get_current_language() : $language;
479
-
480
- $subscription->data->language = $language;
481
- $subscription->optin = $this->is_double_optin() ? 'double' : 'single';
482
- $subscription->if_exists = empty($this->options['multiple']) ? TNP_Subscription::EXISTING_ERROR : TNP_Subscription::EXISTING_MERGE;
483
-
484
- $lists = $this->get_lists();
485
- foreach ($lists as $list) {
486
- if ($list->forced) {
487
- $subscription->data->lists['' . $list->id] = 1;
488
- continue;
489
- }
490
- // Enforced by language
491
- if ($language && in_array($language, $list->languages)) {
492
- $subscription->data->lists['' . $list->id] = 1;
493
- }
494
- }
495
-
496
- return $subscription;
497
- }
498
-
499
- /**
500
- *
501
- * @param TNP_Subscription $subscription
502
- *
503
- * @return TNP_User|WP_Error
504
- */
505
- function subscribe2(TNP_Subscription $subscription) {
506
-
507
- $this->logger->debug($subscription);
508
-
509
- $this->sanitize($subscription->data);
510
-
511
- if (empty($subscription->data->email)) {
512
- return new WP_Error('email', 'Wrong email address');
513
- }
514
-
515
- if (!empty($subscription->data->country) && strlen($subscription->data->country) != 2) {
516
- return new WP_Error('country', 'Country code length error. ISO 3166-1 alpha-2 format (2 letters)');
517
- }
518
-
519
- // Here we should have a clean subscription data
520
- // Filter?
521
-
522
- if ($subscription->spamcheck) {
523
- // TODO: Use autoload
524
- require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
525
- $antispam = NewsletterAntispam::instance();
526
- if ($antispam->is_spam($subscription)) {
527
- return new WP_Error('spam', 'This looks like a spam subscription');
528
- }
529
- }
530
-
531
- // Exists?
532
- $user = $this->get_user_by_email($subscription->data->email);
533
-
534
- // Do we accept repeated subscriptions?
535
- if ($user != null && $subscription->if_exists === TNP_Subscription::EXISTING_ERROR) {
536
- //$this->show_message('error', $user);
537
- return new WP_Error('exists', 'Email address already registered and Newsletter sets to block repeated registrations. You can change this behavior or the user message above on subscription configuration panel.');
538
- }
539
-
540
-
541
- if ($user != null) {
542
-
543
- $this->logger->info('Subscription of an address with status ' . $user->status);
544
-
545
- // We cannot communicate with bounced addresses, there is no reason to proceed
546
- // TODO: Evaluate if the bounce status is very old, possible reset it
547
- if ($user->status == TNP_User::STATUS_BOUNCED || $user->status == TNP_User::STATUS_COMPLAINED) {
548
- return new WP_Error('bounced', 'Subscriber present and blocked');
549
- }
550
-
551
- if ($user->status == TNP_User::STATUS_UNSUBSCRIBED) {
552
- // Special behavior?
553
- }
554
-
555
- if ($subscription->optin == 'single') {
556
- $user->status = TNP_User::STATUS_CONFIRMED;
557
- } else {
558
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
559
-
560
- set_transient('newsletter_subscription_' . $user->id, $subscription->data, 3600 * 48);
561
-
562
- // This status is *not* stored it indicate a temporary status to show the correct messages
563
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
564
-
565
- $this->send_message('confirmation', $user);
566
-
567
- return $user;
568
- } else {
569
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
570
- }
571
- }
572
-
573
- // Can be updated on the fly?
574
- $subscription->data->merge_in($user);
575
- } else {
576
- $this->logger->info('New subscriber');
577
-
578
- $user = new TNP_User();
579
- $subscription->data->merge_in($user);
580
-
581
- $user->token = $this->get_token();
582
-
583
- $user->status = $subscription->optin == 'single' ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
584
- $user->updated = time();
585
- }
586
-
587
- $user->ip = $this->process_ip($user->ip);
588
-
589
- $user = apply_filters('newsletter_user_subscribe', $user);
590
-
591
- $user = $this->save_user($user);
592
-
593
- $this->add_user_log($user, 'subscribe');
594
-
595
- // Notification to admin (only for new confirmed subscriptions)
596
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
597
- do_action('newsletter_user_confirmed', $user);
598
- $this->notify_admin_on_subscription($user);
599
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
600
- }
601
-
602
- if ($subscription->send_emails) {
603
- $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
604
- }
605
-
606
- // Used by Autoresponder (probably)
607
- do_action('newsletter_user_post_subscribe', $user);
608
-
609
- return $user;
610
- }
611
-
612
- /**
613
- * Create a subscription using the $_REQUEST data. Does security checks.
614
- *
615
- * @deprecated since version 6.9.0
616
- * @param string $status The status to use for this subscription (confirmed, not confirmed, ...)
617
- * @param bool $emails If the confirmation/welcome email should be sent or the subscription should be silent
618
- * @return TNP_User
619
- */
620
- function subscribe($status = null, $emails = true) {
621
-
622
- $this->logger->debug('Subscription start');
623
-
624
- // Validation
625
- $ip = $this->get_remote_ip();
626
- $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
627
- $first_name = '';
628
- if (isset($_REQUEST['nn'])) {
629
- $first_name = $this->normalize_name(stripslashes($_REQUEST['nn']));
630
- }
631
-
632
- $last_name = '';
633
- if (isset($_REQUEST['ns'])) {
634
- $last_name = $this->normalize_name(stripslashes($_REQUEST['ns']));
635
- }
636
-
637
- $opt_in = (int) $this->options['noconfirmation']; // 0 - double, 1 - single
638
- if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
639
- switch ($_REQUEST['optin']) {
640
- case 'single': $opt_in = self::OPTIN_SINGLE;
641
- break;
642
- case 'double': $opt_in = self::OPTIN_DOUBLE;
643
- break;
644
- }
645
- }
646
-
647
- if ($status != null) {
648
- // If a status is forced and it is requested to be "confirmed" is like a single opt in
649
- // $status here can only be confirmed or not confirmed
650
- // TODO: Add a check on status values
651
- if ($status == TNP_User::STATUS_CONFIRMED) {
652
- $opt_in = self::OPTIN_SINGLE;
653
- } else {
654
- $opt_in = self::OPTIN_DOUBLE;
655
- }
656
- }
657
-
658
- $user = $this->get_user($email);
659
-
660
- if ($user != null) {
661
- // Email already registered in our database
662
- $this->logger->info('Subscription of an address with status ' . $user->status);
663
-
664
- // Bounced
665
- // TODO: Manage other cases when added
666
- if ($user->status == 'B') {
667
- // Non persistent status to decide which message to show (error)
668
- $user->status = 'E';
669
- return $user;
670
- }
671
-
672
- // Is there any relevant data change? If so we can proceed otherwise if repeated subscriptions are disabled
673
- // show an already subscribed message
674
-
675
- if (empty($this->options['multiple'])) {
676
- $user->status = 'E';
677
- return $user;
678
- }
679
-
680
- // If the subscriber is confirmed, we cannot change his data in double opt in mode, we need to
681
- // temporary store and wait for activation
682
- if ($user->status == TNP_User::STATUS_CONFIRMED && $opt_in == self::OPTIN_DOUBLE) {
683
-
684
- set_transient($this->get_user_key($user), $_REQUEST, 3600 * 48);
685
-
686
- // This status is *not* stored it indicate a temporary status to show the correct messages
687
- $user->status = 'S';
688
-
689
- $this->send_message('confirmation', $user);
690
-
691
- return $user;
692
- }
693
- }
694
-
695
- // Here we have a new subscription or we can process the subscription even with a pre-existant user for example
696
- // because it is not confirmed
697
- if ($user != null) {
698
- $this->logger->info("Email address subscribed but not confirmed");
699
- $user = array('id' => $user->id);
700
- } else {
701
- $this->logger->info("New email address");
702
- $user = array('email' => $email);
703
- }
704
-
705
- $user = $this->update_user_from_request($user);
706
-
707
- $user['token'] = $this->get_token();
708
- $ip = $this->process_ip($ip);
709
- $user['ip'] = $ip;
710
- $user['geo'] = 0;
711
- $user['status'] = $opt_in == self::OPTIN_SINGLE ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
712
-
713
- $user['updated'] = time();
714
-
715
- $user = apply_filters('newsletter_user_subscribe', $user);
716
-
717
- $user = $this->save_user($user);
718
-
719
- $this->add_user_log($user, 'subscribe');
720
-
721
- // Notification to admin (only for new confirmed subscriptions)
722
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
723
- do_action('newsletter_user_confirmed', $user);
724
- $this->notify_admin_on_subscription($user);
725
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
726
- }
727
-
728
- if ($emails) {
729
- $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
730
- }
731
-
732
- $user = apply_filters('newsletter_user_post_subscribe', $user);
733
-
734
- return $user;
735
- }
736
-
737
- function add_microdata($message) {
738
- 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>';
739
- }
740
-
741
- /**
742
- * Builds a subscription object starting from values in the $_REQUEST
743
- * global variable. It DOES NOT sanitizie or formally check the values.
744
- * Usually data comes from a form submission.
745
- * https://www.thenewsletterplugin.com/documentation/subscription/newsletter-forms/
746
- *
747
- * @return TNP_Subscription
748
- */
749
- function build_subscription() {
750
-
751
- $language = '';
752
- if (!empty($_REQUEST['nlang'])) {
753
- $language = $_REQUEST['nlang'];
754
- } else {
755
- $language = $this->get_current_language();
756
- }
757
-
758
- $subscription = $this->get_default_subscription($language);
759
- $data = $subscription->data;
760
-
761
- $data->email = $_REQUEST['ne'];
762
-
763
- if (isset($_REQUEST['nn'])) {
764
- $data->name = stripslashes($_REQUEST['nn']);
765
- }
766
-
767
- if (isset($_REQUEST['ns'])) {
768
- $data->surname = stripslashes($_REQUEST['ns']);
769
- }
770
-
771
- if (!empty($_REQUEST['nx'])) {
772
- $data->sex = $_REQUEST['nx'][0];
773
- }
774
-
775
- if (isset($_REQUEST['nr'])) {
776
- $data->referrer = $_REQUEST['nr'];
777
- }
778
-
779
- // From the antibot form
780
- if (isset($_REQUEST['nhr'])) {
781
- $data->http_referer = stripslashes($_REQUEST['nhr']);
782
- } else if (isset($_SERVER['HTTP_REFERER'])) {
783
- $data->http_referer = $_SERVER['HTTP_REFERER'];
784
- }
785
-
786
- // New profiles
787
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
788
- // If the profile cannot be set by subscriber, skip it.
789
- if ($this->options_profile['profile_' . $i . '_status'] == 0) {
790
- continue;
791
- }
792
- if (isset($_REQUEST['np' . $i])) {
793
- $data->profiles['' . $i] = stripslashes($_REQUEST['np' . $i]);
794
- }
795
- }
796
-
797
- // Lists (field name is nl[] and values the list number so special forms with radio button can work)
798
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
799
- $this->logger->debug($_REQUEST['nl']);
800
- foreach ($_REQUEST['nl'] as $list_id) {
801
- $list = $this->get_list($list_id);
802
- if (!$list || $list->is_private()) {
803
- // To administrator show an error to make him aware of the wrong form configuration
804
- if (current_user_can('administrator')) {
805
- $this->dienow('Invalid list', 'List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
806
- }
807
- // Ignore this list
808
- continue;
809
- }
810
- $data->lists['' . $list_id] = 1;
811
- }
812
- } else {
813
- $this->logger->debug('No lists received');
814
- }
815
-
816
- // Opt-in mode
817
- if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
818
- switch ($_REQUEST['optin']) {
819
- case 'single': $subscription->optin = 'single';
820
- break;
821
- case 'double': $subscription->optin = 'double';
822
- break;
823
- }
824
- }
825
-
826
- return $subscription;
827
- }
828
-
829
- /**
830
- * Processes the request and fill in the *array* representing a subscriber with submitted values
831
- * (filtering when necessary).
832
- *
833
- * @deprecated since version 6.9.0
834
- * @param array $user An array partially filled with subscriber data
835
- * @return array The filled array representing a subscriber
836
- */
837
- function update_user_from_request($user) {
838
-
839
- if (isset($_REQUEST['nn'])) {
840
- $user['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
841
- }
842
- // TODO: required checking
843
-
844
- if (isset($_REQUEST['ns'])) {
845
- $user['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
846
- }
847
- // TODO: required checking
848
-
849
- if (!empty($_REQUEST['nx'])) {
850
- $user['sex'] = $this->normalize_sex($_REQUEST['nx'][0]);
851
- }
852
- // TODO: valid values check
853
-
854
- if (isset($_REQUEST['nr'])) {
855
- $user['referrer'] = strip_tags(trim($_REQUEST['nr']));
856
- }
857
-
858
- $language = '';
859
- if (!empty($_REQUEST['nlang'])) {
860
- $language = strtolower(strip_tags($_REQUEST['nlang']));
861
- // TODO: Check if it's an allowed language code
862
- $user['language'] = $language;
863
- } else {
864
- $language = $this->get_current_language();
865
- $user['language'] = $language;
866
- }
867
-
868
- // From the antibot form
869
- if (isset($_REQUEST['nhr'])) {
870
- $user['http_referer'] = strip_tags(trim($_REQUEST['nhr']));
871
- } else if (isset($_SERVER['HTTP_REFERER'])) {
872
- $user['http_referer'] = strip_tags(trim($_SERVER['HTTP_REFERER']));
873
- }
874
-
875
- if (strlen($user['http_referer']) > 200) {
876
- $user['http_referer'] = mb_substr($user['http_referer'], 0, 200);
877
- }
878
-
879
- // New profiles
880
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
881
- // If the profile cannot be set by subscriber, skip it.
882
- if ($this->options_profile['profile_' . $i . '_status'] == 0) {
883
- continue;
884
- }
885
- if (isset($_REQUEST['np' . $i])) {
886
- $user['profile_' . $i] = trim(stripslashes($_REQUEST['np' . $i]));
887
- }
888
- }
889
-
890
- // Extra validation to explain the administrator while the submitted data could
891
- // be interpreted only partially
892
- if (current_user_can('administrator')) {
893
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
894
- foreach ($_REQUEST['nl'] as $list_id) {
895
- $list = $this->get_list($list_id);
896
- if ($list && $list->status == TNP_List::STATUS_PRIVATE) {
897
- $this->dienow('Invalid list', '[old] List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
898
- }
899
- }
900
- }
901
- }
902
- // Preferences (field names are nl[] and values the list number so special forms with radio button can work)
903
- // Permetto l'aggiunta solo delle liste pubbliche
904
- if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
905
- $lists = $this->get_lists_public();
906
- //$this->logger->debug($_REQUEST['nl']);
907
- foreach ($lists as $list) {
908
- if (in_array('' . $list->id, $_REQUEST['nl'])) {
909
- $user['list_' . $list->id] = 1;
910
- }
911
- }
912
- } else {
913
- $this->logger->debug('No lists received');
914
- }
915
-
916
- // Forced lists (general or by language)
917
- // Forzo l'aggiunta delle liste forzate
918
- $lists = $this->get_lists();
919
- foreach ($lists as $list) {
920
- if ($list->forced) {
921
- $user['list_' . $list->id] = 1;
922
- }
923
- if (in_array($language, $list->languages)) {
924
- $user['list_' . $list->id] = 1;
925
- }
926
- }
927
-
928
- // TODO: should be removed!!!
929
- if (defined('NEWSLETTER_FEED_VERSION')) {
930
- $options_feed = get_option('newsletter_feed', array());
931
- if ($options_feed['add_new'] == 1) {
932
- $user['feed'] = 1;
933
- }
934
- }
935
- return $user;
936
- }
937
-
938
- /**
939
- * Sends a service message applying the template to the HTML part
940
- *
941
- * @param TNP_User $user
942
- * @param string $subject
943
- * @param string|array $message If string it is considered HTML, if array it should contains the key "html" and "text"
944
- * @return type
945
- */
946
- function mail($user, $subject, $message) {
947
- $language = $this->get_user_language($user);
948
-
949
- $options_template = $this->get_options('template', $language);
950
-
951
- $template = trim($options_template['template']);
952
- if (empty($template) || strpos($template, '{message}') === false) {
953
- $template = '{message}';
954
- }
955
-
956
- if (is_array($message)) {
957
- $message['html'] = str_replace('{message}', $message['html'], $template);
958
- $message['html'] = $this->replace($message['html'], $user);
959
- $message['text'] = $this->replace($message['text'], $user);
960
- } else {
961
- $message = str_replace('{message}', $message, $template);
962
- $message = $this->replace($message, $user);
963
- }
964
-
965
- $headers = [];
966
-
967
- // Replaces tags from the template
968
-
969
- $subject = $this->replace($subject, $user);
970
-
971
- return Newsletter::instance()->mail($user->email, $subject, $message, $headers);
972
- }
973
-
974
- /**
975
- * Confirms a subscription changing the user status and, possibly, merging the
976
- * temporary data if present.
977
- *
978
- * @param TNP_User $user Optionally it can be null (user search from requests paramaters, but deprecated, or a user id)
979
- * @return TNP_User
980
- */
981
- function confirm($user = null, $emails = true) {
982
-
983
- // Compatibility with WP Registration Addon
984
- if (!$user) {
985
- $user = $this->get_user_from_request(true);
986
- } else if (is_numeric($user)) {
987
- $user = $this->get_user($user);
988
- }
989
-
990
- if (!$user) {
991
- $this->dienow('Subscriber not found', '', 404);
992
- }
993
- // End compatibility
994
- // Should be merged?
995
- $data = get_transient('newsletter_subscription_' . $user->id);
996
- if ($data !== false) {
997
- $data->merge_in($user);
998
- //$this->merge($user, $data);
999
- $user = $this->save_user($user);
1000
- $user->status = TNP_User::STATUS_NOT_CONFIRMED;
1001
- delete_transient('newsletter_subscription_' . $user->id);
1002
- } else {
1003
- $new_email = get_transient('newsletter_user_' . $user->id . '_email');
1004
- if ($new_email) {
1005
- $data = ['id' => $user->id, 'email' => $new_email];
1006
- $this->save_user($data);
1007
- delete_transient('newsletter_user_' . $user->id . '_email');
1008
- }
1009
- }
1010
-
1011
-
1012
- $this->update_user_last_activity($user);
1013
-
1014
- setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
1015
-
1016
- if ($user->status == TNP_User::STATUS_CONFIRMED) {
1017
- $this->add_user_log($user, 'activate');
1018
- do_action('newsletter_user_confirmed', $user);
1019
- return $user;
1020
- }
1021
-
1022
- $this->set_user_status($user, TNP_User::STATUS_CONFIRMED);
1023
-
1024
- $user = $this->get_user($user);
1025
-
1026
- $this->add_user_log($user, 'activate');
1027
-
1028
- do_action('newsletter_user_confirmed', $user);
1029
- $this->notify_admin_on_subscription($user);
1030
-
1031
- if ($emails) {
1032
- $this->send_message('confirmed', $user);
1033
- }
1034
-
1035
- return $user;
1036
- }
1037
-
1038
- /**
1039
- * Sends a message (activation, welcome, cancellation, ...) with the correct template
1040
- * and checking if the message itself is disabled
1041
- *
1042
- * @param string $type
1043
- * @param TNP_User $user
1044
- */
1045
- function send_message($type, $user, $force = false) {
1046
- if (!$force && !empty($this->options[$type . '_disabled'])) {
1047
- return true;
1048
- }
1049
-
1050
- $language = $this->get_user_language($user);
1051
-
1052
- $options = $this->get_options('', $language);
1053
- $message = [];
1054
- $message['html'] = do_shortcode($options[$type . '_message']);
1055
- $message['text'] = $this->get_text_message($type);
1056
- if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
1057
- $message['html'] = $this->add_microdata($message['html']);
1058
- }
1059
- $subject = $options[$type . '_subject'];
1060
-
1061
- return $this->mail($user, $subject, $message);
1062
- }
1063
-
1064
- function get_text_message($type) {
1065
- switch ($type) {
1066
- case 'confirmation':
1067
- return __('To confirm your subscription follow the link below.', 'newsletter') . "\n\n{subscription_confirm_url}";
1068
- case 'confirmed':
1069
- return __('Your subscription has been confirmed.', 'newsletter');
1070
- }
1071
- return '';
1072
- }
1073
-
1074
- function is_double_optin() {
1075
- return $this->options['noconfirmation'] == 0;
1076
- }
1077
-
1078
- /**
1079
- * Sends the activation email without conditions.
1080
- *
1081
- * @param stdClass $user
1082
- * @return bool
1083
- */
1084
- function send_activation_email($user) {
1085
- return $this->send_message('confirmation', $user, true);
1086
- }
1087
-
1088
- /**
1089
- * Finds the right way to show the message identified by $key (welcome, unsubscription, ...) redirecting the user to the
1090
- * WordPress page or loading the configured url or activating the standard page.
1091
- */
1092
- function show_message($key, $user, $alert = '', $email = null) {
1093
- $url = '';
1094
-
1095
- if (isset($_REQUEST['ncu'])) {
1096
- // Custom URL from the form
1097
- $url = $_REQUEST['ncu'];
1098
- } else {
1099
- // Per message custom URL from configuration (language variants could not be supported)
1100
- $options = $this->get_options('', $this->get_user_language($user));
1101
- if (!empty($options[$key . '_url'])) {
1102
- $url = $options[$key . '_url'];
1103
- }
1104
- }
1105
-
1106
- $url = Newsletter::instance()->build_message_url($url, $key, $user, $email, $alert);
1107
- wp_redirect($url);
1108
-
1109
- die();
1110
- }
1111
-
1112
- function get_message_key_from_request() {
1113
- if (empty($_GET['nm'])) {
1114
- return 'subscription';
1115
- }
1116
- $key = $_GET['nm'];
1117
- switch ($key) {
1118
- case 's': return 'confirmation';
1119
- case 'c': return 'confirmed';
1120
- case 'u': return 'unsubscription';
1121
- case 'uc': return 'unsubscribed';
1122
- case 'p':
1123
- case 'pe':
1124
- return 'profile';
1125
- default: return $key;
1126
- }
1127
- }
1128
-
1129
- var $privacy_url = false;
1130
-
1131
- /**
1132
- * Generates the privacy URL and cache it.
1133
- *
1134
- * @return string
1135
- */
1136
- function get_privacy_url() {
1137
- if ($this->privacy_url === false) {
1138
- $this->setup_form_options();
1139
- if (!empty($this->form_options['privacy_use_wp_url']) && function_exists('get_privacy_policy_url')) {
1140
- $this->privacy_url = get_privacy_policy_url();
1141
- } else {
1142
- $this->privacy_url = $this->form_options['privacy_url'];
1143
- }
1144
- }
1145
- return $this->privacy_url;
1146
- }
1147
-
1148
- function get_form_javascript() {
1149
-
1150
- }
1151
-
1152
- /**
1153
- * Manages the custom forms made with [newsletter_form] and internal [newsletter_field] shortcodes.
1154
- *
1155
- * @param array $attrs
1156
- * @param string $content
1157
- * @return string
1158
- */
1159
- function get_subscription_form_custom($attrs = [], $content = '') {
1160
- if (!is_array($attrs)) {
1161
- $attrs = [];
1162
- }
1163
-
1164
- $this->setup_form_options();
1165
-
1166
- $attrs = array_merge(['class' => 'tnp-subscription', 'style' => ''], $attrs);
1167
-
1168
- $action = esc_attr($this->build_action_url('s'));
1169
- $class = esc_attr($attrs['class']);
1170
- $style = esc_attr($attrs['style']);
1171
- $buffer = '<form method="post" action="' . $action . '" class="' . $class . '" style="' . $style . '">' . "\n";
1172
-
1173
- $language = $this->get_current_language();
1174
-
1175
- $buffer .= $this->get_form_hidden_fields($attrs);
1176
-
1177
- $buffer .= do_shortcode($content);
1178
-
1179
- if (isset($attrs['button_label'])) {
1180
- $label = $attrs['button_label'];
1181
- } else {
1182
- $label = $this->form_options['subscribe'];
1183
- }
1184
-
1185
- if (!empty($label)) {
1186
- $buffer .= '<div class="tnp-field tnp-field-button">';
1187
- if (strpos($label, 'http') === 0) {
1188
- $buffer .= '<input class="tnp-button-image" type="image" src="' . $label . '">';
1189
- } else {
1190
- $buffer .= '<input class="tnp-button" type="submit" value="' . $label . '">';
1191
- }
1192
- $buffer .= '</div>';
1193
- }
1194
-
1195
- $buffer .= '</form>';
1196
-
1197
- return $buffer;
1198
- }
1199
-
1200
- /** Generates the hidden field for lists which should be implicitely set with a subscription form.
1201
- *
1202
- * @param string $lists Comma separated directly from the shortcode "lists" attribute
1203
- * @param string $language ???
1204
- * @return string
1205
- */
1206
- function get_form_implicit_lists($lists, $language = '') {
1207
- $buffer = '';
1208
-
1209
- $arr = explode(',', $lists);
1210
-
1211
- foreach ($arr as $a) {
1212
- $a = trim($a);
1213
- if (empty($a))
1214
- continue;
1215
-
1216
- $list = $this->get_list($a);
1217
- if (!$list) {
1218
- $buffer .= $this->build_field_admin_notice('List "' . $a . '" added to the form is not configured, skipped.');
1219
- continue;
1220
- }
1221
-
1222
- if ($list->is_private()) {
1223
- $buffer .= $this->build_field_admin_notice('List ' . $a . ' is private cannot be used in a public form.');
1224
- continue;
1225
- }
1226
-
1227
- if ($list->forced) {
1228
- $buffer .= $this->build_field_admin_notice('List ' . $a . ' is already enforced on every subscription there is no need to specify it.');
1229
- continue;
1230
- }
1231
-
1232
- $buffer .= "<input type='hidden' name='nl[]' value='" . esc_attr($a) . "'>\n";
1233
- }
1234
- return $buffer;
1235
- }
1236
-
1237
- /**
1238
- * Builds all the hidden fields of a subscription form. Implicit lists, confirmation url,
1239
- * referrer, language, ...
1240
- *
1241
- * @param array $attrs Attributes of form shortcode
1242
- * @return string HTML with the hidden fields
1243
- */
1244
- function get_form_hidden_fields($attrs) {
1245
- $b = '';
1246
-
1247
- // Compatibility
1248
- if (isset($attrs['list'])) {
1249
- $attrs['lists'] = $attrs['list'];
1250
- }
1251
- if (isset($attrs['lists'])) {
1252
- $b .= $this->get_form_implicit_lists($attrs['lists']);
1253
- }
1254
-
1255
- if (isset($attrs['referrer'])) {
1256
- $b .= '<input type="hidden" name="nr" value="' . esc_attr($attrs['referrer']) . '">';
1257
- }
1258
-
1259
- if (isset($attrs['confirmation_url'])) {
1260
- if ($attrs['confirmation_url'] === '#') {
1261
- $attrs['confirmation_url'] = $_SERVER['REQUEST_URI'];
1262
- }
1263
-
1264
- $b .= '<input type="hidden" name="ncu" value="' . esc_attr($attrs['confirmation_url']) . '">';
1265
- }
1266
-
1267
- if (isset($attrs['optin'])) {
1268
- $optin = trim(strtolower($attrs['optin']));
1269
- if ($optin !== 'double' && $optin !== 'single') {
1270
- $b .= $this->build_field_admin_notice('The optin is set to an invalid value.');
1271
- } else {
1272
- if ($optin !== 'double' && $this->is_double_optin() && empty($this->options['optin_override'])) {
1273
- $b .= $this->build_field_admin_notice('The optin is specified but cannot be overridden (see the subscription configiraton page).');
1274
- } else {
1275
- $b .= '<input type="hidden" name="optin" value="' . esc_attr($optin) . '">';
1276
- }
1277
- }
1278
- }
1279
-
1280
- $language = $this->get_current_language();
1281
- $b .= '<input type="hidden" name="nlang" value="' . esc_attr($language) . '">';
1282
-
1283
- return $b;
1284
- }
1285
-
1286
- /**
1287
- * Internal use only
1288
- *
1289
- * @param type $name
1290
- * @param type $attrs
1291
- * @param type $suffix
1292
- * @return string
1293
- */
1294
- private function _shortcode_label($name, $attrs, $suffix = null) {
1295
-
1296
- if (!$suffix) {
1297
- $suffix = $name;
1298
- }
1299
- $buffer = '<label for="' . esc_attr($attrs['id']) . '">';
1300
- if (isset($attrs['label'])) {
1301
- if (empty($attrs['label'])) {
1302
- return;
1303
- } else {
1304
- $buffer .= esc_html($attrs['label']);
1305
- }
1306
- } else {
1307
- if (isset($this->form_options[$name])) {
1308
- $buffer .= esc_html($this->form_options[$name]);
1309
- }
1310
- }
1311
- $buffer .= "</label>\n";
1312
- return $buffer;
1313
- }
1314
-
1315
- /**
1316
- * Creates a notices to be displayed near a subscription form field to inform of worng configurations.
1317
- * It is created only if the current user looking at the form is the administrator.
1318
- *
1319
- * @param string $message
1320
- * @return string
1321
- */
1322
- function build_field_admin_notice($message) {
1323
- if (!current_user_can('administrator')) {
1324
- return '';
1325
- }
1326
- return '<p style="background-color: #eee; color: #000; padding: 10px; margin: 10px 0">' . $message . ' <strong>This notice is shown only to administrators to help with configuration.</strong></p>';
1327
- }
1328
-
1329
- function shortcode_newsletter_field($attrs, $content = '') {
1330
- // Counter to create unique ID for checkbox and labels
1331
- static $idx = 0;
1332
-
1333
- $idx++;
1334
- $attrs['id'] = 'tnp-' . $idx;
1335
-
1336
- $this->setup_form_options();
1337
- $language = $this->get_current_language();
1338
-
1339
- $name = $attrs['name'];
1340
-
1341
- $buffer = '';
1342
-
1343
- if ($name == 'email') {
1344
- $buffer .= '<div class="tnp-field tnp-field-email">';
1345
-
1346
- $buffer .= $this->_shortcode_label('email', $attrs);
1347
-
1348
- $buffer .= '<input class="tnp-email" type="email" name="ne" id="' . esc_attr($attrs['id']) . '" value=""';
1349
- if (isset($attrs['placeholder'])) {
1350
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1351
- }
1352
- $buffer .= ' required>';
1353
- if (isset($attrs['button_label'])) {
1354
- $label = $attrs['button_label'];
1355
- if (strpos($label, 'http') === 0) {
1356
- $buffer .= ' <input class="tnp-submit-image" type="image" src="' . esc_attr(esc_url_raw($label)) . '">';
1357
- } else {
1358
- $buffer .= ' <input class="tnp-submit" type="submit" value="' . esc_attr($label) . '" style="width: 29%">';
1359
- }
1360
- }
1361
- $buffer .= "</div>\n";
1362
- return $buffer;
1363
- }
1364
-
1365
- if ($name == 'first_name' || $name == 'name') {
1366
- $buffer .= '<div class="tnp-field tnp-field-firstname">';
1367
- $buffer .= $this->_shortcode_label('name', $attrs);
1368
-
1369
- $buffer .= '<input class="tnp-name" type="text" name="nn" id="' . esc_attr($attrs['id']) . '" value=""';
1370
- if (isset($attrs['placeholder'])) {
1371
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1372
- }
1373
- if ($this->form_options['name_rules'] == 1) {
1374
- $buffer .= ' required';
1375
- }
1376
- $buffer .= '>';
1377
- $buffer .= "</div>\n";
1378
- return $buffer;
1379
- }
1380
-
1381
- if ($name == 'last_name' || $name == 'surname') {
1382
- $buffer .= '<div class="tnp-field tnp-field-surname">';
1383
- $buffer .= $this->_shortcode_label('surname', $attrs);
1384
-
1385
- $buffer .= '<input class="tnp-surname" type="text" name="ns" id="' . esc_attr($attrs['id']) . '" value=""';
1386
- if (isset($attrs['placeholder'])) {
1387
- $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1388
- }
1389
- if ($this->form_options['surname_rules'] == 1) {
1390
- $buffer .= ' required';
1391
- }
1392
- $buffer .= '>';
1393
- $buffer .= '</div>';
1394
- return $buffer;
1395
- }
1396
-
1397
- // Single list
1398
- if ($name == 'preference' || $name == 'list') {
1399
- if (!isset($attrs['number'])) {
1400
- return $this->build_field_admin_notice('List number not specified.');
1401
- }
1402
- $number = (int) $attrs['number'];
1403
- $list = $this->get_list($number, $language);
1404
- if (!$list) {
1405
- return $this->build_field_admin_notice('List ' . $number . ' is not configured, cannot be shown.');
1406
- }
1407
-
1408
- if ($list->status == 0 || $list->forced) {
1409
- return $this->build_field_admin_notice('List ' . $number . ' is private or enforced cannot be shown.');
1410
- }
1411
-
1412
- if (isset($attrs['hidden'])) {
1413
- return '<input type="hidden" name="nl[]" value="' . esc_attr($list->id) . '">';
1414
- }
1415
-
1416
- $idx++;
1417
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="tnp-' . $idx . '">';
1418
- $buffer .= '<input type="checkbox" id="tnp-' . $idx . '" name="nl[]" value="' . esc_attr($list->id) . '"';
1419
- if (isset($attrs['checked'])) {
1420
- $buffer .= ' checked';
1421
- }
1422
- $buffer .= '>';
1423
- if (isset($attrs['label'])) {
1424
- if ($attrs['label'] != '') {
1425
- $buffer .= '&nbsp;' . esc_html($attrs['label']) . '</label>';
1426
- }
1427
- } else {
1428
- $buffer .= '&nbsp;' . esc_html($list->name) . '</label>';
1429
- }
1430
- $buffer .= "</div>\n";
1431
-
1432
- return $buffer;
1433
- }
1434
-
1435
- // All lists
1436
- if ($name == 'lists' || $name == 'preferences') {
1437
- $lists = $this->get_lists_for_subscription($language);
1438
- if (!empty($lists) && isset($attrs['layout']) && $attrs['layout'] === 'dropdown') {
1439
-
1440
- $buffer .= '<div class="tnp-field tnp-lists">';
1441
- // There is not a default "label" for the block of lists, so it can only be specified in the shortcode attrs as "label"
1442
- $buffer .= $this->_shortcode_label('lists', $attrs);
1443
- $buffer .= '<select class="tnp-lists" name="nl[]" required>';
1444
-
1445
- if (!empty($attrs['first_option_label'])) {
1446
- $buffer .= '<option value="" selected="true" disabled="disabled">' . esc_html($attrs['first_option_label']) . '</option>';
1447
- }
1448
-
1449
- foreach ($lists as $list) {
1450
- $buffer .= '<option value="' . $list->id . '">' . esc_html($list->name) . '</option>';
1451
- }
1452
- $buffer .= '</select>';
1453
- $buffer .= '</div>';
1454
- } else {
1455
-
1456
- foreach ($lists as $list) {
1457
- $idx++;
1458
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="nl' . $idx . '">';
1459
- $buffer .= '<input type="checkbox" id="nl' . $idx . '" name="nl[]" value="' . $list->id . '"';
1460
- if ($list->checked) {
1461
- $buffer .= ' checked';
1462
- }
1463
- $buffer .= '>&nbsp;' . esc_html($list->name) . '</label>';
1464
- $buffer .= "</div>\n";
1465
- }
1466
- }
1467
- return $buffer;
1468
- }
1469
-
1470
- if ($name == 'sex' || $name == 'gender') {
1471
- $buffer .= '<div class="tnp-field tnp-field-gender">';
1472
- $buffer .= $this->_shortcode_label('sex', $attrs);
1473
-
1474
- $buffer .= '<select name="nx" class="tnp-gender" id="tnp-gender"';
1475
- if ($this->form_options['sex_rules']) {
1476
- $buffer .= ' required ';
1477
- }
1478
- $buffer .= '>';
1479
- if ($this->form_options['sex_rules']) {
1480
- $buffer .= '<option value=""></option>';
1481
- }
1482
- $buffer .= '<option value="n">' . esc_html($this->form_options['sex_none']) . '</option>';
1483
- $buffer .= '<option value="f">' . esc_html($this->form_options['sex_female']) . '</option>';
1484
- $buffer .= '<option value="m">' . esc_html($this->form_options['sex_male']) . '</option>';
1485
- $buffer .= '</select>';
1486
- $buffer .= "</div>\n";
1487
- return $buffer;
1488
- }
1489
-
1490
- if ($name == 'profile') {
1491
- if (!isset($attrs['number'])) {
1492
- return $this->build_field_admin_notice('Extra profile number not specified.');
1493
- }
1494
-
1495
- $number = (int) $attrs['number'];
1496
-
1497
- $profile = TNP_Profile_Service::get_profile_by_id($number, $language);
1498
-
1499
- if (!$profile) {
1500
- return $this->build_field_admin_notice('Extra profile ' . $number . ' is not configured, cannot be shown.');
1501
- }
1502
-
1503
- if ($profile->status == 0) {
1504
- return $this->build_field_admin_notice('Extra profile ' . $number . ' is private, cannot be shown.');
1505
- }
1506
-
1507
- $size = isset($attrs['size']) ? $attrs['size'] : '';
1508
- $buffer .= '<div class="tnp-field tnp-field-profile">';
1509
- $buffer .= $this->_shortcode_label('profile_' . $profile->id, $attrs);
1510
-
1511
- $placeholder = isset($attrs['placeholder']) ? $attrs['placeholder'] : $profile->placeholder;
1512
-
1513
- // Text field
1514
- if ($profile->type == TNP_Profile::TYPE_TEXT) {
1515
- $buffer .= '<input class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" type="text" size="' . esc_attr($size) . '" name="np' . $number . '" placeholder="' . esc_attr($placeholder) . '"';
1516
- if ($profile->is_required()) {
1517
- $buffer .= ' required';
1518
- }
1519
- $buffer .= '>';
1520
- }
1521
-
1522
- // Select field
1523
- if ($profile->type == TNP_Profile::TYPE_SELECT) {
1524
- $buffer .= '<select class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" name="np' . $number . '"';
1525
- if ($profile->is_required()) {
1526
- $buffer .= ' required';
1527
- }
1528
- $buffer .= '>';
1529
- if (!empty($placeholder)) {
1530
- $buffer .= '<option value="" selected disabled>' . esc_html($placeholder) . '</option>';
1531
- }
1532
- foreach ($profile->options as $option) {
1533
- $buffer .= '<option>' . esc_html(trim($option)) . '</option>';
1534
- }
1535
- $buffer .= "</select>\n";
1536
- }
1537
-
1538
- $buffer .= "</div>\n";
1539
-
1540
- return $buffer;
1541
- }
1542
-
1543
- if (strpos($name, 'privacy') === 0) {
1544
- if (!isset($attrs['url'])) {
1545
- $attrs['url'] = $this->get_privacy_url();
1546
- }
1547
-
1548
- if (!isset($attrs['label'])) {
1549
- $attrs['label'] = $this->form_options['privacy'];
1550
- }
1551
-
1552
- $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-privacy">';
1553
-
1554
- $idx++;
1555
- $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy" id="tnp-' . $idx . '"> ';
1556
- $buffer .= '<label for="tnp-' . $idx . '">';
1557
- if (!empty($attrs['url'])) {
1558
- $buffer .= '<a target="_blank" href="' . esc_attr($attrs['url']) . '">';
1559
- }
1560
- $buffer .= $attrs['label'];
1561
- if (!empty($attrs['url'])) {
1562
- $buffer .= '</a>';
1563
- }
1564
- $buffer .= '</label>';
1565
- $buffer .= '</div>';
1566
-
1567
- return $buffer;
1568
- }
1569
- }
1570
-
1571
- /**
1572
- * Builds the privacy field only for completely generated forms.
1573
- *
1574
- * @return string Empty id the privacy filed is not configured
1575
- */
1576
- function get_privacy_field($pre_html = '', $post_html = '') {
1577
- $this->setup_form_options();
1578
- $privacy_status = (int) $this->form_options['privacy_status'];
1579
- if (empty($privacy_status)) {
1580
- return '';
1581
- }
1582
-
1583
- $buffer = '<label>';
1584
- if ($privacy_status === 1) {
1585
- $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy">&nbsp;';
1586
- }
1587
- $url = $this->get_privacy_url();
1588
- if (!empty($url)) {
1589
- $buffer .= '<a target="_blank" href="' . esc_attr($url) . '">';
1590
- $buffer .= esc_attr($this->form_options['privacy']) . '</a>';
1591
- } else {
1592
- $buffer .= esc_html($this->form_options['privacy']);
1593
- }
1594
-
1595
- $buffer .= "</label>";
1596
-
1597
- return $pre_html . $buffer . $post_html;
1598
- }
1599
-
1600
- /**
1601
- * The new standard form.
1602
- *
1603
- * @param string $referrer Deprecated since 6.9.1, use the "referrer" key on $attrs
1604
- * @param string $action
1605
- * @param string $attrs
1606
- * @return string The full HTML form
1607
- */
1608
- function get_subscription_form($referrer = '', $action = null, $attrs = []) {
1609
- $language = $this->get_current_language();
1610
- $options_profile = $this->get_options('profile', $language);
1611
-
1612
- if (!is_array($attrs)) {
1613
- $attrs = [];
1614
- }
1615
-
1616
- // Possible alternative form actions (used by...?)
1617
- if (isset($attrs['action'])) {
1618
- $action = $attrs['action'];
1619
- }
1620
-
1621
- // The referrer parameter is deprecated
1622
- if (!empty($referrer)) {
1623
- $attrs['referrer'] = $referrer;
1624
- }
1625
-
1626
- $buffer = '';
1627
-
1628
- if (empty($action)) {
1629
- $action = $this->build_action_url('s');
1630
- }
1631
-
1632
- if (isset($attrs['before'])) {
1633
- $buffer .= $attrs['before'];
1634
- } else {
1635
- if (isset($attrs['class'])) {
1636
- $buffer .= '<div class="tnp tnp-subscription ' . $attrs['class'] . '">' . "\n";
1637
- } else {
1638
- $buffer .= '<div class="tnp tnp-subscription">' . "\n";
1639
- }
1640
- }
1641
-
1642
- $buffer .= '<form method="post" action="' . esc_attr($action) . '">' . "\n\n";
1643
-
1644
- $buffer .= $this->get_form_hidden_fields($attrs);
1645
-
1646
- if ($options_profile['name_status'] == 2) {
1647
- $buffer .= $this->shortcode_newsletter_field(['name' => 'first_name']);
1648
- }
1649
-
1650
- if ($options_profile['surname_status'] == 2) {
1651
- $buffer .= $this->shortcode_newsletter_field(['name' => 'last_name']);
1652
- }
1653
-
1654
- $buffer .= $this->shortcode_newsletter_field(['name' => 'email']);
1655
-
1656
- if (isset($options_profile['sex_status']) && $options_profile['sex_status'] == 2) {
1657
- $buffer .= $this->shortcode_newsletter_field(['name' => 'gender']);
1658
- }
1659
-
1660
- // Extra profile fields
1661
- for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1662
- // Not for subscription form
1663
- if ($options_profile['profile_' . $i . '_status'] != 2) {
1664
- continue;
1665
- }
1666
-
1667
- $buffer .= $this->shortcode_newsletter_field(['name' => 'profile', 'number' => $i]);
1668
- }
1669
-
1670
- $tmp = '';
1671
- $lists = $this->get_lists_for_subscription($language);
1672
- if (!empty($attrs['lists_field_layout']) && $attrs['lists_field_layout'] == 'dropdown') {
1673
- if (empty($attrs['lists_field_empty_label'])) {
1674
- $attrs['lists_field_empty_label'] = '';
1675
- }
1676
- if (empty($attrs['lists_field_label'])) {
1677
- $attrs['lists_field_label'] = '';
1678
- }
1679
- $buffer .= $this->shortcode_newsletter_field(['name' => 'lists', 'layout' => 'dropdown', 'first_option_label' => $attrs['lists_field_empty_label'], 'label' => $attrs['lists_field_label']]);
1680
- } else {
1681
- $buffer .= $this->shortcode_newsletter_field(['name' => 'lists']);
1682
- }
1683
-
1684
-
1685
-
1686
- // Deprecated
1687
- $extra = apply_filters('newsletter_subscription_extra', array());
1688
- foreach ($extra as $x) {
1689
- $label = $x['label'];
1690
- if (empty($label)) {
1691
- $label = '&nbsp;';
1692
- }
1693
- $name = '';
1694
- if (!empty($x['name'])) {
1695
- $name = $x['name'];
1696
- }
1697
- $buffer .= '<div class="tnp-field tnp-field-' . $name . '"><label>' . $label . "</label>";
1698
- $buffer .= $x['field'] . "</div>\n";
1699
- }
1700
-
1701
- $buffer .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1702
-
1703
- $buffer .= '<div class="tnp-field tnp-field-button">';
1704
-
1705
- $button_style = '';
1706
- if (!empty($attrs['button_color'])) {
1707
- $button_style = 'style="background-color:' . esc_attr($attrs['button_color']) . '"';
1708
- }
1709
-
1710
- if (strpos($options_profile['subscribe'], 'http') === 0) {
1711
- $buffer .= '<input class="tnp-submit-image" type="image" src="' . esc_attr($options_profile['subscribe']) . '">' . "\n";
1712
- } else {
1713
- $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($options_profile['subscribe']) . '" ' . $button_style . '>' . "\n";
1714
- }
1715
-
1716
- $buffer .= "</div>\n</form>\n";
1717
-
1718
- if (isset($attrs['after'])) {
1719
- $buffer .= $attrs['after'];
1720
- } else {
1721
- $buffer .= "</div>\n";
1722
- }
1723
-
1724
- return $buffer;
1725
- }
1726
-
1727
- function get_form($number) {
1728
- $options = get_option('newsletter_forms');
1729
-
1730
- $form = $options['form_' . $number];
1731
-
1732
- $form = do_shortcode($form);
1733
-
1734
- $action = $this->build_action_url('s');
1735
-
1736
- if (stripos($form, '<form') === false) {
1737
- $form = '<form method="post" action="' . $action . '">' . $form . '</form>';
1738
- }
1739
-
1740
- // For compatibility
1741
- $form = str_replace('{newsletter_url}', $action, $form);
1742
-
1743
- $form = $this->replace_lists($form);
1744
-
1745
- return $form;
1746
- }
1747
-
1748
- /** Replaces on passed text the special tag {lists} that can be used to show the preferences as a list of checkbox.
1749
- * They are called lists but on configuration panel they are named preferences!
1750
- *
1751
- * @param string $buffer
1752
- * @return string
1753
- */
1754
- function replace_lists($buffer) {
1755
- $checkboxes = '';
1756
- $lists = $this->get_lists_for_subscription($this->get_current_language());
1757
- foreach ($lists as $list) {
1758
- $checkboxes .= '<input type="checkbox" name="nl[]" value="' . $list->id . '"/>&nbsp;' . $list->name . '<br />';
1759
- }
1760
- $buffer = str_replace('{lists}', $checkboxes, $buffer);
1761
- $buffer = str_replace('{preferences}', $checkboxes, $buffer);
1762
- return $buffer;
1763
- }
1764
-
1765
- function notify_admin_on_subscription($user) {
1766
-
1767
- if (empty($this->options['notify'])) {
1768
- return;
1769
- }
1770
-
1771
- $message = $this->generate_admin_notification_message($user);
1772
- $email = trim($this->options['notify_email']);
1773
- $subject = $this->generate_admin_notification_subject('New subscription');
1774
-
1775
- Newsletter::instance()->mail($email, $subject, array('html' => $message));
1776
- }
1777
-
1778
- /**
1779
- * Builds the minimal subscription form, with only the email field and inline
1780
- * submit button. If enabled the privacy checkbox is added.
1781
- *
1782
- * @param type $attrs
1783
- * @return string
1784
- */
1785
- function get_subscription_form_minimal($attrs) {
1786
-
1787
- $this->setup_form_options();
1788
-
1789
- if (!is_array($attrs)) {
1790
- $attrs = [];
1791
- }
1792
-
1793
- $attrs = array_merge(array('class' => '', 'referrer' => 'minimal',
1794
- 'button' => $this->form_options['subscribe'], 'button_color' => '',
1795
- 'button_radius' => '', 'placeholder' => $this->form_options['email']), $attrs);
1796
-
1797
- $form = '';
1798
-
1799
- $form .= '<div class="tnp tnp-subscription-minimal ' . $attrs['class'] . '">';
1800
- $form .= '<form action="' . esc_attr($this->build_action_url('s')) . '" method="post">';
1801
-
1802
- $form .= $this->get_form_hidden_fields($attrs);
1803
-
1804
- $form .= '<input class="tnp-email" type="email" required name="ne" value="" placeholder="' . esc_attr($attrs['placeholder']) . '">';
1805
- $form .= '<input class="tnp-submit" type="submit" value="' . esc_attr($attrs['button']) . '"'
1806
- . ' style="background-color:' . esc_attr($attrs['button_color']) . '">';
1807
-
1808
- $form .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1809
-
1810
- $form .= "</form></div>\n";
1811
-
1812
- return $form;
1813
- }
1814
-
1815
- function shortcode_newsletter_form($attrs, $content) {
1816
-
1817
- if (isset($attrs['type']) && $attrs['type'] === 'minimal') {
1818
- return $this->get_subscription_form_minimal($attrs);
1819
- }
1820
-
1821
- // Custom form using the [newsletter_field] shortcodes
1822
- if (!empty($content)) {
1823
- return $this->get_subscription_form_custom($attrs, $content);
1824
- }
1825
-
1826
- // Custom form hand coded and saved in the custom forms option
1827
- if (isset($attrs['form'])) {
1828
- return $this->get_form((int) $attrs['form']);
1829
- }
1830
-
1831
- // Custom hand coded form (as above, new syntax)
1832
- if (isset($attrs['number'])) {
1833
- return $this->get_form((int) $attrs['number']);
1834
- }
1835
-
1836
- return $this->get_subscription_form(null, null, $attrs);
1837
- }
1838
-
1839
- /**
1840
- *
1841
- * @global wpdb $wpdb
1842
- * @param array $attrs
1843
- * @param string $content
1844
- * @return string
1845
- */
1846
- function shortcode_newsletter($attrs, $content) {
1847
- global $wpdb;
1848
-
1849
- $message_key = $this->get_message_key_from_request();
1850
- if ($message_key == 'confirmation') {
1851
- $user = $this->get_user_from_request(false, 'preconfirm');
1852
- } else {
1853
- $user = $this->get_user_from_request();
1854
- }
1855
-
1856
- $message = apply_filters('newsletter_page_text', '', $message_key, $user);
1857
-
1858
- $options = $this->get_options('', $this->get_current_language($user));
1859
-
1860
- if (empty($message)) {
1861
- $message = $options[$message_key . '_text'];
1862
-
1863
- // TODO: the if can be removed
1864
- if ($message_key == 'confirmed') {
1865
- $message .= $options[$message_key . '_tracking'];
1866
- }
1867
- }
1868
-
1869
- // Now check what form must be added
1870
- if ($message_key == 'subscription') {
1871
- if (isset($attrs['show_form']) && $attrs['show_form'] === 'false') {
1872
- //return $this->build_field_admin_notice('The [newsletter] shortcode is configured to not show the subscription form.');
1873
- return;
1874
- }
1875
-
1876
- // Compatibility check
1877
- if (stripos($message, '<form') !== false) {
1878
- $message = str_ireplace('<form', '<form method="post" action="' . esc_attr($this->get_subscribe_url()) . '"', $message);
1879
- } else {
1880
-
1881
- if (strpos($message, '{subscription_form') === false) {
1882
- $message .= '{subscription_form}';
1883
- }
1884
-
1885
- if (isset($attrs['form'])) {
1886
- $message = str_replace('{subscription_form}', $this->get_form($attrs['form']), $message);
1887
- } else {
1888
- $message = str_replace('{subscription_form}', $this->get_subscription_form('page', null, $attrs), $message);
1889
- }
1890
- }
1891
- }
1892
-
1893
- $email = $this->get_email_from_request();
1894
-
1895
- $message = $this->replace($message, $user, $email, 'page');
1896
-
1897
- $message = do_shortcode($message);
1898
-
1899
- if (isset($_REQUEST['alert'])) {
1900
- // slashes are already added by wordpress!
1901
- $message .= '<script>alert("' . esc_js(strip_tags($_REQUEST['alert'])) . '");</script>';
1902
- }
1903
-
1904
- return $message;
1905
- }
1906
-
1907
- }
1908
-
1909
- NewsletterSubscription::instance();
1910
-
1911
- // Compatibility code
1912
-
1913
- /**
1914
- * @deprecated
1915
- * @param int $number
1916
- */
1917
- function newsletter_form($number = null) {
1918
- if ($number != null) {
1919
- echo NewsletterSubscription::instance()->get_form($number);
1920
- } else {
1921
- echo NewsletterSubscription::instance()->get_subscription_form();
1922
- }
1923
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') || exit;
4
+
5
+ class NewsletterSubscription extends NewsletterModule {
6
+
7
+ const MESSAGE_CONFIRMED = 'confirmed';
8
+ const OPTIN_DOUBLE = 0;
9
+ const OPTIN_SINGLE = 1;
10
+
11
+ static $instance;
12
+
13
+ /**
14
+ * @var array
15
+ */
16
+ var $options_profile;
17
+
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;
25
+
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;
33
+
34
+ /**
35
+ * @var array
36
+ */
37
+ var $options_lists;
38
+
39
+ /**
40
+ * @return NewsletterSubscription
41
+ */
42
+ static function instance() {
43
+ if (self::$instance == null) {
44
+ self::$instance = new NewsletterSubscription();
45
+ }
46
+ return self::$instance;
47
+ }
48
+
49
+ function __construct() {
50
+
51
+ parent::__construct('subscription', '2.2.7', null, array('lists', 'template', 'profile', 'antibot'));
52
+ $this->options_profile = $this->get_options('profile');
53
+ $this->options_lists = $this->get_options('lists');
54
+
55
+ // Must be called after the Newsletter::hook_init, since some constants are defined
56
+ // there.
57
+ add_action('init', array($this, 'hook_init'), 90);
58
+ }
59
+
60
+ function hook_init() {
61
+ add_action('newsletter_action', array($this, 'hook_newsletter_action'), 10, 3);
62
+ if (is_admin()) {
63
+ add_action('admin_init', array($this, 'hook_admin_init'));
64
+ } else {
65
+ // Shortcode for the Newsletter page
66
+ add_shortcode('newsletter', array($this, 'shortcode_newsletter'));
67
+ add_shortcode('newsletter_form', array($this, 'shortcode_newsletter_form'));
68
+ add_shortcode('newsletter_field', array($this, 'shortcode_newsletter_field'));
69
+ }
70
+ }
71
+
72
+ function hook_admin_init() {
73
+ // So the user can add JS and other code
74
+ if (isset($_GET['page']) && $_GET['page'] === 'newsletter_subscription_forms') {
75
+ header('X-XSS-Protection: 0');
76
+ }
77
+
78
+ if (function_exists('register_block_type')) {
79
+ // Add custom blocks to Gutenberg
80
+ wp_register_script('tnp-blocks', plugins_url('newsletter') . '/includes/tnp-blocks.js', array('wp-blocks', 'wp-element'), NEWSLETTER_VERSION);
81
+ register_block_type('tnp/minimal', array('editor_script' => 'tnp-blocks'));
82
+ }
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @global wpdb $wpdb
88
+ * @return mixed
89
+ */
90
+ function hook_newsletter_action($action, $user, $email) {
91
+ global $wpdb;
92
+
93
+ switch ($action) {
94
+ case 'profile-change':
95
+ if ($this->antibot_form_check()) {
96
+
97
+ if (!$user || $user->status != TNP_user::STATUS_CONFIRMED) {
98
+ $this->dienow('Subscriber not found or not confirmed.', 'Even the wrong subscriber token can lead to this error.', 404);
99
+ }
100
+
101
+ if (!$email) {
102
+ $this->dienow('Newsletter not found', 'The newsletter containing the link has been deleted.', 404);
103
+ }
104
+
105
+ if (isset($_REQUEST['list'])) {
106
+ $list_id = (int) $_REQUEST['list'];
107
+
108
+ // Check if the list is public
109
+ $list = $this->get_list($list_id);
110
+ if (!$list || $list->status == TNP_List::STATUS_PRIVATE) {
111
+ $this->dienow('List change not allowed.', 'Please check if the list is marked as "private".', 400);
112
+ }
113
+
114
+ if (empty($_REQUEST['redirect'])) {
115
+ $url = home_url();
116
+ } else {
117
+ $url = esc_url_raw($_REQUEST['redirect']);
118
+ }
119
+ $this->set_user_list($user, $list_id, $_REQUEST['value']);
120
+
121
+ $user = $this->get_user($user->id);
122
+ $this->add_user_log($user, 'cta');
123
+ NewsletterStatistics::instance()->add_click($url, $user->id, $email->id);
124
+ wp_redirect($url);
125
+ die();
126
+ }
127
+ } else {
128
+ $this->request_to_antibot_form('Continue');
129
+ }
130
+
131
+ die();
132
+
133
+ case 'm':
134
+ case 'message':
135
+ include dirname(__FILE__) . '/page.php';
136
+ die();
137
+
138
+ // normal subscription
139
+ case 's':
140
+ case 'subscribe':
141
+
142
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
143
+ $this->dienow('Invalid request', 'The subscription request was not made with a HTTP POST', 400);
144
+ }
145
+
146
+ $options_antibot = $this->get_options('antibot');
147
+
148
+ $captcha = !empty($options_antibot['captcha']);
149
+
150
+ if (!empty($_GET['_wp_amp_action_xhr_converted']) || !empty($options_antibot['disabled']) || $this->antibot_form_check($captcha)) {
151
+
152
+ $subscription = $this->build_subscription();
153
+
154
+ $user = $this->subscribe2($subscription);
155
+
156
+ if (is_wp_error($user)) {
157
+ if ($user->get_error_code() === 'exists') {
158
+ $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
159
+ $options = $this->get_options('', $language);
160
+ $this->dienow($options['error_text'], $user->get_error_message(), 200);
161
+ }
162
+ $this->dienow('Registration failed.', $user->get_error_message(), 400);
163
+ }
164
+
165
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
166
+ $this->show_message('confirmed', $user);
167
+ }
168
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
169
+ $this->show_message('confirmation', $user);
170
+ }
171
+ } else {
172
+ $language = isset($_REQUEST['nlang']) ? $_REQUEST['nlang'] : '';
173
+ $options = $this->get_form_options($language);
174
+ $this->request_to_antibot_form($options['subscribe'], $captcha);
175
+ }
176
+ die();
177
+
178
+ // AJAX subscription
179
+ case 'ajaxsub':
180
+
181
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
182
+ $this->dienow('Invalid request');
183
+ }
184
+
185
+ $subscription = $this->build_subscription();
186
+
187
+ $user = $this->subscribe2($subscription);
188
+
189
+ if (is_wp_error($user)) {
190
+ if ($user->get_error_code() === 'exists') {
191
+ echo $this->options['error_text'];
192
+ die();
193
+ } else {
194
+ $this->dienow('Registration failed.', $user->get_error_message(), 400);
195
+ }
196
+ } else {
197
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
198
+ $key = 'confirmed';
199
+ }
200
+
201
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
202
+ $key = 'confirmation';
203
+ }
204
+ }
205
+
206
+ $message = $this->replace($this->options[$key . '_text'], $user);
207
+ if (isset($this->options[$key . '_tracking'])) {
208
+ $message .= $this->options[$key . '_tracking'];
209
+ }
210
+ echo $message;
211
+ die();
212
+
213
+ case 'c':
214
+ case 'confirm':
215
+ if (!$user) {
216
+ $this->dienow(__('Subscriber not found.', 'newsletter'), 'Or it is not present or the secret key does not match.', 404);
217
+ }
218
+
219
+ if ($this->antibot_form_check()) {
220
+ $user = $this->confirm($user);
221
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
222
+ $this->show_message('confirmed', $user);
223
+ } else {
224
+ $this->request_to_antibot_form('Confirm');
225
+ }
226
+ die();
227
+ break;
228
+
229
+ default:
230
+ return;
231
+ }
232
+ }
233
+
234
+ function upgrade() {
235
+ global $wpdb, $charset_collate;
236
+
237
+ parent::upgrade();
238
+
239
+ $newsletter = Newsletter::instance();
240
+ $lists_options = $this->get_options('lists');
241
+ $profile_options = $this->get_options('profile');
242
+
243
+ if (empty($lists_options)) {
244
+ foreach ($profile_options as $key => $value) {
245
+ if (strpos($key, 'list_') === 0) {
246
+ $lists_options[$key] = $value;
247
+ }
248
+ }
249
+ }
250
+
251
+ for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
252
+ // Options migration to the new set
253
+ if (!empty($profile_options['list_' . $i]) && empty($lists_options['list_' . $i])) {
254
+ $lists_options['list_' . $i] = $profile_options['list_' . $i];
255
+ $lists_options['list_' . $i . '_checked'] = $profile_options['list_' . $i . '_checked'];
256
+ $lists_options['list_' . $i . '_forced'] = $profile_options['list_' . $i . '_forced'];
257
+ }
258
+
259
+ if (!isset($profile_options['list_' . $i . '_forced'])) {
260
+ $profile_options['list_' . $i . '_forced'] = empty($this->options['preferences_' . $i]) ? 0 : 1;
261
+ $lists_options['list_' . $i . '_forced'] = empty($this->options['preferences_' . $i]) ? 0 : 1;
262
+ }
263
+ }
264
+
265
+ $this->save_options($profile_options, 'profile');
266
+ $this->save_options($lists_options, 'lists');
267
+
268
+ $default_options = $this->get_default_options();
269
+
270
+ if (empty($this->options['error_text'])) {
271
+ $this->options['error_text'] = $default_options['error_text'];
272
+ $this->save_options($this->options);
273
+ }
274
+
275
+ $this->init_options('template', false);
276
+
277
+ global $wpdb, $charset_collate;
278
+
279
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
280
+
281
+ $sql = "CREATE TABLE `" . $wpdb->prefix . "newsletter_user_logs` (
282
+ `id` int(11) NOT NULL AUTO_INCREMENT,
283
+ `user_id` int(11) NOT NULL DEFAULT 0,
284
+ `ip` varchar(50) NOT NULL DEFAULT '',
285
+ `source` varchar(50) NOT NULL DEFAULT '',
286
+ `data` longtext,
287
+ `created` int(11) NOT NULL DEFAULT 0,
288
+ PRIMARY KEY (`id`)
289
+ ) $charset_collate;";
290
+
291
+ dbDelta($sql);
292
+
293
+ return true;
294
+ }
295
+
296
+ function first_install() {
297
+
298
+ }
299
+
300
+ function admin_menu() {
301
+ $this->add_menu_page('options', __('List building', 'newsletter'));
302
+ $this->add_admin_page('lists', __('Lists', 'newsletter'));
303
+ $this->add_admin_page('profile', __('Subscription Form', 'newsletter'));
304
+ $this->add_menu_page('antibot', __('Security', 'newsletter'));
305
+ $this->add_admin_page('forms', __('Forms', 'newsletter'));
306
+ $this->add_admin_page('template', __('Template', 'newsletter'));
307
+ }
308
+
309
+ /**
310
+ * This method has been redefined for compatibility with the old options naming. It would
311
+ * be better to change them instead. The subscription options should be named
312
+ * "newsletter_subscription" while the form field options, actually named
313
+ * "newsletter_profile", should be renamed "newsletter_subscription_profile" (since
314
+ * they are retrived with get_options('profile')) or "newsletter_subscription_fields" or
315
+ * "newsletter_subscription_form".
316
+ *
317
+ * @param array $options
318
+ * @param string $sub
319
+ */
320
+ function save_options($options, $sub = '', $autoload = null, $language = '') {
321
+ if (empty($sub) && empty($language)) {
322
+ // For compatibility the options are wrongly named
323
+ return update_option('newsletter', $options, $autoload);
324
+ }
325
+
326
+ if (empty($sub) && !empty($language)) {
327
+ return update_option('newsletter_' . $language, $options, $autoload);
328
+ }
329
+
330
+ if ($sub == 'profile') {
331
+ if (empty($language)) {
332
+ $this->options_profile = $options;
333
+ return update_option('newsletter_profile', $options, $autoload);
334
+ } else {
335
+ return update_option('newsletter_profile_' . $language, $options, $autoload);
336
+ }
337
+ // For compatibility the options are wrongly named
338
+ }
339
+
340
+ if ($sub == 'forms') {
341
+ // For compatibility the options are wrongly named
342
+ return update_option('newsletter_forms', $options, $autoload);
343
+ }
344
+
345
+ if ($sub == 'lists') {
346
+ $this->options_lists = $options;
347
+ }
348
+ return parent::save_options($options, $sub, $autoload, $language);
349
+ }
350
+
351
+ function get_options($sub = '', $language = '') {
352
+ if ($sub == '') {
353
+ // For compatibility the options are wrongly named
354
+ if ($language) {
355
+ $options = get_option('newsletter_' . $language, []);
356
+ $options = array_merge(get_option('newsletter', []), $options);
357
+ } else {
358
+ $options = get_option('newsletter', []);
359
+ }
360
+ if (!is_array($options)) {
361
+ $options = array();
362
+ }
363
+
364
+ return $options;
365
+ }
366
+ if ($sub == 'profile') {
367
+ if ($language) {
368
+ // All that because for unknown reasome, sometime the options are returned as string, maybe a WPML
369
+ // interference...
370
+ $i18n_options = get_option('newsletter_profile_' . $language, []);
371
+ if (!is_array($i18n_options)) {
372
+ $i18n_options = [];
373
+ }
374
+ $options = get_option('newsletter_profile', []);
375
+ if (!is_array($options)) {
376
+ $options = [];
377
+ }
378
+ $options = array_merge($options, array_filter($i18n_options));
379
+ } else {
380
+ $options = get_option('newsletter_profile', []);
381
+ }
382
+ // For compatibility the options are wrongly named
383
+ return $options;
384
+ }
385
+ if ($sub == 'forms') {
386
+ // For compatibility the options are wrongly named
387
+ return get_option('newsletter_forms', []);
388
+ }
389
+ return parent::get_options($sub, $language);
390
+ }
391
+
392
+ /**
393
+ * Prepares the options used to build a subscription form for the current language
394
+ * in internal variable $form_options (for optimization). Can be called many times.
395
+ */
396
+ function setup_form_options() {
397
+ if (empty($this->form_options)) {
398
+ $this->form_options = $this->get_options('profile', $this->get_current_language());
399
+ }
400
+ }
401
+
402
+ function get_form_options($language = '') {
403
+ return $this->get_options('profile', $language);
404
+ }
405
+
406
+ function set_updated($user, $time = 0, $ip = '') {
407
+ global $wpdb;
408
+ if (!$time) {
409
+ $time = time();
410
+ }
411
+
412
+ if (!$ip) {
413
+ $ip = $this->get_remote_ip();
414
+ }
415
+ $ip = $this->process_ip($ip);
416
+
417
+ if (is_object($user)) {
418
+ $id = $user->id;
419
+ } else if (is_array($user)) {
420
+ $id = $user['id'];
421
+ }
422
+
423
+ $id = (int) $id;
424
+
425
+ $wpdb->update(NEWSLETTER_USERS_TABLE, array('updated' => $time, 'ip' => $ip, 'geo' => 0), array('id' => $id));
426
+ }
427
+
428
+ /**
429
+ * Sanitize the subscription data collected before process them. Cleanup the lists, the optin mode, email, first name,
430
+ * last name, adds mandatory lists, get (if not provided) and process the IP.
431
+ *
432
+ * @param TNP_Subscription_Data $data
433
+ */
434
+ private function sanitize($data) {
435
+ $data->email = $this->normalize_email($data->email);
436
+ if (!empty($data->name)) {
437
+ $data->name = $this->normalize_name($data->name);
438
+ }
439
+ if (!empty($data->surname)) {
440
+ $data->surname = $this->normalize_name($data->surname);
441
+ }
442
+
443
+ if (empty($data->ip)) {
444
+ $data->ip = $this->get_remote_ip();
445
+ }
446
+ $data->ip = $this->process_ip($data->ip);
447
+
448
+ if (isset($data->http_referer)) {
449
+ $data->http_referer = mb_substr(strip_tags($data->http_referer), 0, 200);
450
+ }
451
+
452
+ if (isset($data->sex)) {
453
+ $data->sex = $this->normalize_sex($data->sex);
454
+ }
455
+
456
+ if (!isset($data->language)) {
457
+ $data->language = $this->get_current_language();
458
+ } else {
459
+ $data->language = strtolower(strip_tags($data->language));
460
+ }
461
+
462
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
463
+ $key = 'profile_' . $i;
464
+ if (isset($data->$key)) {
465
+ $data->$key = trim($data->$key);
466
+ }
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Builds a default subscription object to be used to collect data and subscription options.
472
+ *
473
+ * @return TNP_Subscription
474
+ */
475
+ function get_default_subscription($language = null) {
476
+ $subscription = new TNP_Subscription();
477
+
478
+ $language = is_null($language) ? $this->get_current_language() : $language;
479
+
480
+ $subscription->data->language = $language;
481
+ $subscription->optin = $this->is_double_optin() ? 'double' : 'single';
482
+ $subscription->if_exists = empty($this->options['multiple']) ? TNP_Subscription::EXISTING_ERROR : TNP_Subscription::EXISTING_MERGE;
483
+
484
+ $lists = $this->get_lists();
485
+ foreach ($lists as $list) {
486
+ if ($list->forced) {
487
+ $subscription->data->lists['' . $list->id] = 1;
488
+ continue;
489
+ }
490
+ // Enforced by language
491
+ if ($language && in_array($language, $list->languages)) {
492
+ $subscription->data->lists['' . $list->id] = 1;
493
+ }
494
+ }
495
+
496
+ return $subscription;
497
+ }
498
+
499
+ /**
500
+ *
501
+ * @param TNP_Subscription $subscription
502
+ *
503
+ * @return TNP_User|WP_Error
504
+ */
505
+ function subscribe2(TNP_Subscription $subscription) {
506
+
507
+ $this->logger->debug($subscription);
508
+
509
+ $this->sanitize($subscription->data);
510
+
511
+ if (empty($subscription->data->email)) {
512
+ return new WP_Error('email', 'Wrong email address');
513
+ }
514
+
515
+ if (!empty($subscription->data->country) && strlen($subscription->data->country) != 2) {
516
+ return new WP_Error('country', 'Country code length error. ISO 3166-1 alpha-2 format (2 letters)');
517
+ }
518
+
519
+ // Here we should have a clean subscription data
520
+ // Filter?
521
+
522
+ if ($subscription->spamcheck) {
523
+ // TODO: Use autoload
524
+ require_once NEWSLETTER_INCLUDES_DIR . '/antispam.php';
525
+ $antispam = NewsletterAntispam::instance();
526
+ if ($antispam->is_spam($subscription)) {
527
+ return new WP_Error('spam', 'This looks like a spam subscription');
528
+ }
529
+ }
530
+
531
+ // Exists?
532
+ $user = $this->get_user_by_email($subscription->data->email);
533
+
534
+ // Do we accept repeated subscriptions?
535
+ if ($user != null && $subscription->if_exists === TNP_Subscription::EXISTING_ERROR) {
536
+ //$this->show_message('error', $user);
537
+ return new WP_Error('exists', 'Email address already registered and Newsletter sets to block repeated registrations. You can change this behavior or the user message above on subscription configuration panel.');
538
+ }
539
+
540
+
541
+ if ($user != null) {
542
+
543
+ $this->logger->info('Subscription of an address with status ' . $user->status);
544
+
545
+ // We cannot communicate with bounced addresses, there is no reason to proceed
546
+ // TODO: Evaluate if the bounce status is very old, possible reset it
547
+ if ($user->status == TNP_User::STATUS_BOUNCED || $user->status == TNP_User::STATUS_COMPLAINED) {
548
+ return new WP_Error('bounced', 'Subscriber present and blocked');
549
+ }
550
+
551
+ if ($user->status == TNP_User::STATUS_UNSUBSCRIBED) {
552
+ // Special behavior?
553
+ }
554
+
555
+ if ($subscription->optin == 'single') {
556
+ $user->status = TNP_User::STATUS_CONFIRMED;
557
+ } else {
558
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
559
+
560
+ set_transient('newsletter_subscription_' . $user->id, $subscription->data, 3600 * 48);
561
+
562
+ // This status is *not* stored it indicate a temporary status to show the correct messages
563
+ $user->status = TNP_User::STATUS_NOT_CONFIRMED;
564
+
565
+ $this->send_message('confirmation', $user);
566
+
567
+ return $user;
568
+ } else {
569
+ $user->status = TNP_User::STATUS_NOT_CONFIRMED;
570
+ }
571
+ }
572
+
573
+ // Can be updated on the fly?
574
+ $subscription->data->merge_in($user);
575
+ } else {
576
+ $this->logger->info('New subscriber');
577
+
578
+ $user = new TNP_User();
579
+ $subscription->data->merge_in($user);
580
+
581
+ $user->token = $this->get_token();
582
+
583
+ $user->status = $subscription->optin == 'single' ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
584
+ $user->updated = time();
585
+ }
586
+
587
+ $user->ip = $this->process_ip($user->ip);
588
+
589
+ $user = apply_filters('newsletter_user_subscribe', $user);
590
+
591
+ $user = $this->save_user($user);
592
+
593
+ $this->add_user_log($user, 'subscribe');
594
+
595
+ // Notification to admin (only for new confirmed subscriptions)
596
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
597
+ do_action('newsletter_user_confirmed', $user);
598
+ $this->notify_admin_on_subscription($user);
599
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
600
+ }
601
+
602
+ if ($subscription->send_emails) {
603
+ $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
604
+ }
605
+
606
+ // Used by Autoresponder (probably)
607
+ do_action('newsletter_user_post_subscribe', $user);
608
+
609
+ return $user;
610
+ }
611
+
612
+ /**
613
+ * Create a subscription using the $_REQUEST data. Does security checks.
614
+ *
615
+ * @deprecated since version 6.9.0
616
+ * @param string $status The status to use for this subscription (confirmed, not confirmed, ...)
617
+ * @param bool $emails If the confirmation/welcome email should be sent or the subscription should be silent
618
+ * @return TNP_User
619
+ */
620
+ function subscribe($status = null, $emails = true) {
621
+
622
+ $this->logger->debug('Subscription start');
623
+
624
+ // Validation
625
+ $ip = $this->get_remote_ip();
626
+ $email = $this->normalize_email(stripslashes($_REQUEST['ne']));
627
+ $first_name = '';
628
+ if (isset($_REQUEST['nn'])) {
629
+ $first_name = $this->normalize_name(stripslashes($_REQUEST['nn']));
630
+ }
631
+
632
+ $last_name = '';
633
+ if (isset($_REQUEST['ns'])) {
634
+ $last_name = $this->normalize_name(stripslashes($_REQUEST['ns']));
635
+ }
636
+
637
+ $opt_in = (int) $this->options['noconfirmation']; // 0 - double, 1 - single
638
+ if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
639
+ switch ($_REQUEST['optin']) {
640
+ case 'single': $opt_in = self::OPTIN_SINGLE;
641
+ break;
642
+ case 'double': $opt_in = self::OPTIN_DOUBLE;
643
+ break;
644
+ }
645
+ }
646
+
647
+ if ($status != null) {
648
+ // If a status is forced and it is requested to be "confirmed" is like a single opt in
649
+ // $status here can only be confirmed or not confirmed
650
+ // TODO: Add a check on status values
651
+ if ($status == TNP_User::STATUS_CONFIRMED) {
652
+ $opt_in = self::OPTIN_SINGLE;
653
+ } else {
654
+ $opt_in = self::OPTIN_DOUBLE;
655
+ }
656
+ }
657
+
658
+ $user = $this->get_user($email);
659
+
660
+ if ($user != null) {
661
+ // Email already registered in our database
662
+ $this->logger->info('Subscription of an address with status ' . $user->status);
663
+
664
+ // Bounced
665
+ // TODO: Manage other cases when added
666
+ if ($user->status == 'B') {
667
+ // Non persistent status to decide which message to show (error)
668
+ $user->status = 'E';
669
+ return $user;
670
+ }
671
+
672
+ // Is there any relevant data change? If so we can proceed otherwise if repeated subscriptions are disabled
673
+ // show an already subscribed message
674
+
675
+ if (empty($this->options['multiple'])) {
676
+ $user->status = 'E';
677
+ return $user;
678
+ }
679
+
680
+ // If the subscriber is confirmed, we cannot change his data in double opt in mode, we need to
681
+ // temporary store and wait for activation
682
+ if ($user->status == TNP_User::STATUS_CONFIRMED && $opt_in == self::OPTIN_DOUBLE) {
683
+
684
+ set_transient($this->get_user_key($user), $_REQUEST, 3600 * 48);
685
+
686
+ // This status is *not* stored it indicate a temporary status to show the correct messages
687
+ $user->status = 'S';
688
+
689
+ $this->send_message('confirmation', $user);
690
+
691
+ return $user;
692
+ }
693
+ }
694
+
695
+ // Here we have a new subscription or we can process the subscription even with a pre-existant user for example
696
+ // because it is not confirmed
697
+ if ($user != null) {
698
+ $this->logger->info("Email address subscribed but not confirmed");
699
+ $user = array('id' => $user->id);
700
+ } else {
701
+ $this->logger->info("New email address");
702
+ $user = array('email' => $email);
703
+ }
704
+
705
+ $user = $this->update_user_from_request($user);
706
+
707
+ $user['token'] = $this->get_token();
708
+ $ip = $this->process_ip($ip);
709
+ $user['ip'] = $ip;
710
+ $user['geo'] = 0;
711
+ $user['status'] = $opt_in == self::OPTIN_SINGLE ? TNP_User::STATUS_CONFIRMED : TNP_User::STATUS_NOT_CONFIRMED;
712
+
713
+ $user['updated'] = time();
714
+
715
+ $user = apply_filters('newsletter_user_subscribe', $user);
716
+
717
+ $user = $this->save_user($user);
718
+
719
+ $this->add_user_log($user, 'subscribe');
720
+
721
+ // Notification to admin (only for new confirmed subscriptions)
722
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
723
+ do_action('newsletter_user_confirmed', $user);
724
+ $this->notify_admin_on_subscription($user);
725
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
726
+ }
727
+
728
+ if ($emails) {
729
+ $this->send_message(($user->status == TNP_User::STATUS_CONFIRMED) ? 'confirmed' : 'confirmation', $user);
730
+ }
731
+
732
+ $user = apply_filters('newsletter_user_post_subscribe', $user);
733
+
734
+ return $user;
735
+ }
736
+
737
+ function add_microdata($message) {
738
+ 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>';
739
+ }
740
+
741
+ /**
742
+ * Builds a subscription object starting from values in the $_REQUEST
743
+ * global variable. It DOES NOT sanitizie or formally check the values.
744
+ * Usually data comes from a form submission.
745
+ * https://www.thenewsletterplugin.com/documentation/subscription/newsletter-forms/
746
+ *
747
+ * @return TNP_Subscription
748
+ */
749
+ function build_subscription() {
750
+
751
+ $language = '';
752
+ if (!empty($_REQUEST['nlang'])) {
753
+ $language = $_REQUEST['nlang'];
754
+ } else {
755
+ $language = $this->get_current_language();
756
+ }
757
+
758
+ $subscription = $this->get_default_subscription($language);
759
+ $data = $subscription->data;
760
+
761
+ $data->email = $_REQUEST['ne'];
762
+
763
+ if (isset($_REQUEST['nn'])) {
764
+ $data->name = stripslashes($_REQUEST['nn']);
765
+ }
766
+
767
+ if (isset($_REQUEST['ns'])) {
768
+ $data->surname = stripslashes($_REQUEST['ns']);
769
+ }
770
+
771
+ if (!empty($_REQUEST['nx'])) {
772
+ $data->sex = $_REQUEST['nx'][0];
773
+ }
774
+
775
+ if (isset($_REQUEST['nr'])) {
776
+ $data->referrer = $_REQUEST['nr'];
777
+ }
778
+
779
+ // From the antibot form
780
+ if (isset($_REQUEST['nhr'])) {
781
+ $data->http_referer = stripslashes($_REQUEST['nhr']);
782
+ } else if (isset($_SERVER['HTTP_REFERER'])) {
783
+ $data->http_referer = $_SERVER['HTTP_REFERER'];
784
+ }
785
+
786
+ // New profiles
787
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
788
+ // If the profile cannot be set by subscriber, skip it.
789
+ if ($this->options_profile['profile_' . $i . '_status'] == 0) {
790
+ continue;
791
+ }
792
+ if (isset($_REQUEST['np' . $i])) {
793
+ $data->profiles['' . $i] = stripslashes($_REQUEST['np' . $i]);
794
+ }
795
+ }
796
+
797
+ // Lists (field name is nl[] and values the list number so special forms with radio button can work)
798
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
799
+ $this->logger->debug($_REQUEST['nl']);
800
+ foreach ($_REQUEST['nl'] as $list_id) {
801
+ $list = $this->get_list($list_id);
802
+ if (!$list || $list->is_private()) {
803
+ // To administrator show an error to make him aware of the wrong form configuration
804
+ if (current_user_can('administrator')) {
805
+ $this->dienow('Invalid list', 'List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
806
+ }
807
+ // Ignore this list
808
+ continue;
809
+ }
810
+ $data->lists['' . $list_id] = 1;
811
+ }
812
+ } else {
813
+ $this->logger->debug('No lists received');
814
+ }
815
+
816
+ // Opt-in mode
817
+ if (!empty($this->options['optin_override']) && isset($_REQUEST['optin'])) {
818
+ switch ($_REQUEST['optin']) {
819
+ case 'single': $subscription->optin = 'single';
820
+ break;
821
+ case 'double': $subscription->optin = 'double';
822
+ break;
823
+ }
824
+ }
825
+
826
+ return $subscription;
827
+ }
828
+
829
+ /**
830
+ * Processes the request and fill in the *array* representing a subscriber with submitted values
831
+ * (filtering when necessary).
832
+ *
833
+ * @deprecated since version 6.9.0
834
+ * @param array $user An array partially filled with subscriber data
835
+ * @return array The filled array representing a subscriber
836
+ */
837
+ function update_user_from_request($user) {
838
+
839
+ if (isset($_REQUEST['nn'])) {
840
+ $user['name'] = $this->normalize_name(stripslashes($_REQUEST['nn']));
841
+ }
842
+ // TODO: required checking
843
+
844
+ if (isset($_REQUEST['ns'])) {
845
+ $user['surname'] = $this->normalize_name(stripslashes($_REQUEST['ns']));
846
+ }
847
+ // TODO: required checking
848
+
849
+ if (!empty($_REQUEST['nx'])) {
850
+ $user['sex'] = $this->normalize_sex($_REQUEST['nx'][0]);
851
+ }
852
+ // TODO: valid values check
853
+
854
+ if (isset($_REQUEST['nr'])) {
855
+ $user['referrer'] = strip_tags(trim($_REQUEST['nr']));
856
+ }
857
+
858
+ $language = '';
859
+ if (!empty($_REQUEST['nlang'])) {
860
+ $language = strtolower(strip_tags($_REQUEST['nlang']));
861
+ // TODO: Check if it's an allowed language code
862
+ $user['language'] = $language;
863
+ } else {
864
+ $language = $this->get_current_language();
865
+ $user['language'] = $language;
866
+ }
867
+
868
+ // From the antibot form
869
+ if (isset($_REQUEST['nhr'])) {
870
+ $user['http_referer'] = strip_tags(trim($_REQUEST['nhr']));
871
+ } else if (isset($_SERVER['HTTP_REFERER'])) {
872
+ $user['http_referer'] = strip_tags(trim($_SERVER['HTTP_REFERER']));
873
+ }
874
+
875
+ if (strlen($user['http_referer']) > 200) {
876
+ $user['http_referer'] = mb_substr($user['http_referer'], 0, 200);
877
+ }
878
+
879
+ // New profiles
880
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
881
+ // If the profile cannot be set by subscriber, skip it.
882
+ if ($this->options_profile['profile_' . $i . '_status'] == 0) {
883
+ continue;
884
+ }
885
+ if (isset($_REQUEST['np' . $i])) {
886
+ $user['profile_' . $i] = trim(stripslashes($_REQUEST['np' . $i]));
887
+ }
888
+ }
889
+
890
+ // Extra validation to explain the administrator while the submitted data could
891
+ // be interpreted only partially
892
+ if (current_user_can('administrator')) {
893
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
894
+ foreach ($_REQUEST['nl'] as $list_id) {
895
+ $list = $this->get_list($list_id);
896
+ if ($list && $list->status == TNP_List::STATUS_PRIVATE) {
897
+ $this->dienow('Invalid list', '[old] List ' . $list_id . ' has been submitted but it is set as private. Please fix the subscription form.');
898
+ }
899
+ }
900
+ }
901
+ }
902
+ // Preferences (field names are nl[] and values the list number so special forms with radio button can work)
903
+ // Permetto l'aggiunta solo delle liste pubbliche
904
+ if (isset($_REQUEST['nl']) && is_array($_REQUEST['nl'])) {
905
+ $lists = $this->get_lists_public();
906
+ //$this->logger->debug($_REQUEST['nl']);
907
+ foreach ($lists as $list) {
908
+ if (in_array('' . $list->id, $_REQUEST['nl'])) {
909
+ $user['list_' . $list->id] = 1;
910
+ }
911
+ }
912
+ } else {
913
+ $this->logger->debug('No lists received');
914
+ }
915
+
916
+ // Forced lists (general or by language)
917
+ // Forzo l'aggiunta delle liste forzate
918
+ $lists = $this->get_lists();
919
+ foreach ($lists as $list) {
920
+ if ($list->forced) {
921
+ $user['list_' . $list->id] = 1;
922
+ }
923
+ if (in_array($language, $list->languages)) {
924
+ $user['list_' . $list->id] = 1;
925
+ }
926
+ }
927
+
928
+ // TODO: should be removed!!!
929
+ if (defined('NEWSLETTER_FEED_VERSION')) {
930
+ $options_feed = get_option('newsletter_feed', array());
931
+ if ($options_feed['add_new'] == 1) {
932
+ $user['feed'] = 1;
933
+ }
934
+ }
935
+ return $user;
936
+ }
937
+
938
+ /**
939
+ * Sends a service message applying the template to the HTML part
940
+ *
941
+ * @param TNP_User $user
942
+ * @param string $subject
943
+ * @param string|array $message If string it is considered HTML, if array it should contains the key "html" and "text"
944
+ * @return type
945
+ */
946
+ function mail($user, $subject, $message) {
947
+ $language = $this->get_user_language($user);
948
+
949
+ $options_template = $this->get_options('template', $language);
950
+
951
+ $template = trim($options_template['template']);
952
+ if (empty($template) || strpos($template, '{message}') === false) {
953
+ $template = '{message}';
954
+ }
955
+
956
+ if (is_array($message)) {
957
+ $message['html'] = str_replace('{message}', $message['html'], $template);
958
+ $message['html'] = $this->replace($message['html'], $user);
959
+ $message['text'] = $this->replace($message['text'], $user);
960
+ } else {
961
+ $message = str_replace('{message}', $message, $template);
962
+ $message = $this->replace($message, $user);
963
+ }
964
+
965
+ $headers = [];
966
+
967
+ // Replaces tags from the template
968
+
969
+ $subject = $this->replace($subject, $user);
970
+
971
+ return Newsletter::instance()->mail($user->email, $subject, $message, $headers);
972
+ }
973
+
974
+ /**
975
+ * Confirms a subscription changing the user status and, possibly, merging the
976
+ * temporary data if present.
977
+ *
978
+ * @param TNP_User $user Optionally it can be null (user search from requests paramaters, but deprecated, or a user id)
979
+ * @return TNP_User
980
+ */
981
+ function confirm($user = null, $emails = true) {
982
+
983
+ // Compatibility with WP Registration Addon
984
+ if (!$user) {
985
+ $user = $this->get_user_from_request(true);
986
+ } else if (is_numeric($user)) {
987
+ $user = $this->get_user($user);
988
+ }
989
+
990
+ if (!$user) {
991
+ $this->dienow('Subscriber not found', '', 404);
992
+ }
993
+ // End compatibility
994
+ // Should be merged?
995
+ $data = get_transient('newsletter_subscription_' . $user->id);
996
+ if ($data !== false) {
997
+ $data->merge_in($user);
998
+ //$this->merge($user, $data);
999
+ $user = $this->save_user($user);
1000
+ $user->status = TNP_User::STATUS_NOT_CONFIRMED;
1001
+ delete_transient('newsletter_subscription_' . $user->id);
1002
+ } else {
1003
+ $new_email = get_transient('newsletter_user_' . $user->id . '_email');
1004
+ if ($new_email) {
1005
+ $data = ['id' => $user->id, 'email' => $new_email];
1006
+ $this->save_user($data);
1007
+ delete_transient('newsletter_user_' . $user->id . '_email');
1008
+ }
1009
+ }
1010
+
1011
+
1012
+ $this->update_user_last_activity($user);
1013
+
1014
+ setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
1015
+
1016
+ if ($user->status == TNP_User::STATUS_CONFIRMED) {
1017
+ $this->add_user_log($user, 'activate');
1018
+ do_action('newsletter_user_confirmed', $user);
1019
+ return $user;
1020
+ }
1021
+
1022
+ $this->set_user_status($user, TNP_User::STATUS_CONFIRMED);
1023
+
1024
+ $user = $this->get_user($user);
1025
+
1026
+ $this->add_user_log($user, 'activate');
1027
+
1028
+ do_action('newsletter_user_confirmed', $user);
1029
+ $this->notify_admin_on_subscription($user);
1030
+
1031
+ if ($emails) {
1032
+ $this->send_message('confirmed', $user);
1033
+ }
1034
+
1035
+ return $user;
1036
+ }
1037
+
1038
+ /**
1039
+ * Sends a message (activation, welcome, cancellation, ...) with the correct template
1040
+ * and checking if the message itself is disabled
1041
+ *
1042
+ * @param string $type
1043
+ * @param TNP_User $user
1044
+ */
1045
+ function send_message($type, $user, $force = false) {
1046
+ if (!$force && !empty($this->options[$type . '_disabled'])) {
1047
+ return true;
1048
+ }
1049
+
1050
+ $language = $this->get_user_language($user);
1051
+
1052
+ $options = $this->get_options('', $language);
1053
+ $message = [];
1054
+ $message['html'] = do_shortcode($options[$type . '_message']);
1055
+ $message['text'] = $this->get_text_message($type);
1056
+ if ($user->status == TNP_User::STATUS_NOT_CONFIRMED) {
1057
+ $message['html'] = $this->add_microdata($message['html']);
1058
+ }
1059
+ $subject = $options[$type . '_subject'];
1060
+
1061
+ return $this->mail($user, $subject, $message);
1062
+ }
1063
+
1064
+ function get_text_message($type) {
1065
+ switch ($type) {
1066
+ case 'confirmation':
1067
+ return __('To confirm your subscription follow the link below.', 'newsletter') . "\n\n{subscription_confirm_url}";
1068
+ case 'confirmed':
1069
+ return __('Your subscription has been confirmed.', 'newsletter');
1070
+ }
1071
+ return '';
1072
+ }
1073
+
1074
+ function is_double_optin() {
1075
+ return $this->options['noconfirmation'] == 0;
1076
+ }
1077
+
1078
+ /**
1079
+ * Sends the activation email without conditions.
1080
+ *
1081
+ * @param stdClass $user
1082
+ * @return bool
1083
+ */
1084
+ function send_activation_email($user) {
1085
+ return $this->send_message('confirmation', $user, true);
1086
+ }
1087
+
1088
+ /**
1089
+ * Finds the right way to show the message identified by $key (welcome, unsubscription, ...) redirecting the user to the
1090
+ * WordPress page or loading the configured url or activating the standard page.
1091
+ */
1092
+ function show_message($key, $user, $alert = '', $email = null) {
1093
+ $url = '';
1094
+
1095
+ if (isset($_REQUEST['ncu'])) {
1096
+ // Custom URL from the form
1097
+ $url = $_REQUEST['ncu'];
1098
+ } else {
1099
+ // Per message custom URL from configuration (language variants could not be supported)
1100
+ $options = $this->get_options('', $this->get_user_language($user));
1101
+ if (!empty($options[$key . '_url'])) {
1102
+ $url = $options[$key . '_url'];
1103
+ }
1104
+ }
1105
+
1106
+ $url = Newsletter::instance()->build_message_url($url, $key, $user, $email, $alert);
1107
+ wp_redirect($url);
1108
+
1109
+ die();
1110
+ }
1111
+
1112
+ function get_message_key_from_request() {
1113
+ if (empty($_GET['nm'])) {
1114
+ return 'subscription';
1115
+ }
1116
+ $key = $_GET['nm'];
1117
+ switch ($key) {
1118
+ case 's': return 'confirmation';
1119
+ case 'c': return 'confirmed';
1120
+ case 'u': return 'unsubscription';
1121
+ case 'uc': return 'unsubscribed';
1122
+ case 'p':
1123
+ case 'pe':
1124
+ return 'profile';
1125
+ default: return $key;
1126
+ }
1127
+ }
1128
+
1129
+ var $privacy_url = false;
1130
+
1131
+ /**
1132
+ * Generates the privacy URL and cache it.
1133
+ *
1134
+ * @return string
1135
+ */
1136
+ function get_privacy_url() {
1137
+ if ($this->privacy_url === false) {
1138
+ $this->setup_form_options();
1139
+ if (!empty($this->form_options['privacy_use_wp_url']) && function_exists('get_privacy_policy_url')) {
1140
+ $this->privacy_url = get_privacy_policy_url();
1141
+ } else {
1142
+ $this->privacy_url = $this->form_options['privacy_url'];
1143
+ }
1144
+ }
1145
+ return $this->privacy_url;
1146
+ }
1147
+
1148
+ function get_form_javascript() {
1149
+
1150
+ }
1151
+
1152
+ /**
1153
+ * Manages the custom forms made with [newsletter_form] and internal [newsletter_field] shortcodes.
1154
+ *
1155
+ * @param array $attrs
1156
+ * @param string $content
1157
+ * @return string
1158
+ */
1159
+ function get_subscription_form_custom($attrs = [], $content = '') {
1160
+ if (!is_array($attrs)) {
1161
+ $attrs = [];
1162
+ }
1163
+
1164
+ $this->setup_form_options();
1165
+
1166
+ $attrs = array_merge(['class' => 'tnp-subscription', 'style' => ''], $attrs);
1167
+
1168
+ $action = esc_attr($this->build_action_url('s'));
1169
+ $class = esc_attr($attrs['class']);
1170
+ $style = esc_attr($attrs['style']);
1171
+ $buffer = '<form method="post" action="' . $action . '" class="' . $class . '" style="' . $style . '">' . "\n";
1172
+
1173
+ $language = $this->get_current_language();
1174
+
1175
+ $buffer .= $this->get_form_hidden_fields($attrs);
1176
+
1177
+ $buffer .= do_shortcode($content);
1178
+
1179
+ if (isset($attrs['button_label'])) {
1180
+ $label = $attrs['button_label'];
1181
+ } else {
1182
+ $label = $this->form_options['subscribe'];
1183
+ }
1184
+
1185
+ if (!empty($label)) {
1186
+ $buffer .= '<div class="tnp-field tnp-field-button">';
1187
+ if (strpos($label, 'http') === 0) {
1188
+ $buffer .= '<input class="tnp-button-image" type="image" src="' . $label . '">';
1189
+ } else {
1190
+ $buffer .= '<input class="tnp-button" type="submit" value="' . $label . '">';
1191
+ }
1192
+ $buffer .= '</div>';
1193
+ }
1194
+
1195
+ $buffer .= '</form>';
1196
+
1197
+ return $buffer;
1198
+ }
1199
+
1200
+ /** Generates the hidden field for lists which should be implicitely set with a subscription form.
1201
+ *
1202
+ * @param string $lists Comma separated directly from the shortcode "lists" attribute
1203
+ * @param string $language ???
1204
+ * @return string
1205
+ */
1206
+ function get_form_implicit_lists($lists, $language = '') {
1207
+ $buffer = '';
1208
+
1209
+ $arr = explode(',', $lists);
1210
+
1211
+ foreach ($arr as $a) {
1212
+ $a = trim($a);
1213
+ if (empty($a))
1214
+ continue;
1215
+
1216
+ $list = $this->get_list($a);
1217
+ if (!$list) {
1218
+ $buffer .= $this->build_field_admin_notice('List "' . $a . '" added to the form is not configured, skipped.');
1219
+ continue;
1220
+ }
1221
+
1222
+ if ($list->is_private()) {
1223
+ $buffer .= $this->build_field_admin_notice('List ' . $a . ' is private cannot be used in a public form.');
1224
+ continue;
1225
+ }
1226
+
1227
+ if ($list->forced) {
1228
+ $buffer .= $this->build_field_admin_notice('List ' . $a . ' is already enforced on every subscription there is no need to specify it.');
1229
+ continue;
1230
+ }
1231
+
1232
+ $buffer .= "<input type='hidden' name='nl[]' value='" . esc_attr($a) . "'>\n";
1233
+ }
1234
+ return $buffer;
1235
+ }
1236
+
1237
+ /**
1238
+ * Builds all the hidden fields of a subscription form. Implicit lists, confirmation url,
1239
+ * referrer, language, ...
1240
+ *
1241
+ * @param array $attrs Attributes of form shortcode
1242
+ * @return string HTML with the hidden fields
1243
+ */
1244
+ function get_form_hidden_fields($attrs) {
1245
+ $b = '';
1246
+
1247
+ // Compatibility
1248
+ if (isset($attrs['list'])) {
1249
+ $attrs['lists'] = $attrs['list'];
1250
+ }
1251
+ if (isset($attrs['lists'])) {
1252
+ $b .= $this->get_form_implicit_lists($attrs['lists']);
1253
+ }
1254
+
1255
+ if (isset($attrs['referrer'])) {
1256
+ $b .= '<input type="hidden" name="nr" value="' . esc_attr($attrs['referrer']) . '">';
1257
+ }
1258
+
1259
+ if (isset($attrs['confirmation_url'])) {
1260
+ if ($attrs['confirmation_url'] === '#') {
1261
+ $attrs['confirmation_url'] = $_SERVER['REQUEST_URI'];
1262
+ }
1263
+
1264
+ $b .= '<input type="hidden" name="ncu" value="' . esc_attr($attrs['confirmation_url']) . '">';
1265
+ }
1266
+
1267
+ if (isset($attrs['optin'])) {
1268
+ $optin = trim(strtolower($attrs['optin']));
1269
+ if ($optin !== 'double' && $optin !== 'single') {
1270
+ $b .= $this->build_field_admin_notice('The optin is set to an invalid value.');
1271
+ } else {
1272
+ if ($optin !== 'double' && $this->is_double_optin() && empty($this->options['optin_override'])) {
1273
+ $b .= $this->build_field_admin_notice('The optin is specified but cannot be overridden (see the subscription configiraton page).');
1274
+ } else {
1275
+ $b .= '<input type="hidden" name="optin" value="' . esc_attr($optin) . '">';
1276
+ }
1277
+ }
1278
+ }
1279
+
1280
+ $language = $this->get_current_language();
1281
+ $b .= '<input type="hidden" name="nlang" value="' . esc_attr($language) . '">';
1282
+
1283
+ return $b;
1284
+ }
1285
+
1286
+ /**
1287
+ * Internal use only
1288
+ *
1289
+ * @param type $name
1290
+ * @param type $attrs
1291
+ * @param type $suffix
1292
+ * @return string
1293
+ */
1294
+ private function _shortcode_label($name, $attrs, $suffix = null) {
1295
+
1296
+ if (!$suffix) {
1297
+ $suffix = $name;
1298
+ }
1299
+ $buffer = '<label for="' . esc_attr($attrs['id']) . '">';
1300
+ if (isset($attrs['label'])) {
1301
+ if (empty($attrs['label'])) {
1302
+ return;
1303
+ } else {
1304
+ $buffer .= esc_html($attrs['label']);
1305
+ }
1306
+ } else {
1307
+ if (isset($this->form_options[$name])) {
1308
+ $buffer .= esc_html($this->form_options[$name]);
1309
+ }
1310
+ }
1311
+ $buffer .= "</label>\n";
1312
+ return $buffer;
1313
+ }
1314
+
1315
+ /**
1316
+ * Creates a notices to be displayed near a subscription form field to inform of worng configurations.
1317
+ * It is created only if the current user looking at the form is the administrator.
1318
+ *
1319
+ * @param string $message
1320
+ * @return string
1321
+ */
1322
+ function build_field_admin_notice($message) {
1323
+ if (!current_user_can('administrator')) {
1324
+ return '';
1325
+ }
1326
+ return '<p style="background-color: #eee; color: #000; padding: 10px; margin: 10px 0">' . $message . ' <strong>This notice is shown only to administrators to help with configuration.</strong></p>';
1327
+ }
1328
+
1329
+ function shortcode_newsletter_field($attrs, $content = '') {
1330
+ // Counter to create unique ID for checkbox and labels
1331
+ static $idx = 0;
1332
+
1333
+ $idx++;
1334
+ $attrs['id'] = 'tnp-' . $idx;
1335
+
1336
+ $this->setup_form_options();
1337
+ $language = $this->get_current_language();
1338
+
1339
+ $name = $attrs['name'];
1340
+
1341
+ $buffer = '';
1342
+
1343
+ if ($name == 'email') {
1344
+ $buffer .= '<div class="tnp-field tnp-field-email">';
1345
+
1346
+ $buffer .= $this->_shortcode_label('email', $attrs);
1347
+
1348
+ $buffer .= '<input class="tnp-email" type="email" name="ne" id="' . esc_attr($attrs['id']) . '" value=""';
1349
+ if (isset($attrs['placeholder'])) {
1350
+ $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1351
+ }
1352
+ $buffer .= ' required>';
1353
+ if (isset($attrs['button_label'])) {
1354
+ $label = $attrs['button_label'];
1355
+ if (strpos($label, 'http') === 0) {
1356
+ $buffer .= ' <input class="tnp-submit-image" type="image" src="' . esc_attr(esc_url_raw($label)) . '">';
1357
+ } else {
1358
+ $buffer .= ' <input class="tnp-submit" type="submit" value="' . esc_attr($label) . '" style="width: 29%">';
1359
+ }
1360
+ }
1361
+ $buffer .= "</div>\n";
1362
+ return $buffer;
1363
+ }
1364
+
1365
+ if ($name == 'first_name' || $name == 'name') {
1366
+ $buffer .= '<div class="tnp-field tnp-field-firstname">';
1367
+ $buffer .= $this->_shortcode_label('name', $attrs);
1368
+
1369
+ $buffer .= '<input class="tnp-name" type="text" name="nn" id="' . esc_attr($attrs['id']) . '" value=""';
1370
+ if (isset($attrs['placeholder'])) {
1371
+ $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1372
+ }
1373
+ if ($this->form_options['name_rules'] == 1) {
1374
+ $buffer .= ' required';
1375
+ }
1376
+ $buffer .= '>';
1377
+ $buffer .= "</div>\n";
1378
+ return $buffer;
1379
+ }
1380
+
1381
+ if ($name == 'last_name' || $name == 'surname') {
1382
+ $buffer .= '<div class="tnp-field tnp-field-surname">';
1383
+ $buffer .= $this->_shortcode_label('surname', $attrs);
1384
+
1385
+ $buffer .= '<input class="tnp-surname" type="text" name="ns" id="' . esc_attr($attrs['id']) . '" value=""';
1386
+ if (isset($attrs['placeholder'])) {
1387
+ $buffer .= ' placeholder="' . esc_attr($attrs['placeholder']) . '"';
1388
+ }
1389
+ if ($this->form_options['surname_rules'] == 1) {
1390
+ $buffer .= ' required';
1391
+ }
1392
+ $buffer .= '>';
1393
+ $buffer .= '</div>';
1394
+ return $buffer;
1395
+ }
1396
+
1397
+ // Single list
1398
+ if ($name == 'preference' || $name == 'list') {
1399
+ if (!isset($attrs['number'])) {
1400
+ return $this->build_field_admin_notice('List number not specified.');
1401
+ }
1402
+ $number = (int) $attrs['number'];
1403
+ $list = $this->get_list($number, $language);
1404
+ if (!$list) {
1405
+ return $this->build_field_admin_notice('List ' . $number . ' is not configured, cannot be shown.');
1406
+ }
1407
+
1408
+ if ($list->status == 0 || $list->forced) {
1409
+ return $this->build_field_admin_notice('List ' . $number . ' is private or enforced cannot be shown.');
1410
+ }
1411
+
1412
+ if (isset($attrs['hidden'])) {
1413
+ return '<input type="hidden" name="nl[]" value="' . esc_attr($list->id) . '">';
1414
+ }
1415
+
1416
+ $idx++;
1417
+ $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="tnp-' . $idx . '">';
1418
+ $buffer .= '<input type="checkbox" id="tnp-' . $idx . '" name="nl[]" value="' . esc_attr($list->id) . '"';
1419
+ if (isset($attrs['checked'])) {
1420
+ $buffer .= ' checked';
1421
+ }
1422
+ $buffer .= '>';
1423
+ if (isset($attrs['label'])) {
1424
+ if ($attrs['label'] != '') {
1425
+ $buffer .= '&nbsp;' . esc_html($attrs['label']) . '</label>';
1426
+ }
1427
+ } else {
1428
+ $buffer .= '&nbsp;' . esc_html($list->name) . '</label>';
1429
+ }
1430
+ $buffer .= "</div>\n";
1431
+
1432
+ return $buffer;
1433
+ }
1434
+
1435
+ // All lists
1436
+ if ($name == 'lists' || $name == 'preferences') {
1437
+ $lists = $this->get_lists_for_subscription($language);
1438
+ if (!empty($lists) && isset($attrs['layout']) && $attrs['layout'] === 'dropdown') {
1439
+
1440
+ $buffer .= '<div class="tnp-field tnp-lists">';
1441
+ // There is not a default "label" for the block of lists, so it can only be specified in the shortcode attrs as "label"
1442
+ $buffer .= $this->_shortcode_label('lists', $attrs);
1443
+ $buffer .= '<select class="tnp-lists" name="nl[]" required>';
1444
+
1445
+ if (!empty($attrs['first_option_label'])) {
1446
+ $buffer .= '<option value="" selected="true" disabled="disabled">' . esc_html($attrs['first_option_label']) . '</option>';
1447
+ }
1448
+
1449
+ foreach ($lists as $list) {
1450
+ $buffer .= '<option value="' . $list->id . '">' . esc_html($list->name) . '</option>';
1451
+ }
1452
+ $buffer .= '</select>';
1453
+ $buffer .= '</div>';
1454
+ } else {
1455
+
1456
+ foreach ($lists as $list) {
1457
+ $idx++;
1458
+ $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-list"><label for="nl' . $idx . '">';
1459
+ $buffer .= '<input type="checkbox" id="nl' . $idx . '" name="nl[]" value="' . $list->id . '"';
1460
+ if ($list->checked) {
1461
+ $buffer .= ' checked';
1462
+ }
1463
+ $buffer .= '>&nbsp;' . esc_html($list->name) . '</label>';
1464
+ $buffer .= "</div>\n";
1465
+ }
1466
+ }
1467
+ return $buffer;
1468
+ }
1469
+
1470
+ if ($name == 'sex' || $name == 'gender') {
1471
+ $buffer .= '<div class="tnp-field tnp-field-gender">';
1472
+ $buffer .= $this->_shortcode_label('sex', $attrs);
1473
+
1474
+ $buffer .= '<select name="nx" class="tnp-gender" id="tnp-gender"';
1475
+ if ($this->form_options['sex_rules']) {
1476
+ $buffer .= ' required ';
1477
+ }
1478
+ $buffer .= '>';
1479
+ if ($this->form_options['sex_rules']) {
1480
+ $buffer .= '<option value=""></option>';
1481
+ }
1482
+ $buffer .= '<option value="n">' . esc_html($this->form_options['sex_none']) . '</option>';
1483
+ $buffer .= '<option value="f">' . esc_html($this->form_options['sex_female']) . '</option>';
1484
+ $buffer .= '<option value="m">' . esc_html($this->form_options['sex_male']) . '</option>';
1485
+ $buffer .= '</select>';
1486
+ $buffer .= "</div>\n";
1487
+ return $buffer;
1488
+ }
1489
+
1490
+ if ($name == 'profile') {
1491
+ if (!isset($attrs['number'])) {
1492
+ return $this->build_field_admin_notice('Extra profile number not specified.');
1493
+ }
1494
+
1495
+ $number = (int) $attrs['number'];
1496
+
1497
+ $profile = TNP_Profile_Service::get_profile_by_id($number, $language);
1498
+
1499
+ if (!$profile) {
1500
+ return $this->build_field_admin_notice('Extra profile ' . $number . ' is not configured, cannot be shown.');
1501
+ }
1502
+
1503
+ if ($profile->status == 0) {
1504
+ return $this->build_field_admin_notice('Extra profile ' . $number . ' is private, cannot be shown.');
1505
+ }
1506
+
1507
+ $size = isset($attrs['size']) ? $attrs['size'] : '';
1508
+ $buffer .= '<div class="tnp-field tnp-field-profile">';
1509
+ $buffer .= $this->_shortcode_label('profile_' . $profile->id, $attrs);
1510
+
1511
+ $placeholder = isset($attrs['placeholder']) ? $attrs['placeholder'] : $profile->placeholder;
1512
+
1513
+ // Text field
1514
+ if ($profile->type == TNP_Profile::TYPE_TEXT) {
1515
+ $buffer .= '<input class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" type="text" size="' . esc_attr($size) . '" name="np' . $number . '" placeholder="' . esc_attr($placeholder) . '"';
1516
+ if ($profile->is_required()) {
1517
+ $buffer .= ' required';
1518
+ }
1519
+ $buffer .= '>';
1520
+ }
1521
+
1522
+ // Select field
1523
+ if ($profile->type == TNP_Profile::TYPE_SELECT) {
1524
+ $buffer .= '<select class="tnp-profile tnp-profile-' . $number . '" id="tnp-profile_' . $number . '" name="np' . $number . '"';
1525
+ if ($profile->is_required()) {
1526
+ $buffer .= ' required';
1527
+ }
1528
+ $buffer .= '>';
1529
+ if (!empty($placeholder)) {
1530
+ $buffer .= '<option value="" selected disabled>' . esc_html($placeholder) . '</option>';
1531
+ }
1532
+ foreach ($profile->options as $option) {
1533
+ $buffer .= '<option>' . esc_html(trim($option)) . '</option>';
1534
+ }
1535
+ $buffer .= "</select>\n";
1536
+ }
1537
+
1538
+ $buffer .= "</div>\n";
1539
+
1540
+ return $buffer;
1541
+ }
1542
+
1543
+ if (strpos($name, 'privacy') === 0) {
1544
+ if (!isset($attrs['url'])) {
1545
+ $attrs['url'] = $this->get_privacy_url();
1546
+ }
1547
+
1548
+ if (!isset($attrs['label'])) {
1549
+ $attrs['label'] = $this->form_options['privacy'];
1550
+ }
1551
+
1552
+ $buffer .= '<div class="tnp-field tnp-field-checkbox tnp-field-privacy">';
1553
+
1554
+ $idx++;
1555
+ $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy" id="tnp-' . $idx . '"> ';
1556
+ $buffer .= '<label for="tnp-' . $idx . '">';
1557
+ if (!empty($attrs['url'])) {
1558
+ $buffer .= '<a target="_blank" href="' . esc_attr($attrs['url']) . '">';
1559
+ }
1560
+ $buffer .= $attrs['label'];
1561
+ if (!empty($attrs['url'])) {
1562
+ $buffer .= '</a>';
1563
+ }
1564
+ $buffer .= '</label>';
1565
+ $buffer .= '</div>';
1566
+
1567
+ return $buffer;
1568
+ }
1569
+ }
1570
+
1571
+ /**
1572
+ * Builds the privacy field only for completely generated forms.
1573
+ *
1574
+ * @return string Empty id the privacy filed is not configured
1575
+ */
1576
+ function get_privacy_field($pre_html = '', $post_html = '') {
1577
+ $this->setup_form_options();
1578
+ $privacy_status = (int) $this->form_options['privacy_status'];
1579
+ if (empty($privacy_status)) {
1580
+ return '';
1581
+ }
1582
+
1583
+ $buffer = '<label>';
1584
+ if ($privacy_status === 1) {
1585
+ $buffer .= '<input type="checkbox" name="ny" required class="tnp-privacy">&nbsp;';
1586
+ }
1587
+ $url = $this->get_privacy_url();
1588
+ if (!empty($url)) {
1589
+ $buffer .= '<a target="_blank" href="' . esc_attr($url) . '">';
1590
+ $buffer .= esc_attr($this->form_options['privacy']) . '</a>';
1591
+ } else {
1592
+ $buffer .= esc_html($this->form_options['privacy']);
1593
+ }
1594
+
1595
+ $buffer .= "</label>";
1596
+
1597
+ return $pre_html . $buffer . $post_html;
1598
+ }
1599
+
1600
+ /**
1601
+ * The new standard form.
1602
+ *
1603
+ * @param string $referrer Deprecated since 6.9.1, use the "referrer" key on $attrs
1604
+ * @param string $action
1605
+ * @param string $attrs
1606
+ * @return string The full HTML form
1607
+ */
1608
+ function get_subscription_form($referrer = '', $action = null, $attrs = []) {
1609
+ $language = $this->get_current_language();
1610
+ $options_profile = $this->get_options('profile', $language);
1611
+
1612
+ if (!is_array($attrs)) {
1613
+ $attrs = [];
1614
+ }
1615
+
1616
+ // Possible alternative form actions (used by...?)
1617
+ if (isset($attrs['action'])) {
1618
+ $action = $attrs['action'];
1619
+ }
1620
+
1621
+ // The referrer parameter is deprecated
1622
+ if (!empty($referrer)) {
1623
+ $attrs['referrer'] = $referrer;
1624
+ }
1625
+
1626
+ $buffer = '';
1627
+
1628
+ if (empty($action)) {
1629
+ $action = $this->build_action_url('s');
1630
+ }
1631
+
1632
+ if (isset($attrs['before'])) {
1633
+ $buffer .= $attrs['before'];
1634
+ } else {
1635
+ if (isset($attrs['class'])) {
1636
+ $buffer .= '<div class="tnp tnp-subscription ' . $attrs['class'] . '">' . "\n";
1637
+ } else {
1638
+ $buffer .= '<div class="tnp tnp-subscription">' . "\n";
1639
+ }
1640
+ }
1641
+
1642
+ $buffer .= '<form method="post" action="' . esc_attr($action) . '">' . "\n\n";
1643
+
1644
+ $buffer .= $this->get_form_hidden_fields($attrs);
1645
+
1646
+ if ($options_profile['name_status'] == 2) {
1647
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'first_name']);
1648
+ }
1649
+
1650
+ if ($options_profile['surname_status'] == 2) {
1651
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'last_name']);
1652
+ }
1653
+
1654
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'email']);
1655
+
1656
+ if (isset($options_profile['sex_status']) && $options_profile['sex_status'] == 2) {
1657
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'gender']);
1658
+ }
1659
+
1660
+ // Extra profile fields
1661
+ for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
1662
+ // Not for subscription form
1663
+ if ($options_profile['profile_' . $i . '_status'] != 2) {
1664
+ continue;
1665
+ }
1666
+
1667
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'profile', 'number' => $i]);
1668
+ }
1669
+
1670
+ $tmp = '';
1671
+ $lists = $this->get_lists_for_subscription($language);
1672
+ if (!empty($attrs['lists_field_layout']) && $attrs['lists_field_layout'] == 'dropdown') {
1673
+ if (empty($attrs['lists_field_empty_label'])) {
1674
+ $attrs['lists_field_empty_label'] = '';
1675
+ }
1676
+ if (empty($attrs['lists_field_label'])) {
1677
+ $attrs['lists_field_label'] = '';
1678
+ }
1679
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'lists', 'layout' => 'dropdown', 'first_option_label' => $attrs['lists_field_empty_label'], 'label' => $attrs['lists_field_label']]);
1680
+ } else {
1681
+ $buffer .= $this->shortcode_newsletter_field(['name' => 'lists']);
1682
+ }
1683
+
1684
+
1685
+
1686
+ // Deprecated
1687
+ $extra = apply_filters('newsletter_subscription_extra', array());
1688
+ foreach ($extra as $x) {
1689
+ $label = $x['label'];
1690
+ if (empty($label)) {
1691
+ $label = '&nbsp;';
1692
+ }
1693
+ $name = '';
1694
+ if (!empty($x['name'])) {
1695
+ $name = $x['name'];
1696
+ }
1697
+ $buffer .= '<div class="tnp-field tnp-field-' . $name . '"><label>' . $label . "</label>";
1698
+ $buffer .= $x['field'] . "</div>\n";
1699
+ }
1700
+
1701
+ $buffer .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1702
+
1703
+ $buffer .= '<div class="tnp-field tnp-field-button">';
1704
+
1705
+ $button_style = '';
1706
+ if (!empty($attrs['button_color'])) {
1707
+ $button_style = 'style="background-color:' . esc_attr($attrs['button_color']) . '"';
1708
+ }
1709
+
1710
+ if (strpos($options_profile['subscribe'], 'http') === 0) {
1711
+ $buffer .= '<input class="tnp-submit-image" type="image" src="' . esc_attr($options_profile['subscribe']) . '">' . "\n";
1712
+ } else {
1713
+ $buffer .= '<input class="tnp-submit" type="submit" value="' . esc_attr($options_profile['subscribe']) . '" ' . $button_style . '>' . "\n";
1714
+ }
1715
+
1716
+ $buffer .= "</div>\n</form>\n";
1717
+
1718
+ if (isset($attrs['after'])) {
1719
+ $buffer .= $attrs['after'];
1720
+ } else {
1721
+ $buffer .= "</div>\n";
1722
+ }
1723
+
1724
+ return $buffer;
1725
+ }
1726
+
1727
+ function get_form($number) {
1728
+ $options = get_option('newsletter_forms');
1729
+
1730
+ $form = $options['form_' . $number];
1731
+
1732
+ $form = do_shortcode($form);
1733
+
1734
+ $action = $this->build_action_url('s');
1735
+
1736
+ if (stripos($form, '<form') === false) {
1737
+ $form = '<form method="post" action="' . $action . '">' . $form . '</form>';
1738
+ }
1739
+
1740
+ // For compatibility
1741
+ $form = str_replace('{newsletter_url}', $action, $form);
1742
+
1743
+ $form = $this->replace_lists($form);
1744
+
1745
+ return $form;
1746
+ }
1747
+
1748
+ /** Replaces on passed text the special tag {lists} that can be used to show the preferences as a list of checkbox.
1749
+ * They are called lists but on configuration panel they are named preferences!
1750
+ *
1751
+ * @param string $buffer
1752
+ * @return string
1753
+ */
1754
+ function replace_lists($buffer) {
1755
+ $checkboxes = '';
1756
+ $lists = $this->get_lists_for_subscription($this->get_current_language());
1757
+ foreach ($lists as $list) {
1758
+ $checkboxes .= '<input type="checkbox" name="nl[]" value="' . $list->id . '"/>&nbsp;' . $list->name . '<br />';
1759
+ }
1760
+ $buffer = str_replace('{lists}', $checkboxes, $buffer);
1761
+ $buffer = str_replace('{preferences}', $checkboxes, $buffer);
1762
+ return $buffer;
1763
+ }
1764
+
1765
+ function notify_admin_on_subscription($user) {
1766
+
1767
+ if (empty($this->options['notify'])) {
1768
+ return;
1769
+ }
1770
+
1771
+ $message = $this->generate_admin_notification_message($user);
1772
+ $email = trim($this->options['notify_email']);
1773
+ $subject = $this->generate_admin_notification_subject('New subscription');
1774
+
1775
+ Newsletter::instance()->mail($email, $subject, array('html' => $message));
1776
+ }
1777
+
1778
+ /**
1779
+ * Builds the minimal subscription form, with only the email field and inline
1780
+ * submit button. If enabled the privacy checkbox is added.
1781
+ *
1782
+ * @param type $attrs
1783
+ * @return string
1784
+ */
1785
+ function get_subscription_form_minimal($attrs) {
1786
+
1787
+ $this->setup_form_options();
1788
+
1789
+ if (!is_array($attrs)) {
1790
+ $attrs = [];
1791
+ }
1792
+
1793
+ $attrs = array_merge(array('class' => '', 'referrer' => 'minimal',
1794
+ 'button' => $this->form_options['subscribe'], 'button_color' => '',
1795
+ 'button_radius' => '', 'placeholder' => $this->form_options['email']), $attrs);
1796
+
1797
+ $form = '';
1798
+
1799
+ $form .= '<div class="tnp tnp-subscription-minimal ' . $attrs['class'] . '">';
1800
+ $form .= '<form action="' . esc_attr($this->build_action_url('s')) . '" method="post">';
1801
+
1802
+ $form .= $this->get_form_hidden_fields($attrs);
1803
+
1804
+ $form .= '<input class="tnp-email" type="email" required name="ne" value="" placeholder="' . esc_attr($attrs['placeholder']) . '">';
1805
+ $form .= '<input class="tnp-submit" type="submit" value="' . esc_attr($attrs['button']) . '"'
1806
+ . ' style="background-color:' . esc_attr($attrs['button_color']) . '">';
1807
+
1808
+ $form .= $this->get_privacy_field('<div class="tnp-field tnp-privacy-field">', '</div>');
1809
+
1810
+ $form .= "</form></div>\n";
1811
+
1812
+ return $form;
1813
+ }
1814
+
1815
+ function shortcode_newsletter_form($attrs, $content) {
1816
+
1817
+ if (isset($attrs['type']) && $attrs['type'] === 'minimal') {
1818
+ return $this->get_subscription_form_minimal($attrs);
1819
+ }
1820
+
1821
+ // Custom form using the [newsletter_field] shortcodes
1822
+ if (!empty($content)) {
1823
+ return $this->get_subscription_form_custom($attrs, $content);
1824
+ }
1825
+
1826
+ // Custom form hand coded and saved in the custom forms option
1827
+ if (isset($attrs['form'])) {
1828
+ return $this->get_form((int) $attrs['form']);
1829
+ }
1830
+
1831
+ // Custom hand coded form (as above, new syntax)
1832
+ if (isset($attrs['number'])) {
1833
+ return $this->get_form((int) $attrs['number']);
1834
+ }
1835
+
1836
+ return $this->get_subscription_form(null, null, $attrs);
1837
+ }
1838
+
1839
+ /**
1840
+ *
1841
+ * @global wpdb $wpdb
1842
+ * @param array $attrs
1843
+ * @param string $content
1844
+ * @return string
1845
+ */
1846
+ function shortcode_newsletter($attrs, $content) {
1847
+ global $wpdb;
1848
+
1849
+ $message_key = $this->get_message_key_from_request();
1850
+ if ($message_key == 'confirmation') {
1851
+ $user = $this->get_user_from_request(false, 'preconfirm');
1852
+ } else {
1853
+ $user = $this->get_user_from_request();
1854
+ }
1855
+
1856
+ $message = apply_filters('newsletter_page_text', '', $message_key, $user);
1857
+
1858
+ $options = $this->get_options('', $this->get_current_language($user));
1859
+
1860
+ if (empty($message)) {
1861
+ $message = $options[$message_key . '_text'];
1862
+
1863
+ // TODO: the if can be removed
1864
+ if ($message_key == 'confirmed') {
1865
+ $message .= $options[$message_key . '_tracking'];
1866
+ }
1867
+ }
1868
+
1869
+ // Now check what form must be added
1870
+ if ($message_key == 'subscription') {
1871
+ if (isset($attrs['show_form']) && $attrs['show_form'] === 'false') {
1872
+ //return $this->build_field_admin_notice('The [newsletter] shortcode is configured to not show the subscription form.');
1873
+ return;
1874
+ }
1875
+
1876
+ // Compatibility check
1877
+ if (stripos($message, '<form') !== false) {
1878
+ $message = str_ireplace('<form', '<form method="post" action="' . esc_attr($this->get_subscribe_url()) . '"', $message);
1879
+ } else {
1880
+
1881
+ if (strpos($message, '{subscription_form') === false) {
1882
+ $message .= '{subscription_form}';
1883
+ }
1884
+
1885
+ if (isset($attrs['form'])) {
1886
+ $message = str_replace('{subscription_form}', $this->get_form($attrs['form']), $message);
1887
+ } else {
1888
+ $message = str_replace('{subscription_form}', $this->get_subscription_form('page', null, $attrs), $message);
1889
+ }
1890
+ }
1891
+ }
1892
+
1893
+ $email = $this->get_email_from_request();
1894
+
1895
+ $message = $this->replace($message, $user, $email, 'page');
1896
+
1897
+ $message = do_shortcode($message);
1898
+
1899
+ if (isset($_REQUEST['alert'])) {
1900
+ // slashes are already added by wordpress!
1901
+ $message .= '<script>alert("' . esc_js(strip_tags($_REQUEST['alert'])) . '");</script>';
1902
+ }
1903
+
1904
+ return $message;
1905
+ }
1906
+
1907
+ }
1908
+
1909
+ NewsletterSubscription::instance();
1910
+
1911
+ // Compatibility code
1912
+
1913
+ /**
1914
+ * @deprecated
1915
+ * @param int $number
1916
+ */
1917
+ function newsletter_form($number = null) {
1918
+ if ($number != null) {
1919
+ echo NewsletterSubscription::instance()->get_form($number);
1920
+ } else {
1921
+ echo NewsletterSubscription::instance()->get_subscription_form();
1922
+ }
1923
+ }
tnp-header.php CHANGED
@@ -1,335 +1,335 @@
1
- <?php
2
- global $current_user, $wpdb;
3
-
4
- defined('ABSPATH') || exit;
5
-
6
- $dismissed = get_option('newsletter_dismissed', []);
7
-
8
- $user_count = Newsletter::instance()->get_user_count();
9
-
10
- $is_administrator = current_user_can('administrator');
11
-
12
- function newsletter_print_entries($group) {
13
- $entries = apply_filters('newsletter_menu_' . $group, array());
14
- if (!$entries) {
15
- return;
16
- }
17
-
18
- foreach ($entries as &$entry) {
19
- echo '<li><a href="', $entry['url'], '">', $entry['label'];
20
- if (!empty($entry['description'])) {
21
- echo '<small>', $entry['description'], '</small>';
22
- }
23
- echo '</a></li>';
24
- }
25
- }
26
-
27
- // Check the status to show a warning if needed
28
- $status_options = Newsletter::instance()->get_options('status');
29
- $warning = false;
30
-
31
- $warning |= empty($status_options['mail']);
32
- ?>
33
-
34
- <div class="tnp-drowpdown" id="tnp-header">
35
- <a href="?page=newsletter_main_index"><img src="<?php echo plugins_url('newsletter'); ?>/admin/images/logo-red.png" class="tnp-header-logo" style="vertical-align: bottom;"></a>
36
- <ul>
37
- <li><a href="#"><i class="fas fa-users"></i> <?php _e('Subscribers', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
38
- <ul>
39
- <li>
40
- <a href="?page=newsletter_users_index"><i class="fas fa-search"></i> <?php _e('Search And Edit', 'newsletter') ?>
41
- <small><?php _e('Add, edit, search', 'newsletter') ?></small></a>
42
- </li>
43
-
44
- <li>
45
- <a href="?page=newsletter_profile_index"><i class="fas fa-user-circle"></i> <?php _e('Profile page', 'newsletter') ?>
46
- <small><?php _e('The subscriber personal profile editing panel', 'newsletter') ?></small></a>
47
- </li>
48
-
49
- <?php if (!class_exists('NewsletterImport')) { ?>
50
- <li>
51
- <a href="?page=newsletter_users_import"><i class="fas fa-upload"></i> <?php _e('Import', 'newsletter') ?>
52
- <small><?php _e('Import from external sources', 'newsletter') ?></small></a>
53
- </li>
54
- <?php } ?>
55
-
56
- <li>
57
- <a href="?page=newsletter_users_export"><i class="fas fa-download"></i> <?php _e('Export', 'newsletter') ?>
58
- <small><?php _e('Export your subscribers list', 'newsletter') ?></small></a>
59
- </li>
60
-
61
- <li>
62
- <a href="?page=newsletter_users_massive"><i class="fas fa-wrench"></i> <?php _e('Maintenance', 'newsletter') ?>
63
- <small><?php _e('Massive actions: change list, clean up, ...', 'newsletter') ?></small></a>
64
- </li>
65
-
66
- <li>
67
- <a href="?page=newsletter_users_statistics"><i class="fas fa-chart-bar"></i> <?php _e('Statistics', 'newsletter') ?>
68
- <small><?php _e('All about your subscribers', 'newsletter') ?></small></a>
69
- </li>
70
-
71
- <?php newsletter_print_entries('subscribers') ?>
72
- </ul>
73
- </li>
74
- <li><a href="#"><i class="fas fa-list"></i> <?php _e('List Building', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
75
- <ul>
76
- <li>
77
- <a href="?page=newsletter_subscription_profile"><i class="fas fa-check-square"></i> <?php _e('Subscription Form Fields, Buttons, Labels', 'newsletter') ?>
78
- <small><?php _e('When and what data to collect', 'newsletter') ?></small></a>
79
- </li>
80
-
81
- <li>
82
- <a href="?page=newsletter_subscription_options"><i class="fas fa-sign-in-alt"></i> <?php _e('Subscription', 'newsletter') ?>
83
- <small><?php _e('The subscription process in detail', 'newsletter') ?></small></a>
84
- </li>
85
-
86
- <li>
87
- <a href="?page=newsletter_subscription_lists"><i class="fas fa-th-list"></i> <?php _e('Lists', 'newsletter') ?>
88
- <small><?php _e('Profile the subscribers for a better targeting', 'newsletter') ?></small></a>
89
- </li>
90
-
91
- <li>
92
- <a href="?page=newsletter_subscription_antibot"><i class="fas fa-lock"></i> <?php _e('Security', 'newsletter') ?>
93
- <small><?php _e('Spam subscriptions control', 'newsletter') ?></small></a>
94
- </li>
95
-
96
- <li>
97
- <a href="?page=newsletter_unsubscription_index"><i class="fas fa-sign-out-alt"></i> <?php _e('Unsubscription', 'newsletter') ?>
98
- <small><?php _e('How to give the last goodbye (or avoid it!)', 'newsletter') ?></small></a>
99
- </li>
100
-
101
- <?php
102
- newsletter_print_entries('subscription');
103
- ?>
104
- </ul>
105
- </li>
106
-
107
- <li>
108
- <a href="#"><i class="fas fa-newspaper"></i> <?php _e('Newsletters', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
109
- <ul>
110
- <li>
111
- <a href="?page=newsletter_emails_composer"><i class="fas fa-plus"></i> <?php _e('Create newsletter', 'newsletter') ?>
112
- <small><?php _e('Start your new campaign', 'newsletter') ?></small></a>
113
- </li>
114
-
115
- <li>
116
- <a href="?page=newsletter_emails_index"><i class="fas fa-newspaper"></i> <?php _e('Newsletters', 'newsletter') ?>
117
- <small><?php _e('The classic "write & send" newsletters', 'newsletter') ?></small></a>
118
- </li>
119
-
120
- <li>
121
- <a href="<?php echo NewsletterStatistics::instance()->get_index_url() ?>"><i class="fas fa-chart-bar"></i> <?php _e('Statistics', 'newsletter') ?>
122
- <small><?php _e('Tracking configuration and basic data', 'newsletter') ?></small></a>
123
- </li>
124
- <?php
125
- newsletter_print_entries('newsletters');
126
- ?>
127
- </ul>
128
- </li>
129
-
130
- <li>
131
- <a href="#"><i class="fas fa-cog"></i> <?php _e('Settings', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
132
- <ul>
133
- <?php if ($is_administrator) { ?>
134
- <li>
135
- <a href="?page=newsletter_main_main"><i class="fas fa-cogs"></i> <?php _e('General Settings', 'newsletter') ?>
136
- <small><?php _e('Delivery speed, sender details, ...', 'newsletter') ?></small></a>
137
- </li>
138
- <?php if (!class_exists('NewsletterSmtp')) { ?>
139
- <li>
140
- <a href="?page=newsletter_main_smtp"><i class="fas fa-server"></i> <?php _e('SMTP', 'newsletter') ?>
141
- <small><?php _e('External mail server', 'newsletter') ?></small>
142
- </a>
143
- </li>
144
- <?php } ?>
145
- <?php } ?>
146
-
147
- <li>
148
- <a href="?page=newsletter_main_info"><i class="fas fa-info"></i> <?php _e('Company Info', 'newsletter') ?>
149
- <small><?php _e('Social, address, logo and general info', 'newsletter') ?></small></a>
150
- </li>
151
-
152
- <li>
153
- <a href="?page=newsletter_subscription_template"><i class="fas fa-file-alt"></i> <?php _e('Messages Template', 'newsletter') ?>
154
- <small><?php _e('Change the look of your service emails', 'newsletter') ?></small></a>
155
- </li>
156
-
157
- <?php
158
- newsletter_print_entries('settings');
159
- ?>
160
- </ul>
161
- </li>
162
-
163
- <?php if ($is_administrator) { ?>
164
- <li>
165
- <a href="#"><i class="fas fa-thermometer"></i> <?php _e('System', 'newsletter') ?>
166
- <?php if ($warning) { ?>
167
- <i class="fas fa-exclamation-triangle" style="color: red;"></i>
168
- <?php } ?>
169
- </a>
170
- <ul>
171
- <li>
172
- <a href="<?php echo admin_url('site-health.php') ?> "><i class="fas fa-file"></i> <?php _e('Site health') ?>
173
- <small><?php _e('WP native site health checks', 'newsletter') ?></small></a>
174
- </li>
175
- <li>
176
- <a href="?page=newsletter_system_delivery"><i class="fas fa-file"></i> <?php _e('Delivery Diagnostic', 'newsletter') ?>
177
- <small><?php _e('Delivery analysis and test', 'newsletter') ?></small></a>
178
- </li>
179
- <li>
180
- <a href="?page=newsletter_system_scheduler"><i class="fas fa-robot"></i> <?php _e('Scheduler', 'newsletter') ?>
181
- <small><?php _e('WordPress background jobs', 'newsletter') ?></small></a>
182
- </li>
183
- <li>
184
- <a href="?page=newsletter_system_status"><i class="fas fa-file"></i> <?php _e('Status', 'newsletter') ?>
185
- <small><?php _e('Checks and parameters', 'newsletter') ?></small></a>
186
- </li>
187
-
188
- <li>
189
- <a href="?page=newsletter_system_logs"><i class="fas fa-file"></i> <?php _e('Logs', 'newsletter') ?>
190
- <small><?php _e('Plugin and addons logs', 'newsletter') ?></small></a>
191
- </li>
192
- </ul>
193
- </li>
194
- <?php } ?>
195
-
196
- <?php
197
- $license_data = Newsletter::instance()->get_license_data();
198
- $premium_url = 'https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=plugin&utm_content=' . urlencode($_GET['page']);
199
- ?>
200
-
201
- <?php if (empty($license_data)) { ?>
202
- <?php if (time() < 1638226799) { ?>
203
- <li class="tnp-professional-extensions-button" style="background-color: #000; color: #fff; border-color: #FF5F65!important; border-width: 2px !important;"><a href="https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=black-friday" target="_blank">
204
- <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
205
- </li>
206
- <?php } else { ?>
207
- <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
208
- <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
209
- </li>
210
- <?php } ?>
211
- <?php } elseif (is_wp_error($license_data)) { ?>
212
- <li class="tnp-professional-extensions-button-red">
213
- <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('Unable to check', 'newsletter') ?></a>
214
- </li>
215
-
216
- <?php } elseif ($license_data->expire == 0) { ?>
217
- <?php if (time() < 1638226799) { ?>
218
- <li class="tnp-professional-extensions-button" style="background-color: #000; color: #fff; border-color: #FF5F65!important; border-width: 2px !important;"><a href="https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=black-friday" target="_blank">
219
- <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
220
- </li>
221
- <?php } else { ?>
222
- <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
223
- <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
224
- </li>
225
- <?php } ?>
226
-
227
- <?php } elseif ($license_data->expire < time()) { ?>
228
-
229
- <li class="tnp-professional-extensions-button-red">
230
- <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('License expired', 'newsletter') ?></a>
231
- </li>
232
-
233
- <?php } elseif ($license_data->expire >= time()) { ?>
234
-
235
- <?php $p = class_exists('NewsletterExtensions') ? 'newsletter_extensions_index' : 'newsletter_main_extensions'; ?>
236
- <li class="tnp-professional-extensions-button">
237
- <a href="?page=<?php echo $p ?>"><i class="fas fa-check-square"></i> <?php _e('License active', 'newsletter') ?></a>
238
- </li>
239
-
240
- <?php } ?>
241
- </ul>
242
- </div>
243
-
244
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-shortcode'])) { ?>
245
- <?php
246
- // Check of Newsletter dedicated page
247
- if (!empty(Newsletter::instance()->options['page'])) {
248
- if (get_post_status(Newsletter::instance()->options['page']) === 'publish') {
249
- $content = get_post_field('post_content', Newsletter::instance()->options['page']);
250
- // With and without attributes
251
- if (strpos($content, '[newsletter]') === false && strpos($content, '[newsletter ') === false) {
252
- ?>
253
- <div class="tnp-notice">
254
- <a href="<?php echo $_SERVER['REQUEST_URI'] . '&noheader=1&dismiss=newsletter-shortcode' ?>" class="tnp-dismiss">&times;</a>
255
- The Newsletter dedicated page does not contain the [newsletter] shortcode. If you're using a visual composer it could be ok.
256
- <a href="<?php echo site_url('/wp-admin/post.php') ?>?post=<?php echo esc_attr(Newsletter::instance()->options['page']) ?>&action=edit"><strong>Edit the page</strong></a>.
257
-
258
- </div>
259
- <?php
260
- }
261
- }
262
- }
263
- ?>
264
- <?php } ?>
265
-
266
- <?php if (isset($_GET['debug']) || !isset($dismissed['rate']) && $user_count > 300) { ?>
267
- <div class="tnp-notice">
268
- <a href="<?php echo $_SERVER['REQUEST_URI'] . '&noheader=1&dismiss=rate' ?>" class="tnp-dismiss">&times;</a>
269
-
270
- We never asked before and we're curious: <a href="http://wordpress.org/plugins/newsletter/" target="_blank">would you rate this plugin</a>?
271
- (few seconds required - account on WordPress.org required, every blog owner should have one...). <strong>Really appreciated, The Newsletter Team</strong>.
272
-
273
- </div>
274
- <?php } ?>
275
-
276
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-page']) && empty(Newsletter::instance()->options['page'])) { ?>
277
- <div class="tnp-notice">
278
- <a href="<?php echo $_SERVER['REQUEST_URI'] . '&noheader=1&dismiss=newsletter-page' ?>" class="tnp-dismiss">&times;</a>
279
-
280
- You should create a blog page to show the subscription form and the subscription messages. Go to the
281
- <a href="?page=newsletter_main_main">general settings panel</a> to configure it.
282
-
283
- </div>
284
- <?php } ?>
285
-
286
- <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-subscribe']) && get_option('newsletter_install_time') && get_option('newsletter_install_time') < time() - 86400 * 15) { ?>
287
- <div class="tnp-notice">
288
- <a href="<?php echo $_SERVER['REQUEST_URI'] . '&noheader=1&dismiss=newsletter-subscribe' ?>" class="tnp-dismiss">&times;</a>
289
- If you want to be informed of important updates of Newsletter, you may want to subscribe to our newsletter<br>
290
- <form action="https://www.thenewsletterplugin.com/?na=s" target="_blank" method="post">
291
- <input type="hidden" value="plugin-header" name="nr">
292
- <input type="hidden" value="3" name="nl[]">
293
- <input type="hidden" value="single" name="optin">
294
- <input type="email" name="ne" value="<?php echo esc_attr($current_user->user_email) ?>">
295
- <input type="submit" value="<?php esc_attr_e('Subscribe', 'newsletter') ?>">
296
- </form>
297
- </div>
298
- <?php } ?>
299
-
300
- <?php
301
- if (!defined('NEWSLETTER_CRON_WARNINGS') || NEWSLETTER_CRON_WARNINGS) {
302
- $x = NewsletterSystem::instance()->get_job_status();
303
- if ($x !== NewsletterSystem::JOB_OK) {
304
- echo '<div class="tnpc-warning">The are issues with the delivery engine. Please <a href="?page=newsletter_system_scheduler">check them here</a>.</div>';
305
- }
306
- }
307
- ?>
308
-
309
- <?php
310
- if ($_GET['page'] !== 'newsletter_emails_edit') {
311
-
312
- $last_failed_newsletters = Newsletter::instance()->get_emails_by_field('status', TNP_Email::STATUS_ERROR);
313
-
314
- foreach ($last_failed_newsletters as $newsletter) {
315
- echo '<div class="tnpc-error">';
316
- printf(__('Newsletter "%s" stopped by fatal error.', 'newsletter'), esc_html($newsletter->subject));
317
- echo '&nbsp;';
318
- $c = new NewsletterControls();
319
- $c->btn_link('?page=newsletter_emails_edit&id=' . $newsletter->id, __('Check', 'newsletter'));
320
- echo '</div>';
321
- }
322
- }
323
- ?>
324
-
325
- <div id="tnp-notification">
326
- <?php
327
- if (isset($controls)) {
328
- $controls->show();
329
- $controls->messages = '';
330
- $controls->errors = '';
331
- }
332
- ?>
333
- </div>
334
-
335
-
1
+ <?php
2
+ global $current_user, $wpdb;
3
+
4
+ defined('ABSPATH') || exit;
5
+
6
+ $dismissed = get_option('newsletter_dismissed', []);
7
+
8
+ $user_count = Newsletter::instance()->get_user_count();
9
+
10
+ $is_administrator = current_user_can('administrator');
11
+
12
+ function newsletter_print_entries($group) {
13
+ $entries = apply_filters('newsletter_menu_' . $group, array());
14
+ if (!$entries) {
15
+ return;
16
+ }
17
+
18
+ foreach ($entries as &$entry) {
19
+ echo '<li><a href="', $entry['url'], '">', $entry['label'];
20
+ if (!empty($entry['description'])) {
21
+ echo '<small>', $entry['description'], '</small>';
22
+ }
23
+ echo '</a></li>';
24
+ }
25
+ }
26
+
27
+ // Check the status to show a warning if needed
28
+ $status_options = Newsletter::instance()->get_options('status');
29
+ $warning = false;
30
+
31
+ //$warning |= empty($status_options['mail']);
32
+ ?>
33
+
34
+ <div class="tnp-drowpdown" id="tnp-header">
35
+ <a href="?page=newsletter_main_index"><img src="<?php echo plugins_url('newsletter'); ?>/admin/images/logo-red.png" class="tnp-header-logo" style="vertical-align: bottom;"></a>
36
+ <ul>
37
+ <li><a href="#"><i class="fas fa-users"></i> <?php _e('Subscribers', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
38
+ <ul>
39
+ <li>
40
+ <a href="?page=newsletter_users_index"><i class="fas fa-search"></i> <?php _e('Search And Edit', 'newsletter') ?>
41
+ <small><?php _e('Add, edit, search', 'newsletter') ?></small></a>
42
+ </li>
43
+
44
+ <li>
45
+ <a href="?page=newsletter_profile_index"><i class="fas fa-user-circle"></i> <?php _e('Profile page', 'newsletter') ?>
46
+ <small><?php _e('The subscriber personal profile editing panel', 'newsletter') ?></small></a>
47
+ </li>
48
+
49
+ <?php if (!class_exists('NewsletterImport')) { ?>
50
+ <li>
51
+ <a href="?page=newsletter_users_import"><i class="fas fa-upload"></i> <?php _e('Import', 'newsletter') ?>
52
+ <small><?php _e('Import from external sources', 'newsletter') ?></small></a>
53
+ </li>
54
+ <?php } ?>
55
+
56
+ <li>
57
+ <a href="?page=newsletter_users_export"><i class="fas fa-download"></i> <?php _e('Export', 'newsletter') ?>
58
+ <small><?php _e('Export your subscribers list', 'newsletter') ?></small></a>
59
+ </li>
60
+
61
+ <li>
62
+ <a href="?page=newsletter_users_massive"><i class="fas fa-wrench"></i> <?php _e('Maintenance', 'newsletter') ?>
63
+ <small><?php _e('Massive actions: change list, clean up, ...', 'newsletter') ?></small></a>
64
+ </li>
65
+
66
+ <li>
67
+ <a href="?page=newsletter_users_statistics"><i class="fas fa-chart-bar"></i> <?php _e('Statistics', 'newsletter') ?>
68
+ <small><?php _e('All about your subscribers', 'newsletter') ?></small></a>
69
+ </li>
70
+
71
+ <?php newsletter_print_entries('subscribers') ?>
72
+ </ul>
73
+ </li>
74
+ <li><a href="#"><i class="fas fa-list"></i> <?php _e('List Building', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
75
+ <ul>
76
+ <li>
77
+ <a href="?page=newsletter_subscription_profile"><i class="fas fa-check-square"></i> <?php _e('Subscription Form Fields, Buttons, Labels', 'newsletter') ?>
78
+ <small><?php _e('When and what data to collect', 'newsletter') ?></small></a>
79
+ </li>
80
+
81
+ <li>
82
+ <a href="?page=newsletter_subscription_options"><i class="fas fa-sign-in-alt"></i> <?php _e('Subscription', 'newsletter') ?>
83
+ <small><?php _e('The subscription process in detail', 'newsletter') ?></small></a>
84
+ </li>
85
+
86
+ <li>
87
+ <a href="?page=newsletter_subscription_lists"><i class="fas fa-th-list"></i> <?php _e('Lists', 'newsletter') ?>
88
+ <small><?php _e('Profile the subscribers for a better targeting', 'newsletter') ?></small></a>
89
+ </li>
90
+
91
+ <li>
92
+ <a href="?page=newsletter_subscription_antibot"><i class="fas fa-lock"></i> <?php _e('Security', 'newsletter') ?>
93
+ <small><?php _e('Spam subscriptions control', 'newsletter') ?></small></a>
94
+ </li>
95
+
96
+ <li>
97
+ <a href="?page=newsletter_unsubscription_index"><i class="fas fa-sign-out-alt"></i> <?php _e('Unsubscription', 'newsletter') ?>
98
+ <small><?php _e('How to give the last goodbye (or avoid it!)', 'newsletter') ?></small></a>
99
+ </li>
100
+
101
+ <?php
102
+ newsletter_print_entries('subscription');
103
+ ?>
104
+ </ul>
105
+ </li>
106
+
107
+ <li>
108
+ <a href="#"><i class="fas fa-newspaper"></i> <?php _e('Newsletters', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
109
+ <ul>
110
+ <li>
111
+ <a href="?page=newsletter_emails_composer"><i class="fas fa-plus"></i> <?php _e('Create newsletter', 'newsletter') ?>
112
+ <small><?php _e('Start your new campaign', 'newsletter') ?></small></a>
113
+ </li>
114
+
115
+ <li>
116
+ <a href="?page=newsletter_emails_index"><i class="fas fa-newspaper"></i> <?php _e('Newsletters', 'newsletter') ?>
117
+ <small><?php _e('The classic "write & send" newsletters', 'newsletter') ?></small></a>
118
+ </li>
119
+
120
+ <li>
121
+ <a href="<?php echo NewsletterStatistics::instance()->get_index_url() ?>"><i class="fas fa-chart-bar"></i> <?php _e('Statistics', 'newsletter') ?>
122
+ <small><?php _e('Tracking configuration and basic data', 'newsletter') ?></small></a>
123
+ </li>
124
+ <?php
125
+ newsletter_print_entries('newsletters');
126
+ ?>
127
+ </ul>
128
+ </li>
129
+
130
+ <li>
131
+ <a href="#"><i class="fas fa-cog"></i> <?php _e('Settings', 'newsletter') ?> <i class="fas fa-chevron-down"></i></a>
132
+ <ul>
133
+ <?php if ($is_administrator) { ?>
134
+ <li>
135
+ <a href="?page=newsletter_main_main"><i class="fas fa-cogs"></i> <?php _e('General Settings', 'newsletter') ?>
136
+ <small><?php _e('Delivery speed, sender details, ...', 'newsletter') ?></small></a>
137
+ </li>
138
+ <?php if (!class_exists('NewsletterSmtp')) { ?>
139
+ <li>
140
+ <a href="?page=newsletter_main_smtp"><i class="fas fa-server"></i> <?php _e('SMTP', 'newsletter') ?>
141
+ <small><?php _e('External mail server', 'newsletter') ?></small>
142
+ </a>
143
+ </li>
144
+ <?php } ?>
145
+ <?php } ?>
146
+
147
+ <li>
148
+ <a href="?page=newsletter_main_info"><i class="fas fa-info"></i> <?php _e('Company Info', 'newsletter') ?>
149
+ <small><?php _e('Social, address, logo and general info', 'newsletter') ?></small></a>
150
+ </li>
151
+
152
+ <li>
153
+ <a href="?page=newsletter_subscription_template"><i class="fas fa-file-alt"></i> <?php _e('Messages Template', 'newsletter') ?>
154
+ <small><?php _e('Change the look of your service emails', 'newsletter') ?></small></a>
155
+ </li>
156
+
157
+ <?php
158
+ newsletter_print_entries('settings');
159
+ ?>
160
+ </ul>
161
+ </li>
162
+
163
+ <?php if ($is_administrator) { ?>
164
+ <li>
165
+ <a href="#"><i class="fas fa-thermometer"></i> <?php _e('System', 'newsletter') ?>
166
+ <?php if ($warning) { ?>
167
+ <i class="fas fa-exclamation-triangle" style="color: red;"></i>
168
+ <?php } ?>
169
+ </a>
170
+ <ul>
171
+ <li>
172
+ <a href="<?php echo admin_url('site-health.php') ?> "><i class="fas fa-file"></i> <?php _e('Site health') ?>
173
+ <small><?php _e('WP native site health checks', 'newsletter') ?></small></a>
174
+ </li>
175
+ <li>
176
+ <a href="?page=newsletter_system_delivery"><i class="fas fa-file"></i> <?php _e('Delivery Diagnostic', 'newsletter') ?>
177
+ <small><?php _e('Delivery analysis and test', 'newsletter') ?></small></a>
178
+ </li>
179
+ <li>
180
+ <a href="?page=newsletter_system_scheduler"><i class="fas fa-robot"></i> <?php _e('Scheduler', 'newsletter') ?>
181
+ <small><?php _e('WordPress background jobs', 'newsletter') ?></small></a>
182
+ </li>
183
+ <li>
184
+ <a href="?page=newsletter_system_status"><i class="fas fa-file"></i> <?php _e('Status', 'newsletter') ?>
185
+ <small><?php _e('Checks and parameters', 'newsletter') ?></small></a>
186
+ </li>
187
+
188
+ <li>
189
+ <a href="?page=newsletter_system_logs"><i class="fas fa-file"></i> <?php _e('Logs', 'newsletter') ?>
190
+ <small><?php _e('Plugin and addons logs', 'newsletter') ?></small></a>
191
+ </li>
192
+ </ul>
193
+ </li>
194
+ <?php } ?>
195
+
196
+ <?php
197
+ $license_data = Newsletter::instance()->get_license_data();
198
+ $premium_url = 'https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=plugin&utm_content=' . urlencode($_GET['page']);
199
+ ?>
200
+
201
+ <?php if (empty($license_data)) { ?>
202
+ <?php if (time() < 1638226799) { ?>
203
+ <li class="tnp-professional-extensions-button" style="background-color: #000; color: #fff; border-color: #FF5F65!important; border-width: 2px !important;"><a href="https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=black-friday" target="_blank">
204
+ <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
205
+ </li>
206
+ <?php } else { ?>
207
+ <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
208
+ <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
209
+ </li>
210
+ <?php } ?>
211
+ <?php } elseif (is_wp_error($license_data)) { ?>
212
+ <li class="tnp-professional-extensions-button-red">
213
+ <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('Unable to check', 'newsletter') ?></a>
214
+ </li>
215
+
216
+ <?php } elseif ($license_data->expire == 0) { ?>
217
+ <?php if (time() < 1638226799) { ?>
218
+ <li class="tnp-professional-extensions-button" style="background-color: #000; color: #fff; border-color: #FF5F65!important; border-width: 2px !important;"><a href="https://www.thenewsletterplugin.com/premium?utm_source=header&utm_medium=link&utm_campaign=black-friday" target="_blank">
219
+ <i class="fas fa-trophy"></i> BLACK FRIDAY IS LIVE</a>
220
+ </li>
221
+ <?php } else { ?>
222
+ <li class="tnp-professional-extensions-button"><a href="<?php echo $premium_url ?>" target="_blank">
223
+ <i class="fas fa-trophy"></i> <?php _e('Get Professional Addons', 'newsletter') ?></a>
224
+ </li>
225
+ <?php } ?>
226
+
227
+ <?php } elseif ($license_data->expire < time()) { ?>
228
+
229
+ <li class="tnp-professional-extensions-button-red">
230
+ <a href="?page=newsletter_main_main"><i class="fas fa-hand-paper" style="color: white"></i> <?php _e('License expired', 'newsletter') ?></a>
231
+ </li>
232
+
233
+ <?php } elseif ($license_data->expire >= time()) { ?>
234
+
235
+ <?php $p = class_exists('NewsletterExtensions') ? 'newsletter_extensions_index' : 'newsletter_main_extensions'; ?>
236
+ <li class="tnp-professional-extensions-button">
237
+ <a href="?page=<?php echo $p ?>"><i class="fas fa-check-square"></i> <?php _e('License active', 'newsletter') ?></a>
238
+ </li>
239
+
240
+ <?php } ?>
241
+ </ul>
242
+ </div>
243
+
244
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-shortcode'])) { ?>
245
+ <?php
246
+ // Check of Newsletter dedicated page
247
+ if (!empty(Newsletter::instance()->options['page'])) {
248
+ if (get_post_status(Newsletter::instance()->options['page']) === 'publish') {
249
+ $content = get_post_field('post_content', Newsletter::instance()->options['page']);
250
+ // With and without attributes
251
+ if (strpos($content, '[newsletter]') === false && strpos($content, '[newsletter ') === false) {
252
+ ?>
253
+ <div class="tnp-notice">
254
+ <a href="<?php echo $_SERVER['REQUEST_URI'] . '&noheader=1&dismiss=newsletter-shortcode' ?>" class="tnp-dismiss">&times;</a>
255
+ The Newsletter dedicated page does not contain the [newsletter] shortcode. If you're using a visual composer it could be ok.
256
+ <a href="<?php echo site_url('/wp-admin/post.php') ?>?post=<?php echo esc_attr(Newsletter::instance()->options['page']) ?>&action=edit"><strong>Edit the page</strong></a>.
257
+
258
+ </div>
259
+ <?php
260
+ }
261
+ }
262
+ }
263
+ ?>
264
+ <?php } ?>
265
+
266
+ <?php if (isset($_GET['debug']) || !isset($dismissed['rate']) && $user_count > 300) { ?>
267
+ <div class="tnp-notice">
268
+ <a href="<?php echo $_SERVER['REQUEST_URI'] . '&noheader=1&dismiss=rate' ?>" class="tnp-dismiss">&times;</a>
269
+
270
+ We never asked before and we're curious: <a href="http://wordpress.org/plugins/newsletter/" target="_blank">would you rate this plugin</a>?
271
+ (few seconds required - account on WordPress.org required, every blog owner should have one...). <strong>Really appreciated, The Newsletter Team</strong>.
272
+
273
+ </div>
274
+ <?php } ?>
275
+
276
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-page']) && empty(Newsletter::instance()->options['page'])) { ?>
277
+ <div class="tnp-notice">
278
+ <a href="<?php echo $_SERVER['REQUEST_URI'] . '&noheader=1&dismiss=newsletter-page' ?>" class="tnp-dismiss">&times;</a>
279
+
280
+ You should create a blog page to show the subscription form and the subscription messages. Go to the
281
+ <a href="?page=newsletter_main_main">general settings panel</a> to configure it.
282
+
283
+ </div>
284
+ <?php } ?>
285
+
286
+ <?php if (isset($_GET['debug']) || !isset($dismissed['newsletter-subscribe']) && get_option('newsletter_install_time') && get_option('newsletter_install_time') < time() - 86400 * 15) { ?>
287
+ <div class="tnp-notice">
288
+ <a href="<?php echo $_SERVER['REQUEST_URI'] . '&noheader=1&dismiss=newsletter-subscribe' ?>" class="tnp-dismiss">&times;</a>
289
+ If you want to be informed of important updates of Newsletter, you may want to subscribe to our newsletter<br>
290
+ <form action="https://www.thenewsletterplugin.com/?na=s" target="_blank" method="post">
291
+ <input type="hidden" value="plugin-header" name="nr">
292
+ <input type="hidden" value="3" name="nl[]">
293
+ <input type="hidden" value="single" name="optin">
294
+ <input type="email" name="ne" value="<?php echo esc_attr($current_user->user_email) ?>">
295
+ <input type="submit" value="<?php esc_attr_e('Subscribe', 'newsletter') ?>">
296
+ </form>
297
+ </div>
298
+ <?php } ?>
299
+
300
+ <?php
301
+ if (!defined('NEWSLETTER_CRON_WARNINGS') || NEWSLETTER_CRON_WARNINGS) {
302
+ $x = NewsletterSystem::instance()->get_job_status();
303
+ if ($x !== NewsletterSystem::JOB_OK) {
304
+ echo '<div class="tnpc-warning">The are issues with the delivery engine. Please <a href="?page=newsletter_system_scheduler">check them here</a>.</div>';
305
+ }
306
+ }
307
+ ?>
308
+
309
+ <?php
310
+ if ($_GET['page'] !== 'newsletter_emails_edit') {
311
+
312
+ $last_failed_newsletters = Newsletter::instance()->get_emails_by_field('status', TNP_Email::STATUS_ERROR);
313
+
314
+ foreach ($last_failed_newsletters as $newsletter) {
315
+ echo '<div class="tnpc-error">';
316
+ printf(__('Newsletter "%s" stopped by fatal error.', 'newsletter'), esc_html($newsletter->subject));
317
+ echo '&nbsp;';
318
+ $c = new NewsletterControls();
319
+ $c->btn_link('?page=newsletter_emails_edit&id=' . $newsletter->id, __('Check', 'newsletter'));
320
+ echo '</div>';
321
+ }
322
+ }
323
+ ?>
324
+
325
+ <div id="tnp-notification">
326
+ <?php
327
+ if (isset($controls)) {
328
+ $controls->show();
329
+ $controls->messages = '';
330
+ $controls->errors = '';
331
+ }
332
+ ?>
333
+ </div>
334
+
335
+