Completed
Push — dev ( 4f142e...7457f7 )
by Darko
07:07
created

Releases   F

Complexity

Total Complexity 225

Size/Duplication

Total Lines 1391
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 14
Bugs 4 Features 0
Metric Value
wmc 225
eloc 792
c 14
b 4
f 0
dl 0
loc 1391
ccs 0
cts 443
cp 0
rs 1.808

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B getBrowseRange() 0 60 10
A getBrowseOrdering() 0 15 1
A deleteMultiple() 0 9 2
A getLatestUsenetPostDate() 0 5 2
A getShowsCount() 0 18 3
A getEarliestUsenetPostDate() 0 5 2
B getBrowseOrder() 0 26 10
B getBrowseCount() 0 19 7
A getReleasedGroupsForSelect() 0 17 3
B getShowsRange() 0 47 5
A getForExport() 0 29 6
A showPasswords() 0 10 3
A updateMulti() 0 16 4
A uSQL() 0 18 4
A deleteSingle() 0 33 6
D apiSearch() 0 91 18
A searchSimilar() 0 23 6
F tvSearch() 0 148 41
D moviesSearch() 0 89 20
A getPagerCount() 0 16 2
F search() 0 129 22
F apiTvSearch() 0 140 35
C animeSearch() 0 84 12

How to fix   Complexity   

Complex Class

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

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

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

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

182
        $orderArr = explode('_', /** @scrutinizer ignore-type */ ($orderBy === '' ? 'posted_desc' : $orderBy));
Loading history...
183
        switch ($orderArr[0]) {
184
            case 'cat':
185
                $orderField = 'categories_id';
186
                break;
187
            case 'name':
188
                $orderField = 'searchname';
189
                break;
190
            case 'size':
191
                $orderField = 'size';
192
                break;
193
            case 'files':
194
                $orderField = 'totalpart';
195
                break;
196
            case 'stats':
197
                $orderField = 'grabs';
198
                break;
199
            case 'posted':
200
            default:
201
                $orderField = 'postdate';
202
                break;
203
        }
204
205
        return [$orderField, isset($orderArr[1]) && preg_match('/^(asc|desc)$/i', $orderArr[1]) ? $orderArr[1] : 'desc'];
206
    }
207
208
    /**
209
     * Return ordering types usable on site.
210
     *
211
     * @return string[]
212
     */
213
    public function getBrowseOrdering(): array
214
    {
215
        return [
216
            'name_asc',
217
            'name_desc',
218
            'cat_asc',
219
            'cat_desc',
220
            'posted_asc',
221
            'posted_desc',
222
            'size_asc',
223
            'size_desc',
224
            'files_asc',
225
            'files_desc',
226
            'stats_asc',
227
            'stats_desc',
228
        ];
229
    }
230
231
    /**
232
     * Get list of releases available for export.
233
     *
234
     *
235
     * @param string $postFrom
236
     * @param string $postTo
237
     * @param string $groupID
238
     *
239
     * @return Collection|\Illuminate\Support\Collection|static[]
240
     */
241
    public function getForExport($postFrom = '', $postTo = '', $groupID = '')
242
    {
243
        $query = self::query()
244
            ->where('r.nzbstatus', NZB::NZB_ADDED)
245
            ->select(['r.searchname', 'r.guid', 'g.name as gname', DB::raw("CONCAT(cp.title,'_',c.title) AS catName")])
246
            ->from('releases as r')
247
            ->leftJoin('categories as c', 'c.id', '=', 'r.categories_id')
248
            ->leftJoin('root_categories as cp', 'cp.id', '=', 'c.root_categories_id')
249
            ->leftJoin('usenet_groups as g', 'g.id', '=', 'r.groups_id');
250
251
        if ($groupID !== '') {
252
            $query->where('r.groups_id', $groupID);
253
        }
254
255
        if ($postFrom !== '') {
256
            $dateParts = explode('/', $postFrom);
257
            if (\count($dateParts) === 3) {
258
                $query->where('r.postdate', '>', $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0].'00:00:00');
259
            }
260
        }
261
262
        if ($postTo !== '') {
263
            $dateParts = explode('/', $postTo);
264
            if (\count($dateParts) === 3) {
265
                $query->where('r.postdate', '<', $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0].'23:59:59');
266
            }
267
        }
268
269
        return $query->get();
270
    }
271
272
    /**
273
     * Get date in this format : 01/01/2014 of the oldest release.
274
     *
275
     * @note Used for exporting NZBs.
276
     * @return mixed
277
     */
278
    public function getEarliestUsenetPostDate()
279
    {
280
        $row = self::query()->selectRaw("DATE_FORMAT(min(postdate), '%d/%m/%Y') AS postdate")->first();
281
282
        return $row === null ? '01/01/2014' : $row['postdate'];
283
    }
284
285
    /**
286
     * Get date in this format : 01/01/2014 of the newest release.
287
     *
288
     * @note Used for exporting NZBs.
289
     * @return mixed
290
     */
291
    public function getLatestUsenetPostDate()
292
    {
293
        $row = self::query()->selectRaw("DATE_FORMAT(max(postdate), '%d/%m/%Y') AS postdate")->first();
294
295
        return $row === null ? '01/01/2014' : $row['postdate'];
296
    }
297
298
    /**
299
     * Gets all groups for drop down selection on NZB-Export web page.
300
     *
301
     * @param bool $blnIncludeAll
302
     *
303
     * @note Used for exporting NZBs.
304
     * @return array
305
     */
306
    public function getReleasedGroupsForSelect($blnIncludeAll = true): array
