feat(plugins): add before channel/item hooks to allow podcast/episode data edit when generating rss

This commit is contained in:
Yassine Doghri 2024-05-17 14:01:04 +00:00
parent 8ec79097bb
commit 80d2c48ee2
15 changed files with 48 additions and 31 deletions

View File

@ -47,6 +47,8 @@ if (! function_exists('get_rss_feed')) {
"<?xml version='1.0' encoding='utf-8'?><rss version='2.0' xmlns:itunes='{$itunesNamespace}' xmlns:podcast='{$podcastNamespace}' xmlns:atom='{$atomNamespace}' 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:atom='{$atomNamespace}' xmlns:content='http://purl.org/rss/1.0/modules/content/'></rss>"
); );
$plugins->rssBeforeChannel($podcast);
$channel = $rss->addChild('channel'); $channel = $rss->addChild('channel');
$atomLink = $channel->addChild('link', null, $atomNamespace); $atomLink = $channel->addChild('link', null, $atomNamespace);
@ -298,13 +300,15 @@ if (! function_exists('get_rss_feed')) {
} }
// run plugins hook at the end // run plugins hook at the end
$plugins->channelTag($podcast, $channel); $plugins->rssAfterChannel($podcast, $channel);
foreach ($episodes as $episode) { foreach ($episodes as $episode) {
if ($episode->is_premium && ! $subscription instanceof Subscription) { if ($episode->is_premium && ! $subscription instanceof Subscription) {
continue; continue;
} }
$plugins->rssBeforeItem($episode);
$item = $channel->addChild('item'); $item = $channel->addChild('item');
$item->addChild('title', $episode->title, null, false); $item->addChild('title', $episode->title, null, false);
$enclosure = $item->addChild('enclosure'); $enclosure = $item->addChild('enclosure');
@ -460,7 +464,7 @@ if (! function_exists('get_rss_feed')) {
], $item); ], $item);
} }
$plugins->itemTag($episode, $item); $plugins->rssAfterItem($episode, $item);
} }
return $rss->asXML(); return $rss->asXML();

View File

