Passed
Push — master ( 34c1d4...643411 )
by Darko
12:34 queued 05:21
created

Movie::getMovieRange()   D

Complexity

Conditions 17
Paths 24

Size

Total Lines 101
Code Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 11
Bugs 6 Features 0
Metric Value
eloc 87
c 11
b 6
f 0
dl 0
loc 101
rs 4.4569
cc 17
nc 24
nop 7

How to fix   Long Method    Complexity   

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:

1
<?php
2
3
namespace Blacklight;
4
5
use aharen\OMDbAPI;
6
use App\Models\Category;
7
use App\Models\MovieInfo;
8
use App\Models\Release;
9
use App\Models\Settings;
10
use Blacklight\libraries\FanartTV;
11
use Blacklight\processing\tv\TraktTv;
12
use Blacklight\utility\Utility;
13
use DariusIII\ItunesApi\Exceptions\InvalidProviderException;
14
use DariusIII\ItunesApi\Exceptions\MovieNotFoundException;
15
use DariusIII\ItunesApi\Exceptions\SearchNoResultsException;
16
use DariusIII\ItunesApi\iTunes;
17
use GuzzleHttp\Client;
18
use GuzzleHttp\Exception\GuzzleException;
19
use Illuminate\Database\Eloquent\Builder;
20
use Illuminate\Database\Eloquent\Model;
21
use Illuminate\Support\Arr;
22
use Illuminate\Support\Carbon;
23
use Illuminate\Support\Facades\Cache;
24
use Illuminate\Support\Facades\DB;
25
use Illuminate\Support\Facades\File;
26
use Illuminate\Support\Facades\Log;
27
use Illuminate\Support\Str;
28
use Imdb\Config;
29
use Imdb\Title;
30
use Imdb\TitleSearch;
31
use Tmdb\Exception\TmdbApiException;
32
use Tmdb\Laravel\Facades\Tmdb;
33
34
/**
35
 * Class Movie.
36
 */
