diff options
Diffstat (limited to 'application/forms/SyncPropertyForm.php')
-rw-r--r-- | application/forms/SyncPropertyForm.php | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/application/forms/SyncPropertyForm.php b/application/forms/SyncPropertyForm.php new file mode 100644 index 0000000..720237e --- /dev/null +++ b/application/forms/SyncPropertyForm.php @@ -0,0 +1,444 @@ +<?php + +namespace Icinga\Module\Director\Forms; + +use Exception; +use Icinga\Module\Director\Hook\ImportSourceHook; +use Icinga\Module\Director\Objects\SyncProperty; +use Icinga\Module\Director\Objects\SyncRule; +use Icinga\Module\Director\Objects\IcingaObject; +use Icinga\Module\Director\Objects\ImportSource; +use Icinga\Module\Director\Web\Form\DirectorObjectForm; + +class SyncPropertyForm extends DirectorObjectForm +{ + /** + * @var SyncRule + */ + private $rule; + + /** @var ImportSource */ + private $importSource; + + /** @var ImportSourceHook */ + private $importSourceHook; + + private $dummyObject; + + const EXPRESSION = '__EXPRESSION__'; + + /** + * @throws \Zend_Form_Exception + */ + public function setup() + { + $this->addHidden('rule_id', $this->rule->get('id')); + + $this->addElement('select', 'source_id', array( + 'label' => $this->translate('Source Name'), + 'multiOptions' => $this->enumImportSource(), + 'required' => true, + 'class' => 'autosubmit', + )); + if (! $this->hasObject() && ! $this->getSentValue('source_id')) { + return; + } + + $this->addElement('select', 'destination_field', array( + 'label' => $this->translate('Destination Field'), + 'multiOptions' => $this->optionalEnum($this->listDestinationFields()), + 'required' => true, + 'class' => 'autosubmit', + )); + + if ($this->getSentValue('destination_field')) { + $destination = $this->getSentValue('destination_field'); + } elseif ($this->hasObject()) { + $destination = $this->getObject()->destination_field; + } else { + return; + } + + $isCustomvar = substr($destination, 0, 5) === 'vars.'; + + if ($isCustomvar) { + $varname = substr($destination, 5); + $this->addElement('text', 'customvar', array( + 'label' => $this->translate('Custom variable'), + 'required' => true, + 'ignore' => true, + )); + + if ($varname !== '*') { + $this->setElementValue('destination_field', 'vars.*'); + $this->setElementValue('customvar', $varname); + if ($this->hasObject()) { + $this->getObject()->destination_field = 'vars.*'; + } + } + } + + $this->addSourceColumnElement($destination); + + $this->addElement('YesNo', 'use_filter', array( + 'label' => $this->translate('Set based on filter'), + 'ignore' => true, + 'class' => 'autosubmit', + 'required' => true, + )); + + if ($this->hasBeenSent()) { + $useFilter = $this->getSentValue('use_filter'); + if ($useFilter === null) { + $this->setElementValue('use_filter', $useFilter = 'n'); + } + } else { + $expression = $this->getObject()->filter_expression; + $useFilter = ($expression === null || strlen($expression) === 0) ? 'n' : 'y'; + $this->setElementValue('use_filter', $useFilter); + } + + if ($useFilter === 'y') { + $this->addElement('text', 'filter_expression', array( + 'label' => $this->translate('Filter Expression'), + 'description' => $this->translate( + 'This allows to filter for specific parts within the given source expression.' + . ' You are allowed to refer all imported columns. Examples: host=www* would' + . ' set this property only for rows imported with a host property starting' + . ' with "www". Complex example: host=www*&!(address=127.*|address6=::1)' + ), + 'required' => true, + // TODO: validate filter + )); + } + + if ($isCustomvar || $destination === 'vars') { + $this->addElement('select', 'merge_policy', array( + 'label' => $this->translate('Merge Policy'), + 'description' => $this->translate( + 'Whether you want to merge or replace the destination field.' + . ' Makes no difference for strings' + ), + 'required' => true, + 'multiOptions' => $this->optionalEnum(array( + 'merge' => 'merge', + 'override' => 'replace' + )) + )); + } else { + $this->addHidden('merge_policy', 'override'); + } + + $this->setButtons(); + } + + protected function hasSubOption($options, $key) + { + foreach ($options as $mainKey => $sub) { + if (! is_array($sub)) { + // null -> please choose - or similar + continue; + } + + if (array_key_exists($key, $sub)) { + return true; + } + } + + return false; + } + + /** + * @param $destination + * @return $this + * @throws \Zend_Form_Exception + */ + protected function addSourceColumnElement($destination) + { + $error = false; + + $srcTitle = $this->translate('Source columns'); + try { + $columns[$srcTitle] = $this->listSourceColumns(); + natsort($columns[$srcTitle]); + } catch (Exception $e) { + $srcTitle .= sprintf(' (%s)', $this->translate('failed to fetch')); + $columns[$srcTitle] = array(); + $error = sprintf( + $this->translate('Unable to fetch data: %s'), + $e->getMessage() + ); + } + + if ($destination === 'import') { + $this->addIcingaTempateColumns($columns); + } elseif ($destination === 'list_id') { + $this->addDatalistsColumns($columns); + } + + $xpTitle = $this->translate('Expert mode'); + $columns[$xpTitle][self::EXPRESSION] = $this->translate('Custom expression'); + + $this->addElement('select', 'source_column', array( + 'label' => $this->translate('Source Column'), + 'multiOptions' => $this->optionalEnum($columns), + 'required' => true, + 'ignore' => true, + 'class' => 'autosubmit', + )); + + if ($error) { + $this->getElement('source_column')->addError($error); + } + + $showExpression = false; + + if ($this->hasBeenSent()) { + $sentValue = $this->getSentValue('source_column'); + if ($sentValue === self::EXPRESSION) { + $showExpression = true; + } + } elseif ($this->hasObject()) { + $objectValue = $this->getObject()->source_expression; + if ($this->hasSubOption($columns, $objectValue)) { + $this->setElementValue('source_column', $objectValue); + } else { + $this->setElementValue('source_column', self::EXPRESSION); + $showExpression = true; + } + } + + if ($showExpression) { + $this->addElement('text', 'source_expression', array( + 'label' => $this->translate('Source Expression'), + 'description' => $this->translate( + 'A custom string. Might contain source columns, please use placeholders' + . ' of the form ${columnName} in such case. Structured data sources' + . ' can be referenced as ${columnName.sub.key}' + ), + 'required' => true, + )); + } + + + return $this; + } + + protected function addIcingaTempateColumns(&$columns) + { + $funcTemplates = 'enum' . ucfirst($this->rule->get('object_type')) . 'Templates'; + if (method_exists($this->db, $funcTemplates)) { + $templates = $this->db->$funcTemplates(); + if (! empty($templates)) { + $templates = array_combine($templates, $templates); + } + + $title = $this->translate('Existing templates'); + $columns[$title] = $templates; + natsort($columns[$title]); + } + } + + protected function addDatalistsColumns(&$columns) + { + // Clear other columns, we don't allow them right now + $columns = []; + $db = $this->db->getDbAdapter(); + $enum = $db->fetchPairs( + $db->select()->from('director_datalist', ['id', 'list_name'])->order('list_name') + ); + + $columns[$this->translate('Existing Data Lists')] = $enum; + } + + protected function enumImportSource() + { + $sources = $this->db->enumImportSource(); + $usedIds = $this->rule->listInvolvedSourceIds(); + if (empty($usedIds)) { + return $this->optionalEnum($sources); + } + $usedSources = array(); + foreach ($usedIds as $id) { + $usedSources[$id] = $sources[$id]; + unset($sources[$id]); + } + + if (empty($sources)) { + return $this->optionalEnum($usedSources); + } + + return $this->optionalEnum( + array( + $this->translate('Used sources') => $usedSources, + $this->translate('Other sources') => $sources + ) + ); + } + + /** + * @return array + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\NotFoundError + */ + protected function listSourceColumns() + { + $columns = array(); + $source = $this->getImportSource(); + $hook = $this->getImportSourceHook(); + foreach ($hook->listColumns() as $col) { + $columns['${' . $col . '}'] = $col; + } + + foreach ($source->listModifierTargetProperties() as $property) { + $columns['${' . $property . '}'] = $property; + } + + return $columns; + } + + protected function listDestinationFields() + { + $props = []; + $special = []; + $dummy = $this->dummyObject(); + + if ($dummy instanceof IcingaObject) { + if ($dummy->supportsCustomVars()) { + $special['vars.*'] = $this->translate('Custom variable (vars.)'); + $special['vars'] = $this->translate('All custom variables (vars)'); + } + if ($dummy->supportsImports()) { + $special['import'] = $this->translate('Inheritance (import)'); + } + if ($dummy->supportsArguments()) { + $special['arguments'] = $this->translate('Arguments'); + } + if ($dummy->supportsGroups()) { + $special['groups'] = $this->translate('Group membership'); + } + if ($dummy->supportsRanges()) { + $special['ranges'] = $this->translate('Time ranges'); + } + } + + foreach ($dummy->listProperties() as $prop) { + if ($dummy instanceof IcingaObject && $prop === 'id') { + continue; + } + + // TODO: allow those fields, but munge them (store ids) + //if (preg_match('~_id$~', $prop)) continue; + if (substr($prop, -3) === '_id') { + $short = substr($prop, 0, -3); + if ($dummy instanceof IcingaObject) { + if ($dummy->hasRelation($short)) { + $prop = $short; + } else { + continue; + } + } + } + + $props[$prop] = $prop; + } + + if ($dummy instanceof IcingaObject) { + foreach ($dummy->listMultiRelations() as $prop) { + $props[$prop] = sprintf('%s (%s)', $prop, $this->translate('a list')); + } + } + + ksort($props); + + $result = []; + if (! empty($special)) { + $result[$this->translate('Special properties')] = $special; + } + if (! empty($props)) { + $result[$this->translate('Object properties')] = $props; + } + + return $result; + } + + /** + * @return ImportSource + * @throws \Icinga\Exception\NotFoundError + */ + protected function getImportSource() + { + if ($this->importSource === null) { + if ($this->hasObject()) { + $id = (int) $this->object->get('source_id'); + } else { + $id = (int) $this->getSentValue('source_id'); + } + $this->importSource = ImportSource::loadWithAutoIncId($id, $this->db); + } + + return $this->importSource; + } + + /** + * @return ImportSourceHook + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\NotFoundError + */ + protected function getImportSourceHook() + { + if ($this->importSourceHook === null) { + $this->importSourceHook = ImportSourceHook::loadByName( + $this->getImportSource()->get('source_name'), + $this->db + ); + } + + return $this->importSourceHook; + } + + public function onSuccess() + { + /** @var SyncProperty $object */ + $object = $this->getObject(); + $object->set('rule_id', $this->rule->get('id')); // ?! + + if ($this->getValue('use_filter') === 'n') { + $object->set('filter_expression', null); + } + + $sourceColumn = $this->getValue('source_column'); + $this->removeElement('source_column'); + + if ($sourceColumn !== self::EXPRESSION) { + $object->set('source_expression', $sourceColumn); + } + + $destination = $this->getValue('destination_field'); + if ($destination === 'vars.*') { + $destination = $this->getValue('customvar'); + $object->set('destination_field', 'vars.' . $destination); + } + + return parent::onSuccess(); + } + + protected function dummyObject() + { + if ($this->dummyObject === null) { + $this->dummyObject = IcingaObject::createByType( + $this->rule->get('object_type'), + array(), + $this->db + ); + } + + return $this->dummyObject; + } + + public function setRule(SyncRule $rule) + { + $this->rule = $rule; + return $this; + } +} |