Completed
Push — dev ( 9030eb...c12075 )
by Darko
08:50
created

Releases::apiSearch()   C

Complexity

Conditions 15
Paths 14

Size

Total Lines 68
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

Changes 0
Metric Value
eloc 56
dl 0
loc 68
rs 5.9166
c 0
b 0
f 0
ccs 0
cts 19
cp 0
cc 15
nc 14
nop 9
crap 240

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Blacklight;
4
5
use App\Models\Category;
6
use App\Models\Group;
7
use App\Models\Release;
8
use App\Models\Settings;
9
use Blacklight\utility\Utility;
10
use Chumper\Zipper\Zipper;
11
use Illuminate\Support\Arr;
12
use Illuminate\Support\Facades\Cache;
13
use Illuminate\Support\Facades\DB;
14
use Illuminate\Support\Facades\File;
15
16
/**
17
 * Class Releases.
18
 */
19
class Releases
20
{
21
    // RAR/ZIP Passworded indicator.
22
    public const PASSWD_NONE = 0; // No password.
23
    public const PASSWD_POTENTIAL = 1; // Might have a password.
24
    public const BAD_FILE = 2; // Possibly broken RAR/ZIP.
25
    public const PASSWD_RAR = 10; // Definitely passworded.
26
27
    /**
28
     * @var \Blacklight\SphinxSearch
29
     */
30
    public $sphinxSearch;
31
32
    /**
33
     * @var int
34
     */
35
    public $passwordStatus;
36
37
    /**
38
     * @var array Class instances.
39
     * @throws \Exception
40
     */
41
    public function __construct(array $options = [])
42
    {
43
        $defaults = [
44
            'Settings' => null,
45
            'Groups'   => null,
46
        ];
47
        $options += $defaults;
48
49
        $this->sphinxSearch = new SphinxSearch();
50
    }
51
52
    /**
53
     * Used for Browse results.
54
     *
55
     *
56
     * @param       $page
57
     * @param       $cat
58
     * @param       $start
59
     * @param       $num
60
     * @param       $orderBy
61
     * @param int   $maxAge
62
     * @param array $excludedCats
63
     * @param array $tags
64
     * @param int   $groupName
65
     * @param int   $minSize
66
     *
67
     * @return \Illuminate\Database\Eloquent\Collection|mixed
68
     */
69
    public function getBrowseRange($page, $cat, $start, $num, $orderBy, $maxAge = -1, array $excludedCats = [], $groupName = -1, $minSize = 0, array $tags = [])
70
    {
71
        $orderBy = $this->getBrowseOrder($orderBy);
72
73
        $qry = sprintf(
74
            "SELECT r.*, cp.title AS parent_category, c.title AS sub_category,
75
				CONCAT(cp.title, ' > ', c.title) AS category_name,
76
				CONCAT(cp.id, ',', c.id) AS category_ids,
77
				df.failed AS failed,
78
				rn.releases_id AS nfoid,
79
				re.releases_id AS reid,
80
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
81
				tve.title, tve.firstaired
82
			FROM
83
			(
84
				SELECT r.*, g.name AS group_name
85
				FROM releases r
86
				LEFT JOIN groups g ON g.id = r.groups_id
87
				%s
88
				WHERE r.nzbstatus = %d
89
				AND r.passwordstatus %s
90
				%s %s %s %s %s %s
91
				ORDER BY %s %s %s
92
			) r
93
			LEFT JOIN categories c ON c.id = r.categories_id
94
			LEFT JOIN categories cp ON cp.id = c.parentid
95
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
96
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
97
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
98
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
99
			LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
100
			GROUP BY r.id
101
			ORDER BY %10\$s %11\$s",
102
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
103
            NZB::NZB_ADDED,
104
            $this->showPasswords(),
105
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
106
            Category::getCategorySearch($cat),
107
            ($maxAge > 0 ? (' AND postdate > NOW() - INTERVAL '.$maxAge.' DAY ') : ''),
108
            (\count($excludedCats) ? (' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')') : ''),
109
            ((int) $groupName !== -1 ? sprintf(' AND g.name = %s ', escapeString($groupName)) : ''),
110
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : ''),
111
            $orderBy[0],
112
            $orderBy[1],
113
            ($start === false ? '' : ' LIMIT '.$num.' OFFSET '.$start)
114
        );
115
116
        $releases = Cache::get(md5($qry.$page));
117
        if ($releases !== null) {
118
            return $releases;
119
        }
120
        $sql = Release::fromQuery($qry);
121
        if (\count($sql) > 0) {
122
            $possibleRows = $this->getBrowseCount($cat, $maxAge, $excludedCats, $groupName, $tags);
123
            $sql[0]->_totalcount = $sql[0]->_totalrows = $possibleRows;
124
        }
125
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
126
        Cache::put(md5($qry.$page), $sql, $expiresAt);
127
128
        return $sql;
129
    }
130
131
    /**
132
     * Used for pager on browse page.
133
     *
134
     * @param array      $cat
135
     * @param int        $maxAge
136
     * @param array      $excludedCats
137
     * @param string|int $groupName
138
     *
139
     * @param array      $tags
140
     *
141
     * @return int
142
     */
143
    public function getBrowseCount($cat, $maxAge = -1, array $excludedCats = [], $groupName = '', array $tags = []): int
144
    {
145
        $sql = sprintf(
146
            'SELECT COUNT(r.id) AS count
147
				FROM releases r
148
				%s %s
149
				WHERE r.nzbstatus = %d
150
				AND r.passwordstatus %s
151
				%s
152
				%s %s %s %s ',
153
            ($groupName !== -1 ? 'LEFT JOIN groups g ON g.id = r.groups_id' : ''),
154
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
155
            NZB::NZB_ADDED,
156
            $this->showPasswords(),
157
            ($groupName !== -1 ? sprintf(' AND g.name = %s', escapeString($groupName)) : ''),
158
            ! empty($tags) ? ' AND tt.tag_name IN ('.escapeString(implode(',', $tags)).')' : '',
159
            Category::getCategorySearch($cat),
160
            ($maxAge > 0 ? (' AND r.postdate > NOW() - INTERVAL '.$maxAge.' DAY ') : ''),
161
            (\count($excludedCats) ? (' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')') : '')
162
        );
163
        $count = Cache::get(md5($sql));
164
        if ($count !== null) {
165
            return $count;
166
        }
167
        $count = Release::fromQuery($sql);
168
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_short'));
169
        Cache::put(md5($sql), $count[0]->count, $expiresAt);
170
171
        return $count[0]->count ?? 0;
172
    }
173
174
    /**
175
     * @return string
176
     */
177
    public function showPasswords()
178
    {
179
        $setting = (int) Settings::settingValue('..showpasswordedrelease');
180
        $setting = $setting ?? 10;
181
        switch ($setting) {
182
            case 0: // Hide releases with a password or a potential password (Hide unprocessed releases).
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
183
184
                    return '= '.self::PASSWD_NONE;
185
            case 1: // Show releases with no password or a potential password (Show unprocessed releases).
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
186
187
                    return '<= '.self::PASSWD_POTENTIAL;
188
            case 2: // Hide releases with a password or a potential password (Show unprocessed releases).
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
189
                    return '<= '.self::PASSWD_NONE;
190
            case 10: // Shows everything.
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
191
            default:
192
                    return '<= '.self::PASSWD_RAR;
193
        }
194
    }
195
196
    /**
197
     * Use to order releases on site.
198
     *
199
     * @param string|array $orderBy
200
     *
201
     * @return array
202
     */
203
    public function getBrowseOrder($orderBy): array
204
    {
205
        $orderArr = explode('_', ($orderBy === '' ? 'posted_desc' : $orderBy));
0 ignored issues
show
Bug introduced by
It seems like $orderBy === '' ? 'posted_desc' : $orderBy can also be of type array; however, parameter $string of explode() does only seem to accept 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

205
        $orderArr = explode('_', /** @scrutinizer ignore-type */ ($orderBy === '' ? 'posted_desc' : $orderBy));
Loading history...
206
        switch ($orderArr[0]) {
207
            case 'cat':
208
                $orderField = 'categories_id';
209
                break;
210
            case 'name':
211
                $orderField = 'searchname';
212
                break;
213
            case 'size':
214
                $orderField = 'size';
215
                break;
216
            case 'files':
217
                $orderField = 'totalpart';
218
                break;
219
            case 'stats':
220
                $orderField = 'grabs';
221
                break;
222
            case 'posted':
223
            default:
224
                $orderField = 'postdate';
225
                break;
226
        }
227
228
        return [$orderField, isset($orderArr[1]) && preg_match('/^(asc|desc)$/i', $orderArr[1]) ? $orderArr[1] : 'desc'];
229
    }
230
231
    /**
232
     * Return ordering types usable on site.
233
     *
234
     * @return string[]
235
     */
236
    public function getBrowseOrdering(): array
237
    {
238
        return [
239
            'name_asc',
240
            'name_desc',
241
            'cat_asc',
242
            'cat_desc',
243
            'posted_asc',
244
            'posted_desc',
245
            'size_asc',
246
            'size_desc',
247
            'files_asc',
248
            'files_desc',
249
            'stats_asc',
250
            'stats_desc',
251
        ];
252
    }
253
254
    /**
255
     * Get list of releases available for export.
256
     *
257
     *
258
     * @param string $postFrom
259
     * @param string $postTo
260
     * @param string $groupID
261
     *
262
     * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|static[]
263
     */
264
    public function getForExport($postFrom = '', $postTo = '', $groupID = '')
265
    {
266
        $query = Release::query()
267
            ->where('r.nzbstatus', NZB::NZB_ADDED)
268
            ->select(['r.searchname', 'r.guid', 'g.name as gname', DB::raw("CONCAT(cp.title,'_',c.title) AS catName")])
269
            ->from('releases as r')
270
            ->leftJoin('categories as c', 'c.id', '=', 'r.categories_id')
271
            ->leftJoin('categories as cp', 'cp.id', '=', 'c.parentid')
272
            ->leftJoin('groups as g', 'g.id', '=', 'r.groups_id');
273
274
        if ($groupID !== '') {
275
            $query->where('r.groups_id', $groupID);
276
        }
277
278
        if ($postFrom !== '') {
279
            $dateParts = explode('/', $postFrom);
280
            if (\count($dateParts) === 3) {
281
                $query->where('r.postdate', '>', $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0].'00:00:00');
282
            }
283
        }
284
285
        if ($postTo !== '') {
286
            $dateParts = explode('/', $postTo);
287
            if (\count($dateParts) === 3) {
288
                $query->where('r.postdate', '<', $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0].'23:59:59');
289
            }
290
        }
291
292
        return $query->get();
293
    }
