Merge branch 'feature/field-loader-12904'

fixes #12904
This commit is contained in:
Thomas Gelf 2016-10-13 21:25:18 +00:00
commit 9e977eaf9a
4 changed files with 329 additions and 286 deletions

View File

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

View File

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

View File

@ -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(

View File

@ -0,0 +1,204 @@
<?php
namespace Icinga\Module\Director\Web\Form;
use Icinga\Module\Director\Exception\NestingError;
use Icinga\Module\Director\Objects\IcingaServiceSet;
use Icinga\Module\Director\Objects\DirectorDatafield;
use stdClass;
class IcingaObjectFieldLoader
{
protected $form;
protected $object;
protected function __construct(DirectorObjectForm $form)
{
$this->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;
}
}