HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/hi.naniguide.com/app/Http/Controllers/Customer/AnalyticsController.php
<?php

namespace App\Http\Controllers\Customer;

use App\Http\Controllers\Controller;
use App\Models\BounceLog;
use App\Models\CampaignRecipient;
use App\Models\CampaignLog;
use App\Models\CampaignTracking;
use App\Models\Complaint;
use App\Models\Campaign;
use App\Models\ListSubscriber;
use App\Models\SendingDomain;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;

class AnalyticsController extends Controller
{
    /**
     * Display the analytics dashboard.
     */
    public function index(Request $request)
    {
        $customer = auth('customer')->user();

        $range = (int) $request->query('range', 7);
        if (!in_array($range, [7, 30], true)) {
            $range = 7;
        }

        $domainId = $request->query('domain');
        $domainId = is_numeric($domainId) ? (int) $domainId : null;

        $end = Carbon::today($customer?->timezone ?: config('app.timezone'))->endOfDay();
        $start = (clone $end)->subDays($range - 1)->startOfDay();

        $prevEnd = (clone $start)->subDay()->endOfDay();
        $prevStart = (clone $prevEnd)->subDays($range - 1)->startOfDay();

        $domains = SendingDomain::query()
            ->where('customer_id', $customer->id)
            ->orderBy('domain')
            ->get(['id', 'domain']);

        $campaignScope = function ($q) use ($customer, $domainId) {
            $q->where('customer_id', $customer->id);
            if ($domainId) {
                $q->where('sending_domain_id', $domainId);
            }
        };

        $recipientBase = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('sent_at')
            ->whereBetween('sent_at', [$start, $end]);

        $recipientBasePrev = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('sent_at')
            ->whereBetween('sent_at', [$prevStart, $prevEnd]);

        $total = (clone $recipientBase)->count();
        $delivered = (clone $recipientBase)->whereIn('status', ['sent', 'opened', 'clicked'])->count();
        $bounced = (clone $recipientBase)->where('status', 'bounced')->count();

        $totalPrev = (clone $recipientBasePrev)->count();
        $deliveredPrev = (clone $recipientBasePrev)->whereIn('status', ['sent', 'opened', 'clicked'])->count();
        $bouncedPrev = (clone $recipientBasePrev)->where('status', 'bounced')->count();

        $unsubscribed = ListSubscriber::query()
            ->whereHas('list', function ($q) use ($customer) {
                $q->where('customer_id', $customer->id);
            })
            ->whereNotNull('unsubscribed_at')
            ->whereBetween('unsubscribed_at', [$start, $end])
            ->count();

        $unsubscribedPrev = ListSubscriber::query()
            ->whereHas('list', function ($q) use ($customer) {
                $q->where('customer_id', $customer->id);
            })
            ->whereNotNull('unsubscribed_at')
            ->whereBetween('unsubscribed_at', [$prevStart, $prevEnd])
            ->count();

        $hardBounced = BounceLog::query()
            ->whereHas('campaign', $campaignScope)
            ->where('bounce_type', 'hard')
            ->whereBetween('bounced_at', [$start, $end])
            ->count();

        $hardBouncedPrev = BounceLog::query()
            ->whereHas('campaign', $campaignScope)
            ->where('bounce_type', 'hard')
            ->whereBetween('bounced_at', [$prevStart, $prevEnd])
            ->count();

        $opened = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('opened_at')
            ->whereBetween('opened_at', [$start, $end])
            ->count();

        $openedPrev = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('opened_at')
            ->whereBetween('opened_at', [$prevStart, $prevEnd])
            ->count();

        $clicked = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('clicked_at')
            ->whereBetween('clicked_at', [$start, $end])
            ->count();

        $clickedPrev = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('clicked_at')
            ->whereBetween('clicked_at', [$prevStart, $prevEnd])
            ->count();

        $complained = Complaint::query()
            ->whereHas('campaign', $campaignScope)
            ->whereBetween('complained_at', [$start, $end])
            ->count();

        $complainedPrev = Complaint::query()
            ->whereHas('campaign', $campaignScope)
            ->whereBetween('complained_at', [$prevStart, $prevEnd])
            ->count();

        $deliveryRate = $total > 0 ? round(($delivered / $total) * 100, 2) : 0;
        $hardBounceRate = $total > 0 ? round(($hardBounced / $total) * 100, 2) : 0;
        $unsubscribeRate = $total > 0 ? round(($unsubscribed / $total) * 100, 2) : 0;
        $spamRate = $total > 0 ? round(($complained / $total) * 100, 2) : 0;

        $bounceRate = $total > 0 ? round(($bounced / $total) * 100, 2) : 0;
        $openRate = $total > 0 ? round(($opened / $total) * 100, 2) : 0;
        $clickRate = $total > 0 ? round(($clicked / $total) * 100, 2) : 0;
        $clickThroughRate = $opened > 0 ? round(($clicked / $opened) * 100, 2) : 0;

        $deliveryRatePrev = $totalPrev > 0 ? round(($deliveredPrev / $totalPrev) * 100, 2) : 0;
        $hardBounceRatePrev = $totalPrev > 0 ? round(($hardBouncedPrev / $totalPrev) * 100, 2) : 0;
        $unsubscribeRatePrev = $totalPrev > 0 ? round(($unsubscribedPrev / $totalPrev) * 100, 2) : 0;
        $spamRatePrev = $totalPrev > 0 ? round(($complainedPrev / $totalPrev) * 100, 2) : 0;
        $openRatePrev = $totalPrev > 0 ? round(($openedPrev / $totalPrev) * 100, 2) : 0;
        $clickRatePrev = $totalPrev > 0 ? round(($clickedPrev / $totalPrev) * 100, 2) : 0;
        $clickThroughRatePrev = $openedPrev > 0 ? round(($clickedPrev / $openedPrev) * 100, 2) : 0;

        $deltas = [
            'sent' => $totalPrev > 0 ? round((($total - $totalPrev) / $totalPrev) * 100, 2) : null,
            'open_rate' => round($openRate - $openRatePrev, 2),
            'click_rate' => round($clickRate - $clickRatePrev, 2),
            'click_through' => round($clickThroughRate - $clickThroughRatePrev, 2),
            'delivery_rate' => round($deliveryRate - $deliveryRatePrev, 2),
            'hard_bounce_rate' => round($hardBounceRate - $hardBounceRatePrev, 2),
            'unsubscribe_rate' => round($unsubscribeRate - $unsubscribeRatePrev, 2),
            'spam_rate' => round($spamRate - $spamRatePrev, 2),
        ];

        $dates = [];
        $cursor = $start->copy();
        while ($cursor->lte($end)) {
            $dates[] = $cursor->toDateString();
            $cursor->addDay();
        }

        $series = collect($dates)->mapWithKeys(function ($d) {
            return [$d => [
                'day' => $d,
                'total' => 0,
                'delivered' => 0,
                'bounced' => 0,
                'complained' => 0,
                'clicked' => 0,
                'opened' => 0,
                'unsubscribed' => 0,
                'hard_bounced' => 0,
            ]];
        })->all();

        $recipientByDay = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('sent_at')
            ->whereBetween('sent_at', [$start, $end])
            ->selectRaw('DATE(sent_at) as day')
            ->selectRaw('COUNT(*) as total')
            ->selectRaw('SUM(CASE WHEN status IN ("sent","opened","clicked") THEN 1 ELSE 0 END) as delivered')
            ->selectRaw('SUM(CASE WHEN status = "bounced" THEN 1 ELSE 0 END) as bounced')
            ->groupBy('day')
            ->get();

        foreach ($recipientByDay as $row) {
            $day = (string) $row->day;
            if (!isset($series[$day])) {
                continue;
            }
            $series[$day]['total'] = (int) $row->total;
            $series[$day]['delivered'] = (int) $row->delivered;
            $series[$day]['bounced'] = (int) $row->bounced;
        }

        $opensByDay = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('opened_at')
            ->whereBetween('opened_at', [$start, $end])
            ->selectRaw('DATE(opened_at) as day')
            ->selectRaw('COUNT(*) as opened')
            ->groupBy('day')
            ->get();

        foreach ($opensByDay as $row) {
            $day = (string) $row->day;
            if (!isset($series[$day])) {
                continue;
            }
            $series[$day]['opened'] = (int) $row->opened;
        }

        $clicksByDay = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('clicked_at')
            ->whereBetween('clicked_at', [$start, $end])
            ->selectRaw('DATE(clicked_at) as day')
            ->selectRaw('COUNT(*) as clicked')
            ->groupBy('day')
            ->get();

        foreach ($clicksByDay as $row) {
            $day = (string) $row->day;
            if (!isset($series[$day])) {
                continue;
            }
            $series[$day]['clicked'] = (int) $row->clicked;
        }

        if ($opened > 0 && collect($series)->sum('opened') === 0) {
            $opensByDay = CampaignLog::query()
                ->whereHas('campaign', $campaignScope)
                ->where('event', 'opened')
                ->whereBetween('created_at', [$start, $end])
                ->selectRaw('DATE(created_at) as day')
                ->selectRaw('COUNT(*) as opened')
                ->groupBy('day')
                ->get();

            foreach ($opensByDay as $row) {
                $day = (string) $row->day;
                if (!isset($series[$day])) {
                    continue;
                }
                $series[$day]['opened'] = (int) $row->opened;
            }
        }

        if ($clicked > 0 && collect($series)->sum('clicked') === 0) {
            $clicksByDay = CampaignLog::query()
                ->whereHas('campaign', $campaignScope)
                ->where('event', 'clicked')
                ->whereBetween('created_at', [$start, $end])
                ->selectRaw('DATE(created_at) as day')
                ->selectRaw('COUNT(*) as clicked')
                ->groupBy('day')
                ->get();

            foreach ($clicksByDay as $row) {
                $day = (string) $row->day;
                if (!isset($series[$day])) {
                    continue;
                }
                $series[$day]['clicked'] = (int) $row->clicked;
            }
        }

        $complaintsByDay = Complaint::query()
            ->whereHas('campaign', $campaignScope)
            ->whereBetween('complained_at', [$start, $end])
            ->selectRaw('DATE(complained_at) as day')
            ->selectRaw('COUNT(*) as complained')
            ->groupBy('day')
            ->get();

        foreach ($complaintsByDay as $row) {
            $day = (string) $row->day;
            if (!isset($series[$day])) {
                continue;
            }
            $series[$day]['complained'] = (int) $row->complained;
        }

        $unsubscribedByDay = ListSubscriber::query()
            ->whereHas('list', function ($q) use ($customer) {
                $q->where('customer_id', $customer->id);
            })
            ->whereNotNull('unsubscribed_at')
            ->whereBetween('unsubscribed_at', [$start, $end])
            ->selectRaw('DATE(unsubscribed_at) as day')
            ->selectRaw('COUNT(*) as unsubscribed')
            ->groupBy('day')
            ->get();

        foreach ($unsubscribedByDay as $row) {
            $day = (string) $row->day;
            if (!isset($series[$day])) {
                continue;
            }
            $series[$day]['unsubscribed'] = (int) $row->unsubscribed;
        }

        $hardBouncedByDay = BounceLog::query()
            ->whereHas('campaign', $campaignScope)
            ->where('bounce_type', 'hard')
            ->whereBetween('bounced_at', [$start, $end])
            ->selectRaw('DATE(bounced_at) as day')
            ->selectRaw('COUNT(*) as hard_bounced')
            ->groupBy('day')
            ->get();

        foreach ($hardBouncedByDay as $row) {
            $day = (string) $row->day;
            if (!isset($series[$day])) {
                continue;
            }
            $series[$day]['hard_bounced'] = (int) $row->hard_bounced;
        }

        $maxStack = 0;
        foreach ($series as $row) {
            $stack = (int) ($row['delivered'] + $row['opened'] + $row['clicked'] + $row['bounced'] + $row['complained']);
            $maxStack = max($maxStack, $stack);
        }
        $maxStack = max(1, $maxStack);

        $rateSeries = array_values(array_map(function (array $row) {
            $total = (int) ($row['total'] ?? 0);
            $opened = (int) ($row['opened'] ?? 0);
            $clicked = (int) ($row['clicked'] ?? 0);

            $openRate = $total > 0 ? round(($opened / $total) * 100, 2) : 0;
            $clickRate = $total > 0 ? round(($clicked / $total) * 100, 2) : 0;
            $clickThrough = $opened > 0 ? round(($clicked / $opened) * 100, 2) : 0;

            return [
                'day' => $row['day'],
                'open_rate' => $openRate,
                'click_rate' => $clickRate,
                'click_through' => $clickThrough,
            ];
        }, array_values($series)));

        $deviceCase = 'CASE '
            . 'WHEN user_agent LIKE "%iPad%" OR user_agent LIKE "%Tablet%" THEN "Tablet" '
            . 'WHEN user_agent LIKE "%Watch%" THEN "Smartwatch" '
            . 'WHEN user_agent LIKE "%iPhone%" OR (user_agent LIKE "%Android%" AND user_agent LIKE "%Mobile%") THEN "Smartphone" '
            . 'WHEN user_agent LIKE "%Macintosh%" OR user_agent LIKE "%Windows%" OR user_agent LIKE "%Linux%" THEN "Desktop/Laptop" '
            . 'ELSE "Other" END';

        $deviceRows = CampaignTracking::query()
            ->whereHas('campaign', $campaignScope)
            ->whereIn('event_type', ['opened', 'clicked'])
            ->whereBetween('event_at', [$start, $end])
            ->selectRaw($deviceCase . ' as device')
            ->selectRaw('SUM(CASE WHEN event_type = "opened" THEN 1 ELSE 0 END) as opened')
            ->selectRaw('SUM(CASE WHEN event_type = "clicked" THEN 1 ELSE 0 END) as clicked')
            ->groupBy('device')
            ->get();

        if ($deviceRows->sum(fn ($row) => (int) ($row->opened ?? 0) + (int) ($row->clicked ?? 0)) === 0 && ($opened > 0 || $clicked > 0)) {
            $deviceRows = CampaignLog::query()
                ->whereHas('campaign', $campaignScope)
                ->whereIn('event', ['opened', 'clicked'])
                ->whereNotNull('user_agent')
                ->whereBetween('created_at', [$start, $end])
                ->selectRaw($deviceCase . ' as device')
                ->selectRaw('SUM(CASE WHEN event = "opened" THEN 1 ELSE 0 END) as opened')
                ->selectRaw('SUM(CASE WHEN event = "clicked" THEN 1 ELSE 0 END) as clicked')
                ->groupBy('device')
                ->get();
        }

        $deviceOrder = ['Smartphone', 'Desktop/Laptop', 'Tablet', 'Smartwatch', 'Other'];
        $deviceStats = collect($deviceOrder)->map(function (string $label) use ($deviceRows) {
            $row = $deviceRows->firstWhere('device', $label);
            return [
                'label' => $label,
                'opened' => (int) ($row->opened ?? 0),
                'clicked' => (int) ($row->clicked ?? 0),
            ];
        })->values()->all();

        $campaignAgg = CampaignRecipient::query()
            ->whereHas('campaign', $campaignScope)
            ->whereNotNull('sent_at')
            ->whereBetween('sent_at', [$start, $end])
            ->selectRaw('campaign_id')
            ->selectRaw('COUNT(*) as sent')
            ->selectRaw('SUM(CASE WHEN status IN ("sent","opened","clicked") THEN 1 ELSE 0 END) as delivered')
            ->selectRaw('SUM(CASE WHEN clicked_at IS NOT NULL THEN 1 ELSE 0 END) as clicked')
            ->selectRaw('SUM(CASE WHEN opened_at IS NOT NULL THEN 1 ELSE 0 END) as opened')
            ->selectRaw('SUM(CASE WHEN status = "bounced" THEN 1 ELSE 0 END) as bounced')
            ->selectRaw('SUM(CASE WHEN status = "unsubscribed" THEN 1 ELSE 0 END) as unsubscribed')
            ->groupBy('campaign_id')
            ->get()
            ->keyBy('campaign_id');

        $campaignIds = $campaignAgg->keys()->filter()->values();

        $complaintsAgg = Complaint::query()
            ->whereHas('campaign', $campaignScope)
            ->whereBetween('complained_at', [$start, $end])
            ->selectRaw('campaign_id')
            ->selectRaw('COUNT(*) as complained')
            ->groupBy('campaign_id')
            ->get()
            ->keyBy('campaign_id');

        $campaignModels = Campaign::query()
            ->where('customer_id', $customer->id)
            ->whereIn('id', $campaignIds)
            ->when($domainId, fn ($q) => $q->where('sending_domain_id', $domainId))
            ->orderByDesc('send_at')
            ->orderByDesc('created_at')
            ->get(['id', 'name', 'subject', 'send_at', 'created_at']);

        $campaignRows = $campaignModels->map(function (Campaign $c) use ($campaignAgg, $complaintsAgg) {
            $agg = $campaignAgg->get($c->id);
            $sent = (int) ($agg->sent ?? 0);
            $delivered = (int) ($agg->delivered ?? 0);
            $opened = (int) ($agg->opened ?? 0);
            $clicked = (int) ($agg->clicked ?? 0);
            $unsubscribed = (int) ($agg->unsubscribed ?? 0);
            $complained = (int) ($complaintsAgg->get($c->id)->complained ?? 0);

            $clickRate = $sent > 0 ? round(($clicked / $sent) * 100, 2) : 0;
            $clickThrough = $opened > 0 ? round(($clicked / $opened) * 100, 2) : 0;
            $deliveredRate = $sent > 0 ? round(($delivered / $sent) * 100, 2) : 0;
            $unsubscribeRate = $sent > 0 ? round(($unsubscribed / $sent) * 100, 2) : 0;
            $spamRate = $sent > 0 ? round(($complained / $sent) * 100, 2) : 0;

            return [
                'id' => $c->id,
                'name' => $c->name,
                'published_at' => ($c->send_at ?? $c->created_at),
                'sent' => $sent,
                'click_rate' => $clickRate,
                'click_through' => $clickThrough,
                'delivered_rate' => $deliveredRate,
                'unsubscribe_rate' => $unsubscribeRate,
                'spam_rate' => $spamRate,
            ];
        })->values()->all();

        $page = LengthAwarePaginator::resolveCurrentPage();
        $perPage = 10;
        $campaignPage = array_slice($campaignRows, max(0, ($page - 1) * $perPage), $perPage);
        $campaigns = new LengthAwarePaginator(
            $campaignPage,
            count($campaignRows),
            $perPage,
            $page,
            [
                'path' => $request->url(),
                'query' => $request->query(),
            ]
        );

        return view('customer.analytics.index', [
            'domains' => $domains,
            'selectedDomainId' => $domainId,
            'range' => $range,
            'summary' => [
                'total' => $total,
                'delivered' => $delivered,
                'delivery_rate' => $deliveryRate,
                'bounced' => $bounced,
                'bounce_rate' => $bounceRate,
                'complained' => $complained,
                'opened' => $opened,
                'open_rate' => $openRate,
                'clicked' => $clicked,
                'click_rate' => $clickRate,
                'click_through' => $clickThroughRate,
                'unsubscribed' => $unsubscribed,
                'unsubscribe_rate' => $unsubscribeRate,
                'hard_bounced' => $hardBounced,
                'hard_bounce_rate' => $hardBounceRate,
                'spam_rate' => $spamRate,
                'deltas' => $deltas,
            ],
            'series' => array_values($series),
            'rateSeries' => $rateSeries,
            'deviceStats' => $deviceStats,
            'campaigns' => $campaigns,
            'maxStack' => $maxStack,
        ]);
    }
}