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