Completed
Push — dev ( c3752c...5d53e8 )
by Darko
06:12
created

Movie::getColumnKeys()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
ccs 0
cts 1
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Blacklight;
4
5
use Imdb\Title;
6
use Imdb\Config;
7
use aharen\OMDbAPI;
8
use Imdb\TitleSearch;
9
use GuzzleHttp\Client;
10
use App\Models\Release;
11
use App\Models\Category;
12
use App\Models\Settings;
13
use App\Models\MovieInfo;
14
use Illuminate\Support\Arr;
15
use Illuminate\Support\Carbon;
16
use Tmdb\Laravel\Facades\Tmdb;
17
use Blacklight\utility\Utility;
18
use DariusIII\ItunesApi\iTunes;
19
use Blacklight\libraries\FanartTV;
20
use Illuminate\Support\Facades\DB;
21
use Illuminate\Support\Facades\File;
22
use Tmdb\Exception\TmdbApiException;
23
use Blacklight\processing\tv\TraktTv;
24
use Illuminate\Support\Facades\Cache;
25
use DariusIII\ItunesApi\Exceptions\MovieNotFoundException;
26
use DariusIII\ItunesApi\Exceptions\SearchNoResultsException;
27
28
/**
29
 * Class Movie.
30
 */
31
class Movie
32
{
33
    /**
34
     * @var int
35
     */
36
    protected const MATCH_PERCENT = 75;
37
38
    /**
39
     * @var int
40
     */
41
    protected const YEAR_MATCH_PERCENT = 80;
42
43
    /**
44
     * Current title being passed through various sites/api's.
45
     * @var string
46
     */
47
    protected $currentTitle = '';
48
49
    /**
50
     * Current year of parsed search name.
51
     * @var string
52
     */
53
    protected $currentYear = '';
54
55
    /**
56
     * Current release id of parsed search name.
57
     *
58
     * @var string
59
     */
60
    protected $currentRelID = '';
61
62
    /**
63
     * @var string
64
     */
65
    protected $showPasswords;
66
67
    /**
68
     * @var \Blacklight\ReleaseImage
69
     */
70
    protected $releaseImage;
71
72
    /**
73
     * @var \Tmdb\Client
74
     */
75
    protected $tmdbclient;
76
77
    /**
78
     * @var \GuzzleHttp\Client
79
     */
80
    protected $client;
81
82
    /**
83
     * Language to fetch from IMDB.
84
     * @var string
85
     */
86
    protected $lookuplanguage;
87
88
    /**
89
     * @var \Blacklight\libraries\FanartTV
90
     */
91
    public $fanart;
92
93
    /**
94
     * @var null|string
95
     */
96
    public $fanartapikey;
97
98
    /**
99
     * @var null|string
100
     */
101
    public $omdbapikey;
102
103
    /**
104
     * @var bool
105
     */
106
    public $imdburl;
107
108
    /**
109
     * @var int
110
     */
111
    public $movieqty;
112
113
    /**
114
     * @var bool
115
     */
116
    public $echooutput;
117
118
    /**
119
     * @var string
120
     */
121
    public $imgSavePath;
122
123
    /**
124
     * @var string
125
     */
126
    public $service;
127
128
    /**
129
     * @var \Tmdb\ApiToken
130
     */
131
    public $tmdbtoken;
132
133
    /**
134
     * @var \Blacklight\processing\tv\TraktTv
135
     */
136
    public $traktTv;
137
138
    /**
139
     * @var OMDbAPI|null
140
     */
141
    public $omdbApi;
142
143
    /**
144
     * @var \Imdb\Config
145
     */
146
    private $config;
147
148
    /**
149
     * @var null|string
150
     */
151
    protected $traktcheck;
152
153
    /**
154
     * @var \Blacklight\ColorCLI
155
     */
156
    protected $colorCli;
157
158
    /**
159
     * @param array $options Class instances / Echo to CLI.
160
     * @throws \Exception
161
     */
162
    public function __construct(array $options = [])
163
    {
164
        $defaults = [
165
            'Echo'         => false,
166
            'Logger'    => null,
167
            'ReleaseImage' => null,
168
            'Settings'     => null,
169
            'TMDb'         => null,
170
        ];
171
        $options += $defaults;
172
173
        $this->releaseImage = ($options['ReleaseImage'] instanceof ReleaseImage ? $options['ReleaseImage'] : new ReleaseImage());
174
        $this->colorCli = new ColorCLI();
175
        $this->traktcheck = Settings::settingValue('APIs..trakttvclientkey');
176
        if ($this->traktcheck !== null) {
177
            $this->traktTv = new TraktTv(['Settings' => null]);
178
        }
179
        $this->client = new Client();
180
        $this->fanartapikey = Settings::settingValue('APIs..fanarttvkey');
181
        if ($this->fanartapikey !== null) {
182
            $this->fanart = new FanartTV($this->fanartapikey);
183
        }
184
        $this->omdbapikey = Settings::settingValue('APIs..omdbkey');
185
        if ($this->omdbapikey !== null) {
186
            $this->omdbApi = new OMDbAPI($this->omdbapikey);
187
        }
188
189
        $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...
190
        $this->config = new Config();
191
        $this->config->language = $this->lookuplanguage;
192
        $this->config->throwHttpExceptions = false;
193
        $cacheDir = resource_path().'/tmp/imdb_cache';
194
        if (! File::isDirectory($cacheDir)) {
195
            File::makeDirectory($cacheDir, 0777, false, true);
196
        }
197
        $this->config->cachedir = $cacheDir;
198
199
        $this->imdburl = (int) Settings::settingValue('indexer.categorise.imdburl') !== 0;
200
        $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...
201
        $this->showPasswords = (new Releases())->showPasswords();
202
203
        $this->echooutput = ($options['Echo'] && config('nntmux.echocli'));
204
        $this->imgSavePath = NN_COVERS.'movies/';
205
        $this->service = '';
206
    }
207
208
    /**
209
     * @param $imdbId
210
     *
211
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|null|object
212
     */
213
    public function getMovieInfo($imdbId)
214
    {
215
        return MovieInfo::query()->where('imdbid', $imdbId)->first();
216
    }
217
218
    /**
219
     * Get movie releases with covers for movie browse page.
220
     *
221
     *
222
     * @param       $page
223
     * @param       $cat
224
     * @param       $start
225
     * @param       $num
226
     * @param       $orderBy
227
     * @param int   $maxAge
228
     * @param array $excludedCats
229
     *
230
     * @return array|mixed
231
     */
232
    public function getMovieRange($page, $cat, $start, $num, $orderBy, $maxAge = -1, array $excludedCats = [])
233
    {
234
        $catsrch = '';
235
        if (\count($cat) > 0 && $cat[0] !== -1) {
236
            $catsrch = Category::getCategorySearch($cat);
237
        }
238
        $order = $this->getMovieOrder($orderBy);
239
        $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium'));
240
        $moviesSql =
241
            sprintf(
242
                "
243
					SELECT SQL_CALC_FOUND_ROWS
244
						m.imdbid,
245
						GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id
246
					FROM movieinfo m
247
					LEFT JOIN releases r USING (imdbid)
248
					WHERE r.nzbstatus = 1
249
					AND m.title != ''
250
					AND m.imdbid != '0000000'
251
					AND r.passwordstatus %s
252
					%s %s %s %s
253
					GROUP BY m.imdbid
254
					ORDER BY %s %s %s",
255
                $this->showPasswords,
256
                $this->getBrowseBy(),
257
                (! empty($catsrch) ? $catsrch : ''),
258
                (
259
                    $maxAge > 0
260
                    ? 'AND r.postdate > NOW() - INTERVAL '.$maxAge.'DAY '
261
                    : ''
262
                ),
263
                (\count($excludedCats) > 0 ? ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')' : ''),
264
                $order[0],
265
                $order[1],
266
                ($start === false ? '' : ' LIMIT '.$num.' OFFSET '.$start)
267
            );
268
        $movieCache = Cache::get(md5($moviesSql.$page));
269
        if ($movieCache !== null) {
270
            $movies = $movieCache;
271
        } else {
272
            $data = MovieInfo::fromQuery($moviesSql);
273
            $movies = ['total' => DB::select('SELECT FOUND_ROWS() AS total'), 'result' => $data];
274
            Cache::put(md5($moviesSql.$page), $movies, $expiresAt);
275
        }
276
        $movieIDs = $releaseIDs = [];
277
        if (! empty($movies['result'])) {
278
            foreach ($movies['result'] as $movie => $id) {
279
                $movieIDs[] = $id->imdbid;
280
                $releaseIDs[] = $id->grp_release_id;
281
            }
282
        }
283
284
        $sql = sprintf(
285
            "
286
			SELECT
287
				GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id,
288
				GROUP_CONCAT(r.rarinnerfilecount ORDER BY r.postdate DESC SEPARATOR ',') AS grp_rarinnerfilecount,
289
				GROUP_CONCAT(r.haspreview ORDER BY r.postdate DESC SEPARATOR ',') AS grp_haspreview,
290
				GROUP_CONCAT(r.passwordstatus ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_password,
291
				GROUP_CONCAT(r.guid ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_guid,
292
				GROUP_CONCAT(rn.releases_id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_nfoid,
293
				GROUP_CONCAT(g.name ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grpname,
294
				GROUP_CONCAT(r.searchname ORDER BY r.postdate DESC SEPARATOR '#') AS grp_release_name,
295
				GROUP_CONCAT(r.postdate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_postdate,
296
				GROUP_CONCAT(r.size ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_size,
297
				GROUP_CONCAT(r.totalpart ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_totalparts,
298
				GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_comments,
299
				GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grabs,
300
				GROUP_CONCAT(df.failed ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_failed,
301
				GROUP_CONCAT(cp.title, ' > ', c.title ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_catname,
302
			m.*,
303
			g.name AS group_name,
304
			rn.releases_id AS nfoid
305
			FROM releases r
306
			LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id
307
			LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id
308
			LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id
309
			LEFT OUTER JOIN categories c ON c.id = r.categories_id
310
			LEFT OUTER JOIN root_categories cp ON cp.id = c.root_categories_id
311
			INNER JOIN movieinfo m ON m.imdbid = r.imdbid
312
			WHERE m.imdbid IN (%s)
313
			AND r.id IN (%s) %s
314
			GROUP BY m.imdbid
315
			ORDER BY %s %s",
316
            (\is_array($movieIDs) && ! empty($movieIDs) ? implode(',', $movieIDs) : -1),
317
            (\is_array($releaseIDs) && ! empty($releaseIDs) ? implode(',', $releaseIDs) : -1),
318
            (! empty($catsrch) ? $catsrch : ''),
319
            $order[0],
320
            $order[1]
321
        );
322
        $return = Cache::get(md5($sql.$page));
323
        if ($return !== null) {
324
            return $return;
325
        }
326
        $return = Release::fromQuery($sql);
327
        if (\count($return) > 0) {
328
            $return[0]->_totalcount = $movies['total'][0]->total ?? 0;
329
        }
330
        Cache::put(md5($sql.$page), $return, $expiresAt);
331
332
        return $return;
333
    }
334
335
    /**
336
     * Get the order type the user requested on the movies page.
337
     *
338
     * @param $orderBy
339
     *
340
     * @return array
341
     */
342
    protected function getMovieOrder($orderBy): array
343
    {
344
        $orderArr = explode('_', (($orderBy === '') ? 'MAX(r.postdate)' : $orderBy));
345
        switch ($orderArr[0]) {
346
            case 'title':
347
                $orderField = 'm.title';
348
                break;
349
            case 'year':
350
                $orderField = 'm.year';
351
                break;
352
            case 'rating':
353
                $orderField = 'm.rating';
354
                break;
355
            case 'posted':
356
            default:
357
                $orderField = 'MAX(r.postdate)';
358
                break;
359
        }
360
361
        return [$orderField, isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1]) ? $orderArr[1] : 'desc'];
362
    }
363
364
    /**
365
     * Order types for movies page.
366
     *
367
     * @return array
368
     */
369
    public function getMovieOrdering(): array
370
    {
371
        return ['title_asc', 'title_desc', 'year_asc', 'year_desc', 'rating_asc', 'rating_desc'];
372
    }
373
374
    /**
375
     * @return string
376
     */
377
    protected function getBrowseBy(): string
378
    {
379
        $browseBy = ' ';
380
        $browseByArr = ['title', 'director', 'actors', 'genre', 'rating', 'year', 'imdb'];
381
        foreach ($browseByArr as $bb) {
382
            if (request()->has($bb) && ! empty(request()->input($bb))) {
383
                $bbv = stripslashes(request()->input($bb));
384
                if ($bb === 'rating') {
385
                    $bbv .= '.';
386
                }
387
                if ($bb === 'imdb') {
388
                    $browseBy .= sprintf('AND m.imdbid = %d', $bbv);
389
                } else {
390
                    $browseBy .= 'AND m.'.$bb.' '.'LIKE '.escapeString('%'.$bbv.'%');
391
                }
392
            }
393
        }
394
395
        return $browseBy;
396
    }
397
398
    /**
399
     * Get trailer using IMDB Id.
400
     *
401
     * @param int $imdbID
402
     *
403
     * @return bool|string
404
     * @throws \Exception
405
     */
406
    public function getTrailer($imdbID)
407
    {
408
        $trailer = MovieInfo::query()->where('imdbid', $imdbID)->where('trailer', '<>', '')->first(['trailer']);
409
        if ($trailer !== null) {
410
            return $trailer['trailer'];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $trailer['trailer'] also could return the type Illuminate\Database\Eloq...uent\Relations\Relation which is incompatible with the documented return type boolean|string.
Loading history...
411
        }
412
413
        if ($this->traktcheck !== null) {
414
            $data = $this->traktTv->client->movieSummary('tt'.$imdbID, 'full');
415
            if ($data !== false) {
416
                $this->parseTraktTv($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type string; however, parameter $data of Blacklight\Movie::parseTraktTv() does only seem to accept 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

416
                $this->parseTraktTv(/** @scrutinizer ignore-type */ $data);
Loading history...
417
                if (! empty($data['trailer'])) {
418
                    return $data['trailer'];
419
                }
420
            }
421
        }
422
423
        $trailer = Utility::imdb_trailers($imdbID);
424
        if ($trailer) {
425
            MovieInfo::query()->where('imdbid', $imdbID)->update(['trailer' => $trailer]);
426
427
            return $trailer;
428
        }
429
430
        return false;
431
    }
432
433
    /**
434
     * Parse trakt info, insert into DB.
435
     *
436
     * @param array $data
437
     *
438
     * @return mixed
439
     */
440
    public function parseTraktTv(&$data)
441
    {
442
        if (empty($data['ids']['imdb'])) {
443
            return false;
444
        }
445
446
        if (! empty($data['trailer'])) {
447
            $data['trailer'] = str_ireplace(
448
                ['watch?v=', 'http://'],
449
                ['embed/', 'https://'],
450
                $data['trailer']
451
            );
452
        }
453
        $imdbid = (strpos($data['ids']['imdb'], 'tt') === 0) ? substr($data['ids']['imdb'], 2) : $data['ids']['imdb'];
454
        $cover = 0;
455
        if (File::isFile($this->imgSavePath.$imdbid).'-cover.jpg') {
456
            $cover = 1;
457
        }
458
459
        return $this->update([
460
            'genres'   => $this->checkTraktValue($data['genres']),
461
            'imdbid'   => $this->checkTraktValue($imdbid),
462
            'language' => $this->checkTraktValue($data['language']),
463
            'plot'     => $this->checkTraktValue($data['overview']),
464
            'rating'   => $this->checkTraktValue($data['rating']),
465
            'tagline'  => $this->checkTraktValue($data['tagline']),
466
            'title'    => $this->checkTraktValue($data['title']),
467
            'tmdbid'   => $this->checkTraktValue($data['ids']['tmdb']),
468
            'traktid'  => $this->checkTraktValue($data['ids']['trakt']),
469
            'trailer'  => $this->checkTraktValue($data['trailer']),
470
            'cover'    => $cover,
471
            'year'     => $this->checkTraktValue($data['year']),
472
        ]);
473
    }
474
475
    /**
476
     * Checks if the value is set and not empty, returns it, else empty string.
477
     *
478
     * @param mixed $value
479
     *
480
     * @return string
481
     */
482
    private function checkTraktValue($value): string
483
    {
484
        if (\is_array($value) && ! empty($value)) {
485
            $temp = '';
486
            foreach ($value as $val) {
487
                if (! \is_array($val) && ! \is_object($val)) {
488
                    $temp .= $val;
489
                }
490
            }
491
            $value = $temp;
492
        }
493
494
        return ! empty($value) ? $value : '';
495
    }
496
497
    /**
498
     * Get array of column keys, for inserting / updating.
499
     *
500
     * @return array
501
     */
502
    public function getColumnKeys(): array
503
    {
504
        return [
505
            'actors', 'backdrop', 'cover', 'director', 'genre', 'imdbid', 'language',
506
            'plot', 'rating', 'rtrating', 'tagline', 'title', 'tmdbid', 'traktid', 'trailer', 'type', 'year',
507
        ];
508
    }
509
510
    /**
511
     * Update movie on movie-edit page.
512
     *
513
     * @param array $values Array of keys/values to update. See $validKeys
514
     * @return bool
515
     */
516
    public function update(array $values)
517
    {
518
        if (! \count($values)) {
519
            return false;
520
        }
521
522
        $validKeys = $this->getColumnKeys();
523
524
        $query = [
525
            '0' => 'INSERT INTO movieinfo (updated_at, created_at, ',
526
            '1' => ' VALUES (NOW(), NOW(), ',
527
            '2' => 'ON DUPLICATE KEY UPDATE updated_at = NOW(), ',
528
        ];
529
        $found = 0;
530
        foreach ($values as $key => $value) {
531
            if (! empty($value) && \in_array($key, $validKeys, false)) {
532
                $found++;
533
                $query[0] .= "$key, ";
534
                if (\in_array($key, ['genre', 'language'], false)) {
535
                    $value = substr($value, 0, 64);
536
                }
537
                $value = escapeString($value);
538
                $query[1] .= "$value, ";
539
                $query[2] .= "$key = $value, ";
540
            }
541
        }
542
        if (! $found) {
543
            return false;
544
        }
545
        foreach ($query as $key => $value) {
546
            $query[$key] = rtrim($value, ', ');
547
        }
548
549
        MovieInfo::fromQuery($query[0].') '.$query[1].') '.$query[2]);
550
    }
551
552
    /**
553
     * Returns a tmdb, imdb or trakt variable, the one that is set. Empty string if both not set.
554
     *
555
     * @param string $variable1
556
     * @param string $variable2
557
     * @param string $variable3
558
     * @param string $variable4
559
     * @param string $variable5
560
     *
561
     * @return array|string
562
     */
563
    protected function setVariables($variable1, $variable2, $variable3, $variable4, $variable5)
564
    {
565
        if (! empty($variable1)) {
566
            return $variable1;
567
        }
568
        if (! empty($variable2)) {
569
            return $variable2;
570
        }
571
        if (! empty($variable3)) {
572
            return $variable3;
573
        }
574
        if (! empty($variable4)) {
575
            return $variable4;
576
        }
577
        if (! empty($variable5)) {
578
            return $variable5;
579
        }
580
581
        return '';
582
    }
583
584
    /**
585
     * Fetch IMDB/TMDB/TRAKT/OMDB/iTunes info for the movie.
586
     *
587
     * @param $imdbId
588
     *
589
     * @return bool
590
     * @throws \Exception
591
     */
592
    public function updateMovieInfo($imdbId): bool
593
    {
594
        if ($this->echooutput && $this->service !== '' && Utility::isCLI()) {
595
            $this->colorCli->primary('Fetching IMDB info from TMDB/IMDB/Trakt/OMDB/iTunes using IMDB id: '.$imdbId);
596
        }
597
598
        // Check TMDB for IMDB info.
599
        $tmdb = $this->fetchTMDBProperties($imdbId);
600
601
        // Check IMDB for movie info.
602
        $imdb = $this->fetchIMDBProperties($imdbId);
603
604
        // Check TRAKT for movie info
605
        $trakt = $this->fetchTraktTVProperties($imdbId);
606
607
        // Check OMDb for movie info
608
        $omdb = $this->fetchOmdbAPIProperties($imdbId);
609
610
        // Check iTunes for movie info as last resort (iTunes do not provide all the info we need)
611
612
        $iTunes = $this->fetchItunesMovieProperties($this->currentTitle);
613
614
        if (! $imdb && ! $tmdb && ! $trakt && ! $omdb && empty($iTunes)) {
615
            return false;
616
        }
617
618
        // Check FanArt.tv for cover and background images.
619
        $fanart = $this->fetchFanartTVProperties($imdbId);
620
621
        $mov = [];
622
623
        $mov['cover'] = $mov['backdrop'] = $mov['banner'] = 0;
624
        $mov['type'] = $mov['director'] = $mov['actors'] = $mov['language'] = '';
625
626
        $mov['imdbid'] = $imdbId;
627
        $mov['tmdbid'] = (! isset($tmdb['tmdbid']) || $tmdb['tmdbid'] === '') ? 0 : $tmdb['tmdbid'];
628
        $mov['traktid'] = (! isset($trakt['id']) || $trakt['id'] === '') ? 0 : $trakt['id'];
629
630
        // Prefer Fanart.tv cover over TMDB,TMDB over IMDB,IMDB over OMDB and OMDB over iTunes.
631
        if (! empty($fanart['cover'])) {
632
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $fanart['cover'], $this->imgSavePath);
633
        } elseif (! empty($tmdb['cover'])) {
634
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $tmdb['cover'], $this->imgSavePath);
635
        } elseif (! empty($imdb['cover'])) {
636
            $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

636
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', /** @scrutinizer ignore-type */ $imdb['cover'], $this->imgSavePath);
Loading history...
637
        } elseif (! empty($omdb['cover'])) {
638
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $omdb['cover'], $this->imgSavePath);
639
        } elseif (! empty($iTunes['cover'])) {
640
            $mov['cover'] = $this->releaseImage->saveImage($imdbId.'-cover', $iTunes['cover'], $this->imgSavePath);
641
        }
642
643
        // Backdrops.
644
        if (! empty($fanart['backdrop'])) {
645
            $mov['backdrop'] = $this->releaseImage->saveImage($imdbId.'-backdrop', $fanart['backdrop'], $this->imgSavePath, 1920, 1024);
646
        } elseif (! empty($tmdb['backdrop'])) {
647
            $mov['backdrop'] = $this->releaseImage->saveImage($imdbId.'-backdrop', $tmdb['backdrop'], $this->imgSavePath, 1920, 1024);
648
        }
649
650
        // Banner
651
        if (! empty($fanart['banner'])) {
652
            $mov['banner'] = $this->releaseImage->saveImage($imdbId.'-banner', $fanart['banner'], $this->imgSavePath);
653
        }
654
655
        // RottenTomatoes rating from OmdbAPI
656
        if (! empty($omdb['rtRating'])) {
657
            $mov['rtrating'] = $omdb['rtRating'];
658
        }
659
660
        $mov['title'] = $this->setVariables($imdb['title'], $tmdb['title'], $trakt['title'], $omdb['title'], $iTunes['title']);
661
        $mov['rating'] = $this->setVariables($imdb['rating'] ?? '', $tmdb['rating'] ?? '', $trakt['rating'] ?? '', $omdb['rating'] ?? '', $iTunes['rating'] ?? '');
662
        $mov['plot'] = $this->setVariables($imdb['plot'], $tmdb['plot'], $trakt['overview'], $omdb['plot'], $iTunes['plot']);
663
        $mov['tagline'] = $this->setVariables($imdb['tagline'], $tmdb['tagline'], $trakt['tagline'], $omdb['tagline'], $iTunes['tagline']);
664
        $mov['year'] = $this->setVariables($imdb['year'], $tmdb['year'], $trakt['year'], $omdb['year'], $iTunes['year']);
665
        $mov['genre'] = $this->setVariables($imdb['genre'], $tmdb['genre'], $trakt['genres'], $omdb['genre'], $iTunes['genre']);
666
667
        if (! empty($imdb['type'])) {
668
            $mov['type'] = $imdb['type'];
669
        }
670
671
        if (! empty($imdb['director'])) {
672
            $mov['director'] = \is_array($imdb['director']) ? implode(', ', array_unique($imdb['director'])) : $imdb['director'];
673
        } elseif (! empty($omdb['director'])) {
674
            $mov['director'] = \is_array($omdb['director']) ? implode(', ', array_unique($omdb['director'])) : $omdb['director'];
675
        } elseif (! empty($tmdb['director'])) {
676
            $mov['director'] = \is_array($tmdb['director']) ? implode(', ', array_unique($tmdb['director'])) : $tmdb['director'];
677
        }
678
679
        if (! empty($imdb['actors'])) {
680
            $mov['actors'] = \is_array($imdb['actors']) ? implode(', ', array_unique($imdb['actors'])) : $imdb['actors'];
681
        } elseif (! empty($omdb['actors'])) {
682
            $mov['actors'] = \is_array($omdb['actors']) ? implode(', ', array_unique($omdb['actors'])) : $omdb['actors'];
683
        } elseif (! empty($tmdb['actors'])) {
684
            $mov['actors'] = \is_array($tmdb['actors']) ? implode(', ', array_unique($tmdb['actors'])) : $tmdb['actors'];
685
        }
686
687
        if (! empty($imdb['language'])) {
688
            $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...
689
        } elseif (! empty($omdb['language'])) {
690
            $mov['language'] = \is_array($imdb['language']) ? implode(', ', array_unique($omdb['language'])) : $omdb['language'];
691
        }
692
693
        if (\is_array($mov['genre'])) {
694
            $mov['genre'] = implode(', ', array_unique($mov['genre']));
695
        }
696
697
        if (\is_array($mov['type'])) {
698
            $mov['type'] = implode(', ', array_unique($mov['type']));
699
        }
700
701
        $mov['title'] = html_entity_decode($mov['title'], ENT_QUOTES, 'UTF-8');
702
703
        $mov['title'] = str_replace(['/', '\\'], '', $mov['title']);
704
        $movieID = $this->update([
705
            'actors'    => html_entity_decode($mov['actors'], ENT_QUOTES, 'UTF-8'),
706
            'backdrop'  => $mov['backdrop'],
707
            'cover'     => $mov['cover'],
708
            'director'  => html_entity_decode($mov['director'], ENT_QUOTES, 'UTF-8'),
709
            'genre'     => html_entity_decode($mov['genre'], ENT_QUOTES, 'UTF-8'),
710
            'imdbid'    => $mov['imdbid'],
711
            'language'  => html_entity_decode($mov['language'], ENT_QUOTES, 'UTF-8'),
712
            'plot'      => html_entity_decode(preg_replace('/\s+See full summary »/u', ' ', $mov['plot']), ENT_QUOTES, 'UTF-8'),
713
            'rating'    => round($mov['rating'], 1),
714
            'rtrating' => $mov['rtrating'] ?? 'N/A',
715
            'tagline'   => html_entity_decode($mov['tagline'], ENT_QUOTES, 'UTF-8'),
716
            'title'     => $mov['title'],
717
            'tmdbid'    => $mov['tmdbid'],
718
            'traktid'   => $mov['traktid'],
719
            'type'      => html_entity_decode(ucwords(preg_replace('/[\.\_]/', ' ', $mov['type'])), ENT_QUOTES, 'UTF-8'),
720
            'year'      => $mov['year'],
721
        ]);
722
723
        if ($this->echooutput && $this->service !== '' && Utility::isCLI()) {
724
            $this->colorCli->headerOver('Added/updated movie: ').
0 ignored issues
show
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

724
            /** @scrutinizer ignore-type */ $this->colorCli->headerOver('Added/updated movie: ').
Loading history...
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...
725
                $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...
726
                    $mov['title'].
727
                    ' ('.
728
                    $mov['year'].
729
                    ') - '.
730
                    $mov['imdbid']
731
                );
732
        }
733
734
        return (int) $movieID > 0;
735
    }
736
737
    /**
738
     * Fetch FanArt.tv backdrop / cover / title.
739
     *
740
     * @param $imdbId
741
     *
742
     * @return array|false
743
     */
744
    protected function fetchFanartTVProperties($imdbId)
745
    {
746
        if ($this->fanartapikey !== null) {
747
            $art = $this->fanart->getMovieFanArt('tt'.$imdbId);
748
749
            if (! empty($art)) {
750
                if (isset($art['status']) && $art['status'] === 'error') {
751
                    return false;
752
                }
753
                $ret = [];
754
                if (! empty($art['moviebackground'][0]['url'])) {
755
                    $ret['backdrop'] = $art['moviebackground'][0]['url'];
756
                } elseif (! empty($art['moviethumb'][0]['url'])) {
757
                    $ret['backdrop'] = $art['moviethumb'][0]['url'];
758
                }
759
                if (! empty($art['movieposter'][0]['url'])) {
760
                    $ret['cover'] = $art['movieposter'][0]['url'];
761
                }
762
                if (! empty($art['moviebanner'][0]['url'])) {
763
                    $ret['banner'] = $art['moviebanner'][0]['url'];
764
                }
765
766
                if (isset($ret['backdrop'], $ret['cover'])) {
767
                    $ret['title'] = $imdbId;
768
                    if (isset($art['name'])) {
769
                        $ret['title'] = $art['name'];
770
                    }
771
                    if ($this->echooutput && Utility::isCLI()) {
772
                        $this->colorCli->alternateOver('Fanart Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->colorCli->alternateOver('Fanart Found ') targeting Blacklight\ColorCLI::alternateOver() 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->alternateOver('Fanart Found ') 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

772
                        /** @scrutinizer ignore-type */ $this->colorCli->alternateOver('Fanart Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL;
Loading history...
Bug introduced by
Are you sure the usage of $this->colorCli->headerOver($ret['title']) 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...
773
                    }
774
775
                    return $ret;
776
                }
777
            }
778
        }
779
780
        return false;
781
    }
782
783
    /**
784
     * Fetch info for IMDB id from TMDB.
785
     *
786
     *
787
     * @param      $imdbId
788
     * @param bool $text
789
     *
790
     * @return array|false
791
     */
792
    public function fetchTMDBProperties($imdbId, $text = false)
793
    {
794
        $lookupId = $text === false && (\strlen($imdbId) === 7 || strlen($imdbId) === 8) ? 'tt'.$imdbId : $imdbId;
795
796
        try {
797
            $tmdbLookup = Tmdb::getMoviesApi()->getMovie($lookupId, ['append_to_response' => 'credits']);
798
        } catch (TmdbApiException $error) {
799
            if (Utility::isCLI()) {
800
                $this->colorCli->error($error->getMessage());
801
            }
802
803
            return false;
804
        }
805
806
        if (! empty($tmdbLookup)) {
807
            if ($this->currentTitle !== '') {
808
                // Check the similarity.
809
                similar_text($this->currentTitle, $tmdbLookup['title'], $percent);
810
                if ($percent < self::MATCH_PERCENT) {
811
                    return false;
812
                }
813
            }
814
815
            if ($this->currentYear !== '') {
816
                // Check the similarity.
817
                similar_text($this->currentYear, Carbon::parse($tmdbLookup['release_date'])->year, $percent);
818
                if ($percent < self::YEAR_MATCH_PERCENT) {
819
                    return false;
820
                }
821
            }
822
823
            $ret = [];
824
            $ret['title'] = $tmdbLookup['title'];
825
826
            $ret['tmdbid'] = $tmdbLookup['id'];
827
            $ret['imdbid'] = str_replace('tt', '', $tmdbLookup['imdb_id']);
828
            $vote = $tmdbLookup['vote_average'];
829
            if ($vote !== null) {
830
                $ret['rating'] = (int) $vote === 0 ? '' : $vote;
831
            } else {
832
                $ret['rating'] = '';
833
            }
834
            $actors = Arr::pluck($tmdbLookup['credits']['cast'], 'name');
835
            if (! empty($actors)) {
836
                $ret['actors'] = $actors;
837
            } else {
838
                $ret['actors'] = '';
839
            }
840
            foreach ($tmdbLookup['credits']['crew'] as $crew) {
841
                if ($crew['department'] === 'Directing' && $crew['job'] === 'Director') {
842
                    $ret['director'] = $crew['name'];
843
                }
844
            }
845
            $overview = $tmdbLookup['overview'];
846
            if (! empty($overview)) {
847
                $ret['plot'] = $overview;
848
            } else {
849
                $ret['plot'] = '';
850
            }
851
            $tagline = $tmdbLookup['tagline'];
852
853
            $ret['tagline'] = $tagline ?? '';
854
855
            $released = $tmdbLookup['release_date'];
856
            if (! empty($released)) {
857
                $ret['year'] = Carbon::parse($released)->year;
858
            } else {
859
                $ret['year'] = '';
860
            }
861
            $genresa = $tmdbLookup['genres'];
862
            if (! empty($genresa) && \count($genresa) > 0) {
863
                $genres = [];
864
                foreach ($genresa as $genre) {
865
                    $genres[] = $genre['name'];
866
                }
867
                $ret['genre'] = $genres;
868
            } else {
869
                $ret['genre'] = '';
870
            }
871
            $posterp = $tmdbLookup['poster_path'];
872
            if (! empty($posterp)) {
873
                $ret['cover'] = 'https://image.tmdb.org/t/p/original'.$posterp;
874
            } else {
875
                $ret['cover'] = '';
876
            }
877
            $backdrop = $tmdbLookup['backdrop_path'];
878
            if (! empty($backdrop)) {
879
                $ret['backdrop'] = 'https://image.tmdb.org/t/p/original'.$backdrop;
880
            } else {
881
                $ret['backdrop'] = '';
882
            }
883
            if ($this->echooutput && Utility::isCLI()) {
884
                $this->colorCli->primaryOver('TMDb Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->colorCli->headerOver($ret['title']) 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->primaryOver('TMDb Found ') 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

884
                /** @scrutinizer ignore-type */ $this->colorCli->primaryOver('TMDb Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL;
Loading history...
Bug introduced by
Are you sure the usage of $this->colorCli->primaryOver('TMDb Found ') targeting Blacklight\ColorCLI::primaryOver() 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...
885
            }
886
887
            return $ret;
888
        }
889
890
        return false;
891
    }
892
893
    /**
894
     * @param $imdbId
895
     *
896
     * @return array|false
897
     */
898
    public function fetchIMDBProperties($imdbId)
899
    {
900
        $realId = (new Title($imdbId, $this->config))->real_id();
901
        $result = new Title($realId, $this->config);
902
        $title = ! empty($result->orig_title()) ? $result->orig_title() : $result->title();
903
        if (! empty($title)) {
904
            if (! empty($this->currentTitle)) {
905
                similar_text($this->currentTitle, $title, $percent);
906
                if ($percent >= self::MATCH_PERCENT) {
907
                    similar_text($this->currentYear, $result->year(), $percent);
908
                    if ($percent >= self::YEAR_MATCH_PERCENT) {
909
                        $ret = [
910
                            'title' => $title,
911
                            'tagline' => $result->tagline() ?? '',
912
                            'plot' => Arr::get($result->plot_split(), '0.plot'),
913
                            'rating' => ! empty($result->rating()) ? $result->rating() : '',
914
                            'year' => $result->year() ?? '',
915
                            'cover' => $result->photo() ?? '',
916
                            'genre' => $result->genre() ?? '',
917
                            'language' => $result->language() ?? '',
918
                            'type' => $result->movietype() ?? '',
919
                        ];
920
921
                        if ($this->echooutput && Utility::isCLI()) {
922
                            $this->colorCli->headerOver('IMDb Found ').$this->colorCli->primaryOver($title).PHP_EOL;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->colorCli->headerOver('IMDb Found ') 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 the usage of $this->colorCli->primaryOver($title) targeting Blacklight\ColorCLI::primaryOver() 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->headerOver('IMDb Found ') 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

922
                            /** @scrutinizer ignore-type */ $this->colorCli->headerOver('IMDb Found ').$this->colorCli->primaryOver($title).PHP_EOL;
Loading history...
923
                        }
924
925
                        return $ret;
926
                    }
927
928
                    return false;
929
                }
930
931
                return false;
932
            }
933
934
            $ret = [
935
                'title' => $title,
936
                'tagline' => $result->tagline() ?? '',
937
                'plot' => Arr::get($result->plot_split(), '0.plot'),
938
                'rating' => ! empty($result->rating()) ? $result->rating() : '',
939
                'year' => $result->year() ?? '',
940
                'cover' => $result->photo() ?? '',
941
                'genre' => $result->genre() ?? '',
942
                'language' => $result->language() ?? '',
943
                'type' => $result->movietype() ?? '',
944
            ];
945
946
            return $ret;
947
        }
948
949
        return false;
950
    }
951
952
    /**
953
     * Fetch TraktTV backdrop / cover / title.
954
     *
955
     * @param $imdbId
956
     *
957
     * @return array|false
958
     * @throws \Exception
959
     */
960
    public function fetchTraktTVProperties($imdbId)
961
    {
962
        if ($this->traktcheck !== null) {
963
            $resp = $this->traktTv->client->movieSummary('tt'.$imdbId, 'full');
964
            if ($resp !== false) {
965
                similar_text($this->currentTitle, $resp['title'], $percent);
966
                if ($percent >= self::MATCH_PERCENT) {
967
                    similar_text($this->currentYear, $resp['year'], $percent);
968
                    if ($percent >= self::YEAR_MATCH_PERCENT) {
969
                        $ret = [];
970
                        if (isset($resp['ids']['trakt'])) {
971
                            $ret['id'] = $resp['ids']['trakt'];
972
                        }
973
974
                        $ret['overview'] = $resp['overview'] ?? '';
975
                        $ret['tagline'] = $resp['tagline'] ?? '';
976
977
                        if (isset($resp['title'])) {
978
                            $ret['title'] = $resp['title'];
979
                        } else {
980
                            return false;
981
                        }
982
                        if ($this->echooutput && Utility::isCLI()) {
983
                            $this->colorCli->alternateOver('Trakt Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL;
0 ignored issues
show
Bug introduced by
Are you sure $this->colorCli->alternateOver('Trakt Found ') 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

983
                            /** @scrutinizer ignore-type */ $this->colorCli->alternateOver('Trakt Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL;
Loading history...
Bug introduced by
Are you sure the usage of $this->colorCli->alternateOver('Trakt Found ') targeting Blacklight\ColorCLI::alternateOver() 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 the usage of $this->colorCli->headerOver($ret['title']) 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...
984
                        }
985
986
                        return $ret;
987
                    }
988
989
                    return false;
990
                }
991
992
                return false;
993
            }
994
995
            return false;
996
        }
997
998
        return false;
999
    }
1000
1001
    /**
1002
     * Fetch OMDb backdrop / cover / title.
1003
     *
1004
     * @param $imdbId
1005
     *
1006
     * @return array|false
1007
     */
1008
    public function fetchOmdbAPIProperties($imdbId)
1009
    {
1010
        if ($this->omdbapikey !== null) {
1011
            $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

1011
            /** @scrutinizer ignore-call */ 
1012
            $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...
1012
1013
            if (\is_object($resp) && $resp->message === 'OK' && $resp->data->Response !== 'False') {
0 ignored issues
show
introduced by
The condition is_object($resp) is always false.
Loading history...
1014
                similar_text($this->currentTitle, $resp->data->Title, $percent);
1015
                if ($percent >= self::MATCH_PERCENT) {
1016
                    similar_text($this->currentYear, $resp->data->Year, $percent);
1017
                    if ($percent >= self::YEAR_MATCH_PERCENT) {
1018
                        $ret = [
1019
                            'title' => $resp->data->Title ?? '',
1020
                            'cover' => $resp->data->Poster ?? '',
1021
                            'genre' => $resp->data->Genre ?? '',
1022
                            'year' => $resp->data->Year ?? '',
1023
                            'plot' => $resp->data->Plot ?? '',
1024
                            'rating' => $resp->data->imdbRating ?? '',
1025
                            'rtRating' => $resp->data->Ratings[1]->Value ?? '',
1026
                            'tagline' => $resp->data->Tagline ?? '',
1027
                            'director' => $resp->data->Director ?? '',
1028
                            'actors' => $resp->data->Actors ?? '',
1029
                            'language' => $resp->data->Language ?? '',
1030
                            'boxOffice' => $resp->data->BoxOffice ?? '',
1031
                        ];
1032
1033
                        if ($this->echooutput && Utility::isCLI()) {
1034
                            $this->colorCli->alternateOver('OMDbAPI Found ').$this->colorCli->headerOver($ret['title']).PHP_EOL;
1035
                        }
1036
1037
                        return $ret;
1038
                    }
1039
1040
                    return false;
1041
                }
1042
1043
                return false;
1044
            }
1045
1046
            return false;
1047
        }
1048
1049
        return false;
1050
    }
1051
1052
    /**
1053
     * @param string $title
1054
     *
1055
     * @return array|bool
1056
     * @throws \DariusIII\ItunesApi\Exceptions\InvalidProviderException
1057
     * @throws \Exception
1058
     */
1059
    public function fetchItunesMovieProperties($title)
1060
    {
1061
        $movie = true;
1062
        try {
1063
            $iTunesMovie = iTunes::load('movie')->fetchOneByName($title);
1064
        } catch (MovieNotFoundException $e) {
1065
            $movie = false;
1066
        } catch (SearchNoResultsException $e) {
1067
            $movie = false;
1068
        }
1069
1070
        if ($movie !== false) {
1071
            similar_text($this->currentTitle, $iTunesMovie->getName(), $percent);
1072
            if ($percent >= self::MATCH_PERCENT) {
1073
                $movie = [
1074
                    'title' => $iTunesMovie->getName(),
1075
                    '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

1075
                    'director' => $iTunesMovie->/** @scrutinizer ignore-call */ getDirector() ?? '',
Loading history...
1076
                    '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

1076
                    'tagline' => $iTunesMovie->/** @scrutinizer ignore-call */ getTagLine() ?? '',
Loading history...
1077
                    '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

1077
                    'cover' => str_replace('100x100', '800x800', $iTunesMovie->/** @scrutinizer ignore-call */ getCover()),
Loading history...
1078
                    '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

1078
                    'genre' => $iTunesMovie->/** @scrutinizer ignore-call */ getGenre() ?? '',
Loading history...
1079
                    '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

1079
                    'plot' => $iTunesMovie->/** @scrutinizer ignore-call */ getDescription() ?? '',
Loading history...
1080
                    '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

1080
                    'year' => $iTunesMovie->/** @scrutinizer ignore-call */ getReleaseDate() ? $iTunesMovie->getReleaseDate()->format('Y') : '',
Loading history...
1081
                ];
1082
            } else {
1083
                $movie = false;
1084
            }
1085
        }
1086
1087
        return $movie;
1088
    }
1089
1090
    /**
1091
     * Update a release with a IMDB id.
1092
     *
1093
     * @param string $buffer Data to parse a IMDB id/Trakt Id from.
1094
     * @param string $service Method that called this method.
1095
     * @param int $id id of the release.
1096
     * @param int $processImdb To get IMDB info on this IMDB id or not.
1097
     *
1098
     * @return string
1099
     * @throws \Exception
1100
     */
1101
    public function doMovieUpdate($buffer, $service, $id, $processImdb = 1): string
1102
    {
1103
        $imdbID = false;
1104
        if (\is_string($buffer) && preg_match('/(?:imdb.*?)?(?:tt|Title\?)(?P<imdbid>\d{5,7})/i', $buffer, $matches)) {
1105
            $imdbID = $matches['imdbid'];
1106
        }
1107
1108
        if ($imdbID !== false) {
1109
            $this->service = $service;
1110
            if ($this->echooutput && $this->service !== '' && Utility::isCLI()) {
1111
                $this->colorCli->primary($this->service.' found IMDBid: tt'.$imdbID, true);
1112
            }
1113
1114
            $movieInfoId = MovieInfo::query()->where('imdbid', $imdbID)->first(['id']);
1115
1116
            Release::query()->where('id', $id)->update(['imdbid' =>$imdbID, 'movieinfo_id' => $movieInfoId !== null ? $movieInfoId['id'] : null]);
1117
1118
            // If set, scan for imdb info.
1119
            if ($processImdb === 1) {
1120
                $movCheck = $this->getMovieInfo($imdbID);
1121
                if ($movCheck === null || (isset($movCheck['updated_at']) && (time() - strtotime($movCheck['updated_at'])) > 2592000)) {
0 ignored issues
show
Bug introduced by
It seems like $movCheck['updated_at'] can also be of type Illuminate\Database\Eloq...uent\Relations\Relation and Illuminate\Database\Eloquent\Relations\Relation; however, parameter $time of strtotime() 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

1121
                if ($movCheck === null || (isset($movCheck['updated_at']) && (time() - strtotime(/** @scrutinizer ignore-type */ $movCheck['updated_at'])) > 2592000)) {
Loading history...
1122
                    $info = $this->updateMovieInfo($imdbID);
1123
                    if ($info === false) {
1124
                        Release::query()->where('id', $id)->update(['imdbid' => 0000000]);
1125
                    } elseif ($info === true) {
0 ignored issues
show
introduced by
The condition $info === true is always true.
Loading history...
1126
                        $movieInfoId = MovieInfo::query()->where('imdbid', $imdbID)->first(['id']);
1127
1128
                        Release::query()->where('id', $id)->update(['imdbid' =>$imdbID, 'movieinfo_id' => $movieInfoId !== null ? $movieInfoId['id'] : null]);
1129
                    }
1130
                }
1131
            }
1132
        }
1133
1134
        return $imdbID;
1135
    }
1136
1137
    /**
1138
     * Process releases with no IMDB id's.
1139
     *
1140
     *
1141
     * @param string $groupID
1142
     * @param string $guidChar
1143
     * @param int $lookupIMDB
1144
     * @throws \Exception
1145
     */
1146
    public function processMovieReleases($groupID = '', $guidChar = '', $lookupIMDB = 1): void
1147
    {
1148
        if ($lookupIMDB === 0) {
1149
            return;
1150
        }
1151
1152
        // Get all releases without an IMDB id.
1153
        $sql = Release::query()
1154
            ->select(['searchname', 'id'])
1155
            ->whereBetween('categories_id', [Category::MOVIE_ROOT, Category::MOVIE_OTHER])
1156
            ->whereNull('imdbid')
1157
            ->where('nzbstatus', '=', 1);
1158
        if ($groupID !== '') {
1159
            $sql->where('groups_id', $groupID);
1160
        }
1161
1162
        if ($guidChar !== '') {
1163
            $sql->where('leftguid', $guidChar);
1164
        }
1165
1166
        if ((int) $lookupIMDB === 2) {
1167
            $sql->where('isrenamed', '=', 1);
1168
        }
1169
1170
        $res = $sql->limit($this->movieqty)->get();
1171
1172
        $movieCount = \count($res);
1173
1174
        if ($movieCount > 0) {
1175
            if ($this->echooutput && $movieCount > 1) {
1176
                $this->colorCli->header('Processing '.$movieCount.' movie releases.');
1177
            }
1178
1179
            // Loop over releases.
1180
            foreach ($res as $arr) {
1181
                // Try to get a name/year.
1182
                if ($this->parseMovieSearchName($arr['searchname']) === false) {
1183
                    //We didn't find a name, so set to all 0's so we don't parse again.
1184
                    Release::query()->where('id', $arr['id'])->update(['imdbid' => 0000000]);
1185
                    continue;
1186
                }
1187
                $this->currentRelID = $arr['id'];
1188
1189
                $movieName = $this->currentTitle;
1190
                if ($this->currentYear !== '') {
1191
                    $movieName .= ' ('.$this->currentYear.')';
1192
                }
1193
1194
                if ($this->echooutput && Utility::isCLI()) {
1195
                    $this->colorCli->primaryOver('Looking up: ').$this->colorCli->headerOver($movieName);
0 ignored issues
show
Bug introduced by
Are you sure $this->colorCli->primaryOver('Looking up: ') 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

1195
                    /** @scrutinizer ignore-type */ $this->colorCli->primaryOver('Looking up: ').$this->colorCli->headerOver($movieName);
Loading history...
Bug introduced by
Are you sure the usage of $this->colorCli->headerOver($movieName) 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 the usage of $this->colorCli->primaryOver('Looking up: ') targeting Blacklight\ColorCLI::primaryOver() 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...
1196
                }
1197
1198
                $movieUpdated = false;
1199
1200
                // Check local DB.
1201
                $getIMDBid = $this->localIMDBSearch();
1202
1203
                if ($getIMDBid !== false) {
1204
                    $imdbID = $this->doMovieUpdate('tt'.$getIMDBid, 'Local DB', $arr['id']);
1205
                    if ($imdbID !== false) {
1206
                        $movieUpdated = true;
1207
                    }
1208
                }
1209
1210
                // Check on IMDb first
1211
                if ($movieUpdated === false) {
1212
                    $imdbSearch = new TitleSearch($this->config);
1213
                    foreach ($imdbSearch->search($this->currentTitle, [TitleSearch::MOVIE]) as $imdbTitle) {
1214
                        if (! empty($imdbTitle->orig_title())) {
1215
                            similar_text($imdbTitle->orig_title(), $this->currentTitle, $percent);
1216
                            if ($percent >= self::MATCH_PERCENT) {
1217
                                similar_text($this->currentYear, $imdbTitle->year(), $percent);
1218
                                if ($percent >= self::YEAR_MATCH_PERCENT) {
1219
                                    $getIMDBid = $imdbTitle->imdbid();
1220
                                    $imdbID = $this->doMovieUpdate('tt'.$getIMDBid, 'IMDb', $arr['id']);
1221
                                    if ($imdbID !== false) {
1222
                                        $movieUpdated = true;
1223
                                    }
1224
                                }
1225
                            }
1226
                        }
1227
                    }
1228
                }
1229
1230
                // Check on OMDbAPI
1231
                if ($movieUpdated === false) {
1232
                    $omdbTitle = strtolower(str_replace(' ', '_', $this->currentTitle));
1233
                    if ($this->omdbapikey !== null) {
1234
                        $buffer = $this->omdbApi->search($omdbTitle, 'movie');
1235
1236
                        if (\is_object($buffer) && $buffer->message === 'OK' && $buffer->data->Response === 'True') {
1237
                            $getIMDBid = $buffer->data->Search[0]->imdbID;
1238
1239
                            if (! empty($getIMDBid)) {
1240
                                $imdbID = $this->doMovieUpdate($getIMDBid, 'OMDbAPI', $arr['id']);
1241
                                if ($imdbID !== false) {
1242
                                    $movieUpdated = true;
1243
                                }
1244
                            }
1245
                        }
1246
                    }
1247
                }
1248
1249
                // Check on Trakt.
1250
                if ($movieUpdated === false && $this->traktcheck !== null) {
1251
                    $data = $this->traktTv->client->movieSummary($movieName, 'full');
1252
                    if ($data !== false) {
1253
                        $this->parseTraktTv($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type string; however, parameter $data of Blacklight\Movie::parseTraktTv() does only seem to accept 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

1253
                        $this->parseTraktTv(/** @scrutinizer ignore-type */ $data);
Loading history...
1254
                        if (! empty($data['ids']['imdb'])) {
1255
                            $imdbID = $this->doMovieUpdate($data['ids']['imdb'], 'Trakt', $arr['id']);
1256
                            if ($imdbID !== false) {
1257
                                $movieUpdated = true;
1258
                            }
1259
                        }
1260
                    }
1261
                }
1262
1263
                // Check on The Movie Database.
1264
                if ($movieUpdated === false) {
1265
                    $data = Tmdb::getSearchApi()->searchMovies($this->currentTitle);
1266
                    if (($data['total_results'] > 0) && ! empty($data['results'])) {
1267
                        foreach ($data['results'] as $result) {
1268
                            if (! empty($result['id']) && ! empty($result['release_date'])) {
1269
                                similar_text($this->currentYear, Carbon::parse($result['release_date'])->year, $percent);
1270
                                if ($percent >= self::YEAR_MATCH_PERCENT) {
1271
                                    $ret = $this->fetchTMDBProperties($result['id'], true);
1272
                                    if ($ret !== false && ! empty($ret['imdbid'])) {
1273
                                        $imdbID = $this->doMovieUpdate('tt'.$ret['imdbid'], 'TMDB', $arr['id']);
1274
                                        if ($imdbID !== false) {
1275
                                            $movieUpdated = true;
1276
                                        }
1277
                                    }
1278
                                }
1279
                            } else {
1280
                                $movieUpdated = false;
1281
                            }
1282
                        }
1283
                    } else {
1284
                        $movieUpdated = false;
1285
                    }
1286
                }
1287
1288
                // We failed to get an IMDB id from all sources.
1289
                if ($movieUpdated === false) {
1290
                    Release::query()->where('id', $arr['id'])->update(['imdbid' => 0000000]);
1291
                }
1292
            }
1293
        }
1294
    }
1295
1296
    /**
1297
     * @return mixed|false
1298
     */
1299
    protected function localIMDBSearch()
1300
    {
1301
        //If we found a year, try looking in a 4 year range.
1302
        $check = MovieInfo::query()
1303
            ->where('title', 'like', '%'.$this->currentTitle.'%');
1304
1305
        if ($this->currentYear !== '') {
1306
            $start = Carbon::parse($this->currentYear)->subYears(2)->year;
1307
            $end = Carbon::parse($this->currentYear)->addYears(2)->year;
1308
            $check->whereBetween('year', [$start, $end]);
1309
        }
1310
        $IMDBCheck = $check->first(['imdbid']);
1311
1312
        return $IMDBCheck === null ? false : $IMDBCheck->imdbid;
1313
    }
1314
1315
    /**
1316
     * Parse a movie name from a release search name.
1317
     *
1318
     * @param string $releaseName
1319
     *
1320
     * @return bool
1321
     */
1322
    protected function parseMovieSearchName($releaseName): bool
1323
    {
1324
        $name = $year = '';
1325
        $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]';
1326
1327
        /* Initial scan of getting a year/name.
1328
         * [\w. -]+ Gets 0-9a-z. - characters, most scene movie titles contain these chars.
1329
         * ie: [61420]-[FULL]-[a.b.foreignEFNet]-[ Coraline.2009.DUTCH.INTERNAL.1080p.BluRay.x264-VeDeTT ]-[21/85] - "vedett-coralien-1080p.r04" yEnc
1330
         * Then we look up the year, (19|20)\d\d, so $matches[1] would be Coraline $matches[2] 2009
1331
         */
1332
        if (preg_match('/(?P<name>[\w. -]+)[^\w](?P<year>(19|20)\d\d)/i', $releaseName, $matches)) {
1333
            $name = $matches['name'];
1334
            $year = $matches['year'];
1335
1336
        /* If we didn't find a year, try to get a name anyways.
1337
         * Try to look for a title before the $followingList and after anything but a-z0-9 two times or more (-[ for example)
1338
         */
1339
        } elseif (preg_match('/([^\w]{2,})?(?P<name>[\w .-]+?)'.$followingList.'/i', $releaseName, $matches)) {
1340
            $name = $matches['name'];
1341
        }
1342
1343
        // Check if we got something.
1344
        if ($name !== '') {
1345
1346
            // If we still have any of the words in $followingList, remove them.
1347
            $name = preg_replace('/'.$followingList.'/i', ' ', $name);
1348
            // Remove periods, underscored, anything between parenthesis.
1349
            $name = preg_replace('/\(.*?\)|[._]/i', ' ', $name);
1350
            // Finally remove multiple spaces and trim leading spaces.
1351
            $name = trim(preg_replace('/\s{2,}/', ' ', $name));
1352
            // Check if the name is long enough and not just numbers.
1353
            if (\strlen($name) > 4 && ! preg_match('/^\d+$/', $name)) {
1354
                $this->currentTitle = $name;
1355
                $this->currentYear = $year;
1356
1357
                return true;
1358
            }
1359
        }
1360
1361
        return false;
1362
    }
1363
1364
    /**
1365
     * Get IMDB genres.
1366
     *
1367
     * @return array
1368
     */
1369
    public function getGenres(): array
1370
    {
1371
        return [
1372
            'Action',
1373
            'Adventure',
1374
            'Animation',
1375
            'Biography',
1376
            'Comedy',
1377
            'Crime',
1378
            'Documentary',
1379
            'Drama',
1380
            'Family',
1381
            'Fantasy',
1382
            'Film-Noir',
1383
            'Game-Show',
1384
            'History',
1385
            'Horror',
1386
            'Music',
1387
            'Musical',
1388
            'Mystery',
1389
            'News',
1390
            'Reality-TV',
1391
            'Romance',
1392
            'Sci-Fi',
1393
            'Sport',
1394
            'Talk-Show',
1395
            'Thriller',
1396
            'War',
1397
            'Western',
1398
        ];
1399
    }
1400
}
1401