PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Classes/ListClass.php +1066 -0 10.2.0 → 11.1.0 View file →
@@ -1,0 +1,1066 @@
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 +}