Passed
Push — master ( 68504f...ba1af3 )
by Darko
03:47
created

Releases   F

Complexity

Total Complexity 205

Size/Duplication

Total Lines 1087
Duplicated Lines 0 %

Importance

Changes 24
Bugs 7 Features 0
Metric Value
wmc 205
eloc 682
c 24
b 7
f 0
dl 0
loc 1087
rs 1.918

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getBrowseOrdering() 0 15 1
A deleteMultiple() 0 9 2
C apiSearch() 0 73 16
C getBrowseRange() 0 62 10
B searchSimilar() 0 27 8
F tvSearch() 0 134 39
A getLatestUsenetPostDate() 0 5 2
A getShowsCount() 0 18 3
C moviesSearch() 0 68 17
A updateMulti() 0 16 4
A getEarliestUsenetPostDate() 0 5 2
A getPagerCount() 0 23 2
A uSQL() 0 18 4
A getBrowseOrder() 0 13 4
D search() 0 109 18
F apiTvSearch() 0 126 34
A getBrowseCount() 0 17 5
A showPasswords() 0 8 1
B deleteSingle() 0 37 7
A getReleasedGroupsForSelect() 0 17 3
A getShowsRange() 0 36 5
B animeSearch() 0 62 11
A getForExport() 0 29 6

How to fix   Complexity   

Complex Class

Complex classes like Releases 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 Releases, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Blacklight;
4
5
use App\Models\Category;
6
use App\Models\Release;
7
use App\Models\Settings;
8
use App\Models\UsenetGroup;
9
use Elasticsearch;
10
use Elasticsearch\Common\Exceptions\Missing404Exception;
11
use Illuminate\Database\Eloquent\Collection;
12
use Illuminate\Support\Arr;
13
use Illuminate\Support\Facades\Cache;
14
use Illuminate\Support\Facades\DB;
15
use Illuminate\Support\Facades\File;
16
17
/**
18
 * Class Releases.
19
 */
