From 1ac4a2050c8076eb96e07e83721ebc9db864db94 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:47:21 +0200 Subject: Adding upstream version 0.3.3. Signed-off-by: Daniel Baumann --- library/Toplevelview/Tree/TLVHostGroupNode.php | 134 ++++++++++ library/Toplevelview/Tree/TLVHostNode.php | 81 ++++++ library/Toplevelview/Tree/TLVIcingaNode.php | 25 ++ library/Toplevelview/Tree/TLVServiceNode.php | 143 +++++++++++ library/Toplevelview/Tree/TLVStatus.php | 115 +++++++++ library/Toplevelview/Tree/TLVTree.php | 251 +++++++++++++++++++ library/Toplevelview/Tree/TLVTreeNode.php | 334 +++++++++++++++++++++++++ 7 files changed, 1083 insertions(+) create mode 100644 library/Toplevelview/Tree/TLVHostGroupNode.php create mode 100644 library/Toplevelview/Tree/TLVHostNode.php create mode 100644 library/Toplevelview/Tree/TLVIcingaNode.php create mode 100644 library/Toplevelview/Tree/TLVServiceNode.php create mode 100644 library/Toplevelview/Tree/TLVStatus.php create mode 100644 library/Toplevelview/Tree/TLVTree.php create mode 100644 library/Toplevelview/Tree/TLVTreeNode.php (limited to 'library/Toplevelview/Tree') diff --git a/library/Toplevelview/Tree/TLVHostGroupNode.php b/library/Toplevelview/Tree/TLVHostGroupNode.php new file mode 100644 index 0000000..a6dc7e4 --- /dev/null +++ b/library/Toplevelview/Tree/TLVHostGroupNode.php @@ -0,0 +1,134 @@ + */ + +namespace Icinga\Module\Toplevelview\Tree; + +use Icinga\Application\Benchmark; +use Icinga\Exception\NotFoundError; +use Icinga\Module\Toplevelview\Monitoring\Hostgroupsummary; + +class TLVHostGroupNode extends TLVIcingaNode +{ + protected $type = 'hostgroup'; + + protected $key = 'hostgroup'; + + protected static $titleKey = 'hostgroup'; + + public static function fetch(TLVTree $root) + { + Benchmark::measure('Begin fetching hostgroups'); + + if (! array_key_exists('hostgroup', $root->registeredObjects) or empty($root->registeredObjects['hostgroup'])) { + throw new NotFoundError('No hostgroups registered to fetch!'); + } + + $names = array_keys($root->registeredObjects['hostgroup']); + + $options = []; + foreach (['notification_periods', 'host_never_unhandled', 'ignored_notification_periods'] as $opt) { + $options[$opt] = $root->get($opt); + } + + // Note: this uses a patched version of Hostsgroupsummary / HostgroupsummaryQuery ! + $hostgroups = new Hostgroupsummary( + $root->getBackend(), + array( + 'hostgroup_name', + 'hosts_down_handled', + 'hosts_down_unhandled', + 'hosts_total', + 'hosts_unreachable_handled', + 'hosts_unreachable_unhandled', + 'hosts_downtime_handled', + 'hosts_downtime_active', + 'hosts_up', + 'services_critical_handled', + 'services_critical_unhandled', + 'services_ok', + 'services_total', + 'services_unknown_handled', + 'services_unknown_unhandled', + 'services_warning_handled', + 'services_warning_unhandled', + 'services_downtime_handled', + 'services_downtime_active', + ), + $options + ); + + $hostgroups->where('hostgroup_name', $names); + + foreach ($hostgroups as $hostgroup) { + $root->registeredObjects['hostgroup'][$hostgroup->hostgroup_name] = $hostgroup; + } + + Benchmark::measure('Finished fetching hostgroups'); + } + + public function getStatus() + { + if ($this->status === null) { + $this->status = $status = new TLVStatus(); + $key = $this->getKey(); + + if (($data = $this->root->getFetched($this->type, $key)) !== null) { + $status->set('total', $data->hosts_total + $data->services_total); + $status->set('ok', $data->hosts_up + $data->services_ok); + + $status->set('critical_handled', $data->services_critical_handled); + $status->set('critical_unhandled', $data->services_critical_unhandled); + + if ($this->getRoot()->get('host_never_unhandled') === true) { + $status->add( + 'critical_handled', + $data->hosts_down_handled + + $data->hosts_unreachable_handled + + $data->hosts_down_unhandled + + $data->hosts_unreachable_unhandled + ); + } else { + $status->add( + 'critical_handled', + $data->hosts_down_handled + + $data->hosts_unreachable_handled + ); + $status->add( + 'critical_unhandled', + $data->hosts_down_unhandled + + $data->hosts_unreachable_unhandled + ); + } + + $status->set('warning_handled', $data->services_warning_handled); + $status->set('warning_unhandled', $data->services_warning_unhandled); + $status->set('unknown_handled', $data->services_unknown_handled); + $status->set('unknown_unhandled', $data->services_unknown_unhandled); + + $status->set( + 'downtime_handled', + $data->hosts_downtime_handled + + $data->services_downtime_handled + ); + $status->set( + 'downtime_active', + $data->hosts_downtime_active + + $data->services_downtime_active + ); + + // extra metadata for view + $status->setMeta('hosts_total', $data->hosts_total); + $status->setMeta( + 'hosts_unhandled', + $data->hosts_down_unhandled + + $data->hosts_unreachable_unhandled + ); + + $status->set('missing', 0); + } else { + $status->add('missing', 1); + } + } + return $this->status; + } +} diff --git a/library/Toplevelview/Tree/TLVHostNode.php b/library/Toplevelview/Tree/TLVHostNode.php new file mode 100644 index 0000000..f03ae47 --- /dev/null +++ b/library/Toplevelview/Tree/TLVHostNode.php @@ -0,0 +1,81 @@ + */ + +namespace Icinga\Module\Toplevelview\Tree; + +use Icinga\Application\Benchmark; +use Icinga\Exception\NotFoundError; + +class TLVHostNode extends TLVIcingaNode +{ + protected $type = 'host'; + + protected $key = 'host'; + + protected static $titleKey = 'host'; + + public static function fetch(TLVTree $root) + { + Benchmark::measure('Begin fetching hosts'); + + if (! array_key_exists('host', $root->registeredObjects) or empty($root->registeredObjects['host'])) { + throw new NotFoundError('No hosts registered to fetch!'); + } + + $names = array_keys($root->registeredObjects['host']); + + $hosts = $root->getBackend()->select() + ->from('hoststatus', array( + 'host_name', + 'host_hard_state', + 'host_handled', + 'host_in_downtime', + 'host_notifications_enabled', + )) + ->where('host_name', $names); + + foreach ($hosts as $host) { + $root->registeredObjects['host'][$host->host_name] = $host; + } + + Benchmark::measure('Finished fetching hosts'); + } + + public function getStatus() + { + if ($this->status === null) { + $this->status = $status = new TLVStatus(); + $key = $this->getKey(); + + if (($data = $this->root->getFetched($this->type, $key)) !== null) { + $status->zero(); + $status->add('total'); + + $state = $data->host_hard_state; + + if ($data->host_in_downtime > 0 || $data->host_notifications_enabled === '0') { + $status->add('downtime_active'); + $state = '10'; + $handled = ''; + } elseif ($data->host_handled === '1' || $this->getRoot()->get('host_never_unhandled') === true) { + $handled = '_handled'; + } else { + $handled = '_unhandled'; + } + + if ($state === '0') { + $status->add('ok'); + } elseif ($state === '1' || $state === '2') { + $status->add('critical' . $handled); + } elseif ($state === '10') { + $status->add('downtime_handled'); + } else { + $status->add('unknown_handled'); + } + } else { + $status->add('missing', 1); + } + } + return $this->status; + } +} diff --git a/library/Toplevelview/Tree/TLVIcingaNode.php b/library/Toplevelview/Tree/TLVIcingaNode.php new file mode 100644 index 0000000..82779b0 --- /dev/null +++ b/library/Toplevelview/Tree/TLVIcingaNode.php @@ -0,0 +1,25 @@ + */ + +namespace Icinga\Module\Toplevelview\Tree; + +use Icinga\Exception\NotImplementedError; + +class TLVIcingaNode extends TLVTreeNode +{ + protected static $canHaveChildren = false; + + /** + * Interface to fetch data for the implementation + * + * Needs to be extended / replaced by class + * + * @param $root + * + * @throws NotImplementedError + */ + public static function fetch(/** @noinspection PhpUnusedParameterInspection */ TLVTree $root) + { + throw new NotImplementedError('fetch() has not been implemented for %s', get_class(new static)); + } +} diff --git a/library/Toplevelview/Tree/TLVServiceNode.php b/library/Toplevelview/Tree/TLVServiceNode.php new file mode 100644 index 0000000..2036714 --- /dev/null +++ b/library/Toplevelview/Tree/TLVServiceNode.php @@ -0,0 +1,143 @@ + */ + +namespace Icinga\Module\Toplevelview\Tree; + +use Icinga\Application\Benchmark; +use Icinga\Exception\NotFoundError; +use Icinga\Module\Toplevelview\Monitoring\Servicestatus; + +class TLVServiceNode extends TLVIcingaNode +{ + protected $type = 'service'; + + protected $key = '{host}!{service}'; + + public function getTitle() + { + return sprintf( + '%s: %s', + $this->get('host'), + $this->get('service') + ); + } + + public function register() + { + // also register host, because that's what we fetch data with + $hostDummy = new TLVHostNode(); + $this->root->registerObject($hostDummy->getType(), $this->get('host'), get_class($hostDummy)); + + // register myself + return parent::register(); + } + + public function getKey() + { + return sprintf('%s!%s', $this->properties['host'], $this->properties['service']); + } + + public static function fetch(TLVTree $root) + { + Benchmark::measure('Begin fetching services'); + + if (! array_key_exists('service', $root->registeredObjects) or empty($root->registeredObjects['service'])) { + throw new NotFoundError('No services registered to fetch!'); + } + + $names = array_keys($root->registeredObjects['host']); + + $columns = array( + 'host_name', + 'service_description', + 'service_hard_state', + 'service_handled', + 'service_handled_wo_host', + 'service_notifications_enabled', + 'service_notification_period', + 'service_is_flapping', + 'service_in_downtime', + ); + + $options = []; + foreach (['notification_periods', 'host_never_unhandled', 'ignored_notification_periods'] as $opt) { + $options[$opt] = $root->get($opt); + } + + if ($root->get('notification_periods') === true) { + $columns[] = 'service_in_notification_period'; + } + + // Note: this uses a patched version of Servicestatus / ServicestatusQuery ! + $services = new Servicestatus($root->getBackend(), $columns, $options); + $services->where('host_name', $names); + + foreach ($services as $service) { + $key = sprintf('%s!%s', $service->host_name, $service->service_description); + if (array_key_exists($key, $root->registeredObjects['service'])) { + $root->registeredObjects['service'][$key] = $service; + } + } + + Benchmark::measure('Finished fetching services'); + } + + public function getStatus() + { + if ($this->status === null) { + $this->status = $status = new TLVStatus(); + $key = $this->getKey(); + + if (($data = $this->root->getFetched($this->type, $key)) !== null) { + $status->zero(); + $status->add('total'); + + $state = $data->service_hard_state; + + if ($this->getRoot()->get('notification_periods') === true) { + $notInPeriod = $data->service_in_notification_period === '0'; + } else { + $notInPeriod = false; + } + + if ($this->getRoot()->get('host_never_unhandled') === true) { + $isHandled = $data->service_handled_wo_host === '1'; + } else { + $isHandled = $data->service_handled === '1'; + } + $isHandled = $isHandled || $data->service_is_flapping === '1'; + + if ($data->service_in_downtime > 0 + || $data->service_notifications_enabled === '0' + || $notInPeriod + ) { + $status->add('downtime_active'); + if ($state !== '0') { + $state = '10'; + } + } + + if ($isHandled) { + $handled = '_handled'; + } else { + $handled = '_unhandled'; + } + + if ($state === '0' || $state === '99') { + $status->add('ok', 1); + } elseif ($state === '1') { + $status->add('warning' . $handled, 1); + } elseif ($state === '2') { + $status->add('critical' . $handled, 1); + } elseif ($state === '10') { + $status->add('downtime_handled'); + } else { + $status->add('unknown' . $handled, 1); + } + } else { + $status->add('missing', 1); + } + } + return $this->status; + } +} diff --git a/library/Toplevelview/Tree/TLVStatus.php b/library/Toplevelview/Tree/TLVStatus.php new file mode 100644 index 0000000..afecc1e --- /dev/null +++ b/library/Toplevelview/Tree/TLVStatus.php @@ -0,0 +1,115 @@ + */ + +namespace Icinga\Module\Toplevelview\Tree; + +class TLVStatus +{ + protected $properties = array( + 'critical_unhandled' => null, + 'critical_handled' => null, + 'warning_unhandled' => null, + 'warning_handled' => null, + 'unknown_unhandled' => null, + 'unknown_handled' => null, + 'downtime_handled' => null, + 'downtime_active' => null, + 'ok' => null, + 'missing' => null, + 'total' => null, + ); + + protected static $statusPriority = array( + 'critical_unhandled', + 'warning_unhandled', + 'unknown_unhandled', // Note: old TLV ignored UNKNOWN basically + 'critical_handled', + 'warning_handled', + 'unknown_handled', + 'ok', + 'downtime_handled', + 'missing', + ); + + protected $meta = array(); + + public function merge(TLVStatus $status) + { + $properties = $status->getProperties(); + foreach (array_keys($this->properties) as $key) { + if ($this->properties[$key] === null) { + $this->properties[$key] = $properties[$key]; + } else { + $this->properties[$key] += $properties[$key]; + } + } + return $this; + } + + public function get($key) + { + return $this->properties[$key]; + } + + public function set($key, $value) + { + $this->properties[$key] = (int) $value; + return $this; + } + + public function getProperties() + { + return $this->properties; + } + + public function add($key, $value = 1) + { + if ($this->properties[$key] === null) { + $this->properties[$key] = 0; + } + $this->properties[$key] += (int) $value; + return $this; + } + + public function zero() + { + foreach (array_keys($this->properties) as $key) { + $this->properties[$key] = 0; + } + return $this; + } + + public function getOverall() + { + foreach (static::$statusPriority as $key) { + if ($this->properties[$key] !== null && $this->properties[$key] > 0) { + return $this->cssFriendly($key); + } + } + return 'missing'; + } + + protected function cssFriendly($key) + { + return str_replace('_', ' ', $key); + } + + public function getMeta($key) + { + if (array_key_exists($key, $this->meta)) { + return $this->meta[$key]; + } else { + return null; + } + } + + public function getAllMeta() + { + return $this->meta; + } + + public function setMeta($key, $value) + { + $this->meta[$key] = $value; + } +} diff --git a/library/Toplevelview/Tree/TLVTree.php b/library/Toplevelview/Tree/TLVTree.php new file mode 100644 index 0000000..817d52c --- /dev/null +++ b/library/Toplevelview/Tree/TLVTree.php @@ -0,0 +1,251 @@ + */ + +namespace Icinga\Module\Toplevelview\Tree; + +use Icinga\Application\Logger; +use Icinga\Exception\IcingaException; +use Icinga\Exception\NotFoundError; +use Icinga\Exception\ProgrammingError; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Module\Toplevelview\Util\Json; +use Icinga\Module\Toplevelview\ViewConfig; +use Icinga\Web\FileCache; +use stdClass; + +class TLVTree extends TLVTreeNode +{ + protected static $titleKey = 'name'; + + public $registeredTypes = array(); + + public $registeredObjects = array(); + + protected $fetchedData = array(); + + protected $fetched = false; + + protected $fetchTime; + + protected $cacheLifetime = 60; + + /** + * @var MonitoringBackend + */ + protected $backend; + + /** + * @var ViewConfig + */ + protected $config; + + public function getById($id) + { + $ids = explode('-', $id); + $currentNode = $this; + + foreach ($ids as $i) { + $children = $currentNode->getChildren(); + if (! empty($children) && array_key_exists($i, $children)) { + $currentNode = $children[$i]; + } else { + throw new NotFoundError( + 'Could not find ID %s after %s for path %s', + $i, + $currentNode->getFullId(), + $id + ); + } + } + + return $currentNode; + } + + /** + * @return ViewConfig + */ + public function getConfig() + { + return $this->config; + } + + /** + * @param ViewConfig $config + * + * @return $this + */ + public function setConfig(ViewConfig $config) + { + $this->config = $config; + return $this; + } + + public function registerObject($type, $name, $class) + { + if (array_key_exists($type, $this->registeredTypes) && $this->registeredTypes[$type] !== $class) { + throw new ProgrammingError( + 'Tried to register the same type by multiple classes: %s - %s - %s', + $type, + $this->registeredTypes[$type], + $class + ); + } + + $this->registeredTypes[$type] = $class; + $this->registeredObjects[$type][$name] = null; + } + + protected function getCacheName() + { + $config = $this->getConfig(); + return sprintf( + '%s-%s.json', + $config->getName(), + $config->getTextChecksum() + ); + } + + protected function loadCache() + { + if (($lifetime = $this->getCacheLifetime()) <= 0) { + return; + } + + $cacheName = $this->getCacheName(); + try { + $cache = FileCache::instance('toplevelview'); + $currentTime = time(); + $newerThan = $currentTime - $lifetime; + + if ($cache->has($cacheName)) { + $cachedData = Json::decode($cache->get($cacheName)); + + if (property_exists($cachedData, 'data') + && $cachedData->data !== null + && property_exists($cachedData, 'ts') + && $cachedData->ts <= $currentTime // too new maybe + && $cachedData->ts > $newerThan // too old + ) { + foreach ($cachedData->data as $type => $objects) { + $this->registeredObjects[$type] = (array) $objects; + $this->fetchedData[$type] = true; + } + + $this->fetchTime = $cachedData->ts; + $this->fetched = true; + } + } + } catch (IcingaException $e) { + Logger::error('Could not load from toplevelview cache %s: %s', $cacheName, $e->getMessage()); + } + } + + protected function storeCache() + { + if (($lifetime = $this->getCacheLifetime()) <= 0) { + return; + } + + $cacheName = $this->getCacheName(); + try { + $cache = FileCache::instance('toplevelview'); + + $cachedData = new stdClass; + $cachedData->ts = $this->fetchTime; + $cachedData->data = $this->registeredObjects; + + $cache->store($cacheName, Json::encode($cachedData)); + } catch (IcingaException $e) { + Logger::error('Could not store to toplevelview cache %s: %s', $cacheName, $e->getMessage()); + } + } + + protected function fetchType($type) + { + if (! array_key_exists($type, $this->registeredTypes)) { + throw new ProgrammingError('Type %s has not been registered', $type); + } + + if (! array_key_exists($type, $this->fetchedData)) { + /** @var TLVIcingaNode $class */ + $class = $this->registeredTypes[$type]; + $class::fetch($this); + $this->fetchedData[$type] = true; + } + + return $this; + } + + protected function ensureFetched() + { + if ($this->fetched !== true) { + $this->loadCache(); + + if ($this->fetched !== true) { + foreach (array_keys($this->registeredTypes) as $type) { + $this->fetchType($type); + } + + $this->fetchTime = time(); + $this->fetched = true; + + $this->storeCache(); + } + } + + return $this; + } + + public function getFetched($type, $key) + { + $this->ensureFetched(); + + if (array_key_exists($key, $this->registeredObjects[$type]) + && $this->registeredObjects[$type][$key] !== null + ) { + return $this->registeredObjects[$type][$key]; + } else { + return null; + } + } + + public function getBackend() + { + return $this->backend; + } + + public function setBackend(MonitoringBackend $backend) + { + $this->backend = $backend; + return $this; + } + + /** + * @return int time + */ + public function getFetchTime() + { + $this->ensureFetched(); + + return $this->fetchTime; + } + + /** + * @return int seconds + */ + public function getCacheLifetime() + { + return $this->cacheLifetime; + } + + /** + * @param int $cacheLifetime In seconds + * + * @return $this + */ + public function setCacheLifetime($cacheLifetime) + { + $this->cacheLifetime = $cacheLifetime; + return $this; + } +} diff --git a/library/Toplevelview/Tree/TLVTreeNode.php b/library/Toplevelview/Tree/TLVTreeNode.php new file mode 100644 index 0000000..fd70e49 --- /dev/null +++ b/library/Toplevelview/Tree/TLVTreeNode.php @@ -0,0 +1,334 @@ + */ + +namespace Icinga\Module\Toplevelview\Tree; + +use Icinga\Application\Benchmark; +use Icinga\Data\Tree\TreeNode; +use Icinga\Exception\ConfigurationError; +use Icinga\Exception\NotImplementedError; +use Icinga\Exception\ProgrammingError; + +class TLVTreeNode extends TreeNode +{ + /** + * @var string + */ + protected $type = 'node'; + + protected $key = null; + + /** + * @var TLVTree + */ + protected $root; + + /** + * @var TLVTreeNode + */ + protected $parent; + + /** + * @var string + */ + protected $fullId; + + /** + * @var TLVStatus + */ + protected $status; + + /** + * @var array + */ + protected $properties = array(); + + protected static $canHaveChildren = true; + + /** + * The key which represents the display title + * + * @var string + */ + protected static $titleKey = 'name'; + + /** + * Mapping types to its implementation class + * + * @var array + */ + protected static $typeMap = array( + 'host' => 'Icinga\\Module\\Toplevelview\\Tree\\TLVHostNode', + 'service' => 'Icinga\\Module\\Toplevelview\\Tree\\TLVServiceNode', + 'hostgroup' => 'Icinga\\Module\\Toplevelview\\Tree\\TLVHostGroupNode', + ); + + /** + * Mapping keys to a type + * + * Warning: order is important when keys overlap! + * + * @var array + */ + protected static $typeKeyMap = array( + 'service' => array('host', 'service'), + 'host' => 'host', + 'hostgroup' => 'hostgroup', + ); + + /** + * @param $array + * @param TLVTreeNode|null $parent + * @param TLVTree $root + * + * @return static + * + * @throws NotImplementedError + * @throws ProgrammingError + */ + public static function fromArray($array, TLVTreeNode $parent = null, TLVTree $root = null) + { + if ($root === null) { + Benchmark::measure('Begin loading TLVTree from array'); + } + + // try to detect type + if (! array_key_exists('type', $array)) { + foreach (self::$typeKeyMap as $type => $keys) { + if (! is_array($keys)) { + $keys = array($keys); + } + $matched = false; + foreach ($keys as $k) { + if (array_key_exists($k, $array)) { + $matched = true; + } else { + continue 2; + } + } + // if all keys are present + if ($matched === true) { + $array['type'] = $type; + break; + } + } + } + + if (array_key_exists('type', $array)) { + $type = $array['type']; + if (array_key_exists($type, self::$typeMap)) { + $node = new self::$typeMap[$type]; + $node->type = $type; + } else { + throw new NotImplementedError('Could not find type "%s" for %s', $type, var_export($array, true)); + } + } elseif ($root === null) { + $node = new static; + } else { + $node = new self; + } + + if ($root === null) { + $node->root = true; // is root + $node->parent = null; + $root = $parent = $node; + } elseif ($parent === null) { + throw new ProgrammingError('You must specify the direct parent!'); + } else { + $node->root = $root; + $node->parent = $parent; + } + + foreach ($array as $key => $value) { + if ($key !== 'children') { + $node->properties[$key] = $value; + } elseif (is_array($value)) { // only array values for children + foreach ($value as $i => $child) { + $childNode = self::fromArray($child, $node, $root); + $childNode->id = $i; + $node->appendChild($childNode); + } + } + } + + $node->register(); + + if ($root === $node) { + Benchmark::measure('Finished loading TLVTree from array'); + } + + return $node; + } + + /** + * Retrieve all objects as breadcrumb + * + * @param array $list for recursion + * + * @return TLVTreeNode[] + */ + public function getBreadCrumb(&$list = array()) + { + array_unshift($list, $this); + if ($this->parent !== $this->root) { + $this->parent->getBreadCrumb($list); + } + return $list; + } + + /** + * + * @return mixed|string + */ + public function getFullId() + { + if ($this->fullId === null) { + $id = (string) $this->id; + if ($this->parent !== $this->root) { + $this->fullId = $this->parent->getFullId() . '-' . $id; + } else { + $this->fullId = $id; + } + } + return $this->fullId; + } + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + public function getProperties() + { + return $this->properties; + } + + public function setProperties($array) + { + $this->properties = $array; + return $this; + } + + public function get($key) + { + if (array_key_exists($key, $this->properties)) { + return $this->properties[$key]; + } else { + return null; + } + } + + public function set($key, $value) + { + $this->properties[$key] = $value; + return $this; + } + + public function getTitle() + { + if (array_key_exists(static::$titleKey, $this->properties)) { + return $this->properties[static::$titleKey]; + } else { + return null; + } + } + + public function getKey() + { + if ($this->key === null) { + throw new ProgrammingError('Can not get key for %s', get_class($this)); + } + + if (array_key_exists($this->key, $this->properties)) { + return $this->properties[$this->key]; + } else { + throw new ProgrammingError( + 'Can not retrieve key for %s in %s', + $this->key, + get_class($this) + ); + } + } + + /** + * @return TLVTree + */ + public function getRoot() + { + return $this->root; + } + + /** + * @return TLVTreeNode + */ + public function getParent() + { + return $this->parent; + } + + /** + * @return TLVTreeNode[] + */ + public function getChildren() + { + return parent::getChildren(); + } + + /** + * Append a child node as the last child of this node + * + * @param TreeNode $child The child to append + * + * @return $this + * + * @throws ConfigurationError When node does not allow children + */ + public function appendChild(TreeNode $child) + { + if (static::$canHaveChildren === true) { + $this->children[] = $child; + } else { + throw new ConfigurationError('Can not add children below type %s', $this->type); + } + return $this; + } + + protected function register() + { + if ($this->type !== 'node') { + $this->root->registerObject($this->type, $this->getKey(), get_class($this)); + } + return $this; + } + + /** + * @return TLVStatus + * @throws ProgrammingError + */ + public function getStatus() + { + if (static::$canHaveChildren === true) { + if ($this->status === null) { + $this->status = new TLVStatus; + + $missed = true; + foreach ($this->getChildren() as $child) { + $this->status->merge($child->getStatus()); + $missed = false; + } + + // Note: old TLV does not count an empty branch as missing... + if ($missed) { + $this->status->add('missing', 1); + } + } + + return $this->status; + } else { + throw new ProgrammingError('getStatus() needs to be implemented for %s', get_class($this)); + } + } +} -- cgit v1.2.3