diff --git a/app/Controllers/Admin/Episode.php b/app/Controllers/Admin/Episode.php index 92f9a8ea..0ab9a8e5 100644 --- a/app/Controllers/Admin/Episode.php +++ b/app/Controllers/Admin/Episode.php @@ -141,6 +141,7 @@ class Episode extends BaseController : null, 'type' => $this->request->getPost('type'), 'is_blocked' => $this->request->getPost('block') == 'yes', + 'custom_rss_string' => $this->request->getPost('custom_rss'), 'created_by' => user(), 'updated_by' => user(), 'published_at' => $publicationDate @@ -236,6 +237,9 @@ class Episode extends BaseController : null; $this->episode->type = $this->request->getPost('type'); $this->episode->is_blocked = $this->request->getPost('block') == 'yes'; + $this->episode->custom_rss_string = $this->request->getPost( + 'custom_rss' + ); $publicationDate = $this->request->getPost('publication_date'); $this->episode->published_at = $publicationDate diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php index 342280c5..5e467a3b 100644 --- a/app/Controllers/Admin/Podcast.php +++ b/app/Controllers/Admin/Podcast.php @@ -163,6 +163,7 @@ class Podcast extends BaseController 'copyright' => $this->request->getPost('copyright'), 'location' => $this->request->getPost('location_name'), 'payment_pointer' => $this->request->getPost('payment_pointer'), + 'custom_rss_string' => $this->request->getPost('custom_rss'), 'is_blocked' => $this->request->getPost('block') === 'yes', 'is_completed' => $this->request->getPost('complete') === 'yes', 'is_locked' => $this->request->getPost('lock') === 'yes', @@ -259,6 +260,9 @@ class Podcast extends BaseController $this->podcast->payment_pointer = $this->request->getPost( 'payment_pointer' ); + $this->podcast->custom_rss_string = $this->request->getPost( + 'custom_rss' + ); $this->podcast->is_blocked = $this->request->getPost('block') === 'yes'; $this->podcast->is_completed = $this->request->getPost('complete') === 'yes'; 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 19a81616..e926d36f 100644 --- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php +++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php @@ -138,6 +138,10 @@ class AddPodcasts extends Migration 'constraint' => 12, 'null' => true, ], + 'custom_rss' => [ + 'type' => 'JSON', + 'null' => true, + ], 'created_by' => [ 'type' => 'INT', 'unsigned' => 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 7d33d942..72f7f199 100644 --- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php +++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php @@ -124,6 +124,10 @@ class AddEpisodes extends Migration 'constraint' => 12, 'null' => true, ], + 'custom_rss' => [ + 'type' => 'JSON', + 'null' => true, + ], 'created_by' => [ 'type' => 'INT', 'unsigned' => true, diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 99bc4804..f79dfbe0 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -106,6 +106,13 @@ class Episode extends Entity */ protected $publication_status; + /** + * Return custom rss as string + * + * @var string + */ + protected $custom_rss_string; + protected $dates = [ 'published_at', 'created_at', @@ -136,6 +143,7 @@ class Episode extends Entity 'location_name' => '?string', 'location_geo' => '?string', 'location_osmid' => '?string', + 'custom_rss' => '?json-array', 'created_by' => 'integer', 'updated_by' => 'integer', ]; @@ -564,4 +572,56 @@ class Episode extends Entity } return $this; } + + /** + * Get custom rss tag as XML String + * + * @return string + * + */ + function getCustomRssString() + { + helper('rss'); + if (empty($this->attributes['custom_rss'])) { + return ''; + } else { + $xmlNode = (new \App\Libraries\SimpleRSSElement( + '' + )) + ->addChild('channel') + ->addChild('item'); + array_to_rss( + [ + 'elements' => $this->custom_rss, + ], + $xmlNode + ); + return str_replace(['', ''], '', $xmlNode->asXML()); + } + } + + /** + * Saves custom rss tag into json + * + * @param string $customRssString + * + */ + function setCustomRssString($customRssString) + { + helper('rss'); + $customRssArray = rss_to_array( + simplexml_load_string( + '' . + $customRssString . + '' + ) + )['elements'][0]['elements'][0]; + if (array_key_exists('elements', $customRssArray)) { + $this->attributes['custom_rss'] = json_encode( + $customRssArray['elements'] + ); + } else { + $this->attributes['custom_rss'] = null; + } + } } diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index aa4a4afe..813123f5 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -80,6 +80,13 @@ class Podcast extends Entity */ protected $description; + /** + * Return custom rss as string + * + * @var string + */ + protected $custom_rss_string; + protected $casts = [ 'id' => 'integer', 'title' => 'string', @@ -106,6 +113,7 @@ class Podcast extends Entity 'location_geo' => '?string', 'location_osmid' => '?string', 'payment_pointer' => '?string', + 'custom_rss' => '?json-array', 'created_by' => 'integer', 'updated_by' => 'integer', ]; @@ -480,4 +488,58 @@ class Podcast extends Entity } return $this; } + + /** + * Get custom rss tag as XML String + * + * @return string + * + */ + function getCustomRssString() + { + helper('rss'); + if (empty($this->attributes['custom_rss'])) { + return ''; + } else { + $xmlNode = (new \App\Libraries\SimpleRSSElement( + '' + ))->addChild('channel'); + array_to_rss( + [ + 'elements' => $this->custom_rss, + ], + $xmlNode + ); + return str_replace( + ['', ''], + '', + $xmlNode->asXML() + ); + } + } + + /** + * Saves custom rss tag into json + * + * @param string $customRssString + * + */ + function setCustomRssString($customRssString) + { + helper('rss'); + $customRssArray = rss_to_array( + simplexml_load_string( + '' . + $customRssString . + '' + ) + )['elements'][0]; + if (array_key_exists('elements', $customRssArray)) { + $this->attributes['custom_rss'] = json_encode( + $customRssArray['elements'] + ); + } else { + $this->attributes['custom_rss'] = null; + } + } } diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index 69a08539..debb495d 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -242,6 +242,15 @@ function get_rss_feed($podcast, $serviceSlug = '') $image->addChild('title', $podcast->title); $image->addChild('link', $podcast->link); + if (!empty($podcast->custom_rss)) { + array_to_rss( + [ + 'elements' => $podcast->custom_rss, + ], + $channel + ); + } + foreach ($episodes as $episode) { $item = $channel->addChild('item'); $item->addChild('title', $episode->title); @@ -393,6 +402,15 @@ function get_rss_feed($podcast, $serviceSlug = '') $episode->is_blocked && $item->addChild('block', 'Yes', $itunes_namespace); + + if (!empty($episode->custom_rss)) { + array_to_rss( + [ + 'elements' => $episode->custom_rss, + ], + $item + ); + } } return $rss->asXML(); @@ -429,3 +447,71 @@ function add_category_tag($node, $category) } $node->addChild('category', $category->apple_category); } + +/** + * Converts XML to array + * + * @param \SimpleRSSElement $xmlNode + * + * @return array + */ +function rss_to_array($xmlNode) +{ + $nameSpaces = [ + '', + 'http://www.itunes.com/dtds/podcast-1.0.dtd', + 'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md', + ]; + $arrayNode = []; + $arrayNode['name'] = $xmlNode->getName(); + $arrayNode['namespace'] = $xmlNode->getNamespaces(false); + if (count($xmlNode->attributes()) > 0) { + foreach ($xmlNode->attributes() as $key => $value) { + $arrayNode['attributes'][$key] = (string) $value; + } + } + $textcontent = trim((string) $xmlNode); + if (strlen($textcontent) > 0) { + $arrayNode['content'] = $textcontent; + } + foreach ($nameSpaces as $currentNameSpace) { + foreach ($xmlNode->children($currentNameSpace) as $childXmlNode) { + $arrayNode['elements'][] = rss_to_array($childXmlNode); + } + } + return $arrayNode; +} + +/** + * Inserts array (converted to XML node) in XML node + * + * @param array $arrayNode + * @param \SimpleRSSElement $xmlNode The XML parent node where this arrayNode should be attached + * + */ +function array_to_rss($arrayNode, &$xmlNode) +{ + if (array_key_exists('elements', $arrayNode)) { + foreach ($arrayNode['elements'] as $childArrayNode) { + $childXmlNode = $xmlNode->addChild( + $childArrayNode['name'], + array_key_exists('content', $childArrayNode) + ? $childArrayNode['content'] + : null, + empty($childArrayNode['namespace']) + ? null + : current($childArrayNode['namespace']) + ); + if (array_key_exists('attributes', $childArrayNode)) { + foreach ( + $childArrayNode['attributes'] + as $attributeKey => $attributeValue + ) { + $childXmlNode->addAttribute($attributeKey, $attributeValue); + } + } + array_to_rss($childArrayNode, $childXmlNode); + } + } + return $xmlNode; +} diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php index 18df1b40..27f448e1 100644 --- a/app/Language/en/Episode.php +++ b/app/Language/en/Episode.php @@ -87,6 +87,11 @@ return [ 'location_section_subtitle' => 'What place is this episode about?', 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real place or fictional', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', 'submit_create' => 'Create episode', 'submit_edit' => 'Save episode', ], diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index f7c87572..5afb4554 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -72,6 +72,11 @@ return [ 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', 'status_section_title' => 'Status', 'status_section_subtitle' => 'Dead or alive?', 'block' => 'Podcast should be hidden from all platforms', diff --git a/app/Language/fr/Episode.php b/app/Language/fr/Episode.php index 78f94404..70f233af 100644 --- a/app/Language/fr/Episode.php +++ b/app/Language/fr/Episode.php @@ -88,6 +88,11 @@ return [ 'location_section_subtitle' => 'De quel lieu cet épisode parle-t-il ?', 'location_name' => 'Nom ou adresse du lieu', 'location_name_hint' => 'Ce lieu peut être réel ou fictif', + 'advanced_section_title' => 'Paramètres avancés', + 'advanced_section_subtitle' => + 'Si vous avez besoin d’une balise que nous n’avons pas couverte, définissez-la ici.', + 'custom_rss' => 'Balises RSS personnalisées pour l’épisode', + 'custom_rss_hint' => 'Ceci sera injecté dans la balise ❬item❭.', 'submit_create' => 'Créer l’épisode', 'submit_edit' => 'Enregistrer l’épisode', ], diff --git a/app/Language/fr/Podcast.php b/app/Language/fr/Podcast.php index 20672a4c..02d3cfb9 100644 --- a/app/Language/fr/Podcast.php +++ b/app/Language/fr/Podcast.php @@ -74,6 +74,11 @@ return [ 'Adresse de paiement (Payment Pointer) pour Web Monetization', 'payment_pointer_hint' => 'L’adresse où vous recevrez de l’argent grâce à Web Monetization', + 'advanced_section_title' => 'Paramètres avancés', + 'advanced_section_subtitle' => + 'Si vous avez besoin d’une balise que nous n’avons pas couverte, définissez-la ici.', + 'custom_rss' => 'Balises RSS personnalisées pour le podcast', + 'custom_rss_hint' => 'Ceci sera injecté dans la balise ❬channel❭.', 'status_section_title' => 'Statut', 'status_section_subtitle' => 'Vivant ou mort ?', 'block' => 'Le podcast doit être masqué sur toutes les plateformes', diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index 3237daaf..6a071434 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -38,6 +38,7 @@ class EpisodeModel extends Model 'location_name', 'location_geo', 'location_osmid', + 'custom_rss', 'published_at', 'created_by', 'updated_by', diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index fc23d5dd..6f0e360c 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -41,6 +41,7 @@ class PodcastModel extends Model 'location_geo', 'location_osmid', 'payment_pointer', + 'custom_rss', 'created_by', 'updated_by', ]; diff --git a/app/Views/admin/episode/create.php b/app/Views/admin/episode/create.php index 35f6fc0d..2c5dffda 100644 --- a/app/Views/admin/episode/create.php +++ b/app/Views/admin/episode/create.php @@ -324,6 +324,25 @@ ]) ?> + + + 'custom_rss', + 'name' => 'custom_rss', + 'class' => 'form-textarea', + 'value' => old('custom_rss'), +]) ?> + + + + + 'custom_rss', + 'name' => 'custom_rss', + 'class' => 'form-textarea', + 'value' => old('custom_rss', $episode->custom_rss_string), +]) ?> + + 'image', 'name' => 'image', 'class' => 'form-input', - 'required' => 'required', 'type' => 'file', 'accept' => '.jpg,.jpeg,.png', @@ -59,27 +58,21 @@ 'required' => 'required', ]) ?> - 'mb-4']) ?> + 'mb-4', +]) ?> 'episodic', - 'name' => 'type', - 'class' => 'form-radio-btn', - ], + ['id' => 'episodic', 'name' => 'type', 'class' => 'form-radio-btn'], 'episodic', old('type') ? old('type') == 'episodic' : true ) ?> 'serial', - 'name' => 'type', - 'class' => 'form-radio-btn', - ], + ['id' => 'serial', 'name' => 'type', 'class' => 'form-radio-btn'], 'serial', old('type') ? old('type') == 'serial' : false ) ?> @@ -288,6 +281,25 @@ ]) ?> + + + 'custom_rss', + 'name' => 'custom_rss', + 'class' => 'form-textarea', + 'value' => old('custom_rss'), +]) ?> + + + + + 'custom_rss', + 'name' => 'custom_rss', + 'class' => 'form-textarea', + 'value' => old('custom_rss', $podcast->custom_rss_string), +]) ?> + +