20
class Releases extends Release
21
{
22
    // RAR/ZIP Password indicator.
23
    public const PASSWD_NONE = 0; // No password.
24
25
    public const PASSWD_RAR = 1; // Definitely passworded.
26
27
    public int $passwordStatus;
28
29
    private ManticoreSearch $manticoreSearch;
30
31
    private ElasticSearchSiteSearch $elasticSearch;
32
33
    /**
34
     * @var array Class instances.
35
     *
36
     * @throws \Exception
37
     */
38
    public function __construct()
39
    {
40
        parent::__construct();
41
        $this->manticoreSearch = new ManticoreSearch;
42
        $this->elasticSearch = new ElasticSearchSiteSearch;
43
    }
44
45
    /**
46
     * Used for Browse results.
47
     *
48
     * @return Collection|mixed
49
     */
50
    public function getBrowseRange($page, $cat, $start, $num, $orderBy, int $maxAge = -1, array $excludedCats = [], int|string $groupName = -1, int $minSize = 0): mixed
51
    {
52
        $page = max(1, $page);
53
        $start = max(0, $start);
54
55
        $orderBy = $this->getBrowseOrder($orderBy);
56
57
        $query = self::query()
58
            ->with(['group', 'category', 'category.parent', 'video', 'video.episode', 'videoData', 'nfo', 'failed'])
59
            ->where('nzbstatus', NZB::NZB_ADDED)
60
            ->where('passwordstatus', $this->showPasswords());
61
62
        if ($cat) {
63
            $categories = Category::getCategorySearch($cat, null, true);
64
            // If categories is empty, we don't want to return anything.
65
            if (! empty($categories)) {
66
                // if we have more than one category, we need to use whereIn
67
                if (\count($categories) > 1) {
0 ignored issues
show
Bug introduced by
It seems like $categories can also be of type string; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

67
                if (\count(/** @scrutinizer ignore-type */ $categories) > 1) {
Loading history...
68
                    $query->whereIn('categories_id', $categories);
69
                } else {
70
                    $query->where('categories_id', $categories);
71
                }
72
            }
73
        }
74
75
        if ($maxAge > 0) {
76
            $query->where('postdate', '>', now()->subDays($maxAge));
77
        }
78
79
        if (! empty($excludedCats)) {
80
            $query->whereNotIn('categories_id', $excludedCats);
81
        }
82
83
        if ($groupName !== -1) {
84
            $query->whereHas('usenetGroup', function ($q) use ($groupName) {
85
                $q->where('name', $groupName);
86
            });
87
        }
88
89
        if ($minSize > 0) {
90
            $query->where('size', '>=', $minSize);
91
        }
92
93
        $query->orderBy($orderBy[0], $orderBy[1])
94
            ->skip($start)
95
            ->take($num);
96
97
        $releases = Cache::get(md5($query->toSql().$page));
0 ignored issues
show
Bug introduced by
Are you sure $query->toSql() of type Illuminate\Database\Eloquent\Builder|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

97
        $releases = Cache::get(md5(/** @scrutinizer ignore-type */ $query->toSql().$page));
Loading history...
98
        if ($releases !== null) {
99
            return $releases;
100
        }
101
102
        $sql = $query->get();
103
        if ($sql->isNotEmpty()) {
104
            $possibleRows = $this->getBrowseCount($cat, $maxAge, $excludedCats, $groupName);
105
            $sql[0]->_totalcount = $sql[0]->_totalrows = $possibleRows;
106
        }
107
108
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
109
        Cache::put(md5($query->toSql().$page), $sql, $expiresAt);
110
111
        return $sql;
112
    }
113
114
    /**
115
     * Used for pager on browse page.
116
     */
117
    public function getBrowseCount(array $cat, int $maxAge = -1, array $excludedCats = [], int|string $groupName = ''): int
118
    {
119
        return $this->getPagerCount(sprintf(
120
            'SELECT COUNT(r.id) AS count
121
				FROM releases r
122
				%s
123
				WHERE r.nzbstatus = %d
124
				AND r.passwordstatus %s
125
				%s
126
				%s %s %s ',
127
            ($groupName !== -1 ? 'LEFT JOIN usenet_groups g ON g.id = r.groups_id' : ''),
128
            NZB::NZB_ADDED,
129
            $this->showPasswords(),
130
            ($groupName !== -1 ? sprintf(' AND g.name = %s', escapeString($groupName)) : ''),
131
            Category::getCategorySearch($cat),
0 ignored issues
show
Bug introduced by
It seems like App\Models\Category::getCategorySearch($cat) can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

131
            /** @scrutinizer ignore-type */ Category::getCategorySearch($cat),
Loading history...
132
            ($maxAge > 0 ? (' AND r.postdate > NOW() - INTERVAL '.$maxAge.' DAY ') : ''),
133
            (\count($excludedCats) ? (' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')') : '')
134
        ));
135
    }
136
137
    public function showPasswords(): string
138
    {
139
        $show = (int) Settings::settingValue('showpasswordedrelease');
140
        $setting = $show ?? 0;
141
142
        return match ($setting) {
143
            1 => '<= '.self::PASSWD_RAR,
144
            default => '= '.self::PASSWD_NONE,
145
        };
146
    }
147
148
    /**
149
     * Use to order releases on site.
150
     */
151
    public function getBrowseOrder(array|string $orderBy): array
152
    {
153
        $orderArr = explode('_', ($orderBy === '' ? 'posted_desc' : $orderBy));
0 ignored issues
show
introduced by
The condition $orderBy === '' is always false.
Loading history...
Bug introduced by
$orderBy === '' ? 'posted_desc' : $orderBy of type array is incompatible with the type string expected by parameter $string of explode(). ( Ignorable by Annotation )

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

153
        $orderArr = explode('_', /** @scrutinizer ignore-type */ ($orderBy === '' ? 'posted_desc' : $orderBy));
Loading history...
154
        $orderField = match ($orderArr[0]) {
155
            'cat' => 'categories_id',
156
            'name' => 'searchname',
157
            'size' => 'size',
158
            'files' => 'totalpart',
159
            'stats' => 'grabs',
160
            default => 'postdate',
161
        };
162
163
        return [$orderField, isset($orderArr[1]) && preg_match('/^(asc|desc)$/i', $orderArr[1]) ? $orderArr[1] : 'desc'];
164
    }
165
166
    /**
167
     * Return ordering types usable on site.
168
     *
169
     * @return string[]
170
     */
171
    public function getBrowseOrdering(): array
172
    {
173
        return [
174
            'name_asc',
175
            'name_desc',
176
            'cat_asc',
177
            'cat_desc',
178
            'posted_asc',
179
            'posted_desc',
180
            'size_asc',
181
            'size_desc',
182
            'files_asc',
183
            'files_desc',
184
            'stats_asc',
185
            'stats_desc',
186
        ];
187
    }
188
189
    /**
190
     * @return Release[]|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Query\Builder[]|\Illuminate\Support\Collection
191
     */
192
    public function getForExport(string $postFrom = '', string $postTo = '', string $groupID = '')
193
    {
194
        $query = self::query()
195
            ->where('r.nzbstatus', NZB::NZB_ADDED)
196
            ->select(['r.searchname', 'r.guid', 'g.name as gname', DB::raw("CONCAT(cp.title,'_',c.title) AS catName")])
197
            ->from('releases as r')
0 ignored issues
show
Bug introduced by
'releases as r' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $table of Illuminate\Database\Query\Builder::from(). ( Ignorable by Annotation )

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

197
            ->from(/** @scrutinizer ignore-type */ 'releases as r')
Loading history...
198
            ->leftJoin('categories as c', 'c.id', '=', 'r.categories_id')
199
            ->leftJoin('root_categories as cp', 'cp.id', '=', 'c.root_categories_id')
200
            ->leftJoin('usenet_groups as g', 'g.id', '=', 'r.groups_id');
201
202
        if ($groupID !== '') {
203
            $query->where('r.groups_id', $groupID);
204
        }
205
206
        if ($postFrom !== '') {
207
            $dateParts = explode('/', $postFrom);
208
            if (\count($dateParts) === 3) {
209
                $query->where('r.postdate', '>', $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0].'00:00:00');
210
            }
211
        }
212
213
        if ($postTo !== '') {
214
            $dateParts = explode('/', $postTo);
215
            if (\count($dateParts) === 3) {
216
                $query->where('r.postdate', '<', $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0].'23:59:59');
217
            }
218
        }
219
220
        return $query->get();
221
    }
222
223
    /**
224
     * @return mixed|string
225
     */
226
    public function getEarliestUsenetPostDate(): mixed
227
    {
228
        $row = self::query()->selectRaw("DATE_FORMAT(min(postdate), '%d/%m/%Y') AS postdate")->first();
229
230
        return $row === null ? '01/01/2014' : $row['postdate'];
231
    }
232
233
    /**
234
     * @return mixed|string
235
     */
236
    public function getLatestUsenetPostDate(): mixed
237
    {
238
        $row = self::query()->selectRaw("DATE_FORMAT(max(postdate), '%d/%m/%Y') AS postdate")->first();
239
240
        return $row === null ? '01/01/2014' : $row['postdate'];
241
    }
242
243
    public function getReleasedGroupsForSelect(bool $blnIncludeAll = true): array
244
    {
245
        $groups = self::query()
246
            ->selectRaw('DISTINCT g.id, g.name')
247
            ->leftJoin('usenet_groups as g', 'g.id', '=', 'releases.groups_id')
248
            ->get();
249
        $temp_array = [];
250
251
        if ($blnIncludeAll) {
252
            $temp_array[-1] = '--All Groups--';
253
        }
254
255
        foreach ($groups as $group) {
256
            $temp_array[$group['id']] = $group['name'];
257
        }
258
259
        return $temp_array;
260
    }
261
262
    /**
263
     * @return \Illuminate\Cache\|\Illuminate\Database\Eloquent\Collection|mixed
264
     */
265
    public function getShowsRange($userShows, $offset, $limit, $orderBy, int $maxAge = -1, array $excludedCats = [])
266
    {
267
        $orderBy = $this->getBrowseOrder($orderBy);
268
        $sql = sprintf(
269
            "SELECT r.id, r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id, r.haspreview, r.jpgstatus,  cp.title AS parent_category, c.title AS sub_category,
270
					CONCAT(cp.title, '->', c.title) AS category_name
271
				FROM releases r
272
				LEFT JOIN categories c ON c.id = r.categories_id
273
				LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
274
				WHERE %s %s
275
				AND r.nzbstatus = %d
276
				AND r.categories_id BETWEEN %d AND %d
277
				AND r.passwordstatus %s
278
				%s
279
				GROUP BY r.id
280
				ORDER BY %s %s %s",
281
            $this->uSQL($userShows, 'videos_id'),
282
            (! empty($excludedCats) ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
283
            NZB::NZB_ADDED,
284
            Category::TV_ROOT,
285
            Category::TV_OTHER,
286
            $this->showPasswords(),
287
            ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : ''),
288
            $orderBy[0],
289
            $orderBy[1],
290
            ($offset === false ? '' : (' LIMIT '.$limit.' OFFSET '.$offset))
291
        );
292
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_long'));
293
        $result = Cache::get(md5($sql));
294
        if ($result !== null) {
295
            return $result;
296
        }
297
        $result = self::fromQuery($sql);
298
        Cache::put(md5($sql), $result, $expiresAt);
299
300
        return $result;
301
    }
302
303
    public function getShowsCount($userShows, int $maxAge = -1, array $excludedCats = []): int
304
    {
305
        return $this->getPagerCount(
306
            sprintf(
307
                'SELECT r.id
308
				FROM releases r
309
				WHERE %s %s
310
				AND r.nzbstatus = %d
311
				AND r.categories_id BETWEEN %d AND %d
312
				AND r.passwordstatus %s
313
				%s',
314
                $this->uSQL($userShows, 'videos_id'),
315
                (\count($excludedCats) ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
316
                NZB::NZB_ADDED,
317
                Category::TV_ROOT,
318
                Category::TV_OTHER,
319
                $this->showPasswords(),
320
                ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '')
321
            )
322
        );
323
    }
324
325
    /**
326
     * @throws \Exception
327
     */
328
    public function deleteMultiple(int|array|string $list): void
329
    {
330
        $list = (array) $list;
331
332
        $nzb = new NZB;
333
        $releaseImage = new ReleaseImage;
334
335
        foreach ($list as $identifier) {
336
            $this->deleteSingle(['g' => $identifier, 'i' => false], $nzb, $releaseImage);
337
        }
338
    }
339
340
    /**
341
     * Deletes a single release by GUID, and all the corresponding files.
342
     *
343
     * @param  array  $identifiers  ['g' => Release GUID(mandatory), 'id => ReleaseID(optional, pass
344
     *                              false)]
345
     *
346
     * @throws \Exception
347
     */
348
    public function deleteSingle(array $identifiers, NZB $nzb, ReleaseImage $releaseImage): void
349
    {
350
        // Delete NZB from disk.
351
        $nzbPath = $nzb->NZBPath($identifiers['g']);
352
        if (! empty($nzbPath)) {
353
            File::delete($nzbPath);
354
        }
355
356
        // Delete images.
357
        $releaseImage->delete($identifiers['g']);
358
359
        if (config('nntmux.elasticsearch_enabled') === true) {
360
            if ($identifiers['i'] === false) {
361
                $identifiers['i'] = Release::query()->where('guid', $identifiers['g'])->first(['id']);
362
                if ($identifiers['i'] !== null) {
363
                    $identifiers['i'] = $identifiers['i']['id'];
364
                }
365
            }
366
            if ($identifiers['i'] !== null) {
367
                $params = [
368
                    'index' => 'releases',
369
                    'id' => $identifiers['i'],
370
                ];
371
372
                try {
373
                    Elasticsearch::delete($params);
374
                } catch (Missing404Exception $e) {
375
                    // we do nothing here just catch the error, we don't care if release is missing from ES, we are deleting it anyway
376
                }
377
            }
378
        } else {
379
            // Delete from sphinx.
380
            $this->manticoreSearch->deleteRelease($identifiers);
381
        }
382
383
        // Delete from DB.
384
        self::whereGuid($identifiers['g'])->delete();
385
    }
386
387
    /**
388
     * @return bool|int
389
     */
390
    public function updateMulti($guids, $category, $grabs, $videoId, $episodeId, $anidbId, $imdbId)
391
    {
392
        if (! \is_array($guids) || \count($guids) < 1) {
393
            return false;
394
        }
395
396
        $update = [
397
            'categories_id' => $category === -1 ? 'categories_id' : $category,
398
            'grabs' => $grabs,
399
            'videos_id' => $videoId,
400
            'tv_episodes_id' => $episodeId,
401
            'anidbid' => $anidbId,
402
            'imdbid' => $imdbId,
403
        ];
404
405
        return self::query()->whereIn('guid', $guids)->update($update);
406
    }
407
408
    /**
409
     * Creates part of a query for some functions.
410
     */
411
    public function uSQL(Collection|array $userQuery, string $type): string
412
    {
413
        $sql = '(1=2 ';
414
        foreach ($userQuery as $query) {
415
            $sql .= sprintf('OR (r.%s = %d', $type, $query->$type);
416
            if (! empty($query->categories)) {
417
                $catsArr = explode('|', $query->categories);
418
                if (\count($catsArr) > 1) {
419
                    $sql .= sprintf(' AND r.categories_id IN (%s)', implode(',', $catsArr));
420
                } else {
421
                    $sql .= sprintf(' AND r.categories_id = %d', $catsArr[0]);
422
                }
423
            }
424
            $sql .= ') ';
425
        }
426
        $sql .= ') ';
427
428
        return $sql;
429
    }
430
431
    /**
432
     * Function for searching on the site (by subject, searchname or advanced).
433
     *
434
     *
435
     * @return array|Collection|mixed
436
     */
437
    public function search(array $searchArr, $groupName, $sizeFrom, $sizeTo, $daysNew, $daysOld, int $offset = 0, int $limit = 1000, array|string $orderBy = '', int $maxAge = -1, array $excludedCats = [], string $type = 'basic', array $cat = [-1], int $minSize = 0): mixed
438
    {
439
        $sizeRange = [
440
            1 => 1,
441
            2 => 2.5,
442
            3 => 5,
443
            4 => 10,
444
            5 => 20,
445
            6 => 30,
446
            7 => 40,
447
            8 => 80,
448
            9 => 160,
449
            10 => 320,
450
            11 => 640,
451
        ];
452
        if ($orderBy === '') {
453
            $orderBy = [];
454
            $orderBy[0] = 'postdate ';
455
            $orderBy[1] = 'desc ';
456
        } else {
457
            $orderBy = $this->getBrowseOrder($orderBy);
458
        }
459
460
        $searchFields = Arr::where($searchArr, static function ($value) {
461
            return $value !== -1;
462
        });
463
464
        $phrases = array_values($searchFields);
465
466
        if (config('nntmux.elasticsearch_enabled') === true) {
467
            $searchResult = $this->elasticSearch->indexSearch($phrases, $limit);
468
        } else {
469
            $searchResult = $this->manticoreSearch->searchIndexes('releases_rt', '', [], $searchFields);
470
            if (! empty($searchResult)) {
471
                $searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
472
            }
473
        }
474
475
        if (empty($searchResult)) {
476
            return collect();
477
        }
478
479
        $catQuery = '';
480
        if ($type === 'basic') {
481
            $catQuery = Category::getCategorySearch($cat);
482
        } elseif ($type === 'advanced' && (int) $cat[0] !== -1) {
483
            $catQuery = sprintf('AND r.categories_id = %d', $cat[0]);
484
        }
485
        $whereSql = sprintf(
486
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s %s %s %s',
487
            $this->showPasswords(),
488
            NZB::NZB_ADDED,
489
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
490
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', UsenetGroup::getIDByName($groupName)) : ''),
0 ignored issues
show
Bug introduced by
It seems like App\Models\UsenetGroup::getIDByName($groupName) can also be of type false; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

490
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', /** @scrutinizer ignore-type */ UsenetGroup::getIDByName($groupName)) : ''),
Loading history...
491
            (array_key_exists($sizeFrom, $sizeRange) ? ' AND r.size > '.(104857600 * (int) $sizeRange[$sizeFrom]).' ' : ''),
492
            (array_key_exists($sizeTo, $sizeRange) ? ' AND r.size < '.(104857600 * (int) $sizeRange[$sizeTo]).' ' : ''),
493
            $catQuery,
0 ignored issues
show
Bug introduced by
It seems like $catQuery can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

493
            /** @scrutinizer ignore-type */ $catQuery,
Loading history...
494
            ((int) $daysNew !== -1 ? sprintf(' AND r.postdate < (NOW() - INTERVAL %d DAY) ', $daysNew) : ''),
495
            ((int) $daysOld !== -1 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $daysOld) : ''),
496
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
497
            ('AND r.id IN ('.implode(',', $searchResult).')'),
498
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
499
        );
500
        $baseSql = sprintf(
501
            "SELECT r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id, r.haspreview, r.jpgstatus,  cp.title AS parent_category, c.title AS sub_category,
502
				CONCAT(cp.title, ' > ', c.title) AS category_name,
503
				df.failed AS failed,
504
				g.name AS group_name,
505
				rn.releases_id AS nfoid,
506
				re.releases_id AS reid,
507
				cp.id AS categoryparentid,
508
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
509
				tve.firstaired
510
			FROM releases r
511
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
512
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
513
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
514
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
515
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
516
			LEFT JOIN categories c ON c.id = r.categories_id
517
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
518
			LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
519
			%s",
520
            $whereSql
521
        );
522
        $sql = sprintf(
523
            'SELECT * FROM (
524
				%s
525
			) r
526
			ORDER BY r.%s %s
527
			LIMIT %d OFFSET %d',
528
            $baseSql,
529
            $orderBy[0],
530
            $orderBy[1],
531
            $limit,
532
            $offset
533
        );