307
    {
308
        $groups = self::query()
309
            ->selectRaw('DISTINCT g.id, g.name')
310
            ->leftJoin('usenet_groups as g', 'g.id', '=', 'releases.groups_id')
311
            ->get();
312
        $temp_array = [];
313
314
        if ($blnIncludeAll) {
315
            $temp_array[-1] = '--All Groups--';
316
        }
317
318
        foreach ($groups as $group) {
319
            $temp_array[$group['id']] = $group['name'];
320
        }
321
322
        return $temp_array;
323
    }
324
325
    /**
326
     * Get TV for My Shows page.
327
     *
328
     *
329
     * @param $userShows
330
     * @param $offset
331
     * @param $limit
332
     * @param $orderBy
333
     * @param int $maxAge
334
     * @param array $excludedCats
335
     * @return Collection|mixed
336
     */
337
    public function getShowsRange($userShows, $offset, $limit, $orderBy, $maxAge = -1, array $excludedCats = [])
338
    {
339
        $orderBy = $this->getBrowseOrder($orderBy);
340
        $sql = sprintf(
341
            "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,
342
					CONCAT(cp.title, '-', c.title) AS category_name,
343
					g.name AS group_name,
344
					rn.releases_id AS nfoid, re.releases_id AS reid,
345
					tve.firstaired,
346
					df.failed AS failed
347
				FROM releases r
348
				LEFT OUTER JOIN video_data re ON re.releases_id = r.id
349
				LEFT JOIN usenet_groups g ON g.id = r.groups_id
350
				LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
351
				LEFT OUTER JOIN tv_episodes tve ON tve.videos_id = r.videos_id
352
				LEFT JOIN categories c ON c.id = r.categories_id
353
				LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
354
				LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
355
				WHERE %s %s
356
				AND r.nzbstatus = %d
357
				AND r.categories_id BETWEEN %d AND %d
358
				AND r.passwordstatus %s
359
				%s
360
				GROUP BY r.id
361
				ORDER BY %s %s %s",
362
            $this->uSQL($userShows, 'videos_id'),
363
            (\count($excludedCats) ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
364
            NZB::NZB_ADDED,
365
            Category::TV_ROOT,
366
            Category::TV_OTHER,
367
            $this->showPasswords(),
368
            ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : ''),
369
            $orderBy[0],
370
            $orderBy[1],
371
            ($offset === false ? '' : (' LIMIT '.$limit.' OFFSET '.$offset))
372
        );
373
374
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
375
        $result = Cache::get(md5($sql));
376
        if ($result !== null) {
377
            return $result;
378
        }
379
380
        $result = self::fromQuery($sql);
381
        Cache::put(md5($sql), $result, $expiresAt);
382
383
        return $result;
384
    }
385
386
    /**
387
     * Get count for my shows page pagination.
388
     *
389
     * @param       $userShows
390
     * @param int   $maxAge
391
     * @param array $excludedCats
392
     *
393
     * @return int
394
     */
395
    public function getShowsCount($userShows, $maxAge = -1, array $excludedCats = []): int
396
    {
397
        return $this->getPagerCount(
398
            sprintf(
399
                'SELECT r.id
400
				FROM releases r
401
				WHERE %s %s
402
				AND r.nzbstatus = %d
403
				AND r.categories_id BETWEEN %d AND %d
404
				AND r.passwordstatus %s
405
				%s',
406
                $this->uSQL($userShows, 'videos_id'),
407
                (\count($excludedCats) ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
408
                NZB::NZB_ADDED,
409
                Category::TV_ROOT,
410
                Category::TV_OTHER,
411
                $this->showPasswords(),
412
                ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '')
413
            )
414
        );
415
    }
416
417
    /**
418
     * Delete multiple releases, or a single by ID.
419
     *
420
     * @param array|int|string $list   Array of GUID or ID of releases to delete.
421
     * @throws \Exception
422
     */
423
    public function deleteMultiple($list): void
424
    {
425
        $list = (array) $list;
426
427
        $nzb = new NZB();
428
        $releaseImage = new ReleaseImage();
429
430
        foreach ($list as $identifier) {
431
            $this->deleteSingle(['g' => $identifier, 'i' => false], $nzb, $releaseImage);
432
        }
433
    }
434
435
    /**
436
     * Deletes a single release by GUID, and all the corresponding files.
437
     *
438
     * @param array                    $identifiers ['g' => Release GUID(mandatory), 'id => ReleaseID(optional, pass
439
     *                                              false)]
440
     * @param \Blacklight\NZB          $nzb
441
     * @param \Blacklight\ReleaseImage $releaseImage
442
     *
443
     * @throws \Exception
444
     */
445
    public function deleteSingle($identifiers, NZB $nzb, ReleaseImage $releaseImage): void
446
    {
447
        // Delete NZB from disk.
448
        $nzbPath = $nzb->NZBPath($identifiers['g']);
449
        if (! empty($nzbPath)) {
450
            File::delete($nzbPath);
451
        }
452
453
        // Delete images.
454
        $releaseImage->delete($identifiers['g']);
455
456
        if (config('nntmux.elasticsearch_enabled') === true) {
457
            if ($identifiers['i'] === false) {
458
                $identifiers['i'] = Release::query()->where('guid', $identifiers['g'])->first(['id']);
459
                if ($identifiers['i'] !== null) {
460
                    $identifiers['i'] = $identifiers['i']['id'];
461
                }
462
            }
463
            if ($identifiers['i'] !== false) {
464
                $params = [
465
                    'index' => 'releases',
466
                    'id' => $identifiers['i'],
467
                ];
468
469
                \Elasticsearch::delete($params);
470
            }
471
        } else {
472
            // Delete from sphinx.
473
            $this->sphinxSearch->deleteRelease($identifiers);
474
        }
475
476
        // Delete from DB.
477
        self::whereGuid($identifiers['g'])->delete();
478
    }
