PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.11.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.11.1
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.11.1, at back/Classes/CampaignClass.php

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