X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=application%2Fplugin%2FPluginManager.php;h=939db1eaafa84acdb68915c4fdda659d1f416665;hb=bcba6bd353161fab456b423e93571ab027d5423c;hp=7881e3bea1675ad02b5d255fca316792cdd81f00;hpb=e2dff28b44fafcf11a1db7985c50cd40e6945821;p=github%2Fshaarli%2FShaarli.git diff --git a/application/plugin/PluginManager.php b/application/plugin/PluginManager.php index 7881e3be..939db1ea 100644 --- a/application/plugin/PluginManager.php +++ b/application/plugin/PluginManager.php @@ -1,8 +1,11 @@ /`. + * - `callable` string, function name or FQN class's method, e.g. `demo_plugin_custom_controller`. + */ + protected $registeredRoutes = []; /** * @var ConfigManager Configuration Manager instance. @@ -35,6 +46,9 @@ class PluginManager */ protected $errors; + /** @var callable[]|null Preloaded list of hook function for filterSearchEntry() */ + protected $filterSearchEntryHooks = null; + /** * Plugins subdirectory. * @@ -57,7 +71,7 @@ class PluginManager public function __construct(&$conf) { $this->conf = $conf; - $this->errors = array(); + $this->errors = []; } /** @@ -85,6 +99,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])); } } } @@ -98,22 +115,20 @@ class PluginManager * * @return void */ - public function executeHooks($hook, &$data, $params = array()) + public function executeHooks($hook, &$data, $params = []) { - if (!empty($params['target'])) { - $data['_PAGE_'] = $params['target']; - } - - if (isset($params['loggedin'])) { - $data['_LOGGEDIN_'] = $params['loggedin']; - } - - if (isset($params['basePath'])) { - $data['_BASE_PATH_'] = $params['basePath']; - } + $metadataParameters = [ + 'target' => '_PAGE_', + 'loggedin' => '_LOGGEDIN_', + 'basePath' => '_BASE_PATH_', + 'rootPath' => '_ROOT_PATH_', + 'bookmarkService' => '_BOOKMARK_SERVICE_', + ]; - if (isset($params['bookmarkService'])) { - $data['_BOOKMARK_SERVICE_'] = $params['bookmarkService']; + foreach ($metadataParameters as $parameter => $metaKey) { + if (array_key_exists($parameter, $params)) { + $data[$metaKey] = $params[$parameter]; + } } foreach ($this->loadedPlugins as $plugin) { @@ -128,6 +143,10 @@ class PluginManager } } } + + foreach ($metadataParameters as $metaKey) { + unset($data[$metaKey]); + } } /** @@ -163,6 +182,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; } @@ -194,7 +229,7 @@ class PluginManager */ public function getPluginsMeta() { - $metaData = array(); + $metaData = []; $dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR | GLOB_MARK); // Browse all plugin directories. @@ -215,9 +250,9 @@ class PluginManager if (isset($metaData[$plugin]['parameters'])) { $params = explode(';', $metaData[$plugin]['parameters']); } else { - $params = array(); + $params = []; } - $metaData[$plugin]['parameters'] = array(); + $metaData[$plugin]['parameters'] = []; foreach ($params as $param) { if (empty($param)) { continue; @@ -234,6 +269,22 @@ class PluginManager return $metaData; } + /** + * @return array List of registered custom routes by plugins. + */ + public function getRegisteredRoutes(): array + { + return $this->registeredRoutes; + } + + /** + * @return array List of registered filter_search_entry hooks + */ + public function getFilterSearchEntryHooks(): ?array + { + return $this->filterSearchEntryHooks; + } + /** * Return the list of encountered errors. * @@ -243,4 +294,76 @@ class PluginManager { return $this->errors; } + + /** + * Apply additional filter on every search result of BookmarkFilter calling plugins hooks. + * + * @param Bookmark $bookmark To check. + * @param array $context Additional info about search context, depends on the search source. + * + * @return bool True if the result must be kept in search results, false otherwise. + */ + public function filterSearchEntry(Bookmark $bookmark, array $context): bool + { + if ($this->filterSearchEntryHooks === null) { + $this->loadFilterSearchEntryHooks(); + } + + if ($this->filterSearchEntryHooks === []) { + return true; + } + + foreach ($this->filterSearchEntryHooks as $filterSearchEntryHook) { + if ($filterSearchEntryHook($bookmark, $context) === false) { + return false; + } + } + + return true; + } + + /** + * filterSearchEntry() method will be called for every search result, + * so for performances we preload existing functions to invoke them directly. + */ + protected function loadFilterSearchEntryHooks(): void + { + $this->filterSearchEntryHooks = []; + + foreach ($this->loadedPlugins as $plugin) { + $hookFunction = $this->buildHookName('filter_search_entry', $plugin); + + if (function_exists($hookFunction)) { + $this->filterSearchEntryHooks[] = $hookFunction; + } + } + } + + /** + * 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; + } }