From 5dd94ee6e0409f7f8d941139d0c622f6923b6e10 Mon Sep 17 00:00:00 2001 From: Yassine Doghri Date: Thu, 2 May 2024 15:32:27 +0000 Subject: [PATCH] feat(plugins): add settings page for podcast and episode if defined in the plugin's manifest - rename options to settings --- app/Config/Autoload.php | 2 +- .../Admin/Language/en/EpisodeNavigation.php | 1 + .../Admin/Language/en/PodcastNavigation.php | 1 + modules/Plugins/BasePlugin.php | 30 +-- modules/Plugins/Config/Routes.php | 34 ++- .../Plugins/Controllers/PluginController.php | 193 ++++++++++++++++++ .../Plugins/Controllers/PluginsController.php | 84 -------- modules/Plugins/Helpers/plugins_helper.php | 33 ++- modules/Plugins/Language/en/Plugins.php | 14 +- modules/Plugins/Plugins.php | 49 ++++- themes/cp_admin/_partials/_nav_menu.php | 19 +- themes/cp_admin/episode/_sidebar.php | 17 +- themes/cp_admin/plugins/_plugin.php | 13 +- themes/cp_admin/plugins/_settings.php | 12 ++ themes/cp_admin/plugins/settings.php | 15 -- themes/cp_admin/plugins/settings_episode.php | 22 ++ themes/cp_admin/plugins/settings_general.php | 22 ++ themes/cp_admin/plugins/settings_podcast.php | 22 ++ themes/cp_admin/podcast/_sidebar.php | 13 ++ 19 files changed, 450 insertions(+), 146 deletions(-) create mode 100644 modules/Plugins/Controllers/PluginController.php delete mode 100644 modules/Plugins/Controllers/PluginsController.php create mode 100644 themes/cp_admin/plugins/_settings.php delete mode 100644 themes/cp_admin/plugins/settings.php create mode 100644 themes/cp_admin/plugins/settings_episode.php create mode 100644 themes/cp_admin/plugins/settings_general.php create mode 100644 themes/cp_admin/plugins/settings_podcast.php diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index c75ea20c..cb8830b9 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -109,7 +109,7 @@ class Autoload extends AutoloadConfig * * @var list */ - public $helpers = ['auth', 'setting', 'icons']; + public $helpers = ['auth', 'setting', 'icons', 'plugins']; public function __construct() { diff --git a/modules/Admin/Language/en/EpisodeNavigation.php b/modules/Admin/Language/en/EpisodeNavigation.php index 1406e301..ef3cdec0 100644 --- a/modules/Admin/Language/en/EpisodeNavigation.php +++ b/modules/Admin/Language/en/EpisodeNavigation.php @@ -20,4 +20,5 @@ return [ 'video-clips-create' => 'New video clip', 'soundbites-list' => 'Soundbites', 'soundbites-create' => 'New soundbite', + 'plugins' => 'Plugins', ]; diff --git a/modules/Admin/Language/en/PodcastNavigation.php b/modules/Admin/Language/en/PodcastNavigation.php index bb777707..a6b73e4a 100644 --- a/modules/Admin/Language/en/PodcastNavigation.php +++ b/modules/Admin/Language/en/PodcastNavigation.php @@ -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', diff --git a/modules/Plugins/BasePlugin.php b/modules/Plugins/BasePlugin.php index db808aa4..31beca42 100644 --- a/modules/Plugins/BasePlugin.php +++ b/modules/Plugins/BasePlugin.php @@ -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' => [], ], ]; diff --git a/modules/Plugins/Config/Routes.php b/modules/Plugins/Config/Routes.php index c2e4caa8..5ec00065 100644 --- a/modules/Plugins/Config/Routes.php +++ b/modules/Plugins/Config/Routes.php @@ -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', + ]); + }); } ); diff --git a/modules/Plugins/Controllers/PluginController.php b/modules/Plugins/Controllers/PluginController.php new file mode 100644 index 00000000..fd40049e --- /dev/null +++ b/modules/Plugins/Controllers/PluginController.php @@ -0,0 +1,193 @@ +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(); + } +} diff --git a/modules/Plugins/Controllers/PluginsController.php b/modules/Plugins/Controllers/PluginsController.php deleted file mode 100644 index e0ed0477..00000000 --- a/modules/Plugins/Controllers/PluginsController.php +++ /dev/null @@ -1,84 +0,0 @@ -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(); - } -} diff --git a/modules/Plugins/Helpers/plugins_helper.php b/modules/Plugins/Helpers/plugins_helper.php index b71536bc..f475cad5 100644 --- a/modules/Plugins/Helpers/plugins_helper.php +++ b/modules/Plugins/Helpers/plugins_helper.php @@ -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); } diff --git a/modules/Plugins/Language/en/Plugins.php b/modules/Plugins/Language/en/Plugins.php index f801caa1..52ea95a0 100644 --- a/modules/Plugins/Language/en/Plugins.php +++ b/modules/Plugins/Language/en/Plugins.php @@ -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!', + ], ]; diff --git a/modules/Plugins/Plugins.php b/modules/Plugins/Plugins.php index 531c20dc..24616ebc 100644 --- a/modules/Plugins/Plugins.php +++ b/modules/Plugins/Plugins.php @@ -57,6 +57,48 @@ class Plugins return array_slice(static::$plugins, (($page - 1) * $perPage), $perPage); } + /** + * @return array + */ + 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 + */ + 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 diff --git a/themes/cp_admin/_partials/_nav_menu.php b/themes/cp_admin/_partials/_nav_menu.php index 7184724e..f675d603 100644 --- a/themes/cp_admin/_partials/_nav_menu.php +++ b/themes/cp_admin/_partials/_nav_menu.php @@ -1,9 +1,14 @@