@ -187,7 +187,7 @@ class PluginController extends BaseController
$validatedData = $this->validator->getValidated(); $validatedData = $this->validator->getValidated();
foreach ($plugin->getSettingsFields('general') as $field) { foreach ($plugin->getSettingsFields($type) as $field) {
$value = $validatedData[$field->key] ?? null; $value = $validatedData[$field->key] ?? null;
$fieldValue = $value === '' ? null : match ($plugins::FIELDS_CASTS[$field->type] ?? 'text') { $fieldValue = $value === '' ? null : match ($plugins::FIELDS_CASTS[$field->type] ?? 'text') {
'bool' => $value === 'yes', 'bool' => $value === 'yes',

View File

@ -52,7 +52,7 @@ abstract class BasePlugin implements PluginInterface
// TODO: cache manifest data // TODO: cache manifest data
$manifestPath = $directory . '/manifest.json'; $manifestPath = $directory . '/manifest.json';
$manifestContents = file_get_contents($manifestPath); $manifestContents = @file_get_contents($manifestPath);
if (! $manifestContents) { if (! $manifestContents) {
$manifestContents = '{}'; $manifestContents = '{}';
@ -93,18 +93,19 @@ abstract class BasePlugin implements PluginInterface
$this->{$name} = $value; $this->{$name} = $value;
} }
public function init(): void public function rssBeforeChannel(Podcast $podcast): void
{
// add to admin navigation
// TODO: setup navigation and views?
}
public function channelTag(Podcast $podcast, SimpleRSSElement $channel): void
{ {
} }
public function itemTag(Episode $episode, SimpleRSSElement $item): void public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
{
}
public function rssBeforeItem(Episode $episode): void
{
}
public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void
{ {
} }

View File

@ -10,9 +10,13 @@ use App\Libraries\SimpleRSSElement;
interface PluginInterface interface PluginInterface
{ {
public function channelTag(Podcast $podcast, SimpleRSSElement $channel): void; public function rssBeforeChannel(Podcast $podcast): void;
public function itemTag(Episode $episode, SimpleRSSElement $item): void; public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void;
public function rssBeforeItem(Episode $episode): void;
public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void;
public function siteHead(): void; public function siteHead(): void;
} }

View File

@ -10,9 +10,11 @@ use App\Libraries\SimpleRSSElement;
use Config\Database; use Config\Database;
/** /**
* @method void channelTag(Podcast $podcast, SimpleRSSElement $channel) * @method void rssBeforeChannel(Podcast $podcast)
* @method void itemTag(Episode $episode, SimpleRSSElement $item) * @method void rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel)
* @method string siteHead() * @method void rssBeforeItem(Episode $episode)
* @method void rssAfterItem(Episode $episode, SimpleRSSElement $item)
* @method void siteHead()
*/ */
class Plugins class Plugins
{ {
@ -21,7 +23,7 @@ class Plugins
/** /**
* @var list<string> * @var list<string>
*/ */
public const HOOKS = ['channelTag', 'itemTag', 'siteHead']; public const HOOKS = ['rssBeforeChannel', 'rssAfterChannel', 'rssBeforeItem', 'rssAfterItem', 'siteHead'];
public const FIELDS_VALIDATIONS = [ public const FIELDS_VALIDATIONS = [
'checkbox' => ['permit_empty'], 'checkbox' => ['permit_empty'],

View File

@ -34,7 +34,7 @@ class Manifest extends ManifestObject
'license' => 'permit_empty|string', 'license' => 'permit_empty|string',
'private' => 'permit_empty|is_boolean', 'private' => 'permit_empty|is_boolean',
'keywords.*' => 'permit_empty', 'keywords.*' => 'permit_empty',
'hooks.*' => 'permit_empty|in_list[channelTag,itemTag,siteHead]', 'hooks.*' => 'permit_empty|in_list[rssBeforeChannel,rssAfterChannel,rssBeforeItem,rssAfterItem,siteHead]',
'settings' => 'permit_empty|is_list', 'settings' => 'permit_empty|is_list',
'repository' => 'permit_empty|is_list', 'repository' => 'permit_empty|is_list',
]; ];

View File

@ -87,7 +87,13 @@
"description": "The hooks used by the plugin.", "description": "The hooks used by the plugin.",
"type": "array", "type": "array",
"items": { "items": {
"enum": ["channelTag", "itemTag", "siteHead"] "enum": [
"rssBeforeChannel",
"rssAfterChannel",
"rssBeforeItem",
"rssAfterItem",
"siteHead"
]
}, },
"uniqueItems": true "uniqueItems": true
}, },

View File

@ -28,7 +28,7 @@
label="<?= esc(lang('Contributor.form.role')) ?>" label="<?= esc(lang('Contributor.form.role')) ?>"
options="<?= esc(json_encode($roleOptions)) ?>" options="<?= esc(json_encode($roleOptions)) ?>"
placeholder="<?= lang('Contributor.form.role_placeholder') ?>" placeholder="<?= lang('Contributor.form.role_placeholder') ?>"
selected="<?= setting('AuthGroups.defaultPodcastGroup') ?>" defaultValue="<?= setting('AuthGroups.defaultPodcastGroup') ?>"
isRequired="true" /> isRequired="true" />
<x-Button type="submit" class="self-end" variant="primary"><?= lang('Contributor.form.submit_add') ?></x-Button> <x-Button type="submit" class="self-end" variant="primary"><?= lang('Contributor.form.submit_add') ?></x-Button>

View File

@ -19,7 +19,7 @@
name="role" name="role"
label="<?= esc(lang('Contributor.form.role')) ?>" label="<?= esc(lang('Contributor.form.role')) ?>"
options="<?= esc(json_encode($roleOptions)) ?>" options="<?= esc(json_encode($roleOptions)) ?>"
selected="<?= $contributorGroup ?>" defaultValue="<?= $contributorGroup ?>"
placeholder="<?= lang('Contributor.form.role_placeholder') ?>" placeholder="<?= lang('Contributor.form.role_placeholder') ?>"
isRequired="true" /> isRequired="true" />

View File

@ -30,7 +30,7 @@
label="<?= esc(lang('Person.episode_form.persons')) ?>" label="<?= esc(lang('Person.episode_form.persons')) ?>"
hint="<?= esc(lang('Person.episode_form.persons_hint')) ?>" hint="<?= esc(lang('Person.episode_form.persons_hint')) ?>"
options="<?= esc(json_encode($personOptions)) ?>" options="<?= esc(json_encode($personOptions)) ?>"
selected="<?= esc(json_encode(old('persons', []))) ?>" defaultValue="<?= esc(json_encode(old('persons', []))) ?>"
isRequired="true" isRequired="true"
/> />
@ -41,7 +41,7 @@
label="<?= esc(lang('Person.episode_form.roles')) ?>" label="<?= esc(lang('Person.episode_form.roles')) ?>"
hint="<?= esc(lang('Person.episode_form.roles_hint')) ?>" hint="<?= esc(lang('Person.episode_form.roles_hint')) ?>"
options="<?= esc(json_encode($taxonomyOptions)) ?>" options="<?= esc(json_encode($taxonomyOptions)) ?>"
selected="<?= esc(json_encode(old('roles', []))) ?>" defaultValue="<?= esc(json_encode(old('roles', []))) ?>"
/> />
<x-Button variant="primary" type="submit" class="self-end"><?= lang('Person.episode_form.submit_add') ?></x-Button> <x-Button variant="primary" type="submit" class="self-end"><?= lang('Person.episode_form.submit_add') ?></x-Button>

View File

@ -44,7 +44,7 @@
as="Select" as="Select"
name="language" name="language"
label="<?= esc(lang('Podcast.form.language')) ?>" label="<?= esc(lang('Podcast.form.language')) ?>"
selected="<?= $browserLang ?>" defaultValue="<?= $browserLang ?>"
isRequired="true" isRequired="true"
options="<?= esc(json_encode($languageOptions)) ?>" /> options="<?= esc(json_encode($languageOptions)) ?>" />

View File

@ -30,7 +30,7 @@
label="<?= esc(lang('Person.podcast_form.persons')) ?>" label="<?= esc(lang('Person.podcast_form.persons')) ?>"
hint="<?= esc(lang('Person.podcast_form.persons_hint')) ?>" hint="<?= esc(lang('Person.podcast_form.persons_hint')) ?>"
options="<?= esc(json_encode($personOptions)) ?>" options="<?= esc(json_encode($personOptions)) ?>"
selected="<?= esc(json_encode(old('persons', []))) ?>" defaultValue="<?= esc(json_encode(old('persons', []))) ?>"
isRequired="true" /> isRequired="true" />
<x-Forms.Field <x-Forms.Field
@ -40,7 +40,7 @@
label="<?= esc(lang('Person.podcast_form.roles')) ?>" label="<?= esc(lang('Person.podcast_form.roles')) ?>"
hint="<?= esc(lang('Person.podcast_form.roles_hint')) ?>" hint="<?= esc(lang('Person.podcast_form.roles_hint')) ?>"
options="<?= esc(json_encode($taxonomyOptions)) ?>" options="<?= esc(json_encode($taxonomyOptions)) ?>"
selected="<?= esc(json_encode(old('roles', []))) ?>" defaultValue="<?= esc(json_encode(old('roles', []))) ?>"
/> />
<x-Button variant="primary" class="self-end" type="submit"><?= lang('Person.podcast_form.submit_add') ?></x-Button> <x-Button variant="primary" class="self-end" type="submit"><?= lang('Person.podcast_form.submit_add') ?></x-Button>

View File

@ -30,7 +30,7 @@
name="role" name="role"
label="<?= esc(lang('User.form.role')) ?>" label="<?= esc(lang('User.form.role')) ?>"
options="<?= esc(json_encode($roleOptions)) ?>" options="<?= esc(json_encode($roleOptions)) ?>"
selected="<?= setting('AuthGroups.defaultGroup') ?>" defaultValue="<?= setting('AuthGroups.defaultGroup') ?>"
isRequired="true" /> isRequired="true" />
<x-Button variant="primary" type="submit" class="self-end"><?= lang('User.form.submit_create') ?></x-Button> <x-Button variant="primary" type="submit" class="self-end"><?= lang('User.form.submit_create') ?></x-Button>

View File

@ -23,7 +23,7 @@
name="role" name="role"
label="<?= esc(lang('User.form.role')) ?>" label="<?= esc(lang('User.form.role')) ?>"
options="<?= esc(json_encode($roleOptions)) ?>" options="<?= esc(json_encode($roleOptions)) ?>"
selected="<?= esc(get_instance_group($user)) ?>" defaultValue="<?= esc(get_instance_group($user)) ?>"
isRequired="true" /> isRequired="true" />
<x-Button variant="primary" type="submit" class="self-end mt-4"><?= lang('User.form.submit_edit') ?></x-Button> <x-Button variant="primary" type="submit" class="self-end mt-4"><?= lang('User.form.submit_edit') ?></x-Button>

View File

@ -25,7 +25,7 @@
'redis' => lang('Install.form.cacheHandlerOptions.redis'), 'redis' => lang('Install.form.cacheHandlerOptions.redis'),
'predis' => lang('Install.form.cacheHandlerOptions.predis'), 'predis' => lang('Install.form.cacheHandlerOptions.predis'),
])) ?>" ])) ?>"
selected="file" defaultValue="file"
isRequired="true" /> isRequired="true" />
<?php // @icon('arrow-right-fill')?> <?php // @icon('arrow-right-fill')?>
<x-Button variant="primary" class="self-end" iconRight="arrow-right-fill" type="submit"><?= lang('Install.form.next') ?></x-Button> <x-Button variant="primary" class="self-end" iconRight="arrow-right-fill" type="submit"><?= lang('Install.form.next') ?></x-Button>