HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/iphim.naniguide.com/app/Title.php
<?php

namespace App;

use App\Services\Data\Contracts\DataProvider;
use App\Services\Data\Local\LocalDataProvider;
use App\Services\Data\Tmdb\TmdbApi;
use App\Services\Traits\HasCreditableRelation;
use Carbon\Carbon;
use Common\Comments\Comment;
use Common\Search\Searchable;
use Common\Settings\Settings;
use Common\Tags\Tag;
use DB;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Str;

/**
 * Class Title
 * @property integer $id;
 * @property boolean $allow_update;
 * @property boolean $fully_synced;
 * @property string $model_type;
 * @property boolean $series_ended;
 * @property Carbon $updated_at;
 * @property Carbon $release_date;
 * @property-read Collection $keywords;
 * @property-read Collection $genres;
 * @property-read Collection $videos;
 * @property-read Collection $images;
 * @property-read Collection|Season[] $seasons;
 * @property-read int $seasons_count;
 * @property Season $season;
 * @property integer $season_count;
 * @property string|null $tmdb_id;
 * @method whereGenre(string|array $genres);
 */
class Title extends Model
{
    use HasCreditableRelation, Searchable;

    const MOVIE_TYPE = 'movie';
    const SERIES_TYPE = 'series';
    const MODEL_TYPE = 'title';
    const LOCAL_PROVIDER = 'local';

    protected $guarded = ['id', 'type'];
    protected $dates = ['release_date'];
    protected $appends = ['rating', 'model_type', 'vote_count'];

    public $hidden = [
        'imdb_rating',
        'imdb_votes_num',
        'tmdb_vote_average',
        'local_vote_average',
        'local_vote_count',
        'tmdb_vote_count',
        'mc_user_score',
        'mc_critic_score',
    ];

    protected $casts = [
        'id' => 'integer',
        'allow_update' => 'boolean',
        'series_ended' => 'boolean',
        'is_series' => 'boolean',
        'tmdb_vote_count' => 'integer',
        'runtime' => 'integer',
        'views' => 'integer',
        'season_count' => 'integer',
        'episode_count' => 'integer',
        'year' => 'integer',
        'popularity' => 'integer',
        'tmdb_vote_average' => 'float',
        'local_vote_average' => 'float',
        'fully_synced' => 'boolean',
        'adult' => 'boolean',
        'rating' => 'float',
        'vote_count' => 'integer',
        'show_videos' => 'boolean',
        'stream_videos_count' => 'integer',
    ];

    public function needsUpdating($forceAutomation = false)
    {
        // auto update disabled in settings
        if (
            !$forceAutomation &&
            app(Settings::class)->get('content.title_provider') ===
                Title::LOCAL_PROVIDER
        ) {
            return false;
        }

        // title was never synced from external site
        if (!$this->exists || ($this->allow_update && !$this->fully_synced)) {
            return true;
        }

        // series ended and fully synced
        //if ($this->series_ended) return false;

        if (!$this->release_date) {
            return true;
        }

        // title was released over a month ago, no need to sync anymore
        //if ($this->release_date->lessThan(Carbon::now()->subMonth())) return false;

        // only partial data was fetched
        if (
            !$this->runtime &&
            !$this->revenue &&
            !$this->country &&
            !$this->budget &&
            !$this->imdb_id
        ) {
            return true;
        }

        // sync every week
        return $this->allow_update &&
            $this->updated_at->lessThan(Carbon::now()->subWeek());
    }

    /**
     * @param Collection $titles
     * @param string $uniqueKey
     * @return Collection
     */
    public function insertOrRetrieve(Collection $titles, $uniqueKey)
    {
        // TODO: refactor this into a trait so can use in both title and person models
        // (and possible elsewhere (genres, tags) etc)

        // model "guarded" property will not work here, need to
        // manually unset props that have no corresponding db columns
        $titles = $titles->map(function ($value) {
            unset($value['relation_data']);
            unset($value['id']);
            unset($value['model_type']);
            return $value;
        });

        $existing = $this->whereIn($uniqueKey, $titles->pluck($uniqueKey))
            ->get()
            ->mapWithKeys(function ($title) use ($uniqueKey) {
                return [$title[$uniqueKey] => $title];
            });

        $new = $titles->filter(function ($title) use ($existing, $uniqueKey) {
            return !isset($existing[$title[$uniqueKey]]);
        });

        if ($new->isNotEmpty()) {
            $new->transform(function ($title) {
                $title['created_at'] = Arr::get(
                    $title,
                    'created_at',
                    Carbon::now(),
                );
                return $title;
            });
            $this->insert($new->toArray());
            return $this->whereIn(
                $uniqueKey,
                $titles->pluck($uniqueKey),
            )->get();
        } else {
            return $existing;
        }
    }

    /**
     * @return float
     */
    public function getRatingAttribute()
    {
        return Arr::get($this->attributes, config('common.site.rating_column'));
    }