479
480
    /**
481
     * @param $guids
482
     * @param $category
483
     * @param $grabs
484
     * @param $videoId
485
     * @param $episodeId
486
     * @param $anidbId
487
     * @param $imdbId
488
     * @return bool|int
489
     */
490
    public function updateMulti($guids, $category, $grabs, $videoId, $episodeId, $anidbId, $imdbId)
491
    {
492
        if (! \is_array($guids) || \count($guids) < 1) {
493
            return false;
494
        }
495
496
        $update = [
497
            'categories_id'     => $category === -1 ? 'categories_id' : $category,
498
            'grabs'          => $grabs,
499
            'videos_id'      => $videoId,
500
            'tv_episodes_id' => $episodeId,
501
            'anidbid'        => $anidbId,
502
            'imdbid'         => $imdbId,
503
        ];
504
505
        return self::query()->whereIn('guid', $guids)->update($update);
506
    }
507
508
    /**
509
     * Creates part of a query for some functions.
510
     *
511
     * @param array|Collection  $userQuery
512
     * @param string $type
513
     *
514
     * @return string
515
     */
516
    public function uSQL($userQuery, $type): string
517
    {
518
        $sql = '(1=2 ';
519
        foreach ($userQuery as $query) {
520
            $sql .= sprintf('OR (r.%s = %d', $type, $query->$type);
521
            if (! empty($query->categories)) {
522
                $catsArr = explode('|', $query->categories);
523
                if (\count($catsArr) > 1) {
524
                    $sql .= sprintf(' AND r.categories_id IN (%s)', implode(',', $catsArr));
525
                } else {
526
                    $sql .= sprintf(' AND r.categories_id = %d', $catsArr[0]);
527
                }
528
            }
529
            $sql .= ') ';
530
        }
531
        $sql .= ') ';
532
533
        return $sql;
534
    }
535
536
    /**
537
     * Function for searching on the site (by subject, searchname or advanced).
538
     *
539
     *
540
     * @param array $searchArr
541
     * @param              $groupName
542
     * @param              $sizeFrom
543
     * @param              $sizeTo
544
     * @param              $daysNew
545
     * @param              $daysOld
546
     * @param int $offset
547
     * @param int $limit
548
     * @param string|array $orderBy
549
     * @param int $maxAge
550
     * @param array $excludedCats
551
     * @param string $type
552
     * @param array $cat
553
     * @param int $minSize
554
     * @param array $tags
555
     *
556
     * @return array|Collection|mixed
557
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
558
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
559
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
560
     */
561
    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 = [])
562
    {
563
        $sizeRange = [
564
            1 => 1,
565
            2 => 2.5,
566
            3 => 5,
567
            4 => 10,
568
            5 => 20,
569
            6 => 30,
570
            7 => 40,
571
            8 => 80,
572
            9 => 160,
573
            10 => 320,
574
            11 => 640,
575
        ];
576
        if ($orderBy === '') {
577
            $orderBy = [];
578
            $orderBy[0] = 'postdate ';
579
            $orderBy[1] = 'desc ';
580
        } else {
581
            $orderBy = $this->getBrowseOrder($orderBy);
582
        }
583
584
        $searchFields = Arr::where($searchArr, function ($value) {
585
            return $value !== -1;
586
        });
587
588
        $fields = $phrases = [];
589
        foreach ($searchFields as $key => $value) {
590
            $fields[] = $key;
591
            $phrases[] = $value;
592
        }
593
594
        if (config('nntmux.elasticsearch_enabled') === true) {
595
            $search = [
596
                'index' => 'releases',
597
                'body' => [
598
                    'query' => [
599
                        'multi_match' => [
600
                            'query' => implode(' ', $phrases),
601
                            'fields' => $fields,
602
                            'type' => 'phrase',
603
                        ]
604
                    ],
605
                    'size' => $limit,
606
                ],
607
            ];
608
609
            $results = \Elasticsearch::search($search);
610
611
            $searchResult = [];
612
            foreach ($results['hits']['hits'] as $result) {
613
                $searchResult[] = $result['_source']['id'];
614
            }
615
        } else {
616
            $results = $this->sphinxSearch->searchIndexes('releases_rt', '', [], $searchFields);
617
618
            $searchResult = Arr::pluck($results, 'id');
619
        }
620
621
        $catQuery = '';
622
        if ($type === 'basic') {
623
            $catQuery = Category::getCategorySearch($cat);
624
        } elseif ($type === 'advanced' && (int) $cat[0] !== -1) {
625
            $catQuery = sprintf('AND r.categories_id = %d', $cat[0]);
626
        }
627
        $whereSql = sprintf(
628
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s %s %s %s %s',
629
            $this->showPasswords(),
630
            NZB::NZB_ADDED,
631
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
632
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
633
            ((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

633
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', /** @scrutinizer ignore-type */ UsenetGroup::getIDByName($groupName)) : ''),
Loading history...
634
            (array_key_exists($sizeFrom, $sizeRange) ? ' AND r.size > '.(104857600 * (int) $sizeRange[$sizeFrom]).' ' : ''),
635
            (array_key_exists($sizeTo, $sizeRange) ? ' AND r.size < '.(104857600 * (int) $sizeRange[$sizeTo]).' ' : ''),
636
            $catQuery,
637
            ((int) $daysNew !== -1 ? sprintf(' AND r.postdate < (NOW() - INTERVAL %d DAY) ', $daysNew) : ''),
638
            ((int) $daysOld !== -1 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $daysOld) : ''),
639
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
640
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
641
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
642
        );
643
        $baseSql = sprintf(
644
            "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,
645
				CONCAT(cp.title, ' > ', c.title) AS category_name,
646
				df.failed AS failed,
647
				g.name AS group_name,
648
				rn.releases_id AS nfoid,
649
				re.releases_id AS reid,
650
				cp.id AS categoryparentid,
651
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
652
				tve.firstaired
653
			FROM releases r
654
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
655
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
656
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
657
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
658
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
659
			LEFT JOIN categories c ON c.id = r.categories_id
660
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
661
			LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
662
			%s %s",
663
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
664
            $whereSql
665
        );
666
        $sql = sprintf(
667
            'SELECT * FROM (
668
				%s
669
			) r
670
			ORDER BY r.%s %s
671
			LIMIT %d OFFSET %d',
672
            $baseSql,
673
            $orderBy[0],
674
            $orderBy[1],
675
            $limit,
676
            $offset
677
        );
678
        $releases = Cache::get(md5($sql));
679
        if ($releases !== null) {
680
            return $releases;
681
        }
682
        $releases = ! empty($searchResult) ? self::fromQuery($sql) : collect();
683
        if ($releases->isNotEmpty()) {
684
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
685
        }
686
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
687
        Cache::put(md5($sql), $releases, $expiresAt);
688
689
        return $releases;
690
    }
