PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.4
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.4
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.4, at back/Controllers/Stats/GlobalStats.php

903 lines 34.3 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 $selectedMailIds = array_unique($data['selectedMailid']);
377 $mailid = count($selectedMailIds) !== 1 ? 0 : (int)reset($selectedMailIds);
378
379 if (empty($mailid)) {
380 return;
381 }
382
383 $mailSelected = (int)$this->getVarFiltersListing('int', 'mail_id_language', $mailid);
384
385 $data['lists'] = [];
386 $data['emailsSent'] = [];
387 $data['emailsOpen'] = [];
388 $data['bounces'] = [];
389 $data['unsubscribed'] = [];
390 $data['click'] = [];
391 $data['listsStats'] = [];
392 $data['userPerList'] = [];
393
394 $mailClass = new MailClass();
395 $mail = $mailClass->getOneById($mailSelected);
396
397 if (empty($mail) || !in_array($mailid, [$mail->id, $mail->parent_id])) {
398 $mailSelected = $mailid;
399 $mail = $mailClass->getOneById($mailSelected);
400 }
401
402 if (empty($mail)) {
403 return;
404 }
405
406 $mainMailId = empty($mail->parent_id) ? $mail->id : $mail->parent_id;
407
408 // get lists from the selected campaign
409 $query = 'SELECT l.id, l.name, l.color FROM #__acym_list l';
410 $query .= ' JOIN #__acym_mail_has_list ml on l.id = ml.list_id';
411 $query .= ' WHERE ml.mail_id = '.intval($mainMailId);
412
413 $data['lists'] = acym_loadObjectList($query, 'id');
414 $listIds = implode(',', array_keys($data['lists']));
415
416 if (!empty($listIds)) {
417 //get nbSent, nbOpen, nbBounce, nbUnsubscribe
418 $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';
419 $query .= ' JOIN #__acym_user_stat us on ul.user_id = us.user_id';
420 $query .= ' WHERE ul.list_id in ('.$listIds.') AND us.mail_id = '.$mailSelected;
421 $query .= ' GROUP BY ul.list_id;';
422 $data['listsStats'] = acym_loadObjectList($query, 'list_id');
423
424 //get nbClick
425 $query = 'SELECT ul.list_id, COUNT(uc.click) as nbClick';
426 $query .= ' FROM #__acym_user_has_list ul ';
427 $query .= ' JOIN #__acym_url_click uc on ul.user_id = uc.user_id';
428 $query .= ' WHERE ul.list_id in ('.$listIds.') AND uc.mail_id = '.$mailSelected;
429 $query .= ' GROUP BY ul.list_id;';
430
431 $data['nbClick'] = acym_loadObjectList($query, 'list_id');
432
433 //get nbUser per list
434 $query = 'SELECT ul.list_id, COUNT(ul.user_id) as nbUser';
435 $query .= ' FROM #__acym_user_has_list ul';
436 $query .= ' JOIN #__acym_user_stat us ON ul.user_id = us.user_id';
437 $query .= ' WHERE ul.list_id in ('.$listIds.') AND us.mail_id = '.$mailSelected;
438 $query .= ' GROUP BY ul.list_id;';
439
440 $data['userPerList'] = acym_loadObjectList($query, 'list_id');
441 }
442
443 //format data for barChart
444 foreach ($data['listsStats'] as $listId => $item) {
445 $nbSent = empty($item->nbSent) ? 1 : $item->nbSent;
446
447 $data['emailsSent'][$listId] = [
448 'label' => $data['lists'][$listId]->name,
449 'value' => $item->nbSent,
450 'color' => $data['lists'][$listId]->color,
451 ];
452 $data['emailsOpen'][$listId] = [
453 'label' => $data['lists'][$listId]->name,
454 'value' => round(($item->nbOpen * 100) / $nbSent),
455 'color' => $data['lists'][$listId]->color,
456 ];
457 $data['bounces'][$listId] = [
458 'label' => $data['lists'][$listId]->name,
459 'value' => round(($item->nbBounce * 100) / $nbSent),
460 'color' => $data['lists'][$listId]->color,
461 ];
462 $data['unsubscribed'][$listId] = [
463 'label' => $data['lists'][$listId]->name,
464 'value' => round(($item->nbUnsub * 100) / $nbSent),
465 'color' => $data['lists'][$listId]->color,
466 ];
467 }
468
469 foreach ($data['userPerList'] as $listId => $item) {
470 $nbUser = $item->nbUser;
471 // we can't provide lists and their colors if there is no click,
472 // so we initialize the values with $data['lists'] to display lists with no click rather than nothing
473 $data['click'][$listId] = ['label' => $data['lists'][$listId]->name, 'value' => 0, 'color' => $data['lists'][$listId]->color];
474
475 if ($nbUser > 0 && !empty($data['nbClick'][$listId]->nbClick)) {
476 $data['click'][$listId]['value'] = round(($data['nbClick'][$listId]->nbClick * 100) / $nbUser);
477 }
478 }
479 }
480
481 private function prepareAbTestMails(array &$data): void
482 {
483 if (empty($this->selectedMailIds)) return;
484
485 $mailClass = new MailClass();
486
487 $mailVersions = [];
488
489 if (empty($data['mailInformation']->parent_id)) {
490 $mailVersions = $mailClass->getParentAndChildMails($data['mailInformation']->id);
491 } elseif (!empty($data['mailInformation']->parent_id)) {
492 $parentEmail = $mailClass->getOneById($data['mailInformation']->parent_id);
493 if (empty($parentEmail)) return;
494 $mailVersions = $mailClass->getParentAndChildMails($parentEmail->id);
495 }
496
497 $data['emailVersions'] = [];
498
499 foreach ($mailVersions as $email) {
500 $data['emailVersions'][$email->id] = $email->name.' - '.$email->subject;
501 }
502 }
503
504 private function prepareMultilingualMails(array &$data): void
505 {
506 if (empty($this->selectedMailIds)) return;
507
508 $mailClass = new MailClass();
509
510 $translatedEmails = [];
511
512 if (empty($data['mailInformation']->parent_id)) {
513 $translatedEmails = $mailClass->getTranslationsById($data['mailInformation']->id, true, true);
514 } elseif (!empty($data['mailInformation']->parent_id)) {
515 $parentEmail = $mailClass->getOneById($data['mailInformation']->parent_id);
516 if (empty($parentEmail)) return;
517 $translatedEmails = $mailClass->getTranslationsById($parentEmail->id, true, true);
518 }
519
520 $data['emailTranslations'] = [];
521 $allLanguages = acym_getLanguages();
522
523 foreach ($translatedEmails as $email) {
524 if (!empty($email->language)) {
525 $data['emailTranslations'][$email->id] = empty($allLanguages[$email->language]) ? $email->language
526 : $allLanguages[$email->language]->name;
527 }
528 }
529 }
530
531 private function prepareDevicesStats(array &$data): void
532 {
533 $campaignClass = new CampaignClass();
534 $devicesCampaign = $campaignClass->getDevicesWithCountByMailId($this->selectedMailIds);
535
536 $defaultDataDevices = [
537 'ACYM_MOBILE' => 0,
538 'ACYM_DESKTOP' => 0,
539 'ACYM_OTHER' => 0,
540 'ACYM_UNKNOWN' => 0,
541 ];
542 $data['devices'] = $defaultDataDevices;
543
544 foreach ($devicesCampaign as $oneDevice) {
545 if (empty($oneDevice->number)) {
546 continue;
547 }
548
549 if (empty($oneDevice->device)) {
550 $device = 'ACYM_UNKNOWN';
551 } elseif (in_array($oneDevice->device, array_merge(BrowserDetection::PLATFORMS_MOBILE, BrowserDetection::LEGACY_PLATFORMS_MOBILE))) {
552 $device = 'ACYM_MOBILE';
553 } elseif (in_array($oneDevice->device, array_merge(BrowserDetection::PLATFORMS_DESKTOP, BrowserDetection::LEGACY_PLATFORMS_DESKTOP))) {
554 $device = 'ACYM_DESKTOP';
555 } else {
556 $device = 'ACYM_OTHER';
557 }
558
559 $data['devices'][$device] += $oneDevice->number;
560 }
561
562 if ($data['devices'] === $defaultDataDevices) {
563 $data['devices'] = [];
564 }
565 }
566
567 private function prepareOpenSourcesStats(array &$data): void
568 {
569 $userStatClass = new UserStatClass();
570 $openedFromStats = $userStatClass->getOpenSourcesStats($this->selectedMailIds);
571
572 $data['openedWith'] = [];
573 foreach ($openedFromStats as $oneSource) {
574 if (empty($oneSource->number)) {
575 continue;
576 }
577
578 if (!empty($oneSource->opened_with) && isset(BrowserDetection::LEGACY_OPENED_WITH_MAP[$oneSource->opened_with])) {
579 $oneSource->opened_with = BrowserDetection::LEGACY_OPENED_WITH_MAP[$oneSource->opened_with];
580 }
581
582 if (empty($oneSource->opened_with)) {
583 $oneSource->opened_with = 'ACYM_UNKNOWN';
584 }
585
586 $data['openedWith'][$oneSource->opened_with] = $oneSource->number;
587 }
588 }
589
590 public function preparecharts(array &$data): void
591 {
592 $mailStatClass = new MailStatClass();
593
594 $data['mail'] = $mailStatClass->getSentFailByMailIds($this->selectedMailIds);
595 if (empty($data['mail'])) return;
596
597 $campaignClass = new CampaignClass();
598 $urlClickClass = new UrlClickClass();
599
600 //For the total opening, the doughnut chart
601 $data['mail']->totalMail = $data['mail']->sent + $data['mail']->fail;
602 $data['mail']->percentageSent = empty($data['mail']->totalMail) ? 0 : number_format(($data['mail']->sent * 100) / $data['mail']->totalMail, 2);
603 $data['mail']->allSent = empty($data['mail']->totalMail)
604 ? acym_translationSprintf('ACYM_X_MAIL_SUCCESSFULLY_SENT_OF_X', 0, 0)
605 : acym_translationSprintf(
606 'ACYM_X_MAIL_SUCCESSFULLY_SENT_OF_X',
607 $data['mail']->sent,
608 $data['mail']->totalMail
609 );
610
611 //open rate
612 $openRateCampaign = empty($this->selectedMailIds) ? $campaignClass->getOpenRateAllCampaign() : $campaignClass->getOpenRateCampaigns($this->selectedMailIds);
613 $data['mail']->percentageOpen = empty($openRateCampaign->sent) ? 0 : number_format(($openRateCampaign->open_unique * 100) / $openRateCampaign->sent, 2);
614 $data['mail']->allOpen = empty($openRateCampaign->sent)
615 ? acym_translationSprintf('ACYM_X_MAIL_OPENED_OF_X', 0, 0)
616 : acym_translationSprintf(
617 'ACYM_X_MAIL_OPENED_OF_X',
618 $openRateCampaign->open_unique,
619 $openRateCampaign->sent
620 );
621
622 //click rate
623 $clickRateCampaign = $urlClickClass->getNumberUsersClicked($this->selectedMailIds);
624 $data['mail']->percentageClick = empty($data['mail']->sent) ? 0 : number_format(($clickRateCampaign * 100) / $data['mail']->sent, 2);
625 $data['mail']->allClick = empty($data['mail']->sent)
626 ? acym_translationSprintf('ACYM_X_MAIL_CLICKED_OF_X', 0, 0)
627 : acym_translationSprintf(
628 'ACYM_X_MAIL_CLICKED_OF_X',
629 $clickRateCampaign,
630 $data['mail']->sent
631 );
632
633 //bounce rate
634 $bounceRateCampaign = empty($this->selectedMailIds) ? $campaignClass->getBounceRateAllCampaign() : $campaignClass->getBounceRateCampaigns($this->selectedMailIds);
635 $data['mail']->percentageBounce = empty($data['mail']->sent) ? 0 : number_format(($bounceRateCampaign->bounce_unique * 100) / $data['mail']->sent, 2);
636 $data['mail']->allBounce = empty($data['mail']->sent)
637 ? acym_translationSprintf('ACYM_X_BOUNCE_OF_X', 0, 0)
638 : acym_translationSprintf(
639 'ACYM_X_BOUNCE_OF_X',
640 $bounceRateCampaign->bounce_unique,
641 $data['mail']->sent
642 );
643
644 if (!empty($this->selectedMailIds)) {
645 //unsubscribe rate
646 $mailStat = $mailStatClass->getByMailIds($this->selectedMailIds);
647 $data['mail']->allUnsub = 0;
648 $data['mail']->percentageUnsub = 0;
649 if (!empty($mailStat)) {
650 $data['mail']->percentageUnsub = empty($data['mail']->sent) ? 0 : number_format(($mailStat->unsubscribe_total * 100) / $data['mail']->sent, 2);
651 $data['mail']->allUnsub = empty($data['mail']->sent)
652 ? acym_translationSprintf('ACYM_X_USERS_UNSUBSCRIBED_OF_X', 0, 0)
653 : acym_translationSprintf(
654 'ACYM_X_USERS_UNSUBSCRIBED_OF_X',
655 $mailStat->unsubscribe_total,
656 $data['mail']->sent
657 );
658 }
659 }
660
661 $this->prepareDevicesStats($data);
662 $this->prepareOpenSourcesStats($data);
663 $this->prepareLineChart($data['mail'], $this->selectedMailIds);
664 $this->prepareComparedStats($data);
665 }
666
667 private function decode(array &$detailedStats, array $columnsToDecode = ['mail_subject', 'mail_name']): void
668 {
669 foreach ($detailedStats as $oneDetailedStat) {
670 foreach ($columnsToDecode as $column) {
671 if (!empty($oneDetailedStat->$column)) {
672 $oneDetailedStat->$column = acym_utf8Decode($oneDetailedStat->$column);
673 }
674 }
675 }
676 }
677
678 private function prepareLinksDetailsListing(array &$data): void
679 {
680 $data['search'] = $this->getVarFiltersListing('string', 'links_details_search', '');
681 $data['ordering'] = $this->getVarFiltersListing('cmd', 'links_details_ordering', 'id');
682 $data['orderingSortOrder'] = $this->getVarFiltersListing('cmd', 'links_details_ordering_sort_order', 'desc');
683
684 if (empty($this->selectedMailIds)) return;
685
686 $pagination = new PaginationHelper();
687 $urlClickClass = new UrlClickClass();
688
689 $detailedStatsPerPage = $pagination->getListLimit();
690 $page = $this->getVarFiltersListing('int', 'links_details_pagination_page', 1);
691
692 $urlClicks = $urlClickClass->getUrlsFromMailsWithDetails(
693 [
694 'ordering' => $data['ordering'],
695 'search' => $data['search'],
696 'detailedStatsPerPage' => $detailedStatsPerPage,
697 'offset' => ($page - 1) * $detailedStatsPerPage,
698 'ordering_sort_order' => $data['orderingSortOrder'],
699 'mail_ids' => $this->selectedMailIds,
700 ]
701 );
702
703 $this->decode($urlClicks['links_details']);
704
705 // Prepare the pagination
706 $pagination->setStatus((int)$urlClicks['total']->total, $page, $detailedStatsPerPage);
707
708 $data['pagination'] = $pagination;
709 $data['links_details'] = $urlClicks['links_details'];
710 $data['query'] = $urlClicks['query'];
711 }
712
713 private function prepareListReceivers(array &$data): void
714 {
715 if (empty($this->selectedMailIds)) return;
716
717 $mailStatClass = new MailStatClass();
718 $mailClass = new MailClass();
719
720 $data['mailStat'] = $mailStatClass->getByMailIds($this->selectedMailIds);
721 $data['lists'] = $mailClass->getAllListsByMailId($this->selectedMailIds[0]);
722 }
723
724 private function exportGlobalFormatted(): void
725 {
726 $exportHelper = new ExportHelper();
727 $data = [];
728 $this->prepareDefaultPageInfo($data);
729 $data['show_date_filters'] = true;
730 $data['page_title'] = false;
731 $timeLinechart = acym_getVar('string', 'time_linechart', 'month');
732
733 $this->prepareMailFilter($data);
734 $this->prepareClickStats($data);
735 $this->preparecharts($data);
736 $this->prepareDefaultLineChart($data);
737 $this->prepareDefaultDevicesChart($data);
738 $this->prepareDefaultBrowsersChart($data);
739
740 $globalDonut = [
741 $data['mail']->percentageSent,
742 $data['mail']->percentageOpen,
743 $data['mail']->percentageClick,
744 $data['mail']->percentageBounce,
745 $data['mail']->percentageUnsub,
746 ];
747 $mailName = empty($this->selectedMailIds) ? acym_translation('ACYM_ALL_MAILS') : $data['mailInformation']->name;
748 $globalLine = $data['mail']->$timeLinechart;
749
750 $exportHelper->exportStatsFormattedCSV($mailName, $globalDonut, $globalLine, $timeLinechart);
751 exit;
752 }
753
754 private function exportGlobalFull(): void
755 {
756 $exportHelper = new ExportHelper();
757 $data = [];
758 $this->prepareDefaultPageInfo($data);
759
760 $where = '';
761 if (!empty($this->selectedMailIds)) {
762 $where = 'WHERE mail_id IN ('.implode(',', $this->selectedMailIds).')';
763 }
764
765 $columnsMailStat = acym_getColumns('mail_stat');
766 $columnsToExport = [];
767
768 $columnsToExport['mail.subject'] = acym_translation('ACYM_EMAIL_SUBJECT');
769 foreach ($columnsMailStat as $column) {
770 if ($column === 'mail_id') {
771 continue;
772 }
773
774 $translation = acym_translation('ACYM_'.strtoupper($column).'_COLUMN_STAT');
775 if ($column === 'send_date') {
776 $translation = acym_translation('ACYM_SEND_DATE');
777 } elseif ($column === 'click_total') {
778 $translation = acym_translation('ACYM_TOTAL_CLICKS');
779 } elseif ($column === 'click_unique') {
780 $translation = acym_translation('ACYM_UNIQUE_CLICKS');
781 }
782
783 $columnsToExport['mailstat.'.$column] = $translation;
784 }
785
786 $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;
787 $exportHelper->exportStatsFullCSV($query, $columnsToExport);
788 exit;
789 }
790
791 private function prepareComparedStats(array &$data): void
792 {
793 if (empty($data['totalSubscribers']) || empty($data['mail']->percentageOpen) || empty($data['mail']->percentageClick) || empty($data['mail']->percentageBounce)) {
794 return;
795 }
796
797 $currentDate = acym_date('now', 'Y-m-d');
798
799 $newStats = [
800 'date' => $currentDate,
801 'value' => [
802 'totalSubscribers' => $data['totalSubscribers'],
803 'openRate' => $data['mail']->percentageOpen,
804 'clickRate' => $data['mail']->percentageClick,
805 'bounceRate' => $data['mail']->percentageBounce,
806 'totalNewSubscribers' => $data['totalSubscribers'],
807 ],
808 ];
809
810 $keys = [
811 'totalSubscribersHistory' => true,
812 'percentageOpenHistory' => false,
813 'percentageClickHistory' => false,
814 'percentageBounceHistory' => false,
815 'totalNewSubscribersHistory' => false,
816 ];
817
818 $oldEvolutionData = json_decode($this->config->get('statsEvolution', '{}'), true);
819
820 foreach ($keys as $key => $isPercentage) {
821 $history = json_decode($this->config->get($key, '[]'), true);
822
823 if (!empty($history) && end($history)['date'] === $currentDate) {
824 continue;
825 }
826
827 $history[] = ['date' => $currentDate, 'value' => $newStats['value'][$this->getStatKey($key)]];
828
829 if (count($history) > 30) {
830 array_shift($history);
831 }
832
833 $this->config->saveConfig([$key => json_encode($history)]);
834
835 $evolutionKey = $this->getEvolutionKey($key);
836 $oldEvolutionData[$evolutionKey] = $this->calculateEvolution($history, $isPercentage);
837 }
838
839 $this->config->saveConfig(['statsEvolution' => json_encode($oldEvolutionData)]);
840
841 if (!empty($data)) {
842 $this->updateDashboardData($data);
843 }
844 }
845
846 private function getStatKey(string $key): ?string
847 {
848 return [
849 'totalSubscribersHistory' => 'totalSubscribers',
850 'percentageOpenHistory' => 'openRate',
851 'percentageClickHistory' => 'clickRate',
852 'percentageBounceHistory' => 'bounceRate',
853 'totalNewSubscribersHistory' => 'totalNewSubscribers',
854 ][$key] ?? null;
855 }
856
857 private function getEvolutionKey(string $key): ?string
858 {
859 return [
860 'totalSubscribersHistory' => 'totalSubscribersEvolution',
861 'percentageOpenHistory' => 'openRateEvolution',
862 'percentageClickHistory' => 'clickRateEvolution',
863 'percentageBounceHistory' => 'bounceRateEvolution',
864 'totalNewSubscribersHistory' => 'totalNewSubscribersEvolution',
865 ][$key] ?? null;
866 }
867
868 private function calculateEvolution(array $history, bool $isPercentage = false): ?float
869 {
870 if (empty($history)) {
871 return null;
872 }
873
874 $oldValue = $history[0]['value'] ?? 0;
875 $newValue = end($history)['value'] ?? 0;
876
877 if ($oldValue === 0) {
878 return null;
879 }
880
881 if ($isPercentage) {
882 // For totalSubscribers: percentage variation
883 return round((($newValue - $oldValue) / $oldValue) * 100, 2);
884 } else {
885 // For other stats: absolute difference
886 return round($newValue - $oldValue, 2);
887 }
888 }
889
890 private function updateDashboardData(array &$data): void
891 {
892 $evolutionData = json_decode($this->config->get('statsEvolution', '{}'), true);
893
894 if (!empty($evolutionData)) {
895 $data['totalSubscribersEvolution'] = $evolutionData['totalSubscribersEvolution'] ?? null;
896 $data['openRateEvolution'] = $evolutionData['openRateEvolution'] ?? null;
897 $data['clickRateEvolution'] = $evolutionData['clickRateEvolution'] ?? null;
898 $data['bounceRateEvolution'] = $evolutionData['bounceRateEvolution'] ?? null;
899 $data['totalNewSubscribersEvolution'] = $evolutionData['totalNewSubscribersEvolution'] ?? null;
900 }
901 }
902 }
903