From 0d930efb46b69a8f3bc26a204229cd76a644cd89 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 13 Oct 2016 07:54:31 +0000 Subject: [PATCH 1/2] IcingaObject: add new resolving helper methods This for example allows to get a single inherited var and their origin --- library/Director/Objects/IcingaObject.php | 46 ++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/library/Director/Objects/IcingaObject.php b/library/Director/Objects/IcingaObject.php index 6f0f0d7f..d68a0305 100644 --- a/library/Director/Objects/IcingaObject.php +++ b/library/Director/Objects/IcingaObject.php @@ -421,7 +421,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer $this->resolveUnresolvedRelatedProperties(); if ($this->supportsCustomVars() && $this->vars !== null && $this->vars()->hasBeenModified()) { -//var_dump($this->vars()); exit; return true; } @@ -798,6 +797,51 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer return $default; } + public function getInheritedProperty($key, $default = null) + { + if (array_key_exists($key, $this->unresolvedRelatedProperties)) { + $this->resolveUnresolvedRelatedProperty($key); + $this->invalidateResolveCache(); + } + + $properties = $this->getInheritedProperties(); + if (property_exists($properties, $key)) { + return $properties->$key; + } + + return $default; + } + + public function getInheritedVar($varname) + { + try { + $vars = $this->getInheritedVars(); + } catch (NestingError $e) { + return null; + } + + if (property_exists($vars, $varname)) { + return $vars->$varname; + } else { + return null; + } + } + + public function getOriginForVar($varname) + { + try { + $origins = $this->getOriginsVars(); + } catch (NestingError $e) { + return null; + } + + if (property_exists($origins, $varname)) { + return $origins->$varname; + } else { + return null; + } + } + public function getResolvedProperties() { return $this->getResolved('Properties'); From 913c2687b6dc3cd6f6a80cd943292ee77f1d7150 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 13 Oct 2016 07:59:34 +0000 Subject: [PATCH 2/2] IcingaObjectFieldLoader: first implementation Should already replace all former functionality refs #12904 --- .../Director/Objects/DirectorDatafield.php | 59 ++++ .../Director/Web/Form/DirectorObjectForm.php | 306 ++---------------- .../Web/Form/IcingaObjectFieldLoader.php | 204 ++++++++++++ 3 files changed, 284 insertions(+), 285 deletions(-) create mode 100644 library/Director/Web/Form/IcingaObjectFieldLoader.php diff --git a/library/Director/Objects/DirectorDatafield.php b/library/Director/Objects/DirectorDatafield.php index bc4fcb4e..994a3e1a 100644 --- a/library/Director/Objects/DirectorDatafield.php +++ b/library/Director/Objects/DirectorDatafield.php @@ -3,6 +3,8 @@ namespace Icinga\Module\Director\Objects; use Icinga\Module\Director\Data\Db\DbObjectWithSettings; +use Icinga\Module\Director\Db; +use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Web\Form\QuickBaseForm; class DirectorDatafield extends DbObjectWithSettings @@ -26,6 +28,38 @@ class DirectorDatafield extends DbObjectWithSettings protected $settingsRemoteId = 'datafield_id'; + private $required = false; + + private $object; + + public static function fromDbRow($row, Db $connection) + { + $obj = static::create((array) $row, $connection); + $obj->loadedFromDb = true; + // TODO: $obj->setUnmodified(); + $obj->hasBeenModified = false; + $obj->modifiedProperties = array(); + + // TODO: eventually prefetch + $obj->onLoadFromDb(); + return $obj; + } + + protected function setObject(IcingaObject $object) + { + $this->object = $object; + } + + protected function getObject() + { + return $this->object; + } + + protected function setRequired($value) + { + $this->required = (bool) $value; + } + public function getFormElement(QuickBaseForm $form, $name = null) { $className = $this->datatype; @@ -54,6 +88,31 @@ class DirectorDatafield extends DbObjectWithSettings $el->setDescription($this->description); } + $this->applyObjectData($el, $form); + return $el; } + + protected function applyObjectData($el, $form) + { + $object = $form->getObject(); + if ($object instanceof IcingaObject) { + if ($object->isTemplate()) { + $el->setRequired(false); + } + + $varname = $this->varname; + + $form->setInheritedValue( + $el, + $object->getInheritedVar($varname), + $object->getOriginForVar($varname) + ); + + } else { + if ($this->required) { + $el->setRequired(true); + } + } + } } diff --git a/library/Director/Web/Form/DirectorObjectForm.php b/library/Director/Web/Form/DirectorObjectForm.php index 5dbb3771..485390b1 100644 --- a/library/Director/Web/Form/DirectorObjectForm.php +++ b/library/Director/Web/Form/DirectorObjectForm.php @@ -88,21 +88,22 @@ abstract class DirectorObjectForm extends QuickForm $object->imports = $el->getValue(); } } - $object->resolveUnresolvedRelatedProperties(); + return $this; } - protected function isObject() + public function isObject() { return $this->getSentOrObjectValue('object_type') === 'object'; } - protected function isTemplate() + public function isTemplate() { return $this->getSentOrObjectValue('object_type') === 'template'; } + // TODO: move to a subform protected function handleRanges($object, & $values) { if (! $object->supportsRanges()) { @@ -129,27 +130,6 @@ abstract class DirectorObjectForm extends QuickForm foreach ($object->ranges()->getRanges() as $key => $value) { $this->addRange($key, $value); } - - /* - // TODO implement when new logic is there - $this->addElement('simpleNote', '_newrange_hint', array('label' => 'New range')); - $this->addElement('text', '_newrange_name', array( - 'label' => 'Name' - )); - $this->addElement('text', '_newrange_value', array( - 'label' => 'Value' - )); - */ - } - - protected function addToFieldsDisplayGroup($elements) - { - return $this->addElementsToGroup( - $elements, - 'custom_fields', - 50, - $this->translate('Custom properties') - ); } protected function addToCheckExecutionDisplayGroup($elements) @@ -162,17 +142,7 @@ abstract class DirectorObjectForm extends QuickForm ); } - protected function addToCommandFieldsDisplayGroup($elements) - { - return $this->addElementsToGroup( - $elements, - 'command_fields', - 55, - $this->translate('Command-specific custom vars') - ); - } - - protected function addElementsToGroup($elements, $group, $order, $legend = null) + public function addElementsToGroup($elements, $group, $order, $legend = null) { if (! is_array($elements)) { $elements = array($elements); @@ -291,216 +261,19 @@ abstract class DirectorObjectForm extends QuickForm } foreach ($props as $k => $v) { + $this->setElementValue($k, $v); if ($k !== 'object_name' && property_exists($inherited, $k)) { - $this->setElementValue($k, $v, $inherited->$k, $origins->$k); - if ($el = $this->getElement($k)) { - $el->setRequired(false); + $el = $this->getElement($k); + if ($el) { + $this->setInheritedValue($el, $inherited->$k, $origins->$k); } - } else { - $this->setElementValue($k, $v); } } } protected function handleCustomVars($object, & $values) { - if (! $object->supportsCustomVars()) { - return; - } - - try { - $fields = $object->getResolvedFields(); - $inherits = $object->getInheritedVars(); - $origins = $object->getOriginsVars(); - } catch (NestingError $e) { - $fields = $object->getFields(); - $inherits = (object) array(); - $origins = (object) array(); - } - try { - if ($object->hasCheckCommand()) { - $checkCommand = $object->getCheckCommand(); - $checkFields = $checkCommand->getResolvedFields(); - $checkVars = $checkCommand->getResolvedVars(); - } else { - $checkFields = (object) array(); - } - } catch (NestingError $e) { - $checkFields = (object) array(); - } - - if ($this->hasBeenSent()) { - $vars = array(); - $handled = array(); - $newvar = array( - 'type' => 'string', - 'name' => null, - 'value' => null, - ); - - foreach ($values as $key => $value) { - - if (substr($key, 0, 4) === 'var_') { - if (substr($key, -6) === '___ADD') { - continue; - } - - $mykey = substr($key, 4); - - // Get value through form element. - // TODO: reorder the related code. Create elements once - foreach (array($fields, $checkFields) as $fieldSet) { - if (property_exists($fieldSet, $mykey)) { - $field = $fieldSet->$mykey; - $datafield = DirectorDatafield::load($field->datafield_id, $this->getDb()); - $el = $datafield->getFormElement($this); - $value = $el->setValue($value)->getValue(); - } - } - - if (property_exists($fields, $mykey) && $fields->$mykey->format === 'json') { - $value = json_decode($value); - } - - if (property_exists($checkFields, $mykey) && $checkFields->$mykey->format === 'json') { - $value = json_decode($value); - } - - $vars[$mykey] = $value; - $handled[$key] = true; - } -/* - if (substr($key, 0, 8) === '_newvar_') { - $newvar[substr($key, 8)] = $value; - $handled[$key] = true; - } -*/ - } - - foreach ($vars as $k => $v) { - if ($v === '' || $v === null) { - unset($object->vars()->$k); - } else { - $object->vars()->$k = $v; - } - } - - if ($newvar['name'] && $newvar['value']) { - $object->vars()->{$newvar['name']} = $newvar['value']; - } - - foreach ($handled as $key) { - unset($values[$key]); - } - } - - $vars = $object->getVars(); - - foreach ($fields as $field) { - $varname = $field->varname; - - // Get value from the related varname if set: - if (property_exists($vars, $varname)) { - $value = $vars->$varname; - } else { - $value = null; - } - - if (property_exists($inherits, $varname)) { - $inheritedValue = $inherits->$varname; - $inheritFrom = $origins->$varname; - if ($inheritFrom === $object->object_name) { - $inherited = false; - } else { - $inherited = true; - } - } else { - $inheritedValue = null; - $inheritFrom = false; - $inherited = false; - } - - $this->addField($field, $value, $inheritedValue, $inheritFrom); - } - - foreach ($checkFields as $field) { - $varname = $field->varname; - if (property_exists($vars, $varname)) { - $value = $vars->$varname; - } else { - $value = null; - } - if (property_exists($checkVars, $varname)) { - $inheritedValue = $checkVars->$varname; - $inheritFrom = $this->translate('check command'); - } else { - $inheritedValue = null; - $inheritFrom = false; - } - - // Command vars are overridden at object level: - if (property_exists($inherits, $varname)) { - $inheritedValue = $inherits->$varname; - $inheritFrom = $origins->$varname; - if ($inheritFrom === $object->object_name) { - $inherited = false; - } else { - $inherited = true; - } - } - - $this->addCommandField($field, $value, $inheritedValue, $inheritFrom); - if ($inheritedValue !== null) { - $this->getElement('var_' . $field->varname)->setRequired(false); - } - } - - // TODO Define whether admins are allowed to set those - /* - // Additional vars - foreach ($vars as $key => $value) { - // Did we already create a field for this var? Then skip it: - if (array_key_exists($key, $fields)) { - continue; - } - if (array_key_exists($key, $checkFields)) { - continue; - } - - // TODO: handle structured vars - if (! is_string($value)) { - continue; - } - - // Show inheritance information in case we inherited this var: - if (isset($inherited->$key)) { - $this->addCustomVar($key, $value, $inherited->$key, $origins->$key); - } else { - $this->addCustomVar($key, $value); - } - - } - */ - - if (/* TODO: add free var */false && $object->isTemplate()) { - $this->addElement('text', '_newvar_name', array( - 'label' => 'Add var' - )); - $this->addElement('text', '_newvar_value', array( - 'label' => 'Value' - )); - $this->addElement('select', '_newvar_format', array( - 'label' => 'Type', - 'multiOptions' => array('string' => $this->translate('String')) - )); - $this->addToFieldsDisplayGroup( - array( - '_newvar_name', - '_newvar_value', - '_newvar_format', - ) - ); - } + IcingaObjectFieldLoader::addFieldsToForm($this, $values); } protected function isNew() @@ -561,43 +334,6 @@ abstract class DirectorObjectForm extends QuickForm return $this; } - // TODO: unify addField and addCommandField. Do they need to differ? - protected function addField($field, $value = null, $inherited = null, $inheritedFrom = null) - { - $datafield = DirectorDatafield::load($field->datafield_id, $this->getDb()); - $el = $datafield->getFormElement($this); - - if ($field->is_required === 'y' && ! $this->isTemplate() && $inherited === null) { - $el->setRequired(true); - } - - $this->addElement($el); - $this->addToFieldsDisplayGroup($el); - if (! $el->hasErrors()) { - $this->setElementValue($el->getName(), $value, $inherited, $inheritedFrom); - } - - return $el; - } - - protected function addCommandField($field, $value = null, $inherited = null, $inheritedFrom = null) - { - $datafield = DirectorDatafield::load($field->datafield_id, $this->getDb()); - $el = $datafield->getFormElement($this); - - if ($field->is_required === 'y' && ! $this->isTemplate() && $inherited === null) { - $el->setRequired(true); - } - - $this->addElement($el); - $this->addToCommandFieldsDisplayGroup($el); - if (! $el->hasErrors()) { - $this->setElementValue($el->getName(), $value, $inherited, $inheritedFrom); - } - - return $el; - } - protected function setSentValue($name, $value) { if ($this->hasBeenSent()) { @@ -610,18 +346,24 @@ abstract class DirectorObjectForm extends QuickForm return $this->setElementValue($name, $value); } - protected function setElementValue($name, $value = null, $inherited = null, $inheritedFrom = null) + public function setElementValue($name, $value = null) { $el = $this->getElement($name); if (! $el) { + // Not showing an error, as most object properties do not exist. Not + // yet, because IMO this should be checked. + // $this->addError(sprintf($this->translate('Form element "%s" does not exist'), $name)); return; } if ($value !== null) { $el->setValue($value); } + } - if ($inherited === null || empty($inherited)) { + public function setInheritedValue($el, $inherited, $inheritedFrom) + { + if ($inherited === null) { return; } @@ -642,6 +384,9 @@ abstract class DirectorObjectForm extends QuickForm $el->setAttrib('placeholder', $inherited . sprintf($txtInherited, $inheritedFrom)); } } + + // We inherited a value, so no need to require the field + $el->setRequired(false); } public function setListUrl($url) @@ -1031,15 +776,6 @@ abstract class DirectorObjectForm extends QuickForm return $this; } - protected function addCustomVar($key, $value, $inherited = null, $inheritedFrom = null) - { - $label = 'vars.' . $key; - $key = 'var_' . $key; - $this->addElement('text', $key, array('label' => $label)); - $this->setElementValue($key, $value, $inherited, $inheritedFrom); - $this->addToFieldsDisplayGroup($key); - } - protected function addRange($key, $range) { $this->addElement('text', 'range_' . $key, array( diff --git a/library/Director/Web/Form/IcingaObjectFieldLoader.php b/library/Director/Web/Form/IcingaObjectFieldLoader.php new file mode 100644 index 00000000..cd3ce093 --- /dev/null +++ b/library/Director/Web/Form/IcingaObjectFieldLoader.php @@ -0,0 +1,204 @@ +form = $form; + $this->object = $form->getObject(); + } + + public static function addFieldsToForm(DirectorObjectForm $form, & $values) + { + if (! $form->getObject()->supportsCustomVars()) { + return $form; + } + + $loader = new static($form); + $loader->addFields(); + if ($values !== null) { + $loader->setValues($loader->stripKeyPrefix($values, 'var_')); + } + + return $form; + } + + protected function stripKeyPrefix($array, $prefix) + { + $new = array(); + $len = strlen($prefix); + foreach ($array as $key => $value) { + if (substr($key, 0, $len) === $prefix) { + $new[substr($key, $len)] = $value; + } + } + + return $new; + } + + protected function setValues($values) + { + $vars = $this->object->vars(); + $form = $this->form; + + foreach ($values as $key => $value) { + if ($el = $form->getElement('var_' . $key)) { + if ($value === '' || $value === null) { + continue; + } + $el->setValue($value); + $vars->set($key, $el->getValue()); + } + } + } + + protected function addFields() + { + $object = $this->object; + if ($object instanceof IcingaServiceSet) { + } else { + $this->attachFields( + $this->prepareObjectFields($object) + ); + } + + $this->setValues($object->getVars()); + } + + protected function attachFields($fields) + { + $form = $this->form; + $elements = array(); + foreach ($fields as $field) { + $elements[] = $field->getFormElement($form); + } + + if (empty($elements)) { + return $this; + } + + return $form->addElementsToGroup( + $elements, + 'custom_fields', + 50, + $form->translate('Custom properties') + ); + } + + protected function prepareObjectFields($object) + { + $fields = $this->loadResolvedFieldsForObject($object); + + if ($object->hasProperty('command_id')) { + $command = $object->getResolvedRelated('command'); + if ($command) { + $cmdFields = $this->loadResolvedFieldsForObject($command); + foreach ($cmdFields as $varname => $field) { + if (! array_key_exists($varname, $fields)) { + $fields[$varname] = $field; + } + } + } + } + + return $fields; + } + + protected function mergeFields($listOfFields) + { + // TODO: Merge field for different object, mostly sets + } + + protected function loadResolvedFieldsForObject($object) + { + $result = $this->loadDataFieldsForObjects( + array_merge( + $object->templateResolver()->fetchResolvedParents(), + array($object) + ) + ); + + $fields = array(); + foreach ($result as $objectId => $varFields) { + foreach ($varFields as $var => $field) { + $fields[$var] = $field; + } + } + + return $fields; + } + + protected function getDb() + { + return $this->form->getDb(); + } + + protected function loadDataFieldsForObjects($objectList) + { + if (empty($objectList)) { + // Or should we fail? + return array(); + } + + $ids = array(); + $objects = array(); + foreach ($objectList as $object) { + $ids[] = $object->id; + $objects[$object->id] = $object; + } + + $connection = $object->getConnection(); + $db = $connection->getDbAdapter(); + + $idColumn = 'f.' . $object->getShortTableName() . '_id'; + + $query = $db->select()->from( + array('df' => 'director_datafield'), + array( + 'object_id' => $idColumn, + 'is_required' => 'f.is_required', + 'id' => 'df.id', + 'varname' => 'df.varname', + 'caption' => 'df.caption', + 'description' => 'df.description', + 'datatype' => 'df.datatype', + 'format' => 'df.format', + ) + )->join( + array('f' => $object->getTableName() . '_field'), + 'df.id = f.datafield_id', + array() + )->where($idColumn . ' IN (?)', $ids) + ->order('df.caption ASC'); + + $res = $db->fetchAll($query); + + $result = array(); + foreach ($res as $r) { + $id = $r->object_id; + unset($r->object_id); + $r->object = $objects[$id]; + if (! array_key_exists($id, $result)) { + $result[$id] = new stdClass; + } + + $result[$id]->{$r->varname} = DirectorDatafield::fromDbRow( + $r, + $connection + ); + } + + return $result; + } +}