PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.4
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.4
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
acymailing / back / Classes / UserClass.php

UserClass.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.0.4, at back/Classes/UserClass.php

1,872 lines 71.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Classes;
4
5 use AcyMailing\Helpers\AutomationHelper;
6 use AcyMailing\Helpers\MailerHelper;
7 use AcyMailing\Helpers\PaginationHelper;
8 use AcyMailing\Core\AcymClass;
9
10 class UserClass extends AcymClass
11 {
12 public const STATUS_SUBSCRIBED = 1;
13 private const RESTRICTED_FIELDS = [
14 'id',
15 'key',
16 'confirmed',
17 'active',
18 'cms_id',
19 'creation_date',
20 ];
21
22 private const FORM_ALLOWED_FIELDS = [
23 'email',
24 'name',
25 'language',
26 'tracking',
27 ];
28
29 public bool $checkVisitor = true;
30 public bool $requireId = false;
31 public bool $sendConf = true;
32 public bool $confirmationSentSuccess = false;
33 public bool $newUser = false;
34 public bool $blockNotifications = false;
35 public bool $subscribed = false;
36 public string $confirmationSentError;
37 public bool $triggers = true;
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 public bool $forceConfAdmin = false;
41
42 public bool $sendWelcomeEmail = true;
43 public bool $sendUnsubscribeEmail = true;
44
45 private bool $allowModif = false;
46 private bool $forceConf = false;
47
48 public function __construct()
49 {
50 parent::__construct();
51
52 $this->table = 'user';
53 $this->pkey = 'id';
54 }
55
56 /**
57 * Get users depending on filters (search, status, pagination)
58 */
59 public function getMatchingElements(array $settings = []): array
60 {
61 $results = [];
62 // Initialize the queries
63 $columns = '`user`.*';
64 if (!empty($settings['columns'])) {
65 foreach ($settings['columns'] as $key => $value) {
66 $settings['columns'][$key] = $key === 'join' ? acym_secureDBColumn($value) : 'user.'.acym_secureDBColumn($value);
67 }
68 $columns = implode(', ', $settings['columns']);
69 }
70
71 $query = $columns.' FROM #__acym_user AS `user`';
72 $queryCount = 'SELECT COUNT(DISTINCT user.id) AS total FROM #__acym_user AS user';
73 $queryStatus = 'SELECT COUNT(DISTINCT user.id) AS number, user.confirmed + user.active*2 AS score FROM #__acym_user AS user';
74 $filters = [];
75
76 if (!empty($settings['join'])) {
77 $query .= $this->getJoinForQuery($settings['join']);
78 }
79 $this->handleSegmentFilter($settings, $filters, $query, $queryCount, $queryStatus);
80 $this->handleSubscriptionFilter($settings, $filters, $query, $queryCount, $queryStatus);
81 $this->handleFrontend($settings, $query, $queryCount, $queryStatus);
82 $this->handleSearchFilter($settings, $filters, $query, $queryCount, $queryStatus);
83 $results['status'] = $this->handleUserStatusFilter($settings, $filters, $queryStatus);
84 $entityResult = $this->handleEntitySelect($settings, $filters);
85
86 if (!empty($entityResult)) {
87 return $entityResult;
88 }
89
90 if (!empty($filters)) {
91 $query .= ' WHERE ('.implode(') AND (', $filters).')';
92 $queryCount .= ' WHERE ('.implode(') AND (', $filters).')';
93 }
94
95 if (!empty($settings['ordering']) && !empty($settings['ordering_sort_order'])) {
96 $query .= ' ORDER BY user.'.acym_secureDBColumn($settings['ordering']).' '.acym_secureDBColumn(strtoupper($settings['ordering_sort_order']));
97 }
98
99 if (empty($settings['offset']) || $settings['offset'] < 0) {
100 $settings['offset'] = 0;
101 }
102
103 if (empty($settings['elementsPerPage']) || $settings['elementsPerPage'] < 1) {
104 $pagination = new PaginationHelper();
105 $settings['elementsPerPage'] = $pagination->getListLimit();
106 }
107
108 // This line warns the database that a big query is about to be ran
109 acym_query('SET SQL_BIG_SELECTS=1');
110 // The beginning of $query can be modified in handleSearchFilter, don't move the SELECT up
111 $results['elements'] = acym_loadObjectList('SELECT DISTINCT '.$query, '', $settings['offset'], $settings['elementsPerPage']);
112 $results['total'] = acym_loadObject($queryCount);
113
114 // Get CMS username separately if needed
115 if (!empty($results['elements']) && !empty($settings['cms_username'])) {
116 $cmsIds = array_diff(array_column($results['elements'], 'cms_id'), [0, '']);
117 if (!empty($cmsIds)) {
118 global $acymCmsUserVars;
119 $userNames = acym_loadObjectList(
120 'SELECT '.$acymCmsUserVars->id.' AS id, '.$acymCmsUserVars->username.' AS `cms_username`
121 FROM '.$acymCmsUserVars->table.'
122 WHERE id IN ('.implode(', ', array_values($cmsIds)).')',
123 'id'
124 );
125 foreach ($results['elements'] as $key => $oneElement) {
126 if (empty($results['elements'][$key]->cms_id)) continue;
127 // Clean cms link on subscribers where CMS user no longer exists
128 if (empty($userNames[$results['elements'][$key]->cms_id])) {
129 $oneUser = $this->getOneById($results['elements'][$key]->id);
130 $oneUser->cms_id = 0;
131 $this->save($oneUser);
132 $results['elements'][$key]->cms_id = 0;
133 continue;
134 }
135 $results['elements'][$key]->cms_username = $userNames[$results['elements'][$key]->cms_id]->cms_username;
136 }
137 }
138 }
139
140 return $results;
141 }
142
143 private function handleSubscriptionFilter(array $settings, array &$filters, string &$query, string &$queryCount, string &$queryStatus): void
144 {
145 if (empty($settings['list'])) {
146 return;
147 }
148
149 $listJoin = ' JOIN #__acym_user_has_list AS list ON user.id = list.user_id AND list.list_id = '.intval($settings['list']).' ';
150
151 if ($settings['list_status'] === 'none') {
152 $listJoin = ' LEFT'.$listJoin;
153 $filters[] = 'list.user_id IS NULL';
154 } else {
155 $filters[] = 'list.status = '.($settings['list_status'] === 'sub' ? 1 : 0);
156 }
157
158 $query .= $listJoin;
159 $queryCount .= $listJoin;
160 $queryStatus .= $listJoin;
161 }
162
163 private function handleFrontend(array $settings, string &$query, string &$queryCount, string &$queryStatus): void
164 {
165 if (!acym_isAdmin()) {
166 $settings['creator_id'] = acym_currentUserId();
167 if (empty($settings['creator_id'])) {
168 $settings['creator_id'] = '-1';
169 }
170 }
171
172 if (!empty($settings['creator_id'])) {
173 $userGroups = acym_getGroupsByUser($settings['creator_id']);
174 $groupCondition = 'list.access LIKE "%,'.implode(',%" OR list.access LIKE "%,', $userGroups).',%"';
175 $joinList = ' JOIN #__acym_user_has_list as user_list ON user_list.user_id = user.id JOIN #__acym_list AS list ON list.id = user_list.list_id AND (list.cms_user_id = '.intval(
176 $settings['creator_id']
177 ).' OR '.$groupCondition.')';
178
179 $query .= $joinList;
180 $queryCount .= $joinList;
181 $queryStatus .= $joinList;
182 }
183 }
184
185 private function handleSearchFilter(array $settings, array &$filters, string &$query, string &$queryCount, string &$queryStatus): void
186 {
187 if (empty($settings['search'])) return;
188
189 $searchValue = acym_escapeDB('%'.$settings['search'].'%');
190 $searchFilter = 'user.email LIKE '.$searchValue.' OR user.name LIKE '.$searchValue.' OR user.id = '.intval($settings['search']);
191
192 // Search in visible custom fields
193 $listingFields = acym_loadResultArray('SELECT `id` FROM #__acym_field WHERE `'.(acym_isAdmin() ? 'back' : 'front').'end_listing` = 1');
194 if (!empty($listingFields)) {
195 $query = 'DISTINCT '.$query;
196 $join = ' LEFT JOIN #__acym_user_has_field AS userfield ON user.id = userfield.user_id AND userfield.field_id IN ('.implode(', ', $listingFields).') AND ';
197 $cfSearch = 'userfield.value LIKE '.$searchValue;
198
199 $fieldsWithMultipleValues = acym_loadObjectList(
200 'SELECT *
201 FROM #__acym_field
202 WHERE id IN ('.implode(', ', $listingFields).')
203 AND `value` LIKE '.acym_escapeDB('%"title":"'.$settings['search'].'"%')
204 );
205
206 if (!empty($fieldsWithMultipleValues)) {
207 foreach ($fieldsWithMultipleValues as $oneField) {
208 $oneField->value = json_decode($oneField->value, true);
209 foreach ($oneField->value as $value) {
210 if (stripos($value['title'], $settings['search']) !== false) {
211 $cfSearch .= ' OR (userfield.field_id = '.intval($oneField->id).' AND userfield.value LIKE '.acym_escapeDB('%'.$value['value'].'%').')';
212 }
213 }
214 }
215 }
216
217 $join .= '('.$cfSearch.')';
218 $searchFilter .= ' OR userfield.field_id IS NOT NULL';
219
220 $query .= $join;
221 $queryCount .= $join;
222 $queryStatus .= $join;
223 }
224
225 $filters[] = $searchFilter;
226 }
227
228 private function handleUserStatusFilter(array $settings, array &$filters, string $queryStatus): array
229 {
230 if (!empty($filters)) {
231 $queryStatus .= ' WHERE ('.implode(') AND (', $filters).')';
232 }
233
234 $usersPerStatusObject = acym_loadObjectList($queryStatus.' GROUP BY score', 'score');
235
236 $usersPerStatus = [];
237 for ($i = 0; $i < 4; $i++) {
238 $usersPerStatus[$i] = empty($usersPerStatusObject[$i]) ? 0 : $usersPerStatusObject[$i]->number;
239 }
240
241 if (!empty($settings['status'])) {
242 $allowedStatus = [
243 'active' => 'active = 1',
244 'inactive' => 'active = 0',
245 'confirmed' => 'confirmed = 1',
246 'unconfirmed' => 'confirmed = 0',
247 ];
248 if (empty($allowedStatus[$settings['status']])) {
249 die('Injection denied');
250 }
251 $filters[] = 'user.'.$allowedStatus[$settings['status']];
252 }
253
254 return [
255 'all' => array_sum($usersPerStatus),
256 'active' => $usersPerStatus[2] + $usersPerStatus[3],
257 'inactive' => $usersPerStatus[0] + $usersPerStatus[1],
258 'confirmed' => $usersPerStatus[1] + $usersPerStatus[3],
259 'unconfirmed' => $usersPerStatus[0] + $usersPerStatus[2],
260 ];
261 }
262
263 private function handleEntitySelect(array $settings, array &$filters): ?array
264 {
265 if (isset($settings['hiddenElements'])) {
266 if (empty($settings['hiddenElements'])) {
267 $filters[] = 'user.id IS NOT NULL';
268 } else {
269 acym_arrayToInteger($settings['hiddenElements']);
270 $filters[] = 'user.id NOT IN('.implode(',', $settings['hiddenElements']).')';
271 }
272 }
273
274 if (isset($settings['onlyElements'])) {
275 if (empty($settings['onlyElements'])) {
276 $filters[] = 'user.id IS NULL';
277 } else {
278 acym_arrayToInteger($settings['onlyElements']);
279 $filters[] = 'user.id IN('.implode(',', $settings['onlyElements']).')';
280 }
281 }
282
283 // For the entity select when selecting "show only selected users"
284 if (!empty($settings['showOnlySelected'])) {
285 if (!empty($settings['selectedUsers'])) {
286 acym_arrayToInteger($settings['selectedUsers']);
287 $filters[] = 'user.id IN('.implode(',', $settings['selectedUsers']).')';
288 } else {
289 return ['users' => [], 'total' => 0];
290 }
291 }
292
293 return null;
294 }
295
296 private function handleSegmentFilter(array $settings, array &$filters, string &$query, string &$queryCount, string &$queryStatus): void
297 {
298 if (!array_key_exists('segment', $settings)) return;
299 if ($settings['segment'] == 0) return;
300
301 $segmentClass = (new SegmentClass())->getOneById($settings['segment']);
302 $automationHelpers = [];
303 if (!empty($segmentClass) && !empty($segmentClass->filters)) {
304 foreach ($segmentClass->filters as $or => $orValues) {
305 if (empty($orValues)) continue;
306 $automationHelpers[$or] = new AutomationHelper();
307 foreach ($orValues as $and => $andValues) {
308 $and = intval($and);
309 foreach ($andValues as $filterName => $options) {
310 acym_trigger('onAcymProcessFilter_'.$filterName, [&$automationHelpers[$or], &$options, $and.'_'.$or]);
311 }
312 }
313 }
314 }
315
316 $whereBlocks = [];
317 $join = '';
318 foreach ($automationHelpers as $automationHelper) {
319 if (!empty($automationHelper->where)) {
320 $whereBlocks[] = ' ('.implode(') and (', $automationHelper->where).')';
321 }
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 }
328 }
329 if (!empty($whereBlocks)) {
330 $filters[] = implode(' OR ', $whereBlocks);
331 }
332 $query .= $join;
333 $queryCount .= $join;
334 $queryStatus .= $join;
335 }
336
337 public function getJoinForQuery(string $joinType): string
338 {
339 if (strpos($joinType, 'join_list') !== false) {
340 $listId = explode('-', $joinType);
341
342 return ' LEFT JOIN #__acym_user_has_list AS userlist ON user.id = userlist.user_id AND userlist.status = 1 AND userlist.list_id = '.intval($listId[1]);
343 }
344
345 return '';
346 }
347
348 public function getOneByCMSId(?int $id): ?object
349 {
350 if (empty($id)) {
351 return null;
352 }
353
354 $user = acym_loadObject('SELECT * FROM #__acym_user WHERE cms_id = '.intval($id));
355
356 return empty($user) ? null : $user;
357 }
358
359 public function getOneByEmail(?string $email, bool $autoAdd = false): ?object
360 {
361 if (!acym_isValidEmail($email)) {
362 return null;
363 }
364
365 $user = acym_loadObject('SELECT * FROM #__acym_user WHERE email = '.acym_escapeDB($email));
366
367 if (empty($user) && $autoAdd) {
368 $newUser = new \stdClass();
369 $newUser->email = $email;
370 $this->sendConf = false;
371 $this->checkVisitor = false;
372
373 $userId = $this->save($newUser);
374 if (empty($userId)) {
375 return null;
376 }
377
378 $user = $this->getOneById($userId);
379 }
380
381 return empty($user) ? null : $user;
382 }
383
384 public function getByColumnValue(string $column, string $value): array
385 {
386 $userColumns = acym_getColumns('user');
387 if (!in_array($column, $userColumns)) return [];
388
389 $query = 'SELECT * FROM #__acym_user WHERE '.acym_secureDBColumn($column).' = '.acym_escapeDB($value);
390
391 return acym_loadObjectList($query, $this->pkey);
392 }
393
394 public function getUserSubscriptionById(
395 int $userId,
396 string $key = 'id',
397 bool $includeManagement = false,
398 bool $visible = false,
399 bool $needTranslation = false,
400 bool $sortBySubscription = false,
401 int $mailId = 0,
402 bool $campaignOnly = false
403 ): array {
404 $mailType = null;
405 if (!empty($mailId)) {
406 $mailType = (new MailClass())->getMailType(intval($mailId));
407 }
408
409 $query = 'SELECT list.id, list.translation, list.name, list.display_name, list.color, list.active, list.visible, list.description, userlist.status, userlist.subscription_date, userlist.unsubscribe_date
410 FROM #__acym_list AS list
411 JOIN #__acym_user_has_list AS userlist
412 ON list.id = userlist.list_id ';
413
414 if (!empty($mailId) && $campaignOnly && !empty($mailType) && $mailType !== MailClass::TYPE_FOLLOWUP) {
415 $query .= 'JOIN #__acym_mail_has_list AS mail_list ON list.id = mail_list.list_id AND mail_list.mail_id = '.intval($mailId);
416 }
417
418 $query .= ' WHERE userlist.user_id = '.intval($userId);
419
420 if (!$includeManagement && !empty($mailType) && $mailType === MailClass::TYPE_FOLLOWUP) {
421 $query .= ' AND list.type = '.acym_escapeDB(ListClass::LIST_TYPE_FOLLOWUP);
422 } elseif (!$includeManagement) {
423 $types = [ListClass::LIST_TYPE_STANDARD, ListClass::LIST_TYPE_FOLLOWUP];
424 $types = array_map('acym_escapeDB', $types);
425 $query .= ' AND list.type IN ('.implode(',', $types).')';
426 }
427
428 if ($visible) {
429 $query .= ' AND list.visible = 1';
430 }
431
432 if ($sortBySubscription) {
433 $query .= ' ORDER BY userlist.subscription_date DESC';
434 }
435
436 $lists = acym_loadObjectList($query, $key);
437
438 if (acym_isMultilingual() && $needTranslation) {
439 $listClass = new ListClass();
440 $lists = $listClass->getTranslatedNameDescription($lists);
441 }
442
443 return $lists;
444 }
445
446 public function getUserStandardListIdById(int $userId): array
447 {
448 $query = 'SELECT list.id FROM #__acym_list AS list
449 JOIN #__acym_user_has_list AS user_list ON list.id = user_list.list_id AND user_list.status = 1 AND user_list.user_id = '.$userId.'
450 WHERE list.visible = 1 AND list.type = '.acym_escapeDB(ListClass::LIST_TYPE_STANDARD);
451
452 return acym_loadResultArray($query);
453 }
454
455 public function getAllListsUserSubscriptionById(int $userId, string $key = 'id', bool $needTranslation = false): array
456 {
457 $query = 'SELECT list.id, list.translation, list.name, list.color, list.active, list.visible, userlist.status, userlist.subscription_date, userlist.unsubscribe_date
458 FROM #__acym_list AS list
459 LEFT JOIN #__acym_user_has_list AS userlist
460 ON list.id = userlist.list_id
461 AND userlist.user_id = '.intval($userId);
462
463 $lists = acym_loadObjectList($query, $key);
464
465 if (acym_isMultilingual() && $needTranslation) {
466 $listClass = new ListClass();
467 $lists = $listClass->getTranslatedNameDescription($lists);
468 }
469
470 return $lists;
471 }
472
473 public function getUsersSubscriptionsByIds(array $userIds, int $creatorId = 0): array
474 {
475 acym_arrayToInteger($userIds);
476 $query = 'SELECT `list`.`id`, `list`.`color`, `list`.`name`, `userlist`.`user_id`, `userlist`.`status`
477 FROM #__acym_list AS `list`
478 JOIN #__acym_user_has_list AS `userlist`
479 ON `list`.`id` = `userlist`.`list_id`
480 WHERE `userlist`.`user_id` IN ('.implode(',', $userIds).')
481 AND `list`.`type` = '.acym_escapeDB(ListClass::LIST_TYPE_STANDARD);
482
483 if (!empty($creatorId)) {
484 $userGroups = acym_getGroupsByUser($creatorId);
485
486 $safeGroups = [];
487 foreach ($userGroups as $group) {
488 $safeGroups[] = acym_escapeDB('%,'.$group.',%');
489 }
490 $query .= ' AND (`list`.`cms_user_id` = '.intval($creatorId).' OR `list`.`access` LIKE '.implode(' OR `list`.`access` LIKE ', $safeGroups).')';
491 }
492 $query .= ' ORDER BY `userlist`.`status` DESC, `list`.`id` ASC';
493
494 return acym_loadObjectList($query);
495 }
496
497 public function getCountTotalUsers(): int
498 {
499 $numberOfUsers = acym_loadResult('SELECT COUNT(id) FROM #__acym_user');
500
501 return empty($numberOfUsers) ? 0 : $numberOfUsers;
502 }
503
504 public function getSubscriptionStatus(int $userId, array $listIds = [], ?int $wantedStatus = null): array
505 {
506 $query = 'SELECT status, list_id
507 FROM #__acym_user_has_list
508 WHERE user_id = '.intval($userId);
509 if (!empty($listIds)) {
510 acym_arrayToInteger($listIds);
511 $query .= ' AND list_id IN ('.implode(',', $listIds).')';
512 }
513
514 if ($wantedStatus !== null) {
515 $query .= ' AND status = '.intval($wantedStatus);
516 }
517
518 return acym_loadObjectList($query, 'list_id');
519 }
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 */
528 public function identify(bool $onlyValue = false, ?string $idName = null, ?string $keyName = null): ?object
529 {
530 $id = acym_getVar('int', empty($idName) ? 'id' : $idName, 0);
531 $key = acym_getVar('string', empty($keyName) ? 'key' : $keyName, '');
532
533 if (empty($id) && !empty($idName) && empty($key) && !empty($keyName)) {
534 $id = acym_getVar('int', 'id', 0);
535 $key = acym_getVar('string', 'key', 0);
536 }
537
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...
541 $currentUserid = acym_currentUserId();
542 if (!empty($currentUserid)) {
543 return $this->getOneByCMSId($currentUserid);
544 }
545 if (!$onlyValue) {
546 acym_enqueueMessage(acym_translation('ACYM_LOGIN'), 'error');
547 }
548
549 return null;
550 }
551
552 $userIdentified = acym_loadObject('SELECT * FROM #__acym_user WHERE `id` = '.intval($id).' AND `key` = '.acym_escapeDB($key));
553
554 if (!empty($userIdentified)) {
555 return $userIdentified;
556 }
557
558 if (!$onlyValue) {
559 acym_enqueueMessage(acym_translation('ACYM_USER_NOT_FOUND'), 'error');
560 }
561
562 return null;
563 }
564
565 public function subscribe($userIds, array $addLists, bool $trigger = true, bool $forceFront = false): bool
566 {
567 if (empty($addLists)) {
568 return false;
569 }
570 acym_arrayToInteger($addLists);
571
572 //TODO was previously shown as an int in the dev documentation, hence not adding the type yet
573 if (!is_array($userIds)) {
574 $userIds = [$userIds];
575 }
576 acym_arrayToInteger($userIds);
577
578 $listClass = new ListClass();
579 $historyClass = new HistoryClass();
580
581 $confirmationRequired = !empty($this->config->get('require_confirmation', 1));
582 $subscribedToLists = false;
583 $historyData = acym_translationSprintf('ACYM_LISTS_NUMBERS', implode(', ', $addLists));
584
585 foreach ($userIds as $userId) {
586 $user = $this->getOneById($userId);
587 if (empty($user)) continue;
588 $currentSubscription = $this->getUserSubscriptionById($userId, 'id', true);
589
590 $currentlySubscribed = [];
591 $currentlyUnsubscribed = [];
592 foreach ($currentSubscription as $oneList) {
593 if ($oneList->status == 1) {
594 $currentlySubscribed[$oneList->id] = $oneList;
595 } else {
596 $currentlyUnsubscribed[$oneList->id] = $oneList;
597 }
598 }
599
600 $subscribedLists = [];
601 $listsNotExisting = [];
602 foreach ($addLists as $oneListId) {
603 // The user is already subscribed
604 if (empty($oneListId) || !empty($currentlySubscribed[$oneListId])) {
605 continue;
606 }
607
608 $list = $listClass->getOneById($oneListId);
609 if (empty($list)) {
610 $listsNotExisting[] = $oneListId;
611 continue;
612 }
613
614 $subscription = new \stdClass();
615 $subscription->user_id = $userId;
616 $subscription->list_id = $oneListId;
617 $subscription->status = 1;
618 $subscription->subscription_date = gmdate('Y-m-d H:i:s', time());
619
620 if (empty($currentSubscription[$oneListId])) {
621 // The user isn't already subscribed, we subscribe him!
622 acym_insertObject('#__acym_user_has_list', $subscription);
623 } elseif (!empty($currentlyUnsubscribed[$oneListId])) {
624 // The user re-subscribes or we have to confirm the subscription
625 acym_updateObject('#__acym_user_has_list', $subscription, ['user_id', 'list_id']);
626 }
627
628 $subscribedLists[] = $oneListId;
629 $subscribedToLists = true;
630 }
631
632 if (!empty($listsNotExisting)) {
633 $this->errors['list_not_existing'] = acym_translationSprintf('ACYM_LIST_NOT_FOUND', implode(', ', $listsNotExisting));
634 }
635
636 $historyClass->insert($userId, 'subscribed', [$historyData]);
637
638 if ($trigger) {
639 acym_trigger('onAcymAfterUserSubscribe', [&$user, &$subscribedLists]);
640 }
641
642 if ($this->sendWelcomeEmail && (!$confirmationRequired || $user->confirmed == 1) && $user->active == 1) {
643 $listClass->sendWelcome($userId, $subscribedLists, $forceFront);
644 }
645 }
646
647 return $subscribedToLists;
648 }
649
650 private function registerUnsubUser(array $userIds): void
651 {
652 $mailId = acym_getVar('int', 'mail_id', 0);
653 if (empty($mailId)) return;
654
655 $mailStatClass = new MailStatClass();
656 $userStatClass = new UserStatClass();
657
658 $countUnsubscribe = 0;
659
660 foreach ($userIds as $id) {
661 $userStat = $userStatClass->getOneByMailAndUserId($mailId, $id);
662 // We didn't send this email to this user, something is wrong
663 if (empty($userStat)) continue;
664
665 $newUserStat = new \stdClass();
666 $newUserStat->mail_id = $mailId;
667 $newUserStat->user_id = $id;
668 $newUserStat->unsubscribe = empty($userStat->unsubscribe) ? 1 : $userStat->unsubscribe + 1;
669
670 if (empty($userStat->unsubscribe)) {
671 $countUnsubscribe++;
672 }
673
674 $userStatClass->save($newUserStat);
675 }
676
677 $mailStat = new \stdClass();
678 $mailStat->mail_id = $mailId;
679 $mailStat->unsubscribe_total = $countUnsubscribe;
680
681 $mailStatClass->save($mailStat);
682 }
683
684 public function unsubscribe($userIds, array $lists): bool
685 {
686 if (empty($lists)) {
687 return false;
688 }
689
690 //TODO was previously shown as an int in the dev documentation, hence not adding the type yet
691 if (!is_array($userIds)) {
692 $userIds = [$userIds];
693 }
694 acym_arrayToInteger($userIds);
695 acym_arrayToInteger($lists);
696
697 $listClass = new ListClass();
698 $unsubscribedFromLists = false;
699 foreach ($userIds as $userId) {
700 $user = $this->getOneById($userId);
701
702 if (empty($user)) {
703 continue;
704 }
705
706 $currentSubscription = $this->getUserSubscriptionById($userId);
707
708 $currentlySubscribed = [];
709 $currentlyUnsubscribed = [];
710 foreach ($currentSubscription as $oneList) {
711 if ($oneList->status == 0) {
712 $currentlyUnsubscribed[$oneList->id] = $oneList;
713 } elseif ($oneList->status == 1) {
714 $currentlySubscribed[$oneList->id] = $oneList;
715 }
716 }
717
718 $unsubscribedLists = [];
719 $listsNotExisting = [];
720 foreach ($lists as $oneListId) {
721 // The user is already unsubscribed
722 if (empty($oneListId) || !empty($currentlyUnsubscribed[$oneListId])) {
723 continue;
724 }
725
726 // Don't unsubscribe from non-subscribed lists
727 if (empty($currentlySubscribed[$oneListId])) {
728 continue;
729 }
730
731 $list = $listClass->getOneById($oneListId);
732 if (empty($list)) {
733 $listsNotExisting[] = $oneListId;
734 continue;
735 }
736
737 $subscription = new \stdClass();
738 $subscription->user_id = $userId;
739 $subscription->list_id = $oneListId;
740 $subscription->status = 0;
741 $subscription->unsubscribe_date = gmdate('Y-m-d H:i:s', time());
742 if (empty($currentSubscription[$oneListId])) {
743 // The user isn't already subscribed, we unsubscribe him directly
744 acym_insertObject('#__acym_user_has_list', $subscription);
745 } else {
746 // The user unsubscribed from a list
747 acym_updateObject('#__acym_user_has_list', $subscription, ['user_id', 'list_id']);
748 }
749
750 $unsubscribedLists[] = $oneListId;
751 $unsubscribedFromLists = true;
752 }
753
754 if (!empty($listsNotExisting)) {
755 $this->errors['list_not_existing'] = acym_translationSprintf('ACYM_LIST_NOT_FOUND', implode(', ', $listsNotExisting));
756 }
757
758 acym_query(
759 'DELETE FROM #__acym_queue WHERE user_id = '.intval($userId).' AND mail_id IN (
760 SELECT followup_mail.mail_id FROM #__acym_followup_has_mail AS followup_mail
761 JOIN #__acym_followup AS followup ON followup.id = followup_mail.followup_id AND followup.list_id IN ('.implode(',', $lists).')
762 )'
763 );
764
765 $historyClass = new HistoryClass();
766 $historyData = acym_translationSprintf('ACYM_LISTS_NUMBERS', implode(', ', $lists));
767 $historyClass->insert($userId, 'unsubscribed', [$historyData], 0, acym_getVar('string', 'unsubscribe_reason'));
768
769 if ($this->triggers) {
770 acym_trigger('onAcymAfterUserUnsubscribe', [&$user, &$unsubscribedLists]);
771 }
772
773 if ($this->sendUnsubscribeEmail) {
774 $listClass->sendUnsubscribe($userId, $unsubscribedLists);
775 }
776
777 if (!empty($unsubscribedLists)) {
778 foreach ($unsubscribedLists as $i => $oneListId) {
779 $currentList = $listClass->getOneById($oneListId);
780 $unsubscribedLists[$i] = $currentList->name;
781 }
782 $this->sendNotification($user->id, 'acy_notification_unsub', ['lists' => implode(', ', $unsubscribedLists)]);
783 }
784 }
785
786 if ($unsubscribedFromLists) {
787 $this->registerUnsubUser($userIds);
788 }
789
790 return $unsubscribedFromLists;
791 }
792
793 public function unsubscribeOnSubscriptions(int $userId, array $listIds): bool
794 {
795 if (empty($listIds)) {
796 return false;
797 }
798
799 acym_arrayToInteger($listIds);
800
801 $subscribedLists = acym_loadResultArray('SELECT list_id FROM #__acym_user_has_list WHERE user_id = '.intval($userId).' AND list_id IN ('.implode(',', $listIds).')');
802
803 if (!empty($subscribedLists)) $this->unsubscribe([$userId], $subscribedLists);
804
805 return true;
806 }
807
808 public function removeSubscription(array $userIds, array $listIds = []): void
809 {
810 if (empty($listIds) || empty($userIds)) {
811 return;
812 }
813
814 acym_arrayToInteger($userIds);
815 acym_arrayToInteger($listIds);
816
817 acym_query(
818 'DELETE FROM #__acym_user_has_list
819 WHERE user_id IN ('.implode(',', $userIds).')
820 AND list_id IN ('.implode(',', $listIds).')'
821 );
822 }
823
824 public function onlyManageableUsers(array &$elements): void
825 {
826 if (acym_isAdmin() && acym_isAllowed('users')) {
827 return;
828 }
829
830 $listClass = new ListClass();
831 $manageableLists = $listClass->getManageableLists();
832 if (empty($manageableLists)) {
833 return;
834 }
835
836 $currentUserId = acym_currentUserId();
837 acym_arrayToInteger($elements);
838
839 $selfManagement = in_array($currentUserId, $elements);
840
841 $elements = acym_loadResultArray(
842 'SELECT DISTINCT user_id
843 FROM #__acym_user_has_list
844 WHERE `list_id` IN ('.implode(',', $manageableLists).')
845 AND `user_id` IN ('.implode(',', $elements).')'
846 );
847
848 if ($selfManagement && !in_array($currentUserId, $elements)) {
849 $elements[] = $currentUserId;
850 }
851 }
852
853 public function delete(array $elements, bool $forceDelete = false): int
854 {
855 acym_arrayToInteger($elements);
856 $this->onlyManageableUsers($elements);
857
858 if (empty($elements)) {
859 return 0;
860 }
861
862 if (acym_isAdmin() || $forceDelete || 'delete' === $this->config->get('frontend_delete_button', 'delete')) {
863 acym_query('DELETE FROM #__acym_user_has_list WHERE user_id IN ('.implode(',', $elements).')');
864 acym_query('DELETE FROM #__acym_queue WHERE user_id IN ('.implode(',', $elements).')');
865 acym_query('DELETE FROM #__acym_user_has_field WHERE user_id IN ('.implode(',', $elements).')');
866 acym_query('DELETE FROM #__acym_history WHERE user_id IN ('.implode(',', $elements).')');
867 acym_query('DELETE FROM #__acym_user_stat WHERE user_id IN ('.implode(',', $elements).')');
868
869 $scenarioProcessIds = acym_loadResultArray('SELECT id FROM #__acym_scenario_process WHERE user_id IN ('.implode(',', $elements).')');
870
871 if (!empty($scenarioProcessIds)) {
872 acym_query('DELETE FROM #__acym_scenario_history_line WHERE scenario_process_id IN ('.implode(',', $scenarioProcessIds).')');
873 acym_query('DELETE FROM #__acym_scenario_queue WHERE scenario_process_id IN ('.implode(',', $scenarioProcessIds).')');
874 acym_query('DELETE FROM #__acym_scenario_process WHERE id IN ('.implode(',', $scenarioProcessIds).')');
875 }
876
877 $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS;
878 foreach ($elements as $userId) {
879 $folderPath = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS.$userId.DS);
880 if (is_dir($folderPath)) {
881 acym_deleteFolder($folderPath);
882 }
883 }
884
885 return parent::delete($elements);
886 } else {
887 $listClass = new ListClass();
888 $manageableLists = $listClass->getManageableLists();
889
890 if (!empty($manageableLists)) {
891 $this->removeSubscription($elements, $manageableLists);
892 acym_query(
893 'DELETE queue
894 FROM #__acym_queue AS queue
895 JOIN #__acym_mail_has_list AS maillist
896 ON queue.mail_id = maillist.mail_id
897 WHERE queue.user_id IN ('.implode(',', $elements).')
898 AND maillist.list_id IN ('.implode(',', $manageableLists).')'
899 );
900 }
901
902 return count($elements);
903 }
904 }
905
906 public function save(object $user, array $customFields = [], bool $ajax = false): ?int
907 {
908 if (empty($user->email) && empty($user->id)) {
909 return null;
910 }
911
912 if (isset($user->email)) {
913 $user->email = strtolower($user->email);
914 if (!acym_isValidEmail($user->email)) {
915 $this->errors[] = acym_translation('ACYM_VALID_EMAIL');
916
917 return null;
918 }
919 }
920
921 if (empty($user->id)) {
922 if (!isset($user->active)) {
923 $user->active = 1;
924 }
925
926 if (!isset($user->tracking)) {
927 $user->tracking = $this->config->get('dont_track_by_default', 0) ? 0 : 1;
928 }
929
930 if (empty($user->language)) {
931 // Take the user account's language
932 if (!acym_isAdmin()) {
933 $cmsUserLanguage = acym_getCmsUserLanguage();
934 }
935
936 if (!empty($cmsUserLanguage)) {
937 $user->language = $cmsUserLanguage;
938 } elseif (acym_isMultilingual()) {
939 // Take the configuration's language
940 $configUserLanguage = $this->config->get('multilingual_user_default', 'current_language');
941 $user->language = $configUserLanguage === 'current_language' ? acym_getLanguageTag() : $configUserLanguage;
942 }
943 }
944
945 $currentUserid = acym_currentUserId();
946 $currentEmail = acym_currentUserEmail();
947 $allowVisitors = $this->config->get('allow_visitor', 1) == 1;
948 if ($this->checkVisitor && !acym_isAdmin() && !$allowVisitors && (empty($currentUserid) || strtolower($currentEmail) !== $user->email)) {
949 //We don't accept the subscription as either the user is not logged in or it's not the good one
950 $this->errors[] = acym_translation('ACYM_ONLY_LOGGED');
951
952 return null;
953 }
954
955 if (empty($user->name) && $this->config->get('generate_name', 1)) {
956 $user->name = ucwords(trim(str_replace(['.', '_', ')', ',', '(', '-', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0], ' ', substr($user->email, 0, strpos($user->email, '@')))));
957 }
958
959 $source = acym_getVar('string', 'acy_source', '');
960 if (empty($user->source) && !empty($source)) {
961 $user->source = $source;
962 }
963
964 $user->key = acym_generateKey(14);
965
966 $user->creation_date = gmdate('Y-m-d H:i:s', time());
967 } elseif (!empty($user->confirmed)) {
968 $oldUser = $this->getOneByIdWithCustomFields($user->id);
969 if (!empty($oldUser) && empty($oldUser['confirmed'])) {
970 $user->confirmation_date = gmdate('Y-m-d H:i:s', time());
971 $user->confirmation_ip = acym_getIP();
972
973 if ($this->triggers) {
974 acym_trigger('onAcymAfterUserConfirm', [&$user]);
975 }
976 }
977 } else {
978 $oldUser = $this->getOneByIdWithCustomFields($user->id);
979 }
980
981 if (!empty($user->id) && isset($user->active) && !empty($oldUser)) {
982 $wasActive = isset($oldUser['active']) ? (int)$oldUser['active'] : 1;
983 $isNowActive = (int)$user->active;
984 if ($wasActive === 1 && $isNowActive === 0) {
985 $user->deactivation_date = acym_date('now', 'Y-m-d H:i:s');
986 } elseif ($wasActive === 0 && $isNowActive === 1) {
987 $user->deactivation_date = null;
988 }
989 }
990
991 foreach ($user as $oneAttribute => $value) {
992 if (empty($value)) {
993 continue;
994 }
995
996 $oneAttribute = trim(strtolower($oneAttribute));
997 if (!in_array($oneAttribute, self::RESTRICTED_FIELDS)) {
998 $user->$oneAttribute = acym_stripTags($value);
999 }
1000
1001 // Convert into utf-8 in case of it's not already
1002 // Double test on UTF-8 because the preg_match returns an error on string longer than 200 characters
1003 if (is_numeric($user->$oneAttribute)) {
1004 continue;
1005 }
1006
1007 if (function_exists('mb_detect_encoding')) {
1008 if (mb_detect_encoding($user->$oneAttribute, 'UTF-8', true) !== 'UTF-8') {
1009 $user->$oneAttribute = acym_utf8Encode($user->$oneAttribute);
1010 }
1011 } elseif (
1012 !preg_match(
1013 '%^(?:[\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$%xs',
1014 $user->$oneAttribute
1015 )
1016 ) {
1017 $user->$oneAttribute = acym_utf8Encode($user->$oneAttribute);
1018 }
1019 }
1020
1021 // Prevent SQL error on flags that aren't cleaned
1022 if (!empty($user->automation) && strlen($user->automation) > 20) {
1023 $user->automation = '';
1024 }
1025
1026 if (empty($user->id)) {
1027 if (empty($user->cms_id) && !empty($user->email)) {
1028 global $acymCmsUserVars;
1029 $userCmsID = acym_loadResult(
1030 'SELECT '.acym_secureDBColumn($acymCmsUserVars->id).' FROM '.$acymCmsUserVars->table.' WHERE '.acym_secureDBColumn(
1031 $acymCmsUserVars->email
1032 ).' = '.acym_escapeDB($user->email)
1033 );
1034
1035 if (!empty($userCmsID)) $user->cms_id = $userCmsID;
1036 }
1037 if ($this->triggers) {
1038 $result = acym_trigger('onAcymBeforeUserCreate', [&$user]);
1039 if (is_array($result) && in_array(false, $result)) {
1040 $acycheckerError = acym_getVar('string', 'acychecker_error');
1041 if (!empty($acycheckerError)) {
1042 if ($ajax) {
1043 $this->errors[] = $acycheckerError;
1044 } else {
1045 acym_enqueueMessage($acycheckerError, 'error');
1046 }
1047 }
1048
1049 return null;
1050 }
1051 }
1052 } else {
1053 if ($this->triggers) {
1054 acym_trigger('onAcymBeforeUserModify', [&$user]);
1055 }
1056 }
1057
1058 $userID = parent::save($user);
1059
1060 // Save custom fields if there are any
1061 $fieldClass = new FieldClass();
1062 if (!empty($customFields)) {
1063 foreach ($customFields as $key => $value) {
1064 if (!is_string($value)) {
1065 continue;
1066 }
1067
1068 // Remove most of the existing emojis that are not supported
1069 $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $value);
1070
1071 $field = $fieldClass->getOneById($key);
1072 if (empty($field) || $field->type !== 'textarea') {
1073 $value = preg_replace('/\s+/', ' ', $value);
1074 }
1075 $customFields[$key] = $value;
1076 }
1077 }
1078 $fieldClass->store($userID, $customFields, $ajax);
1079 if (!empty($fieldClass->errors)) {
1080 $this->errors = array_merge($this->errors, $fieldClass->errors);
1081 }
1082
1083 $historyClass = new HistoryClass();
1084 if (empty($user->id)) {
1085 $user->id = $userID;
1086 $historyClass->insert($user->id, 'created');
1087 if ($this->triggers) {
1088 acym_trigger('onAcymAfterUserCreate', [&$user]);
1089 acym_triggerCmsHook('onAcymAfterUserSave', [$user, true]);
1090 }
1091 } else {
1092 $historyClass->insert($user->id, 'modified');
1093 if ($this->triggers) {
1094 acym_trigger('onAcymAfterUserModify', [&$user, &$oldUser]);
1095 acym_triggerCmsHook('onAcymAfterUserSave', [$user, false]);
1096 }
1097 }
1098
1099 $this->sendConfirmation($userID);
1100
1101 return $userID;
1102 }
1103
1104 public function saveForm(bool $ajax = false): bool
1105 {
1106 $allowUserModifications = $this->config->get('allow_modif', 'data') === 'all' || $this->allowModif;
1107 $allowSubscriptionModifications = $this->config->get('allow_modif', 'data') !== 'none' || $this->allowModif;
1108
1109 $user = new \stdClass();
1110 $connectedUser = $this->identify(true, 'userId', 'userKey');
1111
1112 $userData = acym_getVar('array', 'user', []);
1113 if (!empty($userData)) {
1114 foreach ($userData as $attribute => $value) {
1115 if (!in_array($attribute, self::FORM_ALLOWED_FIELDS, true)) {
1116 continue;
1117 }
1118 $user->$attribute = $value;
1119 }
1120 }
1121
1122 if (!empty($connectedUser->id)) {
1123 $user->id = $connectedUser->id;
1124 }
1125
1126 if (empty($user->email)) {
1127 if (!empty($connectedUser->email)) {
1128 $user->email = $connectedUser->email;
1129 }
1130 }
1131
1132 if (empty($user->email)) {
1133 $this->errors[] = acym_translation('ACYM_VALID_EMAIL');
1134
1135 return false;
1136 }
1137
1138 if (!$allowUserModifications && !empty($connectedUser)) {
1139 if ($connectedUser->email != $user->email) {
1140 $this->errors[] = acym_translation('ACYM_NOT_ALLOWED_MODIFY_USER');
1141
1142 return false;
1143 }
1144
1145 $allowUserModifications = true;
1146 $allowSubscriptionModifications = true;
1147 }
1148
1149 // Check if it already exists...
1150 if (!empty($user->email)) {
1151 // Do we already have a user with this same e-mail address?
1152 if (empty($user->id)) {
1153 $user->id = 0;
1154 }
1155 $existUser = acym_loadObject('SELECT * FROM #__acym_user WHERE email = '.acym_escapeDB($user->email).' AND id != '.intval($user->id));
1156 if (!empty($existUser->id)) {
1157 if (!$this->allowModif && !$allowSubscriptionModifications) {
1158 $this->errors[] = acym_translation('ACYM_ADDRESS_TAKEN');
1159
1160 return false;
1161 } else {
1162 $user->id = $existUser->id;
1163 }
1164 }
1165 }
1166
1167 // Did the user ask for its e-mail address to be modified?
1168 // If so, we set it as not confirmed automatically.
1169 if (!empty($user->id) && !empty($user->email)) {
1170 $existUser = $this->getOneById($user->id);
1171 if (trim(strtolower($user->email)) != strtolower($existUser->email)) {
1172 $user->confirmed = 0;
1173 }
1174 }
1175
1176 $fromProfile = acym_getVar('int', 'acyprofile', 0) == 1;
1177
1178 $this->newUser = empty($user->id);
1179 if (empty($user->id) || $allowUserModifications) {
1180 if ($this->newUser && $this->config->get('require_confirmation', 1) == 1) {
1181 $user->confirmed = 0;
1182 }
1183
1184 if ($fromProfile && !(isset($user->confirmed) && empty($user->confirmed))) {
1185 $this->sendConf = false;
1186 }
1187
1188 // Get custom fields to save them if exist
1189 $customFieldData = acym_getVar('array', 'customField', []);
1190 $id = $this->save($user, $customFieldData, $ajax);
1191 $allowSubscriptionModifications = true;
1192 } else {
1193 $id = $user->id;
1194 //We didn't save the user... but we should check the confirm field anyway
1195 if (isset($user->confirmed) && empty($user->confirmed)) {
1196 $this->sendConfirmation($id);
1197 }
1198 }
1199
1200 if (empty($id)) return false;
1201
1202 $formData = acym_getVar('array', 'data', []);
1203 acym_setVar('userId', $id);
1204
1205 if (!acym_isAdmin()) {
1206 $hiddenlistsString = acym_getVar('string', 'hiddenlists', '');
1207 $hiddenlists = explode(',', $hiddenlistsString);
1208 $visibleSubscription = acym_getVar('array', 'subscription', []);
1209 $subscribeLists = array_merge($hiddenlists, $visibleSubscription);
1210 acym_arrayToInteger($subscribeLists);
1211
1212 if (!empty($subscribeLists)) {
1213 foreach ($subscribeLists as $oneListId) {
1214 $formData['listsub'][$oneListId] = ['status' => 1];
1215 }
1216 }
1217 }
1218
1219 if (empty($formData['listsub'])) {
1220 if (!$fromProfile) $this->sendNotification($id, 'acy_notification_subform');
1221
1222 $this->sendNotification(
1223 $id,
1224 $this->newUser ? 'acy_notification_create' : 'acy_notification_profile'
1225 );
1226
1227 return true;
1228 }
1229
1230 if (!$allowSubscriptionModifications) {
1231 $this->requireId = true;
1232 $this->errors[] = acym_translation('ACYM_NOT_ALLOWED_MODIFY_USER');
1233
1234 return false;
1235 }
1236
1237 $addLists = [];
1238 $unsubLists = [];
1239 foreach ($formData['listsub'] as $listID => $oneList) {
1240 if ($oneList['status'] == 1) {
1241 $addLists[] = $listID;
1242 } else {
1243 $unsubLists[] = $listID;
1244 }
1245 }
1246
1247 $this->subscribed = $this->subscribe([$id], $addLists);
1248
1249 if (!$fromProfile) $this->sendNotification($id, 'acy_notification_subform');
1250
1251 $this->sendNotification(
1252 $id,
1253 $this->newUser ? 'acy_notification_create' : 'acy_notification_profile'
1254 );
1255
1256 if (!$this->newUser) $this->unsubscribe([$id], $unsubLists);
1257
1258 return true;
1259 }
1260
1261 public function sendConfirmation(int $userID): void
1262 {
1263 if (!$this->forceConf && !$this->sendConf) {
1264 return;
1265 }
1266
1267 if ($this->config->get('require_confirmation', 1) != 1 || (acym_isAdmin() && !$this->forceConfAdmin)) {
1268 return;
1269 }
1270
1271 $myUser = $this->getOneById($userID);
1272
1273 if (!empty($myUser->confirmed)) {
1274 return;
1275 }
1276
1277 $mailerHelper = new MailerHelper();
1278 $mailerHelper->report = (bool)$this->config->get('confirm_message', 0);
1279
1280 //TODO $mailerHelper->addParam('user:subscription', implode('<br/>', $subscription));
1281 $mailClass = new MailClass();
1282 $confirmationEmail = $mailClass->getOneByName('acy_confirm');
1283 $this->confirmationSentSuccess = !empty($confirmationEmail) && $mailerHelper->sendOne($confirmationEmail->id, $myUser->id);
1284 $this->confirmationSentError = $mailerHelper->reportMessage;
1285 }
1286
1287 public function setInactive(array $elements): void
1288 {
1289 $this->deactivate($elements);
1290 }
1291
1292 public function deactivate(array $userIds): void
1293 {
1294 $userIds = array_map('intval', $userIds);
1295 $userIds = array_filter($userIds);
1296
1297 if (empty($userIds)) return;
1298
1299 acym_query('UPDATE `#__acym_user` SET `active` = 0, `deactivation_date` = UTC_TIMESTAMP() WHERE `id` IN ('.implode(',', $userIds).')');
1300 }
1301
1302 public function setActive(array $elements): void
1303 {
1304 $this->reactivate($elements);
1305 }
1306
1307 public function reactivate(array $userIds): void
1308 {
1309 $userIds = array_map('intval', $userIds);
1310 $userIds = array_filter($userIds);
1311
1312 if (empty($userIds)) return;
1313
1314 acym_query('UPDATE `#__acym_user` SET `active` = 1, `deactivation_date` = NULL WHERE `id` IN ('.implode(',', $userIds).')');
1315 }
1316
1317 public function confirm(int $userId): void
1318 {
1319 $user = $this->getOneById($userId);
1320 if (empty($user)) return;
1321
1322 // We confirm the user and add the confirmation_date and confirmation_ip in the table.
1323 $confirmDate = gmdate('Y-m-d H:i:s', time());
1324 $ip = acym_getIP();
1325 $query = 'UPDATE `#__acym_user`';
1326 $query .= ' SET `confirmed` = 1, `confirmation_date` = '.acym_escapeDB($confirmDate).', `confirmation_ip` = '.acym_escapeDB($ip);
1327 $query .= ' WHERE `id` = '.intval($userId).' LIMIT 1';
1328
1329 try {
1330 $res = acym_query($query);
1331 } catch (\Exception $e) {
1332 $res = false;
1333 }
1334 if ($res === false) {
1335 $errorMessage = isset($e) ? $e->getMessage() : acym_getDBError();
1336 // If there is an error we definitely want to warn the user about it.
1337 $msg = acym_translation('ACYM_CONTACT_ADMIN_ERROR').'<br />'.substr(acym_stripTags($errorMessage), 0, 200).'...';
1338 acym_display($msg, 'error');
1339 exit;
1340 }
1341
1342 acym_trigger('onAcymAfterUserConfirm', [&$user]);
1343 $this->sendNotification($userId, 'acy_notification_confirm');
1344
1345 $historyClass = new HistoryClass();
1346 $historyClass->insert($userId, 'confirmed');
1347
1348 if (empty($this->config->get('require_confirmation', 1))) return;
1349
1350 $listIDs = acym_loadResultArray('SELECT `list_id` FROM `#__acym_user_has_list` WHERE `status` = 1 AND `user_id` = '.intval($userId));
1351
1352 if (empty($listIDs)) return;
1353
1354 $listClass = new ListClass();
1355 $listClass->sendWelcome($userId, $listIDs);
1356 }
1357
1358 public function getOneByIdWithCustomFields(int $id): array
1359 {
1360 $user = $this->getOneById($id);
1361 if (empty($user)) {
1362 return [];
1363 }
1364
1365 $user = get_object_vars($user);
1366
1367 $fieldsValue = acym_loadObjectList(
1368 'SELECT user_field.`value`, field.`name`
1369 FROM #__acym_user_has_field AS `user_field`
1370 LEFT JOIN #__acym_field AS `field` ON user_field.`field_id` = field.`id`
1371 WHERE user_field.`user_id` = '.intval($id),
1372 'name'
1373 );
1374
1375 foreach ($fieldsValue as $key => $value) {
1376 $fieldsValue[$key] = $value->value;
1377 }
1378
1379 return array_merge($user, $fieldsValue);
1380 }
1381
1382 public function getCustomFieldValueById(int $id): array
1383 {
1384 $fieldsValue = acym_loadObjectList(
1385 'SELECT user_field.value AS value, field.name AS name, field.type AS type, field.value AS field_params, field.id AS id
1386 FROM #__acym_user_has_field as user_field
1387 LEFT JOIN #__acym_field as field ON user_field.field_id = field.id
1388 WHERE user_field.user_id = '.intval($id),
1389 'id'
1390 );
1391
1392 $fieldReturn = [];
1393
1394 foreach ($fieldsValue as $fieldId => $field) {
1395 if (in_array($field->type, ['checkbox', 'radio', 'single_dropdown', 'multiple_dropdown'])) {
1396 $field->field_params = json_decode($field->field_params, true);
1397 $field->value = explode(',', $field->value);
1398 $values = [];
1399 foreach ($field->value as $oneValue) {
1400 $key = array_search($oneValue, array_column($field->field_params, 'value'));
1401 $values[] = $field->field_params[$key]['title'];
1402 }
1403 $fieldReturn[$field->name] = implode(',', $values);
1404 } else {
1405 $fieldReturn[$field->name] = $field->value;
1406 }
1407 }
1408
1409 return $fieldReturn;
1410 }
1411
1412 public function getAllColumnsUserAndCustomField(bool $inAction = false, bool $withSubInfos = false): array
1413 {
1414 $return = [];
1415
1416 // Basic fields in the user table
1417 $userFields = acym_getColumns('user');
1418 foreach ($userFields as $value) {
1419 $return[$value] = $value;
1420 }
1421
1422 $languageFieldId = $this->config->get(FieldClass::LANGUAGE_FIELD_ID_KEY, 0);
1423
1424 // Acy custom fields except name, email and language because they already are in the user table
1425 $customFields = acym_loadObjectList(
1426 'SELECT * FROM #__acym_field WHERE id NOT IN (1, 2, '.intval($languageFieldId).') '.($inAction ? 'AND type != "phone"' : ''),
1427 'id'
1428 );
1429 if (!empty($customFields)) {
1430 foreach ($customFields as $key => $value) {
1431 $return[$key] = $value->name;
1432 }
1433 }
1434
1435 if ($withSubInfos) {
1436 $listCLass = new ListClass();
1437 $listFields = $listCLass->getAll('name');
1438 if (!empty($listFields)) {
1439 foreach ($listFields as $key => $value) {
1440 if ($value->type === ListClass::LIST_TYPE_FOLLOWUP) {
1441 continue;
1442 }
1443 $return['subscription_date_'.$value->id] = acym_translation('ACYM_SUBSCRIPTION_DATE').' '.$value->name;
1444 $return['unsubscription_date_'.$value->id] = acym_translation('ACYM_UNSUBSCRIPTION_DATE').' '.$value->name;
1445 }
1446 }
1447 }
1448
1449 return $return;
1450 }
1451
1452 public function getAllUserFields(object $user): object
1453 {
1454 if (empty($user->id)) {
1455 return $user;
1456 }
1457
1458 $query = 'SELECT field.*, field.value AS field_value, userfield.*
1459 FROM #__acym_field AS field
1460 LEFT JOIN #__acym_user_has_field AS userfield ON field.id = userfield.field_id AND userfield.user_id = '.intval($user->id).'
1461 WHERE field.id NOT IN(1, 2)';
1462
1463 $allFields = acym_loadObjectList($query);
1464
1465 foreach ($allFields as $oneField) {
1466 $fieldOptions = @json_decode(empty($oneField->option) ? '{}' : $oneField->option);
1467 if (in_array($oneField->type, ['multiple_dropdown', 'radio', 'checkbox', 'single_dropdown'])) {
1468 $oneField->field_value = json_decode($oneField->field_value, true);
1469 if (!empty($oneField->value)) {
1470 $oneField->value = explode(',', $oneField->value);
1471 $values = [];
1472 foreach ($oneField->field_value as $oneFieldValue) {
1473 foreach ($oneField->value as $oneValue) {
1474 if ($oneFieldValue['value'] == $oneValue) {
1475 $values[] = $oneFieldValue['title'];
1476 }
1477 }
1478 }
1479 $oneField->value = implode(',', $values);
1480 }
1481 } elseif ($oneField->type === 'file' && !empty($oneField->value)) {
1482 $tryJsonDecode = json_decode($oneField->value);
1483 if (json_last_error() === JSON_ERROR_NONE && is_array($tryJsonDecode)) {
1484 $oneField->value = !empty($tryJsonDecode) ? $tryJsonDecode[0] : null;
1485 }
1486 } elseif ($oneField->type === 'date') {
1487 if (empty($fieldOptions->format)) {
1488 acym_logError('Date field with ID '.$oneField->id.' without format option');
1489 continue;
1490 }
1491 $formatToDisplay = explode('%', str_replace('y', 'Y', $fieldOptions->format));
1492 unset($formatToDisplay[0]);
1493 if (!empty($oneField->value)) {
1494 $oneField->value = acym_date($oneField->value, implode('-', $formatToDisplay));
1495 }
1496 }
1497 $user->{$oneField->namekey} = empty($oneField->value) ? '' : $oneField->value;
1498 }
1499
1500 return $user;
1501 }
1502
1503 public function getAllSimpleData(): array
1504 {
1505 return acym_loadObjectList('SELECT email, name FROM #__acym_user');
1506 }
1507
1508 public function synchSaveCmsUser(array $user, bool $isnew, ?array $oldUser = null): void
1509 {
1510 // If the source is not already defined, we define it here
1511 $source = acym_getVar('string', 'acy_source', '');
1512 if (empty($source)) acym_setVar('acy_source', ACYM_CMS);
1513
1514 if (!$this->config->get('regacy', 0)) return;
1515
1516 $this->checkVisitor = false;
1517 $this->sendConf = false;
1518
1519 $regacyForceConf = $this->config->get('regacy_forceconf', 0);
1520 /* * * * * * * * * * * * * * * * * * *
1521 * Step 1: create / update the user *
1522 * * * * * * * * * * * * * * * * * * */
1523 $cmsUser = new \stdClass();
1524 $cmsUser->email = trim(acym_stripTags($user['email']));
1525 if (!acym_isValidEmail($cmsUser->email)) return;
1526 if (!empty($user['name'])) $cmsUser->name = trim(acym_stripTags($user['name']));
1527 if (!$regacyForceConf) $cmsUser->confirmed = 1;
1528 $cmsUser->active = 1 - intval($user['block']);
1529 $cmsUser->cms_id = $user['id'];
1530
1531 if (!$isnew && !empty($oldUser['email']) && $user['email'] != $oldUser['email']) {
1532 // The user changed its email address, load the current Acy user if any
1533 $acyUser = $this->getOneByEmail($oldUser['email']);
1534 if (!empty($acyUser)) $cmsUser->id = $acyUser->id;
1535 }
1536
1537 // Just in case of this is an existing user but the e-mail address has been modified by something else
1538 if (empty($cmsUser->id) && !empty($cmsUser->cms_id)) {
1539 $acyUser = $this->getOneByCMSId($cmsUser->cms_id);
1540 if (!empty($acyUser)) $cmsUser->id = $acyUser->id;
1541 }
1542
1543 $acyUser = $this->getOneByEmail($cmsUser->email);
1544 // If an Acy user with the same email address already exists
1545 if (!empty($acyUser)) {
1546 // And wasn't linked to the site account, link it
1547 if (empty($cmsUser->id)) {
1548 $cmsUser->id = $acyUser->id;
1549 } elseif ($cmsUser->id != $acyUser->id) {
1550 // And has a different id, delete it
1551 $this->delete([$acyUser->id]);
1552 }
1553 } else {
1554 $cmsUser->source = $source;
1555 }
1556
1557 $isnew = $isnew || empty($cmsUser->id);
1558
1559 $id = $this->save($cmsUser);
1560 if (empty($id)) {
1561 return;
1562 }
1563
1564 // Force trigger confirmation process on cms user confirmation (send welcome emails, automation, save history...)
1565 $confirmationRequired = $this->config->get('require_confirmation', 1);
1566 if (!$isnew && !$regacyForceConf && $user['block'] == 0 && !empty($oldUser['block']) && $confirmationRequired == 1) {
1567 $this->confirm($id);
1568 }
1569
1570 /* * * * * * * * * * * * * * * * * * * * * *
1571 * Step 2: Handle the user's subscription *
1572 * * * * * * * * * * * * * * * * * * * * * */
1573
1574 $currentSubscription = $this->getSubscriptionStatus($id);
1575
1576 // In the Acy configuration, we can tell AcyMailing to automatically subscribe newly created users to some lists
1577 $autoLists = $isnew ? $this->config->get('regacy_autolists', '') : '';
1578 $autoLists = explode(',', $autoLists);
1579 acym_arrayToInteger($autoLists);
1580
1581 $listsClass = new ListClass();
1582 $allLists = $listsClass->getAll();
1583
1584 // The user can select some lists on the registration form
1585 $visibleLists = acym_getVar('string', 'regacy_visible_lists', '');
1586 $visibleLists = explode(',', $visibleLists);
1587 acym_arrayToInteger($visibleLists);
1588
1589 $visibleListsChecked = acym_getVar('array', 'regacy_visible_lists_checked', []);
1590 acym_arrayToInteger($visibleListsChecked);
1591
1592
1593 // Handle the unsubscription
1594 if (!$isnew && !empty($visibleLists)) {
1595 $currentlySubscribedLists = [];
1596 foreach ($currentSubscription as $oneSubscription) {
1597 if ($oneSubscription->status == 1) $currentlySubscribedLists[] = $oneSubscription->list_id;
1598 }
1599 $unsubscribeLists = array_intersect($currentlySubscribedLists, array_diff($visibleLists, $visibleListsChecked));
1600 $this->unsubscribe([$id], $unsubscribeLists);
1601 }
1602
1603 // Handle the subscription
1604 $listsToSubscribe = [];
1605 foreach ($allLists as $oneList) {
1606 if (!$oneList->active) continue;
1607 if (!empty($currentSubscription[$oneList->id]) && $currentSubscription[$oneList->id]->status == 1) continue;
1608
1609 if (
1610 in_array($oneList->id, $visibleListsChecked)
1611 || (
1612 in_array($oneList->id, $autoLists)
1613 && !in_array($oneList->id, $visibleLists) && empty($currentSubscription[$oneList->id])
1614 )
1615 ) {
1616 $listsToSubscribe[] = $oneList->id;
1617 }
1618 }
1619
1620 if (!empty($listsToSubscribe)) $this->subscribe([$id], $listsToSubscribe);
1621
1622 if ($isnew) $this->sendNotification($id, 'acy_notification_create');
1623
1624 // We don't force the confirmation email, or the user is disabled, or he's already confirmed
1625 $acymailingUser = $this->getOneById($id);
1626 if (!empty($user['block']) || !empty($acymailingUser->confirmed)) return;
1627
1628 // New active user, or just activated the user, send the email
1629 if ($isnew || !empty($oldUser['block'])) {
1630 if ($confirmationRequired && $regacyForceConf) {
1631 $this->forceConf = true;
1632 $this->sendConfirmation($id);
1633 }
1634
1635 // Send welcome emails on CMS user confirmation (no Acym confirmation required)
1636 if (!$confirmationRequired && !empty($oldUser['email'])) {
1637 $listIDs = acym_loadResultArray('SELECT `list_id` FROM `#__acym_user_has_list` WHERE `status` = 1 AND `user_id` = '.intval($id));
1638 if (empty($listIDs)) return;
1639 $listsClass->sendWelcome($id, $listIDs);
1640 }
1641 }
1642 }
1643
1644 public function synchDeleteCmsUser(string $userEmail): void
1645 {
1646 $acyUser = $this->getOneByEmail($userEmail);
1647
1648 if (empty($acyUser)) return;
1649
1650 if ($this->config->get('regacy', '0') == 1 && $this->config->get('regacy_delete', '0') == 1) {
1651 $this->delete([$acyUser->id]);
1652 } else {
1653 acym_query('UPDATE #__acym_user SET `cms_id` = 0 WHERE `id` = '.intval($acyUser->id));
1654 }
1655 }
1656
1657 /**
1658 * Search users via partial email address
1659 */
1660 public function getUsersLikeEmail(string $search): array
1661 {
1662 $query = 'SELECT id, email FROM #__acym_user WHERE email LIKE '.acym_escapeDB('%'.$search.'%');
1663
1664 return acym_loadObjectList($query);
1665 }
1666
1667 public function sendNotification(int $userId, string $notification, array $params = []): void
1668 {
1669 if (empty($userId) || $this->blockNotifications) return;
1670
1671 $notifyUsers = explode(',', $this->config->get($notification));
1672 if (acym_isAdmin() || empty($notifyUsers)) return;
1673
1674 $mailerHelper = new MailerHelper();
1675 $mailerHelper->report = false;
1676 $mailerHelper->autoAddUser = true;
1677
1678 $user = $this->getOneById($userId);
1679 $userField = empty($user) ? null : $this->getAllUserFields($user);
1680 if (!empty($userField)) {
1681 foreach ($userField as $map => $value) {
1682 $mailerHelper->addParam('user:'.$map, $value);
1683 }
1684 }
1685
1686 // Load the subscription
1687 $rawSubscription = $this->getUserSubscriptionById($userId);
1688 $subscription = [''];
1689 foreach ($rawSubscription as $listId => $listData) {
1690 $currentList = $listData->name.' => ';
1691 if (intval($listData->status) === 1) {
1692 $currentList .= acym_translation('ACYM_SUBSCRIBED').' - '.$listData->subscription_date;
1693 } else {
1694 $currentList .= acym_translation('ACYM_UNSUBSCRIBED').' - '.$listData->unsubscribe_date;
1695 }
1696 $subscription[] = $currentList;
1697 }
1698 $mailerHelper->addParam('user:subscription', implode('<br/>', $subscription));
1699
1700 if (!empty($params)) {
1701 foreach ($params as $name => $value) {
1702 $mailerHelper->addParam($name, $value);
1703 }
1704 }
1705
1706 $mailClass = new MailClass();
1707 foreach ($notifyUsers as $oneUser) {
1708 if (!acym_isValidEmail($oneUser)) {
1709 continue;
1710 }
1711
1712 $admin = $this->getOneByEmail($oneUser, true);
1713
1714 $notificationEmail = $mailClass->getOneByName($notification);
1715 if (!empty($admin) && !empty($notificationEmail)) {
1716 try {
1717 $mailerHelper->sendOne($notificationEmail->id, $admin->id);
1718 } catch (\Exception $e) {
1719 acym_logError($e->getMessage());
1720 }
1721 }
1722 }
1723 }
1724
1725 public function getMailHistory(int $userId)
1726 {
1727 $mailHistory = acym_loadObjectList(
1728 'SELECT user_stat.*, mail.id, mail.subject, SUM(url_click.click) as click
1729 FROM #__acym_user_stat AS user_stat
1730 JOIN #__acym_mail AS mail ON mail.id = user_stat.mail_id
1731 LEFT JOIN #__acym_url_click AS url_click ON user_stat.mail_id = url_click.mail_id AND url_click.user_id = '.intval($userId).'
1732 WHERE user_stat.user_id = '.intval($userId).'
1733 GROUP BY mail_id
1734 ORDER BY send_date DESC
1735 LIMIT 50',
1736 'mail_id'
1737 );
1738 $mailClass = new MailClass();
1739
1740 return $mailClass->decode($mailHistory);
1741 }
1742
1743 public function deleteHistoryPeriod(): array
1744 {
1745 if (empty($this->config->get('delete_user_history_enabled', 0))) return [];
1746 $deleteOverSecond = $this->config->get('delete_user_history', 0);
1747 if (empty($deleteOverSecond)) return [];
1748 $date = time() - $deleteOverSecond;
1749
1750 $query = 'DELETE FROM #__acym_history WHERE date < '.intval($date);
1751
1752 try {
1753 $status = acym_query($query);
1754 $message = empty($status) ? '' : acym_translationSprintf('ACYM_DELETE_X_ROWS_TABLE_X', $status, strtolower(acym_translation('ACYM_USER_HISTORY')));
1755 } catch (\Exception $e) {
1756 $status = false;
1757 $message = $e->getMessage();
1758 }
1759
1760 return ['status' => $status !== false, 'message' => $message];
1761 }
1762
1763 public function resetSubscription(array $userIds, array $lists): bool
1764 {
1765 if (empty($lists)) return false;
1766
1767 acym_arrayToInteger($userIds);
1768 acym_arrayToInteger($lists);
1769
1770 foreach ($userIds as $userId) {
1771 acym_query(
1772 'DELETE FROM `#__acym_user_has_list`
1773 WHERE user_id = '.$userId.'
1774 AND list_id IN ('.implode(',', $lists).')'
1775 );
1776
1777 acym_query(
1778 'DELETE FROM #__acym_queue WHERE user_id = '.$userId.' AND mail_id IN (
1779 SELECT followup_mail.mail_id FROM #__acym_followup_has_mail AS followup_mail
1780 JOIN #__acym_followup AS followup ON followup.id = followup_mail.followup_id AND followup.list_id IN ('.implode(',', $lists).')
1781 )'
1782 );
1783
1784 $historyClass = new HistoryClass();
1785 $historyData = acym_translationSprintf('ACYM_LISTS_NUMBERS', implode(', ', $lists));
1786 $historyClass->insert($userId, 'reset_subscription', [$historyData]);
1787 }
1788
1789 return true;
1790 }
1791
1792 public function addMissingKeys(): int
1793 {
1794 $usersMissingKey = acym_loadResultArray('SELECT `id` FROM #__acym_user WHERE `key` IS NULL OR `key` = ""');
1795 foreach ($usersMissingKey as $oneUserId) {
1796 acym_query('UPDATE #__acym_user SET `key` = '.acym_escapeDB(acym_generateKey(14)).' WHERE `id` = '.intval($oneUserId));
1797 }
1798
1799 return count($usersMissingKey);
1800 }
1801
1802 public function hasUserAccess(int $subscriberId): bool
1803 {
1804 $userId = acym_currentUserId();
1805 if (empty($userId)) {
1806 return false;
1807 }
1808
1809 if (acym_isAdmin() && acym_isAllowed('users')) {
1810 return true;
1811 }
1812
1813 $user = $this->getOneById($subscriberId);
1814 $userEmail = acym_currentUserEmail();
1815 if (!empty($user) && $user->email === $userEmail) {
1816 return true;
1817 }
1818
1819 $listClass = new ListClass();
1820 $allowedListIds = $listClass->getManageableLists();
1821 if (empty($allowedListIds)) {
1822 return false;
1823 }
1824
1825 $query = 'SELECT COUNT(*) FROM #__acym_user AS user
1826 JOIN #__acym_user_has_list AS map ON map.user_id = user.id
1827 WHERE map.list_id IN ('.implode(',', $allowedListIds).')
1828 AND user.id = '.intval($subscriberId);
1829
1830 return acym_loadResult($query) > 0;
1831 }
1832
1833 public function getXUsers(array $options = []): array
1834 {
1835 $limit = $options['limit'] ?? 10;
1836 $offset = $options['offset'] ?? 0;
1837 $filters = $options['filters'] ?? [];
1838
1839 $conditions = [];
1840 foreach ($filters as $column => $filter) {
1841 if (in_array($column, ['id', 'cms_id', 'active', 'confirmed', 'tracking'])) {
1842 $conditions[] = acym_secureDBColumn($column).' = '.intval($filter);
1843 } else {
1844 $conditions[] = acym_secureDBColumn($column).' LIKE '.acym_escapeDB('%'.$filter.'%');
1845 }
1846 }
1847
1848 if (!empty($options['created_after'])) {
1849 $conditions[] = 'creation_date > '.acym_escapeDB($options['created_after']);
1850 }
1851
1852 if (!empty($options['confirmed_after'])) {
1853 $conditions[] = 'confirmation_date > '.acym_escapeDB($options['confirmed_after']);
1854 }
1855
1856 $query = 'SELECT * FROM #__acym_user';
1857 if (!empty($conditions)) {
1858 $query .= ' WHERE '.implode(' AND ', $conditions);
1859 }
1860
1861 return acym_loadObjectList($query, $this->pkey, $offset, $limit);
1862 }
1863
1864 public function disableTracking(object $user, int $mailId): void
1865 {
1866 acym_query('UPDATE #__acym_user SET `tracking` = 0 WHERE `id` = '.intval($user->id));
1867
1868 $historyClass = new HistoryClass();
1869 $historyClass->insert($user->id, 'tracking_disabled', [], $mailId);
1870 }
1871 }
1872