PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Classes / UserClass.php

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

1,873 lines 71.2 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 }
596 if ($oneList->status == 0) {
597 $currentlyUnsubscribed[$oneList->id] = $oneList;
598 }
599 }
600
601 $subscribedLists = [];
602 $listsNotExisting = [];
603 foreach ($addLists as $oneListId) {
604 // The user is already subscribed
605 if (empty($oneListId) || !empty($currentlySubscribed[$oneListId])) {
606 continue;
607 }
608
609 $list = $listClass->getOneById($oneListId);
610 if (empty($list)) {
611 $listsNotExisting[] = $oneListId;
612 continue;
613 }
614
615 $subscription = new \stdClass();
616 $subscription->user_id = $userId;
617 $subscription->list_id = $oneListId;
618 $subscription->status = 1;
619 $subscription->subscription_date = gmdate('Y-m-d H:i:s', time());
620
621 if (empty($currentSubscription[$oneListId])) {
622 // The user isn't already subscribed, we subscribe him!
623 acym_insertObject('#__acym_user_has_list', $subscription);
624 } elseif (!empty($currentlyUnsubscribed[$oneListId])) {
625 // The user re-subscribes or we have to confirm the subscription
626 acym_updateObject('#__acym_user_has_list', $subscription, ['user_id', 'list_id']);
627 }
628
629 $subscribedLists[] = $oneListId;
630 $subscribedToLists = true;
631 }
632
633 if (!empty($listsNotExisting)) {
634 $this->errors['list_not_existing'] = acym_translationSprintf('ACYM_LIST_NOT_FOUND', implode(', ', $listsNotExisting));
635 }
636
637 $historyClass->insert($userId, 'subscribed', [$historyData]);
638
639 if ($trigger) {
640 acym_trigger('onAcymAfterUserSubscribe', [&$user, &$subscribedLists]);
641 }
642
643 if ($this->sendWelcomeEmail && (!$confirmationRequired || $user->confirmed == 1) && $user->active == 1) {
644 $listClass->sendWelcome($userId, $subscribedLists, $forceFront);
645 }
646 }
647
648 return $subscribedToLists;
649 }
650
651 private function registerUnsubUser(array $userIds): void
652 {
653 $mailId = acym_getVar('int', 'mail_id', 0);
654 if (empty($mailId)) return;
655
656 $mailStatClass = new MailStatClass();
657 $userStatClass = new UserStatClass();
658
659 $countUnsubscribe = 0;
660
661 foreach ($userIds as $id) {
662 $userStat = $userStatClass->getOneByMailAndUserId($mailId, $id);
663 // We didn't send this email to this user, something is wrong
664 if (empty($userStat)) continue;
665
666 $newUserStat = new \stdClass();
667 $newUserStat->mail_id = $mailId;
668 $newUserStat->user_id = $id;
669 $newUserStat->unsubscribe = empty($userStat->unsubscribe) ? 1 : $userStat->unsubscribe + 1;
670
671 if (empty($userStat->unsubscribe)) {
672 $countUnsubscribe++;
673 }
674
675 $userStatClass->save($newUserStat);
676 }
677
678 $mailStat = new \stdClass();
679 $mailStat->mail_id = $mailId;
680 $mailStat->unsubscribe_total = $countUnsubscribe;
681
682 $mailStatClass->save($mailStat);
683 }
684
685 public function unsubscribe($userIds, array $lists): bool
686 {
687 if (empty($lists)) {
688 return false;
689 }
690
691 //TODO was previously shown as an int in the dev documentation, hence not adding the type yet
692 if (!is_array($userIds)) {
693 $userIds = [$userIds];
694 }
695 acym_arrayToInteger($userIds);
696 acym_arrayToInteger($lists);
697
698 $listClass = new ListClass();
699 $unsubscribedFromLists = false;
700 foreach ($userIds as $userId) {
701 $user = $this->getOneById($userId);
702
703 if (empty($user)) {
704 continue;
705 }
706
707 $currentSubscription = $this->getUserSubscriptionById($userId);
708
709 $currentlySubscribed = [];
710 $currentlyUnsubscribed = [];
711 foreach ($currentSubscription as $oneList) {
712 if ($oneList->status == 0) {
713 $currentlyUnsubscribed[$oneList->id] = $oneList;
714 } elseif ($oneList->status == 1) {
715 $currentlySubscribed[$oneList->id] = $oneList;
716 }
717 }
718
719 $unsubscribedLists = [];
720 $listsNotExisting = [];
721 foreach ($lists as $oneListId) {
722 // The user is already unsubscribed
723 if (empty($oneListId) || !empty($currentlyUnsubscribed[$oneListId])) {
724 continue;
725 }
726
727 // Don't unsubscribe from non-subscribed lists
728 if (empty($currentlySubscribed[$oneListId])) {
729 continue;
730 }
731
732 $list = $listClass->getOneById($oneListId);
733 if (empty($list)) {
734 $listsNotExisting[] = $oneListId;
735 continue;
736 }
737
738 $subscription = new \stdClass();
739 $subscription->user_id = $userId;
740 $subscription->list_id = $oneListId;
741 $subscription->status = 0;
742 $subscription->unsubscribe_date = gmdate('Y-m-d H:i:s', time());
743 if (empty($currentSubscription[$oneListId])) {
744 // The user isn't already subscribed, we unsubscribe him directly
745 acym_insertObject('#__acym_user_has_list', $subscription);
746 } else {
747 // The user unsubscribed from a list
748 acym_updateObject('#__acym_user_has_list', $subscription, ['user_id', 'list_id']);
749 }
750
751 $unsubscribedLists[] = $oneListId;
752 $unsubscribedFromLists = true;
753 }
754
755 if (!empty($listsNotExisting)) {
756 $this->errors['list_not_existing'] = acym_translationSprintf('ACYM_LIST_NOT_FOUND', implode(', ', $listsNotExisting));
757 }
758
759 acym_query(
760 'DELETE FROM #__acym_queue WHERE user_id = '.intval($userId).' AND mail_id IN (
761 SELECT followup_mail.mail_id FROM #__acym_followup_has_mail AS followup_mail
762 JOIN #__acym_followup AS followup ON followup.id = followup_mail.followup_id AND followup.list_id IN ('.implode(',', $lists).')
763 )'
764 );
765
766 $historyClass = new HistoryClass();
767 $historyData = acym_translationSprintf('ACYM_LISTS_NUMBERS', implode(', ', $lists));
768 $historyClass->insert($userId, 'unsubscribed', [$historyData], 0, acym_getVar('string', 'unsubscribe_reason'));
769
770 if ($this->triggers) {
771 acym_trigger('onAcymAfterUserUnsubscribe', [&$user, &$unsubscribedLists]);
772 }
773
774 if ($this->sendUnsubscribeEmail) {
775 $listClass->sendUnsubscribe($userId, $unsubscribedLists);
776 }
777
778 if (!empty($unsubscribedLists)) {
779 foreach ($unsubscribedLists as $i => $oneListId) {
780 $currentList = $listClass->getOneById($oneListId);
781 $unsubscribedLists[$i] = $currentList->name;
782 }
783 $this->sendNotification($user->id, 'acy_notification_unsub', ['lists' => implode(', ', $unsubscribedLists)]);
784 }
785 }
786
787 if ($unsubscribedFromLists) {
788 $this->registerUnsubUser($userIds);
789 }
790
791 return $unsubscribedFromLists;
792 }
793
794 public function unsubscribeOnSubscriptions(int $userId, array $listIds): bool
795 {
796 if (empty($listIds)) {
797 return false;
798 }
799
800 acym_arrayToInteger($listIds);
801
802 $subscribedLists = acym_loadResultArray('SELECT list_id FROM #__acym_user_has_list WHERE user_id = '.intval($userId).' AND list_id IN ('.implode(',', $listIds).')');
803
804 if (!empty($subscribedLists)) $this->unsubscribe([$userId], $subscribedLists);
805
806 return true;
807 }
808
809 public function removeSubscription(array $userIds, array $listIds = []): void
810 {
811 if (empty($listIds) || empty($userIds)) {
812 return;
813 }
814
815 acym_arrayToInteger($userIds);
816 acym_arrayToInteger($listIds);
817
818 acym_query(
819 'DELETE FROM #__acym_user_has_list
820 WHERE user_id IN ('.implode(',', $userIds).')
821 AND list_id IN ('.implode(',', $listIds).')'
822 );
823 }
824
825 public function onlyManageableUsers(array &$elements): void
826 {
827 if (acym_isAdmin() && acym_isAllowed('users')) {
828 return;
829 }
830
831 $listClass = new ListClass();
832 $manageableLists = $listClass->getManageableLists();
833 if (empty($manageableLists)) {
834 return;
835 }
836
837 $currentUserId = acym_currentUserId();
838 acym_arrayToInteger($elements);
839
840 $selfManagement = in_array($currentUserId, $elements);
841
842 $elements = acym_loadResultArray(
843 'SELECT DISTINCT user_id
844 FROM #__acym_user_has_list
845 WHERE `list_id` IN ('.implode(',', $manageableLists).')
846 AND `user_id` IN ('.implode(',', $elements).')'
847 );
848
849 if ($selfManagement && !in_array($currentUserId, $elements)) {
850 $elements[] = $currentUserId;
851 }
852 }
853
854 public function delete(array $elements, bool $forceDelete = false): int
855 {
856 acym_arrayToInteger($elements);
857 $this->onlyManageableUsers($elements);
858
859 if (empty($elements)) {
860 return 0;
861 }
862
863 if (acym_isAdmin() || $forceDelete || 'delete' === $this->config->get('frontend_delete_button', 'delete')) {
864 acym_query('DELETE FROM #__acym_user_has_list WHERE user_id IN ('.implode(',', $elements).')');
865 acym_query('DELETE FROM #__acym_queue WHERE user_id IN ('.implode(',', $elements).')');
866 acym_query('DELETE FROM #__acym_user_has_field WHERE user_id IN ('.implode(',', $elements).')');
867 acym_query('DELETE FROM #__acym_history WHERE user_id IN ('.implode(',', $elements).')');
868 acym_query('DELETE FROM #__acym_user_stat WHERE user_id IN ('.implode(',', $elements).')');
869
870 $scenarioProcessIds = acym_loadResultArray('SELECT id FROM #__acym_scenario_process WHERE user_id IN ('.implode(',', $elements).')');
871
872 if (!empty($scenarioProcessIds)) {
873 acym_query('DELETE FROM #__acym_scenario_history_line WHERE scenario_process_id IN ('.implode(',', $scenarioProcessIds).')');
874 acym_query('DELETE FROM #__acym_scenario_queue WHERE scenario_process_id IN ('.implode(',', $scenarioProcessIds).')');
875 acym_query('DELETE FROM #__acym_scenario_process WHERE id IN ('.implode(',', $scenarioProcessIds).')');
876 }
877
878 $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS;
879 foreach ($elements as $userId) {
880 $folderPath = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS.$userId.DS);
881 if (is_dir($folderPath)) {
882 acym_deleteFolder($folderPath);
883 }
884 }
885
886 return parent::delete($elements);
887 } else {
888 $listClass = new ListClass();
889 $manageableLists = $listClass->getManageableLists();
890
891 if (!empty($manageableLists)) {
892 $this->removeSubscription($elements, $manageableLists);
893 acym_query(
894 'DELETE queue
895 FROM #__acym_queue AS queue
896 JOIN #__acym_mail_has_list AS maillist
897 ON queue.mail_id = maillist.mail_id
898 WHERE queue.user_id IN ('.implode(',', $elements).')
899 AND maillist.list_id IN ('.implode(',', $manageableLists).')'
900 );
901 }
902
903 return count($elements);
904 }
905 }
906
907 public function save(object $user, array $customFields = [], bool $ajax = false): ?int
908 {
909 if (empty($user->email) && empty($user->id)) {
910 return null;
911 }
912
913 if (isset($user->email)) {
914 $user->email = strtolower($user->email);
915 if (!acym_isValidEmail($user->email)) {
916 $this->errors[] = acym_translation('ACYM_VALID_EMAIL');
917
918 return null;
919 }
920 }
921
922 if (empty($user->id)) {
923 if (!isset($user->active)) {
924 $user->active = 1;
925 }
926
927 if (!isset($user->tracking)) {
928 $user->tracking = $this->config->get('dont_track_by_default', 0) ? 0 : 1;
929 }
930
931 if (empty($user->language)) {
932 // Take the user account's language
933 if (!acym_isAdmin()) {
934 $cmsUserLanguage = acym_getCmsUserLanguage();
935 }
936
937 if (!empty($cmsUserLanguage)) {
938 $user->language = $cmsUserLanguage;
939 } elseif (acym_isMultilingual()) {
940 // Take the configuration's language
941 $configUserLanguage = $this->config->get('multilingual_user_default', 'current_language');
942 $user->language = $configUserLanguage === 'current_language' ? acym_getLanguageTag() : $configUserLanguage;
943 }
944 }
945
946 $currentUserid = acym_currentUserId();
947 $currentEmail = acym_currentUserEmail();
948 $allowVisitors = $this->config->get('allow_visitor', 1) == 1;
949 if ($this->checkVisitor && !acym_isAdmin() && !$allowVisitors && (empty($currentUserid) || strtolower($currentEmail) !== $user->email)) {
950 //We don't accept the subscription as either the user is not logged in or it's not the good one
951 $this->errors[] = acym_translation('ACYM_ONLY_LOGGED');
952
953 return null;
954 }
955
956 if (empty($user->name) && $this->config->get('generate_name', 1)) {
957 $user->name = ucwords(trim(str_replace(['.', '_', ')', ',', '(', '-', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0], ' ', substr($user->email, 0, strpos($user->email, '@')))));
958 }
959
960 $source = acym_getVar('string', 'acy_source', '');
961 if (empty($user->source) && !empty($source)) {
962 $user->source = $source;
963 }
964
965 $user->key = acym_generateKey(14);
966
967 $user->creation_date = gmdate('Y-m-d H:i:s', time());
968 } elseif (!empty($user->confirmed)) {
969 $oldUser = $this->getOneByIdWithCustomFields($user->id);
970 if (!empty($oldUser) && empty($oldUser['confirmed'])) {
971 $user->confirmation_date = gmdate('Y-m-d H:i:s', time());
972 $user->confirmation_ip = acym_getIP();
973
974 if ($this->triggers) {
975 acym_trigger('onAcymAfterUserConfirm', [&$user]);
976 }
977 }
978 } else {
979 $oldUser = $this->getOneByIdWithCustomFields($user->id);
980 }
981
982 if (!empty($user->id) && isset($user->active) && !empty($oldUser)) {
983 $wasActive = isset($oldUser['active']) ? (int)$oldUser['active'] : 1;
984 $isNowActive = (int)$user->active;
985 if ($wasActive === 1 && $isNowActive === 0) {
986 $user->deactivation_date = acym_date('now', 'Y-m-d H:i:s');
987 } elseif ($wasActive === 0 && $isNowActive === 1) {
988 $user->deactivation_date = null;
989 }
990 }
991
992 foreach ($user as $oneAttribute => $value) {
993 if (empty($value)) {
994 continue;
995 }
996
997 $oneAttribute = trim(strtolower($oneAttribute));
998 if (!in_array($oneAttribute, self::RESTRICTED_FIELDS)) {
999 $user->$oneAttribute = acym_stripTags($value);
1000 }
1001
1002 // Convert into utf-8 in case of it's not already
1003 // Double test on UTF-8 because the preg_match returns an error on string longer than 200 characters
1004 if (is_numeric($user->$oneAttribute)) {
1005 continue;
1006 }
1007
1008 if (function_exists('mb_detect_encoding')) {
1009 if (mb_detect_encoding($user->$oneAttribute, 'UTF-8', true) !== 'UTF-8') {
1010 $user->$oneAttribute = acym_utf8Encode($user->$oneAttribute);
1011 }
1012 } elseif (
1013 !preg_match(
1014 '%^(?:[\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',
1015 $user->$oneAttribute
1016 )
1017 ) {
1018 $user->$oneAttribute = acym_utf8Encode($user->$oneAttribute);
1019 }
1020 }
1021
1022 // Prevent SQL error on flags that aren't cleaned
1023 if (!empty($user->automation) && strlen($user->automation) > 20) {
1024 $user->automation = '';
1025 }
1026
1027 if (empty($user->id)) {
1028 if (empty($user->cms_id) && !empty($user->email)) {
1029 global $acymCmsUserVars;
1030 $userCmsID = acym_loadResult(
1031 'SELECT '.acym_secureDBColumn($acymCmsUserVars->id).' FROM '.$acymCmsUserVars->table.' WHERE '.acym_secureDBColumn(
1032 $acymCmsUserVars->email
1033 ).' = '.acym_escapeDB($user->email)
1034 );
1035
1036 if (!empty($userCmsID)) $user->cms_id = $userCmsID;
1037 }
1038 if ($this->triggers) {
1039 $result = acym_trigger('onAcymBeforeUserCreate', [&$user]);
1040 if (is_array($result) && in_array(false, $result)) {
1041 $acycheckerError = acym_getVar('string', 'acychecker_error');
1042 if (!empty($acycheckerError)) {
1043 if ($ajax) {
1044 $this->errors[] = $acycheckerError;
1045 } else {
1046 acym_enqueueMessage($acycheckerError, 'error');
1047 }
1048 }
1049
1050 return null;
1051 }
1052 }
1053 } else {
1054 if ($this->triggers) {
1055 acym_trigger('onAcymBeforeUserModify', [&$user]);
1056 }
1057 }
1058
1059 $userID = parent::save($user);
1060
1061 // Save custom fields if there are any
1062 $fieldClass = new FieldClass();
1063 if (!empty($customFields)) {
1064 foreach ($customFields as $key => $value) {
1065 if (!is_string($value)) {
1066 continue;
1067 }
1068
1069 // Remove most of the existing emojis that are not supported
1070 $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $value);
1071
1072 $field = $fieldClass->getOneById($key);
1073 if (empty($field) || $field->type !== 'textarea') {
1074 $value = preg_replace('/\s+/', ' ', $value);
1075 }
1076 $customFields[$key] = $value;
1077 }
1078 }
1079 $fieldClass->store($userID, $customFields, $ajax);
1080 if (!empty($fieldClass->errors)) {
1081 $this->errors = array_merge($this->errors, $fieldClass->errors);
1082 }
1083
1084 $historyClass = new HistoryClass();
1085 if (empty($user->id)) {
1086 $user->id = $userID;
1087 $historyClass->insert($user->id, 'created');
1088 if ($this->triggers) {
1089 acym_trigger('onAcymAfterUserCreate', [&$user]);
1090 acym_triggerCmsHook('onAcymAfterUserSave', [$user, true]);
1091 }
1092 } else {
1093 $historyClass->insert($user->id, 'modified');
1094 if ($this->triggers) {
1095 acym_trigger('onAcymAfterUserModify', [&$user, &$oldUser]);
1096 acym_triggerCmsHook('onAcymAfterUserSave', [$user, false]);
1097 }
1098 }
1099
1100 $this->sendConfirmation($userID);
1101
1102 return $userID;
1103 }
1104
1105 public function saveForm(bool $ajax = false): bool
1106 {
1107 $allowUserModifications = $this->config->get('allow_modif', 'data') === 'all' || $this->allowModif;
1108 $allowSubscriptionModifications = $this->config->get('allow_modif', 'data') !== 'none' || $this->allowModif;
1109
1110 $user = new \stdClass();
1111 $connectedUser = $this->identify(true, 'userId', 'userKey');
1112
1113 $userData = acym_getVar('array', 'user', []);
1114 if (!empty($userData)) {
1115 foreach ($userData as $attribute => $value) {
1116 if (!in_array($attribute, self::FORM_ALLOWED_FIELDS, true)) {
1117 continue;
1118 }
1119 $user->$attribute = $value;
1120 }
1121 }
1122
1123 if (!empty($connectedUser->id)) {
1124 $user->id = $connectedUser->id;
1125 }
1126
1127 if (empty($user->email)) {
1128 if (!empty($connectedUser->email)) {
1129 $user->email = $connectedUser->email;
1130 }
1131 }
1132
1133 if (empty($user->email)) {
1134 $this->errors[] = acym_translation('ACYM_VALID_EMAIL');
1135
1136 return false;
1137 }
1138
1139 if (!$allowUserModifications && !empty($connectedUser)) {
1140 if ($connectedUser->email != $user->email) {
1141 $this->errors[] = acym_translation('ACYM_NOT_ALLOWED_MODIFY_USER');
1142
1143 return false;
1144 }
1145
1146 $allowUserModifications = true;
1147 $allowSubscriptionModifications = true;
1148 }
1149
1150 // Check if it already exists...
1151 if (!empty($user->email)) {
1152 // Do we already have a user with this same e-mail address?
1153 if (empty($user->id)) {
1154 $user->id = 0;
1155 }
1156 $existUser = acym_loadObject('SELECT * FROM #__acym_user WHERE email = '.acym_escapeDB($user->email).' AND id != '.intval($user->id));
1157 if (!empty($existUser->id)) {
1158 if (!$this->allowModif && !$allowSubscriptionModifications) {
1159 $this->errors[] = acym_translation('ACYM_ADDRESS_TAKEN');
1160
1161 return false;
1162 } else {
1163 $user->id = $existUser->id;
1164 }
1165 }
1166 }
1167
1168 // Did the user ask for its e-mail address to be modified?
1169 // If so, we set it as not confirmed automatically.
1170 if (!empty($user->id) && !empty($user->email)) {
1171 $existUser = $this->getOneById($user->id);
1172 if (trim(strtolower($user->email)) != strtolower($existUser->email)) {
1173 $user->confirmed = 0;
1174 }
1175 }
1176
1177 $fromProfile = acym_getVar('int', 'acyprofile', 0) == 1;
1178
1179 $this->newUser = empty($user->id);
1180 if (empty($user->id) || $allowUserModifications) {
1181 if ($this->newUser && $this->config->get('require_confirmation', 1) == 1) {
1182 $user->confirmed = 0;
1183 }
1184
1185 if ($fromProfile && !(isset($user->confirmed) && empty($user->confirmed))) {
1186 $this->sendConf = false;
1187 }
1188
1189 // Get custom fields to save them if exist
1190 $customFieldData = acym_getVar('array', 'customField', []);
1191 $id = $this->save($user, $customFieldData, $ajax);
1192 $allowSubscriptionModifications = true;
1193 } else {
1194 $id = $user->id;
1195 //We didn't save the user... but we should check the confirm field anyway
1196 if (isset($user->confirmed) && empty($user->confirmed)) {
1197 $this->sendConfirmation($id);
1198 }
1199 }
1200
1201 if (empty($id)) return false;
1202
1203 $formData = acym_getVar('array', 'data', []);
1204 acym_setVar('userId', $id);
1205
1206 if (!acym_isAdmin()) {
1207 $hiddenlistsString = acym_getVar('string', 'hiddenlists', '');
1208 $hiddenlists = explode(',', $hiddenlistsString);
1209 $visibleSubscription = acym_getVar('array', 'subscription', []);
1210 $subscribeLists = array_merge($hiddenlists, $visibleSubscription);
1211 acym_arrayToInteger($subscribeLists);
1212
1213 if (!empty($subscribeLists)) {
1214 foreach ($subscribeLists as $oneListId) {
1215 $formData['listsub'][$oneListId] = ['status' => 1];
1216 }
1217 }
1218 }
1219
1220 if (empty($formData['listsub'])) {
1221 if (!$fromProfile) $this->sendNotification($id, 'acy_notification_subform');
1222
1223 $this->sendNotification(
1224 $id,
1225 $this->newUser ? 'acy_notification_create' : 'acy_notification_profile'
1226 );
1227
1228 return true;
1229 }
1230
1231 if (!$allowSubscriptionModifications) {
1232 $this->requireId = true;
1233 $this->errors[] = acym_translation('ACYM_NOT_ALLOWED_MODIFY_USER');
1234
1235 return false;
1236 }
1237
1238 $addLists = [];
1239 $unsubLists = [];
1240 foreach ($formData['listsub'] as $listID => $oneList) {
1241 if ($oneList['status'] == 1) {
1242 $addLists[] = $listID;
1243 } else {
1244 $unsubLists[] = $listID;
1245 }
1246 }
1247
1248 $this->subscribed = $this->subscribe([$id], $addLists);
1249
1250 if (!$fromProfile) $this->sendNotification($id, 'acy_notification_subform');
1251
1252 $this->sendNotification(
1253 $id,
1254 $this->newUser ? 'acy_notification_create' : 'acy_notification_profile'
1255 );
1256
1257 if (!$this->newUser) $this->unsubscribe([$id], $unsubLists);
1258
1259 return true;
1260 }
1261
1262 public function sendConfirmation(int $userID): void
1263 {
1264 if (!$this->forceConf && !$this->sendConf) {
1265 return;
1266 }
1267
1268 if ($this->config->get('require_confirmation', 1) != 1 || (acym_isAdmin() && !$this->forceConfAdmin)) {
1269 return;
1270 }
1271
1272 $myUser = $this->getOneById($userID);
1273
1274 if (!empty($myUser->confirmed)) {
1275 return;
1276 }
1277
1278 $mailerHelper = new MailerHelper();
1279 $mailerHelper->report = (bool)$this->config->get('confirm_message', 0);
1280
1281 //TODO $mailerHelper->addParam('user:subscription', implode('<br/>', $subscription));
1282 $mailClass = new MailClass();
1283 $confirmationEmail = $mailClass->getOneByName('acy_confirm');
1284 $this->confirmationSentSuccess = !empty($confirmationEmail) && $mailerHelper->sendOne($confirmationEmail->id, $myUser->id);
1285 $this->confirmationSentError = $mailerHelper->reportMessage;
1286 }
1287
1288 public function setInactive(array $elements): void
1289 {
1290 $this->deactivate($elements);
1291 }
1292
1293 public function deactivate(array $userIds): void
1294 {
1295 $userIds = array_map('intval', $userIds);
1296 $userIds = array_filter($userIds);
1297
1298 if (empty($userIds)) return;
1299
1300 acym_query('UPDATE `#__acym_user` SET `active` = 0, `deactivation_date` = UTC_TIMESTAMP() WHERE `id` IN ('.implode(',', $userIds).')');
1301 }
1302
1303 public function setActive(array $elements): void
1304 {
1305 $this->reactivate($elements);
1306 }
1307
1308 public function reactivate(array $userIds): void
1309 {
1310 $userIds = array_map('intval', $userIds);
1311 $userIds = array_filter($userIds);
1312
1313 if (empty($userIds)) return;
1314
1315 acym_query('UPDATE `#__acym_user` SET `active` = 1, `deactivation_date` = NULL WHERE `id` IN ('.implode(',', $userIds).')');
1316 }
1317
1318 public function confirm(int $userId): void
1319 {
1320 $user = $this->getOneById($userId);
1321 if (empty($user)) return;
1322
1323 // We confirm the user and add the confirmation_date and confirmation_ip in the table.
1324 $confirmDate = gmdate('Y-m-d H:i:s', time());
1325 $ip = acym_getIP();
1326 $query = 'UPDATE `#__acym_user`';
1327 $query .= ' SET `confirmed` = 1, `confirmation_date` = '.acym_escapeDB($confirmDate).', `confirmation_ip` = '.acym_escapeDB($ip);
1328 $query .= ' WHERE `id` = '.intval($userId).' LIMIT 1';
1329
1330 try {
1331 $res = acym_query($query);
1332 } catch (\Exception $e) {
1333 $res = false;
1334 }
1335 if ($res === false) {
1336 $errorMessage = isset($e) ? $e->getMessage() : acym_getDBError();
1337 // If there is an error we definitely want to warn the user about it.
1338 $msg = acym_translation('ACYM_CONTACT_ADMIN_ERROR').'<br />'.substr(acym_stripTags($errorMessage), 0, 200).'...';
1339 acym_display($msg, 'error');
1340 exit;
1341 }
1342
1343 acym_trigger('onAcymAfterUserConfirm', [&$user]);
1344 $this->sendNotification($userId, 'acy_notification_confirm');
1345
1346 $historyClass = new HistoryClass();
1347 $historyClass->insert($userId, 'confirmed');
1348
1349 if (empty($this->config->get('require_confirmation', 1))) return;
1350
1351 $listIDs = acym_loadResultArray('SELECT `list_id` FROM `#__acym_user_has_list` WHERE `status` = 1 AND `user_id` = '.intval($userId));
1352
1353 if (empty($listIDs)) return;
1354
1355 $listClass = new ListClass();
1356 $listClass->sendWelcome($userId, $listIDs);
1357 }
1358
1359 public function getOneByIdWithCustomFields(int $id): array
1360 {
1361 $user = $this->getOneById($id);
1362 if (empty($user)) {
1363 return [];
1364 }
1365
1366 $user = get_object_vars($user);
1367
1368 $fieldsValue = acym_loadObjectList(
1369 'SELECT user_field.`value`, field.`name`
1370 FROM #__acym_user_has_field AS `user_field`
1371 LEFT JOIN #__acym_field AS `field` ON user_field.`field_id` = field.`id`
1372 WHERE user_field.`user_id` = '.intval($id),
1373 'name'
1374 );
1375
1376 foreach ($fieldsValue as $key => $value) {
1377 $fieldsValue[$key] = $value->value;
1378 }
1379
1380 return array_merge($user, $fieldsValue);
1381 }
1382
1383 public function getCustomFieldValueById(int $id): array
1384 {
1385 $fieldsValue = acym_loadObjectList(
1386 'SELECT user_field.value AS value, field.name AS name, field.type AS type, field.value AS field_params, field.id AS id
1387 FROM #__acym_user_has_field as user_field
1388 LEFT JOIN #__acym_field as field ON user_field.field_id = field.id
1389 WHERE user_field.user_id = '.intval($id),
1390 'id'
1391 );
1392
1393 $fieldReturn = [];
1394
1395 foreach ($fieldsValue as $fieldId => $field) {
1396 if (in_array($field->type, ['checkbox', 'radio', 'single_dropdown', 'multiple_dropdown'])) {
1397 $field->field_params = json_decode($field->field_params, true);
1398 $field->value = explode(',', $field->value);
1399 $values = [];
1400 foreach ($field->value as $oneValue) {
1401 $key = array_search($oneValue, array_column($field->field_params, 'value'));
1402 $values[] = $field->field_params[$key]['title'];
1403 }
1404 $fieldReturn[$field->name] = implode(',', $values);
1405 } else {
1406 $fieldReturn[$field->name] = $field->value;
1407 }
1408 }
1409
1410 return $fieldReturn;
1411 }
1412
1413 public function getAllColumnsUserAndCustomField(bool $inAction = false, bool $withSubInfos = false): array
1414 {
1415 $return = [];
1416
1417 // Basic fields in the user table
1418 $userFields = acym_getColumns('user');
1419 foreach ($userFields as $value) {
1420 $return[$value] = $value;
1421 }
1422
1423 $languageFieldId = $this->config->get(FieldClass::LANGUAGE_FIELD_ID_KEY, 0);
1424
1425 // Acy custom fields except name, email and language because they already are in the user table
1426 $customFields = acym_loadObjectList(
1427 'SELECT * FROM #__acym_field WHERE id NOT IN (1, 2, '.intval($languageFieldId).') '.($inAction ? 'AND type != "phone"' : ''),
1428 'id'
1429 );
1430 if (!empty($customFields)) {
1431 foreach ($customFields as $key => $value) {
1432 $return[$key] = $value->name;
1433 }
1434 }
1435
1436 if ($withSubInfos) {
1437 $listCLass = new ListClass();
1438 $listFields = $listCLass->getAll('name');
1439 if (!empty($listFields)) {
1440 foreach ($listFields as $key => $value) {
1441 if ($value->type === ListClass::LIST_TYPE_FOLLOWUP) {
1442 continue;
1443 }
1444 $return['subscription_date_'.$value->id] = acym_translation('ACYM_SUBSCRIPTION_DATE').' '.$value->name;
1445 $return['unsubscription_date_'.$value->id] = acym_translation('ACYM_UNSUBSCRIPTION_DATE').' '.$value->name;
1446 }
1447 }
1448 }
1449
1450 return $return;
1451 }
1452
1453 public function getAllUserFields(object $user): object
1454 {
1455 if (empty($user->id)) {
1456 return $user;
1457 }
1458
1459 $query = 'SELECT field.*, field.value AS field_value, userfield.*
1460 FROM #__acym_field AS field
1461 LEFT JOIN #__acym_user_has_field AS userfield ON field.id = userfield.field_id AND userfield.user_id = '.intval($user->id).'
1462 WHERE field.id NOT IN(1, 2)';
1463
1464 $allFields = acym_loadObjectList($query);
1465
1466 foreach ($allFields as $oneField) {
1467 $fieldOptions = @json_decode(empty($oneField->option) ? '{}' : $oneField->option);
1468 if (in_array($oneField->type, ['multiple_dropdown', 'radio', 'checkbox', 'single_dropdown'])) {
1469 $oneField->field_value = json_decode($oneField->field_value, true);
1470 if (!empty($oneField->value)) {
1471 $oneField->value = explode(',', $oneField->value);
1472 $values = [];
1473 foreach ($oneField->field_value as $oneFieldValue) {
1474 foreach ($oneField->value as $oneValue) {
1475 if ($oneFieldValue['value'] == $oneValue) {
1476 $values[] = $oneFieldValue['title'];
1477 }
1478 }
1479 }
1480 $oneField->value = implode(',', $values);
1481 }
1482 } elseif ($oneField->type === 'file' && !empty($oneField->value)) {
1483 $tryJsonDecode = json_decode($oneField->value);
1484 if (json_last_error() === JSON_ERROR_NONE && is_array($tryJsonDecode)) {
1485 $oneField->value = !empty($tryJsonDecode) ? $tryJsonDecode[0] : null;
1486 }
1487 } elseif ($oneField->type === 'date') {
1488 if (empty($fieldOptions->format)) {
1489 acym_logError('Date field with ID '.$oneField->id.' without format option');
1490 continue;
1491 }
1492 $formatToDisplay = explode('%', str_replace('y', 'Y', $fieldOptions->format));
1493 unset($formatToDisplay[0]);
1494 if (!empty($oneField->value)) {
1495 $oneField->value = acym_date($oneField->value, implode('-', $formatToDisplay));
1496 }
1497 }
1498 $user->{$oneField->namekey} = empty($oneField->value) ? '' : $oneField->value;
1499 }
1500
1501 return $user;
1502 }
1503
1504 public function getAllSimpleData(): array
1505 {
1506 return acym_loadObjectList('SELECT email, name FROM #__acym_user');
1507 }
1508
1509 public function synchSaveCmsUser(array $user, bool $isnew, ?array $oldUser = null): void
1510 {
1511 // If the source is not already defined, we define it here
1512 $source = acym_getVar('string', 'acy_source', '');
1513 if (empty($source)) acym_setVar('acy_source', ACYM_CMS);
1514
1515 if (!$this->config->get('regacy', 0)) return;
1516
1517 $this->checkVisitor = false;
1518 $this->sendConf = false;
1519
1520 $regacyForceConf = $this->config->get('regacy_forceconf', 0);
1521 /* * * * * * * * * * * * * * * * * * *
1522 * Step 1: create / update the user *
1523 * * * * * * * * * * * * * * * * * * */
1524 $cmsUser = new \stdClass();
1525 $cmsUser->email = trim(acym_stripTags($user['email']));
1526 if (!acym_isValidEmail($cmsUser->email)) return;
1527 if (!empty($user['name'])) $cmsUser->name = trim(acym_stripTags($user['name']));
1528 if (!$regacyForceConf) $cmsUser->confirmed = 1;
1529 $cmsUser->active = 1 - intval($user['block']);
1530 $cmsUser->cms_id = $user['id'];
1531
1532 if (!$isnew && !empty($oldUser['email']) && $user['email'] != $oldUser['email']) {
1533 // The user changed its email address, load the current Acy user if any
1534 $acyUser = $this->getOneByEmail($oldUser['email']);
1535 if (!empty($acyUser)) $cmsUser->id = $acyUser->id;
1536 }
1537
1538 // Just in case of this is an existing user but the e-mail address has been modified by something else
1539 if (empty($cmsUser->id) && !empty($cmsUser->cms_id)) {
1540 $acyUser = $this->getOneByCMSId($cmsUser->cms_id);
1541 if (!empty($acyUser)) $cmsUser->id = $acyUser->id;
1542 }
1543
1544 $acyUser = $this->getOneByEmail($cmsUser->email);
1545 // If an Acy user with the same email address already exists
1546 if (!empty($acyUser)) {
1547 // And wasn't linked to the site account, link it
1548 if (empty($cmsUser->id)) {
1549 $cmsUser->id = $acyUser->id;
1550 } elseif ($cmsUser->id != $acyUser->id) {
1551 // And has a different id, delete it
1552 $this->delete([$acyUser->id]);
1553 }
1554 } else {
1555 $cmsUser->source = $source;
1556 }
1557
1558 $isnew = $isnew || empty($cmsUser->id);
1559
1560 $id = $this->save($cmsUser);
1561 if (empty($id)) {
1562 return;
1563 }
1564
1565 // Force trigger confirmation process on cms user confirmation (send welcome emails, automation, save history...)
1566 $confirmationRequired = $this->config->get('require_confirmation', 1);
1567 if (!$isnew && !$regacyForceConf && $user['block'] == 0 && !empty($oldUser['block']) && $confirmationRequired == 1) {
1568 $this->confirm($id);
1569 }
1570
1571 /* * * * * * * * * * * * * * * * * * * * * *
1572 * Step 2: Handle the user's subscription *
1573 * * * * * * * * * * * * * * * * * * * * * */
1574
1575 $currentSubscription = $this->getSubscriptionStatus($id);
1576
1577 // In the Acy configuration, we can tell AcyMailing to automatically subscribe newly created users to some lists
1578 $autoLists = $isnew ? $this->config->get('regacy_autolists', '') : '';
1579 $autoLists = explode(',', $autoLists);
1580 acym_arrayToInteger($autoLists);
1581
1582 $listsClass = new ListClass();
1583 $allLists = $listsClass->getAll();
1584
1585 // The user can select some lists on the registration form
1586 $visibleLists = acym_getVar('string', 'regacy_visible_lists', '');
1587 $visibleLists = explode(',', $visibleLists);
1588 acym_arrayToInteger($visibleLists);
1589
1590 $visibleListsChecked = acym_getVar('array', 'regacy_visible_lists_checked', []);
1591 acym_arrayToInteger($visibleListsChecked);
1592
1593
1594 // Handle the unsubscription
1595 if (!$isnew && !empty($visibleLists)) {
1596 $currentlySubscribedLists = [];
1597 foreach ($currentSubscription as $oneSubscription) {
1598 if ($oneSubscription->status == 1) $currentlySubscribedLists[] = $oneSubscription->list_id;
1599 }
1600 $unsubscribeLists = array_intersect($currentlySubscribedLists, array_diff($visibleLists, $visibleListsChecked));
1601 $this->unsubscribe([$id], $unsubscribeLists);
1602 }
1603
1604 // Handle the subscription
1605 $listsToSubscribe = [];
1606 foreach ($allLists as $oneList) {
1607 if (!$oneList->active) continue;
1608 if (!empty($currentSubscription[$oneList->id]) && $currentSubscription[$oneList->id]->status == 1) continue;
1609
1610 if (
1611 in_array($oneList->id, $visibleListsChecked)
1612 || (
1613 in_array($oneList->id, $autoLists)
1614 && !in_array($oneList->id, $visibleLists) && empty($currentSubscription[$oneList->id])
1615 )
1616 ) {
1617 $listsToSubscribe[] = $oneList->id;
1618 }
1619 }
1620
1621 if (!empty($listsToSubscribe)) $this->subscribe([$id], $listsToSubscribe);
1622
1623 if ($isnew) $this->sendNotification($id, 'acy_notification_create');
1624
1625 // We don't force the confirmation email, or the user is disabled, or he's already confirmed
1626 $acymailingUser = $this->getOneById($id);
1627 if (!empty($user['block']) || !empty($acymailingUser->confirmed)) return;
1628
1629 // New active user, or just activated the user, send the email
1630 if ($isnew || !empty($oldUser['block'])) {
1631 if ($confirmationRequired && $regacyForceConf) {
1632 $this->forceConf = true;
1633 $this->sendConfirmation($id);
1634 }
1635
1636 // Send welcome emails on CMS user confirmation (no Acym confirmation required)
1637 if (!$confirmationRequired && !empty($oldUser['email'])) {
1638 $listIDs = acym_loadResultArray('SELECT `list_id` FROM `#__acym_user_has_list` WHERE `status` = 1 AND `user_id` = '.intval($id));
1639 if (empty($listIDs)) return;
1640 $listsClass->sendWelcome($id, $listIDs);
1641 }
1642 }
1643 }
1644
1645 public function synchDeleteCmsUser(string $userEmail): void
1646 {
1647 $acyUser = $this->getOneByEmail($userEmail);
1648
1649 if (empty($acyUser)) return;
1650
1651 if ($this->config->get('regacy', '0') == 1 && $this->config->get('regacy_delete', '0') == 1) {
1652 $this->delete([$acyUser->id]);
1653 } else {
1654 acym_query('UPDATE #__acym_user SET `cms_id` = 0 WHERE `id` = '.intval($acyUser->id));
1655 }
1656 }
1657
1658 /**
1659 * Search users via partial email address
1660 */
1661 public function getUsersLikeEmail(string $search): array
1662 {
1663 $query = 'SELECT id, email FROM #__acym_user WHERE email LIKE '.acym_escapeDB('%'.$search.'%');
1664
1665 return acym_loadObjectList($query);
1666 }
1667
1668 public function sendNotification(int $userId, string $notification, array $params = []): void
1669 {
1670 if (empty($userId) || $this->blockNotifications) return;
1671
1672 $notifyUsers = explode(',', $this->config->get($notification));
1673 if (acym_isAdmin() || empty($notifyUsers)) return;
1674
1675 $mailerHelper = new MailerHelper();
1676 $mailerHelper->report = false;
1677 $mailerHelper->autoAddUser = true;
1678
1679 $user = $this->getOneById($userId);
1680 $userField = empty($user) ? null : $this->getAllUserFields($user);
1681 if (!empty($userField)) {
1682 foreach ($userField as $map => $value) {
1683 $mailerHelper->addParam('user:'.$map, $value);
1684 }
1685 }
1686
1687 // Load the subscription
1688 $rawSubscription = $this->getUserSubscriptionById($userId);
1689 $subscription = [''];
1690 foreach ($rawSubscription as $listId => $listData) {
1691 $currentList = $listData->name.' => ';
1692 if (intval($listData->status) === 1) {
1693 $currentList .= acym_translation('ACYM_SUBSCRIBED').' - '.$listData->subscription_date;
1694 } else {
1695 $currentList .= acym_translation('ACYM_UNSUBSCRIBED').' - '.$listData->unsubscribe_date;
1696 }
1697 $subscription[] = $currentList;
1698 }
1699 $mailerHelper->addParam('user:subscription', implode('<br/>', $subscription));
1700
1701 if (!empty($params)) {
1702 foreach ($params as $name => $value) {
1703 $mailerHelper->addParam($name, $value);
1704 }
1705 }
1706
1707 $mailClass = new MailClass();
1708 foreach ($notifyUsers as $oneUser) {
1709 if (!acym_isValidEmail($oneUser)) {
1710 continue;
1711 }
1712
1713 $admin = $this->getOneByEmail($oneUser, true);
1714
1715 $notificationEmail = $mailClass->getOneByName($notification);
1716 if (!empty($admin) && !empty($notificationEmail)) {
1717 try {
1718 $mailerHelper->sendOne($notificationEmail->id, $admin->id);
1719 } catch (\Exception $e) {
1720 acym_logError($e->getMessage());
1721 }
1722 }
1723 }
1724 }
1725
1726 public function getMailHistory(int $userId)
1727 {
1728 $mailHistory = acym_loadObjectList(
1729 'SELECT user_stat.*, mail.id, mail.subject, SUM(url_click.click) as click
1730 FROM #__acym_user_stat AS user_stat
1731 JOIN #__acym_mail AS mail ON mail.id = user_stat.mail_id
1732 LEFT JOIN #__acym_url_click AS url_click ON user_stat.mail_id = url_click.mail_id AND url_click.user_id = '.intval($userId).'
1733 WHERE user_stat.user_id = '.intval($userId).'
1734 GROUP BY mail_id
1735 ORDER BY send_date DESC
1736 LIMIT 50',
1737 'mail_id'
1738 );
1739 $mailClass = new MailClass();
1740
1741 return $mailClass->decode($mailHistory);
1742 }
1743
1744 public function deleteHistoryPeriod(): array
1745 {
1746 if (empty($this->config->get('delete_user_history_enabled', 0))) return [];
1747 $deleteOverSecond = $this->config->get('delete_user_history', 0);
1748 if (empty($deleteOverSecond)) return [];
1749 $date = time() - $deleteOverSecond;
1750
1751 $query = 'DELETE FROM #__acym_history WHERE date < '.intval($date);
1752
1753 try {
1754 $status = acym_query($query);
1755 $message = empty($status) ? '' : acym_translationSprintf('ACYM_DELETE_X_ROWS_TABLE_X', $status, strtolower(acym_translation('ACYM_USER_HISTORY')));
1756 } catch (\Exception $e) {
1757 $status = false;
1758 $message = $e->getMessage();
1759 }
1760
1761 return ['status' => $status !== false, 'message' => $message];
1762 }
1763
1764 public function resetSubscription(array $userIds, array $lists): bool
1765 {
1766 if (empty($lists)) return false;
1767
1768 acym_arrayToInteger($userIds);
1769 acym_arrayToInteger($lists);
1770
1771 foreach ($userIds as $userId) {
1772 acym_query(
1773 'DELETE FROM `#__acym_user_has_list`
1774 WHERE user_id = '.$userId.'
1775 AND list_id IN ('.implode(',', $lists).')'
1776 );
1777
1778 acym_query(
1779 'DELETE FROM #__acym_queue WHERE user_id = '.$userId.' AND mail_id IN (
1780 SELECT followup_mail.mail_id FROM #__acym_followup_has_mail AS followup_mail
1781 JOIN #__acym_followup AS followup ON followup.id = followup_mail.followup_id AND followup.list_id IN ('.implode(',', $lists).')
1782 )'
1783 );
1784
1785 $historyClass = new HistoryClass();
1786 $historyData = acym_translationSprintf('ACYM_LISTS_NUMBERS', implode(', ', $lists));
1787 $historyClass->insert($userId, 'reset_subscription', [$historyData]);
1788 }
1789
1790 return true;
1791 }
1792
1793 public function addMissingKeys(): int
1794 {
1795 $usersMissingKey = acym_loadResultArray('SELECT `id` FROM #__acym_user WHERE `key` IS NULL OR `key` = ""');
1796 foreach ($usersMissingKey as $oneUserId) {
1797 acym_query('UPDATE #__acym_user SET `key` = '.acym_escapeDB(acym_generateKey(14)).' WHERE `id` = '.intval($oneUserId));
1798 }
1799
1800 return count($usersMissingKey);
1801 }
1802
1803 public function hasUserAccess(int $subscriberId): bool
1804 {
1805 $userId = acym_currentUserId();
1806 if (empty($userId)) {
1807 return false;
1808 }
1809
1810 if (acym_isAdmin() && acym_isAllowed('users')) {
1811 return true;
1812 }
1813
1814 $user = $this->getOneById($subscriberId);
1815 $userEmail = acym_currentUserEmail();
1816 if (!empty($user) && $user->email === $userEmail) {
1817 return true;
1818 }
1819
1820 $listClass = new ListClass();
1821 $allowedListIds = $listClass->getManageableLists();
1822 if (empty($allowedListIds)) {
1823 return false;
1824 }
1825
1826 $query = 'SELECT COUNT(*) FROM #__acym_user AS user
1827 JOIN #__acym_user_has_list AS map ON map.user_id = user.id
1828 WHERE map.list_id IN ('.implode(',', $allowedListIds).')
1829 AND user.id = '.intval($subscriberId);
1830
1831 return acym_loadResult($query) > 0;
1832 }
1833
1834 public function getXUsers(array $options = []): array
1835 {
1836 $limit = $options['limit'] ?? 10;
1837 $offset = $options['offset'] ?? 0;
1838 $filters = $options['filters'] ?? [];
1839
1840 $conditions = [];
1841 foreach ($filters as $column => $filter) {
1842 if (in_array($column, ['id', 'cms_id', 'active', 'confirmed', 'tracking'])) {
1843 $conditions[] = acym_secureDBColumn($column).' = '.intval($filter);
1844 } else {
1845 $conditions[] = acym_secureDBColumn($column).' LIKE '.acym_escapeDB('%'.$filter.'%');
1846 }
1847 }
1848
1849 if (!empty($options['created_after'])) {
1850 $conditions[] = 'creation_date > '.acym_escapeDB($options['created_after']);
1851 }
1852
1853 if (!empty($options['confirmed_after'])) {
1854 $conditions[] = 'confirmation_date > '.acym_escapeDB($options['confirmed_after']);
1855 }
1856
1857 $query = 'SELECT * FROM #__acym_user';
1858 if (!empty($conditions)) {
1859 $query .= ' WHERE '.implode(' AND ', $conditions);
1860 }
1861
1862 return acym_loadObjectList($query, $this->pkey, $offset, $limit);
1863 }
1864
1865 public function disableTracking(object $user, int $mailId): void
1866 {
1867 acym_query('UPDATE #__acym_user SET `tracking` = 0 WHERE `id` = '.intval($user->id));
1868
1869 $historyClass = new HistoryClass();
1870 $historyClass->insert($user->id, 'tracking_disabled', [], $mailId);
1871 }
1872 }
1873