summaryrefslogtreecommitdiffstats
path: root/library/Director/Objects/IcingaObject.php
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Director/Objects/IcingaObject.php3258
1 files changed, 3258 insertions, 0 deletions
diff --git a/library/Director/Objects/IcingaObject.php b/library/Director/Objects/IcingaObject.php
new file mode 100644
index 0000000..04ae32b
--- /dev/null
+++ b/library/Director/Objects/IcingaObject.php
@@ -0,0 +1,3258 @@
+<?php
+
+namespace Icinga\Module\Director\Objects;
+
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\CustomVariable\CustomVariables;
+use Icinga\Module\Director\Data\Db\DbDataFormatter;
+use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
+use Icinga\Module\Director\IcingaConfig\AssignRenderer;
+use Icinga\Module\Director\Data\Db\DbObject;
+use Icinga\Module\Director\Db\Cache\PrefetchCache;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Exception\NestingError;
+use Icinga\Module\Director\IcingaConfig\ExtensibleSet;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\IcingaConfig\IcingaConfigRenderer;
+use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
+use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
+use Icinga\Module\Director\Repository\IcingaTemplateRepository;
+use LogicException;
+use RuntimeException;
+
+abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
+{
+ const RESOLVE_ERROR = '(unable to resolve)';
+
+ protected $keyName = 'object_name';
+
+ protected $autoincKeyName = 'id';
+
+ /** @var bool Whether this Object supports custom variables */
+ protected $supportsCustomVars = false;
+
+ /** @var bool Whether there exist Groups for this object type */
+ protected $supportsGroups = false;
+
+ /** @var bool Whether this Object makes use of (time) ranges */
+ protected $supportsRanges = false;
+
+ /** @var bool Whether inheritance via "imports" property is supported */
+ protected $supportsImports = false;
+
+ /** @var bool Allows controlled custom var access through Fields */
+ protected $supportsFields = false;
+
+ /** @var bool Whether this object can be rendered as 'apply Object' */
+ protected $supportsApplyRules = false;
+
+ /** @var bool Whether Sets of object can be defined */
+ protected $supportsSets = false;
+
+ /** @var bool Whether this Object supports template-based Choices */
+ protected $supportsChoices = false;
+
+ /** @var bool If the object is rendered in legacy config */
+ protected $supportedInLegacy = false;
+
+ protected $rangeClass;
+
+ protected $type;
+
+ /* key/value!! */
+ protected $booleans = [];
+
+ // Property suffixed with _id must exist
+ protected $relations = [
+ // property => PropertyClass
+ ];
+
+ protected $relatedSets = [
+ // property => ExtensibleSetClass
+ ];
+
+ protected $multiRelations = [
+ // property => IcingaObjectClass
+ ];
+
+ /** @var IcingaObjectMultiRelations[] */
+ protected $loadedMultiRelations = [];
+
+ /**
+ * Allows to set properties pointing to related objects by name without
+ * loading the related object.
+ *
+ * @var array
+ */
+ protected $unresolvedRelatedProperties = [];
+
+ protected $loadedRelatedSets = [];
+
+ // Will be rendered first, before imports
+ protected $prioritizedProperties = [];
+
+ protected $propertiesNotForRendering = [
+ 'id',
+ 'object_name',
+ 'object_type',
+ ];
+
+ /**
+ * Array of interval property names
+ *
+ * Those will be automagically munged to integers (seconds) and rendered
+ * as durations (e.g. 2m 10s). Array expects (propertyName => renderedKey)
+ *
+ * @var array
+ */
+ protected $intervalProperties = [];
+
+ /** @var Db */
+ protected $connection;
+
+ private $vars;
+
+ /** @var IcingaObjectGroups */
+ private $groups;
+
+ private $imports;
+
+ /** @var IcingaTimePeriodRanges - TODO: generic ranges */
+ private $ranges;
+
+ private $shouldBeRemoved = false;
+
+ private $resolveCache = [];
+
+ private $cachedPlainUnmodified;
+
+ private $templateResolver;
+
+ protected static $tree;
+
+ /**
+ * @return Db
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ public function propertyIsBoolean($property)
+ {
+ return array_key_exists($property, $this->booleans);
+ }
+
+ public function propertyIsInterval($property)
+ {
+ return array_key_exists($property, $this->intervalProperties);
+ }
+
+ /**
+ * Whether a property ends with _id and might refer another object
+ *
+ * @param $property string Property name, like zone_id
+ *
+ * @return bool
+ */
+ public function propertyIsRelation($property)
+ {
+ if ($key = $this->stripIdSuffix($property)) {
+ return $this->hasRelation($key);
+ }
+
+ return false;
+ }
+
+ protected function stripIdSuffix($key)
+ {
+ $end = substr($key, -3);
+
+ if ('_id' === $end) {
+ return substr($key, 0, -3);
+ }
+
+ return false;
+ }
+
+ public function propertyIsRelatedSet($property)
+ {
+ return array_key_exists($property, $this->relatedSets);
+ }
+
+ public function propertyIsMultiRelation($property)
+ {
+ return array_key_exists($property, $this->multiRelations);
+ }
+
+ public function listMultiRelations()
+ {
+ return array_keys($this->multiRelations);
+ }
+
+ public function getMultiRelation($property)
+ {
+ if (! $this->hasLoadedMultiRelation($property)) {
+ $this->loadMultiRelation($property);
+ }
+
+ return $this->loadedMultiRelations[$property];
+ }
+
+ public function setMultiRelation($property, $values)
+ {
+ $this->getMultiRelation($property)->set($values);
+ return $this;
+ }
+
+ private function loadMultiRelation($property)
+ {
+ if ($this->hasBeenLoadedFromDb()) {
+ $rel = IcingaObjectMultiRelations::loadForStoredObject(
+ $this,
+ $property,
+ $this->multiRelations[$property]
+ );
+ } else {
+ $rel = new IcingaObjectMultiRelations(
+ $this,
+ $property,
+ $this->multiRelations[$property]
+ );
+ }
+
+ $this->loadedMultiRelations[$property] = $rel;
+ }
+
+ private function hasLoadedMultiRelation($property)
+ {
+ return array_key_exists($property, $this->loadedMultiRelations);
+ }
+
+ private function loadAllMultiRelations()
+ {
+ foreach (array_keys($this->multiRelations) as $key) {
+ if (! $this->hasLoadedMultiRelation($key)) {
+ $this->loadMultiRelation($key);
+ }
+ }
+
+ ksort($this->loadedMultiRelations);
+ return $this->loadedMultiRelations;
+ }
+
+ protected function getRelatedSetClass($property)
+ {
+ $prefix = '\\Icinga\\Module\\Director\\IcingaConfig\\';
+ return $prefix . $this->relatedSets[$property];
+ }
+
+ /**
+ * @param $property
+ * @return ExtensibleSet
+ */
+ protected function getRelatedSet($property)
+ {
+ if (! array_key_exists($property, $this->loadedRelatedSets)) {
+ /** @var ExtensibleSet $class */
+ $class = $this->getRelatedSetClass($property);
+ $this->loadedRelatedSets[$property]
+ = $class::forIcingaObject($this, $property);
+ }
+
+ return $this->loadedRelatedSets[$property];
+ }
+
+ /**
+ * @return ExtensibleSet[]
+ */
+ protected function relatedSets()
+ {
+ $sets = [];
+ foreach ($this->relatedSets as $key => $class) {
+ $sets[$key] = $this->getRelatedSet($key);
+ }
+
+ return $sets;
+ }
+
+ /**
+ * Whether the given property name is a short name for a relation
+ *
+ * This might be 'zone' for 'zone_id'
+ *
+ * @param string $property Property name
+ *
+ * @return bool
+ */
+ public function hasRelation($property)
+ {
+ return array_key_exists($property, $this->relations);
+ }
+
+ protected function getRelationClass($property)
+ {
+ return __NAMESPACE__ . '\\' . $this->relations[$property];
+ }
+
+ protected function getRelationObjectClass($property)
+ {
+ return $this->relations[$property];
+ }
+
+ /**
+ * @param $property
+ * @return IcingaObject
+ */
+ public function getRelated($property)
+ {
+ return $this->getRelatedObject($property, $this->{$property . '_id'});
+ }
+
+ /**
+ * @param $property
+ * @param $id
+ * @return string
+ */
+ public function getRelatedObjectName($property, $id)
+ {
+ return $this->getRelatedObject($property, $id)->getObjectName();
+ }
+
+ /**
+ * @param $property
+ * @param $id
+ * @return IcingaObject
+ */
+ protected function getRelatedObject($property, $id)
+ {
+ /** @var IcingaObject $class */
+ $class = $this->getRelationClass($property);
+ try {
+ $object = $class::loadWithAutoIncId($id, $this->connection);
+ } catch (NotFoundError $e) {
+ throw new RuntimeException($e->getMessage(), 0, $e);
+ }
+
+ return $object;
+ }
+
+ /**
+ * @param $property
+ * @return IcingaObject|null
+ */
+ public function getResolvedRelated($property)
+ {
+ $id = $this->getSingleResolvedProperty($property . '_id');
+
+ if ($id) {
+ return $this->getRelatedObject($property, $id);
+ }
+
+ return null;
+ }
+
+ public function prefetchAllRelatedTypes()
+ {
+ foreach (array_unique(array_values($this->relations)) as $relClass) {
+ /** @var static $class */
+ $class = __NAMESPACE__ . '\\' . $relClass;
+ $class::prefetchAll($this->getConnection());
+ }
+ }
+
+ public static function prefetchAllRelationsByType($type, Db $db)
+ {
+ /** @var static $class */
+ $class = DbObjectTypeRegistry::classByType($type);
+ /** @var static $dummy */
+ $dummy = $class::create([], $db);
+ $dummy->prefetchAllRelatedTypes();
+ }
+
+ /**
+ * Whether this Object supports custom variables
+ *
+ * @return bool
+ */
+ public function supportsCustomVars()
+ {
+ return $this->supportsCustomVars;
+ }
+
+ /**
+ * Whether there exist Groups for this object type
+ *
+ * @return bool
+ */
+ public function supportsGroups()
+ {
+ return $this->supportsGroups;
+ }
+
+ /**
+ * Whether this Object makes use of (time) ranges
+ *
+ * @return bool
+ */
+ public function supportsRanges()
+ {
+ return $this->supportsRanges;
+ }
+
+ /**
+ * Whether this object supports (command) Arguments
+ *
+ * @return bool
+ */
+ public function supportsArguments()
+ {
+ return $this instanceof ObjectWithArguments;
+ }
+
+ /**
+ * Whether this object supports inheritance through the "imports" property
+ *
+ * @return bool
+ */
+ public function supportsImports()
+ {
+ return $this->supportsImports;
+ }
+
+ /**
+ * Whether this object allows controlled custom var access through fields
+ *
+ * @return bool
+ */
+ public function supportsFields()
+ {
+ return $this->supportsFields;
+ }
+
+ /**
+ * Whether this object can be rendered as 'apply Object'
+ *
+ * @return bool
+ */
+ public function supportsApplyRules()
+ {
+ return $this->supportsApplyRules;
+ }
+
+ /**
+ * Whether this object supports 'assign' properties
+ *
+ * @return bool
+ */
+ public function supportsAssignments()
+ {
+ return $this->isApplyRule();
+ }
+
+ /**
+ * Whether this object can be part of a 'set'
+ *
+ * @return bool
+ */
+ public function supportsSets()
+ {
+ return $this->supportsSets;
+ }
+
+ /**
+ * Whether this object supports template-based Choices
+ *
+ * @return bool
+ */
+ public function supportsChoices()
+ {
+ return $this->supportsChoices;
+ }
+
+ public function setAssignments($value)
+ {
+ return IcingaObjectLegacyAssignments::applyToObject($this, $value);
+ }
+
+ /**
+ * @codingStandardsIgnoreStart
+ *
+ * @param Filter|string $filter
+ *
+ * @throws LogicException
+ *
+ * @return self
+ */
+ public function setAssign_filter($filter)
+ {
+ if (! $this->supportsAssignments() && $filter !== null) {
+ if ($this->hasProperty('object_type')) {
+ $type = $this->get('object_type');
+ } else {
+ $type = get_class($this);
+ }
+
+ if ($type === null) {
+ throw new LogicException(
+ 'Cannot set assign_filter unless object_type has been set'
+ );
+ }
+ throw new LogicException(sprintf(
+ 'I can only assign for applied objects or objects with native'
+ . ' support for assignments, got %s',
+ $type
+ ));
+ }
+
+ // @codingStandardsIgnoreEnd
+ if ($filter instanceof Filter) {
+ $filter = $filter->toQueryString();
+ }
+
+ return $this->reallySet('assign_filter', $filter);
+ }
+
+ /**
+ * It sometimes makes sense to defer lookups for related properties. This
+ * kind of lazy-loading allows us to for example set host = 'localhost' and
+ * render an object even when no such host exists. Think of the activity log,
+ * one might want to visualize a history host or service template even when
+ * the related command has been deleted in the meantime.
+ *
+ * @return self
+ */
+ public function resolveUnresolvedRelatedProperties()
+ {
+ foreach ($this->unresolvedRelatedProperties as $name => $p) {
+ $this->resolveUnresolvedRelatedProperty($name);
+ }
+
+ return $this;
+ }
+
+ public function getUnresolvedRelated($property)
+ {
+ if ($this->hasRelation($property)) {
+ $property .= '_id';
+ if (isset($this->unresolvedRelatedProperties[$property])) {
+ return $this->unresolvedRelatedProperties[$property];
+ }
+
+ return null;
+ }
+
+ throw new RuntimeException(sprintf(
+ '%s "%s" has no %s reference',
+ $this->getShortTableName(),
+ $this->getObjectName(),
+ $property
+ ));
+ }
+
+ /**
+ * @param $name
+ */
+ protected function resolveUnresolvedRelatedProperty($name)
+ {
+ $short = substr($name, 0, -3);
+ /** @var IcingaObject $class */
+ $class = $this->getRelationClass($short);
+ try {
+ $object = $class::load(
+ $this->unresolvedRelatedProperties[$name],
+ $this->connection
+ );
+ } catch (NotFoundError $e) {
+ // Hint: eventually a NotFoundError would be better
+ throw new RuntimeException(sprintf(
+ 'Unable to load object (%s: %s) referenced from %s "%s", %s',
+ $short,
+ $this->unresolvedRelatedProperties[$name],
+ $this->getShortTableName(),
+ $this->getObjectName(),
+ lcfirst($e->getMessage())
+ ), $e->getCode(), $e);
+ }
+
+ $id = $object->get('id');
+ // Happens when load() get's a branched object, created in the branch
+ if ($id !== null) {
+ $this->reallySet($name, $id);
+ unset($this->unresolvedRelatedProperties[$name]);
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasBeenModified()
+ {
+ if (parent::hasBeenModified()) {
+ return true;
+ }
+
+ if ($this->hasUnresolvedRelatedProperties()) {
+ $this->resolveUnresolvedRelatedProperties();
+
+ // Duplicates above code, but this makes it faster:
+ if (parent::hasBeenModified()) {
+ return true;
+ }
+ }
+
+ if ($this->supportsCustomVars() && $this->vars !== null && $this->vars()->hasBeenModified()) {
+ return true;
+ }
+
+ if ($this->supportsGroups() && $this->groups !== null && $this->groups()->hasBeenModified()) {
+ return true;
+ }
+
+ if ($this->supportsImports() && $this->imports !== null && $this->imports()->hasBeenModified()) {
+ return true;
+ }
+
+ if ($this->supportsRanges() && $this->ranges !== null && $this->ranges()->hasBeenModified()) {
+ return true;
+ }
+
+ if ($this instanceof ObjectWithArguments
+ && $this->gotArguments()
+ && $this->arguments()->hasBeenModified()
+ ) {
+ return true;
+ }
+
+ foreach ($this->loadedRelatedSets as $set) {
+ if ($set->hasBeenModified()) {
+ return true;
+ }
+ }
+
+ foreach ($this->loadedMultiRelations as $rel) {
+ if ($rel->hasBeenModified()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function hasUnresolvedRelatedProperties()
+ {
+ return ! empty($this->unresolvedRelatedProperties);
+ }
+
+ protected function hasUnresolvedRelatedProperty($name)
+ {
+ return array_key_exists($name, $this->unresolvedRelatedProperties);
+ }
+
+ /**
+ * @param $key
+ * @return mixed
+ */
+ protected function getRelationId($key)
+ {
+ if ($this->hasUnresolvedRelatedProperty($key)) {
+ $this->resolveUnresolvedRelatedProperty($key);
+ }
+
+ return parent::get($key);
+ }
+
+ /**
+ * @param $key
+ * @return string|null
+ */
+ protected function getRelatedProperty($key)
+ {
+ $idKey = $key . '_id';
+ if ($this->hasUnresolvedRelatedProperty($idKey)) {
+ return $this->unresolvedRelatedProperties[$idKey];
+ }
+
+ if ($id = $this->get($idKey)) {
+ /** @var IcingaObject $class */
+ $class = $this->getRelationClass($key);
+ try {
+ $object = $class::loadWithAutoIncId($id, $this->connection);
+ } catch (NotFoundError $e) {
+ throw new RuntimeException($e->getMessage(), 0, $e);
+ }
+
+ return $object->getObjectName();
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string $key
+ * @return \Icinga\Module\Director\CustomVariable\CustomVariable|mixed|null
+ */
+ public function get($key)
+ {
+ if (substr($key, 0, 5) === 'vars.') {
+ $var = $this->vars()->get(substr($key, 5));
+ if ($var === null) {
+ return $var;
+ }
+
+ return $var->getValue();
+ }
+
+ // e.g. zone_id
+ if ($this->propertyIsRelation($key)) {
+ return $this->getRelationId($key);
+ }
+
+ // e.g. zone
+ if ($this->hasRelation($key)) {
+ return $this->getRelatedProperty($key);
+ }
+
+ if ($this->propertyIsRelatedSet($key)) {
+ return $this->getRelatedSet($key)->toPlainObject();
+ }
+
+ if ($this->propertyIsMultiRelation($key)) {
+ return $this->getMultiRelation($key)->listRelatedNames();
+ }
+
+ return parent::get($key);
+ }
+
+ public function setProperties($props)
+ {
+ if (is_array($props)) {
+ if (array_key_exists('object_type', $props) && key($props) !== 'object_type') {
+ $type = $props['object_type'];
+ unset($props['object_type']);
+ $props = ['object_type' => $type] + $props;
+ }
+ }
+ return parent::setProperties($props);
+ }
+
+ public function set($key, $value)
+ {
+ if ($key === 'vars') {
+ $value = (array) $value;
+ $unset = [];
+ foreach ($this->vars() as $k => $f) {
+ if (! array_key_exists($k, $value)) {
+ $unset[] = $k;
+ }
+ }
+ foreach ($unset as $k) {
+ unset($this->vars()->$k);
+ }
+ foreach ($value as $k => $v) {
+ $this->vars()->set($k, $v);
+ }
+ return $this;
+ }
+
+ if (substr($key, 0, 5) === 'vars.') {
+ //TODO: allow for deep keys
+ $this->vars()->set(substr($key, 5), $value);
+ return $this;
+ }
+
+ if ($this instanceof ObjectWithArguments
+ && substr($key, 0, 10) === 'arguments.') {
+ $this->arguments()->set(substr($key, 10), $value);
+ return $this;
+ }
+
+ if ($this->propertyIsBoolean($key)) {
+ return parent::set($key, DbDataFormatter::normalizeBoolean($value));
+ }
+
+ // e.g. zone_id
+ if ($this->propertyIsRelation($key)) {
+ return $this->setRelation($key, $value);
+ }
+
+ // e.g. zone
+ if ($this->hasRelation($key)) {
+ return $this->setUnresolvedRelation($key, $value);
+ }
+
+ if ($this->propertyIsMultiRelation($key)) {
+ $this->setMultiRelation($key, $value);
+ return $this;
+ }
+
+ if ($this->propertyIsRelatedSet($key)) {
+ $this->getRelatedSet($key)->set($value);
+ return $this;
+ }
+
+ if ($this->propertyIsInterval($key)) {
+ return parent::set($key, c::parseInterval($value));
+ }
+
+ return parent::set($key, $value);
+ }
+
+ private function setRelation($key, $value)
+ {
+ if ((int) $key !== (int) $this->$key) {
+ unset($this->unresolvedRelatedProperties[$key]);
+ }
+ return parent::set($key, $value);
+ }
+
+ private function setUnresolvedRelation($key, $value)
+ {
+ if ($value === null || strlen($value) === 0) {
+ unset($this->unresolvedRelatedProperties[$key . '_id']);
+ return parent::set($key . '_id', null);
+ }
+
+ $this->unresolvedRelatedProperties[$key . '_id'] = $value;
+ return $this;
+ }
+
+ protected function setRanges($ranges)
+ {
+ $this->ranges()->set((array) $ranges);
+ return $this;
+ }
+
+ protected function getRanges()
+ {
+ return $this->ranges()->getValues();
+ }
+
+ protected function setDisabled($disabled)
+ {
+ return $this->reallySet('disabled', DbDataFormatter::normalizeBoolean($disabled));
+ }
+
+ public function isDisabled()
+ {
+ return $this->get('disabled') === 'y';
+ }
+
+ public function markForRemoval($remove = true)
+ {
+ $this->shouldBeRemoved = $remove;
+ return $this;
+ }
+
+ public function shouldBeRemoved()
+ {
+ return $this->shouldBeRemoved;
+ }
+
+ public function shouldBeRenamed()
+ {
+ return $this->hasBeenLoadedFromDb()
+ && $this->getOriginalProperty('object_name') !== $this->getObjectName();
+ }
+
+ /**
+ * @return IcingaObjectGroups
+ */
+ public function groups()
+ {
+ $this->assertGroupsSupport();
+ if ($this->groups === null) {
+ if ($this->hasBeenLoadedFromDb() && $this->get('id')) {
+ $this->groups = IcingaObjectGroups::loadForStoredObject($this);
+ } else {
+ $this->groups = new IcingaObjectGroups($this);
+ }
+ }
+
+ return $this->groups;
+ }
+
+ public function hasModifiedGroups()
+ {
+ $this->assertGroupsSupport();
+ if ($this->groups === null) {
+ return false;
+ }
+
+ return $this->groups->hasBeenModified();
+ }
+
+ public function getAppliedGroups()
+ {
+ $this->assertGroupsSupport();
+ if (! $this instanceof IcingaHost) {
+ throw new RuntimeException('getAppliedGroups is only available for hosts currently!');
+ }
+ if (! $this->hasBeenLoadedFromDb()) {
+ // There are no stored related/resolved groups. We'll also not resolve
+ // them here on demand.
+ return [];
+ }
+ $id = $this->get('id');
+ if ($id === null) {
+ // Do not fail for branches. Should be handled otherwise
+ // TODO: throw an Exception, once we are able to deal with this
+ return [];
+ }
+
+ $type = strtolower($this->getType());
+ $query = $this->db->select()->from(
+ ['gr' => "icinga_${type}group_${type}_resolved"],
+ ['g.object_name']
+ )->join(
+ ['g' => "icinga_${type}group"],
+ "g.id = gr.${type}group_id",
+ []
+ )->joinLeft(
+ ['go' => "icinga_${type}group_${type}"],
+ "go.${type}group_id = gr.${type}group_id AND go.${type}_id = " . (int) $id,
+ []
+ )->where(
+ "gr.${type}_id = ?",
+ (int) $id
+ )->where("go.${type}_id IS NULL")->order('g.object_name');
+
+ return $this->db->fetchCol($query);
+ }
+
+ /**
+ * @return IcingaTimePeriodRanges
+ */
+ public function ranges()
+ {
+ $this->assertRangesSupport();
+ if ($this->ranges === null) {
+ /** @var IcingaTimePeriodRanges $class */
+ $class = $this->getRangeClass();
+ if ($this->hasBeenLoadedFromDb()) {
+ $this->ranges = $class::loadForStoredObject($this);
+ } else {
+ $this->ranges = new $class($this);
+ }
+ }
+
+ return $this->ranges;
+ }
+
+ protected function getRangeClass()
+ {
+ if ($this->rangeClass === null) {
+ $this->rangeClass = get_class($this) . 'Ranges';
+ }
+
+ return $this->rangeClass;
+ }
+
+ /**
+ * @return IcingaObjectImports
+ */
+ public function imports()
+ {
+ $this->assertImportsSupport();
+ if ($this->imports === null) {
+ // can not use hasBeenLoadedFromDb() when in onStore()
+ if ($this->getProperty('id') !== null) {
+ $this->imports = IcingaObjectImports::loadForStoredObject($this);
+ } else {
+ $this->imports = new IcingaObjectImports($this);
+ }
+ }
+
+ return $this->imports;
+ }
+
+ public function gotImports()
+ {
+ return $this->imports !== null;
+ }
+
+ public function setImports($imports)
+ {
+ if (! is_array($imports) && $imports !== null) {
+ $imports = [$imports];
+ }
+
+ try {
+ $this->imports()->set($imports);
+ } catch (NestingError $e) {
+ $this->imports = new IcingaObjectImports($this);
+ // Force modification, otherwise it won't be stored when empty
+ $this->imports->setModified()->set($imports);
+ }
+
+ if ($this->imports()->hasBeenModified()) {
+ $this->invalidateResolveCache();
+ }
+ }
+
+ public function getImports()
+ {
+ return $this->listImportNames();
+ }
+
+ /**
+ * @deprecated This should no longer be in use
+ * @return IcingaTemplateResolver
+ */
+ public function templateResolver()
+ {
+ if ($this->templateResolver === null) {
+ $this->templateResolver = new IcingaTemplateResolver($this);
+ }
+
+ return $this->templateResolver;
+ }
+
+ public function getResolvedProperty($key, $default = null)
+ {
+ if (array_key_exists($key, $this->unresolvedRelatedProperties)) {
+ $this->resolveUnresolvedRelatedProperty($key);
+ $this->invalidateResolveCache();
+ }
+
+ $properties = $this->getResolvedProperties();
+ if (property_exists($properties, $key)) {
+ return $properties->$key;
+ }
+
+ 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;
+ }
+
+ return null;
+ }
+
+ public function getResolvedVar($varName)
+ {
+ try {
+ $vars = $this->getResolvedVars();
+ } catch (NestingError $e) {
+ return null;
+ }
+
+ if (property_exists($vars, $varName)) {
+ return $vars->$varName;
+ }
+
+ return null;
+ }
+
+ public function getOriginForVar($varName)
+ {
+ try {
+ $origins = $this->getOriginsVars();
+ } catch (NestingError $e) {
+ return null;
+ }
+
+ if (property_exists($origins, $varName)) {
+ return $origins->$varName;
+ }
+
+ return null;
+ }
+
+ public function getResolvedProperties()
+ {
+ return $this->getResolved('Properties');
+ }
+
+ public function getInheritedProperties()
+ {
+ return $this->getInherited('Properties');
+ }
+
+ public function getOriginsProperties()
+ {
+ return $this->getOrigins('Properties');
+ }
+
+ public function resolveProperties()
+ {
+ return $this->resolve('Properties');
+ }
+
+ public function getResolvedVars()
+ {
+ return $this->getResolved('Vars');
+ }
+
+ public function getInheritedVars()
+ {
+ return $this->getInherited('Vars');
+ }
+
+ public function resolveVars()
+ {
+ return $this->resolve('Vars');
+ }
+
+ public function getOriginsVars()
+ {
+ return $this->getOrigins('Vars');
+ }
+
+ public function getVars()
+ {
+ $vars = [];
+ foreach ($this->vars() as $key => $var) {
+ if ($var->hasBeenDeleted()) {
+ continue;
+ }
+
+ $vars[$key] = $var->getValue();
+ }
+ ksort($vars);
+
+ return (object) $vars;
+ }
+
+ /**
+ * This is mostly for magic getters
+ * @return array
+ */
+ public function getGroups()
+ {
+ return $this->groups()->listGroupNames();
+ }
+
+ /**
+ * @return array
+ * @throws NotFoundError
+ */
+ public function listInheritedGroupNames()
+ {
+ $parents = $this->imports()->getObjects();
+ /** @var IcingaObject $parent */
+ foreach (array_reverse($parents) as $parent) {
+ $inherited = $parent->getGroups();
+ if (! empty($inherited)) {
+ return $inherited;
+ }
+ }
+
+ return [];
+ }
+
+ public function setGroups($groups)
+ {
+ $this->groups()->set($groups);
+ return $this;
+ }
+
+ /**
+ * @return array
+ * @throws NotFoundError
+ */
+ public function listResolvedGroupNames()
+ {
+ $groups = $this->groups()->listGroupNames();
+ if (empty($groups)) {
+ return $this->listInheritedGroupNames();
+ }
+
+ return $groups;
+ }
+
+ /**
+ * @param $group
+ * @return bool
+ * @throws NotFoundError
+ */
+ public function hasGroup($group)
+ {
+ if ($group instanceof static) {
+ $group = $group->getObjectName();
+ }
+
+ return in_array($group, $this->listResolvedGroupNames());
+ }
+
+ protected function getResolved($what)
+ {
+ $func = 'resolve' . $what;
+ $res = $this->$func();
+ return $res['_MERGED_'];
+ }
+
+ protected function getInherited($what)
+ {
+ $func = 'resolve' . $what;
+ $res = $this->$func();
+ return $res['_INHERITED_'];
+ }
+
+ protected function getOrigins($what)
+ {
+ $func = 'resolve' . $what;
+ $res = $this->$func();
+ return $res['_ORIGINS_'];
+ }
+
+ protected function hasResolveCached($what)
+ {
+ return array_key_exists($what, $this->resolveCache);
+ }
+
+ protected function & getResolveCached($what)
+ {
+ return $this->resolveCache[$what];
+ }
+
+ protected function storeResolvedCache($what, $vals)
+ {
+ $this->resolveCache[$what] = $vals;
+ }
+
+ public function invalidateResolveCache()
+ {
+ $this->resolveCache = [];
+ return $this;
+ }
+
+ public function countDirectDescendants()
+ {
+ $db = $this->getDb();
+ $table = $this->getTableName();
+ $type = $this->getShortTableName();
+
+ $query = $db->select()->from(
+ ['oi' => $table . '_inheritance'],
+ ['cnt' => 'COUNT(*)']
+ )->where('oi.parent_' . $type . '_id = ?', (int) $this->get('id'));
+
+ return $db->fetchOne($query);
+ }
+
+ protected function triggerLoopDetection()
+ {
+ // $this->templateResolver()->listResolvedParentIds();
+ }
+
+ public function getSingleResolvedProperty($key, $default = null)
+ {
+ if (array_key_exists($key, $this->unresolvedRelatedProperties)) {
+ $this->resolveUnresolvedRelatedProperty($key);
+ $this->invalidateResolveCache();
+ }
+
+ if ($my = $this->get($key)) {
+ if ($my !== null) {
+ return $my;
+ }
+ }
+
+ /** @var IcingaObject[] $imports */
+ try {
+ $imports = array_reverse($this->imports()->getObjects());
+ } catch (NotFoundError $e) {
+ throw new RuntimeException($e->getMessage(), 0, $e);
+ }
+
+ // Eventually trigger loop detection
+ $this->listAncestorIds();
+
+ foreach ($imports as $object) {
+ $v = $object->getSingleResolvedProperty($key);
+ if (null !== $v) {
+ return $v;
+ }
+ }
+
+ return $default;
+ }
+
+ protected function resolve($what)
+ {
+ if ($this->hasResolveCached($what)) {
+ return $this->getResolveCached($what);
+ }
+
+ // Force exception
+ if ($this->hasBeenLoadedFromDb()) {
+ $this->triggerLoopDetection();
+ }
+
+ $vals = [];
+ $vals['_MERGED_'] = (object) [];
+ $vals['_INHERITED_'] = (object) [];
+ $vals['_ORIGINS_'] = (object) [];
+ // $objects = $this->imports()->getObjects();
+ $objects = IcingaTemplateRepository::instanceByObject($this)
+ ->getTemplatesIndexedByNameFor($this, true);
+
+ $get = 'get' . $what;
+ $getInherited = 'getInherited' . $what;
+ $getOrigins = 'getOrigins' . $what;
+
+ $blacklist = ['id', 'uuid', 'object_type', 'object_name', 'disabled'];
+ foreach ($objects as $name => $object) {
+ $origins = $object->$getOrigins();
+
+ foreach ($object->$getInherited() as $key => $value) {
+ if (in_array($key, $blacklist)) {
+ continue;
+ }
+
+ if (! property_exists($origins, $key)) {
+ // TODO: Introduced with group membership resolver or
+ // choices - this should not be required. Check this!
+ continue;
+ }
+
+ // $vals[$name]->$key = $value;
+ $vals['_MERGED_']->$key = $value;
+ $vals['_INHERITED_']->$key = $value;
+ $vals['_ORIGINS_']->$key = $origins->$key;
+ }
+
+ foreach ($object->$get() as $key => $value) {
+ // TODO: skip if default value?
+ if ($value === null) {
+ continue;
+ }
+ if (in_array($key, $blacklist)) {
+ continue;
+ }
+ $vals['_MERGED_']->$key = $value;
+ $vals['_INHERITED_']->$key = $value;
+ $vals['_ORIGINS_']->$key = $name;
+ }
+ }
+
+ foreach ($this->$get() as $key => $value) {
+ if ($value === null) {
+ continue;
+ }
+
+ $vals['_MERGED_']->$key = $value;
+ }
+
+ $this->storeResolvedCache($what, $vals);
+
+ return $vals;
+ }
+
+ public function matches(Filter $filter)
+ {
+ // TODO: speed up by passing only desired properties (filter columns) to
+ // toPlainObject method
+ /** @var FilterChain|FilterExpression $filter */
+ return $filter->matches($this->toPlainObject());
+ }
+
+ protected function assertCustomVarsSupport()
+ {
+ if (! $this->supportsCustomVars()) {
+ throw new LogicException(sprintf(
+ 'Objects of type "%s" have no custom vars',
+ $this->getType()
+ ));
+ }
+
+ return $this;
+ }
+
+ protected function assertGroupsSupport()
+ {
+ if (! $this->supportsGroups()) {
+ throw new LogicException(sprintf(
+ 'Objects of type "%s" have no groups',
+ $this->getType()
+ ));
+ }
+
+ return $this;
+ }
+
+ protected function assertRangesSupport()
+ {
+ if (! $this->supportsRanges()) {
+ throw new LogicException(sprintf(
+ 'Objects of type "%s" have no ranges',
+ $this->getType()
+ ));
+ }
+
+ return $this;
+ }
+
+ protected function assertImportsSupport()
+ {
+ if (! $this->supportsImports()) {
+ throw new LogicException(sprintf(
+ 'Objects of type "%s" have no imports',
+ $this->getType()
+ ));
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return CustomVariables
+ */
+ public function vars()
+ {
+ $this->assertCustomVarsSupport();
+ if ($this->vars === null) {
+ if ($this->hasBeenLoadedFromDb()) {
+ if (PrefetchCache::shouldBeUsed()) {
+ $this->vars = PrefetchCache::instance()->vars($this);
+ } else {
+ if ($this->get('id')) {
+ $this->vars = CustomVariables::loadForStoredObject($this);
+ } else {
+ $this->vars = new CustomVariables();
+ }
+ }
+
+ if ($this->getShortTableName() === 'host') {
+ $this->vars->setOverrideKeyName(
+ $this->getConnection()->settings()->override_services_varname
+ );
+ }
+ } else {
+ $this->vars = new CustomVariables();
+ }
+ }
+
+ return $this->vars;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasInitializedVars()
+ {
+ $this->assertCustomVarsSupport();
+
+ return $this->vars !== null;
+ }
+
+ public function getVarsTableName()
+ {
+ return $this->getTableName() . '_var';
+ }
+
+ public function getShortTableName()
+ {
+ // strlen('icinga_') = 7
+ return substr($this->getTableName(), 7);
+ }
+
+ public function getVarsIdColumn()
+ {
+ return $this->getShortTableName() . '_id';
+ }
+
+ public function hasProperty($key)
+ {
+ if ($this->propertyIsRelatedSet($key)) {
+ return true;
+ }
+
+ if ($this->propertyIsMultiRelation($key)) {
+ return true;
+ }
+
+ return parent::hasProperty($key);
+ }
+
+ public function isObject()
+ {
+ return $this->hasProperty('object_type')
+ && $this->get('object_type') === 'object';
+ }
+
+ public function isTemplate()
+ {
+ return $this->hasProperty('object_type')
+ && $this->get('object_type') === 'template';
+ }
+
+ public function isExternal()
+ {
+ return $this->hasProperty('object_type')
+ && $this->get('object_type') === 'external_object';
+ }
+
+ public function isApplyRule()
+ {
+ return $this->hasProperty('object_type')
+ && $this->get('object_type') === 'apply';
+ }
+
+ public function setBeingLoadedFromDb()
+ {
+ if ($this instanceof ObjectWithArguments && $this->gotArguments()) {
+ $this->arguments()->setBeingLoadedFromDb();
+ }
+ if ($this->supportsImports() && $this->gotImports()) {
+ $this->imports()->setBeingLoadedFromDb();
+ }
+ if ($this->supportsCustomVars() && $this->vars !== null) {
+ $this->vars()->setBeingLoadedFromDb();
+ }
+ if ($this->supportsGroups() && $this->groups !== null) {
+ $this->groups()->setBeingLoadedFromDb();
+ }
+ if ($this->supportsRanges() && $this->ranges !== null) {
+ $this->ranges()->setBeingLoadedFromDb();
+ }
+
+ foreach ($this->loadedRelatedSets as $set) {
+ $set->setBeingLoadedFromDb();
+ }
+
+ foreach ($this->loadedMultiRelations as $multiRelation) {
+ $multiRelation->setBeingLoadedFromDb();
+ }
+ // This might trigger DB requests and 404's. We might want to defer this, but a call to
+ // hasBeenModified triggers anyway:
+ $this->resolveUnresolvedRelatedProperties();
+
+ parent::setBeingLoadedFromDb();
+ }
+
+ /**
+ * @throws NotFoundError
+ * @throws \Icinga\Module\Director\Exception\DuplicateKeyException
+ * @throws \Zend_Db_Adapter_Exception
+ */
+ protected function storeRelatedObjects()
+ {
+ $this
+ ->storeCustomVars()
+ ->storeGroups()
+ ->storeMultiRelations()
+ ->storeImports()
+ ->storeRanges()
+ ->storeRelatedSets()
+ ->storeArguments();
+ }
+
+ /**
+ * @throws NotFoundError
+ */
+ protected function beforeStore()
+ {
+ $this->resolveUnresolvedRelatedProperties();
+ if ($this->gotImports()) {
+ $this->imports()->getObjects();
+ }
+ }
+
+ /**
+ * @throws NotFoundError
+ * @throws \Icinga\Module\Director\Exception\DuplicateKeyException
+ * @throws \Zend_Db_Adapter_Exception
+ */
+ public function onInsert()
+ {
+ DirectorActivityLog::logCreation($this, $this->connection);
+ $this->storeRelatedObjects();
+ }
+
+ /**
+ * @throws NotFoundError
+ * @throws \Icinga\Module\Director\Exception\DuplicateKeyException
+ * @throws \Zend_Db_Adapter_Exception
+ */
+ public function onUpdate()
+ {
+ DirectorActivityLog::logModification($this, $this->connection);
+ $this->storeRelatedObjects();
+ }
+
+ public function onStore()
+ {
+ $this->notifyResolvers();
+ }
+
+ /**
+ * @return self
+ */
+ protected function storeCustomVars()
+ {
+ if ($this->supportsCustomVars()) {
+ $this->vars !== null && $this->vars()->storeToDb($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ protected function storeGroups()
+ {
+ if ($this->supportsGroups()) {
+ $this->groups !== null && $this->groups()->store();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ protected function storeMultiRelations()
+ {
+ foreach ($this->loadedMultiRelations as $rel) {
+ $rel->store();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ protected function storeRanges()
+ {
+ if ($this->supportsRanges()) {
+ $this->ranges !== null && $this->ranges()->store();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ * @throws \Icinga\Module\Director\Exception\DuplicateKeyException
+ */
+ protected function storeArguments()
+ {
+ if ($this instanceof ObjectWithArguments) {
+ $this->gotArguments() && $this->arguments()->store();
+ }
+
+ return $this;
+ }
+
+ protected function notifyResolvers()
+ {
+ }
+
+ /**
+ * @return $this
+ */
+ protected function storeRelatedSets()
+ {
+ foreach ($this->loadedRelatedSets as $set) {
+ if ($set->hasBeenModified()) {
+ $set->store();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ * @throws NotFoundError
+ * @throws \Zend_Db_Adapter_Exception
+ */
+ protected function storeImports()
+ {
+ if ($this->supportsImports()) {
+ $this->imports !== null && $this->imports()->store();
+ }
+
+ return $this;
+ }
+
+ public function beforeDelete()
+ {
+ $this->cachedPlainUnmodified = $this->getPlainUnmodifiedObject();
+ }
+
+ public function getCachedUnmodifiedObject()
+ {
+ return $this->cachedPlainUnmodified;
+ }
+
+ public function onDelete()
+ {
+ DirectorActivityLog::logRemoval($this, $this->connection);
+ }
+
+ public function toSingleIcingaConfig()
+ {
+ $config = new IcingaConfig($this->connection);
+ $object = $this;
+ if ($object->isExternal()) {
+ $object->set('object_type', 'object');
+ $wasExternal = true;
+ } else {
+ $wasExternal = false;
+ }
+
+ try {
+ $object->renderToConfig($config);
+ } catch (Exception $e) {
+ $message = $e->getMessage();
+ $showTrace = false;
+ if ($showTrace) {
+ $message .= "\n" . $e->getTraceAsString();
+ }
+ $config->configFile(
+ 'failed-to-render'
+ )->prepend(
+ "/** Failed to render this object **/\n"
+ . '/* ' . $message . ' */'
+ );
+ }
+ if ($wasExternal) {
+ $object->set('object_type', 'external_object');
+ }
+
+ return $config;
+ }
+
+ public function isSupportedInLegacy()
+ {
+ return $this->supportedInLegacy;
+ }
+
+ public function renderToLegacyConfig(IcingaConfig $config)
+ {
+ if ($this->isExternal()) {
+ return;
+ }
+
+ if (! $this->isSupportedInLegacy()) {
+ $config->configFile(
+ 'director/ignored-objects',
+ '.cfg'
+ )->prepend(
+ sprintf(
+ "# Not supported for legacy config: %s object_name=%s\n",
+ get_class($this),
+ $this->getObjectName()
+ )
+ );
+ return;
+ }
+
+ $filename = $this->getRenderingFilename();
+
+ $deploymentMode = $config->getDeploymentMode();
+ if ($deploymentMode === 'active-passive') {
+ if ($this->getSingleResolvedProperty('zone_id')
+ && array_key_exists('enable_active_checks', $this->defaultProperties)
+ ) {
+ $passive = clone($this);
+ $passive->set('enable_active_checks', false);
+
+ $config->configFile(
+ 'director/master/' . $filename,
+ '.cfg'
+ )->addLegacyObject($passive);
+ }
+ } elseif ($deploymentMode === 'masterless') {
+ // no additional config
+ } else {
+ throw new LogicException(sprintf(
+ 'Unsupported deployment mode: %s',
+ $deploymentMode
+ ));
+ }
+
+ $config->configFile(
+ 'director/' . $this->getRenderingZone($config) . '/' . $filename,
+ '.cfg'
+ )->addLegacyObject($this);
+ }
+
+ public function renderToConfig(IcingaConfig $config)
+ {
+ if ($config->isLegacy()) {
+ $this->renderToLegacyConfig($config);
+ return;
+ }
+
+ if ($this->isExternal()) {
+ return;
+ }
+
+ $config->configFile(
+ 'zones.d/' . $this->getRenderingZone($config) . '/' . $this->getRenderingFilename()
+ )->addObject($this);
+ }
+
+ public function getRenderingFilename()
+ {
+ $type = $this->getShortTableName();
+
+ if ($this->isTemplate()) {
+ $filename = strtolower($type) . '_templates';
+ } elseif ($this->isApplyRule()) {
+ $filename = strtolower($type) . '_apply';
+ } else {
+ $filename = strtolower($type) . 's';
+ }
+
+ return $filename;
+ }
+
+ /**
+ * @param $zoneId
+ * @param IcingaConfig|null $config
+ * @return string
+ * @throws NotFoundError
+ */
+ protected function getNameForZoneId($zoneId, IcingaConfig $config = null)
+ {
+ // TODO: this is still ugly.
+ if ($config === null) {
+ return IcingaZone::loadWithAutoIncId(
+ $zoneId,
+ $this->getConnection()
+ )->getObjectName();
+ }
+
+ // Config has a lookup cache, is faster:
+ return $config->getZoneName($zoneId);
+ }
+
+ public function getRenderingZone(IcingaConfig $config = null)
+ {
+ if ($this->hasUnresolvedRelatedProperty('zone_id')) {
+ return $this->get('zone');
+ }
+
+ if ($this->hasProperty('zone_id')) {
+ try {
+ if (! $this->supportsImports()) {
+ if ($zoneId = $this->get('zone_id')) {
+ return $this->getNameForZoneId($zoneId, $config);
+ }
+ }
+
+ if ($zoneId = $this->getSingleResolvedProperty('zone_id')) {
+ return $this->getNameForZoneId($zoneId, $config);
+ }
+ } catch (NestingError $e) {
+ throw $e;
+ } catch (Exception $e) {
+ return self::RESOLVE_ERROR;
+ }
+ }
+
+ return $this->getDefaultZone($config);
+ }
+
+ protected function getDefaultZone(IcingaConfig $config = null)
+ {
+ if ($this->prefersGlobalZone()) {
+ return $this->connection->getDefaultGlobalZoneName();
+ }
+
+ return $this->connection->getMasterZoneName();
+ }
+
+ protected function prefersGlobalZone()
+ {
+ return $this->isTemplate() || $this->isApplyRule();
+ }
+
+ protected function renderImports()
+ {
+ if (! $this->supportsImports()) {
+ return '';
+ }
+
+ $ret = '';
+ foreach ($this->getImports() as $name) {
+ $ret .= ' import ' . c::renderString($name) . "\n";
+ }
+
+ if ($ret !== '') {
+ $ret .= "\n";
+ }
+
+ return $ret;
+ }
+
+ protected function renderLegacyImports()
+ {
+ if ($this->supportsImports()) {
+ return $this->imports()->toLegacyConfigString();
+ }
+
+ return '';
+ }
+
+ protected function renderLegacyRelationProperty($propertyName, $id, $renderKey = null)
+ {
+ return $this->renderLegacyObjectProperty(
+ $renderKey ?: $propertyName,
+ c1::renderString($this->getRelatedObjectName($propertyName, $id))
+ );
+ }
+
+ // Disabled is a virtual property
+ protected function renderDisabled()
+ {
+ return '';
+ }
+
+ /**
+ * @codingStandardsIgnoreStart
+ */
+ protected function renderLegacyHost_id($value)
+ {
+ if (is_array($value)) {
+ return c1::renderKeyValue('host_name', c1::renderArray($value));
+ }
+
+ return $this->renderLegacyRelationProperty(
+ 'host',
+ $this->get('host_id'),
+ 'host_name'
+ );
+ }
+
+ /**
+ * Display Name only exists for host/service in Icinga 1
+ *
+ * Render it as alias for everything by default.
+ *
+ * Alias does not exist in Icinga 2 currently!
+ *
+ * @return string
+ */
+ protected function renderLegacyDisplay_Name()
+ {
+ return c1::renderKeyValue('alias', $this->display_name);
+ }
+
+ protected function renderLegacyTimeout()
+ {
+ return '';
+ }
+
+ protected function renderLegacyEnable_active_checks()
+ {
+ return $this->renderLegacyBooleanProperty(
+ 'enable_active_checks',
+ 'active_checks_enabled'
+ );
+ }
+
+ protected function renderLegacyEnable_passive_checks()
+ {
+ return $this->renderLegacyBooleanProperty(
+ 'enable_passive_checks',
+ 'passive_checks_enabled'
+ );
+ }
+
+ protected function renderLegacyEnable_event_handler()
+ {
+ return $this->renderLegacyBooleanProperty(
+ 'enable_active_checks',
+ 'event_handler_enabled'
+ );
+ }
+
+ protected function renderLegacyEnable_notifications()
+ {
+ return $this->renderLegacyBooleanProperty(
+ 'enable_notifications',
+ 'notifications_enabled'
+ );
+ }
+
+ protected function renderLegacyEnable_perfdata()
+ {
+ return $this->renderLegacyBooleanProperty(
+ 'enable_perfdata',
+ 'process_perf_data'
+ );
+ }
+
+ protected function renderLegacyVolatile()
+ {
+ // @codingStandardsIgnoreEnd
+ return $this->renderLegacyBooleanProperty(
+ 'volatile',
+ 'is_volatile'
+ );
+ }
+
+ protected function renderLegacyBooleanProperty($property, $legacyKey)
+ {
+ return c1::renderKeyValue(
+ $legacyKey,
+ c1::renderBoolean($this->get($property))
+ );
+ }
+
+ protected function renderProperties()
+ {
+ $out = '';
+ $blacklist = array_merge(
+ $this->propertiesNotForRendering,
+ $this->prioritizedProperties
+ );
+
+ foreach ($this->properties as $key => $value) {
+ if (in_array($key, $blacklist)) {
+ continue;
+ }
+
+ $out .= $this->renderObjectProperty($key, $value);
+ }
+
+ return $out;
+ }
+
+ protected function renderLegacyProperties()
+ {
+ $out = '';
+ $blacklist = array_merge(
+ $this->propertiesNotForRendering,
+ [] /* $this->prioritizedProperties */
+ );
+
+ foreach ($this->properties as $key => $value) {
+ if (in_array($key, $blacklist)) {
+ continue;
+ }
+
+ $out .= $this->renderLegacyObjectProperty($key, $value);
+ }
+
+ return $out;
+ }
+
+ protected function renderPrioritizedProperties()
+ {
+ $out = '';
+
+ foreach ($this->prioritizedProperties as $key) {
+ $out .= $this->renderObjectProperty($key, $this->properties[$key]);
+ }
+
+ return $out;
+ }
+
+ protected function renderObjectProperty($key, $value)
+ {
+ if (substr($key, -3) === '_id') {
+ $short = substr($key, 0, -3);
+ if ($this->hasUnresolvedRelatedProperty($key)) {
+ return c::renderKeyValue(
+ $short, // NOT
+ c::renderString($this->$short)
+ );
+ }
+ }
+
+ if ($value === null) {
+ return '';
+ }
+
+ $method = 'render' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ return $this->$method($value);
+ }
+
+ if ($this->propertyIsBoolean($key)) {
+ if ($value === $this->defaultProperties[$key]) {
+ return '';
+ }
+
+ return c::renderKeyValue(
+ $this->booleans[$key],
+ c::renderBoolean($value)
+ );
+ }
+
+ if ($this->propertyIsInterval($key)) {
+ return c::renderKeyValue(
+ $this->intervalProperties[$key],
+ c::renderInterval($value)
+ );
+ }
+
+ if (substr($key, -3) === '_id'
+ && $this->hasRelation($relKey = substr($key, 0, -3))
+ ) {
+ return $this->renderRelationProperty($relKey, $value);
+ }
+
+ return c::renderKeyValue(
+ $key,
+ $this->isApplyRule() ?
+ c::renderStringWithVariables($value) :
+ c::renderString($value)
+ );
+ }
+
+ protected function renderLegacyObjectProperty($key, $value)
+ {
+ if (substr($key, -3) === '_id') {
+ $short = substr($key, 0, -3);
+ if ($this->hasUnresolvedRelatedProperty($key)) {
+ return c1::renderKeyValue(
+ $short, // NOT
+ c1::renderString($this->$short)
+ );
+ }
+ }
+
+ if ($value === null) {
+ return '';
+ }
+
+ $method = 'renderLegacy' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ return $this->$method($value);
+ }
+
+ $method = 'render' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ return $this->$method($value);
+ }
+
+ if ($this->propertyIsBoolean($key)) {
+ if ($value === $this->defaultProperties[$key]) {
+ return '';
+ }
+
+ return c1::renderKeyValue(
+ $this->booleans[$key],
+ c1::renderBoolean($value)
+ );
+ }
+
+ if ($this->propertyIsInterval($key)) {
+ return c1::renderKeyValue(
+ $this->intervalProperties[$key],
+ c1::renderInterval($value)
+ );
+ }
+
+ if (substr($key, -3) === '_id'
+ && $this->hasRelation($relKey = substr($key, 0, -3))
+ ) {
+ return $this->renderLegacyRelationProperty($relKey, $value);
+ }
+
+ return c1::renderKeyValue($key, c1::renderString($value));
+ }
+
+ protected function renderBooleanProperty($key)
+ {
+ return c::renderKeyValue($key, c::renderBoolean($this->get($key)));
+ }
+
+ protected function renderPropertyAsSeconds($key)
+ {
+ return c::renderKeyValue($key, c::renderInterval($this->get($key)));
+ }
+
+ protected function renderSuffix()
+ {
+ return "}\n\n";
+ }
+
+ protected function renderLegacySuffix()
+ {
+ return "}\n\n";
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderCustomVars()
+ {
+ if ($this->supportsCustomVars()) {
+ return $this->vars()->toConfigString($this->isApplyRule());
+ }
+
+ return '';
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderLegacyCustomVars()
+ {
+ if ($this->supportsCustomVars()) {
+ return $this->vars()->toLegacyConfigString();
+ }
+
+ return '';
+ }
+
+ public function renderUuid()
+ {
+ return '';
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderGroups()
+ {
+ if ($this->supportsGroups()) {
+ return $this->groups()->toConfigString();
+ }
+
+ return '';
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderLegacyGroups()
+ {
+ if ($this->supportsGroups() && $this->hasBeenLoadedFromDb()) {
+ $applied = [];
+ if ($this instanceof IcingaHost) {
+ $applied = $this->getAppliedGroups();
+ }
+ return $this->groups()->toLegacyConfigString($applied);
+ }
+
+ return '';
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderMultiRelations()
+ {
+ $out = '';
+ foreach ($this->loadAllMultiRelations() as $rel) {
+ $out .= $rel->toConfigString();
+ }
+
+ return $out;
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderLegacyMultiRelations()
+ {
+ $out = '';
+ foreach ($this->loadAllMultiRelations() as $rel) {
+ $out .= $rel->toLegacyConfigString();
+ }
+
+ return $out;
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderRanges()
+ {
+ if ($this->supportsRanges()) {
+ return $this->ranges()->toConfigString();
+ }
+
+ return '';
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderLegacyRanges()
+ {
+ if ($this->supportsRanges()) {
+ return $this->ranges()->toLegacyConfigString();
+ }
+
+ return '';
+ }
+
+ /**
+ * @return string
+ */
+ protected function renderArguments()
+ {
+ return '';
+ }
+
+ protected function renderRelatedSets()
+ {
+ $config = '';
+ foreach ($this->relatedSets as $property => $class) {
+ $config .= $this->getRelatedSet($property)->renderAs($property);
+ }
+ return $config;
+ }
+
+ protected function renderRelationProperty($propertyName, $id, $renderKey = null)
+ {
+ return c::renderKeyValue(
+ $renderKey ?: $propertyName,
+ c::renderString($this->getRelatedObjectName($propertyName, $id))
+ );
+ }
+
+ protected function renderCommandProperty($commandId, $propertyName = 'check_command')
+ {
+ return c::renderKeyValue(
+ $propertyName,
+ c::renderString($this->connection->getCommandName($commandId))
+ );
+ }
+
+ /**
+ * @param $value
+ * @return string
+ * @codingStandardsIgnoreStart
+ */
+ protected function renderLegacyCheck_command($value)
+ {
+ // @codingStandardsIgnoreEnd
+ $args = [];
+ foreach ($this->vars() as $k => $v) {
+ if (substr($k, 0, 3) === 'ARG') {
+ $args[] = $v->getValue();
+ }
+ }
+ array_unshift($args, $value);
+
+ return c1::renderKeyValue('check_command', implode('!', $args));
+ }
+
+ /**
+ * @param $value
+ * @return string
+ * @codingStandardsIgnoreStart
+ */
+ protected function renderLegacyEvent_command($value)
+ {
+ // @codingStandardsIgnoreEnd
+ return c1::renderKeyValue('event_handler', $value);
+ }
+
+ /**
+ * We do not render zone properties, objects are stored to zone dirs
+ *
+ * Avoid complaints for method names with underscore:
+ * @codingStandardsIgnoreStart
+ *
+ * @return string
+ */
+ protected function renderZone_id()
+ {
+ // @codingStandardsIgnoreEnd
+ return '';
+ }
+
+ protected function renderCustomExtensions()
+ {
+ return '';
+ }
+
+ protected function renderLegacyCustomExtensions()
+ {
+ $str = '';
+
+ // Set notification settings for the object to suppress warnings
+ if (array_key_exists('enable_notifications', $this->defaultProperties)
+ && $this->isTemplate()
+ ) {
+ $str .= c1::renderKeyValue('notification_period', 'notification_none');
+ $str .= c1::renderKeyValue('notification_interval', '0');
+ $str .= c1::renderKeyValue('contact_groups', 'icingaadmins');
+ }
+
+ // force rendering of check_command when ARG1 is set
+ if ($this->supportsCustomVars() && array_key_exists('check_command_id', $this->defaultProperties)) {
+ if ($this->get('check_command') === null
+ && $this->vars()->get('ARG1') !== null
+ ) {
+ $command = $this->getResolvedRelated('check_command');
+ $str .= $this->renderLegacyCheck_command($command->getObjectName());
+ }
+ }
+
+ return $str;
+ }
+
+ protected function renderObjectHeader()
+ {
+ return sprintf(
+ "%s %s %s {\n",
+ $this->getObjectTypeName(),
+ $this->getType(),
+ c::renderString($this->getObjectName())
+ );
+ }
+
+ public function getLegacyObjectType()
+ {
+ return strtolower($this->getType());
+ }
+
+ protected function renderLegacyObjectHeader()
+ {
+ $type = $this->getLegacyObjectType();
+
+ if ($this->isTemplate()) {
+ $name = c1::renderKeyValue(
+ $this->getLegacyObjectKeyName(),
+ c1::renderString($this->getObjectName())
+ );
+ } else {
+ $name = c1::renderKeyValue(
+ $this->getLegacyObjectKeyName(),
+ c1::renderString($this->getObjectName())
+ );
+ }
+
+ $str = "define $type {\n$name";
+ if ($this->isTemplate()) {
+ $str .= c1::renderKeyValue('register', '0');
+ }
+
+ return $str;
+ }
+
+ protected function getLegacyObjectKeyName()
+ {
+ if ($this->isTemplate()) {
+ return 'name';
+ }
+
+ return $this->getLegacyObjectType() . '_name';
+ }
+
+ /**
+ * @codingStandardsIgnoreStart
+ */
+ public function renderAssign_Filter()
+ {
+ return ' ' . AssignRenderer::forFilter(
+ Filter::fromQueryString($this->get('assign_filter'))
+ )->renderAssign() . "\n";
+ }
+
+ public function renderLegacyAssign_Filter()
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this instanceof IcingaHostGroup) {
+ $c = " # resolved memberships are set via the individual object\n";
+ } elseif ($this instanceof IcingaService) {
+ $c = " # resolved objects are listed here\n";
+ } else {
+ $c = " # assign is not supported for " . $this->type . "\n";
+ }
+ $c .= ' #' . AssignRenderer::forFilter(
+ Filter::fromQueryString($this->get('assign_filter'))
+ )->renderAssign() . "\n";
+ return $c;
+ }
+
+ public function toLegacyConfigString()
+ {
+ $str = implode([
+ $this->renderLegacyObjectHeader(),
+ $this->renderLegacyImports(),
+ $this->renderLegacyProperties(),
+ //$this->renderArguments(),
+ //$this->renderRelatedSets(),
+ $this->renderLegacyGroups(),
+ $this->renderLegacyMultiRelations(),
+ $this->renderLegacyRanges(),
+ $this->renderLegacyCustomExtensions(),
+ $this->renderLegacyCustomVars(),
+ $this->renderLegacySuffix()
+ ]);
+
+ $str = $this->alignLegacyProperties($str);
+
+ if ($this->isDisabled()) {
+ return
+ "# --- This object has been disabled ---\n"
+ . preg_replace('~^~m', '# ', trim($str))
+ . "\n\n";
+ }
+
+ return $str;
+ }
+
+ protected function alignLegacyProperties($configString)
+ {
+ $lines = explode("\n", $configString);
+ $len = 24;
+
+ foreach ($lines as &$line) {
+ if (preg_match('/^\s{4}([^\t]+)\t+(.+)$/', $line, $m)) {
+ if ($len - strlen($m[1]) < 0) {
+ $fill = ' ';
+ } else {
+ $fill = str_repeat(' ', $len - strlen($m[1]));
+ }
+
+ $line = ' ' . $m[1] . $fill . $m[2];
+ }
+ }
+
+ return implode("\n", $lines);
+ }
+
+ public function toConfigString()
+ {
+ $str = implode([
+ $this->renderObjectHeader(),
+ $this->renderPrioritizedProperties(),
+ $this->renderImports(),
+ $this->renderProperties(),
+ $this->renderArguments(),
+ $this->renderRelatedSets(),
+ $this->renderGroups(),
+ $this->renderMultiRelations(),
+ $this->renderRanges(),
+ $this->renderCustomExtensions(),
+ $this->renderCustomVars(),
+ $this->renderSuffix()
+ ]);
+
+ if ($this->isDisabled()) {
+ return "/* --- This object has been disabled ---\n"
+ // Do not allow strings to break our comment
+ . str_replace('*/', "* /", $str) . "*/\n";
+ }
+
+ return $str;
+ }
+
+ public function isGroup()
+ {
+ return substr($this->getType(), -5) === 'Group';
+ }
+
+ public function hasCheckCommand()
+ {
+ return false;
+ }
+
+ protected function getType()
+ {
+ if ($this->type === null) {
+ $parts = explode('\\', get_class($this));
+ // 6 = strlen('Icinga');
+ $this->type = substr(end($parts), 6);
+ }
+
+ return $this->type;
+ }
+
+ protected function getObjectTypeName()
+ {
+ if ($this->isTemplate()) {
+ return 'template';
+ }
+ if ($this->isApplyRule()) {
+ return 'apply';
+ }
+
+ return 'object';
+ }
+
+ public function getObjectName()
+ {
+ $property = static::getKeyColumnName();
+ if ($this->hasProperty($property)) {
+ return $this->get($property);
+ }
+
+ throw new LogicException(sprintf(
+ 'Trying to access "%s" for an instance of "%s"',
+ $property,
+ get_class($this)
+ ));
+ }
+
+ /**
+ * @deprecated use DbObjectTypeRegistry::classByType()
+ * @param $type
+ * @return string
+ */
+ public static function classByType($type)
+ {
+ return DbObjectTypeRegistry::classByType($type);
+ }
+
+ /**
+ * @param $type
+ * @param array $properties
+ * @param Db|null $db
+ *
+ * @return IcingaObject
+ */
+ public static function createByType($type, $properties = [], Db $db = null)
+ {
+ /** @var IcingaObject $class */
+ $class = DbObjectTypeRegistry::classByType($type);
+ return $class::create($properties, $db);
+ }
+
+ /**
+ * @param $type
+ * @param $id
+ * @param Db $db
+ *
+ * @return IcingaObject
+ * @throws NotFoundError
+ */
+ public static function loadByType($type, $id, Db $db)
+ {
+ /** @var IcingaObject $class */
+ $class = DbObjectTypeRegistry::classByType($type);
+ return $class::load($id, $db);
+ }
+
+ /**
+ * @param $type
+ * @param $id
+ * @param Db $db
+ *
+ * @return bool
+ */
+ public static function existsByType($type, $id, Db $db)
+ {
+ /** @var IcingaObject $class */
+ $class = DbObjectTypeRegistry::classByType($type);
+ return $class::exists($id, $db);
+ }
+
+ public static function getKeyColumnName()
+ {
+ return 'object_name';
+ }
+
+ public static function loadAllByType($type, Db $db, $query = null, $keyColumn = null)
+ {
+ /** @var DbObject $class */
+ $class = DbObjectTypeRegistry::classByType($type);
+
+ if ($keyColumn === null) {
+ if (method_exists($class, 'getKeyColumnName')) {
+ $keyColumn = $class::getKeyColumnName();
+ }
+ }
+
+ if (is_array($class::create()->getKeyName())) {
+ return $class::loadAll($db, $query);
+ }
+
+ if (PrefetchCache::shouldBeUsed()
+ && $query === null
+ && $keyColumn === static::getKeyColumnName()
+ ) {
+ $result = [];
+ foreach ($class::prefetchAll($db) as $row) {
+ $result[$row->$keyColumn] = $row;
+ }
+
+ return $result;
+ }
+
+ return $class::loadAll($db, $query, $keyColumn);
+ }
+
+ /**
+ * @param $type
+ * @param Db $db
+ * @return IcingaObject[]
+ */
+ public static function loadAllExternalObjectsByType($type, Db $db)
+ {
+ /** @var IcingaObject $class */
+ $class = DbObjectTypeRegistry::classByType($type);
+ $dummy = $class::create();
+
+ if (is_array($dummy->getKeyName())) {
+ throw new LogicException(sprintf(
+ 'There is no support for loading external objects of type "%s"',
+ $type
+ ));
+ }
+
+ $query = $db->getDbAdapter()
+ ->select()
+ ->from($dummy->getTableName())
+ ->where('object_type = ?', 'external_object');
+
+ return $class::loadAll($db, $query, 'object_name');
+ }
+
+ public static function fromJson($json, Db $connection = null)
+ {
+ return static::fromPlainObject(json_decode($json), $connection);
+ }
+
+ public static function fromPlainObject($plain, Db $connection = null)
+ {
+ return static::create((array) $plain, $connection);
+ }
+
+ /**
+ * @param IcingaObject $object
+ * @param null $preserve
+ * @return $this
+ * @throws NotFoundError
+ */
+ public function replaceWith(IcingaObject $object, $preserve = [])
+ {
+ return $this->replaceWithProperties($object->toPlainObject(), $preserve);
+ }
+
+ /**
+ * @param array|object $properties
+ * @param array $preserve
+ * @return $this
+ * @throws NotFoundError
+ */
+ public function replaceWithProperties($properties, $preserve = [])
+ {
+ $properties = (array) $properties;
+ foreach ($preserve as $k) {
+ $v = $this->get($k);
+ if ($v !== null) {
+ $properties[$k] = $v;
+ }
+ }
+ $this->setProperties($properties);
+
+ return $this;
+ }
+
+ /**
+ * TODO: with rules? What if I want to override vars? Drop in favour of vars.x?
+ *
+ * @param IcingaObject $object
+ * @param bool $replaceVars
+ * @return $this
+ * @throws NotFoundError
+ */
+ public function merge(IcingaObject $object, $replaceVars = false)
+ {
+ $object = clone($object);
+
+ if ($object->supportsCustomVars()) {
+ $vars = $object->getVars();
+ $object->set('vars', []);
+ }
+
+ if ($object->supportsGroups()) {
+ $groups = $object->getGroups();
+ $object->set('groups', []);
+ }
+
+ if ($object->supportsImports()) {
+ $imports = $object->listImportNames();
+ $object->set('imports', []);
+ }
+
+ $plain = (array) $object->toPlainObject(false, false);
+ unset($plain['vars'], $plain['groups'], $plain['imports']);
+ foreach ($plain as $p => $v) {
+ if ($v === null) {
+ // We want default values, but no null values
+ continue;
+ }
+
+ $this->set($p, $v);
+ }
+
+ if ($object->supportsCustomVars()) {
+ $myVars = $this->vars();
+ if ($replaceVars) {
+ $this->set('vars', $vars);
+ } else {
+ /** @var CustomVariables $vars */
+ foreach ($vars as $key => $var) {
+ $myVars->set($key, $var);
+ }
+ }
+ }
+
+ if ($object->supportsGroups()) {
+ if (! empty($groups)) {
+ $this->set('groups', $groups);
+ }
+ }
+
+ if ($object->supportsImports()) {
+ if (! empty($imports)) {
+ $this->set('imports', $imports);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param bool $resolved
+ * @param bool $skipDefaults
+ * @param array|null $chosenProperties
+ * @param bool $resolveIds
+ * @param bool $keepId
+ * @return object
+ * @throws NotFoundError
+ */
+ public function toPlainObject(
+ $resolved = false,
+ $skipDefaults = false,
+ array $chosenProperties = null,
+ $resolveIds = true,
+ $keepId = false
+ ) {
+ $props = [];
+
+ if ($resolved) {
+ $p = $this->getInheritedProperties();
+ foreach ($this->properties as $k => $v) {
+ if ($v === null && property_exists($p, $k)) {
+ continue;
+ }
+ $p->$k = $v;
+ }
+ } else {
+ $p = $this->properties;
+ }
+
+ foreach ($p as $k => $v) {
+ // Do not ship ids for IcingaObjects:
+ if ($k === $this->getUuidColumn()) {
+ continue;
+ }
+ if ($resolveIds) {
+ if ($k === 'id' && $keepId === false && $this->hasProperty('object_name')) {
+ continue;
+ }
+
+ if ('_id' === substr($k, -3)) {
+ $relKey = substr($k, 0, -3);
+
+ if ($this->hasRelation($relKey)) {
+ if ($this->hasUnresolvedRelatedProperty($k)) {
+ $v = $this->$relKey;
+ } elseif ($v !== null) {
+ $v = $this->getRelatedObjectName($relKey, $v);
+ }
+
+ $k = $relKey;
+ } else {
+ throw new LogicException(sprintf(
+ 'No such relation: %s',
+ $relKey
+ ));
+ }
+ }
+ }
+
+ // TODO: Do not ship null properties based on flag?
+ if (!$skipDefaults || $this->differsFromDefaultValue($k, $v)) {
+ if ($k === 'disabled' || $this->propertyIsBoolean($k)) {
+ $props[$k] = $this->booleanForDbValue($v);
+ } else {
+ $props[$k] = $v;
+ }
+ }
+ }
+
+ if ($this->supportsGroups()) {
+ // TODO: resolve
+ $groups = $this->groups()->listGroupNames();
+ if ($resolved && empty($groups)) {
+ $groups = $this->listInheritedGroupNames();
+ }
+
+ $props['groups'] = $groups;
+ }
+
+ foreach ($this->loadAllMultiRelations() as $key => $rel) {
+ if (count($rel) || !$skipDefaults) {
+ $props[$key] = $rel->listRelatedNames();
+ }
+ }
+
+ if ($this instanceof ObjectWithArguments) {
+ $props['arguments'] = $this->arguments()->toPlainObject(
+ false,
+ $skipDefaults
+ );
+ }
+
+ if ($this->supportsCustomVars()) {
+ if ($resolved) {
+ $props['vars'] = $this->getResolvedVars();
+ } else {
+ $props['vars'] = $this->getVars();
+ }
+ }
+
+ if ($this->supportsImports()) {
+ if ($resolved) {
+ $props['imports'] = [];
+ } else {
+ $props['imports'] = $this->listImportNames();
+ }
+ }
+
+ if ($this->supportsRanges()) {
+ // TODO: resolve
+ $props['ranges'] = $this->get('ranges');
+ }
+
+ if ($skipDefaults) {
+ foreach (['imports', 'ranges', 'arguments'] as $key) {
+ if (empty($props[$key])) {
+ unset($props[$key]);
+ }
+ }
+
+ if (array_key_exists('vars', $props)) {
+ if (count((array) $props['vars']) === 0) {
+ unset($props['vars']);
+ }
+ }
+ if (empty($props['groups'])) {
+ unset($props['groups']);
+ }
+ }
+
+ foreach ($this->relatedSets() as $property => $set) {
+ if ($resolved) {
+ if ($this->supportsImports()) {
+ $set = clone($set);
+ foreach ($this->imports()->getObjects() as $parent) {
+ $set->inheritFrom($parent->getRelatedSet($property));
+ }
+ }
+
+ $values = $set->getResolvedValues();
+ if (empty($values)) {
+ if (!$skipDefaults) {
+ $props[$property] = null;
+ }
+ } else {
+ $props[$property] = $values;
+ }
+ } else {
+ if ($set->isEmpty()) {
+ if (!$skipDefaults) {
+ $props[$property] = null;
+ }
+ } else {
+ $props[$property] = $set->toPlainObject();
+ }
+ }
+ }
+
+ if ($chosenProperties !== null) {
+ $chosen = [];
+ foreach ($chosenProperties as $k) {
+ if (array_key_exists($k, $props)) {
+ $chosen[$k] = $props[$k];
+ }
+ }
+
+ $props = $chosen;
+ }
+ ksort($props);
+
+ return (object) $props;
+ }
+
+ protected function booleanForDbValue($value)
+ {
+ if ($value === 'y') {
+ return true;
+ }
+ if ($value === 'n') {
+ return false;
+ }
+
+ return $value; // let this fail elsewhere, if not null
+ }
+
+ public function listImportNames()
+ {
+ if ($this->gotImports()) {
+ return $this->imports()->listImportNames();
+ }
+
+ return $this->templateTree()->listParentNamesFor($this);
+ }
+
+ public function listFlatResolvedImportNames()
+ {
+ return $this->templateTree()->getAncestorsFor($this);
+ }
+
+ public function listImportIds()
+ {
+ return $this->templateTree()->listParentIdsFor($this);
+ }
+
+ public function listAncestorIds()
+ {
+ return $this->templateTree()->listAncestorIdsFor($this);
+ }
+
+ protected function templateTree()
+ {
+ return $this->templates()->tree();
+ }
+
+ protected function templates()
+ {
+ return IcingaTemplateRepository::instanceByObject($this, $this->getConnection());
+ }
+
+ protected function differsFromDefaultValue($key, $value)
+ {
+ if (array_key_exists($key, $this->defaultProperties)) {
+ return $value !== $this->defaultProperties[$key];
+ }
+
+ return $value !== null;
+ }
+
+ protected function mapHostsToZones($names)
+ {
+ $map = [];
+
+ foreach ($names as $hostname) {
+ /** @var IcingaHost $host */
+ $host = IcingaHost::load($hostname, $this->connection);
+
+ $zone = $host->getRenderingZone();
+ if (! array_key_exists($zone, $map)) {
+ $map[$zone] = [];
+ }
+
+ $map[$zone][] = $hostname;
+ }
+
+ ksort($map);
+
+ return $map;
+ }
+
+ public function getUrlParams()
+ {
+ $params = [];
+ if ($column = $this->getUuidColumn()) {
+ return [$column => $this->getUniqueId()->toString()];
+ }
+
+ if ($this->isApplyRule() && ! $this instanceof IcingaScheduledDowntime) {
+ $params['id'] = $this->get('id');
+ } else {
+ $params = ['name' => $this->getObjectName()];
+
+ if ($this->hasProperty('host_id') && $this->get('host_id')) {
+ $params['host'] = $this->get('host');
+ }
+
+ if ($this->hasProperty('service_id') && $this->get('service_id')) {
+ $params['service'] = $this->get('service');
+ }
+
+ if ($this->hasProperty('service_set_id') && $this->get('service_set_id')) {
+ $params['set'] = $this->get('service_set');
+ }
+ }
+
+ return $params;
+ }
+
+ public function getOnDeleteUrl()
+ {
+ $plural= preg_replace('/cys$/', 'cies', strtolower($this->getShortTableName()) . 's');
+ return 'director/' . $plural;
+ }
+
+ /**
+ * @param bool $resolved
+ * @param bool $skipDefaults
+ * @param array|null $chosenProperties
+ * @return string
+ * @throws NotFoundError
+ */
+ public function toJson(
+ $resolved = false,
+ $skipDefaults = false,
+ array $chosenProperties = null
+ ) {
+
+ return json_encode($this->toPlainObject($resolved, $skipDefaults, $chosenProperties));
+ }
+
+ public function getPlainUnmodifiedObject()
+ {
+ $props = [];
+
+ foreach ($this->getOriginalProperties() as $k => $v) {
+ // Do not ship ids for IcingaObjects:
+ if ($k === 'id' && $this->hasProperty('object_name')) {
+ continue;
+ }
+ if ($k === $this->getUuidColumn()) {
+ continue;
+ }
+ if ($k === 'disabled' && $v === null) {
+ continue;
+ }
+
+ if ('_id' === substr($k, -3)) {
+ $relKey = substr($k, 0, -3);
+
+ if ($this->hasRelation($relKey)) {
+ if ($v !== null) {
+ $v = $this->getRelatedObjectName($relKey, $v);
+ }
+
+ $k = $relKey;
+ }
+ }
+
+ if ($this->differsFromDefaultValue($k, $v)) {
+ if ($k === 'disabled' || $this->propertyIsBoolean($k)) {
+ $props[$k] = $this->booleanForDbValue($v);
+ } else {
+ $props[$k] = $v;
+ }
+ }
+ }
+
+ if ($this->supportsCustomVars()) {
+ $originalVars = $this->vars()->getOriginalVars();
+ if (! empty($originalVars)) {
+ $props['vars'] = (object) [];
+ foreach ($originalVars as $name => $var) {
+ $props['vars']->$name = $var->getValue();
+ }
+ }
+ }
+ if ($this->supportsGroups()) {
+ $groups = $this->groups()->listOriginalGroupNames();
+ if (! empty($groups)) {
+ $props['groups'] = $groups;
+ }
+ }
+ if ($this->supportsImports()) {
+ $imports = $this->imports()->listOriginalImportNames();
+ if (! empty($imports)) {
+ $props['imports'] = $imports;
+ }
+ }
+
+ if ($this instanceof ObjectWithArguments) {
+ $args = $this->arguments()->toUnmodifiedPlainObject();
+ if (! empty($args)) {
+ $props['arguments'] = $args;
+ }
+ }
+
+ if ($this->supportsRanges()) {
+ $ranges = $this->ranges()->getOriginalValues();
+ if (!empty($ranges)) {
+ $props['ranges'] = $ranges;
+ }
+ }
+
+ foreach ($this->relatedSets() as $property => $set) {
+ if ($set->isEmpty()) {
+ continue;
+ }
+
+ $props[$property] = $set->getPlainUnmodifiedObject();
+ }
+
+ foreach ($this->loadAllMultiRelations() as $key => $rel) {
+ $old = $rel->listOriginalNames();
+ if (! empty($old)) {
+ $props[$key] = $old;
+ }
+ }
+
+ return (object) $props;
+ }
+
+ public function __toString()
+ {
+ try {
+ return $this->toConfigString();
+ } catch (Exception $e) {
+ trigger_error($e);
+ $previousHandler = set_exception_handler(
+ function () {
+ }
+ );
+ restore_error_handler();
+ if ($previousHandler !== null) {
+ call_user_func($previousHandler, $e);
+ die();
+ }
+
+ die($e->getMessage());
+ }
+ }
+
+ public function __destruct()
+ {
+ unset($this->resolveCache);
+ unset($this->vars);
+ unset($this->groups);
+ unset($this->imports);
+ unset($this->ranges);
+ if ($this instanceof ObjectWithArguments) {
+ $this->unsetArguments();
+ }
+
+ parent::__destruct();
+ }
+}