    public function getVoteCountAttribute()
    {
        $column = str_replace(
            '_average',
            '_count',
            config('common.site.rating_column'),
        );
        return Arr::get($this->attributes, $column) ?: 0;
    }

    public function setReleaseDateAttribute($value)
    {
        $this->attributes['release_date'] = $value;

        if ($this->release_date) {
            $this->attributes['year'] = $this->release_date->year;
        }
    }

    /**
     * @return BelongsToMany
     */
    public function genres()
    {
        return $this->morphToMany(Tag::class, 'taggable')
            ->where('tags.type', 'genre')
            ->select('tags.id', 'tags.type', 'name', 'display_name');
    }

    /**
     * @return BelongsToMany
     */
    public function keywords()
    {
        return $this->morphToMany(Tag::class, 'taggable')
            ->where('tags.type', 'keyword')
            ->select('tags.id', 'tags.type', 'name', 'display_name');
    }

    /**
     * @return BelongsToMany
     */
    public function countries()
    {
        return $this->morphToMany(Tag::class, 'taggable')
            ->where('tags.type', 'production_country')
            ->select('tags.id', 'tags.type', 'name', 'display_name');
    }

    /**
     * @return HasMany
     */
    public function videos()
    {
        [$col, $dir] = explode(
            ':',
            app(Settings::class)->get('streaming.default_sort', 'order:asc'),
        );

        $query = $this->hasMany(Video::class);
        if ($col === 'score') {
            $query->selectScore();
        } elseif ($col === 'order') {
            return $query->orderBy(DB::raw('`order` = 0, `order`'), $dir);
        } else {
            return $query->orderBy($col, $dir);
        }
    }

    public function stream_videos()
    {
        [$col, $dir] = explode(
            ':',
            app(Settings::class)->get('streaming.default_sort', 'order:asc'),
        );
        return $this->hasMany(Video::class)
            ->where('approved', true)
            ->where('category', 'full')
            ->orderBy($col, $dir);
    }

    /**
     * @return MorphMany
     */
    public function images()
    {
        return $this->morphMany(Image::class, 'model')
            ->select(['id', 'model_id', 'model_type', 'url', 'type', 'source'])
            ->orderBy('order', 'asc');
    }

    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable')->orderBy(
            'created_at',
            'desc',
        );
    }

    /**
     * @return HasMany
     */
    public function seasons()
    {
        return $this->hasMany(Season::class);
    }

    /**
     * @return HasOne
     */
    public function season()
    {
        return $this->hasOne(Season::class);
    }

    /**
     * @return HasMany
     */
    public function episodes()
    {
        return $this->hasMany(Episode::class);
    }

    /**
     * Get last and next episode to air for this series.
     *
     * @return Collection|Episode[]
     */
    public function getLastAndNextEpisodes()
    {
        $episodes = app(Episode::class)
            ->where('title_id', $this->id)
            ->where('season_number', $this->season_count)
            ->whereBetween('release_date', [
                Carbon::now()->subWeek(2),
                Carbon::now()->addWeek(2),
            ])
            ->whereNotNull('description')
            ->orderBy('release_date', 'desc')
            ->limit(6)
            ->get()
            ->sortBy('release_date')
            ->values();

        if (
            $episodes->count() < 2 ||
            (!$episodes[0]['poster'] && !$episodes[1]['poster'])
        ) {
            return null;
        }

        return collect([
            'next_episode' => $episodes[1],
            'current_episode' => $episodes[0],
        ]);
    }

    /**
     * @param array $params
     * @return DataProvider
     */
    public static function dataProvider($params = [])
    {
        $settings = app(Settings::class);

        // Fetch title seasons, even if automation is disabled because they can't be
        // fetched when importing multiple titles without hitting tmdb api rate limits
        if (
            Arr::get($params, 'forSeason') &&
            $settings->get('content.force_season_update')
        ) {
            return app(TmdbApi::class);
        }

        if (
            app(Settings::class)->get('content.title_provider') !==
            Title::LOCAL_PROVIDER
        ) {
            return app(TmdbApi::class);
        } else {
            return app(LocalDataProvider::class);
        }
    }

    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'original_title' => $this->original_title,
            'release_date' => $this->release_date,
            'popularity' => $this->popularity,
            'created_at' => $this->created_at->timestamp ?? '_null',
            'updated_at' => $this->updated_at->timestamp ?? '_null',
        ];
    }

    public static function filterableFields(): array
    {
        return ['id', 'created_at', 'updated_at', 'release_date', 'popularity'];
    }

    public function toNormalizedArray(): array
    {
        return [
            'id' => $this->id,
            'name' => "$this->name ($this->year)",
            'description' => Str::limit($this->description, 100),
            'image' => $this->poster,
            'model_type' => self::MODEL_TYPE,
        ];
    }

    public static function getModelTypeAttribute(): string
    {
        return self::MODEL_TYPE;
    }
}