294
295
    /**
296
     * Get date in this format : 01/01/2014 of the oldest release.
297
     *
298
     * @note Used for exporting NZB's.
299
     * @return mixed
300
     */
301
    public function getEarliestUsenetPostDate()
302
    {
303
        $row = Release::query()->selectRaw("DATE_FORMAT(min(postdate), '%d/%m/%Y') AS postdate")->first();
304
305
        return $row === null ? '01/01/2014' : $row['postdate'];
306
    }
307
308
    /**
309
     * Get date in this format : 01/01/2014 of the newest release.
310
     *
311
     * @note Used for exporting NZB's.
312
     * @return mixed
313
     */
314
    public function getLatestUsenetPostDate()
315
    {
316
        $row = Release::query()->selectRaw("DATE_FORMAT(max(postdate), '%d/%m/%Y') AS postdate")->first();
317
318
        return $row === null ? '01/01/2014' : $row['postdate'];
319
    }
320
321
    /**
322
     * Gets all groups for drop down selection on NZB-Export web page.
323
     *
324
     * @param bool $blnIncludeAll
325
     *
326
     * @note Used for exporting NZB's.
327
     * @return array
328
     */
329
    public function getReleasedGroupsForSelect($blnIncludeAll = true): array
330
    {
331
        $groups = Release::query()
332
            ->selectRaw('DISTINCT g.id, g.name')
333
            ->leftJoin('groups as g', 'g.id', '=', 'releases.groups_id')
334
            ->get();
335
        $temp_array = [];
336
337
        if ($blnIncludeAll) {
338
            $temp_array[-1] = '--All Groups--';
339
        }
340
341
        foreach ($groups as $group) {
342
            $temp_array[$group['id']] = $group['name'];
343
        }
344
345
        return $temp_array;
346
    }
347
348
    /**
349
     * Cache of concatenated category ID's used in queries.
350
     * @var null|array
351
     */
352
    private $concatenatedCategoryIDsCache = null;
353
354
    /**
355
     * Gets / sets a string of concatenated category ID's used in queries.
356
     *
357
     * @return array|null|string
358
     */
359
    public function getConcatenatedCategoryIDs()
360
    {
361
        if ($this->concatenatedCategoryIDsCache === null) {
362
            $result = Category::query()
363
                ->remember(config('nntmux.cache_expiry_long'))
364
                ->whereNotNull('categories.parentid')
365
                ->whereNotNull('cp.id')
366
                ->selectRaw('CONCAT(cp.id, ", ", categories.id) AS category_ids')
367
                ->leftJoin('categories as cp', 'cp.id', '=', 'categories.parentid')
368
                ->get();
369
            if (isset($result[0]['category_ids'])) {
370
                $this->concatenatedCategoryIDsCache = $result[0]['category_ids'];
371
            }
372
        }
373
374
        return $this->concatenatedCategoryIDsCache;
375
    }
