Completed
Push — dev ( 9d68d6...d70300 )
by Darko
07:07
created

Releases::tvSearch()   F

Complexity

Conditions 43
Paths 2526

Size

Total Lines 171
Code Lines 124

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 1892

Importance

Changes 19
Bugs 6 Features 0
Metric Value
eloc 124
c 19
b 6
f 0
dl 0
loc 171
ccs 0
cts 66
cp 0
rs 0
cc 43
nc 2526
nop 12
crap 1892

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
				FROM releases r
344
				LEFT JOIN categories c ON c.id = r.categories_id
345
				LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
346
				WHERE %s %s
347
				AND r.nzbstatus = %d
348
				AND r.categories_id BETWEEN %d AND %d
349
				AND r.passwordstatus %s
350
				%s
351
				GROUP BY r.id
352
				ORDER BY %s %s %s",
353
            $this->uSQL($userShows, 'videos_id'),
354
            (! empty ($excludedCats) ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
355
            NZB::NZB_ADDED,
356
            Category::TV_ROOT,
357
            Category::TV_OTHER,
358
            $this->showPasswords(),
359
            ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : ''),
360
            $orderBy[0],
361
            $orderBy[1],
362
            ($offset === false ? '' : (' LIMIT '.$limit.' OFFSET '.$offset))
363
        );
364
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_long'));
365
        $result = Cache::get(md5($sql));
366
        if ($result !== null) {
367
            return $result;
368
        }
369
        $result = self::fromQuery($sql);
370
        Cache::put(md5($sql), $result, $expiresAt);
371
        return $result;
372
    }
373
374
    /**
375
     * Get count for my shows page pagination.
376
     *
377
     * @param       $userShows
378
     * @param int   $maxAge
379
     * @param array $excludedCats
380
     *
381
     * @return int
382
     */
383
    public function getShowsCount($userShows, $maxAge = -1, array $excludedCats = []): int
