summaryrefslogtreecommitdiffstats
path: root/library/Icingadb/Hook/ExtensionHook/ObjectsDetailExtensionHook.php
blob: cc496674d6c00e24799f9b173cd621582c87501c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<?php

/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */

namespace Icinga\Module\Icingadb\Hook\ExtensionHook;

use Exception;
use Icinga\Application\Hook;
use Icinga\Application\Logger;
use Icinga\Exception\IcingaException;
use Icinga\Module\Icingadb\Hook\HostsDetailExtensionHook;
use Icinga\Module\Icingadb\Hook\ServicesDetailExtensionHook;
use InvalidArgumentException;
use ipl\Html\Attributes;
use ipl\Html\HtmlElement;
use ipl\Html\HtmlString;
use ipl\Html\Text;
use ipl\Html\ValidHtml;
use ipl\Orm\Query;
use ipl\Stdlib\BaseFilter;
use ipl\Stdlib\Filter;

abstract class ObjectsDetailExtensionHook extends BaseExtensionHook
{
    use BaseFilter;

    /**
     * Load all extensions for the given objects
     *
     * @param string $objectType
     * @param Query $query
     * @param Filter\Rule $baseFilter
     *
     * @return array<int, ValidHtml>
     *
     * @throws InvalidArgumentException If the given object type is not supported
     */
    final public static function loadExtensions(string $objectType, Query $query, Filter\Rule $baseFilter): array
    {
        switch ($objectType) {
            case 'host':
                $hookName = 'Icingadb\\HostsDetailExtension';
                break;
            case 'service':
                $hookName = 'Icingadb\\ServicesDetailExtension';
                break;
            default:
                throw new InvalidArgumentException(
                    sprintf('%s is not a supported object type', $objectType)
                );
        }

        $extensions = [];
        $lastUsedLocations = [];
        /** @var HostsDetailExtensionHook|ServicesDetailExtensionHook $hook */
        foreach (Hook::all($hookName) as $hook) {
            $location = $hook->getLocation();
            if ($location < 0) {
                $location = null;
            }

            if ($location === null) {
                $section = $hook->getSection();
                if (! isset(self::BASE_LOCATIONS[$section])) {
                    Logger::error('Detail extension %s is using an invalid section: %s', get_class($hook), $section);
                    $section = self::DETAIL_SECTION;
                }

                if (isset($lastUsedLocations[$section])) {
                    $location = ++$lastUsedLocations[$section];
                } else {
                    $location = self::BASE_LOCATIONS[$section];
                    $lastUsedLocations[$section] = $location;
                }
            }

            try {
                // It may be ValidHtml, but modules shouldn't be able to break our views.
                // That's why it needs to be rendered instantly, as any error will then
                // be caught here.
                $extension = (string) $hook->setBaseFilter($baseFilter)->getHtmlForObjects(clone $query);

                $moduleName = $hook->getModule()->getName();

                $extensions[$location] = new HtmlElement(
                    'div',
                    Attributes::create([
                        'class' => 'icinga-module module-' . $moduleName,
                        'data-icinga-module' => $moduleName
                    ]),
                    HtmlString::create($extension)
                );
            } catch (Exception $e) {
                Logger::error("Failed to load details extension: %s\n%s", $e, $e->getTraceAsString());
                $extensions[$location] = Text::create(IcingaException::describe($e));
            }
        }

        return $extensions;
    }
}