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

GlobalStats.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.0.2, at back/Controllers/Stats/GlobalStats.php

892 lines 34.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Controllers\Stats;
4
5 use AcyMailing\Classes\CampaignClass;
6 use AcyMailing\Classes\MailClass;
7 use AcyMailing\Classes\MailStatClass;
8 use AcyMailing\Classes\UrlClickClass;
9 use AcyMailing\Classes\UserStatClass;
10 use AcyMailing\Helpers\ExportHelper;
11 use AcyMailing\Helpers\MailerHelper;
12 use AcyMailing\Helpers\PaginationHelper;
13 use AcyMailing\Helpers\WorkflowHelper;
14 use AcyMailing\Libraries\Browser\BrowserDetection;
15 use AcyMailing\Classes\UserClass;
16
17 trait GlobalStats
18 {
19 public function globalStats(): void
20 {
21 acym_setVar('layout', 'global_stats');
22
23 $data = [];
24
25 $this->prepareDefaultPageInfo($data);
26
27 $this->prepareOpenTimeChart($data);
28 $this->preparecharts($data);
29 $this->prepareListReceivers($data);
30 $this->prepareDefaultLineChart($data);
31 $this->prepareDefaultDevicesChart($data);
32 $this->prepareDefaultBrowsersChart($data);
33 if (count($this->selectedMailIds) == 1) {
34 if ($data['isAbTest']) {
35 $this->prepareAbTestMails($data);
36 } elseif (acym_isMultilingual()) {
37 $this->prepareMultilingualMails($data);
38 }
39 }
40 $this->prepareMailFilter($data);
41
42 parent::display($data);
43 }
44
45 private function prepareMailFilter(array &$data): void
46 {
47 $data['input_mail_ids'] = '';
48
49 if (!empty($this->multiLanguageMailAdded) || !empty($this->generatedMailAdded)) {
50 $this->selectedMailIds = array_filter(
51 $this->selectedMailIds,
52 function ($mailId) {
53 return !in_array($mailId, $this->multiLanguageMailAdded) && !in_array($mailId, $this->generatedMailAdded);
54 }
55 );
56 }
57
58 if (count($this->selectedMailIds) > 1) {
59 $data['input_mail_ids'] = '<input type="hidden" value="'.implode(',', $this->selectedMailIds).'" name="mail_ids">';
60 }
61
62 $attributes = [
63 'class' => 'acym__select acym_select2_ajax acym__stats__select__mails',
64 'data-placeholder' => acym_translation('ACYM_ALL_EMAILS'),
65 'data-ctrl' => 'stats',
66 'data-task' => 'searchSentMail',
67 'data-min' => '0',
68 'id' => 'mail_ids',
69 ];
70
71 if (!empty($this->selectedMailIds)) {
72 $attributes['data-selected'] = implode(',', $this->selectedMailIds);
73 }
74
75 $data['mail_filter_options'] = $attributes;
76 $data['emailVersionsFilters'] = [];
77
78 if (!empty($data['emailVersions'])) {
79 $data['emailVersionsFilters'] = [
80 'options' => $data['emailVersions'],
81 'mailId' => $this->selectedMailIds[0],
82 ];
83 }
84 if (!empty($data['emailTranslations'])) {
85 $data['emailVersionsFilters'] = [
86 'options' => $data['emailTranslations'],
87 'mailId' => $this->selectedMailIds[0],
88 ];
89 }
90 }
91
92 private function prepareClickStats(array &$data): void
93 {
94 if (empty($data['selectedMailid'])) {
95 return;
96 }
97
98 $urlClickClass = new UrlClickClass();
99 $allClickInfo = $urlClickClass->getAllLinkFromEmail($this->selectedMailIds[0]);
100
101 $data['url_click'] = [];
102 $data['url_click']['allClick'] = $allClickInfo['allClick'];
103
104 $allPercentage = [];
105 foreach ($allClickInfo['urls_click'] as $url) {
106 $percentage = 0;
107 if (empty($url->click)) {
108 $data['url_click'][$url->name] = ['percentage' => $percentage, 'numberClick' => '0'];
109 } else {
110 $percentage = intval(($url->click * 100) / $allClickInfo['allClick']);
111 $data['url_click'][$url->name] = ['percentage' => $percentage, 'numberClick' => $url->click];
112 }
113 $allPercentage[] = $percentage;
114 }
115
116 $helperMailer = new MailerHelper();
117 if (!empty($data['mailInformation'])) {
118 $helperMailer->body = $data['mailInformation']->body;
119 $helperMailer->statClick(intval($data['mailInformation']->id), 0, true);
120 $data['mailInformation']->body = preg_replace('#&(amp;)?autoSubId=[^"]+"#Uis', '"', $helperMailer->body);
121 }
122
123 if (!empty($allPercentage)) {
124 $maxPercentage = max($allPercentage);
125
126 foreach ($data['url_click'] as $name => $val) {
127 if ($name === 'allClick') continue;
128 $percentageRecalc = intval(($val['percentage'] * 100) / $maxPercentage);
129 if ($percentageRecalc <= 33) {
130 $data['url_click'][$name]['color'] = '0, 164, 255';
131 } elseif ($percentageRecalc <= 66) {
132 $data['url_click'][$name]['color'] = '248, 31, 255';
133 } else {
134 $data['url_click'][$name]['color'] = '255, 82, 89';
135 }
136 }
137 }
138
139 $data['url_click'] = json_encode($data['url_click']);
140
141 $data['url_foundation_email'] = ACYM_CSS.'libraries/foundation_email.min.css?v='.filemtime(ACYM_MEDIA.'css'.DS.'libraries'.DS.'foundation_email.min.css');
142 $data['url_click_map_email'] = ACYM_CSS.'click_map.min.css?v='.filemtime(ACYM_MEDIA.'css'.DS.'click_map.min.css');
143 }
144
145 public function prepareDefaultLineChart(array &$data): void
146 {
147 $data['example_line_chart'] = [
148 'month' => [
149 'Jan 18' => ['open' => '150', 'click' => '40'],
150 ],
151 'day' => [
152 '23 Jan' => ['open' => '150', 'click' => '40'],
153 ],
154 'hour' => [
155 '23 Jan' => ['open' => '150', 'click' => '40'],
156 '23 Jan 08:00' => ['open' => '25', 'click' => '10'],
157 '23 Jan 09:00' => ['open' => '50', 'click' => '10'],
158 '23 Jan 10:00' => ['open' => '16', 'click' => '10'],
159 '23 Jan 11:00' => ['open' => '59', 'click' => '10'],
160 ],
161 ];
162 }
163
164 public function prepareDefaultDevicesChart(array &$data): void
165 {
166 $data['example_devices_chart'] = [
167 'ACYM_MOBILE' => 25662,
168 'ACYM_DESKTOP' => 12471,
169 'ACYM_OTHER' => 3548,
170 'ACYM_UNKNOWN' => 6213,
171 ];
172 }
173
174 public function prepareDefaultBrowsersChart(array &$data): void
175 {
176 $data['example_source_chart'] = [
177 'Apple Mail' => 15835,
178 'Google Chrome' => 13375,
179 'Safari' => 2667,
180 'Outlook' => 2458,
181 'Edge' => 1190,
182 'Firefox' => 826,
183 'ACYM_OTHER' => 4775,
184 'ACYM_UNKNOWN' => 6123,
185 ];
186 }
187
188 public function setDataForChartLine(): void
189 {
190 $newStart = acym_date(acym_getVar('string', 'start'), 'Y-m-d H:i:s');
191 $newEnd = acym_date(acym_getVar('string', 'end'), 'Y-m-d H:i:s');
192 $mailIds = acym_getVar('int', 'id');
193
194 if (empty($mailIds)) {
195 $mailIds = [];
196 }
197
198 $mailClass = new MailClass();
199 if (!empty($mailIds)) {
200 $mailIds = $mailClass->getAutomaticMailIds($mailIds);
201 }
202
203 if (!empty($mailIds) && !is_array($mailIds)) {
204 $mailIds = [$mailIds];
205 }
206
207 if ($newStart >= $newEnd) {
208 echo 'error';
209 exit;
210 }
211
212 $statsCampaignSelected = new \stdClass();
213 $this->prepareLineChart($statsCampaignSelected, $mailIds, $newStart, $newEnd);
214
215 acym_lineChart($statsCampaignSelected->month, $statsCampaignSelected->day, $statsCampaignSelected->hour, true);
216 exit;
217 }
218
219 private function getValues(string $modifier, string $intervalCode, array $campaignOpens, array $campaignClicks, string $dateCode, bool $hour = false): array
220 {
221 $opens = [];
222 foreach ($campaignOpens as $one) {
223 if (!empty($one->open_date)) {
224 $opens[acym_date(acym_getTimeFromUTCDate($one->open_date), $dateCode)] = $one->open;
225 }
226 }
227
228 $clicks = [];
229 foreach ($campaignClicks as $one) {
230 if (!empty($one->date_click)) {
231 $clicks[acym_date(acym_getTimeFromUTCDate($one->date_click), $dateCode)] = $one->click;
232 }
233 }
234
235 $startDate = $campaignOpens[0]->open_date ?? null;
236 if (!empty($campaignClicks) && !empty($campaignClicks[0]->date_click)) {
237 $clickDate = $campaignClicks[0]->date_click;
238 if (!empty($startDate)) {
239 $startDate = min([$startDate, $clickDate]);
240 } else {
241 $startDate = $clickDate;
242 }
243 }
244
245 $lastOpen = end($campaignOpens);
246 $endDate = $lastOpen->open_date ?? null;
247 if (!empty($campaignClicks)) {
248 $lastClick = end($campaignClicks);
249 $clickDate = $lastClick->date_click ?? null;
250 if (!empty($endDate) && !empty($clickDate)) {
251 $endDate = max([$endDate, $clickDate]);
252 } elseif (!empty($clickDate)) {
253 $endDate = $clickDate;
254 }
255 }
256
257 if (empty($startDate) || empty($endDate)) {
258 return [];
259 }
260
261 // Apply timezone for chart by hour
262 if ($modifier === 'min') {
263 $startDate = acym_date(acym_getTimeFromUTCDate($startDate), 'Y-m-d H:i:s');
264 $endDate = acym_date(acym_getTimeFromUTCDate($endDate), 'Y-m-d H:i:s');
265 }
266
267 $begin = new \DateTime($startDate);
268 $end = new \DateTime($endDate);
269
270 $end->modify('+1 '.$modifier);
271
272 $interval = new \DateInterval($intervalCode);
273 $daterange = new \DatePeriod($begin, $interval, $end);
274
275 $result = [];
276 foreach ($daterange as $date) {
277 $one = $date->format($dateCode);
278
279 $current = [];
280 $current['open'] = empty($opens[$one]) ? 0 : $opens[$one];
281 $current['click'] = empty($clicks[$one]) ? 0 : $clicks[$one];
282
283 $key = $hour ? $one.':00' : $one;
284 $result[$key] = $current;
285 }
286
287 return $result;
288 }
289
290 public function prepareLineChart(object &$statsCampaignSelected, array $mailIdsOfCampaign, string $newStart = '', string $newEnd = ''): void
291 {
292 $campaignClass = new CampaignClass();
293 $statsCampaignSelected->hasStats = true;
294
295 //We get the opening by month, day, hour
296 $campaignOpenByMonth = $campaignClass->getOpenByMonth($mailIdsOfCampaign, $newStart, $newEnd);
297 $campaignOpenByDay = $campaignClass->getOpenByDay($mailIdsOfCampaign, $newStart, $newEnd);
298 $campaignOpenByHour = $campaignClass->getOpenByHour($mailIdsOfCampaign, $newStart, $newEnd);
299
300 if (empty($campaignOpenByMonth) || empty($campaignOpenByDay) || empty($campaignOpenByHour)) {
301 $statsCampaignSelected->hasStats = false;
302
303 return;
304 }
305
306 $urlClickClass = new UrlClickClass();
307 $campaignClickByMonth = $urlClickClass->getAllClickByMailMonth($mailIdsOfCampaign, $newStart, $newEnd);
308 $campaignClickByDay = $urlClickClass->getAllClickByMailDay($mailIdsOfCampaign, $newStart, $newEnd);
309 $campaignClickByHour = $urlClickClass->getAllClickByMailHour($mailIdsOfCampaign, $newStart, $newEnd);
310
311 $statsCampaignSelected->month = $this->getValues('day', 'P1M', $campaignOpenByMonth, $campaignClickByMonth, 'Y-m');
312 $statsCampaignSelected->day = $this->getValues('hour', 'P1D', $campaignOpenByDay, $campaignClickByDay, 'Y-m-d');
313 $statsCampaignSelected->hour = $this->getValues('min', 'PT1H', $campaignOpenByHour, $campaignClickByHour, 'Y-m-d H:i', true);
314
315 $allHour = array_keys($statsCampaignSelected->hour);
316
317 //those are the dates when the first was open and the last was open
318 $statsCampaignSelected->startEndDateHour = [];
319 if (!empty($allHour)) {
320 $statsCampaignSelected->startEndDateHour['start'] = $allHour[0];
321 $statsCampaignSelected->startEndDateHour['end'] = end($allHour);
322 }
323 }
324
325 public function exportGlobal(): void
326 {
327 wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
328
329 $exportType = acym_getVar('string', 'export_type', 'charts');
330
331 $functionName = 'exportGlobal'.ucfirst($exportType);
332
333 if (!method_exists($this, $functionName)) {
334 acym_enqueueMessage(acym_translation('ACYM_EXPORT_METHOD_NOT_FOUND'), 'error');
335 $this->listing();
336
337 return;
338 }
339
340 $this->$functionName();
341 }
342
343 public function prepareOpenTimeChart(array &$data): void
344 {
345 $userStatClass = new UserStatClass();
346 $statsDB = $userStatClass->getOpenTimeStats($this->selectedMailIds);
347
348 if (empty($statsDB['total_open'])) {
349 $data['openTime'] = $userStatClass->getDefaultStat();
350 $data['empty_open'] = true;
351
352 return;
353 }
354
355 $data['empty_open'] = false;
356
357 $stats = [];
358
359 for ($day = 0; $day < 7; $day++) {
360 $stats[$day] = [];
361 for ($hour = 0; $hour < 8; $hour++) {
362 if (empty($statsDB['stats'][$day.'_'.$hour])) {
363 $percentage = 0;
364 } else {
365 $percentage = ($statsDB['stats'][$day.'_'.$hour]->open_total * 100) / $statsDB['total_open'];
366 }
367 $stats[$day][$hour] = round($percentage);
368 }
369 }
370
371 $data['openTime'] = $stats;
372 }
373
374 public function prepareStatByList(array &$data): void
375 {
376 $mailid = (count(array_unique($data['selectedMailid'])) != 1) ? 0 : $data['selectedMailid'][0];
377
378 if (empty($mailid)) {
379 return;
380 }
381
382 $mailSelected = $this->getVarFiltersListing('int', 'mail_id_language', $mailid);
383
384 $mailClass = new MailClass();
385 $mail = $mailClass->getOneById($mailSelected);
386 $mainMailId = empty($mail->parent_id) ? $mail->id : $mail->parent_id;
387
388 $data['lists'] = [];
389 $data['emailsSent'] = [];
390 $data['emailsOpen'] = [];
391 $data['bounces'] = [];
392 $data['unsubscribed'] = [];
393 $data['click'] = [];
394 $data['listsStats'] = [];
395 $data['userPerList'] = [];
396
397 // get lists from the selected campaign
398 $query = 'SELECT l.id, l.name, l.color FROM #__acym_list l';
399 $query .= ' JOIN #__acym_mail_has_list ml on l.id = ml.list_id';
400 $query .= ' WHERE ml.mail_id = '.$mainMailId;
401
402 $data['lists'] = acym_loadObjectList($query, 'id');
403 $listIds = implode(',', array_keys($data['lists']));
404
405 if (!empty($listIds)) {
406 //get nbSent, nbOpen, nbBounce, nbUnsubscribe
407 $query = 'SELECT ul.list_id, SUM(us.sent) as nbSent, SUM(IF(us.open >= 1, 1, 0)) as nbOpen, SUM(us.bounce) as nbBounce, SUM(us.unsubscribe) as nbUnsub from #__acym_user_has_list ul';
408 $query .= ' JOIN #__acym_user_stat us on ul.user_id = us.user_id';
409 $query .= ' WHERE ul.list_id in ('.$listIds.') AND us.mail_id = '.$mailSelected;
410 $query .= ' GROUP BY ul.list_id;';
411 $data['listsStats'] = acym_loadObjectList($query, 'list_id');
412
413 //get nbClick
414 $query = 'SELECT ul.list_id, COUNT(uc.click) as nbClick';
415 $query .= ' FROM #__acym_user_has_list ul ';
416 $query .= ' JOIN #__acym_url_click uc on ul.user_id = uc.user_id';
417 $query .= ' WHERE ul.list_id in ('.$listIds.') AND uc.mail_id = '.$mailSelected;
418 $query .= ' GROUP BY ul.list_id;';
419
420 $data['nbClick'] = acym_loadObjectList($query, 'list_id');
421
422 //get nbUser per list
423 $query = 'SELECT ul.list_id, COUNT(ul.user_id) as nbUser';
424 $query .= ' FROM #__acym_user_has_list ul';
425 $query .= ' JOIN #__acym_user_stat us ON ul.user_id = us.user_id';
426 $query .= ' WHERE ul.list_id in ('.$listIds.') AND us.mail_id = '.$mailSelected;
427 $query .= ' GROUP BY ul.list_id;';
428
429 $data['userPerList'] = acym_loadObjectList($query, 'list_id');
430 }
431
432 //format data for barChart
433 foreach ($data['listsStats'] as $listId => $item) {
434 $nbSent = empty($item->nbSent) ? 1 : $item->nbSent;
435
436 $data['emailsSent'][$listId] = [
437 'label' => $data['lists'][$listId]->name,
438 'value' => $item->nbSent,
439 'color' => $data['lists'][$listId]->color,
440 ];
441 $data['emailsOpen'][$listId] = [
442 'label' => $data['lists'][$listId]->name,
443 'value' => round(($item->nbOpen * 100) / $nbSent),
444 'color' => $data['lists'][$listId]->color,
445 ];
446 $data['bounces'][$listId] = [
447 'label' => $data['lists'][$listId]->name,
448 'value' => round(($item->nbBounce * 100) / $nbSent),
449 'color' => $data['lists'][$listId]->color,
450 ];
451 $data['unsubscribed'][$listId] = [
452 'label' => $data['lists'][$listId]->name,
453 'value' => round(($item->nbUnsub * 100) / $nbSent),
454 'color' => $data['lists'][$listId]->color,
455 ];
456 }
457
458 foreach ($data['userPerList'] as $listId => $item) {
459 $nbUser = $item->nbUser;
460 // we can't provide lists and their colors if there is no click,
461 // so we initialize the values with $data['lists'] to display lists with no click rather than nothing
462 $data['click'][$listId] = ['label' => $data['lists'][$listId]->name, 'value' => 0, 'color' => $data['lists'][$listId]->color];
463
464 if ($nbUser > 0 && !empty($data['nbClick'][$listId]->nbClick)) {
465 $data['click'][$listId]['value'] = round(($data['nbClick'][$listId]->nbClick * 100) / $nbUser);
466 }
467 }
468 }
469
470 private function prepareAbTestMails(array &$data): void
471 {
472 if (empty($this->selectedMailIds)) return;
473
474 $mailClass = new MailClass();
475
476 $mailVersions = [];
477
478 if (empty($data['mailInformation']->parent_id)) {
479 $mailVersions = $mailClass->getParentAndChildMails($data['mailInformation']->id);
480 } elseif (!empty($data['mailInformation']->parent_id)) {
481 $parentEmail = $mailClass->getOneById($data['mailInformation']->parent_id);
482 if (empty($parentEmail)) return;
483 $mailVersions = $mailClass->getParentAndChildMails($parentEmail->id);
484 }
485
486 $data['emailVersions'] = [];
487
488 foreach ($mailVersions as $email) {
489 $data['emailVersions'][$email->id] = $email->name.' - '.$email->subject;
490 }
491 }
492
493 private function prepareMultilingualMails(array &$data): void
494 {
495 if (empty($this->selectedMailIds)) return;
496
497 $mailClass = new MailClass();
498
499 $translatedEmails = [];
500
501 if (empty($data['mailInformation']->parent_id)) {
502 $translatedEmails = $mailClass->getTranslationsById($data['mailInformation']->id, true, true);
503 } elseif (!empty($data['mailInformation']->parent_id)) {
504 $parentEmail = $mailClass->getOneById($data['mailInformation']->parent_id);
505 if (empty($parentEmail)) return;
506 $translatedEmails = $mailClass->getTranslationsById($parentEmail->id, true, true);
507 }
508
509 $data['emailTranslations'] = [];
510 $allLanguages = acym_getLanguages();
511
512 foreach ($translatedEmails as $email) {
513 if (!empty($email->language)) {
514 $data['emailTranslations'][$email->id] = empty($allLanguages[$email->language]) ? $email->language
515 : $allLanguages[$email->language]->name;
516 }
517 }
518 }
519
520 private function prepareDevicesStats(array &$data): void
521 {
522 $campaignClass = new CampaignClass();
523 $devicesCampaign = $campaignClass->getDevicesWithCountByMailId($this->selectedMailIds);
524
525 $defaultDataDevices = [
526 'ACYM_MOBILE' => 0,
527 'ACYM_DESKTOP' => 0,
528 'ACYM_OTHER' => 0,
529 'ACYM_UNKNOWN' => 0,
530 ];
531 $data['devices'] = $defaultDataDevices;
532
533 foreach ($devicesCampaign as $oneDevice) {
534 if (empty($oneDevice->number)) {
535 continue;
536 }
537
538 if (empty($oneDevice->device)) {
539 $device = 'ACYM_UNKNOWN';
540 } elseif (in_array($oneDevice->device, array_merge(BrowserDetection::PLATFORMS_MOBILE, BrowserDetection::LEGACY_PLATFORMS_MOBILE))) {
541 $device = 'ACYM_MOBILE';
542 } elseif (in_array($oneDevice->device, array_merge(BrowserDetection::PLATFORMS_DESKTOP, BrowserDetection::LEGACY_PLATFORMS_DESKTOP))) {
543 $device = 'ACYM_DESKTOP';
544 } else {
545 $device = 'ACYM_OTHER';
546 }
547
548 $data['devices'][$device] += $oneDevice->number;
549 }
550
551 if ($data['devices'] === $defaultDataDevices) {
552 $data['devices'] = [];
553 }
554 }
555
556 private function prepareOpenSourcesStats(array &$data): void
557 {
558 $userStatClass = new UserStatClass();
559 $openedFromStats = $userStatClass->getOpenSourcesStats($this->selectedMailIds);
560
561 $data['openedWith'] = [];
562 foreach ($openedFromStats as $oneSource) {
563 if (empty($oneSource->number)) {
564 continue;
565 }
566
567 if (!empty($oneSource->opened_with) && isset(BrowserDetection::LEGACY_OPENED_WITH_MAP[$oneSource->opened_with])) {
568 $oneSource->opened_with = BrowserDetection::LEGACY_OPENED_WITH_MAP[$oneSource->opened_with];
569 }
570
571 if (empty($oneSource->opened_with)) {
572 $oneSource->opened_with = 'ACYM_UNKNOWN';
573 }
574
575 $data['openedWith'][$oneSource->opened_with] = $oneSource->number;
576 }
577 }
578
579 public function preparecharts(array &$data): void
580 {
581 $mailStatClass = new MailStatClass();
582
583 $data['mail'] = $mailStatClass->getSentFailByMailIds($this->selectedMailIds);
584 if (empty($data['mail'])) return;
585
586 $campaignClass = new CampaignClass();
587 $urlClickClass = new UrlClickClass();
588
589 //For the total opening, the doughnut chart
590 $data['mail']->totalMail = $data['mail']->sent + $data['mail']->fail;
591 $data['mail']->percentageSent = empty($data['mail']->totalMail) ? 0 : number_format(($data['mail']->sent * 100) / $data['mail']->totalMail, 2);
592 $data['mail']->allSent = empty($data['mail']->totalMail)
593 ? acym_translationSprintf('ACYM_X_MAIL_SUCCESSFULLY_SENT_OF_X', 0, 0)
594 : acym_translationSprintf(
595 'ACYM_X_MAIL_SUCCESSFULLY_SENT_OF_X',
596 $data['mail']->sent,
597 $data['mail']->totalMail
598 );
599
600 //open rate
601 $openRateCampaign = empty($this->selectedMailIds) ? $campaignClass->getOpenRateAllCampaign() : $campaignClass->getOpenRateCampaigns($this->selectedMailIds);
602 $data['mail']->percentageOpen = empty($openRateCampaign->sent) ? 0 : number_format(($openRateCampaign->open_unique * 100) / $openRateCampaign->sent, 2);
603 $data['mail']->allOpen = empty($openRateCampaign->sent)
604 ? acym_translationSprintf('ACYM_X_MAIL_OPENED_OF_X', 0, 0)
605 : acym_translationSprintf(
606 'ACYM_X_MAIL_OPENED_OF_X',
607 $openRateCampaign->open_unique,
608 $openRateCampaign->sent
609 );
610
611 //click rate
612 $clickRateCampaign = $urlClickClass->getNumberUsersClicked($this->selectedMailIds);
613 $data['mail']->percentageClick = empty($data['mail']->sent) ? 0 : number_format(($clickRateCampaign * 100) / $data['mail']->sent, 2);
614 $data['mail']->allClick = empty($data['mail']->sent)
615 ? acym_translationSprintf('ACYM_X_MAIL_CLICKED_OF_X', 0, 0)
616 : acym_translationSprintf(
617 'ACYM_X_MAIL_CLICKED_OF_X',
618 $clickRateCampaign,
619 $data['mail']->sent
620 );
621
622 //bounce rate
623 $bounceRateCampaign = empty($this->selectedMailIds) ? $campaignClass->getBounceRateAllCampaign() : $campaignClass->getBounceRateCampaigns($this->selectedMailIds);
624 $data['mail']->percentageBounce = empty($data['mail']->sent) ? 0 : number_format(($bounceRateCampaign->bounce_unique * 100) / $data['mail']->sent, 2);
625 $data['mail']->allBounce = empty($data['mail']->sent)
626 ? acym_translationSprintf('ACYM_X_BOUNCE_OF_X', 0, 0)
627 : acym_translationSprintf(
628 'ACYM_X_BOUNCE_OF_X',
629 $bounceRateCampaign->bounce_unique,
630 $data['mail']->sent
631 );
632
633 if (!empty($this->selectedMailIds)) {
634 //unsubscribe rate
635 $mailStat = $mailStatClass->getByMailIds($this->selectedMailIds);
636 $data['mail']->allUnsub = 0;
637 $data['mail']->percentageUnsub = 0;
638 if (!empty($mailStat)) {
639 $data['mail']->percentageUnsub = empty($data['mail']->sent) ? 0 : number_format(($mailStat->unsubscribe_total * 100) / $data['mail']->sent, 2);
640 $data['mail']->allUnsub = empty($data['mail']->sent)
641 ? acym_translationSprintf('ACYM_X_USERS_UNSUBSCRIBED_OF_X', 0, 0)
642 : acym_translationSprintf(
643 'ACYM_X_USERS_UNSUBSCRIBED_OF_X',
644 $mailStat->unsubscribe_total,
645 $data['mail']->sent
646 );
647 }
648 }
649
650 $this->prepareDevicesStats($data);
651 $this->prepareOpenSourcesStats($data);
652 $this->prepareLineChart($data['mail'], $this->selectedMailIds);
653 $this->prepareComparedStats($data);
654 }
655
656 private function decode(array &$detailedStats, array $columnsToDecode = ['mail_subject', 'mail_name']): void
657 {
658 foreach ($detailedStats as $oneDetailedStat) {
659 foreach ($columnsToDecode as $column) {
660 if (!empty($oneDetailedStat->$column)) {
661 $oneDetailedStat->$column = acym_utf8Decode($oneDetailedStat->$column);
662 }
663 }
664 }
665 }
666
667 private function prepareLinksDetailsListing(array &$data): void
668 {
669 $data['search'] = $this->getVarFiltersListing('string', 'links_details_search', '');
670 $data['ordering'] = $this->getVarFiltersListing('cmd', 'links_details_ordering', 'id');
671 $data['orderingSortOrder'] = $this->getVarFiltersListing('cmd', 'links_details_ordering_sort_order', 'desc');
672
673 if (empty($this->selectedMailIds)) return;
674
675 $pagination = new PaginationHelper();
676 $urlClickClass = new UrlClickClass();
677
678 $detailedStatsPerPage = $pagination->getListLimit();
679 $page = $this->getVarFiltersListing('int', 'links_details_pagination_page', 1);
680
681 $urlClicks = $urlClickClass->getUrlsFromMailsWithDetails(
682 [
683 'ordering' => $data['ordering'],
684 'search' => $data['search'],
685 'detailedStatsPerPage' => $detailedStatsPerPage,
686 'offset' => ($page - 1) * $detailedStatsPerPage,
687 'ordering_sort_order' => $data['orderingSortOrder'],
688 'mail_ids' => $this->selectedMailIds,
689 ]
690 );
691
692 $this->decode($urlClicks['links_details']);
693
694 // Prepare the pagination
695 $pagination->setStatus((int)$urlClicks['total']->total, $page, $detailedStatsPerPage);
696
697 $data['pagination'] = $pagination;
698 $data['links_details'] = $urlClicks['links_details'];
699 $data['query'] = $urlClicks['query'];
700 }
701
702 private function prepareListReceivers(array &$data): void
703 {
704 if (empty($this->selectedMailIds)) return;
705
706 $mailStatClass = new MailStatClass();
707 $mailClass = new MailClass();
708
709 $data['mailStat'] = $mailStatClass->getByMailIds($this->selectedMailIds);
710 $data['lists'] = $mailClass->getAllListsByMailId($this->selectedMailIds[0]);
711 }
712
713 private function exportGlobalFormatted(): void
714 {
715 $exportHelper = new ExportHelper();
716 $data = [];
717 $this->prepareDefaultPageInfo($data);
718 $data['show_date_filters'] = true;
719 $data['page_title'] = false;
720 $timeLinechart = acym_getVar('string', 'time_linechart', 'month');
721
722 $this->prepareMailFilter($data);
723 $this->prepareClickStats($data);
724 $this->preparecharts($data);
725 $this->prepareDefaultLineChart($data);
726 $this->prepareDefaultDevicesChart($data);
727 $this->prepareDefaultBrowsersChart($data);
728
729 $globalDonut = [
730 $data['mail']->percentageSent,
731 $data['mail']->percentageOpen,
732 $data['mail']->percentageClick,
733 $data['mail']->percentageBounce,
734 $data['mail']->percentageUnsub,
735 ];
736 $mailName = empty($this->selectedMailIds) ? acym_translation('ACYM_ALL_MAILS') : $data['mailInformation']->name;
737 $globalLine = $data['mail']->$timeLinechart;
738
739 $exportHelper->exportStatsFormattedCSV($mailName, $globalDonut, $globalLine, $timeLinechart);
740 exit;
741 }
742
743 private function exportGlobalFull(): void
744 {
745 $exportHelper = new ExportHelper();
746 $data = [];
747 $this->prepareDefaultPageInfo($data);
748
749 $where = '';
750 if (!empty($this->selectedMailIds)) {
751 $where = 'WHERE mail_id IN ('.implode(',', $this->selectedMailIds).')';
752 }
753
754 $columnsMailStat = acym_getColumns('mail_stat');
755 $columnsToExport = [];
756
757 $columnsToExport['mail.subject'] = acym_translation('ACYM_EMAIL_SUBJECT');
758 foreach ($columnsMailStat as $column) {
759 if ($column === 'mail_id') {
760 continue;
761 }
762
763 $translation = acym_translation('ACYM_'.strtoupper($column).'_COLUMN_STAT');
764 if ($column === 'send_date') {
765 $translation = acym_translation('ACYM_SEND_DATE');
766 } elseif ($column === 'click_total') {
767 $translation = acym_translation('ACYM_TOTAL_CLICKS');
768 } elseif ($column === 'click_unique') {
769 $translation = acym_translation('ACYM_UNIQUE_CLICKS');
770 }
771
772 $columnsToExport['mailstat.'.$column] = $translation;
773 }
774
775 $query = 'SELECT '.implode(', ', array_keys($columnsToExport)).' FROM #__acym_mail_stat AS mailstat LEFT JOIN #__acym_mail AS mail ON mail.id = mailstat.mail_id '.$where;
776 $exportHelper->exportStatsFullCSV($query, $columnsToExport);
777 exit;
778 }
779
780 private function prepareComparedStats(array &$data): void
781 {
782 if (empty($data['totalSubscribers']) || empty($data['mail']->percentageOpen) || empty($data['mail']->percentageClick) || empty($data['mail']->percentageBounce)) {
783 return;
784 }
785
786 $currentDate = acym_date('now', 'Y-m-d');
787
788 $newStats = [
789 'date' => $currentDate,
790 'value' => [
791 'totalSubscribers' => $data['totalSubscribers'],
792 'openRate' => $data['mail']->percentageOpen,
793 'clickRate' => $data['mail']->percentageClick,
794 'bounceRate' => $data['mail']->percentageBounce,
795 'totalNewSubscribers' => $data['totalSubscribers'],
796 ],
797 ];
798
799 $keys = [
800 'totalSubscribersHistory' => true,
801 'percentageOpenHistory' => false,
802 'percentageClickHistory' => false,
803 'percentageBounceHistory' => false,
804 'totalNewSubscribersHistory' => false,
805 ];
806
807 $oldEvolutionData = json_decode($this->config->get('statsEvolution', '{}'), true);
808
809 foreach ($keys as $key => $isPercentage) {
810 $history = json_decode($this->config->get($key, '[]'), true);
811
812 if (!empty($history) && end($history)['date'] === $currentDate) {
813 continue;
814 }
815
816 $history[] = ['date' => $currentDate, 'value' => $newStats['value'][$this->getStatKey($key)]];
817
818 if (count($history) > 30) {
819 array_shift($history);
820 }
821
822 $this->config->saveConfig([$key => json_encode($history)]);
823
824 $evolutionKey = $this->getEvolutionKey($key);
825 $oldEvolutionData[$evolutionKey] = $this->calculateEvolution($history, $isPercentage);
826 }
827
828 $this->config->saveConfig(['statsEvolution' => json_encode($oldEvolutionData)]);
829
830 if (!empty($data)) {
831 $this->updateDashboardData($data);
832 }
833 }
834
835 private function getStatKey(string $key): ?string
836 {
837 return [
838 'totalSubscribersHistory' => 'totalSubscribers',
839 'percentageOpenHistory' => 'openRate',
840 'percentageClickHistory' => 'clickRate',
841 'percentageBounceHistory' => 'bounceRate',
842 'totalNewSubscribersHistory' => 'totalNewSubscribers',
843 ][$key] ?? null;
844 }
845
846 private function getEvolutionKey(string $key): ?string
847 {
848 return [
849 'totalSubscribersHistory' => 'totalSubscribersEvolution',
850 'percentageOpenHistory' => 'openRateEvolution',
851 'percentageClickHistory' => 'clickRateEvolution',
852 'percentageBounceHistory' => 'bounceRateEvolution',
853 'totalNewSubscribersHistory' => 'totalNewSubscribersEvolution',
854 ][$key] ?? null;
855 }
856
857 private function calculateEvolution(array $history, bool $isPercentage = false): ?float
858 {
859 if (empty($history)) {
860 return null;
861 }
862
863 $oldValue = $history[0]['value'] ?? 0;
864 $newValue = end($history)['value'] ?? 0;
865
866 if ($oldValue === 0) {
867 return null;
868 }
869
870 if ($isPercentage) {
871 // For totalSubscribers: percentage variation
872 return round((($newValue - $oldValue) / $oldValue) * 100, 2);
873 } else {
874 // For other stats: absolute difference
875 return round($newValue - $oldValue, 2);
876 }
877 }
878
879 private function updateDashboardData(array &$data): void
880 {
881 $evolutionData = json_decode($this->config->get('statsEvolution', '{}'), true);
882
883 if (!empty($evolutionData)) {
884 $data['totalSubscribersEvolution'] = $evolutionData['totalSubscribersEvolution'] ?? null;
885 $data['openRateEvolution'] = $evolutionData['openRateEvolution'] ?? null;
886 $data['clickRateEvolution'] = $evolutionData['clickRateEvolution'] ?? null;
887 $data['bounceRateEvolution'] = $evolutionData['bounceRateEvolution'] ?? null;
888 $data['totalNewSubscribersEvolution'] = $evolutionData['totalNewSubscribersEvolution'] ?? null;
889 }
890 }
891 }
892