PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.5
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.5
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 / ListClass.php

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

1,067 lines 36.3 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\MailerHelper;
6 use AcyMailing\Helpers\PaginationHelper;
7 use AcyMailing\Core\AcymClass;
8
9 class ListClass extends AcymClass
10 {
11 const LIST_TYPE_STANDARD = 'standard';
12 const LIST_TYPE_FRONT = 'front';
13 const LIST_TYPE_FOLLOWUP = 'followup';
14 const COLOR_PARTS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
15
16 public function __construct()
17 {
18 parent::__construct();
19
20 $this->table = 'list';
21 $this->pkey = 'id';
22 }
23
24 public function getMatchingElements(array $settings = []): array
25 {
26 $columns = 'list.*';
27 if (!empty($settings['columns'])) {
28 foreach ($settings['columns'] as $key => $value) {
29 if ($value == 'lists.list_id') {
30 unset($settings['columns'][$key]);
31 continue;
32 }
33 $settings['columns'][$key] = $key === 'join' ? acym_secureDBColumn($value) : 'list.'.acym_secureDBColumn($value);
34 }
35 $columns = implode(', ', $settings['columns']);
36 }
37
38 $query = 'SELECT '.$columns.' FROM #__acym_list AS list';
39 $queryCount = 'SELECT COUNT(list.id) AS total FROM #__acym_list AS list';
40 if (!empty($settings['join'])) $query .= $this->getJoinForQuery($settings['join']);
41 /*
42 This query will return for example:
43
44 array(
45 6 => 0,
46 2 => 1,
47 5 => 2,
48 12 => 3
49 )
50
51 It would mean that:
52
53 6 lists are disabled and invisible 0 + (0*2) = 0
54 2 lists are active and invisible 1 + (0*2) = 1
55 5 lists are inactive and visible 0 + (1*2) = 2
56 12 lists are active and visible 1 + (1*2) = 3
57
58 So there are 2 + 12 active lists and 5 + 12 visible lists, get it?
59 */
60 $queryStatus = 'SELECT COUNT(id) AS number, active + (visible*2) AS score FROM #__acym_list AS list';
61 $filters = [];
62 $listsId = [];
63
64 if (!empty($settings['tag'])) {
65 $tagJoin = ' JOIN #__acym_tag AS tag ON list.id = tag.id_element';
66 $query .= $tagJoin;
67 $queryCount .= $tagJoin;
68 $queryStatus .= $tagJoin;
69 $filters[] = 'tag.name = '.acym_escapeDB($settings['tag']);
70 $filters[] = 'tag.type = "list"';
71 }
72
73 if (!empty($settings['search'])) {
74 $filters[] = 'list.name LIKE '.acym_escapeDB('%'.$settings['search'].'%');
75 }
76
77 if (!empty($settings['creator_id']) && !acym_isAdmin()) {
78 $userGroups = acym_getGroupsByUser($settings['creator_id']);
79 $groupCondition = '(list.access LIKE "%,'.implode(',%" OR list.access LIKE "%,', $userGroups).',%")';
80
81 $filters[] = 'list.cms_user_id = '.intval($settings['creator_id']).' OR '.$groupCondition.'';
82 }
83
84 $filters[] = 'list.type = '.acym_escapeDB(self::LIST_TYPE_STANDARD);
85
86 if (!empty($filters)) {
87 $queryStatus .= ' WHERE ('.implode(') AND (', $filters).')';
88 }
89
90 if (!empty($settings['status'])) {
91 $allowedStatus = [
92 'active' => 'active = 1',
93 'inactive' => 'active = 0',
94 'visible' => 'visible = 1',
95 'invisible' => 'visible = 0',
96 ];
97 if (empty($allowedStatus[$settings['status']])) {
98 die('Injection denied');
99 }
100 $filters[] = 'list.'.$allowedStatus[$settings['status']];
101 }
102
103 if (!empty($settings['where'])) {
104 $filters[] = $settings['where'];
105 }
106
107
108 if (!empty($filters)) {
109 $query .= ' WHERE ('.implode(') AND (', $filters).')';
110 $queryCount .= ' WHERE ('.implode(') AND (', $filters).')';
111 }
112
113 if (!empty($settings['ordering']) && !empty($settings['ordering_sort_order'])) {
114 $query .= ' ORDER BY list.'.acym_secureDBColumn($settings['ordering']).' '.acym_secureDBColumn(strtoupper($settings['ordering_sort_order']));
115 }
116
117 if (empty($settings['offset']) || $settings['offset'] < 0) {
118 $settings['offset'] = 0;
119 }
120
121 if (empty($settings['elementsPerPage']) || $settings['elementsPerPage'] < 1) {
122 $pagination = new PaginationHelper();
123 $settings['elementsPerPage'] = $pagination->getListLimit();
124 }
125
126 $results['elements'] = acym_loadObjectList($query, '', $settings['offset'], $settings['elementsPerPage']);
127 if (!empty($settings['join'])) {
128 $this->setSelectedList($results['elements'], $settings['join']);
129 }
130 foreach ($results['elements'] as $i => $oneList) {
131 $listsId[] = $oneList->id;
132 $results['elements'][$i]->sendable_users = 0;
133 $results['elements'][$i]->unconfirmed_users = 0;
134 $results['elements'][$i]->unsubscribed_users = 0;
135 $results['elements'][$i]->inactive_users = 0;
136 $results['elements'][$i]->newSub = 0;
137 $results['elements'][$i]->newUnsub = 0;
138 }
139
140 $countUserByList = [];
141 $countEvolByList = [];
142 if (!empty($listsId) && empty($settings['entitySelect'])) {
143 $countUserByList = $this->getSubscribersCountPerStatusByListIds($listsId);
144 $countEvolByList = $this->getSubscribersEvolutionByListIds($listsId);
145 }
146
147 foreach ($results['elements'] as $i => $list) {
148 $results['elements'][$i]->tags = [];
149 foreach ($countUserByList as $userList) {
150 if ($list->id == $userList->list_id) {
151 $results['elements'][$i]->sendable_users = $userList->sendable_users;
152 $results['elements'][$i]->unconfirmed_users = $userList->unconfirmed_users;
153 $results['elements'][$i]->unsubscribed_users = $userList->unsubscribed_users;
154 $results['elements'][$i]->inactive_users = $userList->inactive_users;
155 }
156 }
157 if (isset($countEvolByList[$list->id]->newSub)) $results['elements'][$i]->newSub = $countEvolByList[$list->id]->newSub;
158 if (isset($countEvolByList[$list->id]->newUnsub)) $results['elements'][$i]->newUnsub = $countEvolByList[$list->id]->newUnsub;
159 }
160
161 $results['total'] = acym_loadObject($queryCount);
162
163 $listsPerStatus = acym_loadObjectList($queryStatus.' GROUP BY score', 'score');
164 for ($i = 0; $i < 4; $i++) {
165 $listsPerStatus[$i] = empty($listsPerStatus[$i]) ? 0 : $listsPerStatus[$i]->number;
166 }
167
168 $results['status'] = [
169 'all' => array_sum($listsPerStatus),
170 'active' => $listsPerStatus[1] + $listsPerStatus[3],
171 'inactive' => $listsPerStatus[0] + $listsPerStatus[2],
172 'visible' => $listsPerStatus[2] + $listsPerStatus[3],
173 'invisible' => $listsPerStatus[0] + $listsPerStatus[1],
174 ];
175
176 return $results;
177 }
178
179 public function getOneById(int $id): ?object
180 {
181 $list = parent::getOneById($id);
182
183 if (!empty($list)) {
184 $list->translation = empty($list->translation) ? [] : json_decode($list->translation, true);
185 }
186
187 return $list;
188 }
189
190 private function setSelectedList(array $elements, string $join): void
191 {
192 if (strpos($join, 'join_list') === false) {
193 return;
194 }
195
196 $listsIds = explode('-', $join);
197 $listsIds = $listsIds[1];
198 $listsIds = explode(',', $listsIds);
199 foreach ($elements as $element) {
200 $element->list_id = in_array($element->id, $listsIds) ? $element->id : null;
201 }
202 }
203
204 private function getJoinForQuery(string $joinType): string
205 {
206 if (strpos($joinType, 'join_mail') !== false) {
207 $mailId = explode('-', $joinType);
208
209 return ' LEFT JOIN #__acym_mail_has_list as maillist ON list.id = maillist.list_id AND maillist.mail_id = '.intval($mailId[1]);
210 }
211 if (strpos($joinType, 'join_user') !== false) {
212 $userId = explode('-', $joinType);
213
214 return ' LEFT JOIN #__acym_user_has_list as userlist ON list.id = userlist.list_id AND userlist.status = 1 AND userlist.user_id = '.intval($userId[1]);
215 }
216
217 return '';
218 }
219
220 public function getListsWithIdNameCount(array $settings): array
221 {
222 $filters = [];
223
224 if (isset($settings['ids'])) {
225 if (empty($settings['ids'])) {
226 return ['lists' => [], 'total' => 0];
227 } else {
228 acym_arrayToInteger($settings['ids']);
229 $filters[] = 'list.id IN ('.implode(',', $settings['ids']).')';
230 }
231 }
232
233 $query = 'SELECT list.id, list.name, list.color, list.active, COUNT(user.id) AS subscribers
234 FROM #__acym_list AS list
235 LEFT JOIN #__acym_user_has_list AS userList
236 ON list.id = userList.list_id
237 AND userList.status = 1
238 LEFT JOIN #__acym_user AS user
239 ON user.id = userList.user_id
240 AND user.active = 1 ';
241
242 if (intval($this->config->get('require_confirmation', 1)) === 1) {
243 $query .= ' AND user.confirmed = 1 ';
244 }
245
246 $queryCount = 'SELECT COUNT(list.id) FROM #__acym_list AS list';
247
248 if (!acym_isAdmin()) {
249 $currentUserId = acym_currentUserId();
250 if (empty($currentUserId)) {
251 return [
252 'lists' => [],
253 'total' => 0,
254 ];
255 }
256
257 $userGroups = acym_getGroupsByUser($currentUserId);
258 $groupCondition = '(list.access LIKE "%,'.implode(',%" OR list.access LIKE "%,', $userGroups).',%")';
259
260 $filters[] = 'list.cms_user_id = '.intval($currentUserId).' OR '.$groupCondition;
261 }
262
263 if (!empty($settings['search'])) {
264 $filters[] = 'list.name LIKE '.acym_escapeDB('%'.$settings['search'].'%');
265 }
266
267 if (!empty($settings['already'])) {
268 acym_arrayToInteger($settings['already']);
269 $filters[] = 'list.id NOT IN('.implode(',', $settings['already']).')';
270 }
271 $filters[] = 'list.type = '.acym_escapeDB(self::LIST_TYPE_STANDARD);
272
273 if (!empty($filters)) {
274 $query .= ' WHERE ('.implode(') AND (', $filters).')';
275 $queryCount .= ' WHERE ('.implode(') AND (', $filters).')';
276 }
277
278 $query .= ' GROUP BY list.id ';
279
280 return [
281 'lists' => acym_loadObjectList($query, '', $settings['offset'], $settings['listsPerPage']),
282 'total' => (int)acym_loadResult($queryCount),
283 ];
284 }
285
286 public function getListsByIds(array $ids): array
287 {
288 acym_arrayToInteger($ids);
289 if (empty($ids)) {
290 return [];
291 }
292
293 $query = 'SELECT * FROM #__acym_list WHERE id IN ('.implode(', ', $ids).')';
294
295 return acym_loadObjectList($query);
296 }
297
298 public function getSubscribersCount(array $listsIds): int
299 {
300 acym_arrayToInteger($listsIds);
301 if (empty($listsIds)) {
302 return 0;
303 }
304
305 $query = 'SELECT COUNT(DISTINCT user.id)
306 FROM #__acym_user AS user
307 JOIN #__acym_user_has_list AS userList ON user.id = userList.user_id
308 WHERE userList.list_id IN ('.implode(",", $listsIds).') AND userList.status = 1 AND user.active = 1';
309
310 if ($this->config->get('require_confirmation', 1) == 1) {
311 $query .= ' AND user.confirmed = 1';
312 }
313
314 return (int)acym_loadResult($query);
315 }
316
317 public function getSubscribersForList(array $options = []): array
318 {
319 $listIds = $options['listIds'] ?? [];
320 $offset = $options['offset'] ?? 0;
321 $limit = $options['limit'] ?? 100;
322 $status = $options['status'] ?? null;
323 $orderBy = $options['orderBy'] ?? '';
324 $orderBySort = strtolower((string)($options['orderBySort'] ?? ''));
325 if (!in_array($orderBySort, ['asc', 'desc'], true)) {
326 $orderBySort = empty($orderBy) ? 'asc' : 'desc';
327 }
328 $subscribedAfter = $options['subscribed_after'] ?? null;
329 $unsubscribedAfter = $options['unsubscribed_after'] ?? null;
330
331 if (empty($listIds)) {
332 return [];
333 }
334
335 acym_arrayToInteger($listIds);
336
337 $statusCondition = '';
338 if (!is_null($status)) {
339 $statusCondition .= ' AND user_list.status = '.intval($status);
340 }
341
342 if (!empty($subscribedAfter)) {
343 $statusCondition .= ' AND user_list.subscription_date > '.acym_escapeDB($subscribedAfter);
344 }
345
346 if (!empty($unsubscribedAfter)) {
347 $statusCondition .= ' AND user_list.unsubscribe_date > '.acym_escapeDB($unsubscribedAfter);
348 }
349
350 $requestSub = 'SELECT user.*, user_list.status, user_list.subscription_date, user_list.unsubscribe_date FROM #__acym_user AS user';
351 $requestSub .= ' LEFT JOIN #__acym_user_has_list AS user_list ON user.id = user_list.user_id';
352 $requestSub .= ' WHERE user.active = 1 AND user_list.list_id IN ('.implode(', ', $listIds).')'.$statusCondition;
353
354 $orderByColumn = empty($orderBy) ? 'user.id' : acym_secureDBColumn($orderBy);
355 $requestSub .= ' ORDER BY '.$orderByColumn.' '.$orderBySort;
356
357 // Any other column can hold duplicates, we need a unique one to break the ties
358 if (!in_array($orderByColumn, ['id', 'user.id'], true)) {
359 $requestSub .= ', user.id ASC';
360 }
361
362 return acym_loadObjectList(
363 $requestSub,
364 '',
365 $offset,
366 $limit
367 );
368 }
369
370 public function delete(array $elements, bool $force = false): int
371 {
372 if (!$force) {
373 $this->onlyManageableLists($elements);
374 }
375
376 if (empty($elements)) {
377 return 0;
378 }
379
380 foreach ($elements as $id) {
381 acym_query('DELETE FROM #__acym_mail_has_list WHERE list_id = '.intval($id));
382 acym_query('DELETE FROM #__acym_user_has_list WHERE list_id = '.intval($id));
383 acym_query('DELETE FROM #__acym_tag WHERE `id_element` = '.intval($id).' AND `type` = "list"');
384 }
385
386 return parent::delete($elements);
387 }
388
389 public function synchDeleteCmsList(int $userId): void
390 {
391 $query = 'SELECT * FROM #__acym_list WHERE type = "'.self::LIST_TYPE_FRONT.'" AND cms_user_id = '.intval($userId);
392 $listFrontManagement = acym_loadObject($query);
393 if (!empty($listFrontManagement)) {
394 $this->delete([$listFrontManagement->id]);
395 }
396 }
397
398 public function save(object $list): ?int
399 {
400 if (isset($list->tags)) {
401 $tags = $list->tags;
402 unset($list->tags);
403 }
404
405 $oldList = null;
406
407 if (empty($list->id)) {
408 if (empty($list->cms_user_id)) {
409 $list->cms_user_id = acym_currentUserId();
410 }
411
412 $list->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
413 } else {
414 $oldList = $this->getOneById($list->id);
415 }
416
417 if (!empty($list->translation)) {
418 if (is_array($list->translation)) $list->translation = json_encode($list->translation);
419 } else {
420 $list->translation = '';
421 }
422
423 foreach ($list as $oneAttribute => $value) {
424 if (empty($value)) continue;
425
426 $list->$oneAttribute = acym_stripTags($value);
427 }
428
429 if (empty($list->description)) {
430 $list->description = '';
431 }
432
433 if (!isset($list->access) && empty($list->id)) {
434 $list->access = '';
435 }
436
437 if (empty($list->id)) {
438 acym_trigger('onAcymBeforeListCreate', [&$list]);
439 } else {
440 acym_trigger('onAcymBeforeListModify', [&$list]);
441 }
442
443 $listID = parent::save($list);
444
445 if (empty($listID)) {
446 return null;
447 }
448
449 if (isset($tags)) {
450 $tagClass = new TagClass();
451 $tagClass->setTags(TagClass::TYPE_LIST, intval($listID), $tags);
452 }
453
454 $list->id = $listID;
455
456 if (is_null($oldList)) {
457 acym_trigger('onAcymAfterListCreate', [&$list]);
458 } else {
459 acym_trigger('onAcymAfterListModify', [&$list, &$oldList]);
460 }
461
462 return $listID;
463 }
464
465 public function getAllWithIdName(): array
466 {
467 $endQuery = '';
468 if (!acym_isAdmin()) {
469 $creatorId = acym_currentUserId();
470
471 $userGroups = acym_getGroupsByUser($creatorId);
472 $groupCondition = '(access LIKE "%,'.implode(',%" OR access LIKE "%,', $userGroups).',%")';
473
474 $endQuery = ' AND (cms_user_id = '.intval($creatorId).' OR '.$groupCondition.')';
475 }
476 $lists = acym_loadObjectList('SELECT id, name FROM #__acym_list WHERE type = '.acym_escapeDB(self::LIST_TYPE_STANDARD).$endQuery, 'id');
477
478 $listsToReturn = [];
479
480 foreach ($lists as $key => $list) {
481 $listsToReturn[$key] = $list->name;
482 }
483
484 return $listsToReturn;
485 }
486
487 public function getAllForSelect(bool $emptyFirst = true, int $userFrontID = 0, bool $needTranslation = false, bool $needFrontLabel = false): array
488 {
489 $groupCondition = '';
490 if (!empty($userFrontID)) {
491 $userGroups = acym_getGroupsByUser($userFrontID);
492 $groupCondition = ' AND (cms_user_id = '.intval($userFrontID).' OR (access LIKE "%,'.implode(',%" OR access LIKE "%,', $userGroups).',%"))';
493 }
494
495 $lists = acym_loadObjectList('SELECT * FROM #__acym_list WHERE type = '.acym_escapeDB(self::LIST_TYPE_STANDARD).$groupCondition, 'id');
496
497 if (acym_isMultilingual() && $needTranslation) {
498 $lists = $this->getTranslatedNameDescription($lists);
499 }
500
501 $return = [];
502
503 if ($emptyFirst) {
504 $return[''] = acym_translation('ACYM_SELECT_A_LIST');
505 }
506
507 foreach ($lists as $key => $list) {
508 $return[$key] = $needFrontLabel && !empty($list->display_name) ? $list->display_name : $list->name;
509 }
510
511 return $return;
512 }
513
514 public function getAllWithoutManagement(bool $needTranslation = false): array
515 {
516 $lists = acym_loadObjectList('SELECT * FROM #__acym_list WHERE `type` = '.acym_escapeDB(self::LIST_TYPE_STANDARD), 'id');
517
518 if (acym_isMultilingual() && $needTranslation) {
519 $lists = $this->getTranslatedNameDescription($lists);
520 }
521
522 return $lists;
523 }
524
525 public function getTranslatedNameDescription(array $lists): array
526 {
527 $currentLanguageTag = acym_getLanguageTag();
528 foreach ($lists as $id => $list) {
529 if (empty($list->translation)) continue;
530
531 $list->translation = json_decode($list->translation, true);
532 if (!empty($list->translation[$currentLanguageTag])) {
533 if (!empty($list->translation[$currentLanguageTag]['name'])) {
534 $list->name = $list->translation[$currentLanguageTag]['name'];
535 }
536
537 if (!empty($list->translation[$currentLanguageTag]['display_name'])) {
538 $list->display_name = $list->translation[$currentLanguageTag]['display_name'];
539 }
540
541 if (!empty($list->translation[$currentLanguageTag]['description'])) {
542 $list->description = $list->translation[$currentLanguageTag]['description'];
543 }
544 }
545 }
546
547 return $lists;
548 }
549
550 /**
551 * Sends the welcome emails attached to the specified lists
552 */
553 public function sendWelcome(int $userID, array $listIDs, bool $forceFront = false): void
554 {
555 if (!$forceFront && acym_isAdmin()) {
556 return;
557 }
558
559 acym_arrayToInteger($listIDs);
560 if (empty($listIDs)) {
561 return;
562 }
563
564 $messages = acym_loadObjectList('SELECT `welcome_id` FROM #__acym_list WHERE `id` IN ('.implode(',', $listIDs).') AND `active` = 1');
565
566 if (empty($messages)) {
567 return;
568 }
569
570 $alreadySent = [];
571 $mailerHelper = new MailerHelper();
572 $mailerHelper->report = (bool)$this->config->get('welcome_message', 1);
573 foreach ($messages as $oneMessage) {
574 $mailid = $oneMessage->welcome_id;
575 if (empty($mailid)) continue;
576
577 //We don't send twice the same message
578 if (isset($alreadySent[$mailid])) {
579 continue;
580 }
581
582 $mailerHelper->trackEmail = true;
583 $mailerHelper->sendOne($mailid, $userID);
584 $alreadySent[$mailid] = true;
585 }
586 }
587
588 /**
589 * Sends the unsubscribe emails attached to the specified lists
590 */
591 public function sendUnsubscribe(int $userID, array $listIDs): void
592 {
593 if (acym_isAdmin()) {
594 return;
595 }
596
597 acym_arrayToInteger($listIDs);
598 if (empty($listIDs)) {
599 return;
600 }
601
602 $messages = acym_loadObjectList('SELECT `unsubscribe_id` FROM #__acym_list WHERE `id` IN ('.implode(',', $listIDs).') AND `active` = 1');
603
604 if (empty($messages)) {
605 return;
606 }
607
608 $alreadySent = [];
609 $mailerHelper = new MailerHelper();
610 $mailerHelper->report = (bool)$this->config->get('unsub_message', 1);
611 foreach ($messages as $oneMessage) {
612 if (!empty($oneMessage->unsubscribe_id)) {
613 $mailid = $oneMessage->unsubscribe_id;
614
615 //We don't send twice the same message
616 if (isset($alreadySent[$mailid])) {
617 continue;
618 }
619
620 $mailerHelper->trackEmail = true;
621 $mailerHelper->sendOne($mailid, $userID);
622 $alreadySent[$mailid] = true;
623 }
624 }
625 }
626
627 public function addDefaultList()
628 {
629 $listId = acym_loadResult('SELECT `id` FROM #__acym_list LIMIT 1');
630 if (empty($listId)) {
631 $defaultList = new \stdClass();
632 $defaultList->name = 'Newsletters';
633 $defaultList->color = '#3366ff';
634 $defaultList->description = '';
635
636 $this->save($defaultList);
637 }
638 }
639
640 public function getTotalSubCount(array $ids): int
641 {
642 if (empty($ids)) {
643 return 0;
644 }
645
646 acym_arrayToInteger($ids);
647 $this->onlyManageableLists($ids);
648 $query = 'SELECT COUNT(DISTINCT hasList.user_id)
649 FROM #__acym_user_has_list AS hasList
650 JOIN #__acym_user AS user
651 ON hasList.user_id = user.id
652 WHERE hasList.status = 1
653 AND user.active = 1
654 AND hasList.list_id IN ('.implode(',', $ids).')';
655
656
657 if ($this->config->get('require_confirmation', 1) == 1) {
658 $query .= ' AND user.confirmed = 1 ';
659 }
660
661 return intval(acym_loadResult($query));
662 }
663
664 /**
665 * Get all mails attached to a list
666 */
667 public function getMailsByListId(int $listId): array
668 {
669 $mailIds = acym_loadResultArray('SELECT mail_id FROM #__acym_mail_has_list WHERE list_id = '.intval($listId));
670
671 return empty($mailIds) ? [] : $mailIds;
672 }
673
674 public function getSubscribersCountPerStatusByListIds(array $listIds = []): array
675 {
676 $condList = '';
677 if (!empty($listIds)) {
678 acym_arrayToInteger($listIds);
679 $condList = 'AND userList.list_id IN ('.implode(',', $listIds).')';
680 }
681
682 /*
683 * score:
684 * An unconfirmed and active user will have a score of 0 + 1*2 = 2
685 * A confirmed and active user will have a score of 1 + 1*2 = 3
686 * A confirmed and inactive user will have a score of 1 + 0*2 = 1
687 */
688 $query = 'SELECT userList.list_id, COUNT(userList.user_id) AS users, acyuser.confirmed + acyuser.active*2 AS score
689 FROM #__acym_user_has_list AS userList
690 JOIN #__acym_user AS acyuser
691 ON acyuser.id = userList.user_id
692 WHERE userList.status = 1
693 '.$condList.'
694 GROUP BY score, userList.list_id';
695 $results = acym_loadObjectList($query);
696
697 $confirmationRequired = $this->config->get('require_confirmation', 1);
698 $listsUserStats = [];
699 foreach ($results as $oneResult) {
700 if (!isset($listsUserStats[$oneResult->list_id])) {
701 $listsUserStats[$oneResult->list_id] = $this->initList($oneResult->list_id);
702 }
703
704 // Joomla 4 casts the result into an int
705 $oneResult->score = (string)$oneResult->score;
706
707 if (in_array($oneResult->score, ['0', '1'])) {
708 $listsUserStats[$oneResult->list_id]->inactive_users += $oneResult->users;
709 }
710
711 if (in_array($oneResult->score, ['0', '2'])) {
712 $listsUserStats[$oneResult->list_id]->unconfirmed_users += $oneResult->users;
713 }
714
715 if ($oneResult->score === '3' || ($confirmationRequired != 1 && $oneResult->score == '2')) {
716 $listsUserStats[$oneResult->list_id]->sendable_users += $oneResult->users;
717 }
718 }
719
720 $query = 'SELECT userList.list_id, COUNT(userList.user_id) AS users
721 FROM #__acym_user_has_list AS userList
722 WHERE userList.status = 0
723 '.$condList.'
724 GROUP BY userList.list_id';
725 $unsubscribed = acym_loadObjectList($query);
726
727 foreach ($unsubscribed as $oneList) {
728 if (!isset($listsUserStats[$oneList->list_id])) {
729 $listsUserStats[$oneList->list_id] = $this->initList($oneList->list_id);
730 }
731 $listsUserStats[$oneList->list_id]->unsubscribed_users = $oneList->users;
732 }
733
734 return $listsUserStats;
735 }
736
737 public function getSubscribersEvolutionByListIds(array $listIds): array
738 {
739 $condList = '';
740 if (!empty($listIds)) {
741 acym_arrayToInteger($listIds);
742 $condList = ' AND list_id IN ('.implode(',', $listIds).')';
743 }
744 $dateNow = acym_loadResult('SELECT DATE_SUB(NOW(), INTERVAL 1 MONTH)');
745 $queryEvolSub = 'SELECT list_id, COUNT(*) AS newSub FROM #__acym_user_has_list';
746 $queryEvolSub .= ' WHERE subscription_date > '.acym_escapeDB($dateNow).' '.$condList;
747 $queryEvolSub .= ' GROUP BY list_id';
748 $evolSubscibers = acym_loadObjectList($queryEvolSub, 'list_id');
749
750 $queryEvolUnsub = 'SELECT list_id, COUNT(*) AS newUnsub FROM #__acym_user_has_list';
751 $queryEvolUnsub .= ' WHERE unsubscribe_date > '.acym_escapeDB($dateNow).' '.$condList;
752 $queryEvolUnsub .= ' GROUP BY list_id';
753 $newUnsubscribers = acym_loadObjectList($queryEvolUnsub, 'list_id');
754
755 foreach ($newUnsubscribers as $listId => $oneUnsub) {
756 if (empty($evolSubscibers[$listId])) {
757 $evolSubscibers[$listId] = new \stdClass();
758 }
759 $evolSubscibers[$listId]->newUnsub = $oneUnsub->newUnsub;
760 }
761
762 return $evolSubscibers;
763 }
764
765 public function getYearSubEvolutionPerList(int $listId): array
766 {
767 // Get next month from 1 year ago
768 $month = gmdate('n') + 1;
769 $year = gmdate('Y') - 1;
770 $initDate = $year.'-'.$month.'-01';
771 if ($month == 13) {
772 $initDate = gmdate('Y').'-01-01';
773 }
774
775 // Get new subscribers per month
776 $queryEvolSub = 'SELECT MONTH(subscription_date) as monthSub, DATE_FORMAT(subscription_date,"%Y_%m") AS unit, COUNT(user_id) AS nbUser';
777 $queryEvolSub .= ' FROM `#__acym_user_has_list`';
778 $queryEvolSub .= ' WHERE subscription_date >= "'.$initDate.'" AND list_id = '.$listId;
779 $queryEvolSub .= ' GROUP BY unit';
780 $evolSubscibers = acym_loadObjectList($queryEvolSub, 'unit');
781
782 // Get unsubscribers per month
783 $queryEvolUnsub = 'SELECT MONTH(unsubscribe_date) as monthUnsub, DATE_FORMAT(unsubscribe_date,"%Y_%m") AS unit, COUNT(user_id) AS nbUser';
784 $queryEvolUnsub .= ' FROM `#__acym_user_has_list`';
785 $queryEvolUnsub .= ' WHERE unsubscribe_date >= "'.$initDate.'" AND list_id = '.$listId;
786 $queryEvolUnsub .= ' GROUP BY unit';
787 $evolUnsubscibers = acym_loadObjectList($queryEvolUnsub, 'unit');
788
789 return [
790 'subscribers' => $evolSubscibers,
791 'unsubscribers' => $evolUnsubscibers,
792 ];
793 }
794
795 private function initList(int $listId): object
796 {
797 $list = new \stdClass();
798 $list->list_id = $listId;
799 $list->sendable_users = 0;
800 $list->unconfirmed_users = 0;
801 $list->inactive_users = 0;
802 $list->unsubscribed_users = 0;
803
804 return $list;
805 }
806
807 public function getManageableLists(): array
808 {
809 $idCurrentUser = acym_currentUserId();
810 if (empty($idCurrentUser)) {
811 return [];
812 }
813
814 $userGroups = acym_getGroupsByUser($idCurrentUser);
815
816 return acym_loadResultArray(
817 'SELECT id
818 FROM #__acym_list
819 WHERE cms_user_id = '.intval($idCurrentUser).'
820 OR (access LIKE "%,'.implode(',%" OR access LIKE "%,', $userGroups).',%")'
821 );
822 }
823
824 public function onlyManageableLists(&$elements): void
825 {
826 if (acym_isAdmin()) {
827 return;
828 }
829
830 $manageableLists = $this->getManageableLists();
831 $elements = array_intersect($elements, $manageableLists);
832 }
833
834 public function hasUserAccess(int $listId): bool
835 {
836 if (acym_isAdmin() && acym_isAllowed('lists')) {
837 return true;
838 }
839
840 return in_array($listId, $this->getManageableLists());
841 }
842
843 public function getfrontManagementList(): int
844 {
845 $idCurrentUser = acym_currentUserId();
846 if (empty($idCurrentUser)) {
847 return 0;
848 }
849
850 $frontListId = acym_loadResult('SELECT id FROM #__acym_list WHERE type = '.acym_escapeDB(self::LIST_TYPE_FRONT).' AND cms_user_id = '.intval($idCurrentUser));
851
852 if (!empty($frontListId)) {
853 return $frontListId;
854 }
855
856 $frontList = new \stdClass();
857 $frontList->name = 'frontlist_'.$idCurrentUser;
858 $frontList->active = 1;
859 $frontList->visible = 0;
860 $frontList->cms_user_id = $idCurrentUser;
861 $frontList->type = self::LIST_TYPE_FRONT;
862
863 return (int)$this->save($frontList);
864 }
865
866 public function setWelcomeUnsubEmail(array $listIds, int $mailId, string $type): bool
867 {
868 if (!in_array($type, [MailClass::TYPE_WELCOME, MailClass::TYPE_UNSUBSCRIBE]) || empty($mailId)) {
869 return false;
870 }
871
872 $column = esc_attr($type).'_id';
873 $columnsList = acym_getColumns('list');
874 if (!in_array($column, $columnsList)) {
875 return false;
876 }
877
878 acym_arrayToInteger($listIds);
879 $listIds = array_filter($listIds);
880
881 if (!$this->removeWelcomeUnsubByMailId($mailId, $listIds, $column)) {
882 return false;
883 }
884
885 foreach ($listIds as $listId) {
886 $list = $this->getOneById($listId);
887 if (empty($list)) {
888 continue;
889 }
890
891 $list->{$column} = $mailId;
892
893 $savedListId = $this->save($list);
894 if (empty($savedListId)) {
895 return false;
896 }
897 }
898
899 return true;
900 }
901
902 private function removeWelcomeUnsubByMailId(int $mailId, array $listIds, string $column): bool
903 {
904 if (empty($mailId)) {
905 return false;
906 }
907
908 $query = 'SELECT * FROM #__acym_list WHERE '.acym_secureDBColumn($column).' = '.$mailId;
909
910 if (!empty($listIds)) {
911 acym_arrayToInteger($listIds);
912 $query .= ' AND `id` NOT IN ('.implode(',', $listIds).')';
913 }
914
915 $lists = acym_loadObjectList($query);
916 if (empty($lists)) {
917 return true;
918 }
919
920 foreach ($lists as $list) {
921 $list->{$column} = null;
922
923 $savedListId = $this->save($list);
924 if (empty($savedListId)) {
925 return false;
926 }
927 }
928
929 return true;
930 }
931
932 public function getListIdsByWelcomeUnsub(int $mailId, bool $welcome = true): array
933 {
934 $type = $welcome ? 'welcome_id' : 'unsubscribe_id';
935
936 $return = acym_loadResultArray('SELECT id FROM #__acym_list WHERE '.$type.' = '.intval($mailId));
937
938 return empty($return) ? [] : $return;
939 }
940
941 public function getUsersForSummaryModal(int $id, int $offset, int $limit, string $search): array
942 {
943 $whereQuery = ' AND user_list.status = 1 AND user.active = 1';
944
945 if (!empty($search)) {
946 $search = acym_escapeDB('%'.$search.'%');
947 $whereQuery .= ' AND (user.email LIKE '.$search.' OR user.name LIKE '.$search.' OR user.id LIKE '.$search.') ';
948 }
949
950 if ($this->config->get('require_confirmation', '1') === '1') {
951 $whereQuery .= ' AND user.confirmed = 1';
952 }
953
954 $query = 'SELECT user.email, user.name, user.id
955 FROM #__acym_user as user
956 JOIN #__acym_user_has_list as user_list ON user.id = user_list.user_id
957 WHERE user_list.list_id = '.intval($id).$whereQuery.' LIMIT '.intval($offset).', '.intval($limit);
958
959
960 return acym_loadObjectList($query, 'id');
961 }
962
963 public function getXLists(array $options = []): array
964 {
965 $limit = $options['limit'] ?? 10;
966 $offset = $options['offset'] ?? 0;
967 $filters = $options['filters'] ?? ['type' => self::LIST_TYPE_STANDARD];
968
969 $conditions = [];
970 foreach ($filters as $column => $filter) {
971 switch ($column) {
972 case 'id':
973 case 'active':
974 case 'visible':
975 case 'clean':
976 case 'welcome_id':
977 case 'unsubscribe_id':
978 case 'cms_user_id':
979 case 'tracking':
980 $conditions[] = acym_secureDBColumn($column).' = '.intval($filter);
981 break;
982 default:
983 $conditions[] = acym_secureDBColumn($column).' LIKE '.acym_escapeDB('%'.$filter.'%');
984 }
985 }
986
987 $query = 'SELECT * FROM #__acym_list';
988 if (!empty($conditions)) {
989 $query .= ' WHERE '.implode(' AND ', $conditions);
990 }
991
992 return acym_loadObjectList($query, $this->pkey, $offset, $limit);
993 }
994
995 public function getOneList(): bool
996 {
997 $firstList = acym_loadObject('SELECT * FROM #__acym_list WHERE `id` != 1');
998
999 return !empty($firstList);
1000 }
1001
1002 public function getMostUsedLists(): array
1003 {
1004 return acym_loadObjectList(
1005 'SELECT list_id
1006 FROM #__acym_mail_has_list
1007 GROUP BY list_id
1008 ORDER BY COUNT(*) DESC
1009 LIMIT 5'
1010 );
1011 }
1012
1013 public function getListsSubscribersAndUnsubscribeUsersFromUserHasList(): array
1014 {
1015 $query = 'SELECT list_id,
1016 SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS subscribers,
1017 SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS unsubscribed_users
1018 FROM `#__acym_user_has_list`
1019 GROUP BY list_id';
1020
1021 return acym_loadObjectList($query);
1022 }
1023
1024 public function updateListsSubscribersAndUnsubscribeUsers(int $listId, int $subscribers, int $unsubscribeUsers, int $new_sub, int $new_unsub): void
1025 {
1026 acym_query(
1027 'UPDATE `#__acym_list`
1028 SET `subscribers` = '.$subscribers.', `unsubscribed_users` = '.$unsubscribeUsers.', `new_sub` = '.$new_sub.', `new_unsub` = '.$new_unsub.'
1029 WHERE id = '.$listId
1030 );
1031 }
1032
1033 public function getMostUsedListsByIdsWithSubscribersData(array $ids = []): array
1034 {
1035 if (empty($ids)) {
1036 return [];
1037 }
1038 acym_arrayToInteger($ids);
1039 $query = 'SELECT name, subscribers, unsubscribed_users, new_sub, new_unsub FROM `#__acym_list` WHERE id IN ('.implode(', ', $ids).')';
1040
1041 return acym_loadObjectList($query);
1042 }
1043
1044 public function getUserListEntries(array $userIds, array $listIds): array
1045 {
1046 if (empty($userIds) || empty($listIds)) {
1047 return [];
1048 }
1049
1050 $userIdsStr = implode(',', array_map('intval', $userIds));
1051 $listIdsStr = implode(',', array_map('intval', $listIds));
1052
1053 $query = 'SELECT * FROM `#__acym_user_has_list`
1054 WHERE user_id IN ('.$userIdsStr.') AND list_id IN ('.$listIdsStr.')';
1055
1056 $results = acym_loadObjectList($query);
1057
1058 $entries = [];
1059 foreach ($results as $row) {
1060 $key = $row->user_id.'_'.$row->list_id;
1061 $entries[$key] = $row;
1062 }
1063
1064 return $entries;
1065 }
1066 }
1067