534
        $releases = Cache::get(md5($sql));
535
        if ($releases !== null) {
536
            return $releases;
537
        }
538
        $releases = self::fromQuery($sql);
539
        if ($releases->isNotEmpty()) {
540
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
541
        }
542
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
543
        Cache::put(md5($sql), $releases, $expiresAt);
544
545
        return $releases;
546
    }
547
548
    /**
549
     * Search function for API.
550
     *
551
     *
552
     * @return Collection|mixed
553
     */
554
    public function apiSearch($searchName, $groupName, int $offset = 0, int $limit = 1000, int $maxAge = -1, array $excludedCats = [], array $cat = [-1], int $minSize = 0): mixed
555
    {
556
        if ($searchName !== -1) {
557
            if (config('nntmux.elasticsearch_enabled') === true) {
558
                $searchResult = $this->elasticSearch->indexSearchApi($searchName, $limit);
559
            } else {
560
                $searchResult = $this->manticoreSearch->searchIndexes('releases_rt', $searchName, ['searchname']);
561
                if (! empty($searchResult)) {
562
                    $searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
563
                }
564
            }
565
        }
566
567
        $catQuery = Category::getCategorySearch($cat);
568
569
        $whereSql = sprintf(
570
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s',
571
            $this->showPasswords(),
572
            NZB::NZB_ADDED,
573
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
574
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', UsenetGroup::getIDByName($groupName)) : ''),
0 ignored issues
show
Bug introduced by
It seems like App\Models\UsenetGroup::getIDByName($groupName) can also be of type false; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

574
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', /** @scrutinizer ignore-type */ UsenetGroup::getIDByName($groupName)) : ''),
Loading history...
575
            $catQuery,
0 ignored issues
show
Bug introduced by
It seems like $catQuery can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

575
            /** @scrutinizer ignore-type */ $catQuery,
Loading history...
576
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
577
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
578
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
579
        );
580
        $baseSql = sprintf(
581
            "SELECT r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id, r.haspreview, r.jpgstatus, m.imdbid, m.tmdbid, m.traktid, cp.title AS parent_category, c.title AS sub_category,
582
				CONCAT(cp.title, ' > ', c.title) AS category_name,
583
				g.name AS group_name,
584
				cp.id AS categoryparentid,
585
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
586
				tve.firstaired, tve.title, tve.series, tve.episode
587
			FROM releases r
588
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
589
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
590
			LEFT JOIN movieinfo m ON m.id = r.movieinfo_id
591
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
592
			LEFT JOIN categories c ON c.id = r.categories_id
593
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
594
			%s",
595
            $whereSql
596
        );
597
        $sql = sprintf(
598
            'SELECT * FROM (
599
				%s
600
			) r
601
			ORDER BY r.postdate DESC
602
			LIMIT %d OFFSET %d',
603
            $baseSql,
604
            $limit,
605
            $offset
606
        );
