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
This commit is contained in:
Yassine Doghri 2021-05-17 17:11:23 +00:00
parent 6b74a9e98a
commit 93e605b406
No known key found for this signature in database
GPG Key ID: 3E7F89498B960C9F
48 changed files with 944 additions and 926 deletions

View File

@ -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',

View File

@ -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()

View File

@ -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();

View File

@ -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();

View File

@ -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',
);

View File

@ -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()

View File

@ -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();

View File

@ -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(

View File

@ -142,7 +142,7 @@ class AddPodcasts extends Migration
'constraint' => 32,
'null' => true,
],
'location_osm_id' => [
'location_osm' => [
'type' => 'VARCHAR',
'constraint' => 12,
'null' => true,

View File

@ -137,7 +137,7 @@ class AddEpisodes extends Migration
'constraint' => 32,
'null' => true,
],
'location_osm_id' => [
'location_osm' => [
'type' => 'VARCHAR',
'constraint' => 12,
'null' => true,

View File

@ -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',

View File

@ -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',

View File

@ -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";

View File

@ -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";

View File

@ -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

View File

@ -21,7 +21,7 @@ use CodeIgniter\Entity\Entity;
*/
class Category extends Entity
{
protected ?Category $parent;
protected ?Category $parent = null;
/**
* @var array<string, string>

View File

@ -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;

View File

@ -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->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']);
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -21,7 +21,7 @@ use RuntimeException;
*/
class Note extends ActivityPubNote
{
protected ?Episode $episode;
protected ?Episode $episode = null;
/**
* @var array<string, string>

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}
// ------------------------------------------------------------------------

View File

@ -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>';
}
}

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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,
);

View File

@ -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;
}
}

View File

@ -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')) {

View File

@ -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}";

View File

@ -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;
}
}

View File

@ -45,7 +45,7 @@ class HttpSignature
public function __construct(IncomingRequest $request = null)
{
if (is_null($request)) {
if ($request === null) {
$request = Services::request();
}

View File

@ -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),
);
}
}

View File

@ -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;
}
}

View File

@ -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',

View File

@ -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<string> $persons
* @param array<string, string> $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<string, string>
*/
@ -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<string> $persons
* @param array<string, string> $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_*');

View File

@ -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'],

View File

@ -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'],
) ?>

View File

@ -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>

View File

@ -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() ?>

View File

@ -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() ?>

View File

@ -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

View File

@ -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" />

View File

@ -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,

View File

@ -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">