691
692
    /**
693
     * Search function for API.
694
     *
695
     *
696
     * @param       $searchName
697
     * @param       $groupName
698
     * @param int $offset
699
     * @param int $limit
700
     * @param int $maxAge
701
     * @param array $excludedCats
702
     * @param array $cat
703
     * @param int $minSize
704
     * @param array $tags
705
     *
706
     * @return Collection|mixed
707
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
708
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
709
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
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
            if (config('nntmux.elasticsearch_enabled') === true) {
715
                $search = [
716
                    'index' => 'releases',
717
                    'body' => [
718
                        'query' => [
719
                            'multi_match' => [
720
                                'query' => $searchName,
721
                                'fields' => ['searchname'],
722
                                'type' => 'phrase',
723
                            ],
724
                        ],
725
                        'size' => $limit,
726
                    ],
727
                ];
728
729
                $results = \Elasticsearch::search($search);
730
731
                $searchResult = [];
732
                foreach ($results['hits']['hits'] as $result) {
733
                    $searchResult[] = $result['_source']['id'];
734
                }
735
            } else {
736
                $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $searchName, ['searchname']), 'id');
737
            }
738
        }
739
740
        $catQuery = Category::getCategorySearch($cat);
741
742
        $whereSql = sprintf(
743
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s',
744
            $this->showPasswords(),
745
            NZB::NZB_ADDED,
746
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
747
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
748
            ((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

748
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', /** @scrutinizer ignore-type */ UsenetGroup::getIDByName($groupName)) : ''),
Loading history...
749
            $catQuery,
750
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
751
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
752
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
753
        );
754
        $baseSql = sprintf(
755
            "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,
756
				CONCAT(cp.title, ' > ', c.title) AS category_name,
757
				g.name AS group_name,
758
				cp.id AS categoryparentid,
759
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
760
				tve.firstaired, tve.title, tve.series, tve.episode
761
			FROM releases r
762
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
763
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
764
			LEFT JOIN movieinfo m ON m.id = r.movieinfo_id
765
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
766
			LEFT JOIN categories c ON c.id = r.categories_id
767
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
768
			%s %s",
769
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
770
            $whereSql
771
        );
772
        $sql = sprintf(
773
            'SELECT * FROM (
774
				%s
775
			) r
776
			ORDER BY r.postdate DESC
777
			LIMIT %d OFFSET %d',
778
            $baseSql,
779
            $limit,
780
            $offset
781
        );
782
        $releases = Cache::get(md5($sql));
783
        if ($releases !== null) {
784
            return $releases;
785
        }
786
        if ($searchName !== -1 && ! empty($searchResult)) {
787
            $releases = self::fromQuery($sql);
788
        } elseif ($searchName !== -1 && empty($searchResult)) {
789
            $releases = collect();
790
        } elseif ($searchName === -1) {
791
            $releases = self::fromQuery($sql);
792
        } else {
793
            $releases = collect();
794
        }
795
        if ($releases->isNotEmpty()) {
796
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
797
        }
798
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
799
        Cache::put(md5($sql), $releases, $expiresAt);
800
801
        return $releases;
802
    }
803
804
    /**
805
     * Search TV Shows via API.
806
     *
807
     *
808
     * @param array $siteIdArr
809
     * @param string $series
810
     * @param string $episode
811
     * @param string $airDate
812
     * @param int $offset
813
     * @param int $limit
814
     * @param string $name
815
     * @param array $cat
816
     * @param int $maxAge
817
     * @param int $minSize
818
     * @param array $excludedCategories
819
     * @param array $tags
820
     * @return Collection|mixed
821
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
822
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
823
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
824
     */
825
    public function tvSearch(array $siteIdArr = [], $series = '', $episode = '', $airDate = '', $offset = 0, $limit = 100, $name = '', array $cat = [-1], $maxAge = -1, $minSize = 0, array $excludedCategories = [], array $tags = [])
