| @@ -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 | } |