summaryrefslogtreecommitdiffstats
path: root/modules/monitoring/library/Monitoring/Object
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:46:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:46:43 +0000
commit3e02d5aff85babc3ffbfcf52313f2108e313aa23 (patch)
treeb01f3923360c20a6a504aff42d45670c58af3ec5 /modules/monitoring/library/Monitoring/Object
parentInitial commit. (diff)
downloadicingaweb2-upstream.tar.xz
icingaweb2-upstream.zip
Adding upstream version 2.12.1.upstream/2.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/monitoring/library/Monitoring/Object')
-rw-r--r--modules/monitoring/library/Monitoring/Object/Acknowledgement.php215
-rw-r--r--modules/monitoring/library/Monitoring/Object/Host.php205
-rw-r--r--modules/monitoring/library/Monitoring/Object/HostList.php133
-rw-r--r--modules/monitoring/library/Monitoring/Object/Macro.php83
-rw-r--r--modules/monitoring/library/Monitoring/Object/MonitoredObject.php930
-rw-r--r--modules/monitoring/library/Monitoring/Object/ObjectList.php295
-rw-r--r--modules/monitoring/library/Monitoring/Object/Service.php220
-rw-r--r--modules/monitoring/library/Monitoring/Object/ServiceList.php184
8 files changed, 2265 insertions, 0 deletions
diff --git a/modules/monitoring/library/Monitoring/Object/Acknowledgement.php b/modules/monitoring/library/Monitoring/Object/Acknowledgement.php
new file mode 100644
index 0000000..3cd0d20
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/Acknowledgement.php
@@ -0,0 +1,215 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use InvalidArgumentException;
+use Traversable;
+use Icinga\Util\StringHelper;
+
+/**
+ * Acknowledgement of a host or service incident
+ */
+class Acknowledgement
+{
+ /**
+ * Author of the acknowledgement
+ *
+ * @var string
+ */
+ protected $author;
+
+ /**
+ * Comment of the acknowledgement
+ *
+ * @var string
+ */
+ protected $comment;
+
+ /**
+ * Entry time of the acknowledgement
+ *
+ * @var int
+ */
+ protected $entryTime;
+
+ /**
+ * Expiration time of the acknowledgment
+ *
+ * @var int|null
+ */
+ protected $expirationTime;
+
+ /**
+ * Whether the acknowledgement is sticky
+ *
+ * Sticky acknowledgements suppress notifications until the host or service recovers
+ *
+ * @var bool
+ */
+ protected $sticky = false;
+
+ /**
+ * Create a new acknowledgement of a host or service incident
+ *
+ * @param array|object|Traversable $properties
+ *
+ * @throws InvalidArgumentException If the type of the given properties is invalid
+ */
+ public function __construct($properties = null)
+ {
+ if ($properties !== null) {
+ $this->setProperties($properties);
+ }
+ }
+
+ /**
+ * Get the author of the acknowledgement
+ *
+ * @return string
+ */
+ public function getAuthor()
+ {
+ return $this->author;
+ }
+
+ /**
+ * Set the author of the acknowledgement
+ *
+ * @param string $author
+ *
+ * @return $this
+ */
+ public function setAuthor($author)
+ {
+ $this->author = (string) $author;
+ return $this;
+ }
+
+ /**
+ * Get the comment of the acknowledgement
+ *
+ * @return string
+ */
+ public function getComment()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * Set the comment of the acknowledgement
+ *
+ * @param string $comment
+ *
+ * @return $this
+ */
+ public function setComment($comment)
+ {
+ $this->comment = (string) $comment;
+
+ return $this;
+ }
+
+ /**
+ * Get the entry time of the acknowledgement
+ *
+ * @return int
+ */
+ public function getEntryTime()
+ {
+ return $this->entryTime;
+ }
+
+ /**
+ * Set the entry time of the acknowledgement
+ *
+ * @param int $entryTime
+ *
+ * @return $this
+ */
+ public function setEntryTime($entryTime)
+ {
+ $this->entryTime = (int) $entryTime;
+
+ return $this;
+ }
+
+ /**
+ * Get the expiration time of the acknowledgement
+ *
+ * @return int|null
+ */
+ public function getExpirationTime()
+ {
+ return $this->expirationTime;
+ }
+
+ /**
+ * Set the expiration time of the acknowledgement
+ *
+ * @param int|null $expirationTime Unix timestamp
+ *
+ * @return $this
+ */
+ public function setExpirationTime($expirationTime = null)
+ {
+ $this->expirationTime = $expirationTime !== null ? (int) $expirationTime : null;
+
+ return $this;
+ }
+
+ /**
+ * Get whether the acknowledgement is sticky
+ *
+ * @return bool
+ */
+ public function getSticky()
+ {
+ return $this->sticky;
+ }
+
+ /**
+ * Set whether the acknowledgement is sticky
+ *
+ * @param bool $sticky
+ *
+ * @return $this
+ */
+ public function setSticky($sticky = true)
+ {
+ $this->sticky = (bool) $sticky;
+ return $this;
+ }
+
+ /**
+ * Get whether the acknowledgement expires
+ *
+ * @return bool
+ */
+ public function expires()
+ {
+ return $this->expirationTime !== null;
+ }
+
+ /**
+ * Set the properties of the acknowledgement
+ *
+ * @param array|object|Traversable $properties
+ *
+ * @return $this
+ * @throws InvalidArgumentException If the type of the given properties is invalid
+ */
+ public function setProperties($properties)
+ {
+ if (! is_array($properties) && ! is_object($properties) && ! $properties instanceof Traversable) {
+ throw new InvalidArgumentException('Properties must be either an array or an instance of Traversable');
+ }
+ foreach ($properties as $name => $value) {
+ $setter = 'set' . ucfirst(StringHelper::cname($name));
+ if (method_exists($this, $setter)) {
+ $this->$setter($value);
+ }
+ }
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php
new file mode 100644
index 0000000..dd8538b
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/Host.php
@@ -0,0 +1,205 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\Filter\FilterEqual;
+use Icinga\Module\Monitoring\DataView\Hoststatus;
+use InvalidArgumentException;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+
+/**
+ * An Icinga host
+ */
+class Host extends MonitoredObject
+{
+ /**
+ * Host state 'UP'
+ */
+ const STATE_UP = 0;
+
+ /**
+ * Host state 'DOWN'
+ */
+ const STATE_DOWN = 1;
+
+ /**
+ * Host state 'UNREACHABLE'
+ */
+ const STATE_UNREACHABLE = 2;
+
+ /**
+ * Host state 'PENDING'
+ */
+ const STATE_PENDING = 99;
+
+ /**
+ * Type of the Icinga host
+ *
+ * @var string
+ */
+ public $type = self::TYPE_HOST;
+
+ /**
+ * Prefix of the Icinga host
+ *
+ * @var string
+ */
+ public $prefix = 'host_';
+
+ /**
+ * Hostname
+ *
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * The services running on the hosts
+ *
+ * @var \Icinga\Module\Monitoring\Object\Service[]
+ */
+ protected $services;
+
+ /**
+ * Create a new host
+ *
+ * @param MonitoringBackend $backend Backend to fetch host information from
+ * @param string $host Hostname
+ */
+ public function __construct(MonitoringBackend $backend, $host)
+ {
+ parent::__construct($backend);
+ $this->host = $host;
+ }
+
+ /**
+ * Get the hostname
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Get the data view to fetch the host information from
+ *
+ * @return Hoststatus
+ */
+ protected function getDataView()
+ {
+ $columns = array(
+ 'host_acknowledged',
+ 'host_acknowledgement_type',
+ 'host_action_url',
+ 'host_active_checks_enabled',
+ 'host_active_checks_enabled_changed',
+ 'host_address',
+ 'host_address6',
+ 'host_alias',
+ 'host_attempt',
+ 'host_check_command',
+ 'host_check_execution_time',
+ 'host_check_interval',
+ 'host_check_latency',
+ 'host_check_source',
+ 'host_check_timeperiod',
+ 'host_current_check_attempt',
+ 'host_current_notification_number',
+ 'host_display_name',
+ 'host_event_handler_enabled',
+ 'host_event_handler_enabled_changed',
+ 'host_flap_detection_enabled',
+ 'host_flap_detection_enabled_changed',
+ 'host_handled',
+ 'host_icon_image',
+ 'host_icon_image_alt',
+ 'host_in_downtime',
+ 'host_is_flapping',
+ 'host_is_reachable',
+ 'host_last_check',
+ 'host_last_notification',
+ 'host_last_state_change',
+ 'host_long_output',
+ 'host_max_check_attempts',
+ 'host_name',
+ 'host_next_check',
+ 'host_next_update',
+ 'host_notes',
+ 'host_notes_url',
+ 'host_notifications_enabled',
+ 'host_notifications_enabled_changed',
+ 'host_obsessing',
+ 'host_obsessing_changed',
+ 'host_output',
+ 'host_passive_checks_enabled',
+ 'host_passive_checks_enabled_changed',
+ 'host_percent_state_change',
+ 'host_perfdata',
+ 'host_process_perfdata' => 'host_process_performance_data',
+ 'host_state',
+ 'host_state_type',
+ 'instance_name'
+ );
+ return $this->backend->select()->from('hoststatus', $columns)
+ ->whereEx(new FilterEqual('host_name', '=', $this->host));
+ }
+
+ /**
+ * Fetch the services running on the host
+ *
+ * @return $this
+ */
+ public function fetchServices()
+ {
+ $services = array();
+ foreach ($this->backend->select()->from('servicestatus', array('service_description'))
+ ->where('host_name', $this->host)
+ ->applyFilter($this->getFilter())
+ ->getQuery() as $service) {
+ $services[] = new Service($this->backend, $this->host, $service->service_description);
+ }
+ $this->services = $services;
+ return $this;
+ }
+
+ /**
+ * Get the optional translated textual representation of a host state
+ *
+ * @param int $state
+ * @param bool $translate
+ *
+ * @return string
+ * @throws InvalidArgumentException If the host state is not valid
+ */
+ public static function getStateText($state, $translate = false)
+ {
+ $translate = (bool) $translate;
+ switch ((int) $state) {
+ case self::STATE_UP:
+ $text = $translate ? mt('monitoring', 'UP') : 'up';
+ break;
+ case self::STATE_DOWN:
+ $text = $translate ? mt('monitoring', 'DOWN') : 'down';
+ break;
+ case self::STATE_UNREACHABLE:
+ $text = $translate ? mt('monitoring', 'UNREACHABLE') : 'unreachable';
+ break;
+ case self::STATE_PENDING:
+ $text = $translate ? mt('monitoring', 'PENDING') : 'pending';
+ break;
+ default:
+ throw new InvalidArgumentException(sprintf('Invalid host state \'%s\'', $state));
+ }
+ return $text;
+ }
+
+ public function getNotesUrls()
+ {
+ return $this->resolveAllStrings(
+ MonitoredObject::parseAttributeUrls($this->host_notes_url)
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/HostList.php b/modules/monitoring/library/Monitoring/Object/HostList.php
new file mode 100644
index 0000000..8b1947d
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/HostList.php
@@ -0,0 +1,133 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Data\SimpleQuery;
+use Icinga\Util\StringHelper;
+
+/**
+ * A host list
+ */
+class HostList extends ObjectList
+{
+ protected $dataViewName = 'hoststatus';
+
+ protected $columns = array('host_name');
+
+ protected function fetchObjects()
+ {
+ $hosts = array();
+ $query = $this->backend->select()->from($this->dataViewName, $this->columns)->applyFilter($this->filter)
+ ->getQuery()->getSelectQuery()->query();
+ foreach ($query as $row) {
+ /** @var object $row */
+ $host = new Host($this->backend, $row->host_name);
+ $host->setProperties($row);
+ $hosts[] = $host;
+ }
+ return $hosts;
+ }
+
+ /**
+ * Create a state summary of all hosts that can be consumed by hostssummary.phtml
+ *
+ * @return SimpleQuery
+ */
+ public function getStateSummary()
+ {
+ $hostStates = array_fill_keys(self::getHostStatesSummaryEmpty(), 0);
+ foreach ($this as $host) {
+ $unhandled = (bool) $host->problem === true && (bool) $host->handled === false;
+
+ $stateName = 'hosts_' . $host::getStateText($host->state);
+ ++$hostStates[$stateName];
+ ++$hostStates[$stateName. ($unhandled ? '_unhandled' : '_handled')];
+ }
+
+ $hostStates['hosts_total'] = count($this);
+
+ $ds = new ArrayDatasource(array((object) $hostStates));
+ return $ds->select();
+ }
+
+ /**
+ * Return an empty array with all possible host state names
+ *
+ * @return array An array containing all possible host states as keys and 0 as values.
+ */
+ public static function getHostStatesSummaryEmpty()
+ {
+ return StringHelper::cartesianProduct(
+ array(
+ array('hosts'),
+ array(
+ Host::getStateText(Host::STATE_UP),
+ Host::getStateText(Host::STATE_DOWN),
+ Host::getStateText(Host::STATE_UNREACHABLE),
+ Host::getStateText(Host::STATE_PENDING)
+ ),
+ array(null, 'handled', 'unhandled')
+ ),
+ '_'
+ );
+ }
+
+ /**
+ * Returns a Filter that matches all hosts in this list
+ *
+ * @return Filter
+ */
+ public function objectsFilter($columns = array('host' => 'host'))
+ {
+ $filterExpression = array();
+ foreach ($this as $host) {
+ $filterExpression[] = Filter::where($columns['host'], $host->getName());
+ }
+ return FilterOr::matchAny($filterExpression);
+ }
+
+ /**
+ * Get the comments
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Hostcomment
+ */
+ public function getComments()
+ {
+ return $this->backend
+ ->select()
+ ->from('hostcomment', array('host_name'))
+ ->applyFilter(clone $this->filter);
+ }
+
+ /**
+ * Get the scheduled downtimes
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Hostdowntime
+ */
+ public function getScheduledDowntimes()
+ {
+ return $this->backend
+ ->select()
+ ->from('hostdowntime', array('host_name'))
+ ->applyFilter(clone $this->filter);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getUnacknowledgedObjects()
+ {
+ $unhandledObjects = array();
+ foreach ($this as $object) {
+ if (! in_array((int) $object->state, array(0, 99)) &&
+ (bool) $object->host_acknowledged === false) {
+ $unhandledObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($unhandledObjects);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/Macro.php b/modules/monitoring/library/Monitoring/Object/Macro.php
new file mode 100644
index 0000000..18a1855
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/Macro.php
@@ -0,0 +1,83 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Exception;
+use Icinga\Application\Logger;
+use stdClass;
+
+/**
+ * Expand macros in string in the context of MonitoredObjects
+ */
+class Macro
+{
+ /**
+ * Known icinga macros
+ *
+ * @var array
+ */
+ private static $icingaMacros = array(
+ 'HOSTNAME' => 'host_name',
+ 'HOSTADDRESS' => 'host_address',
+ 'HOSTADDRESS6' => 'host_address6',
+ 'SERVICEDESC' => 'service_description',
+ 'host.name' => 'host_name',
+ 'host.address' => 'host_address',
+ 'host.address6' => 'host_address6',
+ 'service.description' => 'service_description',
+ 'service.name' => 'service_description'
+ );
+
+ /**
+ * Return the given string with macros being resolved
+ *
+ * @param string $input The string in which to look for macros
+ * @param MonitoredObject|stdClass $object The host or service used to resolve macros
+ *
+ * @return string The substituted or unchanged string
+ */
+ public static function resolveMacros($input, $object)
+ {
+ $matches = array();
+ if (preg_match_all('@\$([^\$\s]+)\$@', $input, $matches)) {
+ foreach ($matches[1] as $key => $value) {
+ $newValue = self::resolveMacro($value, $object);
+ if ($newValue !== $value) {
+ $input = str_replace($matches[0][$key], $newValue, $input);
+ }
+ }
+ }
+
+ return $input;
+ }
+
+ /**
+ * Resolve a macro based on the given object
+ *
+ * @param string $macro The macro to resolve
+ * @param MonitoredObject|stdClass $object The object used to resolve the macro
+ *
+ * @return string The new value or the macro if it cannot be resolved
+ */
+ public static function resolveMacro($macro, $object)
+ {
+ if (isset(self::$icingaMacros[$macro]) && isset($object->{self::$icingaMacros[$macro]})) {
+ return $object->{self::$icingaMacros[$macro]};
+ }
+
+ try {
+ $value = $object->$macro;
+ } catch (Exception $e) {
+ $objectName = $object->getName();
+ if ($object instanceof Service) {
+ $objectName = $object->getHost()->getName() . '!' . $objectName;
+ }
+
+ $value = null;
+ Logger::debug('Unable to resolve macro "%s" on object "%s". An error occured: %s', $macro, $objectName, $e);
+ }
+
+ return $value !== null ? $value : $macro;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
new file mode 100644
index 0000000..91fd9e7
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
@@ -0,0 +1,930 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\Filter\FilterEqual;
+use stdClass;
+use InvalidArgumentException;
+use Icinga\Authentication\Auth;
+use Icinga\Application\Config;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filterable;
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Util\GlobFilter;
+use Icinga\Web\UrlParams;
+
+/**
+ * A monitored Icinga object, i.e. host or service
+ */
+abstract class MonitoredObject implements Filterable
+{
+ /**
+ * Type host
+ */
+ const TYPE_HOST = 'host';
+
+ /**
+ * Type service
+ */
+ const TYPE_SERVICE = 'service';
+
+ /**
+ * Acknowledgement of the host or service if any
+ *
+ * @var object
+ */
+ protected $acknowledgement;
+
+ /**
+ * Backend to fetch object information from
+ *
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ /**
+ * Comments
+ *
+ * @var array
+ */
+ protected $comments;
+
+ /**
+ * This object's obfuscated custom variables
+ *
+ * @var array
+ */
+ protected $customvars;
+
+ /**
+ * This object's obfuscated custom variables, names not lower case
+ *
+ * @var array
+ */
+ protected $customvarsWithOriginalNames;
+
+ /**
+ * The host custom variables
+ *
+ * @var array
+ */
+ protected $hostVariables;
+
+ /**
+ * The service custom variables
+ *
+ * @var array
+ */
+ protected $serviceVariables;
+
+ /**
+ * Contact groups
+ *
+ * @var array
+ */
+ protected $contactgroups;
+
+ /**
+ * Contacts
+ *
+ * @var array
+ */
+ protected $contacts;
+
+ /**
+ * Downtimes
+ *
+ * @var array
+ */
+ protected $downtimes;
+
+ /**
+ * Event history
+ *
+ * @var \Icinga\Module\Monitoring\DataView\EventHistory
+ */
+ protected $eventhistory;
+
+ /**
+ * Filter
+ *
+ * @var Filter
+ */
+ protected $filter;
+
+ /**
+ * Host groups
+ *
+ * @var array
+ */
+ protected $hostgroups;
+
+ /**
+ * Prefix of the Icinga object, i.e. 'host_' or 'service_'
+ *
+ * @var string
+ */
+ protected $prefix;
+
+ /**
+ * Properties
+ *
+ * @var object
+ */
+ protected $properties;
+
+ /**
+ * Service groups
+ *
+ * @var array
+ */
+ protected $servicegroups;
+
+ /**
+ * Type of the Icinga object, i.e. 'host' or 'service'
+ *
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * Stats
+ *
+ * @var object
+ */
+ protected $stats;
+
+ /**
+ * The properties to hide from the user
+ *
+ * @var GlobFilter
+ */
+ protected $blacklistedProperties = null;
+
+ /**
+ * Create a monitored object, i.e. host or service
+ *
+ * @param MonitoringBackend $backend Backend to fetch object information from
+ */
+ public function __construct(MonitoringBackend $backend)
+ {
+ $this->backend = $backend;
+ }
+
+ /**
+ * Get the object's data view
+ *
+ * @return \Icinga\Module\Monitoring\DataView\DataView
+ */
+ abstract protected function getDataView();
+
+ /**
+ * Get all note urls configured for this monitored object
+ *
+ * @return array All note urls as a string
+ */
+ abstract public function getNotesUrls();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ // Left out on purpose. Interface is deprecated.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function applyFilter(Filter $filter)
+ {
+ $this->getFilter()->addFilter($filter);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFilter()
+ {
+ if ($this->filter === null) {
+ $this->filter = Filter::matchAll();
+ }
+
+ return $this->filter;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setFilter(Filter $filter)
+ {
+ // Left out on purpose. Interface is deprecated.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ // Left out on purpose. Interface is deprecated.
+ }
+
+ /**
+ * Require the object's type to be one of the given types
+ *
+ * @param array $oneOf
+ *
+ * @return bool
+ * @throws InvalidArgumentException If the object's type is not one of the given types.
+ */
+ public function assertOneOf(array $oneOf)
+ {
+ if (! in_array($this->type, $oneOf)) {
+ throw new InvalidArgumentException;
+ }
+ return true;
+ }
+
+ /**
+ * Fetch the object's properties
+ *
+ * @return bool
+ */
+ public function fetch()
+ {
+ $properties = $this->getDataView()->applyFilter($this->getFilter())->getQuery()->fetchRow();
+
+ if ($properties === false) {
+ return false;
+ }
+
+ if (isset($properties->host_contacts)) {
+ $this->contacts = array();
+ foreach (preg_split('~,~', $properties->host_contacts) as $contact) {
+ $this->contacts[] = (object) array(
+ 'contact_name' => $contact,
+ 'contact_alias' => $contact,
+ 'contact_email' => null,
+ 'contact_pager' => null,
+ );
+ }
+ }
+
+ $this->properties = $properties;
+
+ return true;
+ }
+
+ /**
+ * Fetch the object's acknowledgement
+ */
+ public function fetchAcknowledgement()
+ {
+ if ($this->comments === null) {
+ $this->fetchComments();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fetch the object's comments
+ *
+ * @return $this
+ */
+ public function fetchComments()
+ {
+ $commentsView = $this->backend->select()->from('comment', array(
+ 'author' => 'comment_author_name',
+ 'comment' => 'comment_data',
+ 'expiration' => 'comment_expiration',
+ 'id' => 'comment_internal_id',
+ 'name' => 'comment_name',
+ 'persistent' => 'comment_is_persistent',
+ 'timestamp' => 'comment_timestamp',
+ 'type' => 'comment_type'
+ ));
+ if ($this->type === self::TYPE_SERVICE) {
+ $commentsView
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+ } else {
+ $commentsView->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+ }
+ $commentsView
+ ->whereEx(new FilterEqual('comment_type', '=', ['ack', 'comment']))
+ ->whereEx(new FilterEqual('object_type', '=', $this->type));
+
+ $comments = $commentsView->fetchAll();
+
+ if ((bool) $this->properties->{$this->prefix . 'acknowledged'}) {
+ $ackCommentIdx = null;
+
+ foreach ($comments as $i => $comment) {
+ if ($comment->type === 'ack') {
+ $this->acknowledgement = new Acknowledgement(array(
+ 'author' => $comment->author,
+ 'comment' => $comment->comment,
+ 'entry_time' => $comment->timestamp,
+ 'expiration_time' => $comment->expiration,
+ 'sticky' => (int) $this->properties->{$this->prefix . 'acknowledgement_type'} === 2
+ ));
+ $ackCommentIdx = $i;
+ break;
+ }
+ }
+
+ if ($ackCommentIdx !== null) {
+ unset($comments[$ackCommentIdx]);
+ }
+ }
+
+ $this->comments = $comments;
+
+ return $this;
+ }
+
+ /**
+ * Fetch the object's contact groups
+ *
+ * @return $this
+ */
+ public function fetchContactgroups()
+ {
+ $contactsGroups = $this->backend->select()->from('contactgroup', array(
+ 'contactgroup_name',
+ 'contactgroup_alias'
+ ));
+ if ($this->type === self::TYPE_SERVICE) {
+ $contactsGroups
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+ } else {
+ $contactsGroups->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+ }
+ $this->contactgroups = $contactsGroups;
+ return $this;
+ }
+
+ /**
+ * Fetch the object's contacts
+ *
+ * @return $this
+ */
+ public function fetchContacts()
+ {
+ $contacts = $this->backend->select()->from("{$this->type}contact", array(
+ 'contact_name',
+ 'contact_alias',
+ 'contact_email',
+ 'contact_pager',
+ ));
+ if ($this->type === self::TYPE_SERVICE) {
+ $contacts
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+ } else {
+ $contacts->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+ }
+ $this->contacts = $contacts;
+ return $this;
+ }
+
+ /**
+ * Fetch this object's obfuscated custom variables
+ *
+ * @return $this
+ */
+ public function fetchCustomvars()
+ {
+
+ if ($this->type === self::TYPE_SERVICE) {
+ $this->fetchServiceVariables();
+ $customvars = $this->serviceVariables;
+ } else {
+ $this->fetchHostVariables();
+ $customvars = $this->hostVariables;
+ }
+
+ $this->customvars = $customvars;
+ $this->hideBlacklistedProperties();
+ $this->customvars = $this->obfuscateCustomVars($this->customvars, null);
+ $this->customvarsWithOriginalNames = $this->obfuscateCustomVars($this->customvarsWithOriginalNames, null);
+
+ return $this;
+ }
+
+ /**
+ * Obfuscate custom variables recursively
+ *
+ * @param stdClass|array $customvars The custom variables to obfuscate
+ *
+ * @return stdClass|array The obfuscated custom variables
+ */
+ protected function obfuscateCustomVars($customvars, $_)
+ {
+ return self::protectCustomVars($customvars);
+ }
+
+ public static function protectCustomVars($customvars)
+ {
+ $blacklist = [];
+ $blacklistPattern = '';
+
+ if (($blacklistConfig = Config::module('monitoring')->get('security', 'protected_customvars', '')) !== '') {
+ foreach (explode(',', $blacklistConfig) as $customvar) {
+ $nonWildcards = array();
+ foreach (explode('*', $customvar) as $nonWildcard) {
+ $nonWildcards[] = preg_quote($nonWildcard, '/');
+ }
+ $blacklist[] = implode('.*', $nonWildcards);
+ }
+ $blacklistPattern = '/^(' . implode('|', $blacklist) . ')$/i';
+ }
+
+ if (! $blacklistPattern) {
+ return $customvars;
+ }
+
+ $obfuscator = function ($vars) use ($blacklistPattern, &$obfuscator) {
+ $result = [];
+ foreach ($vars as $name => $value) {
+ if ($blacklistPattern && preg_match($blacklistPattern, $name)) {
+ $result[$name] = '***';
+ } elseif ($value instanceof stdClass || is_array($value)) {
+ $obfuscated = $obfuscator($value);
+ $result[$name] = $value instanceof stdClass ? (object) $obfuscated : $obfuscated;
+ } else {
+ $result[$name] = $value;
+ }
+ }
+
+ return $result;
+ };
+ $obfuscatedCustomVars = $obfuscator($customvars);
+
+ return $customvars instanceof stdClass ? (object) $obfuscatedCustomVars : $obfuscatedCustomVars;
+ }
+
+ /**
+ * Hide all blacklisted properties from the user as restricted by monitoring/blacklist/properties
+ *
+ * Currently this only affects the custom variables
+ */
+ protected function hideBlacklistedProperties()
+ {
+ if ($this->blacklistedProperties === null) {
+ $this->blacklistedProperties = new GlobFilter(
+ Auth::getInstance()->getRestrictions('monitoring/blacklist/properties')
+ );
+ }
+
+ $allProperties = $this->blacklistedProperties->removeMatching(
+ [$this->type => ['vars' => $this->customvars]]
+ );
+ $this->customvars = isset($allProperties[$this->type]['vars'])
+ ? $allProperties[$this->type]['vars']
+ : [];
+
+ $allProperties = $this->blacklistedProperties->removeMatching(
+ [$this->type => ['vars' => $this->customvarsWithOriginalNames]]
+ );
+ $this->customvarsWithOriginalNames = isset($allProperties[$this->type]['vars'])
+ ? $allProperties[$this->type]['vars']
+ : [];
+ }
+
+ /**
+ * Fetch the host custom variables related to this object
+ *
+ * @return $this
+ */
+ public function fetchHostVariables()
+ {
+ $query = $this->backend->select()->from('customvar', array(
+ 'varname',
+ 'varvalue',
+ 'is_json'
+ ))
+ ->whereEx(new FilterEqual('object_type', '=', static::TYPE_HOST))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+
+ $this->hostVariables = [];
+
+ if ($this->type === static::TYPE_HOST) {
+ $this->customvarsWithOriginalNames = [];
+ }
+
+ foreach ($query as $row) {
+ if ($row->is_json) {
+ $this->hostVariables[strtolower($row->varname)] = json_decode($row->varvalue);
+ } else {
+ $this->hostVariables[strtolower($row->varname)] = $row->varvalue;
+ }
+
+ if ($this->type === static::TYPE_HOST) {
+ $this->customvarsWithOriginalNames[$row->varname] = $this->hostVariables[strtolower($row->varname)];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fetch the service custom variables related to this object
+ *
+ * @return $this
+ *
+ * @throws ProgrammingError In case this object is not a service
+ */
+ public function fetchServiceVariables()
+ {
+ if ($this->type !== static::TYPE_SERVICE) {
+ throw new ProgrammingError('Cannot fetch service custom variables for non-service objects');
+ }
+
+ $query = $this->backend->select()->from('customvar', array(
+ 'varname',
+ 'varvalue',
+ 'is_json'
+ ))
+ ->whereEx(new FilterEqual('object_type', '=', static::TYPE_SERVICE))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+
+ $this->serviceVariables = [];
+ $this->customvarsWithOriginalNames = [];
+ foreach ($query as $row) {
+ if ($row->is_json) {
+ $this->customvarsWithOriginalNames[$row->varname] = json_decode($row->varvalue);
+ $this->serviceVariables[strtolower($row->varname)] = $this->customvarsWithOriginalNames[$row->varname];
+ } else {
+ $this->serviceVariables[strtolower($row->varname)] = $row->varvalue;
+ $this->customvarsWithOriginalNames[$row->varname] = $row->varvalue;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fetch the object's downtimes
+ *
+ * @return $this
+ */
+ public function fetchDowntimes()
+ {
+ $downtimes = $this->backend->select()->from('downtime', array(
+ 'author_name' => 'downtime_author_name',
+ 'comment' => 'downtime_comment',
+ 'duration' => 'downtime_duration',
+ 'end' => 'downtime_end',
+ 'entry_time' => 'downtime_entry_time',
+ 'id' => 'downtime_internal_id',
+ 'is_fixed' => 'downtime_is_fixed',
+ 'is_flexible' => 'downtime_is_flexible',
+ 'is_in_effect' => 'downtime_is_in_effect',
+ 'name' => 'downtime_name',
+ 'objecttype' => 'object_type',
+ 'scheduled_end' => 'downtime_scheduled_end',
+ 'scheduled_start' => 'downtime_scheduled_start',
+ 'start' => 'downtime_start'
+ ))
+ ->whereEx(new FilterEqual('object_type', '=', $this->type))
+ ->order('downtime_is_in_effect', 'DESC')
+ ->order('downtime_scheduled_start', 'ASC');
+ if ($this->type === self::TYPE_SERVICE) {
+ $downtimes
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+ } else {
+ $downtimes
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+ }
+ $this->downtimes = $downtimes->getQuery()->fetchAll();
+ return $this;
+ }
+
+ /**
+ * Fetch the object's event history
+ *
+ * @return $this
+ */
+ public function fetchEventhistory()
+ {
+ $eventHistory = $this->backend
+ ->select()
+ ->from(
+ 'eventhistory',
+ array(
+ 'id',
+ 'object_type',
+ 'host_name',
+ 'host_display_name',
+ 'service_description',
+ 'service_display_name',
+ 'timestamp',
+ 'state',
+ 'output',
+ 'type'
+ )
+ )
+ ->whereEx(new FilterEqual('object_type', '=', $this->type))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+
+ if ($this->type === self::TYPE_SERVICE) {
+ $eventHistory->whereEx(
+ new FilterEqual('service_description', '=', $this->service_description)
+ );
+ }
+
+ $this->eventhistory = $eventHistory;
+ return $this;
+ }
+
+ /**
+ * Fetch the object's host groups
+ *
+ * @return $this
+ */
+ public function fetchHostgroups()
+ {
+ $this->hostgroups = $this->backend->select()
+ ->from('hostgroup', array('hostgroup_name', 'hostgroup_alias'))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name))
+ ->applyFilter($this->getFilter())
+ ->fetchPairs();
+ return $this;
+ }
+
+ /**
+ * Fetch the object's service groups
+ *
+ * @return $this
+ */
+ public function fetchServicegroups()
+ {
+ $query = $this->backend->select()
+ ->from('servicegroup', array('servicegroup_name', 'servicegroup_alias'))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+
+ if ($this->type === self::TYPE_SERVICE) {
+ $query->whereEx(
+ new FilterEqual('service_description', '=', $this->service_description)
+ );
+ }
+
+ $this->servicegroups = $query->applyFilter($this->getFilter())->fetchPairs();
+ return $this;
+ }
+
+ /**
+ * Fetch stats
+ *
+ * @return $this
+ */
+ public function fetchStats()
+ {
+ $this->stats = $this->backend->select()->from('servicestatussummary', array(
+ 'services_total',
+ 'services_ok',
+ 'services_critical',
+ 'services_critical_unhandled',
+ 'services_critical_handled',
+ 'services_warning',
+ 'services_warning_unhandled',
+ 'services_warning_handled',
+ 'services_unknown',
+ 'services_unknown_unhandled',
+ 'services_unknown_handled',
+ 'services_pending',
+ ))
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->applyFilter($this->getFilter())
+ ->fetchRow();
+ return $this;
+ }
+
+ /**
+ * Get all action urls configured for this monitored object
+ *
+ * @return array All note urls as a string
+ */
+ public function getActionUrls()
+ {
+ return $this->resolveAllStrings(
+ MonitoredObject::parseAttributeUrls($this->action_url)
+ );
+ }
+
+ /**
+ * Get the type of the object
+ *
+ * @param bool $translate
+ *
+ * @return string
+ */
+ public function getType($translate = false)
+ {
+ if ($translate !== false) {
+ switch ($this->type) {
+ case self::TYPE_HOST:
+ $type = mt('montiroing', 'host');
+ break;
+ case self::TYPE_SERVICE:
+ $type = mt('monitoring', 'service');
+ break;
+ default:
+ throw new InvalidArgumentException('Invalid type ' . $this->type);
+ }
+ } else {
+ $type = $this->type;
+ }
+ return $type;
+ }
+
+ /**
+ * Parse the content of the action_url or notes_url attributes
+ *
+ * Find all occurences of http links, separated by whitespaces and quoted
+ * by single or double-ticks.
+ *
+ * @link http://docs.icinga.com/latest/de/objectdefinitions.html
+ *
+ * @param string $urlString A string containing one or more urls
+ * @return array Array of urls as strings
+ */
+ public static function parseAttributeUrls($urlString)
+ {
+ if (empty($urlString)) {
+ return array();
+ }
+ $links = array();
+ if (strpos($urlString, "' ") === false) {
+ $links[] = $urlString;
+ } else {
+ // parse notes-url format
+ foreach (explode("' ", $urlString) as $url) {
+ $url = strpos($url, "'") === 0 ? substr($url, 1) : $url;
+ $url = strrpos($url, "'") === strlen($url) - 1 ? substr($url, 0, strlen($url) - 1) : $url;
+ $links[] = $url;
+ }
+ }
+ return $links;
+ }
+
+ /**
+ * Fetch all available data of the object
+ *
+ * @return $this
+ */
+ public function populate()
+ {
+ $this
+ ->fetchComments()
+ ->fetchContactgroups()
+ ->fetchContacts()
+ ->fetchCustomvars()
+ ->fetchDowntimes();
+
+ // Call fetchHostgroups or fetchServicegroups depending on the object's type
+ $fetchGroups = 'fetch' . ucfirst($this->type) . 'groups';
+ $this->$fetchGroups();
+
+ return $this;
+ }
+
+ /**
+ * Resolve macros in all given strings in the current object context
+ *
+ * @param array $strs An array of urls as string
+ *
+ * @return array
+ */
+ protected function resolveAllStrings(array $strs)
+ {
+ foreach ($strs as $i => $str) {
+ $strs[$i] = Macro::resolveMacros($str, $this);
+ }
+ return $strs;
+ }
+
+ /**
+ * Set the object's properties
+ *
+ * @param object $properties
+ *
+ * @return $this
+ */
+ public function setProperties($properties)
+ {
+ $this->properties = (object) $properties;
+ return $this;
+ }
+
+ public function __isset($name)
+ {
+ if (property_exists($this->properties, $name)) {
+ return isset($this->properties->$name);
+ } elseif (property_exists($this, $name)) {
+ return isset($this->$name);
+ }
+ return false;
+ }
+
+ public function __get($name)
+ {
+ if (property_exists($this->properties, $name)) {
+ return $this->properties->$name;
+ } elseif (property_exists($this, $name)) {
+ if ($this->$name === null) {
+ $fetchMethod = 'fetch' . ucfirst($name);
+ $this->$fetchMethod();
+ }
+
+ return $this->$name;
+ } elseif (preg_match('/^_(host|service)_(.+)/i', $name, $matches)) {
+ if (strtolower($matches[1]) === static::TYPE_HOST) {
+ if ($this->hostVariables === null) {
+ $this->fetchHostVariables();
+ }
+
+ $customvars = $this->hostVariables;
+ } else {
+ if ($this->serviceVariables === null) {
+ $this->fetchServiceVariables();
+ }
+
+ $customvars = $this->serviceVariables;
+ }
+
+ $variableName = strtolower($matches[2]);
+ if (isset($customvars[$variableName])) {
+ return $customvars[$variableName];
+ }
+
+ return null; // Unknown custom variables MUST NOT throw an error
+ } elseif (in_array($name, array('contact_name', 'contactgroup_name', 'hostgroup_name', 'servicegroup_name'))) {
+ if ($name === 'contact_name') {
+ if ($this->contacts === null) {
+ $this->fetchContacts();
+ }
+
+ return array_map(function ($el) {
+ return $el->contact_name;
+ }, $this->contacts);
+ } elseif ($name === 'contactgroup_name') {
+ if ($this->contactgroups === null) {
+ $this->fetchContactgroups();
+ }
+
+ return array_map(function ($el) {
+ return $el->contactgroup_name;
+ }, $this->contactgroups);
+ } elseif ($name === 'hostgroup_name') {
+ if ($this->hostgroups === null) {
+ $this->fetchHostgroups();
+ }
+
+ return array_keys($this->hostgroups);
+ } else { // $name === 'servicegroup_name'
+ if ($this->servicegroups === null) {
+ $this->fetchServicegroups();
+ }
+
+ return array_keys($this->servicegroups);
+ }
+ } elseif (strpos($name, $this->prefix) !== 0) {
+ $propertyName = strtolower($name);
+ $prefixedName = $this->prefix . $propertyName;
+ if (property_exists($this->properties, $prefixedName)) {
+ return $this->properties->$prefixedName;
+ }
+
+ if ($this->type === static::TYPE_HOST) {
+ if ($this->hostVariables === null) {
+ $this->fetchHostVariables();
+ }
+
+ $customvars = $this->hostVariables;
+ } else { // $this->type === static::TYPE_SERVICE
+ if ($this->serviceVariables === null) {
+ $this->fetchServiceVariables();
+ }
+
+ $customvars = $this->serviceVariables;
+ }
+
+ if (isset($customvars[$propertyName])) {
+ return $customvars[$propertyName];
+ }
+ }
+
+ throw new InvalidPropertyException('Can\'t access property \'%s\'. Property does not exist.', $name);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/ObjectList.php b/modules/monitoring/library/Monitoring/Object/ObjectList.php
new file mode 100644
index 0000000..5237c56
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/ObjectList.php
@@ -0,0 +1,295 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use ArrayIterator;
+use Countable;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filterable;
+use Icinga\Module\Monitoring\DataView\Downtime;
+use IteratorAggregate;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Traversable;
+
+abstract class ObjectList implements Countable, IteratorAggregate, Filterable
+{
+ /**
+ * @var string
+ */
+ protected $dataViewName;
+
+ /**
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ /**
+ * @var array
+ */
+ protected $columns;
+
+ /**
+ * @var Filter
+ */
+ protected $filter;
+
+ /**
+ * @var array
+ */
+ protected $objects;
+
+ /**
+ * @var int
+ */
+ protected $count;
+
+ public function __construct(MonitoringBackend $backend)
+ {
+ $this->backend = $backend;
+ }
+
+ /**
+ * @param array $columns
+ *
+ * @return $this
+ */
+ public function setColumns(array $columns)
+ {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * @param Filter $filter
+ *
+ * @return $this
+ */
+ public function setFilter(Filter $filter)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * @return Filter|FilterChain
+ */
+ public function getFilter()
+ {
+ if ($this->filter === null) {
+ $this->filter = Filter::matchAll();
+ }
+
+ return $this->filter;
+ }
+
+ public function applyFilter(Filter $filter)
+ {
+ $this->getFilter()->addFilter($filter);
+ return $this;
+ }
+
+ public function addFilter(Filter $filter)
+ {
+ $this->getFilter()->addFilter($filter);
+ }
+
+ public function where($condition, $value = null)
+ {
+ $this->getFilter()->addFilter(Filter::where($condition, $value));
+ }
+
+ abstract protected function fetchObjects();
+
+ /**
+ * @return array
+ */
+ public function fetch()
+ {
+ if ($this->objects === null) {
+ $this->objects = $this->fetchObjects();
+ }
+ return $this->objects;
+ }
+
+ public function count(): int
+ {
+ if ($this->count === null) {
+ $this->count = (int) $this->backend
+ ->select()
+ ->from($this->dataViewName, $this->columns)
+ ->applyFilter($this->filter)
+ ->getQuery()
+ ->count();
+ }
+
+ return $this->count;
+ }
+
+ public function getIterator(): Traversable
+ {
+ if ($this->objects === null) {
+ $this->fetch();
+ }
+ return new ArrayIterator($this->objects);
+ }
+
+ /**
+ * Get the comments
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Comment
+ */
+ public function getComments()
+ {
+ return $this->backend->select()->from('comment')->applyFilter($this->filter);
+ }
+
+ /**
+ * Get the scheduled downtimes
+ *
+ * @return Downtime
+ */
+ public function getScheduledDowntimes()
+ {
+ return $this->backend->select()->from('downtime')->applyFilter($this->filter);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getAcknowledgedObjects()
+ {
+ $acknowledgedObjects = array();
+ foreach ($this as $object) {
+ if ((bool) $object->acknowledged === true) {
+ $acknowledgedObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($acknowledgedObjects);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getObjectsInDowntime()
+ {
+ $objectsInDowntime = array();
+ foreach ($this as $object) {
+ if ((bool) $object->in_downtime === true) {
+ $objectsInDowntime[] = $object;
+ }
+ }
+ return $this->newFromArray($objectsInDowntime);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getUnhandledObjects()
+ {
+ $unhandledObjects = array();
+ foreach ($this as $object) {
+ if ((bool) $object->problem === true && (bool) $object->handled === false) {
+ $unhandledObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($unhandledObjects);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getProblemObjects()
+ {
+ $handledObjects = array();
+ foreach ($this as $object) {
+ if ((bool) $object->problem === true) {
+ $handledObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($handledObjects);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ abstract public function getUnacknowledgedObjects();
+
+ /**
+ * Create a ObjectList from an array of hosts without querying a backend
+ *
+ * @return ObjectList
+ */
+ protected function newFromArray(array $objects)
+ {
+ $class = get_called_class();
+ $list = new $class($this->backend);
+ $list->objects = $objects;
+ $list->count = count($objects);
+ $list->filter = $list->objectsFilter();
+ return $list;
+ }
+
+ /**
+ * Create a filter that matches exactly the elements of this object list
+ *
+ * @param array $columns Override default column names.
+ *
+ * @return Filter
+ */
+ abstract public function objectsFilter($columns = array());
+
+ /**
+ * Get the feature status
+ *
+ * @return array
+ */
+ public function getFeatureStatus()
+ {
+ // null - init
+ // 0 - disabled
+ // 1 - enabled
+ // 2 - enabled & disabled
+ $featureStatus = array(
+ 'active_checks_enabled' => null,
+ 'passive_checks_enabled' => null,
+ 'obsessing' => null,
+ 'notifications_enabled' => null,
+ 'event_handler_enabled' => null,
+ 'flap_detection_enabled' => null
+ );
+
+ $features = array();
+
+ foreach ($featureStatus as $feature => &$status) {
+ $features[$feature] = &$status;
+ }
+
+ foreach ($this as $object) {
+ foreach ($features as $feature => &$status) {
+ $enabled = (int) $object->{$feature};
+ if (! isset($status)) {
+ $status = $enabled;
+ } elseif ($status !== $enabled) {
+ unset($features[$feature]);
+ if (empty($features)) {
+ break 2;
+ }
+
+ break;
+ }
+ }
+ }
+
+ return $featureStatus;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/Service.php b/modules/monitoring/library/Monitoring/Object/Service.php
new file mode 100644
index 0000000..a63db6f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/Service.php
@@ -0,0 +1,220 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\Filter\FilterEqual;
+use Icinga\Module\Monitoring\DataView\Servicestatus;
+use InvalidArgumentException;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+
+/**
+ * An Icinga service
+ */
+class Service extends MonitoredObject
+{
+ /**
+ * Service state 'OK'
+ */
+ const STATE_OK = 0;
+
+ /**
+ * Service state 'WARNING'
+ */
+ const STATE_WARNING = 1;
+
+ /**
+ * Service state 'CRITICAL'
+ */
+ const STATE_CRITICAL = 2;
+
+ /**
+ * Service state 'UNKNOWN'
+ */
+ const STATE_UNKNOWN = 3;
+
+ /**
+ * Service state 'PENDING'
+ */
+ const STATE_PENDING = 99;
+
+ /**
+ * Type of the Icinga service
+ *
+ * @var string
+ */
+ public $type = self::TYPE_SERVICE;
+
+ /**
+ * Prefix of the Icinga service
+ *
+ * @var string
+ */
+ public $prefix = 'service_';
+
+ /**
+ * Host the service is running on
+ *
+ * @var Host
+ */
+ protected $host;
+
+ /**
+ * Service name
+ *
+ * @var string
+ */
+ protected $service;
+
+ /**
+ * Create a new service
+ *
+ * @param MonitoringBackend $backend Backend to fetch service information from
+ * @param string $host Hostname the service is running on
+ * @param string $service Service name
+ */
+ public function __construct(MonitoringBackend $backend, $host, $service)
+ {
+ parent::__construct($backend);
+ $this->host = new Host($backend, $host);
+ $this->service = $service;
+ }
+
+ /**
+ * Get the host the service is running on
+ *
+ * @return Host
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Get the service name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->service;
+ }
+
+ /**
+ * Get the data view
+ *
+ * @return Servicestatus
+ */
+ protected function getDataView()
+ {
+ return $this->backend->select()->from('servicestatus', array(
+ 'instance_name',
+ 'host_attempt',
+ 'host_icon_image',
+ 'host_icon_image_alt',
+ 'host_acknowledged',
+ 'host_active_checks_enabled',
+ 'host_address',
+ 'host_address6',
+ 'host_alias',
+ 'host_display_name',
+ 'host_handled',
+ 'host_in_downtime',
+ 'host_is_flapping',
+ 'host_last_state_change',
+ 'host_name',
+ 'host_notifications_enabled',
+ 'host_passive_checks_enabled',
+ 'host_state',
+ 'host_state_type',
+ 'service_icon_image',
+ 'service_icon_image_alt',
+ 'service_acknowledged',
+ 'service_acknowledgement_type',
+ 'service_action_url',
+ 'service_active_checks_enabled',
+ 'service_active_checks_enabled_changed',
+ 'service_attempt',
+ 'service_check_command',
+ 'service_check_execution_time',
+ 'service_check_interval',
+ 'service_check_latency',
+ 'service_check_source',
+ 'service_check_timeperiod',
+ 'service_current_notification_number',
+ 'service_description',
+ 'service_display_name',
+ 'service_event_handler_enabled',
+ 'service_event_handler_enabled_changed',
+ 'service_flap_detection_enabled',
+ 'service_flap_detection_enabled_changed',
+ 'service_handled',
+ 'service_in_downtime',
+ 'service_is_flapping',
+ 'service_is_reachable',
+ 'service_last_check',
+ 'service_last_notification',
+ 'service_last_state_change',
+ 'service_long_output',
+ 'service_next_check',
+ 'service_next_update',
+ 'service_notes',
+ 'service_notes_url',
+ 'service_notifications_enabled',
+ 'service_notifications_enabled_changed',
+ 'service_obsessing',
+ 'service_obsessing_changed',
+ 'service_output',
+ 'service_passive_checks_enabled',
+ 'service_passive_checks_enabled_changed',
+ 'service_percent_state_change',
+ 'service_perfdata',
+ 'service_process_perfdata' => 'service_process_performance_data',
+ 'service_state',
+ 'service_state_type'
+ ))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host->getName()))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service));
+ }
+
+ /**
+ * Get the optional translated textual representation of a service state
+ *
+ * @param int $state
+ * @param bool $translate
+ *
+ * @return string
+ * @throws InvalidArgumentException If the service state is not valid
+ */
+ public static function getStateText($state, $translate = false)
+ {
+ $translate = (bool) $translate;
+ switch ((int) $state) {
+ case self::STATE_OK:
+ $text = $translate ? mt('monitoring', 'OK') : 'ok';
+ break;
+ case self::STATE_WARNING:
+ $text = $translate ? mt('monitoring', 'WARNING') : 'warning';
+ break;
+ case self::STATE_CRITICAL:
+ $text = $translate ? mt('monitoring', 'CRITICAL') : 'critical';
+ break;
+ case self::STATE_UNKNOWN:
+ $text = $translate ? mt('monitoring', 'UNKNOWN') : 'unknown';
+ break;
+ case self::STATE_PENDING:
+ $text = $translate ? mt('monitoring', 'PENDING') : 'pending';
+ break;
+ default:
+ throw new InvalidArgumentException(sprintf('Invalid service state \'%s\'', $state));
+ }
+ return $text;
+ }
+
+ public function getNotesUrls()
+ {
+ return $this->resolveAllStrings(
+ MonitoredObject::parseAttributeUrls($this->service_notes_url)
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/ServiceList.php b/modules/monitoring/library/Monitoring/Object/ServiceList.php
new file mode 100644
index 0000000..5bc0bdb
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/ServiceList.php
@@ -0,0 +1,184 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Data\SimpleQuery;
+use Icinga\Util\StringHelper;
+
+/**
+ * A service list
+ */
+class ServiceList extends ObjectList
+{
+ protected $hostStateSummary;
+
+ protected $serviceStateSummary;
+
+ protected $dataViewName = 'servicestatus';
+
+ protected $columns = array('host_name', 'service_description');
+
+ protected function fetchObjects()
+ {
+ $services = array();
+ $query = $this->backend->select()->from($this->dataViewName, $this->columns)->applyFilter($this->filter)
+ ->getQuery()->getSelectQuery()->query();
+ foreach ($query as $row) {
+ /** @var object $row */
+ $service = new Service($this->backend, $row->host_name, $row->service_description);
+ $service->setProperties($row);
+ $services[] = $service;
+ }
+ return $services;
+ }
+
+ /**
+ * Create a state summary of all services that can be consumed by servicesummary.phtml
+ *
+ * @return SimpleQuery
+ */
+ public function getServiceStateSummary()
+ {
+ if (! $this->serviceStateSummary) {
+ $this->initStateSummaries();
+ }
+
+ $ds = new ArrayDatasource(array((object) $this->serviceStateSummary));
+ return $ds->select();
+ }
+
+ /**
+ * Create a state summary of all hosts that can be consumed by hostsummary.phtml
+ *
+ * @return SimpleQuery
+ */
+ public function getHostStateSummary()
+ {
+ if (! $this->hostStateSummary) {
+ $this->initStateSummaries();
+ }
+
+ $ds = new ArrayDatasource(array((object) $this->hostStateSummary));
+ return $ds->select();
+ }
+
+ /**
+ * Calculate the current state summary and populate hostStateSummary and serviceStateSummary
+ * properties
+ */
+ protected function initStateSummaries()
+ {
+ $serviceStates = array_fill_keys(self::getServiceStatesSummaryEmpty(), 0);
+ $hostStates = array_fill_keys(HostList::getHostStatesSummaryEmpty(), 0);
+
+ foreach ($this as $service) {
+ $unhandled = false;
+ if ((bool) $service->problem === true && (bool) $service->handled === false) {
+ $unhandled = true;
+ }
+
+ $stateName = 'services_' . $service::getStateText($service->state);
+ ++$serviceStates[$stateName];
+ ++$serviceStates[$stateName . ($unhandled ? '_unhandled' : '_handled')];
+
+ if (! isset($knownHostStates[$service->getHost()->getName()])) {
+ $unhandledHost = (bool) $service->host_problem === true && (bool) $service->host_handled === false;
+ ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)];
+ ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)
+ . ($unhandledHost ? '_unhandled' : '_handled')];
+ $knownHostStates[$service->getHost()->getName()] = true;
+ }
+ }
+
+ $serviceStates['services_total'] = count($this);
+ $this->hostStateSummary = $hostStates;
+ $this->serviceStateSummary = $serviceStates;
+ }
+
+ /**
+ * Return an empty array with all possible host state names
+ *
+ * @return array An array containing all possible host states as keys and 0 as values.
+ */
+ public static function getServiceStatesSummaryEmpty()
+ {
+ return StringHelper::cartesianProduct(
+ array(
+ array('services'),
+ array(
+ Service::getStateText(Service::STATE_OK),
+ Service::getStateText(Service::STATE_WARNING),
+ Service::getStateText(Service::STATE_CRITICAL),
+ Service::getStateText(Service::STATE_UNKNOWN),
+ Service::getStateText(Service::STATE_PENDING)
+ ),
+ array(null, 'handled', 'unhandled')
+ ),
+ '_'
+ );
+ }
+
+ /**
+ * Returns a Filter that matches all hosts in this HostList
+ *
+ * @param array $columns Override filter column names
+ *
+ * @return Filter
+ */
+ public function objectsFilter($columns = array('host' => 'host', 'service' => 'service'))
+ {
+ $filterExpression = array();
+ foreach ($this as $service) {
+ $filterExpression[] = Filter::matchAll(
+ Filter::where($columns['host'], $service->getHost()->getName()),
+ Filter::where($columns['service'], $service->getName())
+ );
+ }
+ return FilterOr::matchAny($filterExpression);
+ }
+
+ /**
+ * Get the comments
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Hostcomment
+ */
+ public function getComments()
+ {
+ return $this->backend
+ ->select()
+ ->from('servicecomment', array('host_name', 'service_description'))
+ ->applyFilter(clone $this->filter);
+ }
+
+ /**
+ * Get the scheduled downtimes
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Servicedowntime
+ */
+ public function getScheduledDowntimes()
+ {
+ return $this->backend
+ ->select()
+ ->from('servicedowntime', array('host_name', 'service_description'))
+ ->applyFilter(clone $this->filter);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getUnacknowledgedObjects()
+ {
+ $unhandledObjects = array();
+ foreach ($this as $object) {
+ if (! in_array((int) $object->state, array(0, 99)) &&
+ (bool) $object->service_acknowledged === false) {
+ $unhandledObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($unhandledObjects);
+ }
+}