376
377
    /**
378
     * Get TV for My Shows page.
379
     *
380
     *
381
     * @param $userShows
382
     * @param $offset
383
     * @param $limit
384
     * @param $orderBy
385
     * @param int $maxAge
386
     * @param array $excludedCats
387
     * @return \Illuminate\Database\Eloquent\Collection|mixed
388
     */
389
    public function getShowsRange($userShows, $offset, $limit, $orderBy, $maxAge = -1, array $excludedCats = [])
390
    {
391
        $orderBy = $this->getBrowseOrder($orderBy);
392
        $sql = sprintf(
393
                "SELECT r.*,
394
					CONCAT(cp.title, '-', c.title) AS category_name,
395
					%s AS category_ids,
396
					groups.name AS group_name,
397
					rn.releases_id AS nfoid, re.releases_id AS reid,
398
					tve.firstaired,
399
					df.failed AS failed
400
				FROM releases r
401
				LEFT OUTER JOIN video_data re ON re.releases_id = r.id
402
				LEFT JOIN groups ON groups.id = r.groups_id
403
				LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
404
				LEFT OUTER JOIN tv_episodes tve ON tve.videos_id = r.videos_id
405
				LEFT JOIN categories c ON c.id = r.categories_id
406
				LEFT JOIN categories cp ON cp.id = c.parentid
407
				LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
408
				WHERE %s %s
409
				AND r.nzbstatus = %d
410
				AND r.categories_id BETWEEN %d AND %d
411
				AND r.passwordstatus %s
412
				%s
413
				GROUP BY r.id
414
				ORDER BY %s %s %s",
415
                $this->getConcatenatedCategoryIDs(),
0 ignored issues
show
Bug introduced by
It seems like $this->getConcatenatedCategoryIDs() can also be of type array; however, parameter $args of sprintf() does only seem to accept 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

415
                /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
416
                $this->uSQL($userShows, 'videos_id'),
417
                (\count($excludedCats) ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
418
                NZB::NZB_ADDED,
419
                Category::TV_ROOT,
420
                Category::TV_OTHER,
421
                $this->showPasswords(),
422
                ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : ''),
423
                $orderBy[0],
424
                $orderBy[1],
425
                ($offset === false ? '' : (' LIMIT '.$limit.' OFFSET '.$offset))
426
        );
427
428
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
429
        $result = Cache::get(md5($sql));
430
        if ($result !== null) {
431
            return $result;
432
        }
433
434
        $result = Release::fromQuery($sql);
435
        Cache::put(md5($sql), $result, $expiresAt);
436
437
        return $result;
438
    }
439
440
    /**
441
     * Get count for my shows page pagination.
442
     *
443
     * @param       $userShows
444
     * @param int   $maxAge
445
     * @param array $excludedCats
446
     *
447
     * @return int
448
     */
449
    public function getShowsCount($userShows, $maxAge = -1, array $excludedCats = []): int
450
    {
451
        return $this->getPagerCount(
452
            sprintf(
453
                'SELECT r.id
454
				FROM releases r
455
				WHERE %s %s
456
				AND r.nzbstatus = %d
457
				AND r.categories_id BETWEEN %d AND %d
458
				AND r.passwordstatus %s
459
				%s',
460
                $this->uSQL($userShows, 'videos_id'),
461
                (\count($excludedCats) ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
462
                NZB::NZB_ADDED,
463
                Category::TV_ROOT,
464
                Category::TV_OTHER,
465
                $this->showPasswords(),
466
                ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '')
467
            )
468
        );
469
    }
470
471
    /**
472
     * Delete multiple releases, or a single by ID.
473
     *
474
     * @param array|int|string $list   Array of GUID or ID of releases to delete.
475
     * @throws \Exception
476
     */
477
    public function deleteMultiple($list): void
478
    {
479
        $list = (array) $list;
480
481
        $nzb = new NZB();
482
        $releaseImage = new ReleaseImage();
483
484
        foreach ($list as $identifier) {
485
            $this->deleteSingle(['g' => $identifier, 'i' => false], $nzb, $releaseImage);
486
        }
487
    }
488
489
    /**
490
     * Deletes a single release by GUID, and all the corresponding files.
491
     *
492
     * @param array                    $identifiers ['g' => Release GUID(mandatory), 'id => ReleaseID(optional, pass
493
     *                                              false)]
494
     * @param \Blacklight\NZB          $nzb
495
     * @param \Blacklight\ReleaseImage $releaseImage
496
     *
497
     * @throws \Exception
498
     */
499
    public function deleteSingle($identifiers, NZB $nzb, ReleaseImage $releaseImage): void
500
    {
501
        // Delete NZB from disk.
502
        $nzbPath = $nzb->NZBPath($identifiers['g']);
503
        if (! empty($nzbPath)) {
504
            File::delete($nzbPath);
505
        }
506
507
        // Delete images.
508
        $releaseImage->delete($identifiers['g']);
509
510
        // Delete from sphinx.
511
        $this->sphinxSearch->deleteRelease($identifiers);
512
513
        // Delete from DB.
514
        Release::whereGuid($identifiers['g'])->delete();
515
    }
516
517
    /**
518
     * @param $guids
519
     * @param $category
520
     * @param $grabs
521
     * @param $videoId
522
     * @param $episodeId
523
     * @param $anidbId
524
     * @param $imdbId
525
     * @return bool|int
526
     */
527
    public function updateMulti($guids, $category, $grabs, $videoId, $episodeId, $anidbId, $imdbId)
528
    {
529
        if (! \is_array($guids) || \count($guids) < 1) {
530
            return false;
531
        }
532
533
        $update = [
534
            'categories_id'     => $category === -1 ? 'categories_id' : $category,
535
            'grabs'          => $grabs,
536
            'videos_id'      => $videoId,
537
            'tv_episodes_id' => $episodeId,
538
            'anidbid'        => $anidbId,
539
            'imdbid'         => $imdbId,
540
        ];
541
542
        return Release::query()->whereIn('guid', $guids)->update($update);
543
    }
544
545
    /**
546
     * Creates part of a query for some functions.
547
     *
548
     * @param array|\Illuminate\Database\Eloquent\Collection  $userQuery
549
     * @param string $type
550
     *
551
     * @return string
552
     */
553
    public function uSQL($userQuery, $type): string
