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;
     }