Completed
Push — dev ( 829269...a67f04 )
by Darko
09:05
created

Releases::getZipped()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 27
ccs 0
cts 0
cp 0
rs 9.1111
c 0
b 0
f 0
cc 6
nc 10
nop 1
crap 42

1 Method

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

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

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

634
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', /** @scrutinizer ignore-type */ UsenetGroup::getIDByName($groupName)) : ''),
Loading history...
635
            (array_key_exists($sizeFrom, $sizeRange) ? ' AND r.size > '.(104857600 * (int) $sizeRange[$sizeFrom]).' ' : ''),
636
            (array_key_exists($sizeTo, $sizeRange) ? ' AND r.size < '.(104857600 * (int) $sizeRange[$sizeTo]).' ' : ''),
637
            $catQuery,
638
            ((int) $daysNew !== -1 ? sprintf(' AND r.postdate < (NOW() - INTERVAL %d DAY) ', $daysNew) : ''),
639
            ((int) $daysOld !== -1 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $daysOld) : ''),
640
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
641
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
642
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
643
        );
644
        $baseSql = sprintf(
645
            "SELECT r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id, r.haspreview, r.jpgstatus,  cp.title AS parent_category, c.title AS sub_category,
646
				CONCAT(cp.title, ' > ', c.title) AS category_name,
647
				%s AS category_ids,
648
				df.failed AS failed,
649
				g.name AS group_name,
650
				rn.releases_id AS nfoid,
651
				re.releases_id AS reid,
652
				cp.id AS categoryparentid,
653
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
654
				tve.firstaired
655
			FROM releases r
656
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
657
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
658
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
659
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
660
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
661
			LEFT JOIN categories c ON c.id = r.categories_id
662
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
663
			LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
664
			%s %s",
665
            $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

665
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
666
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
667
            $whereSql
668
        );
669
        $sql = sprintf(
670
            'SELECT * FROM (
671
				%s
672
			) r
673
			ORDER BY r.%s %s
674
			LIMIT %d OFFSET %d',
675
            $baseSql,
676
            $orderBy[0],
677
            $orderBy[1],
678
            $limit,
679
            $offset
680
        );
681
        $releases = Cache::get(md5($sql));
682
        if ($releases !== null) {
683
            return $releases;
684
        }
685
        $releases = ! empty($searchResult) ? self::fromQuery($sql) : collect();
686
        if ($releases->isNotEmpty()) {
687
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
688
        }
689
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
690
        Cache::put(md5($sql), $releases, $expiresAt);
691
692
        return $releases;
693
    }
694
695
    /**
696
     * Search function for API.
697
     *
698
     *
699
     * @param       $searchName
700
     * @param       $groupName
701
     * @param int   $offset
702
     * @param int   $limit
703
     * @param int   $maxAge
704
     * @param array $excludedCats
705
     * @param array $cat
706
     * @param int   $minSize
707
     * @param array $tags
708
     *
709
     * @return Collection|mixed
710
     */
711
    public function apiSearch($searchName, $groupName, $offset = 0, $limit = 1000, $maxAge = -1, array $excludedCats = [], array $cat = [-1], $minSize = 0, array $tags = [])
712
    {
713
        if ($searchName !== -1) {
714
            $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $searchName, ['searchname']), 'id');
715
        }
716
717
        $catQuery = Category::getCategorySearch($cat);
718
719
        $whereSql = sprintf(
720
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s',
721
            $this->showPasswords(),
722
            NZB::NZB_ADDED,
723
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
724
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
725
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', UsenetGroup::getIDByName($groupName)) : ''),
0 ignored issues
show
Bug introduced by
It seems like App\Models\UsenetGroup::getIDByName($groupName) can also be of type false; however, parameter $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

725
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', /** @scrutinizer ignore-type */ UsenetGroup::getIDByName($groupName)) : ''),
Loading history...
726
            $catQuery,
727
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
728
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
729
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
730
        );