554
    {
555
        $sql = '(1=2 ';
556
        foreach ($userQuery as $query) {
557
            $sql .= sprintf('OR (r.%s = %d', $type, $query->$type);
558
            if (! empty($query->categories)) {
559
                $catsArr = explode('|', $query->categories);
560
                if (\count($catsArr) > 1) {
561
                    $sql .= sprintf(' AND r.categories_id IN (%s)', implode(',', $catsArr));
562
                } else {
563
                    $sql .= sprintf(' AND r.categories_id = %d', $catsArr[0]);
564
                }
565
            }
566
            $sql .= ') ';
567
        }
568
        $sql .= ') ';
569
570
        return $sql;
571
    }
572
573
    /**
574
     * Function for searching on the site (by subject, searchname or advanced).
575
     *
576
     *
577
     * @param  array       $searchArr
578
     * @param              $groupName
579
     * @param              $sizeFrom
580
     * @param              $sizeTo
581
     * @param              $daysNew
582
     * @param              $daysOld
583
     * @param int          $offset
584
     * @param int          $limit
585
     * @param string|array $orderBy
586
     * @param int          $maxAge
587
     * @param array        $excludedCats
588
     * @param string       $type
589
     * @param array        $cat
590
     * @param int          $minSize
591
     * @param array        $tags
592
     *
593
     * @return array|\Illuminate\Database\Eloquent\Collection|mixed
594
     */
595
    public function search($searchArr, $groupName, $sizeFrom, $sizeTo, $daysNew, $daysOld, $offset = 0, $limit = 1000, $orderBy = '', $maxAge = -1, array $excludedCats = [], $type = 'basic', array $cat = [-1], $minSize = 0, array $tags = [])
596
    {
597
        $sizeRange = [
598
            1 => 1,
599
            2 => 2.5,
600
            3 => 5,
601
            4 => 10,
602
            5 => 20,
603
            6 => 30,
604
            7 => 40,
605
            8 => 80,
606
            9 => 160,
607
            10 => 320,
608
            11 => 640,
609
        ];
610
        if ($orderBy === '') {
611
            $orderBy = [];
612
            $orderBy[0] = 'postdate ';
613
            $orderBy[1] = 'desc ';
614
        } else {
615
            $orderBy = $this->getBrowseOrder($orderBy);
616
        }
617
618
        $searchFields = Arr::where($searchArr, function ($value) {
619
            return $value !== -1;
620
        });
621
622
        $results = $this->sphinxSearch->searchIndexes('releases_rt', '', [], $searchFields);
623
624
        $searchResult = Arr::pluck($results, 'id');
625
626
        $catQuery = '';
627
        if ($type === 'basic') {
628
            $catQuery = Category::getCategorySearch($cat);
629
        } elseif ($type === 'advanced' && (int) $cat[0] !== -1) {
630
            $catQuery = sprintf('AND r.categories_id = %d', $cat[0]);
631
        }
632
        $whereSql = sprintf(
633
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s %s %s %s %s',
634
            $this->showPasswords(),
635
            NZB::NZB_ADDED,
636
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
637
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
638
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', Group::getIDByName($groupName)) : ''),
639
            (array_key_exists($sizeFrom, $sizeRange) ? ' AND r.size > '.(104857600 * (int) $sizeRange[$sizeFrom]).' ' : ''),
640
            (array_key_exists($sizeTo, $sizeRange) ? ' AND r.size < '.(104857600 * (int) $sizeRange[$sizeTo]).' ' : ''),
641
            $catQuery,
642
            ((int) $daysNew !== -1 ? sprintf(' AND r.postdate < (NOW() - INTERVAL %d DAY) ', $daysNew) : ''),
643
            ((int) $daysOld !== -1 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $daysOld) : ''),
644
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
645
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
646
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
647
        );
648
        $baseSql = sprintf(
649
            "SELECT r.searchname, r.guid, r.postdate, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id, cp.title AS parent_category, c.title AS sub_category,
650
				CONCAT(cp.title, ' > ', c.title) AS category_name,
651
				%s AS category_ids,
652
				df.failed AS failed,
653
				g.name AS group_name,
654
				rn.releases_id AS nfoid,
655
				re.releases_id AS reid,
656
				cp.id AS categoryparentid,
657
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
658
				tve.firstaired
659
			FROM releases r
660
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
661
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
662
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
663
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
664
			LEFT JOIN groups g ON g.id = r.groups_id
665
			LEFT JOIN categories c ON c.id = r.categories_id
666
			LEFT JOIN categories cp ON cp.id = c.parentid
667
			LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
668
			%s %s",
669
            $this->getConcatenatedCategoryIDs(),
0 ignored issues
show
Bug introduced by
It seems like $this->getConcatenatedCategoryIDs() can also be of type array; however, parameter $args of sprintf() does only seem to accept 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

669
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
670
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
671
            $whereSql
672
        );
673
        $sql = sprintf(
674
            'SELECT * FROM (
675
				%s
676
			) r
677
			ORDER BY r.%s %s
678
			LIMIT %d OFFSET %d',
679
            $baseSql,
680
            $orderBy[0],
681
            $orderBy[1],
682
            $limit,
683
            $offset
684
        );
685
        $releases = Cache::get(md5($sql));
686
        if ($releases !== null) {
687
            return $releases;
688
        }
689
        $releases = ! empty($searchResult) ? Release::fromQuery($sql) : collect();
690
        if ($releases->isNotEmpty()) {
691
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
692
        }
693
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
694
        Cache::put(md5($sql), $releases, $expiresAt);
695
696
        return $releases;
697
    }
698
699
    /**
700
     * Search function for API.
701
     *
702
     *
703
     * @param       $searchName
704
     * @param       $groupName
705
     * @param int   $offset
706
     * @param int   $limit
707
     * @param int   $maxAge
708
     * @param array $excludedCats
709
     * @param array $cat
710
     * @param int   $minSize
711
     * @param array $tags
712
     *
713
     * @return \Illuminate\Database\Eloquent\Collection|mixed
714
     */
715
    public function apiSearch($searchName, $groupName, $offset = 0, $limit = 1000, $maxAge = -1, array $excludedCats = [], array $cat = [-1], $minSize = 0, array $tags = [])
716
    {
717
        if ($searchName !== -1) {
718
            $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $searchName, ['searchname']), 'id');
719
        }
720
721
        $catQuery = Category::getCategorySearch($cat);
722
723
        $whereSql = sprintf(
724
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s',
725
            $this->showPasswords(),
726
            NZB::NZB_ADDED,
727
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
728
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
729
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', Group::getIDByName($groupName)) : ''),
730
            $catQuery,
731
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
732
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
733
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
734
        );
