From 93e605b40639e251ce727f17551f37e529648869 Mon Sep 17 00:00:00 2001 From: Yassine Doghri <yassine@doghri.fr> Date: Mon, 17 May 2021 17:11:23 +0000 Subject: [PATCH] refactor(persons): move podcast and episode persons models to person model for consistency - fix lazy loading properties + podcast import controller - rename all snake_case variables to camelCase - fix broken routes - refactor Location construction logic and setters --- app/Config/Routes.php | 6 +- app/Controllers/Admin/EpisodeController.php | 37 +-- .../Admin/EpisodePersonController.php | 14 +- app/Controllers/Admin/PersonController.php | 9 +- app/Controllers/Admin/PodcastController.php | 9 +- .../Admin/PodcastImportController.php | 147 ++++++----- .../Admin/PodcastPersonController.php | 14 +- app/Controllers/PageController.php | 50 ++-- .../2020-05-30-101500_add_podcasts.php | 2 +- .../2020-06-05-170000_add_episodes.php | 2 +- .../2020-06-05-180000_add_soundbites.php | 3 +- ...2020-12-25-140000_add_episodes_persons.php | 2 +- .../Seeds/FakePodcastsAnalyticsSeeder.php | 42 ++-- .../Seeds/FakeWebsiteAnalyticsSeeder.php | 24 +- app/Entities/Actor.php | 2 +- app/Entities/Category.php | 2 +- app/Entities/Credit.php | 6 +- app/Entities/Episode.php | 92 ++++--- app/Entities/Image.php | 2 +- app/Entities/Location.php | 73 +++++- app/Entities/Note.php | 2 +- app/Entities/Person.php | 33 ++- app/Entities/Podcast.php | 69 +++--- app/Entities/User.php | 4 +- app/Helpers/components_helper.php | 61 ++++- app/Helpers/form_helper.php | 12 +- app/Helpers/id3_helper.php | 8 +- app/Helpers/location_helper.php | 62 ----- app/Helpers/misc_helper.php | 4 +- app/Helpers/rss_helper.php | 105 ++++---- app/Helpers/svg_helper.php | 16 +- app/Helpers/url_helper.php | 16 -- app/Libraries/ActivityPub/ActivityRequest.php | 2 +- .../Helpers/activitypub_helper.php | 8 +- app/Libraries/ActivityPub/HttpSignature.php | 2 +- .../Controllers/AnalyticsController.php | 15 +- app/Libraries/SimpleRSSElement.php | 16 +- app/Models/EpisodeModel.php | 2 +- app/Models/PersonModel.php | 232 ++++++++++-------- app/Models/PodcastModel.php | 14 +- .../admin/episode/{person.php => persons.php} | 58 +++-- app/Views/admin/episode/soundbites.php | 52 ++-- app/Views/admin/podcast/person.php | 135 ---------- app/Views/admin/podcast/persons.php | 129 ++++++++++ app/Views/errors/html/error_exception.php | 8 +- app/Views/podcast/_layout_authenticated.php | 2 +- app/Views/podcast/_partials/header.php | 32 +-- app/Views/podcast/episode.php | 33 +-- 48 files changed, 844 insertions(+), 826 deletions(-) delete mode 100644 app/Helpers/location_helper.php rename app/Views/admin/episode/{person.php => persons.php} (58%) delete mode 100644 app/Views/admin/podcast/person.php create mode 100644 app/Views/admin/podcast/persons.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index a279b940ff..4539867c1e 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -164,13 +164,13 @@ $routes->group( ]); $routes->group('persons', function ($routes): void { - $routes->get('/', 'PodcastPodcastController/$1', [ + $routes->get('/', 'PodcastPersonController/$1', [ 'as' => 'podcast-person-manage', 'filter' => 'permission:podcast-edit', ]); $routes->post( '/', - 'PodcastPodcastController::attemptAdd/$1', + 'PodcastPersonController::attemptAdd/$1', [ 'filter' => 'permission:podcast-edit', ], @@ -178,7 +178,7 @@ $routes->group( $routes->get( '(:num)/remove', - 'PodcastPodcastController::remove/$1/$2', + 'PodcastPersonController::remove/$1/$2', [ 'as' => 'podcast-person-remove', 'filter' => 'permission:podcast-edit', diff --git a/app/Controllers/Admin/EpisodeController.php b/app/Controllers/Admin/EpisodeController.php index 423d1b3ba9..4eb61cf2ac 100644 --- a/app/Controllers/Admin/EpisodeController.php +++ b/app/Controllers/Admin/EpisodeController.php @@ -12,6 +12,7 @@ use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use Config\Database; use App\Entities\Episode; +use App\Entities\Location; use App\Entities\Note; use App\Entities\Podcast; use App\Models\EpisodeModel; @@ -133,7 +134,9 @@ class EpisodeController extends BaseController 'audio_file' => $this->request->getFile('audio_file'), 'description_markdown' => $this->request->getPost('description'), 'image' => $this->request->getFile('image'), - 'location' => $this->request->getPost('location_name'), + 'location' => new Location( + $this->request->getPost('location_name'), + ), 'transcript' => $this->request->getFile('transcript'), 'chapters' => $this->request->getFile('chapters'), 'parental_advisory' => @@ -249,7 +252,9 @@ class EpisodeController extends BaseController $this->episode->description_markdown = $this->request->getPost( 'description', ); - $this->episode->location = $this->request->getPost('location_name'); + $this->episode->location = new Location( + $this->request->getPost('location_name'), + ); $this->episode->parental_advisory = $this->request->getPost('parental_advisory') !== 'undefined' ? $this->request->getPost('parental_advisory') @@ -673,17 +678,17 @@ class EpisodeController extends BaseController public function soundbitesAttemptEdit(): RedirectResponse { - $soundbites_array = $this->request->getPost('soundbites_array'); + $soundbites = $this->request->getPost('soundbites'); $rules = [ - 'soundbites_array.0.start_time' => - 'permit_empty|required_with[soundbites_array.0.duration]|decimal|greater_than_equal_to[0]', - 'soundbites_array.0.duration' => - 'permit_empty|required_with[soundbites_array.0.start_time]|decimal|greater_than_equal_to[0]', + 'soundbites.0.start_time' => + 'permit_empty|required_with[soundbites.0.duration]|decimal|greater_than_equal_to[0]', + 'soundbites.0.duration' => + 'permit_empty|required_with[soundbites.0.start_time]|decimal|greater_than_equal_to[0]', ]; - foreach (array_keys($soundbites_array) as $soundbite_id) { + foreach (array_keys($soundbites) as $soundbite_id) { $rules += [ - "soundbites_array.{$soundbite_id}.start_time" => 'required|decimal|greater_than_equal_to[0]', - "soundbites_array.{$soundbite_id}.duration" => 'required|decimal|greater_than_equal_to[0]', + "soundbites.{$soundbite_id}.start_time" => 'required|decimal|greater_than_equal_to[0]', + "soundbites.{$soundbite_id}.duration" => 'required|decimal|greater_than_equal_to[0]', ]; } if (!$this->validate($rules)) { @@ -693,16 +698,13 @@ class EpisodeController extends BaseController ->with('errors', $this->validator->getErrors()); } - foreach ($soundbites_array as $soundbite_id => $soundbite) { - if ( - $soundbite['start_time'] !== null && - $soundbite['duration'] !== null - ) { + foreach ($soundbites as $soundbite_id => $soundbite) { + if ((int) $soundbite['start_time'] < (int) $soundbite['duration']) { $data = [ 'podcast_id' => $this->podcast->id, 'episode_id' => $this->episode->id, - 'start_time' => $soundbite['start_time'], - 'duration' => $soundbite['duration'], + 'start_time' => (int) $soundbite['start_time'], + 'duration' => (int) $soundbite['duration'], 'label' => $soundbite['label'], 'updated_by' => user_id(), ]; @@ -711,6 +713,7 @@ class EpisodeController extends BaseController } else { $data += ['id' => $soundbite_id]; } + $soundbiteModel = new SoundbiteModel(); if (!$soundbiteModel->save($data)) { return redirect() diff --git a/app/Controllers/Admin/EpisodePersonController.php b/app/Controllers/Admin/EpisodePersonController.php index c38c571c42..caec435d00 100644 --- a/app/Controllers/Admin/EpisodePersonController.php +++ b/app/Controllers/Admin/EpisodePersonController.php @@ -23,7 +23,7 @@ class EpisodePersonController extends BaseController public function _remap(string $method, string ...$params): mixed { - if (count($params) <= 2) { + if (count($params) < 2) { throw PageNotFoundException::forPageNotFound(); } @@ -54,10 +54,6 @@ class EpisodePersonController extends BaseController $data = [ 'episode' => $this->episode, 'podcast' => $this->podcast, - 'episodePersons' => (new PersonModel())->getEpisodePersons( - $this->podcast->id, - $this->episode->id, - ), 'personOptions' => (new PersonModel())->getPersonOptions(), 'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(), ]; @@ -65,7 +61,7 @@ class EpisodePersonController extends BaseController 0 => $this->podcast->title, 1 => $this->episode->title, ]); - return view('admin/episode/person', $data); + return view('admin/episode/persons', $data); } public function attemptAdd(): RedirectResponse @@ -91,12 +87,12 @@ class EpisodePersonController extends BaseController return redirect()->back(); } - public function remove(int $episodePersonId): RedirectResponse + public function remove(int $personId): RedirectResponse { - (new PersonModel())->removeEpisodePersons( + (new PersonModel())->removePersonFromEpisode( $this->podcast->id, $this->episode->id, - $episodePersonId, + $personId, ); return redirect()->back(); diff --git a/app/Controllers/Admin/PersonController.php b/app/Controllers/Admin/PersonController.php index 7d32264b30..6e07467def 100644 --- a/app/Controllers/Admin/PersonController.php +++ b/app/Controllers/Admin/PersonController.php @@ -8,6 +8,7 @@ namespace App\Controllers\Admin; +use App\Entities\Image; use CodeIgniter\HTTP\RedirectResponse; use App\Entities\Person; use CodeIgniter\Exceptions\PageNotFoundException; @@ -77,7 +78,7 @@ class PersonController extends BaseController 'full_name' => $this->request->getPost('full_name'), 'unique_name' => $this->request->getPost('unique_name'), 'information_url' => $this->request->getPost('information_url'), - 'image' => $this->request->getFile('image'), + 'image' => new Image($this->request->getFile('image')), 'created_by' => user_id(), 'updated_by' => user_id(), ]); @@ -125,9 +126,9 @@ class PersonController extends BaseController $this->person->information_url = $this->request->getPost( 'information_url', ); - $image = $this->request->getFile('image'); - if ($image->isValid()) { - $this->person->image = $image; + $imageFile = $this->request->getFile('image'); + if ($imageFile !== null && $imageFile->isValid()) { + $this->person->image = new Image($imageFile); } $this->person->updated_by = user_id(); diff --git a/app/Controllers/Admin/PodcastController.php b/app/Controllers/Admin/PodcastController.php index 47172c908f..cbeb6022d1 100644 --- a/app/Controllers/Admin/PodcastController.php +++ b/app/Controllers/Admin/PodcastController.php @@ -9,6 +9,7 @@ namespace App\Controllers\Admin; use App\Entities\Image; +use App\Entities\Location; use App\Entities\Podcast; use CodeIgniter\Exceptions\PageNotFoundException; use Config\Database; @@ -170,7 +171,9 @@ class PodcastController extends BaseController 'publisher' => $this->request->getPost('publisher'), 'type' => $this->request->getPost('type'), 'copyright' => $this->request->getPost('copyright'), - 'location' => $this->request->getPost('location_name'), + 'location' => new Location( + $this->request->getPost('location_name'), + ), 'payment_pointer' => $this->request->getPost('payment_pointer'), 'custom_rss_string' => $this->request->getPost('custom_rss'), 'partner_id' => $this->request->getPost('partner_id'), @@ -271,7 +274,9 @@ class PodcastController extends BaseController $this->podcast->owner_email = $this->request->getPost('owner_email'); $this->podcast->type = $this->request->getPost('type'); $this->podcast->copyright = $this->request->getPost('copyright'); - $this->podcast->location = $this->request->getPost('location_name'); + $this->podcast->location = new Location( + $this->request->getPost('location_name'), + ); $this->podcast->payment_pointer = $this->request->getPost( 'payment_pointer', ); diff --git a/app/Controllers/Admin/PodcastImportController.php b/app/Controllers/Admin/PodcastImportController.php index 75ec683294..f94997b25a 100644 --- a/app/Controllers/Admin/PodcastImportController.php +++ b/app/Controllers/Admin/PodcastImportController.php @@ -16,6 +16,7 @@ use Config\Database; use Podlibre\PodcastNamespace\ReversedTaxonomy; use App\Entities\Episode; use App\Entities\Image; +use App\Entities\Location; use App\Entities\Person; use App\Models\CategoryModel; use App\Models\LanguageModel; @@ -121,7 +122,7 @@ class PodcastImportController extends BaseController try { if ( - $nsItunes->image !== null && + isset($nsItunes->image) && $nsItunes->image->attributes()['href'] !== null ) { $imageFile = download_file( @@ -133,6 +134,15 @@ class PodcastImportController extends BaseController ); } + $location = null; + if (isset($nsPodcast->location)) { + $location = new Location( + (string) $nsPodcast->location, + (string) $nsPodcast->location->attributes()['geo'], + (string) $nsPodcast->location->attributes()['osm'], + ); + } + $podcast = new Podcast([ 'name' => $this->request->getPost('name'), 'imported_feed_url' => $this->request->getPost( @@ -150,40 +160,27 @@ class PodcastImportController extends BaseController 'language_code' => $this->request->getPost('language'), 'category_id' => $this->request->getPost('category'), 'parental_advisory' => - $nsItunes->explicit === null - ? null - : (in_array($nsItunes->explicit, ['yes', 'true']) + isset($nsItunes->explicit) + ? (in_array((string) $nsItunes->explicit, ['yes', 'true']) ? 'explicit' - : (in_array($nsItunes->explicit, ['no', 'false']) + : (in_array((string) $nsItunes->explicit, ['no', 'false']) ? 'clean' - : null)), + : null)) + : null, 'owner_name' => (string) $nsItunes->owner->name, 'owner_email' => (string) $nsItunes->owner->email, 'publisher' => (string) $nsItunes->author, - 'type' => - $nsItunes->type === null ? 'episodic' : $nsItunes->type, + 'type' => isset($nsItunes->type) ? (string) $nsItunes->type : 'episodic', 'copyright' => (string) $feed->channel[0]->copyright, 'is_blocked' => - $nsItunes->block === null - ? false - : $nsItunes->block === 'yes', + isset($nsItunes->block) + ? (string) $nsItunes->block === 'yes' + : false, 'is_completed' => - $nsItunes->complete === null - ? false - : $nsItunes->complete === 'yes', - 'location_name' => $nsPodcast->location - ? (string) $nsPodcast->location - : null, - 'location_geo' => - !$nsPodcast->location || - $nsPodcast->location->attributes()['geo'] === null - ? null - : (string) $nsPodcast->location->attributes()['geo'], - 'location_osm_id' => - !$nsPodcast->location || - $nsPodcast->location->attributes()['osm'] === null - ? null - : (string) $nsPodcast->location->attributes()['osm'], + isset($nsItunes->complete) + ? (string) $nsItunes->complete === 'yes' + : false, + 'location' => $location, 'created_by' => user_id(), 'updated_by' => user_id(), ]); @@ -277,18 +274,17 @@ class PodcastImportController extends BaseController } } - $personGroup = - isset($podcastPerson->attributes()['group']) - ? ['slug' => ''] - : ReversedTaxonomy::$taxonomy[(string) $podcastPerson->attributes()['group']]; - $personRole = - isset($podcastPerson->attributes()['role']) || - $personGroup === null - ? ['slug' => ''] - : $personGroup['roles'][strval($podcastPerson->attributes()['role'])]; + // TODO: these checks should be in the taxonomy as default values + $podcastPersonGroup = $podcastPerson->attributes()['group'] ?? "Cast"; + $podcastPersonRole = $podcastPerson->attributes()['role'] ?? "Host"; + + $personGroup = ReversedTaxonomy::$taxonomy[(string) $podcastPersonGroup]; + + $personGroupSlug = $personGroup['slug']; + $personRoleSlug = $personGroup['roles'][(string) $podcastPersonRole]['slug']; $podcastPersonModel = new PersonModel(); - if (!$podcastPersonModel->addPodcastPerson($newPodcastId, $newPersonId, $personGroup['slug'], $personRole['slug'])) { + if (!$podcastPersonModel->addPodcastPerson($newPodcastId, $newPersonId, $personGroupSlug, $personRoleSlug)) { return redirect() ->back() ->withInput() @@ -341,7 +337,7 @@ class PodcastImportController extends BaseController }; if ( - $nsItunes->image !== null && + isset($nsItunes->image) && $nsItunes->image->attributes()['href'] !== null ) { $episodeImage = new Image( @@ -353,6 +349,15 @@ class PodcastImportController extends BaseController $episodeImage = null; } + $location = null; + if (isset($nsPodcast->location)) { + $location = new Location( + (string) $nsPodcast->location, + (string) $nsPodcast->location->attributes()['geo'], + (string) $nsPodcast->location->attributes()['osm'], + ); + } + $newEpisode = new Episode([ 'podcast_id' => $newPodcastId, 'guid' => $item->guid ?? null, @@ -367,13 +372,13 @@ class PodcastImportController extends BaseController 'description_html' => $itemDescriptionHtml, 'image' => $episodeImage, 'parental_advisory' => - $nsItunes->explicit === null - ? null - : (in_array($nsItunes->explicit, ['yes', 'true']) + isset($nsItunes->explicit) + ? (in_array((string) $nsItunes->explicit, ['yes', 'true']) ? 'explicit' - : (in_array($nsItunes->explicit, ['no', 'false']) + : (in_array((string) $nsItunes->explicit, ['no', 'false']) ? 'clean' - : null)), + : null)) + : null, 'number' => $this->request->getPost('force_renumber') === 'yes' ? $itemNumber @@ -382,27 +387,13 @@ class PodcastImportController extends BaseController $this->request->getPost('season_number') === null ? $nsItunes->season : $this->request->getPost('season_number'), - 'type' => - $nsItunes->episodeType === null - ? 'full' - : $nsItunes->episodeType, - 'is_blocked' => - $nsItunes->block === null - ? false - : $nsItunes->block === 'yes', - 'location_name' => $nsPodcast->location - ? $nsPodcast->location - : null, - 'location_geo' => - !$nsPodcast->location || - $nsPodcast->location->attributes()['geo'] === null - ? null - : $nsPodcast->location->attributes()['geo'], - 'location_osm_id' => - !$nsPodcast->location || - $nsPodcast->location->attributes()['osm'] === null - ? null - : $nsPodcast->location->attributes()['osm'], + 'type' => isset($nsItunes->episodeType) + ? (string) $nsItunes->episodeType + : 'full', + 'is_blocked' => isset($nsItunes->block) + ? (string) $nsItunes->block === 'yes' + : false, + 'location' => $location, 'created_by' => user_id(), 'updated_by' => user_id(), 'published_at' => strtotime($item->pubDate), @@ -425,14 +416,16 @@ class PodcastImportController extends BaseController if (($newPerson = $personModel->getPerson($fullName)) !== null) { $newPersonId = $newPerson->id; } else { - $newEpisodePerson = new Person([ + $newPerson = new Person([ 'full_name' => $fullName, - 'slug' => slugify($fullName), + 'unique_name' => slugify($fullName), 'information_url' => $episodePerson->attributes()['href'], - 'image' => new Image(download_file($episodePerson->attributes()['img'])) + 'image' => new Image(download_file($episodePerson->attributes()['img'])), + 'created_by' => user_id(), + 'updated_by' => user_id(), ]); - if (!($newPersonId = $personModel->insert($newEpisodePerson))) { + if (!($newPersonId = $personModel->insert($newPerson))) { return redirect() ->back() ->withInput() @@ -440,19 +433,17 @@ class PodcastImportController extends BaseController } } - $personGroup = - $episodePerson->attributes()['group'] === null - ? ['slug' => ''] - : ReversedTaxonomy::$taxonomy[strval($episodePerson->attributes()['group'])]; - $personRole = - $episodePerson->attributes()['role'] === null || - $personGroup === null - ? ['slug' => ''] - : $personGroup['roles'][strval($episodePerson->attributes()['role'])]; + // TODO: these checks should be in the taxonomy as default values + $episodePersonGroup = $episodePerson->attributes()['group'] ?? "Cast"; + $episodePersonRole = $episodePerson->attributes()['role'] ?? "Host"; + + $personGroup = ReversedTaxonomy::$taxonomy[(string) $episodePersonGroup]; + $personGroupSlug = $personGroup['slug']; + $personRoleSlug = $personGroup['roles'][(string) $episodePersonRole]['slug']; $episodePersonModel = new PersonModel(); - if (!$episodePersonModel->addEpisodePerson($newPodcastId, $newEpisodeId, $newPersonId, $personGroup['slug'], $personRole['slug'])) { + if (!$episodePersonModel->addEpisodePerson($newPodcastId, $newEpisodeId, $newPersonId, $personGroupSlug, $personRoleSlug)) { return redirect() ->back() ->withInput() diff --git a/app/Controllers/Admin/PodcastPersonController.php b/app/Controllers/Admin/PodcastPersonController.php index 623088a95a..3b6a8147b6 100644 --- a/app/Controllers/Admin/PodcastPersonController.php +++ b/app/Controllers/Admin/PodcastPersonController.php @@ -54,13 +54,13 @@ class PodcastPersonController extends BaseController replace_breadcrumb_params([ 0 => $this->podcast->title, ]); - return view('admin/podcast/person', $data); + return view('admin/podcast/persons', $data); } public function attemptAdd(): RedirectResponse { $rules = [ - 'person' => 'required', + 'persons' => 'required', ]; if (!$this->validate($rules)) { @@ -72,18 +72,18 @@ class PodcastPersonController extends BaseController (new PersonModel())->addPodcastPersons( $this->podcast->id, - $this->request->getPost('person'), - $this->request->getPost('person_group_role'), + $this->request->getPost('persons'), + $this->request->getPost('roles') ?? [], ); return redirect()->back(); } - public function remove(int $podcastPersonId): RedirectResponse + public function remove(int $personId): RedirectResponse { - (new PersonModel())->removePodcastPersons( + (new PersonModel())->removePersonFromPodcast( $this->podcast->id, - $podcastPersonId, + $personId, ); return redirect()->back(); diff --git a/app/Controllers/PageController.php b/app/Controllers/PageController.php index 97d55c6652..8f02363ab6 100644 --- a/app/Controllers/PageController.php +++ b/app/Controllers/PageController.php @@ -69,26 +69,26 @@ class PageController extends BaseController $allCredits = (new CreditModel())->findAll(); // Unlike the carpenter, we make a tree from a table: - $person_group = null; - $person_id = null; - $person_role = null; + $personGroup = null; + $personId = null; + $personRole = null; $credits = []; foreach ($allCredits as $credit) { - if ($person_group !== $credit->person_group) { - $person_group = $credit->person_group; - $person_id = $credit->person_id; - $person_role = $credit->person_role; - $credits[$person_group] = [ + if ($personGroup !== $credit->person_group) { + $personGroup = $credit->person_group; + $personId = $credit->person_id; + $personRole = $credit->person_role; + $credits[$personGroup] = [ 'group_label' => $credit->group_label, 'persons' => [ - $person_id => [ + $personId => [ 'full_name' => $credit->person->full_name, 'thumbnail_url' => $credit->person->image->thumbnail_url, 'information_url' => $credit->person->information_url, 'roles' => [ - $person_role => [ + $personRole => [ 'role_label' => $credit->role_label, 'is_in' => [ [ @@ -97,7 +97,7 @@ class PageController extends BaseController : $credit->podcast->link, 'title' => $credit->episode_id ? (count($allPodcasts) > 1 - ? "{$credit->podcast->title} â–¸ " + ? "{$credit->podcast->title} › " : '') . $credit->episode ->title . @@ -117,16 +117,16 @@ class PageController extends BaseController ], ], ]; - } elseif ($person_id !== $credit->person_id) { - $person_id = $credit->person_id; - $person_role = $credit->person_role; - $credits[$person_group]['persons'][$person_id] = [ + } elseif ($personId !== $credit->person_id) { + $personId = $credit->person_id; + $personRole = $credit->person_role; + $credits[$personGroup]['persons'][$personId] = [ 'full_name' => $credit->person->full_name, 'thumbnail_url' => $credit->person->image->thumbnail_url, 'information_url' => $credit->person->information_url, 'roles' => [ - $person_role => [ + $personRole => [ 'role_label' => $credit->role_label, 'is_in' => [ [ @@ -135,7 +135,7 @@ class PageController extends BaseController : $credit->podcast->link, 'title' => $credit->episode_id ? (count($allPodcasts) > 1 - ? "{$credit->podcast->title} â–¸ " + ? "{$credit->podcast->title} › " : '') . $credit->episode->title . episode_numbering( @@ -151,10 +151,10 @@ class PageController extends BaseController ], ], ]; - } elseif ($person_role !== $credit->person_role) { - $person_role = $credit->person_role; - $credits[$person_group]['persons'][$person_id]['roles'][ - $person_role + } elseif ($personRole !== $credit->person_role) { + $personRole = $credit->person_role; + $credits[$personGroup]['persons'][$personId]['roles'][ + $personRole ] = [ 'role_label' => $credit->role_label, 'is_in' => [ @@ -164,7 +164,7 @@ class PageController extends BaseController : $credit->podcast->link, 'title' => $credit->episode_id ? (count($allPodcasts) > 1 - ? "{$credit->podcast->title} â–¸ " + ? "{$credit->podcast->title} › " : '') . $credit->episode->title . episode_numbering( @@ -178,15 +178,15 @@ class PageController extends BaseController ], ]; } else { - $credits[$person_group]['persons'][$person_id]['roles'][ - $person_role + $credits[$personGroup]['persons'][$personId]['roles'][ + $personRole ]['is_in'][] = [ 'link' => $credit->episode_id ? $credit->episode->link : $credit->podcast->link, 'title' => $credit->episode_id ? (count($allPodcasts) > 1 - ? "{$credit->podcast->title} â–¸ " + ? "{$credit->podcast->title} › " : '') . $credit->episode->title . episode_numbering( diff --git a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php index aa32c8895b..e8eafd4445 100644 --- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php +++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php @@ -142,7 +142,7 @@ class AddPodcasts extends Migration 'constraint' => 32, 'null' => true, ], - 'location_osm_id' => [ + 'location_osm' => [ 'type' => 'VARCHAR', 'constraint' => 12, 'null' => true, diff --git a/app/Database/Migrations/2020-06-05-170000_add_episodes.php b/app/Database/Migrations/2020-06-05-170000_add_episodes.php index 83ee1e4a32..d3d42aa575 100644 --- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php +++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php @@ -137,7 +137,7 @@ class AddEpisodes extends Migration 'constraint' => 32, 'null' => true, ], - 'location_osm_id' => [ + 'location_osm' => [ 'type' => 'VARCHAR', 'constraint' => 12, 'null' => true, diff --git a/app/Database/Migrations/2020-06-05-180000_add_soundbites.php b/app/Database/Migrations/2020-06-05-180000_add_soundbites.php index 43b3126a06..32183c52b3 100644 --- a/app/Database/Migrations/2020-06-05-180000_add_soundbites.php +++ b/app/Database/Migrations/2020-06-05-180000_add_soundbites.php @@ -35,7 +35,8 @@ class AddSoundbites extends Migration 'type' => 'DECIMAL(8,3)', ], 'duration' => [ - 'type' => 'DECIMAL(8,3)', + // soundbite duration cannot be higher than 9999,999 seconds ~ 2.77 hours + 'type' => 'DECIMAL(7,3)', ], 'label' => [ 'type' => 'VARCHAR', diff --git a/app/Database/Migrations/2020-12-25-140000_add_episodes_persons.php b/app/Database/Migrations/2020-12-25-140000_add_episodes_persons.php index 1bff6c1d0a..9382b9ac88 100644 --- a/app/Database/Migrations/2020-12-25-140000_add_episodes_persons.php +++ b/app/Database/Migrations/2020-12-25-140000_add_episodes_persons.php @@ -44,7 +44,7 @@ class AddEpisodesPersons extends Migration 'constraint' => 32, ], ]); - $this->forge->addKey('id', true); + $this->forge->addPrimaryKey('id', true); $this->forge->addUniqueKey([ 'podcast_id', 'episode_id', diff --git a/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php b/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php index b80ebd16e8..0a8e3d1560 100644 --- a/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php +++ b/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php @@ -52,12 +52,12 @@ class FakePodcastsAnalyticsSeeder extends Seeder $date < strtotime('now'); $date = strtotime(date('Y-m-d', $date) . ' +1 day') ) { - $analytics_podcasts = []; - $analytics_podcasts_by_hour = []; - $analytics_podcasts_by_country = []; - $analytics_podcasts_by_episode = []; - $analytics_podcasts_by_player = []; - $analytics_podcasts_by_region = []; + $analyticsPodcasts = []; + $analyticsPodcastsByHour = []; + $analyticsPodcastsByCountry = []; + $analyticsPodcastsByEpisode = []; + $analyticsPodcastsByPlayer = []; + $analyticsPodcastsByRegion = []; $episodes = (new EpisodeModel()) ->where([ @@ -72,9 +72,9 @@ class FakePodcastsAnalyticsSeeder extends Seeder $probability1 = (int) floor(exp(3 - $age / 40)) + 1; for ( - $num_line = 0; - $num_line < rand(1, $probability1); - ++$num_line + $lineNumber = 0; + $lineNumber < rand(1, $probability1); + ++$lineNumber ) { $probability2 = (int) floor(exp(6 - $age / 20)) + 10; @@ -129,7 +129,7 @@ class FakePodcastsAnalyticsSeeder extends Seeder $hits = rand(0, $probability2); - $analytics_podcasts[] = [ + $analyticsPodcasts[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'duration' => rand(60, 3600), @@ -137,26 +137,26 @@ class FakePodcastsAnalyticsSeeder extends Seeder 'hits' => $hits, 'unique_listeners' => $hits, ]; - $analytics_podcasts_by_hour[] = [ + $analyticsPodcastsByHour[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'hour' => rand(0, 23), 'hits' => $hits, ]; - $analytics_podcasts_by_country[] = [ + $analyticsPodcastsByCountry[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'country_code' => $countryCode, 'hits' => $hits, ]; - $analytics_podcasts_by_episode[] = [ + $analyticsPodcastsByEpisode[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'episode_id' => $episode->id, 'age' => $age, 'hits' => $hits, ]; - $analytics_podcasts_by_player[] = [ + $analyticsPodcastsByPlayer[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'service' => $service, @@ -166,7 +166,7 @@ class FakePodcastsAnalyticsSeeder extends Seeder 'is_bot' => $isBot, 'hits' => $hits, ]; - $analytics_podcasts_by_region[] = [ + $analyticsPodcastsByRegion[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'country_code' => $countryCode, @@ -180,27 +180,27 @@ class FakePodcastsAnalyticsSeeder extends Seeder $this->db ->table('analytics_podcasts') ->ignore(true) - ->insertBatch($analytics_podcasts); + ->insertBatch($analyticsPodcasts); $this->db ->table('analytics_podcasts_by_hour') ->ignore(true) - ->insertBatch($analytics_podcasts_by_hour); + ->insertBatch($analyticsPodcastsByHour); $this->db ->table('analytics_podcasts_by_country') ->ignore(true) - ->insertBatch($analytics_podcasts_by_country); + ->insertBatch($analyticsPodcastsByCountry); $this->db ->table('analytics_podcasts_by_episode') ->ignore(true) - ->insertBatch($analytics_podcasts_by_episode); + ->insertBatch($analyticsPodcastsByEpisode); $this->db ->table('analytics_podcasts_by_player') ->ignore(true) - ->insertBatch($analytics_podcasts_by_player); + ->insertBatch($analyticsPodcastsByPlayer); $this->db ->table('analytics_podcasts_by_region') ->ignore(true) - ->insertBatch($analytics_podcasts_by_region); + ->insertBatch($analyticsPodcastsByRegion); } } else { echo "COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n"; diff --git a/app/Database/Seeds/FakeWebsiteAnalyticsSeeder.php b/app/Database/Seeds/FakeWebsiteAnalyticsSeeder.php index 51697c9bca..21fbc8fad6 100644 --- a/app/Database/Seeds/FakeWebsiteAnalyticsSeeder.php +++ b/app/Database/Seeds/FakeWebsiteAnalyticsSeeder.php @@ -192,9 +192,9 @@ class FakeWebsiteAnalyticsSeeder extends Seeder $date < strtotime('now'); $date = strtotime(date('Y-m-d', $date) . ' +1 day') ) { - $website_by_browser = []; - $website_by_entry_page = []; - $website_by_referer = []; + $websiteByBrowser = []; + $websiteByEntryPage = []; + $websiteByReferer = []; $episodes = (new EpisodeModel()) ->where([ @@ -209,9 +209,9 @@ class FakeWebsiteAnalyticsSeeder extends Seeder $probability1 = (int) floor(exp(3 - $age / 40)) + 1; for ( - $num_line = 0; - $num_line < rand(1, $probability1); - ++$num_line + $lineNumber = 0; + $lineNumber < rand(1, $probability1); + ++$lineNumber ) { $probability2 = (int) floor(exp(6 - $age / 20)) + 10; @@ -228,19 +228,19 @@ class FakeWebsiteAnalyticsSeeder extends Seeder $hits = rand(0, $probability2); - $website_by_browser[] = [ + $websiteByBrowser[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'browser' => $browser, 'hits' => $hits, ]; - $website_by_entry_page[] = [ + $websiteByEntryPage[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'entry_page_url' => $episode->link, 'hits' => $hits, ]; - $website_by_referer[] = [ + $websiteByReferer[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), 'referer_url' => @@ -254,15 +254,15 @@ class FakeWebsiteAnalyticsSeeder extends Seeder $this->db ->table('analytics_website_by_browser') ->ignore(true) - ->insertBatch($website_by_browser); + ->insertBatch($websiteByBrowser); $this->db ->table('analytics_website_by_entry_page') ->ignore(true) - ->insertBatch($website_by_entry_page); + ->insertBatch($websiteByEntryPage); $this->db ->table('analytics_website_by_referer') ->ignore(true) - ->insertBatch($website_by_referer); + ->insertBatch($websiteByReferer); } } else { echo "COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n"; diff --git a/app/Entities/Actor.php b/app/Entities/Actor.php index 34054745fe..bc2fde32fb 100644 --- a/app/Entities/Actor.php +++ b/app/Entities/Actor.php @@ -18,7 +18,7 @@ use RuntimeException; */ class Actor extends ActivityPubActor { - protected ?Podcast $podcast; + protected ?Podcast $podcast = null; protected bool $is_podcast; public function getIsPodcast(): bool diff --git a/app/Entities/Category.php b/app/Entities/Category.php index a650d65156..afc4e26583 100644 --- a/app/Entities/Category.php +++ b/app/Entities/Category.php @@ -21,7 +21,7 @@ use CodeIgniter\Entity\Entity; */ class Category extends Entity { - protected ?Category $parent; + protected ?Category $parent = null; /** * @var array<string, string> diff --git a/app/Entities/Credit.php b/app/Entities/Credit.php index 5c6ea62896..93934634ef 100644 --- a/app/Entities/Credit.php +++ b/app/Entities/Credit.php @@ -29,9 +29,9 @@ use CodeIgniter\Entity\Entity; */ class Credit extends Entity { - protected ?Person $person; - protected ?Podcast $podcast; - protected ?Episode $episode; + protected ?Person $person = null; + protected ?Podcast $podcast = null; + protected ?Episode $episode = null; protected string $group_label; protected string $role_label; diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 178baac572..a9d7265612 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -58,10 +58,10 @@ use RuntimeException; * @property int $season_number * @property string $type * @property bool $is_blocked - * @property Location $location + * @property Location|null $location * @property string|null $location_name * @property string|null $location_geo - * @property string|null $location_osm_id + * @property string|null $location_osm * @property array|null $custom_rss * @property string $custom_rss_string * @property int $favourites_total @@ -90,7 +90,7 @@ class Episode extends Entity protected string $audio_file_opengraph_url; protected string $embeddable_player_url; protected Image $image; - protected ?string $description; + protected ?string $description = null; protected File $transcript_file; protected File $chapters_file; @@ -109,9 +109,9 @@ class Episode extends Entity */ protected $notes = []; - protected ?Location $location; + protected ?Location $location = null; protected string $custom_rss_string; - protected string $publication_status; + protected ?string $publication_status = null; /** * @var string[] @@ -152,7 +152,7 @@ class Episode extends Entity 'is_blocked' => 'boolean', 'location_name' => '?string', 'location_geo' => '?string', - 'location_osm_id' => '?string', + 'location_osm' => '?string', 'custom_rss' => '?json-array', 'favourites_total' => 'integer', 'reblogs_total' => 'integer', @@ -202,7 +202,7 @@ class Episode extends Entity { helper(['media', 'id3']); - $audio_metadata = get_file_tags($audioFile); + $audioMetadata = get_file_tags($audioFile); $this->attributes['audio_file_path'] = save_media( $audioFile, @@ -210,11 +210,11 @@ class Episode extends Entity $this->attributes['slug'], ); $this->attributes['audio_file_duration'] = - $audio_metadata['playtime_seconds']; - $this->attributes['audio_file_mimetype'] = $audio_metadata['mime_type']; - $this->attributes['audio_file_size'] = $audio_metadata['filesize']; + $audioMetadata['playtime_seconds']; + $this->attributes['audio_file_mimetype'] = $audioMetadata['mime_type']; + $this->attributes['audio_file_size'] = $audioMetadata['filesize']; $this->attributes['audio_file_header_size'] = - $audio_metadata['avdataoffset']; + $audioMetadata['avdataoffset']; return $this; } @@ -471,10 +471,8 @@ class Episode extends Entity $this->getPodcast()->partner_link_url !== null && $this->getPodcast()->partner_image_url !== null ) { - $descriptionHtml .= "<div><a href=\"{$this->getPartnerLink( - $serviceSlug, - )}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\"><img src=\"{$this->getPartnerImageUrl( - $serviceSlug, + $descriptionHtml .= "<div><a href=\"{$this->getPartnerLink($serviceSlug, + )}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\"><img src=\"{$this->getPartnerImageUrl($serviceSlug, )}\" alt=\"Partner image\" /></a></div>"; } @@ -504,47 +502,41 @@ class Episode extends Entity public function getPublicationStatus(): string { - if ($this->publication_status !== '') { - return $this->publication_status; - } - - if ($this->published_at === null) { - return 'not_published'; - } - - helper('date'); - if ($this->published_at->isBefore(Time::now())) { - return 'published'; + if ($this->publication_status === null) { + if ($this->published_at === null) { + $this->publication_status = 'not_published'; + } elseif ($this->published_at->isBefore(Time::now())) { + $this->publication_status = 'published'; + } else { + $this->publication_status = 'scheduled'; + } } - return 'scheduled'; + return $this->publication_status; } /** * Saves the location name and fetches OpenStreetMap info */ - public function setLocation(?string $newLocationName = null): static + public function setLocation(?Location $location = null): static { - if ($newLocationName === null) { + if ($location === null) { $this->attributes['location_name'] = null; $this->attributes['location_geo'] = null; - $this->attributes['location_osm_id'] = null; - } + $this->attributes['location_osm'] = null; - helper('location'); - - $oldLocationName = $this->attributes['location_name']; + return $this; + } if ( - $oldLocationName === null || - $oldLocationName !== $newLocationName + !isset($this->attributes['location_name']) || + $this->attributes['location_name'] !== $location->name ) { - $this->attributes['location_name'] = $newLocationName; + $location->fetchOsmLocation(); - if ($location = fetch_osm_location($newLocationName)) { - $this->attributes['location_geo'] = $location['geo']; - $this->attributes['location_osm_id'] = $location['osm_id']; - } + $this->attributes['location_name'] = $location->name; + $this->attributes['location_geo'] = $location->geo; + $this->attributes['location_osm'] = $location->osm; } return $this; @@ -557,11 +549,11 @@ class Episode extends Entity } if ($this->location === null) { - $this->location = new Location([ - 'name' => $this->location_name, - 'geo' => $this->location_geo, - 'osm_id' => $this->location_osm_id, - ]); + $this->location = new Location( + $this->location_name, + $this->location_geo, + $this->location_osm, + ); } return $this->location; @@ -645,9 +637,9 @@ class Episode extends Entity } return rtrim($this->getPodcast()->partner_image_url, '/') . - '?pid=' . - $this->getPodcast()->partner_id . - '&guid=' . - urlencode($this->attributes['guid']); + '?pid=' . + $this->getPodcast()->partner_id . + '&guid=' . + urlencode($this->attributes['guid']); } } diff --git a/app/Entities/Image.php b/app/Entities/Image.php index 74196cad7a..ddbd7c2f6e 100644 --- a/app/Entities/Image.php +++ b/app/Entities/Image.php @@ -36,7 +36,7 @@ use RuntimeException; class Image extends Entity { protected Images $config; - protected ?File $file; + protected ?File $file = null; protected string $dirname; protected string $filename; protected string $extension; diff --git a/app/Entities/Location.php b/app/Entities/Location.php index bfce2ddf75..40f256fa8c 100644 --- a/app/Entities/Location.php +++ b/app/Entities/Location.php @@ -9,12 +9,13 @@ namespace App\Entities; use CodeIgniter\Entity\Entity; +use Config\Services; /** * @property string $url * @property string $name * @property string|null $geo - * @property string|null $osm_id + * @property string|null $osm */ class Location extends Entity { @@ -23,15 +24,30 @@ class Location extends Entity */ const OSM_URL = 'https://www.openstreetmap.org/'; + /** + * @var string + */ + const NOMINATIM_URL = 'https://nominatim.openstreetmap.org/'; + + public function __construct( + protected string $name, + protected ?string $geo = null, + protected ?string $osm = null + ) { + parent::__construct([ + 'name' => $name, + 'geo' => $geo, + 'osm' => $osm + ]); + } + public function getUrl(): string { - if ($this->osm_id !== null) { + if ($this->osm !== null) { return self::OSM_URL . - ['N' => 'node', 'W' => 'way', 'R' => 'relation'][ - substr($this->osm_id, 0, 1) - ] . + ['N' => 'node', 'W' => 'way', 'R' => 'relation'][substr($this->osm, 0, 1)] . '/' . - substr($this->osm_id, 1); + substr($this->osm, 1); } if ($this->geo !== null) { @@ -42,4 +58,49 @@ class Location extends Entity return self::OSM_URL . 'search?query=' . urlencode($this->name); } + + /** + * Fetches places from Nominatim OpenStreetMap + * + * @return array<string, string>|null + */ + public function fetchOsmLocation(): self + { + $client = Services::curlrequest(); + + $response = $client->request( + 'GET', + self::NOMINATIM_URL . + 'search.php?q=' . + urlencode($this->name) . + '&polygon_geojson=1&format=jsonv2', + [ + 'headers' => [ + 'User-Agent' => 'Castopod/' . CP_VERSION, + 'Accept' => 'application/json', + ], + ], + ); + + $places = json_decode( + $response->getBody(), + false, + 512, + JSON_THROW_ON_ERROR, + ); + + if ($places === []) { + return $this; + } + + if (isset($places[0]->lat, $places[0]->lon)) { + $this->attributes['geo'] = "geo:{$places[0]->lat},{$places[0]->lon}"; + } + + if (isset($places[0]->osm_type, $places[0]->osm_id)) { + $this->attributes['osm'] = strtoupper(substr($places[0]->osm_type, 0, 1)) . $places[0]->osm_id; + } + + return $this; + } } diff --git a/app/Entities/Note.php b/app/Entities/Note.php index eeec4e3fdd..21155da362 100644 --- a/app/Entities/Note.php +++ b/app/Entities/Note.php @@ -21,7 +21,7 @@ use RuntimeException; */ class Note extends ActivityPubNote { - protected ?Episode $episode; + protected ?Episode $episode = null; /** * @var array<string, string> diff --git a/app/Entities/Person.php b/app/Entities/Person.php index 7bfd0b163f..eb15dbdc6a 100644 --- a/app/Entities/Person.php +++ b/app/Entities/Person.php @@ -8,7 +8,9 @@ namespace App\Entities; +use App\Models\PersonModel; use CodeIgniter\Entity\Entity; +use RuntimeException; /** * @property int $id @@ -20,16 +22,16 @@ use CodeIgniter\Entity\Entity; * @property string $image_mimetype * @property int $created_by * @property int $updated_by - * @property string|null $group - * @property string|null $role - * @property Podcast|null $podcast - * @property Episode|null $episode + * @property string[]|null $roles */ class Person extends Entity { protected Image $image; - protected ?Podcast $podcast; - protected ?Episode $episode; + + /** + * @var string[]|null + */ + protected ?array $roles = null; /** * @var array<string, string> @@ -43,8 +45,6 @@ class Person extends Entity 'image_mimetype' => 'string', 'podcast_id' => '?integer', 'episode_id' => '?integer', - 'group' => '?string', - 'role' => '?string', 'created_by' => 'integer', 'updated_by' => 'integer', ]; @@ -73,4 +73,21 @@ class Person extends Entity $this->attributes['image_mimetype'], ); } + + /** + * @return stdClass[] + */ + public function getRoles(): array { + if ($this->podcast_id === null) { + throw new RuntimeException( + 'Person must have a podcast_id before getting roles.', + ); + } + + if ($this->roles === null) { + $this->roles = (new PersonModel())->getPersonRoles($this->id, $this->podcast_id, $this->episode_id); + } + + return $this->roles; + } } diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index 7b84d3df9f..55ba035675 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -27,7 +27,7 @@ use RuntimeException; * @property string $link * @property string $feed_url * @property string $title - * @property string $description Holds text only description, striped of any markdown or html special characters + * @property string|null $description Holds text only description, striped of any markdown or html special characters * @property string $description_markdown * @property string $description_html * @property Image $image @@ -51,10 +51,10 @@ use RuntimeException; * @property bool $is_locked * @property string|null $imported_feed_url * @property string|null $new_feed_url - * @property Location $location + * @property Location|null $location * @property string|null $location_name * @property string|null $location_geo - * @property string|null $location_osm_id + * @property string|null $location_osm * @property string|null $payment_pointer * @property array|null $custom_rss * @property string $custom_rss_string @@ -78,10 +78,10 @@ use RuntimeException; class Podcast extends Entity { protected string $link; - protected ?Actor $actor; + protected ?Actor $actor = null; protected Image $image; - protected string $description; - protected ?Category $category; + protected ?string $description = null; + protected ?Category $category = null; /** * @var Category[] @@ -123,7 +123,7 @@ class Podcast extends Entity */ protected $funding_platforms = []; - protected ?Location $location; + protected ?Location $location = null; protected string $custom_rss_string; /** @@ -155,7 +155,7 @@ class Podcast extends Entity 'new_feed_url' => '?string', 'location_name' => '?string', 'location_geo' => '?string', - 'location_osm_id' => '?string', + 'location_osm' => '?string', 'payment_pointer' => '?string', 'custom_rss' => '?json-array', 'partner_id' => '?string', @@ -331,17 +331,17 @@ class Podcast extends Entity public function getDescription(): string { - if ($this->description !== '') { - return $this->description; + if ($this->description === null) { + $this->description = trim( + (string) preg_replace( + '~\s+~', + ' ', + strip_tags($this->attributes['description_html']), + ), + ); } - return trim( - preg_replace( - '~\s+~', - ' ', - strip_tags($this->attributes['description_html']), - ), - ); + return $this->description; } /** @@ -451,28 +451,25 @@ class Podcast extends Entity /** * Saves the location name and fetches OpenStreetMap info */ - public function setLocation(?string $newLocationName = null): static + public function setLocation(?Location $location = null): static { - if ($newLocationName === null) { + if ($location === null) { $this->attributes['location_name'] = null; $this->attributes['location_geo'] = null; - $this->attributes['location_osm_id'] = null; - } - - helper('location'); + $this->attributes['location_osm'] = null; - $oldLocationName = $this->attributes['location_name']; + return $this; + } if ( - $oldLocationName === null || - $oldLocationName !== $newLocationName + !isset($this->attributes['location_name']) || + $this->attributes['location_name'] !== $location->name ) { - $this->attributes['location_name'] = $newLocationName; + $location->fetchOsmLocation(); - if ($location = fetch_osm_location($newLocationName)) { - $this->attributes['location_geo'] = $location['geo']; - $this->attributes['location_osm_id'] = $location['osm_id']; - } + $this->attributes['location_name'] = $location->name; + $this->attributes['location_geo'] = $location->geo; + $this->attributes['location_osm'] = $location->osm; } return $this; @@ -485,11 +482,11 @@ class Podcast extends Entity } if ($this->location === null) { - $this->location = new Location([ - 'name' => $this->location_name, - 'geo' => $this->location_geo, - 'osm_id' => $this->location_osm_id, - ]); + $this->location = new Location( + $this->location_name, + $this->location_geo, + $this->location_osm, + ); } return $this->location; diff --git a/app/Entities/User.php b/app/Entities/User.php index e899ff8f1e..909799f100 100644 --- a/app/Entities/User.php +++ b/app/Entities/User.php @@ -27,9 +27,9 @@ use Myth\Auth\Entities\User as MythAuthUser; class User extends MythAuthUser { /** - * @var Podcast[] + * @var Podcast[]|null */ - protected $podcasts = []; + protected ?array $podcasts = null; /** * Array of field names and the type of value to cast them as diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php index 647bfe2031..172bfca118 100644 --- a/app/Helpers/components_helper.php +++ b/app/Helpers/components_helper.php @@ -7,6 +7,7 @@ */ use App\Entities\Location; +use App\Entities\Person; use CodeIgniter\View\Table; use CodeIgniter\I18n\Time; @@ -291,13 +292,11 @@ if (!function_exists('publication_button')) { * Publication button component * * Displays the appropriate publication button depending on the publication status. - * - * @param boolean $publicationStatus the episode's publication status * */ function publication_button( int $podcastId, int $episodeId, - bool $publicationStatus + string $publicationStatus ): string { switch ($publicationStatus) { case 'not_published': @@ -416,3 +415,59 @@ if (!function_exists('location_link')) { } // ------------------------------------------------------------------------ + +if (!function_exists('person_list')) { + /** + * Returns list of persons images + * + * @param Person[] $persons + */ + function person_list(array $persons, string $class = ''): string + { + if ($persons === []) { + return ''; + } + + $personList = "<div class='flex w-full space-x-2 overflow-y-auto {$class}'>"; + + foreach ($persons as $person) { + $personList .= anchor( + $person->information_url ?? '#', + "<img + src='{$person->image->thumbnail_url}' + alt='$person->full_name' + class='object-cover w-12 h-12 rounded-full' />", + [ + 'class' => + 'flex-shrink-0 focus:outline-none focus:ring focus:ring-inset', + 'target' => '_blank', + 'rel' => 'noreferrer noopener', + 'title' => + '<strong>' . + $person->full_name . + '</strong>' . + implode( + array_map(function ($role) { + return '<br />' . + lang( + 'PersonsTaxonomy.persons.' . + $role->group . + '.roles.' . + $role->role . + '.label', + ); + }, $person->roles), + ), + 'data-toggle' => 'tooltip', + 'data-placement' => 'bottom', + ], + ); + } + + $personList .= '</div>'; + + return $personList; + } +} + +// ------------------------------------------------------------------------ diff --git a/app/Helpers/form_helper.php b/app/Helpers/form_helper.php index 180efd5e43..2a8830086e 100644 --- a/app/Helpers/form_helper.php +++ b/app/Helpers/form_helper.php @@ -99,14 +99,14 @@ if (!function_exists('form_label')) { /** * Form Label Tag * - * @param string $label_text The text to appear onscreen + * @param string $text The text to appear onscreen * @param string $id The id the label applies to * @param array<string, string> $attributes Additional attributes * @param string $hintText Hint text to add next to the label * @param boolean $isOptional adds an optional text if true */ function form_label( - string $label_text = '', + string $text = '', string $id = '', array $attributes = [], string $hintText = '', @@ -124,19 +124,19 @@ if (!function_exists('form_label')) { } } - $label_content = $label_text; + $labelContent = $text; if ($isOptional) { - $label_content .= + $labelContent .= '<small class="ml-1 lowercase">(' . lang('Common.optional') . ')</small>'; } if ($hintText !== '') { - $label_content .= hint_tooltip($hintText, 'ml-1'); + $labelContent .= hint_tooltip($hintText, 'ml-1'); } - return $label . '>' . $label_content . '</label>'; + return $label . '>' . $labelContent . '</label>'; } } diff --git a/app/Helpers/id3_helper.php b/app/Helpers/id3_helper.php index b0739c6c73..d13239dda6 100644 --- a/app/Helpers/id3_helper.php +++ b/app/Helpers/id3_helper.php @@ -54,9 +54,9 @@ if (!function_exists('write_audio_file_tags')) { $APICdata = file_get_contents($cover->getRealPath()); // TODO: variables used for podcast specific tags - // $podcast_url = $episode->podcast->link; - // $podcast_feed_url = $episode->podcast->feed_url; - // $episode_media_url = $episode->link; + // $podcastUrl = $episode->podcast->link; + // $podcastFeedUrl = $episode->podcast->feed_url; + // $episodeMediaUrl = $episode->link; // populate data array $TagData = [ @@ -74,7 +74,7 @@ if (!function_exists('write_audio_file_tags')) { ], 'genre' => ['Podcast'], 'comment' => [$episode->description], - 'track_number' => [strval($episode->number)], + 'track_number' => [(string) $episode->number], 'copyright_message' => [$episode->podcast->copyright], 'publisher' => [ empty($episode->podcast->publisher) diff --git a/app/Helpers/location_helper.php b/app/Helpers/location_helper.php deleted file mode 100644 index 9a91e8a1d5..0000000000 --- a/app/Helpers/location_helper.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php - -/** - * @copyright 2020 Podlibre - * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 - * @link https://castopod.org/ - */ - -use Config\Services; - -if (!function_exists('fetch_osm_location')) { - /** - * Fetches places from Nominatim OpenStreetMap - * - * TODO: move this to Location object? - * - * @return array<string, string>|null - */ - function fetch_osm_location(string $locationName): ?array - { - $osmObject = null; - - try { - $client = Services::curlrequest(); - - $response = $client->request( - 'GET', - 'https://nominatim.openstreetmap.org/search.php?q=' . - urlencode($locationName) . - '&polygon_geojson=1&format=jsonv2', - [ - 'headers' => [ - 'User-Agent' => 'Castopod/' . CP_VERSION, - 'Accept' => 'application/json', - ], - ], - ); - $places = json_decode( - $response->getBody(), - true, - 512, - JSON_THROW_ON_ERROR, - ); - - $osmObject = [ - 'geo' => - empty($places[0]['lat']) || empty($places[0]['lon']) - ? null - : "geo:{$places[0]['lat']},{$places[0]['lon']}", - 'osm_id' => empty($places[0]['osm_type']) - ? null - : strtoupper(substr($places[0]['osm_type'], 0, 1)) . - $places[0]['osm_id'], - ]; - } catch (Exception $exception) { - //If things go wrong the show must go on - log_message('critical', $exception); - } - - return $osmObject; - } -} diff --git a/app/Helpers/misc_helper.php b/app/Helpers/misc_helper.php index d8d4e8a29e..d90e8fa5f3 100644 --- a/app/Helpers/misc_helper.php +++ b/app/Helpers/misc_helper.php @@ -29,7 +29,7 @@ if (!function_exists('slugify')) { // replace non letter or digits by - $text = preg_replace('~[^\pL\d]+~u', '-', $text); - $unwanted_array = [ + $unwanted = [ 'Å ' => 'S', 'Å¡' => 's', 'Ä' => 'Dj', @@ -107,7 +107,7 @@ if (!function_exists('slugify')) { '/' => '-', ' ' => '-', ]; - $text = strtr($text, $unwanted_array); + $text = strtr($text, $unwanted); // transliterate $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index 41adc5460d..8fed1cd0ad 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -23,31 +23,31 @@ if (!function_exists('get_rss_feed')) { { $episodes = $podcast->episodes; - $itunes_namespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + $itunesNamespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; - $podcast_namespace = + $podcastNamespace = 'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md'; $rss = new SimpleRSSElement( - "<?xml version='1.0' encoding='utf-8'?><rss version='2.0' xmlns:itunes='{$itunes_namespace}' xmlns:podcast='{$podcast_namespace}' xmlns:content='http://purl.org/rss/1.0/modules/content/'></rss>", + "<?xml version='1.0' encoding='utf-8'?><rss version='2.0' xmlns:itunes='{$itunesNamespace}' xmlns:podcast='{$podcastNamespace}' xmlns:content='http://purl.org/rss/1.0/modules/content/'></rss>", ); $channel = $rss->addChild('channel'); - $atom_link = $channel->addChild( + $atomLink = $channel->addChild( 'atom:link', null, 'http://www.w3.org/2005/Atom', ); - $atom_link->addAttribute('href', $podcast->feed_url); - $atom_link->addAttribute('rel', 'self'); - $atom_link->addAttribute('type', 'application/rss+xml'); + $atomLink->addAttribute('href', $podcast->feed_url); + $atomLink->addAttribute('rel', 'self'); + $atomLink->addAttribute('type', 'application/rss+xml'); if ($podcast->new_feed_url !== null) { $channel->addChild( 'new-feed-url', $podcast->new_feed_url, - $itunes_namespace, + $itunesNamespace, ); } @@ -65,33 +65,30 @@ if (!function_exists('get_rss_feed')) { $channel->addChild('title', $podcast->title); $channel->addChildWithCDATA('description', $podcast->description_html); - $itunes_image = $channel->addChild('image', null, $itunes_namespace); + $itunesImage = $channel->addChild('image', null, $itunesNamespace); // FIXME: This should be downsized to 1400x1400 - $itunes_image->addAttribute('href', $podcast->image->url); + $itunesImage->addAttribute('href', $podcast->image->url); $channel->addChild('language', $podcast->language_code); if ($podcast->location !== null) { $locationElement = $channel->addChild( 'location', htmlspecialchars($podcast->location->name), - $podcast_namespace, + $podcastNamespace, ); if ($podcast->location->geo !== null) { $locationElement->addAttribute('geo', $podcast->location->geo); } - if ($podcast->location->osm_id !== null) { - $locationElement->addAttribute( - 'osm', - $podcast->location->osm_id, - ); + if ($podcast->location->osm !== null) { + $locationElement->addAttribute('osm', $podcast->location->osm); } } if ($podcast->payment_pointer !== null) { $valueElement = $channel->addChild( 'value', null, - $podcast_namespace, + $podcastNamespace, ); $valueElement->addAttribute('type', 'webmonetization'); $valueElement->addAttribute('method', ''); @@ -99,7 +96,7 @@ if (!function_exists('get_rss_feed')) { $recipientElement = $valueElement->addChild( 'valueRecipient', null, - $podcast_namespace, + $podcastNamespace, ); $recipientElement->addAttribute('name', $podcast->owner_name); $recipientElement->addAttribute('type', 'ILP'); @@ -113,14 +110,14 @@ if (!function_exists('get_rss_feed')) { ->addChild( 'locked', $podcast->is_locked ? 'yes' : 'no', - $podcast_namespace, + $podcastNamespace, ) ->addAttribute('owner', $podcast->owner_email); if ($podcast->imported_feed_url !== null) { $channel->addChild( 'previousUrl', $podcast->imported_feed_url, - $podcast_namespace, + $podcastNamespace, ); } @@ -128,7 +125,7 @@ if (!function_exists('get_rss_feed')) { $podcastingPlatformElement = $channel->addChild( 'id', null, - $podcast_namespace, + $podcastNamespace, ); $podcastingPlatformElement->addAttribute( 'platform', @@ -152,7 +149,7 @@ if (!function_exists('get_rss_feed')) { $socialPlatformElement = $channel->addChild( 'social', $socialPlatform->link_content, - $podcast_namespace, + $podcastNamespace, ); $socialPlatformElement->addAttribute( 'platform', @@ -170,7 +167,7 @@ if (!function_exists('get_rss_feed')) { $fundingPlatformElement = $channel->addChild( 'funding', $fundingPlatform->link_content, - $podcast_namespace, + $podcastNamespace, ); $fundingPlatformElement->addAttribute( 'platform', @@ -188,7 +185,7 @@ if (!function_exists('get_rss_feed')) { $podcastPersonElement = $channel->addChild( 'person', htmlspecialchars($podcastPerson->full_name), - $podcast_namespace, + $podcastNamespace, ); if ( @@ -242,29 +239,29 @@ if (!function_exists('get_rss_feed')) { $channel->addChild( 'explicit', $podcast->parental_advisory === 'explicit' ? 'true' : 'false', - $itunes_namespace, + $itunesNamespace, ); $channel->addChild( 'author', $podcast->publisher ? $podcast->publisher : $podcast->owner_name, - $itunes_namespace, + $itunesNamespace, ); $channel->addChild('link', $podcast->link); - $owner = $channel->addChild('owner', null, $itunes_namespace); + $owner = $channel->addChild('owner', null, $itunesNamespace); - $owner->addChild('name', $podcast->owner_name, $itunes_namespace); + $owner->addChild('name', $podcast->owner_name, $itunesNamespace); - $owner->addChild('email', $podcast->owner_email, $itunes_namespace); + $owner->addChild('email', $podcast->owner_email, $itunesNamespace); - $channel->addChild('type', $podcast->type, $itunes_namespace); + $channel->addChild('type', $podcast->type, $itunesNamespace); $podcast->copyright && $channel->addChild('copyright', $podcast->copyright); $podcast->is_blocked && - $channel->addChild('block', 'Yes', $itunes_namespace); + $channel->addChild('block', 'Yes', $itunesNamespace); $podcast->is_completed && - $channel->addChild('complete', 'Yes', $itunes_namespace); + $channel->addChild('complete', 'Yes', $itunesNamespace); $image = $channel->addChild('image'); $image->addChild('url', $podcast->image->feed_url); @@ -304,7 +301,7 @@ if (!function_exists('get_rss_feed')) { $locationElement = $item->addChild( 'location', htmlspecialchars($episode->location->name), - $podcast_namespace, + $podcastNamespace, ); if ($episode->location->geo !== null) { $locationElement->addAttribute( @@ -312,10 +309,10 @@ if (!function_exists('get_rss_feed')) { $episode->location->geo, ); } - if ($episode->location->osm_id !== null) { + if ($episode->location->osm !== null) { $locationElement->addAttribute( 'osm', - $episode->location->osm_id, + $episode->location->osm, ); } } @@ -326,15 +323,15 @@ if (!function_exists('get_rss_feed')) { $item->addChild( 'duration', $episode->audio_file_duration, - $itunes_namespace, + $itunesNamespace, ); $item->addChild('link', $episode->link); - $episode_itunes_image = $item->addChild( + $episodeItunesImage = $item->addChild( 'image', null, - $itunes_namespace, + $itunesNamespace, ); - $episode_itunes_image->addAttribute( + $episodeItunesImage->addAttribute( 'href', $episode->image->feed_url, ); @@ -345,24 +342,24 @@ if (!function_exists('get_rss_feed')) { $episode->parental_advisory === 'explicit' ? 'true' : 'false', - $itunes_namespace, + $itunesNamespace, ); $episode->number && - $item->addChild('episode', $episode->number, $itunes_namespace); + $item->addChild('episode', $episode->number, $itunesNamespace); $episode->season_number && $item->addChild( 'season', $episode->season_number, - $itunes_namespace, + $itunesNamespace, ); - $item->addChild('episodeType', $episode->type, $itunes_namespace); + $item->addChild('episodeType', $episode->type, $itunesNamespace); if ($episode->transcript_file_url) { $transcriptElement = $item->addChild( 'transcript', null, - $podcast_namespace, + $podcastNamespace, ); $transcriptElement->addAttribute( 'url', @@ -387,7 +384,7 @@ if (!function_exists('get_rss_feed')) { $chaptersElement = $item->addChild( 'chapters', null, - $podcast_namespace, + $podcastNamespace, ); $chaptersElement->addAttribute( 'url', @@ -403,7 +400,7 @@ if (!function_exists('get_rss_feed')) { $soundbiteElement = $item->addChild( 'soundbite', empty($soundbite->label) ? null : $soundbite->label, - $podcast_namespace, + $podcastNamespace, ); $soundbiteElement->addAttribute( 'start_time', @@ -419,7 +416,7 @@ if (!function_exists('get_rss_feed')) { $episodePersonElement = $item->addChild( 'person', htmlspecialchars($episodePerson->full_name), - $podcast_namespace, + $podcastNamespace, ); if ( !empty($episodePerson->role) && @@ -461,7 +458,7 @@ if (!function_exists('get_rss_feed')) { } $episode->is_blocked && - $item->addChild('block', 'Yes', $itunes_namespace); + $item->addChild('block', 'Yes', $itunesNamespace); if (!empty($episode->custom_rss)) { array_to_rss( @@ -483,10 +480,10 @@ if (!function_exists('add_category_tag')) { */ function add_category_tag(SimpleXMLElement $node, Category $category): void { - $itunes_namespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + $itunesNamespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; - $itunes_category = $node->addChild('category', '', $itunes_namespace); - $itunes_category->addAttribute( + $itunesCategory = $node->addChild('category', '', $itunesNamespace); + $itunesCategory->addAttribute( 'text', $category->parent !== null ? $category->parent->apple_category @@ -494,12 +491,12 @@ if (!function_exists('add_category_tag')) { ); if ($category->parent !== null) { - $itunes_category_child = $itunes_category->addChild( + $itunesCategoryChild = $itunesCategory->addChild( 'category', '', - $itunes_namespace, + $itunesNamespace, ); - $itunes_category_child->addAttribute( + $itunesCategoryChild->addAttribute( 'text', $category->apple_category, ); diff --git a/app/Helpers/svg_helper.php b/app/Helpers/svg_helper.php index b612016921..6078aa771f 100644 --- a/app/Helpers/svg_helper.php +++ b/app/Helpers/svg_helper.php @@ -16,16 +16,16 @@ if (!function_exists('icon')) { */ function icon(string $name, string $class = ''): string { - $svg_contents = file_get_contents('assets/icons/' . $name . '.svg'); + $svgContents = file_get_contents('assets/icons/' . $name . '.svg'); if ($class !== '') { - $svg_contents = str_replace( + $svgContents = str_replace( '<svg', '<svg class="' . $class . '"', - $svg_contents, + $svgContents, ); } - return $svg_contents; + return $svgContents; } } @@ -39,14 +39,14 @@ if (!function_exists('svg')) { */ function svg(string $name, ?string $class = null): string { - $svg_contents = file_get_contents('assets/images/' . $name . '.svg'); + $svgContents = file_get_contents('assets/images/' . $name . '.svg'); if ($class) { - $svg_contents = str_replace( + $svgContents = str_replace( '<svg', '<svg class="' . $class . '"', - $svg_contents, + $svgContents, ); } - return $svg_contents; + return $svgContents; } } diff --git a/app/Helpers/url_helper.php b/app/Helpers/url_helper.php index f92d34b0b8..d58614bb5b 100644 --- a/app/Helpers/url_helper.php +++ b/app/Helpers/url_helper.php @@ -27,22 +27,6 @@ if (!function_exists('host_url')) { } } -if (!function_exists('current_season_url')) { - /** - * Return the podcast URL with season number to use in views - */ - function current_season_url(): string - { - $season_query_string = ''; - if (isset($_GET['season'])) { - $season_query_string = '?season=' . $_GET['season']; - } elseif (isset($_GET['year'])) { - $season_query_string = '?year=' . $_GET['year']; - } - return current_url() . $season_query_string; - } -} - //-------------------------------------------------------------------- if (!function_exists('extract_params_from_episode_uri')) { diff --git a/app/Libraries/ActivityPub/ActivityRequest.php b/app/Libraries/ActivityPub/ActivityRequest.php index f2c88e09da..a79559763f 100644 --- a/app/Libraries/ActivityPub/ActivityRequest.php +++ b/app/Libraries/ActivityPub/ActivityRequest.php @@ -85,7 +85,7 @@ class ActivityRequest $date = Time::now('GMT')->format('D, d M Y H:i:s T'); $digest = 'SHA-256=' . base64_encode($this->getBodyDigest()); $contentType = $this->options['headers']['Content-Type']; - $contentLength = strval(strlen($this->request->getBody())); + $contentLength = (string) strlen($this->request->getBody()); $userAgent = 'Castopod'; $plainText = "(request-target): post {$path}\nhost: {$host}\ndate: {$date}\ndigest: {$digest}\ncontent-type: {$contentType}\ncontent-length: {$contentLength}\nuser-agent: {$userAgent}"; diff --git a/app/Libraries/ActivityPub/Helpers/activitypub_helper.php b/app/Libraries/ActivityPub/Helpers/activitypub_helper.php index 53df425dfc..abb73622c8 100644 --- a/app/Libraries/ActivityPub/Helpers/activitypub_helper.php +++ b/app/Libraries/ActivityPub/Helpers/activitypub_helper.php @@ -182,7 +182,7 @@ if (!function_exists('create_preview_card_from_url')) { // Check that, at least, the url and title are set if ($media->url && $media->title) { - $preview_card = new PreviewCard([ + $newPreviewCard = new PreviewCard([ 'url' => (string) $url, 'title' => $media->title, 'description' => $media->description, @@ -199,15 +199,15 @@ if (!function_exists('create_preview_card_from_url')) { if ( !($newPreviewCardId = model('PreviewCardModel')->insert( - $preview_card, + $newPreviewCard, true, )) ) { return null; } - $preview_card->id = $newPreviewCardId; - return $preview_card; + $newPreviewCard->id = $newPreviewCardId; + return $newPreviewCard; } } diff --git a/app/Libraries/ActivityPub/HttpSignature.php b/app/Libraries/ActivityPub/HttpSignature.php index f62e2fe690..46ab35ae05 100644 --- a/app/Libraries/ActivityPub/HttpSignature.php +++ b/app/Libraries/ActivityPub/HttpSignature.php @@ -45,7 +45,7 @@ class HttpSignature public function __construct(IncomingRequest $request = null) { - if (is_null($request)) { + if ($request === null) { $request = Services::request(); } diff --git a/app/Libraries/Analytics/Controllers/AnalyticsController.php b/app/Libraries/Analytics/Controllers/AnalyticsController.php index 9f0ea4ace1..40c0478338 100644 --- a/app/Libraries/Analytics/Controllers/AnalyticsController.php +++ b/app/Libraries/Analytics/Controllers/AnalyticsController.php @@ -39,18 +39,21 @@ class AnalyticsController extends Controller ); } - public function getData(int $podcastId, int $episodeId): ResponseInterface - { - $analytics_model = new $this->className(); + public function getData( + int $podcastId, + ?int $episodeId = null + ): ResponseInterface { + $analyticsModel = new $this->className(); $methodName = $this->methodName; - if ($episodeId !== 0) { + + if ($episodeId === null) { return $this->response->setJSON( - $analytics_model->$methodName($podcastId, $episodeId), + $analyticsModel->$methodName($podcastId), ); } return $this->response->setJSON( - $analytics_model->$methodName($podcastId), + $analyticsModel->$methodName($podcastId, $episodeId), ); } } diff --git a/app/Libraries/SimpleRSSElement.php b/app/Libraries/SimpleRSSElement.php index 1bb2517b64..aa2b45fbd0 100644 --- a/app/Libraries/SimpleRSSElement.php +++ b/app/Libraries/SimpleRSSElement.php @@ -26,15 +26,15 @@ class SimpleRSSElement extends SimpleXMLElement string $value = '', ?string $namespace = null ) { - $new_child = parent::addChild($name, '', $namespace); + $newChild = parent::addChild($name, '', $namespace); - if ($new_child !== null) { - $node = dom_import_simplexml($new_child); + if ($newChild !== null) { + $node = dom_import_simplexml($newChild); $no = $node->ownerDocument; $node->appendChild($no->createCDATASection($value)); } - return $new_child; + return $newChild; } /** @@ -49,14 +49,14 @@ class SimpleRSSElement extends SimpleXMLElement */ public function addChild($name, $value = null, $namespace = null) { - $new_child = parent::addChild($name, '', $namespace); + $newChild = parent::addChild($name, '', $namespace); - if ($new_child !== null) { - $node = dom_import_simplexml($new_child); + if ($newChild !== null) { + $node = dom_import_simplexml($newChild); $no = $node->ownerDocument; $node->appendChild($no->createTextNode(esc($value))); } - return $new_child; + return $newChild; } } diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index c39e741719..cde7b10271 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -83,7 +83,7 @@ class EpisodeModel extends Model 'is_blocked', 'location_name', 'location_geo', - 'location_osm_id', + 'location_osm', 'custom_rss', 'favourites_total', 'reblogs_total', diff --git a/app/Models/PersonModel.php b/app/Models/PersonModel.php index ae861561d7..d7ce0be846 100644 --- a/app/Models/PersonModel.php +++ b/app/Models/PersonModel.php @@ -19,6 +19,7 @@ class PersonModel extends Model * @var string */ protected $table = 'persons'; + /** * @var string */ @@ -98,6 +99,90 @@ class PersonModel extends Model return $this->where('full_name', $fullName)->first(); } + /** + * @return stdClass[] + */ + public function getPersonRoles(int $personId, int $podcastId, ?int $episodeId): array { + if ($episodeId) { + $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_person#{$personId}_roles"; + + if (!($found = cache($cacheName))) { + $found = $this + ->select('episodes_persons.person_group as group, episodes_persons.person_role as role') + ->join('episodes_persons', 'persons.id = episodes_persons.person_id') + ->where('persons.id', $personId) + ->where('episodes_persons.episode_id', $episodeId) + ->get() + ->getResultObject(); + } + } else { + $cacheName = "podcast#{$podcastId}_person#{$personId}_roles"; + + if (!($found = cache($cacheName))) { + $found = $this + ->select('podcasts_persons.person_group as group, podcasts_persons.person_role as role') + ->join('podcasts_persons', 'persons.id = podcasts_persons.person_id') + ->where('persons.id', $personId) + ->where('podcasts_persons.podcast_id', $podcastId) + ->get() + ->getResultObject(); + } + } + + return $found; + } + + /** + * @return array<string, string> + */ + public function getPersonOptions(): array + { + $options = []; + + if (!($options = cache('person_options'))) { + $options = array_reduce( + $this->select('`id`, `full_name`') + ->orderBy('`full_name`', 'ASC') + ->findAll(), + function ($result, $person) { + $result[$person->id] = $person->full_name; + return $result; + }, + [], + ); + cache()->save('person_options', $options, DECADE); + } + + return $options; + } + + /** + * @return array<string, string> + */ + public function getTaxonomyOptions(): array + { + $options = []; + $locale = service('request')->getLocale(); + $cacheName = "taxonomy_options_{$locale}"; + + /** @var array<string, array> */ + $personsTaxonomy = lang('PersonsTaxonomy.persons'); + + if (!($options = cache($cacheName))) { + foreach ($personsTaxonomy as $group_key => $group) { + foreach ($group['roles'] as $role_key => $role) { + $options[ + "{$group_key},{$role_key}" + ] = "{$group['label']} › {$role['label']}"; + } + } + + cache()->save($cacheName, $options, DECADE); + } + + return $options; + } + public function addPerson( string $fullName, ?string $informationUrl, @@ -122,12 +207,12 @@ class PersonModel extends Model { $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_persons"; if (!($found = cache($cacheName))) { - $found = $this->db - ->table('episodes_persons') - ->select('episodes_persons.*') - ->where('episode_id', $episodeId) - ->join('persons', 'person_id=persons.id') - ->orderby('full_name') + $found = $this + ->select('persons.*, episodes_persons.podcast_id, episodes_persons.episode_id') + ->distinct() + ->join('episodes_persons', 'persons.id = episodes_persons.person_id') + ->where('episodes_persons.episode_id', $episodeId) + ->orderby('persons.full_name') ->findAll(); cache()->save($cacheName, $found, DECADE); @@ -143,12 +228,12 @@ class PersonModel extends Model { $cacheName = "podcast#{$podcastId}_persons"; if (!($found = cache($cacheName))) { - $found = $this->db - ->table('podcasts_persons') - ->select('podcasts_persons.*') - ->where('podcast_id', $podcastId) - ->join('persons', 'person_id=persons.id') - ->orderby('full_name') + $found = $this + ->select('persons.*, podcasts_persons.podcast_id as podcast_id') + ->distinct() + ->join('podcasts_persons', 'persons.id=podcasts_persons.person_id') + ->where('podcasts_persons.podcast_id', $podcastId) + ->orderby('persons.full_name') ->findAll(); cache()->save($cacheName, $found, DECADE); @@ -161,29 +246,29 @@ class PersonModel extends Model int $podcastId, int $episodeId, int $personId, - string $group, - string $role + string $groupSlug, + string $roleSlug ): int|bool { return $this->db->table('episodes_persons')->insert([ 'podcast_id' => $podcastId, 'episode_id' => $episodeId, 'person_id' => $personId, - 'person_group' => $group, - 'person_role' => $role, + 'person_group' => $groupSlug, + 'person_role' => $roleSlug, ]); } public function addPodcastPerson( int $podcastId, int $personId, - string $group, - string $role + string $groupSlug, + string $roleSlug ): int|bool { return $this->db->table('podcasts_persons')->insert([ 'podcast_id' => $podcastId, 'person_id' => $personId, - 'person_group' => $group, - 'person_role' => $role, + 'person_group' => $groupSlug, + 'person_role' => $roleSlug, ]); } @@ -198,34 +283,36 @@ class PersonModel extends Model public function addPodcastPersons( int $podcastId, array $persons = [], - array $groupsRoles = [] + array $roles = [] ): int|bool { if ($persons === []) { return 0; } - $this->clearCache(['podcast_id' => $podcastId]); + cache()->delete("podcast#{$podcastId}_persons"); + (new PodcastModel())->clearCache(['id' => $podcastId]); + $data = []; foreach ($persons as $person) { - if ($groupsRoles === []) { + if ($roles === []) { $data[] = [ 'podcast_id' => $podcastId, 'person_id' => $person, ]; } - foreach ($groupsRoles as $group_role) { - $group_role = explode(',', $group_role); + foreach ($roles as $role) { + $groupRole = explode(',', $role); $data[] = [ 'podcast_id' => $podcastId, 'person_id' => $person, - 'person_group' => $group_role[0], - 'person_role' => $group_role[1], + 'person_group' => $groupRole[0], + 'person_role' => $groupRole[1], ]; } } - return $this->insertBatch($data); + return $this->db->table('podcasts_persons')->insertBatch($data); } /** @@ -233,11 +320,11 @@ class PersonModel extends Model * * @return BaseResult|bool Number of rows inserted or FALSE on failure */ - public function removePodcastPersons(int $podcastId, int $personId): BaseResult|bool + public function removePersonFromPodcast(int $podcastId, int $personId): BaseResult|bool { - return $this->delete([ - 'id' => $personId, + return $this->db->table('podcasts_persons')->delete([ 'podcast_id' => $podcastId, + 'person_id' => $personId, ]); } @@ -245,7 +332,7 @@ class PersonModel extends Model * Add persons to episode * * @param int[] $personIds - * @param string[] $groups_roles + * @param string[] $groupsRoles * * @return bool|int Number of rows inserted or FALSE on failure */ @@ -253,24 +340,22 @@ class PersonModel extends Model int $podcastId, int $episodeId, array $personIds, - array $groups_roles + array $groupsRoles ): bool|int { if (!empty($personIds)) { - $this->clearCache([ - 'episode_id' => $episodeId, - ]); + (new EpisodeModel())->clearCache(['id' => $episodeId]); $data = []; foreach ($personIds as $personId) { - if ($groups_roles !== []) { - foreach ($groups_roles as $group_role) { - $group_role = explode(',', $group_role); + if ($groupsRoles !== []) { + foreach ($groupsRoles as $groupRole) { + $groupRole = explode(',', $groupRole); $data[] = [ 'podcast_id' => $podcastId, 'episode_id' => $episodeId, 'person_id' => $personId, - 'person_group' => $group_role[0], - 'person_role' => $group_role[1], + 'person_group' => $groupRole[0], + 'person_role' => $groupRole[1], ]; } } else { @@ -281,7 +366,7 @@ class PersonModel extends Model ]; } } - return $this->insertBatch($data); + return $this->db->table('episodes_persons')->insertBatch($data); } return 0; } @@ -289,69 +374,18 @@ class PersonModel extends Model /** * @return BaseResult|bool */ - public function removeEpisodePersons( + public function removePersonFromEpisode( int $podcastId, int $episodeId, int $personId ): BaseResult|bool { - return $this->delete([ + return $this->db->table('episodes_persons')->delete([ 'podcast_id' => $podcastId, 'episode_id' => $episodeId, - 'id' => $personId, + 'person_id' => $personId, ]); } - /** - * @return array<string, string> - */ - public function getPersonOptions(): array - { - $options = []; - - if (!($options = cache('person_options'))) { - $options = array_reduce( - $this->select('`id`, `full_name`') - ->orderBy('`full_name`', 'ASC') - ->findAll(), - function ($result, $person) { - $result[$person->id] = $person->full_name; - return $result; - }, - [], - ); - cache()->save('person_options', $options, DECADE); - } - - return $options; - } - - /** - * @return array<string, string> - */ - public function getTaxonomyOptions(): array - { - $options = []; - $locale = service('request')->getLocale(); - $cacheName = "taxonomy_options_{$locale}"; - - /** @var array<string, array> */ - $personsTaxonomy = lang('PersonsTaxonomy.persons'); - - if (!($options = cache($cacheName))) { - foreach ($personsTaxonomy as $group_key => $group) { - foreach ($group['roles'] as $role_key => $role) { - $options[ - "{$group_key},{$role_key}" - ] = "{$group['label']} â–¸ {$role['label']}"; - } - } - - cache()->save($cacheName, $options, DECADE); - } - - return $options; - } - /** * @param mixed[] $data * @@ -359,12 +393,10 @@ class PersonModel extends Model */ protected function clearCache(array $data): array { - $person = (new PersonModel())->find( - is_array($data['id']) ? $data['id'][0] : $data['id'], - ); + $personId = is_array($data['id']) ? $data['id']['id'] : $data['id']; cache()->delete('person_options'); - cache()->delete("person#{$person->id}"); + cache()->delete("person#{$personId}"); // clear cache for every credits page cache()->deleteMatching('page_credits_*'); diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index daf491beb4..dddcc5e095 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -53,7 +53,7 @@ class PodcastModel extends Model 'is_locked', 'location_name', 'location_geo', - 'location_osm_id', + 'location_osm', 'payment_pointer', 'custom_rss', 'partner_id', @@ -218,11 +218,11 @@ class PodcastModel extends Model ->delete(); } - public function getContributorGroupId(int $userId, int $podcastId): int|false + public function getContributorGroupId(int $userId, int|string $podcastId): int|false { if (!is_numeric($podcastId)) { // identifier is the podcast name, request must be a join - $user_podcast = $this->db + $userPodcast = $this->db ->table('podcasts_users') ->select('group_id, user_id') ->join('podcasts', 'podcasts.id = podcasts_users.podcast_id') @@ -233,7 +233,7 @@ class PodcastModel extends Model ->get() ->getResultObject(); } else { - $user_podcast = $this->db + $userPodcast = $this->db ->table('podcasts_users') ->select('group_id') ->where([ @@ -244,8 +244,8 @@ class PodcastModel extends Model ->getResultObject(); } - return count($user_podcast) > 0 - ? $user_podcast[0]->group_id + return count($userPodcast) > 0 + ? $userPodcast[0]->group_id : false; } @@ -446,7 +446,7 @@ class PodcastModel extends Model * * @return mixed[] */ - protected function clearCache(array $data): array + public function clearCache(array $data): array { $podcast = (new PodcastModel())->getPodcastById( is_array($data['id']) ? $data['id'][0] : $data['id'], diff --git a/app/Views/admin/episode/person.php b/app/Views/admin/episode/persons.php similarity index 58% rename from app/Views/admin/episode/person.php rename to app/Views/admin/episode/persons.php index 03a828f172..08d681de13 100644 --- a/app/Views/admin/episode/person.php +++ b/app/Views/admin/episode/persons.php @@ -5,7 +5,7 @@ <?= $this->endSection() ?> <?= $this->section('pageTitle') ?> -<?= lang('Person.episode_form.title') ?> (<?= count($episodePersons) ?>) +<?= lang('Person.episode_form.title') ?> (<?= count($episode->persons) ?>) <?= $this->endSection() ?> <?= $this->section('headerRight') ?> @@ -25,7 +25,6 @@ ]) ?> <?= csrf_field() ?> -<?php if ($episodePersons): ?> <?= form_section( lang('Person.episode_form.manage_section_title'), @@ -37,42 +36,45 @@ [ [ 'header' => lang('Person.episode_form.person'), - 'cell' => function ($episodePerson) { + 'cell' => function ($person) { return '<div class="flex">' . '<a href="' . - route_to('person-view', $episodePerson->person->id) . - "\"><img src=\"{$episodePerson->person->image->thumbnail_url}\" alt=\"{$episodePerson->person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" . + route_to('person-view', $person->id) . + "\"><img src=\"{$person->image->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" . '<div class="flex flex-col ml-3">' . - $episodePerson->person->full_name . - ($episodePerson->person_group && $episodePerson->person_role - ? '<span class="text-sm text-gray-600">' . - lang( - "PersonsTaxonomy.persons.{$episodePerson->person_group}.label", - ) . - ' â–¸ ' . - lang( - "PersonsTaxonomy.persons.{$episodePerson->person_group}.roles.{$episodePerson->person_role}.label", - ) . - '</span>' - : '') . - (empty($episodePerson->person->information_url) + $person->full_name . + implode( + '', + array_map(function ($role) { + return '<span class="text-sm text-gray-600">' . + lang( + "PersonsTaxonomy.persons.{$role->group}.label", + ) . + ' › ' . + lang( + "PersonsTaxonomy.persons.{$role->group}.roles.{$role->role}.label", + ) . + '</span>'; + }, $person->roles), + ) . + ($person->information_url === null ? '' - : "<a href=\"{$episodePerson->person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . - $episodePerson->person->information_url . + : "<a href=\"{$person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . + $person->information_url . '</a>') . '</div></div>'; }, ], [ 'header' => lang('Common.actions'), - 'cell' => function ($episodePerson): string { + 'cell' => function ($person): string { return button( lang('Person.episode_form.remove'), route_to( 'episode-person-remove', - $episodePerson->podcast_id, - $episodePerson->episode_id, - $episodePerson->id, + $person->podcast_id, + $person->episode_id, + $person->id, ), [ 'variant' => 'danger', @@ -82,11 +84,10 @@ }, ], ], - $episodePersons, + $episode->persons, ) ?> <?= form_section_close() ?> -<?php endif; ?> <?= form_section( @@ -117,10 +118,7 @@ 'person_group_role[]', $taxonomyOptions, old('person_group_role', []), - [ - 'id' => 'person_group_role', - 'class' => 'form-select mb-4', - ], + ['id' => 'person_group_role', 'class' => 'form-select mb-4'], ) ?> diff --git a/app/Views/admin/episode/soundbites.php b/app/Views/admin/episode/soundbites.php index e8c7579607..462fa1a05e 100644 --- a/app/Views/admin/episode/soundbites.php +++ b/app/Views/admin/episode/soundbites.php @@ -53,34 +53,40 @@ <tr> <td class="px-1 py-2 font-medium bg-white border border-light-blue-500"><?= form_input( [ - 'id' => "soundbites_array[{$soundbite->id}][start_time]", - 'name' => "soundbites_array[{$soundbite->id}][start_time]", + 'type' => 'number', + 'min' => 0, + 'max' => $episode->audio_file_duration, + 'step' => 'any', + 'id' => "soundbites[{$soundbite->id}][start_time]", + 'name' => "soundbites[{$soundbite->id}][start_time]", 'class' => 'form-input w-full border-none text-center', 'value' => $soundbite->start_time, 'data-type' => 'soundbite-field', 'data-field-type' => 'start-time', 'data-soundbite-id' => $soundbite->id, 'required' => 'required', - 'min' => '0', ], ) ?></td> <td class="px-1 py-2 font-medium bg-white border border-light-blue-500"><?= form_input( [ - 'id' => "soundbites_array[{$soundbite->id}][duration]", - 'name' => "soundbites_array[{$soundbite->id}][duration]", + 'type' => 'number', + 'min' => 0, + 'max' => $episode->audio_file_duration, + 'step' => 'any', + 'id' => "soundbites[{$soundbite->id}][duration]", + 'name' => "soundbites[{$soundbite->id}][duration]", 'class' => 'form-input w-full border-none text-center', 'value' => $soundbite->duration, 'data-type' => 'soundbite-field', 'data-field-type' => 'duration', 'data-soundbite-id' => $soundbite->id, 'required' => 'required', - 'min' => '0', ], ) ?></td> <td class="px-1 py-2 font-medium bg-white border border-light-blue-500"><?= form_input( [ - 'id' => "soundbites_array[{$soundbite->id}][label]", - 'name' => "soundbites_array[{$soundbite->id}][label]", + 'id' => "soundbites[{$soundbite->id}][label]", + 'name' => "soundbites[{$soundbite->id}][label]", 'class' => 'form-input w-full border-none', 'value' => $soundbite->label, ], @@ -116,20 +122,27 @@ <tr> <td class="px-1 py-4 font-medium bg-white border border-light-blue-500"><?= form_input( [ - 'id' => 'soundbites_array[0][start_time]', - 'name' => 'soundbites_array[0][start_time]', + 'type' => 'number', + 'min' => 0, + 'max' => $episode->audio_file_duration, + 'step' => 'any', + 'id' => 'soundbites[0][start_time]', + 'name' => 'soundbites[0][start_time]', 'class' => 'form-input w-full border-none text-center', 'value' => old('start_time'), 'data-soundbite-id' => '0', 'data-type' => 'soundbite-field', 'data-field-type' => 'start-time', - 'min' => '0', ], ) ?></td> <td class="px-1 py-4 font-medium bg-white border border-light-blue-500"><?= form_input( [ - 'id' => 'soundbites_array[0][duration]', - 'name' => 'soundbites_array[0][duration]', + 'type' => 'number', + 'min' => 0, + 'max' => $episode->audio_file_duration, + 'step' => 'any', + 'id' => 'soundbites[0][duration]', + 'name' => 'soundbites[0][duration]', 'class' => 'form-input w-full border-none text-center', 'value' => old('duration'), 'data-soundbite-id' => '0', @@ -140,8 +153,8 @@ ) ?></td> <td class="px-1 py-4 font-medium bg-white border border-light-blue-500"><?= form_input( [ - 'id' => 'soundbites_array[0][label]', - 'name' => 'soundbites_array[0][label]', + 'id' => 'soundbites[0][label]', + 'name' => 'soundbites[0][label]', 'class' => 'form-input w-full border-none', 'value' => old('label'), ], @@ -149,7 +162,7 @@ <td class="px-4 py-2"><?= icon_button( 'play', lang('Episode.soundbites_form.play'), - null, + '', ['variant' => 'primary'], [ 'data-type' => 'play-soundbite', @@ -170,13 +183,12 @@ </td><td class="px-4 py-2"><?= icon_button( 'timer', lang('Episode.soundbites_form.bookmark'), - null, + '', ['variant' => 'info'], [ 'data-type' => 'get-soundbite', - 'data-start-time-field-name' => - 'soundbites_array[0][start_time]', - 'data-duration-field-name' => 'soundbites_array[0][duration]', + 'data-start-time-field-name' => 'soundbites[0][start_time]', + 'data-duration-field-name' => 'soundbites[0][duration]', ], ) ?></td></tr> </tbody> diff --git a/app/Views/admin/podcast/person.php b/app/Views/admin/podcast/person.php deleted file mode 100644 index f0a5b99cf0..0000000000 --- a/app/Views/admin/podcast/person.php +++ /dev/null @@ -1,135 +0,0 @@ -<?= $this->extend('admin/_layout') ?> - -<?= $this->section('title') ?> -<?= lang('Person.podcast_form.title') ?> -<?= $this->endSection() ?> - -<?= $this->section('pageTitle') ?> -<?= lang('Person.podcast_form.title') ?> (<?= count($podcastPersons) ?>) -<?= $this->endSection() ?> - -<?= $this->section('headerRight') ?> -<?= button( - lang('Person.create'), - route_to('person-create'), - ['variant' => 'primary', 'iconLeft' => 'add'], - ['class' => 'mr-2'], -) ?> -<?= $this->endSection() ?> - -<?= $this->section('content') ?> - -<?= form_open(route_to('podcast-person-edit', $podcast->id), [ - 'method' => 'post', - 'class' => 'flex flex-col', -]) ?> -<?= csrf_field() ?> - -<?php if ($podcastPersons): ?> - - <?= form_section( - lang('Person.podcast_form.manage_section_title'), - lang('Person.podcast_form.manage_section_subtitle'), - ) ?> - - - <?= data_table( - [ - [ - 'header' => lang('Person.podcast_form.person'), - 'cell' => function ($podcastPerson) { - return '<div class="flex">' . - '<a href="' . - route_to('person-view', $podcastPerson->person->id) . - "\"><img src=\"{$podcastPerson->person->image->thumbnail_url}\" alt=\"{$podcastPerson->person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" . - '<div class="flex flex-col ml-3">' . - $podcastPerson->person->full_name . - ($podcastPerson->person_group && - $podcastPerson->person_role - ? '<span class="text-sm text-gray-600">' . - lang( - "PersonsTaxonomy.persons.{$podcastPerson->person_group}.label", - ) . - ' â–¸ ' . - lang( - "PersonsTaxonomy.persons.{$podcastPerson->person_group}.roles.{$podcastPerson->person_role}.label", - ) . - '</span>' - : '') . - (empty($podcastPerson->person->information_url) - ? '' - : "<a href=\"{$podcastPerson->person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . - $podcastPerson->person->information_url . - '</a>') . - '</div></div>'; - }, - ], - [ - 'header' => lang('Common.actions'), - 'cell' => function ($podcastPerson): string { - return button( - lang('Person.podcast_form.remove'), - route_to( - 'podcast-person-remove', - $podcastPerson->podcast_id, - $podcastPerson->id, - ), - [ - 'variant' => 'danger', - 'size' => 'small', - ], - ); - }, - ], - ], - $podcastPersons, - ) ?> - - <?= form_section_close() ?> -<?php endif; ?> - - -<?= form_section( - lang('Person.podcast_form.add_section_title'), - lang('Person.podcast_form.add_section_subtitle'), -) ?> - -<?= form_label( - lang('Person.podcast_form.person'), - 'person', - [], - lang('Person.podcast_form.person_hint'), -) ?> -<?= form_multiselect('person[]', $personOptions, old('person', []), [ - 'id' => 'person', - 'class' => 'form-select mb-4', - 'required' => 'required', -]) ?> - -<?= form_label( - lang('Person.podcast_form.group_role'), - 'group_role', - [], - lang('Person.podcast_form.group_role_hint'), - true, -) ?> -<?= form_multiselect( - 'person_group_role[]', - $taxonomyOptions, - old('person_group_role', []), - [ - 'id' => 'person_group_role', - 'class' => 'form-select mb-4', - ], -) ?> - -<?= form_section_close() ?> -<?= button( - lang('Person.podcast_form.submit_add'), - '', - ['variant' => 'primary'], - ['type' => 'submit', 'class' => 'self-end'], -) ?> -<?= form_close() ?> - -<?= $this->endSection() ?> diff --git a/app/Views/admin/podcast/persons.php b/app/Views/admin/podcast/persons.php new file mode 100644 index 0000000000..8edb6876a2 --- /dev/null +++ b/app/Views/admin/podcast/persons.php @@ -0,0 +1,129 @@ +<?= $this->extend('admin/_layout') ?> + +<?= $this->section('title') ?> +<?= lang('Person.podcast_form.title') ?> +<?= $this->endSection() ?> + +<?= $this->section('pageTitle') ?> +<?= lang('Person.podcast_form.title') ?> (<?= count($podcast->persons) ?>) +<?= $this->endSection() ?> + +<?= $this->section('headerRight') ?> +<?= button( + lang('Person.create'), + route_to('person-create'), + ['variant' => 'primary', 'iconLeft' => 'add'], + ['class' => 'mr-2'], +) ?> +<?= $this->endSection() ?> + +<?= $this->section('content') ?> + +<?= form_open(route_to('podcast-person-edit', $podcast->id), [ + 'method' => 'post', + 'class' => 'flex flex-col', +]) ?> +<?= csrf_field() ?> + +<?= form_section( + lang('Person.podcast_form.manage_section_title'), + lang('Person.podcast_form.manage_section_subtitle'), +) ?> + + +<?= data_table( + [ + [ + 'header' => lang('Person.podcast_form.person'), + 'cell' => function ($person) { + return '<div class="flex">' . + '<a href="' . + route_to('person-view', $person->id) . + "\"><img src=\"{$person->image->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" . + '<div class="flex flex-col ml-3">' . + $person->full_name . + implode( + '', + array_map(function ($role) { + return '<span class="text-sm text-gray-600">' . + lang( + "PersonsTaxonomy.persons.{$role->group}.label", + ) . + ' › ' . + lang( + "PersonsTaxonomy.persons.{$role->group}.roles.{$role->role}.label", + ) . + '</span>'; + }, $person->roles), + ) . + ($person->information_url === null + ? '' + : "<a href=\"{$person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . + $person->information_url . + '</a>') . + '</div></div>'; + }, + ], + [ + 'header' => lang('Common.actions'), + 'cell' => function ($person): string { + return button( + lang('Person.podcast_form.remove'), + route_to( + 'podcast-person-remove', + $person->podcast_id, + $person->id, + ), + [ + 'variant' => 'danger', + 'size' => 'small', + ], + ); + }, + ], + ], + $podcast->persons, +) ?> + +<?= form_section_close() ?> + + +<?= form_section( + lang('Person.podcast_form.add_section_title'), + lang('Person.podcast_form.add_section_subtitle'), +) ?> + +<?= form_label( + lang('Person.podcast_form.person'), + 'person', + [], + lang('Person.podcast_form.person_hint'), +) ?> +<?= form_multiselect('persons[]', $personOptions, old('persons', []), [ + 'id' => 'persons', + 'class' => 'form-select mb-4', + 'required' => 'required', +]) ?> + +<?= form_label( + lang('Person.podcast_form.roles'), + 'roles', + [], + lang('Person.podcast_form.roles_hint'), + true, +) ?> +<?= form_multiselect('roles[]', $taxonomyOptions, old('roles', []), [ + 'id' => 'roles', + 'class' => 'form-select mb-4', +]) ?> + +<?= form_section_close() ?> +<?= button( + lang('Person.podcast_form.submit_add'), + '', + ['variant' => 'primary'], + ['type' => 'submit', 'class' => 'self-end'], +) ?> +<?= form_close() ?> + +<?= $this->endSection() ?> diff --git a/app/Views/errors/html/error_exception.php b/app/Views/errors/html/error_exception.php index 5ec36809c0..d8ef20ebc0 100644 --- a/app/Views/errors/html/error_exception.php +++ b/app/Views/errors/html/error_exception.php @@ -3,7 +3,7 @@ use Config\Services; use CodeIgniter\CodeIgniter; -$error_id = uniqid('error', true); +$errorId = uniqid('error', true); ?> <!doctype html> <html> @@ -103,12 +103,12 @@ $error_id = uniqid('error', true); $row['class'] . $row['type'] . $row['function'], ) ?> <?php if (!empty($row['args'])): ?> - <?php $args_id = $error_id . 'args' . $index; ?> + <?php $argsId = $errorId . 'args' . $index; ?> ( <a href="#" onclick="return toggle('<?= esc( - $args_id, + $argsId, 'attr', ) ?>');">arguments</a> ) - <div class="args" id="<?= esc($args_id, 'attr') ?>"> + <div class="args" id="<?= esc($argsId, 'attr') ?>"> <table cellspacing="0"> <?php diff --git a/app/Views/podcast/_layout_authenticated.php b/app/Views/podcast/_layout_authenticated.php index b0ee155529..33a6cc638c 100644 --- a/app/Views/podcast/_layout_authenticated.php +++ b/app/Views/podcast/_layout_authenticated.php @@ -27,7 +27,7 @@ 'text-2xl inline-flex items-baseline font-bold font-display', ], ) ?> - <?php if (user()->podcasts): ?> + <?php if (user()->podcasts !== []): ?> <button type="button" class="inline-flex items-center px-6 py-2 mt-auto font-semibold outline-none focus:ring" id="interact-as-dropdown" data-dropdown="button" data-dropdown-target="interact-as-dropdown-menu" aria-haspopup="true" aria-expanded="false"> <img src="<?= interact_as_actor() ->avatar_image_url ?>" class="w-8 h-8 mr-2 rounded-full" /> diff --git a/app/Views/podcast/_partials/header.php b/app/Views/podcast/_partials/header.php index 2273053ab1..38e46073c2 100644 --- a/app/Views/podcast/_partials/header.php +++ b/app/Views/podcast/_partials/header.php @@ -42,37 +42,7 @@ </span> <?php endforeach; ?> </div> - <?php if (!empty($persons)): ?> - <div class="flex w-full mb-6 space-x-2 overflow-y-auto"> - <?php foreach ($persons as $person): ?> - <?php if ($person['information_url']): ?> - <a href="<?= $person[ - 'information_url' - ] ?>" target="_blank" rel="noreferrer noopener" class="flex-shrink-0"> - <img - src="<?= $person['thumbnail_url'] ?>" - alt="<?= $person['full_name'] ?>" - class="object-cover w-12 h-12 rounded-full" - data-toggle="tooltip" - data-placement="bottom" - title="[<?= $person['full_name'] ?>] <?= $person[ - 'roles' -] ?>" /> - </a> - <?php else: ?> - <img - src="<?= $person['thumbnail_url'] ?>" - alt="<?= $person['full_name'] ?>" - class="object-cover w-12 h-12 rounded-full" - data-toggle="tooltip" - data-placement="bottom" - title="[<?= $person['full_name'] ?>] <?= $person[ - 'roles' -] ?>" /> - <?php endif; ?> - <?php endforeach; ?> - </div> - <?php endif; ?> + <?= person_list($podcast->persons, 'mb-6') ?> <div class="space-x-4"> <a href="#" class="hover:underline"><?= lang('Podcast.followers', [ 'numberOfFollowers' => $podcast->actor->followers_count, diff --git a/app/Views/podcast/episode.php b/app/Views/podcast/episode.php index a6ea7b1816..694313ae88 100644 --- a/app/Views/podcast/episode.php +++ b/app/Views/podcast/episode.php @@ -111,37 +111,8 @@ ], ) ?> </div> - <?php if ($episode->location !== null): ?> - <?= location_link($episode->location, 'text-sm mb-4') ?> - <?php endif; ?> - <?php if ($episodePersons): ?> - <div class="flex w-full space-x-2 overflow-y-auto"> - <?php foreach ($episodePersons as $person): ?> - <?php if ($person['information_url']): ?> - <a href="<?= $person[ - 'information_url' - ] ?>" target="_blank" rel="noreferrer noopener" class="flex-shrink-0"> - <img src="<?= $person[ - 'thumbnail_url' - ] ?>" alt="<?= $person[ - 'full_name' -] ?>" class="object-cover w-12 h-12 rounded-full" data-toggle="tooltip" -data-placement="bottom" title="[<?= $person['full_name'] ?>] <?= $person[ - 'roles' -] ?>" /></a> - <?php else: ?> - <img src="<?= $person[ - 'thumbnail_url' - ] ?>" alt="<?= $person[ - 'full_name' -] ?>" class="object-cover w-12 h-12 rounded-full" data-toggle="tooltip" -data-placement="bottom" title="[<?= $person['full_name'] ?>] <?= $person[ - 'roles' -] ?>" /> - <?php endif; ?> - <?php endforeach; ?> - </div> - <?php endif; ?> + <?= location_link($episode->location, 'text-sm mb-4') ?> + <?= person_list($episode->persons) ?> </div> </div> <audio controls preload="none" class="w-full mt-auto"> -- GitLab