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