735
        $baseSql = sprintf(
736
            "SELECT r.searchname, r.guid, r.postdate, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id, m.imdbid, m.tmdbid, m.traktid, cp.title AS parent_category, c.title AS sub_category,
737
				CONCAT(cp.title, ' > ', c.title) AS category_name,
738
				%s AS category_ids,
739
				g.name AS group_name,
740
				cp.id AS categoryparentid,
741
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
742
				tve.firstaired, tve.title, tve.series, tve.episode
743
			FROM releases r
744
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
745
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
746
			LEFT JOIN movieinfo m ON m.id = r.movieinfo_id
747
			LEFT JOIN groups g ON g.id = r.groups_id
748
			LEFT JOIN categories c ON c.id = r.categories_id
749
			LEFT JOIN categories cp ON cp.id = c.parentid
750
			%s %s",
751
            $this->getConcatenatedCategoryIDs(),
0 ignored issues
show
Bug introduced by
It seems like $this->getConcatenatedCategoryIDs() can also be of type array; however, parameter $args of sprintf() does only seem to accept 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

751
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
752
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
753
            $whereSql
754
        );
755
        $sql = sprintf(
756
            'SELECT * FROM (
757
				%s
758
			) r
759
			ORDER BY r.postdate DESC
760
			LIMIT %d OFFSET %d',
761
            $baseSql,
762
            $limit,
763
            $offset
764
        );
765
        $releases = Cache::get(md5($sql));
766
        if ($releases !== null) {
767
            return $releases;
768
        }
769
        if ($searchName !== -1 && ! empty($searchResult)) {
770
            $releases = Release::fromQuery($sql);
771
        } elseif ($searchName !== -1 && empty($searchResult)) {
772
            $releases = collect();
773
        } else {
774
            $releases = Release::fromQuery($sql);
775
        }
776
        if ($releases->isNotEmpty()) {
777
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
778
        }
779
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
780
        Cache::put(md5($sql), $releases, $expiresAt);
781
782
        return $releases;
783
    }
784
785
    /**
786
     * Search TV Shows via API.
787
     *
788
     *
789
     * @param array $siteIdArr
790
     * @param string $series
791
     * @param string $episode
792
     * @param string $airdate
793
     * @param int $offset
794
     * @param int $limit
795
     * @param string $name
796
     * @param array $cat
797
     * @param int $maxAge
798
     * @param int $minSize
799
     * @param array $excludedCategories
800
     * @param array $tags
801
     * @return \Illuminate\Database\Eloquent\Collection|mixed
802
     */
803
    public function tvSearch(array $siteIdArr = [], $series = '', $episode = '', $airdate = '', $offset = 0, $limit = 100, $name = '', array $cat = [-1], $maxAge = -1, $minSize = 0, array $excludedCategories = [], array $tags = [])
804
    {
805
        $siteSQL = [];
806
        $showSql = '';
807
808
        foreach ($siteIdArr as $column => $Id) {
809
            if ($Id > 0) {
810
                $siteSQL[] = sprintf('v.%s = %d', $column, $Id);
811
            }
812
        }
813
814
        if (\count($siteSQL) > 0) {
815
            // If we have show info, find the Episode ID/Video ID first to avoid table scans
816
            $showQry = sprintf(
817
                "
818
				SELECT
819
					v.id AS video,
820
					GROUP_CONCAT(tve.id SEPARATOR ',') AS episodes
821
				FROM videos v
822
				LEFT JOIN tv_episodes tve ON v.id = tve.videos_id
823
				WHERE (%s) %s %s %s
824
				GROUP BY v.id
825
				LIMIT 1",
826
                implode(' OR ', $siteSQL),
827
                ($series !== '' ? sprintf('AND tve.series = %d', (int) preg_replace('/^s0*/i', '', $series)) : ''),
828
                ($episode !== '' ? sprintf('AND tve.episode = %d', (int) preg_replace('/^e0*/i', '', $episode)) : ''),
829
                ($airdate !== '' ? sprintf('AND DATE(tve.firstaired) = %s', escapeString($airdate)) : '')
830
            );
831
            $show = Release::fromQuery($showQry);
832
833
            if (! empty($show[0])) {
834
                if ((! empty($series) || ! empty($episode) || ! empty($airdate)) && $show[0]->episodes !== '') {
835
                    $showSql = sprintf('AND r.tv_episodes_id IN (%s)', $show[0]->episodes);
836
                } elseif ((int) $show[0]->video > 0) {
837
                    $showSql = 'AND r.videos_id = '.$show[0]->video;
838
                    // If $series is set but episode is not, return Season Packs only
839
                    if (! empty($series) && empty($episode)) {
840
                        $showSql .= ' AND r.tv_episodes_id = 0';
841
                    }
842
                } else {
843
                    // If we were passed Episode Info and no match was found, do not run the query
844
                    return [];
845
                }
846
            } else {
847
                // If we were passed Site ID Info and no match was found, do not run the query
848
                return [];
849
            }
850
        }
851
        // If $name is set it is a fallback search, add available SxxExx/airdate info to the query
852
        if (! empty($name) && $showSql === '') {
853
            if (! empty($series) && (int) $series < 1900) {
854
                $name .= sprintf(' S%s', str_pad($series, 2, '0', STR_PAD_LEFT));
855
                if (! empty($episode) && strpos($episode, '/') === false) {
856
                    $name .= sprintf('E%s', str_pad($episode, 2, '0', STR_PAD_LEFT));
857
                }
858
            } elseif (! empty($airdate)) {
859
                $name .= sprintf(' %s', str_replace(['/', '-', '.', '_'], ' ', $airdate));
860
            }
861
        }
862
863
        if (! empty($name)) {
864
            $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $name, ['searchname']), 'id');
865
        }
866
867
        $whereSql = sprintf(
868
            'WHERE r.nzbstatus = %d
869
			AND r.passwordstatus %s
870
			%s %s %s %s %s %s %s',
871
            NZB::NZB_ADDED,
872
            $this->showPasswords(),
873
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
874
            $showSql,
875
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
876
            Category::getCategorySearch($cat),
877
            ($maxAge > 0 ? sprintf('AND r.postdate > NOW() - INTERVAL %d DAY', $maxAge) : ''),
878
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : ''),
879
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : ''
880
        );
