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 | back/Classes/UserClass.php +134 -28 10.11.1 → 11.1.0 View file →
@@ -19,9 +19,8 @@
19 19 'creation_date',
20 20 ];
21 21
22 22 private const FORM_ALLOWED_FIELDS = [
23 - 'id',
24 23 'email',
25 24 'name',
26 25 'language',
27 26 'tracking',
@@ -36,8 +35,9 @@
36 35 public bool $subscribed = false;
37 36 public string $confirmationSentError;
38 37 public bool $triggers = true;
39 38
39 + // For integration, for example someone is activating user subscription on membership pro on the joomla backend and he needs to send the confirmation emails
40 40 public bool $forceConfAdmin = false;
41 41
42 42 public bool $sendWelcomeEmail = true;
43 43 public bool $sendUnsubscribeEmail = true;
@@ -52,11 +52,15 @@
52 52 $this->table = 'user';
53 53 $this->pkey = 'id';
54 54 }
55 55
56 + /**
57 + * Get users depending on filters (search, status, pagination)
58 + */
56 59 public function getMatchingElements(array $settings = []): array
57 60 {
58 61 $results = [];
62 + // Initialize the queries
59 63 $columns = '`user`.*';
60 64 if (!empty($settings['columns'])) {
61 65 foreach ($settings['columns'] as $key => $value) {
62 66 $settings['columns'][$key] = $key === 'join' ? acym_secureDBColumn($value) : 'user.'.acym_secureDBColumn($value);
@@ -100,12 +104,15 @@
100 104 $pagination = new PaginationHelper();
101 105 $settings['elementsPerPage'] = $pagination->getListLimit();
102 106 }
103 107
108 + // This line warns the database that a big query is about to be ran
104 109 acym_query('SET SQL_BIG_SELECTS=1');
110 + // The beginning of $query can be modified in handleSearchFilter, don't move the SELECT up
105 111 $results['elements'] = acym_loadObjectList('SELECT DISTINCT '.$query, '', $settings['offset'], $settings['elementsPerPage']);
106 112 $results['total'] = acym_loadObject($queryCount);
107 113
114 + // Get CMS username separately if needed
108 115 if (!empty($results['elements']) && !empty($settings['cms_username'])) {
109 116 $cmsIds = array_diff(array_column($results['elements'], 'cms_id'), [0, '']);
110 117 if (!empty($cmsIds)) {
111 118 global $acymCmsUserVars;
@@ -116,8 +123,9 @@
116 123 'id'
117 124 );
118 125 foreach ($results['elements'] as $key => $oneElement) {
119 126 if (empty($results['elements'][$key]->cms_id)) continue;
127 + // Clean cms link on subscribers where CMS user no longer exists
120 128 if (empty($userNames[$results['elements'][$key]->cms_id])) {
121 129 $oneUser = $this->getOneById($results['elements'][$key]->id);
122 130 $oneUser->cms_id = 0;
123 131 $this->save($oneUser);
@@ -180,8 +188,9 @@
180 188
181 189 $searchValue = acym_escapeDB('%'.$settings['search'].'%');
182 190 $searchFilter = 'user.email LIKE '.$searchValue.' OR user.name LIKE '.$searchValue.' OR user.id = '.intval($settings['search']);
183 191
192 + // Search in visible custom fields
184 193 $listingFields = acym_loadResultArray('SELECT `id` FROM #__acym_field WHERE `'.(acym_isAdmin() ? 'back' : 'front').'end_listing` = 1');
185 194 if (!empty($listingFields)) {
186 195 $query = 'DISTINCT '.$query;
187 196 $join = ' LEFT JOIN #__acym_user_has_field AS userfield ON user.id = userfield.user_id AND userfield.field_id IN ('.implode(', ', $listingFields).') AND ';
@@ -270,8 +279,9 @@
270 279 $filters[] = 'user.id IN('.implode(',', $settings['onlyElements']).')';
271 280 }
272 281 }
273 282
283 + // For the entity select when selecting "show only selected users"
274 284 if (!empty($settings['showOnlySelected'])) {
275 285 if (!empty($settings['selectedUsers'])) {
276 286 acym_arrayToInteger($settings['selectedUsers']);
277 287 $filters[] = 'user.id IN('.implode(',', $settings['selectedUsers']).')';
@@ -302,20 +312,23 @@
302 312 }
303 313 }
304 314 }
305 315
306 - $where = '';
316 + $whereBlocks = [];
307 317 $join = '';
308 - foreach ($automationHelpers as $index => $automationHelper) {
318 + foreach ($automationHelpers as $automationHelper) {
309 319 if (!empty($automationHelper->where)) {
310 - $where .= ' ('.implode(') and (', $automationHelper->where).')';
311 - if ($index != count($automationHelpers) - 1) $where .= ' OR ';
320 + $whereBlocks[] = ' ('.implode(') and (', $automationHelper->where).')';
312 321 }
313 - if (!empty($automationHelper->join)) $join .= ' JOIN '.implode(' JOIN ', $automationHelper->join);
314 - if (!empty($automationHelper->leftjoin)) $join .= ' LEFT JOIN '.implode(' LEFT JOIN ', $automationHelper->leftjoin);
322 + if (!empty($automationHelper->join)) {
323 + $join .= ' JOIN '.implode(' JOIN ', $automationHelper->join);
324 + }
325 + if (!empty($automationHelper->leftjoin)) {
326 + $join .= ' LEFT JOIN '.implode(' LEFT JOIN ', $automationHelper->leftjoin);
327 + }
315 328 }
316 - if (!empty($where)) {
317 - $filters[] = $where;
329 + if (!empty($whereBlocks)) {
330 + $filters[] = implode(' OR ', $whereBlocks);
318 331 }
319 332 $query .= $join;
320 333 $queryCount .= $join;
321 334 $queryStatus .= $join;
@@ -504,8 +517,15 @@
504 517
505 518 return acym_loadObjectList($query, 'list_id');
506 519 }
507 520
521 + /**
522 + * Identify a user from the acy user_id and key of the e-mail
523 + *
524 + * @param bool $onlyValue only return the user, don't display errors
525 + *
526 + * @return ?object the identified user or null if not found
527 + */
508 528 public function identify(bool $onlyValue = false, ?string $idName = null, ?string $keyName = null): ?object
509 529 {
510 530 $id = acym_getVar('int', empty($idName) ? 'id' : $idName, 0);
511 531 $key = acym_getVar('string', empty($keyName) ? 'key' : $keyName, '');
@@ -515,8 +535,10 @@
515 535 $key = acym_getVar('string', 'key', 0);
516 536 }
517 537
518 538 if (empty($id) || empty($key)) {
539 + //Check if the user is not already in the session...
540 + //Check if the user is not loaded in...
519 541 $currentUserid = acym_currentUserId();
520 542 if (!empty($currentUserid)) {
521 543 return $this->getOneByCMSId($currentUserid);
522 544 }
@@ -546,8 +568,9 @@
546 568 return false;
547 569 }
548 570 acym_arrayToInteger($addLists);
549 571
572 + //TODO was previously shown as an int in the dev documentation, hence not adding the type yet
550 573 if (!is_array($userIds)) {
551 574 $userIds = [$userIds];
552 575 }
553 576 acym_arrayToInteger($userIds);
@@ -568,10 +591,9 @@
568 591 $currentlyUnsubscribed = [];
569 592 foreach ($currentSubscription as $oneList) {
570 593 if ($oneList->status == 1) {
571 594 $currentlySubscribed[$oneList->id] = $oneList;
572 - }
573 - if ($oneList->status == 0) {
595 + } else {
574 596 $currentlyUnsubscribed[$oneList->id] = $oneList;
575 597 }
576 598 }
577 599
@@ -577,8 +599,9 @@
577 599
578 600 $subscribedLists = [];
579 601 $listsNotExisting = [];
580 602 foreach ($addLists as $oneListId) {
603 + // The user is already subscribed
581 604 if (empty($oneListId) || !empty($currentlySubscribed[$oneListId])) {
582 605 continue;
583 606 }
584 607
@@ -591,13 +614,15 @@
591 614 $subscription = new \stdClass();
592 615 $subscription->user_id = $userId;
593 616 $subscription->list_id = $oneListId;
594 617 $subscription->status = 1;
595 - $subscription->subscription_date = date('Y-m-d H:i:s', time() - date('Z'));
618 + $subscription->subscription_date = gmdate('Y-m-d H:i:s', time());
596 619
597 620 if (empty($currentSubscription[$oneListId])) {
621 + // The user isn't already subscribed, we subscribe him!
598 622 acym_insertObject('#__acym_user_has_list', $subscription);
599 623 } elseif (!empty($currentlyUnsubscribed[$oneListId])) {
624 + // The user re-subscribes or we have to confirm the subscription
600 625 acym_updateObject('#__acym_user_has_list', $subscription, ['user_id', 'list_id']);
601 626 }
602 627
603 628 $subscribedLists[] = $oneListId;
@@ -633,8 +658,9 @@
633 658 $countUnsubscribe = 0;
634 659
635 660 foreach ($userIds as $id) {
636 661 $userStat = $userStatClass->getOneByMailAndUserId($mailId, $id);
662 + // We didn't send this email to this user, something is wrong
637 663 if (empty($userStat)) continue;
638 664
639 665 $newUserStat = new \stdClass();
640 666 $newUserStat->mail_id = $mailId;
@@ -660,8 +686,9 @@
660 686 if (empty($lists)) {
661 687 return false;
662 688 }
663 689
690 + //TODO was previously shown as an int in the dev documentation, hence not adding the type yet
664 691 if (!is_array($userIds)) {
665 692 $userIds = [$userIds];
666 693 }
667 694 acym_arrayToInteger($userIds);
@@ -690,12 +717,14 @@
690 717
691 718 $unsubscribedLists = [];
692 719 $listsNotExisting = [];
693 720 foreach ($lists as $oneListId) {
721 + // The user is already unsubscribed
694 722 if (empty($oneListId) || !empty($currentlyUnsubscribed[$oneListId])) {
695 723 continue;
696 724 }
697 725
726 + // Don't unsubscribe from non-subscribed lists
698 727 if (empty($currentlySubscribed[$oneListId])) {
699 728 continue;
700 729 }
701 730
@@ -708,12 +737,14 @@
708 737 $subscription = new \stdClass();
709 738 $subscription->user_id = $userId;
710 739 $subscription->list_id = $oneListId;
711 740 $subscription->status = 0;
712 - $subscription->unsubscribe_date = date('Y-m-d H:i:s', time() - date('Z'));
741 + $subscription->unsubscribe_date = gmdate('Y-m-d H:i:s', time());
713 742 if (empty($currentSubscription[$oneListId])) {
743 + // The user isn't already subscribed, we unsubscribe him directly
714 744 acym_insertObject('#__acym_user_has_list', $subscription);
715 745 } else {
746 + // The user unsubscribed from a list
716 747 acym_updateObject('#__acym_user_has_list', $subscription, ['user_id', 'list_id']);
717 748 }
718 749
719 750 $unsubscribedLists[] = $oneListId;
@@ -791,9 +822,9 @@
791 822 }
792 823
793 824 public function onlyManageableUsers(array &$elements): void
794 825 {
795 - if (acym_isAdmin()) {
826 + if (acym_isAdmin() && acym_isAllowed('users')) {
796 827 return;
797 828 }
798 829
799 830 $listClass = new ListClass();
@@ -891,9 +922,14 @@
891 922 if (!isset($user->active)) {
892 923 $user->active = 1;
893 924 }
894 925
926 + if (!isset($user->tracking)) {
927 + $user->tracking = $this->config->get('dont_track_by_default', 0) ? 0 : 1;
928 + }
929 +
895 930 if (empty($user->language)) {
931 + // Take the user account's language
896 932 if (!acym_isAdmin()) {
897 933 $cmsUserLanguage = acym_getCmsUserLanguage();
898 934 }
899 935
@@ -898,11 +934,10 @@
898 934 }
899 935
900 936 if (!empty($cmsUserLanguage)) {
901 937 $user->language = $cmsUserLanguage;
902 - } elseif (acym_isMultilingual()) {
903 - $configUserLanguage = $this->config->get('multilingual_user_default', 'current_language');
904 - $user->language = $configUserLanguage === 'current_language' ? acym_getLanguageTag() : $configUserLanguage;
938 + } else {
939 + $user->language = acym_getDefaultUserLanguage();
905 940 }
906 941 }
907 942
908 943 $currentUserid = acym_currentUserId();
@@ -908,8 +943,9 @@
908 943 $currentUserid = acym_currentUserId();
909 944 $currentEmail = acym_currentUserEmail();
910 945 $allowVisitors = $this->config->get('allow_visitor', 1) == 1;
911 946 if ($this->checkVisitor && !acym_isAdmin() && !$allowVisitors && (empty($currentUserid) || strtolower($currentEmail) !== $user->email)) {
947 + //We don't accept the subscription as either the user is not logged in or it's not the good one
912 948 $this->errors[] = acym_translation('ACYM_ONLY_LOGGED');
913 949
914 950 return null;
915 951 }
@@ -924,13 +960,13 @@
924 960 }
925 961
926 962 $user->key = acym_generateKey(14);
927 963
928 - $user->creation_date = date('Y-m-d H:i:s', time() - date('Z'));
964 + $user->creation_date = gmdate('Y-m-d H:i:s', time());
929 965 } elseif (!empty($user->confirmed)) {
930 966 $oldUser = $this->getOneByIdWithCustomFields($user->id);
931 967 if (!empty($oldUser) && empty($oldUser['confirmed'])) {
932 - $user->confirmation_date = date('Y-m-d H:i:s', time() - date('Z'));
968 + $user->confirmation_date = gmdate('Y-m-d H:i:s', time());
933 969 $user->confirmation_ip = acym_getIP();
934 970
935 971 if ($this->triggers) {
936 972 acym_trigger('onAcymAfterUserConfirm', [&$user]);
@@ -956,11 +992,13 @@
956 992 }
957 993
958 994 $oneAttribute = trim(strtolower($oneAttribute));
959 995 if (!in_array($oneAttribute, self::RESTRICTED_FIELDS)) {
960 - $user->$oneAttribute = strip_tags($value);
996 + $user->$oneAttribute = acym_stripTags($value);
961 997 }
962 998
999 + // Convert into utf-8 in case of it's not already
1000 + // Double test on UTF-8 because the preg_match returns an error on string longer than 200 characters
963 1001 if (is_numeric($user->$oneAttribute)) {
964 1002 continue;
965 1003 }
966 1004
@@ -977,8 +1015,9 @@
977 1015 $user->$oneAttribute = acym_utf8Encode($user->$oneAttribute);
978 1016 }
979 1017 }
980 1018
1019 + // Prevent SQL error on flags that aren't cleaned
981 1020 if (!empty($user->automation) && strlen($user->automation) > 20) {
982 1021 $user->automation = '';
983 1022 }
984 1023
@@ -994,9 +1033,9 @@
994 1033 if (!empty($userCmsID)) $user->cms_id = $userCmsID;
995 1034 }
996 1035 if ($this->triggers) {
997 1036 $result = acym_trigger('onAcymBeforeUserCreate', [&$user]);
998 - if (in_array(false, $result)) {
1037 + if (is_array($result) && in_array(false, $result)) {
999 1038 $acycheckerError = acym_getVar('string', 'acychecker_error');
1000 1039 if (!empty($acycheckerError)) {
1001 1040 if ($ajax) {
1002 1041 $this->errors[] = $acycheckerError;
@@ -1015,8 +1054,9 @@
1015 1054 }
1016 1055
1017 1056 $userID = parent::save($user);
1018 1057
1058 + // Save custom fields if there are any
1019 1059 $fieldClass = new FieldClass();
1020 1060 if (!empty($customFields)) {
1021 1061 foreach ($customFields as $key => $value) {
1022 1062 if (!is_string($value)) {
@@ -1022,8 +1062,9 @@
1022 1062 if (!is_string($value)) {
1023 1063 continue;
1024 1064 }
1025 1065
1066 + // Remove most of the existing emojis that are not supported
1026 1067 $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $value);
1027 1068
1028 1069 $field = $fieldClass->getOneById($key);
1029 1070 if (empty($field) || $field->type !== 'textarea') {
@@ -1075,8 +1116,12 @@
1075 1116 $user->$attribute = $value;
1076 1117 }
1077 1118 }
1078 1119
1120 + if (!empty($connectedUser->id)) {
1121 + $user->id = $connectedUser->id;
1122 + }
1123 +
1079 1124 if (empty($user->email)) {
1080 1125 if (!empty($connectedUser->email)) {
1081 1126 $user->email = $connectedUser->email;
1082 1127 }
@@ -1098,9 +1143,11 @@
1098 1143 $allowUserModifications = true;
1099 1144 $allowSubscriptionModifications = true;
1100 1145 }
1101 1146
1147 + // Check if it already exists...
1102 1148 if (!empty($user->email)) {
1149 + // Do we already have a user with this same e-mail address?
1103 1150 if (empty($user->id)) {
1104 1151 $user->id = 0;
1105 1152 }
1106 1153 $existUser = acym_loadObject('SELECT * FROM #__acym_user WHERE email = '.acym_escapeDB($user->email).' AND id != '.intval($user->id));
@@ -1114,8 +1161,10 @@
1114 1161 }
1115 1162 }
1116 1163 }
1117 1164
1165 + // Did the user ask for its e-mail address to be modified?
1166 + // If so, we set it as not confirmed automatically.
1118 1167 if (!empty($user->id) && !empty($user->email)) {
1119 1168 $existUser = $this->getOneById($user->id);
1120 1169 if (trim(strtolower($user->email)) != strtolower($existUser->email)) {
1121 1170 $user->confirmed = 0;
@@ -1121,18 +1170,27 @@
1121 1170 $user->confirmed = 0;
1122 1171 }
1123 1172 }
1124 1173
1174 + $fromProfile = acym_getVar('int', 'acyprofile', 0) == 1;
1175 +
1125 1176 $this->newUser = empty($user->id);
1126 1177 if (empty($user->id) || $allowUserModifications) {
1127 1178 if ($this->newUser && $this->config->get('require_confirmation', 1) == 1) {
1128 1179 $user->confirmed = 0;
1129 1180 }
1181 +
1182 + if ($fromProfile && !(isset($user->confirmed) && empty($user->confirmed))) {
1183 + $this->sendConf = false;
1184 + }
1185 +
1186 + // Get custom fields to save them if exist
1130 1187 $customFieldData = acym_getVar('array', 'customField', []);
1131 1188 $id = $this->save($user, $customFieldData, $ajax);
1132 1189 $allowSubscriptionModifications = true;
1133 1190 } else {
1134 1191 $id = $user->id;
1192 + //We didn't save the user... but we should check the confirm field anyway
1135 1193 if (isset($user->confirmed) && empty($user->confirmed)) {
1136 1194 $this->sendConfirmation($id);
1137 1195 }
1138 1196 }
@@ -1155,10 +1213,8 @@
1155 1213 }
1156 1214 }
1157 1215 }
1158 1216
1159 - $fromProfile = acym_getVar('int', 'acyprofile', 0) == 1;
1160 -
1161 1217 if (empty($formData['listsub'])) {
1162 1218 if (!$fromProfile) $this->sendNotification($id, 'acy_notification_subform');
1163 1219
1164 1220 $this->sendNotification(
@@ -1218,8 +1274,9 @@
1218 1274
1219 1275 $mailerHelper = new MailerHelper();
1220 1276 $mailerHelper->report = (bool)$this->config->get('confirm_message', 0);
1221 1277
1278 + //TODO $mailerHelper->addParam('user:subscription', implode('<br/>', $subscription));
1222 1279 $mailClass = new MailClass();
1223 1280 $confirmationEmail = $mailClass->getOneByName('acy_confirm');
1224 1281 $this->confirmationSentSuccess = !empty($confirmationEmail) && $mailerHelper->sendOne($confirmationEmail->id, $myUser->id);
1225 1282 $this->confirmationSentError = $mailerHelper->reportMessage;
@@ -1259,9 +1316,10 @@
1259 1316 {
1260 1317 $user = $this->getOneById($userId);
1261 1318 if (empty($user)) return;
1262 1319
1263 - $confirmDate = date('Y-m-d H:i:s', time() - date('Z'));
1320 + // We confirm the user and add the confirmation_date and confirmation_ip in the table.
1321 + $confirmDate = gmdate('Y-m-d H:i:s', time());
1264 1322 $ip = acym_getIP();
1265 1323 $query = 'UPDATE `#__acym_user`';
1266 1324 $query .= ' SET `confirmed` = 1, `confirmation_date` = '.acym_escapeDB($confirmDate).', `confirmation_ip` = '.acym_escapeDB($ip);
1267 1325 $query .= ' WHERE `id` = '.intval($userId).' LIMIT 1';
@@ -1272,9 +1330,10 @@
1272 1330 $res = false;
1273 1331 }
1274 1332 if ($res === false) {
1275 1333 $errorMessage = isset($e) ? $e->getMessage() : acym_getDBError();
1276 - $msg = acym_translation('ACYM_CONTACT_ADMIN_ERROR').'<br />'.substr(strip_tags($errorMessage), 0, 200).'...';
1334 + // If there is an error we definitely want to warn the user about it.
1335 + $msg = acym_translation('ACYM_CONTACT_ADMIN_ERROR').'<br />'.substr(acym_stripTags($errorMessage), 0, 200).'...';
1277 1336 acym_display($msg, 'error');
1278 1337 exit;
1279 1338 }
1280 1339
@@ -1351,8 +1410,9 @@
1351 1410 public function getAllColumnsUserAndCustomField(bool $inAction = false, bool $withSubInfos = false): array
1352 1411 {
1353 1412 $return = [];
1354 1413
1414 + // Basic fields in the user table
1355 1415 $userFields = acym_getColumns('user');
1356 1416 foreach ($userFields as $value) {
1357 1417 $return[$value] = $value;
1358 1418 }
@@ -1358,8 +1418,9 @@
1358 1418 }
1359 1419
1360 1420 $languageFieldId = $this->config->get(FieldClass::LANGUAGE_FIELD_ID_KEY, 0);
1361 1421
1422 + // Acy custom fields except name, email and language because they already are in the user table
1362 1423 $customFields = acym_loadObjectList(
1363 1424 'SELECT * FROM #__acym_field WHERE id NOT IN (1, 2, '.intval($languageFieldId).') '.($inAction ? 'AND type != "phone"' : ''),
1364 1425 'id'
1365 1426 );
@@ -1443,8 +1504,9 @@
1443 1504 }
1444 1505
1445 1506 public function synchSaveCmsUser(array $user, bool $isnew, ?array $oldUser = null): void
1446 1507 {
1508 + // If the source is not already defined, we define it here
1447 1509 $source = acym_getVar('string', 'acy_source', '');
1448 1510 if (empty($source)) acym_setVar('acy_source', ACYM_CMS);
1449 1511
1450 1512 if (!$this->config->get('regacy', 0)) return;
@@ -1452,21 +1514,26 @@
1452 1514 $this->checkVisitor = false;
1453 1515 $this->sendConf = false;
1454 1516
1455 1517 $regacyForceConf = $this->config->get('regacy_forceconf', 0);
1518 + /* * * * * * * * * * * * * * * * * * *
1519 + * Step 1: create / update the user *
1520 + * * * * * * * * * * * * * * * * * * */
1456 1521 $cmsUser = new \stdClass();
1457 - $cmsUser->email = trim(strip_tags($user['email']));
1522 + $cmsUser->email = trim(acym_stripTags($user['email']));
1458 1523 if (!acym_isValidEmail($cmsUser->email)) return;
1459 - if (!empty($user['name'])) $cmsUser->name = trim(strip_tags($user['name']));
1524 + if (!empty($user['name'])) $cmsUser->name = trim(acym_stripTags($user['name']));
1460 1525 if (!$regacyForceConf) $cmsUser->confirmed = 1;
1461 1526 $cmsUser->active = 1 - intval($user['block']);
1462 1527 $cmsUser->cms_id = $user['id'];
1463 1528
1464 1529 if (!$isnew && !empty($oldUser['email']) && $user['email'] != $oldUser['email']) {
1530 + // The user changed its email address, load the current Acy user if any
1465 1531 $acyUser = $this->getOneByEmail($oldUser['email']);
1466 1532 if (!empty($acyUser)) $cmsUser->id = $acyUser->id;
1467 1533 }
1468 1534
1535 + // Just in case of this is an existing user but the e-mail address has been modified by something else
1469 1536 if (empty($cmsUser->id) && !empty($cmsUser->cms_id)) {
1470 1537 $acyUser = $this->getOneByCMSId($cmsUser->cms_id);
1471 1538 if (!empty($acyUser)) $cmsUser->id = $acyUser->id;
1472 1539 }
@@ -1471,12 +1538,15 @@
1471 1538 if (!empty($acyUser)) $cmsUser->id = $acyUser->id;
1472 1539 }
1473 1540
1474 1541 $acyUser = $this->getOneByEmail($cmsUser->email);
1542 + // If an Acy user with the same email address already exists
1475 1543 if (!empty($acyUser)) {
1544 + // And wasn't linked to the site account, link it
1476 1545 if (empty($cmsUser->id)) {
1477 1546 $cmsUser->id = $acyUser->id;
1478 1547 } elseif ($cmsUser->id != $acyUser->id) {
1548 + // And has a different id, delete it
1479 1549 $this->delete([$acyUser->id]);
1480 1550 }
1481 1551 } else {
1482 1552 $cmsUser->source = $source;
@@ -1488,16 +1558,21 @@
1488 1558 if (empty($id)) {
1489 1559 return;
1490 1560 }
1491 1561
1562 + // Force trigger confirmation process on cms user confirmation (send welcome emails, automation, save history...)
1492 1563 $confirmationRequired = $this->config->get('require_confirmation', 1);
1493 1564 if (!$isnew && !$regacyForceConf && $user['block'] == 0 && !empty($oldUser['block']) && $confirmationRequired == 1) {
1494 1565 $this->confirm($id);
1495 1566 }
1496 1567
1568 + /* * * * * * * * * * * * * * * * * * * * * *
1569 + * Step 2: Handle the user's subscription *
1570 + * * * * * * * * * * * * * * * * * * * * * */
1497 1571
1498 1572 $currentSubscription = $this->getSubscriptionStatus($id);
1499 1573
1574 + // In the Acy configuration, we can tell AcyMailing to automatically subscribe newly created users to some lists
1500 1575 $autoLists = $isnew ? $this->config->get('regacy_autolists', '') : '';
1501 1576 $autoLists = explode(',', $autoLists);
1502 1577 acym_arrayToInteger($autoLists);
1503 1578
@@ -1503,8 +1578,9 @@
1503 1578
1504 1579 $listsClass = new ListClass();
1505 1580 $allLists = $listsClass->getAll();
1506 1581
1582 + // The user can select some lists on the registration form
1507 1583 $visibleLists = acym_getVar('string', 'regacy_visible_lists', '');
1508 1584 $visibleLists = explode(',', $visibleLists);
1509 1585 acym_arrayToInteger($visibleLists);
1510 1586
@@ -1511,8 +1587,9 @@
1511 1587 $visibleListsChecked = acym_getVar('array', 'regacy_visible_lists_checked', []);
1512 1588 acym_arrayToInteger($visibleListsChecked);
1513 1589
1514 1590
1591 + // Handle the unsubscription
1515 1592 if (!$isnew && !empty($visibleLists)) {
1516 1593 $currentlySubscribedLists = [];
1517 1594 foreach ($currentSubscription as $oneSubscription) {
1518 1595 if ($oneSubscription->status == 1) $currentlySubscribedLists[] = $oneSubscription->list_id;
@@ -1520,8 +1597,9 @@
1520 1597 $unsubscribeLists = array_intersect($currentlySubscribedLists, array_diff($visibleLists, $visibleListsChecked));
1521 1598 $this->unsubscribe([$id], $unsubscribeLists);
1522 1599 }
1523 1600
1601 + // Handle the subscription
1524 1602 $listsToSubscribe = [];
1525 1603 foreach ($allLists as $oneList) {
1526 1604 if (!$oneList->active) continue;
1527 1605 if (!empty($currentSubscription[$oneList->id]) && $currentSubscription[$oneList->id]->status == 1) continue;
@@ -1540,11 +1618,13 @@
1540 1618 if (!empty($listsToSubscribe)) $this->subscribe([$id], $listsToSubscribe);
1541 1619
1542 1620 if ($isnew) $this->sendNotification($id, 'acy_notification_create');
1543 1621
1622 + // We don't force the confirmation email, or the user is disabled, or he's already confirmed
1544 1623 $acymailingUser = $this->getOneById($id);
1545 1624 if (!empty($user['block']) || !empty($acymailingUser->confirmed)) return;
1546 1625
1626 + // New active user, or just activated the user, send the email
1547 1627 if ($isnew || !empty($oldUser['block'])) {
1548 1628 if ($confirmationRequired && $regacyForceConf) {
1549 1629 $this->forceConf = true;
1550 1630 $this->sendConfirmation($id);
@@ -1549,8 +1629,9 @@
1549 1629 $this->forceConf = true;
1550 1630 $this->sendConfirmation($id);
1551 1631 }
1552 1632
1633 + // Send welcome emails on CMS user confirmation (no Acym confirmation required)
1553 1634 if (!$confirmationRequired && !empty($oldUser['email'])) {
1554 1635 $listIDs = acym_loadResultArray('SELECT `list_id` FROM `#__acym_user_has_list` WHERE `status` = 1 AND `user_id` = '.intval($id));
1555 1636 if (empty($listIDs)) return;
1556 1637 $listsClass->sendWelcome($id, $listIDs);
@@ -1570,8 +1651,11 @@
1570 1651 acym_query('UPDATE #__acym_user SET `cms_id` = 0 WHERE `id` = '.intval($acyUser->id));
1571 1652 }
1572 1653 }
1573 1654
1655 + /**
1656 + * Search users via partial email address
1657 + */
1574 1658 public function getUsersLikeEmail(string $search): array
1575 1659 {
1576 1660 $query = 'SELECT id, email FROM #__acym_user WHERE email LIKE '.acym_escapeDB('%'.$search.'%');
1577 1661
@@ -1596,8 +1680,9 @@
1596 1680 $mailerHelper->addParam('user:'.$map, $value);
1597 1681 }
1598 1682 }
1599 1683
1684 + // Load the subscription
1600 1685 $rawSubscription = $this->getUserSubscriptionById($userId);
1601 1686 $subscription = [''];
1602 1687 foreach ($rawSubscription as $listId => $listData) {
1603 1688 $currentList = $listData->name.' => ';
@@ -1718,9 +1803,9 @@
1718 1803 if (empty($userId)) {
1719 1804 return false;
1720 1805 }
1721 1806
1722 - if (acym_isAdmin()) {
1807 + if (acym_isAdmin() && acym_isAllowed('users')) {
1723 1808 return true;
1724 1809 }
1725 1810
1726 1811 $user = $this->getOneById($subscriberId);
@@ -1747,8 +1832,13 @@
1747 1832 {
1748 1833 $limit = $options['limit'] ?? 10;
1749 1834 $offset = $options['offset'] ?? 0;
1750 1835 $filters = $options['filters'] ?? [];
1836 + $orderBy = $options['orderBy'] ?? '';
1837 + $orderBySort = strtolower((string)($options['orderBySort'] ?? ''));
1838 + if (!in_array($orderBySort, ['asc', 'desc'], true)) {
1839 + $orderBySort = 'asc';
1840 + }
1751 1841
1752 1842 $conditions = [];
1753 1843 foreach ($filters as $column => $filter) {
1754 1844 if (in_array($column, ['id', 'cms_id', 'active', 'confirmed', 'tracking'])) {
@@ -1770,7 +1860,23 @@
1770 1860 if (!empty($conditions)) {
1771 1861 $query .= ' WHERE '.implode(' AND ', $conditions);
1772 1862 }
1773 1863
1864 + $orderByColumn = empty($orderBy) ? $this->pkey : acym_secureDBColumn($orderBy);
1865 + $query .= ' ORDER BY '.$orderByColumn.' '.$orderBySort;
1866 +
1867 + // Any other column can hold duplicates, we need a unique one to break the ties
1868 + if ($orderByColumn !== $this->pkey) {
1869 + $query .= ', '.$this->pkey.' ASC';
1870 + }
1871 +
1774 1872 return acym_loadObjectList($query, $this->pkey, $offset, $limit);
1873 + }
1874 +
1875 + public function disableTracking(object $user, int $mailId): void
1876 + {
1877 + acym_query('UPDATE #__acym_user SET `tracking` = 0 WHERE `id` = '.intval($user->id));
1878 +
1879 + $historyClass = new HistoryClass();
1880 + $historyClass->insert($user->id, 'tracking_disabled', [], $mailId);
1775 1881 }
1776 1882 }