607
        $releases = Cache::get(md5($sql));
608
        if ($releases !== null) {
609
            return $releases;
610
        }
611
        if ($searchName !== -1 && ! empty($searchResult)) {
612
            $releases = self::fromQuery($sql);
613
        } elseif ($searchName !== -1 && empty($searchResult)) {
614
            $releases = collect();
615
        } elseif ($searchName === -1) {
616
            $releases = self::fromQuery($sql);
617
        } else {
618
            $releases = collect();
619
        }
620
        if ($releases->isNotEmpty()) {
621
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
622
        }
623
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
624
        Cache::put(md5($sql), $releases, $expiresAt);
625
626
        return $releases;
627
    }
628
629
    /**
630
     * Search for TV shows via API.
631
     *
632
     * @return array|\Illuminate\Cache\|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|mixed
633
     */
634
    public function tvSearch(array $siteIdArr = [], string $series = '', string $episode = '', string $airDate = '', int $offset = 0, int $limit = 100, string $name = '', array $cat = [-1], int $maxAge = -1, int $minSize = 0, array $excludedCategories = []): mixed
635
    {
636
        $siteSQL = [];
637
        $showSql = '';
638
        foreach ($siteIdArr as $column => $Id) {
639
            if ($Id > 0) {
640
                $siteSQL[] = sprintf('v.%s = %d', $column, $Id);
641
            }
642
        }
643
644
        if (\count($siteSQL) > 0) {
645
            // If we have show info, find the Episode ID/Video ID first to avoid table scans
646
            $showQry = sprintf(
647
                "
648
				SELECT
649
					v.id AS video,
650
					GROUP_CONCAT(tve.id SEPARATOR ',') AS episodes
651
				FROM videos v
652
				LEFT JOIN tv_episodes tve ON v.id = tve.videos_id
653
				WHERE (%s) %s %s %s
654
				GROUP BY v.id
655
				LIMIT 1",
656
                implode(' OR ', $siteSQL),
657
                ($series !== '' ? sprintf('AND tve.series = %d', (int) preg_replace('/^s0*/i', '', $series)) : ''),
658
                ($episode !== '' ? sprintf('AND tve.episode = %d', (int) preg_replace('/^e0*/i', '', $episode)) : ''),
659
                ($airDate !== '' ? sprintf('AND DATE(tve.firstaired) = %s', escapeString($airDate)) : '')
660
            );
661
662
            $show = self::fromQuery($showQry);
663
664
            if ($show->isNotEmpty()) {
665
                if ((! empty($episode) && ! empty($series)) && $show[0]->episodes !== '') {
666
                    $showSql .= ' AND r.tv_episodes_id IN ('.$show[0]->episodes.') AND tve.series = '.$series;
667
                } elseif (! empty($episode) && $show[0]->episodes !== '') {
668
                    $showSql = sprintf('AND r.tv_episodes_id IN (%s)', $show[0]->episodes);
669
                } elseif (! empty($series) && empty($episode)) {
670
                    // If $series is set but episode is not, return Season Packs and Episodes
671
                    $showSql .= ' AND r.tv_episodes_id IN ('.$show[0]->episodes.') AND tve.series = '.$series;
672
                }
673
                if ($show[0]->video > 0) {
674
                    $showSql .= ' AND r.videos_id = '.$show[0]->video;
675
                }
676
            } else {
677
                // If we were passed Site ID Info and no match was found, do not run the query
678
                return [];
679
            }
680
        }
681
682
        // If $name is set it is a fallback search, add available SxxExx/airdate info to the query
683
        if (! empty($name) && $showSql === '') {
684
            if (! empty($series) && (int) $series < 1900) {
685
                $name .= sprintf(' S%s', str_pad($series, 2, '0', STR_PAD_LEFT));
686
                if (! empty($episode) && ! str_contains($episode, '/')) {
687
                    $name .= sprintf('E%s', str_pad($episode, 2, '0', STR_PAD_LEFT));
688
                }
689
                // If season is not empty but episode is, add a wildcard to the search
690
                if (empty($episode)) {
691
                    $name .= '*';
692
                }
693
            } elseif (! empty($airDate)) {
694
                $name .= sprintf(' %s', str_replace(['/', '-', '.', '_'], ' ', $airDate));
695
            }
696
        }
697
        if (! empty($name)) {
698
            if (config('nntmux.elasticsearch_enabled') === true) {
699
                $searchResult = $this->elasticSearch->indexSearchTMA($name, $limit);
700
            } else {
701
                $searchResult = $this->manticoreSearch->searchIndexes('releases_rt', $name, ['searchname']);
702
                if (! empty($searchResult)) {
703
                    $searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
704
                }
705
            }
706
707
            if (empty($searchResult)) {
708
                return collect();
709
            }
710
        }
711
        $whereSql = sprintf(
712
            'WHERE r.nzbstatus = %d
713
			AND r.passwordstatus %s
714
			%s %s %s %s %s %s',
715
            NZB::NZB_ADDED,
716
            $this->showPasswords(),
717
            $showSql,
718
            (! empty($name) && ! empty($searchResult)) ? 'AND r.id IN ('.implode(',', $searchResult).')' : '',
719
            Category::getCategorySearch($cat, 'tv'),
0 ignored issues
show
Bug introduced by
It seems like App\Models\Category::get...egorySearch($cat, 'tv') can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

719
            /** @scrutinizer ignore-type */ Category::getCategorySearch($cat, 'tv'),
Loading history...
720
            $maxAge > 0 ? sprintf('AND r.postdate > NOW() - INTERVAL %d DAY', $maxAge) : '',
721
            $minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '',
722
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : ''
723
        );
724
        $baseSql = sprintf(
725
            "SELECT r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id, r.haspreview, r.jpgstatus,
726
				v.title, v.countries_id, v.started, v.tvdb, v.trakt,
727
					v.imdb, v.tmdb, v.tvmaze, v.tvrage, v.source,
728
				tvi.summary, tvi.publisher, tvi.image,
729
				tve.series, tve.episode, tve.se_complete, tve.title, tve.firstaired, tve.summary, cp.title AS parent_category, c.title AS sub_category,
730
				CONCAT(cp.title, ' > ', c.title) AS category_name,
731
				g.name AS group_name,
732
				rn.releases_id AS nfoid,
733
				re.releases_id AS reid
734
			FROM releases r
735
			LEFT OUTER JOIN videos v ON r.videos_id = v.id AND v.type = 0
736
			LEFT OUTER JOIN tv_info tvi ON v.id = tvi.videos_id
737
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
738
			LEFT JOIN categories c ON c.id = r.categories_id
739
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
740
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
741
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
742
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
743
			%s",
744
            $whereSql
745
        );
746
        $sql = sprintf(
747
            '%s
748
			ORDER BY postdate DESC
749
			LIMIT %d OFFSET %d',
750
            $baseSql,
751
            $limit,
752
            $offset
753
        );
754
        $releases = Cache::get(md5($sql));
755
        if ($releases !== null) {
756
            return $releases;
757
        }
758
        $releases = ((! empty($name) && ! empty($searchResult)) || empty($name)) ? self::fromQuery($sql) : [];
759
        if (count($releases) !== 0 && $releases->isNotEmpty()) {
0 ignored issues
show
Bug introduced by
It seems like $releases can also be of type Illuminate\Database\Eloq...gHasThroughRelationship; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

759
        if (count(/** @scrutinizer ignore-type */ $releases) !== 0 && $releases->isNotEmpty()) {
Loading history...
760
            $releases[0]->_totalrows = $this->getPagerCount(
761
                preg_replace('#LEFT(\s+OUTER)?\s+JOIN\s+(?!tv_episodes)\s+.*ON.*=.*\n#i', ' ', $baseSql)
762
            );
763
        }
764
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
765
        Cache::put(md5($sql), $releases, $expiresAt);
766
767
        return $releases;
768
    }
769
770
    /**
771
     * Search TV Shows via APIv2.
772
     *
773
     *
774
     * @return Collection|mixed
775
     */
776
    public function apiTvSearch(array $siteIdArr = [], string $series = '', string $episode = '', string $airDate = '', int $offset = 0, int $limit = 100, string $name = '', array $cat = [-1], int $maxAge = -1, int $minSize = 0, array $excludedCategories = []): mixed
777
    {
778
        $siteSQL = [];
779
        $showSql = '';
780
        foreach ($siteIdArr as $column => $Id) {
781
            if ($Id > 0) {
782
                $siteSQL[] = sprintf('v.%s = %d', $column, $Id);
783
            }
784
        }
785
786
        if (\count($siteSQL) > 0) {
787
            // If we have show info, find the Episode ID/Video ID first to avoid table scans
788
            $showQry = sprintf(
789
                "
790
				SELECT
791
					v.id AS video,
792
					GROUP_CONCAT(tve.id SEPARATOR ',') AS episodes
793
				FROM videos v
794
				LEFT JOIN tv_episodes tve ON v.id = tve.videos_id
795
				WHERE (%s) %s %s %s
796
				GROUP BY v.id
797
				LIMIT 1",
798
                implode(' OR ', $siteSQL),
799
                ($series !== '' ? sprintf('AND tve.series = %d', (int) preg_replace('/^s0*/i', '', $series)) : ''),
800
                ($episode !== '' ? sprintf('AND tve.episode = %d', (int) preg_replace('/^e0*/i', '', $episode)) : ''),
801
                ($airDate !== '' ? sprintf('AND DATE(tve.firstaired) = %s', escapeString($airDate)) : '')
802
            );
803
804
            $show = self::fromQuery($showQry);
805
            if ($show->isNotEmpty()) {
806
                if ((! empty($episode) && ! empty($series)) && $show[0]->episodes !== '') {
807
                    $showSql .= ' AND r.tv_episodes_id IN ('.$show[0]->episodes.') AND tve.series = '.$series;
808
                } elseif (! empty($episode) && $show[0]->episodes !== '') {
809
                    $showSql = sprintf('AND r.tv_episodes_id IN (%s)', $show[0]->episodes);
810
                } elseif (! empty($series) && empty($episode)) {
811
                    // If $series is set but episode is not, return Season Packs and Episodes
812
                    $showSql .= ' AND r.tv_episodes_id IN ('.$show[0]->episodes.') AND tve.series = '.$series;
813
                }
814
                if ($show[0]->video > 0) {
815
                    $showSql .= ' AND r.videos_id = '.$show[0]->video;
816
                }
817
            } else {
818
                // If we were passed Site ID Info and no match was found, do not run the query
819
                return [];
820
            }
821
        }
822
        // If $name is set it is a fallback search, add available SxxExx/airdate info to the query
823
        if (! empty($name) && $showSql === '') {
824
            if (! empty($series) && (int) $series < 1900) {
825
                $name .= sprintf(' S%s', str_pad($series, 2, '0', STR_PAD_LEFT));
826
                if (! empty($episode) && ! str_contains($episode, '/')) {
827
                    $name .= sprintf('E%s', str_pad($episode, 2, '0', STR_PAD_LEFT));
828
                }
829
                // If season is not empty but episode is, add a wildcard to the search
830
                if (empty($episode)) {
831
                    $name .= '*';
832
                }
833
            } elseif (! empty($airDate)) {
834
                $name .= sprintf(' %s', str_replace(['/', '-', '.', '_'], ' ', $airDate));
835
            }
836
        }
837
        if (! empty($name)) {
838
            if (config('nntmux.elasticsearch_enabled') === true) {
839
                $searchResult = $this->elasticSearch->indexSearchTMA($name, $limit);
840
            } else {
841
                $searchResult = $this->manticoreSearch->searchIndexes('releases_rt', $name, ['searchname']);
842
                if (! empty($searchResult)) {
843
                    $searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
844
                }
845
            }
846
847
            if (empty($searchResult)) {
848
                return collect();
849
            }
850
        }
851
        $whereSql = sprintf(
852
            'WHERE r.nzbstatus = %d
853
			AND r.passwordstatus %s
854
			%s %s %s %s %s %s',
855
            NZB::NZB_ADDED,
856
            $this->showPasswords(),
857
            $showSql,
858
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
859
            Category::getCategorySearch($cat, 'tv'),
0 ignored issues
show
Bug introduced by
It seems like App\Models\Category::get...egorySearch($cat, 'tv') can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

859
            /** @scrutinizer ignore-type */ Category::getCategorySearch($cat, 'tv'),
Loading history...
860
            ($maxAge > 0 ? sprintf('AND r.postdate > NOW() - INTERVAL %d DAY', $maxAge) : ''),
861
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : ''),
862
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : ''
863
        );
864
        $baseSql = sprintf(
865
            "SELECT r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.tv_episodes_id, r.haspreview, r.jpgstatus,
866
				v.title, v.type, v.tvdb, v.trakt,v.imdb, v.tmdb, v.tvmaze, v.tvrage,
867
				tve.series, tve.episode, tve.se_complete, tve.title, tve.firstaired, cp.title AS parent_category, c.title AS sub_category,
868
				CONCAT(cp.title, ' > ', c.title) AS category_name,
869
				g.name AS group_name
870
			FROM releases r
871
			LEFT OUTER JOIN videos v ON r.videos_id = v.id AND v.type = 0
872
			LEFT OUTER JOIN tv_info tvi ON v.id = tvi.videos_id
873
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
874
			LEFT JOIN categories c ON c.id = r.categories_id
875
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
876
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
877
			%s",
878
            $whereSql
879
        );
880
        $sql = sprintf(
881
            '%s
882
			ORDER BY postdate DESC
883
			LIMIT %d OFFSET %d',
884
            $baseSql,
885
            $limit,
886
            $offset
887
        );
888
        $releases = Cache::get(md5($sql));
889
        if ($releases !== null) {
890
            return $releases;
891
        }
892
        $releases = self::fromQuery($sql);
893
        if ($releases->isNotEmpty()) {
894
            $releases[0]->_totalrows = $this->getPagerCount(
895
                preg_replace('#LEFT(\s+OUTER)?\s+JOIN\s+(?!tv_episodes)\s+.*ON.*=.*\n#i', ' ', $baseSql)
896
            );
897
        }
898
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
899
        Cache::put(md5($sql), $releases, $expiresAt);
900
901
        return $releases;
902
    }