881
        $baseSql = sprintf(
882
            "SELECT r.searchname, r.guid, r.postdate, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id,
883
				v.title, v.countries_id, v.started, v.tvdb, v.trakt,
884
					v.imdb, v.tmdb, v.tvmaze, v.tvrage, v.source,
885
				tvi.summary, tvi.publisher, tvi.image,
886
				tve.series, tve.episode, tve.se_complete, tve.title, tve.firstaired, tve.summary, cp.title AS parent_category, c.title AS sub_category,
887
				CONCAT(cp.title, ' > ', c.title) AS category_name,
888
				%s AS category_ids,
889
				g.name AS group_name,
890
				rn.releases_id AS nfoid,
891
				re.releases_id AS reid
892
			FROM releases r
893
			LEFT OUTER JOIN videos v ON r.videos_id = v.id AND v.type = 0
894
			LEFT OUTER JOIN tv_info tvi ON v.id = tvi.videos_id
895
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
896
			LEFT JOIN categories c ON c.id = r.categories_id
897
			LEFT JOIN categories cp ON cp.id = c.parentid
898
			LEFT JOIN groups g ON g.id = r.groups_id
899
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
900
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
901
			%s %s",
902
            $this->getConcatenatedCategoryIDs(),
0 ignored issues
show
Bug introduced by
It seems like $this->getConcatenatedCategoryIDs() can also be of type array; however, parameter $args of sprintf() does only seem to accept 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

902
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
903
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
904
            $whereSql
905
        );
906
        $sql = sprintf(
907
            '%s
908
			ORDER BY postdate DESC
909
			LIMIT %d OFFSET %d',
910
            $baseSql,
911
            $limit,
912
            $offset
913
        );
914
        $releases = Cache::get(md5($sql));
915
        if ($releases !== null) {
916
            return $releases;
917
        }
918
        $releases = Release::fromQuery($sql);
919
        if ($releases->isNotEmpty()) {
920
            $releases[0]->_totalrows = $this->getPagerCount(
921
                preg_replace('#LEFT(\s+OUTER)?\s+JOIN\s+(?!tv_episodes)\s+.*ON.*=.*\n#i', ' ', $baseSql)
922
            );
923
        }
924
925
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
926
        Cache::put(md5($sql), $releases, $expiresAt);
927
928
        return $releases;
929
    }
930
931
    /**
932
     * Search TV Shows via APIv2.
933
     *
934
     *
935
     * @param array $siteIdArr
936
     * @param string $series
937
     * @param string $episode
938
     * @param string $airdate
939
     * @param int $offset
940
     * @param int $limit
941
     * @param string $name
942
     * @param array $cat
943
     * @param int $maxAge
944
     * @param int $minSize
945
     * @param array $excludedCategories
946
     * @param array $tags
947
     * @return \Illuminate\Database\Eloquent\Collection|mixed
948
     */
949
    public function apiTvSearch(array $siteIdArr = [], $series = '', $episode = '', $airdate = '', $offset = 0, $limit = 100, $name = '', array $cat = [-1], $maxAge = -1, $minSize = 0, array $excludedCategories = [], array $tags = [])
950
    {
951
        $siteSQL = [];
952
        $showSql = '';
953
        foreach ($siteIdArr as $column => $Id) {
954
            if ($Id > 0) {
955
                $siteSQL[] = sprintf('v.%s = %d', $column, $Id);
956
            }
957
        }
958
959
        if (\count($siteSQL) > 0) {
960
            // If we have show info, find the Episode ID/Video ID first to avoid table scans
961
            $showQry = sprintf(
962
                "
963
				SELECT
964
					v.id AS video,
965
					GROUP_CONCAT(tve.id SEPARATOR ',') AS episodes
966
				FROM videos v
967
				LEFT JOIN tv_episodes tve ON v.id = tve.videos_id
968
				WHERE (%s) %s %s %s
969
				GROUP BY v.id
970
				LIMIT 1",
971
                implode(' OR ', $siteSQL),
972
                ($series !== '' ? sprintf('AND tve.series = %d', (int) preg_replace('/^s0*/i', '', $series)) : ''),
973
                ($episode !== '' ? sprintf('AND tve.episode = %d', (int) preg_replace('/^e0*/i', '', $episode)) : ''),
974
                ($airdate !== '' ? sprintf('AND DATE(tve.firstaired) = %s', escapeString($airdate)) : '')
975
            );
976
            $show = Release::fromQuery($showQry);
977
978
            if ($show->isNotEmpty()) {
979
                if ((! empty($series) || ! empty($episode) || ! empty($airdate)) && $show[0]->episodes != '') {
980
                    $showSql = sprintf('AND r.tv_episodes_id IN (%s)', $show[0]->episodes);
981
                } elseif ((int) $show[0]->video > 0) {
982
                    $showSql = 'AND r.videos_id = '.$show[0]->video;
983
                    // If $series is set but episode is not, return Season Packs only
984
                    if (! empty($series) && empty($episode)) {
985
                        $showSql .= ' AND r.tv_episodes_id = 0';
986
                    }
987
                } else {
988
                    // If we were passed Episode Info and no match was found, do not run the query
989
                    return [];
990
                }
991
            } else {
992
                // If we were passed Site ID Info and no match was found, do not run the query
993
                return [];
994
            }
995
        }
996
        // If $name is set it is a fallback search, add available SxxExx/airdate info to the query
997
        if (! empty($name) && $showSql === '') {
998
            if (! empty($series) && (int) $series < 1900) {
999
                $name .= sprintf(' S%s', str_pad($series, 2, '0', STR_PAD_LEFT));
1000
                if (! empty($episode) && strpos($episode, '/') === false) {
1001
                    $name .= sprintf('E%s', str_pad($episode, 2, '0', STR_PAD_LEFT));
1002
                }
1003
            } elseif (! empty($airdate)) {
1004
                $name .= sprintf(' %s', str_replace(['/', '-', '.', '_'], ' ', $airdate));
1005
            }
1006
        }
1007
1008
        if (! empty($name)) {
1009
            $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $name, ['searchname']), 'id');
1010
        }
1011
1012
        $whereSql = sprintf(
1013
            'WHERE r.nzbstatus = %d
1014
			AND r.passwordstatus %s
1015
			%s %s %s %s %s %s %s',
1016
            NZB::NZB_ADDED,
1017
            $this->showPasswords(),
1018
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
1019
            $showSql,
1020
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
1021
            Category::getCategorySearch($cat),
1022
            ($maxAge > 0 ? sprintf('AND r.postdate > NOW() - INTERVAL %d DAY', $maxAge) : ''),
1023
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : ''),
1024
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : ''
1025
        );
