PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | front/FrontControllers/FrontusersController.php +299 -71 10.2.2 → 11.1.0 View file →
@@ -4,12 +4,15 @@
4 4
5 5 use AcyMailing\Classes\FieldClass;
6 6 use AcyMailing\Classes\ListClass;
7 7 use AcyMailing\Classes\MailClass;
8 +use AcyMailing\Classes\ScenarioClass;
8 9 use AcyMailing\Classes\UserClass;
10 +use AcyMailing\Classes\UserStatClass;
9 11 use AcyMailing\Controllers\UsersController;
10 12 use AcyMailing\Helpers\CaptchaHelper;
11 13 use AcyMailing\Helpers\EntitySelectHelper;
14 +use AcyMailing\Helpers\ExportHelper;
12 15 use AcyMailing\Helpers\ToolbarHelper;
13 16 use AcyMailing\Helpers\UserHelper;
14 17 use AcyMailing\Core\AcymParameter;
15 18
@@ -18,9 +21,9 @@
18 21 public function __construct()
19 22 {
20 23 parent::__construct();
21 24
22 - if (ACYM_CMS == 'joomla') {
25 + if (ACYM_CMS === 'joomla') {
23 26 $menu = acym_getMenu();
24 27 if (is_object($menu)) {
25 28 $params = method_exists($menu, 'getParams') ? $menu->getParams() : $menu->params;
26 29 $menuParams = new AcymParameter($params);
@@ -31,8 +34,9 @@
31 34 $this->publicFrontTasks = [
32 35 'subscribe',
33 36 'unsubscribe',
34 37 'unsubscribeAll',
38 + 'disableTracking',
35 39 'saveSubscriptions',
36 40 'unsubscribePage',
37 41 'confirm',
38 42 'profile',
@@ -47,12 +51,18 @@
47 51
48 52 private function displayMessage(string $message, bool $ajax, string $type = 'error'): void
49 53 {
50 54 if ($ajax) {
51 - echo '{"message":"'.acym_translation($message, true).'","type":"'.$type.'","code":"1"}';
55 + echo json_encode(
56 + [
57 + 'message' => acym_translation($message),
58 + 'type' => $type,
59 + 'code' => '1',
60 + ]
61 + );
52 62 } else {
53 63 acym_header('Content-type:text/html; charset=utf-8');
54 - echo '<script>alert("'.acym_translation($message, true).'"); window.history.go(-1);</script>';
64 + echo '<script>alert("'.esc_html(acym_translation($message)).'"); window.history.go(-1);</script>';
55 65 }
56 66 exit;
57 67 }
58 68
@@ -59,22 +69,33 @@
59 69 public function subscribe(): void
60 70 {
61 71 acym_checkRobots();
62 72
63 - if (!acym_getVar('string', 'acy_source') && !empty($_GET['user'])) {
73 + $formData = acym_getVar('array', 'user', []);
74 + if (!acym_getVar('string', 'acy_source') && !empty($formData)) {
75 + // Coming from a subscription via url...
64 76 acym_setVar('acy_source', 'url');
65 77 }
66 78
79 + // Do we have to return an ajax response or a web page ?
67 80 $ajax = acym_getVar('int', 'ajax', 0);
68 81 if ($ajax) {
82 + //in case of the page displays some warnings or whatever
69 83 @ob_end_clean();
70 - acym_header('Content-type:text/html; charset=utf-8');
84 + acym_header('Content-type:application/json; charset=utf-8');
71 85 }
72 86
87 + //We only allow logged in users and this user is not logged it...
73 88 $currentUserid = acym_currentUserId();
74 89 if ((int)$this->config->get('allow_visitor', 1) != 1 && empty($currentUserid)) {
75 90 if ($ajax) {
76 - echo '{"message":"'.acym_translation('ACYM_ONLY_LOGGED', true).'","type":"error","code":"0"}';
91 + echo json_encode(
92 + [
93 + 'message' => acym_translation('ACYM_ONLY_LOGGED'),
94 + 'type' => 'error',
95 + 'code' => '0',
96 + ]
97 + );
77 98 exit;
78 99 } else {
79 100 acym_askLog(false, 'ACYM_ONLY_LOGGED');
80 101
@@ -88,9 +109,8 @@
88 109 $this->displayMessage('ACYM_WRONG_CAPTCHA', $ajax);
89 110 }
90 111 }
91 112
92 - $formData = acym_getVar('array', 'user', [], '');
93 113 $user = new \stdClass();
94 114 if (!empty($formData['email'])) {
95 115 $user->email = $formData['email'];
96 116 }
@@ -103,18 +123,21 @@
103 123 }
104 124 }
105 125 $user->email = trim($user->email);
106 126
127 + // Check email validity
107 128 if (empty($user->email) || !acym_isValidEmail($user->email, true)) {
108 129 $this->displayMessage('ACYM_VALID_EMAIL', $ajax);
109 130 }
110 131
132 + // E-mail is valid now...
133 + // Check if this user already exists or not...
111 134 $alreadyExists = $userClass->getOneByEmail($user->email);
112 135 if (!empty($alreadyExists->id)) {
113 136 $user->id = $alreadyExists->id;
114 137 }
115 138
116 - $result = $userClass->saveForm($ajax);
139 + $successfullySaved = $userClass->saveForm($ajax);
117 140 $user->id = acym_getVar('int', 'userId');
118 141
119 142 if (!empty($userClass->errors)) {
120 143 $this->displayMessage(implode('<br /><br />', $userClass->errors), $ajax);
@@ -119,12 +142,13 @@
119 142 if (!empty($userClass->errors)) {
120 143 $this->displayMessage(implode('<br /><br />', $userClass->errors), $ajax);
121 144 }
122 145
123 - if ($result === false || empty($user->id)) {
146 + if (!$successfullySaved || empty($user->id)) {
124 147 $this->displayMessage('ACYM_ERROR_SAVE_USER', $ajax);
125 148 }
126 149
150 + //We will now load back the user from the one we saved so that we will have all its infos
127 151 $myuser = $userClass->getOneById($user->id);
128 152 if (empty($myuser->id)) {
129 153 $this->displayMessage('ACYM_ERROR_SAVE_USER', $ajax);
130 154 }
@@ -129,13 +153,14 @@
129 153 $this->displayMessage('ACYM_ERROR_SAVE_USER', $ajax);
130 154 }
131 155
132 156 $msgtype = 'success';
133 - $extraMsg = '';
134 157 if (empty($myuser->confirmed) && $this->config->get('require_confirmation', 1) == 1) {
135 - $extraMsg = strip_tags(acym_getVar('string', 'confirmation_message'));
136 158 if ($userClass->confirmationSentSuccess || empty($userClass->confirmationSentError)) {
137 - $msg = !empty($extraMsg) ? $extraMsg : 'ACYM_CONFIRMATION_SENT';
159 + $msg = acym_stripTags(acym_getVar('string', 'confirmation_message', ''));
160 + if (empty($msg)) {
161 + $msg = 'ACYM_CONFIRMATION_SENT';
162 + }
138 163 $code = 2;
139 164 } else {
140 165 $msg = $userClass->confirmationSentError;
141 166 $code = 7;
@@ -142,10 +167,12 @@
142 167 $msgtype = 'error';
143 168 }
144 169 } else {
145 170 if ($userClass->subscribed) {
146 - $msg = strip_tags(acym_getVar('string', 'confirmation_message'));
147 - if (empty($msg)) $msg = 'ACYM_SUBSCRIPTION_OK';
171 + $msg = acym_stripTags(acym_getVar('string', 'confirmation_message', ''));
172 + if (empty($msg)) {
173 + $msg = 'ACYM_SUBSCRIPTION_OK';
174 + }
148 175 $code = 3;
149 176 } else {
150 177 $msg = 'ACYM_ALREADY_SUBSCRIBED';
151 178 $code = 5;
@@ -152,8 +179,9 @@
152 179 $enqueueMsgType = 'info';
153 180 }
154 181 }
155 182
183 + // Replace tags inside the $msg and redirect link
156 184 $replace = [];
157 185 foreach ($myuser as $oneProp => $oneVal) {
158 186 $replace['{user:'.$oneProp.'}'] = $oneVal;
159 187 }
@@ -158,15 +186,19 @@
158 186 $replace['{user:'.$oneProp.'}'] = $oneVal;
159 187 }
160 188
161 189 $msg = str_replace(array_keys($replace), $replace, acym_translation($msg));
162 - if (!empty($extraMsg)) {
163 - $msg = str_replace(array_keys($replace), $replace, acym_translation($extraMsg)).'<br />'.$msg;
164 - }
165 190
166 191 if ($ajax) {
192 + //Make sure the message has a valid format for Ajax... so the user can customize it the way he wants without breaking anything
167 193 $msg = str_replace(["\n", "\r", '"', '\\'], [' ', ' ', "'", '\\\\'], $msg);
168 - echo '{"message":"'.$msg.'","type":"'.$msgtype.'","code":"'.$code.'"}';
194 + echo json_encode(
195 + [
196 + 'message' => $msg,
197 + 'type' => $msgtype,
198 + 'code' => $code,
199 + ]
200 + );
169 201 exit;
170 202 } else {
171 203 acym_enqueueMessage($msg, !empty($enqueueMsgType) ? $enqueueMsgType : $msgtype);
172 204 }
@@ -191,14 +223,46 @@
191 223 return;
192 224 }
193 225 }
194 226
227 + if (empty($currentUser) || empty($currentUser->id)) {
228 + $currentUser = $alreadyExists;
229 + }
230 +
195 231 $visibleSubscription = acym_getVar('array', 'subscription', []);
196 232 $hiddenLists = trim(acym_getVar('string', 'hiddenlists', ''));
197 233 $hiddenSubscription = empty($hiddenLists) ? [] : explode(',', $hiddenLists);
198 234 $unsubscribeLists = array_merge($visibleSubscription, $hiddenSubscription);
235 + acym_arrayToInteger($unsubscribeLists);
199 236
200 237 $mailId = acym_getVar('int', 'mail_id', 0);
238 +
239 + // Protect against bots
240 + if (!empty($mailId) && !empty($currentUser->id)) {
241 + $userStatClass = new UserStatClass();
242 + $userStat = $userStatClass->getOneByMailAndUserId($mailId, $currentUser->id);
243 + $delay = $this->config->get('tracking_delay', 0);
244 + if (acym_isRobot() || (!empty($userStat) && !empty($delay) && acym_getTimeFromUTCDate($userStat->send_date) > time() - $delay)) {
245 + return;
246 + }
247 + }
248 +
249 + if (empty($unsubscribeLists)) {
250 + $mailClass = new MailClass();
251 + $mailType = $mailClass->getMailType($mailId);
252 + if (empty($currentUser) || empty($currentUser->id)) {
253 + $currentUser = $userClass->identify(true, 'userId', 'userKey');
254 + }
255 +
256 + if ($mailType === $mailClass::TYPE_SCENARIO) {
257 + $scenarioClass = new ScenarioClass();
258 + $scenarioId = $scenarioClass->getScenarioIdByMailId($mailId);
259 + if (!empty($currentUser->id)) {
260 + $scenarioClass->markUserUnsubscribeFromScenario($currentUser->id, $scenarioId);
261 + }
262 + }
263 + }
264 +
201 265 if (!empty($mailId)) {
202 266 $mailClass = new MailClass();
203 267 $unsubscribeLists = array_keys($mailClass->getAllListsByMailId($mailId));
204 268 }
@@ -204,14 +268,19 @@
204 268 }
205 269
206 270 if (empty($unsubscribeLists)) {
207 271 $msg = 'ACYM_NO_SUBSCRIPTION_LINKED_EMAIL';
208 - } elseif (false === $userClass->unsubscribe($alreadyExists->id, $unsubscribeLists)) {
272 + } elseif (false === $userClass->unsubscribe([$alreadyExists->id], $unsubscribeLists)) {
209 273 $msg = 'ACYM_UNSUBSCRIPTION_NOT_IN_LIST';
210 274 } else {
211 275 $msg = 'ACYM_UNSUBSCRIPTION_OK';
212 276 }
213 277
278 + if (!empty($mailType) && $mailType === $mailClass::TYPE_SCENARIO) {
279 + $msg = 'ACYM_UNSUBSCRIPTION_SCENARIO_OK';
280 + }
281 +
282 +
214 283 $this->endUnsubscribe($msg, $ajax);
215 284 }
216 285
217 286 private function endUnsubscribe(string $msg, bool $ajax, string $type = 'success'): void
@@ -218,9 +287,15 @@
218 287 {
219 288 $msg = acym_translation($msg);
220 289
221 290 if ($ajax) {
222 - echo '{"message":"'.str_replace('"', '\"', $msg).'","type":"'.$type.'","code":"10"}';
291 + echo json_encode(
292 + [
293 + 'message' => $msg,
294 + 'type' => $type,
295 + 'code' => '10',
296 + ]
297 + );
223 298 exit;
224 299 }
225 300 acym_enqueueMessage($msg, $type);
226 301
@@ -228,11 +303,29 @@
228 303 if (empty($redirectUrl)) {
229 304 $redirectUrl = acym_rootURI();
230 305 }
231 306
232 - acym_redirect($redirectUrl);
307 + acym_redirect($this->normalizeRedirectUrl($redirectUrl), '', 'message', true);
233 308 }
234 309
310 + private function normalizeRedirectUrl(string $url): string
311 + {
312 + if ($url === '') {
313 + return '';
314 + }
315 + if (preg_match('~^[a-z][a-z0-9+.\-]*://~i', $url)) {
316 + return $url;
317 + }
318 + if (strpos($url, '/') === 0 || strpos($url, 'index.php') === 0) {
319 + return $url;
320 + }
321 + if (preg_match('~^[^/?#]+\.[a-z]{2,}(?:[/?#]|$)~i', $url)) {
322 + return 'https://'.$url;
323 + }
324 +
325 + return $url;
326 + }
327 +
235 328 public function unsubscribe(): void
236 329 {
237 330 acym_checkRobots();
238 331 $userClass = new UserClass();
@@ -237,17 +330,23 @@
237 330 acym_checkRobots();
238 331 $userClass = new UserClass();
239 332
240 333 $redirectToUnsubPage = $this->config->get('unsubscribe_page', 1);
334 + $direct = acym_getVar('int', 'direct', 0);
241 335
336 + // Do we have to return an ajax response or a web page ?
242 337 $ajax = acym_getVar('int', 'ajax', 0);
243 338 if ($ajax) {
244 339 @ob_end_clean();
245 - acym_header('Content-type:text/html; charset=utf-8');
340 + acym_header('Content-type:application/json; charset=utf-8');
246 341 }
247 342
248 343 $currentUserid = acym_currentUserId();
249 344 $user = $userClass->identify(true, 'userId', 'userKey');
345 + if (empty($user)) {
346 + $user = $userClass->identify(true, 'user_id', 'user_key');
347 + $direct = 1;
348 + }
250 349 if (empty($user) && empty($currentUserid) && $this->config->get('captcha', 'none') !== 'none' && acym_level(ACYM_ESSENTIAL)) {
251 350 $captchaClass = new CaptchaHelper();
252 351 if (!$captchaClass->check()) {
253 352 $this->displayMessage('ACYM_WRONG_CAPTCHA', $ajax);
@@ -254,19 +353,18 @@
254 353 }
255 354 }
256 355
257 356 $formData = acym_getVar('array', 'user', []);
357 + $email = '';
258 358
259 - if (empty($formData['email'])) {
260 - if (empty($user)) {
261 - return;
262 - }
263 -
264 - if (!empty($user->email)) {
265 - $email = $user->email;
266 - }
267 - } else {
268 - $email = trim(strip_tags($formData['email']));
359 + if (!empty($formData['email'])) {
360 + $email = trim(acym_stripTags($formData['email']));
361 + } elseif (empty($user)) {
362 + return;
363 + } elseif (!empty($user->email)) {
364 + $email = $user->email;
365 + } elseif (!empty(acym_currentUserEmail())) {
366 + $email = acym_currentUserEmail();
269 367 }
270 368
271 369 $currentEmail = acym_currentUserEmail();
272 370 if (empty($email) && !empty($currentEmail)) {
@@ -272,8 +370,9 @@
272 370 if (empty($email) && !empty($currentEmail)) {
273 371 $email = $currentEmail;
274 372 }
275 373
374 + // Check email validity
276 375 if (empty($email) || !acym_isValidEmail($email)) {
277 376 $this->displayMessage('ACYM_VALID_EMAIL', $ajax);
278 377 }
279 378
@@ -278,22 +377,85 @@
278 377 }
279 378
280 379 $alreadyExists = $userClass->getOneByEmail($email);
281 380
381 + // User not found
282 382 if (empty($alreadyExists->id)) {
283 - $this->displayMessage('ACYM_SUB_NOT_IN_LIST', $ajax);
383 + $this->displayMessage(acym_translationSprintf('ACYM_SUB_NOT_IN_LIST', $email), $ajax);
284 384 }
285 385
286 386 $fromModuleOrWidget = acym_getVar('string', 'acysubmode', '');
287 - if (!empty($fromModuleOrWidget) && in_array($fromModuleOrWidget, ['form_acym', 'mod_acym', 'widget_acym'])) {
387 + if ((!empty($fromModuleOrWidget) && in_array($fromModuleOrWidget, ['form_acym', 'mod_acym', 'widget_acym'])) || $ajax || !$redirectToUnsubPage || !empty($direct)) {
288 388 $this->unsubscribeDirectly($alreadyExists, $ajax);
289 - } elseif ($redirectToUnsubPage && !$ajax) {
389 + } elseif ($redirectToUnsubPage && !$ajax && !$direct) {
390 + $identifiedEmail = !empty($user->email) ? $user->email : $currentEmail;
391 + if (empty($identifiedEmail) || strtolower($alreadyExists->email) !== strtolower($identifiedEmail)) {
392 + $this->displayMessage('ACYM_NOT_ALLOWED_MODIFY_USER', $ajax);
393 + }
290 394 $this->unsubscribePage($alreadyExists);
291 - } else {
292 - $this->unsubscribeDirectly($alreadyExists, $ajax);
293 395 }
294 396 }
295 397
398 + public function disableTracking(): void
399 + {
400 + acym_checkRobots();
401 +
402 + // Do we have to return an ajax response or a web page ?
403 + $ajax = acym_getVar('int', 'ajax', 0);
404 + if ($ajax) {
405 + @ob_end_clean();
406 + acym_header('Content-type:application/json; charset=utf-8');
407 + }
408 +
409 + $userClass = new UserClass();
410 + $user = $userClass->identify(true, 'userId', 'userKey');
411 +
412 + if (empty($user->id)) {
413 + $this->displayMessage('ACYM_USER_NOT_FOUND', $ajax);
414 + }
415 +
416 + // Protect against bots opening the link right after the email was sent
417 + $mailId = acym_getVar('int', 'mail_id', 0);
418 + if (!empty($mailId)) {
419 + $userStatClass = new UserStatClass();
420 + $userStat = $userStatClass->getOneByMailAndUserId($mailId, $user->id);
421 + $delay = $this->config->get('tracking_delay', 0);
422 + if (acym_isRobot() || (!empty($userStat) && !empty($delay) && acym_getTimeFromUTCDate($userStat->send_date) > time() - $delay)) {
423 + return;
424 + }
425 + }
426 +
427 + if ((int)$user->tracking !== 0) {
428 + $userClass->disableTracking($user, $mailId);
429 + }
430 +
431 + $this->endDisableTracking('ACYM_TRACKING_DISABLED_OK', $ajax);
432 + }
433 +
434 + private function endDisableTracking(string $msg, bool $ajax, string $type = 'success'): void
435 + {
436 + $msg = acym_translation($msg);
437 +
438 + if ($ajax) {
439 + echo json_encode(
440 + [
441 + 'message' => $msg,
442 + 'type' => $type,
443 + 'code' => '10',
444 + ]
445 + );
446 + exit;
447 + }
448 + acym_enqueueMessage($msg, $type);
449 +
450 + $redirectUrl = urldecode(acym_getVar('string', 'redirectunsub', ''));
451 + if (empty($redirectUrl)) {
452 + $redirectUrl = acym_rootURI();
453 + }
454 +
455 + acym_redirect($this->normalizeRedirectUrl($redirectUrl), '', 'message', true);
456 + }
457 +
296 458 private function getUserFromUnsubPage(): object
297 459 {
298 460 $userID = acym_getVar('int', 'user_id');
299 461 $userClass = new UserClass();
@@ -312,14 +474,17 @@
312 474 private function unsubscribeAllInner(bool $update = false): void
313 475 {
314 476 $userClass = new UserClass();
315 477 $user = $this->getUserFromUnsubPage();
316 - $redirectUrl = $this->config->get('unsub_redirect_url', acym_rootURI());
478 + $redirectUrl = urldecode(acym_getVar('string', 'redirectunsub', ''));
479 + if (empty($redirectUrl)) {
480 + $redirectUrl = $this->config->get('unsub_redirect_url', '');
481 + }
317 482
318 483 $allLists = $userClass->getUserSubscriptionById($user->id);
319 484 if (empty($allLists)) {
320 485 acym_enqueueMessage(acym_translation('ACYM_NOT_SUBSCRIBED_ANY_LIST'), 'info');
321 - acym_redirect(!empty($redirectUrl) ? $redirectUrl : acym_rootURI());
486 + acym_redirect($this->normalizeRedirectUrl(!empty($redirectUrl) ? $redirectUrl : acym_rootURI()), '', 'message', true);
322 487 }
323 488
324 489 $lists = [];
325 490 foreach ($allLists as $list) {
@@ -328,18 +493,22 @@
328 493 }
329 494 }
330 495
331 496 $userClass->sendNotification($user->id, 'acy_notification_unsuball');
497 + // A notification is sent when a user unsubscribes from a list, be we already sent a notification for this
332 498 $userClass->blockNotifications = true;
333 - $userClass->unsubscribe($user->id, $lists);
499 + $userClass->unsubscribe([$user->id], $lists);
334 500 }
335 501
336 502 private function redirectUnsubWorked(): void
337 503 {
338 504 acym_enqueueMessage(acym_translation('ACYM_SUBSCRIPTION_UPDATED_OK'));
339 - $redirectUrl = $this->config->get('unsub_redirect_url');
505 + $redirectUrl = urldecode(acym_getVar('string', 'redirectunsub', ''));
506 + if (empty($redirectUrl)) {
507 + $redirectUrl = $this->config->get('unsub_redirect_url', '');
508 + }
340 509 if (!empty($redirectUrl)) {
341 - acym_redirect($redirectUrl);
510 + acym_redirect($this->normalizeRedirectUrl($redirectUrl), '', 'message', true);
342 511 } else {
343 512 acym_redirect(acym_rootURI());
344 513 }
345 514 }
@@ -346,9 +515,9 @@
346 515
347 516 public function unsubscribeAll(): void
348 517 {
349 518 $userClass = new UserClass();
350 - if ($userClass->identify(true, 'user_id', 'user_key') === false) {
519 + if (empty($userClass->identify(true, 'user_id', 'user_key'))) {
351 520 acym_enqueueMessage(acym_translation('ACYM_USER_NOT_FOUND'), 'error');
352 521
353 522 acym_redirect(acym_rootURI());
354 523 }
@@ -355,9 +524,9 @@
355 524
356 525 $displayCheckedLists = acym_getVar('string', 'displayed_checked_lists', '');
357 526 if (!empty($displayCheckedLists)) {
358 527 $user = $this->getUserFromUnsubPage();
359 - $userClass->unsubscribe($user->id, explode(',', $displayCheckedLists));
528 + $userClass->unsubscribe([$user->id], explode(',', $displayCheckedLists));
360 529 } else {
361 530 $this->unsubscribeAllInner();
362 531 }
363 532
@@ -369,33 +538,42 @@
369 538
370 539 public function saveSubscriptions(): void
371 540 {
372 541 $userClass = new UserClass();
373 - if ($userClass->identify(true, 'user_id', 'user_key') === false) {
542 + if (empty($userClass->identify(true, 'user_id', 'user_key'))) {
374 543 $this->displayMessage('ACYM_USER_NOT_FOUND', false);
375 544 }
376 545
546 + // Get the user
377 547 $user = $this->getUserFromUnsubPage();
548 + // Get the list the user want to sub
378 549 $listsChecked = acym_getVar('array', 'lists', []);
379 550 $listsChecked = array_filter($listsChecked, function ($status) {
380 551 return intval($status) === 1;
381 552 });
382 553 $listsChecked = array_keys($listsChecked);
554 + // Get the displayed lists
383 555 $displayedCheckedLists = explode(',', acym_getVar('string', 'displayed_checked_lists', ''));
556 + // We get the user subscriptions
384 557 $userSubscriptions = $userClass->getUserSubscriptionById($user->id);
385 558
386 - $userClass->subscribe($user->id, $listsChecked);
559 + // We subscribe the user to the checked lists
560 + $userClass->subscribe([$user->id], $listsChecked);
387 561
562 + // We unsub to the unchecked lists
388 563 $listsToUnsub = [];
389 564 foreach ($userSubscriptions as $subscription) {
390 - if (!in_array($subscription->id, $listsChecked)
565 + // The list wasn't checked && the list is displayed && the user is subscribed to it
566 + if (
567 + !in_array($subscription->id, $listsChecked)
391 568 && in_array($subscription->id, $displayedCheckedLists)
392 - && intval($subscription->status) === 1) {
569 + && intval($subscription->status) === 1
570 + ) {
393 571 $listsToUnsub[] = $subscription->id;
394 572 }
395 573 }
396 574
397 - $userClass->unsubscribe($user->id, $listsToUnsub);
575 + $userClass->unsubscribe([$user->id], $listsToUnsub);
398 576 $this->redirectUnsubWorked();
399 577 }
400 578
401 579 public function unsubscribePage(object $alreadyExists): void
@@ -405,14 +583,20 @@
405 583 $mailId = acym_getVar('int', 'mail_id', 0);
406 584 $campaignListOnly = $this->config->get('unsubscribe_campaign_list_only', '0') === '1';
407 585 $displaySurvey = $this->config->get('unsubpage_survey', '0') === '1';
408 586 $surveyAnswers = $this->config->get('unsub_survey', '[]');
409 - $unsubscribeColor = $this->config->get('unsubscribe_color', '#00a4ff');
587 + $unsubscribeColor = $this->config->get('unsubscribe_color');
588 + if (empty($unsubscribeColor)) {
589 + $unsubscribeColor = '#0079d3';
590 + }
410 591 $hoverColor = $this->darkenRGBColor($unsubscribeColor, 20);
411 592
412 593 $surveyAnswers = json_decode($surveyAnswers, true);
413 594
414 - if (ACYM_CMS === 'joomla' && !empty($_GET['language'])) $lang = $_GET['language'];
595 + $languageCode = acym_getVar('string', 'language');
596 + if (ACYM_CMS === 'joomla' && !empty($languageCode)) {
597 + $lang = $languageCode;
598 + }
415 599 acym_setLanguage($lang);
416 600 acym_loadLanguage($lang);
417 601
418 602 $userSubscriptions = $userClass->getUserSubscriptionById($alreadyExists->id, 'id', false, false, true, false, $mailId, $campaignListOnly);
@@ -451,17 +635,15 @@
451 635 $data['surveyAnswers'][] = acym_translation('ACYM_OTHER');
452 636 $data['surveyAnswers'] = array_combine($data['surveyAnswers'], $data['surveyAnswers']);
453 637 }
454 638
455 - if (!empty($this->config->get('unsubscribe_color'))) {
456 - $unsubscribeColor = $this->config->get('unsubscribe_color');
457 - $hoverColor = $this->darkenRGBColor($unsubscribeColor, 20);
458 - $data['unsubscribeColor'] = $unsubscribeColor;
459 - $data['hoverColor'] = $hoverColor;
460 - }
639 + $data['unsubscribeColor'] = $unsubscribeColor;
640 + $data['hoverColor'] = $hoverColor;
461 641 $data['svgImage'] = $this->getSVGImage($unsubscribeColor, $hoverColor);
462 642
463 643 acym_setVar('layout', 'unsubscribepage');
644 + acym_header('Content-Type: text/html; charset=utf-8');
645 +
464 646 parent::display($data);
465 647 }
466 648
467 649 private function getSVGImage(string $unsubscribeColor, string $hoverColor): string
@@ -512,8 +694,9 @@
512 694 if (acym_isRobot()) {
513 695 return;
514 696 }
515 697
698 + // We identify the user
516 699 $userClass = new UserClass();
517 700 $user = $userClass->identify(true, 'userId', 'userKey');
518 701 if (empty($user)) {
519 702 acym_enqueueMessage(acym_translation('ACYM_USER_NOT_FOUND'), 'error');
@@ -529,20 +712,23 @@
529 712 acym_enqueueMessage(acym_translation('ACYM_SUBSCRIPTION_CONFIRMED'), 'success');
530 713 }
531 714 }
532 715
716 + // Now we can really confirm the user
533 717 if (!$user->confirmed) {
534 718 $userClass->confirm($user->id);
535 719 }
536 720
721 + // Get the redirect url from the configuration page if specified
537 722 $redirectUrl = $this->config->get('confirm_redirect');
538 723 if (!empty($redirectUrl)) {
539 724 $replace = [];
725 + // We replace tags in case of the user added some dynamic information to its url.
540 726 foreach ($user as $key => $val) {
541 727 $replace['{user:'.$key.'}'] = $val;
542 728 }
543 729 $redirectUrl = str_replace(array_keys($replace), $replace, $redirectUrl);
544 - acym_redirect($redirectUrl);
730 + acym_redirect($redirectUrl, '', 'message', false);
545 731
546 732 return;
547 733 }
548 734
@@ -554,8 +740,9 @@
554 740 $userClass = new UserClass();
555 741 $user = $userClass->identify(true);
556 742
557 743 if (empty($user)) {
744 + // Check if the subscription is allowed for non registered users
558 745 $allowvisitor = $this->config->get('allow_visitor', 1);
559 746 if (empty($allowvisitor)) {
560 747 acym_askLog(true, 'ACYM_ONLY_LOGGED', 'message');
561 748
@@ -572,8 +759,9 @@
572 759 acym_addScript(false, ACYM_JS.'libraries/email-misspelled.min.js?v='.filemtime(ACYM_MEDIA.'js'.DS.'libraries'.DS.'email-misspelled.min.js'));
573 760 }
574 761 acym_addScript(false, ACYM_JS.'module.min.js?v='.filemtime(ACYM_MEDIA.'js'.DS.'module.min.js'));
575 762
763 + // Get the page parameters
576 764 $params = new \stdClass();
577 765 $menu = acym_getMenu();
578 766 if (is_object($menu)) {
579 767 $params->source = 'Menu n°'.$menu->id;
@@ -580,8 +768,9 @@
580 768 $menuParameters = method_exists($menu, 'getParams') ? $menu->getParams() : $menu->params;
581 769 $menuparams = new AcymParameter($menuParameters);
582 770
583 771 if (!empty($menuparams)) {
772 + // Joomla specific params
584 773 $params->suffix = $menuparams->get('pageclass_sfx', '');
585 774 $params->page_heading = $menuparams->get('page_heading');
586 775 $params->show_page_heading = $menuparams->get('show_page_heading', 0);
587 776
@@ -594,8 +783,9 @@
594 783 if ($menuparams->get('robots')) {
595 784 acym_addMetadata('robots', $menuparams->get('robots'));
596 785 }
597 786
787 + // Our params
598 788 $params->lists = $menuparams->get('lists', 'none');
599 789 $params->listschecked = $menuparams->get('listschecked', 'none');
600 790 $params->dropdown = $menuparams->get('dropdown');
601 791 $params->hiddenlists = $menuparams->get('hiddenlists', 'None');
@@ -619,8 +809,9 @@
619 809 }
620 810
621 811 public function prepareParams(object $values): array
622 812 {
813 + //TODO problem with the types throughout the entire method
623 814 if (!isset($values->lists)) {
624 815 $values->lists = 'none';
625 816 }
626 817 if (!isset($values->listschecked)) {
@@ -632,13 +823,18 @@
632 823 if (!isset($values->hiddenlists)) {
633 824 $values->hiddenlists = 'None';
634 825 }
635 826 if (empty($values->fields)) {
636 - $values->fields = ['1', '2'];
827 + $values->fields = [2];
828 + } elseif (is_string($values->fields)) {
829 + $values->fields = explode(',', $values->fields);
637 830 }
638 831 if (!in_array('2', $values->fields) && !in_array(2, $values->fields)) {
639 832 $values->fields[] = '2';
640 833 }
834 + if (!isset($values->page_heading)) {
835 + $values->page_heading = '';
836 + }
641 837 if (!isset($values->introtext)) {
642 838 $values->introtext = '';
643 839 }
644 840 if (!isset($values->posttext)) {
@@ -654,8 +850,9 @@
654 850 if ((empty($values->lists) || in_array('None', $values->lists)) && (empty($values->hiddenlists) || strtolower(implode('', $values->hiddenlists)) == 'none')) {
655 851 $values->lists[] = 'All';
656 852 }
657 853
854 + // Get the current user and his subscription
658 855 $userClass = new UserClass();
659 856 $user = $userClass->identify(true);
660 857 if (empty($user)) {
661 858 $listClass = new ListClass();
@@ -736,8 +933,9 @@
736 933 }
737 934 $values->hiddenlists = $hiddenListsArray;
738 935 }
739 936
937 + // Overriding the list with the URL parameters: &listid= for displayed list, &hiddenlist= for hidden lists
740 938 $defaultSubscription = $subscription;
741 939 $forceLists = acym_getVar('string', 'listid', '');
742 940 if (!empty($forceLists)) {
743 941 $subscription = [];
@@ -793,15 +991,15 @@
793 991 }
794 992
795 993 public function savechanges(): void
796 994 {
797 - acym_checkToken();
995 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
798 996 acym_checkRobots();
799 997
800 998 $userClass = new UserClass();
801 999
802 1000
803 - $status = $userClass->saveForm();
1001 + $status = $userClass->saveForm(true);
804 1002 if ($status) {
805 1003 if ($userClass->confirmationSentSuccess) {
806 1004 $this->displayMessage('ACYM_CONFIRMATION_SENT', true, 'success');
807 1005 } elseif ($userClass->newUser) {
@@ -821,9 +1019,9 @@
821 1019 }
822 1020
823 1021 public function exportdata(): void
824 1022 {
825 - acym_checkToken();
1023 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
826 1024
827 1025 $userClass = new UserClass();
828 1026 $user = $userClass->identify(true, 'userId', 'userKey');
829 1027
@@ -831,14 +1029,42 @@
831 1029 acym_redirect(acym_rootURI());
832 1030 }
833 1031
834 1032 $userHelper = new UserHelper();
835 - $userHelper->exportdata((int)$user->id);
1033 + $userData = $userHelper->getUserData($user);
1034 + $exportData = [];
1035 + foreach ($userData as $oneCategory) {
1036 + if (!isset($exportData[$oneCategory['group_label']])) {
1037 + $exportData[$oneCategory['group_label']] = [];
1038 + }
1039 +
1040 + $exportData[$oneCategory['group_label']][] = $oneCategory['data'];
1041 + }
1042 +
1043 + $exportFiles = [
1044 + [
1045 + 'name' => 'data.json',
1046 + 'data' => json_encode($exportData),
1047 + ],
1048 + ];
1049 +
1050 + $tempFolder = ACYM_MEDIA.'tmp'.DS;
1051 + acym_createArchive($tempFolder.'export_data_user_'.$user->id, $exportFiles);
1052 +
1053 + $exportHelper = new ExportHelper();
1054 + $exportHelper->setDownloadHeaders('export_data_user_'.$user->id, 'zip');
1055 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Downloading a file, better than WP functions for memory handling.
1056 + readfile($tempFolder.'export_data_user_'.$user->id.'.zip');
1057 +
1058 + // Avoid issue when user cancels the download
1059 + ignore_user_abort(true);
1060 + acym_deleteFile($tempFolder.'export_data_user_'.$user->id.'.zip');
1061 + exit;
836 1062 }
837 1063
838 1064 public function gdprDelete(): void
839 1065 {
840 - acym_checkToken();
1066 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
841 1067
842 1068 $userClass = new UserClass();
843 1069 $user = $userClass->identify(true, 'userId', 'userKey');
844 1070 if (empty($user->id)) {
@@ -845,29 +1071,31 @@
845 1071 acym_redirect(acym_rootURI());
846 1072 }
847 1073
848 1074 $userClass->delete([$user->id], true);
1075 +
1076 + acym_redirect(acym_rootURI(), 'ACYM_GDPR_DATA_DELETED');
849 1077 }
850 1078
851 1079 public function ajaxGetEnqueuedMessages(): void
852 1080 {
853 - acym_session();
854 -
855 1081 $output = '';
856 1082 $types = ['success', 'info', 'warning', 'error'];
857 1083 foreach ($types as $type) {
858 - if (empty($_SESSION['acymessage'.$type])) continue;
1084 + $messages = acym_getVar('array', 'acymessage'.$type, [], 'SESSION', ACYM_ALLOWHTML);
1085 + if (empty($messages)) {
1086 + continue;
1087 + }
859 1088
860 - $messages = $_SESSION['acymessage'.$type];
861 1089 if (!is_array($messages)) {
862 1090 $messages = [$messages];
863 1091 }
864 1092
865 - $output .= '<div class="acym_callout acym__callout__front__'.$type.'" role="alert">';
1093 + $output .= '<div class="acym_callout acym__callout__front__'.esc_attr($type).'" role="alert">';
866 1094 $output .= '<div>'.implode(' ', $messages).'</div>';
867 - $output .= '<button class="acym_callout_close" aria-label="'.acym_escape(acym_translation('ACYM_CLOSE_NOTIFICATION')).'">x</button></div>';
1095 + $output .= '<button class="acym_callout_close" aria-label="'.esc_attr(acym_translation('ACYM_CLOSE_NOTIFICATION')).'">x</button></div>';
868 1096
869 - unset($_SESSION['acymessage'.$type]);
1097 + acym_setSession('acymessage'.$type, null, true);
870 1098 }
871 1099
872 1100 if (!empty($output)) {
873 1101 $output = '<div id="acym__callout__container">'.$output.'</div>';