Merge branch 'feature/conditional-requirements-8508'

resolves #8508
This commit is contained in:
Johannes Meyer 2015-03-10 12:07:57 +01:00
commit b978fdb115
18 changed files with 1632 additions and 453 deletions

View File

@ -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');
}
}

View File

@ -3,13 +3,12 @@
namespace Icinga\Module\Monitoring;
use Icinga\Application\Platform;
use Icinga\Web\Form;
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;
@ -17,6 +16,8 @@ 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;
/**
* Monitoring Module Setup Wizard
@ -135,22 +136,62 @@ class MonitoringWizard extends Wizard implements SetupWizard
*/
public function getRequirements()
{
$requirements = new Requirements();
$set = new RequirementSet();
$requirements->addOptional(
'existing_php_mod_sockets',
mt('monitoring', 'PHP Module: Sockets'),
mt(
// TODO(8254): Add this to the $backendSet
$set->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;
$backendSet = new RequirementSet(false, RequirementSet::MODE_OR);
$mysqlSet = new RequirementSet(true);
$mysqlSet->add(new PhpModuleRequirement(array(
'optional' => true,
'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.'
)
)));
$mysqlSet->add(new ClassRequirement(array(
'optional' => true,
'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.'
)
)));
$backendSet->merge($mysqlSet);
$pgsqlSet = new RequirementSet(true);
$pgsqlSet->add(new PhpModuleRequirement(array(
'optional' => true,
'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.'
)
)));
$pgsqlSet->add(new ClassRequirement(array(
'optional' => true,
'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.'
)
)));
$backendSet->merge($pgsqlSet);
$set->merge($backendSet);
return $set;
}
}

View File

@ -4,7 +4,7 @@
namespace Icinga\Module\Setup\Forms;
use Icinga\Web\Form;
use Icinga\Module\Setup\Requirements;
use Icinga\Module\Setup\SetupWizard;
/**
* Wizard page to list setup requirements
@ -12,11 +12,11 @@ use Icinga\Module\Setup\Requirements;
class RequirementsPage extends Form
{
/**
* The requirements to list
* The wizard
*
* @var Requirements
* @var SetupWizard
*/
protected $requirements;
protected $wizard;
/**
* Initialize this page
@ -28,30 +28,30 @@ class RequirementsPage extends Form
}
/**
* Set the requirements to list
* Set the wizard
*
* @param Requirements $requirements
* @param SetupWizard $wizard
*
* @return self
*/
public function setRequirements(Requirements $requirements)
public function setWizard(SetupWizard $wizard)
{
$this->requirements = $requirements;
$this->wizard = $wizard;
return $this;
}
/**
* Return the requirements to list
* Return the wizard
*
* @return Requirements
* @return SetupWizard
*/
public function getRequirements()
public function getWizard()
{
return $this->requirements;
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->requirements->fulfilled();
return $this->wizard->getRequirements()->fulfilled();
}
}

View File

@ -1,64 +1,38 @@
<?php
use Icinga\Web\Wizard;
use Icinga\Module\Setup\Requirements;
$requirements = $form->getRequirements();
?>
<table class="requirements">
<tbody>
<?php foreach ($requirements as $requirement): ?>
<tr>
<td><h2><?= $requirement->title; ?></h2></td>
<td style="width: 50%">
<?php if (is_array($requirement->description)): ?>
<ul>
<?php foreach ($requirement->description as $desc): ?>
<li><?= $desc; ?></li>
<?php endforeach ?>
</ul>
<?php else: ?>
<?= $requirement->description; ?>
<?php endif ?>
</td>
<td class="state <?= $requirement->state === Requirements::STATE_OK ? 'fulfilled' : (
$requirement->state === Requirements::STATE_OPTIONAL ? 'not-available' : 'missing'
); ?>"><?= $requirement->message; ?></td>
</tr>
<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 ?>
<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>
<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 (! $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>
</form>

View File

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

View File

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

View File

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

View File

@ -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());
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,333 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Setup;
use LogicException;
use RecursiveIterator;
/**
* Container to store and handle requirements
*/
class RequirementSet implements RecursiveIterator
{
/**
* Mode AND (all requirements must be met)
*/
const MODE_AND = 0;
/**
* Mode OR (at least one requirement must be met)
*/
const MODE_OR = 1;
/**
* 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
*/
protected $mode;
/**
* The registered requirements
*
* @var array
*/
protected $requirements;
/**
* The raw state of this set's requirements
*
* @var bool
*/
private $forcedState;
/**
* Initialize a new set of requirements
*
* @param bool $optional Whether this set is optional
* @param int $mode The mode by which to evaluate this set
*/
public function __construct($optional = false, $mode = null)
{
$this->optional = $optional;
$this->requirements = array();
$this->setMode($mode ?: static::MODE_AND);
}
/**
* 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
*
* @return RequirementSet
*
* @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 which the requirements are evaluated
*
* @return int
*/
public function getMode()
{
return $this->mode;
}
/**
* Register a requirement
*
* @param Requirement $requirement The requirement to add
*
* @return RequirementSet
*/
public function add(Requirement $requirement)
{
$merged = false;
foreach ($this->requirements as $knownRequirement) {
if ($knownRequirement instanceof Requirement && $requirement->equals($knownRequirement)) {
$knownRequirement->setOptional($requirement->isOptional());
foreach ($requirement->getDescriptions() as $description) {
$knownRequirement->addDescription($description);
}
$merged = true;
break;
}
}
if (! $merged) {
$this->requirements[] = $requirement;
}
return $this;
}
/**
* Return all registered requirements
*
* @return array
*/
public function getAll()
{
return $this->requirements;
}
/**
* Register the given set of requirements
*
* @param RequirementSet $set The set to register
*
* @return RequirementSet
*/
public function merge(RequirementSet $set)
{
if ($this->getMode() === $set->getMode() && $this->isOptional() === $set->isOptional()) {
foreach ($set->getAll() as $requirement) {
if ($requirement instanceof static) {
$this->merge($requirement);
} else {
$this->add($requirement);
}
}
} else {
$this->requirements[] = $set;
}
return $this;
}
/**
* 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($force = false)
{
$state = $this->isOptional();
if (! $force && $state) {
return true;
}
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;
}
} elseif ($force || !$requirement->isOptional()) {
$state = false;
if ($this->getMode() === static::MODE_AND) {
break;
}
}
}
if ($force) {
return $this->forcedState = $state;
}
return $this->state = $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|RequirementSet
*/
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);
}
/**
* Return this set of requirements rendered as HTML
*
* @return string
*/
public function __toString()
{
$renderer = new RequirementsRenderer($this);
return (string) $renderer;
}
}

