diff options
author | ArthurHoaro <arthur@hoa.ro> | 2020-10-27 19:23:45 +0100 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2020-11-15 12:41:43 +0100 |
commit | a6e9c08499f9f79dad88cb3ae9eacda0e0c34c96 (patch) | |
tree | 41f70d7dc478e70a4a3ce4a578839316f5578765 | |
parent | 6f9e0609f4c118142504234ebcc7d93456b5e588 (diff) | |
download | Shaarli-a6e9c08499f9f79dad88cb3ae9eacda0e0c34c96.tar.gz Shaarli-a6e9c08499f9f79dad88cb3ae9eacda0e0c34c96.tar.zst Shaarli-a6e9c08499f9f79dad88cb3ae9eacda0e0c34c96.zip |
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 #143
-rw-r--r-- | application/container/ContainerBuilder.php | 17 | ||||
-rw-r--r-- | application/plugin/PluginManager.php | 64 | ||||
-rw-r--r-- | application/plugin/exception/PluginInvalidRouteException.php | 26 | ||||
-rw-r--r-- | doc/md/dev/Plugin-system.md | 25 | ||||
-rw-r--r-- | index.php | 22 | ||||
-rw-r--r-- | phpcs.xml | 1 | ||||
-rw-r--r-- | plugins/demo_plugin/DemoPluginController.php | 24 | ||||
-rw-r--r-- | plugins/demo_plugin/demo_plugin.php | 19 | ||||
-rw-r--r-- | tests/PluginManagerTest.php | 39 | ||||
-rw-r--r-- | tests/container/ContainerBuilderTest.php | 5 | ||||
-rw-r--r-- | tests/plugins/test/test.php | 16 | ||||
-rw-r--r-- | tests/plugins/test_route_invalid/test_route_invalid.php | 12 | ||||
-rw-r--r-- | tpl/default/pluginscontent.html | 13 |
13 files changed, 270 insertions, 13 deletions
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 | |||
50 | /** @var LoginManager */ | 50 | /** @var LoginManager */ |
51 | protected $login; | 51 | protected $login; |
52 | 52 | ||
53 | /** @var PluginManager */ | ||
54 | protected $pluginManager; | ||
55 | |||
53 | /** @var LoggerInterface */ | 56 | /** @var LoggerInterface */ |
54 | protected $logger; | 57 | protected $logger; |
55 | 58 | ||
@@ -61,12 +64,14 @@ class ContainerBuilder | |||
61 | SessionManager $session, | 64 | SessionManager $session, |
62 | CookieManager $cookieManager, | 65 | CookieManager $cookieManager, |
63 | LoginManager $login, | 66 | LoginManager $login, |
67 | PluginManager $pluginManager, | ||
64 | LoggerInterface $logger | 68 | LoggerInterface $logger |
65 | ) { | 69 | ) { |
66 | $this->conf = $conf; | 70 | $this->conf = $conf; |
67 | $this->session = $session; | 71 | $this->session = $session; |
68 | $this->login = $login; | 72 | $this->login = $login; |
69 | $this->cookieManager = $cookieManager; | 73 | $this->cookieManager = $cookieManager; |
74 | $this->pluginManager = $pluginManager; | ||
70 | $this->logger = $logger; | 75 | $this->logger = $logger; |
71 | } | 76 | } |
72 | 77 | ||
@@ -78,12 +83,10 @@ class ContainerBuilder | |||
78 | $container['sessionManager'] = $this->session; | 83 | $container['sessionManager'] = $this->session; |
79 | $container['cookieManager'] = $this->cookieManager; | 84 | $container['cookieManager'] = $this->cookieManager; |
80 | $container['loginManager'] = $this->login; | 85 | $container['loginManager'] = $this->login; |
86 | $container['pluginManager'] = $this->pluginManager; | ||
81 | $container['logger'] = $this->logger; | 87 | $container['logger'] = $this->logger; |
82 | $container['basePath'] = $this->basePath; | 88 | $container['basePath'] = $this->basePath; |
83 | 89 | ||
84 | $container['plugins'] = function (ShaarliContainer $container): PluginManager { | ||
85 | return new PluginManager($container->conf); | ||
86 | }; | ||
87 | 90 | ||
88 | $container['history'] = function (ShaarliContainer $container): History { | 91 | $container['history'] = function (ShaarliContainer $container): History { |
89 | return new History($container->conf->get('resource.history')); | 92 | return new History($container->conf->get('resource.history')); |
@@ -113,14 +116,6 @@ class ContainerBuilder | |||
113 | ); | 116 | ); |
114 | }; | 117 | }; |
115 | 118 | ||
116 | $container['pluginManager'] = function (ShaarliContainer $container): PluginManager { | ||
117 | $pluginManager = new PluginManager($container->conf); | ||
118 | |||
119 | $pluginManager->load($container->conf->get('general.enabled_plugins')); | ||
120 | |||
121 | return $pluginManager; | ||
122 | }; | ||
123 | |||
124 | $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { | 119 | $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { |
125 | return new FormatterFactory( | 120 | return new FormatterFactory( |
126 | $container->conf, | 121 | $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; | |||
4 | 4 | ||
5 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
6 | use Shaarli\Plugin\Exception\PluginFileNotFoundException; | 6 | use Shaarli\Plugin\Exception\PluginFileNotFoundException; |
7 | use Shaarli\Plugin\Exception\PluginInvalidRouteException; | ||
7 | 8 | ||
8 | /** | 9 | /** |
9 | * Class PluginManager | 10 | * Class PluginManager |
@@ -26,6 +27,14 @@ class PluginManager | |||
26 | */ | 27 | */ |
27 | private $loadedPlugins = []; | 28 | private $loadedPlugins = []; |
28 | 29 | ||
30 | /** @var array List of registered routes. Contains keys: | ||
31 | * - `method`: HTTP method, GET/POST/PUT/PATCH/DELETE | ||
32 | * - `route` (path): without prefix, e.g. `/up/{variable}` | ||
33 | * It will be later prefixed by `/plugin/<plugin name>/`. | ||
34 | * - `callable` string, function name or FQN class's method, e.g. `demo_plugin_custom_controller`. | ||
35 | */ | ||
36 | protected $registeredRoutes = []; | ||
37 | |||
29 | /** | 38 | /** |
30 | * @var ConfigManager Configuration Manager instance. | 39 | * @var ConfigManager Configuration Manager instance. |
31 | */ | 40 | */ |
@@ -86,6 +95,9 @@ class PluginManager | |||
86 | $this->loadPlugin($dirs[$index], $plugin); | 95 | $this->loadPlugin($dirs[$index], $plugin); |
87 | } catch (PluginFileNotFoundException $e) { | 96 | } catch (PluginFileNotFoundException $e) { |
88 | error_log($e->getMessage()); | 97 | error_log($e->getMessage()); |
98 | } catch (\Throwable $e) { | ||
99 | $error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage(); | ||
100 | $this->errors = array_unique(array_merge($this->errors, [$error])); | ||
89 | } | 101 | } |
90 | } | 102 | } |
91 | } | 103 | } |
@@ -166,6 +178,22 @@ class PluginManager | |||
166 | } | 178 | } |
167 | } | 179 | } |
168 | 180 | ||
181 | $registerRouteFunction = $pluginName . '_register_routes'; | ||
182 | $routes = null; | ||
183 | if (function_exists($registerRouteFunction)) { | ||
184 | $routes = call_user_func($registerRouteFunction); | ||
185 | } | ||
186 | |||
187 | if ($routes !== null) { | ||
188 | foreach ($routes as $route) { | ||
189 | if (static::validateRouteRegistration($route)) { | ||
190 | $this->registeredRoutes[$pluginName][] = $route; | ||
191 | } else { | ||
192 | throw new PluginInvalidRouteException($pluginName); | ||
193 | } | ||
194 | } | ||
195 | } | ||
196 | |||
169 | $this->loadedPlugins[] = $pluginName; | 197 | $this->loadedPlugins[] = $pluginName; |
170 | } | 198 | } |
171 | 199 | ||
@@ -238,6 +266,14 @@ class PluginManager | |||
238 | } | 266 | } |
239 | 267 | ||
240 | /** | 268 | /** |
269 | * @return array List of registered custom routes by plugins. | ||
270 | */ | ||
271 | public function getRegisteredRoutes(): array | ||
272 | { | ||
273 | return $this->registeredRoutes; | ||
274 | } | ||
275 | |||
276 | /** | ||
241 | * Return the list of encountered errors. | 277 | * Return the list of encountered errors. |
242 | * | 278 | * |
243 | * @return array List of errors (empty array if none exists). | 279 | * @return array List of errors (empty array if none exists). |
@@ -246,4 +282,32 @@ class PluginManager | |||
246 | { | 282 | { |
247 | return $this->errors; | 283 | return $this->errors; |
248 | } | 284 | } |
285 | |||
286 | /** | ||
287 | * Checks whether provided input is valid to register a new route. | ||
288 | * It must contain keys `method`, `route`, `callable` (all strings). | ||
289 | * | ||
290 | * @param string[] $input | ||
291 | * | ||
292 | * @return bool | ||
293 | */ | ||
294 | protected static function validateRouteRegistration(array $input): bool | ||
295 | { | ||
296 | if ( | ||
297 | !array_key_exists('method', $input) | ||
298 | || !in_array(strtoupper($input['method']), ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) | ||
299 | ) { | ||
300 | return false; | ||
301 | } | ||
302 | |||
303 | if (!array_key_exists('route', $input) || !preg_match('#^[a-z\d/\.\-_]+$#', $input['route'])) { | ||
304 | return false; | ||
305 | } | ||
306 | |||
307 | if (!array_key_exists('callable', $input)) { | ||
308 | return false; | ||
309 | } | ||
310 | |||
311 | return true; | ||
312 | } | ||
249 | } | 313 | } |
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 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Plugin\Exception; | ||
6 | |||
7 | use Exception; | ||
8 | |||
9 | /** | ||
10 | * Class PluginFileNotFoundException | ||
11 | * | ||
12 | * Raise when plugin files can't be found. | ||
13 | */ | ||
14 | class PluginInvalidRouteException extends Exception | ||
15 | { | ||
16 | /** | ||
17 | * Construct exception with plugin name. | ||
18 | * Generate message. | ||
19 | * | ||
20 | * @param string $pluginName name of the plugin not found | ||
21 | */ | ||
22 | public function __construct() | ||
23 | { | ||
24 | $this->message = 'trying to register invalid route.'; | ||
25 | } | ||
26 | } | ||
diff --git a/doc/md/dev/Plugin-system.md b/doc/md/dev/Plugin-system.md index f09fadc2..79654011 100644 --- a/doc/md/dev/Plugin-system.md +++ b/doc/md/dev/Plugin-system.md | |||
@@ -139,6 +139,31 @@ Each file contain two keys: | |||
139 | 139 | ||
140 | > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. | 140 | > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. |
141 | 141 | ||
142 | ### Register plugin's routes | ||
143 | |||
144 | Shaarli lets you register custom Slim routes for your plugin. | ||
145 | |||
146 | To register a route, the plugin must include a function called `function <plugin_name>_register_routes(): array`. | ||
147 | |||
148 | This method must return an array of routes, each entry must contain the following keys: | ||
149 | |||
150 | - `method`: HTTP method, `GET/POST/PUT/PATCH/DELETE` | ||
151 | - `route` (path): without prefix, e.g. `/up/{variable}` | ||
152 | It will be later prefixed by `/plugin/<plugin name>/`. | ||
153 | - `callable` string, function name or FQN class's method to execute, e.g. `demo_plugin_custom_controller`. | ||
154 | |||
155 | Callable functions or methods must have `Slim\Http\Request` and `Slim\Http\Response` parameters | ||
156 | and return a `Slim\Http\Response`. We recommend creating a dedicated class and extend either | ||
157 | `ShaarliVisitorController` or `ShaarliAdminController` to use helper functions they provide. | ||
158 | |||
159 | A dedicated plugin template is available for rendering content: `pluginscontent.html` using `content` placeholder. | ||
160 | |||
161 | > **Warning**: plugins are not able to use RainTPL template engine for their content due to technical restrictions. | ||
162 | > RainTPL does not allow to register multiple template folders, so all HTML rendering must be done within plugin | ||
163 | > custom controller. | ||
164 | |||
165 | Check out the `demo_plugin` for a live example: `GET <shaarli_url>/plugin/demo_plugin/custom`. | ||
166 | |||
142 | ### Understanding relative paths | 167 | ### Understanding relative paths |
143 | 168 | ||
144 | Because Shaarli is a self-hosted tool, an instance can either be installed at the root directory, or under a subfolder. | 169 | Because Shaarli is a self-hosted tool, an instance can either be installed at the root directory, or under a subfolder. |
@@ -31,6 +31,7 @@ use Psr\Log\LogLevel; | |||
31 | use Shaarli\Config\ConfigManager; | 31 | use Shaarli\Config\ConfigManager; |
32 | use Shaarli\Container\ContainerBuilder; | 32 | use Shaarli\Container\ContainerBuilder; |
33 | use Shaarli\Languages; | 33 | use Shaarli\Languages; |
34 | use Shaarli\Plugin\PluginManager; | ||
34 | use Shaarli\Security\BanManager; | 35 | use Shaarli\Security\BanManager; |
35 | use Shaarli\Security\CookieManager; | 36 | use Shaarli\Security\CookieManager; |
36 | use Shaarli\Security\LoginManager; | 37 | use Shaarli\Security\LoginManager; |
@@ -87,7 +88,17 @@ date_default_timezone_set($conf->get('general.timezone', 'UTC')); | |||
87 | 88 | ||
88 | $loginManager->checkLoginState(client_ip_id($_SERVER)); | 89 | $loginManager->checkLoginState(client_ip_id($_SERVER)); |
89 | 90 | ||
90 | $containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager, $logger); | 91 | $pluginManager = new PluginManager($conf); |
92 | $pluginManager->load($conf->get('general.enabled_plugins', [])); | ||
93 | |||
94 | $containerBuilder = new ContainerBuilder( | ||
95 | $conf, | ||
96 | $sessionManager, | ||
97 | $cookieManager, | ||
98 | $loginManager, | ||
99 | $pluginManager, | ||
100 | $logger | ||
101 | ); | ||
91 | $container = $containerBuilder->build(); | 102 | $container = $containerBuilder->build(); |
92 | $app = new App($container); | 103 | $app = new App($container); |
93 | 104 | ||
@@ -154,6 +165,15 @@ $app->group('/admin', function () { | |||
154 | $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); | 165 | $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); |
155 | })->add('\Shaarli\Front\ShaarliAdminMiddleware'); | 166 | })->add('\Shaarli\Front\ShaarliAdminMiddleware'); |
156 | 167 | ||
168 | $app->group('/plugin', function () use ($pluginManager) { | ||
169 | foreach ($pluginManager->getRegisteredRoutes() as $pluginName => $routes) { | ||
170 | $this->group('/' . $pluginName, function () use ($routes) { | ||
171 | foreach ($routes as $route) { | ||
172 | $this->{strtolower($route['method'])}('/' . ltrim($route['route'], '/'), $route['callable']); | ||
173 | } | ||
174 | }); | ||
175 | } | ||
176 | })->add('\Shaarli\Front\ShaarliMiddleware'); | ||
157 | 177 | ||
158 | // REST API routes | 178 | // REST API routes |
159 | $app->group('/api/v1', function () { | 179 | $app->group('/api/v1', function () { |
@@ -18,5 +18,6 @@ | |||
18 | <rule ref="PSR1.Files.SideEffects.FoundWithSymbols"> | 18 | <rule ref="PSR1.Files.SideEffects.FoundWithSymbols"> |
19 | <!-- index.php bootstraps everything, so yes mixed symbols with side effects --> | 19 | <!-- index.php bootstraps everything, so yes mixed symbols with side effects --> |
20 | <exclude-pattern>index.php</exclude-pattern> | 20 | <exclude-pattern>index.php</exclude-pattern> |
21 | <exclude-pattern>plugins/*</exclude-pattern> | ||
21 | </rule> | 22 | </rule> |
22 | </ruleset> | 23 | </ruleset> |
diff --git a/plugins/demo_plugin/DemoPluginController.php b/plugins/demo_plugin/DemoPluginController.php new file mode 100644 index 00000000..b8ace9c8 --- /dev/null +++ b/plugins/demo_plugin/DemoPluginController.php | |||
@@ -0,0 +1,24 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\DemoPlugin; | ||
6 | |||
7 | use Shaarli\Front\Controller\Admin\ShaarliAdminController; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | class DemoPluginController extends ShaarliAdminController | ||
12 | { | ||
13 | public function index(Request $request, Response $response): Response | ||
14 | { | ||
15 | $this->assignView( | ||
16 | 'content', | ||
17 | '<div class="center">' . | ||
18 | 'This is a demo page. I have access to Shaarli container, so I\'m free to do whatever I want here.' . | ||
19 | '</div>' | ||
20 | ); | ||
21 | |||
22 | return $response->write($this->render('pluginscontent')); | ||
23 | } | ||
24 | } | ||
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php index 22d27b68..15cfc2c5 100644 --- a/plugins/demo_plugin/demo_plugin.php +++ b/plugins/demo_plugin/demo_plugin.php | |||
@@ -7,6 +7,8 @@ | |||
7 | * Can be used by plugin developers to make their own plugin. | 7 | * Can be used by plugin developers to make their own plugin. |
8 | */ | 8 | */ |
9 | 9 | ||
10 | require_once __DIR__ . '/DemoPluginController.php'; | ||
11 | |||
10 | /* | 12 | /* |
11 | * RENDER HEADER, INCLUDES, FOOTER | 13 | * RENDER HEADER, INCLUDES, FOOTER |
12 | * | 14 | * |
@@ -60,6 +62,17 @@ function demo_plugin_init($conf) | |||
60 | return $errors; | 62 | return $errors; |
61 | } | 63 | } |
62 | 64 | ||
65 | function demo_plugin_register_routes(): array | ||
66 | { | ||
67 | return [ | ||
68 | [ | ||
69 | 'method' => 'GET', | ||
70 | 'route' => '/custom', | ||
71 | 'callable' => 'Shaarli\DemoPlugin\DemoPluginController:index', | ||
72 | ], | ||
73 | ]; | ||
74 | } | ||
75 | |||
63 | /** | 76 | /** |
64 | * Hook render_header. | 77 | * Hook render_header. |
65 | * Executed on every page render. | 78 | * Executed on every page render. |
@@ -304,7 +317,11 @@ function hook_demo_plugin_render_editlink($data) | |||
304 | function hook_demo_plugin_render_tools($data) | 317 | function hook_demo_plugin_render_tools($data) |
305 | { | 318 | { |
306 | // field_plugin | 319 | // field_plugin |
307 | $data['tools_plugin'][] = 'tools_plugin'; | 320 | $data['tools_plugin'][] = '<div class="tools-item"> |
321 | <a href="' . $data['_BASE_PATH_'] . '/plugin/demo_plugin/custom"> | ||
322 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Demo Plugin Custom Route</span> | ||
323 | </a> | ||
324 | </div>'; | ||
308 | 325 | ||
309 | return $data; | 326 | return $data; |
310 | } | 327 | } |
diff --git a/tests/PluginManagerTest.php b/tests/PluginManagerTest.php index efef5e87..8947f679 100644 --- a/tests/PluginManagerTest.php +++ b/tests/PluginManagerTest.php | |||
@@ -120,4 +120,43 @@ class PluginManagerTest extends \Shaarli\TestCase | |||
120 | $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); | 120 | $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); |
121 | $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); | 121 | $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); |
122 | } | 122 | } |
123 | |||
124 | /** | ||
125 | * Test plugin custom routes - note that there is no check on callable functions | ||
126 | */ | ||
127 | public function testRegisteredRoutes(): void | ||
128 | { | ||
129 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | ||
130 | $this->pluginManager->load([self::$pluginName]); | ||
131 | |||
132 | $expectedParameters = [ | ||
133 | [ | ||
134 | 'method' => 'GET', | ||
135 | 'route' => '/test', | ||
136 | 'callable' => 'getFunction', | ||
137 | ], | ||
138 | [ | ||
139 | 'method' => 'POST', | ||
140 | 'route' => '/custom', | ||
141 | 'callable' => 'postFunction', | ||
142 | ], | ||
143 | ]; | ||
144 | $meta = $this->pluginManager->getRegisteredRoutes(); | ||
145 | static::assertSame($expectedParameters, $meta[self::$pluginName]); | ||
146 | } | ||
147 | |||
148 | /** | ||
149 | * Test plugin custom routes with invalid route | ||
150 | */ | ||
151 | public function testRegisteredRoutesInvalid(): void | ||
152 | { | ||
153 | $plugin = 'test_route_invalid'; | ||
154 | $this->pluginManager->load([$plugin]); | ||
155 | |||
156 | $meta = $this->pluginManager->getRegisteredRoutes(); | ||
157 | static::assertSame([], $meta); | ||
158 | |||
159 | $errors = $this->pluginManager->getErrors(); | ||
160 | static::assertSame(['test_route_invalid [plugin incompatibility]: trying to register invalid route.'], $errors); | ||
161 | } | ||
123 | } | 162 | } |
diff --git a/tests/container/ContainerBuilderTest.php b/tests/container/ContainerBuilderTest.php index 3d43c344..04d4ef01 100644 --- a/tests/container/ContainerBuilderTest.php +++ b/tests/container/ContainerBuilderTest.php | |||
@@ -43,11 +43,15 @@ class ContainerBuilderTest extends TestCase | |||
43 | /** @var CookieManager */ | 43 | /** @var CookieManager */ |
44 | protected $cookieManager; | 44 | protected $cookieManager; |
45 | 45 | ||
46 | /** @var PluginManager */ | ||
47 | protected $pluginManager; | ||
48 | |||
46 | public function setUp(): void | 49 | public function setUp(): void |
47 | { | 50 | { |
48 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 51 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
49 | $this->sessionManager = $this->createMock(SessionManager::class); | 52 | $this->sessionManager = $this->createMock(SessionManager::class); |
50 | $this->cookieManager = $this->createMock(CookieManager::class); | 53 | $this->cookieManager = $this->createMock(CookieManager::class); |
54 | $this->pluginManager = $this->createMock(PluginManager::class); | ||
51 | 55 | ||
52 | $this->loginManager = $this->createMock(LoginManager::class); | 56 | $this->loginManager = $this->createMock(LoginManager::class); |
53 | $this->loginManager->method('isLoggedIn')->willReturn(true); | 57 | $this->loginManager->method('isLoggedIn')->willReturn(true); |
@@ -57,6 +61,7 @@ class ContainerBuilderTest extends TestCase | |||
57 | $this->sessionManager, | 61 | $this->sessionManager, |
58 | $this->cookieManager, | 62 | $this->cookieManager, |
59 | $this->loginManager, | 63 | $this->loginManager, |
64 | $this->pluginManager, | ||
60 | $this->createMock(LoggerInterface::class) | 65 | $this->createMock(LoggerInterface::class) |
61 | ); | 66 | ); |
62 | } | 67 | } |
diff --git a/tests/plugins/test/test.php b/tests/plugins/test/test.php index 03be4f4e..34cd339e 100644 --- a/tests/plugins/test/test.php +++ b/tests/plugins/test/test.php | |||
@@ -27,3 +27,19 @@ function hook_test_error() | |||
27 | { | 27 | { |
28 | new Unknown(); | 28 | new Unknown(); |
29 | } | 29 | } |
30 | |||
31 | function test_register_routes(): array | ||
32 | { | ||
33 | return [ | ||
34 | [ | ||
35 | 'method' => 'GET', | ||
36 | 'route' => '/test', | ||
37 | 'callable' => 'getFunction', | ||
38 | ], | ||
39 | [ | ||
40 | 'method' => 'POST', | ||
41 | 'route' => '/custom', | ||
42 | 'callable' => 'postFunction', | ||
43 | ], | ||
44 | ]; | ||
45 | } | ||
diff --git a/tests/plugins/test_route_invalid/test_route_invalid.php b/tests/plugins/test_route_invalid/test_route_invalid.php new file mode 100644 index 00000000..0c5a5101 --- /dev/null +++ b/tests/plugins/test_route_invalid/test_route_invalid.php | |||
@@ -0,0 +1,12 @@ | |||
1 | <?php | ||
2 | |||
3 | function test_route_invalid_register_routes(): array | ||
4 | { | ||
5 | return [ | ||
6 | [ | ||
7 | 'method' => 'GET', | ||
8 | 'route' => 'not a route', | ||
9 | 'callable' => 'getFunction', | ||
10 | ], | ||
11 | ]; | ||
12 | } | ||
diff --git a/tpl/default/pluginscontent.html b/tpl/default/pluginscontent.html new file mode 100644 index 00000000..1e4f6b80 --- /dev/null +++ b/tpl/default/pluginscontent.html | |||
@@ -0,0 +1,13 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html{if="$language !== 'auto'"} lang="{$language}"{/if}> | ||
3 | <head> | ||
4 | {include="includes"} | ||
5 | </head> | ||
6 | <body> | ||
7 | {include="page.header"} | ||
8 | |||
9 | {$content} | ||
10 | |||
11 | {include="page.footer"} | ||
12 | </body> | ||
13 | </html> | ||