826
    {
827
        $siteSQL = [];
828
        $showSql = '';
829
        foreach ($siteIdArr as $column => $Id) {
830
            if ($Id > 0) {
831
                $siteSQL[] = sprintf('v.%s = %d', $column, $Id);
832
            }
833
        }
834
        if (\count($siteSQL) > 0) {
835
            // If we have show info, find the Episode ID/Video ID first to avoid table scans
836
            $showQry = sprintf(
837
                "
838
				SELECT
839
					v.id AS video,
840
					GROUP_CONCAT(tve.id SEPARATOR ',') AS episodes
841
				FROM videos v
842
				LEFT JOIN tv_episodes tve ON v.id = tve.videos_id
843
				WHERE (%s) %s %s %s
844
				GROUP BY v.id
845
				LIMIT 1",
846
                implode(' OR ', $siteSQL),
847
                ($series !== '' ? sprintf('AND tve.series = %d', (int) preg_replace('/^s0*/i', '', $series)) : ''),
848
                ($episode !== '' ? sprintf('AND tve.episode = %d', (int) preg_replace('/^e0*/i', '', $episode)) : ''),
849
                ($airDate !== '' ? sprintf('AND DATE(tve.firstaired) = %s', escapeString($airDate)) : '')
850
            );
851
            $show = self::fromQuery($showQry);
852
            if (! empty($show[0]) && $show->isNotEmpty()) {
853
                if ((! empty($series) || ! empty($episode) || ! empty($airDate)) && $show[0]->episodes !== '') {
854
                    $showSql = sprintf('AND r.tv_episodes_id IN (%s)', $show[0]->episodes);
855
                } elseif ((int) $show[0]->video > 0) {
856
                    $showSql = 'AND r.videos_id = '.$show[0]->video;
857
                    // If $series is set but episode is not, return Season Packs only
858
                    if (! empty($series) && empty($episode)) {
859
                        $showSql .= ' AND r.tv_episodes_id = 0';
860
                    }
861
                } else {
862
                    // If we were passed Episode Info and no match was found, do not run the query
863
                    return [];
864
                }
865
            } else {
866
                // If we were passed Site ID Info and no match was found, do not run the query
867
                return [];
868
            }
869
        }
870
        // If $name is set it is a fallback search, add available SxxExx/airdate info to the query
871
        if (! empty($name) && $showSql === '') {
872
            if (! empty($series) && (int) $series < 1900) {
873
                $name .= sprintf(' S%s', str_pad($series, 2, '0', STR_PAD_LEFT));
874
                if (! empty($episode) && strpos($episode, '/') === false) {
875
                    $name .= sprintf('E%s', str_pad($episode, 2, '0', STR_PAD_LEFT));
876
                }
877
            } elseif (! empty($airDate)) {
878
                $name .= sprintf(' %s', str_replace(['/', '-', '.', '_'], ' ', $airDate));
879
            }
880
        }
881
        if (! empty($name)) {
882
            if (config('nntmux.elasticsearch_enabled') === true) {
883
                $search = [
884
                    'index' => 'releases',
885
                    'body' => [
886
                        'query' => [
887
                            'multi_match' => [
888
                                'query' => $name,
889
                                'fields' => ['searchname'],
890
                                'type' => 'phrase',
891
                            ],
892
                        ],
893
                        'size' => $limit,
894
                    ],
895
                ];
896
897
                $results = \Elasticsearch::search($search);
898
899
                $searchResult = [];
900
                foreach ($results['hits']['hits'] as $result) {
901
                    $searchResult[] = $result['_source']['id'];
902
                }
903
                if (empty($searchResult)) {
904
                    return collect();
905
                }
906
            } else {
907
                $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $name, ['searchname']), 'id');
908
909
                if (empty($searchResult)) {
910
                    return collect();
911
                }
912
            }
913
        }
914
        $whereSql = sprintf(
915
            'WHERE r.nzbstatus = %d
916
			AND r.passwordstatus %s
917
			%s %s %s %s %s %s %s',
918
            NZB::NZB_ADDED,
919
            $this->showPasswords(),
920
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
921
            $showSql,
922
            (! empty($name) && ! empty($searchResult)) ? 'AND r.id IN ('.implode(',', $searchResult).')' : '',
923
            Category::getCategorySearch($cat),
924
            $maxAge > 0 ? sprintf('AND r.postdate > NOW() - INTERVAL %d DAY', $maxAge) : '',
925
            $minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '',
926
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : ''
927
        );
928
        $baseSql = sprintf(
929
            "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,
930
				v.title, v.countries_id, v.started, v.tvdb, v.trakt,
931
					v.imdb, v.tmdb, v.tvmaze, v.tvrage, v.source,
932
				tvi.summary, tvi.publisher, tvi.image,
933
				tve.series, tve.episode, tve.se_complete, tve.title, tve.firstaired, tve.summary, cp.title AS parent_category, c.title AS sub_category,
934
				CONCAT(cp.title, ' > ', c.title) AS category_name,
935
				g.name AS group_name,
936
				rn.releases_id AS nfoid,
937
				re.releases_id AS reid
938
			FROM releases r
939
			LEFT OUTER JOIN videos v ON r.videos_id = v.id AND v.type = 0
940
			LEFT OUTER JOIN tv_info tvi ON v.id = tvi.videos_id
941
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
942
			LEFT JOIN categories c ON c.id = r.categories_id
943
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
944
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
945
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
946
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
947
			%s %s",
948
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
949
            $whereSql
950
        );
