Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/zabbix/zabbix.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'ui/include/classes/core/CModuleManager.php')
-rw-r--r--ui/include/classes/core/CModuleManager.php396
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;
}
}