From a6e9c08499f9f79dad88cb3ae9eacda0e0c34c96 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 27 Oct 2020 19:23:45 +0100 Subject: Plugin system: allow plugins to provide custom routes - each route will be prefixed by `/plugin/` - add a new template for plugins rendering - add a live example in the demo_plugin Check out the "Plugin System" documentation for more detail. Related to #143 --- application/container/ContainerBuilder.php | 17 ++---- application/plugin/PluginManager.php | 64 ++++++++++++++++++++++ .../exception/PluginInvalidRouteException.php | 26 +++++++++ 3 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 application/plugin/exception/PluginInvalidRouteException.php (limited to 'application') diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php index f0234eca..6d69a880 100644 --- a/application/container/ContainerBuilder.php +++ b/application/container/ContainerBuilder.php @@ -50,6 +50,9 @@ class ContainerBuilder /** @var LoginManager */ protected $login; + /** @var PluginManager */ + protected $pluginManager; + /** @var LoggerInterface */ protected $logger; @@ -61,12 +64,14 @@ class ContainerBuilder SessionManager $session, CookieManager $cookieManager, LoginManager $login, + PluginManager $pluginManager, LoggerInterface $logger ) { $this->conf = $conf; $this->session = $session; $this->login = $login; $this->cookieManager = $cookieManager; + $this->pluginManager = $pluginManager; $this->logger = $logger; } @@ -78,12 +83,10 @@ class ContainerBuilder $container['sessionManager'] = $this->session; $container['cookieManager'] = $this->cookieManager; $container['loginManager'] = $this->login; + $container['pluginManager'] = $this->pluginManager; $container['logger'] = $this->logger; $container['basePath'] = $this->basePath; - $container['plugins'] = function (ShaarliContainer $container): PluginManager { - return new PluginManager($container->conf); - }; $container['history'] = function (ShaarliContainer $container): History { return new History($container->conf->get('resource.history')); @@ -113,14 +116,6 @@ class ContainerBuilder ); }; - $container['pluginManager'] = function (ShaarliContainer $container): PluginManager { - $pluginManager = new PluginManager($container->conf); - - $pluginManager->load($container->conf->get('general.enabled_plugins')); - - return $pluginManager; - }; - $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { return new FormatterFactory( $container->conf, diff --git a/application/plugin/PluginManager.php b/application/plugin/PluginManager.php index 3ea55728..7fc0cb04 100644 --- a/application/plugin/PluginManager.php +++ b/application/plugin/PluginManager.php @@ -4,6 +4,7 @@ namespace Shaarli\Plugin; use Shaarli\Config\ConfigManager; use Shaarli\Plugin\Exception\PluginFileNotFoundException; +use Shaarli\Plugin\Exception\PluginInvalidRouteException; /** * Class PluginManager @@ -26,6 +27,14 @@ class PluginManager */ private $loadedPlugins = []; + /** @var array List of registered routes. Contains keys: + * - `method`: HTTP method, GET/POST/PUT/PATCH/DELETE + * - `route` (path): without prefix, e.g. `/up/{variable}` + * It will be later prefixed by `/plugin//`. + * - `callable` string, function name or FQN class's method, e.g. `demo_plugin_custom_controller`. + */ + protected $registeredRoutes = []; + /** * @var ConfigManager Configuration Manager instance. */ @@ -86,6 +95,9 @@ class PluginManager $this->loadPlugin($dirs[$index], $plugin); } catch (PluginFileNotFoundException $e) { error_log($e->getMessage()); + } catch (\Throwable $e) { + $error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage(); + $this->errors = array_unique(array_merge($this->errors, [$error])); } } } @@ -166,6 +178,22 @@ class PluginManager } } + $registerRouteFunction = $pluginName . '_register_routes'; + $routes = null; + if (function_exists($registerRouteFunction)) { + $routes = call_user_func($registerRouteFunction); + } + + if ($routes !== null) { + foreach ($routes as $route) { + if (static::validateRouteRegistration($route)) { + $this->registeredRoutes[$pluginName][] = $route; + } else { + throw new PluginInvalidRouteException($pluginName); + } + } + } + $this->loadedPlugins[] = $pluginName; } @@ -237,6 +265,14 @@ class PluginManager return $metaData; } + /** + * @return array List of registered custom routes by plugins. + */ + public function getRegisteredRoutes(): array + { + return $this->registeredRoutes; + } + /** * Return the list of encountered errors. * @@ -246,4 +282,32 @@ class PluginManager { return $this->errors; } + + /** + * Checks whether provided input is valid to register a new route. + * It must contain keys `method`, `route`, `callable` (all strings). + * + * @param string[] $input + * + * @return bool + */ + protected static function validateRouteRegistration(array $input): bool + { + if ( + !array_key_exists('method', $input) + || !in_array(strtoupper($input['method']), ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) + ) { + return false; + } + + if (!array_key_exists('route', $input) || !preg_match('#^[a-z\d/\.\-_]+$#', $input['route'])) { + return false; + } + + if (!array_key_exists('callable', $input)) { + return false; + } + + return true; + } } diff --git a/application/plugin/exception/PluginInvalidRouteException.php b/application/plugin/exception/PluginInvalidRouteException.php new file mode 100644 index 00000000..6ba9bc43 --- /dev/null +++ b/application/plugin/exception/PluginInvalidRouteException.php @@ -0,0 +1,26 @@ +message = 'trying to register invalid route.'; + } +} -- cgit v1.2.3