cube = $cube; } /** * Render the given facts * * @param $facts * @return string */ abstract public function renderFacts($facts); /** * Returns the base url for the details action * * @return string */ abstract protected function getDetailsBaseUrl(); /** * Get the severity sort columns * * @return Generator */ abstract protected function getSeveritySortColumns(): Generator; /** * Initialize all we need */ protected function initialize() { $this->started = false; $this->initializeDimensions() ->initializeFacts() ->initializeLastRow() ->initializeSummaries(); } /** * @return $this */ protected function initializeLastRow() { $object = (object) array(); foreach ($this->dimensions as $dimension) { $object->{$dimension->getName()} = null; } $this->lastRow = $object; return $this; } /** * @return $this */ protected function initializeDimensions() { $this->dimensions = $this->cube->listDimensions(); $min = 3; $cnt = count($this->dimensions); if ($cnt < $min) { $pos = 0; $diff = $min - $cnt; $this->dimensionOrder = []; foreach ($this->dimensions as $name => $_) { $this->dimensionOrder[$pos++ + $diff] = $name; } } else { $this->dimensionOrder = array_keys($this->dimensions); } $this->reversedDimensions = array_reverse($this->dimensionOrder); $this->dimensionLevels = array_flip($this->dimensionOrder); return $this; } /** * @return $this */ protected function initializeFacts() { $this->facts = $this->cube->listFacts(); return $this; } /** * @return $this */ protected function initializeSummaries() { $this->summaries = (object) array(); return $this; } /** * @param object $row * @return bool */ protected function startsDimension($row) { foreach ($this->dimensionOrder as $name) { if ($row->$name === null) { $this->summaries->$name = $this->extractFacts($row); return true; } } return false; } /** * @param $row * @return object */ protected function extractFacts($row) { $res = (object) array(); foreach ($this->facts as $fact) { $res->$fact = $row->$fact; } return $res; } public function render(View $view) { $this->view = $view; $this->initialize(); $htm = $this->beginContainer(); $results = $this->cube->fetchAll(); if (! empty($results) && $this->cube::isUsingIcingaDb()) { $sortBy = $this->cube->getSortBy(); if ($sortBy && $sortBy[0] === $this->cube::DIMENSION_SEVERITY_SORT_PARAM) { $isSortDirDesc = isset($sortBy[1]) && $sortBy[1] !== 'asc'; $results = $this->sortBySeverity($results, $isSortDirDesc); } } foreach ($results as $row) { $htm .= $this->renderRow($row); } return $htm . $this->closeDimensions() . $this->endContainer(); } /** * Sort the results by severity * * @param $results array The fetched results * @param $isSortDirDesc bool Whether the sort direction is descending * * @return Generator */ private function sortBySeverity(array $results, bool $isSortDirDesc): Generator { $perspective = end($this->dimensionOrder); $resultsCount = count($results); $tree = [new TreeNode()]; $prepareHeaders = function (array $tree, object $row): TreeNode { $node = (new TreeNode()) ->setValue($row); $parent = end($tree); $parent->appendChild($node); return $node; }; $i = 0; do { $row = $results[$i]; while ($row->$perspective === null) { $tree[] = $prepareHeaders($tree, $row); if (! isset($results[++$i])) { break; } $row = $results[$i]; } for (; $i < $resultsCount; $i++) { $row = $results[$i]; $anyNull = false; foreach ($this->dimensionOrder as $dimension) { if ($row->$dimension === null) { $anyNull = true; array_pop($tree); } } if ($anyNull) { break; } $prepareHeaders($tree, $row); } } while ($i < $resultsCount); $nodes = function (TreeNode $node) use (&$nodes, $isSortDirDesc): Generator { yield $node->getValue(); $children = $node->getChildren(); uasort($children, function (TreeNode $a, TreeNode $b) use ($isSortDirDesc): int { foreach ($this->getSeveritySortColumns() as $column) { $comparison = $a->getValue()->$column <=> $b->getValue()->$column; if ($comparison !== 0) { return $comparison * ($isSortDirDesc ? -1 : 1); } } // $a and $b are equal in terms of $priorities. return 0; }); foreach ($children as $node) { yield from $nodes($node); } }; return $nodes($tree[1]); } protected function renderRow($row) { $htm = ''; if ($dimension = $this->startsDimension($row)) { return $htm; } $htm .= $this->closeDimensionsForRow($row); $htm .= $this->beginDimensionsForRow($row); $htm .= $this->renderFacts($row); $this->lastRow = $row; return $htm; } protected function beginDimensionsForRow($row) { $last = $this->lastRow; foreach ($this->dimensionOrder as $name) { if ($last->$name !== $row->$name) { return $this->beginDimensionsUpFrom($name, $row); } } return ''; } protected function beginDimensionsUpFrom($dimension, $row) { $htm = ''; $found = false; foreach ($this->dimensionOrder as $name) { if ($name === $dimension) { $found = true; } if ($found) { $htm .= $this->beginDimension($name, $row); } } return $htm; } protected function closeDimensionsForRow($row) { $last = $this->lastRow; foreach ($this->dimensionOrder as $name) { if ($last->$name !== $row->$name) { return $this->closeDimensionsDownTo($name); } } return ''; } protected function closeDimensionsDownTo($name) { $htm = ''; foreach ($this->reversedDimensions as $dimension) { $htm .= $this->closeDimension($dimension); if ($name === $dimension) { break; } } return $htm; } protected function closeDimensions() { $htm = ''; foreach ($this->reversedDimensions as $name) { $htm .= $this->closeDimension($name); } return $htm; } protected function closeDimension($name) { if (! $this->started) { return ''; } $indent = $this->getIndent($name); return $indent . ' ' . "\n" . $indent . "\n"; } protected function getIndent($name) { return str_repeat(' ', $this->getLevel($name)); } protected function beginDimension($name, $row) { $indent = $this->getIndent($name); if (! $this->started) { $this->started = true; } $view = $this->view; $dimension = $this->cube->getDimension($name); return $indent . '
' . "\n" . $indent . '
' . $this->renderDimensionLabel($name, $row) . '
' . "\n" . $indent . '
' . "\n"; } /** * Render the label for a given dimension name * * To have some context available, also * * @param $name * @param $row * @return string */ protected function renderDimensionLabel($name, $row) { $caption = $row->$name; if (empty($caption)) { $caption = '_'; } return $this->view->escape($caption); } protected function getDetailsUrl($name, $row) { $url = Url::fromPath($this->getDetailsBaseUrl()); if ($this->cube instanceof IcingaDbCube && $this->cube->hasBaseFilter()) { /** @var Filter\Rule $baseFilter */ $baseFilter = $this->cube->getBaseFilter(); $url->setFilter($baseFilter); } $urlParams = $url->getParams(); $dimensions = array_merge(array_keys($this->cube->listDimensions()), $this->cube->listSlices()); $urlParams->add('dimensions', DimensionParams::update($dimensions)->getParams()); foreach ($this->cube->listDimensionsUpTo($name) as $dimensionName) { $urlParams->add($this->cube::SLICE_PREFIX . $dimensionName, $row->$dimensionName); } foreach ($this->cube->getSlices() as $key => $val) { $urlParams->add($this->cube::SLICE_PREFIX . $key, $val); } return $url; } protected function getSliceUrl($name, $row) { return $this->view->url() ->setParam($this->cube::SLICE_PREFIX . $name, $row->$name); } protected function isOuterDimension($name) { return $this->reversedDimensions[0] !== $name; } protected function getDimensionClassString($name, $row) { return implode(' ', $this->getDimensionClasses($name, $row)); } protected function getDimensionClasses($name, $row) { return array('cube-dimension' . $this->getLevel($name)); } protected function getLevel($name) { return $this->dimensionLevels[$name]; } /** * @return string */ protected function beginContainer() { return '
' . "\n"; } /** * @return string */ protected function endContainer() { return '
' . "\n"; } /** * Well... just to be on the safe side */ public function __destruct() { unset($this->cube); } }