feat(custom-rss): add custom xml tag injection in rss feed for ❬channel❭ and ❬item❭

This commit is contained in:
Benjamin Bellamy 2021-03-19 16:12:36 +00:00
parent ea5383643c
commit 6ecdaad911
17 changed files with 327 additions and 12 deletions

View File

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

View File

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

View File

@ -138,6 +138,10 @@ class AddPodcasts extends Migration
'constraint' => 12,
'null' => true,
],
'custom_rss' => [
'type' => 'JSON',
'null' => true,
],
'created_by' => [
'type' => 'INT',
'unsigned' => true,

View File

@ -124,6 +124,10 @@ class AddEpisodes extends Migration
'constraint' => 12,
'null' => true,
],
'custom_rss' => [
'type' => 'JSON',
'null' => true,
],
'created_by' => [
'type' => 'INT',
'unsigned' => true,

View File

@ -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(
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>'
))
->addChild('channel')
->addChild('item');
array_to_rss(
[
'elements' => $this->custom_rss,
],
$xmlNode
);
return str_replace(['<item>', '</item>'], '', $xmlNode->asXML());
}
}
/**
* Saves custom rss tag into json
*
* @param string $customRssString
*
*/
function setCustomRssString($customRssString)
{
helper('rss');
$customRssArray = rss_to_array(
simplexml_load_string(
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><item>' .
$customRssString .
'</item></channel></rss>'
)
)['elements'][0]['elements'][0];
if (array_key_exists('elements', $customRssArray)) {
$this->attributes['custom_rss'] = json_encode(
$customRssArray['elements']
);
} else {
$this->attributes['custom_rss'] = null;
}
}
}

View File

@ -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(
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>'
))->addChild('channel');
array_to_rss(
[
'elements' => $this->custom_rss,
],
$xmlNode
);
return str_replace(
['<channel>', '</channel>'],
'',
$xmlNode->asXML()
);
}
}
/**
* Saves custom rss tag into json
*
* @param string $customRssString
*
*/
function setCustomRssString($customRssString)
{
helper('rss');
$customRssArray = rss_to_array(
simplexml_load_string(
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel>' .
$customRssString .
'</channel></rss>'
)
)['elements'][0];
if (array_key_exists('elements', $customRssArray)) {
$this->attributes['custom_rss'] = json_encode(
$customRssArray['elements']
);
} else {
$this->attributes['custom_rss'] = null;
}
}
}

View File

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

View File

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

View File

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

View File

@ -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 dune balise que nous navons 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',
],

View File

@ -74,6 +74,11 @@ return [
'Adresse de paiement (Payment Pointer) pour Web Monetization',
'payment_pointer_hint' =>
'Ladresse où vous recevrez de largent grâce à Web Monetization',
'advanced_section_title' => 'Paramètres avancés',
'advanced_section_subtitle' =>
'Si vous avez besoin dune balise que nous navons 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',

View File

@ -38,6 +38,7 @@ class EpisodeModel extends Model
'location_name',
'location_geo',
'location_osmid',
'custom_rss',
'published_at',
'created_by',
'updated_by',

View File

@ -41,6 +41,7 @@ class PodcastModel extends Model
'location_geo',
'location_osmid',
'payment_pointer',
'custom_rss',
'created_by',
'updated_by',
];

View File

@ -324,6 +324,25 @@
]) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.advanced_section_title'),
lang('Episode.form.advanced_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.custom_rss'),
'custom_rss',
[],
lang('Episode.form.custom_rss_hint'),
true
) ?>
<?= form_textarea([
'id' => 'custom_rss',
'name' => 'custom_rss',
'class' => 'form-textarea',
'value' => old('custom_rss'),
]) ?>
<?= form_section_close() ?>
<?= button(
lang('Episode.form.submit_create'),
null,

View File

@ -388,6 +388,25 @@
</div>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.advanced_section_title'),
lang('Episode.form.advanced_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.custom_rss'),
'custom_rss',
[],
lang('Episode.form.custom_rss_hint'),
true
) ?>
<?= form_textarea([
'id' => 'custom_rss',
'name' => 'custom_rss',
'class' => 'form-textarea',
'value' => old('custom_rss', $episode->custom_rss_string),
]) ?>
<?= form_section_close() ?>
<?= button(
lang('Episode.form.submit_edit'),
null,

View File

@ -27,7 +27,6 @@
'id' => 'image',
'name' => 'image',
'class' => 'form-input',
'required' => 'required',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
@ -59,27 +58,21 @@
'required' => 'required',
]) ?>
<?= form_fieldset('', ['class' => 'mb-4']) ?>
<?= form_fieldset('', [
'class' => 'mb-4',
]) ?>
<legend>
<?= lang('Podcast.form.type.label') .
hint_tooltip(lang('Podcast.form.type.hint'), 'ml-1') ?>
</legend>
<?= form_radio(
[
'id' => 'episodic',
'name' => 'type',
'class' => 'form-radio-btn',
],
['id' => 'episodic', 'name' => 'type', 'class' => 'form-radio-btn'],
'episodic',
old('type') ? old('type') == 'episodic' : true
) ?>
<label for="episodic"><?= lang('Podcast.form.type.episodic') ?></label>
<?= form_radio(
[
'id' => '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 @@
]) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Podcast.form.advanced_section_title'),
lang('Podcast.form.advanced_section_subtitle')
) ?>
<?= form_label(
lang('Podcast.form.custom_rss'),
'custom_rss',
[],
lang('Podcast.form.custom_rss_hint'),
true
) ?>
<?= form_textarea([
'id' => 'custom_rss',
'name' => 'custom_rss',
'class' => 'form-textarea',
'value' => old('custom_rss'),
]) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Podcast.form.status_section_title'),
lang('Podcast.form.status_section_subtitle')

View File

@ -291,6 +291,25 @@
]) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Podcast.form.advanced_section_title'),
lang('Podcast.form.advanced_section_subtitle')
) ?>
<?= form_label(
lang('Podcast.form.custom_rss'),
'custom_rss',
[],
lang('Podcast.form.custom_rss_hint'),
true
) ?>
<?= form_textarea([
'id' => 'custom_rss',
'name' => 'custom_rss',
'class' => 'form-textarea',
'value' => old('custom_rss', $podcast->custom_rss_string),
]) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Podcast.form.status_section_title'),
lang('Podcast.form.status_section_subtitle')