Plugin system: allow plugins to provide custom routes

- each route will be prefixed by `/plugin/<plugin_name>`
  - 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 
This commit is contained in:
ArthurHoaro 2020-10-27 19:23:45 +01:00
parent 6f9e0609f4
commit a6e9c08499
13 changed files with 270 additions and 13 deletions

View file

@ -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/<plugin name>/`.
* - `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;
}
}

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Shaarli\Plugin\Exception;
use Exception;
/**
* Class PluginFileNotFoundException
*
* Raise when plugin files can't be found.
*/
class PluginInvalidRouteException extends Exception
{
/**
* Construct exception with plugin name.
* Generate message.
*
* @param string $pluginName name of the plugin not found
*/
public function __construct()
{
$this->message = 'trying to register invalid route.';
}
}