384
    {
385
        return $this->getPagerCount(
386
            sprintf(
387
                'SELECT r.id
388
				FROM releases r
389
				WHERE %s %s
390
				AND r.nzbstatus = %d
391
				AND r.categories_id BETWEEN %d AND %d
392
				AND r.passwordstatus %s
393
				%s',
394
                $this->uSQL($userShows, 'videos_id'),
395
                (\count($excludedCats) ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
396
                NZB::NZB_ADDED,
397
                Category::TV_ROOT,
398
                Category::TV_OTHER,
399
                $this->showPasswords(),
400
                ($maxAge > 0 ? sprintf(' AND r.postdate > NOW() - INTERVAL %d DAY ', $maxAge) : '')
401
            )
402
        );
403
    }
404
405
    /**
406
     * Delete multiple releases, or a single by ID.
407
     *
408
     * @param array|int|string $list   Array of GUID or ID of releases to delete.
409
     * @throws \Exception
410
     */
411
    public function deleteMultiple($list): void
412
    {
413
        $list = (array) $list;
414
415
        $nzb = new NZB();
416
        $releaseImage = new ReleaseImage();
417
418
        foreach ($list as $identifier) {
419
            $this->deleteSingle(['g' => $identifier, 'i' => false], $nzb, $releaseImage);
420
        }
421
    }
422
423
    /**
424
     * Deletes a single release by GUID, and all the corresponding files.
425
     *
426
     * @param array                    $identifiers ['g' => Release GUID(mandatory), 'id => ReleaseID(optional, pass
427
     *                                              false)]
428
     * @param \Blacklight\NZB          $nzb
429
     * @param \Blacklight\ReleaseImage $releaseImage
430
     *
431
     * @throws \Exception
432
     */
433
    public function deleteSingle($identifiers, NZB $nzb, ReleaseImage $releaseImage): void
434
    {
435
        // Delete NZB from disk.
436
        $nzbPath = $nzb->NZBPath($identifiers['g']);
437
        if (! empty($nzbPath)) {
438
            File::delete($nzbPath);
439
        }
440
441
        // Delete images.
442
        $releaseImage->delete($identifiers['g']);
443
444
        if (config('nntmux.elasticsearch_enabled') === true) {
445
            if ($identifiers['i'] === false) {
446
                $identifiers['i'] = Release::query()->where('guid', $identifiers['g'])->first(['id']);
447
                if ($identifiers['i'] !== null) {
448
                    $identifiers['i'] = $identifiers['i']['id'];
449
                }
450
            }
451
            if ($identifiers['i'] !== false) {
452
                $params = [
453
                    'index' => 'releases',
454
                    'id' => $identifiers['i'],
455
                ];
456
457
                \Elasticsearch::delete($params);
458
            }
459
        } else {
460
            // Delete from sphinx.
461
            $this->sphinxSearch->deleteRelease($identifiers);
462
        }
463
464
        // Delete from DB.
465
        self::whereGuid($identifiers['g'])->delete();
466
    }
467
468
    /**
469
     * @param $guids
470
     * @param $category
471
     * @param $grabs
472
     * @param $videoId
473
     * @param $episodeId
474
     * @param $anidbId
475
     * @param $imdbId
476
     * @return bool|int
477
     */
478
    public function updateMulti($guids, $category, $grabs, $videoId, $episodeId, $anidbId, $imdbId)
479
    {
480
        if (! \is_array($guids) || \count($guids) < 1) {
481
            return false;
482
        }
483
484
        $update = [
485
            'categories_id'     => $category === -1 ? 'categories_id' : $category,
486
            'grabs'          => $grabs,
487
            'videos_id'      => $videoId,
488
            'tv_episodes_id' => $episodeId,
489
            'anidbid'        => $anidbId,
490
            'imdbid'         => $imdbId,
491
        ];
492
493
        return self::query()->whereIn('guid', $guids)->update($update);
494
    }
495
496
    /**
497
     * Creates part of a query for some functions.
498
     *
499
     * @param array|Collection  $userQuery
500
     * @param string $type
501
     *
502
     * @return string
503
     */
504
    public function uSQL($userQuery, $type): string
505
    {
506
        $sql = '(1=2 ';
507
        foreach ($userQuery as $query) {
508
            $sql .= sprintf('OR (r.%s = %d', $type, $query->$type);
509
            if (! empty($query->categories)) {
510
                $catsArr = explode('|', $query->categories);
511
                if (\count($catsArr) > 1) {
512
                    $sql .= sprintf(' AND r.categories_id IN (%s)', implode(',', $catsArr));
513
                } else {
514
                    $sql .= sprintf(' AND r.categories_id = %d', $catsArr[0]);
515
                }
516
            }
517
            $sql .= ') ';
518
        }
519
        $sql .= ') ';
520
521
        return $sql;
522
    }
523
524
    /**
525
     * Function for searching on the site (by subject, searchname or advanced).
526
     *
527
     *
528
     * @param array $searchArr
529
     * @param              $groupName
530
     * @param              $sizeFrom
531
     * @param              $sizeTo
532
     * @param              $daysNew
533
     * @param              $daysOld
534
     * @param int $offset
535
     * @param int $limit
536
     * @param string|array $orderBy
537
     * @param int $maxAge
538
     * @param array $excludedCats
539
     * @param string $type
540
     * @param array $cat
541
     * @param int $minSize
542
     * @param array $tags
543
     *
544
     * @return array|Collection|mixed
545
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
546
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
547
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
548
     */
549
    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 = [])