View File

@ -1,197 +0,0 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Setup;
use ArrayIterator;
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
*
* @var array
*/
protected $requirements = array();
/**
* Register a requirement
*
* @param object $requirement The requirement to add
*
* @return self
*/
public function add($name, $requirement)
{
$this->requirements[$name] = array_key_exists($name, $this->requirements)
? $this->combine($this->requirements[$name], $requirement)
: $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
*
* @return array
*/
public function getAll()
{
return $this->requirements;
}
/**
* Return an iterator of all registered requirements
*
* @return ArrayIterator
*/
public function getIterator()
{
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
*/
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;
}
}
return $this;
}
/**
* Return whether all mandatory requirements are fulfilled
*
* @return bool
*/
public function fulfilled()
{
foreach ($this->getAll() as $requirement) {
if ($requirement->state === static::STATE_MANDATORY) {
return false;
}
}
return true;
}
}

View File

@ -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();
}
}

View File

@ -18,7 +18,7 @@ interface SetupWizard
/**
* Return the requirements of this wizard
*
* @return Requirements
* @return RequirementSet
*/
public function getRequirements();
}

View File

@ -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
@ -111,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') {
@ -351,203 +355,159 @@ class WebWizard extends Wizard implements SetupWizard
/**
* @see SetupWizard::getRequirements()
*/
public function getRequirements()
public function getRequirements($skipModules = false)
{
$requirements = new Requirements();
$set = new RequirementSet();
$phpVersion = Platform::getPhpVersion();
$requirements->addMandatory(
'php_version_>=_5_3_2',
mt('setup', 'PHP Version'),
mt(
$set->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(
$set->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(
$set->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.')
$set->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')
$set->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(
$set->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.'
)
)));
$set->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(
$set->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(
$set->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(
$set->add(new PhpModuleRequirement(array(
'optional' => true,
'condition' => 'Imagick',
'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.')
'In case you want graphs being exported to PDF as well, you\'ll need the ImageMagick extension for PHP.'
)
);
)));
$requirements->addOptional(
'existing_php_mod_pdo_pgsql',
mt('setup', 'PHP Module: PDO-PostgreSQL'),
mt(
$mysqlSet = new RequirementSet(true);
$mysqlSet->add(new PhpModuleRequirement(array(
'optional' => true,
'condition' => 'mysql',
'alias' => 'PDO-MySQL',
'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.')
'To store users or preferences in a MySQL database the PDO-MySQL module for PHP is required.'
)
);
$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.')
)));
$mysqlSet->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.'
)
);
)));
$set->merge($mysqlSet);
$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.')
$pgsqlSet = new RequirementSet(true);
$pgsqlSet->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.'
)
);
)));
$pgsqlSet->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.'
)
)));
$set->merge($pgsqlSet);
$configDir = Icinga::app()->getConfigDir();
$requirements->addMandatory(
'writable_directory_' . $configDir,
mt('setup', 'Writable Config Directory'),
mt(
$set->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());
if (! $skipModules) {
foreach ($this->getWizards() as $wizard) {
$set->merge($wizard->getRequirements());
}
}
return $requirements;
return $set;
}
}

View File

@ -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();
}
}

View File

@ -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,58 @@
}
}
#setup table.requirements {
form#setup_requirements {
margin-top: 2em;
padding-top: 0.5em;
border-top: 2px solid @colorPetrol;
div.buttons div.requirements-refresh {
width: 25%;
float: right;
text-align: center;
a.button-like {
padding: 0.1em 0.4em;
}
}
}
#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;
@ -201,24 +235,6 @@
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;
}
}
}
}
}
}