2016-02-29 12:23:08 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Icinga\Module\Director\IcingaConfig;
|
|
|
|
|
|
|
|
use Icinga\Exception\InvalidPropertyException;
|
2016-02-29 15:39:25 +01:00
|
|
|
use Icinga\Exception\ProgrammingError;
|
2016-02-29 12:38:31 +01:00
|
|
|
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
|
2016-02-29 15:39:25 +01:00
|
|
|
use Icinga\Module\Director\Objects\IcingaObject;
|
2016-02-29 12:23:08 +01:00
|
|
|
|
|
|
|
class ExtensibleSet
|
|
|
|
{
|
|
|
|
protected $ownValues;
|
|
|
|
|
|
|
|
protected $plusValues = array();
|
|
|
|
|
|
|
|
protected $minusValues = array();
|
|
|
|
|
|
|
|
protected $resolvedValues;
|
|
|
|
|
|
|
|
protected $allowedValues;
|
|
|
|
|
|
|
|
protected $inheritedValues = array();
|
|
|
|
|
2016-02-29 15:39:25 +01:00
|
|
|
protected $fromDb = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var IcingaObject
|
|
|
|
*/
|
|
|
|
protected $object;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Object property name pointing to this set
|
|
|
|
*
|
|
|
|
* This also implies set table called <object_table>_<propertyName>_set
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $propertyName;
|
|
|
|
|
2016-02-29 12:23:08 +01:00
|
|
|
public function __construct($values = null)
|
|
|
|
{
|
|
|
|
if (null !== $values) {
|
|
|
|
$this->override($values);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-29 15:39:25 +01:00
|
|
|
public static function forIcingaObject(IcingaObject $object, $propertyName)
|
|
|
|
{
|
|
|
|
$set = new static;
|
2016-02-29 17:35:41 +01:00
|
|
|
$set->object = $object;
|
|
|
|
$set->propertyName = $propertyName;
|
2016-02-29 15:39:25 +01:00
|
|
|
|
|
|
|
if ($object->hasBeenLoadedFromDb()) {
|
|
|
|
$set->loadFromDb();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $set;
|
|
|
|
}
|
|
|
|
|
2016-02-29 19:00:52 +01:00
|
|
|
public function set($set)
|
|
|
|
{
|
|
|
|
if (is_array($set) || is_string($set)) {
|
|
|
|
$this->reset();
|
|
|
|
$this->override($set);
|
|
|
|
} elseif (is_object($set)) {
|
|
|
|
$this->reset();
|
|
|
|
|
|
|
|
foreach (array('override', 'extend', 'blacklist') as $method) {
|
|
|
|
if (property_exists($set, $method)) {
|
|
|
|
$this->$method($set->$method);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new ProgrammingError(
|
|
|
|
'ExtensibleSet::set accepts only plain arrays or objects'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-02-29 15:39:25 +01:00
|
|
|
public function hasBeenLoadedFromDb()
|
|
|
|
{
|
|
|
|
return $this->fromDb !== null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function hasBeenModified()
|
|
|
|
{
|
|
|
|
if ($this->hasBeenLoadedFromDb()) {
|
2016-02-29 17:35:41 +01:00
|
|
|
if ($this->ownValues !== $this->fromDb['override']) {
|
|
|
|
return true;
|
|
|
|
}
|
2016-02-29 15:39:25 +01:00
|
|
|
|
2016-02-29 17:35:41 +01:00
|
|
|
if ($this->plusValues !== $this->fromDb['extend']) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->minusValues !== $this->fromDb['blacklist']) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2016-02-29 15:39:25 +01:00
|
|
|
} else {
|
|
|
|
if ($this->ownValues === null
|
|
|
|
&& empty($this->plusValues)
|
|
|
|
&& empty($this->minusValues)
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function loadFromDb()
|
|
|
|
{
|
|
|
|
$db = $this->object->getDb();
|
|
|
|
|
|
|
|
$query = $db->select()->from(
|
|
|
|
$this->tableName(),
|
|
|
|
array(
|
|
|
|
'property',
|
|
|
|
'merge_behaviour'
|
|
|
|
)
|
|
|
|
)->where($this->foreignKey() . ' = ?', $this->object->id);
|
|
|
|
|
|
|
|
$byBehaviour = array(
|
|
|
|
'override' => array(),
|
|
|
|
'extend' => array(),
|
|
|
|
'blacklist' => array(),
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($db->fetchAll($query) as $row) {
|
2016-02-29 17:35:41 +01:00
|
|
|
if (! array_key_exists($row->merge_behaviour, $byBehaviour)) {
|
2016-02-29 15:39:25 +01:00
|
|
|
throw new ProgrammingError(
|
|
|
|
'Got unknown merge_behaviour "%s". Schema change?',
|
|
|
|
$row->merge_behaviour
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$byBehaviour[$row->merge_behaviour][] = $row->property;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($byBehaviour as $method => &$values) {
|
2016-02-29 18:29:58 +01:00
|
|
|
if (empty($values)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-02-29 15:39:25 +01:00
|
|
|
sort($values);
|
|
|
|
$this->$method($values);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($byBehaviour['override'])) {
|
|
|
|
$byBehaviour['override'] = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->fromDb = $byBehaviour;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function foreignKey()
|
|
|
|
{
|
|
|
|
return $this->object->getShortTableName() . '_id';
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function tableName()
|
|
|
|
{
|
2016-02-29 17:35:41 +01:00
|
|
|
return implode('_', array(
|
2016-02-29 15:39:25 +01:00
|
|
|
$this->object->getTableName(),
|
|
|
|
$this->propertyName,
|
|
|
|
'set'
|
2016-02-29 17:35:41 +01:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getObject()
|
|
|
|
{
|
|
|
|
return $this->object;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function store()
|
|
|
|
{
|
|
|
|
if (null === $this->object) {
|
|
|
|
throw new ProgrammingError(
|
|
|
|
'Cannot store ExtensibleSet with no assigned object'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! $this->hasBeenModified()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->storeToDb();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function storeToDb()
|
|
|
|
{
|
|
|
|
$db = $this->object->getDb();
|
|
|
|
|
|
|
|
if ($db === null) {
|
|
|
|
throw new ProgrammingError(
|
|
|
|
'Cannot store a set for an unstored related object'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$table = $this->tableName();
|
|
|
|
$props = array(
|
|
|
|
$this->foreignKey() => $this->object->id
|
2016-02-29 15:39:25 +01:00
|
|
|
);
|
2016-02-29 17:35:41 +01:00
|
|
|
|
|
|
|
$db->delete(
|
|
|
|
$this->tableName(),
|
|
|
|
$db->quoteInto(
|
|
|
|
$this->foreignKey() . ' = ?',
|
|
|
|
$this->object->id
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($this->ownValues !== null) {
|
|
|
|
$props['merge_behaviour'] = 'override';
|
|
|
|
foreach ($this->ownValues as $value) {
|
|
|
|
$db->insert(
|
|
|
|
$table,
|
|
|
|
array_merge($props, array('property' => $value))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->fromDb['override'] = $this->ownValues;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! empty($this->plusValues)) {
|
|
|
|
$props['merge_behaviour'] = 'extend';
|
|
|
|
foreach ($this->plusValues as $value) {
|
|
|
|
$db->insert(
|
|
|
|
$table,
|
|
|
|
array_merge($props, array('property' => $value))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->fromDb['extend'] = $this->ownValues;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! empty($this->minusValues)) {
|
|
|
|
$props['merge_behaviour'] = 'blacklist';
|
|
|
|
foreach ($this->minusValues as $value) {
|
|
|
|
$db->insert(
|
|
|
|
$table,
|
|
|
|
array_merge($props, array('property' => $value))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->fromDb['blacklist'] = $this->ownValues;
|
|
|
|
}
|
2016-02-29 15:39:25 +01:00
|
|
|
}
|
|
|
|
|
2016-02-29 12:23:08 +01:00
|
|
|
public function override($values)
|
|
|
|
{
|
|
|
|
$this->ownValues = array();
|
|
|
|
$this->inheritedValues = array();
|
|
|
|
|
|
|
|
$this->addValuesTo($this->ownValues, $values);
|
|
|
|
|
|
|
|
return $this->addResolvedValues($values);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function extend($values)
|
|
|
|
{
|
|
|
|
$this->addValuesTo($this->plusValues, $values);
|
|
|
|
return $this->addResolvedValues($values);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function blacklist($values)
|
|
|
|
{
|
|
|
|
$this->addValuesTo($this->minusValues, $values);
|
|
|
|
|
|
|
|
if ($this->hasBeenResolved()) {
|
|
|
|
$this->removeValuesFrom($this->resolvedValues, $values);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getResolvedValues()
|
|
|
|
{
|
|
|
|
if (! $this->hasBeenResolved()) {
|
|
|
|
$this->recalculate();
|
|
|
|
}
|
|
|
|
|
|
|
|
sort($this->resolvedValues);
|
|
|
|
|
|
|
|
return $this->resolvedValues;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function inheritFrom(ExtensibleSet $parent)
|
|
|
|
{
|
|
|
|
if ($this->ownValues !== null) {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->hasBeenResolved()) {
|
2016-02-29 19:00:52 +01:00
|
|
|
$this->resolvedValues = null;
|
2016-02-29 12:23:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->inheritedValues = array();
|
|
|
|
|
|
|
|
$this->addValuesTo(
|
|
|
|
$this->inheritedValues,
|
|
|
|
$this->stripBlacklistedValues($parent->getResolvedValues())
|
|
|
|
);
|
|
|
|
|
|
|
|
return $this->recalculate();
|
|
|
|
}
|
|
|
|
|
2016-02-29 12:25:35 +01:00
|
|
|
public function forgetInheritedValues()
|
2016-02-29 12:23:08 +01:00
|
|
|
{
|
2016-02-29 12:25:35 +01:00
|
|
|
$this->inheritedValues = array();
|
|
|
|
return $this;
|
|
|
|
}
|
2016-02-29 12:23:08 +01:00
|
|
|
|
2016-02-29 18:30:32 +01:00
|
|
|
protected function renderArray($array)
|
|
|
|
{
|
|
|
|
$safe = array();
|
|
|
|
foreach ($array as $value) {
|
|
|
|
$safe[] = c::alreadyRendered($value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return c::renderArray($safe);
|
|
|
|
}
|
|
|
|
|
2016-02-29 12:38:31 +01:00
|
|
|
public function renderAs($key, $prefix = ' ')
|
|
|
|
{
|
|
|
|
$parts = array();
|
|
|
|
|
|
|
|
if ($this->ownValues !== null) {
|
|
|
|
$parts[] = c::renderKeyValue(
|
|
|
|
$key,
|
2016-02-29 18:30:32 +01:00
|
|
|
$this->renderArray($this->ownValues),
|
2016-02-29 12:38:31 +01:00
|
|
|
$prefix
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! empty($this->plusValues)) {
|
|
|
|
$parts[] = c::renderKeyOperatorValue(
|
|
|
|
$key,
|
|
|
|
'+=',
|
2016-02-29 18:30:32 +01:00
|
|
|
$this->renderArray($this->plusValues),
|
2016-02-29 12:38:31 +01:00
|
|
|
$prefix
|
|
|
|
);
|
|
|
|
}
|
2016-02-29 15:39:25 +01:00
|
|
|
|
2016-02-29 12:38:31 +01:00
|
|
|
if (! empty($this->minusValues)) {
|
|
|
|
$parts[] = c::renderKeyOperatorValue(
|
|
|
|
$key,
|
|
|
|
'-=',
|
2016-02-29 18:30:32 +01:00
|
|
|
$this->renderArray($this->minusValues),
|
2016-02-29 12:38:31 +01:00
|
|
|
$prefix
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode('', $parts);
|
|
|
|
}
|
|
|
|
|
2016-02-29 15:39:25 +01:00
|
|
|
public function isRestricted()
|
|
|
|
{
|
|
|
|
return $this->allowedValues === null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function enumAllowedValues()
|
|
|
|
{
|
|
|
|
if ($this->isRestricted()) {
|
|
|
|
throw new ProgrammingError(
|
|
|
|
'No allowed value set available, this set is not restricted'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_combine($this->allowedValues, $this->allowedValues);
|
|
|
|
}
|
|
|
|
|
2016-02-29 12:25:35 +01:00
|
|
|
protected function hasBeenResolved()
|
|
|
|
{
|
|
|
|
return $this->resolvedValues !== null;
|
2016-02-29 12:23:08 +01:00
|
|
|
}
|
|
|
|
|
2016-02-29 12:25:35 +01:00
|
|
|
protected function stripBlacklistedValues($array)
|
2016-02-29 12:23:08 +01:00
|
|
|
{
|
2016-02-29 12:25:35 +01:00
|
|
|
$this->removeValuesFrom($array, $this->minusValues);
|
|
|
|
|
|
|
|
return $array;
|
2016-02-29 12:23:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function assertValidValue($value)
|
|
|
|
{
|
|
|
|
if (null === $this->allowedValues) {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_array($value, $this->allowedValues)) {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new InvalidPropertyException(
|
|
|
|
'Got invalid property "%s", allowed are: (%s)',
|
|
|
|
$value,
|
|
|
|
implode(', ', $this->allowedValues)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function addValuesTo(&$array, $values)
|
|
|
|
{
|
|
|
|
foreach ($this->wantArray($values) as $value) {
|
|
|
|
$this->addTo($array, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function addResolvedValues($values)
|
|
|
|
{
|
|
|
|
if (! $this->hasBeenResolved()) {
|
|
|
|
$this->resolvedValues = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->addValuesTo(
|
|
|
|
$this->resolvedValues,
|
|
|
|
$this->stripBlacklistedValues($this->wantArray($values))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function removeValuesFrom(&$array, $values)
|
|
|
|
{
|
|
|
|
foreach ($this->wantArray($values) as $value) {
|
|
|
|
$this->removeFrom($array, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function addTo(&$array, $value)
|
|
|
|
{
|
|
|
|
if (! in_array($value, $array)) {
|
|
|
|
$this->assertValidValue($value);
|
|
|
|
$array[] = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function removeFrom(&$array, $value)
|
|
|
|
{
|
|
|
|
if (false !== ($pos = array_search($value, $array))) {
|
|
|
|
unset($array[$pos]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function recalculate()
|
|
|
|
{
|
|
|
|
$this->resolvedValues = array();
|
|
|
|
if ($this->ownValues === null) {
|
|
|
|
$this->addValuesTo($this->resolvedValues, $this->inheritedValues);
|
|
|
|
} else {
|
|
|
|
$this->addValuesTo($this->resolvedValues, $this->ownValues);
|
|
|
|
}
|
|
|
|
$this->addValuesTo($this->resolvedValues, $this->plusValues);
|
|
|
|
$this->removeFrom($this->resolvedValues, $this->minusValues);
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function reset()
|
|
|
|
{
|
2016-02-29 19:00:52 +01:00
|
|
|
$this->ownValues = null;
|
|
|
|
$this->plusValues = array();
|
|
|
|
$this->minusValues = array();
|
2016-02-29 12:23:08 +01:00
|
|
|
$this->resolvedValues = null;
|
2016-02-29 19:00:52 +01:00
|
|
|
$this->inheritedValues = array();
|
2016-02-29 12:23:08 +01:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-02-29 15:39:25 +01:00
|
|
|
protected function translate($string)
|
|
|
|
{
|
|
|
|
return mt('director', $string);
|
|
|
|
}
|
|
|
|
|
2016-02-29 12:23:08 +01:00
|
|
|
protected function wantArray($values)
|
|
|
|
{
|
|
|
|
if (is_array($values)) {
|
|
|
|
return $values;
|
|
|
|
}
|
|
|
|
|
|
|
|
return array($values);
|
|
|
|
}
|
|
|
|
}
|