1<?php
2
3namespace Icinga\Module\Businessprocess\Renderer;
4
5use Icinga\Exception\ProgrammingError;
6use Icinga\Module\Businessprocess\BpNode;
7use Icinga\Module\Businessprocess\BpConfig;
8use Icinga\Module\Businessprocess\ImportedNode;
9use Icinga\Module\Businessprocess\Node;
10use Icinga\Module\Businessprocess\Web\Url;
11use ipl\Html\BaseHtmlElement;
12use ipl\Html\Html;
13use ipl\Html\HtmlDocument;
14
15abstract class Renderer extends HtmlDocument
16{
17    /** @var BpConfig */
18    protected $config;
19
20    /** @var BpNode */
21    protected $parent;
22
23    /** @var bool Administrative actions are hidden unless unlocked */
24    protected $locked = true;
25
26    /** @var Url */
27    protected $url;
28
29    /** @var Url */
30    protected $baseUrl;
31
32    /** @var array */
33    protected $path = array();
34
35    /** @var bool */
36    protected $isBreadcrumb = false;
37
38    /**
39     * Renderer constructor.
40     *
41     * @param BpConfig $config
42     * @param BpNode|null $parent
43     */
44    public function __construct(BpConfig $config, BpNode $parent = null)
45    {
46        $this->config = $config;
47        $this->parent = $parent;
48    }
49
50    /**
51     * @return BpConfig
52     */
53    public function getBusinessProcess()
54    {
55        return $this->config;
56    }
57
58    /**
59     * Whether this will render all root nodes
60     *
61     * @return bool
62     */
63    public function wantsRootNodes()
64    {
65        return $this->parent === null;
66    }
67
68    /**
69     * Whether this will only render parts of given config
70     *
71     * @return bool
72     */
73    public function rendersSubNode()
74    {
75        return $this->parent !== null;
76    }
77
78    public function rendersImportedNode()
79    {
80        return $this->parent !== null && $this->parent->getBpConfig()->getName() !== $this->config->getName();
81    }
82
83    public function setParentNode(BpNode $node)
84    {
85        $this->parent = $node;
86        return $this;
87    }
88
89    /**
90     * @return BpNode
91     */
92    public function getParentNode()
93    {
94        return $this->parent;
95    }
96
97    /**
98     * @return BpNode[]
99     */
100    public function getParentNodes()
101    {
102        if ($this->wantsRootNodes()) {
103            return array();
104        }
105
106        return $this->parent->getParents();
107    }
108
109    /**
110     * @return BpNode[]
111     */
112    public function getChildNodes()
113    {
114        if ($this->wantsRootNodes()) {
115            return $this->config->getRootNodes();
116        } else {
117            return $this->parent->getChildren();
118        }
119    }
120
121    /**
122     * @return int
123     */
124    public function countChildNodes()
125    {
126        if ($this->wantsRootNodes()) {
127            return $this->config->countChildren();
128        } else {
129            return $this->parent->countChildren();
130        }
131    }
132
133    /**
134     * @param $summary
135     * @return BaseHtmlElement
136     */
137    public function renderStateBadges($summary)
138    {
139        $elements = [];
140
141        foreach ($summary as $state => $cnt) {
142            if ($cnt === 0
143                || $state === 'OK'
144                || $state === 'UP'
145            ) {
146                continue;
147            }
148
149            $elements[] = Html::tag(
150                'span',
151                [
152                    'class' => [
153                        'badge',
154                        'badge-' . strtolower($state)
155                    ],
156                    // TODO: We should translate this in this module
157                    'title' => mt('monitoring', $state)
158                ],
159                $cnt
160            );
161        }
162
163        if (!empty($elements)) {
164            $container = Html::tag('div', ['class' => 'badges']);
165            foreach ($elements as $element) {
166                $container->add($element);
167            }
168
169            return $container;
170        }
171        return null;
172    }
173
174    public function getNodeClasses(Node $node)
175    {
176        if ($node->isMissing()) {
177            $classes = array('missing');
178        } else {
179            $classes = array(
180                strtolower($node->getStateName())
181            );
182            if ($node->hasMissingChildren()) {
183                $classes[] = 'missing-children';
184            }
185        }
186
187        if ($node->isHandled()) {
188            $classes[] = 'handled';
189        }
190
191        if ($node instanceof BpNode) {
192            $classes[] = 'process-node';
193        } else {
194            $classes[] = 'monitored-node';
195        }
196        // TODO: problem?
197        return $classes;
198    }
199
200    /**
201     * Return the url to the given node's source configuration
202     *
203     * @param   BpNode  $node
204     *
205     * @return  Url
206     */
207    public function getSourceUrl(BpNode $node)
208    {
209        if ($node instanceof ImportedNode) {
210            $name = $node->getNodeName();
211            $paths = $node->getBpConfig()->getBpNode($name)->getPaths();
212        } else {
213            $name = $node->getName();
214            $paths = $node->getPaths();
215        }
216
217        $url = $this->getUrl()->setParams([
218            'config'    => $node->getBpConfig()->getName(),
219            'node'      => $name
220        ]);
221        // This depends on the fact that the node's root path is the last element in $paths
222        $url->getParams()->addValues('path', array_slice(array_pop($paths), 0, -1));
223        if (! $this->isLocked()) {
224            $url->getParams()->add('unlocked', true);
225        }
226
227        return $url;
228    }
229
230    /**
231     * @param Node $node
232     * @param $path
233     * @return string
234     */
235    public function getId(Node $node, $path)
236    {
237        return md5((empty($path) ? '' : implode(';', $path)) . $node->getName());
238    }
239
240    public function setPath(array $path)
241    {
242        $this->path = $path;
243        return $this;
244    }
245
246    /**
247     * @return array
248     */
249    public function getPath()
250    {
251        return $this->path;
252    }
253
254    public function getCurrentPath()
255    {
256        $path = $this->getPath();
257        if ($this->rendersSubNode()) {
258            $path[] = $this->rendersImportedNode()
259                ? $this->parent->getIdentifier()
260                : $this->parent->getName();
261        }
262
263        return $path;
264    }
265
266    /**
267     * @param Url $url
268     * @return $this
269     */
270    public function setUrl(Url $url)
271    {
272        $this->url = $url->without(array(
273            'action',
274            'deletenode',
275            'deleteparent',
276            'editnode',
277            'simulationnode',
278            'view'
279        ));
280        $this->setBaseUrl($this->url);
281        return $this;
282    }
283
284    /**
285     * @param Url $url
286     * @return $this
287     */
288    protected function setBaseUrl(Url $url)
289    {
290        $this->baseUrl = $url->without(array('node', 'path'));
291        return $this;
292    }
293
294    public function getUrl()
295    {
296        return $this->url;
297    }
298
299    /**
300     * @return Url
301     * @throws ProgrammingError
302     */
303    public function getBaseUrl()
304    {
305        if ($this->baseUrl === null) {
306            throw new ProgrammingError('Renderer has no baseUrl');
307        }
308
309        return clone($this->baseUrl);
310    }
311
312    /**
313     * @return bool
314     */
315    public function isLocked()
316    {
317        return $this->locked;
318    }
319
320    /**
321     * @return $this
322     */
323    public function lock()
324    {
325        $this->locked = true;
326        return $this;
327    }
328
329    /**
330     * @return $this
331     */
332    public function unlock()
333    {
334        $this->locked = false;
335        return $this;
336    }
337
338    /**
339     * TODO: Get rid of this
340     *
341     * @return $this
342     */
343    public function setIsBreadcrumb()
344    {
345        $this->isBreadcrumb = true;
346        return $this;
347    }
348
349    public function isBreadcrumb()
350    {
351        return $this->isBreadcrumb;
352    }
353
354    protected function createUnboundParent(BpConfig $bp)
355    {
356        return $bp->getNode('__unbound__');
357    }
358
359    /**
360     * Just to be on the safe side
361     */
362    public function __destruct()
363    {
364        unset($this->parent);
365        unset($this->config);
366    }
367}
368