Completed
Push — dev ( bc7008...1b04e6 )
by Darko
07:33
created

Releases::moviesSearch()   C

Complexity

Conditions 17
Paths 6

Size

Total Lines 61
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 306

Importance

Changes 0
Metric Value
eloc 47
dl 0
loc 61
ccs 0
cts 0
cp 0
rs 5.2166
c 0
b 0
f 0
cc 17
nc 6
nop 11
crap 306

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

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

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

685
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
686
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
687
            $whereSql
688
        );
689
        $sql = sprintf(
690
            'SELECT * FROM (
691
				%s
692
			) r
693
			ORDER BY r.%s %s
694
			LIMIT %d OFFSET %d',
695
            $baseSql,
696
            $orderBy[0],
697
            $orderBy[1],
698
            $limit,
699
            $offset
700
        );
701
        $releases = Cache::get(md5($sql));
702
        if ($releases !== null) {
703
            return $releases;
704
        }
705
        $releases = ! empty($searchResult) ? Release::fromQuery($sql) : [];
706
        if (! empty($releases) && \count($releases) > 0) {
707
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
708
        }
709
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
710
        Cache::put(md5($sql), $releases, $expiresAt);
711
712
        return $releases;
713
    }
714
715
    /**
716
     * Search function for API.
717
     *
718
     *
719
     * @param       $searchName
720
     * @param       $groupName
721
     * @param int   $offset
722
     * @param int   $limit
723
     * @param int   $maxAge
724
     * @param array $excludedCats
725
     * @param array $cat
726
     * @param int   $minSize
727
     * @param array $tags
728
     *
729
     * @return \Illuminate\Database\Eloquent\Collection|mixed
730
     */
731
    public function apiSearch($searchName, $groupName, $offset = 0, $limit = 1000, $maxAge = -1, array $excludedCats = [], array $cat = [-1], $minSize = 0, array $tags = [])
732
    {
733
        if ($searchName !== -1) {
734
            $searchResult = array_pluck($this->sphinxSearch->searchIndexes('releases_rt', $searchName, ['searchname']), 'id');
735
        }
736
737
        $catQuery = Category::getCategorySearch($cat);
738
739
        $whereSql = sprintf(
740
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s',
741
            $this->showPasswords(),
742
            NZB::NZB_ADDED,
743
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
744
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
745
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', Group::getIDByName($groupName)) : ''),
746
            $catQuery,
747
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
748
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
749
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
750
        );
751
        $baseSql = sprintf(
752
            "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,
753
				CONCAT(cp.title, ' > ', c.title) AS category_name,
754
				%s AS category_ids,
755
				g.name AS group_name,
756
				cp.id AS categoryparentid,
757
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
758
				tve.firstaired, tve.title, tve.series, tve.episode
759
			FROM releases r
760
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
761
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
762
			LEFT JOIN movieinfo m ON m.id = r.movieinfo_id
763
			LEFT JOIN groups g ON g.id = r.groups_id
764
			LEFT JOIN categories c ON c.id = r.categories_id
765
			LEFT JOIN categories cp ON cp.id = c.parentid
766
			%s %s",
767
            $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

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

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

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

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

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

1245
            $results = $this->search(getSimilarName($name), -1, -1, -1, -1, '', '', 0, 0, -1, -1, 0, /** @scrutinizer ignore-type */ '', '', -1, $excludedCats, [$parentCat]);
Loading history...
1246
            if (! $results) {
0 ignored issues
show
introduced by
$results is of type Illuminate\Database\Eloquent\Collection, thus it always evaluated to true.
Loading history...
1247
                return $results;
1248
            }
1249
1250
            $ret = [];
1251
            foreach ($results as $res) {
1252
                if ($res['id'] !== $currentID && $res['categoryparentid'] === $parentCat) {
1253
                    $ret[] = $res;
1254
                }
1255
            }
1256
        }
1257
1258
        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...
1259
    }
1260
1261
    /**
1262
     * @param array $guids
1263
     *
1264
     * @return string
1265
     * @throws \Exception
1266
     */
1267
    public function getZipped(array $guids = []): string
1268
    {
1269
        $nzb = new NZB();
1270
        $zipped = new Zipper();
1271
        $zippedFileName = now()->format('Ymdhis').'.nzb.zip';
1272
        $zippedFilePath = resource_path().'/tmp/'.$zippedFileName;
1273
1274
        foreach ($guids as $guid) {
1275
            $nzbPath = $nzb->NZBPath($guid);
1276
1277
            if ($nzbPath) {
1278
                $nzbContents = Utility::unzipGzipFile($nzbPath);
1279
1280
                if ($nzbContents) {
1281
                    $filename = $guid;
1282
                    $r = Release::getByGuid($guid);
1283
                    if ($r) {
1284
                        $filename = $r['searchname'];
1285
                    }
1286
                    $zipped->make($zippedFilePath)->addString($filename.'.nzb', $nzbContents);
1287
                }
1288
            }
1289
        }
1290
1291
        $zipped->close();
1292
1293
        return File::isFile($zippedFilePath) ? $zippedFilePath : '';
1294
    }
1295
1296
    /**
1297
     * Get count of releases for pager.
1298
     *
1299
     *
1300
     * @param string $query The query to get the count from.
1301
     *
1302
     * @return int
1303
     */
1304
    private function getPagerCount($query): int
1305
    {
1306
        $sql = sprintf(
1307
            'SELECT COUNT(z.id) AS count FROM (%s LIMIT %s) z',
1308
            preg_replace('/SELECT.+?FROM\s+releases/is', 'SELECT r.id FROM releases', $query),
1309
            config('nntmux.max_pager_results')
1310
        );
1311
        $count = Cache::get(md5($sql));
1312
        if ($count !== null) {
1313
            return $count;
1314
        }
1315
        $count = Release::fromQuery($sql);
1316
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_short'));
1317
        Cache::put(md5($sql), $count[0]->count, $expiresAt);
1318
1319
        return $count[0]->count ?? 0;
1320
    }
1321
}
1322