731
        $baseSql = sprintf(
732
            "SELECT r.searchname, r.guid, r.postdate, r.groups_id, r.categories_id, r.size, r.totalpart, r.fromname, r.passwordstatus, r.grabs, r.comments, r.adddate, r.videos_id, r.tv_episodes_id, r.haspreview, r.jpgstatus, m.imdbid, m.tmdbid, m.traktid, cp.title AS parent_category, c.title AS sub_category,
733
				CONCAT(cp.title, ' > ', c.title) AS category_name,
734
				%s AS category_ids,
735
				g.name AS group_name,
736
				cp.id AS categoryparentid,
737
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
738
				tve.firstaired, tve.title, tve.series, tve.episode
739
			FROM releases r
740
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
741
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
742
			LEFT JOIN movieinfo m ON m.id = r.movieinfo_id
743
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
744
			LEFT JOIN categories c ON c.id = r.categories_id
745
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
746
			%s %s",
747
            $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

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

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

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

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

1189
            /** @scrutinizer ignore-type */ $this->getConcatenatedCategoryIDs(),
Loading history...
1190
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
1191
            $whereSql
1192
        );
1193
        $sql = sprintf(
1194
            '%s
1195
			ORDER BY postdate DESC
1196
			LIMIT %d OFFSET %d',
1197
            $baseSql,
1198
            $limit,
1199
            $offset
1200
        );
1201
1202
        $releases = Cache::get(md5($sql));
1203
        if ($releases !== null) {
1204
            return $releases;
1205
        }
1206
        $releases = self::fromQuery($sql);
1207
        if ($releases->isNotEmpty()) {
1208
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
1209
        }
1210
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
1211
        Cache::put(md5($sql), $releases, $expiresAt);
1212
1213
        return $releases;
1214
    }
1215
1216
    /**
1217
     * @param $currentID
1218
     * @param $name
1219
     * @param array $excludedCats
1220
     * @return array|Collection
1221
     */
1222
    public function searchSimilar($currentID, $name, array $excludedCats = [])
1223
    {
1224
        // Get the category for the parent of this release.
1225
        $ret = false;
1226
        $currRow = self::getCatByRelId($currentID);
1227
        if ($currRow !== null) {
1228
            $catRow = Category::find($currRow['categories_id']);
1229
            $parentCat = $catRow['root_categories_id'];
1230
1231
            $results = $this->search(['searchname' => getSimilarName($name)], -1, '', '', -1, -1, 0, config('nntmux.items_per_page'), '', -1, $excludedCats, [$parentCat]);
1232
            if (! $results) {
1233
                return $results;
1234
            }
1235
1236
            $ret = [];
1237
            foreach ($results as $res) {
1238
                if ($res['id'] !== $currentID && $res['categoryparentid'] === $parentCat) {
1239
                    $ret[] = $res;
1240
                }
1241
            }
1242
        }
1243
1244
        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...
1245
    }
1246
1247
    /**
1248
     * Get count of releases for pager.
1249
     *
1250
     *
1251
     * @param string $query The query to get the count from.
1252
     *
1253
     * @return int
1254
     */
1255
    private function getPagerCount($query): int
1256
    {
1257
        $sql = sprintf(
1258
            'SELECT COUNT(z.id) AS count FROM (%s LIMIT %s) z',
1259
            preg_replace('/SELECT.+?FROM\s+releases/is', 'SELECT r.id FROM releases', $query),
1260
            config('nntmux.max_pager_results')
1261
        );
1262
        $count = Cache::get(md5($sql));
1263
        if ($count !== null) {
1264
            return $count;
1265
        }
1266
        $count = self::fromQuery($sql);
1267
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_short'));
1268
        Cache::put(md5($sql), $count[0]->count, $expiresAt);
1269
1270
        return $count[0]->count ?? 0;
1271
    }
1272
}
1273