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 +333 -96 10.0.1 → 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',
@@ -44,37 +48,54 @@
44 48
45 49 }
46 50
47 51
48 - private function displayMessage($message, $ajax, $type = 'error')
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 - public function subscribe()
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 }
@@ -179,9 +211,9 @@
179 211
180 212 acym_redirect($redirectUrl, '', 'message', true);
181 213 }
182 214
183 - private function unsubscribeDirectly($alreadyExists, $ajax)
215 + private function unsubscribeDirectly(object $alreadyExists, bool $ajax): void
184 216 {
185 217 $userClass = new UserClass();
186 218 if ($this->config->get('allow_modif', 'data') === 'none') {
187 219 $currentUser = $userClass->identify(true, 'userId', 'userKey');
@@ -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,23 +268,34 @@
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 - private function endUnsubscribe($msg, $ajax, $type = 'success')
286 + private function endUnsubscribe(string $msg, bool $ajax, string $type = 'success'): void
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,26 +303,50 @@
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
235 - public function unsubscribe()
310 + private function normalizeRedirectUrl(string $url): string
236 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 +
328 + public function unsubscribe(): void
329 + {
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,24 +377,87 @@
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
296 - private function getUserFromUnsubPage()
398 + public function disableTracking(): void
297 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 +
458 + private function getUserFromUnsubPage(): object
459 + {
298 460 $userID = acym_getVar('int', 'user_id');
299 461 $userClass = new UserClass();
300 462
301 463 $user = $userClass->getOneById($userID);
@@ -308,18 +470,21 @@
308 470
309 471 return $user;
310 472 }
311 473
312 - private function unsubscribeAllInner($update = false)
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,27 +493,31 @@
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 - private function redirectUnsubWorked()
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 515
347 - public function unsubscribeAll()
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
@@ -366,41 +535,49 @@
366 535 $this->redirectUnsubWorked();
367 536 }
368 537 }
369 538
370 - public function saveSubscriptions()
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
377 -
546 + // Get the user
378 547 $user = $this->getUserFromUnsubPage();
548 + // Get the list the user want to sub
379 549 $listsChecked = acym_getVar('array', 'lists', []);
380 550 $listsChecked = array_filter($listsChecked, function ($status) {
381 551 return intval($status) === 1;
382 552 });
383 553 $listsChecked = array_keys($listsChecked);
554 + // Get the displayed lists
384 555 $displayedCheckedLists = explode(',', acym_getVar('string', 'displayed_checked_lists', ''));
556 + // We get the user subscriptions
385 557 $userSubscriptions = $userClass->getUserSubscriptionById($user->id);
386 558
387 - $userClass->subscribe($user->id, $listsChecked);
559 + // We subscribe the user to the checked lists
560 + $userClass->subscribe([$user->id], $listsChecked);
388 561
562 + // We unsub to the unchecked lists
389 563 $listsToUnsub = [];
390 564 foreach ($userSubscriptions as $subscription) {
391 - 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)
392 568 && in_array($subscription->id, $displayedCheckedLists)
393 - && intval($subscription->status) === 1) {
569 + && intval($subscription->status) === 1
570 + ) {
394 571 $listsToUnsub[] = $subscription->id;
395 572 }
396 573 }
397 574
398 - $userClass->unsubscribe($user->id, $listsToUnsub);
575 + $userClass->unsubscribe([$user->id], $listsToUnsub);
399 576 $this->redirectUnsubWorked();
400 577 }
401 578
402 - public function unsubscribePage($alreadyExists)
579 + public function unsubscribePage(object $alreadyExists): void
403 580 {
404 581 $userClass = new UserClass();
405 582 $lang = acym_getVar('string', 'language', acym_getLanguageTag());
406 583 $mailId = acym_getVar('int', 'mail_id', 0);
@@ -406,14 +583,20 @@
406 583 $mailId = acym_getVar('int', 'mail_id', 0);
407 584 $campaignListOnly = $this->config->get('unsubscribe_campaign_list_only', '0') === '1';
408 585 $displaySurvey = $this->config->get('unsubpage_survey', '0') === '1';
409 586 $surveyAnswers = $this->config->get('unsub_survey', '[]');
410 - $unsubscribeColor = $this->config->get('unsubscribe_color', '#00a4ff');
587 + $unsubscribeColor = $this->config->get('unsubscribe_color');
588 + if (empty($unsubscribeColor)) {
589 + $unsubscribeColor = '#0079d3';
590 + }
411 591 $hoverColor = $this->darkenRGBColor($unsubscribeColor, 20);
412 592
413 593 $surveyAnswers = json_decode($surveyAnswers, true);
414 594
415 - 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 + }
416 599 acym_setLanguage($lang);
417 600 acym_loadLanguage($lang);
418 601
419 602 $userSubscriptions = $userClass->getUserSubscriptionById($alreadyExists->id, 'id', false, false, true, false, $mailId, $campaignListOnly);
@@ -452,21 +635,19 @@
452 635 $data['surveyAnswers'][] = acym_translation('ACYM_OTHER');
453 636 $data['surveyAnswers'] = array_combine($data['surveyAnswers'], $data['surveyAnswers']);
454 637 }
455 638
456 - if (!empty($this->config->get('unsubscribe_color'))) {
457 - $unsubscribeColor = $this->config->get('unsubscribe_color');
458 - $hoverColor = $this->darkenRGBColor($unsubscribeColor, 20);
459 - $data['unsubscribeColor'] = $unsubscribeColor;
460 - $data['hoverColor'] = $hoverColor;
461 - }
639 + $data['unsubscribeColor'] = $unsubscribeColor;
640 + $data['hoverColor'] = $hoverColor;
462 641 $data['svgImage'] = $this->getSVGImage($unsubscribeColor, $hoverColor);
463 642
464 643 acym_setVar('layout', 'unsubscribepage');
644 + acym_header('Content-Type: text/html; charset=utf-8');
645 +
465 646 parent::display($data);
466 647 }
467 648
468 - public function getSVGImage($unsubscribeColor, $hoverColor)
649 + private function getSVGImage(string $unsubscribeColor, string $hoverColor): string
469 650 {
470 651 if ($this->config->get('display_unsub_image') === '1') {
471 652 $svgPath = ACYM_IMAGES.'unsubscribe/unsub_image.svg';
472 653
@@ -481,9 +662,9 @@
481 662
482 663 return '';
483 664 }
484 665
485 - public function hexToRGB($hexColor)
666 + public function hexToRGB(string $hexColor): array
486 667 {
487 668 $hexColor = ltrim($hexColor, '#');
488 669
489 670 if (strlen($hexColor) === 3) {
@@ -496,9 +677,9 @@
496 677
497 678 return [$r, $g, $b];
498 679 }
499 680
500 - public function darkenRGBColor($hexColor, $percent)
681 + public function darkenRGBColor(string $hexColor, int $percent): string
501 682 {
502 683 $rgbValues = $this->hexToRGB($hexColor);
503 684
504 685 $darkenedRGB = array_map(function ($value) use ($percent) {
@@ -507,14 +688,15 @@
507 688
508 689 return sprintf('rgb(%d, %d, %d)', $darkenedRGB[0], $darkenedRGB[1], $darkenedRGB[2]);
509 690 }
510 691
511 - public function confirm()
692 + public function confirm(): void
512 693 {
513 694 if (acym_isRobot()) {
514 695 return;
515 696 }
516 697
698 + // We identify the user
517 699 $userClass = new UserClass();
518 700 $user = $userClass->identify(true, 'userId', 'userKey');
519 701 if (empty($user)) {
520 702 acym_enqueueMessage(acym_translation('ACYM_USER_NOT_FOUND'), 'error');
@@ -530,20 +712,23 @@
530 712 acym_enqueueMessage(acym_translation('ACYM_SUBSCRIPTION_CONFIRMED'), 'success');
531 713 }
532 714 }
533 715
716 + // Now we can really confirm the user
534 717 if (!$user->confirmed) {
535 718 $userClass->confirm($user->id);
536 719 }
537 720
721 + // Get the redirect url from the configuration page if specified
538 722 $redirectUrl = $this->config->get('confirm_redirect');
539 723 if (!empty($redirectUrl)) {
540 724 $replace = [];
725 + // We replace tags in case of the user added some dynamic information to its url.
541 726 foreach ($user as $key => $val) {
542 727 $replace['{user:'.$key.'}'] = $val;
543 728 }
544 729 $redirectUrl = str_replace(array_keys($replace), $replace, $redirectUrl);
545 - acym_redirect($redirectUrl);
730 + acym_redirect($redirectUrl, '', 'message', false);
546 731
547 732 return;
548 733 }
549 734
@@ -549,14 +734,15 @@
549 734
550 735 acym_redirect(acym_rootURI());
551 736 }
552 737
553 - public function profile()
738 + public function profile(): void
554 739 {
555 740 $userClass = new UserClass();
556 741 $user = $userClass->identify(true);
557 742
558 743 if (empty($user)) {
744 + // Check if the subscription is allowed for non registered users
559 745 $allowvisitor = $this->config->get('allow_visitor', 1);
560 746 if (empty($allowvisitor)) {
561 747 acym_askLog(true, 'ACYM_ONLY_LOGGED', 'message');
562 748
@@ -573,8 +759,9 @@
573 759 acym_addScript(false, ACYM_JS.'libraries/email-misspelled.min.js?v='.filemtime(ACYM_MEDIA.'js'.DS.'libraries'.DS.'email-misspelled.min.js'));
574 760 }
575 761 acym_addScript(false, ACYM_JS.'module.min.js?v='.filemtime(ACYM_MEDIA.'js'.DS.'module.min.js'));
576 762
763 + // Get the page parameters
577 764 $params = new \stdClass();
578 765 $menu = acym_getMenu();
579 766 if (is_object($menu)) {
580 767 $params->source = 'Menu n°'.$menu->id;
@@ -581,8 +768,9 @@
581 768 $menuParameters = method_exists($menu, 'getParams') ? $menu->getParams() : $menu->params;
582 769 $menuparams = new AcymParameter($menuParameters);
583 770
584 771 if (!empty($menuparams)) {
772 + // Joomla specific params
585 773 $params->suffix = $menuparams->get('pageclass_sfx', '');
586 774 $params->page_heading = $menuparams->get('page_heading');
587 775 $params->show_page_heading = $menuparams->get('show_page_heading', 0);
588 776
@@ -595,8 +783,9 @@
595 783 if ($menuparams->get('robots')) {
596 784 acym_addMetadata('robots', $menuparams->get('robots'));
597 785 }
598 786
787 + // Our params
599 788 $params->lists = $menuparams->get('lists', 'none');
600 789 $params->listschecked = $menuparams->get('listschecked', 'none');
601 790 $params->dropdown = $menuparams->get('dropdown');
602 791 $params->hiddenlists = $menuparams->get('hiddenlists', 'None');
@@ -618,10 +807,11 @@
618 807 acym_setVar('layout', 'profile');
619 808 parent::display($data);
620 809 }
621 810
622 - public function prepareParams($values): array
811 + public function prepareParams(object $values): array
623 812 {
813 + //TODO problem with the types throughout the entire method
624 814 if (!isset($values->lists)) {
625 815 $values->lists = 'none';
626 816 }
627 817 if (!isset($values->listschecked)) {
@@ -633,13 +823,18 @@
633 823 if (!isset($values->hiddenlists)) {
634 824 $values->hiddenlists = 'None';
635 825 }
636 826 if (empty($values->fields)) {
637 - $values->fields = ['1', '2'];
827 + $values->fields = [2];
828 + } elseif (is_string($values->fields)) {
829 + $values->fields = explode(',', $values->fields);
638 830 }
639 831 if (!in_array('2', $values->fields) && !in_array(2, $values->fields)) {
640 832 $values->fields[] = '2';
641 833 }
834 + if (!isset($values->page_heading)) {
835 + $values->page_heading = '';
836 + }
642 837 if (!isset($values->introtext)) {
643 838 $values->introtext = '';
644 839 }
645 840 if (!isset($values->posttext)) {
@@ -655,13 +850,19 @@
655 850 if ((empty($values->lists) || in_array('None', $values->lists)) && (empty($values->hiddenlists) || strtolower(implode('', $values->hiddenlists)) == 'none')) {
656 851 $values->lists[] = 'All';
657 852 }
658 853
854 + // Get the current user and his subscription
659 855 $userClass = new UserClass();
660 856 $user = $userClass->identify(true);
661 857 if (empty($user)) {
662 858 $listClass = new ListClass();
663 - $subscription = $listClass->getAll('id', true);
859 + $subscription = $listClass->getAll('id');
860 +
861 + if (acym_isMultilingual()) {
862 + $subscription = $listClass->getTranslatedNameDescription($subscription);
863 + }
864 +
664 865 $user = new \stdClass();
665 866 $user->id = 0;
666 867 $user->key = 0;
667 868
@@ -732,8 +933,9 @@
732 933 }
733 934 $values->hiddenlists = $hiddenListsArray;
734 935 }
735 936
937 + // Overriding the list with the URL parameters: &listid= for displayed list, &hiddenlist= for hidden lists
736 938 $defaultSubscription = $subscription;
737 939 $forceLists = acym_getVar('string', 'listid', '');
738 940 if (!empty($forceLists)) {
739 941 $subscription = [];
@@ -787,17 +989,17 @@
787 989
788 990 return $data;
789 991 }
790 992
791 - public function savechanges()
993 + public function savechanges(): void
792 994 {
793 - acym_checkToken();
995 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
794 996 acym_checkRobots();
795 997
796 998 $userClass = new UserClass();
797 999
798 1000
799 - $status = $userClass->saveForm();
1001 + $status = $userClass->saveForm(true);
800 1002 if ($status) {
801 1003 if ($userClass->confirmationSentSuccess) {
802 1004 $this->displayMessage('ACYM_CONFIRMATION_SENT', true, 'success');
803 1005 } elseif ($userClass->newUser) {
@@ -815,11 +1017,11 @@
815 1017
816 1018 exit;
817 1019 }
818 1020
819 - public function exportdata()
1021 + public function exportdata(): void
820 1022 {
821 - acym_checkToken();
1023 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
822 1024
823 1025 $userClass = new UserClass();
824 1026 $user = $userClass->identify(true, 'userId', 'userKey');
825 1027
@@ -827,38 +1029,73 @@
827 1029 acym_redirect(acym_rootURI());
828 1030 }
829 1031
830 1032 $userHelper = new UserHelper();
831 - $userHelper->exportdata($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;
832 1062 }
833 1063
834 - public function gdprDelete()
1064 + public function gdprDelete(): void
835 1065 {
836 - acym_checkToken();
1066 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
1067 +
837 1068 $userClass = new UserClass();
838 1069 $user = $userClass->identify(true, 'userId', 'userKey');
839 - $userClass->delete($user->id);
1070 + if (empty($user->id)) {
1071 + acym_redirect(acym_rootURI());
1072 + }
1073 +
1074 + $userClass->delete([$user->id], true);
1075 +
1076 + acym_redirect(acym_rootURI(), 'ACYM_GDPR_DATA_DELETED');
840 1077 }
841 1078
842 - public function ajaxGetEnqueuedMessages()
1079 + public function ajaxGetEnqueuedMessages(): void
843 1080 {
844 - acym_session();
845 -
846 1081 $output = '';
847 1082 $types = ['success', 'info', 'warning', 'error'];
848 1083 foreach ($types as $type) {
849 - if (empty($_SESSION['acymessage'.$type])) continue;
1084 + $messages = acym_getVar('array', 'acymessage'.$type, [], 'SESSION', ACYM_ALLOWHTML);
1085 + if (empty($messages)) {
1086 + continue;
1087 + }
850 1088
851 - $messages = $_SESSION['acymessage'.$type];
852 1089 if (!is_array($messages)) {
853 1090 $messages = [$messages];
854 1091 }
855 1092
856 - $output .= '<div class="acym_callout acym__callout__front__'.$type.'" role="alert">';
857 - $output .= implode(' ', $messages);
858 - $output .= '<button class="acym_callout_close" aria-label="'.acym_escape(acym_translation('ACYM_CLOSE_NOTIFICATION')).'">x</button></div>';
1093 + $output .= '<div class="acym_callout acym__callout__front__'.esc_attr($type).'" role="alert">';
1094 + $output .= '<div>'.implode(' ', $messages).'</div>';
1095 + $output .= '<button class="acym_callout_close" aria-label="'.esc_attr(acym_translation('ACYM_CLOSE_NOTIFICATION')).'">x</button></div>';
859 1096
860 - unset($_SESSION['acymessage'.$type]);
1097 + acym_setSession('acymessage'.$type, null, true);
861 1098 }
862 1099
863 1100 if (!empty($output)) {
864 1101 $output = '<div id="acym__callout__container">'.$output.'</div>';