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/plugin/PluginManager.php | 64 ++++++++++++++++++++++ .../exception/PluginInvalidRouteException.php | 26 +++++++++ 2 files changed, 90 insertions(+) create mode 100644 application/plugin/exception/PluginInvalidRouteException.php (limited to 'application/plugin') 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