903
904
    /**
905
     * Search anime releases.
906
     *
907
     *
908
     * @return Collection|mixed
909
     */
910
    public function animeSearch($aniDbID, int $offset = 0, int $limit = 100, string $name = '', array $cat = [-1], int $maxAge = -1, array $excludedCategories = []): mixed
911
    {
912
        if (! empty($name)) {
913
            if (config('nntmux.elasticsearch_enabled') === true) {
914
                $searchResult = $this->elasticSearch->indexSearchTMA($name, $limit);
915
            } else {
916
                $searchResult = $this->manticoreSearch->searchIndexes('releases_rt', $name, ['searchname']);
917
                if (! empty($searchResult)) {
918
                    $searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
919
                }
920
            }
921
922
            if (empty($searchResult)) {
923
                return collect();
924
            }
925
        }
926
927
        $whereSql = sprintf(
928
            'WHERE r.passwordstatus %s
929
			AND r.nzbstatus = %d
930
			%s %s %s %s %s',
931
            $this->showPasswords(),
932
            NZB::NZB_ADDED,
933
            ($aniDbID > -1 ? sprintf(' AND r.anidbid = %d ', $aniDbID) : ''),
934
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
935
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : '',
936
            Category::getCategorySearch($cat),
0 ignored issues
show
Bug introduced by
It seems like App\Models\Category::getCategorySearch($cat) can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

936
            /** @scrutinizer ignore-type */ Category::getCategorySearch($cat),
Loading history...
937
            ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '')
938
        );
