diff options
Diffstat (limited to 'ui/include/classes/core/CModuleManager.php')
-rw-r--r-- | ui/include/classes/core/CModuleManager.php | 396 |
1 files changed, 246 insertions, 150 deletions
diff --git a/ui/include/classes/core/CModuleManager.php b/ui/include/classes/core/CModuleManager.php index d54a96b593c..cf3f1229dbb 100644 --- a/ui/include/classes/core/CModuleManager.php +++ b/ui/include/classes/core/CModuleManager.php @@ -19,8 +19,12 @@ **/ -use Core\CModule, - CController as CAction; +use CController as CAction; + +use Zabbix\Core\{ + CModule, + CWidget +}; /** * Module manager class for testing and loading user modules. @@ -28,64 +32,65 @@ use Core\CModule, final class CModuleManager { /** + * Lowest supported manifest version. + */ + private const MIN_MANIFEST_VERSION = 2; + + /** * Highest supported manifest version. */ - const MAX_MANIFEST_VERSION = 1; + private const MAX_MANIFEST_VERSION = 2; /** - * Home path of modules. - * - * @var string + * Root path of modules. + */ + private string $root_path; + + /** + * Current action name. */ - private $modules_dir; + private string $action_name; /** * Manifest data of added modules. - * - * @var array */ - private $manifests = []; + private array $manifests = []; /** - * List of instantiated, initialized modules. - * - * @var array + * DB moduleids of added modules. */ - private $modules = []; + private array $moduleids = []; /** - * List of errors caused by module initialization. - * - * @var array + * List of instantiated, initialized modules. */ - private $errors = []; + private array $modules = []; /** - * @param string $modules_dir Home path of modules. + * List of errors caused by module initialization. */ - public function __construct(string $modules_dir) { - $this->modules_dir = $modules_dir; - } + private array $errors = []; /** - * Get home path of modules. - * - * @return string + * @param string $root_path Root path of modules. */ - public function getModulesDir(): string { - return $this->modules_dir; + public function __construct(string $root_path) { + $this->root_path = $root_path; } /** * Add module and prepare it's manifest data. * * @param string $relative_path Relative path to the module. - * @param string $id Stored module ID to optionally check the manifest module ID against. + * @param string|null $moduleid DB module ID. + * @param string|null $id Stored module ID to optionally check the manifest module ID against. * @param array|null $config Override configuration to use instead of one stored in the manifest file. * * @return array|null Either manifest data or null if manifest file had errors or IDs didn't match. */ - public function addModule(string $relative_path, string $id = null, array $config = null): ?array { + public function addModule(string $relative_path, string $moduleid = null, string $id = null, + array $config = null): ?array { + $manifest = $this->loadManifest($relative_path); // Ignore module without a valid manifest. @@ -104,88 +109,12 @@ final class CModuleManager { } $this->manifests[$relative_path] = $manifest; + $this->moduleids[$relative_path] = $moduleid; return $manifest; } - /** - * Get namespaces of all added modules. - * - * @return array - */ - public function getNamespaces(): array { - $namespaces = []; - - foreach ($this->manifests as $relative_path => $manifest) { - $module_path = $this->modules_dir.'/'.$relative_path; - $namespaces['Modules\\'.$manifest['namespace']] = [$module_path]; - } - - return $namespaces; - } - - /** - * Check added modules for conflicts. - * - * @return array Lists of conflicts and conflicting modules. - */ - public function checkConflicts(): array { - $ids = []; - $namespaces = []; - $actions = []; - - foreach ($this->manifests as $relative_path => $manifest) { - $ids[$manifest['id']][] = $relative_path; - $namespaces[$manifest['namespace']][] = $relative_path; - foreach (array_keys($manifest['actions']) as $action_name) { - $actions[$action_name][] = $relative_path; - } - } - - foreach (['ids', 'namespaces', 'actions'] as $var) { - $$var = array_filter($$var, function($list) { - return count($list) > 1; - }); - } - - $conflicts = []; - $conflicting_manifests = []; - - foreach ($ids as $id => $relative_paths) { - $conflicts[] = _s('Identical ID (%1$s) is used by modules located at %2$s.', $id, - implode(', ', $relative_paths) - ); - $conflicting_manifests = array_merge($conflicting_manifests, $relative_paths); - } - - foreach ($namespaces as $namespace => $relative_paths) { - $conflicts[] = _s('Identical namespace (%1$s) is used by modules located at %2$s.', $namespace, - implode(', ', $relative_paths) - ); - $conflicting_manifests = array_merge($conflicting_manifests, $relative_paths); - } - - $relative_paths = array_unique(array_reduce($actions, function($carry, $item) { - return array_merge($carry, $item); - }, [])); - - if ($relative_paths) { - $conflicts[] = _s('Identical actions are used by modules located at %1$s.', implode(', ', $relative_paths)); - $conflicting_manifests = array_merge($conflicting_manifests, $relative_paths); - } - - return [ - 'conflicts' => $conflicts, - 'conflicting_manifests' => array_unique($conflicting_manifests) - ]; - } - - /** - * Check, instantiate and initialize all added modules. - * - * @return array List of initialized modules. - */ - public function initModules(): array { + public function initModules(): void { [ 'conflicts' => $this->errors, 'conflicting_manifests' => $conflicting_manifests @@ -194,63 +123,66 @@ final class CModuleManager { $non_conflicting_manifests = array_diff_key($this->manifests, array_flip($conflicting_manifests)); foreach ($non_conflicting_manifests as $relative_path => $manifest) { - $path = $this->modules_dir.'/'.$relative_path; + $base_classname = $manifest['type'] === CModule::TYPE_WIDGET ? CWidget::class : CModule::class; + $classname = $manifest['type'] === CModule::TYPE_WIDGET ? 'Widget' : 'Module'; - if (is_file($path.'/Module.php')) { - $module_class = implode('\\', ['Modules', $manifest['namespace'], 'Module']); + $module_class = $base_classname; + + try { + if (is_file($this->root_path.'/'.$relative_path.'/'.$classname.'.php')) { + $module_class = implode('\\', [$manifest['namespace'], $classname]); - if (!class_exists($module_class, true)) { - $this->errors[] = _s('Wrong Module.php class name for module located at %1$s.', $relative_path); + if (!class_exists($module_class)) { + $this->errors[] = _s('Wrong %1$s.php class name for module located at %2$s.', $classname, + $relative_path + ); - continue; + return; + } } - } - else { - $module_class = CModule::class; - } - try { /** @var CModule $instance */ - $instance = new $module_class($path, $manifest); + $instance = new $module_class($manifest, $this->moduleids[$relative_path], $relative_path); - if ($instance instanceof CModule) { + if ($instance instanceof $base_classname) { $instance->init(); $this->modules[$instance->getId()] = $instance; } else { - $this->errors[] = _s('Module.php class must extend %1$s for module located at %2$s.', - CModule::class, $relative_path + $this->errors[] = _s('%1$s.php class must extend %2$s for module located at %3$s.', + $classname, $base_classname, $relative_path ); } } - catch (Exception $e) { + catch (Throwable $e) { $this->errors[] = _s('%1$s - thrown by module located at %2$s.', $e->getMessage(), $relative_path); } } - - return $this->modules; } /** - * Get add initialized modules. - * - * @return array + * Get initialized modules. */ public function getModules(): array { return $this->modules; } + public function setActionName(string $action_name): self { + $this->action_name = $action_name; + + return $this; + } + /** - * Get loaded module instance associated with given action name. - * - * @param string $action_name + * Get loaded module instance associated with action. * * @return CModule|null */ - public function getModuleByActionName(string $action_name): ?CModule { + public function getActionModule(): ?CModule { + /** @var CModule $module */ foreach ($this->modules as $module) { - if (array_key_exists($action_name, $module->getActions())) { + if (array_key_exists($this->action_name, $module->getActions())) { return $module; } } @@ -259,17 +191,69 @@ final class CModuleManager { } /** + * Get initialized widget modules. + */ + public function getWidgets(bool $for_template_dashboard_only = false): array { + $widgets = []; + + /** @var CWidget $widget */ + foreach ($this->modules as $widget) { + if (!($widget instanceof CWidget) || ($for_template_dashboard_only && !$widget->hasTemplateSupport())) { + continue; + } + $widgets[$widget->getId()] = $widget; + } + + return $widgets; + } + + public function getWidgetsDefaults(bool $for_template_dashboard_only = false): array { + $widget_defaults = []; + + /** @var CWidget $widget */ + foreach (APP::ModuleManager()->getWidgets($for_template_dashboard_only) as $widget) { + $widget_defaults[$widget->getId()] = $widget->getDefaults(); + } + + return $widget_defaults; + } + + public function getModule($module_id): ?CModule { + if (!array_key_exists($module_id, $this->modules)) { + return null; + } + + return $this->modules[$module_id]; + } + + public function getManifests(): array { + return $this->manifests; + } + + /** + * Get namespaces of all added modules. + */ + public function getNamespaces(): array { + $namespaces = []; + + foreach ($this->manifests as $relative_path => $manifest) { + $namespaces[$manifest['namespace']] = [$this->root_path.'/'.$relative_path]; + } + + return $namespaces; + } + + /** * Get actions of all initialized modules. - * - * @return array */ public function getActions(): array { $actions = []; + /** @var CModule $module */ foreach ($this->modules as $module) { foreach ($module->getActions() as $name => $data) { $actions[$name] = [ - 'class' => implode('\\', ['Modules', $module->getNamespace(), 'Actions', + 'class' => implode('\\', [$module->getNamespace(), 'Actions', str_replace('/', '\\', $data['class']) ]), 'layout' => array_key_exists('layout', $data) ? $data['layout'] : 'layout.htmlpage', @@ -281,6 +265,86 @@ final class CModuleManager { return $actions; } + public function getAssets(): array { + $assets = []; + + /** @var CModule $module */ + foreach ($this->modules as $module) { + if ($module->getType() === CModule::TYPE_WIDGET && !CRouter::isDashboardAction($this->action_name)) { + continue; + } + + $assets[$module->getId()] = $module->getAssets(); + } + + return $assets; + } + + /** + * Get errors encountered while module initialization. + */ + public function getErrors(): array { + return $this->errors; + } + + /** + * Check added modules for conflicts. + * + * @return array Lists of conflicts and conflicting modules. + */ + public function checkConflicts(): array { + $ids = []; + $namespaces = []; + $actions = []; + + foreach ($this->manifests as $relative_path => $manifest) { + $ids[$manifest['id']][] = $relative_path; + $namespaces[$manifest['namespace']][] = $relative_path; + foreach (array_keys($manifest['actions']) as $action_name) { + $actions[$action_name][] = $relative_path; + } + } + + foreach (['ids', 'namespaces', 'actions'] as $var) { + $$var = array_filter($$var, static function($list) { + return count($list) > 1; + }); + } + + $conflicts = []; + $conflicting_manifests = []; + + foreach ($ids as $id => $relative_paths) { + $conflicts[] = _s('Identical ID (%1$s) is used by modules located at %2$s.', $id, + implode(', ', $relative_paths) + ); + $conflicting_manifests = array_merge($conflicting_manifests, $relative_paths); + } + + foreach ($namespaces as $namespace => $relative_paths) { + $conflicts[] = _s('Identical namespace (%1$s) is used by modules located at %2$s.', $namespace, + implode(', ', $relative_paths) + ); + $conflicting_manifests = array_merge($conflicting_manifests, $relative_paths); + } + + $relative_paths = array_unique(array_reduce($actions, static function($carry, $item) { + return array_merge($carry, $item); + }, [])); + + if ($relative_paths) { + $conflicts[] = _s('Identical actions are used by modules located at %1$s.', implode(', ', $relative_paths)); + $conflicting_manifests = array_merge($conflicting_manifests, $relative_paths); + } + + $this->errors = $conflicts; + + return [ + 'conflicts' => $conflicts, + 'conflicting_manifests' => array_unique($conflicting_manifests) + ]; + } + /** * Publish an event to all loaded modules. The module of the responsible action will be served last. * @@ -288,7 +352,7 @@ final class CModuleManager { * @param string $event Event to publish. */ public function publishEvent(CAction $action, string $event): void { - $action_module = $this->getModuleByActionName($action->getAction()); + $action_module = $this->getActionModule(); foreach ($this->modules as $module) { if ($module != $action_module) { @@ -302,15 +366,6 @@ final class CModuleManager { } /** - * Get errors encountered while module initialization. - * - * @return array - */ - public function getErrors(): array { - return $this->errors; - } - - /** * Load and parse module manifest file. * * @param string $relative_path Relative path to the module. @@ -318,8 +373,13 @@ final class CModuleManager { * @return array|null Either manifest data or null if manifest file had errors. */ private function loadManifest(string $relative_path): ?array { - $module_path = $this->modules_dir.'/'.$relative_path; - $manifest_file_name = $module_path.'/manifest.json'; + $relative_path_parts = explode('/', $relative_path, 2); + + if (count($relative_path_parts) != 2) { + return null; + } + + $manifest_file_name = $this->root_path.'/'.$relative_path.'/manifest.json'; if (!is_file($manifest_file_name) || !is_readable($manifest_file_name)) { return null; @@ -343,24 +403,60 @@ final class CModuleManager { } // Check manifest version. - if (!is_numeric($manifest['manifest_version']) || $manifest['manifest_version'] > self::MAX_MANIFEST_VERSION) { + if (!is_numeric($manifest['manifest_version']) || $manifest['manifest_version'] < self::MIN_MANIFEST_VERSION + || $manifest['manifest_version'] > self::MAX_MANIFEST_VERSION) { + return null; + } + + if (trim($manifest['id']) === '' || trim($manifest['name']) === '') { return null; } // Check manifest namespace syntax. - if (!preg_match('/^[a-z_]+$/i', $manifest['namespace'])) { + if (!preg_match('/^[0-9a-z_]+$/i', $manifest['namespace'])) { + return null; + } + + $manifest['namespace'] = ucfirst($relative_path_parts[0]).'\\'.$manifest['namespace']; + + // Check module type. + if (array_key_exists('type', $manifest) + && !in_array($manifest['type'], [CModule::TYPE_MODULE, CModule::TYPE_WIDGET], true)) { return null; } // Ensure empty defaults. $manifest += [ + 'type' => CModule::TYPE_MODULE, 'author' => '', 'url' => '', 'description' => '', 'actions' => [], + 'assets' => [], 'config' => [] ]; + $manifest['assets'] += [ + 'css' => [], + 'js' => [] + ]; + + if ($manifest['type'] === CModule::TYPE_WIDGET) { + if (!array_key_exists('widget', $manifest)) { + $manifest['widget'] = []; + } + + $manifest['widget'] += [ + 'name' => '', + 'form_class' => CWidget::DEFAULT_FORM_CLASS, + 'js_class' => CWidget::DEFAULT_JS_CLASS, + 'size' => CWidget::DEFAULT_SIZE, + 'refresh_rate' => CWidget::DEFAULT_REFRESH_RATE, + 'template_support' => false, + 'use_time_selector' => false + ]; + } + return $manifest; } } |