PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.11.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.11.1
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 10.11.1, at back/Classes/UserClass.php

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