939
        $baseSql = sprintf(
940
            "SELECT r.id, r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.haspreview, r.jpgstatus,  cp.title AS parent_category, c.title AS sub_category,
941
				CONCAT(cp.title, ' > ', c.title) AS category_name,
942
				g.name AS group_name,
943
				rn.releases_id AS nfoid
944
			FROM releases r
945
			LEFT JOIN categories c ON c.id = r.categories_id
946
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
947
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
948
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
949
			%s",
950
            $whereSql
951
        );
952
        $sql = sprintf(
953
            '%s
954
			ORDER BY postdate DESC
955
			LIMIT %d OFFSET %d',
956
            $baseSql,
957
            $limit,
958
            $offset
959
        );
960
        $releases = Cache::get(md5($sql));
961
        if ($releases !== null) {
962
            return $releases;
963
        }
964
        $releases = self::fromQuery($sql);
965
        if ($releases->isNotEmpty()) {
966
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
967
        }
968
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
969
        Cache::put(md5($sql), $releases, $expiresAt);
970
971
        return $releases;
972
    }
973
974
    /**
975
     * Movies search through API and site.
976
     *
977
     *
978
     * @return Collection|mixed
979
     */
980
    public function moviesSearch(int $imDbId = -1, int $tmDbId = -1, int $traktId = -1, int $offset = 0, int $limit = 100, string $name = '', array $cat = [-1], int $maxAge = -1, int $minSize = 0, array $excludedCategories = []): mixed
