params = $params; $this->builder = $this->createBuilder(); $this->dateRange = new MetricDateRange( start: $this->params['startDate'] ?? null, end: $this->params['endDate'] ?? null, timezone: $this->params['timezone'] ?? null, ); $metrics = explode(',', Arr::get($params, 'metrics', 'plays')); return collect($metrics) ->mapWithKeys(function ($metric) { if ($metric === 'movies') { return ['movies' => $this->getTitlesMetric(false)]; } elseif ($metric === 'series') { return ['series' => $this->getTitlesMetric(true)]; } else { $method = sprintf('get%sMetric', ucfirst($metric)); if (method_exists($this, $method)) { return [$metric => $this->$method()]; } return [$metric => []]; } }) ->toArray(); } protected function createBuilder(): Builder { $model = Arr::get($this->params, 'model', ''); $parts = explode('=', $model); // might send track_play=0, check if variable is set, instead of being truthy if (!isset($parts[0]) || !isset($parts[1])) { $parts = ['video_play', 0]; } $model = modelTypeToNamespace($parts[0]); $this->modelId = (int) $parts[1]; switch ($model) { case VideoPlay::class: // all plays, not scoped to any resource (for admin area) Gate::authorize('admin.access'); $builder = VideoPlay::query(); break; case Video::class: $video = Video::findOrFail($this->modelId); Gate::authorize('update', $video); $builder = $video->plays()->getQuery(); break; case Movie::class: case Series::class: case Title::class: $title = Title::findOrFail($this->modelId); Gate::authorize('update', $title); $builder = $title->plays()->getQuery(); break; case Season::class: $season = Season::with(['title'])->findOrFail($this->modelId); Gate::authorize('update', $season->title); $builder = VideoPlay::join( 'videos', 'video_plays.video_id', '=', 'videos.id', ) ->join('titles', 'videos.title_id', '=', 'titles.id') ->where('titles.id', $season->title_id) ->where('videos.season_num', $season->number); break; case Episode::class: $episode = Episode::with(['title'])->findOrFail($this->modelId); Gate::authorize('update', $episode->title); $builder = $episode->plays()->getQuery(); break; default: throw new Exception(); } return $builder; } protected function getPlaysMetric(): array { if (config('common.site.fake_plays_data')) { $data = (new GenerateFakePlaysData())->playsTrend( $this->builder, $this->dateRange, ); } else { $data = (new Trend( $this->builder, dateRange: $this->dateRange, ))->count(); } return [ 'granularity' => $this->dateRange->granularity, 'total' => array_sum(Arr::pluck($data, 'value')), 'datasets' => [ [ 'label' => __('Plays'), 'data' => $data, ], ], ]; } protected function getDevicesMetric(): array { return $this->getPartitionMetric('device', 5); } protected function getBrowsersMetric(): array { return $this->getPartitionMetric('browser', 8); } protected function getPlatformsMetric(): array { return $this->getPartitionMetric('platform', 5); } protected function getTitlesMetric(bool $isSeries = null): array { if (config('common.site.fake_plays_data')) { $data = (new GenerateFakePlaysData())->titles($isSeries); } else { $data = (new Partition( $this->builder ->join('videos', 'video_plays.video_id', '=', 'videos.id') ->join('titles', 'videos.title_id', '=', 'titles.id') ->when( !is_null($isSeries), fn($query) => $query->where('is_series', $isSeries), ) ->orderBy('aggregate', 'desc'), groupBy: 'title_id', dateRange: $this->dateRange, limit: 30, ))->count(); $titles = Title::whereIn('id', Arr::pluck($data, 'label')) ->compact() ->get(); $data = array_map(function ($item) use ($titles) { $title = $titles->firstWhere('id', $item['label']); $item['model'] = $title; $item['label'] = $title->name; return $item; }, $data); } return [ 'datasets' => [ [ 'label' => __('Plays'), 'data' => $data, ], ], ]; } protected function getSeasonsMetric(): array { if (config('common.site.fake_plays_data')) { $data = (new GenerateFakePlaysData())->seasons($this->modelId); } else { $data = (new Partition( $this->builder ->whereNotNull('season_num') ->orderBy('aggregate', 'desc'), groupBy: 'season_num', dateRange: $this->dateRange, limit: 30, ))->count(); $seasons = Season::where('title_id', $this->modelId) ->whereIn('number', Arr::pluck($data, 'label')) ->with(['title' => fn($query) => $query->compact()]) ->get(); $data = array_map(function ($item) use ($seasons) { $season = $seasons->firstWhere('number', (int) $item['label']); $item['model'] = $season; $item['label'] = __('Season :number', [ 'number' => $season->number, ]); return $item; }, $data); } return [ 'datasets' => [ [ 'label' => __('Plays'), 'data' => $data, ], ], ]; } protected function getEpisodesMetric(): array { if (config('common.site.fake_plays_data')) { $data = (new GenerateFakePlaysData())->episodes($this->modelId); } else { $data = (new Partition( $this->builder ->whereNotNull('episode_id') ->orderBy('aggregate', 'desc'), groupBy: 'episode_id', dateRange: $this->dateRange, limit: 30, ))->count(); $episodes = Episode::whereIn('id', Arr::pluck($data, 'label')) ->with(['title' => fn($query) => $query->compact()]) ->get(); $data = array_map(function ($item) use ($episodes) { $episode = $episodes->firstWhere('id', (int) $item['label']); $item['model'] = $episode; $item['label'] = __('Season :s, Episode :e', [ 's' => $episode->season_number, 'e' => $episode->number, ]); return $item; }, $data); } return [ 'datasets' => [ [ 'label' => __('Plays'), 'data' => $data, ], ], ]; } protected function getVideosMetric(): array { if (config('common.site.fake_plays_data')) { $data = (new GenerateFakePlaysData())->videos(); } else { $data = (new Partition( $this->builder->orderBy('aggregate', 'desc'), groupBy: 'video_id', dateRange: $this->dateRange, limit: 30, ))->count(); $videos = Video::whereIn('id', Arr::pluck($data, 'label')) ->with(['title' => fn($query) => $query->compact()]) ->get(); $data = array_map(function ($item) use ($videos) { $video = $videos->firstWhere('id', $item['label']); $item['model'] = $video; $item['label'] = $video?->name; return $item; }, $data); } $data = array_values(array_filter($data, fn($item) => $item['label'])); return [ 'datasets' => [ [ 'label' => __('Plays'), 'data' => $data, ], ], ]; } protected function getUsersMetric(): array { if (config('common.site.fake_plays_data')) { $data = (new GenerateFakePlaysData())->users(); } else { $data = (new Partition( $this->builder->orderBy('aggregate', 'desc'), groupBy: 'user_id', dateRange: $this->dateRange, limit: 30, ))->count(); $userIds = collect($data) ->pluck('label') ->filter() ->unique(); $users = User::whereIn('id', $userIds)->get(); $data = array_map(function ($item) use ($users) { $user = $users->firstWhere('id', $item['label']) ?? new User(['first_name' => __('Guest user')]); $item['model'] = $user; $item['label'] = $user->display_name; return $item; }, $data); } return [ 'datasets' => [ [ 'label' => __('Plays'), 'data' => $data, ], ], ]; } protected function getLocationsMetric(): array { $metric = $this->getPartitionMetric('location'); $countries = app(ValueLists::class)->countries(); $metric['datasets'][0]['data'] = array_map(function ($location) use ( $countries, $metric, ) { // only short country code is stored in DB, get and return full country name as well $location['code'] = strtolower($location['label']); $location['label'] = Arr::first( $countries, fn($country) => strtolower($country['code']) === strtolower($location['code']), )['name'] ?? $location['label']; return $location; }, $metric['datasets'][0]['data']); return $metric; } protected function getPartitionMetric( string $groupBy, int $limit = 10, ): array { if (config('common.site.fake_plays_data')) { $data = (new GenerateFakePlaysData())->partitionMetric($groupBy); } else { $data = (new Partition( $this->builder, groupBy: $groupBy, dateRange: $this->dateRange, limit: $limit, ))->count(); } return [ 'datasets' => [ [ 'label' => __('Plays'), 'data' => $data, ], ], ]; } }