550
    {
551
        $sizeRange = [
552
            1 => 1,
553
            2 => 2.5,
554
            3 => 5,
555
            4 => 10,
556
            5 => 20,
557
            6 => 30,
558
            7 => 40,
559
            8 => 80,
560
            9 => 160,
561
            10 => 320,
562
            11 => 640,
563
        ];
564
        if ($orderBy === '') {
565
            $orderBy = [];
566
            $orderBy[0] = 'postdate ';
567
            $orderBy[1] = 'desc ';
568
        } else {
569
            $orderBy = $this->getBrowseOrder($orderBy);
570
        }
571
572
        $searchFields = Arr::where($searchArr, function ($value) {
573
            return $value !== -1;
574
        });
575
576
        $phrases = [];
577
        foreach ($searchFields as $key => $value) {
578
            $phrases[] = $value;
579
        }
580
581
        if (config('nntmux.elasticsearch_enabled') === true) {
582
            $search = [
583
                'scroll' => '30s',
584
                'index' => 'releases',
585
                'body' => [
586
                    'query' => [
587
                        'query_string' => [
588
                            'query' => implode(' ', $phrases),
589
                            'fields' => ['searchname', 'plainsearchname', 'fromname', 'filename', 'name'],
590
                            'analyze_wildcard' => true,
591
                            'default_operator' => 'and',
592
                        ],
593
                    ],
594
                    'size' => $limit,
595
                    'sort' => [
596
                        'add_date' => [
597
                            'order' =>'desc',
598
                        ],
599
                        'post_date' => [
600
                            'order' => 'desc',
601
                        ],
602
                    ],
603
                ],
604
            ];
605
606
            $results = \Elasticsearch::search($search);
607
608
            $searchResult = [];
609
            while (isset($results['hits']['hits']) && count($results['hits']['hits']) > 0) {
610
                foreach ($results['hits']['hits'] as $result) {
611
                    $searchResult[] = $result['_source']['id'];
612
                }
613
                if (empty($searchResult)) {
614
                    return collect();
615
                }
616
617
                // When done, get the new scroll_id
618
                // You must always refresh your _scroll_id!  It can change sometimes
619
                $scroll_id = $results['_scroll_id'];
620
621
                // Execute a Scroll request and repeat
622
                $results = \Elasticsearch::scroll([
623
                    'scroll_id' => $scroll_id,  //...using our previously obtained _scroll_id
624
                    'scroll'    => '30s',        // and the same timeout window
625
                ]
626
                );
627
            }
628
        } else {
629
            $results = $this->sphinxSearch->searchIndexes('releases_rt', '', [], $searchFields);
630
631
            $searchResult = Arr::pluck($results, 'id');
632
        }
633
634
        $catQuery = '';
635
        if ($type === 'basic') {
636
            $catQuery = Category::getCategorySearch($cat);
637
        } elseif ($type === 'advanced' && (int) $cat[0] !== -1) {
638
            $catQuery = sprintf('AND r.categories_id = %d', $cat[0]);
639
        }
640
        $whereSql = sprintf(
641
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s %s %s %s %s',
642
            $this->showPasswords(),
643
            NZB::NZB_ADDED,
644
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
645
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
646
            ((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

646
            ((int) $groupName !== -1 ? sprintf(' AND r.groups_id = %d ', /** @scrutinizer ignore-type */ UsenetGroup::getIDByName($groupName)) : ''),
Loading history...
647
            (array_key_exists($sizeFrom, $sizeRange) ? ' AND r.size > '.(104857600 * (int) $sizeRange[$sizeFrom]).' ' : ''),
648
            (array_key_exists($sizeTo, $sizeRange) ? ' AND r.size < '.(104857600 * (int) $sizeRange[$sizeTo]).' ' : ''),
649
            $catQuery,
650
            ((int) $daysNew !== -1 ? sprintf(' AND r.postdate < (NOW() - INTERVAL %d DAY) ', $daysNew) : ''),
651
            ((int) $daysOld !== -1 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $daysOld) : ''),
652
            (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
653
            (! empty($searchResult) ? 'AND r.id IN ('.implode(',', $searchResult).')' : ''),
654
            ($minSize > 0 ? sprintf('AND r.size >= %d', $minSize) : '')
655
        );
656
        $baseSql = sprintf(
657
            "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,
658
				CONCAT(cp.title, ' > ', c.title) AS category_name,
659
				df.failed AS failed,
660
				g.name AS group_name,
661
				rn.releases_id AS nfoid,
662
				re.releases_id AS reid,
663
				cp.id AS categoryparentid,
664
				v.tvdb, v.trakt, v.tvrage, v.tvmaze, v.imdb, v.tmdb,
665
				tve.firstaired
666
			FROM releases r
667
			LEFT OUTER JOIN video_data re ON re.releases_id = r.id
668
			LEFT OUTER JOIN videos v ON r.videos_id = v.id
669
			LEFT OUTER JOIN tv_episodes tve ON r.tv_episodes_id = tve.id
670
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
671
			LEFT JOIN usenet_groups g ON g.id = r.groups_id
672
			LEFT JOIN categories c ON c.id = r.categories_id
673
			LEFT JOIN root_categories cp ON cp.id = c.root_categories_id
674
			LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
675
			%s %s",
676
            ! empty($tags) ? ' LEFT JOIN tagging_tagged tt ON tt.taggable_id = r.id' : '',
677
            $whereSql
678
        );
679
        $sql = sprintf(
680
            'SELECT * FROM (
681
				%s
682
			) r
683
			ORDER BY r.%s %s
684
			LIMIT %d OFFSET %d',
685
            $baseSql,
686
            $orderBy[0],
687
            $orderBy[1],
688
            $limit,
689
            $offset
690
        );
691
        $releases = Cache::get(md5($sql));
692
        if ($releases !== null) {
693
            return $releases;
694
        }
695
        $releases = ! empty($searchResult) ? self::fromQuery($sql) : collect();
696
        if ($releases->isNotEmpty()) {
697
            $releases[0]->_totalrows = $this->getPagerCount($baseSql);
698
        }
699
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
700
        Cache::put(md5($sql), $releases, $expiresAt);
701
702
        return $releases;
703
    }
704
705
    /**
706
     * Search function for API.
707
     *
708
     *
709
     * @param       $searchName
710
     * @param       $groupName
711
     * @param int $offset
712
     * @param int $limit
713
     * @param int $maxAge
714
     * @param array $excludedCats
715
     * @param array $cat
716
     * @param int $minSize
717
     * @param array $tags
718
     *
719
     * @return Collection|mixed
720
     * @throws \Foolz\SphinxQL\Exception\ConnectionException
721
     * @throws \Foolz\SphinxQL\Exception\DatabaseException
722
     * @throws \Foolz\SphinxQL\Exception\SphinxQLException
723
     */
724
    public function apiSearch($searchName, $groupName, $offset = 0, $limit = 1000, $maxAge = -1, array $excludedCats = [], array $cat = [-1], $minSize = 0, array $tags = [])
725
    {
726
        if ($searchName !== -1) {
727
            if (config('nntmux.elasticsearch_enabled') === true) {
728
                $search = [
729
                    'scroll' => '30s',
730
                    'index' => 'releases',
731
                    'body' => [
732
                        'query' => [
733
                            'query_string' => [
734
                                'query' => $searchName,
735
                                'fields' => ['searchname', 'plainsearchname'],
736
                                'analyze_wildcard' => true,
737
                                'default_operator' => 'and',
738
                            ],
739
                        ],
740
                        'size' => $limit,
741
                        'sort' => [
742
                            'add_date' => [
743
                                'order' =>'desc',
744
                            ],
745
                            'post_date' => [
746
                                'order' => 'desc',
747
                            ],
748
                        ],
749
                    ],
750
                ];
751
752
                $results = \Elasticsearch::search($search);
753
754
                $searchResult = [];
755
                while (isset($results['hits']['hits']) && count($results['hits']['hits']) > 0) {
756
                    foreach ($results['hits']['hits'] as $result) {
757
                        $searchResult[] = $result['_source']['id'];
758
                    }
759
760
                    // When done, get the new scroll_id
761
                    // You must always refresh your _scroll_id!  It can change sometimes
762
                    $scroll_id = $results['_scroll_id'];
763
764
                    // Execute a Scroll request and repeat
765
                    $results = \Elasticsearch::scroll([
766
                        'scroll_id' => $scroll_id,  //...using our previously obtained _scroll_id
767
                        'scroll'    => '30s',        // and the same timeout window
768
                    ]
769
                    );
770
                }
771
            } else {
772
                $searchResult = Arr::pluck($this->sphinxSearch->searchIndexes('releases_rt', $searchName, ['searchname']), 'id');
773
            }
774
        }
775
776
        $catQuery = Category::getCategorySearch($cat);
777
778
        $whereSql = sprintf(
779
            'WHERE r.passwordstatus %s AND r.nzbstatus = %d %s %s %s %s %s %s %s',
780
            $this->showPasswords(),
781
            NZB::NZB_ADDED,
782
            ! empty($tags) ? " AND tt.tag_name IN ('".implode("','", $tags)."')" : '',
783
            ($maxAge > 0 ? sprintf(' AND r.postdate > (NOW() - INTERVAL %d DAY) ', $maxAge) : ''),
784
            ((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

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