feat(plugins): add settings page for podcast and episode if defined in the plugin's manifest

- rename options to settings
This commit is contained in:
Yassine Doghri 2024-05-02 15:32:27 +00:00
parent 3be613452b
commit 5dd94ee6e0
19 changed files with 450 additions and 146 deletions

View File

@ -109,7 +109,7 @@ class Autoload extends AutoloadConfig
*
* @var list<string>
*/
public $helpers = ['auth', 'setting', 'icons'];
public $helpers = ['auth', 'setting', 'icons', 'plugins'];
public function __construct()
{

View File

@ -20,4 +20,5 @@ return [
'video-clips-create' => 'New video clip',
'soundbites-list' => 'Soundbites',
'soundbites-create' => 'New soundbite',
'plugins' => 'Plugins',
];

View File

@ -20,6 +20,7 @@ return [
'episodes' => 'Episodes',
'episode-list' => 'All episodes',
'episode-create' => 'New episode',
'plugins' => 'Plugins',
'analytics' => 'Analytics',
'podcast-analytics' => 'Audience overview',
'podcast-analytics-webpages' => 'Web pages visits',

View File

@ -22,7 +22,7 @@ use RuntimeException;
* @property string[] $keywords
* @property string[] $hooks
* @property string $iconSrc
* @property array{settings:array{key:string,name:string,description:string}[],podcast:array{key:string,name:string,description:string}[],episode:array{key:string,name:string,description:string}[]} $options
* @property array{general:array{key:string,name:string,description:string}[],podcast:array{key:string,name:string,description:string}[],episode:array{key:string,name:string,description:string}[]} $settings
*/
abstract class BasePlugin implements PluginInterface
{
@ -147,24 +147,24 @@ abstract class BasePlugin implements PluginInterface
$validation = service('validation');
if (array_key_exists('options', $manifest)) {
$optionRules = [
if (array_key_exists('settings', $manifest)) {
$fieldRules = [
'key' => 'required|alpha_numeric',
'name' => 'required|max_length[32]',
'description' => 'permit_empty|max_length[128]',
];
$defaultOption = [
$defaultField = [
'key' => '',
'name' => '',
'description' => '',
];
$validation->setRules($optionRules);
foreach ($manifest['options'] as $key => $options) {
foreach ($options as $key2 => $option) {
$manifest['options'][$key][$key2] = array_merge($defaultOption, $option);
$validation->setRules($fieldRules);
foreach ($manifest['settings'] as $key => $settings) {
foreach ($settings as $key2 => $fields) {
$manifest['settings'][$key][$key2] = array_merge($defaultField, $fields);
if (! $validation->run($manifest['options'][$key][$key2])) {
dd($this->key, $manifest['options'][$key][$key2], $validation->getErrors());
if (! $validation->run($manifest['settings'][$key][$key2])) {
dd($this->key, $manifest['settings'][$key][$key2], $validation->getErrors());
}
}
}
@ -183,7 +183,7 @@ abstract class BasePlugin implements PluginInterface
'website' => 'valid_url_strict',
'keywords.*' => 'permit_empty|in_list[seo,podcasting20,analytics]',
'hooks.*' => 'permit_empty|in_list[' . implode(',', Plugins::HOOKS) . ']',
'options' => 'permit_empty',
'settings' => 'permit_empty',
];
$validation->setRules($rules);
@ -200,10 +200,10 @@ abstract class BasePlugin implements PluginInterface
'website' => '',
'hooks' => [],
'keywords' => [],
'options' => [
'settings' => [],
'podcast' => [],
'episode' => [],
'settings' => [
'general' => [],
'podcast' => [],
'episode' => [],
],
];

View File

@ -13,26 +13,46 @@ $routes->group(
],
static function ($routes): void {
$routes->group('plugins', static function ($routes): void {
$routes->get('/', 'PluginsController::installed', [
$routes->get('/', 'PluginController::installed', [
'as' => 'plugins-installed',
'filter' => 'permission:plugins.manage',
]);
$routes->get('(:segment)', 'PluginsController::settings/$1', [
'as' => 'plugins-settings',
$routes->get('(:segment)', 'PluginController::generalSettings/$1', [
'as' => 'plugins-general-settings',
'filter' => 'permission:plugins.manage',
]);
$routes->post('(:segment)', 'PluginsController::settingsAction/$1', [
'as' => 'plugins-settings-action',
$routes->post('(:segment)', 'PluginController::generalSettingsAction/$1', [
'as' => 'plugins-general-settings-action',
'filter' => 'permission:plugins.manage',
]);
$routes->post('activate/(:segment)', 'PluginsController::activate/$1', [
$routes->post('activate/(:segment)', 'PluginController::activate/$1', [
'as' => 'plugins-activate',
'filter' => 'permission:plugins.manage',
]);
$routes->post('deactivate/(:segment)', 'PluginsController::deactivate/$1', [
$routes->post('deactivate/(:segment)', 'PluginController::deactivate/$1', [
'as' => 'plugins-deactivate',
'filter' => 'permission:plugins.manage',
]);
});
$routes->group('podcasts/(:num)/plugins', static function ($routes): void {
$routes->get('(:segment)', 'PluginController::podcastSettings/$1/$2', [
'as' => 'plugins-podcast-settings',
'filter' => 'permission:podcast#.edit',
]);
$routes->post('(:segment)', 'PluginController::podcastSettingsAction/$1/$2', [
'as' => 'plugins-podcast-settings-action',
'filter' => 'permission:podcast#.edit',
]);
});
$routes->group('podcasts/(:num)/episodes/(:num)/plugins', static function ($routes): void {
$routes->get('(:segment)', 'PluginController::episodeSettings/$1/$2/$3', [
'as' => 'plugins-episode-settings',
'filter' => 'permission:podcast#.edit',
]);
$routes->post('(:segment)', 'PluginController::episodeSettingsAction/$1/$2/$3', [
'as' => 'plugins-episode-settings-action',
'filter' => 'permission:podcast#.edit',
]);
});
}
);

View File

@ -0,0 +1,193 @@
<?php
declare(strict_types=1);
namespace Modules\Plugins\Controllers;
use App\Entities\Episode;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Modules\Admin\Controllers\BaseController;
use Modules\Plugins\Plugins;
class PluginController extends BaseController
{
public function installed(): string
{
/** @var Plugins $plugins */
$plugins = service('plugins');
$pager = service('pager');
$page = (int) ($this->request->getGet('page') ?? 1);
$perPage = 10;
$total = $plugins->getInstalledCount();
$pager_links = $pager->makeLinks($page, $perPage, $total);
return view('plugins/installed', [
'total' => $total,
'plugins' => $plugins->getPlugins($page, $perPage),
'pager_links' => $pager_links,
]);
}
public function generalSettings(string $pluginKey): string
{
/** @var Plugins $plugins */
$plugins = service('plugins');
$plugin = $plugins->getPlugin($pluginKey);
if ($plugin === null) {
throw PageNotFoundException::forPageNotFound();
}
helper('form');
return view('plugins/settings_general', [
'plugin' => $plugin,
]);
}
public function generalSettingsAction(string $pluginKey): RedirectResponse
{
/** @var Plugins $plugins */
$plugins = service('plugins');
$plugin = $plugins->getPlugin($pluginKey);
if ($plugin === null) {
throw PageNotFoundException::forPageNotFound();
}
foreach ($plugin->settings['general'] as $option) {
$optionKey = $option['key'];
$optionValue = $this->request->getPost($optionKey);
$plugins->setOption($pluginKey, $optionKey, $optionValue);
}
return redirect()->back()
->with('message', lang('Plugins.messages.saveSettingsSuccess', [
'pluginName' => $plugin->getName(),
]));
}
public function podcastSettings(string $podcastId, string $pluginKey): string
{
$podcast = (new PodcastModel())->getPodcastById((int) $podcastId);
if (! $podcast instanceof Podcast) {
throw PageNotFoundException::forPageNotFound();
}
/** @var Plugins $plugins */
$plugins = service('plugins');
$plugin = $plugins->getPlugin($pluginKey);
if ($plugin === null) {
throw PageNotFoundException::forPageNotFound();
}
helper('form');
replace_breadcrumb_params([
0 => $podcast->handle,
]);
return view('plugins/settings_podcast', [
'podcast' => $podcast,
'plugin' => $plugin,
]);
}
public function podcastSettingsAction(string $podcastId, string $pluginKey): RedirectResponse
{
/** @var Plugins $plugins */
$plugins = service('plugins');
$plugin = $plugins->getPlugin($pluginKey);
if ($plugin === null) {
throw PageNotFoundException::forPageNotFound();
}
foreach ($plugin->settings['podcast'] as $setting) {
$settingKey = $setting['key'];
$settingValue = $this->request->getPost($settingKey);
$plugins->setOption($pluginKey, $settingKey, $settingValue, ['podcast', (int) $podcastId]);
}
return redirect()->back()
->with('message', lang('Plugins.messages.saveSettingsSuccess', [
'pluginName' => $plugin->getName(),
]));
}
public function episodeSettings(string $podcastId, string $episodeId, string $pluginKey): string
{
$episode = (new EpisodeModel())->getEpisodeById((int) $episodeId);
if (! $episode instanceof Episode) {
throw PageNotFoundException::forPageNotFound();
}
/** @var Plugins $plugins */
$plugins = service('plugins');
$plugin = $plugins->getPlugin($pluginKey);
if ($plugin === null) {
throw PageNotFoundException::forPageNotFound();
}
helper('form');
replace_breadcrumb_params([
0 => $episode->podcast->handle,
1 => $episode->title,
]);
return view('plugins/settings_episode', [
'podcast' => $episode->podcast,
'episode' => $episode,
'plugin' => $plugin,
]);
}
public function episodeSettingsAction(string $podcastId, string $episodeId, string $pluginKey): RedirectResponse
{
/** @var Plugins $plugins */
$plugins = service('plugins');
$plugin = $plugins->getPlugin($pluginKey);
if ($plugin === null) {
throw PageNotFoundException::forPageNotFound();
}
foreach ($plugin->settings['episode'] as $setting) {
$settingKey = $setting['key'];
$settingValue = $this->request->getPost($settingKey);
$plugins->setOption($pluginKey, $settingKey, $settingValue, ['episode', (int) $episodeId]);
}
return redirect()->back()
->with('message', lang('Plugins.messages.saveSettingsSuccess', [
'pluginName' => $plugin->getName(),
]));
}
public function activate(string $pluginKey): RedirectResponse
{
service('plugins')->activate($pluginKey);
return redirect()->back();
}
public function deactivate(string $pluginKey): RedirectResponse
{
service('plugins')->deactivate($pluginKey);
return redirect()->back();
}
}

View File

@ -1,84 +0,0 @@
<?php
declare(strict_types=1);
namespace Modules\Plugins\Controllers;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Modules\Admin\Controllers\BaseController;
use Modules\Plugins\Plugins;
class PluginsController extends BaseController
{
public function installed(): string
{
/** @var Plugins $plugins */
$plugins = service('plugins');
$pager = service('pager');
$page = (int) ($this->request->getGet('page') ?? 1);
$perPage = 10;
$total = $plugins->getInstalledCount();
$pager_links = $pager->makeLinks($page, $perPage, $total);
return view('plugins/installed', [
'total' => $total,
'plugins' => $plugins->getPlugins($page, $perPage),
'pager_links' => $pager_links,
]);
}
public function settings(string $pluginKey): string
{
/** @var Plugins $plugins */
$plugins = service('plugins');
$plugin = $plugins->getPlugin($pluginKey);
if ($plugin === null) {
throw PageNotFoundException::forPageNotFound();
}
helper('form');
return view('plugins/settings', [
'plugin' => $plugin,
]);
}
public function settingsAction(string $pluginKey): RedirectResponse
{
/** @var Plugins $plugins */
$plugins = service('plugins');
$plugin = $plugins->getPlugin($pluginKey);
if ($plugin === null) {
throw PageNotFoundException::forPageNotFound();
}
foreach ($plugin->options['settings'] as $option) {
$optionKey = $option['key'];
$optionValue = $this->request->getPost($optionKey);
$plugins->setOption($pluginKey, $optionKey, $optionValue);
}
return redirect()->back();
}
public function activate(string $pluginKey): RedirectResponse
{
service('plugins')->activate($pluginKey);
return redirect()->back();
}
public function deactivate(string $pluginKey): RedirectResponse
{
service('plugins')->deactivate($pluginKey);
return redirect()->back();
}
}

View File

@ -2,22 +2,49 @@
declare(strict_types=1);
use Modules\Plugins\Plugins;
if (! function_exists('plugins')) {
function plugins(): Plugins
{
return service('plugins');
}
}
if (! function_exists('get_plugin_option')) {
function get_plugin_option(string $pluginKey, string $option): mixed
/**
* @param ?array{'podcast'|'episode',int} $additionalContext
*/
function get_plugin_option(string $pluginKey, string $option, array $additionalContext = null): mixed
{
$key = sprintf('Plugins.%s', $option);
$context = sprintf('plugin:%s', $pluginKey);
if ($additionalContext !== null) {
$context .= sprintf('+%s:%d', ...$additionalContext);
}
return setting()->get($key, $context);
}
}
if (! function_exists('set_plugin_option')) {
function set_plugin_option(string $pluginKey, string $option, mixed $value = null): void
{
/**
* @param ?array{'podcast'|'episode',int} $additionalContext
*/
function set_plugin_option(
string $pluginKey,
string $option,
mixed $value = null,
array $additionalContext = null
): void {
$key = sprintf('Plugins.%s', $option);
$context = sprintf('plugin:%s', $pluginKey);
if ($additionalContext !== null) {
$context .= sprintf('+%s:%d', ...$additionalContext);
}
setting()
->set($key, $value, $context);
}

View File

@ -9,14 +9,18 @@ declare(strict_types=1);
*/
return [
"installed" => "Installed plugins ({count})",
"website" => "Website",
"activate" => "Activate",
"deactivate" => "Deactivate",
"keywords" => [
'installed' => 'Installed plugins ({count})',
'website' => 'Website',
'settings' => '{pluginName} settings',
'activate' => 'Activate',
'deactivate' => 'Deactivate',
'keywords' => [
'podcasting20' => 'Podcasting 2.0',
'seo' => 'SEO',
'analytics' => 'Analytics',
'accessibility' => 'Accessibility',
],
'messages' => [
'saveSettingsSuccess' => '{pluginName} settings were successfully saved!',
],
];

View File

@ -57,6 +57,48 @@ class Plugins
return array_slice(static::$plugins, (($page - 1) * $perPage), $perPage);
}
/**
* @return array<BasePlugin>
*/
public function getPluginsWithPodcastSettings(): array
{
$pluginsWithPodcastSettings = [];
foreach (static::$plugins as $plugin) {
if (! $plugin->isActive()) {
continue;
}
if ($plugin->settings['podcast'] === []) {
continue;
}
$pluginsWithPodcastSettings[] = $plugin;
}
return $pluginsWithPodcastSettings;
}
/**
* @return array<BasePlugin>
*/
public function getPluginsWithEpisodeSettings(): array
{
$pluginsWithEpisodeSettings = [];
foreach (static::$plugins as $plugin) {
if (! $plugin->isActive()) {
continue;
}
if ($plugin->settings['episode'] === []) {
continue;
}
$pluginsWithEpisodeSettings[] = $plugin;
}
return $pluginsWithEpisodeSettings;
}
public function getPlugin(string $key): ?BasePlugin
{
foreach (static::$plugins as $plugin) {
@ -98,9 +140,12 @@ class Plugins
set_plugin_option($pluginKey, 'active', false);
}
public function setOption(string $pluginKey, string $name, string $value): void
/**
* @param ?array{'podcast'|'episode',int} $additionalContext
*/
public function setOption(string $pluginKey, string $name, mixed $value, array $additionalContext = null): void
{
set_plugin_option($pluginKey, $name, $value);
set_plugin_option($pluginKey, $name, $value, $additionalContext);
}
public function getInstalledCount(): int

View File

@ -1,9 +1,14 @@
<nav class="flex flex-col flex-1 py-4 overflow-y-auto">
<?php foreach ($navigation as $section => $data):
if ($data['items'] === []) {
continue;
}
$isSectionActive = false;
$activeItem = '';
foreach ($data['items'] as $item) {
if (url_is(route_to($item, $podcastId ?? null, $episodeId ?? null))) {
$href = str_starts_with($item, '/') ? $item : route_to($item, $podcastId ?? null, $episodeId ?? null);
if (url_is($href)) {
$activeItem = $item;
$isSectionActive = true;
}
@ -27,8 +32,10 @@
<?php endif; ?>
</summary>
<ul class="flex flex-col pb-4">
<?php foreach ($data['items'] as $item):
<?php foreach ($data['items'] as $key => $item):
$isActive = $item === $activeItem;
$label = array_key_exists('items-labels', $data) ? $data['items-labels'][$key] : lang($langKey . '.' . $item);
$href = str_starts_with($item, '/') ? $item : route_to($item, $podcastId ?? null, $episodeId ?? null);
$isAllowed = true;
if (array_key_exists('items-permissions', $data) && array_key_exists($item, $data['items-permissions'])) {
@ -43,13 +50,9 @@
<?php if ($isAllowed): ?>
<a class="relative w-full py-3 pl-14 pr-2 text-sm hover:opacity-100 before:content-chevronRightIcon before:absolute before:-ml-5 before:opacity-0 before:w-5 before:h-5 hover:bg-navigation-active focus:ring-inset focus:ring-accent<?= $isActive
? ' before:opacity-100 font-semibold inline-flex items-center'
: ' hover:before:opacity-60 focus:before:opacity-60' ?>" href="<?= route_to($item, $podcastId ?? null, $episodeId ?? null) ?>"><?= lang(
$langKey . '.' . $item,
) ?></a>
: ' hover:before:opacity-60 focus:before:opacity-60' ?>" href="<?= $href ?>"><?= $label ?></a>
<?php else: ?>
<span data-tooltip="right" title="<?= lang('Navigation.not-authorized') ?>" class="relative w-full py-3 pr-2 text-sm cursor-not-allowed before:inset-y-0 before:my-auto pl-14 hover:opacity-100 before:absolute before:content-prohibitedIcon before:-ml-5 before:opacity-60 before:w-4 before:h-4 hover:bg-navigation-active focus:ring-inset focus:ring-accent"><?= lang(
$langKey . '.' . $item,
) ?></span>
<span data-tooltip="right" title="<?= lang('Navigation.not-authorized') ?>" class="relative w-full py-3 pr-2 text-sm cursor-not-allowed before:inset-y-0 before:my-auto pl-14 hover:opacity-100 before:absolute before:content-prohibitedIcon before:-ml-5 before:opacity-60 before:w-4 before:h-4 hover:bg-navigation-active focus:ring-inset focus:ring-accent"><?= $label ?></span>
<?php endif; ?>
</li>
<?php endforeach; ?>

View File

@ -24,7 +24,22 @@ $episodeNavigation = [
'count-route' => 'video-clips-list',
'add-cta' => 'video-clips-create',
],
]; ?>
'plugins' => [
'icon' => 'puzzle-fill', // @icon('puzzle-fill')
'items' => [],
'items-labels' => [],
'items-permissions' => [],
],
];
foreach (plugins()->getPluginsWithEpisodeSettings() as $plugin) {
$route = route_to('plugins-episode-settings', $podcast->id, $episode->id, $plugin->getKey());
$episodeNavigation['plugins']['items'][] = $route;
$episodeNavigation['plugins']['items-labels'][] = $plugin->getName();
$episodeNavigation['plugins']['items-permissions'][$route] = 'episodes.edit';
}
?>
<a href="<?= route_to('podcast-view', $podcast->id) ?>" class="flex items-center px-4 py-2 focus:ring-inset focus:ring-accent">
<?= icon('arrow-left-line', [

View File

@ -1,4 +1,10 @@
<article class="flex flex-col p-4 rounded-xl bg-elevated border-3 <?= $plugin->isActive() ? 'border-accent-base' : 'border-subtle' ?>">
<article class="flex flex-col p-4 rounded-xl relative bg-elevated border-3 <?= $plugin->isActive() ? 'border-accent-base' : 'border-subtle' ?>">
<?php if ($plugin->settings['general'] !== []): ?>
<?php // @icon('equalizer-fill')?>
<IconButton class="absolute top-0 right-0 mt-4 mr-4" uri="<?= route_to('plugins-general-settings', $plugin->getKey()) ?>" glyph="equalizer-fill"><?= lang('Plugins.settings', [
'pluginName' => $plugin->getName(),
]) ?></IconButton>
<?php endif; ?>
<img class="rounded-full min-w-16 max-w-16 aspect-square" src="<?= $plugin->iconSrc ?>">
<div class="flex flex-col mt-2">
<h2 class="flex items-center text-xl font-bold font-display gap-x-2"><?= $plugin->getName() ?><span class="px-1 font-mono text-xs rounded-full bg-subtle"><?= $plugin->version ?></span></h2>
@ -8,10 +14,7 @@
<a href="<?= $plugin->website ?>" class="inline-flex items-center text-sm font-semibold underline hover:no-underline gap-x-1" target="_blank" rel="noopener noreferrer"><?= icon('link', [
'class' => 'text-gray-500',
]) . lang('Plugins.website') ?></a>
<?php if ($plugin->options['settings'] !== []): ?>
<?php // @icon('equalizer-fill')?>
<IconButton uri="<?= route_to('plugins-settings', $plugin->getKey()) ?>" glyph="equalizer-fill">Settings</IconButton>
<?php endif; ?>
<?php if($plugin->isActive()): ?>
<form class="flex justify-end" method="POST" action="<?= route_to('plugins-deactivate', $plugin->getKey()) ?>">
<?= csrf_field() ?>

View File

@ -0,0 +1,12 @@
<form method="POST" action="<?= $action ?>" class="flex flex-col max-w-sm gap-4" >
<?= csrf_field() ?>
<?php foreach ($plugin->settings[$type] as $field): ?>
<Forms.Field
name="<?= $field['key'] ?>"
label="<?= $field['name'] ?>"
hint="<?= $field['description'] ?>"
value="<?= get_plugin_option($plugin->getKey(), $field['key'], $context) ?>"
/>
<?php endforeach; ?>
<Button class="self-end mt-4" variant="primary" type="submit"><?= lang('Common.forms.save') ?></Button>
</form>

View File

@ -1,15 +0,0 @@
<?= $this->extend('_layout') ?>
<?= $this->section('content') ?>
<form method="POST" action="<?= route_to('plugins-settings-action', $plugin->getKey()) ?>" class="flex flex-col max-w-sm gap-4" >
<?= csrf_field() ?>
<?php foreach ($plugin->options['settings'] as $option): ?>
<Forms.Field
name="<?= $option['key'] ?>"
label="<?= $option['name'] ?>"
hint="<?= $option['description'] ?>"
/>
<?php endforeach; ?>
<Button class="self-end mt-4" variant="primary" type="submit"><?= lang('Plugins.form.save') ?></Button>
</form>
<?= $this->endSection() ?>

View File

@ -0,0 +1,22 @@
<?= $this->extend('_layout') ?>
<?= $this->section('title') ?>
<?= lang('Plugins.settings', [
'pluginName' => $plugin->getName(),
]) ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Plugins.settings', [
'pluginName' => $plugin->getName(),
]) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= view('plugins/_settings', [
'plugin' => $plugin,
'action' => route_to('plugins-episode-settings-action', $podcast->id, $episode->id, $plugin->getKey()),
'type' => 'episode',
'context' => ['episode', $episode->id],
]) ?>
<?= $this->endSection() ?>

View File

@ -0,0 +1,22 @@
<?= $this->extend('_layout') ?>
<?= $this->section('title') ?>
<?= lang('Plugins.settings', [
'pluginName' => $plugin->getName(),
]) ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Plugins.settings', [
'pluginName' => $plugin->getName(),
]) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= view('plugins/_settings', [
'plugin' => $plugin,
'action' => route_to('plugins-general-settings-action', $plugin->getKey()),
'type' => 'general',
'context' => null,
]) ?>
<?= $this->endSection() ?>

View File

@ -0,0 +1,22 @@
<?= $this->extend('_layout') ?>
<?= $this->section('title') ?>
<?= lang('Plugins.settings', [
'pluginName' => $plugin->getName(),
]) ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Plugins.settings', [
'pluginName' => $plugin->getName(),
]) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= view('plugins/_settings', [
'plugin' => $plugin,
'action' => route_to('plugins-podcast-settings-action', $podcast->id, $plugin->getKey()),
'type' => 'podcast',
'context' => ['podcast', $podcast->id],
]) ?>
<?= $this->endSection() ?>

View File

@ -23,6 +23,12 @@ $podcastNavigation = [
'count' => $podcast->getEpisodesCount(),
'count-route' => 'episode-list',
],
'plugins' => [
'icon' => 'puzzle-fill', // @icon('puzzle-fill')
'items' => [],
'items-labels' => [],
'items-permissions' => [],
],
'analytics' => [
'icon' => 'line-chart-fill', // @icon('line-chart-fill')
'items' => [
@ -83,6 +89,13 @@ $podcastNavigation = [
],
];
foreach (plugins()->getPluginsWithPodcastSettings() as $plugin) {
$route = route_to('plugins-podcast-settings', $podcast->id, $plugin->getKey());
$podcastNavigation['plugins']['items'][] = $route;
$podcastNavigation['plugins']['items-labels'][] = $plugin->getName();
$podcastNavigation['plugins']['items-permissions'][$route] = 'edit';
}
?>
<div class="flex gap-2 px-2 py-2 border-b border-navigation">