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