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

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