SeriesController   F
last analyzed

Complexity

Total Complexity 103

Size/Duplication

Total Lines 336
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 103
eloc 217
c 3
b 1
f 0
dl 0
loc 336
rs 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
A showTrending() 0 50 1
F index() 0 273 102

How to fix   Complexity   

Complex Class

Complex classes like SeriesController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SeriesController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Http\Controllers;
4
5
use App\Models\TvEpisode;
6
use App\Models\UserSerie;
7
use App\Models\Video;
8
use Blacklight\Releases;
9
use Carbon\Carbon;
10
use Illuminate\Http\Request;
11
use Illuminate\Support\Arr;
12
13
class SeriesController extends BasePageController
14
{
15
    /**
16
     * @throws \Exception
17
     */
18
    public function index(Request $request, string $id = '')
19
    {
20
        $releases = new Releases;
21
22
        if ($id && ctype_digit($id)) {
23
            $category = -1;
24
            if ($request->has('t') && ctype_digit($request->input('t'))) {
25
                $category = $request->input('t');
26
            }
27
28
            $catarray = [];
29
            $catarray[] = $category;
30
31
            $seriesLimit = (int) config('nntmux.series_view_limit', 200);
32
            $page = $request->has('page') && is_numeric($request->input('page')) ? (int) $request->input('page') : 1;
33
            $page = max($page, 1);
34
            $offset = $seriesLimit > 0 ? ($page - 1) * $seriesLimit : 0;
35
36
            $rel = $releases->tvSearch(['id' => $id], '', '', '', $offset, $seriesLimit, '', $catarray, -1);
37
38
            $show = Video::getByVideoID($id);
39
40
            $nodata = '';
41
            $seasons = [];
42
            $myshows = null;
43
            $seriestitles = '';
44
            $seriessummary = '';
45
            $seriescountry = '';
46
47
            if (! $show) {
48
                $nodata = 'No video information for this series.';
49
            } elseif (! $rel) {
50
                $nodata = 'No releases for this series.';
51
            } else {
52
                $myshows = UserSerie::getShow($this->userdata->id, $show['id']);
53
54
                // Hydrate missing season/episode numbers if tv_episodes_id is set but series/episode missing or zero.
55
                $episodeMeta = collect();
56
                $episodeIds = collect($rel)->pluck('tv_episodes_id')->filter(fn ($v) => $v > 0)->unique()->values();
57
                if ($episodeIds->isNotEmpty()) {
58
                    $episodeMeta = TvEpisode::whereIn('id', $episodeIds)->get()->keyBy('id');
59
                }
60
61
                foreach ($rel as $r) {
62
                    if ($r->tv_episodes_id > 0 && $episodeMeta->has($r->tv_episodes_id)) {
63
                        $meta = $episodeMeta[$r->tv_episodes_id];
64
                        if (empty($r->series) || (int) $r->series === 0) {
65
                            $r->series = (int) $meta->series;
66
                        }
67
                        if (empty($r->episode) || (int) $r->episode === 0) {
68
                            $r->episode = (int) $meta->episode;
69
                        }
70
                        if ((! $meta->series || (int) $meta->series === 0) && ! empty($meta->firstaired)) {
71
                            if (empty($r->series) || (int) $r->series === 0) {
72
                                $r->series = (int) Carbon::parse($meta->firstaired)->format('Y');
73
                            }
74
                            if (empty($r->episode) || (int) $r->episode === 0) {
75
                                $r->episode = (int) Carbon::parse($meta->firstaired)->format('md');
76
                            }
77
                        }
78
                    } elseif (! empty($r->firstaired)) {
79
                        if (empty($r->series) || (int) $r->series === 0) {
80
                            $r->series = (int) Carbon::parse($r->firstaired)->format('Y');
81
                        }
82
                        if (empty($r->episode) || (int) $r->episode === 0) {
83
                            $r->episode = (int) Carbon::parse($r->firstaired)->format('md');
84
                        }
85
                    }
86
87
                    if ((empty($r->series) || (int) $r->series === 0 || empty($r->episode) || (int) $r->episode === 0) && ! empty($r->searchname)) {
88
                        $matched = false;
89
90
                        if (! $matched && preg_match('/\bS(\d{1,2})E(\d{1,3})\b/i', $r->searchname, $m)) {
91
                            if (empty($r->series) || (int) $r->series === 0) {
92
                                $r->series = (int) $m[1];
93
                            }
94
                            if (empty($r->episode) || (int) $r->episode === 0) {
95
                                $r->episode = (int) $m[2];
96
                            }
97
                            $matched = true;
98
                        }
99
100
                        if (! $matched && preg_match('/\b(\d{1,2})x(\d{1,3})\b/i', $r->searchname, $m)) {
101
                            if (empty($r->series) || (int) $r->series === 0) {
102
                                $r->series = (int) $m[1];
103
                            }
104
                            if (empty($r->episode) || (int) $r->episode === 0) {
105
                                $r->episode = (int) $m[2];
106
                            }
107
                            $matched = true;
108
                        }
109
110
                        if (! $matched && preg_match('/\bSeason[\s._-]*(\d{1,2})[\s._-]*Episode[\s._-]*(\d{1,3})\b/i', $r->searchname, $m)) {
111
                            if (empty($r->series) || (int) $r->series === 0) {
112
                                $r->series = (int) $m[1];
113
                            }
114
                            if (empty($r->episode) || (int) $r->episode === 0) {
115
                                $r->episode = (int) $m[2];
116
                            }
117
                            $matched = true;
118
                        }
119
120
                        if (! $matched && preg_match('/\b(\d)(0[1-9]|[1-9]\d)\b/', $r->searchname, $m)) {
121
                            if ((empty($r->series) || (int) $r->series === 0) && (empty($r->episode) || (int) $r->episode === 0)) {
122
                                $r->series = (int) $m[1];
123
                                $r->episode = (int) $m[2];
124
                                $matched = true;
125
                            }
126
                        }
127
128
                        if (! $matched && preg_match('/\b(\d{4})[._-](\d{2})[._-](\d{2})\b/', $r->searchname, $m)) {
129
                            if (empty($r->series) || (int) $r->series === 0) {
130
                                $r->series = (int) $m[1];
131
                            }
132
                            if (empty($r->episode) || (int) $r->episode === 0) {
133
                                $r->episode = (int) ($m[2].$m[3]);
134
                            }
135
                            $matched = true;
136
                        }
137
138
                        if (! $matched && preg_match('/\b(?:Part|Pt)[\s._-]*(\d{1,3})\b/i', $r->searchname, $m)) {
139
                            if (empty($r->episode) || (int) $r->episode === 0) {
140
                                $r->episode = (int) $m[1];
141
                                if (empty($r->series) || (int) $r->series === 0) {
142
                                    $r->series = 1;
143
                                }
144
                                $matched = true;
145
                            }
146
                        }
147
148
                        if (! $matched && (empty($r->episode) || (int) $r->episode === 0) && preg_match('/\bEp?[\s._-]*(\d{1,3})\b/i', $r->searchname, $m)) {
149
                            $r->episode = (int) $m[1];
150
                            if (empty($r->series) || (int) $r->series === 0) {
151
                                $r->series = 1;
152
                            }
153
                        }
154
                    }
155
                }
156
157
                // Sort releases by season, episode, date posted.
158
                $series = $episode = $posted = [];
159
                foreach ($rel as $rlk => $rlv) {
160
                    $series[$rlk] = $rlv->series;
161
                    $episode[$rlk] = $rlv->episode;
162
                    $posted[$rlk] = $rlv->postdate;
163
                }
164
                Arr::sort($series, [[$episode, false], [$posted, false], $rel]);
165
166
                $series = [];
167
                foreach ($rel as $r) {
168
                    $series[$r->series][$r->episode][] = $r;
169
                }
170
171
                $seasons = Arr::sortRecursive($series);
172
173
                // get series name(s), description, country and genre
174
                $seriestitlesArray = $seriessummaryArray = $seriescountryArray = [];
175
                $seriestitlesArray[] = $show['title'];
176
177
                if (! empty($show['summary'])) {
178
                    $seriessummaryArray[] = $show['summary'];
179
                }
180
181
                if (! empty($show['countries_id'])) {
182
                    $seriescountryArray[] = $show['countries_id'];
183
                }
184
185
                $seriestitles = implode('/', array_map('trim', $seriestitlesArray));
186
                $seriessummary = $seriessummaryArray ? array_shift($seriessummaryArray) : '';
187
                $seriescountry = $seriescountryArray ? array_shift($seriescountryArray) : '';
188
            }
189
190
            // Calculate statistics
191
            $episodeCount = 0;
192
            $seasonCount = count($seasons);
193
            $totalSeasonsAvailable = $seasonCount;
194
195
            // Get first and last aired dates from TV episodes
196
            $firstEpisodeAired = null;
197
            $lastEpisodeAired = null;
198
            $totalSeasonsAired = 0;
199
            $totalEpisodesAired = 0;
200
201
            if (! empty($show['id'])) {
202
                $episodeStats = \App\Models\TvEpisode::query()
203
                    ->where('videos_id', $show['id'])
204
                    ->whereNotNull('firstaired')
205
                    ->where('firstaired', '!=', '')
206
                    ->selectRaw('MIN(firstaired) as first_aired, MAX(firstaired) as last_aired, COUNT(DISTINCT series) as total_seasons, COUNT(*) as total_episodes')
207
                    ->first();
208
209
                if ($episodeStats) {
210
                    if (! empty($episodeStats->first_aired) && $episodeStats->first_aired != '0000-00-00') {
0 ignored issues
show
Bug introduced by
The property first_aired does not exist on App\Models\TvEpisode. Did you mean firstaired?
Loading history...
211
                        $firstEpisodeAired = \Carbon\Carbon::parse($episodeStats->first_aired);
212
                    }
213
                    if (! empty($episodeStats->last_aired) && $episodeStats->last_aired != '0000-00-00') {
0 ignored issues
show
Bug introduced by
The property last_aired does not seem to exist on App\Models\TvEpisode. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
214
                        $lastEpisodeAired = \Carbon\Carbon::parse($episodeStats->last_aired);
215
                    }
216
                    $totalSeasonsAired = $episodeStats->total_seasons ?? 0;
0 ignored issues
show
Bug introduced by
The property total_seasons does not seem to exist on App\Models\TvEpisode. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
217
                    $totalEpisodesAired = $episodeStats->total_episodes ?? 0;
0 ignored issues
show
Bug introduced by
The property total_episodes does not exist on App\Models\TvEpisode. Did you mean episode?
Loading history...
218
                }
219
            }
220
221
            foreach ($seasons as $seasonNum => $episodes) {
222
                $episodeCount += count($episodes);
223
            }
224
225
            $catid = $category !== -1 ? $category : '';
226
            $totalRows = ($rel && $rel->count() > 0) ? ($rel[0]->_totalrows ?? $rel->count()) : 0;
227
            $totalPages = $seriesLimit > 0 ? (int) ceil(max($totalRows, 1) / $seriesLimit) : 1;
228
229
            $this->viewData = array_merge($this->viewData, [
230
                'seasons' => $seasons,
231
                'show' => $show,
232
                'myshows' => $myshows,
233
                'seriestitles' => $seriestitles,
234
                'seriessummary' => $seriessummary,
235
                'seriescountry' => $seriescountry,
236
                'category' => $catid,
237
                'nodata' => $nodata,
238
                'episodeCount' => $episodeCount,
239
                'seasonCount' => $seasonCount,
240
                'firstEpisodeAired' => $firstEpisodeAired,
241
                'lastEpisodeAired' => $lastEpisodeAired,
242
                'totalSeasonsAvailable' => $totalSeasonsAvailable,
243
                'totalSeasonsAired' => $totalSeasonsAired,
244
                'totalEpisodesAired' => $totalEpisodesAired,
245
                'pagination' => [
246
                    'per_page' => $seriesLimit,
247
                    'current_page' => $page,
248
                    'total_pages' => $totalPages,
249
                    'total_rows' => $totalRows,
250
                ],
251
                'meta_title' => 'View TV Series',
252
                'meta_keywords' => 'view,series,tv,show,description,details',
253
                'meta_description' => 'View TV Series',
254
            ]);
255
256
            return view('series.viewseries', $this->viewData);
257
        } else {
258
            $letter = ($id && preg_match('/^(0-9|[A-Z])$/i', $id)) ? $id : '0-9';
259
260
            $showname = ($request->has('title') && ! empty($request->input('title'))) ? $request->input('title') : '';
261
262
            if ($showname !== '' && ! $id) {
263
                $letter = '';
264
            }
265
266
            $masterserieslist = Video::getSeriesList($this->userdata->id, $letter, $showname);
267
268
            $serieslist = [];
269
            foreach ($masterserieslist as $s) {
270
                if (preg_match('/^[0-9]/', $s['title'])) {
271
                    $thisrange = '0-9';
272
                } else {
273
                    preg_match('/([A-Z]).*/i', $s['title'], $hits);
274
                    $thisrange = strtoupper($hits[1]);
275
                }
276
                $serieslist[$thisrange][] = $s;
277
            }
278
            ksort($serieslist);
279
280
            $this->viewData = array_merge($this->viewData, [
281
                'serieslist' => $serieslist,
282
                'seriesrange' => range('A', 'Z'),
283
                'seriesletter' => $letter,
284
                'showname' => $showname,
285
                'meta_title' => 'View Series List',
286
                'meta_keywords' => 'view,series,tv,show,description,details',
287
                'meta_description' => 'View Series List',
288
            ]);
289
290
            return view('series.viewserieslist', $this->viewData);
291
        }
292
    }
293
294
    /**
295
     * Show trending TV shows (top 15 most downloaded in last 48 hours)
296
     *
297
     * @throws \Exception
298
     */
299
    public function showTrending(Request $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

299
    public function showTrending(/** @scrutinizer ignore-unused */ Request $request)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
300
    {
301
        // Cache key for trending TV shows (48 hours)
302
        $cacheKey = 'trending_tv_top_15_48h';
303
304
        // Get trending TV shows from cache or calculate (refresh every hour)
305
        $trendingShows = \Illuminate\Support\Facades\Cache::remember($cacheKey, 3600, function () {
306
            // Calculate timestamp for 48 hours ago
307
            $fortyEightHoursAgo = \Illuminate\Support\Carbon::now()->subHours(48);
308
309
            // Get TV shows with their download counts from last 48 hours
310
            // Join with user_downloads to get actual download timestamps
311
            $query = \Illuminate\Support\Facades\DB::table('videos as v')
312
                ->join('tv_info as ti', 'v.id', '=', 'ti.videos_id')
313
                ->join('releases as r', 'v.id', '=', 'r.videos_id')
314
                ->leftJoin('user_downloads as ud', 'r.id', '=', 'ud.releases_id')
315
                ->select([
316
                    'v.id',
317
                    'v.title',
318
                    'v.started',
319
                    'v.tvdb',
320
                    'v.tvmaze',
321
                    'v.trakt',
322
                    'v.tmdb',
323
                    'v.countries_id',
324
                    'ti.summary',
325
                    'ti.image',
326
                    \Illuminate\Support\Facades\DB::raw('COUNT(DISTINCT ud.id) as total_downloads'),
327
                    \Illuminate\Support\Facades\DB::raw('COUNT(DISTINCT r.id) as release_count'),
328
                ])
329
                ->where('v.type', 0) // 0 = TV
330
                ->where('v.title', '!=', '')
331
                ->where('ud.timestamp', '>=', $fortyEightHoursAgo)
332
                ->groupBy('v.id', 'v.title', 'v.started', 'v.tvdb', 'v.tvmaze', 'v.trakt', 'v.tmdb', 'v.countries_id', 'ti.summary', 'ti.image')
333
                ->havingRaw('COUNT(DISTINCT ud.id) > 0')
334
                ->orderByDesc('total_downloads')
0 ignored issues
show
Bug introduced by
'total_downloads' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderByDesc(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

334
                ->orderByDesc(/** @scrutinizer ignore-type */ 'total_downloads')
Loading history...
335
                ->limit(15)
336
                ->get();
337
338
            return $query;
339
        });
340
341
        $this->viewData = array_merge($this->viewData, [
342
            'trendingShows' => $trendingShows,
343
            'meta_title' => 'Trending TV Shows - Last 48 Hours',
344
            'meta_keywords' => 'trending,tv,shows,series,popular,downloads,recent',
345
            'meta_description' => 'Browse the most popular and downloaded TV shows in the last 48 hours',
346
        ]);
347
348
        return view('series.trending', $this->viewData);
349
    }
350
}
351