From 93e605b40639e251ce727f17551f37e529648869 Mon Sep 17 00:00:00 2001 From: Yassine Doghri 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 | 432 ++++++++++-------- 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, 944 insertions(+), 926 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 a279b940..4539867c 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 423d1b3b..4eb61cf2 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 c38c571c..caec435d 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 7d32264b..6e07467d 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 47172c90..cbeb6022 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 75ec6832..f94997b2 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 623088a9..3b6a8147 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 97d55c66..8f02363a 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 aa32c889..e8eafd44 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 83ee1e4a..d3d42aa5 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 43b3126a..32183c52 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 1bff6c1d..9382b9ac 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 b80ebd16..0a8e3d15 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 51697c9b..21fbc8fa 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 34054745..bc2fde32 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 a650d651..afc4e265 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 diff --git a/app/Entities/Credit.php b/app/Entities/Credit.php index 5c6ea628..93934634 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 178baac5..a9d72656 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 .= "
getPartnerLink( - $serviceSlug, - )}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\">getPartnerImageUrl( - $serviceSlug, + $descriptionHtml .= "
getPartnerLink($serviceSlug, + )}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\">getPartnerImageUrl($serviceSlug, )}\" alt=\"Partner image\" />
"; } @@ -504,47 +502,41 @@ class Episode extends Entity public function getPublicationStatus(): string { - if ($this->publication_status !== '') { - return $this->publication_status; + 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'; + } } - if ($this->published_at === null) { - return 'not_published'; - } - - helper('date'); - if ($this->published_at->isBefore(Time::now())) { - return 'published'; - } - - 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; + + return $this; } - helper('location'); - - $oldLocationName = $this->attributes['location_name']; - 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 74196cad..ddbd7c2f 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 bfce2ddf..40f256fa 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|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 eeec4e3f..21155da3 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 diff --git a/app/Entities/Person.php b/app/Entities/Person.php index 7bfd0b16..eb15dbdc 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 @@ -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 7b84d3df..55ba0356 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; + $this->attributes['location_osm'] = null; + + return $this; } - helper('location'); - - $oldLocationName = $this->attributes['location_name']; - 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 e899ff8f..909799f1 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 647bfe20..172bfca1 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 = "
"; + + foreach ($persons as $person) { + $personList .= anchor( + $person->information_url ?? '#', + "$person->full_name", + [ + 'class' => + 'flex-shrink-0 focus:outline-none focus:ring focus:ring-inset', + 'target' => '_blank', + 'rel' => 'noreferrer noopener', + 'title' => + '' . + $person->full_name . + '' . + implode( + array_map(function ($role) { + return '
' . + lang( + 'PersonsTaxonomy.persons.' . + $role->group . + '.roles.' . + $role->role . + '.label', + ); + }, $person->roles), + ), + 'data-toggle' => 'tooltip', + 'data-placement' => 'bottom', + ], + ); + } + + $personList .= '
'; + + return $personList; + } +} + +// ------------------------------------------------------------------------ diff --git a/app/Helpers/form_helper.php b/app/Helpers/form_helper.php index 180efd5e..2a883008 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 $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 .= '(' . lang('Common.optional') . ')'; } if ($hintText !== '') { - $label_content .= hint_tooltip($hintText, 'ml-1'); + $labelContent .= hint_tooltip($hintText, 'ml-1'); } - return $label . '>' . $label_content . ''; + return $label . '>' . $labelContent . ''; } } diff --git a/app/Helpers/id3_helper.php b/app/Helpers/id3_helper.php index b0739c6c..d13239dd 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 9a91e8a1..00000000 --- a/app/Helpers/location_helper.php +++ /dev/null @@ -1,62 +0,0 @@ -|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 d8d4e8a2..d90e8fa5 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 41adc546..8fed1cd0 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( - "", + "", ); $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 b6120169..6078aa77 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( '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 53df425d..abb73622 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 f62e2fe6..46ab35ae 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 9f0ea4ac..40c04783 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 1bb2517b..aa2b45fb 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 c39e7417..cde7b102 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 ae861561..d7ce0be8 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,209 +99,39 @@ class PersonModel extends Model return $this->where('full_name', $fullName)->first(); } - public function addPerson( - string $fullName, - ?string $informationUrl, - string $image - ): int|bool { - $person = new Person([ - 'full_name' => $fullName, - 'unique_name' => slugify($fullName), - 'information_url' => $informationUrl, - 'image' => new Image(download_file($image)), - 'created_by' => user_id(), - 'updated_by' => user_id(), - ]); - - return $this->insert($person); - } - /** - * @return Person[] + * @return stdClass[] */ - public function getEpisodePersons(int $podcastId, int $episodeId): array - { - $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') - ->findAll(); + public function getPersonRoles(int $personId, int $podcastId, ?int $episodeId): array { + if ($episodeId) { + $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_person#{$personId}_roles"; - cache()->save($cacheName, $found, DECADE); + 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 Person[] - */ - public function getPodcastPersons(int $podcastId): array - { - $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') - ->findAll(); - - cache()->save($cacheName, $found, DECADE); - } - - return $found; - } - - public function addEpisodePerson( - int $podcastId, - int $episodeId, - int $personId, - string $group, - string $role - ): int|bool { - return $this->db->table('episodes_persons')->insert([ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'person_id' => $personId, - 'person_group' => $group, - 'person_role' => $role, - ]); - } - - public function addPodcastPerson( - int $podcastId, - int $personId, - string $group, - string $role - ): int|bool { - return $this->db->table('podcasts_persons')->insert([ - 'podcast_id' => $podcastId, - 'person_id' => $personId, - 'person_group' => $group, - 'person_role' => $role, - ]); - } - - /** - * Add persons to podcast - * - * @param array $persons - * @param array $groupsRoles - * - * @return bool|int Number of rows inserted or FALSE on failure - */ - public function addPodcastPersons( - int $podcastId, - array $persons = [], - array $groupsRoles = [] - ): int|bool { - if ($persons === []) { - return 0; - } - - $this->clearCache(['podcast_id' => $podcastId]); - $data = []; - foreach ($persons as $person) { - if ($groupsRoles === []) { - $data[] = [ - 'podcast_id' => $podcastId, - 'person_id' => $person, - ]; - } - - foreach ($groupsRoles as $group_role) { - $group_role = explode(',', $group_role); - $data[] = [ - 'podcast_id' => $podcastId, - 'person_id' => $person, - 'person_group' => $group_role[0], - 'person_role' => $group_role[1], - ]; - } - } - - return $this->insertBatch($data); - } - - /** - * Add persons to episode - * - * @return BaseResult|bool Number of rows inserted or FALSE on failure - */ - public function removePodcastPersons(int $podcastId, int $personId): BaseResult|bool - { - return $this->delete([ - 'id' => $personId, - 'podcast_id' => $podcastId, - ]); - } - - /** - * Add persons to episode - * - * @param int[] $personIds - * @param string[] $groups_roles - * - * @return bool|int Number of rows inserted or FALSE on failure - */ - public function addEpisodePersons( - int $podcastId, - int $episodeId, - array $personIds, - array $groups_roles - ): bool|int { - if (!empty($personIds)) { - $this->clearCache([ - 'episode_id' => $episodeId, - ]); - - $data = []; - foreach ($personIds as $personId) { - if ($groups_roles !== []) { - foreach ($groups_roles as $group_role) { - $group_role = explode(',', $group_role); - $data[] = [ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'person_id' => $personId, - 'person_group' => $group_role[0], - 'person_role' => $group_role[1], - ]; - } - } else { - $data[] = [ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'person_id' => $personId, - ]; - } - } - return $this->insertBatch($data); - } - return 0; - } - - /** - * @return BaseResult|bool - */ - public function removeEpisodePersons( - int $podcastId, - int $episodeId, - int $personId - ): BaseResult|bool { - return $this->delete([ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'id' => $personId, - ]); - } - /** * @return array */ @@ -342,7 +173,7 @@ class PersonModel extends Model foreach ($group['roles'] as $role_key => $role) { $options[ "{$group_key},{$role_key}" - ] = "{$group['label']} ▸ {$role['label']}"; + ] = "{$group['label']} › {$role['label']}"; } } @@ -352,6 +183,209 @@ class PersonModel extends Model return $options; } + public function addPerson( + string $fullName, + ?string $informationUrl, + string $image + ): int|bool { + $person = new Person([ + 'full_name' => $fullName, + 'unique_name' => slugify($fullName), + 'information_url' => $informationUrl, + 'image' => new Image(download_file($image)), + 'created_by' => user_id(), + 'updated_by' => user_id(), + ]); + + return $this->insert($person); + } + + /** + * @return Person[] + */ + public function getEpisodePersons(int $podcastId, int $episodeId): array + { + $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_persons"; + if (!($found = cache($cacheName))) { + $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); + } + + return $found; + } + + /** + * @return Person[] + */ + public function getPodcastPersons(int $podcastId): array + { + $cacheName = "podcast#{$podcastId}_persons"; + if (!($found = cache($cacheName))) { + $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); + } + + return $found; + } + + public function addEpisodePerson( + int $podcastId, + int $episodeId, + int $personId, + string $groupSlug, + string $roleSlug + ): int|bool { + return $this->db->table('episodes_persons')->insert([ + 'podcast_id' => $podcastId, + 'episode_id' => $episodeId, + 'person_id' => $personId, + 'person_group' => $groupSlug, + 'person_role' => $roleSlug, + ]); + } + + public function addPodcastPerson( + int $podcastId, + int $personId, + string $groupSlug, + string $roleSlug + ): int|bool { + return $this->db->table('podcasts_persons')->insert([ + 'podcast_id' => $podcastId, + 'person_id' => $personId, + 'person_group' => $groupSlug, + 'person_role' => $roleSlug, + ]); + } + + /** + * Add persons to podcast + * + * @param array $persons + * @param array $groupsRoles + * + * @return bool|int Number of rows inserted or FALSE on failure + */ + public function addPodcastPersons( + int $podcastId, + array $persons = [], + array $roles = [] + ): int|bool { + if ($persons === []) { + return 0; + } + + cache()->delete("podcast#{$podcastId}_persons"); + (new PodcastModel())->clearCache(['id' => $podcastId]); + + $data = []; + foreach ($persons as $person) { + if ($roles === []) { + $data[] = [ + 'podcast_id' => $podcastId, + 'person_id' => $person, + ]; + } + + foreach ($roles as $role) { + $groupRole = explode(',', $role); + $data[] = [ + 'podcast_id' => $podcastId, + 'person_id' => $person, + 'person_group' => $groupRole[0], + 'person_role' => $groupRole[1], + ]; + } + } + + return $this->db->table('podcasts_persons')->insertBatch($data); + } + + /** + * Add persons to episode + * + * @return BaseResult|bool Number of rows inserted or FALSE on failure + */ + public function removePersonFromPodcast(int $podcastId, int $personId): BaseResult|bool + { + return $this->db->table('podcasts_persons')->delete([ + 'podcast_id' => $podcastId, + 'person_id' => $personId, + ]); + } + + /** + * Add persons to episode + * + * @param int[] $personIds + * @param string[] $groupsRoles + * + * @return bool|int Number of rows inserted or FALSE on failure + */ + public function addEpisodePersons( + int $podcastId, + int $episodeId, + array $personIds, + array $groupsRoles + ): bool|int { + if (!empty($personIds)) { + (new EpisodeModel())->clearCache(['id' => $episodeId]); + + $data = []; + foreach ($personIds as $personId) { + if ($groupsRoles !== []) { + foreach ($groupsRoles as $groupRole) { + $groupRole = explode(',', $groupRole); + $data[] = [ + 'podcast_id' => $podcastId, + 'episode_id' => $episodeId, + 'person_id' => $personId, + 'person_group' => $groupRole[0], + 'person_role' => $groupRole[1], + ]; + } + } else { + $data[] = [ + 'podcast_id' => $podcastId, + 'episode_id' => $episodeId, + 'person_id' => $personId, + ]; + } + } + return $this->db->table('episodes_persons')->insertBatch($data); + } + return 0; + } + + /** + * @return BaseResult|bool + */ + public function removePersonFromEpisode( + int $podcastId, + int $episodeId, + int $personId + ): BaseResult|bool { + return $this->db->table('episodes_persons')->delete([ + 'podcast_id' => $podcastId, + 'episode_id' => $episodeId, + 'person_id' => $personId, + ]); + } + /** * @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 daf491be..dddcc5e0 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 03a828f1..08d681de 100644 --- a/app/Views/admin/episode/person.php +++ b/app/Views/admin/episode/persons.php @@ -5,7 +5,7 @@ endSection() ?> section('pageTitle') ?> - () + (persons) ?>) endSection() ?> section('headerRight') ?> @@ -25,7 +25,6 @@ ]) ?> - lang('Person.episode_form.person'), - 'cell' => function ($episodePerson) { + 'cell' => function ($person) { return '
' . 'person->image->thumbnail_url}\" alt=\"{$episodePerson->person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" />" . + route_to('person-view', $person->id) . + "\">image->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" />" . '
' . - $episodePerson->person->full_name . - ($episodePerson->person_group && $episodePerson->person_role - ? '' . - lang( - "PersonsTaxonomy.persons.{$episodePerson->person_group}.label", - ) . - ' ▸ ' . - lang( - "PersonsTaxonomy.persons.{$episodePerson->person_group}.roles.{$episodePerson->person_role}.label", - ) . - '' - : '') . - (empty($episodePerson->person->information_url) + $person->full_name . + implode( + '', + array_map(function ($role) { + return '' . + lang( + "PersonsTaxonomy.persons.{$role->group}.label", + ) . + ' › ' . + lang( + "PersonsTaxonomy.persons.{$role->group}.roles.{$role->role}.label", + ) . + ''; + }, $person->roles), + ) . + ($person->information_url === null ? '' - : "person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . - $episodePerson->person->information_url . + : "information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . + $person->information_url . '') . '
'; }, ], [ '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, ) ?> - '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 e8c75796..462fa1a0 100644 --- a/app/Views/admin/episode/soundbites.php +++ b/app/Views/admin/episode/soundbites.php @@ -53,34 +53,40 @@ "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', ], ) ?> "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', ], ) ?> "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 @@ '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', ], ) ?> '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 @@ ) ?> '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 @@ 'primary'], [ 'data-type' => 'play-soundbite', @@ -170,13 +183,12 @@ '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]', ], ) ?> diff --git a/app/Views/admin/podcast/person.php b/app/Views/admin/podcast/person.php deleted file mode 100644 index f0a5b99c..00000000 --- a/app/Views/admin/podcast/person.php +++ /dev/null @@ -1,135 +0,0 @@ -extend('admin/_layout') ?> - -section('title') ?> - -endSection() ?> - -section('pageTitle') ?> - () -endSection() ?> - -section('headerRight') ?> - 'primary', 'iconLeft' => 'add'], - ['class' => 'mr-2'], -) ?> -endSection() ?> - -section('content') ?> - -id), [ - 'method' => 'post', - 'class' => 'flex flex-col', -]) ?> - - - - - - - - lang('Person.podcast_form.person'), - 'cell' => function ($podcastPerson) { - return '
' . - 'person->image->thumbnail_url}\" alt=\"{$podcastPerson->person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" />" . - '
' . - $podcastPerson->person->full_name . - ($podcastPerson->person_group && - $podcastPerson->person_role - ? '' . - lang( - "PersonsTaxonomy.persons.{$podcastPerson->person_group}.label", - ) . - ' ▸ ' . - lang( - "PersonsTaxonomy.persons.{$podcastPerson->person_group}.roles.{$podcastPerson->person_role}.label", - ) . - '' - : '') . - (empty($podcastPerson->person->information_url) - ? '' - : "person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . - $podcastPerson->person->information_url . - '') . - '
'; - }, - ], - [ - '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, - ) ?> - - - - - - - - - 'person', - 'class' => 'form-select mb-4', - 'required' => 'required', -]) ?> - - - 'person_group_role', - 'class' => 'form-select mb-4', - ], -) ?> - - - 'primary'], - ['type' => 'submit', 'class' => 'self-end'], -) ?> - - -endSection() ?> diff --git a/app/Views/admin/podcast/persons.php b/app/Views/admin/podcast/persons.php new file mode 100644 index 00000000..8edb6876 --- /dev/null +++ b/app/Views/admin/podcast/persons.php @@ -0,0 +1,129 @@ +extend('admin/_layout') ?> + +section('title') ?> + +endSection() ?> + +section('pageTitle') ?> + (persons) ?>) +endSection() ?> + +section('headerRight') ?> + 'primary', 'iconLeft' => 'add'], + ['class' => 'mr-2'], +) ?> +endSection() ?> + +section('content') ?> + +id), [ + 'method' => 'post', + 'class' => 'flex flex-col', +]) ?> + + + + + + lang('Person.podcast_form.person'), + 'cell' => function ($person) { + return '
' . + 'image->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" />" . + '
' . + $person->full_name . + implode( + '', + array_map(function ($role) { + return '' . + lang( + "PersonsTaxonomy.persons.{$role->group}.label", + ) . + ' › ' . + lang( + "PersonsTaxonomy.persons.{$role->group}.roles.{$role->role}.label", + ) . + ''; + }, $person->roles), + ) . + ($person->information_url === null + ? '' + : "information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . + $person->information_url . + '') . + '
'; + }, + ], + [ + '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, +) ?> + + + + + + + + 'persons', + 'class' => 'form-select mb-4', + 'required' => 'required', +]) ?> + + + 'roles', + 'class' => 'form-select mb-4', +]) ?> + + + 'primary'], + ['type' => 'submit', 'class' => 'self-end'], +) ?> + + +endSection() ?> diff --git a/app/Views/errors/html/error_exception.php b/app/Views/errors/html/error_exception.php index 5ec36809..d8ef20eb 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); ?> @@ -103,12 +103,12 @@ $error_id = uniqid('error', true); $row['class'] . $row['type'] . $row['function'], ) ?> - + ( arguments ) -
+
- podcasts): ?> + podcasts !== []): ?>