diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php index df422d716..580015b25 100644 --- a/application/controllers/ErrorController.php +++ b/application/controllers/ErrorController.php @@ -21,7 +21,8 @@ class ErrorController extends ActionController /** * Regular expression to match exceptions resulting from missing functions/classes */ - const MISSING_DEP_ERROR = "/Uncaught Error:.*(?:undefined function (\S+)|Class '([^']+)' not found).* in ([^:]+)/"; + const MISSING_DEP_ERROR = + "/Uncaught Error:.*(?:undefined function (\S+)|Class ['\"]([^']+)['\"] not found).* in ([^:]+)/"; /** * {@inheritdoc} @@ -108,9 +109,10 @@ class ErrorController extends ActionController } } - if (preg_match('/^(?:Icinga\\\Module\\\(\w+)|(\w+)\\\)/', $match[1] ?: $match[2], $natch)) { + if (preg_match('/^(?:Icinga\\\Module\\\(\w+)|(\w+)\\\(\w+))/', $match[1] ?: $match[2], $natch)) { $this->view->requiredModule = isset($natch[1]) ? strtolower($natch[1]) : null; - $this->view->requiredLibrary = isset($natch[2]) ? $natch[2] : null; + $this->view->requiredVendor = isset($natch[2]) ? $natch[2] : null; + $this->view->requiredProject = isset($natch[3]) ? $natch[3] : null; } } diff --git a/application/views/scripts/error/error.phtml b/application/views/scripts/error/error.phtml index 51ad9143f..ecb1c2b48 100644 --- a/application/views/scripts/error/error.phtml +++ b/application/views/scripts/error/error.phtml @@ -16,42 +16,90 @@ if (isset($stackTraces)) { echo '

' . nl2br($this->escape($message)) . '

'; } } + +$libraries = \Icinga\Application\Icinga::app()->getLibraries(); +$coreReason = []; +$modReason = []; + +if (isset($requiredVendor, $requiredProject) && $requiredVendor && $requiredProject) { + // TODO: I don't like this, can we define requirements somewhere else? + $coreDeps = ['icinga-php-library' => '>= 0.6', 'icinga-php-thirdparty' => '>= 0.10']; + + foreach ($coreDeps as $libraryName => $requiredVersion) { + if (! $libraries->has($libraryName)) { + $coreReason[] = sprintf($this->translate( + 'Library "%s" is required and missing. Please install a version of it matching the required one: %s' + ), $libraryName, $requiredVersion); + } elseif (! $libraries->has($libraryName, $requiredVersion) && $libraries->get($libraryName)->isRequired($requiredVendor, $requiredProject)) { + $coreReason[] = sprintf($this->translate( + 'Library "%s" is required and installed, but its version (%s) does not satisfy the required one: %s' + ), $libraryName, $libraries->get($libraryName)->getVersion() ?: '-', $requiredVersion); + } + } + + if (! empty($coreReason)) { + array_unshift($coreReason, $this->translate('You have unmet dependencies. Please check Icinga Web 2\'s installation instructions.')); + } +} + +if (isset($module)) { + $manager = \Icinga\Application\Icinga::app()->getModuleManager(); + if ($manager->hasUnmetDependencies($module->getName())) { + if (isset($requiredModule) && $requiredModule && isset($module->getRequiredModules()[$requiredModule])) { + if (! $manager->hasInstalled($requiredModule)) { + $modReason[] = sprintf($this->translate( + 'Module "%s" is required and missing. Please install a version of it matching the required one: %s' + ), $requiredModule, $module->getRequiredModules()[$requiredModule]); + } elseif (! $manager->hasEnabled($requiredModule)) { + $modReason[] = sprintf($this->translate( + 'Module "%s" is required and installed, but not enabled. Please enable module "%1$s".' + ), $requiredModule); + } elseif (! $manager->has($requiredModule, $module->getRequiredModules()[$requiredModule])) { + $modReason[] = sprintf($this->translate( + 'Module "%s" is required and installed, but its version (%s) does not satisfy the required one: %s' + ), $requiredModule, $manager->getModule($requiredModule, false)->getVersion(), $module->getRequiredModules()[$requiredModule]); + } + } elseif (isset($requiredVendor, $requiredProject) && $requiredVendor && $requiredProject) { + foreach ($module->getRequiredLibraries() as $libraryName => $requiredVersion) { + if (! $libraries->has($libraryName)) { + $modReason[] = sprintf($this->translate( + 'Library "%s" is required and missing. Please install a version of it matching the required one: %s' + ), $libraryName, $requiredVersion); + } elseif (! $libraries->has($libraryName, $requiredVersion) && $libraries->get($libraryName)->isRequired($requiredVendor, $requiredProject)) { + $modReason[] = sprintf($this->translate( + 'Library "%s" is required and installed, but its version (%s) does not satisfy the required one: %s' + ), $libraryName, $libraries->get($libraryName)->getVersion() ?: '-', $requiredVersion); + } + } + } + + if (! empty($modReason)) { + array_unshift($modReason, sprintf($this->translate( + 'This error might have occurred because module "%s" has unmet dependencies.' + . ' Please check it\'s installation instructions and install missing dependencies.' + ), $module->getName())); + } + } +} + +// The following doesn't use ipl\Html because that's what the error possibly is about ?> - - getModuleManager(); ?> - hasUnmetDependencies($module->getName())): ?> -
-

translate( - 'This error might have occurred because module "%s" has unmet dependencies.' - . ' Please check it\'s installation instructions and install missing dependencies.' - ), $module->getName()) ?>

- getRequiredModules()[$requiredModule])): ?> - hasInstalled($requiredModule)): ?> -

translate( - 'Module "%s" is required and missing. Please install a version of it matching the required one: %s' - ), $requiredModule, $module->getRequiredModules()[$requiredModule]) ?>

- hasEnabled($requiredModule)): ?> -

translate( - 'Module "%s" is required and installed, but not enabled. Please enable module "%1$s".' - ), $requiredModule) ?>

- has($requiredModule, $module->getRequiredModules()[$requiredModule])): ?> -

translate( - 'Module "%s" is required and installed, but its version (%s) does not satisfy the required one: %s' - ), $requiredModule, $manager->getModule($requiredModule, false)->getVersion(), $module->getRequiredModules()[$requiredModule]) ?>

- - getRequiredLibraries()[$requiredLibrary])): ?> - getLibraries(); ?> - has($requiredLibrary)): ?> -

translate( - 'Library "%s" is required and missing. Please install a version of it matching the required one: %s' - ), $requiredLibrary, $module->getRequiredLibraries()[$requiredLibrary]) ?>

- has($requiredLibrary, $module->getRequiredLibraries()[$requiredLibrary])): ?> -

translate( - 'Library "%s" is required and installed, but its version (%s) does not satisfy the required one: %s' - ), $requiredLibrary, $libraries->get($requiredLibrary)->getVersion(), $module->getRequiredLibraries()[$requiredLibrary]) ?>

- - -
- + +
+ +

+ +
+ + + +
+ + +

+ + +
+ diff --git a/library/Icinga/Application/Libraries/Library.php b/library/Icinga/Application/Libraries/Library.php index b2e359b57..ece5efaf5 100644 --- a/library/Icinga/Application/Libraries/Library.php +++ b/library/Icinga/Application/Libraries/Library.php @@ -119,6 +119,22 @@ class Library return $this->version; } + /** + * Check whether the given package is required + * + * @param string $vendor The vendor of the project + * @param string $project The project's name + * + * @return bool + */ + public function isRequired($vendor, $project) + { + // Ensure the parts are lowercase and separated by dashes, not capital letters + $project = strtolower(join('-', preg_split('/\w(?=[A-Z])/', $project))); + + return isset($this->metaData()['require'][strtolower($vendor) . '/' . $project]); + } + /** * Get this library's JS assets *