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

Releases::getShowsRange()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 35
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 31
c 2
b 1
f 0
dl 0
loc 35
ccs 0
cts 13
cp 0
rs 9.1128
cc 5
nc 2
nop 6
crap 30
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