981
    {
982
        if (! empty($name)) {
983
            if (config('nntmux.elasticsearch_enabled') === true) {
984
                $searchResult = $this->elasticSearch->indexSearchTMA($name, $limit);
985
            } else {
986
                $searchResult = $this->manticoreSearch->searchIndexes('releases_rt', $name, ['searchname']);
987
                if (! empty($searchResult)) {
988
                    $searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
989
                }
990
            }
991
992
            if (empty($searchResult)) {
993
                return collect();
994
            }
995
        }
996
997
        $whereSql = sprintf(
998
            'WHERE r.categories_id BETWEEN '.Category::MOVIE_ROOT.' AND '.Category::MOVIE_OTHER.'
999
			AND r.nzbstatus = %d
1000
			AND r.passwordstatus %s
1001
			%s %s %s %s %s %s %s',
1002
            NZB::NZB_ADDED,
1003
            $this->showPasswords(),
1004
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
1005
            ($imDbId !== -1 && is_numeric($imDbId)) ? sprintf(' AND m.imdbid = \'%s\' ', $imDbId) : '',
1006
            ($tmDbId !== -1 && is_numeric($tmDbId)) ? sprintf(' AND m.tmdbid = %d ', $tmDbId) : '',
1007
            ($traktId !== -1 && is_numeric($traktId)) ? sprintf(' AND m.traktid = %d ', $traktId) : '',
1008
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : '',
1009
            Category::getCategorySearch($cat, 'movies'),
0 ignored issues
show
Bug introduced by
It seems like App\Models\Category::get...ySearch($cat, 'movies') can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

1009
            /** @scrutinizer ignore-type */ Category::getCategorySearch($cat, 'movies'),
Loading history...
1010
            $maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '',
1011
            $minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : ''
1012
        );
1013
        $baseSql = sprintf(
1014
            "SELECT r.id, r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.imdbid, r.videos_id, r.tv_episodes_id, r.haspreview, r.jpgstatus, m.imdbid, m.tmdbid, m.traktid, cp.title AS parent_category, c.title AS sub_category,
1015
				concat(cp.title, ' > ', c.title) AS category_name,
1016
				g.name AS group_name,
1017
				rn.releases_id AS nfoid
1018
			FROM releases r
1019
			LEFT JOIN movieinfo m ON m.id = r.movieinfo_id
1020
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
1021
			LEFT JOIN categories c ON c.id = r.categories_id
1022
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
1023
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
1024
			%s",
1025
            $whereSql
1026
        );
1027
        $sql = sprintf(
1028
            '%s
1029
			ORDER BY postdate DESC
1030
			LIMIT %d OFFSET %d',
1031
            $baseSql,
1032
            $limit,
1033
            $offset
1034
        );
1035
1036
        $releases = Cache::get(md5($sql));
1037
        if ($releases !== null) {
1038
            return $releases;
1039
        }
1040
        $releases = self::fromQuery($sql);
1041
        if ($releases->isNotEmpty()) {
1042
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
1043
        }
1044
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
1045
        Cache::put(md5($sql), $releases, $expiresAt);
1046
1047
        return $releases;
1048
    }
