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

Releases::search()   F

Complexity

Conditions 22
Paths 120

Size

Total Lines 129
Code Lines 103

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 506

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 103
c 3
b 1
f 0
dl 0
loc 129
ccs 0
cts 45
cp 0
rs 3.1999
cc 22
nc 120
nop 15
crap 506

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Blacklight;
4
5
use App\Models\Category;
6
use App\Models\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