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

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