1026
        $baseSql = sprintf(
1027
            "SELECT r.searchname, r.guid, r.postdate, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.tv_episodes_id,
1028
				v.title, v.type, v.tvdb, v.trakt,v.imdb, v.tmdb, v.tvmaze, v.tvrage,
1029
				tve.series, tve.episode, tve.se_complete, tve.title, tve.firstaired, cp.title AS parent_category, c.title AS sub_category,
1030
				CONCAT(cp.title, ' > ', c.title) AS category_name,
1031
				%s AS category_ids,
1032
				g.name AS group_name
1033
			FROM releases r
1034
			LEFT OUTER JOIN videos v ON r.videos_id = v.id AND v.type = 0
1035
			LEFT OUTER JOIN tv_info tvi ON v.id = tvi.videos_id
1036
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
1037
			LEFT JOIN categories c ON c.id = r.categories_id
1038
			LEFT JOIN categories cp ON cp.id = c.parentid
1039
			LEFT JOIN groups g ON g.id = r.groups_id
1040
			%s %s",
1041
            $this->getConcatenatedCategoryIDs(),
0 ignored issues
show
Bug introduced by
It seems like $this->getConcatenatedCategoryIDs() can also be of type array; however, parameter $args of sprintf() does only seem to accept 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

1041
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
1042
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
1043
            $whereSql
1044
        );
1045
        $sql = sprintf(
1046
            '%s
1047
			ORDER BY postdate DESC
1048
			LIMIT %d OFFSET %d',
1049
            $baseSql,
1050
            $limit,
1051
            $offset
1052
        );
1053
        $releases = Cache::get(md5($sql));
1054
        if ($releases !== null) {
1055
            return $releases;
1056
        }
1057
        $releases = Release::fromQuery($sql);
1058
        if ($releases->isNotEmpty()) {
1059
            $releases[0]->_totalrows = $this->getPagerCount(
1060
                preg_replace('#LEFT(\s+OUTER)?\s+JOIN\s+(?!tv_episodes)\s+.*ON.*=.*\n#i', ' ', $baseSql)
1061
            );
1062
        }
1063
1064
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
1065
        Cache::put(md5($sql), $releases, $expiresAt);
1066
1067
        return $releases;
1068
    }
1069
1070
    /**
1071
     * Search anime releases.
1072
     *
1073
     *
1074
     * @param $aniDbID
1075
     * @param int $offset
1076
     * @param int $limit
1077
     * @param string $name
1078
     * @param array $cat
1079
     * @param int $maxAge
1080
     * @param array $excludedCategories
1081
     * @return \Illuminate\Database\Eloquent\Collection|mixed
1082
     */
1083
    public function animeSearch($aniDbID, $offset = 0, $limit = 100, $name = '', array $cat = [-1], $maxAge = -1, array $excludedCategories = [])
1084
    {
1085
        if (! empty($name)) {
1086
            $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $name, ['searchname']), 'id');
1087
        }
1088
1089
        $whereSql = sprintf(
1090
            'WHERE r.passwordstatus %s
1091
			AND r.nzbstatus = %d
1092
			%s %s %s %s %s',
1093
            $this->showPasswords(),
1094
            NZB::NZB_ADDED,
1095
            ($aniDbID > -1 ? sprintf(' AND r.anidbid = %d ', $aniDbID) : ''),
1096
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
1097
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : '',
1098
            Category::getCategorySearch($cat),
1099
            ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '')
1100
        );
1101
        $baseSql = sprintf(
1102
            "SELECT r.*, cp.title AS parent_category, c.title AS sub_category,
1103
				CONCAT(cp.title, ' > ', c.title) AS category_name,
1104
				%s AS category_ids,
1105
				g.name AS group_name,
1106
				rn.releases_id AS nfoid,
1107
				re.releases_id AS reid
1108
			FROM releases r
1109
			LEFT JOIN categories c ON c.id = r.categories_id
1110
			LEFT JOIN categories cp ON cp.id = c.parentid
1111
			LEFT JOIN groups g ON g.id = r.groups_id
1112
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
1113
			LEFT OUTER JOIN releaseextrafull re ON re.releases_id = r.id
1114
			%s",
1115
            $this->getConcatenatedCategoryIDs(),
0 ignored issues
show
Bug introduced by
It seems like $this->getConcatenatedCategoryIDs() can also be of type array; however, parameter $args of sprintf() does only seem to accept 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

1115
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
1116
            $whereSql
1117
        );
1118
        $sql = sprintf(
1119
            '%s
1120
			ORDER BY postdate DESC
1121
			LIMIT %d OFFSET %d',
1122
            $baseSql,
1123
            $limit,
1124
            $offset
1125
        );
1126
        $releases = Cache::get(md5($sql));
1127
        if ($releases !== null) {
1128
            return $releases;
1129
        }
1130
        $releases = Release::fromQuery($sql);
1131
        if ($releases->isNotEmpty()) {
1132
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
1133
        }
1134
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
1135
        Cache::put(md5($sql), $releases, $expiresAt);
1136
1137
        return $releases;
1138
    }
1139
1140
    /**
1141
     * Movies search through API and site.
1142
     *
1143
     *
1144
     * @param int $imDbId
1145
     * @param int $tmDbId
1146
     * @param int $traktId
1147
     * @param int $offset
1148
     * @param int $limit
1149
     * @param string $name
1150
     * @param array $cat
1151
     * @param int $maxAge
1152
     * @param int $minSize
1153
     * @param array $excludedCategories
1154
     * @param array $tags
1155
     * @return \Illuminate\Database\Eloquent\Collection|mixed
1156
     */
1157
    public function moviesSearch($imDbId = -1, $tmDbId = -1, $traktId = -1, $offset = 0, $limit = 100, $name = '', array $cat = [-1], $maxAge = -1, $minSize = 0, array $excludedCategories = [], array $tags = [])
1158
    {
1159
        if (! empty($name)) {
1160
            $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $name, ['searchname']), 'id');
1161
        }
1162
1163
        $whereSql = sprintf(
1164
            'WHERE r.categories_id BETWEEN '.Category::MOVIE_ROOT.' AND '.Category::MOVIE_OTHER.'
1165
			AND r.nzbstatus = %d
1166
			AND r.passwordstatus %s
1167
			%s %s %s %s %s %s %s %s',
1168
            NZB::NZB_ADDED,
1169
            $this->showPasswords(),
1170
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
1171
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
1172
            ($imDbId !== -1 && is_numeric($imDbId)) ? sprintf(' AND m.imdbid = %d ', str_pad($imDbId, 7, '0', STR_PAD_LEFT)) : '',
1173
            ($tmDbId !== -1 && is_numeric($tmDbId)) ? sprintf(' AND m.tmdbid = %d ', $tmDbId) : '',
1174
            ($traktId !== -1 && is_numeric($traktId)) ? sprintf(' AND m.traktid = %d ', $traktId) : '',
1175
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : '',
1176
            Category::getCategorySearch($cat),
1177
            $maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '',
1178
            $minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : ''
1179
        );
