From 85e6fce86770b374a21e672b422a3a5cf7ae3655 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Wed, 25 Feb 2015 13:33:42 +0100 Subject: [PATCH 01/12] Rename Platform::zendClassExists() to Platform::classExists() --- library/Icinga/Application/Platform.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Application/Platform.php b/library/Icinga/Application/Platform.php index a2ef0a3ef..355311b97 100644 --- a/library/Icinga/Application/Platform.php +++ b/library/Icinga/Application/Platform.php @@ -182,19 +182,24 @@ class Platform } /** - * Return whether the given Zend framework class exists + * Return whether the given class exists * * @param string $name The name of the class to check * * @return bool */ - public static function zendClassExists($name) + public static function classExists($name) { if (@class_exists($name)) { return true; } - return (@include str_replace('_', '/', $name) . '.php') !== false; + if (strpos($name, '_') !== false) { + // Assume it's a Zend-Framework class + return (@include str_replace('_', '/', $name) . '.php') !== false; + } + + return false; } /** @@ -206,7 +211,7 @@ class Platform */ public static function hasMysqlSupport() { - return static::extensionLoaded('mysql') && static::zendClassExists('Zend_Db_Adapter_Pdo_Mysql'); + return static::extensionLoaded('mysql') && static::classExists('Zend_Db_Adapter_Pdo_Mysql'); } /** @@ -218,6 +223,6 @@ class Platform */ public static function hasPostgresqlSupport() { - return static::extensionLoaded('pgsql') && static::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql'); + return static::extensionLoaded('pgsql') && static::classExists('Zend_Db_Adapter_Pdo_Pgsql'); } } From 24d0999fa45800e96b02032e05f5ad6a50dfa7cb Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Wed, 25 Feb 2015 13:38:38 +0100 Subject: [PATCH 02/12] Register requirements as objects This neutralizes the need for a unique name per requirement as requirements are now compared based on their type and condition. It also allows to implement a solution to add simple conditional requirements. refs #8508 --- .../scripts/form/setup-requirements.phtml | 18 +- modules/setup/library/Setup/Requirement.php | 279 ++++++++++++++++++ modules/setup/library/Setup/Requirements.php | 149 ++-------- 3 files changed, 316 insertions(+), 130 deletions(-) create mode 100644 modules/setup/library/Setup/Requirement.php diff --git a/modules/setup/application/views/scripts/form/setup-requirements.phtml b/modules/setup/application/views/scripts/form/setup-requirements.phtml index b3cf30101..09ca98d7f 100644 --- a/modules/setup/application/views/scripts/form/setup-requirements.phtml +++ b/modules/setup/application/views/scripts/form/setup-requirements.phtml @@ -1,7 +1,6 @@ <?php use Icinga\Web\Wizard; -use Icinga\Module\Setup\Requirements; $requirements = $form->getRequirements(); @@ -10,21 +9,22 @@ $requirements = $form->getRequirements(); <tbody> <?php foreach ($requirements as $requirement): ?> <tr> - <td><h2><?= $requirement->title; ?></h2></td> + <td><h2><?= $requirement->getTitle(); ?></h2></td> <td style="width: 50%"> - <?php if (is_array($requirement->description)): ?> + <?php $descriptions = $requirement->getDescriptions(); ?> + <?php if (count($descriptions) > 1): ?> <ul> - <?php foreach ($requirement->description as $desc): ?> + <?php foreach ($descriptions as $desc): ?> <li><?= $desc; ?></li> <?php endforeach ?> </ul> - <?php else: ?> - <?= $requirement->description; ?> + <?php elseif (! empty($descriptions)): ?> + <?= $descriptions[0]; ?> <?php endif ?> </td> - <td class="state <?= $requirement->state === Requirements::STATE_OK ? 'fulfilled' : ( - $requirement->state === Requirements::STATE_OPTIONAL ? 'not-available' : 'missing' - ); ?>"><?= $requirement->message; ?></td> + <td class="state <?= $requirement->getState() ? 'fulfilled' : ( + $requirement->isOptional() ? 'not-available' : 'missing' + ); ?>"><?= $requirement->getStateText(); ?></td> </tr> <?php endforeach ?> <tr> diff --git a/modules/setup/library/Setup/Requirement.php b/modules/setup/library/Setup/Requirement.php new file mode 100644 index 000000000..37bb5663e --- /dev/null +++ b/modules/setup/library/Setup/Requirement.php @@ -0,0 +1,279 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Setup; + +use LogicException; + +abstract class Requirement +{ + /** + * The state of this requirement + * + * @var bool + */ + protected $state; + + /** + * A descriptive text representing the current state of this requirement + * + * @var string + */ + protected $stateText; + + /** + * The descriptions of this requirement + * + * @var array + */ + protected $descriptions; + + /** + * The title of this requirement + * + * @var string + */ + protected $title; + + /** + * The condition of this requirement + * + * @var mixed + */ + protected $condition; + + /** + * Whether this requirement is optional + * + * @var bool + */ + protected $optional; + + /** + * The alias to display the condition with in a human readable way + * + * @var string + */ + protected $alias; + + /** + * Create a new requirement + * + * @param array $options + * + * @throws LogicException In case there exists no setter for an option's key + */ + public function __construct(array $options = array()) + { + $this->optional = false; + $this->descriptions = array(); + + foreach ($options as $key => $value) { + $setMethod = 'set' . ucfirst($key); + $addMethod = 'add' . ucfirst($key); + if (method_exists($this, $setMethod)) { + $this->$setMethod($value); + } elseif (method_exists($this, $addMethod)) { + $this->$addMethod($value); + } else { + throw LogicException('No setter found for option key: ' . $key); + } + } + } + + /** + * Set the state of this requirement + * + * @param bool $state + * + * @return Requirement + */ + public function setState($state) + { + $this->state = (bool) $state; + return $this; + } + + /** + * Return the state of this requirement + * + * Evaluates the requirement in case there is no state set yet. + * + * @return int + */ + public function getState() + { + if ($this->state === null) { + $this->state = $this->evaluate(); + } + + return $this->state; + } + + /** + * Set a descriptive text for this requirement's current state + * + * @param string $text + * + * @return Requirement + */ + public function setStateText($text) + { + $this->stateText = $text; + return $this; + } + + /** + * Return a descriptive text for this requirement's current state + * + * @return string + */ + public function getStateText() + { + return $this->stateText; + } + + /** + * Add a description for this requirement + * + * @param string $description + * + * @return Requirement + */ + public function addDescription($description) + { + $this->descriptions[] = $description; + return $this; + } + + /** + * Return the descriptions of this wizard + * + * @return array + */ + public function getDescriptions() + { + return $this->descriptions; + } + + /** + * Set the title for this requirement + * + * @param string $title + * + * @return Requirement + */ + public function setTitle($title) + { + $this->title = $title; + return $this; + } + + /** + * Return the title of this requirement + * + * In case there is no title set the alias is returned instead. + * + * @return string + */ + public function getTitle() + { + if ($this->title === null) { + return $this->getAlias(); + } + + return $this->title; + } + + /** + * Set the condition for this requirement + * + * @param mixed $condition + * + * @return Requirement + */ + public function setCondition($condition) + { + $this->condition = $condition; + return $this; + } + + /** + * Return the condition of this requirement + * + * @return mixed + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Set whether this requirement is optional + * + * @param bool $state + * + * @return Requirement + */ + public function setOptional($state = true) + { + $this->optional = (bool) $state; + return $this; + } + + /** + * Return whether this requirement is optional + * + * @return bool + */ + public function isOptional() + { + return $this->optional; + } + + /** + * Set the alias to display the condition with in a human readable way + * + * @param string $alias + * + * @return Requirement + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * Return the alias to display the condition with in a human readable way + * + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * Evaluate this requirement and return whether it is fulfilled + * + * @return bool + */ + abstract protected function evaluate(); + + /** + * Return whether the given requirement equals this one + * + * @param Requirement $requirement + * + * @return bool + */ + public function equals(Requirement $requirement) + { + if ($requirement instanceof static) { + return $this->getCondition() === $requirement->getCondition(); + } + + return false; + } +} diff --git a/modules/setup/library/Setup/Requirements.php b/modules/setup/library/Setup/Requirements.php index bede70aa0..e96a0f7ac 100644 --- a/modules/setup/library/Setup/Requirements.php +++ b/modules/setup/library/Setup/Requirements.php @@ -8,27 +8,9 @@ use IteratorAggregate; /** * Container to store and handle requirements - * - * TODO: Requirements should be registered as objects with a specific purpose (PhpModRequirement, PhpIniRequirement, ..) - * so that it's not necessary to define unique identifiers which may differ between different modules. */ class Requirements implements IteratorAggregate { - /** - * Identifier representing the state OK - */ - const STATE_OK = 2; - - /** - * Identifier representing the state OPTIONAL - */ - const STATE_OPTIONAL = 1; - - /** - * Identifier representing the state MANDATORY - */ - const STATE_MANDATORY = 0; - /** * The registered requirements * @@ -39,44 +21,35 @@ class Requirements implements IteratorAggregate /** * Register a requirement * - * @param object $requirement The requirement to add + * @param Requirement $requirement The requirement to add * - * @return self + * @return Requirements */ - public function add($name, $requirement) + public function add(Requirement $requirement) { - $this->requirements[$name] = array_key_exists($name, $this->requirements) - ? $this->combine($this->requirements[$name], $requirement) - : $requirement; + $merged = false; + foreach ($this as $knownRequirement) { + if ($requirement->equals($knownRequirement)) { + if ($knownRequirement->isOptional() && !$requirement->isOptional()) { + $knownRequirement->setOptional(false); + } + + foreach ($requirement->getDescriptions() as $description) { + $knownRequirement->addDescription($description); + } + + $merged = true; + break; + } + } + + if (! $merged) { + $this->requirements[] = $requirement; + } + return $this; } - /** - * Combine the two given requirements - * - * Returns the most important requirement with the description from the other one being added. - * - * @param object $oldRequirement - * @param object $newRequirement - * - * @return object - */ - protected function combine($oldRequirement, $newRequirement) - { - if ($newRequirement->state === static::STATE_MANDATORY && $oldRequirement->state === static::STATE_OPTIONAL) { - $tempRequirement = $oldRequirement; - $oldRequirement = $newRequirement; - $newRequirement = $tempRequirement; - } - - if (! is_array($oldRequirement->description)) { - $oldRequirement->description = array($oldRequirement->description); - } - - $oldRequirement->description[] = $newRequirement->description; - return $oldRequirement; - } - /** * Return all registered requirements * @@ -97,83 +70,17 @@ class Requirements implements IteratorAggregate return new ArrayIterator($this->getAll()); } - /** - * Register an optional requirement - * - * @param string $name - * @param string $title - * @param string $description - * @param bool $state - * @param string $message - * - * @return self - */ - public function addOptional($name, $title, $description, $state, $message) - { - $this->add( - $name, - (object) array( - 'title' => $title, - 'message' => $message, - 'description' => $description, - 'state' => (bool) $state ? static::STATE_OK : static::STATE_OPTIONAL - ) - ); - return $this; - } - - /** - * Register a mandatory requirement - * - * @param string $name - * @param string $title - * @param string $description - * @param bool $state - * @param string $message - * - * @return self - */ - public function addMandatory($name, $title, $description, $state, $message) - { - $this->add( - $name, - (object) array( - 'title' => $title, - 'message' => $message, - 'description' => $description, - 'state' => (bool) $state ? static::STATE_OK : static::STATE_MANDATORY - ) - ); - return $this; - } - /** * Register the given requirements * * @param Requirements $requirements The requirements to register * - * @return self + * @return Requirements */ public function merge(Requirements $requirements) { - foreach ($requirements->getAll() as $name => $requirement) { - $this->add($name, $requirement); - } - - return $this; - } - - /** - * Make all registered requirements being optional - * - * @return self - */ - public function allOptional() - { - foreach ($this->getAll() as $requirement) { - if ($requirement->state === static::STATE_MANDATORY) { - $requirement->state = static::STATE_OPTIONAL; - } + foreach ($requirements as $requirement) { + $this->add($requirement); } return $this; @@ -186,8 +93,8 @@ class Requirements implements IteratorAggregate */ public function fulfilled() { - foreach ($this->getAll() as $requirement) { - if ($requirement->state === static::STATE_MANDATORY) { + foreach ($this as $requirement) { + if (! $requirement->getState() && !$requirement->isOptional()) { return false; } } From 04630a20beb53e500b5daf4fbd43f90b7325328c Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Wed, 25 Feb 2015 13:39:59 +0100 Subject: [PATCH 03/12] Implement all known requirements as object refs #8508 --- .../library/Monitoring/MonitoringWizard.php | 16 +- .../Setup/Requirement/ClassRequirement.php | 28 ++ .../ConfigDirectoryRequirement.php | 42 +++ .../Setup/Requirement/OSRequirement.php | 27 ++ .../Requirement/PhpConfigRequirement.php | 22 ++ .../Requirement/PhpModuleRequirement.php | 42 +++ .../Requirement/PhpVersionRequirement.php | 28 ++ modules/setup/library/Setup/WebWizard.php | 245 ++++++++---------- 8 files changed, 296 insertions(+), 154 deletions(-) create mode 100644 modules/setup/library/Setup/Requirement/ClassRequirement.php create mode 100644 modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php create mode 100644 modules/setup/library/Setup/Requirement/OSRequirement.php create mode 100644 modules/setup/library/Setup/Requirement/PhpConfigRequirement.php create mode 100644 modules/setup/library/Setup/Requirement/PhpModuleRequirement.php create mode 100644 modules/setup/library/Setup/Requirement/PhpVersionRequirement.php diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php index 4e342aa75..3c7dee886 100644 --- a/modules/monitoring/library/Monitoring/MonitoringWizard.php +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -3,7 +3,6 @@ namespace Icinga\Module\Monitoring; -use Icinga\Application\Platform; use Icinga\Web\Form; use Icinga\Web\Wizard; use Icinga\Web\Request; @@ -17,6 +16,7 @@ use Icinga\Module\Monitoring\Forms\Setup\InstancePage; use Icinga\Module\Monitoring\Forms\Setup\SecurityPage; use Icinga\Module\Monitoring\Forms\Setup\IdoResourcePage; use Icinga\Module\Monitoring\Forms\Setup\LivestatusResourcePage; +use Icinga\Module\Setup\Requirement\PhpModuleRequirement; /** * Monitoring Module Setup Wizard @@ -137,19 +137,15 @@ class MonitoringWizard extends Wizard implements SetupWizard { $requirements = new Requirements(); - $requirements->addOptional( - 'existing_php_mod_sockets', - mt('monitoring', 'PHP Module: Sockets'), - mt( + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'Sockets', + 'description' => mt( 'monitoring', 'In case it\'s desired that a TCP connection is being used by Icinga Web 2 to' . ' access a Livestatus interface, the Sockets module for PHP is required.' - ), - Platform::extensionLoaded('sockets'), - Platform::extensionLoaded('sockets') ? mt('monitoring', 'The PHP Module sockets is available.') : ( - mt('monitoring', 'The PHP Module sockets is not available.') ) - ); + ))); return $requirements; } diff --git a/modules/setup/library/Setup/Requirement/ClassRequirement.php b/modules/setup/library/Setup/Requirement/ClassRequirement.php new file mode 100644 index 000000000..e0f25cf4e --- /dev/null +++ b/modules/setup/library/Setup/Requirement/ClassRequirement.php @@ -0,0 +1,28 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Setup\Requirement; + +use Icinga\Application\Platform; +use Icinga\Module\Setup\Requirement; + +class ClassRequirement extends Requirement +{ + protected function evaluate() + { + $classNameOrPath = $this->getCondition(); + if (Platform::classExists($classNameOrPath)) { + $this->setStateText(sprintf( + mt('setup', 'The %s is available.', 'setup.requirement.class'), + $this->getAlias() ?: $classNameOrPath . ' ' . mt('setup', 'class', 'setup.requirement.class') + )); + return true; + } else { + $this->setStateText(sprintf( + mt('setup', 'The %s is missing.', 'setup.requirement.class'), + $this->getAlias() ?: $classNameOrPath . ' ' . mt('setup', 'class', 'setup.requirement.class') + )); + return false; + } + } +} diff --git a/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php b/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php new file mode 100644 index 000000000..3404717db --- /dev/null +++ b/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php @@ -0,0 +1,42 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Setup\Requirement; + +use Icinga\Module\Setup\Requirement; + +class ConfigDirectoryRequirement extends Requirement +{ + public function getTitle() + { + $title = parent::getTitle(); + if ($title === null) { + return mt('setup', 'Read- and writable configuration directory'); + } + + return $title; + } + + protected function evaluate() + { + $path = $this->getCondition(); + if (file_exists($path)) { + $readable = is_readable($path); + if ($readable && is_writable($path)) { + $this->setStateText(sprintf(mt('setup', 'The directory %s is read- and writable.'), $path)); + return true; + } else { + $this->setStateText(sprintf( + $readable + ? mt('setup', 'The directory %s is not writable.') + : mt('setup', 'The directory %s is not readable.'), + $path + )); + return false; + } + } else { + $this->setStateText(sprintf(mt('setup', 'The directory %s does not exist.'), $path)); + return false; + } + } +} diff --git a/modules/setup/library/Setup/Requirement/OSRequirement.php b/modules/setup/library/Setup/Requirement/OSRequirement.php new file mode 100644 index 000000000..ff185bb3c --- /dev/null +++ b/modules/setup/library/Setup/Requirement/OSRequirement.php @@ -0,0 +1,27 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Setup\Requirement; + +use Icinga\Application\Platform; +use Icinga\Module\Setup\Requirement; + +class OSRequirement extends Requirement +{ + public function getTitle() + { + $title = parent::getTitle(); + if ($title === null) { + return sprintf(mt('setup', '%s Platform'), ucfirst($this->getCondition())); + } + + return $title; + } + + protected function evaluate() + { + $phpOS = Platform::getOperatingSystemName(); + $this->setStateText(sprintf(mt('setup', 'You are running PHP on a %s system.'), ucfirst($phpOS))); + return strtolower($phpOS) === strtolower($this->getCondition()); + } +} diff --git a/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php b/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php new file mode 100644 index 000000000..670c988e4 --- /dev/null +++ b/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php @@ -0,0 +1,22 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Setup\Requirement; + +use Icinga\Application\Platform; +use Icinga\Module\Setup\Requirement; + +class PhpConfigRequirement extends Requirement +{ + protected function evaluate() + { + list($configDirective, $value) = $this->getCondition(); + $configValue = Platform::getPhpConfig($configDirective); + $this->setStateText( + $configValue + ? sprintf(mt('setup', 'The PHP config `%s\' is set to "%s".'), $configDirective, $configValue) + : sprintf(mt('setup', 'The PHP config `%s\' is not defined.'), $configDirective) + ); + return is_bool($value) ? $configValue == $value : $configValue === $value; + } +} diff --git a/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php b/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php new file mode 100644 index 000000000..6581797ce --- /dev/null +++ b/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php @@ -0,0 +1,42 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Setup\Requirement; + +use Icinga\Application\Platform; +use Icinga\Module\Setup\Requirement; + +class PhpModuleRequirement extends Requirement +{ + public function getTitle() + { + $title = parent::getTitle(); + if ($title === $this->getAlias()) { + if ($title === null) { + $title = $this->getCondition(); + } + + return sprintf(mt('setup', 'PHP Module: %s'), $title); + } + + return $title; + } + + protected function evaluate() + { + $moduleName = $this->getCondition(); + if (Platform::extensionLoaded($moduleName)) { + $this->setStateText(sprintf( + mt('setup', 'The PHP module %s is available.'), + $this->getAlias() ?: $moduleName + )); + return true; + } else { + $this->setStateText(sprintf( + mt('setup', 'The PHP module %s is missing.'), + $this->getAlias() ?: $moduleName + )); + return false; + } + } +} diff --git a/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php b/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php new file mode 100644 index 000000000..d6ca5f189 --- /dev/null +++ b/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php @@ -0,0 +1,28 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Setup\Requirement; + +use Icinga\Application\Platform; +use Icinga\Module\Setup\Requirement; + +class PhpVersionRequirement extends Requirement +{ + public function getTitle() + { + $title = parent::getTitle(); + if ($title === null) { + return mt('setup', 'PHP Version'); + } + + return $title; + } + + protected function evaluate() + { + $phpVersion = Platform::getPhpVersion(); + $this->setStateText(sprintf(mt('setup', 'You are running PHP version %s.'), $phpVersion)); + list($operator, $requiredVersion) = $this->getCondition(); + return version_compare($phpVersion, $requiredVersion, $operator); + } +} diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index 7576adbb8..667237243 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -9,7 +9,6 @@ use Icinga\Web\Wizard; use Icinga\Web\Request; use Icinga\Application\Config; use Icinga\Application\Icinga; -use Icinga\Application\Platform; use Icinga\Module\Setup\Forms\ModulePage; use Icinga\Module\Setup\Forms\WelcomePage; use Icinga\Module\Setup\Forms\SummaryPage; @@ -29,8 +28,13 @@ use Icinga\Module\Setup\Steps\GeneralConfigStep; use Icinga\Module\Setup\Steps\ResourceStep; use Icinga\Module\Setup\Steps\AuthenticationStep; use Icinga\Module\Setup\Utils\EnableModuleStep; -use Icinga\Module\Setup\Utils\MakeDirStep; use Icinga\Module\Setup\Utils\DbTool; +use Icinga\Module\Setup\Requirement\OSRequirement; +use Icinga\Module\Setup\Requirement\ClassRequirement; +use Icinga\Module\Setup\Requirement\PhpConfigRequirement; +use Icinga\Module\Setup\Requirement\PhpModuleRequirement; +use Icinga\Module\Setup\Requirement\PhpVersionRequirement; +use Icinga\Module\Setup\Requirement\ConfigDirectoryRequirement; /** * Icinga Web 2 Setup Wizard @@ -351,194 +355,147 @@ class WebWizard extends Wizard implements SetupWizard { $requirements = new Requirements(); - $phpVersion = Platform::getPhpVersion(); - $requirements->addMandatory( - 'php_version_>=_5_3_2', - mt('setup', 'PHP Version'), - mt( + $requirements->add(new PhpVersionRequirement(array( + 'condition' => array('>=', '5.3.2'), + 'description' => mt( 'setup', 'Running Icinga Web 2 requires PHP version 5.3.2. Advanced features' . ' like the built-in web server require PHP version 5.4.' - ), - version_compare($phpVersion, '5.3.2', '>='), - sprintf(mt('setup', 'You are running PHP version %s.'), $phpVersion) - ); + ) + ))); - $defaultTimezone = Platform::getPhpConfig('date.timezone'); - $requirements->addMandatory( - 'existing_default_timezone', - mt('setup', 'Default Timezone'), - sprintf( + $requirements->add(new PhpConfigRequirement(array( + 'condition' => array('date.timezone', true), + 'title' => mt('setup', 'Default Timezone'), + 'description' => sprintf( mt('setup', 'It is required that a default timezone has been set using date.timezone in %s.'), php_ini_loaded_file() ?: 'php.ini' ), - $defaultTimezone, - $defaultTimezone ? sprintf(mt('setup', 'Your default timezone is: %s'), $defaultTimezone) : ( - mt('setup', 'You did not define a default timezone.') - ) - ); + ))); - $requirements->addOptional( - 'platform=linux', - mt('setup', 'Linux Platform'), - mt( + $requirements->add(new OSRequirement(array( + 'optional' => true, + 'condition' => 'linux', + 'description' => mt( 'setup', 'Icinga Web 2 is developed for and tested on Linux. While we cannot' . ' guarantee they will, other platforms may also perform as well.' - ), - Platform::isLinux(), - sprintf(mt('setup', 'You are running PHP on a %s system.'), Platform::getOperatingSystemName()) - ); - - $requirements->addMandatory( - 'existing_php_mod_openssl', - mt('setup', 'PHP Module: OpenSSL'), - mt('setup', 'The PHP module for OpenSSL is required to generate cryptographically safe password salts.'), - Platform::extensionLoaded('openssl'), - Platform::extensionLoaded('openssl') ? mt('setup', 'The PHP module for OpenSSL is available.') : ( - mt('setup', 'The PHP module for OpenSSL is missing.') ) - ); + ))); - $requirements->addOptional( - 'existing_php_mod_json', - mt('setup', 'PHP Module: JSON'), - mt('setup', 'The JSON module for PHP is required for various export functionalities as well as APIs.'), - Platform::extensionLoaded('json'), - Platform::extensionLoaded('json') ? mt('setup', 'The PHP module JSON is available.') : ( - mt('setup', 'The PHP module JSON is missing.') + $requirements->add(new PhpModuleRequirement(array( + 'condition' => 'OpenSSL', + 'description' => mt( + 'setup', + 'The PHP module for OpenSSL is required to generate cryptographically safe password salts.' ) - ); + ))); - $requirements->addOptional( - 'existing_php_mod_ldap', - mt('setup', 'PHP Module: LDAP'), - mt('setup', 'If you\'d like to authenticate users using LDAP the corresponding PHP module is required'), - Platform::extensionLoaded('ldap'), - Platform::extensionLoaded('ldap') ? mt('setup', 'The PHP module LDAP is available') : ( - mt('setup', 'The PHP module LDAP is missing') + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'JSON', + 'description' => mt( + 'setup', + 'The JSON module for PHP is required for various export functionalities as well as APIs.' ) - ); + ))); - $requirements->addOptional( - 'existing_php_mod_intl', - mt('setup', 'PHP Module: INTL'), - mt( + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'LDAP', + 'description' => mt( + 'setup', + 'If you\'d like to authenticate users using LDAP the corresponding PHP module is required.' + ) + ))); + + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'INTL', + 'description' => mt( 'setup', 'If you want your users to benefit from language, timezone and date/time' . ' format negotiation, the INTL module for PHP is required.' - ), - Platform::extensionLoaded('intl'), - Platform::extensionLoaded('intl') ? mt('setup', 'The PHP module INTL is available') : ( - mt('setup', 'The PHP module INTL is missing') ) - ); + ))); // TODO(6172): Remove this requirement once we do not ship dompdf with Icinga Web 2 anymore - $requirements->addOptional( - 'existing_php_mod_dom', - mt('setup', 'PHP Module: DOM'), - mt('setup', 'To be able to export views and reports to PDF, the DOM module for PHP is required.'), - Platform::extensionLoaded('dom'), - Platform::extensionLoaded('dom') ? mt('setup', 'The PHP module DOM is available') : ( - mt('setup', 'The PHP module DOM is missing') - ) - ); - - $requirements->addOptional( - 'existing_php_mod_gd', - mt('setup', 'PHP Module: GD'), - mt( + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'DOM', + 'description' => mt( 'setup', - 'In case you want views being exported to PDF,' - . ' you\'ll need the GD extension for PHP.' - ), - Platform::extensionLoaded('gd'), - Platform::extensionLoaded('gd') ? mt('setup', 'The PHP module GD is available') : ( - mt('setup', 'The PHP module GD is missing') + 'To be able to export views and reports to PDF, the DOM module for PHP is required.' ) - ); + ))); - $requirements->addOptional( - 'existing_php_mod_imagick', - mt('setup', 'PHP Module: Imagick'), - mt( + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'GD', + 'description' => mt( 'setup', - 'In case you want graphs being exported to PDF as well' - . ', you\'ll need the ImageMagick extension for PHP.' - ), - Platform::extensionLoaded('imagick'), - Platform::extensionLoaded('imagick') ? mt('setup', 'The PHP module Imagick is available') : ( - mt('setup', 'The PHP module Imagick is missing') + 'In case you want views being exported to PDF, you\'ll need the GD extension for PHP.' ) - ); + ))); - $requirements->addOptional( - 'existing_php_mod_pdo_mysql', - mt('setup', 'PHP Module: PDO-MySQL'), - mt( + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'Imagick', + 'description' => mt( + 'setup', + 'In case you want graphs being exported to PDF as well, you\'ll need the ImageMagick extension for PHP.' + ) + ))); + + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'mysql', + 'alias' => 'PDO-MySQL', + 'description' => mt( 'setup', 'Is Icinga Web 2 supposed to access a MySQL database the PDO-MySQL module for PHP is required.' - ), - Platform::extensionLoaded('mysql'), - Platform::extensionLoaded('mysql') ? mt('setup', 'The PHP module PDO-MySQL is available.') : ( - mt('setup', 'The PHP module PDO-MySQL is missing.') ) - ); + ))); - $requirements->addOptional( - 'existing_php_mod_pdo_pgsql', - mt('setup', 'PHP Module: PDO-PostgreSQL'), - mt( + $requirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'pgsql', + 'alias' => 'PDO-PostgreSQL', + 'description' => mt( 'setup', 'Is Icinga Web 2 supposed to access a PostgreSQL database' . ' the PDO-PostgreSQL module for PHP is required.' - ), - Platform::extensionLoaded('pgsql'), - Platform::extensionLoaded('pgsql') ? mt('setup', 'The PHP module PDO-PostgreSQL is available.') : ( - mt('setup', 'The PHP module PDO-PostgreSQL is missing.') ) - ); + ))); - $mysqlAdapterFound = Platform::zendClassExists('Zend_Db_Adapter_Pdo_Mysql'); - $requirements->addOptional( - 'existing_class_Zend_Db_Adapter_Pdo_Mysql', - mt('setup', 'Zend Database Adapter For MySQL'), - mt('setup', 'The Zend database adapter for MySQL is required to access a MySQL database.'), - $mysqlAdapterFound, - $mysqlAdapterFound ? mt('setup', 'The Zend database adapter for MySQL is available.') : ( - mt('setup', 'The Zend database adapter for MySQL is missing.') + $requirements->add(new ClassRequirement(array( + 'optional' => true, + 'condition' => 'Zend_Db_Adapter_Pdo_Mysql', + 'alias' => mt('setup', 'Zend database adapter for MySQL'), + 'description' => mt( + 'setup', + 'The Zend database adapter for MySQL is required to access a MySQL database.' ) - ); + ))); - $pgsqlAdapterFound = Platform::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql'); - $requirements->addOptional( - 'existing_class_Zend_Db_Adapter_Pdo_Pgsql', - mt('setup', 'Zend Database Adapter For PostgreSQL'), - mt('setup', 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.'), - $pgsqlAdapterFound, - $pgsqlAdapterFound ? mt('setup', 'The Zend database adapter for PostgreSQL is available.') : ( - mt('setup', 'The Zend database adapter for PostgreSQL is missing.') + $requirements->add(new ClassRequirement(array( + 'optional' => true, + 'condition' => 'Zend_Db_Adapter_Pdo_Pgsql', + 'alias' => mt('setup', 'Zend database adapter for PostgreSQL'), + 'description' => mt( + 'setup', + 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.' ) - ); + ))); - $configDir = Icinga::app()->getConfigDir(); - $requirements->addMandatory( - 'writable_directory_' . $configDir, - mt('setup', 'Writable Config Directory'), - mt( + $requirements->add(new ConfigDirectoryRequirement(array( + 'condition' => Icinga::app()->getConfigDir(), + 'description' => mt( 'setup', 'The Icinga Web 2 configuration directory defaults to "/etc/icingaweb2", if' . ' not explicitly set in the environment variable "ICINGAWEB_CONFIGDIR".' - ), - is_writable($configDir), - sprintf( - is_writable($configDir) ? mt('setup', 'The current configuration directory is writable: %s') : ( - mt('setup', 'The current configuration directory is not writable: %s') - ), - $configDir ) - ); + ))); foreach ($this->getWizards() as $wizard) { $requirements->merge($wizard->getRequirements()); From 8ed4a943f72803de1a0f4cf7ce91d517a496c0ea Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Thu, 26 Feb 2015 10:49:03 +0100 Subject: [PATCH 04/12] Add support for nested requirement sets This allows now to link requirements by an OR condition as well and to nest such grouped requirements in other sets of type AND, and vice versa. refs #8508 --- modules/setup/library/Setup/Requirements.php | 135 +++++++++++++++++-- 1 file changed, 126 insertions(+), 9 deletions(-) diff --git a/modules/setup/library/Setup/Requirements.php b/modules/setup/library/Setup/Requirements.php index e96a0f7ac..aba96c4bc 100644 --- a/modules/setup/library/Setup/Requirements.php +++ b/modules/setup/library/Setup/Requirements.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Setup; use ArrayIterator; +use LogicException; use IteratorAggregate; /** @@ -11,12 +12,77 @@ use IteratorAggregate; */ class Requirements implements IteratorAggregate { + /** + * Mode AND (all requirements must met) + */ + const MODE_AND = 0; + + /** + * Mode OR (at least one requirement must met) + */ + const MODE_OR = 1; + + /** + * The mode by with the requirements are evaluated + * + * @var string + */ + protected $mode; + /** * The registered requirements * * @var array */ - protected $requirements = array(); + protected $requirements; + + /** + * Whether there is any mandatory requirement part of this set + * + * @var bool + */ + protected $containsMandatoryRequirements; + + /** + * Create a new set of requirements + * + * @param int $mode The mode by with to evaluate the requirements + */ + public function __construct($mode = null) + { + $this->requirements = array(); + $this->containsMandatoryRequirements = false; + $this->setMode($mode ?: static::MODE_AND); + } + + /** + * Set the mode by with to evaluate the requirements + * + * @param int $mode + * + * @return Requirements + * + * @throws LogicException In case the given mode is invalid + */ + public function setMode($mode) + { + if ($mode !== static::MODE_AND && $mode !== static::MODE_OR) { + throw new LogicException(sprintf('Invalid mode %u given.'), $mode); + } + + $this->mode = $mode; + return $this; + } + + /** + * Return the mode by with the requirements are evaluated + * + * @return int + */ + public function getMode() + { + return $this->mode; + } /** * Register a requirement @@ -29,8 +95,8 @@ class Requirements implements IteratorAggregate { $merged = false; foreach ($this as $knownRequirement) { - if ($requirement->equals($knownRequirement)) { - if ($knownRequirement->isOptional() && !$requirement->isOptional()) { + if ($knownRequirement instanceof Requirement && $requirement->equals($knownRequirement)) { + if ($this->getMode() === static::MODE_AND && !$requirement->isOptional()) { $knownRequirement->setOptional(false); } @@ -44,6 +110,12 @@ class Requirements implements IteratorAggregate } if (! $merged) { + if ($this->getMode() === static::MODE_OR) { + $requirement->setOptional(); + } elseif (! $requirement->isOptional()) { + $this->containsMandatoryRequirements = true; + } + $this->requirements[] = $requirement; } @@ -60,6 +132,16 @@ class Requirements implements IteratorAggregate return $this->requirements; } + /** + * Return whether there is any mandatory requirement part of this set + * + * @return bool + */ + public function hasAnyMandatoryRequirement() + { + return $this->containsMandatoryRequirements || $this->getMode() === static::MODE_OR; + } + /** * Return an iterator of all registered requirements * @@ -79,26 +161,61 @@ class Requirements implements IteratorAggregate */ public function merge(Requirements $requirements) { - foreach ($requirements as $requirement) { - $this->add($requirement); + if ($this->getMode() === static::MODE_OR && $requirements->getMode() === static::MODE_OR) { + foreach ($requirements as $requirement) { + if ($requirement instanceof static) { + $this->merge($requirement); + } else { + $this->add($requirement); + } + } + } else { + if ($requirements->getMode() === static::MODE_OR) { + $this->containsMandatoryRequirements = true; + } + + $this->requirements[] = $requirements; } return $this; } /** - * Return whether all mandatory requirements are fulfilled + * Return whether all requirements can successfully be evaluated based on the current mode * * @return bool */ public function fulfilled() { + $state = false; foreach ($this as $requirement) { - if (! $requirement->getState() && !$requirement->isOptional()) { - return false; + if ($requirement instanceof static) { + if ($requirement->fulfilled()) { + if ($this->getMode() === static::MODE_OR) { + return true; + } + + $state = true; + } elseif ($this->getMode() === static::MODE_AND && $requirement->hasAnyMandatoryRequirement()) { + return false; + } + } else { + if ($requirement->getState()) { + if ($this->getMode() === static::MODE_OR) { + return true; + } + + $state = true; + } elseif ($this->getMode() === static::MODE_AND) { + if (! $requirement->isOptional()) { + return false; + } + + $state = true; // There may only be optional requirements... + } } } - return true; + return $state; } } From 87fe9bd2ff850e15a02b44405e82c9bb8f27ae17 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Thu, 26 Feb 2015 10:50:05 +0100 Subject: [PATCH 05/12] Adjust the web wizard so that all database dependencies are grouped refs #8508 --- modules/setup/library/Setup/WebWizard.php | 33 ++++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index 667237243..6158d9bd0 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -447,28 +447,17 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $requirements->add(new PhpModuleRequirement(array( + $mysqlRequirements = new Requirements(); + $mysqlRequirements->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'mysql', 'alias' => 'PDO-MySQL', 'description' => mt( 'setup', - 'Is Icinga Web 2 supposed to access a MySQL database the PDO-MySQL module for PHP is required.' + 'To store users or preferences in a MySQL database the PDO-MySQL module for PHP is required.' ) ))); - - $requirements->add(new PhpModuleRequirement(array( - 'optional' => true, - 'condition' => 'pgsql', - 'alias' => 'PDO-PostgreSQL', - 'description' => mt( - 'setup', - 'Is Icinga Web 2 supposed to access a PostgreSQL database' - . ' the PDO-PostgreSQL module for PHP is required.' - ) - ))); - - $requirements->add(new ClassRequirement(array( + $mysqlRequirements->add(new ClassRequirement(array( 'optional' => true, 'condition' => 'Zend_Db_Adapter_Pdo_Mysql', 'alias' => mt('setup', 'Zend database adapter for MySQL'), @@ -477,8 +466,19 @@ class WebWizard extends Wizard implements SetupWizard 'The Zend database adapter for MySQL is required to access a MySQL database.' ) ))); + $requirements->merge($mysqlRequirements); - $requirements->add(new ClassRequirement(array( + $pgsqlRequirements = new Requirements(); + $pgsqlRequirements->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'pgsql', + 'alias' => 'PDO-PostgreSQL', + 'description' => mt( + 'setup', + 'To store users or preferences in a PostgreSQL database the PDO-PostgreSQL module for PHP is required.' + ) + ))); + $pgsqlRequirements->add(new ClassRequirement(array( 'optional' => true, 'condition' => 'Zend_Db_Adapter_Pdo_Pgsql', 'alias' => mt('setup', 'Zend database adapter for PostgreSQL'), @@ -487,6 +487,7 @@ class WebWizard extends Wizard implements SetupWizard 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.' ) ))); + $requirements->merge($pgsqlRequirements); $requirements->add(new ConfigDirectoryRequirement(array( 'condition' => Icinga::app()->getConfigDir(), From 551207b5b8cd1e81d3a6ec3e36a2a1eb18414e98 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Thu, 26 Feb 2015 10:50:45 +0100 Subject: [PATCH 06/12] Add grouped database dependencies to the monitoring wizard refs #8508 --- .../library/Monitoring/MonitoringWizard.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php index 3c7dee886..82ee90e62 100644 --- a/modules/monitoring/library/Monitoring/MonitoringWizard.php +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -16,6 +16,7 @@ use Icinga\Module\Monitoring\Forms\Setup\InstancePage; use Icinga\Module\Monitoring\Forms\Setup\SecurityPage; use Icinga\Module\Monitoring\Forms\Setup\IdoResourcePage; use Icinga\Module\Monitoring\Forms\Setup\LivestatusResourcePage; +use Icinga\Module\Setup\Requirement\ClassRequirement; use Icinga\Module\Setup\Requirement\PhpModuleRequirement; /** @@ -147,6 +148,45 @@ class MonitoringWizard extends Wizard implements SetupWizard ) ))); + $idoRequirements = new Requirements(Requirements::MODE_OR); + $mysqlRequirements = new Requirements(); + $mysqlRequirements->add(new PhpModuleRequirement(array( + 'condition' => 'mysql', + 'alias' => 'PDO-MySQL', + 'description' => mt( + 'monitoring', + 'To access the IDO stored in a MySQL database the PDO-MySQL module for PHP is required.' + ) + ))); + $mysqlRequirements->add(new ClassRequirement(array( + 'condition' => 'Zend_Db_Adapter_Pdo_Mysql', + 'alias' => mt('monitoring', 'Zend database adapter for MySQL'), + 'description' => mt( + 'monitoring', + 'The Zend database adapter for MySQL is required to access a MySQL database.' + ) + ))); + $idoRequirements->merge($mysqlRequirements); + $pgsqlRequirements = new Requirements(); + $pgsqlRequirements->add(new PhpModuleRequirement(array( + 'condition' => 'pgsql', + 'alias' => 'PDO-PostgreSQL', + 'description' => mt( + 'monitoring', + 'To access the IDO stored in a PostgreSQL database the PDO-PostgreSQL module for PHP is required.' + ) + ))); + $pgsqlRequirements->add(new ClassRequirement(array( + 'condition' => 'Zend_Db_Adapter_Pdo_Pgsql', + 'alias' => mt('monitoring', 'Zend database adapter for PostgreSQL'), + 'description' => mt( + 'monitoring', + 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.' + ) + ))); + $idoRequirements->merge($pgsqlRequirements); + $requirements->merge($idoRequirements); + return $requirements; } } From d0a8dd8973c385d804e7f6a5368702b3923bb446 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Thu, 26 Feb 2015 10:52:39 +0100 Subject: [PATCH 07/12] Requirements: Do not implement ArrayIterator but RecursiveIterator refs #8508 --- .../scripts/form/setup-requirements.phtml | 4 +- modules/setup/library/Setup/Requirements.php | 88 +++++++++++++++---- 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/modules/setup/application/views/scripts/form/setup-requirements.phtml b/modules/setup/application/views/scripts/form/setup-requirements.phtml index 09ca98d7f..9918df008 100644 --- a/modules/setup/application/views/scripts/form/setup-requirements.phtml +++ b/modules/setup/application/views/scripts/form/setup-requirements.phtml @@ -1,13 +1,15 @@ <?php +use \RecursiveIteratorIterator; use Icinga\Web\Wizard; $requirements = $form->getRequirements(); +$iterator = new RecursiveIteratorIterator($requirements); ?> <table class="requirements"> <tbody> -<?php foreach ($requirements as $requirement): ?> +<?php foreach ($iterator as $requirement): ?> <tr> <td><h2><?= $requirement->getTitle(); ?></h2></td> <td style="width: 50%"> diff --git a/modules/setup/library/Setup/Requirements.php b/modules/setup/library/Setup/Requirements.php index aba96c4bc..69892f236 100644 --- a/modules/setup/library/Setup/Requirements.php +++ b/modules/setup/library/Setup/Requirements.php @@ -3,14 +3,13 @@ namespace Icinga\Module\Setup; -use ArrayIterator; use LogicException; -use IteratorAggregate; +use RecursiveIterator; /** * Container to store and handle requirements */ -class Requirements implements IteratorAggregate +class Requirements implements RecursiveIterator { /** * Mode AND (all requirements must met) @@ -94,7 +93,7 @@ class Requirements implements IteratorAggregate public function add(Requirement $requirement) { $merged = false; - foreach ($this as $knownRequirement) { + foreach ($this->requirements as $knownRequirement) { if ($knownRequirement instanceof Requirement && $requirement->equals($knownRequirement)) { if ($this->getMode() === static::MODE_AND && !$requirement->isOptional()) { $knownRequirement->setOptional(false); @@ -142,16 +141,6 @@ class Requirements implements IteratorAggregate return $this->containsMandatoryRequirements || $this->getMode() === static::MODE_OR; } - /** - * Return an iterator of all registered requirements - * - * @return ArrayIterator - */ - public function getIterator() - { - return new ArrayIterator($this->getAll()); - } - /** * Register the given requirements * @@ -162,7 +151,7 @@ class Requirements implements IteratorAggregate public function merge(Requirements $requirements) { if ($this->getMode() === static::MODE_OR && $requirements->getMode() === static::MODE_OR) { - foreach ($requirements as $requirement) { + foreach ($requirements->getAll() as $requirement) { if ($requirement instanceof static) { $this->merge($requirement); } else { @@ -188,7 +177,7 @@ class Requirements implements IteratorAggregate public function fulfilled() { $state = false; - foreach ($this as $requirement) { + foreach ($this->requirements as $requirement) { if ($requirement instanceof static) { if ($requirement->fulfilled()) { if ($this->getMode() === static::MODE_OR) { @@ -218,4 +207,71 @@ class Requirements implements IteratorAggregate return $state; } + + /** + * Return whether the current element represents a nested set of requirements + * + * @return bool + */ + public function hasChildren() + { + $current = $this->current(); + return $current instanceof static; + } + + /** + * Return a iterator for the current nested set of requirements + * + * @return RecursiveIterator + */ + public function getChildren() + { + return $this->current(); + } + + /** + * Rewind the iterator to its first element + */ + public function rewind() + { + reset($this->requirements); + } + + /** + * Return whether the current iterator position is valid + * + * @return bool + */ + public function valid() + { + return $this->key() !== null; + } + + /** + * Return the current element in the iteration + * + * @return Requirement|Requirements + */ + public function current() + { + return current($this->requirements); + } + + /** + * Return the position of the current element in the iteration + * + * @return int + */ + public function key() + { + return key($this->requirements); + } + + /** + * Advance the iterator to the next element + */ + public function next() + { + next($this->requirements); + } } From e80786d63dc77e9f506c11754829b3d1d3343034 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Mon, 9 Mar 2015 09:05:56 +0100 Subject: [PATCH 08/12] Rename Requirements to RequirementSet refs #8508 --- .../library/Monitoring/MonitoringWizard.php | 28 ++++++------ .../application/forms/RequirementsPage.php | 18 ++++---- .../{Requirements.php => RequirementSet.php} | 22 +++++----- modules/setup/library/Setup/SetupWizard.php | 2 +- modules/setup/library/Setup/WebWizard.php | 44 +++++++++---------- 5 files changed, 57 insertions(+), 57 deletions(-) rename modules/setup/library/Setup/{Requirements.php => RequirementSet.php} (91%) diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php index 82ee90e62..e746db033 100644 --- a/modules/monitoring/library/Monitoring/MonitoringWizard.php +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -8,7 +8,7 @@ use Icinga\Web\Wizard; use Icinga\Web\Request; use Icinga\Module\Setup\Setup; use Icinga\Module\Setup\SetupWizard; -use Icinga\Module\Setup\Requirements; +use Icinga\Module\Setup\RequirementSet; use Icinga\Module\Setup\Forms\SummaryPage; use Icinga\Module\Monitoring\Forms\Setup\WelcomePage; use Icinga\Module\Monitoring\Forms\Setup\BackendPage; @@ -136,9 +136,9 @@ class MonitoringWizard extends Wizard implements SetupWizard */ public function getRequirements() { - $requirements = new Requirements(); + $set = new RequirementSet(); - $requirements->add(new PhpModuleRequirement(array( + $set->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'Sockets', 'description' => mt( @@ -148,9 +148,9 @@ class MonitoringWizard extends Wizard implements SetupWizard ) ))); - $idoRequirements = new Requirements(Requirements::MODE_OR); - $mysqlRequirements = new Requirements(); - $mysqlRequirements->add(new PhpModuleRequirement(array( + $idoSet = new RequirementSet(RequirementSet::MODE_OR); + $mysqlSet = new RequirementSet(); + $mysqlSet->add(new PhpModuleRequirement(array( 'condition' => 'mysql', 'alias' => 'PDO-MySQL', 'description' => mt( @@ -158,7 +158,7 @@ class MonitoringWizard extends Wizard implements SetupWizard 'To access the IDO stored in a MySQL database the PDO-MySQL module for PHP is required.' ) ))); - $mysqlRequirements->add(new ClassRequirement(array( + $mysqlSet->add(new ClassRequirement(array( 'condition' => 'Zend_Db_Adapter_Pdo_Mysql', 'alias' => mt('monitoring', 'Zend database adapter for MySQL'), 'description' => mt( @@ -166,9 +166,9 @@ class MonitoringWizard extends Wizard implements SetupWizard 'The Zend database adapter for MySQL is required to access a MySQL database.' ) ))); - $idoRequirements->merge($mysqlRequirements); - $pgsqlRequirements = new Requirements(); - $pgsqlRequirements->add(new PhpModuleRequirement(array( + $idoSet->merge($mysqlSet); + $pgsqlSet = new RequirementSet(); + $pgsqlSet->add(new PhpModuleRequirement(array( 'condition' => 'pgsql', 'alias' => 'PDO-PostgreSQL', 'description' => mt( @@ -176,7 +176,7 @@ class MonitoringWizard extends Wizard implements SetupWizard 'To access the IDO stored in a PostgreSQL database the PDO-PostgreSQL module for PHP is required.' ) ))); - $pgsqlRequirements->add(new ClassRequirement(array( + $pgsqlSet->add(new ClassRequirement(array( 'condition' => 'Zend_Db_Adapter_Pdo_Pgsql', 'alias' => mt('monitoring', 'Zend database adapter for PostgreSQL'), 'description' => mt( @@ -184,9 +184,9 @@ class MonitoringWizard extends Wizard implements SetupWizard 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.' ) ))); - $idoRequirements->merge($pgsqlRequirements); - $requirements->merge($idoRequirements); + $idoSet->merge($pgsqlSet); + $set->merge($idoSet); - return $requirements; + return $set; } } diff --git a/modules/setup/application/forms/RequirementsPage.php b/modules/setup/application/forms/RequirementsPage.php index f4b8ee648..086e6e7fc 100644 --- a/modules/setup/application/forms/RequirementsPage.php +++ b/modules/setup/application/forms/RequirementsPage.php @@ -4,7 +4,7 @@ namespace Icinga\Module\Setup\Forms; use Icinga\Web\Form; -use Icinga\Module\Setup\Requirements; +use Icinga\Module\Setup\RequirementSet; /** * Wizard page to list setup requirements @@ -14,9 +14,9 @@ class RequirementsPage extends Form /** * The requirements to list * - * @var Requirements + * @var RequirementSet */ - protected $requirements; + protected $set; /** * Initialize this page @@ -30,24 +30,24 @@ class RequirementsPage extends Form /** * Set the requirements to list * - * @param Requirements $requirements + * @param RequirementSet $set * * @return self */ - public function setRequirements(Requirements $requirements) + public function setRequirements(RequirementSet $set) { - $this->requirements = $requirements; + $this->set = $set; return $this; } /** * Return the requirements to list * - * @return Requirements + * @return RequirementSet */ public function getRequirements() { - return $this->requirements; + return $this->set; } /** @@ -63,6 +63,6 @@ class RequirementsPage extends Form return false; } - return $this->requirements->fulfilled(); + return $this->set->fulfilled(); } } diff --git a/modules/setup/library/Setup/Requirements.php b/modules/setup/library/Setup/RequirementSet.php similarity index 91% rename from modules/setup/library/Setup/Requirements.php rename to modules/setup/library/Setup/RequirementSet.php index 69892f236..6ffce61fb 100644 --- a/modules/setup/library/Setup/Requirements.php +++ b/modules/setup/library/Setup/RequirementSet.php @@ -9,7 +9,7 @@ use RecursiveIterator; /** * Container to store and handle requirements */ -class Requirements implements RecursiveIterator +class RequirementSet implements RecursiveIterator { /** * Mode AND (all requirements must met) @@ -59,7 +59,7 @@ class Requirements implements RecursiveIterator * * @param int $mode * - * @return Requirements + * @return RequirementSet * * @throws LogicException In case the given mode is invalid */ @@ -88,7 +88,7 @@ class Requirements implements RecursiveIterator * * @param Requirement $requirement The requirement to add * - * @return Requirements + * @return RequirementSet */ public function add(Requirement $requirement) { @@ -144,14 +144,14 @@ class Requirements implements RecursiveIterator /** * Register the given requirements * - * @param Requirements $requirements The requirements to register + * @param RequirementSet $set The requirements to register * - * @return Requirements + * @return RequirementSet */ - public function merge(Requirements $requirements) + public function merge(RequirementSet $set) { - if ($this->getMode() === static::MODE_OR && $requirements->getMode() === static::MODE_OR) { - foreach ($requirements->getAll() as $requirement) { + if ($this->getMode() === static::MODE_OR && $set->getMode() === static::MODE_OR) { + foreach ($set->getAll() as $requirement) { if ($requirement instanceof static) { $this->merge($requirement); } else { @@ -159,11 +159,11 @@ class Requirements implements RecursiveIterator } } } else { - if ($requirements->getMode() === static::MODE_OR) { + if ($set->getMode() === static::MODE_OR) { $this->containsMandatoryRequirements = true; } - $this->requirements[] = $requirements; + $this->requirements[] = $set; } return $this; @@ -250,7 +250,7 @@ class Requirements implements RecursiveIterator /** * Return the current element in the iteration * - * @return Requirement|Requirements + * @return Requirement|RequirementSet */ public function current() { diff --git a/modules/setup/library/Setup/SetupWizard.php b/modules/setup/library/Setup/SetupWizard.php index 9e7b45174..1ce948dc1 100644 --- a/modules/setup/library/Setup/SetupWizard.php +++ b/modules/setup/library/Setup/SetupWizard.php @@ -18,7 +18,7 @@ interface SetupWizard /** * Return the requirements of this wizard * - * @return Requirements + * @return RequirementSet */ public function getRequirements(); } diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index 59404efd6..d3bb71bfa 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -357,9 +357,9 @@ class WebWizard extends Wizard implements SetupWizard */ public function getRequirements() { - $requirements = new Requirements(); + $set = new RequirementSet(); - $requirements->add(new PhpVersionRequirement(array( + $set->add(new PhpVersionRequirement(array( 'condition' => array('>=', '5.3.2'), 'description' => mt( 'setup', @@ -368,7 +368,7 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $requirements->add(new PhpConfigRequirement(array( + $set->add(new PhpConfigRequirement(array( 'condition' => array('date.timezone', true), 'title' => mt('setup', 'Default Timezone'), 'description' => sprintf( @@ -377,7 +377,7 @@ class WebWizard extends Wizard implements SetupWizard ), ))); - $requirements->add(new OSRequirement(array( + $set->add(new OSRequirement(array( 'optional' => true, 'condition' => 'linux', 'description' => mt( @@ -387,7 +387,7 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $requirements->add(new PhpModuleRequirement(array( + $set->add(new PhpModuleRequirement(array( 'condition' => 'OpenSSL', 'description' => mt( 'setup', @@ -395,7 +395,7 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $requirements->add(new PhpModuleRequirement(array( + $set->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'JSON', 'description' => mt( @@ -404,7 +404,7 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $requirements->add(new PhpModuleRequirement(array( + $set->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'LDAP', 'description' => mt( @@ -413,7 +413,7 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $requirements->add(new PhpModuleRequirement(array( + $set->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'INTL', 'description' => mt( @@ -424,7 +424,7 @@ class WebWizard extends Wizard implements SetupWizard ))); // TODO(6172): Remove this requirement once we do not ship dompdf with Icinga Web 2 anymore - $requirements->add(new PhpModuleRequirement(array( + $set->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'DOM', 'description' => mt( @@ -433,7 +433,7 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $requirements->add(new PhpModuleRequirement(array( + $set->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'GD', 'description' => mt( @@ -442,7 +442,7 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $requirements->add(new PhpModuleRequirement(array( + $set->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'Imagick', 'description' => mt( @@ -451,8 +451,8 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $mysqlRequirements = new Requirements(); - $mysqlRequirements->add(new PhpModuleRequirement(array( + $mysqlSet = new RequirementSet(); + $mysqlSet->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'mysql', 'alias' => 'PDO-MySQL', @@ -461,7 +461,7 @@ class WebWizard extends Wizard implements SetupWizard 'To store users or preferences in a MySQL database the PDO-MySQL module for PHP is required.' ) ))); - $mysqlRequirements->add(new ClassRequirement(array( + $mysqlSet->add(new ClassRequirement(array( 'optional' => true, 'condition' => 'Zend_Db_Adapter_Pdo_Mysql', 'alias' => mt('setup', 'Zend database adapter for MySQL'), @@ -470,10 +470,10 @@ class WebWizard extends Wizard implements SetupWizard 'The Zend database adapter for MySQL is required to access a MySQL database.' ) ))); - $requirements->merge($mysqlRequirements); + $set->merge($mysqlSet); - $pgsqlRequirements = new Requirements(); - $pgsqlRequirements->add(new PhpModuleRequirement(array( + $pgsqlSet = new RequirementSet(); + $pgsqlSet->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'pgsql', 'alias' => 'PDO-PostgreSQL', @@ -482,7 +482,7 @@ class WebWizard extends Wizard implements SetupWizard 'To store users or preferences in a PostgreSQL database the PDO-PostgreSQL module for PHP is required.' ) ))); - $pgsqlRequirements->add(new ClassRequirement(array( + $pgsqlSet->add(new ClassRequirement(array( 'optional' => true, 'condition' => 'Zend_Db_Adapter_Pdo_Pgsql', 'alias' => mt('setup', 'Zend database adapter for PostgreSQL'), @@ -491,9 +491,9 @@ class WebWizard extends Wizard implements SetupWizard 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.' ) ))); - $requirements->merge($pgsqlRequirements); + $set->merge($pgsqlSet); - $requirements->add(new ConfigDirectoryRequirement(array( + $set->add(new ConfigDirectoryRequirement(array( 'condition' => Icinga::app()->getConfigDir(), 'description' => mt( 'setup', @@ -503,9 +503,9 @@ class WebWizard extends Wizard implements SetupWizard ))); foreach ($this->getWizards() as $wizard) { - $requirements->merge($wizard->getRequirements()); + $set->merge($wizard->getRequirements()); } - return $requirements; + return $set; } } From bc450c573db1446e8f4bdd9cd322c8452e766080 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Tue, 10 Mar 2015 09:12:06 +0100 Subject: [PATCH 09/12] Refactor and fix requirement evaluation refs #8508 --- .../library/Monitoring/MonitoringWizard.php | 10 +- .../setup/library/Setup/RequirementSet.php | 171 +++--- modules/setup/library/Setup/WebWizard.php | 4 +- .../php/library/Setup/RequirementSetTest.php | 496 ++++++++++++++++++ 4 files changed, 613 insertions(+), 68 deletions(-) create mode 100644 modules/setup/test/php/library/Setup/RequirementSetTest.php diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php index e746db033..639986246 100644 --- a/modules/monitoring/library/Monitoring/MonitoringWizard.php +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -148,9 +148,10 @@ class MonitoringWizard extends Wizard implements SetupWizard ) ))); - $idoSet = new RequirementSet(RequirementSet::MODE_OR); - $mysqlSet = new RequirementSet(); + $idoSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mysqlSet = new RequirementSet(true); $mysqlSet->add(new PhpModuleRequirement(array( + 'optional' => true, 'condition' => 'mysql', 'alias' => 'PDO-MySQL', 'description' => mt( @@ -159,6 +160,7 @@ class MonitoringWizard extends Wizard implements SetupWizard ) ))); $mysqlSet->add(new ClassRequirement(array( + 'optional' => true, 'condition' => 'Zend_Db_Adapter_Pdo_Mysql', 'alias' => mt('monitoring', 'Zend database adapter for MySQL'), 'description' => mt( @@ -167,8 +169,9 @@ class MonitoringWizard extends Wizard implements SetupWizard ) ))); $idoSet->merge($mysqlSet); - $pgsqlSet = new RequirementSet(); + $pgsqlSet = new RequirementSet(true); $pgsqlSet->add(new PhpModuleRequirement(array( + 'optional' => true, 'condition' => 'pgsql', 'alias' => 'PDO-PostgreSQL', 'description' => mt( @@ -177,6 +180,7 @@ class MonitoringWizard extends Wizard implements SetupWizard ) ))); $pgsqlSet->add(new ClassRequirement(array( + 'optional' => true, 'condition' => 'Zend_Db_Adapter_Pdo_Pgsql', 'alias' => mt('monitoring', 'Zend database adapter for PostgreSQL'), 'description' => mt( diff --git a/modules/setup/library/Setup/RequirementSet.php b/modules/setup/library/Setup/RequirementSet.php index 6ffce61fb..966cf98cc 100644 --- a/modules/setup/library/Setup/RequirementSet.php +++ b/modules/setup/library/Setup/RequirementSet.php @@ -12,17 +12,31 @@ use RecursiveIterator; class RequirementSet implements RecursiveIterator { /** - * Mode AND (all requirements must met) + * Mode AND (all requirements must be met) */ const MODE_AND = 0; /** - * Mode OR (at least one requirement must met) + * Mode OR (at least one requirement must be met) */ const MODE_OR = 1; /** - * The mode by with the requirements are evaluated + * Whether all requirements meet their condition + * + * @var bool + */ + protected $state; + + /** + * Whether this set is optional + * + * @var bool + */ + protected $optional; + + /** + * The mode by which the requirements are evaluated * * @var string */ @@ -36,26 +50,75 @@ class RequirementSet implements RecursiveIterator protected $requirements; /** - * Whether there is any mandatory requirement part of this set + * The raw state of this set's requirements * * @var bool */ - protected $containsMandatoryRequirements; + private $forcedState; /** - * Create a new set of requirements + * Initialize a new set of requirements * - * @param int $mode The mode by with to evaluate the requirements + * @param bool $optional Whether this set is optional + * @param int $mode The mode by which to evaluate this set */ - public function __construct($mode = null) + public function __construct($optional = false, $mode = null) { + $this->optional = $optional; $this->requirements = array(); - $this->containsMandatoryRequirements = false; $this->setMode($mode ?: static::MODE_AND); } /** - * Set the mode by with to evaluate the requirements + * Set the state of this set + * + * @param bool $state + * + * @return RequirementSet + */ + public function setState($state) + { + $this->state = (bool) $state; + return $this; + } + + /** + * Return the state of this set + * + * Alias for RequirementSet::fulfilled(true). + * + * @return bool + */ + public function getState() + { + return $this->fulfilled(true); + } + + /** + * Set whether this set of requirements should be optional + * + * @param bool $state + * + * @return RequirementSet + */ + public function setOptional($state = true) + { + $this->optional = (bool) $state; + return $this; + } + + /** + * Return whether this set of requirements is optional + * + * @return bool + */ + public function isOptional() + { + return $this->optional; + } + + /** + * Set the mode by which to evaluate the requirements * * @param int $mode * @@ -74,7 +137,7 @@ class RequirementSet implements RecursiveIterator } /** - * Return the mode by with the requirements are evaluated + * Return the mode by which the requirements are evaluated * * @return int */ @@ -95,10 +158,7 @@ class RequirementSet implements RecursiveIterator $merged = false; foreach ($this->requirements as $knownRequirement) { if ($knownRequirement instanceof Requirement && $requirement->equals($knownRequirement)) { - if ($this->getMode() === static::MODE_AND && !$requirement->isOptional()) { - $knownRequirement->setOptional(false); - } - + $knownRequirement->setOptional($requirement->isOptional()); foreach ($requirement->getDescriptions() as $description) { $knownRequirement->addDescription($description); } @@ -109,12 +169,6 @@ class RequirementSet implements RecursiveIterator } if (! $merged) { - if ($this->getMode() === static::MODE_OR) { - $requirement->setOptional(); - } elseif (! $requirement->isOptional()) { - $this->containsMandatoryRequirements = true; - } - $this->requirements[] = $requirement; } @@ -132,25 +186,15 @@ class RequirementSet implements RecursiveIterator } /** - * Return whether there is any mandatory requirement part of this set + * Register the given set of requirements * - * @return bool - */ - public function hasAnyMandatoryRequirement() - { - return $this->containsMandatoryRequirements || $this->getMode() === static::MODE_OR; - } - - /** - * Register the given requirements - * - * @param RequirementSet $set The requirements to register + * @param RequirementSet $set The set to register * * @return RequirementSet */ public function merge(RequirementSet $set) { - if ($this->getMode() === static::MODE_OR && $set->getMode() === static::MODE_OR) { + if ($this->getMode() === $set->getMode() && $this->isOptional() === $set->isOptional()) { foreach ($set->getAll() as $requirement) { if ($requirement instanceof static) { $this->merge($requirement); @@ -159,10 +203,6 @@ class RequirementSet implements RecursiveIterator } } } else { - if ($set->getMode() === static::MODE_OR) { - $this->containsMandatoryRequirements = true; - } - $this->requirements[] = $set; } @@ -172,40 +212,45 @@ class RequirementSet implements RecursiveIterator /** * Return whether all requirements can successfully be evaluated based on the current mode * + * In case this is a optional set of requirements (and $force is false), true is returned immediately. + * + * @param bool $force Whether to ignore the optionality of a set or single requirement + * * @return bool */ - public function fulfilled() + public function fulfilled($force = false) { - $state = false; - foreach ($this->requirements as $requirement) { - if ($requirement instanceof static) { - if ($requirement->fulfilled()) { - if ($this->getMode() === static::MODE_OR) { - return true; - } + $state = $this->isOptional(); + if (! $force && $state) { + return true; + } - $state = true; - } elseif ($this->getMode() === static::MODE_AND && $requirement->hasAnyMandatoryRequirement()) { - return false; + if (! $force && $this->state !== null) { + return $this->state; + } elseif ($force && $this->forcedState !== null) { + return $this->forcedState; + } + + $self = $this->requirements; + foreach ($self as $requirement) { + if ($requirement->getState()) { + $state = true; + if ($this->getMode() === static::MODE_OR) { + break; } - } else { - if ($requirement->getState()) { - if ($this->getMode() === static::MODE_OR) { - return true; - } - - $state = true; - } elseif ($this->getMode() === static::MODE_AND) { - if (! $requirement->isOptional()) { - return false; - } - - $state = true; // There may only be optional requirements... + } elseif ($force || !$requirement->isOptional()) { + $state = false; + if ($this->getMode() === static::MODE_AND) { + break; } } } - return $state; + if ($force) { + return $this->forcedState = $state; + } + + return $this->state = $state; } /** diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index d3bb71bfa..e3091bd4d 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -451,7 +451,7 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - $mysqlSet = new RequirementSet(); + $mysqlSet = new RequirementSet(true); $mysqlSet->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'mysql', @@ -472,7 +472,7 @@ class WebWizard extends Wizard implements SetupWizard ))); $set->merge($mysqlSet); - $pgsqlSet = new RequirementSet(); + $pgsqlSet = new RequirementSet(true); $pgsqlSet->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'pgsql', diff --git a/modules/setup/test/php/library/Setup/RequirementSetTest.php b/modules/setup/test/php/library/Setup/RequirementSetTest.php new file mode 100644 index 000000000..d2c4d2029 --- /dev/null +++ b/modules/setup/test/php/library/Setup/RequirementSetTest.php @@ -0,0 +1,496 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Tests\Icinga\Module\Setup; + +use Icinga\Test\BaseTestCase; +use Icinga\Module\Setup\Requirement; +use Icinga\Module\Setup\RequirementSet; + +class TrueRequirement extends Requirement +{ + protected function evaluate() + { + return true; + } +} + +class FalseRequirement extends Requirement +{ + protected function evaluate() + { + return false; + } +} + +class RequirementSetTest extends BaseTestCase +{ + public function testFlatMandatoryRequirementsOfTypeAnd() + { + $emptySet = new RequirementSet(); + $this->assertFalse($emptySet->fulfilled(), 'A empty mandatory set of type and is fulfilled'); + + $singleTrueSet = new RequirementSet(); + $singleTrueSet->add(new TrueRequirement()); + $this->assertTrue( + $singleTrueSet->fulfilled(), + 'A mandatory set of type and with a single TrueRequirement is not fulfilled' + ); + + $singleFalseSet = new RequirementSet(); + $singleFalseSet->add(new FalseRequirement()); + $this->assertFalse( + $singleFalseSet->fulfilled(), + 'A mandatory set of type and with a single FalseRequirement is fulfilled' + ); + + $mixedSet = new RequirementSet(); + $mixedSet->add(new TrueRequirement()); + $mixedSet->add(new FalseRequirement()); + $this->assertFalse( + $mixedSet->fulfilled(), + 'A mandatory set of type and with one True- and one FalseRequirement is fulfilled' + ); + } + + public function testFlatOptionalRequirementsOfTypeAnd() + { + $emptySet = new RequirementSet(true); + $this->assertTrue($emptySet->fulfilled(), 'A empty optional set of type and is not fulfilled'); + + $singleTrueSet = new RequirementSet(true); + $singleTrueSet->add(new TrueRequirement()); + $this->assertTrue( + $singleTrueSet->fulfilled(), + 'A optional set of type and with a single TrueRequirement is not fulfilled' + ); + + $singleFalseSet = new RequirementSet(true); + $singleFalseSet->add(new FalseRequirement()); + $this->assertTrue( + $singleFalseSet->fulfilled(), + 'A optional set of type and with a single FalseRequirement is not fulfilled' + ); + + $mixedSet = new RequirementSet(true); + $mixedSet->add(new TrueRequirement()); + $mixedSet->add(new FalseRequirement()); + $this->assertTrue( + $mixedSet->fulfilled(), + 'A optional set of type and with one True- and one FalseRequirement is not fulfilled' + ); + } + + public function testFlatMixedRequirementsOfTypeAnd() + { + $mandatoryOptionalTrueSet = new RequirementSet(); + $mandatoryOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $mandatoryOptionalTrueSet->add(new FalseRequirement()); + $this->assertFalse( + $mandatoryOptionalTrueSet->fulfilled(), + 'A mandatory set of type and with one optional True- and one mandatory FalseRequirement is fulfilled' + ); + + $mandatoryOptionalFalseSet = new RequirementSet(); + $mandatoryOptionalFalseSet->add(new TrueRequirement()); + $mandatoryOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $this->assertTrue( + $mandatoryOptionalFalseSet->fulfilled(), + 'A mandatory set of type and with one mandatory True- and one optional FalseRequirement is not fulfilled' + ); + + $optionalOptionalTrueSet = new RequirementSet(true); + $optionalOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $optionalOptionalTrueSet->add(new FalseRequirement()); + $this->assertTrue( + $optionalOptionalTrueSet->fulfilled(), + 'A optional set of type and with one optional True- and one mandatory FalseRequirement is not fulfilled' + ); + + $optionalOptionalFalseSet = new RequirementSet(true); + $optionalOptionalFalseSet->add(new TrueRequirement()); + $optionalOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $this->assertTrue( + $optionalOptionalFalseSet->fulfilled(), + 'A optional set of type and with one mandatory True- and one optional FalseRequirement is not fulfilled' + ); + } + + public function testFlatMandatoryRequirementsOfTypeOr() + { + $emptySet = new RequirementSet(false, RequirementSet::MODE_OR); + $this->assertFalse($emptySet->fulfilled(), 'A empty mandatory set of type or is fulfilled'); + + $singleTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $singleTrueSet->add(new TrueRequirement()); + $this->assertTrue( + $singleTrueSet->fulfilled(), + 'A mandatory set of type or with a single TrueRequirement is not fulfilled' + ); + + $singleFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $singleFalseSet->add(new FalseRequirement()); + $this->assertFalse( + $singleFalseSet->fulfilled(), + 'A mandatory set of type or with a single FalseRequirement is fulfilled' + ); + + $mixedSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mixedSet->add(new TrueRequirement()); + $mixedSet->add(new FalseRequirement()); + $this->assertTrue( + $mixedSet->fulfilled(), + 'A mandatory set of type or with one True- and one FalseRequirement is not fulfilled' + ); + } + + public function testFlatOptionalRequirementsOfTypeOr() + { + $emptySet = new RequirementSet(true, RequirementSet::MODE_OR); + $this->assertTrue($emptySet->fulfilled(), 'A empty optional set of type or is not fulfilled'); + + $singleTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $singleTrueSet->add(new TrueRequirement()); + $this->assertTrue( + $singleTrueSet->fulfilled(), + 'A optional set of type or with a single TrueRequirement is not fulfilled' + ); + + $singleFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $singleFalseSet->add(new FalseRequirement()); + $this->assertTrue( + $singleFalseSet->fulfilled(), + 'A optional set of type or with a single FalseRequirement is not fulfilled' + ); + + $mixedSet = new RequirementSet(true, RequirementSet::MODE_OR); + $mixedSet->add(new TrueRequirement()); + $mixedSet->add(new FalseRequirement()); + $this->assertTrue( + $mixedSet->fulfilled(), + 'A optional set of type or with one True- and one FalseRequirement is not fulfilled' + ); + } + + public function testFlatMixedRequirementsOfTypeOr() + { + $mandatoryOptionalTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $mandatoryOptionalTrueSet->add(new FalseRequirement()); + $this->assertTrue( + $mandatoryOptionalTrueSet->fulfilled(), + 'A mandatory set of type or with one optional True- and one mandatory FalseRequirement is not fulfilled' + ); + + $mandatoryOptionalFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryOptionalFalseSet->add(new TrueRequirement()); + $mandatoryOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $this->assertTrue( + $mandatoryOptionalFalseSet->fulfilled(), + 'A mandatory set of type or with one mandatory True- and one optional FalseRequirement is not fulfilled' + ); + + $optionalOptionalTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $optionalOptionalTrueSet->add(new FalseRequirement()); + $this->assertTrue( + $optionalOptionalTrueSet->fulfilled(), + 'A optional set of type or with one optional True- and one mandatory FalseRequirement is not fulfilled' + ); + + $optionalOptionalFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalFalseSet->add(new TrueRequirement()); + $optionalOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $this->assertTrue( + $optionalOptionalFalseSet->fulfilled(), + 'A optional set of type or with one mandatory True- and one optional FalseRequirement is not fulfilled' + ); + } + + public function testNestedMandatoryRequirementsOfTypeAnd() + { + $trueSet = new RequirementSet(); + $trueSet->add(new TrueRequirement()); + $falseSet = new RequirementSet(); + $falseSet->add(new FalseRequirement()); + + $nestedTrueSet = new RequirementSet(); + $nestedTrueSet->merge($trueSet); + $this->assertTrue( + $nestedTrueSet->fulfilled(), + 'A nested mandatory set of type and with one mandatory TrueRequirement is not fulfilled' + ); + + $nestedFalseSet = new RequirementSet(); + $nestedFalseSet->merge($falseSet); + $this->assertFalse( + $nestedFalseSet->fulfilled(), + 'A nested mandatory set of type and with one mandatory FalseRequirement is fulfilled' + ); + + $nestedMixedSet = new RequirementSet(); + $nestedMixedSet->merge($trueSet); + $nestedMixedSet->merge($falseSet); + $this->assertFalse( + $nestedMixedSet->fulfilled(), + 'Two nested mandatory sets of type and with one mandatory True- and' + . ' one mandatory FalseRequirement respectively are fulfilled' + ); + } + + public function testNestedOptionalRequirementsOfTypeAnd() + { + $trueSet = new RequirementSet(true); + $trueSet->add(new TrueRequirement()); + $falseSet = new RequirementSet(true); + $falseSet->add(new FalseRequirement()); + + $nestedTrueSet = new RequirementSet(true); + $nestedTrueSet->merge($trueSet); + $this->assertTrue( + $nestedTrueSet->fulfilled(), + 'A nested optional set of type and with one mandatory TrueRequirement is not fulfilled' + ); + + $nestedFalseSet = new RequirementSet(true); + $nestedFalseSet->merge($falseSet); + $this->assertTrue( + $nestedFalseSet->fulfilled(), + 'A nested optional set of type and with one mandatory FalseRequirement is not fulfilled' + ); + + $nestedMixedSet = new RequirementSet(true); + $nestedMixedSet->merge($trueSet); + $nestedMixedSet->merge($falseSet); + $this->assertTrue( + $nestedMixedSet->fulfilled(), + 'Two nested optional sets of type and with one mandatory True- and' + . ' one mandatory FalseRequirement respectively are not fulfilled' + ); + } + + public function testNestedMixedRequirementsOfTypeAnd() + { + $mandatoryMandatoryTrueSet = new RequirementSet(); + $mandatoryMandatoryTrueSet->add(new TrueRequirement()); + $mandatoryOptionalTrueSet = new RequirementSet(); + $mandatoryOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $mandatoryMandatoryFalseSet = new RequirementSet(); + $mandatoryMandatoryFalseSet->add(new FalseRequirement()); + $mandatoryOptionalFalseSet = new RequirementSet(); + $mandatoryOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $optionalMandatoryTrueSet = new RequirementSet(true); + $optionalMandatoryTrueSet->add(new TrueRequirement()); + $optionalOptionalTrueSet = new RequirementSet(true); + $optionalOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $optionalMandatoryFalseSet = new RequirementSet(true); + $optionalMandatoryFalseSet->add(new FalseRequirement()); + $optionalOptionalFalseSet = new RequirementSet(true); + $optionalOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + + $mandatoryMandatoryOptionalTrueSet = new RequirementSet(); + $mandatoryMandatoryOptionalTrueSet->merge($mandatoryOptionalTrueSet); + $mandatoryMandatoryOptionalTrueSet->merge($mandatoryMandatoryFalseSet); + $this->assertFalse( + $mandatoryMandatoryOptionalTrueSet->fulfilled(), + 'A mandatory set of type and with two nested mandatory sets of type and where one has a optional' + . ' TrueRequirement and the other one has a mandatory FalseRequirement is fulfilled' + ); + + $mandatoryMandatoryOptionalFalseSet = new RequirementSet(); + $mandatoryMandatoryOptionalFalseSet->merge($mandatoryOptionalFalseSet); + $mandatoryMandatoryOptionalFalseSet->merge($mandatoryMandatoryTrueSet); + $this->assertTrue( + $mandatoryMandatoryOptionalFalseSet->fulfilled(), + 'A mandatory set of type and with two nested mandatory sets of type and where one has a mandatory' + . ' TrueRequirement and the other one has a optional FalseRequirement is not fulfilled' + ); + + $optionalOptionalOptionalTrueSet = new RequirementSet(true); + $optionalOptionalOptionalTrueSet->merge($optionalOptionalTrueSet); + $optionalOptionalOptionalTrueSet->merge($optionalMandatoryFalseSet); + $this->assertTrue( + $optionalOptionalOptionalTrueSet->fulfilled(), + 'A optional set of type and with two nested optional sets of type and where one has a optional' + . ' TrueRequirement and the other one has a mandatory FalseRequirement is not fulfilled' + ); + + $optionalOptionalOptionalFalseSet = new RequirementSet(true); + $optionalOptionalOptionalFalseSet->merge($optionalOptionalFalseSet); + $optionalOptionalOptionalFalseSet->merge($optionalMandatoryTrueSet); + $this->assertTrue( + $optionalOptionalOptionalFalseSet->fulfilled(), + 'A optional set of type and with two nested optional sets of type and where one has a mandatory' + . ' TrueRequirement and the other one has a optional FalseRequirement is not fulfilled' + ); + } + + public function testNestedMandatoryRequirementsOfTypeOr() + { + $trueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $trueSet->add(new TrueRequirement()); + $falseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $falseSet->add(new FalseRequirement()); + + $nestedTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $nestedTrueSet->merge($trueSet); + $this->assertTrue( + $nestedTrueSet->fulfilled(), + 'A nested mandatory set of type or with one mandatory TrueRequirement is not fulfilled' + ); + + $nestedFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $nestedFalseSet->merge($falseSet); + $this->assertFalse( + $nestedFalseSet->fulfilled(), + 'A nested mandatory set of type or with one mandatory FalseRequirement is fulfilled' + ); + + $nestedMixedSet = new RequirementSet(false, RequirementSet::MODE_OR); + $nestedMixedSet->merge($trueSet); + $nestedMixedSet->merge($falseSet); + $this->assertTrue( + $nestedMixedSet->fulfilled(), + 'Two nested mandatory sets of type or with one mandatory True- and' + . ' one mandatory FalseRequirement respectively are not fulfilled' + ); + } + + public function testNestedOptionalRequirementsOfTypeOr() + { + $trueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $trueSet->add(new TrueRequirement()); + $falseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $falseSet->add(new FalseRequirement()); + + $nestedTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $nestedTrueSet->merge($trueSet); + $this->assertTrue( + $nestedTrueSet->fulfilled(), + 'A nested optional set of type or with one mandatory TrueRequirement is not fulfilled' + ); + + $nestedFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $nestedFalseSet->merge($falseSet); + $this->assertTrue( + $nestedFalseSet->fulfilled(), + 'A nested optional set of type or with one mandatory FalseRequirement is not fulfilled' + ); + + $nestedMixedSet = new RequirementSet(true, RequirementSet::MODE_OR); + $nestedMixedSet->merge($trueSet); + $nestedMixedSet->merge($falseSet); + $this->assertTrue( + $nestedMixedSet->fulfilled(), + 'Two nested optional sets of type or with one mandatory True- and' + . ' one mandatory FalseRequirement respectively are not fulfilled' + ); + } + + public function testNestedMixedRequirementsOfTypeOr() + { + $mandatoryMandatoryTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryMandatoryTrueSet->add(new TrueRequirement()); + $mandatoryOptionalTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $mandatoryMandatoryFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryMandatoryFalseSet->add(new FalseRequirement()); + $mandatoryOptionalFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $optionalMandatoryTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalMandatoryTrueSet->add(new TrueRequirement()); + $optionalOptionalTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $optionalMandatoryFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalMandatoryFalseSet->add(new FalseRequirement()); + $optionalOptionalFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + + $mandatoryMandatoryOptionalTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryMandatoryOptionalTrueSet->merge($mandatoryOptionalTrueSet); + $mandatoryMandatoryOptionalTrueSet->merge($mandatoryMandatoryFalseSet); + $this->assertTrue($mandatoryMandatoryOptionalTrueSet->fulfilled()); + + $mandatoryMandatoryOptionalFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryMandatoryOptionalFalseSet->merge($mandatoryOptionalFalseSet); + $mandatoryMandatoryOptionalFalseSet->merge($mandatoryMandatoryTrueSet); + $this->assertTrue($mandatoryMandatoryOptionalFalseSet->fulfilled()); + + $optionalOptionalOptionalTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalOptionalTrueSet->merge($optionalOptionalTrueSet); + $optionalOptionalOptionalTrueSet->merge($optionalMandatoryFalseSet); + $this->assertTrue($optionalOptionalOptionalTrueSet->fulfilled()); + + $optionalOptionalOptionalFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalOptionalFalseSet->merge($optionalOptionalFalseSet); + $optionalOptionalOptionalFalseSet->merge($optionalMandatoryTrueSet); + $this->assertTrue($optionalOptionalOptionalFalseSet->fulfilled()); + } + + public function testNestedMandatoryRequirementsOfDifferentTypes() + { + $true = new TrueRequirement(); + $false = new FalseRequirement(); + + $level1And = new RequirementSet(); + $level2FirstOr = new RequirementSet(false, RequirementSet::MODE_OR); + $level2SecondOr = new RequirementSet(false, RequirementSet::MODE_OR); + $level1And->merge($level2FirstOr)->merge($level2SecondOr); + $level3FirstAnd = new RequirementSet(); + $level3SecondAnd = new RequirementSet(); + $level2FirstOr->merge($level3FirstAnd)->merge($level3SecondAnd); + $level2SecondOr->merge($level3FirstAnd)->merge($level3SecondAnd); + $level3FirstAnd->add($true)->add($true); + $level3SecondAnd->add($false)->add($true); + $this->assertTrue($level1And->fulfilled()); + + $level1Or = new RequirementSet(false, RequirementSet::MODE_OR); + $level2FirstAnd = new RequirementSet(); + $level2SecondAnd = new RequirementSet(); + $level1Or->merge($level2FirstAnd)->merge($level2SecondAnd); + $level3FirstOr = new RequirementSet(false, RequirementSet::MODE_OR); + $level3SecondOr = new RequirementSet(false, RequirementSet::MODE_OR); + $level2FirstAnd->merge($level3FirstOr)->merge($level3SecondOr); + $level2SecondAnd->merge($level3FirstOr)->merge($level3SecondOr); + $level3FirstOr->add($false); + $level3SecondOr->add($true); + $this->assertFalse($level1Or->fulfilled()); + } + + public function testNestedOptionalRequirementsOfDifferentTypes() + { + $true = new TrueRequirement(); + $false = new FalseRequirement(); + + $level1And = new RequirementSet(); + $level2FirstAnd = new RequirementSet(true); + $level2SecondAnd = new RequirementSet(true); + $level1And->merge($level2FirstAnd)->merge($level2SecondAnd); + $level3FirstOr = new RequirementSet(true, RequirementSet::MODE_OR); + $level3SecondOr = new RequirementSet(true, RequirementSet::MODE_OR); + $level2FirstAnd->merge($level3FirstOr)->merge($level3SecondOr); + $level2SecondAnd->merge($level3FirstOr)->merge($level3SecondOr); + $level3FirstOr->add($false); + $level3SecondOr->add($false); + $this->assertFalse($level1And->fulfilled()); + $this->assertTrue($level2FirstAnd->fulfilled()); + $this->assertTrue($level2SecondAnd->fulfilled()); + + $level1Or = new RequirementSet(false, RequirementSet::MODE_OR); + $level2FirstOr = new RequirementSet(true, RequirementSet::MODE_OR); + $level2SecondOr = new RequirementSet(true, RequirementSet::MODE_OR); + $level1Or->merge($level2FirstOr)->merge($level2SecondOr); + $level3FirstAnd = new RequirementSet(true); + $level3SecondAnd = new RequirementSet(true); + $level2FirstOr->merge($level3FirstAnd)->merge($level3SecondAnd); + $level2SecondOr->merge($level3FirstAnd)->merge($level3SecondAnd); + $level3FirstAnd->add($true)->add($true); + $level3SecondAnd->add($false)->add($true); + $this->assertTrue($level1Or->fulfilled()); + } + + public function testNestedMixedRequirementsOfDifferentTypes() + { + $this->markTestIncomplete(); + } +} From c44d5d2a735700e441fe9c9fcb3ade7d57991135 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Tue, 10 Mar 2015 09:31:57 +0100 Subject: [PATCH 10/12] Use a custom RecursiveIteratorIterator to render a RequirementSet refs #8508 --- .../scripts/form/setup-requirements.phtml | 65 ++++----------- .../setup/library/Setup/RequirementSet.php | 11 +++ .../library/Setup/RequirementsRenderer.php | 83 +++++++++++++++++++ public/css/icinga/setup.less | 37 ++++++++- 4 files changed, 144 insertions(+), 52 deletions(-) create mode 100644 modules/setup/library/Setup/RequirementsRenderer.php diff --git a/modules/setup/application/views/scripts/form/setup-requirements.phtml b/modules/setup/application/views/scripts/form/setup-requirements.phtml index 9918df008..571a661b6 100644 --- a/modules/setup/application/views/scripts/form/setup-requirements.phtml +++ b/modules/setup/application/views/scripts/form/setup-requirements.phtml @@ -1,66 +1,35 @@ <?php -use \RecursiveIteratorIterator; use Icinga\Web\Wizard; $requirements = $form->getRequirements(); -$iterator = new RecursiveIteratorIterator($requirements); +echo $requirements; ?> -<table class="requirements"> - <tbody> -<?php foreach ($iterator as $requirement): ?> - <tr> - <td><h2><?= $requirement->getTitle(); ?></h2></td> - <td style="width: 50%"> - <?php $descriptions = $requirement->getDescriptions(); ?> - <?php if (count($descriptions) > 1): ?> - <ul> - <?php foreach ($descriptions as $desc): ?> - <li><?= $desc; ?></li> - <?php endforeach ?> - </ul> - <?php elseif (! empty($descriptions)): ?> - <?= $descriptions[0]; ?> - <?php endif ?> - </td> - <td class="state <?= $requirement->getState() ? 'fulfilled' : ( - $requirement->isOptional() ? 'not-available' : 'missing' - ); ?>"><?= $requirement->getStateText(); ?></td> - </tr> -<?php endforeach ?> - <tr> - <td></td> - <td></td> - <td class="btn-update"> - <div class="buttons"> - <?php $title = $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?> - <?= $this->qlink( - $this->translate('Refresh'), - null, - null, - array( - 'class' => 'button-like', - 'title' => $title, - 'aria-label' => sprintf($this->translate('Refresh the page; %s'), $title) - ) - ); ?> - </div> - </td> - </tr> - </tbody> -</table> +<div class="buttons requirements-refresh"> + <?php $title = $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?> + <?= $this->qlink( + $this->translate('Refresh'), + null, + null, + array( + 'class' => 'button-like', + 'title' => $title, + 'aria-label' => sprintf($this->translate('Refresh the page; %s'), $title) + ) + ); ?> +</div> <form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>"> <?= $form->getElement($form->getTokenElementName()); ?> <?= $form->getElement($form->getUidElementName()); ?> - <div class="buttons" style="margin: 0 0 1.5em;"> + <div class="buttons"> <?= $form->getElement(Wizard::BTN_PREV); ?> <?php $btn = $form->getElement(Wizard::BTN_NEXT); - if (false === $requirements->fulfilled()) { + if (! $requirements->fulfilled()) { $btn->setAttrib('disabled', 1); } echo $btn; ?> </div> -</form> +</form> \ No newline at end of file diff --git a/modules/setup/library/Setup/RequirementSet.php b/modules/setup/library/Setup/RequirementSet.php index 966cf98cc..8bb018d7b 100644 --- a/modules/setup/library/Setup/RequirementSet.php +++ b/modules/setup/library/Setup/RequirementSet.php @@ -319,4 +319,15 @@ class RequirementSet implements RecursiveIterator { next($this->requirements); } + + /** + * Return this set of requirements rendered as HTML + * + * @return string + */ + public function __toString() + { + $renderer = new RequirementsRenderer($this); + return (string) $renderer; + } } diff --git a/modules/setup/library/Setup/RequirementsRenderer.php b/modules/setup/library/Setup/RequirementsRenderer.php new file mode 100644 index 000000000..b768a1e86 --- /dev/null +++ b/modules/setup/library/Setup/RequirementsRenderer.php @@ -0,0 +1,83 @@ +<?php +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Setup; + +use RecursiveIteratorIterator; + +class RequirementsRenderer extends RecursiveIteratorIterator +{ + public function beginIteration() + { + $this->tags[] = '<table class="requirements">'; + $this->tags[] = '<tbody>'; + } + + public function endIteration() + { + $this->tags[] = '</tbody>'; + $this->tags[] = '</table>'; + } + + public function beginChildren() + { + $this->tags[] = '<tr>'; + $currentSet = $this->getSubIterator(); + $state = $currentSet->getState() ? 'fulfilled' : ( + $currentSet->isOptional() ? 'not-available' : 'missing' + ); + $colSpanRequired = $this->hasSingleRequirements($this->getSubIterator($this->getDepth() - 1)); + $this->tags[] = '<td class="set-state ' . $state . '"' . ($colSpanRequired ? ' colspan=3' : '') . '>'; + $this->beginIteration(); + } + + public function endChildren() + { + $this->endIteration(); + $this->tags[] = '</td>'; + $this->tags[] = '</tr>'; + } + + protected function hasSingleRequirements(RequirementSet $requirements) + { + $set = $requirements->getAll(); + foreach ($set as $entry) { + if ($entry instanceof Requirement) { + return true; + } + } + + return false; + } + + public function render() + { + foreach ($this as $requirement) { + $this->tags[] = '<tr>'; + $this->tags[] = '<td class="title"><h2>' . $requirement->getTitle() . '</h2></td>'; + $this->tags[] = '<td class="desc">'; + $descriptions = $requirement->getDescriptions(); + if (count($descriptions) > 1) { + $this->tags[] = '<ul>'; + foreach ($descriptions as $d) { + $this->tags[] = '<li>' . $d . '</li>'; + } + $this->tags[] = '</ul>'; + } elseif (! empty($descriptions)) { + $this->tags[] = $descriptions[0]; + } + $this->tags[] = '</td>'; + $this->tags[] = '<td class="state ' . ($requirement->getState() ? 'fulfilled' : ( + $requirement->isOptional() ? 'not-available' : 'missing' + )) . '">' . $requirement->getStateText() . '</td>'; + $this->tags[] = '</tr>'; + } + + return implode("\n", $this->tags); + } + + public function __toString() + { + return $this->render(); + } +} diff --git a/public/css/icinga/setup.less b/public/css/icinga/setup.less index 66b08417e..31ecd205f 100644 --- a/public/css/icinga/setup.less +++ b/public/css/icinga/setup.less @@ -110,7 +110,8 @@ } #setup div.buttons { - margin: 1.5em 0; + margin-top: 1.5em; // Yes, -top and -bottom, keep it like that... + margin-bottom: 1.5em; .double { position: absolute; @@ -166,25 +167,53 @@ } } -#setup table.requirements { +form#setup_requirements { + padding-top: 0.5em; + border-top: 2px solid @colorPetrol; +} + +div.requirements-refresh { + width: 25%; + margin-left: 75%; + text-align: center; +} + +#setup > table.requirements { font-size: 0.9em; - margin: -1em -1em 2em; +} + +#setup table.requirements { + margin: -1em; border-spacing: 1em; border-collapse: separate; - border-bottom: 2px solid @colorPetrol; td { + padding: 0; + h2 { margin: 0 1em 0 0; } + table { + font-size: 102%; // Just a hack for webkit, remove this in case you can't see any difference or make it work without it + } + ul { margin: 0; padding-left: 1em; list-style-type: square; } + &.title { + width: 25%; + } + + &.desc { + width: 50%; + } + &.state { + width: 25%; color: white; padding: 0.4em; From 59f43a0f5eb2007d15843c09fabfce64d1aa06c6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Tue, 10 Mar 2015 10:56:05 +0100 Subject: [PATCH 11/12] Show module requirements as a separate table refs #8508 --- .../application/forms/RequirementsPage.php | 28 +++++++------- .../scripts/form/setup-requirements.phtml | 37 ++++++++++--------- modules/setup/library/Setup/WebWizard.php | 10 +++-- public/css/icinga/setup.less | 33 +++++------------ 4 files changed, 50 insertions(+), 58 deletions(-) diff --git a/modules/setup/application/forms/RequirementsPage.php b/modules/setup/application/forms/RequirementsPage.php index 086e6e7fc..3ce50b7f1 100644 --- a/modules/setup/application/forms/RequirementsPage.php +++ b/modules/setup/application/forms/RequirementsPage.php @@ -4,7 +4,7 @@ namespace Icinga\Module\Setup\Forms; use Icinga\Web\Form; -use Icinga\Module\Setup\RequirementSet; +use Icinga\Module\Setup\SetupWizard; /** * Wizard page to list setup requirements @@ -12,11 +12,11 @@ use Icinga\Module\Setup\RequirementSet; class RequirementsPage extends Form { /** - * The requirements to list + * The wizard * - * @var RequirementSet + * @var SetupWizard */ - protected $set; + protected $wizard; /** * Initialize this page @@ -28,30 +28,30 @@ class RequirementsPage extends Form } /** - * Set the requirements to list + * Set the wizard * - * @param RequirementSet $set + * @param SetupWizard $wizard * * @return self */ - public function setRequirements(RequirementSet $set) + public function setWizard(SetupWizard $wizard) { - $this->set = $set; + $this->wizard = $wizard; return $this; } /** - * Return the requirements to list + * Return the wizard * - * @return RequirementSet + * @return SetupWizard */ - public function getRequirements() + public function getWizard() { - return $this->set; + return $this->wizard; } /** - * Validate the given form data and check whether the requirements are fulfilled + * Validate the given form data and check whether the wizard's requirements are fulfilled * * @param array $data The data to validate * @@ -63,6 +63,6 @@ class RequirementsPage extends Form return false; } - return $this->set->fulfilled(); + return $this->wizard->getRequirements()->fulfilled(); } } diff --git a/modules/setup/application/views/scripts/form/setup-requirements.phtml b/modules/setup/application/views/scripts/form/setup-requirements.phtml index 571a661b6..fbd2c692a 100644 --- a/modules/setup/application/views/scripts/form/setup-requirements.phtml +++ b/modules/setup/application/views/scripts/form/setup-requirements.phtml @@ -2,23 +2,13 @@ use Icinga\Web\Wizard; -$requirements = $form->getRequirements(); -echo $requirements; - ?> -<div class="buttons requirements-refresh"> - <?php $title = $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?> - <?= $this->qlink( - $this->translate('Refresh'), - null, - null, - array( - 'class' => 'button-like', - 'title' => $title, - 'aria-label' => sprintf($this->translate('Refresh the page; %s'), $title) - ) - ); ?> -</div> +<h1>Icinga Web 2</h1> +<?= $form->getWizard()->getRequirements(true); ?> +<?php foreach ($form->getWizard()->getPage('setup_modules')->getModuleWizards() as $moduleName => $wizard): ?> +<h1><?= ucwords($moduleName) . ' ' . $this->translate('Module'); ?></h1> +<?= $wizard->getRequirements(); ?> +<?php endforeach ?> <form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>"> <?= $form->getElement($form->getTokenElementName()); ?> <?= $form->getElement($form->getUidElementName()); ?> @@ -26,10 +16,23 @@ echo $requirements; <?= $form->getElement(Wizard::BTN_PREV); ?> <?php $btn = $form->getElement(Wizard::BTN_NEXT); - if (! $requirements->fulfilled()) { + if (! $form->getWizard()->getRequirements()->fulfilled()) { $btn->setAttrib('disabled', 1); } echo $btn; ?> + <div class="requirements-refresh"> + <?php $title = $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?> + <?= $this->qlink( + $this->translate('Refresh'), + null, + null, + array( + 'class' => 'button-like', + 'title' => $title, + 'aria-label' => sprintf($this->translate('Refresh the page; %s'), $title) + ) + ); ?> + </div> </div> </form> \ No newline at end of file diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index e3091bd4d..d16b614f5 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -115,7 +115,7 @@ class WebWizard extends Wizard implements SetupWizard public function setupPage(Form $page, Request $request) { if ($page->getName() === 'setup_requirements') { - $page->setRequirements($this->getRequirements()); + $page->setWizard($this); } elseif ($page->getName() === 'setup_preferences_type') { $authData = $this->getPageData('setup_authentication_type'); if ($authData['type'] === 'db') { @@ -355,7 +355,7 @@ class WebWizard extends Wizard implements SetupWizard /** * @see SetupWizard::getRequirements() */ - public function getRequirements() + public function getRequirements($skipModules = false) { $set = new RequirementSet(); @@ -502,8 +502,10 @@ class WebWizard extends Wizard implements SetupWizard ) ))); - foreach ($this->getWizards() as $wizard) { - $set->merge($wizard->getRequirements()); + if (! $skipModules) { + foreach ($this->getWizards() as $wizard) { + $set->merge($wizard->getRequirements()); + } } return $set; diff --git a/public/css/icinga/setup.less b/public/css/icinga/setup.less index 31ecd205f..26c4b4e64 100644 --- a/public/css/icinga/setup.less +++ b/public/css/icinga/setup.less @@ -168,14 +168,19 @@ } form#setup_requirements { + margin-top: 2em; padding-top: 0.5em; border-top: 2px solid @colorPetrol; -} -div.requirements-refresh { - width: 25%; - margin-left: 75%; - text-align: center; + div.buttons div.requirements-refresh { + width: 25%; + float: right; + text-align: center; + + a.button-like { + padding: 0.1em 0.4em; + } + } } #setup > table.requirements { @@ -230,24 +235,6 @@ div.requirements-refresh { background-color: @colorCritical; } } - - &.btn-update { - padding-top: 0.3em; - text-align: center; - - div.buttons { - margin: 0; - - a.button-like { - padding: 0.2em 0.5em; - background-color: @colorPetro; - - &:hover, &:focus { - background-color: #666; - } - } - } - } } } From f4446cbaded08c99ab2ba5d6c0bc3c2fcb8817a5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer <johannes.meyer@netways.de> Date: Tue, 10 Mar 2015 11:00:51 +0100 Subject: [PATCH 12/12] Add todo to the MonitoringWizard related to the livestatus backend type The requirement should be added to the OR-set that has currently both ido requirement sets so that livestatus may the only choice again. refs #8254 --- .../monitoring/library/Monitoring/MonitoringWizard.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php index 639986246..496809c33 100644 --- a/modules/monitoring/library/Monitoring/MonitoringWizard.php +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -138,6 +138,7 @@ class MonitoringWizard extends Wizard implements SetupWizard { $set = new RequirementSet(); + // TODO(8254): Add this to the $backendSet $set->add(new PhpModuleRequirement(array( 'optional' => true, 'condition' => 'Sockets', @@ -148,7 +149,7 @@ class MonitoringWizard extends Wizard implements SetupWizard ) ))); - $idoSet = new RequirementSet(false, RequirementSet::MODE_OR); + $backendSet = new RequirementSet(false, RequirementSet::MODE_OR); $mysqlSet = new RequirementSet(true); $mysqlSet->add(new PhpModuleRequirement(array( 'optional' => true, @@ -168,7 +169,7 @@ class MonitoringWizard extends Wizard implements SetupWizard 'The Zend database adapter for MySQL is required to access a MySQL database.' ) ))); - $idoSet->merge($mysqlSet); + $backendSet->merge($mysqlSet); $pgsqlSet = new RequirementSet(true); $pgsqlSet->add(new PhpModuleRequirement(array( 'optional' => true, @@ -188,8 +189,8 @@ class MonitoringWizard extends Wizard implements SetupWizard 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.' ) ))); - $idoSet->merge($pgsqlSet); - $set->merge($idoSet); + $backendSet->merge($pgsqlSet); + $set->merge($backendSet); return $set; }