From 41c44486854295ec26e358cdcb8766086a0fd30d Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 13 Oct 2016 19:04:30 +0000 Subject: [PATCH 1/6] QuickTable: provide multiselect feature refs #11614 --- library/Director/Web/Table/QuickTable.php | 47 ++++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/library/Director/Web/Table/QuickTable.php b/library/Director/Web/Table/QuickTable.php index 3c707111..94fd2de5 100644 --- a/library/Director/Web/Table/QuickTable.php +++ b/library/Director/Web/Table/QuickTable.php @@ -67,6 +67,41 @@ abstract class QuickTable implements Paginatable } } + protected function getMultiselectProperties() + { + /* array( + * 'url' => 'director/hosts/edit', + * 'sourceUrl' => 'director/hosts', + * 'keys' => 'name' + * ) */ + + return array(); + } + + protected function renderMultiselectAttributes() + { + $props = $this->getMultiselectProperties(); + + if (empty($props)) { + return ''; + } + + $prefix = 'data-icinga-multiselect-'; + $view = $this->view(); + $parts = array(); + $multi = array( + 'url' => $view->href($props['url']), + 'controllers' => $view->href($props['sourceUrl']), + 'data' => implode(',', $props['keys']), + ); + + foreach ($multi as $k => $v) { + $parts[] = $prefix . $k . '="' . $v . '"'; + } + + return ' ' . implode(' ', $parts); + } + protected function renderRow($row) { $htm = " getRowClassesString($row) . ">\n"; @@ -245,14 +280,22 @@ abstract class QuickTable implements Paginatable protected function listTableClasses() { - return array('simple', 'common-table', 'table-row-selectable'); + $classes = array('simple', 'common-table', 'table-row-selectable'); + if (! empty($this->getMultiselectProperties())) { + $classes[] = 'multiselect'; + } + + return $classes; } public function render() { $data = $this->fetchData(); - $htm = 'createClassAttribute($this->listTableClasses()) . '>' . "\n" + $htm = 'createClassAttribute($this->listTableClasses()) + . $this->renderMultiselectAttributes() + . '>' . "\n" . $this->renderTitles($this->getTitles()) . "\n"; foreach ($data as $row) { From 732ebae3392e64afee1f0ec7000016ccc63366c8 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 13 Oct 2016 19:06:15 +0000 Subject: [PATCH 2/6] ActionController: provide a singleTab helper --- library/Director/Web/Controller/ActionController.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/library/Director/Web/Controller/ActionController.php b/library/Director/Web/Controller/ActionController.php index aa6c859d..d1de32c6 100644 --- a/library/Director/Web/Controller/ActionController.php +++ b/library/Director/Web/Controller/ActionController.php @@ -90,6 +90,17 @@ abstract class ActionController extends Controller $this->sendJson((object) array('error' => $message)); } + protected function singleTab($label) + { + return $this->view->tabs = Widget::create('tabs')->add( + 'tab', + array( + 'label' => $label, + 'url' => $this->getRequest()->getUrl() + ) + )->activate('tab'); + } + protected function setConfigTabs() { $this->view->tabs = Widget::create('tabs')->add( From 69237b8ae9609b4b0a778210d46465975866cfee Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 13 Oct 2016 19:07:41 +0000 Subject: [PATCH 3/6] MultiEditForm: first shot, provides imports refs #12465 --- application/forms/IcingaMultiEditForm.php | 194 ++++++++++++++++++ application/tables/IcingaHostTable.php | 9 + .../Web/Controller/ObjectsController.php | 23 +++ 3 files changed, 226 insertions(+) create mode 100644 application/forms/IcingaMultiEditForm.php diff --git a/application/forms/IcingaMultiEditForm.php b/application/forms/IcingaMultiEditForm.php new file mode 100644 index 00000000..35d8638e --- /dev/null +++ b/application/forms/IcingaMultiEditForm.php @@ -0,0 +1,194 @@ +objects = $objects; + return $this; + } + + public function setup() + { + $this->addImportsElements();//->setButtons(); + } + + public function onSuccess() + { +/* +echo '
';
+print_r($this->getVariants('imports'));
+print_r($this->getValues());
+echo '
'; +*/ + foreach ($this->getValues() as $key => $value) { + $parts = preg_split('/_/', $key); + $objectsSum = array_pop($parts); + $valueSum = array_pop($parts); + $property = implode('_', $parts); +//printf("Got %s: %s -> %s
", $property, $valueSum, $objectsSum); + + $found = false; + foreach ($this->getVariants($property) as $json => $objects) { + if ($valueSum !== sha1($json)) { + continue; + } + + if ($objectsSum !== sha1(json_encode($objects))) { + continue; + } + + $found = true; + + foreach ($this->getObjects($objects) as $object) { + $object->$property = $value; + } + } + } + + $modified = 0; + foreach ($this->objects as $object) { + if ($object->hasBeenModified()) { + $modified++; + $object->store(); + } + } + + if ($modified === 0) { + $this->setSuccessMessage($this->translate('No object has been modified')); + } elseif ($modified === 1) { + $this->setSuccessMessage($this->translate('One object has been modified')); + } else { + $this->setSuccessMessage( + sprintf( + $this->translate('%d objects have been modified'), + $modified + ) + ); + } + + parent::onSuccess(); + } + + protected function getVariants($key) + { + $variants = array(); + foreach ($this->objects as $name => $object) { + $value = json_encode($object->$key); + if (! array_key_exists($value, $variants)) { + $variants[$value] = array(); + } + + $variants[$value][] = $name; + } + + foreach ($variants as & $objects) { + natsort($objects); + } + + return $variants; + } + + protected function descriptionForObjects($list) + { + return sprintf( + $this->translate('Changing this value affects %d object(s): %s'), + count($list), + implode(', ', $list) + ); + } + + protected function labelCount($list) + { + return ' (' . count($list) . ')'; + } + + protected function addImportsElements() + { + $enum = $this->enumTemplates(); + if (empty($enum)) { + return $this; + } + + foreach ($this->getVariants('imports') as $json => $objects) { + $value = json_decode($json); + $checksum = sha1($json) . '_' . sha1(json_encode($objects)); + $this->addElement('extensibleSet', 'imports_' . $checksum, array( + 'label' => $this->translate('Imports') . $this->labelCount($objects), + 'description' => $this->translate( + 'Importable templates, add as many as you want. Please note that order' + . ' matters when importing properties from multiple templates: last one' + . ' wins' + ) . '. ' . $this->descriptionForObjects($objects), + 'required' => !$this->object()->isTemplate(), + 'multiOptions' => $this->optionallyAddFromEnum($enum), + 'value' => $value, + 'sorted' => true, + 'class' => 'autosubmit' + )); + } + + return $this; + } + + public function optionallyAddFromEnum($enum) + { + return array( + null => $this->translate('- click to add more -') + ) + $enum; + } + + protected function enumTemplates() + { + $object = $this->object(); + $tpl = $this->db()->enumIcingaTemplates($object->getShortTableName()); + if (empty($tpl)) { + return array(); + } + + if (empty($tpl)) { + return array(); + } + + $tpl = array_combine($tpl, $tpl); + return $tpl; + } + + protected function db() + { + if ($this->db === null) { + $this->db = $this->object()->getConnection(); + } + + return $this->db; + } + + protected function getObjects($names) + { + $res = array(); + foreach ($names as $name) { + $res[$name] = $this->objects[$name]; + } + + return $res; + } + + protected function object() + { + if ($this->object === null) { + $this->object = current($this->objects); + } + + return $this->object; + } +} diff --git a/application/tables/IcingaHostTable.php b/application/tables/IcingaHostTable.php index 799d4ddf..f6054358 100644 --- a/application/tables/IcingaHostTable.php +++ b/application/tables/IcingaHostTable.php @@ -34,6 +34,15 @@ class IcingaHostTable extends IcingaObjectTable return $this->url('director/host', array('name' => $row->host)); } + protected function getMultiselectProperties() + { + return array( + 'url' => 'director/hosts/edit', + 'sourceUrl' => 'director/hosts', + 'keys' => array('name'), + ); + } + public function getTitles() { $view = $this->view(); diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php index 1941c576..edec93be 100644 --- a/library/Director/Web/Controller/ObjectsController.php +++ b/library/Director/Web/Controller/ObjectsController.php @@ -194,6 +194,29 @@ abstract class ObjectsController extends ActionController $this->setViewScript('objects/table'); } + public function editAction() + { + $this->singleTab($this->translate('Multiple objects')); + $filter = Filter::fromQueryString($this->params->toString()); + $dummy = $this->dummyObject(); + $objects = array(); + $db = $this->db(); + foreach ($filter->filters() as $sub) { + foreach ($sub->filters() as $ex) { + if ($ex->isExpression() && $ex->getColumn() === 'name') { + $name = $ex->getExpression(); + $objects[$name] = $dummy::load($name, $db); + } + } + } + $this->view->title = sprintf($this->translate('Modify %d objects'), count($objects)); + $this->view->form = $this->loadForm('IcingaMultiEdit') + ->setObjects($objects) + ->handleRequest(); + + $this->setViewScript('objects/form'); + } + public function templatesAction() { $this->indexAction(); From 856e574c26e55bcf2b8dd535188ade563571e0ef Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 13 Oct 2016 22:06:01 +0000 Subject: [PATCH 4/6] js: fix extensible set for mass edit Honestly, I have no idea why this check came in. Needs more investigation. One far day, works for now. --- public/js/module.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/public/js/module.js b/public/js/module.js index 76b1543d..aaf3c767 100644 --- a/public/js/module.js +++ b/public/js/module.js @@ -99,15 +99,16 @@ extensibleSetAction: function(ev) { var el = ev.currentTarget; - if (el.name.match(/__MOVE_UP$/)) { var $li = $(el).closest('li'); - var $prev = $li.prev() + var $prev = $li.prev(); + // TODO: document what's going on here. if ($li.find('input[type=text].autosubmit')) { if (iid = $prev.find('input[type=text]').attr('id')) { $li.closest('.container').data('activeExtensibleEntry', iid); + } else { + return true; } - return true; } if ($prev.length) { $prev.before($li.detach()); @@ -118,12 +119,14 @@ return false; } else if (el.name.match(/__MOVE_DOWN$/)) { var $li = $(el).closest('li'); - var $next = $li.next() + var $next = $li.next(); + // TODO: document what's going on here. if ($li.find('input[type=text].autosubmit')) { if (iid = $next.find('input[type=text]').attr('id')) { $li.closest('.container').data('activeExtensibleEntry', iid); + } else { + return true; } - return true; } if ($next.length && ! $next.find('.extend-set').length) { $next.after($li.detach()); From 6a54e00402f0fafe93c8b755d77137a736253fa0 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 14 Oct 2016 17:17:07 +0000 Subject: [PATCH 5/6] MultiEdit: code cleanup, add custom var support fixes #12465 fixes #12906 fixes #11614 --- application/forms/IcingaMultiEditForm.php | 163 +++++++----- .../Director/Web/Form/DirectorObjectForm.php | 6 +- .../Web/Form/IcingaObjectFieldLoader.php | 236 ++++++++++++------ 3 files changed, 264 insertions(+), 141 deletions(-) diff --git a/application/forms/IcingaMultiEditForm.php b/application/forms/IcingaMultiEditForm.php index 35d8638e..3de689ab 100644 --- a/application/forms/IcingaMultiEditForm.php +++ b/application/forms/IcingaMultiEditForm.php @@ -2,41 +2,59 @@ namespace Icinga\Module\Director\Forms; -use Icinga\Module\Director\Web\Form\QuickForm; +use Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader; +use Icinga\Module\Director\Web\Form\DirectorObjectForm; +use Zend_Form_Element as ZfElement; -class IcingaMultiEditForm extends QuickForm +class IcingaMultiEditForm extends DirectorObjectForm { private $objects; - private $object; - - private $db; + private $elementGroupMap; public function setObjects($objects) { $this->objects = $objects; + $this->object = current($this->objects); + $this->db = $this->object()->getConnection(); return $this; } public function setup() { - $this->addImportsElements();//->setButtons(); + $object = $this->object; + $this->addImportsElement(); + // $this->addDisabledElement(); + + $loader = new IcingaObjectFieldLoader($object); + $loader->addFieldsToForm($this); + + $this->makeVariants($this->getElement('imports')); + // $this->makeVariants($this->getElement('disabled')); + foreach ($this->getElements() as $el) { + $name =$el->getName(); + if (substr($name, 0, 4) === 'var_') { + $this->makeVariants($el); + } + } + + $this->setButtons(); + } + + /** + * No default objects behaviour + */ + protected function onRequest() + { } public function onSuccess() { -/* -echo '
';
-print_r($this->getVariants('imports'));
-print_r($this->getValues());
-echo '
'; -*/ foreach ($this->getValues() as $key => $value) { $parts = preg_split('/_/', $key); $objectsSum = array_pop($parts); $valueSum = array_pop($parts); $property = implode('_', $parts); -//printf("Got %s: %s -> %s
", $property, $valueSum, $objectsSum); $found = false; foreach ($this->getVariants($property) as $json => $objects) { @@ -49,6 +67,9 @@ echo ''; } $found = true; + if (substr($property, 0, 4) === 'var_') { + $property = 'vars.' . substr($property, 4); + } foreach ($this->getObjects($objects) as $object) { $object->$property = $value; @@ -65,24 +86,80 @@ echo ''; } if ($modified === 0) { - $this->setSuccessMessage($this->translate('No object has been modified')); + $msg = $this->translate('No object has been modified'); } elseif ($modified === 1) { - $this->setSuccessMessage($this->translate('One object has been modified')); + $msg = $this->translate('One object has been modified'); } else { - $this->setSuccessMessage( - sprintf( - $this->translate('%d objects have been modified'), - $modified - ) + $msg = sprintf( + $this->translate('%d objects have been modified'), + $modified ); } - parent::onSuccess(); + $this->redirectOnSuccess($msg); + } + + protected function getDisplayGroupForElement(ZfElement $element) + { + if ($this->elementGroupMap === null) { + $this->resolveDisplayGroups(); + } + + $name = $element->getName(); + if (array_key_exists($name, $this->elementGroupMap)) { + return $this->getDisplayGroup($this->elementGroupMap[$name]); + } else { + return null; + } + } + + protected function resolveDisplayGroups() + { + $this->elementGroupMap = array(); + + foreach ($this->getDisplayGroups() as $group) { + $groupName = $group->getName(); + foreach ($group->getElements() as $name => $e) { + $this->elementGroupMap[$name] = $groupName; + } + } + } + + protected function makeVariants(ZfElement $element) + { + if (! $element) { + return $this; + } + + $key = $element->getName(); + $this->removeElement($key); + $label = $element->getLabel(); + $group = $this->getDisplayGroupForElement($element); + $description = $element->getDescription(); + + foreach ($this->getVariants($key) as $json => $objects) { + $value = json_decode($json); + $checksum = sha1($json) . '_' . sha1(json_encode($objects)); + + $v = clone($element); + $v->setName($key . '_' . $checksum); + $v->setDescription($description . '. ' . $this->descriptionForObjects($objects)); + $v->setLabel($label . $this->labelCount($objects)); + $v->setValue($value); + if ($group) { + $group->addElement($v); + } + $this->addElement($v); + } } protected function getVariants($key) { $variants = array(); + if (substr($key, 0, 4) === 'var_') { + $key = 'vars.' . substr($key, 4); + } + foreach ($this->objects as $name => $object) { $value = json_encode($object->$key); if (! array_key_exists($value, $variants)) { @@ -113,41 +190,6 @@ echo ''; return ' (' . count($list) . ')'; } - protected function addImportsElements() - { - $enum = $this->enumTemplates(); - if (empty($enum)) { - return $this; - } - - foreach ($this->getVariants('imports') as $json => $objects) { - $value = json_decode($json); - $checksum = sha1($json) . '_' . sha1(json_encode($objects)); - $this->addElement('extensibleSet', 'imports_' . $checksum, array( - 'label' => $this->translate('Imports') . $this->labelCount($objects), - 'description' => $this->translate( - 'Importable templates, add as many as you want. Please note that order' - . ' matters when importing properties from multiple templates: last one' - . ' wins' - ) . '. ' . $this->descriptionForObjects($objects), - 'required' => !$this->object()->isTemplate(), - 'multiOptions' => $this->optionallyAddFromEnum($enum), - 'value' => $value, - 'sorted' => true, - 'class' => 'autosubmit' - )); - } - - return $this; - } - - public function optionallyAddFromEnum($enum) - { - return array( - null => $this->translate('- click to add more -') - ) + $enum; - } - protected function enumTemplates() { $object = $this->object(); @@ -182,13 +224,4 @@ echo ''; return $res; } - - protected function object() - { - if ($this->object === null) { - $this->object = current($this->objects); - } - - return $this->object; - } } diff --git a/library/Director/Web/Form/DirectorObjectForm.php b/library/Director/Web/Form/DirectorObjectForm.php index 69b505bc..5ad71c06 100644 --- a/library/Director/Web/Form/DirectorObjectForm.php +++ b/library/Director/Web/Form/DirectorObjectForm.php @@ -257,7 +257,11 @@ abstract class DirectorObjectForm extends QuickForm protected function handleCustomVars($object, & $values) { if ($this->assertResolvedImports()) { - IcingaObjectFieldLoader::addFieldsToForm($this, $object, $values); + $loader = new IcingaObjectFieldLoader($object); + $loader->addFieldsToForm($this); + if ($values) { + $loader->setValues($values, 'var_'); + } } } diff --git a/library/Director/Web/Form/IcingaObjectFieldLoader.php b/library/Director/Web/Form/IcingaObjectFieldLoader.php index 5076dac5..640c2417 100644 --- a/library/Director/Web/Form/IcingaObjectFieldLoader.php +++ b/library/Director/Web/Form/IcingaObjectFieldLoader.php @@ -2,10 +2,11 @@ namespace Icinga\Module\Director\Web\Form; +use stdClass; use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Objects\IcingaServiceSet; use Icinga\Module\Director\Objects\DirectorDatafield; -use stdClass; +use Zend_Form_Element as ZfElement; class IcingaObjectFieldLoader { @@ -13,95 +14,172 @@ class IcingaObjectFieldLoader protected $object; - protected function __construct(DirectorObjectForm $form, IcingaObject $object) + protected $fields; + + protected $elements; + + public function __construct(IcingaObject $object) { - $this->form = $form; $this->object = $object; } - public static function addFieldsToForm(DirectorObjectForm $form, IcingaObject $object, & $values) + public function addFieldsToForm(QuickForm $form) { - if (! $object->supportsCustomVars()) { - return $form; + if ($this->object->supportsCustomVars()) { + $this->attachFieldsToForm($form); } - $loader = new static($form, $object); - $loader->addFields(); - if ($values !== null) { - $loader->setValues($loader->stripKeyPrefix($values, 'var_')); - } - - return $form; + return $this; } - protected function stripKeyPrefix($array, $prefix) + /** + * Set a list of values + * + * Works in a failsafe way, when a field does not exist the value will be + * silently ignored + * + * @param Array $values key/value pairs with variable names and their value + * @param String $prefix An optional prefix that would be stripped from keys + * + * @return self + */ + public function setValues($values, $prefix = null) { - $new = array(); - $len = strlen($prefix); - foreach ($array as $key => $value) { - if (substr($key, 0, $len) === $prefix) { - $new[substr($key, $len)] = $value; - } + if ($prefix !== null) { + $len = strlen($prefix); } - - 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) { + if ($prefix) { + if (substr($key, 0, $len) === $prefix) { + $key = substr($key, $len); + } else { continue; } + } + + if ($el = $this->getElement($key)) { $el->setValue($value); - $vars->set($key, $el->getValue()); + $value = $el->getValue(); + + if ($value === '') { + $value = null; + } + + $vars->set($key, $value); + } + } + + return $this; + } + + /** + * Get the fields for our object + * + * @return DirectorDatafield[] + */ + public function getFields() + { + if ($this->fields === null) { + $this->fields = $this->prepareObjectFields($this->object); + } + + return $this->fields; + } + + /** + * Get the form elements for our fields + * + * @param QuickForm $form Optional + * + * @return ZfElement[] + */ + public function getElements(QuickForm $form = null) + { + if ($this->elements === null) { + $this->elements = $this->createElements($form); + $this->setValuesFromObject($this->object); + } + + return $this->elements; + } + + /** + * Attach our form fields to the given form + * + * This will also create a 'Custom properties' display group + */ + protected function attachFieldsToForm(QuickForm $form) + { + $elements = $this->getElements($form); + + if (! empty($elements)) { + $form->addElementsToGroup( + $elements, + 'custom_fields', + 50, + $form->translate('Custom properties') + ); + } + } + + /** + * Get the form element for a specific field by it's variable name + * + * @return ZfElement|null + */ + protected function getElement($name) + { + $elements = $this->getElements(); + if (array_key_exists($name, $elements)) { + return $this->elements[$name]; + } + + return null; + } + + /** + * Get the form elements based on the given form + * + * @return ZfElement[] + */ + protected function createElements(QuickForm $form) + { + $elements = array(); + + foreach ($this->getFields() as $name => $field) { + $elements[$name] = $field->getFormElement($form); + } + + return $elements; + } + + protected function setValuesFromObject(IcingaObject $object) + { + foreach ($object->getVars() as $k => $v) { + if ($v !== null && $el = $this->getElement($k)) { + $el->setValue($v); } } } - protected function addFields() + protected function mergeFields($listOfFields) { - $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') - ); + // TODO: Merge field for different object, mostly sets } + /** + * Create the fields for our object + * + * + * @return DirectorDatafield[] + */ protected function prepareObjectFields($object) { $fields = $this->loadResolvedFieldsForObject($object); - - if ($object->hasProperty('command_id')) { - $command = $object->getResolvedRelated('command'); + if ($object->hasRelation('check_command')) { + $command = $object->getResolvedRelated('check_command'); if ($command) { $cmdFields = $this->loadResolvedFieldsForObject($command); foreach ($cmdFields as $varname => $field) { @@ -115,11 +193,14 @@ class IcingaObjectFieldLoader return $fields; } - protected function mergeFields($listOfFields) - { - // TODO: Merge field for different object, mostly sets - } - + /** + * Create the fields for our object + * + * Follows the inheritance logic, resolves all fields and keeps the most + * specific ones. Returns a list of fields indexed by variable name + * + * @return DirectorDatafield[] + */ protected function loadResolvedFieldsForObject($object) { $result = $this->loadDataFieldsForObjects( @@ -139,11 +220,16 @@ class IcingaObjectFieldLoader return $fields; } - protected function getDb() - { - return $this->form->getDb(); - } - + /** + * Fetches fields for a given List of objects from the database + * + * Gives a list indexed by object id, with each entry being a list of that + * objects DirectorDatafield instances indexed by variable name + * + * @param IcingaObject[] $objectList List of objects + * + * @return Array + */ protected function loadDataFieldsForObjects($objectList) { $ids = array(); @@ -163,7 +249,7 @@ class IcingaObjectFieldLoader $db = $connection->getDbAdapter(); $idColumn = 'f.' . $object->getShortTableName() . '_id'; - + $query = $db->select()->from( array('df' => 'director_datafield'), array( @@ -193,7 +279,7 @@ class IcingaObjectFieldLoader if (! array_key_exists($id, $result)) { $result[$id] = new stdClass; } - + $result[$id]->{$r->varname} = DirectorDatafield::fromDbRow( $r, $connection From 23ef9a707c3798f46da0344c189f593088ba0dab Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 14 Oct 2016 18:32:34 +0000 Subject: [PATCH 6/6] MultiEdit: make it independent of an object type --- application/controllers/HostsController.php | 4 + application/forms/IcingaMultiEditForm.php | 175 +++++++++++------- .../Web/Controller/ObjectsController.php | 17 +- .../Director/Web/Form/DirectorObjectForm.php | 2 +- 4 files changed, 130 insertions(+), 68 deletions(-) diff --git a/application/controllers/HostsController.php b/application/controllers/HostsController.php index 2d677d67..8fe23a09 100644 --- a/application/controllers/HostsController.php +++ b/application/controllers/HostsController.php @@ -6,4 +6,8 @@ use Icinga\Module\Director\Web\Controller\ObjectsController; class HostsController extends ObjectsController { + protected $multiEdit = array( + 'imports', + 'groups' + ); } diff --git a/application/forms/IcingaMultiEditForm.php b/application/forms/IcingaMultiEditForm.php index 3de689ab..e45e6ddb 100644 --- a/application/forms/IcingaMultiEditForm.php +++ b/application/forms/IcingaMultiEditForm.php @@ -2,8 +2,10 @@ namespace Icinga\Module\Director\Forms; +use Icinga\Module\Director\Web\Form\FormLoader; use Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader; use Icinga\Module\Director\Web\Form\DirectorObjectForm; +use Icinga\Module\Director\Web\Form\QuickForm; use Zend_Form_Element as ZfElement; class IcingaMultiEditForm extends DirectorObjectForm @@ -12,6 +14,10 @@ class IcingaMultiEditForm extends DirectorObjectForm private $elementGroupMap; + private $relatedForm; + + private $propertiesToPick; + public function setObjects($objects) { $this->objects = $objects; @@ -20,19 +26,36 @@ class IcingaMultiEditForm extends DirectorObjectForm return $this; } + public function pickElementsFrom(QuickForm $form, $properties) + { + $this->relatedForm = $form; + $this->propertiesToPick = $properties; + return $this; + } + public function setup() { $object = $this->object; - $this->addImportsElement(); - // $this->addDisabledElement(); $loader = new IcingaObjectFieldLoader($object); $loader->addFieldsToForm($this); - $this->makeVariants($this->getElement('imports')); - // $this->makeVariants($this->getElement('disabled')); + if ($form = $this->relatedForm) { + $form->setDb($object->getConnection()) + ->setObject($object) + ->prepareElements(); + } else { + $this->propertiesToPick = array(); + } + + foreach ($this->propertiesToPick as $property) { + if ($el = $form->getElement($property)) { + $this->makeVariants($el); + } + } + foreach ($this->getElements() as $el) { - $name =$el->getName(); + $name = $el->getName(); if (substr($name, 0, 4) === 'var_') { $this->makeVariants($el); } @@ -41,50 +64,13 @@ class IcingaMultiEditForm extends DirectorObjectForm $this->setButtons(); } - /** - * No default objects behaviour - */ - protected function onRequest() - { - } - public function onSuccess() { foreach ($this->getValues() as $key => $value) { - $parts = preg_split('/_/', $key); - $objectsSum = array_pop($parts); - $valueSum = array_pop($parts); - $property = implode('_', $parts); - - $found = false; - foreach ($this->getVariants($property) as $json => $objects) { - if ($valueSum !== sha1($json)) { - continue; - } - - if ($objectsSum !== sha1(json_encode($objects))) { - continue; - } - - $found = true; - if (substr($property, 0, 4) === 'var_') { - $property = 'vars.' . substr($property, 4); - } - - foreach ($this->getObjects($objects) as $object) { - $object->$property = $value; - } - } - } - - $modified = 0; - foreach ($this->objects as $object) { - if ($object->hasBeenModified()) { - $modified++; - $object->store(); - } + $this->setSubmittedMultiValue($key, $value); } + $modified = $this->storeModifiedObjects(); if ($modified === 0) { $msg = $this->translate('No object has been modified'); } elseif ($modified === 1) { @@ -99,6 +85,54 @@ class IcingaMultiEditForm extends DirectorObjectForm $this->redirectOnSuccess($msg); } + /** + * No default objects behaviour + */ + protected function onRequest() + { + } + + protected function setSubmittedMultiValue($key, $value) + { + $parts = preg_split('/_/', $key); + $objectsSum = array_pop($parts); + $valueSum = array_pop($parts); + $property = implode('_', $parts); + + $found = false; + foreach ($this->getVariants($property) as $json => $objects) { + if ($valueSum !== sha1($json)) { + continue; + } + + if ($objectsSum !== sha1(json_encode($objects))) { + continue; + } + + $found = true; + if (substr($property, 0, 4) === 'var_') { + $property = 'vars.' . substr($property, 4); + } + + foreach ($this->getObjects($objects) as $object) { + $object->$property = $value; + } + } + } + + protected function storeModifiedObjects() + { + $modified = 0; + foreach ($this->objects as $object) { + if ($object->hasBeenModified()) { + $modified++; + $object->store(); + } + } + + return $modified; + } + protected function getDisplayGroupForElement(ZfElement $element) { if ($this->elementGroupMap === null) { @@ -107,17 +141,46 @@ class IcingaMultiEditForm extends DirectorObjectForm $name = $element->getName(); if (array_key_exists($name, $this->elementGroupMap)) { - return $this->getDisplayGroup($this->elementGroupMap[$name]); + $groupName = $this->elementGroupMap[$name]; + + if ($group = $this->getDisplayGroup($groupName)) { + return $group; + } elseif ($this->relatedForm) { + return $this->stealDisplayGroup($groupName, $this->relatedForm); + } } else { return null; } } + protected function stealDisplayGroup($name, $form) + { + if ($group = $this->relatedForm->getDisplayGroup($name)) { + $group = clone($group); + $group->setElements(array()); + $this->_displayGroups[$name] = $group; + $this->_order[$name] = $this->_displayGroups[$name]->getOrder(); + $this->_orderUpdated = true; + + return $group; + } + + return null; + } + protected function resolveDisplayGroups() { $this->elementGroupMap = array(); + if ($form = $this->relatedForm) { + $this->extractFormDisplayGroups($form, true); + } - foreach ($this->getDisplayGroups() as $group) { + $this->extractFormDisplayGroups($this); + } + + protected function extractFormDisplayGroups($form, $clone = false) + { + foreach ($form->getDisplayGroups() as $group) { $groupName = $group->getName(); foreach ($group->getElements() as $name => $e) { $this->elementGroupMap[$name] = $groupName; @@ -127,10 +190,6 @@ class IcingaMultiEditForm extends DirectorObjectForm protected function makeVariants(ZfElement $element) { - if (! $element) { - return $this; - } - $key = $element->getName(); $this->removeElement($key); $label = $element->getLabel(); @@ -190,22 +249,6 @@ class IcingaMultiEditForm extends DirectorObjectForm return ' (' . count($list) . ')'; } - protected function enumTemplates() - { - $object = $this->object(); - $tpl = $this->db()->enumIcingaTemplates($object->getShortTableName()); - if (empty($tpl)) { - return array(); - } - - if (empty($tpl)) { - return array(); - } - - $tpl = array_combine($tpl, $tpl); - return $tpl; - } - protected function db() { if ($this->db === null) { diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php index edec93be..79fbac09 100644 --- a/library/Director/Web/Controller/ObjectsController.php +++ b/library/Director/Web/Controller/ObjectsController.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Web\Controller; +use Icinga\Exception\NotFoundError; use Icinga\Data\Filter\Filter; use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Web\Table\IcingaObjectTable; @@ -12,6 +13,8 @@ abstract class ObjectsController extends ActionController protected $isApified = true; + protected $multiEdit = array(); + protected $globalTypes = array( 'ApiUser', 'Zone', @@ -196,6 +199,13 @@ abstract class ObjectsController extends ActionController public function editAction() { + $type = ucfirst($this->getType()); + + if (empty($this->multiEdit)) { + throw new NotFoundError('Cannot edit multiple "%s" instances', $type); + } + $formName = 'icinga' . $type; + $this->singleTab($this->translate('Multiple objects')); $filter = Filter::fromQueryString($this->params->toString()); $dummy = $this->dummyObject(); @@ -209,9 +219,14 @@ abstract class ObjectsController extends ActionController } } } - $this->view->title = sprintf($this->translate('Modify %d objects'), count($objects)); + $this->view->title = sprintf( + $this->translate('Modify %d objects'), + count($objects) + ); + $this->view->form = $this->loadForm('IcingaMultiEdit') ->setObjects($objects) + ->pickElementsFrom($this->loadForm($formName), $this->multiEdit) ->handleRequest(); $this->setViewScript('objects/form'); diff --git a/library/Director/Web/Form/DirectorObjectForm.php b/library/Director/Web/Form/DirectorObjectForm.php index 5ad71c06..08eb2452 100644 --- a/library/Director/Web/Form/DirectorObjectForm.php +++ b/library/Director/Web/Form/DirectorObjectForm.php @@ -457,7 +457,7 @@ abstract class DirectorObjectForm extends QuickForm { $this->object = $object; if ($this->db === null) { - $this->setDb($db); + $this->setDb($object->getConnection()); } return $this;