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

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

1,058 lines 35.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Classes;
4
5 use AcyMailing\Helpers\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 = $options['orderBySort'] ?? '';
325 $subscribedAfter = $options['subscribed_after'] ?? null;
326 $unsubscribedAfter = $options['unsubscribed_after'] ?? null;
327
328 if (empty($listIds)) {
329 return [];
330 }
331
332 acym_arrayToInteger($listIds);
333
334 $statusCondition = '';
335 if (!is_null($status)) {
336 $statusCondition .= ' AND user_list.status = '.intval($status);
337 }
338
339 if (!empty($subscribedAfter)) {
340 $statusCondition .= ' AND user_list.subscription_date > '.acym_escapeDB($subscribedAfter);
341 }
342
343 if (!empty($unsubscribedAfter)) {
344 $statusCondition .= ' AND user_list.unsubscribe_date > '.acym_escapeDB($unsubscribedAfter);
345 }
346
347 $requestSub = 'SELECT user.*, user_list.status, user_list.subscription_date, user_list.unsubscribe_date FROM #__acym_user AS user';
348 $requestSub .= ' LEFT JOIN #__acym_user_has_list AS user_list ON user.id = user_list.user_id';
349 $requestSub .= ' WHERE user.active = 1 AND user_list.list_id IN ('.implode(', ', $listIds).')'.$statusCondition;
350
351 if (!empty($orderBy)) {
352 if (empty($orderBySort)) $orderBySort = 'desc';
353 $requestSub .= ' ORDER BY '.acym_secureDBColumn($orderBy).' '.acym_secureDBColumn($orderBySort);
354 }
355
356 return acym_loadObjectList(
357 $requestSub,
358 '',
359 $offset,
360 $limit
361 );
362 }
363
364 public function delete(array $elements, bool $force = false): int
365 {
366 if (!$force) {
367 $this->onlyManageableLists($elements);
368 }
369
370 if (empty($elements)) {
371 return 0;
372 }
373
374 foreach ($elements as $id) {
375 acym_query('DELETE FROM #__acym_mail_has_list WHERE list_id = '.intval($id));
376 acym_query('DELETE FROM #__acym_user_has_list WHERE list_id = '.intval($id));
377 acym_query('DELETE FROM #__acym_tag WHERE `id_element` = '.intval($id).' AND `type` = "list"');
378 }
379
380 return parent::delete($elements);
381 }
382
383 public function synchDeleteCmsList(int $userId): void
384 {
385 $query = 'SELECT * FROM #__acym_list WHERE type = "'.self::LIST_TYPE_FRONT.'" AND cms_user_id = '.intval($userId);
386 $listFrontManagement = acym_loadObject($query);
387 if (!empty($listFrontManagement)) {
388 $this->delete([$listFrontManagement->id]);
389 }
390 }
391
392 public function save(object $list): ?int
393 {
394 if (isset($list->tags)) {
395 $tags = $list->tags;
396 unset($list->tags);
397 }
398
399 $oldList = null;
400
401 if (empty($list->id)) {
402 if (empty($list->cms_user_id)) {
403 $list->cms_user_id = acym_currentUserId();
404 }
405
406 $list->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
407 } else {
408 $oldList = $this->getOneById($list->id);
409 }
410
411 if (!empty($list->translation)) {
412 if (is_array($list->translation)) $list->translation = json_encode($list->translation);
413 } else {
414 $list->translation = '';
415 }
416
417 foreach ($list as $oneAttribute => $value) {
418 if (empty($value)) continue;
419
420 $list->$oneAttribute = acym_stripTags($value);
421 }
422
423 if (empty($list->description)) {
424 $list->description = '';
425 }
426
427 if (!isset($list->access) && empty($list->id)) {
428 $list->access = '';
429 }
430
431 if (empty($list->id)) {
432 acym_trigger('onAcymBeforeListCreate', [&$list]);
433 } else {
434 acym_trigger('onAcymBeforeListModify', [&$list]);
435 }
436
437 $listID = parent::save($list);
438
439 if (empty($listID)) {
440 return null;
441 }
442
443 if (isset($tags)) {
444 $tagClass = new TagClass();
445 $tagClass->setTags(TagClass::TYPE_LIST, intval($listID), $tags);
446 }
447
448 $list->id = $listID;
449
450 if (is_null($oldList)) {
451 acym_trigger('onAcymAfterListCreate', [&$list]);
452 } else {
453 acym_trigger('onAcymAfterListModify', [&$list, &$oldList]);
454 }
455
456 return $listID;
457 }
458
459 public function getAllWithIdName(): array
460 {
461 $endQuery = '';
462 if (!acym_isAdmin()) {
463 $creatorId = acym_currentUserId();
464
465 $userGroups = acym_getGroupsByUser($creatorId);
466 $groupCondition = '(access LIKE "%,'.implode(',%" OR access LIKE "%,', $userGroups).',%")';
467
468 $endQuery = ' AND (cms_user_id = '.intval($creatorId).' OR '.$groupCondition.')';
469 }
470 $lists = acym_loadObjectList('SELECT id, name FROM #__acym_list WHERE type = '.acym_escapeDB(self::LIST_TYPE_STANDARD).$endQuery, 'id');
471
472 $listsToReturn = [];
473
474 foreach ($lists as $key => $list) {
475 $listsToReturn[$key] = $list->name;
476 }
477
478 return $listsToReturn;
479 }
480
481 public function getAllForSelect(bool $emptyFirst = true, int $userFrontID = 0, bool $needTranslation = false, bool $needFrontLabel = false): array
482 {
483 $groupCondition = '';
484 if (!empty($userFrontID)) {
485 $userGroups = acym_getGroupsByUser($userFrontID);
486 $groupCondition = ' AND (cms_user_id = '.intval($userFrontID).' OR (access LIKE "%,'.implode(',%" OR access LIKE "%,', $userGroups).',%"))';
487 }
488
489 $lists = acym_loadObjectList('SELECT * FROM #__acym_list WHERE type = '.acym_escapeDB(self::LIST_TYPE_STANDARD).$groupCondition, 'id');
490
491 if (acym_isMultilingual() && $needTranslation) {
492 $lists = $this->getTranslatedNameDescription($lists);
493 }
494
495 $return = [];
496
497 if ($emptyFirst) {
498 $return[''] = acym_translation('ACYM_SELECT_A_LIST');
499 }
500
501 foreach ($lists as $key => $list) {
502 $return[$key] = $needFrontLabel && !empty($list->display_name) ? $list->display_name : $list->name;
503 }
504
505 return $return;
506 }
507
508 public function getAllWithoutManagement(bool $needTranslation = false): array
509 {
510 $lists = acym_loadObjectList('SELECT * FROM #__acym_list WHERE `type` = '.acym_escapeDB(self::LIST_TYPE_STANDARD), 'id');
511
512 if (acym_isMultilingual() && $needTranslation) {
513 $lists = $this->getTranslatedNameDescription($lists);
514 }
515
516 return $lists;
517 }
518
519 public function getTranslatedNameDescription(array $lists): array
520 {
521 $currentLanguageTag = acym_getLanguageTag();
522 foreach ($lists as $id => $list) {
523 if (empty($list->translation)) continue;
524
525 $list->translation = json_decode($list->translation, true);
526 if (!empty($list->translation[$currentLanguageTag])) {
527 if (!empty($list->translation[$currentLanguageTag]['name'])) {
528 $list->name = $list->translation[$currentLanguageTag]['name'];
529 }
530
531 if (!empty($list->translation[$currentLanguageTag]['display_name'])) {
532 $list->display_name = $list->translation[$currentLanguageTag]['display_name'];
533 }
534
535 if (!empty($list->translation[$currentLanguageTag]['description'])) {
536 $list->description = $list->translation[$currentLanguageTag]['description'];
537 }
538 }
539 }
540
541 return $lists;
542 }
543
544 /**
545 * Sends the welcome emails attached to the specified lists
546 */
547 public function sendWelcome(int $userID, array $listIDs, bool $forceFront = false): void
548 {
549 if (!$forceFront && acym_isAdmin()) {
550 return;
551 }
552
553 acym_arrayToInteger($listIDs);
554 if (empty($listIDs)) {
555 return;
556 }
557
558 $messages = acym_loadObjectList('SELECT `welcome_id` FROM #__acym_list WHERE `id` IN ('.implode(',', $listIDs).') AND `active` = 1');
559
560 if (empty($messages)) {
561 return;
562 }
563
564 $alreadySent = [];
565 $mailerHelper = new MailerHelper();
566 $mailerHelper->report = (bool)$this->config->get('welcome_message', 1);
567 foreach ($messages as $oneMessage) {
568 $mailid = $oneMessage->welcome_id;
569 if (empty($mailid)) continue;
570
571 //We don't send twice the same message
572 if (isset($alreadySent[$mailid])) {
573 continue;
574 }
575
576 $mailerHelper->trackEmail = true;
577 $mailerHelper->sendOne($mailid, $userID);
578 $alreadySent[$mailid] = true;
579 }
580 }
581
582 /**
583 * Sends the unsubscribe emails attached to the specified lists
584 */
585 public function sendUnsubscribe(int $userID, array $listIDs): void
586 {
587 if (acym_isAdmin()) {
588 return;
589 }
590
591 acym_arrayToInteger($listIDs);
592 if (empty($listIDs)) {
593 return;
594 }
595
596 $messages = acym_loadObjectList('SELECT `unsubscribe_id` FROM #__acym_list WHERE `id` IN ('.implode(',', $listIDs).') AND `active` = 1');
597
598 if (empty($messages)) {
599 return;
600 }
601
602 $alreadySent = [];
603 $mailerHelper = new MailerHelper();
604 $mailerHelper->report = (bool)$this->config->get('unsub_message', 1);
605 foreach ($messages as $oneMessage) {
606 if (!empty($oneMessage->unsubscribe_id)) {
607 $mailid = $oneMessage->unsubscribe_id;
608
609 //We don't send twice the same message
610 if (isset($alreadySent[$mailid])) {
611 continue;
612 }
613
614 $mailerHelper->trackEmail = true;
615 $mailerHelper->sendOne($mailid, $userID);
616 $alreadySent[$mailid] = true;
617 }
618 }
619 }
620
621 public function addDefaultList()
622 {
623 $listId = acym_loadResult('SELECT `id` FROM #__acym_list LIMIT 1');
624 if (empty($listId)) {
625 $defaultList = new \stdClass();
626 $defaultList->name = 'Newsletters';
627 $defaultList->color = '#3366ff';
628 $defaultList->description = '';
629
630 $this->save($defaultList);
631 }
632 }
633
634 public function getTotalSubCount(array $ids): int
635 {
636 if (empty($ids)) {
637 return 0;
638 }
639
640 acym_arrayToInteger($ids);
641 $this->onlyManageableLists($ids);
642 $query = 'SELECT COUNT(DISTINCT hasList.user_id)
643 FROM #__acym_user_has_list AS hasList
644 JOIN #__acym_user AS user
645 ON hasList.user_id = user.id
646 WHERE hasList.status = 1
647 AND user.active = 1
648 AND hasList.list_id IN ('.implode(',', $ids).')';
649
650
651 if ($this->config->get('require_confirmation', 1) == 1) {
652 $query .= ' AND user.confirmed = 1 ';
653 }
654
655 return intval(acym_loadResult($query));
656 }
657
658 /**
659 * Get all mails attached to a list
660 */
661 public function getMailsByListId(int $listId): array
662 {
663 $mailIds = acym_loadResultArray('SELECT mail_id FROM #__acym_mail_has_list WHERE list_id = '.intval($listId));
664
665 return empty($mailIds) ? [] : $mailIds;
666 }
667
668 public function getSubscribersCountPerStatusByListIds(array $listIds = []): array
669 {
670 $condList = '';
671 if (!empty($listIds)) {
672 acym_arrayToInteger($listIds);
673 $condList = 'AND userList.list_id IN ('.implode(',', $listIds).')';
674 }
675
676 /*
677 * score:
678 * An unconfirmed and active user will have a score of 0 + 1*2 = 2
679 * A confirmed and active user will have a score of 1 + 1*2 = 3
680 * A confirmed and inactive user will have a score of 1 + 0*2 = 1
681 */
682 $query = 'SELECT userList.list_id, COUNT(userList.user_id) AS users, acyuser.confirmed + acyuser.active*2 AS score
683 FROM #__acym_user_has_list AS userList
684 JOIN #__acym_user AS acyuser
685 ON acyuser.id = userList.user_id
686 WHERE userList.status = 1
687 '.$condList.'
688 GROUP BY score, userList.list_id';
689 $results = acym_loadObjectList($query);
690
691 $confirmationRequired = $this->config->get('require_confirmation', 1);
692 $listsUserStats = [];
693 foreach ($results as $oneResult) {
694 if (!isset($listsUserStats[$oneResult->list_id])) {
695 $listsUserStats[$oneResult->list_id] = $this->initList($oneResult->list_id);
696 }
697
698 // Joomla 4 casts the result into an int
699 $oneResult->score = (string)$oneResult->score;
700
701 if (in_array($oneResult->score, ['0', '1'])) {
702 $listsUserStats[$oneResult->list_id]->inactive_users += $oneResult->users;
703 }
704
705 if (in_array($oneResult->score, ['0', '2'])) {
706 $listsUserStats[$oneResult->list_id]->unconfirmed_users += $oneResult->users;
707 }
708
709 if ($oneResult->score === '3' || ($confirmationRequired != 1 && $oneResult->score == '2')) {
710 $listsUserStats[$oneResult->list_id]->sendable_users += $oneResult->users;
711 }
712 }
713
714 $query = 'SELECT userList.list_id, COUNT(userList.user_id) AS users
715 FROM #__acym_user_has_list AS userList
716 WHERE userList.status = 0
717 '.$condList.'
718 GROUP BY userList.list_id';
719 $unsubscribed = acym_loadObjectList($query);
720
721 foreach ($unsubscribed as $oneList) {
722 if (!isset($listsUserStats[$oneList->list_id])) {
723 $listsUserStats[$oneList->list_id] = $this->initList($oneList->list_id);
724 }
725 $listsUserStats[$oneList->list_id]->unsubscribed_users = $oneList->users;
726 }
727
728 return $listsUserStats;
729 }
730
731 public function getSubscribersEvolutionByListIds(array $listIds): array
732 {
733 $condList = '';
734 if (!empty($listIds)) {
735 acym_arrayToInteger($listIds);
736 $condList = ' AND list_id IN ('.implode(',', $listIds).')';
737 }
738 $dateNow = acym_loadResult('SELECT DATE_SUB(NOW(), INTERVAL 1 MONTH)');
739 $queryEvolSub = 'SELECT list_id, COUNT(*) AS newSub FROM #__acym_user_has_list';
740 $queryEvolSub .= ' WHERE subscription_date > '.acym_escapeDB($dateNow).' '.$condList;
741 $queryEvolSub .= ' GROUP BY list_id';
742 $evolSubscibers = acym_loadObjectList($queryEvolSub, 'list_id');
743
744 $queryEvolUnsub = 'SELECT list_id, COUNT(*) AS newUnsub FROM #__acym_user_has_list';
745 $queryEvolUnsub .= ' WHERE unsubscribe_date > '.acym_escapeDB($dateNow).' '.$condList;
746 $queryEvolUnsub .= ' GROUP BY list_id';
747 $newUnsubscribers = acym_loadObjectList($queryEvolUnsub, 'list_id');
748
749 foreach ($newUnsubscribers as $listId => $oneUnsub) {
750 if (empty($evolSubscibers[$listId])) {
751 $evolSubscibers[$listId] = new \stdClass();
752 }
753 $evolSubscibers[$listId]->newUnsub = $oneUnsub->newUnsub;
754 }
755
756 return $evolSubscibers;
757 }
758
759 public function getYearSubEvolutionPerList(int $listId): array
760 {
761 // Get next month from 1 year ago
762 $month = gmdate('n') + 1;
763 $year = gmdate('Y') - 1;
764 $initDate = $year.'-'.$month.'-01';
765 if ($month == 13) {
766 $initDate = gmdate('Y').'-01-01';
767 }
768
769 // Get new subscribers per month
770 $queryEvolSub = 'SELECT MONTH(subscription_date) as monthSub, DATE_FORMAT(subscription_date,"%Y_%m") AS unit, COUNT(user_id) AS nbUser';
771 $queryEvolSub .= ' FROM `#__acym_user_has_list`';
772 $queryEvolSub .= ' WHERE subscription_date >= "'.$initDate.'" AND list_id = '.$listId;
773 $queryEvolSub .= ' GROUP BY unit';
774 $evolSubscibers = acym_loadObjectList($queryEvolSub, 'unit');
775
776 // Get unsubscribers per month
777 $queryEvolUnsub = 'SELECT MONTH(unsubscribe_date) as monthUnsub, DATE_FORMAT(unsubscribe_date,"%Y_%m") AS unit, COUNT(user_id) AS nbUser';
778 $queryEvolUnsub .= ' FROM `#__acym_user_has_list`';
779 $queryEvolUnsub .= ' WHERE unsubscribe_date >= "'.$initDate.'" AND list_id = '.$listId;
780 $queryEvolUnsub .= ' GROUP BY unit';
781 $evolUnsubscibers = acym_loadObjectList($queryEvolUnsub, 'unit');
782
783 return [
784 'subscribers' => $evolSubscibers,
785 'unsubscribers' => $evolUnsubscibers,
786 ];
787 }
788
789 private function initList(int $listId): object
790 {
791 $list = new \stdClass();
792 $list->list_id = $listId;
793 $list->sendable_users = 0;
794 $list->unconfirmed_users = 0;
795 $list->inactive_users = 0;
796 $list->unsubscribed_users = 0;
797
798 return $list;
799 }
800
801 public function getManageableLists(): array
802 {
803 $idCurrentUser = acym_currentUserId();
804 if (empty($idCurrentUser)) {
805 return [];
806 }
807
808 $userGroups = acym_getGroupsByUser($idCurrentUser);
809
810 return acym_loadResultArray(
811 'SELECT id
812 FROM #__acym_list
813 WHERE cms_user_id = '.intval($idCurrentUser).'
814 OR (access LIKE "%,'.implode(',%" OR access LIKE "%,', $userGroups).',%")'
815 );
816 }
817
818 public function onlyManageableLists(&$elements): void
819 {
820 if (acym_isAdmin()) {
821 return;
822 }
823
824 $manageableLists = $this->getManageableLists();
825 $elements = array_intersect($elements, $manageableLists);
826 }
827
828 public function hasUserAccess(int $listId): bool
829 {
830 if (acym_isAdmin() && acym_isAllowed('lists')) {
831 return true;
832 }
833
834 return in_array($listId, $this->getManageableLists());
835 }
836
837 public function getfrontManagementList(): int
838 {
839 $idCurrentUser = acym_currentUserId();
840 if (empty($idCurrentUser)) {
841 return 0;
842 }
843
844 $frontListId = acym_loadResult('SELECT id FROM #__acym_list WHERE type = '.acym_escapeDB(self::LIST_TYPE_FRONT).' AND cms_user_id = '.intval($idCurrentUser));
845
846 if (!empty($frontListId)) {
847 return $frontListId;
848 }
849
850 $frontList = new \stdClass();
851 $frontList->name = 'frontlist_'.$idCurrentUser;
852 $frontList->active = 1;
853 $frontList->visible = 0;
854 $frontList->cms_user_id = $idCurrentUser;
855 $frontList->type = self::LIST_TYPE_FRONT;
856
857 return (int)$this->save($frontList);
858 }
859
860 public function setWelcomeUnsubEmail(array $listIds, int $mailId, string $type): bool
861 {
862 if (!in_array($type, [MailClass::TYPE_WELCOME, MailClass::TYPE_UNSUBSCRIBE]) || empty($mailId)) {
863 return false;
864 }
865
866 $column = esc_attr($type).'_id';
867 $columnsList = acym_getColumns('list');
868 if (!in_array($column, $columnsList)) {
869 return false;
870 }
871
872 if (!$this->removeWelcomeUnsubByMailId($mailId, $listIds, $column)) {
873 return false;
874 }
875
876 foreach ($listIds as $listId) {
877 $list = $this->getOneById($listId);
878 if (empty($list)) {
879 return false;
880 }
881
882 $list->{$column} = $mailId;
883
884 $savedListId = $this->save($list);
885 if (empty($savedListId)) {
886 return false;
887 }
888 }
889
890 return true;
891 }
892
893 private function removeWelcomeUnsubByMailId(int $mailId, array $listIds, string $column): bool
894 {
895 if (empty($mailId)) {
896 return false;
897 }
898
899 $query = 'SELECT * FROM #__acym_list WHERE '.acym_secureDBColumn($column).' = '.$mailId;
900
901 if (!empty($listIds)) {
902 acym_arrayToInteger($listIds);
903 $query .= ' AND `id` NOT IN ('.implode(',', $listIds).')';
904 }
905
906 $lists = acym_loadObjectList($query);
907 if (empty($lists)) {
908 return true;
909 }
910
911 foreach ($lists as $list) {
912 $list->{$column} = null;
913
914 $savedListId = $this->save($list);
915 if (empty($savedListId)) {
916 return false;
917 }
918 }
919
920 return true;
921 }
922
923 public function getListIdsByWelcomeUnsub(int $mailId, bool $welcome = true): array
924 {
925 $type = $welcome ? 'welcome_id' : 'unsubscribe_id';
926
927 $return = acym_loadResultArray('SELECT id FROM #__acym_list WHERE '.$type.' = '.intval($mailId));
928
929 return empty($return) ? [] : $return;
930 }
931
932 public function getUsersForSummaryModal(int $id, int $offset, int $limit, string $search): array
933 {
934 $whereQuery = ' AND user_list.status = 1 AND user.active = 1';
935
936 if (!empty($search)) {
937 $search = acym_escapeDB('%'.$search.'%');
938 $whereQuery .= ' AND (user.email LIKE '.$search.' OR user.name LIKE '.$search.' OR user.id LIKE '.$search.') ';
939 }
940
941 if ($this->config->get('require_confirmation', '1') === '1') {
942 $whereQuery .= ' AND user.confirmed = 1';
943 }
944
945 $query = 'SELECT user.email, user.name, user.id
946 FROM #__acym_user as user
947 JOIN #__acym_user_has_list as user_list ON user.id = user_list.user_id
948 WHERE user_list.list_id = '.intval($id).$whereQuery.' LIMIT '.intval($offset).', '.intval($limit);
949
950
951 return acym_loadObjectList($query, 'id');
952 }
953
954 public function getXLists(array $options = []): array
955 {
956 $limit = $options['limit'] ?? 10;
957 $offset = $options['offset'] ?? 0;
958 $filters = $options['filters'] ?? ['type' => self::LIST_TYPE_STANDARD];
959
960 $conditions = [];
961 foreach ($filters as $column => $filter) {
962 switch ($column) {
963 case 'id':
964 case 'active':
965 case 'visible':
966 case 'clean':
967 case 'welcome_id':
968 case 'unsubscribe_id':
969 case 'cms_user_id':
970 case 'tracking':
971 $conditions[] = acym_secureDBColumn($column).' = '.intval($filter);
972 break;
973 default:
974 $conditions[] = acym_secureDBColumn($column).' LIKE '.acym_escapeDB('%'.$filter.'%');
975 }
976 }
977
978 $query = 'SELECT * FROM #__acym_list';
979 if (!empty($conditions)) {
980 $query .= ' WHERE '.implode(' AND ', $conditions);
981 }
982
983 return acym_loadObjectList($query, $this->pkey, $offset, $limit);
984 }
985
986 public function getOneList(): bool
987 {
988 $firstList = acym_loadObject('SELECT * FROM #__acym_list WHERE `id` != 1');
989
990 return !empty($firstList);
991 }
992
993 public function getMostUsedLists(): array
994 {
995 return acym_loadObjectList(
996 'SELECT list_id
997 FROM #__acym_mail_has_list
998 GROUP BY list_id
999 ORDER BY COUNT(*) DESC
1000 LIMIT 5'
1001 );
1002 }
1003
1004 public function getListsSubscribersAndUnsubscribeUsersFromUserHasList(): array
1005 {
1006 $query = 'SELECT list_id,
1007 SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS subscribers,
1008 SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS unsubscribed_users
1009 FROM `#__acym_user_has_list`
1010 GROUP BY list_id';
1011
1012 return acym_loadObjectList($query);
1013 }
1014
1015 public function updateListsSubscribersAndUnsubscribeUsers(int $listId, int $subscribers, int $unsubscribeUsers, int $new_sub, int $new_unsub): void
1016 {
1017 acym_query(
1018 'UPDATE `#__acym_list`
1019 SET `subscribers` = '.$subscribers.', `unsubscribed_users` = '.$unsubscribeUsers.', `new_sub` = '.$new_sub.', `new_unsub` = '.$new_unsub.'
1020 WHERE id = '.$listId
1021 );
1022 }
1023
1024 public function getMostUsedListsByIdsWithSubscribersData(array $ids = []): array
1025 {
1026 if (empty($ids)) {
1027 return [];
1028 }
1029 acym_arrayToInteger($ids);
1030 $query = 'SELECT name, subscribers, unsubscribed_users, new_sub, new_unsub FROM `#__acym_list` WHERE id IN ('.implode(', ', $ids).')';
1031
1032 return acym_loadObjectList($query);
1033 }
1034
1035 public function getUserListEntries(array $userIds, array $listIds): array
1036 {
1037 if (empty($userIds) || empty($listIds)) {
1038 return [];
1039 }
1040
1041 $userIdsStr = implode(',', array_map('intval', $userIds));
1042 $listIdsStr = implode(',', array_map('intval', $listIds));
1043
1044 $query = 'SELECT * FROM `#__acym_user_has_list`
1045 WHERE user_id IN ('.$userIdsStr.') AND list_id IN ('.$listIdsStr.')';
1046
1047 $results = acym_loadObjectList($query);
1048
1049 $entries = [];
1050 foreach ($results as $row) {
1051 $key = $row->user_id.'_'.$row->list_id;
1052 $entries[$key] = $row;
1053 }
1054
1055 return $entries;
1056 }
1057 }
1058