37
class Movie
38
{
39
    /**
40
     * @var int
41
     */
42
    protected const MATCH_PERCENT = 75;
43
44
    /**
45
     * @var int
46
     */
47
    protected const YEAR_MATCH_PERCENT = 80;
48
49
    /**
50
     * Current title being passed through various sites/api's.
51
     */
52
    protected string $currentTitle = '';
53
54
    /**
55
     * Current year of parsed search name.
56
     */
57
    protected string $currentYear = '';
58
59
    /**
60
     * Current release id of parsed search name.
61
     */
62
    protected string $currentRelID = '';
63
64
    protected string $showPasswords;
65
66
    protected ReleaseImage $releaseImage;
67
68
    protected Client $client;
69
70
    /**
71
     * Language to fetch from IMDB.
72
     */
73
    protected string $lookuplanguage;
74
75
    public FanartTV $fanart;
76
77
    /**
78
     * @var null|string
79
     */
80
    public mixed $fanartapikey;
81
82
    /**
83
     * @var null|string
84
     */
85
    public mixed $omdbapikey;
86
87
    /**
88
     * @var bool
89
     */
90
    public $imdburl;
91
92
    public int $movieqty;
93
94
    public bool $echooutput;
95
96
    public string $imgSavePath;
97
98
    public string $service;
99
100
    public TraktTv $traktTv;
101
102
    public ?OMDbAPI $omdbApi;
103
104
    private Config $config;
105
106
    /**
107
     * @var null|string
108
     */
109
    protected mixed $traktcheck;
110
111
    protected ColorCLI $colorCli;
112
113
    /**
114
     * @throws \Exception
115
     */
116
    public function __construct()
117
    {
118
119
        $this->releaseImage = new ReleaseImage;
120
        $this->colorCli = new ColorCLI;
121
        $this->traktcheck = config('nntmux_api.trakttv_api_key');
122
        if ($this->traktcheck !== null) {
123
            $this->traktTv = new TraktTv(['Settings' => null]);
0 ignored issues
show
Unused Code introduced by
The call to Blacklight\processing\tv\TraktTv::__construct() has too many arguments starting with array('Settings' => null). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

123
            $this->traktTv = /** @scrutinizer ignore-call */ new TraktTv(['Settings' => null]);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
124
        }
125
        $this->client = new Client;
126
        $this->fanartapikey = config('nntmux_api.fanarttv_api_key');
127
        if ($this->fanartapikey !== null) {
128
            $this->fanart = new FanartTV($this->fanartapikey);
129
        }
130
        $this->omdbapikey = config('nntmux_api.omdb_api_key');
131
        if ($this->omdbapikey !== null) {
132
            $this->omdbApi = new OMDbAPI($this->omdbapikey);
133
        }
134
135
        $this->lookuplanguage = Settings::settingValue('indexer.categorise.imdblanguage') !== '' ? (string) Settings::settingValue('indexer.categorise.imdblanguage') : 'en';
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...e.imdblanguage') !== '' is always true.
Loading history...
136
        $this->config = new Config;
137
        $this->config->language = $this->lookuplanguage;
138
        $this->config->throwHttpExceptions = false;
139
        $cacheDir = storage_path('framework/cache/imdb_cache');
140
        if (! File::isDirectory($cacheDir)) {
141
            File::makeDirectory($cacheDir, 0777, false, true);
142
        }
143
        $this->config->cachedir = $cacheDir;
144
145
        $this->imdburl = (int) Settings::settingValue('indexer.categorise.imdburl') !== 0;
146
        $this->movieqty = Settings::settingValue('..maximdbprocessed') !== '' ? (int) Settings::settingValue('..maximdbprocessed') : 100;
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...ximdbprocessed') !== '' is always true.
Loading history...
147
        $this->showPasswords = (new Releases)->showPasswords();
148
149
        $this->echooutput = config('nntmux.echocli');
150
        $this->imgSavePath = storage_path('covers/movies/');
151
        $this->service = '';
152
    }
153
154
    /**
155
     * @return Builder|Model|null|object
156
     */
157
    public function getMovieInfo($imdbId)
158
    {
159
        return MovieInfo::query()->where('imdbid', $imdbId)->first();
160
    }
161
162
    /**
163
     * Get movie releases with covers for movie browse page.
164
     *
165
     *
166
     * @return array|mixed
167
     */
168
    public function getMovieRange($page, $cat, $start, $num, $orderBy, int $maxAge = -1, array $excludedCats = [])
169
    {
170
        $catsrch = '';
171
        if (\count($cat) > 0 && $cat[0] !== -1) {
172
            $catsrch = Category::getCategorySearch($cat);
173
        }
174
        $order = $this->getMovieOrder($orderBy);
175
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
176
        $moviesSql =
177
            sprintf(
178
                "
179
					SELECT SQL_CALC_FOUND_ROWS
180
						m.imdbid,
181
						GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id
182
					FROM movieinfo m
183
					LEFT JOIN releases r USING (imdbid)
184
					WHERE r.nzbstatus = 1
185
					AND m.title != ''
186
					AND m.imdbid != '0000000'
187
					AND r.passwordstatus %s
188
					%s %s %s %s
189
					GROUP BY m.imdbid
190
					ORDER BY %s %s %s",
191
                $this->showPasswords,
192
                $this->getBrowseBy(),
193
                (! empty($catsrch) ? $catsrch : ''),
194
                (
195
                    $maxAge > 0
196
                        ? 'AND r.postdate > NOW() - INTERVAL '.$maxAge.'DAY '
197
                        : ''
198
                ),
199
                \count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : '',
200
                $order[0],
201
                $order[1],
202
                $start === false ? '' : ' LIMIT '.$num.' OFFSET '.$start
203
            );
204
        $movieCache = Cache::get(md5($moviesSql.$page));
205
        if ($movieCache !== null) {
206
            $movies = $movieCache;
207
        } else {
208
            $data = MovieInfo::fromQuery($moviesSql);
209
            $movies = ['total' => DB::select('SELECT FOUND_ROWS() AS total'), 'result' => $data];
210
            Cache::put(md5($moviesSql.$page), $movies, $expiresAt);
211
        }
212
        $movieIDs = $releaseIDs = [];
213
        if (! empty($movies['result'])) {
214
            foreach ($movies['result'] as $movie => $id) {
215
                $movieIDs[] = $id->imdbid;
216
                $releaseIDs[] = $id->grp_release_id;
217
            }
218
        }
219
220
        $sql = sprintf(
221
            "
222
			SELECT
223
				GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id,
224
				GROUP_CONCAT(r.rarinnerfilecount ORDER BY r.postdate DESC SEPARATOR ',') AS grp_rarinnerfilecount,
225
				GROUP_CONCAT(r.haspreview ORDER BY r.postdate DESC SEPARATOR ',') AS grp_haspreview,
226
				GROUP_CONCAT(r.passwordstatus ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_password,
227
				GROUP_CONCAT(r.guid ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_guid,
228
				GROUP_CONCAT(rn.releases_id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_nfoid,
229
				GROUP_CONCAT(g.name ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grpname,
230
				GROUP_CONCAT(r.searchname ORDER BY r.postdate DESC SEPARATOR '#') AS grp_release_name,
231
				GROUP_CONCAT(r.postdate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_postdate,
232
				GROUP_CONCAT(r.size ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_size,
233
				GROUP_CONCAT(r.totalpart ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_totalparts,
234
				GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_comments,
235
				GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grabs,
236
				GROUP_CONCAT(df.failed ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_failed,
237
				GROUP_CONCAT(cp.title, ' > ', c.title ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_catname,
238
			m.*,
239
			g.name AS group_name,
240
			rn.releases_id AS nfoid
241
			FROM releases r
242
			LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id
243
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
244
			LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
245
			LEFT OUTER JOIN categories c ON c.id = r.categories_id
246
			LEFT OUTER JOIN root_categories cp ON cp.id = c.root_categories_id
247
			INNER JOIN movieinfo m ON m.imdbid = r.imdbid
248
			WHERE m.imdbid IN (%s)
249
			AND r.id IN (%s) %s
250
			GROUP BY m.imdbid
251
			ORDER BY %s %s",
252
            (\is_array($movieIDs) && ! empty($movieIDs) ? implode(',', $movieIDs) : -1),
253
            (\is_array($releaseIDs) && ! empty($releaseIDs) ? implode(',', $releaseIDs) : -1),
254
            (! empty($catsrch) ? $catsrch : ''),
255
            $order[0],
256
            $order[1]
257
        );
258
        $return = Cache::get(md5($sql.$page));
259
        if ($return !== null) {
260
            return $return;
261
        }
262
        $return = Release::fromQuery($sql);
263
        if (\count($return) > 0) {
0 ignored issues
show
Bug introduced by
It seems like $return can also be of type Illuminate\Database\Eloq...gHasThroughRelationship; however, parameter $value of count() does only seem to accept Countable|array, 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

263
        if (\count(/** @scrutinizer ignore-type */ $return) > 0) {
Loading history...
264
            $return[0]->_totalcount = $movies['total'][0]->total ?? 0;
265
        }
266
        Cache::put(md5($sql.$page), $return, $expiresAt);
267
268
        return $return;
269
    }
270
271
    /**
272
     * Get the order type the user requested on the movies page.
273
     */
274
    protected function getMovieOrder($orderBy): array
275
    {
276
        $orderArr = explode('_', (($orderBy === '') ? 'MAX(r.postdate)' : $orderBy));
277
        $orderField = match ($orderArr[0]) {
278
            'title' => 'm.title',
279
            'year' => 'm.year',
280
            'rating' => 'm.rating',
281
            default => 'MAX(r.postdate)',
282
        };
283
284
        return [$orderField, isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1]) ? $orderArr[1] : 'desc'];
285
    }
286
287
    /**
288
     * Order types for movies page.
289
     */
290
    public function getMovieOrdering(): array
291
    {
292
        return ['title_asc', 'title_desc', 'year_asc', 'year_desc', 'rating_asc', 'rating_desc'];
293
    }
294
295
    protected function getBrowseBy(): string
296
    {
297
        $browseBy = ' ';
298
        $browseByArr = ['title', 'director', 'actors', 'genre', 'rating', 'year', 'imdb'];
299
        foreach ($browseByArr as $bb) {
300
            if (request()->has($bb) && ! empty(request()->input($bb))) {
301
                $bbv = stripslashes(request()->input($bb));
302
                if ($bb === 'rating') {
303
                    $bbv .= '.';
304
                }
305
                if ($bb === 'imdb') {
306
                    $browseBy .= sprintf(' AND m.imdbid = %d', $bbv);
307
                } else {
308
                    $browseBy .= ' AND m.'.$bb.' '.'LIKE '.escapeString('%'.$bbv.'%');
309
                }
310
            }
311
        }
312
313
        return $browseBy;
314
    }
315
316
    /**
317
     * Get trailer using IMDB Id.
318
     *
319
     * @return bool|string
320
     *
321
     * @throws \Exception
322
     * @throws GuzzleException
323
     */
324
    public function getTrailer(int $imdbId)
325
    {
326
        $trailer = MovieInfo::query()->where('imdbid', $imdbId)->where('trailer', '<>', '')->first(['trailer']);
327
        if ($trailer !== null) {
328
            return $trailer['trailer'];
329
        }
330
331
        if ($this->traktcheck !== null) {
332
            $data = $this->traktTv->client->movieSummary('tt'.$imdbId, 'full');
333
            if (($data !== false) && ! empty($data['trailer'])) {
334
                return $data['trailer'];
335
            }
336
        }
337
338
        $trailer = Utility::imdb_trailers($imdbId);
339
        if ($trailer) {
340
            MovieInfo::query()->where('imdbid', $imdbId)->update(['trailer' => $trailer]);
341
342
            return $trailer;
343
        }
344
345
        return false;
346
    }
347
348
    /**
349
     * Parse trakt info, insert into DB.
350
     *
351
     * @return mixed
352
     */
353
    public function parseTraktTv(array &$data)
354
    {
355
        if (empty($data['ids']['imdb'])) {
356
            return false;
357
        }
358
359
        if (! empty($data['trailer'])) {
360
            $data['trailer'] = str_ireplace(
361
                ['watch?v=', 'http://'],
362
                ['embed/', 'https://'],
363
                $data['trailer']
364
            );
365
        }
366
        $imdbId = (str_starts_with($data['ids']['imdb'], 'tt')) ? substr($data['ids']['imdb'], 2) : $data['ids']['imdb'];
367
        $cover = 0;
368
        if (File::isFile($this->imgSavePath.$imdbId).'-cover.jpg') {
369
            $cover = 1;
370
        }
371
372
        return $this->update([
373
            'genre' => implode(', ', $data['genres']),
374
            'imdbid' => $this->checkTraktValue($imdbId),
375
            'language' => $this->checkTraktValue($data['language']),
376
            'plot' => $this->checkTraktValue($data['overview']),
377
            'rating' => $this->checkTraktValue($data['rating']),
378
            'tagline' => $this->checkTraktValue($data['tagline']),
379
            'title' => $this->checkTraktValue($data['title']),
380
            'tmdbid' => $this->checkTraktValue($data['ids']['tmdb']),
381
            'traktid' => $this->checkTraktValue($data['ids']['trakt']),
382
            'trailer' => $this->checkTraktValue($data['trailer']),
383
            'cover' => $cover,
384
            'year' => $this->checkTraktValue($data['year']),
385
        ]);
386
    }
387
388
    /**
389
     * @return mixed|string
390
     */
391
    private function checkTraktValue($value)
392
    {
393
        if (\is_array($value) && ! empty($value)) {
394
            $temp = '';
395
            foreach ($value as $val) {
396
                if (! \is_array($val) && ! \is_object($val)) {
397
                    $temp .= $val;
398
                }
399
            }
400
            $value = $temp;
401
        }
402
403
        return ! empty($value) ? $value : '';
404
    }
405
406
    /**
407
     * Get array of column keys, for inserting / updating.
408
     */
409
    public function getColumnKeys(): array
410
    {
411
        return [
412
            'actors', 'backdrop', 'cover', 'director', 'genre', 'imdbid', 'language',
413
            'plot', 'rating', 'rtrating', 'tagline', 'title', 'tmdbid', 'traktid', 'trailer', 'type', 'year',
414
        ];
415
    }
416
417
    /**
418
     * Update movie on movie-edit page.
419
     *
420
     * @param  array  $values  Array of keys/values to update. See $validKeys
421
     */
422
    public function update(array $values): bool
423
    {
424
        if (! \count($values)) {
425
            return false;
426
        }
427
428
        $query = [];
429
        $onDuplicateKey = ['created_at' => now()];
430
        $found = 0;
431
        foreach ($values as $key => $value) {
432
            if (! empty($value)) {
433
                $found++;
434
                if (\in_array($key, ['genre', 'language'], false)) {
435
                    $value = substr($value, 0, 64);
436
                }
437
                $query += [$key => $value];
438
                $onDuplicateKey += [$key => $value];
439
            }
440
        }
441
        if (! $found) {
442
            return false;
443
        }
444
        foreach ($query as $key => $value) {
445
            $query[$key] = rtrim($value, ', ');
446
        }
447
448
        MovieInfo::upsert($query, ['imdbid'], $onDuplicateKey);
449
450
        return true;
451
    }
452
453
    /**
454
     * @return array|string
455
     */
456
    protected function setVariables(string|array $variable1, string|array $variable2, string|array $variable3, string|array $variable4, string|array $variable5)
457
    {
458
        if (! empty($variable1)) {
459
            return $variable1;
460
        }
461
        if (! empty($variable2)) {
462
            return $variable2;
463
        }
464
        if (! empty($variable3)) {
465
            return $variable3;
466
        }
467
        if (! empty($variable4)) {
468
            return $variable4;
469
        }
470
        if (! empty($variable5)) {
471
            return $variable5;
472
        }
473
474
        return '';
475
    }
476
477
    /**
478
     * Fetch IMDB/TMDB/TRAKT/OMDB/iTunes info for the movie.
479
     *
480
     *
481
     * @throws \Exception
482
     */
483
    public function updateMovieInfo($imdbId): bool
484
    {
485
        if ($this->echooutput && $this->service !== '') {
486
            $this->colorCli->primary('Fetching IMDB info from TMDB/IMDB/Trakt/OMDB/iTunes using IMDB id: '.$imdbId);
487
        }
488
489
        // Check TMDB for IMDB info.
490
        $tmdb = $this->fetchTMDBProperties($imdbId);
491
492
        // Check IMDB for movie info.
493
        $imdb = $this->fetchIMDBProperties($imdbId);
494
495
        // Check TRAKT for movie info
496
        $trakt = $this->fetchTraktTVProperties($imdbId);
497
498
        // Check OMDb for movie info
499
        $omdb = $this->fetchOmdbAPIProperties($imdbId);
500
501
        // Check iTunes for movie info as last resort (iTunes do not provide all the info we need)
502
503
        $iTunes = $this->fetchItunesMovieProperties($this->currentTitle);
504
505
        if (! $imdb && ! $tmdb && ! $trakt && ! $omdb && empty($iTunes)) {
506
            return false;
507
        }
508
509
        // Check FanArt.tv for cover and background images.
510
        $fanart = $this->fetchFanartTVProperties($imdbId);
511
512
        $mov = [];
513
514
        $mov['cover'] = $mov['backdrop'] = $mov['banner'] = 0;
515
        $mov['type'] = $mov['director'] = $mov['actors'] = $mov['language'] = '';
516
517
        $mov['imdbid'] = $imdbId;
518
        $mov['tmdbid'] = (! isset($tmdb['tmdbid']) || $tmdb['tmdbid'] === '') ? 0 : $tmdb['tmdbid'];
519
        $mov['traktid'] = (! isset($trakt['id']) || $trakt['id'] === '') ? 0 : $trakt['id'];
520
521
        // Prefer Fanart.tv cover over TMDB,TMDB over IMDB,IMDB over OMDB and OMDB over iTunes.
522
        if (! empty($fanart['cover'])) {
523
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $fanart['cover'], $this->imgSavePath);
524
        } elseif (! empty($tmdb['cover'])) {
525
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $tmdb['cover'], $this->imgSavePath);
526
        } elseif (! empty($imdb['cover'])) {
527
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $imdb['cover'], $this->imgSavePath);
0 ignored issues
show
Bug introduced by
It seems like $imdb['cover'] can also be of type false; however, parameter $imgLoc of Blacklight\ReleaseImage::saveImage() 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

527
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', /** @scrutinizer ignore-type */ $imdb['cover'], $this->imgSavePath);
Loading history...
528
        } elseif (! empty($omdb['cover'])) {
529
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $omdb['cover'], $this->imgSavePath);
530
        } elseif (! empty($iTunes['cover'])) {
531
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $iTunes['cover'], $this->imgSavePath);
532
        }
533
534
        // Backdrops.
535
        if (! empty($fanart['backdrop'])) {
536
            $mov['backdrop'] = $this->releaseImage->saveImage($imdbId.'-backdrop', $fanart['backdrop'], $this->imgSavePath, 1920, 1024);
537
        } elseif (! empty($tmdb['backdrop'])) {
538
            $mov['backdrop'] = $this->releaseImage->saveImage($imdbId.'-backdrop', $tmdb['backdrop'], $this->imgSavePath, 1920, 1024);
539
        }
540
541
        // Banner
542
        if (! empty($fanart['banner'])) {
543
            $mov['banner'] = $this->releaseImage->saveImage($imdbId.'-banner', $fanart['banner'], $this->imgSavePath);
544
        }
545
546
        // RottenTomatoes rating from OmdbAPI
547
        if ($omdb !== false && ! empty($omdb['rtRating'])) {
548
            $mov['rtrating'] = $omdb['rtRating'];
549
        }
550
551
        $mov['title'] = $this->setVariables($imdb['title'] ?? '', $tmdb['title'] ?? '', $trakt['title'] ?? '', $omdb['title'] ?? '', $iTunes['title'] ?? '');
552
        $mov['rating'] = $this->setVariables($imdb['rating'] ?? '', $tmdb['rating'] ?? '', $trakt['rating'] ?? '', $omdb['rating'] ?? '', $iTunes['rating'] ?? '');
553
        $mov['plot'] = $this->setVariables($imdb['plot'] ?? '', $tmdb['plot'] ?? '', $trakt['overview'] ?? '', $omdb['plot'] ?? '', $iTunes['plot'] ?? '');
554
        $mov['tagline'] = $this->setVariables($imdb['tagline'] ?? '', $tmdb['tagline'] ?? '', $trakt['tagline'] ?? '', $omdb['tagline'] ?? '', $iTunes['tagline'] ?? '');
555
        $mov['year'] = $this->setVariables($imdb['year'] ?? '', $tmdb['year'] ?? '', $trakt['year'] ?? '', $omdb['year'] ?? '', $iTunes['year'] ?? '');
556
        $mov['genre'] = $this->setVariables($imdb['genre'] ?? '', $tmdb['genre'] ?? '', $trakt['genres'] ?? '', $omdb['genre'] ?? '', $iTunes['genre'] ?? '');
557
558
        if (! empty($imdb['type'])) {
559
            $mov['type'] = $imdb['type'];
560
        }
561
562
        if (! empty($imdb['director'])) {
563
            $mov['director'] = \is_array($imdb['director']) ? implode(', ', array_unique($imdb['director'])) : $imdb['director'];
564
        } elseif (! empty($omdb['director'])) {
565
            $mov['director'] = \is_array($omdb['director']) ? implode(', ', array_unique($omdb['director'])) : $omdb['director'];
566
        } elseif (! empty($tmdb['director'])) {
567
            $mov['director'] = \is_array($tmdb['director']) ? implode(', ', array_unique($tmdb['director'])) : $tmdb['director'];
568
        }
569
570
        if (! empty($imdb['actors'])) {
571
            $mov['actors'] = \is_array($imdb['actors']) ? implode(', ', array_unique($imdb['actors'])) : $imdb['actors'];
572
        } elseif (! empty($omdb['actors'])) {
573
            $mov['actors'] = \is_array($omdb['actors']) ? implode(', ', array_unique($omdb['actors'])) : $omdb['actors'];
574
        } elseif (! empty($tmdb['actors'])) {
575
            $mov['actors'] = \is_array($tmdb['actors']) ? implode(', ', array_unique($tmdb['actors'])) : $tmdb['actors'];
576
        }
577
578
        if (! empty($imdb['language'])) {
579
            $mov['language'] = \is_array($imdb['language']) ? implode(', ', array_unique($imdb['language'])) : $imdb['language'];
0 ignored issues
show
introduced by
The condition is_array($imdb['language']) is always false.
Loading history...
580
        } elseif (! empty($omdb['language']) && ! is_bool($omdb['language'])) {
581
            $mov['language'] = \is_array($omdb['language']) ? implode(', ', array_unique($omdb['language'])) : $omdb['language'];
582
        }
583
584
        if (\is_array($mov['genre'])) {
585
            $mov['genre'] = implode(', ', array_unique($mov['genre']));
586
        }
587
588
        if (\is_array($mov['type'])) {
589
            $mov['type'] = implode(', ', array_unique($mov['type']));
590
        }
591
592
        $mov['title'] = html_entity_decode($mov['title'], ENT_QUOTES, 'UTF-8');
593
594
        $mov['title'] = str_replace(['/', '\\'], '', $mov['title']);
595
        $movieID = $this->update([
596
            'actors' => html_entity_decode($mov['actors'], ENT_QUOTES, 'UTF-8'),
597
            'backdrop' => $mov['backdrop'],
598
            'cover' => $mov['cover'],
599
            'director' => html_entity_decode($mov['director'], ENT_QUOTES, 'UTF-8'),
600
            'genre' => html_entity_decode($mov['genre'], ENT_QUOTES, 'UTF-8'),
601
            'imdbid' => $mov['imdbid'],
602
            'language' => html_entity_decode($mov['language'], ENT_QUOTES, 'UTF-8'),
603
            'plot' => html_entity_decode(preg_replace('/\s+See full summary »/u', ' ', $mov['plot']), ENT_QUOTES, 'UTF-8'),
604
            'rating' => round((int) $mov['rating'], 1),
605
            'rtrating' => $mov['rtrating'] ?? 'N/A',
606
            'tagline' => html_entity_decode($mov['tagline'], ENT_QUOTES, 'UTF-8'),
607
            'title' => $mov['title'],
608
            'tmdbid' => $mov['tmdbid'],
609
            'traktid' => $mov['traktid'],
610
            'type' => html_entity_decode(ucwords(preg_replace('/[\.\_]/', ' ', $mov['type'])), ENT_QUOTES, 'UTF-8'),
611
            'year' => $mov['year'],
612
        ]);
613
614
        if ($this->echooutput && $this->service !== '') {
615
            PHP_EOL.$this->colorCli->headerOver('Added/updated movie: ').
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->colorCli->headerO...Added/updated movie: ') targeting Blacklight\ColorCLI::headerOver() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure $this->colorCli->headerO...Added/updated movie: ') of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

615
            PHP_EOL./** @scrutinizer ignore-type */ $this->colorCli->headerOver('Added/updated movie: ').
Loading history...
616
            $this->colorCli->primary(
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->colorCli->primary...) - ' . $mov['imdbid']) targeting Blacklight\ColorCLI::primary() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure $this->colorCli->primary...) - ' . $mov['imdbid']) of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

616
            /** @scrutinizer ignore-type */ $this->colorCli->primary(
Loading history...
617
                $mov['title'].
618
                ' ('.
619
                $mov['year'].
620
                ') - '.
621
                $mov['imdbid']
622
            );
623
        }
624
625
        return $movieID;
626
    }
627
628
    /**
629
     * Fetch FanArt.tv backdrop / cover / title.
630
     *
631
     * @return array|false
632
     */
633
    protected function fetchFanartTVProperties($imdbId)
634
    {
635
        if ($this->fanartapikey !== null) {
636
            $art = $this->fanart->getMovieFanArt('tt'.$imdbId);
637
638
            if (! empty($art)) {
639
                if (isset($art['status']) && $art['status'] === 'error') {
640
                    return false;
641
                }
642
                $ret = [];
643
                if (! empty($art['moviebackground'][0]['url'])) {
644
                    $ret['backdrop'] = $art['moviebackground'][0]['url'];
645
                } elseif (! empty($art['moviethumb'][0]['url'])) {
646
                    $ret['backdrop'] = $art['moviethumb'][0]['url'];
647
                }
648
                if (! empty($art['movieposter'][0]['url'])) {
649
                    $ret['cover'] = $art['movieposter'][0]['url'];
650
                }
651
                if (! empty($art['moviebanner'][0]['url'])) {
652
                    $ret['banner'] = $art['moviebanner'][0]['url'];
653
                }
654
655
                if (isset($ret['backdrop'], $ret['cover'])) {
656
                    $ret['title'] = $imdbId;
657
                    if (isset($art['name'])) {
658
                        $ret['title'] = $art['name'];
659
                    }
660
                    if ($this->echooutput) {
661
                        $this->colorCli->climate()->info('Fanart found '.$ret['title']);
662
                    }
663
664
                    return $ret;
665
                }
666
            }
667
        }
668
669
        return false;
670
    }
671
672
    /**
673
     * Fetch info for IMDB id from TMDB.
674
     *
675
     *
676
     * @return array|false
677
     */
678
    public function fetchTMDBProperties($imdbId, bool $text = false)
679
    {
680
        $lookupId = $text === false && (\strlen($imdbId) === 7 || strlen($imdbId) === 8) ? 'tt'.$imdbId : $imdbId;
681
682
        try {
683
            $tmdbLookup = Tmdb::getMoviesApi()->getMovie($lookupId, ['append_to_response' => 'credits']);
684
        } catch (TmdbApiException|\ErrorException $error) {
685
            return false;
686
        }
687
688
        if (! empty($tmdbLookup)) {
689
            if ($this->currentTitle !== '') {
690
                // Check the similarity.
691
                similar_text($this->currentTitle, $tmdbLookup['title'], $percent);
692
                if ($percent < self::MATCH_PERCENT) {
693
                    return false;
694
                }
695
            }
696
697
            if ($this->currentYear !== '') {
698
                // Check the similarity.
699
                similar_text($this->currentYear, Carbon::parse($tmdbLookup['release_date'])->year, $percent);
700
                if ($percent < self::YEAR_MATCH_PERCENT) {
701
                    return false;
702
                }
703
            }
704
705
            $ret = [];
706
            $ret['title'] = $tmdbLookup['title'];
707
708
            $ret['tmdbid'] = $tmdbLookup['id'];
709
            $ret['imdbid'] = str_replace('tt', '', $tmdbLookup['imdb_id']);
710
            $vote = $tmdbLookup['vote_average'];
711
            if ($vote !== null) {
712
                $ret['rating'] = (int) $vote === 0 ? '' : $vote;
713
            } else {
714
                $ret['rating'] = '';
715
            }
716
            $actors = Arr::pluck($tmdbLookup['credits']['cast'], 'name');
717
            if (! empty($actors)) {
718
                $ret['actors'] = $actors;
719
            } else {
720
                $ret['actors'] = '';
721
            }
722
            foreach ($tmdbLookup['credits']['crew'] as $crew) {
723
                if ($crew['department'] === 'Directing' && $crew['job'] === 'Director') {
724
                    $ret['director'] = $crew['name'];
725
                }
726
            }
727
            $overview = $tmdbLookup['overview'];
728
            if (! empty($overview)) {
729
                $ret['plot'] = $overview;
730
            } else {
731
                $ret['plot'] = '';
732
            }
733
            $tagline = $tmdbLookup['tagline'];
734
735
            $ret['tagline'] = $tagline ?? '';
736
737
            $released = $tmdbLookup['release_date'];
738
            if (! empty($released)) {
739
                $ret['year'] = Carbon::parse($released)->year;
740
            } else {
741
                $ret['year'] = '';
742
            }
743
            $genresa = $tmdbLookup['genres'];
744
            if (! empty($genresa) && \count($genresa) > 0) {
745
                $genres = [];
746
                foreach ($genresa as $genre) {
747
                    $genres[] = $genre['name'];
748
                }
749
                $ret['genre'] = $genres;
750
            } else {
751
                $ret['genre'] = '';
752
            }
753
            $posterp = $tmdbLookup['poster_path'];
754
            if (! empty($posterp)) {
755
                $ret['cover'] = 'https://image.tmdb.org/t/p/original'.$posterp;
756
            } else {
757
                $ret['cover'] = '';
758
            }
759
            $backdrop = $tmdbLookup['backdrop_path'];
760
            if (! empty($backdrop)) {
761
                $ret['backdrop'] = 'https://image.tmdb.org/t/p/original'.$backdrop;
762
            } else {
763
                $ret['backdrop'] = '';
764
            }
765
            if ($this->echooutput) {
766
                $this->colorCli->climate()->info('TMDb found '.$ret['title']);
767
            }
768
769
            return $ret;
770
        }
771
772
        return false;
773
    }
774
775
    /**
776
     * @return array|false
777
     */
778
    public function fetchIMDBProperties($imdbId)
779
    {
780
        $realId = (new Title($imdbId, $this->config))->real_id();
781
        $result = new Title($realId, $this->config);
782
        $title = ! empty($result->orig_title()) ? $result->orig_title() : $result->title();
783
        if (! empty($title)) {
784
            if (! empty($this->currentTitle)) {
785
                similar_text($this->currentTitle, $title, $percent);
786
                if ($percent >= self::MATCH_PERCENT) {
787
                    similar_text($this->currentYear, $result->year(), $percent);
788
                    if ($percent >= self::YEAR_MATCH_PERCENT) {
789
                        $ret = [
790
                            'title' => $title,
791
                            'tagline' => $result->tagline() ?? '',
792
                            'plot' => Arr::get($result->plot_split(), '0.plot'),
793
                            'rating' => ! empty($result->rating()) ? $result->rating() : '',
794
                            'year' => $result->year() ?? '',
795
                            'cover' => $result->photo() ?? '',
796
                            'genre' => $result->genre() ?? '',
797
                            'language' => $result->language() ?? '',
798
                            'type' => $result->movietype() ?? '',
799
                        ];
800
801
                        if ($this->echooutput) {
802
                            $this->colorCli->climate()->info('IMDb found '.$title);
803
                        }
804
805
                        return $ret;
806
                    }
807
808
                    return false;
809
                }
810
811
                return false;
812
            }
813
814
            return [
815
                'title' => $title,
816
                'tagline' => $result->tagline() ?? '',
817
                'plot' => Arr::get($result->plot_split(), '0.plot'),
818
                'rating' => ! empty($result->rating()) ? $result->rating() : '',
819
                'year' => $result->year() ?? '',
820
                'cover' => $result->photo() ?? '',
821
                'genre' => $result->genre() ?? '',
822
                'language' => $result->language() ?? '',
823
                'type' => $result->movietype() ?? '',
824
            ];
825
        }
826
827
        return false;
828
    }
829
830
    /**
831
     * Fetch TraktTV backdrop / cover / title.
832
     *
833
     * @return array|false
834
     *
835
     * @throws \Exception
836
     * @throws GuzzleException
837
     */
838
    public function fetchTraktTVProperties($imdbId)
839
    {
840
        if ($this->traktcheck !== null) {
841
            $resp = $this->traktTv->client->movieSummary('tt'.$imdbId, 'full');
842
            if ($resp !== false) {
843
                similar_text($this->currentTitle, $resp['title'], $percent);
844
                if ($percent >= self::MATCH_PERCENT) {
845
                    similar_text($this->currentYear, $resp['year'], $percent);
846
                    if ($percent >= self::YEAR_MATCH_PERCENT) {
847
                        $ret = [];
848
                        if (isset($resp['ids']['trakt'])) {
849
                            $ret['id'] = $resp['ids']['trakt'];
850
                        }
851
852
                        $ret['overview'] = $resp['overview'] ?? '';
853
                        $ret['tagline'] = $resp['tagline'] ?? '';
854
                        $ret['year'] = $resp['year'] ?? '';
855
                        $ret['genres'] = $resp['genres'] ?? '';
856
857
                        if (isset($resp['title'])) {
858
                            $ret['title'] = $resp['title'];
859
                        } else {
860
                            return false;
861
                        }
862
863
                        if ($this->echooutput) {
864
                            $this->colorCli->climate()->info('Trakt found '.$ret['title']);
865
                        }
866
867
                        return $ret;
868
                    }
869
870
                    return false;
871
                }
872
873
                return false;
874
            }
875
876
            return false;
877
        }
878
879
        return false;
880
    }
881
882
    /**
883
     * Fetch OMDb backdrop / cover / title.
884
     *
885
     * @return array|false
886
     */
887
    public function fetchOmdbAPIProperties($imdbId)
888
    {
889
        if ($this->omdbapikey !== null) {
890
            $resp = $this->omdbApi->fetch('i', 'tt'.$imdbId);
0 ignored issues
show
Bug introduced by
The method fetch() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

890
            /** @scrutinizer ignore-call */ 
891
            $resp = $this->omdbApi->fetch('i', 'tt'.$imdbId);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
891
892
            if (\is_object($resp) && $resp->message === 'OK' && ! Str::contains($resp->data->Response, 'Error:') && $resp->data->Response !== 'False') {
0 ignored issues
show
introduced by
The condition is_object($resp) is always false.
Loading history...
893
                similar_text($this->currentTitle, $resp->data->Title, $percent);
894
                if ($percent >= self::MATCH_PERCENT) {
895
                    similar_text($this->currentYear, $resp->data->Year, $percent);
896
                    if ($percent >= self::YEAR_MATCH_PERCENT) {
897
                        $ret = [
898
                            'title' => $resp->data->Title ?? '',
899
                            'cover' => $resp->data->Poster ?? '',
900
                            'genre' => $resp->data->Genre ?? '',
901
                            'year' => $resp->data->Year ?? '',
902
                            'plot' => $resp->data->Plot ?? '',
903
                            'rating' => $resp->data->imdbRating ?? '',
904
                            'rtRating' => $resp->data->Ratings[1]->Value ?? '',
905
                            'tagline' => $resp->data->Tagline ?? '',
906
                            'director' => $resp->data->Director ?? '',
907
                            'actors' => $resp->data->Actors ?? '',
908
                            'language' => $resp->data->Language ?? '',
909
                            'boxOffice' => $resp->data->BoxOffice ?? '',
910
                        ];
911
912
                        if ($this->echooutput) {
913
                            $this->colorCli->climate()->info('OMDbAPI Found '.$ret['title']);
914
                        }
915
916
                        return $ret;
917
                    }
918
919
                    return false;
920
                }
921
922
                return false;
923
            }
924
925
            return false;
926
        }
927
928
        return false;
929
    }
930
931
    /**
932
     * @return array|bool
933
     *
934
     * @throws InvalidProviderException
935
     * @throws \Exception
936
     */
937
    public function fetchItunesMovieProperties(string $title)
938
    {
939
        $movie = true;
940
        try {
941
            $iTunesMovie = iTunes::load('movie')->fetchOneByName($title);
942
        } catch (MovieNotFoundException $e) {
943
            $movie = false;
944
        } catch (SearchNoResultsException $e) {
945
            $movie = false;
946
        }
947
948
        if ($movie !== false) {
949
            similar_text($this->currentTitle, $iTunesMovie->getName(), $percent);
950
            if ($percent >= self::MATCH_PERCENT) {
951
                $movie = [
952
                    'title' => $iTunesMovie->getName(),
953
                    'director' => $iTunesMovie->getDirector() ?? '',
0 ignored issues
show
Bug introduced by
The method getDirector() does not exist on DariusIII\ItunesApi\Entities\EntityInterface. It seems like you code against a sub-type of DariusIII\ItunesApi\Entities\EntityInterface such as DariusIII\ItunesApi\Entities\Movie. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

953
                    'director' => $iTunesMovie->/** @scrutinizer ignore-call */ getDirector() ?? '',
Loading history...
954
                    'tagline' => $iTunesMovie->getTagLine() ?? '',
0 ignored issues
show
Bug introduced by
The method getTagLine() does not exist on DariusIII\ItunesApi\Entities\EntityInterface. It seems like you code against a sub-type of DariusIII\ItunesApi\Entities\EntityInterface such as DariusIII\ItunesApi\Entities\Movie. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

954
                    'tagline' => $iTunesMovie->/** @scrutinizer ignore-call */ getTagLine() ?? '',
Loading history...
955
                    'cover' => str_replace('100x100', '800x800', $iTunesMovie->getCover()),
0 ignored issues
show
Bug introduced by
The method getCover() does not exist on DariusIII\ItunesApi\Entities\EntityInterface. It seems like you code against a sub-type of said class. However, the method does not exist in DariusIII\ItunesApi\Entities\Artist. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

955
                    'cover' => str_replace('100x100', '800x800', $iTunesMovie->/** @scrutinizer ignore-call */ getCover()),
Loading history...
956
                    'genre' => $iTunesMovie->getGenre() ?? '',
0 ignored issues
show
Bug introduced by
The method getGenre() does not exist on DariusIII\ItunesApi\Entities\EntityInterface. It seems like you code against a sub-type of said class. However, the method does not exist in DariusIII\ItunesApi\Entities\Artist. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

956
                    'genre' => $iTunesMovie->/** @scrutinizer ignore-call */ getGenre() ?? '',
Loading history...
957
                    'plot' => $iTunesMovie->getDescription() ?? '',
0 ignored issues
show
Bug introduced by
The method getDescription() does not exist on DariusIII\ItunesApi\Entities\EntityInterface. It seems like you code against a sub-type of DariusIII\ItunesApi\Entities\EntityInterface such as DariusIII\ItunesApi\Entities\Ebook or DariusIII\ItunesApi\Entities\Movie. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

957
                    'plot' => $iTunesMovie->/** @scrutinizer ignore-call */ getDescription() ?? '',
Loading history...
958
                    'year' => $iTunesMovie->getReleaseDate() ? $iTunesMovie->getReleaseDate()->format('Y') : '',
0 ignored issues
show
Bug introduced by
The method getReleaseDate() does not exist on DariusIII\ItunesApi\Entities\EntityInterface. It seems like you code against a sub-type of said class. However, the method does not exist in DariusIII\ItunesApi\Entities\Track or DariusIII\ItunesApi\Entities\Artist. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

958
                    'year' => $iTunesMovie->/** @scrutinizer ignore-call */ getReleaseDate() ? $iTunesMovie->getReleaseDate()->format('Y') : '',
Loading history...
959
                ];
960
            } else {
961
                $movie = false;
962
            }
963
        }
964
965
        return $movie;
966
    }
967
968
    /**
969
     * Update a release with a IMDB id.
970
     *
971
     * @param  string  $buffer  Data to parse a IMDB id/Trakt Id from.
972
     * @param  string  $service  Method that called this method.
973
     * @param  int  $id  id of the release.
974
     * @param  int  $processImdb  To get IMDB info on this IMDB id or not.
975
     *
976
     * @throws \Exception
977
     */
978
    public function doMovieUpdate(string $buffer, string $service, int $id, int $processImdb = 1): string
979
    {
980
        $imdbId = false;
981
        if (preg_match('/(?:imdb.*?)?(?:tt|Title\?)(?P<imdbid>\d{5,8})/i', $buffer, $hits)) {
982
            $imdbId = $hits['imdbid'];
983
        }
984
985
        if ($imdbId !== false) {
986
            $this->service = $service;
987
            if ($this->echooutput && $this->service !== '') {
988
                $this->colorCli->climate()->info($this->service.' found IMDBid: tt'.$imdbId);
989
            }
990
991
            $movieInfoId = MovieInfo::query()->where('imdbid', $imdbId)->first(['id']);
992
993
            Release::query()->where('id', $id)->update(['imdbid' => $imdbId, 'movieinfo_id' => $movieInfoId !== null ? $movieInfoId['id'] : null]);
994
995
            // If set, scan for imdb info.
996
            if ($processImdb === 1) {
997
                $movCheck = $this->getMovieInfo($imdbId);
998
                if ($movCheck === null || (isset($movCheck['updated_at']) && (time() - strtotime($movCheck['updated_at'])) > 2592000)) {
999
                    $info = $this->updateMovieInfo($imdbId);
1000
                    if ($info === false) {
1001
                        Release::query()->where('id', $id)->update(['imdbid' => 0000000]);
1002
                    } elseif ($info === true) {
0 ignored issues
show
introduced by
The condition $info === true is always true.
Loading history...
1003
                        $movieInfoId = MovieInfo::query()->where('imdbid', $imdbId)->first(['id']);
1004
1005
                        Release::query()->where('id', $id)->update(['imdbid' => $imdbId, 'movieinfo_id' => $movieInfoId !== null ? $movieInfoId['id'] : null]);
1006
                    }
1007
                }
1008
            }
1009
        }
1010
1011
        return $imdbId;
1012
    }
1013
1014
    /**
1015
     * Process releases with no IMDB id's.
1016
     *
1017
     *
1018
     *
1019
     * @throws \Exception
1020
     * @throws GuzzleException
1021
     */
1022
    public function processMovieReleases(string $groupID = '', string $guidChar = '', int $lookupIMDB = 1): void
1023
    {
1024
        if ($lookupIMDB === 0) {
1025
            return;
1026
        }
1027
1028
        // Get all releases without an IMDB id.
1029
        $sql = Release::query()
1030
            ->select(['searchname', 'id'])
1031
            ->whereBetween('categories_id', [Category::MOVIE_ROOT, Category::MOVIE_OTHER])
1032
            ->whereNull('imdbid')
1033
            ->where('nzbstatus', '=', 1);
1034
        if ($groupID !== '') {
1035
            $sql->where('groups_id', $groupID);
1036
        }
1037
1038
        if ($guidChar !== '') {
1039
            $sql->where('leftguid', $guidChar);
1040
        }
1041
1042
        if ((int) $lookupIMDB === 2) {
1043
            $sql->where('isrenamed', '=', 1);
1044
        }
1045
1046
        $res = $sql->limit($this->movieqty)->get();
1047
1048
        $movieCount = \count($res);
1049
1050
        if ($movieCount > 0) {
1051
            if ($this->echooutput && $movieCount > 1) {
1052
                $this->colorCli->header('Processing '.$movieCount.' movie releases.');
1053
            }
1054
1055
            // Loop over releases.
1056
            foreach ($res as $arr) {
1057
                // Try to get a name/year.
1058
                if (! $this->parseMovieSearchName($arr['searchname'])) {
1059
                    //We didn't find a name, so set to all 0's so we don't parse again.
1060
                    Release::query()->where('id', $arr['id'])->update(['imdbid' => 0000000]);
1061
1062
                    continue;
1063
                }
1064
                $this->currentRelID = $arr['id'];
1065
1066
                $movieName = $this->currentTitle;
1067
                if ($this->currentYear !== '') {
1068
                    $movieName .= ' ('.$this->currentYear.')';
1069
                }
1070
1071
                if ($this->echooutput) {
1072
                    $this->colorCli->climate()->info('Looking up: '.$movieName);
1073
                }
1074
1075
                $movieUpdated = false;
1076
1077
                // Check local DB.
1078
                $getIMDBid = $this->localIMDBSearch();
1079
1080
                if ($getIMDBid !== false) {
1081
                    $imdbId = $this->doMovieUpdate('tt'.$getIMDBid, 'Local DB', $arr['id']);
1082
                    if ($imdbId !== false) {
1083
                        $movieUpdated = true;
1084
                    }
1085
                }
1086
1087
                // Check on IMDb first
1088
                if ($movieUpdated === false) {
1089
                    try {
1090
                        $imdbSearch = new TitleSearch($this->config);
1091
                        foreach ($imdbSearch->search($this->currentTitle, [TitleSearch::MOVIE]) as $imdbTitle) {
1092
                            if (! empty($imdbTitle->title())) {
1093
                                similar_text($imdbTitle->title(), $this->currentTitle, $percent);
1094
                                if ($percent >= self::MATCH_PERCENT) {
1095
                                    similar_text($this->currentYear, $imdbTitle->year(), $percent);
1096
                                    if ($percent >= self::YEAR_MATCH_PERCENT) {
1097
                                        $getIMDBid = $imdbTitle->imdbid();
1098
                                        $imdbId = $this->doMovieUpdate('tt'.$getIMDBid, 'IMDb', $arr['id']);
1099
                                        if ($imdbId !== false) {
1100
                                            $movieUpdated = true;
1101
                                        }
1102
                                    }
1103
                                }
1104
                            }
1105
                        }
1106
                    } catch (\ErrorException $e) {
1107
                        $this->colorCli->error('Error fetching data from imdb occurred', true);
1108
                        Log::debug($e->getMessage());
1109
                    }
1110
                }
1111
1112
                // Check on OMDbAPI
1113
                if ($movieUpdated === false) {
1114
                    $omdbTitle = strtolower(str_replace(' ', '_', $this->currentTitle));
1115
                    if ($this->omdbapikey !== null) {
1116
                        if ($this->currentYear !== '') {
1117
                            $buffer = $this->omdbApi->search($omdbTitle, 'movie', $this->currentYear);
1118
                        } else {
1119
                            $buffer = $this->omdbApi->search($omdbTitle, 'movie');
1120
                        }
1121
1122
                        if (\is_object($buffer) && $buffer->message === 'OK' && ! Str::contains($buffer->data->Response, 'Error:') && $buffer->data->Response === 'True') {
1123
                            $getIMDBid = $buffer->data->Search[0]->imdbID;
1124
1125
                            if (! empty($getIMDBid)) {
1126
                                $imdbId = $this->doMovieUpdate($getIMDBid, 'OMDbAPI', $arr['id']);
1127
                                if ($imdbId !== false) {
1128
                                    $movieUpdated = true;
1129
                                }
1130
                            }
1131
                        }
1132
                    }
1133
                }
1134
1135
                // Check on Trakt.
1136
                if ($movieUpdated === false && $this->traktcheck !== null) {
1137
                    $data = $this->traktTv->client->movieSummary($movieName, 'full');
1138
                    if ($data !== false) {
1139
                        $this->parseTraktTv($data);
1140
                        if (! empty($data['ids']['imdb'])) {
1141
                            $imdbId = $this->doMovieUpdate($data['ids']['imdb'], 'Trakt', $arr['id']);
1142
                            if ($imdbId !== false) {
1143
                                $movieUpdated = true;
1144
                            }
1145
                        }
1146
                    }
1147
                }
1148
1149
                // Check on The Movie Database.
1150
                if ($movieUpdated === false) {
1151
                    try {
1152
                        $data = Tmdb::getSearchApi()->searchMovies($this->currentTitle);
1153
                        if (($data['total_results'] > 0) && ! empty($data['results'])) {
1154
                            foreach ($data['results'] as $result) {
1155
                                if (! empty($result['id']) && ! empty($result['release_date'])) {
1156
                                    similar_text($this->currentYear, Carbon::parse($result['release_date'])->year, $percent);
1157
                                    if ($percent >= self::YEAR_MATCH_PERCENT) {
1158
                                        $ret = $this->fetchTMDBProperties($result['id'], true);
1159
                                        if ($ret !== false && ! empty($ret['imdbid'])) {
1160
                                            $imdbId = $this->doMovieUpdate('tt'.$ret['imdbid'], 'TMDB', $arr['id']);
1161
                                            if ($imdbId !== false) {
1162
                                                $movieUpdated = true;
1163
                                            }
1164
                                        }
1165
                                    }
1166
                                } else {
1167
                                    $movieUpdated = false;
1168
                                }
1169
                            }
1170
                        } else {
1171
                            $movieUpdated = false;
1172
                        }
1173
                    } catch (TmdbApiException|\ErrorException $error) {
1174
                        $movieUpdated = false;
1175
                    }
1176
                }
1177
1178
                // We failed to get an IMDB id from all sources.
1179
                if ($movieUpdated === false) {
1180
                    Release::query()->where('id', $arr['id'])->update(['imdbid' => 0000000]);
1181
                }
1182
            }
1183
        }
1184
    }
1185
1186
    /**
1187
     * @return false|mixed
1188
     */
1189
    protected function localIMDBSearch()
1190
    {
1191
        //If we found a year, try looking in a 4 year range.
1192
        $check = MovieInfo::query()
1193
            ->where('title', 'like', '%'.$this->currentTitle.'%');
1194
1195
        if ($this->currentYear !== '') {
1196
            $start = Carbon::createFromFormat('Y', $this->currentYear)->subYears(2)->year;
0 ignored issues
show
Bug introduced by
The method subYears() does not exist on DateTime. It seems like you code against a sub-type of said class. However, the method does not exist in pq\DateTime. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1196
            $start = Carbon::createFromFormat('Y', $this->currentYear)->/** @scrutinizer ignore-call */ subYears(2)->year;
Loading history...
1197
            $end = Carbon::createFromFormat('Y', $this->currentYear)->addYears(2)->year;
0 ignored issues
show
Bug introduced by
The method addYears() does not exist on DateTime. It seems like you code against a sub-type of said class. However, the method does not exist in pq\DateTime. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1197
            $end = Carbon::createFromFormat('Y', $this->currentYear)->/** @scrutinizer ignore-call */ addYears(2)->year;
Loading history...
1198
            $check->whereBetween('year', [$start, $end]);
1199
        }
1200
        $IMDBCheck = $check->get(['imdbid']);
1201
        foreach ($IMDBCheck as $check) {
1202
            // match the title and year of the movie as close as possible.
1203
            if ($this->currentYear !== '') {
1204
                $IMDBCheck = MovieInfo::query()
1205
                    ->where('imdbid', $check['imdbid'])
1206
                    ->where('title', 'like', '%'.$this->currentTitle.'%')
1207
                    ->whereBetween('year', [$start, $end])
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $start does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $end does not seem to be defined for all execution paths leading up to this point.
Loading history...
1208
                    ->first(['imdbid', 'title']);
1209
            } else {
1210
                $IMDBCheck = MovieInfo::query()
1211
                    ->where('imdbid', $check['imdbid'])
1212
                    ->where('title', 'like', '%'.$this->currentTitle.'%')
1213
                    ->first(['imdbid', 'title']);
1214
            }
1215
            // If we found a match, check if percentage is high enough. If so, return the IMDB id.
1216
            if ($IMDBCheck !== null) {
1217
                similar_text($this->currentTitle, $IMDBCheck['title'], $percent);
1218
                if ($percent >= self::MATCH_PERCENT) {
1219
                    return $IMDBCheck['imdbid'];
1220
                }
1221
            }
1222
        }
1223
1224
        return false;
1225
    }
1226
1227
    /**
1228
     * Parse a movie name from a release search name.
1229
     */
1230
    protected function parseMovieSearchName(string $releaseName): bool
1231
    {
1232
        $name = $year = '';
1233
        $followingList = '[^\w]((1080|480|720)p|AC3D|Directors([^\w]CUT)?|DD5\.1|(DVD|BD|BR)(Rip)?|BluRay|divx|HDTV|iNTERNAL|LiMiTED|(Real\.)?Proper|RE(pack|Rip)|Sub\.?(fix|pack)|Unrated|WEB-DL|(x|H)[ ._-]?264|xvid)[^\w]';
1234
1235
        /* Initial scan of getting a year/name.
1236
         * [\w. -]+ Gets 0-9a-z. - characters, most scene movie titles contain these chars.
1237
         * ie: [61420]-[FULL]-[a.b.foreignEFNet]-[ Coraline.2009.DUTCH.INTERNAL.1080p.BluRay.x264-VeDeTT ]-[21/85] - "vedett-coralien-1080p.r04" yEnc
1238
         * Then we look up the year, (19|20)\d\d, so $hits[1] would be Coraline $hits[2] 2009
1239
         */
1240
        if (preg_match('/(?P<name>[\w. -]+)[^\w](?P<year>(19|20)\d\d)/i', $releaseName, $hits)) {
1241
            $name = $hits['name'];
1242
            $year = $hits['year'];
1243
1244
            /* If we didn't find a year, try to get a name anyways.
1245
             * Try to look for a title before the $followingList and after anything but a-z0-9 two times or more (-[ for example)
1246
             */
1247
        } elseif (preg_match('/([^\w]{2,})?(?P<name>[\w .-]+?)'.$followingList.'/i', $releaseName, $hits)) {
1248
            $name = $hits['name'];
1249
        }
1250
1251
        // Check if we got something.
1252
        if ($name !== '') {
1253
            // If we still have any of the words in $followingList, remove them.
1254
            $name = preg_replace('/'.$followingList.'/i', ' ', $name);
1255
            // Remove periods, underscored, anything between parenthesis.
1256
            $name = preg_replace('/\(.*?\)|[._]/i', ' ', $name);
1257
            // Finally remove multiple spaces and trim leading spaces.
1258
            $name = trim(preg_replace('/\s{2,}/', ' ', $name));
1259
            // Check if the name is long enough and not just numbers.
1260
            if (\strlen($name) > 4 && ! preg_match('/^\d+$/', $name)) {
1261
                $this->currentTitle = $name;
1262
                $this->currentYear = $year;
1263
1264
                return true;
1265
            }
1266
        }
1267
1268
        return false;
1269
    }
1270
1271
    /**
1272
     * Get IMDB genres.
1273
     */
1274
    public function getGenres(): array
1275
    {
1276
        return [
1277
            'Action',
1278
            'Adventure',
1279
            'Animation',
1280
            'Biography',
1281
            'Comedy',
1282
            'Crime',
1283
            'Documentary',
1284
            'Drama',
1285
            'Family',
1286
            'Fantasy',
1287
            'Film-Noir',
1288
            'Game-Show',
1289
            'History',
1290
            'Horror',
1291
            'Music',
1292
            'Musical',
1293
            'Mystery',
1294
            'News',
1295
            'Reality-TV',
1296
            'Romance',
1297
            'Sci-Fi',
1298
            'Sport',
1299
            'Talk-Show',
1300
            'Thriller',
1301
            'War',
1302
            'Western',
1303
        ];
1304
    }
1305
}
1306