PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / trunk
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress vtrunk
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 10.3.0 10.4.0 All 60 releases
acymailing / back / Classes / QueueClass.php

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

738 lines 31.1 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\Controllers\SegmentsController;
6 use AcyMailing\Helpers\AutomationHelper;
7 use AcyMailing\Core\AcymClass;
8
9 class QueueClass extends AcymClass
10 {
11 public array $emailtypes = [];
12
13 /**
14 * Get campaigns depending on filters (search, status, pagination)
15 */
16 public function getMatchingCampaigns(array $settings): array
17 {
18 $queuedMails = acym_loadResultArray('SELECT DISTINCT mail_id FROM #__acym_queue');
19 if (empty($queuedMails)) {
20 return [
21 'elements' => [],
22 'total' => (object)['total' => 0],
23 'status' => [
24 'all' => 0,
25 'sending' => 0,
26 'paused' => 0,
27 'automation' => 0,
28 'followup' => 0,
29 ],
30 ];
31 }
32
33 $campaignClass = new CampaignClass();
34 $mailStatClass = new MailStatClass();
35 $mailClass = new MailClass();
36
37 $queryCount = 'SELECT COUNT(DISTINCT mail.id) AS total
38 FROM #__acym_mail AS mail
39 LEFT JOIN #__acym_campaign AS campaign ON campaign.mail_id = mail.id OR campaign.mail_id = mail.parent_id';
40
41 $query = 'SELECT
42 mail.name,
43 mail.subject,
44 mail.type,
45 mail.id,
46 campaign.id AS campaign,
47 COALESCE(campaign.sending_date, queue.min_sending_date) AS sending_date,
48 campaign.sending_type,
49 campaign.active,
50 campaign.sending_params AS sending_params,
51 queue.nbqueued,
52 mail.language,
53 mail.parent_id
54 FROM #__acym_mail AS mail
55 JOIN (
56 SELECT mail_id, COUNT(*) AS nbqueued, MIN(sending_date) AS min_sending_date
57 FROM #__acym_queue
58 GROUP BY mail_id
59 ) AS queue ON mail.id = queue.mail_id
60 LEFT JOIN #__acym_campaign AS campaign
61 ON mail.id = campaign.mail_id
62 OR mail.parent_id = campaign.mail_id';
63
64 // This query returns an array like "number of mails" => score. cf the equivalent in the list class to understand how it works
65 $queryStatus = 'SELECT COUNT(DISTINCT mail.id) AS number, campaign.active
66 FROM #__acym_mail AS mail
67 LEFT JOIN #__acym_campaign AS campaign ON mail.id = campaign.mail_id';
68
69 if (!empty($settings['tag'])) {
70 $tagJoin = ' JOIN #__acym_tag AS tag
71 ON (mail.id = tag.id_element OR mail.parent_id = tag.id_element)
72 AND tag.type = "mail"
73 AND tag.name = '.acym_escapeDB($settings['tag']);
74 $query .= $tagJoin;
75 $queryCount .= $tagJoin;
76 $queryStatus .= ' JOIN #__acym_tag AS tag ON mail.id = tag.id_element AND tag.type = "mail" AND tag.name = '.acym_escapeDB($settings['tag']);
77 }
78
79 $filters = [];
80 $filters[] = 'campaign.id IS NULL OR campaign.draft = 0';
81
82 if (!empty($settings['search'])) {
83 $filters[] = 'mail.subject LIKE '.acym_escapeDB('%'.$settings['search'].'%').' OR mail.name LIKE '.acym_escapeDB('%'.$settings['search'].'%');
84 }
85
86 $queryStatus .= ' WHERE ('.implode(') AND (', $filters).') AND mail.id IN ('.implode(',', $queuedMails).')';
87
88 if (!empty($settings['status'])) {
89 $allowedStatus = [
90 'sending' => 'campaign.active = 1',
91 'paused' => 'campaign.active = 0',
92 'automation' => 'mail.type = '.acym_escapeDB(MailClass::TYPE_AUTOMATION),
93 'followup' => 'mail.type = '.acym_escapeDB(MailClass::TYPE_FOLLOWUP),
94 ];
95
96 if (empty($allowedStatus[$settings['status']])) {
97 die('Unauthorized filter: '.esc_html($settings['status']));
98 }
99
100 $filters[] = $allowedStatus[$settings['status']];
101 }
102
103 $queryCount .= ' WHERE ('.implode(') AND (', $filters).') AND mail.id IN ('.implode(',', $queuedMails).')';
104 $query .= ' WHERE ('.implode(') AND (', $filters).')';
105 $query .= ' ORDER BY queue.min_sending_date ASC';
106
107 acym_query('SET SQL_BIG_SELECTS=1;');
108 $results = [
109 'elements' => $mailClass->decode(acym_loadObjectList($query, '', $settings['offset'], $settings['campaignsPerPage'])),
110 'total' => acym_loadObject($queryCount),
111 ];
112
113 $isMultilingual = acym_isMultilingual();
114 $campaignRecipientsMultilingual = [];
115
116 // Get the recipients
117 $specialTypes = [];
118 acym_trigger('getCampaignTypes', [&$specialTypes]);
119
120 foreach ($results['elements'] as $i => $oneMail) {
121 $results['elements'][$i]->sending_params = empty($oneMail->sending_params) ? [] : json_decode($oneMail->sending_params, true);
122 if (in_array($oneMail->sending_type, $specialTypes)) {
123 $results['elements'][$i]->iscampaign = false;
124 $results['elements'][$i]->lists = acym_translation('ACYM_SPECIAL_MAIL_SENT_TO');
125 $results['elements'][$i]->recipients = acym_loadResult('SELECT COUNT(*) FROM #__acym_queue WHERE mail_id = '.intval($oneMail->id));
126 } elseif (empty($oneMail->campaign)) {
127 $results['elements'][$i]->iscampaign = false;
128 if ($oneMail->type === MailClass::TYPE_FOLLOWUP) {
129 $results['elements'][$i]->lists = acym_translation('ACYM_MAIL_FROM_FOLLOWUP_SENT_TO');
130 } else {
131 $results['elements'][$i]->lists = acym_translation('ACYM_MAIL_FROM_AUTOMATION_SENT_TO');
132 }
133 $results['elements'][$i]->recipients = acym_loadResult('SELECT COUNT(*) FROM #__acym_queue WHERE mail_id = '.intval($oneMail->id));
134 } else {
135 $mailId = empty($oneMail->parent_id) ? $oneMail->id : $oneMail->parent_id;
136 $results['elements'][$i]->iscampaign = true;
137 $results['elements'][$i]->lists = acym_loadObjectList(
138 'SELECT l.color, l.name , l.id
139 FROM #__acym_list AS l
140 JOIN #__acym_mail_has_list AS ml ON ml.list_id = l.id
141 WHERE ml.mail_id = '.intval($mailId),
142 'id'
143 );
144
145 if (isset($results['elements'][$i]->sending_params['abtest'])) {
146 $isVersionB = $results['elements'][$i]->sending_params['abtest']['B'] == $results['elements'][$i]->id;
147
148 if (empty($results['elements'][$i]->parent_id) || $isVersionB) {
149 $results['elements'][$i]->recipients = intval($mailStatClass->getTotalSubscribersByMailId($results['elements'][$i]->id));
150 } else {
151 $results['elements'][$i]->recipients = intval($mailStatClass->getTotalSubscribersByMailIdWithChild($mailId));
152 }
153 } elseif ($isMultilingual) {
154 if (empty($campaignRecipientsMultilingual[$oneMail->campaign])) {
155 $automationHelper = new AutomationHelper();
156
157 $listIds = array_keys($results['elements'][$i]->lists);
158 acym_arrayToInteger($listIds);
159
160 $automationHelper->join['user_list'] = ' #__acym_user_has_list AS user_list ON user_list.user_id = user.id AND user_list.list_id IN ('.implode(
161 ',',
162 $listIds
163 ).') and user_list.status = 1 ';
164 $automationHelper->leftjoin['mail'] = '`#__acym_mail` AS mail ON `mail`.`language` = `user`.language AND `mail`.`parent_id` = '.intval($mailId);
165 $automationHelper->where[] = '`user_list`.`list_id` IN ('.implode(',', $listIds).') AND `user_list`.`status` = 1';
166
167 $filters = $campaignClass->getFilterCampaign($oneMail->sending_params);
168 if (!empty($filters)) {
169 foreach ($filters as $orValues) {
170 if (empty($orValues)) continue;
171
172 $automationHelperSegment = new AutomationHelper();
173 foreach ($orValues as $and => $andValues) {
174 $and = intval($and);
175 foreach ($andValues as $filterName => $options) {
176 acym_trigger('onAcymProcessFilter_'.$filterName, [&$automationHelperSegment, &$options, &$and]);
177 }
178 }
179 $automationHelperSegment->addFlag(SegmentsController::FLAG_COUNT);
180 }
181
182 $segmentMatchingCondition = 'LIKE';
183 if (!empty($oneMail->sending_params['segment']['invert']) && $oneMail->sending_params['segment']['invert'] === 'exclude') {
184 $segmentMatchingCondition = 'NOT LIKE';
185 }
186 $automationHelper->where[] = 'user.automation '.$segmentMatchingCondition.' "%a'.intval(SegmentsController::FLAG_COUNT).'a%"';
187 }
188
189 $automationHelper->groupBy = 'mail_id';
190 $campaignRecipientsMultilingual[$oneMail->campaign] = acym_loadObjectList(
191 $automationHelper->getQuery(['COUNT(DISTINCT user_list.`user_id`) AS elements', 'IF(mail.id IS NULL, '.intval($mailId).', `mail`.`id`) AS mail_id']),
192 'mail_id'
193 );
194 $automationHelper->removeFlag(SegmentsController::FLAG_COUNT);
195 }
196 $results['elements'][$i]->recipients = intval($campaignRecipientsMultilingual[$oneMail->campaign][$oneMail->id]->elements);
197 } else {
198 $results['elements'][$i]->recipients = intval($mailStatClass->getTotalSubscribersByMailId($mailId));
199 }
200 }
201 }
202
203 $automationNumber = acym_loadResult(
204 'SELECT COUNT(DISTINCT mail.id) FROM #__acym_mail AS mail
205 JOIN #__acym_queue AS queue
206 ON mail.id = queue.mail_id
207 WHERE mail.type = '.acym_escapeDB(MailClass::TYPE_AUTOMATION)
208 );
209 $followupNumber = acym_loadResult(
210 'SELECT COUNT(DISTINCT mail.id)
211 FROM #__acym_mail AS mail
212 JOIN #__acym_queue AS queue
213 ON mail.id = queue.mail_id
214 WHERE mail.type = '.acym_escapeDB(MailClass::TYPE_FOLLOWUP)
215 );
216
217 $elementsPerStatus = acym_loadObjectList($queryStatus.' GROUP BY active');
218 $queuedActiveCampaigns = 0;
219 $queuedPausedCampaigns = 0;
220 foreach ($elementsPerStatus as $element) {
221 if (is_null($element->active)) {
222 continue;
223 }
224
225 if (!empty($element->active)) {
226 $queuedActiveCampaigns = intval($element->number);
227 } else {
228 $queuedPausedCampaigns = intval($element->number);
229 }
230 }
231
232 $results['status'] = [
233 'all' => $queuedActiveCampaigns + $queuedPausedCampaigns + $automationNumber + $followupNumber,
234 'sending' => $queuedActiveCampaigns,
235 'paused' => $queuedPausedCampaigns,
236 'automation' => $automationNumber,
237 'followup' => $followupNumber,
238 ];
239
240 return $results;
241 }
242
243 /**
244 * Get campaigns depending on filters (search, status, pagination)
245 */
246 public function getMatchingScheduledCampaigns(array $settings): array
247 {
248 $mailClass = new MailClass();
249 $query = 'FROM #__acym_mail AS mail
250 JOIN #__acym_campaign AS campaign ON mail.id = campaign.mail_id OR mail.parent_id = campaign.mail_id ';
251
252 if (!empty($settings['tag'])) {
253 $query .= ' JOIN #__acym_tag AS tag ON mail.id = tag.id_element AND tag.type = "mail" AND tag.name = '.acym_escapeDB($settings['tag']);
254 }
255
256 $filters = [
257 'campaign.draft = 0',
258 'campaign.sent = 0',
259 'campaign.sending_type = '.acym_escapeDB(CampaignClass::SENDING_TYPE_SCHEDULED),
260 ];
261
262 if (!empty($settings['search'])) {
263 $filters[] = 'mail.subject LIKE '.acym_escapeDB('%'.$settings['search'].'%').' OR mail.name LIKE '.acym_escapeDB('%'.$settings['search'].'%');
264 }
265
266 $query .= ' WHERE ('.implode(') AND (', $filters).')';
267
268 $queryCount = 'SELECT COUNT(DISTINCT mail.id) AS total '.$query;
269 $query = 'SELECT mail.name, mail.subject, mail.id, campaign.sending_date, campaign.sending_params, mail.language, mail.parent_id '.$query.' GROUP BY mail.id ORDER BY campaign.sending_date ASC';
270
271 acym_query('SET SQL_BIG_SELECTS=1;');
272 $results['elements'] = $mailClass->decode(acym_loadObjectList($query, '', $settings['offset'], $settings['campaignsPerPage']));
273 $results['total'] = acym_loadObject($queryCount);
274
275 foreach ($results['elements'] as $i => $oneMail) {
276 $results['elements'][$i]->sending_params = empty($oneMail->sending_params) ? [] : json_decode($oneMail->sending_params, true);
277 $mailId = empty($oneMail->parent_id) ? $oneMail->id : $oneMail->parent_id;
278 $results['elements'][$i]->lists = acym_loadObjectList(
279 'SELECT l.color, l.name, l.id
280 FROM #__acym_list AS l
281 JOIN #__acym_mail_has_list AS ml ON ml.list_id = l.id
282 WHERE ml.mail_id = '.intval($mailId),
283 'id'
284 );
285 }
286
287 return $results;
288 }
289
290 /**
291 * Get mails depending on filters (search, status, pagination)
292 */
293 public function getMatchingResults(array $settings): array
294 {
295 $query = 'FROM #__acym_queue AS queue
296 JOIN #__acym_mail AS mail ON mail.id = queue.mail_id
297 JOIN #__acym_user AS user ON queue.user_id = user.id ';
298
299 $filters = [];
300
301 if (!empty($settings['tag'])) {
302 $query .= ' JOIN #__acym_tag AS tag ON queue.mail_id = tag.id_element AND tag.type = "mail" AND tag.name = '.acym_escapeDB($settings['tag']);
303 }
304
305 if (!empty($settings['search'])) {
306 $searchColumns = [
307 'user.email',
308 'user.name',
309 'mail.subject',
310 'mail.name',
311 ];
312
313 $filters[] = implode(' LIKE '.acym_escapeDB('%'.$settings['search'].'%').' OR ', $searchColumns).' LIKE '.acym_escapeDB('%'.$settings['search'].'%');
314 }
315
316 if (!empty($filters)) {
317 $query .= ' WHERE ('.implode(') AND (', $filters).')';
318 }
319
320 if (empty($settings['tag'])) {
321 $queryCount = 'SELECT COUNT(queue.mail_id) AS total '.$query;
322 } else {
323 $queryCount = 'SELECT COUNT(DISTINCT queue.mail_id, queue.user_id) AS total '.$query;
324 $query .= ' GROUP BY queue.mail_id, queue.user_id';
325 }
326
327 $query = 'SELECT mail.id, queue.sending_date, mail.name, mail.subject, user.email, user.name AS user_name, queue.user_id, queue.try '.$query.' ORDER BY queue.sending_date ASC';
328
329 $mailClass = new MailClass();
330
331 return [
332 'elements' => $mailClass->decode(acym_loadObjectList($query, '', $settings['offset'], $settings['elementsPerPage'])),
333 'total' => acym_loadObject($queryCount),
334 ];
335 }
336
337 public function scheduleReady(): ?int
338 {
339 $this->messages = [];
340
341 $mailClass = new MailClass();
342
343 $multilingualQuery = acym_isMultilingual() ? ' OR mail.parent_id = campaign.mail_id ' : '';
344
345 $mailReady = $mailClass->decode(
346 acym_loadObjectList(
347 'SELECT mail.id, campaign.sending_date, mail.name, campaign.mail_id AS parent_id, mail.language, campaign.sending_params
348 FROM #__acym_campaign AS campaign
349 JOIN #__acym_mail AS mail
350 ON campaign.mail_id = mail.id '.$multilingualQuery.'
351 WHERE campaign.sending_type = '.acym_escapeDB(CampaignClass::SENDING_TYPE_SCHEDULED).'
352 AND campaign.draft = 0
353 AND campaign.sending_date <= '.acym_escapeDB(acym_date('now', 'Y-m-d H:i:s', false)).'
354 AND campaign.sent = 0',
355 'id'
356 )
357 );
358
359 if (empty($mailReady)) {
360 return null;
361 }
362
363 $nbQueue = [];
364
365 foreach ($mailReady as $mailId => $mail) {
366 // A/B test campaigns are queued later by CampaignClass::send(), so skip queueing here
367 $sendingParams = $mail->sending_params ?? [];
368 if (is_string($sendingParams)) {
369 $sendingParams = json_decode($sendingParams, true) ?? [];
370 }
371
372 if (!empty($sendingParams['abtest']['repartition'])) {
373 $nbQueue[$mailId] = 0;
374 } else {
375 $nbQueue[$mailId] = $this->queue($mail);
376 $this->messages[] = acym_translationSprintf('ACYM_ADDED_QUEUE_SCHEDULE', $nbQueue[$mailId], '<b>'.$mail->name.'</b>');
377 }
378 }
379
380 $mailIds = array_keys($mailReady);
381 acym_arrayToInteger($mailIds);
382 $campaigns = acym_loadObjectList('SELECT id, mail_id FROM #__acym_campaign WHERE mail_id IN ('.implode(',', $mailIds).')');
383 $campaignClass = new CampaignClass();
384 foreach ($campaigns as $campaign) {
385 $result = $campaignClass->send($campaign->id, $nbQueue[$campaign->mail_id]);
386 if (!empty($result) || !acym_isMultilingual()) {
387 continue;
388 }
389
390 $translatedMails = acym_loadResultArray('SELECT id FROM #__acym_mail WHERE id != '.intval($campaign->mail_id).' AND parent_id = '.intval($campaign->mail_id));
391 if (empty($translatedMails)) {
392 continue;
393 }
394
395 foreach ($translatedMails as $translatedMailId) {
396 if (!empty($nbQueue[$translatedMailId])) {
397 $campaignClass->send($campaign->id, $nbQueue[$translatedMailId]);
398 }
399 }
400 }
401
402 return count($mailReady);
403 }
404
405 public function delete(array $elements): int
406 {
407 if (empty($elements)) return 0;
408 acym_arrayToInteger($elements);
409
410 $query = 'DELETE FROM #__acym_queue WHERE mail_id IN ('.implode(',', $elements).')';
411 $result = acym_query($query);
412
413 acym_query('UPDATE #__acym_campaign SET draft = 1, active = 1 WHERE mail_id IN ('.implode(',', $elements).')');
414
415 if (!$result) {
416 return 0;
417 }
418
419 return $result;
420 }
421
422 public function deleteQueuedByUserIds(array $userIds, ?int $mailId = null): int
423 {
424 acym_arrayToInteger($userIds);
425 if (empty($userIds)) {
426 return 0;
427 }
428
429 $query = 'DELETE FROM #__acym_queue WHERE user_id IN ('.implode(',', $userIds).')';
430 if (!empty($mailId)) {
431 $query .= ' AND mail_id = '.intval($mailId);
432 }
433
434 try {
435 $nbDeleted = acym_query($query);
436
437 if ($nbDeleted === false) {
438 $this->errors[] = acym_getDBError();
439
440 return 0;
441 }
442
443 return (int)$nbDeleted;
444 } catch (\Exception $e) {
445 $this->errors[] = $e->getMessage();
446
447 return 0;
448 }
449 }
450
451 public function getReady(int $startFrom, int $limit, int $mailId = 0): array
452 {
453 if (empty($limit)) {
454 return [];
455 }
456
457 $query = 'SELECT queue.*, campaign.sending_params AS sending_params FROM #__acym_queue AS queue';
458 $query .= ' JOIN #__acym_user AS user ON queue.`user_id` = user.`id` ';
459 $query .= ' JOIN #__acym_mail AS mail ON queue.`mail_id` = mail.`id` ';
460 $query .= ' LEFT JOIN #__acym_campaign AS campaign ON campaign.`mail_id` = mail.`id` ';
461 $query .= ' WHERE user.active = 1
462 AND queue.`sending_date` <= '.acym_escapeDB(acym_date('now', 'Y-m-d H:i:s', false)).'
463 AND (campaign.mail_id IS NULL
464 OR (campaign.`active` = 1
465 AND campaign.`draft` = 0
466 )
467 )';
468
469 if ($this->config->get('require_confirmation', 1) == 1) {
470 $query .= ' AND (user.confirmed = 1 OR mail.type = '.acym_escapeDB(MailClass::TYPE_NOTIFICATION).' OR mail.name LIKE "%confirm%")';
471 }
472
473 if (!empty($this->emailtypes)) {
474 foreach ($this->emailtypes as &$oneType) {
475 $oneType = acym_escapeDB($oneType);
476 }
477 $query .= ' AND mail.type IN ('.implode(', ', $this->emailtypes).')';
478 }
479
480 if (!empty($mailId)) {
481 $query .= ' AND queue.`mail_id` = '.intval($mailId);
482 }
483
484 // We don't display this option in the configuration anymore but we use its value if it's set in the database
485 $sendOrder = $this->config->get('sendorder');
486 if (empty($sendOrder)) {
487 $order = 'queue.`user_id` ASC';
488 } elseif ($sendOrder === 'rand') {
489 $order = 'RAND()';
490 } else {
491 $sendOrder = str_replace('subid', 'user_id', $sendOrder);
492 $ordering = explode(',', $sendOrder);
493 $order = 'queue.`'.acym_secureDBColumn(trim($ordering[0])).'` '.acym_secureDBColumn(trim($ordering[1] ?? 'ASC'));
494 }
495
496 $query .= ' ORDER BY queue.`priority` ASC, queue.`sending_date` ASC, '.$order;
497 // You can add a "startqueue" parameter to the url so Acy will not load the first e-mails but will start directly with the 300 or 500 or...
498 $query .= ' LIMIT '.intval($startFrom).','.intval($limit);
499
500 try {
501 $results = acym_loadObjectList($query);
502 } catch (\Exception $e) {
503 $results = null;
504 }
505
506 if ($results === null) {
507 // We got an issue here... maybe the table is crashed so we will repair it.
508 acym_query('REPAIR TABLE #__acym_queue, #__acym_user, #__acym_mail, #__acym_campaign');
509 }
510
511 if (empty($results)) {
512 return [];
513 }
514
515 // This comment doesn't make any sense
516 //We update the first entry from the queue and change its sending_date with +1 so it does not get sent immediately after in case of we had an issue (a time out execution)...
517 //That way e-mails which can't be sent will be sent at the end and we will be able to clean the queue and don't care about what's left in the queue any more
518 //Also it will avoid the same user to receive messages again and again and again in case of there is a problem
519 $firstElementQueued = reset($results);
520 acym_query(
521 'UPDATE #__acym_queue
522 SET sending_date = DATE_ADD(sending_date, INTERVAL 1 SECOND)
523 WHERE mail_id = '.intval($firstElementQueued->mail_id).' AND user_id = '.intval($firstElementQueued->user_id).'
524 LIMIT 1'
525 );
526
527 return $results;
528 }
529
530 public function delayFailed(int $mailId, array $userIds): void
531 {
532 acym_arrayToInteger($userIds);
533 if (empty($mailId) || empty($userIds)) {
534 return;
535 }
536
537 acym_query(
538 'UPDATE #__acym_queue
539 SET sending_date = DATE_ADD(sending_date, INTERVAL 1 HOUR), try = try +1
540 WHERE mail_id = '.intval($mailId).'
541 AND user_id IN ('.implode(',', $userIds).')'
542 );
543 }
544
545 public function delayAll(int $hours): void
546 {
547 if ($hours < 1) {
548 return;
549 }
550
551 acym_query(
552 'UPDATE #__acym_queue
553 SET sending_date = DATE_ADD(sending_date, INTERVAL '.$hours.' HOUR)
554 WHERE sending_date < DATE_ADD(NOW(), INTERVAL '.intval($hours).' HOUR)'
555 );
556 }
557
558 public function getMailReceivers(object $mail, bool $onlyNew = false): AutomationHelper
559 {
560 if (empty($mail->sending_params)) {
561 $sendingParams = [];
562 $mail->filters = [];
563 } else {
564 $sendingParams = is_array($mail->sending_params) ? $mail->sending_params : json_decode($mail->sending_params, true);
565
566 $campaignClass = new CampaignClass();
567 $mail->filters = $campaignClass->getFilterCampaign($sendingParams);
568
569 if (!empty($sendingParams['resendTarget']) && 'new' === $sendingParams['resendTarget']) {
570 $onlyNew = true;
571 }
572 }
573
574 $automationHelper = new AutomationHelper();
575 $automationHelper->join['userlist'] = ' #__acym_user_has_list AS userlist ON user.id = userlist.user_id';
576 $automationHelper->join['maillist'] = ' #__acym_mail_has_list AS maillist ON userlist.list_id = maillist.list_id';
577 $automationHelper->where = [
578 'userlist.status = 1',
579 'maillist.mail_id = '.intval(empty($mail->parent_id) ? $mail->id : $mail->parent_id),
580 ];
581
582 // Send this version only to the users with the correct language
583 if (!$onlyNew && acym_isMultilingual()) {
584 $where = 'user.language = '.acym_escapeDB($mail->language);
585 if ($mail->id == $mail->parent_id) {
586 //TODO check if a user with no language will receive every versions or only the main one
587 $where .= ' OR user.language = "" OR user.language NOT IN (SELECT language FROM #__acym_mail WHERE parent_id = '.intval($mail->id).')';
588 }
589 $automationHelper->where[] = $where;
590 }
591
592 if ($this->config->get('require_confirmation', 1) == 1) {
593 $automationHelper->where[] = '`user`.`confirmed` = 1';
594 }
595
596 if ($onlyNew) {
597 $automationHelper->leftjoin['us'] = '`#__acym_user_stat` AS `us` ON `us`.`user_id` = `user`.`id` AND `us`.`mail_id` IN (SELECT id FROM #__acym_mail WHERE parent_id = '.intval(
598 $mail->id
599 ).' OR id = '.intval($mail->id).')';
600 $automationHelper->where[] = '`us`.`user_id` IS NULL';
601
602 // Do not count the disabled user for the resend counter on summary
603 $automationHelper->where[] = '`user`.`active` = 1';
604 }
605
606 $automationHelper->removeFlag(SegmentsController::FLAG_USERS);
607 $automationHelper->removeFlag(SegmentsController::FLAG_COUNT);
608
609 // Handle potential segment
610 if (empty($mail->filters)) {
611 return $automationHelper;
612 }
613
614 // Mark users matching the segment
615 foreach ($mail->filters as $orValues) {
616 if (empty($orValues)) continue;
617
618 $automationHelperSegment = new AutomationHelper();
619 foreach ($orValues as $and => $andValues) {
620 $and = intval($and);
621 foreach ($andValues as $filterName => $options) {
622 acym_trigger('onAcymProcessFilter_'.$filterName, [&$automationHelperSegment, &$options, &$and]);
623 }
624 }
625 $automationHelperSegment->addFlag(SegmentsController::FLAG_COUNT);
626 }
627
628 $segmentMatchingCondition = 'LIKE';
629 if (!empty($sendingParams['segment']['invert']) && $sendingParams['segment']['invert'] === 'exclude') {
630 $segmentMatchingCondition = 'NOT LIKE';
631 }
632 $automationHelper->where[] = 'user.automation '.$segmentMatchingCondition.' "%a'.intval(SegmentsController::FLAG_COUNT).'a%"';
633
634 return $automationHelper;
635 }
636
637 public function queue(object $mail): int
638 {
639 $automationHelper = $this->getMailReceivers($mail);
640 // Only queue enabled users
641 $automationHelper->where[] = '`user`.`active` = 1';
642
643 $priority = $this->config->get('priority_newsletter', 3);
644 $select = [intval($mail->id), 'userlist.user_id', acym_escapeDB($mail->sending_date), intval($priority), '0'];
645 $inserted = acym_query('INSERT IGNORE INTO #__acym_queue (`mail_id`, `user_id`, `sending_date`, `priority`, `try`) ('.$automationHelper->getQuery($select).')');
646
647 $automationHelper->removeFlag(SegmentsController::FLAG_COUNT);
648
649 return (int)$inserted;
650 }
651
652 public function addQueue(int $userId, int $mailId, string $sendingDate): int
653 {
654 $priority = $this->config->get('priority_newsletter', 3);
655
656 return (int)acym_query(
657 'INSERT IGNORE INTO #__acym_queue (`mail_id`, `user_id`, `sending_date`, `priority`, `try`) VALUES ('.intval($mailId).', '.intval($userId).', '.acym_escapeDB(
658 $sendingDate
659 ).', '.intval($priority).', 0)'
660 );
661 }
662
663 public function unpauseCampaign(int $campaignId, int $active): void
664 {
665 if (acym_query('UPDATE #__acym_campaign SET active = '.intval($active).' WHERE id = '.intval($campaignId))) {
666 acym_enqueueMessage(acym_translation($active ? 'ACYM_UNPAUSE_CAMPAIGN_SUCCESSFUL' : 'ACYM_PAUSE_CAMPAIGN_SUCCESSFUL'), "success");
667 } else {
668 acym_enqueueMessage(acym_translation($active ? 'ACYM_UNPAUSE_CAMPAIGN_FAIL' : 'ACYM_PAUSE_CAMPAIGN_FAIL'), "error");
669 }
670 }
671
672 public function emptyQueue(): int
673 {
674 return (int)acym_query('DELETE FROM `#__acym_queue`');
675 }
676
677 public function cleanQueue(): int
678 {
679 $twoDaysEarlier = acym_date(time() - 172800, 'Y-m-d H:i:s', false);
680
681 $conditionUser = '`user`.`active` = 0';
682 if ($this->config->get('require_confirmation', 1) == 1) {
683 $conditionUser .= ' OR `user`.`confirmed` = 0';
684 }
685
686 $numberOfDaysToWait = $this->config->get('queue_delete_days', 0);
687 $conditionDateDelete = '';
688 if (!empty($numberOfDaysToWait)) {
689 $dateTimeConditionDelete = acym_date(time() - ($numberOfDaysToWait * 86400), 'Y-m-d H:i:s', false);
690 $conditionDateDelete = ' OR (`queue`.`sending_date` < '.acym_escapeDB($dateTimeConditionDelete).')';
691 }
692
693 return (int)acym_query(
694 'DELETE `queue`.*
695 FROM `#__acym_queue` AS `queue`
696 JOIN `#__acym_user` AS `user` ON `queue`.`user_id` = `user`.`id`
697 WHERE (('.$conditionUser.') AND `queue`.`sending_date` < '.acym_escapeDB($twoDaysEarlier).') '.$conditionDateDelete
698 );
699 }
700
701 public function isSendingFinished(int $mailId): bool
702 {
703 $mailClass = new MailClass();
704 $mail = $mailClass->getOneById($mailId);
705
706 if (empty($mail) || $mailClass->isTransactionalMail($mail)) return false;
707
708 $filters = [
709 '`queue`.`mail_id` = '.intval($mailId),
710 '`user`.`active` = 1',
711 ];
712 if ($this->config->get('require_confirmation')) {
713 $filters[] = '`user`.`confirmed` = 1';
714 }
715
716 $res = intval(
717 acym_loadResult(
718 'SELECT COUNT(`queue`.`mail_id`)
719 FROM #__acym_queue AS `queue`
720 JOIN #__acym_user AS `user`
721 ON `queue`.`user_id` = `user`.`id`
722 WHERE '.implode(' AND ', $filters)
723 )
724 );
725
726 return empty($res);
727 }
728
729 public function getQueueParams(int $mailId, int $userId): array
730 {
731 $query = acym_loadObject(
732 'SELECT `params` FROM #__acym_queue WHERE `user_id` = '.intval($userId).' AND `mail_id` = '.$mailId
733 );
734
735 return empty($query) ? [] : json_decode((string)$query->params, true);
736 }
737 }
738