PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.10.2
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.10.2
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Classes / CampaignClass.php

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

1,319 lines 51.7 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\CampaignsController;
6 use AcyMailing\Controllers\SegmentsController;
7 use AcyMailing\Helpers\AutomationHelper;
8 use AcyMailing\Helpers\MailerHelper;
9 use AcyMailing\Helpers\PaginationHelper;
10 use AcyMailing\Core\AcymClass;
11
12 class CampaignClass extends AcymClass
13 {
14 const SENDING_TYPE_NOW = 'now';
15 const SENDING_TYPE_SCHEDULED = 'scheduled';
16 const SENDING_TYPE_AUTO = 'auto';
17 const SENDING_TYPES = [
18 self::SENDING_TYPE_NOW,
19 self::SENDING_TYPE_SCHEDULED,
20 self::SENDING_TYPE_AUTO,
21 ];
22 const AB_TEST_VERSIONS = ['B'];
23
24 public function __construct()
25 {
26 parent::__construct();
27
28 $this->table = 'campaign';
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 ];
44 }
45
46 public function decode($campaign, bool $decodeMail = true)
47 {
48 if (empty($campaign)) return $campaign;
49
50 if (is_array($campaign)) {
51 foreach ($campaign as $i => $oneCampaign) {
52 $campaign[$i] = $this->decode($oneCampaign, false);
53 }
54 } else {
55 $this->fixTypes($campaign);
56 }
57
58 if ($decodeMail) {
59 $mailClass = new MailClass();
60 $campaign = $mailClass->decode($campaign);
61 }
62
63 return $campaign;
64 }
65
66 public function getAll(?string $key = null): array
67 {
68 $allCampaigns = parent::getAll($key);
69
70 return $this->decode($allCampaigns);
71 }
72
73 public function getMatchingElements(array $settings = []): array
74 {
75 $tagClass = new TagClass();
76 $mailClass = new MailClass();
77
78 $query = 'SELECT campaign.*, mail.name, SUM(mail_stat.sent) AS subscribers, mail_stat.open_unique, mail_stat.tracking_sale, mail_stat.currency FROM #__acym_campaign AS campaign';
79 $queryCount = 'FROM #__acym_campaign AS campaign';
80
81 $filters = [];
82 $task = acym_getVar('string', 'task');
83 if ($task === 'campaigns') {
84 $filters[] = 'mail.type = '.acym_escapeDB(MailClass::TYPE_STANDARD);
85 } elseif ($task === 'mailbox_action') {
86 $filters[] = 'mail.type = '.acym_escapeDB(MailClass::TYPE_MAILBOX_ACTION);
87 }
88 $mailIds = [];
89
90 $query .= ' JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id';
91 $queryCount .= ' JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id';
92 $query .= ' LEFT JOIN #__acym_mail_stat AS mail_stat ON campaign.mail_id = mail_stat.mail_id';
93
94 if (!acym_isAdmin()) {
95 $condition = 'mail.creator_id = '.intval(acym_currentUserId());
96 if ($this->config->get('front_campaigns_filter', 'own') === 'allowed') {
97 $listClass = new ListClass();
98 $allowedLists = $listClass->getManageableLists();
99 if (!empty($allowedLists)) {
100 $query .= ' LEFT JOIN #__acym_mail_has_list AS mailList ON campaign.mail_id = mailList.mail_id AND mailList.list_id IN ('.implode(',', $allowedLists).')';
101 $queryCount .= ' LEFT JOIN #__acym_mail_has_list AS mailList ON campaign.mail_id = mailList.mail_id AND mailList.list_id IN ('.implode(',', $allowedLists).')';
102 $condition .= ' OR mailList.list_id IS NOT NULL';
103 }
104 }
105 $filters[] = $condition;
106 $filters[] = 'campaign.sending_params NOT LIKE "%abtest%"';
107 }
108
109 if (!empty($settings['tag'])) {
110 $tagJoin = ' JOIN #__acym_tag AS tag ON campaign.mail_id = tag.id_element';
111 $query .= $tagJoin;
112 $queryCount .= $tagJoin;
113 $filters[] = 'tag.name = '.acym_escapeDB($settings['tag']);
114 $filters[] = 'tag.type = '.acym_escapeDB(TagClass::TYPE_MAIL);
115 }
116
117 if (!empty($settings['search'])) {
118 $filters[] = 'mail.name LIKE '.acym_escapeDB('%'.acym_utf8Encode($settings['search']).'%');
119 }
120
121 if ($settings['status'] !== 'generated') {
122 $operator = $settings['element_tab'] === 'campaigns_auto' ? '=' : '!=';
123 if ($settings['element_tab'] === 'campaigns_auto') {
124 $filters[] = 'campaign.sending_type '.$operator.' '.acym_escapeDB(self::SENDING_TYPE_AUTO);
125 } elseif ($settings['element_tab'] === 'campaigns') {
126 $filters[] = 'campaign.sending_type IN ('.acym_escapeDB(self::SENDING_TYPE_NOW).', '.acym_escapeDB(self::SENDING_TYPE_SCHEDULED).')';
127 } elseif (!empty($settings['element_tab'])) {
128 acym_trigger('onAcymCampaignAddFiltersSpecificListing', [&$filters, $settings['element_tab']]);
129 }
130 }
131
132 if (!empty($filters)) {
133 $query .= ' WHERE ('.implode(') AND (', $filters).')';
134 $queryCount .= ' WHERE ('.implode(') AND (', $filters).')';
135 }
136
137 $statusRequests = [
138 'all' => '(campaign.parent_id IS NULL OR campaign.parent_id = 0)',
139 'scheduled' => 'campaign.sending_type = '.acym_escapeDB(self::SENDING_TYPE_SCHEDULED).' AND (campaign.parent_id IS NULL OR campaign.parent_id = 0)',
140 'sent' => 'campaign.sent = 1 AND (campaign.parent_id IS NULL OR campaign.parent_id = 0)',
141 'draft' => 'campaign.draft = 1 AND (campaign.parent_id IS NULL OR campaign.parent_id = 0)',
142 ];
143
144 if ($settings['element_tab'] == 'campaigns_auto') {
145 $statusRequests['generated'] = 'campaign.sending_type = '.acym_escapeDB(self::SENDING_TYPE_NOW).' AND campaign.parent_id > 0';
146 }
147
148 if (empty($settings['status'])) $settings['status'] = 'all';
149 if (!empty($statusRequests[$settings['status']])) {
150 $query .= empty($filters) ? ' WHERE ' : ' AND ';
151 $query .= $statusRequests[$settings['status']];
152 }
153
154 $query .= ' GROUP BY campaign.id';
155
156 if (!empty($settings['ordering']) && !empty($settings['ordering_sort_order'])) {
157 $table = in_array($settings['ordering'], ['name', 'creation_date']) ? 'mail' : 'campaign';
158 $query .= ' ORDER BY '.$table.'.'.acym_secureDBColumn($settings['ordering']).' '.acym_secureDBColumn(strtoupper($settings['ordering_sort_order']));
159 }
160
161 if (empty($settings['offset']) || $settings['offset'] < 0) {
162 $settings['offset'] = 0;
163 }
164
165 if (empty($settings['elementsPerPage']) || $settings['elementsPerPage'] < 1) {
166 $pagination = new PaginationHelper();
167 $settings['elementsPerPage'] = $pagination->getListLimit();
168 }
169
170 $results['elements'] = $this->decode(acym_loadObjectList($query, '', $settings['offset'], $settings['elementsPerPage']));
171
172 foreach ($results['elements'] as $oneCampaign) {
173 $mailIds[] = $oneCampaign->mail_id;
174 $oneCampaign->tags = '';
175 }
176
177 $tags = $tagClass->getAllTagsByTypeAndElementIds(TagClass::TYPE_MAIL, $mailIds);
178 $lists = $mailClass->getAllListsByMailIds($mailIds);
179
180 $isMultilingual = acym_isMultilingual();
181
182 $urlClickClass = new UrlClickClass();
183 foreach ($results['elements'] as $i => $oneCampaign) {
184 $results['elements'][$i]->tags = [];
185 $results['elements'][$i]->lists = [];
186 $results['elements'][$i]->automation_id = null;
187
188 foreach ($tags as $tag) {
189 if ($oneCampaign->id == $tag->id_element) {
190 $results['elements'][$i]->tags[] = $tag;
191 }
192 }
193
194 foreach ($lists as $list) {
195 if ($oneCampaign->mail_id == $list->mail_id) {
196 array_push($results['elements'][$i]->lists, $list);
197 }
198 }
199
200 if ($settings['element_tab'] === 'campaigns_auto' && $settings['status'] !== 'generated') {
201 $this->getStatsCampaignAuto($results['elements'][$i], $urlClickClass);
202 } else {
203 $isAbTest = isset($results['elements'][$i]->sending_params['abtest']);
204 if ($isMultilingual || $isAbTest) {
205 $this->prepareStatsCampaign($results['elements'][$i]);
206 }
207 $this->getStatsCampaign($results['elements'][$i], $urlClickClass, $isAbTest);
208 }
209 }
210
211 if (!empty($settings['advanced_total'])) {
212 if ($settings['campaign_type'] === 'campaigns') {
213 $queryCount .= (empty($filters) ? ' WHERE ' : ' AND ').'campaign.parent_id IS NULL';
214
215 $results['total'] = acym_loadObject('SELECT COUNT(campaign.id) AS total, SUM(campaign.sent) AS sent, SUM(campaign.draft) AS draft '.$queryCount);
216 $results['total']->all = $results['total']->total;
217 $results['total']->scheduled = acym_loadResult(
218 'SELECT COUNT(campaign.id) '.$queryCount.' AND campaign.`sending_type` = '.acym_escapeDB(self::SENDING_TYPE_SCHEDULED)
219 );
220 } else {
221 $results['total'] = acym_loadObject('SELECT COUNT(campaign.id) AS total '.$queryCount);
222 $results['total']->all = 0;
223 if (!empty($results['total']->total)) {
224 $results['total']->all = $results['total']->total;
225 }
226
227 $results['total']->generated = $this->getNbGeneratedCampaigns();
228 }
229
230 if (CampaignsController::TASK_TYPE_CAMPAIGN_AUTO === $settings['campaign_type'] && 'generated' === $settings['status']) {
231 $results['total']->all = $this->getCountCampaignType(self::SENDING_TYPE_AUTO);
232 }
233 } else {
234 $results['total'] = acym_loadObject('SELECT COUNT(campaign.id) AS total '.$queryCount);
235 }
236
237 return $results;
238 }
239
240 private function prepareStatsCampaign(object $element): void
241 {
242 $query = 'SELECT SUM(mailstat.sent) AS subscribers, SUM(mailstat.open_unique) AS open_unique, SUM(mailstat.tracking_sale) AS tracking_sale
243 FROM #__acym_mail_stat AS mailstat
244 LEFT JOIN #__acym_mail AS mail ON mail.id = mailstat.mail_id WHERE mail.parent_id = '.intval($element->mail_id);
245 $stats = acym_loadObject($query);
246 $element->subscribers += $stats->subscribers;
247 $element->open_unique += $stats->open_unique;
248 $element->tracking_sale += $stats->tracking_sale;
249 }
250
251 public function getStatsCampaign(object $element, UrlClickClass $urlClickClass, bool $isAbTest = false): void
252 {
253 $element->open = 0;
254 $element->click = 0;
255
256 if (!empty($element->subscribers)) {
257 $element->open = number_format($element->open_unique / $element->subscribers * 100, 2);
258
259 $clicksNb = $urlClickClass->getNumberUsersClicked([$element->mail_id], $isAbTest);
260 $element->click = number_format($clicksNb / $element->subscribers * 100, 2);
261 }
262 }
263
264 public function getTrackingSales(object $element): void
265 {
266 $element->sale = 0;
267 $element->currency = '';
268
269 if (acym_isMultilingual()) {
270 $trackingSales = acym_loadObject(
271 'SELECT SUM(tracking_sale) as sale, currency FROM #__acym_user_stat WHERE mail_id IN (SELECT id FROM #__acym_mail WHERE id = '.intval(
272 $element->mail_id
273 ).' OR parent_id = '.intval($element->mail_id).') AND currency IS NOT NULL'
274 );
275 } else {
276 $trackingSales = acym_loadObject(
277 'SELECT SUM(tracking_sale) as sale, currency FROM #__acym_user_stat WHERE mail_id = '.intval($element->mail_id).' AND currency IS NOT NULL'
278 );
279 }
280 $this->formatSaleTracking($element, $trackingSales);
281 }
282
283 private function getStatsCampaignAuto(object $element, UrlClickClass $urlClickClass): void
284 {
285 $generatedMailsStats = acym_loadObjectList(
286 'SELECT mail_stat.*
287 FROM #__acym_mail AS mail
288 JOIN #__acym_mail_stat AS mail_stat
289 ON mail.id = mail_stat.mail_id
290 WHERE mail.id IN (SELECT mail_id FROM #__acym_campaign WHERE parent_id = '.intval($element->id).')'
291 );
292
293 $element->open = 0;
294 $element->click = 0;
295 $element->subscribers = 0;
296 if (empty($generatedMailsStats)) {
297 return;
298 }
299
300 foreach ($generatedMailsStats as $key => $mailsStat) {
301 $element->open += $mailsStat->open_unique;
302 $element->click += $urlClickClass->getNumberUsersClicked([$element->id]);
303 $element->subscribers += $mailsStat->sent;
304 }
305
306 if (!empty($element->subscribers)) {
307 $element->open = number_format($element->open / $element->subscribers * 100, 2);
308 $element->click = number_format($element->click / $element->subscribers * 100, 2);
309 }
310
311 if (!acym_isTrackingSalesActive()) {
312 return;
313 }
314
315 $trackingSales = acym_loadObject(
316 'SELECT SUM(mail_stat.tracking_sale) AS sale, mail_stat.currency
317 FROM #__acym_mail_stat AS mail_stat
318 WHERE mail_id IN (SELECT mail_id FROM #__acym_campaign WHERE parent_id = '.intval($element->id).') AND currency IS NOT NULL'
319 );
320 $this->formatSaleTracking($element, $trackingSales);
321 }
322
323 private function formatSaleTracking(object $element, object $trackingSales): void
324 {
325 $element->sale = $trackingSales->sale;
326 if (empty($element->currency)) $element->currency = '';
327 acym_trigger('getCurrency', [&$element->currency]);
328 }
329
330 public function getOneById(int $id): ?object
331 {
332 $campaign = parent::getOneById($id);
333
334 return empty($campaign) ? null : $this->decode($campaign);
335 }
336
337 public function getOneByIdWithMail(int $id): ?object
338 {
339 $campaign = acym_loadObject(
340 '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
341 FROM #__acym_campaign AS campaign
342 JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id
343 WHERE campaign.id = '.intval($id)
344 );
345
346 return empty($campaign) ? null : $this->decode($campaign);
347 }
348
349 public function getOneCampaignByMailId(int $mailId)
350 {
351 return $this->decode(acym_loadObject('SELECT * FROM #__acym_campaign WHERE mail_id = '.intval($mailId)));
352 }
353
354 public function getAutoCampaignFromGeneratedMailId(int $mailId)
355 {
356 $queryCampaign = 'SELECT * FROM #__acym_campaign WHERE id = (SELECT parent_id FROM #__acym_campaign WHERE mail_id = '.intval($mailId).')';
357
358 return $this->decode(acym_loadObject($queryCampaign));
359 }
360
361 public function manageListsToCampaign($listsIds, int $mailId, array $unselectedListIds = []): bool
362 {
363 if (!empty($unselectedListIds)) {
364 acym_arrayToInteger($unselectedListIds);
365 acym_query('DELETE FROM #__acym_mail_has_list WHERE mail_id = '.intval($mailId).' AND list_id IN ('.implode(', ', $unselectedListIds).')');
366 }
367
368 acym_arrayToInteger($listsIds);
369 if (empty($listsIds)) return false;
370
371 $values = [];
372 $listsIds = array_unique($listsIds);
373 foreach ($listsIds as $listId) {
374 $values[] = '('.intval($mailId).', '.intval($listId).')';
375 }
376
377 if (!empty($values)) {
378 acym_query('INSERT IGNORE INTO #__acym_mail_has_list (`mail_id`, `list_id`) VALUES '.implode(',', $values));
379 }
380
381 return true;
382 }
383
384 public function save(object $element): ?int
385 {
386 $campaign = clone $element;
387 if (isset($campaign->tags)) {
388 $tags = $campaign->tags;
389 unset($campaign->tags);
390 }
391
392 foreach ($campaign as $oneAttribute => $value) {
393 if (in_array($oneAttribute, $this->jsonColumns)) {
394 $campaign->$oneAttribute = json_encode(empty($value) ? [] : $value);
395 } else {
396 if (empty($value)) continue;
397 $campaign->$oneAttribute = strip_tags($value);
398 }
399 }
400
401 $campaignID = parent::save($campaign);
402
403 if (!empty($campaignID) && isset($tags)) {
404 $tagClass = new TagClass();
405 $tagClass->setTags(TagClass::TYPE_MAIL, intval($campaign->mail_id), $tags);
406 }
407
408 return $campaignID;
409 }
410
411 public function onlyManageableCampaigns(array &$elements): void
412 {
413 if (acym_isAdmin()) return;
414
415 $idCurrentUser = acym_currentUserId();
416 if (empty($idCurrentUser)) return;
417
418 $manageable = acym_loadResultArray(
419 'SELECT campaign.id
420 FROM #__acym_campaign AS campaign
421 JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id
422 WHERE mail.creator_id = '.intval($idCurrentUser)
423 );
424 $elements = array_intersect($elements, $manageable);
425 }
426
427 public function hasUserAccess(int $campaignId): bool
428 {
429 $userId = acym_currentUserId();
430 if (empty($userId)) {
431 return false;
432 }
433
434 if (acym_isAdmin()) {
435 return true;
436 }
437
438 $query = 'SELECT COUNT(*) FROM #__acym_campaign AS campaign
439 JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id ';
440
441 $condition = 'mail.creator_id = '.intval($userId);
442 if ($this->config->get('front_campaigns_filter', 'own') === 'allowed') {
443 $listClass = new ListClass();
444 $allowedLists = $listClass->getManageableLists();
445 if (!empty($allowedLists)) {
446 $query .= ' LEFT JOIN #__acym_mail_has_list AS mailList ON campaign.mail_id = mailList.mail_id AND mailList.list_id IN ('.implode(',', $allowedLists).')';
447 $condition .= ' OR mailList.list_id IS NOT NULL';
448 }
449 }
450
451 $query .= 'WHERE campaign.id = '.intval($campaignId).' AND ('.$condition.')';
452
453 return acym_loadResult($query) > 0;
454 }
455
456 public function delete(array $elements): int
457 {
458 acym_arrayToInteger($elements);
459 $this->onlyManageableCampaigns($elements);
460
461 if (empty($elements)) return 0;
462
463 $mailsToDelete = [];
464 foreach ($elements as $id) {
465 $mailsToDelete[] = acym_loadResult('SELECT mail_id FROM #__acym_campaign WHERE id = '.intval($id));
466 acym_query('UPDATE #__acym_campaign SET mail_id = NULL WHERE id = '.intval($id));
467 }
468
469 $mailClass = new MailClass();
470 $mailClass->delete($mailsToDelete);
471
472 return parent::delete($elements);
473 }
474
475 public function countUsersCampaign(int $campaignID, bool $onlyActive = false): int
476 {
477 $campaign = $this->getOneById($campaignID);
478 if (empty($campaign)) {
479 return 0;
480 }
481
482 $mailClass = new MailClass();
483 $lists = $mailClass->getAllListsByMailId($campaign->mail_id);
484 $listsIds = array_column($lists, 'id');
485
486 if (empty($listsIds)) {
487 return 0;
488 }
489
490 $automationHelperBase = new AutomationHelper();
491 $automationHelperBase->removeFlag(SegmentsController::FLAG_COUNT);
492
493 $exclude = !empty($campaign->sending_params['segment']['invert']) && $campaign->sending_params['segment']['invert'] === 'exclude';
494
495 $automationHelpers = [];
496 $filters = $this->getFilterCampaign($campaign->sending_params);
497 foreach ($filters as $or => $orValues) {
498 if (empty($orValues)) continue;
499
500 $automationHelpers[$or] = new AutomationHelper();
501 foreach ($orValues as $and => $andValues) {
502 $and = intval($and);
503 foreach ($andValues as $filterName => $options) {
504 acym_trigger('onAcymProcessFilter_'.$filterName, [&$automationHelpers[$or], &$options, &$and]);
505 }
506 }
507
508 $automationHelpers[$or]->excludeSelected = $exclude;
509 $automationHelpers[$or]->addFlag(SegmentsController::FLAG_COUNT);
510 }
511
512 $join = ' #__acym_user_has_list AS user_list ON user_list.user_id = user.id ';
513 $join .= 'AND user_list.list_id IN ('.implode(',', $listsIds).') ';
514 $join .= 'AND user_list.status = 1 ';
515 if ($this->config->get('require_confirmation', 1) == 1) {
516 $join .= 'AND user.confirmed = 1 ';
517 }
518 if ($onlyActive) {
519 $join .= 'AND user.active = 1 ';
520 }
521
522 if (empty($automationHelpers)) {
523 $automationHelperBase->join['user_list'] = $join;
524
525 return acym_loadResult($automationHelperBase->getQuery(['COUNT(DISTINCT user.id)']));
526 } else {
527 $automationHelperBase = array_pop($automationHelpers);
528 $automationHelperBase->join['user_list'] = $join;
529 $numberOfRecipients = acym_loadResult($automationHelperBase->getQuery(['COUNT(DISTINCT user.id)']));
530 $automationHelperBase->removeFlag(SegmentsController::FLAG_COUNT);
531
532 return $numberOfRecipients;
533 }
534 }
535
536 public function getFilterCampaign($sendingParams): array
537 {
538 if (empty($sendingParams['segment'])) {
539 return [];
540 }
541
542 if (!empty($sendingParams['segment']['filters'])) {
543 return $sendingParams['segment']['filters'];
544 }
545
546 if (!empty($sendingParams['segment']['segment_id'])) {
547 $segmentClass = new SegmentClass();
548 $segment = $segmentClass->getOneById($sendingParams['segment']['segment_id']);
549 if (!empty($segment)) {
550 return $segment->filters;
551 }
552 }
553
554 return [];
555 }
556
557 public function send(int $campaignID, int $result = 0, bool $abTestFinal = false)
558 {
559 $campaign = $this->getOneById($campaignID);
560
561 if (empty($campaign->mail_id)) {
562 $this->errors[] = acym_translation('ACYM_EMAIL_NOT_FOUND');
563
564 return false;
565 }
566
567 $filters = $this->getFilterCampaign($campaign->sending_params);
568 $pluginIsExisting = true;
569
570 if (empty($filters)) {
571 $filters = [0 => []];
572 acym_trigger('onAcymSendCampaignSpecial', [$campaign, &$filters[0], &$pluginIsExisting]);
573 } else {
574 foreach ($filters as $key => $filter) {
575 acym_trigger('onAcymSendCampaignSpecial', [$campaign, &$filters[$key], &$pluginIsExisting]);
576 }
577 }
578
579 if (!$pluginIsExisting) {
580 return false;
581 }
582
583 $lists = acym_loadResultArray('SELECT list_id FROM #__acym_mail_has_list WHERE mail_id = '.intval($campaign->mail_id));
584 if (empty($lists)) {
585 $this->errors[] = acym_translation('ACYM_NO_LIST_SELECTED');
586
587 return false;
588 }
589 acym_arrayToInteger($lists);
590
591 $date = acym_date('now', 'Y-m-d H:i:s', false);
592 $numberUsersInsertedByMailId = [];
593
594 if (empty($result)) {
595 $conditions = [
596 '`user`.`active` = 1',
597 '`ul`.`status` = 1',
598 '`ul`.`list_id` IN ('.implode(',', $lists).')',
599 ];
600 if ($this->config->get('require_confirmation', 1) == 1) {
601 $conditions[] = '`user`.`confirmed` = 1';
602 }
603
604 $automationHelper = new AutomationHelper();
605 $automationHelper->removeFlag(SegmentsController::FLAG_COUNT);
606 $automationHelper->join['ul'] = ' #__acym_user_has_list AS ul ON ul.user_id = user.id ';
607
608 if (!empty($filters)) {
609 $automationHelpers = [];
610 foreach ($filters as $or => $orValues) {
611 if (empty($orValues)) continue;
612
613 $automationHelpers[$or] = new AutomationHelper();
614 foreach ($orValues as $and => $andValues) {
615 $and = intval($and);
616 foreach ($andValues as $filterName => $options) {
617 acym_trigger('onAcymProcessFilter_'.$filterName, [&$automationHelpers[$or], &$options, &$and]);
618 }
619 }
620
621 $automationHelpers[$or]->addFlag(SegmentsController::FLAG_COUNT);
622 }
623
624 if (!empty($automationHelpers)) {
625 $excludingSegment = !empty($campaign->sending_params['segment']['invert']) && $campaign->sending_params['segment']['invert'] === 'exclude';
626 if ($excludingSegment) {
627 $conditions[] = 'user.automation NOT LIKE "%a'.SegmentsController::FLAG_COUNT.'a%"';
628 } else {
629 $conditions[] = 'user.automation LIKE "%a'.SegmentsController::FLAG_COUNT.'a%"';
630 }
631 }
632 }
633
634 if (acym_isMultilingual()) {
635 $select = ['IF(mail.id IS NULL, '.intval($campaign->mail_id).', `mail`.`id`)', 'ul.`user_id`', acym_escapeDB($date)];
636 $automationHelper->leftjoin['mail'] = '`#__acym_mail` AS mail ON `mail`.`language` = `user`.language AND `mail`.`parent_id` = '.intval($campaign->mail_id);
637 } else {
638 $select = [intval($campaign->mail_id), 'ul.`user_id`', acym_escapeDB($date)];
639 }
640
641 if (!empty($campaign->sending_params['resendTarget']) && 'new' === $campaign->sending_params['resendTarget']) {
642 if (acym_isMultilingual()) {
643 $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(
644 $campaign->mail_id
645 ).' OR id = '.intval($campaign->mail_id).')';
646 } else {
647 $automationHelper->leftjoin['us'] = '`#__acym_user_stat` AS `us` ON `us`.`user_id` = `user`.`id` AND `us`.`mail_id` = '.intval($campaign->mail_id);
648 }
649 $conditions[] = '`us`.`user_id` IS NULL';
650 }
651
652 $automationHelper->where = $conditions;
653 if (!empty($campaign->sending_params['abtest']['repartition']) && !$abTestFinal) {
654 $totalNumberOfUsers = acym_loadResult($automationHelper->getQuery(['count(user.id)']));
655 $numberOfUsersToSend = round($campaign->sending_params['abtest']['repartition'] * $totalNumberOfUsers / 100);
656
657 $numberOfUsersToSend1 = round($numberOfUsersToSend / 2);
658 $automationHelper->limit = '0, '.$numberOfUsersToSend1;
659 $select1 = [intval($campaign->mail_id), 'ul.`user_id`', acym_escapeDB($date)];
660 $numberUsersInsertedByMailId[intval($campaign->mail_id)] = acym_query(
661 'INSERT IGNORE INTO `#__acym_queue` (`mail_id`, `user_id`, `sending_date`) '.$automationHelper->getQuery($select1)
662 );
663
664 $numberOfUsersToSend2 = $numberOfUsersToSend1 * 2 > $numberOfUsersToSend ? $numberOfUsersToSend1 - 1 : $numberOfUsersToSend1;
665 $automationHelper->limit = $numberOfUsersToSend.', '.$numberOfUsersToSend2;
666 $select2 = [intval($campaign->sending_params['abtest']['B']), 'ul.`user_id`', acym_escapeDB($date)];
667 $numberUsersInsertedByMailId[intval($campaign->sending_params['abtest']['B'])] = acym_query(
668 'INSERT IGNORE INTO `#__acym_queue` (`mail_id`, `user_id`, `sending_date`) '.$automationHelper->getQuery($select2)
669 );
670 } elseif ($abTestFinal && !empty($campaign->sending_params['abtest']['final'])) {
671 $mailIds = [$campaign->mail_id, $campaign->sending_params['abtest']['B']];
672 acym_arrayToInteger($mailIds);
673 $automationHelper->where[] = 'user.id NOT IN (SELECT user_id FROM #__acym_user_stat WHERE mail_id IN ('.implode(',', $mailIds).'))';
674
675 $select = [intval($campaign->sending_params['abtest']['final']), 'ul.`user_id`', acym_escapeDB($date)];
676 $numberUsersInsertedByMailId[intval($campaign->sending_params['abtest']['final'])] = acym_query(
677 'INSERT IGNORE INTO `#__acym_queue` (`mail_id`, `user_id`, `sending_date`) '.$automationHelper->getQuery($select)
678 );
679 } else {
680 $insertQuery = 'INSERT IGNORE INTO `#__acym_queue` (`mail_id`, `user_id`, `sending_date`) '.$automationHelper->getQuery($select);
681 $numberUsersInsertedByMailId[intval($campaign->mail_id)] = acym_query($insertQuery);
682 }
683
684 $automationHelper->removeFlag(SegmentsController::FLAG_COUNT);
685 } else {
686 $numberUsersInsertedByMailId[intval($campaign->mail_id)] = $result;
687 }
688
689
690 $result += array_sum($numberUsersInsertedByMailId);
691
692 if ($campaign->sending_type == self::SENDING_TYPE_NOW) {
693 $campaign->sending_date = $date;
694 $campaign->draft = 0;
695 $this->save($campaign);
696 }
697
698 $mailStatClass = new MailStatClass();
699 foreach ($numberUsersInsertedByMailId as $mailId => $numberOfUsersInserted) {
700 $newMailStat = new \stdClass();
701 $newMailStat->mail_id = intval($mailId);
702 $newMailStat->total_subscribers = intval($numberOfUsersInserted);
703 $newMailStat->send_date = $date;
704
705 $mailStatClass->save($newMailStat);
706 }
707
708 if ($result === 0) {
709 $this->errors[] = acym_translation('ACYM_NO_SUBSCRIBERS_FOUND');
710
711 return false;
712 }
713
714 acym_query('UPDATE `#__acym_campaign` SET `sent` = 1, `active` = 1 WHERE `mail_id` = '.intval($campaign->mail_id));
715
716 return $result;
717 }
718
719 public function getCampaignForDashboard(): array
720 {
721 $campaigns = acym_loadObjectList(
722 'SELECT campaign.*, mail.name AS name
723 FROM #__acym_campaign AS campaign
724 LEFT JOIN #__acym_mail AS mail
725 ON campaign.mail_id = mail.id
726 WHERE `active` = 1
727 AND `sent` = 0
728 AND `sending_type` = '.acym_escapeDB(self::SENDING_TYPE_SCHEDULED).'
729 LIMIT 3'
730 );
731
732 return $this->decode($campaigns);
733 }
734
735 public function getOpenRateCampaigns(array $mailIds)
736 {
737 acym_arrayToInteger($mailIds);
738
739 $query = 'SELECT SUM(sent) AS sent, SUM(open_unique) AS open_unique FROM #__acym_mail_stat
740 WHERE mail_id IN ('.implode(',', $mailIds).')';
741
742 return acym_loadObject($query);
743 }
744
745 public function getOpenRateAllCampaign()
746 {
747 $query = 'SELECT SUM(sent) as sent, SUM(open_unique) as open_unique FROM #__acym_mail_stat';
748
749 return acym_loadObject($query);
750 }
751
752 public function getBounceRateAllCampaign()
753 {
754 $query = 'SELECT SUM(sent) as sent, SUM(bounce_unique) as bounce_unique FROM #__acym_mail_stat';
755
756 return acym_loadObject($query);
757 }
758
759 public function getBounceRateCampaigns(array $mailIds)
760 {
761 acym_arrayToInteger($mailIds);
762
763 $query = 'SELECT SUM(sent) AS sent, SUM(bounce_unique) AS bounce_unique FROM #__acym_mail_stat
764 WHERE mail_id IN ('.implode(',', $mailIds).')';
765
766 return acym_loadObject($query);
767 }
768
769 public function getOpenByMonth(array $mailIds = [], string $start = '', string $end = ''): array
770 {
771 if (!is_array($mailIds)) $mailIds = [$mailIds];
772 acym_arrayToInteger($mailIds);
773
774 $query = 'SELECT COUNT(user_id) as open, DATE_FORMAT(open_date, \'%Y-%m\') as open_date FROM #__acym_user_stat WHERE open > 0';
775 $query .= empty($mailIds) ? '' : ' AND `mail_id` IN ('.implode(',', $mailIds).')';
776 $query .= ' AND `open_date` > "0000-00-00"';
777 $query .= empty($start) ? '' : ' AND `open_date` >= '.acym_escapeDB($start);
778 $query .= empty($end) ? '' : ' AND `open_date` <= '.acym_escapeDB($end);
779 $query .= ' GROUP BY MONTH(open_date), YEAR(open_date) ORDER BY open_date';
780
781 return acym_loadObjectList($query);
782 }
783
784 public function getOpenByDay(array $mailIds = [], string $start = '', string $end = ''): array
785 {
786 if (!is_array($mailIds)) $mailIds = [$mailIds];
787 acym_arrayToInteger($mailIds);
788
789 $query = 'SELECT COUNT(user_id) as open, DATE_FORMAT(open_date, \'%Y-%m-%d\') as open_date FROM #__acym_user_stat WHERE open > 0';
790 $query .= empty($mailIds) ? '' : ' AND `mail_id` IN ('.implode(',', $mailIds).')';
791 $query .= ' AND `open_date` > "0000-00-00"';
792 $query .= empty($start) ? '' : ' AND `open_date` >= '.acym_escapeDB($start);
793 $query .= empty($end) ? '' : ' AND `open_date` <= '.acym_escapeDB($end);
794 $query .= ' GROUP BY DAYOFYEAR(open_date), YEAR(open_date) ORDER BY open_date';
795
796 return acym_loadObjectList($query);
797 }
798
799 public function getOpenByHour(array $mailIds = [], string $start = '', string $end = ''): array
800 {
801 if (!is_array($mailIds)) $mailIds = [$mailIds];
802 acym_arrayToInteger($mailIds);
803
804 $query = 'SELECT COUNT(user_id) as open, DATE_FORMAT(open_date, \'%Y-%m-%d %H:00:00\') as open_date FROM #__acym_user_stat WHERE open > 0';
805 $query .= empty($mailIds) ? '' : ' AND `mail_id` IN ('.implode(',', $mailIds).')';
806 $query .= ' AND `open_date` > "0000-00-00 00:00:00"';
807 $query .= empty($start) ? '' : ' AND `open_date` >= '.acym_escapeDB($start);
808 $query .= empty($end) ? '' : ' AND `open_date` <= '.acym_escapeDB($end);
809 $query .= ' GROUP BY HOUR(open_date), DAYOFYEAR(open_date), YEAR(open_date) ORDER BY open_date';
810
811 return acym_loadObjectList($query);
812 }
813
814 public function getDevicesWithCountByMailId(array $mailIds = []): array
815 {
816 acym_arrayToInteger($mailIds);
817
818 $query = 'SELECT device, COUNT(*) as number FROM #__acym_user_stat WHERE `open` > 0';
819 if (!empty($mailIds)) $query .= ' AND mail_id IN ('.implode(',', $mailIds).')';
820 $query .= ' GROUP BY device';
821
822 return acym_loadObjectList($query);
823 }
824
825 public function getLastNewsletters(array &$params): array
826 {
827 $querySelect = 'SELECT mail.*, campaign.sending_date ';
828 $queryCountSelect = 'SELECT COUNT(*) FROM (SELECT DISTINCT mail.id ';
829
830 $query = 'FROM #__acym_campaign AS campaign
831 JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id ';
832
833 if (isset($params['userId']) || isset($params['lists'])) {
834 $query .= 'JOIN #__acym_mail_has_list AS maillist ON mail.id = maillist.mail_id ';
835 if (isset($params['userId'])) $query .= 'JOIN #__acym_user_has_list AS userlist ON maillist.list_id = userlist.list_id ';
836 }
837
838 $where = 'WHERE campaign.active = 1 AND campaign.sent = 1 AND mail.type = '.acym_escapeDB(MailClass::TYPE_STANDARD).' AND campaign.visible = 1 ';
839
840 if (isset($params['lists'])) {
841 acym_arrayToInteger($params['lists']);
842 $where .= 'AND maillist.list_id IN ('.implode(', ', $params['lists']).') ';
843 }
844
845 if (isset($params['userId']) && !empty($params['displayUserListOnly'])) {
846 $where .= 'AND userlist.user_id = '.intval($params['userId']).' ';
847 }
848
849 if (isset($params['search'])) {
850 $search = acym_escapeDB('%'.acym_utf8Encode($params['search']).'%');
851 $where .= 'AND (mail.subject LIKE '.$search.' OR mail.body LIKE '.$search.')';
852 }
853
854 $query .= $where;
855 $return = [];
856 $return['count'] = (int)acym_loadResult($queryCountSelect.$query.') AS r ');
857
858 $endQuerySelect = 'GROUP BY mail.id ';
859 $endQuerySelect .= 'ORDER BY campaign.sending_date DESC';
860
861 $page = $params['page'] ?? 0;
862 $numberPerPage = $params['numberPerPage'] ?? 0;
863 $lastNewsletters = $params['limit'] ?? 0;
864
865 if (!empty($page) && !empty($numberPerPage)) {
866 if (!empty($lastNewsletters)) {
867 $limit = $page * $numberPerPage > $lastNewsletters ? fmod($lastNewsletters, $numberPerPage) : $numberPerPage;
868 } else {
869 $limit = $numberPerPage;
870 $nbTotalPage = ceil($return['count'] / $numberPerPage);
871 if ($params['page'] > $nbTotalPage) {
872 $params['page'] = 1;
873 }
874 }
875
876 $offset = ($params['page'] - 1) * $numberPerPage;
877 $endQuerySelect .= ' LIMIT '.intval($offset).', '.intval($limit);
878 } elseif (!empty($lastNewsletters)) {
879 $limit = $lastNewsletters;
880
881 $endQuerySelect .= ' LIMIT '.intval($limit);
882 }
883
884
885 $return['matchingNewsletters'] = $this->decode(acym_loadObjectList($querySelect.$query.$endQuerySelect));
886
887 if (isset($params['language']) && !empty($return['matchingNewsletters'])) {
888 $mailIds = array_column($return['matchingNewsletters'], 'id');
889
890 $translatedEmails = $this->decode(
891 acym_loadObjectList(
892 'SELECT mail.*
893 FROM #__acym_mail AS mail
894 WHERE mail.parent_id IN ('.implode(',', $mailIds).') AND mail.language = '.acym_escapeDB($params['language']),
895 'parent_id'
896 )
897 );
898 }
899
900 $userClass = new UserClass();
901 $userEmail = acym_currentUserEmail();
902 $user = $userClass->getOneByEmail($userEmail);
903
904 foreach ($return['matchingNewsletters'] as $i => $oneNewsletter) {
905 if (isset($translatedEmails[$oneNewsletter->id])) {
906 $translatedEmails[$oneNewsletter->id]->sending_date = $oneNewsletter->sending_date;
907 $oneNewsletter = $translatedEmails[$oneNewsletter->id];
908 }
909 acym_trigger('replaceContent', [&$oneNewsletter, false]);
910 acym_trigger('replaceUserInformation', [&$oneNewsletter, &$user, false]);
911
912 $return['matchingNewsletters'][$i] = $oneNewsletter;
913 }
914
915 return $return;
916 }
917
918 public function getListsByMailId(int $mailId): array
919 {
920 $listIds = acym_loadResultArray('SELECT list_id FROM #__acym_mail_has_list WHERE mail_id = '.intval($mailId));
921 acym_arrayToInteger($listIds);
922
923 return $listIds;
924 }
925
926 public function triggerAutoCampaign(): void
927 {
928 $activeAutoCampaigns = acym_loadObjectList(
929 'SELECT campaign.*, mail.name
930 FROM #__acym_campaign AS campaign
931 JOIN #__acym_mail AS mail ON campaign.`mail_id` = mail.`id`
932 WHERE `active` = 1 AND `sending_type` = '.acym_escapeDB(self::SENDING_TYPE_AUTO)
933 );
934 $activeAutoCampaigns = $this->decode($activeAutoCampaigns);
935
936 if (empty($activeAutoCampaigns)) {
937 return;
938 }
939
940 $mailClass = new MailClass();
941 $mailerHelper = new MailerHelper();
942 $mailerHelper->autoAddUser = true;
943 $adminNotificationEmail = $mailClass->getOneByName('acy_notification_auto_campaign_admin');
944 $time = time();
945
946 foreach ($activeAutoCampaigns as $campaign) {
947 $nextTrigger = $campaign->next_trigger;
948 if (empty($nextTrigger) && !empty($campaign->sending_params['start_date'])) {
949 $nextTrigger = $campaign->sending_params['start_date'];
950 }
951
952 if (!empty($nextTrigger) && acym_getTime(acym_date($nextTrigger, 'Y-m-d H:i')) > $time) {
953 continue;
954 }
955
956 $step = new \stdClass();
957 $step->triggers = $campaign->sending_params;
958 $step->last_execution = $campaign->last_generated;
959 $step->next_execution = $campaign->next_trigger;
960
961 $data = ['time' => $time];
962 $execute = !empty($step->next_execution) && $step->next_execution <= $data['time'];
963 acym_trigger('onAcymExecuteTrigger', [&$step, &$execute, &$data], 'plgAcymTime');
964 $campaign->next_trigger = $step->next_execution;
965 if (!$execute) {
966 $this->save($campaign);
967 continue;
968 }
969
970 $campaignMail = $mailClass->getOneById($campaign->mail_id);
971
972 $lastGenerated = $campaign->last_generated;
973 $shouldGenerate = $this->updateAutoCampaign($campaign, $campaignMail, $time);
974 $this->save($campaign);
975
976 if (!$shouldGenerate) {
977 continue;
978 }
979
980 $generatedCampaign = $this->generateCampaign($campaign, $campaignMail, $lastGenerated, $mailClass);
981 if (empty($generatedCampaign)) {
982 $this->messages[] = acym_translationSprintf('ACYM_CAMPAIGN_FAILED_GENERATING', $campaign->name);
983 continue;
984 }
985
986 $this->messages[] = acym_translationSprintf('ACYM_CAMPAIGN_GENERATED', $campaign->name, $campaign->sending_params['number_generated']);
987
988 if (empty($campaign->sending_params['need_confirm_to_send'])) {
989 $this->send($generatedCampaign->id);
990 } elseif (!empty($campaign->sending_params['admin_notification_emails']) && !empty($adminNotificationEmail)) {
991 $mailerHelper->addParam('campaign_name', $campaign->name);
992 $campaignLink = '<a href="'.acym_backendLink('campaigns&task=summaryGenerated&campaignId='.$generatedCampaign->id).'">'.$campaign->name.'</a>';
993 $mailerHelper->addParam('campaign_link', $campaignLink);
994
995 foreach ($campaign->sending_params['admin_notification_emails'] as $email) {
996 $mailerHelper->sendOne($adminNotificationEmail->id, $email);
997 }
998 }
999 }
1000 }
1001
1002 private function shouldGenerateCampaign(object $campaign, object $campaignMail): bool
1003 {
1004 $results = acym_trigger('generateByCategory', [&$campaignMail], null, function ($plugin) {
1005 $plugin->generateCampaignResult->status = true;
1006 });
1007
1008 foreach ($results as $oneResult) {
1009 if (isset($oneResult->status) && !$oneResult->status) {
1010 $this->messages[] = acym_translationSprintf('ACYM_CAMPAIGN_NOT_GENERATED', $campaign->name, $oneResult->message);
1011
1012 return false;
1013 }
1014 }
1015
1016 return true;
1017 }
1018
1019 private function updateAutoCampaign(object $campaign, object $campaignMail, int $time): bool
1020 {
1021 if (!$this->shouldGenerateCampaign($campaign, $campaignMail)) {
1022 return false;
1023 }
1024
1025 if (empty($campaign->sending_params['number_generated'])) {
1026 $campaign->sending_params['number_generated'] = 1;
1027 } else {
1028 $campaign->sending_params['number_generated']++;
1029 }
1030 $campaign->last_generated = $time;
1031
1032 return true;
1033 }
1034
1035 private function generateCampaign(object $campaign, object $campaignMail, ?int $lastGenerated, MailClass $mailClass): ?object
1036 {
1037 $newMail = $this->generateMailAutoCampaign($campaignMail, $campaign->sending_params['number_generated'], $mailClass);
1038 if (empty($newMail)) {
1039 return null;
1040 }
1041
1042 $newCampaign = new \stdClass();
1043 $newCampaign->mail_id = $newMail->id;
1044 $newCampaign->parent_id = $campaign->id;
1045 $newCampaign->active = 1;
1046 $newCampaign->visible = $campaign->visible;
1047 $newCampaign->draft = 1;
1048 $newCampaign->sending_type = self::SENDING_TYPE_NOW;
1049 $newCampaign->sent = 0;
1050 $newCampaign->last_generated = $lastGenerated;
1051 $newCampaign->sending_params = empty($campaign->sending_params['segment']) ? '' : ['segment' => $campaign->sending_params['segment']];
1052
1053 $newCampaign->id = $this->save($newCampaign);
1054
1055 acym_trigger('replaceContent', [&$newMail, false]);
1056 $mailClass->save($newMail);
1057
1058 return $newCampaign;
1059 }
1060
1061 private function generateMailAutoCampaign(object $newMail, int $generatedMailNumber, MailClass $mailClass): ?object
1062 {
1063 $mailId = $newMail->id;
1064 unset($newMail->id);
1065 $newMail->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
1066 $newMail->name .= ' #'.$generatedMailNumber;
1067
1068 $newMail->id = $mailClass->save($newMail);
1069 if (empty($newMail->id)) {
1070 return null;
1071 }
1072
1073 $this->setListToGeneratedCampaign($mailId, $newMail->id, $mailClass);
1074
1075 if (acym_isMultilingual()) {
1076 $this->generateMailAutoCampaignMultilingual($mailId, $generatedMailNumber, $newMail->id);
1077 }
1078
1079 return $newMail;
1080 }
1081
1082 private function generateMailAutoCampaignMultilingual(int $mailId, int $generatedMailNumber, int $newParentId): void
1083 {
1084 $mailClass = new MailClass();
1085 $mails = $mailClass->getTranslationsById($mailId, true);
1086
1087 foreach ($mails as $mail) {
1088 unset($mail->id);
1089 $mail->name .= ' #'.$generatedMailNumber;
1090 $mail->parent_id = $newParentId;
1091
1092 $mailClass->save($mail);
1093 }
1094 }
1095
1096 private function setListToGeneratedCampaign(int $parentMailId, int $newMailId, MailClass $mailClass): void
1097 {
1098 $lists = $mailClass->getAllListsByMailId($parentMailId);
1099 $listIds = array_keys($lists);
1100
1101 $this->manageListsToCampaign($listIds, $newMailId);
1102 }
1103
1104 public function getLastGenerated(int $mailId): int
1105 {
1106 $lastGenerated = acym_loadResult(
1107 'SELECT `last_generated`
1108 FROM #__acym_campaign
1109 WHERE `mail_id` = '.intval($mailId)
1110 );
1111
1112 return empty($lastGenerated) ? 0 : $lastGenerated;
1113 }
1114
1115 private function getNbGeneratedCampaigns(): int
1116 {
1117 return (int)acym_loadResult(
1118 'SELECT COUNT(*)
1119 FROM #__acym_campaign
1120 WHERE `parent_id` IS NOT NULL
1121 AND `sending_type` = '.acym_escapeDB(self::SENDING_TYPE_NOW)
1122 );
1123 }
1124
1125 public function getAllCampaignsGeneratedWaiting(): array
1126 {
1127 $campaignIds = acym_loadObjectList(
1128 'SELECT id
1129 FROM #__acym_campaign
1130 WHERE parent_id IS NOT NULL
1131 AND sending_type = '.acym_escapeDB(self::SENDING_TYPE_NOW).'
1132 AND draft = 1
1133 AND active = 1
1134 AND sent = 0'
1135 );
1136 acym_arrayToInteger($campaignIds);
1137
1138 return $campaignIds;
1139 }
1140
1141 public function getCampaignsByTypes(array $campaignTypes, bool $onlyActives = false): array
1142 {
1143 if (empty($campaignTypes)) return [];
1144 $campaignTypes = array_map('acym_escapeDB', $campaignTypes);
1145 $query = 'SELECT campaign.* FROM #__acym_campaign AS campaign WHERE campaign.sending_type IN ('.implode(',', $campaignTypes).')';
1146 if ($onlyActives) $query .= ' AND campaign.active = 1';
1147
1148 return $this->decode(acym_loadObjectList($query), false);
1149 }
1150
1151 public function getCountCampaignType(string $type): int
1152 {
1153 if (empty($type)) return 0;
1154
1155 return (int)acym_loadResult('SELECT COUNT(*) FROM #__acym_campaign WHERE sending_type = '.acym_escapeDB($type));
1156 }
1157
1158 public function getAllAbTestCampaignsToFinishSending(): array
1159 {
1160 $query = 'SELECT * FROM #__acym_campaign WHERE sending_params LIKE \'%"abtest":%\' AND draft = 0 AND active = 1 AND sent = 1';
1161
1162 return $this->decode(acym_loadObjectList($query));
1163 }
1164
1165 public function finishAbTestCampaign(object $campaign): bool
1166 {
1167 $abTestParams = empty($campaign->sending_params['abtest']) ? [] : $campaign->sending_params['abtest'];
1168
1169 if (empty($abTestParams['B']) || empty($abTestParams['repartition']) || !empty($abTestParams['abtest_finished']) || empty($abTestParams['after']['action'])) {
1170 return false;
1171 }
1172
1173 if (strtotime($campaign->sending_date.' + '.$abTestParams['after']['days'].' days') > time()) {
1174 return false;
1175 }
1176
1177 $mailStatsClass = new MailStatClass();
1178 $mailIds = [$campaign->mail_id, $abTestParams['B']];
1179 $bestMails = $mailStatsClass->getBestEmailByStats($mailIds, $abTestParams['after']['action']);
1180
1181 if (empty($bestMails)) {
1182 return false;
1183 }
1184
1185 $mailClass = new MailClass();
1186
1187 switch ($abTestParams['after']['action']) {
1188 case 'open_rate':
1189 case 'click_rate':
1190 $mail = $mailClass->getOneById($bestMails[$abTestParams['after']['action']]);
1191
1192 if (empty($mail)) {
1193 return false;
1194 }
1195 break;
1196 case 'click_open_rate':
1197 $bestMailClickRate = $mailClass->getOneById($bestMails['click_rate']);
1198 $bestMailOpenRate = $mailClass->getOneById($bestMails['open_rate']);
1199
1200 if (empty($bestMailClickRate) || empty($bestMailOpenRate)) {
1201 return false;
1202 }
1203
1204 $mail = $bestMailClickRate;
1205 $mail->preheader = $bestMailOpenRate->preheader;
1206 $mail->subject = $bestMailOpenRate->subject;
1207 $mail->from_name = $bestMailOpenRate->from_name;
1208 $mail->from_email = $bestMailOpenRate->from_email;
1209 $mail->reply_to_name = $bestMailOpenRate->reply_to_name;
1210 $mail->reply_to_email = $bestMailOpenRate->reply_to_email;
1211 break;
1212 }
1213
1214 $mail->id = 0;
1215 $mail->parent_id = $campaign->mail_id;
1216 $mail->name .= ' - '.acym_translation('ACYM_ABTEST_WINNER');
1217 $newMailId = $mailClass->save($mail);
1218 if (empty($newMailId)) {
1219 return false;
1220 }
1221
1222 $campaign->sending_params['abtest']['abtest_finished'] = 1;
1223 $campaign->sending_params['abtest']['final'] = $newMailId;
1224
1225 $this->save($campaign);
1226 $this->send($campaign->id, 0, true);
1227
1228 return true;
1229 }
1230
1231 public function isAbTestMail(int $mailId): bool
1232 {
1233 $mailClass = new MailClass();
1234 $mainMailId = $mailClass->getMainMailId($mailId);
1235
1236 $campaign = acym_loadObject(
1237 'SELECT *
1238 FROM #__acym_campaign
1239 WHERE mail_id = '.intval($mainMailId).'
1240 AND sending_params LIKE \'%"abtest":%\''
1241 );
1242
1243 return !empty($campaign);
1244 }
1245
1246 public function resetAbTestVersion(int $campaignId): void
1247 {
1248 if (empty($campaignId)) {
1249 return;
1250 }
1251
1252 $campaign = $this->getOneById($campaignId);
1253
1254 if (empty($campaign)) {
1255 return;
1256 }
1257
1258 if (empty($campaign->sending_params['abtest'])) {
1259 return;
1260 }
1261
1262 foreach (self::AB_TEST_VERSIONS as $version) {
1263 if (!empty($campaign->sending_params['abtest'][$version])) {
1264 $mailClass = new MailClass();
1265 $mailClass->delete([$campaign->sending_params['abtest'][$version]]);
1266 unset($campaign->sending_params['abtest'][$version]);
1267 }
1268 }
1269
1270 $this->save($campaign);
1271 }
1272
1273 public function getXCampaigns(array $options): array
1274 {
1275 $limit = $options['limit'] ?? 10;
1276 $offset = $options['offset'] ?? 0;
1277 $filters = $options['filters'] ?? [];
1278
1279 $conditions = [];
1280 foreach ($filters as $column => $filter) {
1281 switch ($column) {
1282 case 'id':
1283 case 'draf':
1284 case 'active':
1285 case 'mail_id':
1286 case 'sent':
1287 case 'parent_id':
1288 case 'next_trigger':
1289 case 'visible':
1290 case 'last_trigger':
1291 $conditions[] = 'campaign.'.acym_secureDBColumn($column).' = '.intval($filter);
1292 break;
1293 case 'name':
1294 case 'subject':
1295 $conditions[] = 'mail.'.acym_secureDBColumn($column).' LIKE '.acym_escapeDB('%'.$filter.'%');
1296 break;
1297 default:
1298 $conditions[] = 'campaign.'.acym_secureDBColumn($column).' = '.acym_escapeDB('%'.$filter.'%');
1299 }
1300 }
1301
1302 $query = 'SELECT campaign.*, mail.subject, mail.name FROM #__acym_campaign AS campaign
1303 LEFT JOIN #__acym_mail AS mail ON campaign.mail_id = mail.id';
1304
1305 if (!empty($conditions)) {
1306 $query .= ' WHERE '.implode(' AND ', $conditions);
1307 }
1308
1309 return $this->decode(acym_loadObjectList($query, $this->pkey, $offset, $limit));
1310 }
1311
1312 public function getOneCampaign(): bool
1313 {
1314 $firstCampaign = acym_loadObject('SELECT * FROM #__acym_campaign');
1315
1316 return !empty($firstCampaign);
1317 }
1318 }
1319