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/CampaignClass.php +220 -134 10.2.2 → 11.1.0 View file →
@@ -20,10 +20,8 @@
20 20 self::SENDING_TYPE_AUTO,
21 21 ];
22 22 const AB_TEST_VERSIONS = ['B'];
23 23
24 - private array $jsonColumns = ['sending_params'];
25 -
26 24 public function __construct()
27 25 {
28 26 parent::__construct();
29 27
@@ -28,11 +26,25 @@
28 26 parent::__construct();
29 27
30 28 $this->table = 'campaign';
31 29 $this->pkey = 'id';
30 + $this->jsonColumns = ['sending_params'];
31 + $this->intColumns = [
32 + 'id',
33 + 'mail_id',
34 + 'parent_id',
35 + 'draft',
36 + 'active',
37 + 'sent',
38 + 'visible',
39 + 'subscribers',
40 + 'open_unique',
41 + 'tracking_sale',
42 + 'tracking',
43 + ];
32 44 }
33 45
34 - public function decode($campaign, $decodeMail = true)
46 + public function decode($campaign, bool $decodeMail = true)
35 47 {
36 48 if (empty($campaign)) return $campaign;
37 49
38 50 if (is_array($campaign)) {
@@ -38,16 +50,12 @@
38 50 if (is_array($campaign)) {
39 51 foreach ($campaign as $i => $oneCampaign) {
40 52 $campaign[$i] = $this->decode($oneCampaign, false);
41 53 }
54 + } else {
55 + $this->fixTypes($campaign);
42 56 }
43 57
44 - foreach ($this->jsonColumns as $oneColumn) {
45 - if (!isset($campaign->$oneColumn)) continue;
46 -
47 - $campaign->$oneColumn = empty($campaign->$oneColumn) ? [] : json_decode($campaign->$oneColumn, true);
48 - }
49 -
50 58 if ($decodeMail) {
51 59 $mailClass = new MailClass();
52 60 $campaign = $mailClass->decode($campaign);
53 61 }
@@ -88,8 +96,9 @@
88 96 if ($this->config->get('front_campaigns_filter', 'own') === 'allowed') {
89 97 $listClass = new ListClass();
90 98 $allowedLists = $listClass->getManageableLists();
91 99 if (!empty($allowedLists)) {
100 + acym_arrayToInteger($allowedLists);
92 101 $query .= ' LEFT JOIN #__acym_mail_has_list AS mailList ON campaign.mail_id = mailList.mail_id AND mailList.list_id IN ('.implode(',', $allowedLists).')';
93 102 $queryCount .= ' LEFT JOIN #__acym_mail_has_list AS mailList ON campaign.mail_id = mailList.mail_id AND mailList.list_id IN ('.implode(',', $allowedLists).')';
94 103 $condition .= ' OR mailList.list_id IS NOT NULL';
95 104 }
@@ -144,11 +153,12 @@
144 153 }
145 154
146 155 $query .= ' GROUP BY campaign.id';
147 156
148 - if (!empty($settings['ordering']) && !empty($settings['ordering_sort_order'])) {
157 + if (!empty($settings['ordering'])) {
149 158 $table = in_array($settings['ordering'], ['name', 'creation_date']) ? 'mail' : 'campaign';
150 - $query .= ' ORDER BY '.$table.'.'.acym_secureDBColumn($settings['ordering']).' '.acym_secureDBColumn(strtoupper($settings['ordering_sort_order']));
159 + $order = isset($settings['ordering_sort_order']) && strtolower($settings['ordering_sort_order']) === 'asc' ? 'ASC' : 'DESC';
160 + $query .= ' ORDER BY '.$table.'.'.acym_secureDBColumn($settings['ordering']).' '.acym_secureDBColumn($order);
151 161 }
152 162
153 163 if (empty($settings['offset']) || $settings['offset'] < 0) {
154 164 $settings['offset'] = 0;
@@ -228,9 +238,9 @@
228 238
229 239 return $results;
230 240 }
231 241
232 - private function prepareStatsCampaign(&$element)
242 + private function prepareStatsCampaign(object $element): void
233 243 {
234 244 $query = 'SELECT SUM(mailstat.sent) AS subscribers, SUM(mailstat.open_unique) AS open_unique, SUM(mailstat.tracking_sale) AS tracking_sale
235 245 FROM #__acym_mail_stat AS mailstat
236 246 LEFT JOIN #__acym_mail AS mail ON mail.id = mailstat.mail_id WHERE mail.parent_id = '.intval($element->mail_id);
@@ -239,22 +249,29 @@
239 249 $element->open_unique += $stats->open_unique;
240 250 $element->tracking_sale += $stats->tracking_sale;
241 251 }
242 252
243 - public function getStatsCampaign(&$element, $urlClickClass, $isAbTest = false)
253 + public function getStatsCampaign(object $element, UrlClickClass $urlClickClass, bool $isAbTest = false): void
244 254 {
245 255 $element->open = 0;
246 256 $element->click = 0;
247 257
248 258 if (!empty($element->subscribers)) {
249 - $element->open = number_format($element->open_unique / $element->subscribers * 100, 2);
259 + // In anonymous mode opens/clicks are counted globally (totals, not unique subscribers), so a percentage of recipients is meaningless: expose the average number of opens/clicks per email sent instead (e.g. 3.25), rendered as "3.25×" in the view
260 + $anonymousStats = !empty($this->config->get('anonymous_stats', 0));
261 + $clicksNb = $urlClickClass->getNumberUsersClicked([$element->mail_id], $isAbTest);
250 262
251 - $clicksNb = $urlClickClass->getNumberUsersClicked($element->mail_id, $isAbTest);
252 - $element->click = number_format($clicksNb / $element->subscribers * 100, 2);
263 + if ($anonymousStats) {
264 + $element->open = number_format($element->open_unique / $element->subscribers, 2);
265 + $element->click = number_format($clicksNb / $element->subscribers, 2);
266 + } else {
267 + $element->open = number_format($element->open_unique / $element->subscribers * 100, 2);
268 + $element->click = number_format($clicksNb / $element->subscribers * 100, 2);
269 + }
253 270 }
254 271 }
255 272
256 - public function getTrackingSales(&$element)
273 + public function getTrackingSales(object $element): void
257 274 {
258 275 $element->sale = 0;
259 276 $element->currency = '';
260 277
@@ -271,32 +288,48 @@
271 288 }
272 289 $this->formatSaleTracking($element, $trackingSales);
273 290 }
274 291
275 - private function getStatsCampaignAuto(&$element, $urlClickClass)
292 + private function getStatsCampaignAuto(object $element, UrlClickClass $urlClickClass): void
276 293 {
277 294 $generatedMailsStats = acym_loadObjectList(
278 - 'SELECT mail_stat.* FROM #__acym_mail AS mail JOIN #__acym_mail_stat AS mail_stat ON mail.id = mail_stat.mail_id WHERE mail.id IN (SELECT mail_id FROM #__acym_campaign WHERE parent_id = '.intval(
279 - $element->id
280 - ).')'
295 + 'SELECT mail_stat.*
296 + FROM #__acym_mail AS mail
297 + JOIN #__acym_mail_stat AS mail_stat
298 + ON mail.id = mail_stat.mail_id
299 + WHERE mail.id IN (SELECT mail_id FROM #__acym_campaign WHERE parent_id = '.intval($element->id).')'
281 300 );
301 +
282 302 $element->open = 0;
283 303 $element->click = 0;
284 304 $element->subscribers = 0;
285 - if (empty($generatedMailsStats)) return;
305 + if (empty($generatedMailsStats)) {
306 + return;
307 + }
286 308
287 309 foreach ($generatedMailsStats as $key => $mailsStat) {
288 310 $element->open += $mailsStat->open_unique;
289 - $element->click += $urlClickClass->getNumberUsersClicked($element->id);
311 + $element->click += $urlClickClass->getNumberUsersClicked([$element->id]);
290 312 $element->subscribers += $mailsStat->sent;
291 313 }
292 314
293 315 if (!empty($element->subscribers)) {
294 - $element->open = number_format($element->open / $element->subscribers * 100, 2);
295 - $element->click = number_format($element->click / $element->subscribers * 100, 2);
316 + // In anonymous mode opens/clicks are counted globally (totals, not unique subscribers), so expose the average number of opens/clicks per email sent (e.g. 3.25) instead of a percentage, rendered as "3.25×" in the view
317 + $anonymousStats = !empty($this->config->get('anonymous_stats', 0));
318 +
319 + if ($anonymousStats) {
320 + $element->open = number_format($element->open / $element->subscribers, 2);
321 + $element->click = number_format($element->click / $element->subscribers, 2);
322 + } else {
323 + $element->open = number_format($element->open / $element->subscribers * 100, 2);
324 + $element->click = number_format($element->click / $element->subscribers * 100, 2);
325 + }
296 326 }
297 327
298 - if (!acym_isTrackingSalesActive()) return;
328 + //Tracking sales
329 + if (!acym_isTrackingSalesActive()) {
330 + return;
331 + }
299 332
300 333 $trackingSales = acym_loadObject(
301 334 'SELECT SUM(mail_stat.tracking_sale) AS sale, mail_stat.currency
302 335 FROM #__acym_mail_stat AS mail_stat
@@ -304,9 +337,9 @@
304 337 );
305 338 $this->formatSaleTracking($element, $trackingSales);
306 339 }
307 340
308 - private function formatSaleTracking(&$element, $trackingSales)
341 + private function formatSaleTracking(object $element, object $trackingSales): void
309 342 {
310 343 $element->sale = $trackingSales->sale;
311 344 if (empty($element->currency)) $element->currency = '';
312 345 acym_trigger('getCurrency', [&$element->currency]);
@@ -311,43 +344,44 @@
311 344 if (empty($element->currency)) $element->currency = '';
312 345 acym_trigger('getCurrency', [&$element->currency]);
313 346 }
314 347
315 - public function getOneById($id)
348 + public function getOneById(int $id): ?object
316 349 {
317 - return $this->decode(acym_loadObject('SELECT campaign.* FROM #__acym_campaign AS campaign WHERE campaign.id = '.intval($id)));
350 + $campaign = parent::getOneById($id);
351 +
352 + return empty($campaign) ? null : $this->decode($campaign);
318 353 }
319 354
320 - public function getOneByIdWithMail($id)
355 + public function getOneByMailId(int $mailId): ?object
321 356 {
322 - $query = 'SELECT campaign.*, mail.name, mail.subject, mail.body, mail.from_name, mail.from_email, mail.reply_to_name, mail.reply_to_email, mail.bcc, mail.links_language, mail.tracking, mail.translation, mail.bounce_email
323 - FROM #__acym_campaign AS campaign
324 - JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id
325 - WHERE campaign.id = '.intval($id);
357 + $campaign = acym_loadObject(
358 + 'SELECT *
359 + FROM #__acym_campaign
360 + WHERE `mail_id` = '.intval($mailId)
361 + );
326 362
327 - return $this->decode(acym_loadObject($query));
363 + return empty($campaign) ? null : $campaign;
328 364 }
329 365
330 - public function get($identifier, $column = 'id')
366 + public function getOneByIdWithMail(int $id): ?object
331 367 {
332 - return $this->decode(acym_loadObject('SELECT campaign.* FROM #__acym_campaign AS campaign WHERE campaign.'.acym_secureDBColumn($column).' = '.acym_escapeDB($identifier)));
333 - }
368 + $campaign = acym_loadObject(
369 + 'SELECT campaign.*, mail.name, mail.subject, mail.body, mail.from_name, mail.from_email, mail.reply_to_name, mail.reply_to_email, mail.bcc, mail.links_language, mail.tracking, mail.translation, mail.bounce_email
370 + FROM #__acym_campaign AS campaign
371 + JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id
372 + WHERE campaign.id = '.intval($id)
373 + );
334 374
335 - public function getAllCampaignsNameMailId()
336 - {
337 - $query = 'SELECT m.id, m.name
338 - FROM #__acym_campaign AS c
339 - LEFT JOIN #__acym_mail AS m ON c.mail_id = m.id';
340 -
341 - return $this->decode(acym_loadObjectList($query));
375 + return empty($campaign) ? null : $this->decode($campaign);
342 376 }
343 377
344 - public function getOneCampaignByMailId($mailId)
378 + public function getOneCampaignByMailId(int $mailId)
345 379 {
346 380 return $this->decode(acym_loadObject('SELECT * FROM #__acym_campaign WHERE mail_id = '.intval($mailId)));
347 381 }
348 382
349 - public function getAutoCampaignFromGeneratedMailId($mailId)
383 + public function getAutoCampaignFromGeneratedMailId(int $mailId)
350 384 {
351 385 $queryCampaign = 'SELECT * FROM #__acym_campaign WHERE id = (SELECT parent_id FROM #__acym_campaign WHERE mail_id = '.intval($mailId).')';
352 386
353 387 return $this->decode(acym_loadObject($queryCampaign));
@@ -352,9 +386,9 @@
352 386
353 387 return $this->decode(acym_loadObject($queryCampaign));
354 388 }
355 389
356 - public function manageListsToCampaign($listsIds, $mailId, $unselectedListIds = [])
390 + public function manageListsToCampaign($listsIds, int $mailId, array $unselectedListIds = []): bool
357 391 {
358 392 if (!empty($unselectedListIds)) {
359 393 acym_arrayToInteger($unselectedListIds);
360 394 acym_query('DELETE FROM #__acym_mail_has_list WHERE mail_id = '.intval($mailId).' AND list_id IN ('.implode(', ', $unselectedListIds).')');
@@ -362,11 +396,22 @@
362 396
363 397 acym_arrayToInteger($listsIds);
364 398 if (empty($listsIds)) return false;
365 399
400 + $listsIds = array_unique($listsIds);
401 +
402 + $existingListsIds = acym_loadResultArray('SELECT `id` FROM #__acym_list WHERE `id` IN ('.implode(',', $listsIds).')');
403 + acym_arrayToInteger($existingListsIds);
404 +
405 + $unknownListsIds = array_diff($listsIds, $existingListsIds);
406 + if (!empty($unknownListsIds)) {
407 + $this->errors[] = acym_translationSprintf('ACYM_LIST_NOT_FOUND', implode(', ', $unknownListsIds));
408 + }
409 +
410 + if (empty($existingListsIds)) return false;
411 +
366 412 $values = [];
367 - $listsIds = array_unique($listsIds);
368 - foreach ($listsIds as $listId) {
413 + foreach ($existingListsIds as $listId) {
369 414 $values[] = '('.intval($mailId).', '.intval($listId).')';
370 415 }
371 416
372 417 if (!empty($values)) {
@@ -375,11 +420,11 @@
375 420
376 421 return true;
377 422 }
378 423
379 - public function save($campaignToSave)
424 + public function save(object $element): ?int
380 425 {
381 - $campaign = clone $campaignToSave;
426 + $campaign = clone $element;
382 427 if (isset($campaign->tags)) {
383 428 $tags = $campaign->tags;
384 429 unset($campaign->tags);
385 430 }
@@ -388,9 +433,9 @@
388 433 if (in_array($oneAttribute, $this->jsonColumns)) {
389 434 $campaign->$oneAttribute = json_encode(empty($value) ? [] : $value);
390 435 } else {
391 436 if (empty($value)) continue;
392 - $campaign->$oneAttribute = strip_tags($value);
437 + $campaign->$oneAttribute = acym_stripTags($value);
393 438 }
394 439 }
395 440
396 441 $campaignID = parent::save($campaign);
@@ -402,9 +447,9 @@
402 447
403 448 return $campaignID;
404 449 }
405 450
406 - public function onlyManageableCampaigns(&$elements)
451 + public function onlyManageableCampaigns(array &$elements): void
407 452 {
408 453 if (acym_isAdmin()) return;
409 454
410 455 $idCurrentUser = acym_currentUserId();
@@ -425,13 +470,13 @@
425 470 if (empty($userId)) {
426 471 return false;
427 472 }
428 473
429 - if (acym_isAdmin()) {
474 + if (acym_isAdmin() && acym_isAllowed('campaigns')) {
430 475 return true;
431 476 }
432 477
433 - $query = 'SELECT COUNT(*) FROM #__acym_campaign AS campaign
478 + $query = 'SELECT COUNT(*) FROM #__acym_campaign AS campaign
434 479 JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id ';
435 480
436 481 $condition = 'mail.creator_id = '.intval($userId);
437 482 if ($this->config->get('front_campaigns_filter', 'own') === 'allowed') {
@@ -447,11 +492,14 @@
447 492
448 493 return acym_loadResult($query) > 0;
449 494 }
450 495
451 - public function delete($elements)
496 + /**
497 + * Delete a campaign. Needs to delete the tag associated with the campaign and the mail attached to the campaign.
498 + * Deleting a mail of a campaign needs to clean the association table mail_has_list and its tags
499 + */
500 + public function delete(array $elements): int
452 501 {
453 - if (!is_array($elements)) $elements = [$elements];
454 502 acym_arrayToInteger($elements);
455 503 $this->onlyManageableCampaigns($elements);
456 504
457 505 if (empty($elements)) return 0;
@@ -467,9 +515,9 @@
467 515
468 516 return parent::delete($elements);
469 517 }
470 518
471 - public function countUsersCampaign($campaignID, $onlyActive = false): int
519 + public function countUsersCampaign(int $campaignID, bool $onlyActive = false): int
472 520 {
473 521 $campaign = $this->getOneById($campaignID);
474 522 if (empty($campaign)) {
475 523 return 0;
@@ -528,29 +576,32 @@
528 576 return $numberOfRecipients;
529 577 }
530 578 }
531 579
532 - public function getFilterCampaign($sendingParams)
580 + public function getFilterCampaign($sendingParams): array
533 581 {
534 - $filters = [];
582 + if (empty($sendingParams['segment'])) {
583 + return [];
584 + }
535 585
536 - if (!empty($sendingParams['segment'])) {
537 - if (!empty($sendingParams['segment']['filters'])) {
538 - $filters = $sendingParams['segment']['filters'];
539 - } elseif (!empty($sendingParams['segment']['segment_id'])) {
540 - $segmentClass = new SegmentClass();
541 - $segment = $segmentClass->getOneById($sendingParams['segment']['segment_id']);
542 - if (!empty($segment)) {
543 - $filters = $segment->filters;
544 - }
586 + if (!empty($sendingParams['segment']['filters'])) {
587 + return $sendingParams['segment']['filters'];
588 + }
589 +
590 + if (!empty($sendingParams['segment']['segment_id'])) {
591 + $segmentClass = new SegmentClass();
592 + $segment = $segmentClass->getOneById($sendingParams['segment']['segment_id']);
593 + if (!empty($segment)) {
594 + return $segment->filters;
545 595 }
546 596 }
547 597
548 - return $filters;
598 + return [];
549 599 }
550 600
551 - public function send($campaignID, $result = 0, $abTestFinal = false)
601 + public function send(int $campaignID, int $result = 0, bool $abTestFinal = false)
552 602 {
603 + // Make sure the email we're trying to send exists
553 604 $campaign = $this->getOneById($campaignID);
554 605
555 606 if (empty($campaign->mail_id)) {
556 607 $this->errors[] = acym_translation('ACYM_EMAIL_NOT_FOUND');
@@ -564,17 +615,20 @@
564 615 if (empty($filters)) {
565 616 $filters = [0 => []];
566 617 acym_trigger('onAcymSendCampaignSpecial', [$campaign, &$filters[0], &$pluginIsExisting]);
567 618 } else {
619 + // Adds the special campaigns conditions to the "OR" blocs of the segment
568 620 foreach ($filters as $key => $filter) {
569 621 acym_trigger('onAcymSendCampaignSpecial', [$campaign, &$filters[$key], &$pluginIsExisting]);
570 622 }
571 623 }
572 624
625 + // This is a special campaign type, but the required plugin is not installed
573 626 if (!$pluginIsExisting) {
574 627 return false;
575 628 }
576 629
630 + // Make sure some receivers have been selected
577 631 $lists = acym_loadResultArray('SELECT list_id FROM #__acym_mail_has_list WHERE mail_id = '.intval($campaign->mail_id));
578 632 if (empty($lists)) {
579 633 $this->errors[] = acym_translation('ACYM_NO_LIST_SELECTED');
580 634
@@ -631,8 +685,9 @@
631 685 } else {
632 686 $select = [intval($campaign->mail_id), 'ul.`user_id`', acym_escapeDB($date)];
633 687 }
634 688
689 + // Resending a campaign only to users who didn't receive it
635 690 if (!empty($campaign->sending_params['resendTarget']) && 'new' === $campaign->sending_params['resendTarget']) {
636 691 if (acym_isMultilingual()) {
637 692 $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(
638 693 $campaign->mail_id
@@ -654,10 +709,11 @@
654 709 $numberUsersInsertedByMailId[intval($campaign->mail_id)] = acym_query(
655 710 'INSERT IGNORE INTO `#__acym_queue` (`mail_id`, `user_id`, `sending_date`) '.$automationHelper->getQuery($select1)
656 711 );
657 712
713 + // If we have an odd number of users to send, we send one more to the first mail
658 714 $numberOfUsersToSend2 = $numberOfUsersToSend1 * 2 > $numberOfUsersToSend ? $numberOfUsersToSend1 - 1 : $numberOfUsersToSend1;
659 - $automationHelper->limit = $numberOfUsersToSend.', '.$numberOfUsersToSend2;
715 + $automationHelper->limit = $numberOfUsersToSend1.', '.$numberOfUsersToSend2;
660 716 $select2 = [intval($campaign->sending_params['abtest']['B']), 'ul.`user_id`', acym_escapeDB($date)];
661 717 $numberUsersInsertedByMailId[intval($campaign->sending_params['abtest']['B'])] = acym_query(
662 718 'INSERT IGNORE INTO `#__acym_queue` (`mail_id`, `user_id`, `sending_date`) '.$automationHelper->getQuery($select2)
663 719 );
@@ -690,13 +746,12 @@
690 746 }
691 747
692 748 $mailStatClass = new MailStatClass();
693 749 foreach ($numberUsersInsertedByMailId as $mailId => $numberOfUsersInserted) {
694 - $newMailStat = [
695 - 'mail_id' => intval($mailId),
696 - 'total_subscribers' => intval($numberOfUsersInserted),
697 - 'send_date' => $date,
698 - ];
750 + $newMailStat = new \stdClass();
751 + $newMailStat->mail_id = intval($mailId);
752 + $newMailStat->total_subscribers = intval($numberOfUsersInserted);
753 + $newMailStat->send_date = $date;
699 754
700 755 $mailStatClass->save($newMailStat);
701 756 }
702 757
@@ -710,22 +765,29 @@
710 765
711 766 return $result;
712 767 }
713 768
714 - public function getCampaignForDashboard()
769 + public function getCampaignForDashboard(): array
715 770 {
716 - $query = 'SELECT campaign.*, mail.name as name FROM #__acym_campaign as campaign LEFT JOIN #__acym_mail as mail ON campaign.mail_id = mail.id WHERE `active` = 1 AND `sending_type` = '.acym_escapeDB(
717 - self::SENDING_TYPE_SCHEDULED
718 - ).' AND `sent` = 0 LIMIT 3';
771 + $campaigns = acym_loadObjectList(
772 + 'SELECT campaign.*, mail.name AS name
773 + FROM #__acym_campaign AS campaign
774 + LEFT JOIN #__acym_mail AS mail
775 + ON campaign.mail_id = mail.id
776 + WHERE `active` = 1
777 + AND `sent` = 0
778 + AND `sending_type` = '.acym_escapeDB(self::SENDING_TYPE_SCHEDULED).'
779 + LIMIT 3'
780 + );
719 781
720 - return $this->decode(acym_loadObjectList($query));
782 + return $this->decode($campaigns);
721 783 }
722 784
723 - public function getOpenRateCampaigns($mailIds)
785 + public function getOpenRateCampaigns(array $mailIds)
724 786 {
725 - if (!is_array($mailIds)) $mailIds = [$mailIds];
726 787 acym_arrayToInteger($mailIds);
727 788
789 + //TODO move these methods in mail stat class
728 790 $query = 'SELECT SUM(sent) AS sent, SUM(open_unique) AS open_unique FROM #__acym_mail_stat
729 791 WHERE mail_id IN ('.implode(',', $mailIds).')';
730 792
731 793 return acym_loadObject($query);
@@ -744,13 +806,10 @@
744 806
745 807 return acym_loadObject($query);
746 808 }
747 809
748 -
749 - public function getBounceRateCampaigns($mailIds)
810 + public function getBounceRateCampaigns(array $mailIds)
750 811 {
751 - if (!is_array($mailIds)) $mailIds = [$mailIds];
752 -
753 812 acym_arrayToInteger($mailIds);
754 813
755 814 $query = 'SELECT SUM(sent) AS sent, SUM(bounce_unique) AS bounce_unique FROM #__acym_mail_stat
756 815 WHERE mail_id IN ('.implode(',', $mailIds).')';
@@ -757,9 +816,9 @@
757 816
758 817 return acym_loadObject($query);
759 818 }
760 819
761 - public function getOpenByMonth($mailIds = [], $start = '', $end = '')
820 + public function getOpenByMonth(array $mailIds = [], string $start = '', string $end = ''): array
762 821 {
763 822 if (!is_array($mailIds)) $mailIds = [$mailIds];
764 823 acym_arrayToInteger($mailIds);
765 824
@@ -772,22 +831,10 @@
772 831
773 832 return acym_loadObjectList($query);
774 833 }
775 834
776 - public function getOpenByWeek($mail_id = '', $start = '', $end = '')
835 + public function getOpenByDay(array $mailIds = [], string $start = '', string $end = ''): array
777 836 {
778 - $query = 'SELECT COUNT(user_id) as open, DATE_FORMAT(open_date, \'%Y-%m-%d\') as open_date FROM #__acym_user_stat WHERE open > 0';
779 - $query .= empty($mail_id) ? '' : ' AND `mail_id`='.intval($mail_id);
780 - $query .= ' AND `open_date` > "0000-00-00"';
781 - $query .= empty($start) ? '' : ' AND `open_date` >= '.acym_escapeDB($start);
782 - $query .= empty($end) ? '' : ' AND `open_date` <= '.acym_escapeDB($end);
783 - $query .= ' GROUP BY WEEK(open_date), YEAR(open_date) ORDER BY open_date';
784 -
785 - return acym_loadObjectList($query);
786 - }
787 -
788 - public function getOpenByDay($mailIds = [], $start = '', $end = '')
789 - {
790 837 if (!is_array($mailIds)) $mailIds = [$mailIds];
791 838 acym_arrayToInteger($mailIds);
792 839
793 840 $query = 'SELECT COUNT(user_id) as open, DATE_FORMAT(open_date, \'%Y-%m-%d\') as open_date FROM #__acym_user_stat WHERE open > 0';
@@ -799,9 +846,9 @@
799 846
800 847 return acym_loadObjectList($query);
801 848 }
802 849
803 - public function getOpenByHour($mailIds = [], $start = '', $end = '')
850 + public function getOpenByHour(array $mailIds = [], string $start = '', string $end = ''): array
804 851 {
805 852 if (!is_array($mailIds)) $mailIds = [$mailIds];
806 853 acym_arrayToInteger($mailIds);
807 854
@@ -814,11 +861,10 @@
814 861
815 862 return acym_loadObjectList($query);
816 863 }
817 864
818 - public function getDevicesWithCountByMailId($mailIds = [])
865 + public function getDevicesWithCountByMailId(array $mailIds = []): array
819 866 {
820 - if (!is_array($mailIds)) $mailIds = [$mailIds];
821 867 acym_arrayToInteger($mailIds);
822 868
823 869 $query = 'SELECT device, COUNT(*) as number FROM #__acym_user_stat WHERE `open` > 0';
824 870 if (!empty($mailIds)) $query .= ' AND mail_id IN ('.implode(',', $mailIds).')';
@@ -826,32 +872,39 @@
826 872
827 873 return acym_loadObjectList($query);
828 874 }
829 875
830 - public function getLastNewsletters(&$params): array
876 + public function getLastNewsletters(array &$params): array
831 877 {
878 + // Init select elements
832 879 $querySelect = 'SELECT mail.*, campaign.sending_date ';
833 880 $queryCountSelect = 'SELECT COUNT(*) FROM (SELECT DISTINCT mail.id ';
834 881
882 + // Form the query
835 883 $query = 'FROM #__acym_campaign AS campaign
836 884 JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id ';
837 885
886 + // We may need some joins depending on the selected options
838 887 if (isset($params['userId']) || isset($params['lists'])) {
839 888 $query .= 'JOIN #__acym_mail_has_list AS maillist ON mail.id = maillist.mail_id ';
840 889 if (isset($params['userId'])) $query .= 'JOIN #__acym_user_has_list AS userlist ON maillist.list_id = userlist.list_id ';
841 890 }
842 891
892 + // Make sure we display only active campaigns
843 893 $where = 'WHERE campaign.active = 1 AND campaign.sent = 1 AND mail.type = '.acym_escapeDB(MailClass::TYPE_STANDARD).' AND campaign.visible = 1 ';
844 894
895 + // If we want an archive of some specific lists
845 896 if (isset($params['lists'])) {
846 897 acym_arrayToInteger($params['lists']);
847 898 $where .= 'AND maillist.list_id IN ('.implode(', ', $params['lists']).') ';
848 899 }
849 900
901 + // If we want an archive for a specific user
850 902 if (isset($params['userId']) && !empty($params['displayUserListOnly'])) {
851 903 $where .= 'AND userlist.user_id = '.intval($params['userId']).' ';
852 904 }
853 905
906 + // If the user search for a newsletter
854 907 if (isset($params['search'])) {
855 908 $search = acym_escapeDB('%'.acym_utf8Encode($params['search']).'%');
856 909 $where .= 'AND (mail.subject LIKE '.$search.' OR mail.body LIKE '.$search.')';
857 910 }
@@ -859,11 +912,13 @@
859 912 $query .= $where;
860 913 $return = [];
861 914 $return['count'] = (int)acym_loadResult($queryCountSelect.$query.') AS r ');
862 915
916 + // Make sure we display campaigns only once
863 917 $endQuerySelect = 'GROUP BY mail.id ';
864 918 $endQuerySelect .= 'ORDER BY campaign.sending_date DESC';
865 919
920 + // Init the pagination
866 921 $page = $params['page'] ?? 0;
867 922 $numberPerPage = $params['numberPerPage'] ?? 0;
868 923 $lastNewsletters = $params['limit'] ?? 0;
869 924
@@ -919,16 +974,17 @@
919 974
920 975 return $return;
921 976 }
922 977
923 - public function getListsByMailId($mailId)
978 + public function getListsByMailId(int $mailId): array
924 979 {
925 - $query = 'SELECT list_id FROM #__acym_mail_has_list WHERE mail_id = '.intval($mailId);
980 + $listIds = acym_loadResultArray('SELECT list_id FROM #__acym_mail_has_list WHERE mail_id = '.intval($mailId));
981 + acym_arrayToInteger($listIds);
926 982
927 - return acym_loadResultArray($query);
983 + return $listIds;
928 984 }
929 985
930 - public function triggerAutoCampaign()
986 + public function triggerAutoCampaign(): void
931 987 {
932 988 $activeAutoCampaigns = acym_loadObjectList(
933 989 'SELECT campaign.*, mail.name
934 990 FROM #__acym_campaign AS campaign
@@ -947,12 +1003,19 @@
947 1003 $adminNotificationEmail = $mailClass->getOneByName('acy_notification_auto_campaign_admin');
948 1004 $time = time();
949 1005
950 1006 foreach ($activeAutoCampaigns as $campaign) {
951 - if (!empty($campaign->sending_params['start_date']) && (int)acym_getTime(acym_date($campaign->sending_params['start_date'], 'Y-m-d H:i')) > $time) {
1007 + // Check the start date
1008 + $nextTrigger = $campaign->next_trigger;
1009 + if (empty($nextTrigger) && !empty($campaign->sending_params['start_date'])) {
1010 + $nextTrigger = $campaign->sending_params['start_date'];
1011 + }
1012 +
1013 + if (!empty($nextTrigger) && acym_getTime(acym_date($nextTrigger, 'Y-m-d H:i')) > $time) {
952 1014 continue;
953 1015 }
954 1016
1017 + //check if we trigger the campaign
955 1018 $step = new \stdClass();
956 1019 $step->triggers = $campaign->sending_params;
957 1020 $step->last_execution = $campaign->last_generated;
958 1021 $step->next_execution = $campaign->next_trigger;
@@ -965,8 +1028,9 @@
965 1028 $this->save($campaign);
966 1029 continue;
967 1030 }
968 1031
1032 + //update the campaign
969 1033 $campaignMail = $mailClass->getOneById($campaign->mail_id);
970 1034
971 1035 $lastGenerated = $campaign->last_generated;
972 1036 $shouldGenerate = $this->updateAutoCampaign($campaign, $campaignMail, $time);
@@ -975,10 +1039,18 @@
975 1039 if (!$shouldGenerate) {
976 1040 continue;
977 1041 }
978 1042
1043 + //We generate the new campaign
979 1044 $generatedCampaign = $this->generateCampaign($campaign, $campaignMail, $lastGenerated, $mailClass);
1045 + if (empty($generatedCampaign)) {
1046 + $this->messages[] = acym_translationSprintf('ACYM_CAMPAIGN_FAILED_GENERATING', $campaign->name);
1047 + continue;
1048 + }
980 1049
1050 + $this->messages[] = acym_translationSprintf('ACYM_CAMPAIGN_GENERATED', $campaign->name, $campaign->sending_params['number_generated']);
1051 +
1052 + // We send it directly if no confirmation is needed
981 1053 if (empty($campaign->sending_params['need_confirm_to_send'])) {
982 1054 $this->send($generatedCampaign->id);
983 1055 } elseif (!empty($campaign->sending_params['admin_notification_emails']) && !empty($adminNotificationEmail)) {
984 1056 $mailerHelper->addParam('campaign_name', $campaign->name);
@@ -991,14 +1063,16 @@
991 1063 }
992 1064 }
993 1065 }
994 1066
995 - private function shouldGenerateCampaign($campaign, $campaignMail): bool
1067 + private function shouldGenerateCampaign(object $campaign, object $campaignMail): bool
996 1068 {
1069 + // The generateByCategory function is the only one that can stop a campaign generation, with min number of items
997 1070 $results = acym_trigger('generateByCategory', [&$campaignMail], null, function ($plugin) {
998 1071 $plugin->generateCampaignResult->status = true;
999 1072 });
1000 1073
1074 + // If one of the return statuses is "false", we won't generate the campaign
1001 1075 foreach ($results as $oneResult) {
1002 1076 if (isset($oneResult->status) && !$oneResult->status) {
1003 1077 $this->messages[] = acym_translationSprintf('ACYM_CAMPAIGN_NOT_GENERATED', $campaign->name, $oneResult->message);
1004 1078
@@ -1008,9 +1082,9 @@
1008 1082
1009 1083 return true;
1010 1084 }
1011 1085
1012 - private function updateAutoCampaign(&$campaign, $campaignMail, $time): bool
1086 + private function updateAutoCampaign(object $campaign, object $campaignMail, int $time): bool
1013 1087 {
1014 1088 if (!$this->shouldGenerateCampaign($campaign, $campaignMail)) {
1015 1089 return false;
1016 1090 }
@@ -1021,16 +1095,18 @@
1021 1095 $campaign->sending_params['number_generated']++;
1022 1096 }
1023 1097 $campaign->last_generated = $time;
1024 1098
1025 - $this->messages[] = acym_translationSprintf('ACYM_CAMPAIGN_GENERATED', $campaign->name, $campaign->sending_params['number_generated']);
1026 -
1027 1099 return true;
1028 1100 }
1029 1101
1030 - private function generateCampaign($campaign, $campaignMail, $lastGenerated, $mailClass)
1102 + private function generateCampaign(object $campaign, object $campaignMail, ?int $lastGenerated, MailClass $mailClass): ?object
1031 1103 {
1032 1104 $newMail = $this->generateMailAutoCampaign($campaignMail, $campaign->sending_params['number_generated'], $mailClass);
1105 + if (empty($newMail)) {
1106 + return null;
1107 + }
1108 +
1033 1109 $newCampaign = new \stdClass();
1034 1110 $newCampaign->mail_id = $newMail->id;
1035 1111 $newCampaign->parent_id = $campaign->id;
1036 1112 $newCampaign->active = 1;
@@ -1042,8 +1118,9 @@
1042 1118 $newCampaign->sending_params = empty($campaign->sending_params['segment']) ? '' : ['segment' => $campaign->sending_params['segment']];
1043 1119
1044 1120 $newCampaign->id = $this->save($newCampaign);
1045 1121
1122 + // Replace content in the generated mail. MUST be done after campaign has been saved
1046 1123 acym_trigger('replaceContent', [&$newMail, false]);
1047 1124 $mailClass->save($newMail);
1048 1125
1049 1126 return $newCampaign;
@@ -1048,9 +1125,9 @@
1048 1125
1049 1126 return $newCampaign;
1050 1127 }
1051 1128
1052 - private function generateMailAutoCampaign($newMail, $generatedMailNumber, $mailClass)
1129 + private function generateMailAutoCampaign(object $newMail, int $generatedMailNumber, MailClass $mailClass): ?object
1053 1130 {
1054 1131 $mailId = $newMail->id;
1055 1132 unset($newMail->id);
1056 1133 $newMail->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
@@ -1056,8 +1133,12 @@
1056 1133 $newMail->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
1057 1134 $newMail->name .= ' #'.$generatedMailNumber;
1058 1135
1059 1136 $newMail->id = $mailClass->save($newMail);
1137 + if (empty($newMail->id)) {
1138 + return null;
1139 + }
1140 +
1060 1141 $this->setListToGeneratedCampaign($mailId, $newMail->id, $mailClass);
1061 1142
1062 1143 if (acym_isMultilingual()) {
1063 1144 $this->generateMailAutoCampaignMultilingual($mailId, $generatedMailNumber, $newMail->id);
@@ -1065,9 +1146,9 @@
1065 1146
1066 1147 return $newMail;
1067 1148 }
1068 1149
1069 - private function generateMailAutoCampaignMultilingual($mailId, $generatedMailNumber, $newParentId)
1150 + private function generateMailAutoCampaignMultilingual(int $mailId, int $generatedMailNumber, int $newParentId): void
1070 1151 {
1071 1152 $mailClass = new MailClass();
1072 1153 $mails = $mailClass->getTranslationsById($mailId, true);
1073 1154
@@ -1079,9 +1160,9 @@
1079 1160 $mailClass->save($mail);
1080 1161 }
1081 1162 }
1082 1163
1083 - private function setListToGeneratedCampaign($parentMailId, $newMailId, $mailClass)
1164 + private function setListToGeneratedCampaign(int $parentMailId, int $newMailId, MailClass $mailClass): void
1084 1165 {
1085 1166 $lists = $mailClass->getAllListsByMailId($parentMailId);
1086 1167 $listIds = array_keys($lists);
1087 1168
@@ -1087,15 +1168,17 @@
1087 1168
1088 1169 $this->manageListsToCampaign($listIds, $newMailId);
1089 1170 }
1090 1171
1091 - public function getLastGenerated($mailId)
1172 + public function getLastGenerated(int $mailId): int
1092 1173 {
1093 - return acym_loadResult(
1174 + $lastGenerated = acym_loadResult(
1094 1175 'SELECT `last_generated`
1095 1176 FROM #__acym_campaign
1096 1177 WHERE `mail_id` = '.intval($mailId)
1097 1178 );
1179 +
1180 + return empty($lastGenerated) ? 0 : $lastGenerated;
1098 1181 }
1099 1182
1100 1183 private function getNbGeneratedCampaigns(): int
1101 1184 {
@@ -1106,11 +1189,11 @@
1106 1189 AND `sending_type` = '.acym_escapeDB(self::SENDING_TYPE_NOW)
1107 1190 );
1108 1191 }
1109 1192
1110 - public function getAllCampaignsGeneratedWaiting()
1193 + public function getAllCampaignsGeneratedWaiting(): array
1111 1194 {
1112 - return acym_loadObjectList(
1195 + $campaignIds = acym_loadObjectList(
1113 1196 'SELECT id
1114 1197 FROM #__acym_campaign
1115 1198 WHERE parent_id IS NOT NULL
1116 1199 AND sending_type = '.acym_escapeDB(self::SENDING_TYPE_NOW).'
@@ -1117,11 +1200,14 @@
1117 1200 AND draft = 1
1118 1201 AND active = 1
1119 1202 AND sent = 0'
1120 1203 );
1204 + acym_arrayToInteger($campaignIds);
1205 +
1206 + return $campaignIds;
1121 1207 }
1122 1208
1123 - public function getCampaignsByTypes($campaignTypes, $onlyActives = false)
1209 + public function getCampaignsByTypes(array $campaignTypes, bool $onlyActives = false): array
1124 1210 {
1125 1211 if (empty($campaignTypes)) return [];
1126 1212 $campaignTypes = array_map('acym_escapeDB', $campaignTypes);
1127 1213 $query = 'SELECT campaign.* FROM #__acym_campaign AS campaign WHERE campaign.sending_type IN ('.implode(',', $campaignTypes).')';
@@ -1129,16 +1215,16 @@
1129 1215
1130 1216 return $this->decode(acym_loadObjectList($query), false);
1131 1217 }
1132 1218
1133 - public function getCountCampaignType($type)
1219 + public function getCountCampaignType(string $type): int
1134 1220 {
1135 1221 if (empty($type)) return 0;
1136 1222
1137 - return acym_loadResult('SELECT COUNT(*) FROM #__acym_campaign WHERE sending_type = '.acym_escapeDB($type));
1223 + return (int)acym_loadResult('SELECT COUNT(*) FROM #__acym_campaign WHERE sending_type = '.acym_escapeDB($type));
1138 1224 }
1139 1225
1140 - public function getAllAbTestCampaignsToFinishSending()
1226 + public function getAllAbTestCampaignsToFinishSending(): array
1141 1227 {
1142 1228 $query = 'SELECT * FROM #__acym_campaign WHERE sending_params LIKE \'%"abtest":%\' AND draft = 0 AND active = 1 AND sent = 1';
1143 1229
1144 1230 return $this->decode(acym_loadObjectList($query));
@@ -1143,9 +1229,9 @@
1143 1229
1144 1230 return $this->decode(acym_loadObjectList($query));
1145 1231 }
1146 1232
1147 - public function finishAbTestCampaign($campaign)
1233 + public function finishAbTestCampaign(object $campaign): bool
1148 1234 {
1149 1235 $abTestParams = empty($campaign->sending_params['abtest']) ? [] : $campaign->sending_params['abtest'];
1150 1236
1151 1237 if (empty($abTestParams['B']) || empty($abTestParams['repartition']) || !empty($abTestParams['abtest_finished']) || empty($abTestParams['after']['action'])) {
@@ -1196,9 +1282,9 @@
1196 1282 $mail->id = 0;
1197 1283 $mail->parent_id = $campaign->mail_id;
1198 1284 $mail->name .= ' - '.acym_translation('ACYM_ABTEST_WINNER');
1199 1285 $newMailId = $mailClass->save($mail);
1200 - if (!$newMailId) {
1286 + if (empty($newMailId)) {
1201 1287 return false;
1202 1288 }
1203 1289
1204 1290 $campaign->sending_params['abtest']['abtest_finished'] = 1;
@@ -1209,9 +1295,9 @@
1209 1295
1210 1296 return true;
1211 1297 }
1212 1298
1213 - public function isAbTestMail($mailId): bool
1299 + public function isAbTestMail(int $mailId): bool
1214 1300 {
1215 1301 $mailClass = new MailClass();
1216 1302 $mainMailId = $mailClass->getMainMailId($mailId);
1217 1303
@@ -1224,9 +1310,9 @@
1224 1310
1225 1311 return !empty($campaign);
1226 1312 }
1227 1313
1228 - public function resetAbTestVersion($campaignId): void
1314 + public function resetAbTestVersion(int $campaignId): void
1229 1315 {
1230 1316 if (empty($campaignId)) {
1231 1317 return;
1232 1318 }
@@ -1243,9 +1329,9 @@
1243 1329
1244 1330 foreach (self::AB_TEST_VERSIONS as $version) {
1245 1331 if (!empty($campaign->sending_params['abtest'][$version])) {
1246 1332 $mailClass = new MailClass();
1247 - $mailClass->delete($campaign->sending_params['abtest'][$version]);
1333 + $mailClass->delete([$campaign->sending_params['abtest'][$version]]);
1248 1334 unset($campaign->sending_params['abtest'][$version]);
1249 1335 }
1250 1336 }
1251 1337
@@ -1251,9 +1337,9 @@
1251 1337
1252 1338 $this->save($campaign);
1253 1339 }
1254 1340
1255 - public function getXCampaigns(array $options)
1341 + public function getXCampaigns(array $options): array
1256 1342 {
1257 1343 $limit = $options['limit'] ?? 10;
1258 1344 $offset = $options['offset'] ?? 0;
1259 1345 $filters = $options['filters'] ?? [];