1180
        $baseSql = sprintf(
1181
            "SELECT r.searchname, r.guid, r.postdate, 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, m.imdbid, m.tmdbid, m.traktid, cp.title AS parent_category, c.title AS sub_category,
1182
				concat(cp.title, ' > ', c.title) AS category_name,
1183
				%s AS category_ids,
1184
				g.name AS group_name,
1185
				rn.releases_id AS nfoid
1186
			FROM releases r
1187
			LEFT JOIN movieinfo m ON m.id = r.movieinfo_id
1188
			LEFT JOIN groups g ON g.id = r.groups_id
1189
			LEFT JOIN categories c ON c.id = r.categories_id
1190
			LEFT JOIN categories cp ON cp.id = c.parentid
1191
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
1192
			%s %s",
1193
            $this->getConcatenatedCategoryIDs(),
0 ignored issues
show
Bug introduced by
It seems like $this->getConcatenatedCategoryIDs() can also be of type array; however, parameter $args of sprintf() does only seem to accept 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

1193
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
1194
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
1195
            $whereSql
1196
        );
1197
        $sql = sprintf(
1198
            '%s
1199
			ORDER BY postdate DESC
1200
			LIMIT %d OFFSET %d',
1201
            $baseSql,
1202
            $limit,
1203
            $offset
1204
        );
1205
1206
        $releases = Cache::get(md5($sql));
1207
        if ($releases !== null) {
1208
            return $releases;
1209
        }
1210
        $releases = Release::fromQuery($sql);
1211
        if ($releases->isNotEmpty()) {
1212
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
1213
        }
1214
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
1215
        Cache::put(md5($sql), $releases, $expiresAt);
1216
1217
        return $releases;
1218
    }
1219
1220
    /**
1221
     * @param $currentID
1222
     * @param $name
1223
     * @param array $excludedCats
1224
     * @return array|\Illuminate\Database\Eloquent\Collection
1225
     */
1226
    public function searchSimilar($currentID, $name, array $excludedCats = [])
1227
    {
1228
        // Get the category for the parent of this release.
1229
        $ret = false;
1230
        $currRow = Release::getCatByRelId($currentID);
1231
        if ($currRow !== null) {
1232
            $catRow = Category::find($currRow['categories_id']);
1233
            $parentCat = $catRow['parentid'];
1234
1235
            $results = $this->search(getSimilarName($name), -1, -1, -1, -1, '', '', -1, -1, 0, config('nntmux.items_per_page'), '', -1, $excludedCats, [$parentCat]);
0 ignored issues
show
Bug introduced by
-1 of type integer is incompatible with the type array expected by parameter $cat of Blacklight\Releases::search(). ( Ignorable by Annotation )

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

1235
            $results = $this->search(getSimilarName($name), -1, -1, -1, -1, '', '', -1, -1, 0, config('nntmux.items_per_page'), '', /** @scrutinizer ignore-type */ -1, $excludedCats, [$parentCat]);
Loading history...
Bug introduced by
'' of type string is incompatible with the type integer expected by parameter $offset of Blacklight\Releases::search(). ( Ignorable by Annotation )

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

1235
            $results = $this->search(getSimilarName($name), -1, -1, -1, -1, '', /** @scrutinizer ignore-type */ '', -1, -1, 0, config('nntmux.items_per_page'), '', -1, $excludedCats, [$parentCat]);
Loading history...
Bug introduced by
getSimilarName($name) of type string is incompatible with the type array expected by parameter $searchArr of Blacklight\Releases::search(). ( Ignorable by Annotation )

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

1235
            $results = $this->search(/** @scrutinizer ignore-type */ getSimilarName($name), -1, -1, -1, -1, '', '', -1, -1, 0, config('nntmux.items_per_page'), '', -1, $excludedCats, [$parentCat]);
Loading history...
Bug introduced by
$excludedCats of type array is incompatible with the type integer expected by parameter $minSize of Blacklight\Releases::search(). ( Ignorable by Annotation )

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

1235
            $results = $this->search(getSimilarName($name), -1, -1, -1, -1, '', '', -1, -1, 0, config('nntmux.items_per_page'), '', -1, /** @scrutinizer ignore-type */ $excludedCats, [$parentCat]);
Loading history...
1236
            if (! $results) {
1237
                return $results;
1238
            }
1239
1240
            $ret = [];
1241
            foreach ($results as $res) {
1242
                if ($res['id'] !== $currentID && $res['categoryparentid'] === $parentCat) {
1243
                    $ret[] = $res;
1244
                }
1245
            }
1246
        }
1247
1248
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret could also return false which is incompatible with the documented return type Illuminate\Database\Eloquent\Collection|array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1249
    }
1250
1251
    /**
1252
     * @param array $guids
1253
     *
1254
     * @return string
1255
     * @throws \Exception
1256
     */
1257
    public function getZipped(array $guids = []): string
1258
    {
1259
        $nzb = new NZB();
1260
        $zipped = new Zipper();
1261
        $zippedFileName = now()->format('Ymdhis').'.nzb.zip';
1262
        $zippedFilePath = resource_path().'/tmp/'.$zippedFileName;
1263
1264
        foreach ($guids as $guid) {
1265
            $nzbPath = $nzb->NZBPath($guid);
1266
1267
            if ($nzbPath) {
1268
                $nzbContents = Utility::unzipGzipFile($nzbPath);
1269
1270
                if ($nzbContents) {
1271
                    $filename = $guid;
1272
                    $r = Release::getByGuid($guid);
1273
                    if ($r) {
1274
                        $filename = $r['searchname'];
1275
                    }
1276
                    $zipped->make($zippedFilePath)->addString($filename.'.nzb', $nzbContents);
1277
                }
1278
            }
1279
        }
1280
1281
        $zipped->close();
1282
1283
        return File::isFile($zippedFilePath) ? $zippedFilePath : '';
1284
    }
1285
1286
    /**
1287
     * Get count of releases for pager.
1288
     *
1289
     *
1290
     * @param string $query The query to get the count from.
1291
     *
1292
     * @return int
1293
     */
1294
    private function getPagerCount($query): int
1295
    {
1296
        $sql = sprintf(
1297
            'SELECT COUNT(z.id) AS count FROM (%s LIMIT %s) z',
1298
            preg_replace('/SELECT.+?FROM\s+releases/is', 'SELECT r.id FROM releases', $query),
1299
            config('nntmux.max_pager_results')
1300
        );
1301
        $count = Cache::get(md5($sql));
1302
        if ($count !== null) {
1303
            return $count;
1304
        }
1305
        $count = Release::fromQuery($sql);
1306
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_short'));
1307
        Cache::put(md5($sql), $count[0]->count, $expiresAt);
1308
1309
        return $count[0]->count ?? 0;
1310
    }
1311
}
1312