951
        $sql = sprintf(
952
            '%s
953
			ORDER BY postdate DESC
954
			LIMIT %d OFFSET %d',
955
            $baseSql,
956
            $limit,
957
            $offset
958
        );
959
        $releases = Cache::get(md5($sql));
960
        if ($releases !== null) {
961
            return $releases;
962
        }
963
        $releases = ((! empty($name) && ! empty($searchResult)) || empty($name)) ? self::fromQuery($sql) : [];
964
        if (! empty($releases) && $releases->isNotEmpty()) {
965
            $releases[0]->_totalrows = $this->getPagerCount(
966
                preg_replace('#LEFT(\s+OUTER)?\s+JOIN\s+(?!tv_episodes)\s+.*ON.*=.*\n#i', ' ', $baseSql)
967
            );
968
        }
969
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
970
        Cache::put(md5($sql), $releases, $expiresAt);
971
972
        return $releases;
973
    }
974
975
    /**
976
     * Search TV Shows via APIv2.
977
     *
978
     *
979
     * @param array $siteIdArr
980
     * @param string $series
981
     * @param string $episode
982
     * @param string $airDate
983
     * @param int $offset
984
     * @param int $limit
985
     * @param string $name
986
     * @param array $cat
987
     * @param int $maxAge
988
     * @param int $minSize
989
     * @param array $excludedCategories
990
     * @param array $tags
991
     * @return Collection|mixed
992
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
993
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
994
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
995
     */
996
    public function apiTvSearch(array $siteIdArr = [], $series = '', $episode = '', $airDate = '', $offset = 0, $limit = 100, $name = '', array $cat = [-1], $maxAge = -1, $minSize = 0, array $excludedCategories = [], array $tags = [])
997
    {
998
        $siteSQL = [];
999
        $showSql = '';
1000
        foreach ($siteIdArr as $column => $Id) {
1001
            if ($Id > 0) {
1002
                $siteSQL[] = sprintf('v.%s = %d', $column, $Id);
1003
            }
1004
        }
1005
        if (\count($siteSQL) > 0) {
1006
            // If we have show info, find the Episode ID/Video ID first to avoid table scans
1007
            $showQry = sprintf(
1008
                "
1009
				SELECT
1010
					v.id AS video,
1011
					GROUP_CONCAT(tve.id SEPARATOR ',') AS episodes
1012
				FROM videos v
1013
				LEFT JOIN tv_episodes tve ON v.id = tve.videos_id
1014
				WHERE (%s) %s %s %s
1015
				GROUP BY v.id
1016
				LIMIT 1",
1017
                implode(' OR ', $siteSQL),
1018
                ($series !== '' ? sprintf('AND tve.series = %d', (int) preg_replace('/^s0*/i', '', $series)) : ''),
1019
                ($episode !== '' ? sprintf('AND tve.episode = %d', (int) preg_replace('/^e0*/i', '', $episode)) : ''),
1020
                ($airDate !== '' ? sprintf('AND DATE(tve.firstaired) = %s', escapeString($airDate)) : '')
1021
            );
1022
            $show = self::fromQuery($showQry);
1023
            if ($show->isNotEmpty()) {
1024
                if ((! empty($series) || ! empty($episode) || ! empty($airDate)) && $show[0]->episodes != '') {
1025
                    $showSql = sprintf('AND r.tv_episodes_id IN (%s)', $show[0]->episodes);
1026
                } elseif ((int) $show[0]->video > 0) {
1027
                    $showSql = 'AND r.videos_id = '.$show[0]->video;
1028
                    // If $series is set but episode is not, return Season Packs only
1029
                    if (! empty($series) && empty($episode)) {
1030
                        $showSql .= ' AND r.tv_episodes_id = 0';
1031
                    }
1032
                } else {
1033
                    // If we were passed Episode Info and no match was found, do not run the query
1034
                    return [];
1035
                }
1036
            } else {
1037
                // If we were passed Site ID Info and no match was found, do not run the query
1038
                return [];
1039
            }
1040
        }
1041
        // If $name is set it is a fallback search, add available SxxExx/airdate info to the query
1042
        if (! empty($name) && $showSql === '') {
1043
            if (! empty($series) && (int) $series < 1900) {
1044
                $name .= sprintf(' S%s', str_pad($series, 2, '0', STR_PAD_LEFT));
1045
                if (! empty($episode) && strpos($episode, '/') === false) {
1046
                    $name .= sprintf('E%s', str_pad($episode, 2, '0', STR_PAD_LEFT));
1047
                }
1048
            } elseif (! empty($airDate)) {
1049
                $name .= sprintf(' %s', str_replace(['/', '-', '.', '_'], ' ', $airDate));
1050
            }
1051
        }
1052
        if (! empty($name)) {
1053
            if (config('nntmux.elasticsearch_enabled') === true) {
1054
                $search = [
1055
                    'index' => 'releases',
1056
                    'body' => [
1057
                        'multi_match' => [
1058
                            'query' => $name,
1059
                            'fields' => ['searchname'],
1060
                            'type' => 'phrase',
1061
                        ],
1062
                        'size' => $limit,
1063
                    ],
1064
                ];
1065
1066
                $results = \Elasticsearch::search($search);
1067
1068
                $searchResult = [];
1069
                foreach ($results['hits']['hits'] as $result) {
1070
                    $searchResult[] = $result['_source']['id'];
1071
                }
1072
                if (empty($searchResult)) {
1073
                    return collect();
1074
                }
1075
            } else {
1076
                $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $name, ['searchname']), 'id');
1077
1078
                if (empty($searchResult)) {
1079
                    return collect();
1080
                }
1081
            }
