19
app/Actions/People/DeletePeople.php
Executable file
19
app/Actions/People/DeletePeople.php
Executable file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\People;
|
||||
|
||||
use App\Models\Person;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DeletePeople
|
||||
{
|
||||
public function execute(array $ids): void
|
||||
{
|
||||
Person::withoutGlobalScope('adult')
|
||||
->whereIn('id', $ids)
|
||||
->delete();
|
||||
DB::table('creditables')
|
||||
->whereIn('person_id', $ids)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
283
app/Actions/People/GetPersonCredits.php
Executable file
283
app/Actions/People/GetPersonCredits.php
Executable file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\People;
|
||||
|
||||
use App\Models\Person;
|
||||
use App\Models\Title;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GetPersonCredits
|
||||
{
|
||||
private ?int $titleId;
|
||||
|
||||
public function execute(Person $person, $options = []): array
|
||||
{
|
||||
$this->titleId = Arr::get($options, 'titleId');
|
||||
$credits = $this->titleId ? [] : $this->getTitleCredits($person);
|
||||
$seasonCredits = $this->getSeasonCredits($person);
|
||||
$episodeCredits = $this->getEpisodeCredits($person);
|
||||
|
||||
$mergedCredits = $this->mergeCredits(
|
||||
Arr::get($credits, 'all', []),
|
||||
$seasonCredits,
|
||||
$episodeCredits,
|
||||
);
|
||||
$mergedCredits = $this->separateSelfCreditsAndSort($mergedCredits);
|
||||
|
||||
return [
|
||||
'credits' => $mergedCredits,
|
||||
'knownFor' => Arr::get($credits, 'knownFor', []),
|
||||
'total_credits_count' => array_reduce(
|
||||
$mergedCredits,
|
||||
fn($carry, $item) => $carry + count($item),
|
||||
0,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private function mergeCredits($credits1, $credits2, $credits3): array
|
||||
{
|
||||
$mergedCredits = array_merge_recursive($credits1, $credits2, $credits3);
|
||||
|
||||
return array_map(function ($titles) {
|
||||
// sort titles by year
|
||||
usort($titles, fn($a, $b) => $b['year'] - $a['year']);
|
||||
|
||||
$unique = [];
|
||||
|
||||
// if this title already exists and existing
|
||||
// title has episodes property, continue,
|
||||
// otherwise push title into 'unique' array
|
||||
foreach ($titles as $title) {
|
||||
$existing = Arr::get($unique, $title['id']);
|
||||
if ($existing) {
|
||||
$existing['credited_episode_count'] =
|
||||
Arr::get($existing, 'credited_episode_count', 0) +
|
||||
Arr::get($title, 'credited_episode_count', 0);
|
||||
$existing['episodes'] = collect(
|
||||
array_merge(
|
||||
Arr::get($existing, 'episodes', []),
|
||||
Arr::get($title, 'episodes', []),
|
||||
),
|
||||
)
|
||||
->unique('id')
|
||||
->toArray();
|
||||
if (!$this->titleId) {
|
||||
$existing['episodes'] = array_slice(
|
||||
$existing['episodes'],
|
||||
0,
|
||||
5,
|
||||
);
|
||||
}
|
||||
$unique[$title['id']] = $existing;
|
||||
} else {
|
||||
$unique[$title['id']] = $title;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($unique);
|
||||
}, $mergedCredits);
|
||||
}
|
||||
|
||||
private function getTitleCredits(Person $person): array
|
||||
{
|
||||
$credits = $person->credits()->get();
|
||||
|
||||
// generate known for list for actors "known_for" department.
|
||||
$allKnownFor = $credits
|
||||
->filter(function (Title $credit) use ($person) {
|
||||
$knownFor =
|
||||
strtolower($person->known_for) === 'acting'
|
||||
? 'actors'
|
||||
: $person->known_for;
|
||||
return $credit->pivot->department === strtolower($knownFor);
|
||||
})
|
||||
->unique();
|
||||
|
||||
$knownFor = $allKnownFor->where('pivot.order', '<', 10);
|
||||
|
||||
if ($knownFor->count() < 4) {
|
||||
$knownFor = $allKnownFor;
|
||||
}
|
||||
|
||||
// sort by person credit "order" for title as well as title popularity
|
||||
$knownFor = $knownFor
|
||||
->sortBy(function ($title) {
|
||||
$order = $title->pivot->order;
|
||||
$popularity = $title->popularity;
|
||||
return $order - $popularity;
|
||||
})
|
||||
->slice(0, 6)
|
||||
->values();
|
||||
|
||||
// cast to array, so poster/backdrop is not removed later.
|
||||
$knownFor = $knownFor->load(['primaryVideo'])->toArray();
|
||||
|
||||
// remove any data not needed to render person filmography
|
||||
$credits = $credits
|
||||
->map(function (Title $credit) {
|
||||
unset($credit['backdrop']);
|
||||
return $credit;
|
||||
})
|
||||
->groupBy('pivot.department');
|
||||
|
||||
return ['all' => $credits->toArray(), 'knownFor' => $knownFor];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credits for all series seasons person is attached to.
|
||||
*/
|
||||
private function getSeasonCredits(Person $person): array
|
||||
{
|
||||
$seasons = $person
|
||||
->seasonCredits($this->titleId)
|
||||
->with([
|
||||
'title' => fn($query) => $query->select(
|
||||
'id',
|
||||
'name',
|
||||
'release_date',
|
||||
'poster',
|
||||
),
|
||||
'episodes' => fn($query) => $query
|
||||
->select(
|
||||
'id',
|
||||
'name',
|
||||
'release_date',
|
||||
'season_id',
|
||||
'season_number',
|
||||
'episode_number',
|
||||
'title_id',
|
||||
)
|
||||
->orderBy('season_number', 'desc')
|
||||
->orderBy('episode_number', 'desc'),
|
||||
])
|
||||
->get();
|
||||
|
||||
// group all seasons by department, for example "production"
|
||||
$groupedSeasons = $seasons->groupBy('pivot.department');
|
||||
|
||||
return $groupedSeasons
|
||||
->map(function (Collection $departmentGroup) {
|
||||
$seasonsGroupedByTitle = $departmentGroup->groupBy('title.id');
|
||||
|
||||
// attach episodes from all seasons to title
|
||||
return $seasonsGroupedByTitle
|
||||
->map(function (Collection $titleSeasons) {
|
||||
$title = $titleSeasons
|
||||
->first(fn($s) => $s->title)
|
||||
->title->toArray();
|
||||
|
||||
//get episodes from each season and move season "pivot" data to each episode
|
||||
$episodesFromAllSeasons = $titleSeasons
|
||||
->pluck('episodes')
|
||||
->flatten()
|
||||
->values()
|
||||
->map(function ($episode) use ($titleSeasons) {
|
||||
$episode->pivot = $titleSeasons
|
||||
->first()
|
||||
->pivot->toArray();
|
||||
return $episode;
|
||||
});
|
||||
$title[
|
||||
'credited_episode_count'
|
||||
] = $episodesFromAllSeasons->count();
|
||||
if (!$this->titleId) {
|
||||
$episodesFromAllSeasons = $episodesFromAllSeasons->take(
|
||||
5,
|
||||
);
|
||||
}
|
||||
$title['episodes'] = $episodesFromAllSeasons->toArray();
|
||||
return $title;
|
||||
})
|
||||
->values();
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all individual episodes person is credited for.
|
||||
*
|
||||
* This will return array grouped by department, and
|
||||
* series with all episodes person is credited for attached
|
||||
* to that series.
|
||||
*
|
||||
* @param Person $person
|
||||
* @return array
|
||||
*/
|
||||
private function getEpisodeCredits(Person $person)
|
||||
{
|
||||
$episodes = $person
|
||||
->episodeCredits($this->titleId)
|
||||
->with([
|
||||
'title' => function (BelongsTo $query) {
|
||||
$query->select('id', 'name', 'release_date', 'poster');
|
||||
},
|
||||
])
|
||||
->get();
|
||||
|
||||
$groupedByDep = $episodes->groupBy('pivot.department');
|
||||
|
||||
return $groupedByDep
|
||||
->map(
|
||||
fn(Collection $episodes) => $episodes
|
||||
->groupBy('title.id')
|
||||
->map(function (Collection $episodes) {
|
||||
if (!$episodes->first()->title) {
|
||||
return null;
|
||||
}
|
||||
$title = $episodes->first()->title->toArray();
|
||||
$episodes = $episodes->map(function ($episode) {
|
||||
unset($episode->title);
|
||||
return $episode;
|
||||
});
|
||||
$title['credited_episode_count'] = $episodes->count();
|
||||
if (!$this->titleId) {
|
||||
$episodes = $episodes->take(5);
|
||||
}
|
||||
$title['episodes'] = $episodes->toArray();
|
||||
return $title;
|
||||
})
|
||||
->filter()
|
||||
->values(),
|
||||
)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
private function separateSelfCreditsAndSort(array $credits): array
|
||||
{
|
||||
if (!isset($credits['cast'])) {
|
||||
return $credits;
|
||||
}
|
||||
|
||||
$cast = [];
|
||||
$self = [];
|
||||
foreach ($credits['cast'] as $credit) {
|
||||
$char = isset($credit['pivot']['character'])
|
||||
? strtolower($credit['pivot']['character'])
|
||||
: null;
|
||||
if (
|
||||
$char &&
|
||||
($char === 'self' || Str::contains($char, 'himself'))
|
||||
) {
|
||||
$self[] = $credit;
|
||||
} else {
|
||||
$cast[] = $credit;
|
||||
}
|
||||
}
|
||||
$credits['cast'] = $cast;
|
||||
|
||||
// sort before adding "self" to array as that should be last always
|
||||
uksort(
|
||||
$credits,
|
||||
fn($a, $b) => (is_countable($credits[$b])
|
||||
? count($credits[$b])
|
||||
: 0) - (is_countable($credits[$a]) ? count($credits[$a]) : 0),
|
||||
);
|
||||
|
||||
$credits['self'] = $self;
|
||||
return $credits;
|
||||
}
|
||||
}
|
||||
74
app/Actions/People/LoadPrimaryCredit.php
Executable file
74
app/Actions/People/LoadPrimaryCredit.php
Executable file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\People;
|
||||
|
||||
use App\Models\Title;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Pagination\AbstractPaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class LoadPrimaryCredit
|
||||
{
|
||||
public function execute(Collection|AbstractPaginator $people): void
|
||||
{
|
||||
$prefix = DB::getTablePrefix();
|
||||
$titleSelect = Title::select([
|
||||
'titles.id',
|
||||
'titles.is_series',
|
||||
'titles.name',
|
||||
DB::raw("{$prefix}creditables.person_id as pivot_person_id"),
|
||||
DB::raw(
|
||||
"{$prefix}creditables.creditable_id as pivot_creditable_id",
|
||||
),
|
||||
DB::raw(
|
||||
"{$prefix}creditables.creditable_type as pivot_creditable_type",
|
||||
),
|
||||
DB::raw("{$prefix}creditables.job as pivot_job"),
|
||||
DB::raw("{$prefix}creditables.department as pivot_department"),
|
||||
DB::raw(
|
||||
"row_number() over (partition by {$prefix}creditables.person_id order by {$prefix}titles.popularity desc) as laravel_row",
|
||||
),
|
||||
])
|
||||
->join('creditables', 'titles.id', '=', 'creditables.creditable_id')
|
||||
->where('creditables.creditable_type', Title::MODEL_TYPE)
|
||||
->whereIn('creditables.person_id', $people->pluck('id'))
|
||||
// this scope will mess up binding merging below
|
||||
->withoutGlobalScope('adult')
|
||||
->when(
|
||||
!config('tmdb.includeAdult'),
|
||||
fn($q) => $q->where('adult', false),
|
||||
);
|
||||
|
||||
// cache syntax error, if mysql version does not support partition
|
||||
try {
|
||||
$items = DB::table(
|
||||
DB::raw("({$titleSelect->toSql()}) as laravel_table"),
|
||||
)
|
||||
->select('*')
|
||||
->mergeBindings($titleSelect->getQuery())
|
||||
->where('laravel_row', '<=', 1)
|
||||
->orderBy('laravel_row')
|
||||
->get();
|
||||
} catch (QueryException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$credits = Title::hydrate($items->toArray());
|
||||
|
||||
$people->each(function ($person) use ($credits) {
|
||||
$credit = $credits->first(
|
||||
fn($credit) => $credit->pivot_person_id === $person->id,
|
||||
);
|
||||
if ($credit) {
|
||||
$person->primary_credit = [
|
||||
'id' => $credit->id,
|
||||
'is_series' => $credit->is_series,
|
||||
'name' => $credit->name,
|
||||
'year' => $credit->year,
|
||||
'model_type' => $credit->model_type,
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
62
app/Actions/People/PaginatePeople.php
Executable file
62
app/Actions/People/PaginatePeople.php
Executable file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\People;
|
||||
|
||||
use App\Models\Person;
|
||||
use Common\Database\Datasource\Datasource;
|
||||
use Illuminate\Pagination\AbstractPaginator;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PaginatePeople
|
||||
{
|
||||
public function execute(array $params, $builder = null): AbstractPaginator
|
||||
{
|
||||
$builder = $builder ?? Person::query();
|
||||
$isCompact = Arr::get($params, 'compact');
|
||||
|
||||
if ($isCompact) {
|
||||
$builder->select([
|
||||
'id',
|
||||
'name',
|
||||
'birth_date',
|
||||
'death_date',
|
||||
'poster',
|
||||
'popularity',
|
||||
]);
|
||||
}
|
||||
|
||||
$datasource = new Datasource($builder, $params);
|
||||
|
||||
// prevent duplicate items when ordering by columns that are not
|
||||
// guaranteed to be unique (name, popularity, age etc.)
|
||||
$datasource->secondaryOrderCol = 'id';
|
||||
|
||||
if (!Arr::get($params, 'order') && !Arr::get($params, 'orderBy')) {
|
||||
$datasource->order = [
|
||||
'col' => 'popularity',
|
||||
'dir' => 'desc',
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
$datasource->getOrder()['col'] === 'popularity' &&
|
||||
($min = config('content.people_index_min_popularity'))
|
||||
) {
|
||||
$builder->where('popularity', '>', $min);
|
||||
}
|
||||
|
||||
$pagination = $datasource->paginate();
|
||||
|
||||
if (!$isCompact) {
|
||||
$pagination->transform(function (Person $person) {
|
||||
$person->description = Str::limit($person->description, 500);
|
||||
return $person;
|
||||
});
|
||||
|
||||
//app(LoadPrimaryCredit::class)->execute($pagination);
|
||||
}
|
||||
|
||||
return $pagination;
|
||||
}
|
||||
}
|
||||
68
app/Actions/People/StorePersonData.php
Executable file
68
app/Actions/People/StorePersonData.php
Executable file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\People;
|
||||
|
||||
use App\Actions\Titles\Store\StoreCredits;
|
||||
use App\Actions\Titles\StoresMediaImages;
|
||||
use App\Models\Person;
|
||||
|
||||
class StorePersonData
|
||||
{
|
||||
use StoresMediaImages;
|
||||
|
||||
private ?Person $person = null;
|
||||
|
||||
private ?array $data = null;
|
||||
|
||||
public function execute(Person $person, array $data): Person
|
||||
{
|
||||
$this->person = $person;
|
||||
$this->data = $data;
|
||||
|
||||
$this->persistData();
|
||||
$this->persistRelations();
|
||||
|
||||
return $this->person;
|
||||
}
|
||||
|
||||
private function persistData(): void
|
||||
{
|
||||
$personData = array_filter($this->data, function (
|
||||
$value, // make sure we don't overwrite existing values with null
|
||||
) {
|
||||
if (is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if fully_synced is true, override everything and erase any previously set values.
|
||||
// For example if "death_date" was previously set on a person and tmdb now returns null for "death_date", set "death_date" to null in database.
|
||||
if (
|
||||
config('common.site.tmdb_delete_when_sync') &&
|
||||
$this->data['fully_synced']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if "tmdb_delete_when_sync" is false, don't clear existing values, as values set from admin manually might be erased
|
||||
return !is_null($value);
|
||||
});
|
||||
|
||||
$this->person->fill($personData)->save();
|
||||
}
|
||||
|
||||
private function persistRelations(): void
|
||||
{
|
||||
$relations = array_filter($this->data, fn($value) => is_array($value));
|
||||
|
||||
foreach ($relations as $name => $values) {
|
||||
switch ($name) {
|
||||
case 'credits':
|
||||
app(StoreCredits::class)->execute($this->person, $values);
|
||||
break;
|
||||
case 'images':
|
||||
$this->storeImages($values, $this->person);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user