1049
1050
    public function searchSimilar($currentID, $name, array $excludedCats = []): bool|array
1051
    {
1052
        // Get the category for the parent of this release.
1053
        $ret = false;
1054
        $currRow = self::getCatByRelId($currentID);
1055
        if ($currRow !== null) {
1056
            $catRow = Category::find($currRow['categories_id']);
1057
            $parentCat = $catRow !== null ? $catRow['root_categories_id'] : null;
1058
1059
            if ($parentCat === null) {
1060
                return $ret;
1061
            }
1062
1063
            $results = $this->search(['searchname' => getSimilarName($name)], -1, '', '', -1, -1, 0, config('nntmux.items_per_page'), '', -1, $excludedCats, 'basic', [$parentCat]);
1064
            if (! $results) {
1065
                return $ret;
1066
            }
1067
1068
            $ret = [];
1069
            foreach ($results as $res) {
1070
                if ($res['id'] !== $currentID && $res['categoryparentid'] === $parentCat) {
1071
                    $ret[] = $res;
1072
                }
1073
            }
1074
        }
1075
1076
        return $ret;
1077
    }
1078
1079
    /**
1080
     * Get count of releases for pager.
1081
     *
1082
     * @param  string  $query  The query to get the count from.
1083
     */
1084
    private function getPagerCount(string $query): int
1085
    {
1086
        $queryBuilder = DB::table(DB::raw('('.preg_replace(
1087
            '/SELECT.+?FROM\s+releases/is',
1088
            'SELECT r.id FROM releases',
1089
            $query
1090
        ).' LIMIT '.(int) config('nntmux.max_pager_results').') as z'))
1091
            ->selectRaw('COUNT(z.id) as count');
1092
1093
        $sql = $queryBuilder->toSql();
1094
        $count = Cache::get(md5($sql));
1095
1096
        if ($count !== null) {
1097
            return $count;
1098
        }
1099
1100
        $result = $queryBuilder->first();
1101
        $count = $result->count ?? 0;
1102
1103
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_short'));
1104
        Cache::put(md5($sql), $count, $expiresAt);
1105
1106
        return $count;
1107
    }
1108
}
1109