1082
        }
1083
        $whereSql = sprintf(
1084
            'WHERE r.nzbstatus = %d
1085
			AND r.passwordstatus %s
1086
			%s %s %s %s %s %s %s',
1087
            NZB::NZB_ADDED,
1088
            $this->showPasswords(),
1089
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
1090
            $showSql,
1091
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
1092
            Category::getCategorySearch($cat),
1093
            ($maxAge > 0 ? sprintf('AND r.postdate > NOW() - INTERVAL %d DAY', $maxAge) : ''),
1094
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : ''),
1095
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : ''
1096
        );
1097
        $baseSql = sprintf(
1098
            "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,
1099
				v.title, v.type, v.tvdb, v.trakt,v.imdb, v.tmdb, v.tvmaze, v.tvrage,
1100
				tve.series, tve.episode, tve.se_complete, tve.title, tve.firstaired, cp.title AS parent_category, c.title AS sub_category,
1101
				CONCAT(cp.title, ' > ', c.title) AS category_name,
1102
				g.name AS group_name
1103
			FROM releases r
1104
			LEFT OUTER JOIN videos v ON r.videos_id = v.id AND v.type = 0
1105
			LEFT OUTER JOIN tv_info tvi ON v.id = tvi.videos_id
1106
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
1107
			LEFT JOIN categories c ON c.id = r.categories_id
1108
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
1109
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
1110
			%s %s",
1111
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
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(
1129
                preg_replace('#LEFT(\s+OUTER)?\s+JOIN\s+(?!tv_episodes)\s+.*ON.*=.*\n#i', ' ', $baseSql)
1130
            );
1131
        }
1132
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
1133
        Cache::put(md5($sql), $releases, $expiresAt);
1134
1135
        return $releases;
1136
    }
1137
1138
    /**
1139
     * Search anime releases.
1140
     *
1141
     *
1142
     * @param $aniDbID
1143
     * @param int $offset
1144
     * @param int $limit
1145
     * @param string $name
1146
     * @param array $cat
1147
     * @param int $maxAge
1148
     * @param array $excludedCategories
1149
     * @return Collection|mixed
1150
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
1151
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
1152
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
1153
     */
1154
    public function animeSearch($aniDbID, $offset = 0, $limit = 100, $name = '', array $cat = [-1], $maxAge = -1, array $excludedCategories = [])
1155
    {
1156
        if (! empty($name)) {
1157
            if (config('nntmux.elasticsearch_enabled') === true) {
1158
                $search = [
1159
                    'index' => 'releases',
1160
                    'body' => [
1161
                        'query' => [
1162
                            'multi_match' => [
1163
                                'query' => $name,
1164
                                'fields' => ['searchname'],
1165
                                'type' => 'phrase',
1166
                            ],
1167
                        ],
1168
                        'size' => $limit,
1169
                    ],
1170
                ];
1171
1172
                $results = \Elasticsearch::search($search);
1173
1174
                $searchResult = [];
1175
                foreach ($results['hits']['hits'] as $result) {
1176
                    $searchResult[] = $result['_source']['id'];
1177
                }
1178
                if (empty($searchResult)) {
1179
                    return collect();
1180
                }
1181
            } else {
1182
                $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $name, ['searchname']), 'id');
1183
1184
                if (empty($searchResult)) {
1185
                    return collect();
1186
                }
1187
            }
1188
        }
1189
1190
1191
        $whereSql = sprintf(
1192
            'WHERE r.passwordstatus %s
1193
			AND r.nzbstatus = %d
1194
			%s %s %s %s %s',
1195
            $this->showPasswords(),
1196
            NZB::NZB_ADDED,
1197
            ($aniDbID > -1 ? sprintf(' AND r.anidbid = %d ', $aniDbID) : ''),
1198
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
1199
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : '',
1200
            Category::getCategorySearch($cat),
1201
            ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '')
1202
        );
1203
        $baseSql = sprintf(
1204
            "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,
1205
				CONCAT(cp.title, ' > ', c.title) AS category_name,
1206
				g.name AS group_name,
1207
				rn.releases_id AS nfoid,
1208
				re.releases_id AS reid
1209
			FROM releases r
1210
			LEFT JOIN categories c ON c.id = r.categories_id
1211
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
1212
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
1213
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
1214
			LEFT OUTER JOIN releaseextrafull re ON re.releases_id = r.id
1215
			%s",
1216
            $whereSql
1217
        );
1218
        $sql = sprintf(
1219
            '%s
1220
			ORDER BY postdate DESC
1221
			LIMIT %d OFFSET %d',
1222
            $baseSql,
1223
            $limit,
1224
            $offset
1225
        );
1226
        $releases = Cache::get(md5($sql));
1227
        if ($releases !== null) {
1228
            return $releases;
1229
        }
1230
        $releases = self::fromQuery($sql);
1231
        if ($releases->isNotEmpty()) {
1232
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
1233
        }
1234
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
1235
        Cache::put(md5($sql), $releases, $expiresAt);
1236
1237
        return $releases;
1238
    }
1239
1240
    /**
1241
     * Movies search through API and site.
1242
     *
1243
     *
1244
     * @param int $imDbId
1245
     * @param int $tmDbId
1246
     * @param int $traktId
1247
     * @param int $offset
1248
     * @param int $limit
1249
     * @param string $name
1250
     * @param array $cat
1251
     * @param int $maxAge
1252
     * @param int $minSize
1253
     * @param array $excludedCategories
1254
     * @param array $tags
1255
     * @return Collection|mixed
1256
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
1257
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
1258
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
1259
     */
