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 +146 -29 10.11.1 → 11.1.0 View file →
@@ -34,8 +34,9 @@
34 34 $this->publicFrontTasks = [
35 35 'subscribe',
36 36 'unsubscribe',
37 37 'unsubscribeAll',
38 + 'disableTracking',
38 39 'saveSubscriptions',
39 40 'unsubscribePage',
40 41 'confirm',
41 42 'profile',
@@ -59,9 +60,9 @@
59 60 ]
60 61 );
61 62 } else {
62 63 acym_header('Content-type:text/html; charset=utf-8');
63 - 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>';
64 65 }
65 66 exit;
66 67 }
67 68
@@ -68,22 +69,33 @@
68 69 public function subscribe(): void
69 70 {
70 71 acym_checkRobots();
71 72
72 - 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...
73 76 acym_setVar('acy_source', 'url');
74 77 }
75 78
79 + // Do we have to return an ajax response or a web page ?
76 80 $ajax = acym_getVar('int', 'ajax', 0);
77 81 if ($ajax) {
82 + //in case of the page displays some warnings or whatever
78 83 @ob_end_clean();
79 84 acym_header('Content-type:application/json; charset=utf-8');
80 85 }
81 86
87 + //We only allow logged in users and this user is not logged it...
82 88 $currentUserid = acym_currentUserId();
83 89 if ((int)$this->config->get('allow_visitor', 1) != 1 && empty($currentUserid)) {
84 90 if ($ajax) {
85 - 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 + );
86 98 exit;
87 99 } else {
88 100 acym_askLog(false, 'ACYM_ONLY_LOGGED');
89 101
@@ -97,9 +109,8 @@
97 109 $this->displayMessage('ACYM_WRONG_CAPTCHA', $ajax);
98 110 }
99 111 }
100 112
101 - $formData = acym_getVar('array', 'user', [], '');
102 113 $user = new \stdClass();
103 114 if (!empty($formData['email'])) {
104 115 $user->email = $formData['email'];
105 116 }
@@ -112,12 +123,15 @@
112 123 }
113 124 }
114 125 $user->email = trim($user->email);
115 126
127 + // Check email validity
116 128 if (empty($user->email) || !acym_isValidEmail($user->email, true)) {
117 129 $this->displayMessage('ACYM_VALID_EMAIL', $ajax);
118 130 }
119 131
132 + // E-mail is valid now...
133 + // Check if this user already exists or not...
120 134 $alreadyExists = $userClass->getOneByEmail($user->email);
121 135 if (!empty($alreadyExists->id)) {
122 136 $user->id = $alreadyExists->id;
123 137 }
@@ -132,8 +146,9 @@
132 146 if (!$successfullySaved || empty($user->id)) {
133 147 $this->displayMessage('ACYM_ERROR_SAVE_USER', $ajax);
134 148 }
135 149
150 + //We will now load back the user from the one we saved so that we will have all its infos
136 151 $myuser = $userClass->getOneById($user->id);
137 152 if (empty($myuser->id)) {
138 153 $this->displayMessage('ACYM_ERROR_SAVE_USER', $ajax);
139 154 }
@@ -140,9 +155,9 @@
140 155
141 156 $msgtype = 'success';
142 157 if (empty($myuser->confirmed) && $this->config->get('require_confirmation', 1) == 1) {
143 158 if ($userClass->confirmationSentSuccess || empty($userClass->confirmationSentError)) {
144 - $msg = strip_tags(acym_getVar('string', 'confirmation_message', ''));
159 + $msg = acym_stripTags(acym_getVar('string', 'confirmation_message', ''));
145 160 if (empty($msg)) {
146 161 $msg = 'ACYM_CONFIRMATION_SENT';
147 162 }
148 163 $code = 2;
@@ -152,9 +167,9 @@
152 167 $msgtype = 'error';
153 168 }
154 169 } else {
155 170 if ($userClass->subscribed) {
156 - $msg = strip_tags(acym_getVar('string', 'confirmation_message', ''));
171 + $msg = acym_stripTags(acym_getVar('string', 'confirmation_message', ''));
157 172 if (empty($msg)) {
158 173 $msg = 'ACYM_SUBSCRIPTION_OK';
159 174 }
160 175 $code = 3;
@@ -164,8 +179,9 @@
164 179 $enqueueMsgType = 'info';
165 180 }
166 181 }
167 182
183 + // Replace tags inside the $msg and redirect link
168 184 $replace = [];
169 185 foreach ($myuser as $oneProp => $oneVal) {
170 186 $replace['{user:'.$oneProp.'}'] = $oneVal;
171 187 }
@@ -172,10 +188,17 @@
172 188
173 189 $msg = str_replace(array_keys($replace), $replace, acym_translation($msg));
174 190
175 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
176 193 $msg = str_replace(["\n", "\r", '"', '\\'], [' ', ' ', "'", '\\\\'], $msg);
177 - echo '{"message":"'.$msg.'","type":"'.$msgtype.'","code":"'.$code.'"}';
194 + echo json_encode(
195 + [
196 + 'message' => $msg,
197 + 'type' => $msgtype,
198 + 'code' => $code,
199 + ]
200 + );
178 201 exit;
179 202 } else {
180 203 acym_enqueueMessage($msg, !empty($enqueueMsgType) ? $enqueueMsgType : $msgtype);
181 204 }
@@ -212,8 +235,9 @@
212 235 acym_arrayToInteger($unsubscribeLists);
213 236
214 237 $mailId = acym_getVar('int', 'mail_id', 0);
215 238
239 + // Protect against bots
216 240 if (!empty($mailId) && !empty($currentUser->id)) {
217 241 $userStatClass = new UserStatClass();
218 242 $userStat = $userStatClass->getOneByMailAndUserId($mailId, $currentUser->id);
219 243 $delay = $this->config->get('tracking_delay', 0);
@@ -308,8 +332,9 @@
308 332
309 333 $redirectToUnsubPage = $this->config->get('unsubscribe_page', 1);
310 334 $direct = acym_getVar('int', 'direct', 0);
311 335
336 + // Do we have to return an ajax response or a web page ?
312 337 $ajax = acym_getVar('int', 'ajax', 0);
313 338 if ($ajax) {
314 339 @ob_end_clean();
315 340 acym_header('Content-type:application/json; charset=utf-8');
@@ -331,9 +356,9 @@
331 356 $formData = acym_getVar('array', 'user', []);
332 357 $email = '';
333 358
334 359 if (!empty($formData['email'])) {
335 - $email = trim(strip_tags($formData['email']));
360 + $email = trim(acym_stripTags($formData['email']));
336 361 } elseif (empty($user)) {
337 362 return;
338 363 } elseif (!empty($user->email)) {
339 364 $email = $user->email;
@@ -345,8 +370,9 @@
345 370 if (empty($email) && !empty($currentEmail)) {
346 371 $email = $currentEmail;
347 372 }
348 373
374 + // Check email validity
349 375 if (empty($email) || !acym_isValidEmail($email)) {
350 376 $this->displayMessage('ACYM_VALID_EMAIL', $ajax);
351 377 }
352 378
@@ -351,8 +377,9 @@
351 377 }
352 378
353 379 $alreadyExists = $userClass->getOneByEmail($email);
354 380
381 + // User not found
355 382 if (empty($alreadyExists->id)) {
356 383 $this->displayMessage(acym_translationSprintf('ACYM_SUB_NOT_IN_LIST', $email), $ajax);
357 384 }
358 385
@@ -367,8 +394,68 @@
367 394 $this->unsubscribePage($alreadyExists);
368 395 }
369 396 }
370 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 +
371 458 private function getUserFromUnsubPage(): object
372 459 {
373 460 $userID = acym_getVar('int', 'user_id');
374 461 $userClass = new UserClass();
@@ -406,8 +493,9 @@
406 493 }
407 494 }
408 495
409 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
410 498 $userClass->blockNotifications = true;
411 499 $userClass->unsubscribe([$user->id], $lists);
412 500 }
413 501
@@ -454,21 +542,28 @@
454 542 if (empty($userClass->identify(true, 'user_id', 'user_key'))) {
455 543 $this->displayMessage('ACYM_USER_NOT_FOUND', false);
456 544 }
457 545
546 + // Get the user
458 547 $user = $this->getUserFromUnsubPage();
548 + // Get the list the user want to sub
459 549 $listsChecked = acym_getVar('array', 'lists', []);
460 550 $listsChecked = array_filter($listsChecked, function ($status) {
461 551 return intval($status) === 1;
462 552 });
463 553 $listsChecked = array_keys($listsChecked);
554 + // Get the displayed lists
464 555 $displayedCheckedLists = explode(',', acym_getVar('string', 'displayed_checked_lists', ''));
556 + // We get the user subscriptions
465 557 $userSubscriptions = $userClass->getUserSubscriptionById($user->id);
466 558
559 + // We subscribe the user to the checked lists
467 560 $userClass->subscribe([$user->id], $listsChecked);
468 561
562 + // We unsub to the unchecked lists
469 563 $listsToUnsub = [];
470 564 foreach ($userSubscriptions as $subscription) {
565 + // The list wasn't checked && the list is displayed && the user is subscribed to it
471 566 if (
472 567 !in_array($subscription->id, $listsChecked)
473 568 && in_array($subscription->id, $displayedCheckedLists)
474 569 && intval($subscription->status) === 1
@@ -488,14 +583,20 @@
488 583 $mailId = acym_getVar('int', 'mail_id', 0);
489 584 $campaignListOnly = $this->config->get('unsubscribe_campaign_list_only', '0') === '1';
490 585 $displaySurvey = $this->config->get('unsubpage_survey', '0') === '1';
491 586 $surveyAnswers = $this->config->get('unsub_survey', '[]');
492 - $unsubscribeColor = $this->config->get('unsubscribe_color', '#00a4ff');
587 + $unsubscribeColor = $this->config->get('unsubscribe_color');
588 + if (empty($unsubscribeColor)) {
589 + $unsubscribeColor = '#0079d3';
590 + }
493 591 $hoverColor = $this->darkenRGBColor($unsubscribeColor, 20);
494 592
495 593 $surveyAnswers = json_decode($surveyAnswers, true);
496 594
497 - 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 + }
498 599 acym_setLanguage($lang);
499 600 acym_loadLanguage($lang);
500 601
501 602 $userSubscriptions = $userClass->getUserSubscriptionById($alreadyExists->id, 'id', false, false, true, false, $mailId, $campaignListOnly);
@@ -534,14 +635,10 @@
534 635 $data['surveyAnswers'][] = acym_translation('ACYM_OTHER');
535 636 $data['surveyAnswers'] = array_combine($data['surveyAnswers'], $data['surveyAnswers']);
536 637 }
537 638
538 - if (!empty($this->config->get('unsubscribe_color'))) {
539 - $unsubscribeColor = $this->config->get('unsubscribe_color');
540 - $hoverColor = $this->darkenRGBColor($unsubscribeColor, 20);
541 - $data['unsubscribeColor'] = $unsubscribeColor;
542 - $data['hoverColor'] = $hoverColor;
543 - }
639 + $data['unsubscribeColor'] = $unsubscribeColor;
640 + $data['hoverColor'] = $hoverColor;
544 641 $data['svgImage'] = $this->getSVGImage($unsubscribeColor, $hoverColor);
545 642
546 643 acym_setVar('layout', 'unsubscribepage');
547 644 acym_header('Content-Type: text/html; charset=utf-8');
@@ -597,8 +694,9 @@
597 694 if (acym_isRobot()) {
598 695 return;
599 696 }
600 697
698 + // We identify the user
601 699 $userClass = new UserClass();
602 700 $user = $userClass->identify(true, 'userId', 'userKey');
603 701 if (empty($user)) {
604 702 acym_enqueueMessage(acym_translation('ACYM_USER_NOT_FOUND'), 'error');
@@ -614,20 +712,23 @@
614 712 acym_enqueueMessage(acym_translation('ACYM_SUBSCRIPTION_CONFIRMED'), 'success');
615 713 }
616 714 }
617 715
716 + // Now we can really confirm the user
618 717 if (!$user->confirmed) {
619 718 $userClass->confirm($user->id);
620 719 }
621 720
721 + // Get the redirect url from the configuration page if specified
622 722 $redirectUrl = $this->config->get('confirm_redirect');
623 723 if (!empty($redirectUrl)) {
624 724 $replace = [];
725 + // We replace tags in case of the user added some dynamic information to its url.
625 726 foreach ($user as $key => $val) {
626 727 $replace['{user:'.$key.'}'] = $val;
627 728 }
628 729 $redirectUrl = str_replace(array_keys($replace), $replace, $redirectUrl);
629 - acym_redirect($redirectUrl);
730 + acym_redirect($redirectUrl, '', 'message', false);
630 731
631 732 return;
632 733 }
633 734
@@ -639,8 +740,9 @@
639 740 $userClass = new UserClass();
640 741 $user = $userClass->identify(true);
641 742
642 743 if (empty($user)) {
744 + // Check if the subscription is allowed for non registered users
643 745 $allowvisitor = $this->config->get('allow_visitor', 1);
644 746 if (empty($allowvisitor)) {
645 747 acym_askLog(true, 'ACYM_ONLY_LOGGED', 'message');
646 748
@@ -657,8 +759,9 @@
657 759 acym_addScript(false, ACYM_JS.'libraries/email-misspelled.min.js?v='.filemtime(ACYM_MEDIA.'js'.DS.'libraries'.DS.'email-misspelled.min.js'));
658 760 }
659 761 acym_addScript(false, ACYM_JS.'module.min.js?v='.filemtime(ACYM_MEDIA.'js'.DS.'module.min.js'));
660 762
763 + // Get the page parameters
661 764 $params = new \stdClass();
662 765 $menu = acym_getMenu();
663 766 if (is_object($menu)) {
664 767 $params->source = 'Menu n°'.$menu->id;
@@ -665,8 +768,9 @@
665 768 $menuParameters = method_exists($menu, 'getParams') ? $menu->getParams() : $menu->params;
666 769 $menuparams = new AcymParameter($menuParameters);
667 770
668 771 if (!empty($menuparams)) {
772 + // Joomla specific params
669 773 $params->suffix = $menuparams->get('pageclass_sfx', '');
670 774 $params->page_heading = $menuparams->get('page_heading');
671 775 $params->show_page_heading = $menuparams->get('show_page_heading', 0);
672 776
@@ -679,8 +783,9 @@
679 783 if ($menuparams->get('robots')) {
680 784 acym_addMetadata('robots', $menuparams->get('robots'));
681 785 }
682 786
787 + // Our params
683 788 $params->lists = $menuparams->get('lists', 'none');
684 789 $params->listschecked = $menuparams->get('listschecked', 'none');
685 790 $params->dropdown = $menuparams->get('dropdown');
686 791 $params->hiddenlists = $menuparams->get('hiddenlists', 'None');
@@ -704,8 +809,9 @@
704 809 }
705 810
706 811 public function prepareParams(object $values): array
707 812 {
813 + //TODO problem with the types throughout the entire method
708 814 if (!isset($values->lists)) {
709 815 $values->lists = 'none';
710 816 }
711 817 if (!isset($values->listschecked)) {
@@ -717,13 +823,18 @@
717 823 if (!isset($values->hiddenlists)) {
718 824 $values->hiddenlists = 'None';
719 825 }
720 826 if (empty($values->fields)) {
721 - $values->fields = ['1', '2'];
827 + $values->fields = [2];
828 + } elseif (is_string($values->fields)) {
829 + $values->fields = explode(',', $values->fields);
722 830 }
723 831 if (!in_array('2', $values->fields) && !in_array(2, $values->fields)) {
724 832 $values->fields[] = '2';
725 833 }
834 + if (!isset($values->page_heading)) {
835 + $values->page_heading = '';
836 + }
726 837 if (!isset($values->introtext)) {
727 838 $values->introtext = '';
728 839 }
729 840 if (!isset($values->posttext)) {
@@ -739,8 +850,9 @@
739 850 if ((empty($values->lists) || in_array('None', $values->lists)) && (empty($values->hiddenlists) || strtolower(implode('', $values->hiddenlists)) == 'none')) {
740 851 $values->lists[] = 'All';
741 852 }
742 853
854 + // Get the current user and his subscription
743 855 $userClass = new UserClass();
744 856 $user = $userClass->identify(true);
745 857 if (empty($user)) {
746 858 $listClass = new ListClass();
@@ -821,8 +933,9 @@
821 933 }
822 934 $values->hiddenlists = $hiddenListsArray;
823 935 }
824 936
937 + // Overriding the list with the URL parameters: &listid= for displayed list, &hiddenlist= for hidden lists
825 938 $defaultSubscription = $subscription;
826 939 $forceLists = acym_getVar('string', 'listid', '');
827 940 if (!empty($forceLists)) {
828 941 $subscription = [];
@@ -878,9 +991,9 @@
878 991 }
879 992
880 993 public function savechanges(): void
881 994 {
882 - acym_checkToken();
995 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
883 996 acym_checkRobots();
884 997
885 998 $userClass = new UserClass();
886 999
@@ -906,9 +1019,9 @@
906 1019 }
907 1020
908 1021 public function exportdata(): void
909 1022 {
910 - acym_checkToken();
1023 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
911 1024
912 1025 $userClass = new UserClass();
913 1026 $user = $userClass->identify(true, 'userId', 'userKey');
914 1027
@@ -938,18 +1051,20 @@
938 1051 acym_createArchive($tempFolder.'export_data_user_'.$user->id, $exportFiles);
939 1052
940 1053 $exportHelper = new ExportHelper();
941 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.
942 1056 readfile($tempFolder.'export_data_user_'.$user->id.'.zip');
943 1057
1058 + // Avoid issue when user cancels the download
944 1059 ignore_user_abort(true);
945 - unlink($tempFolder.'export_data_user_'.$user->id.'.zip');
1060 + acym_deleteFile($tempFolder.'export_data_user_'.$user->id.'.zip');
946 1061 exit;
947 1062 }
948 1063
949 1064 public function gdprDelete(): void
950 1065 {
951 - acym_checkToken();
1066 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
952 1067
953 1068 $userClass = new UserClass();
954 1069 $user = $userClass->identify(true, 'userId', 'userKey');
955 1070 if (empty($user->id)) {
@@ -956,29 +1071,31 @@
956 1071 acym_redirect(acym_rootURI());
957 1072 }
958 1073
959 1074 $userClass->delete([$user->id], true);
1075 +
1076 + acym_redirect(acym_rootURI(), 'ACYM_GDPR_DATA_DELETED');
960 1077 }
961 1078
962 1079 public function ajaxGetEnqueuedMessages(): void
963 1080 {
964 - acym_session();
965 -
966 1081 $output = '';
967 1082 $types = ['success', 'info', 'warning', 'error'];
968 1083 foreach ($types as $type) {
969 - if (empty($_SESSION['acymessage'.$type])) continue;
1084 + $messages = acym_getVar('array', 'acymessage'.$type, [], 'SESSION', ACYM_ALLOWHTML);
1085 + if (empty($messages)) {
1086 + continue;
1087 + }
970 1088
971 - $messages = $_SESSION['acymessage'.$type];
972 1089 if (!is_array($messages)) {
973 1090 $messages = [$messages];
974 1091 }
975 1092
976 - $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">';
977 1094 $output .= '<div>'.implode(' ', $messages).'</div>';
978 - $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>';
979 1096
980 - unset($_SESSION['acymessage'.$type]);
1097 + acym_setSession('acymessage'.$type, null, true);
981 1098 }
982 1099
983 1100 if (!empty($output)) {
984 1101 $output = '<div id="acym__callout__container">'.$output.'</div>';