summaryrefslogtreecommitdiffstats
path: root/library/Director/Web/Form/IcingaObjectFieldLoader.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director/Web/Form/IcingaObjectFieldLoader.php')
-rw-r--r--library/Director/Web/Form/IcingaObjectFieldLoader.php628
1 files changed, 628 insertions, 0 deletions
diff --git a/library/Director/Web/Form/IcingaObjectFieldLoader.php b/library/Director/Web/Form/IcingaObjectFieldLoader.php
new file mode 100644
index 0000000..c900edf
--- /dev/null
+++ b/library/Director/Web/Form/IcingaObjectFieldLoader.php
@@ -0,0 +1,628 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form;
+
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Director\Hook\HostFieldHook;
+use Icinga\Module\Director\Hook\ServiceFieldHook;
+use Icinga\Module\Director\Objects\DirectorDatafieldCategory;
+use Icinga\Module\Director\Objects\IcingaCommand;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\DirectorDatafield;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\ObjectApplyMatches;
+use Icinga\Web\Hook;
+use stdClass;
+use Zend_Db_Select as ZfSelect;
+use Zend_Form_Element as ZfElement;
+
+class IcingaObjectFieldLoader
+{
+ protected $form;
+
+ /** @var IcingaObject */
+ protected $object;
+
+ /** @var \Icinga\Module\Director\Db */
+ protected $connection;
+
+ /** @var \Zend_Db_Adapter_Abstract */
+ protected $db;
+
+ /** @var DirectorDatafield[] */
+ protected $fields;
+
+ protected $elements;
+
+ protected $forceNull = array();
+
+ /** @var array Map element names to variable names 'elName' => 'varName' */
+ protected $nameMap = array();
+
+ public function __construct(IcingaObject $object)
+ {
+ $this->object = $object;
+ $this->connection = $object->getConnection();
+ $this->db = $this->connection->getDbAdapter();
+ }
+
+ public function addFieldsToForm(DirectorObjectForm $form)
+ {
+ if ($this->fields || $this->object->supportsFields()) {
+ $this->attachFieldsToForm($form);
+ }
+
+ return $this;
+ }
+
+ public function loadFieldsForMultipleObjects($objects)
+ {
+ $fields = array();
+ foreach ($objects as $object) {
+ foreach ($this->prepareObjectFields($object) as $varname => $field) {
+ $varname = $field->get('varname');
+ if (array_key_exists($varname, $fields)) {
+ if ($field->get('datatype') !== $fields[$varname]->datatype) {
+ unset($fields[$varname]);
+ }
+
+ continue;
+ }
+
+ $fields[$varname] = $field;
+ }
+ }
+
+ $this->fields = $fields;
+
+ return $this;
+ }
+
+ /**
+ * Set a list of values
+ *
+ * Works in a fail-safe 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 IcingaObjectFieldLoader
+ *
+ * @throws IcingaException
+ */
+ public function setValues($values, $prefix = null)
+ {
+ if (! $this->object->supportsCustomVars()) {
+ return $this;
+ }
+
+ if ($prefix === null) {
+ $len = null;
+ } else {
+ $len = strlen($prefix);
+ }
+ $vars = $this->object->vars();
+
+ foreach ($values as $key => $value) {
+ if ($len !== null) {
+ if (substr($key, 0, $len) === $prefix) {
+ $key = substr($key, $len);
+ } else {
+ continue;
+ }
+ }
+
+ $varName = $this->getElementVarName($prefix . $key);
+ if ($varName === null) {
+ // throw new IcingaException(
+ // 'Cannot set variable value for "%s", got no such element',
+ // $key
+ // );
+
+ // Silently ignore additional fields. One might have switched
+ // template or command
+ continue;
+ }
+
+ $el = $this->getElement($varName);
+ if ($el === null) {
+ // throw new IcingaException('No such element %s', $key);
+ // Same here.
+ continue;
+ }
+
+ $el->setValue($value);
+ $value = $el->getValue();
+ if ($value === '' || $value === array()) {
+ $value = null;
+ }
+
+ $vars->set($varName, $value);
+ }
+
+ // Hint: this does currently not happen, as removeFilteredFields did not
+ // take place yet. This has been added to be on the safe side when
+ // cleaning things up one future day
+ foreach ($this->forceNull as $key) {
+ $vars->set($key, null);
+ }
+
+ 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 DirectorObjectForm $form Optional
+ *
+ * @return ZfElement[]
+ */
+ public function getElements(DirectorObjectForm $form = null)
+ {
+ if ($this->elements === null) {
+ $this->elements = $this->createElements($form);
+ $this->setValuesFromObject($this->object);
+ }
+
+ return $this->elements;
+ }
+
+ /**
+ * Prepare the form elements for our fields
+ *
+ * @param DirectorObjectForm $form Optional
+ *
+ * @return self
+ */
+ public function prepareElements(DirectorObjectForm $form = null)
+ {
+ if ($this->object->supportsFields()) {
+ $this->getElements($form);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Attach our form fields to the given form
+ *
+ * This will also create a 'Custom properties' display group
+ *
+ * @param DirectorObjectForm $form
+ */
+ protected function attachFieldsToForm(DirectorObjectForm $form)
+ {
+ if ($this->fields === null) {
+ return;
+ }
+ $elements = $this->removeFilteredFields($this->getElements($form));
+
+ foreach ($elements as $element) {
+ $form->addElement($element);
+ }
+
+ $this->attachGroupElements($elements, $form);
+ }
+
+ /**
+ * @param ZfElement[] $elements
+ * @param DirectorObjectForm $form
+ */
+ protected function attachGroupElements(array $elements, DirectorObjectForm $form)
+ {
+ $categories = [];
+ $categoriesFetchedById = [];
+ foreach ($this->fields as $key => $field) {
+ if ($id = $field->get('category_id')) {
+ if (isset($categoriesFetchedById[$id])) {
+ $category = $categoriesFetchedById[$id];
+ } else {
+ $category = DirectorDatafieldCategory::loadWithAutoIncId($id, $form->getDb());
+ $categoriesFetchedById[$id] = $category;
+ }
+ } elseif ($field->hasCategory()) {
+ $category = $field->getCategory();
+ } else {
+ continue;
+ }
+ $categories[$key] = $category;
+ }
+ $prioIdx = \array_flip(\array_keys($categories));
+
+ foreach ($elements as $key => $element) {
+ if (isset($categories[$key])) {
+ $category = $categories[$key];
+ $form->addElementsToGroup(
+ [$element],
+ 'custom_fields:' . $category->get('category_name'),
+ DirectorObjectForm::GROUP_ORDER_CUSTOM_FIELD_CATEGORIES + $prioIdx[$key],
+ $category->get('category_name')
+ );
+ } else {
+ $form->addElementsToGroup(
+ [$element],
+ 'custom_fields',
+ DirectorObjectForm::GROUP_ORDER_CUSTOM_FIELDS,
+ $form->translate('Custom properties')
+ );
+ }
+ }
+ }
+
+ /**
+ * @param ZfElement[] $elements
+ * @return ZfElement[]
+ */
+ protected function removeFilteredFields(array $elements)
+ {
+ $filters = array();
+ foreach ($this->fields as $key => $field) {
+ if ($filter = $field->var_filter) {
+ $filters[$key] = Filter::fromQueryString($filter);
+ }
+ }
+
+ $kill = array();
+ $columns = array();
+ $object = $this->object;
+ if ($object instanceof IcingaHost) {
+ $prefix = 'host.vars.';
+ } elseif ($object instanceof IcingaService) {
+ $prefix = 'service.vars.';
+ } else {
+ return $elements;
+ }
+
+ $object->invalidateResolveCache();
+ $vars = $object::fromPlainObject(
+ $object->toPlainObject(true),
+ $object->getConnection()
+ )->getVars();
+
+ $prefixedVars = (object) array();
+ foreach ($vars as $k => $v) {
+ $prefixedVars->{$prefix . $k} = $v;
+ }
+
+ foreach ($filters as $key => $filter) {
+ ObjectApplyMatches::fixFilterColumns($filter);
+ /** @var $filter FilterChain|FilterExpression */
+ foreach ($filter->listFilteredColumns() as $column) {
+ $column = substr($column, strlen($prefix));
+ $columns[$column] = $column;
+ }
+ if (! $filter->matches($prefixedVars)) {
+ $kill[] = $key;
+ }
+ }
+
+ $vars = $object->vars();
+ foreach ($kill as $key) {
+ unset($elements[$key]);
+ $this->forceNull[$key] = $key;
+ // Hint: this should happen later on, currently execution order is
+ // a little bit weird
+ $vars->set($key, null);
+ }
+
+ foreach ($columns as $col) {
+ if (array_key_exists($col, $elements)) {
+ $el = $elements[$col];
+ $existingClass = $el->getAttrib('class');
+ if ($existingClass !== null && strlen($existingClass)) {
+ $el->setAttrib('class', $existingClass . ' autosubmit');
+ } else {
+ $el->setAttrib('class', 'autosubmit');
+ }
+ }
+ }
+
+ return $elements;
+ }
+
+ protected function getElementVarName($name)
+ {
+ if (array_key_exists($name, $this->nameMap)) {
+ return $this->nameMap[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the form element for a specific field by it's variable name
+ *
+ * @param string $name
+ * @return null|ZfElement
+ */
+ 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
+ *
+ * @param DirectorObjectForm $form
+ *
+ * @return ZfElement[]
+ */
+ protected function createElements(DirectorObjectForm $form)
+ {
+ $elements = array();
+
+ foreach ($this->getFields() as $name => $field) {
+ $el = $field->getFormElement($form);
+ $elName = $el->getName();
+ if (array_key_exists($elName, $this->nameMap)) {
+ $form->addErrorMessage(sprintf(
+ 'Form element name collision, "%s" resolves to "%s", but this is also used for "%s"',
+ $name,
+ $elName,
+ $this->nameMap[$elName]
+ ));
+ }
+ $this->nameMap[$elName] = $name;
+ $elements[$name] = $el;
+ }
+
+ return $elements;
+ }
+
+ /**
+ * @param IcingaObject $object
+ */
+ protected function setValuesFromObject(IcingaObject $object)
+ {
+ foreach ($object->getVars() as $k => $v) {
+ if ($v !== null && $el = $this->getElement($k)) {
+ $el->setValue($v);
+ }
+ }
+ }
+
+ protected function mergeFields($listOfFields)
+ {
+ // TODO: Merge field for different object, mostly sets
+ }
+
+ /**
+ * Create the fields for our object
+ *
+ * @param IcingaObject $object
+ * @return DirectorDatafield[]
+ */
+ protected function prepareObjectFields($object)
+ {
+ $fields = $this->loadResolvedFieldsForObject($object);
+ if ($object->hasRelation('check_command')) {
+ try {
+ /** @var IcingaCommand $command */
+ $command = $object->getResolvedRelated('check_command');
+ } catch (Exception $e) {
+ // Ignore failures
+ $command = null;
+ }
+
+ if ($command) {
+ $cmdLoader = new static($command);
+ $cmdFields = $cmdLoader->getFields();
+ foreach ($cmdFields as $varname => $field) {
+ if (! array_key_exists($varname, $fields)) {
+ $fields[$varname] = $field;
+ }
+ }
+ }
+
+ // TODO -> filters!
+ }
+
+ return $fields;
+ }
+
+ /**
+ * 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
+ *
+ * @param IcingaObject $object
+ *
+ * @return DirectorDatafield[]
+ */
+ protected function loadResolvedFieldsForObject(IcingaObject $object)
+ {
+ $result = $this->loadDataFieldsForObject(
+ $object
+ );
+
+ $fields = array();
+ foreach ($result as $objectId => $varFields) {
+ foreach ($varFields as $var => $field) {
+ $fields[$var] = $field;
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
+ * @param IcingaObject[] $objectList List of objects
+ *
+ * @return array
+ */
+ protected function getIdsForObjectList($objectList)
+ {
+ $ids = [];
+ foreach ($objectList as $object) {
+ if ($object->hasBeenLoadedFromDb()) {
+ $ids[] = $object->get('id');
+ }
+ }
+
+ return $ids;
+ }
+
+ public function fetchFieldDetailsForObject(IcingaObject $object)
+ {
+ $ids = $object->listAncestorIds();
+ if ($id = $object->getProperty('id')) {
+ $ids[] = $id;
+ }
+ return $this->fetchFieldDetailsForIds($ids);
+ }
+
+ /***
+ * @param $objectIds
+ *
+ * @return \stdClass[]
+ */
+ protected function fetchFieldDetailsForIds($objectIds)
+ {
+ if (empty($objectIds)) {
+ return [];
+ }
+
+ $query = $this->prepareSelectForIds($objectIds);
+ return $this->db->fetchAll($query);
+ }
+
+ /**
+ * @param array $ids
+ *
+ * @return ZfSelect
+ */
+ protected function prepareSelectForIds(array $ids)
+ {
+ $object = $this->object;
+
+ $idColumn = 'f.' . $object->getShortTableName() . '_id';
+
+ $query = $this->db->select()->from(
+ array('df' => 'director_datafield'),
+ array(
+ 'object_id' => $idColumn,
+ 'icinga_type' => "('" . $object->getShortTableName() . "')",
+ 'var_filter' => 'f.var_filter',
+ 'is_required' => 'f.is_required',
+ 'id' => 'df.id',
+ 'category_id' => 'df.category_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('CASE WHEN var_filter IS NULL THEN 0 ELSE 1 END ASC')
+ ->order('df.caption ASC');
+
+ return $query;
+ }
+
+ /**
+ * Fetches fields for a given object
+ *
+ * Gives a list indexed by object id, with each entry being a list of that
+ * objects DirectorDatafield instances indexed by variable name
+ *
+ * @param IcingaObject $object
+ *
+ * @return array
+ */
+ public function loadDataFieldsForObject(IcingaObject $object)
+ {
+ $res = $this->fetchFieldDetailsForObject($object);
+
+ $result = [];
+ foreach ($res as $r) {
+ $id = $r->object_id;
+ unset($r->object_id);
+ if (! array_key_exists($id, $result)) {
+ $result[$id] = new stdClass;
+ }
+
+ $result[$id]->{$r->varname} = DirectorDatafield::fromDbRow(
+ $r,
+ $this->connection
+ );
+ }
+
+ foreach ($this->loadHookedDataFieldForObject($object) as $id => $fields) {
+ if (array_key_exists($id, $result)) {
+ foreach ($fields as $varName => $field) {
+ $result[$id]->$varName = $field;
+ }
+ } else {
+ $result[$id] = $fields;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param IcingaObject $object
+ * @return array
+ */
+ protected function loadHookedDataFieldForObject(IcingaObject $object)
+ {
+ $fields = [];
+ if ($object instanceof IcingaHost || $object instanceof IcingaService) {
+ $fields = $this->addHookedFields($object);
+ }
+
+ return $fields;
+ }
+
+ /**
+ * @param IcingaObject $object
+ * @return mixed
+ */
+ protected function addHookedFields(IcingaObject $object)
+ {
+ $fields = [];
+ /** @var HostFieldHook|ServiceFieldHook $hook */
+ $type = ucfirst($object->getShortTableName());
+ foreach (Hook::all("Director\\${type}Field") as $hook) {
+ if ($hook->wants($object)) {
+ $id = $object->get('id');
+ $spec = $hook->getFieldSpec($object);
+ if (!array_key_exists($id, $fields)) {
+ $fields[$id] = new stdClass();
+ }
+ $fields[$id]->{$spec->getVarName()} = $spec->toDataField($object);
+ }
+ }
+ return $fields;
+ }
+}