1260
    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 = [])
1261
    {
1262
        if (! empty($name)) {
1263
            if (config('nntmux.elasticsearch_enabled') === true) {
1264
                $search = [
1265
                    'index' => 'releases',
1266
                    'body' => [
1267
                        'query' => [
1268
                            'multi_match' => [
1269
                                'query' => $name,
1270
                                'fields' => ['searchname'],
1271
                                'type' => 'phrase',
1272
                            ],
1273
                        ],
1274
                        'size' => $limit,
1275
                    ],
1276
                ];
1277
1278
                $results = \Elasticsearch::search($search);
1279
1280
                $searchResult = [];
1281
                foreach ($results['hits']['hits'] as $result) {
1282
                    $searchResult[] = $result['_source']['id'];
1283
                }
1284
                if (empty($searchResult)) {
1285
                    return collect();
1286
                }
1287
            } else {
1288
                $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $name, ['searchname']), 'id');
1289
1290
                if (empty($searchResult)) {
1291
                    return collect();
1292
                }
1293
            }
1294
        }
1295
1296
        $whereSql = sprintf(
1297
            'WHERE r.categories_id BETWEEN '.Category::MOVIE_ROOT.' AND '.Category::MOVIE_OTHER.'
1298
			AND r.nzbstatus = %d
1299
			AND r.passwordstatus %s
1300
			%s %s %s %s %s %s %s %s',
1301
            NZB::NZB_ADDED,
1302
            $this->showPasswords(),
1303
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
1304
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
1305
            ($imDbId !== -1 && is_numeric($imDbId)) ? sprintf(' AND m.imdbid = %d ', $imDbId) : '',
1306
            ($tmDbId !== -1 && is_numeric($tmDbId)) ? sprintf(' AND m.tmdbid = %d ', $tmDbId) : '',
1307
            ($traktId !== -1 && is_numeric($traktId)) ? sprintf(' AND m.traktid = %d ', $traktId) : '',
1308
            ! empty($excludedCategories) ? sprintf('AND r.categories_id NOT IN('.implode(',', $excludedCategories).')') : '',
1309
            Category::getCategorySearch($cat),
1310
            $maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '',
1311
            $minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : ''
1312
        );
1313
        $baseSql = sprintf(
1314
            "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,
1315
				concat(cp.title, ' > ', c.title) AS category_name,
1316
				g.name AS group_name,
1317
				rn.releases_id AS nfoid
1318
			FROM releases r
1319
			LEFT JOIN movieinfo m ON m.id = r.movieinfo_id
1320
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
1321
			LEFT JOIN categories c ON c.id = r.categories_id
1322
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
1323
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
1324
			%s %s",
1325
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
1326
            $whereSql
1327
        );
1328
        $sql = sprintf(
1329
            '%s
1330
			ORDER BY postdate DESC
1331
			LIMIT %d OFFSET %d',
1332
            $baseSql,
1333
            $limit,
1334
            $offset
1335
        );
1336
1337
        $releases = Cache::get(md5($sql));
1338
        if ($releases !== null) {
1339
            return $releases;
1340
        }
1341
        $releases = self::fromQuery($sql);
1342
        if ($releases->isNotEmpty()) {
1343
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
1344
        }
1345
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
1346
        Cache::put(md5($sql), $releases, $expiresAt);
1347
1348
        return $releases;
1349
    }
1350
1351
    /**
1352
     * @param $currentID
1353
     * @param $name
1354
     * @param array $excludedCats
1355
     * @return array|bool
1356
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
1357
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
1358
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
1359
     */
1360
    public function searchSimilar($currentID, $name, array $excludedCats = [])
1361
    {
1362
        // Get the category for the parent of this release.
1363
        $ret = false;
1364
        $currRow = self::getCatByRelId($currentID);
1365
        if ($currRow !== null) {
1366
            $catRow = Category::find($currRow['categories_id']);
1367
            $parentCat = $catRow['root_categories_id'];
1368
1369
            $results = $this->search(['searchname' => getSimilarName($name)], -1, '', '', -1, -1, 0, config('nntmux.items_per_page'), '', -1, $excludedCats, [$parentCat]);
1370
            if (! $results) {
1371
                return $ret;
1372
            }
1373
1374
            $ret = [];
1375
            foreach ($results as $res) {
1376
                if ($res['id'] !== $currentID && $res['categoryparentid'] === $parentCat) {
1377
                    $ret[] = $res;
1378
                }
1379
            }
1380
        }
1381
1382
        return $ret;
1383
    }
1384
1385
    /**
1386
     * Get count of releases for pager.
1387
     *
1388
     *
1389
     * @param string $query The query to get the count from.
1390
     *
1391
     * @return int
1392
     */
1393
    private function getPagerCount($query): int
1394
    {
1395
        $sql = sprintf(
1396
            'SELECT COUNT(z.id) AS count FROM (%s LIMIT %s) z',
1397
            preg_replace('/SELECT.+?FROM\s+releases/is', 'SELECT r.id FROM releases', $query),
1398
            (int) config('nntmux.max_pager_results')
1399
        );
1400
        $count = Cache::get(md5($sql));
1401
        if ($count !== null) {
1402
            return $count;
1403
        }
1404
        $count = self::fromQuery($sql);
1405
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_short'));
1406
        Cache::put(md5($sql), $count[0]->count, $expiresAt);
1407
1408
        return $count[0]->count ?? 0;
1409
    }
1410
}
1411