From b0ecbe079f7fa89971639adab804b3cfc5470cb5 Mon Sep 17 00:00:00 2001
From: Markus Frosch <markus.frosch@icinga.com>
Date: Wed, 7 Mar 2018 15:55:15 +0100
Subject: [PATCH] PluginOutput: Fix text splicing for status tags in HTML
 output

Trailing text was lost in processing.

Also add tests to check this behavior plus some basics.

fixes #3382
---
 .../views/helpers/PluginOutput.php            |  12 ++
 .../views/helpers/PluginOutputTest.php        | 141 ++++++++++++++++++
 2 files changed, 153 insertions(+)
 create mode 100644 modules/monitoring/test/php/application/views/helpers/PluginOutputTest.php

diff --git a/modules/monitoring/application/views/helpers/PluginOutput.php b/modules/monitoring/application/views/helpers/PluginOutput.php
index cecf0262b..1c77b578d 100644
--- a/modules/monitoring/application/views/helpers/PluginOutput.php
+++ b/modules/monitoring/application/views/helpers/PluginOutput.php
@@ -143,16 +143,28 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
                     $offsetLeft = $match[0][1];
                     $matchLength = strlen($match[0][0]);
                     $leftLength = $offsetLeft - $start;
+                    // if there is text before the match
                     if ($leftLength) {
+                        // create node for leading text
                         $text = new DOMText(substr($node->nodeValue, $start, $leftLength));
                         $node->parentNode->insertBefore($text, $node);
                     }
+                    // create the new element for the match
                     $span = $doc->createElement('span', $match[0][0]);
                     $span->setAttribute('class', 'state-' . strtolower($match[1][0]));
                     $node->parentNode->insertBefore($span, $node);
+
+                    // start for next match
                     $start = $offsetLeft + $matchLength;
                 }
                 if ($start) {
+                    // is there text left?
+                    if (strlen($node->nodeValue) > $start) {
+                        // create node for trailing text
+                        $text = new DOMText(substr($node->nodeValue, $start));
+                        $node->parentNode->insertBefore($text, $node);
+                    }
+                    // delete the old node later
                     $nodesToRemove[] = $node;
                 }
             } elseif ($node->nodeType === XML_ELEMENT_NODE) {
diff --git a/modules/monitoring/test/php/application/views/helpers/PluginOutputTest.php b/modules/monitoring/test/php/application/views/helpers/PluginOutputTest.php
new file mode 100644
index 000000000..9a3e4a0fa
--- /dev/null
+++ b/modules/monitoring/test/php/application/views/helpers/PluginOutputTest.php
@@ -0,0 +1,141 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Tests\Icinga\Module\Monitoring\Application\Views\Helpers;
+
+use Icinga\Web\View;
+use Zend_View_Helper_PluginOutput;
+use Icinga\Test\BaseTestCase;
+
+require_once realpath(BaseTestCase::$moduleDir . '/monitoring/application/views/helpers/PluginOutput.php');
+
+class PluginOutputTest extends BaseTestCase
+{
+    /** @var  Zend_View_Helper_PluginOutput */
+    protected $helper;
+
+    const PREFIX_PRE = '<div class="plugin-output preformatted">';
+    const PREFIX = '<div class="plugin-output">';
+    const SUFFIX = '</div>';
+
+    protected static $statusTags = array('OK', 'WARNING', 'CRITICAL', 'UNKNOWN', 'UP', 'DOWN');
+
+    public function setUp()
+    {
+        parent::setUp();
+
+        $this->helper = $h = new Zend_View_Helper_PluginOutput;
+        $h->setView(new View());
+    }
+
+    protected function checkOutput($output, $html, $regexp = false, $isHtml = false)
+    {
+        $actual = $this->helper->pluginOutput($output);
+
+        if ($isHtml) {
+            $prefix = self::PREFIX;
+        } else {
+            $prefix = self::PREFIX_PRE;
+        }
+
+        if ($regexp) {
+            $expect = sprintf(
+                '~%s%s%s~',
+                preg_quote($prefix, '~'),
+                $html,
+                preg_quote(self::SUFFIX, '~')
+            );
+            $this->assertRegExp($expect, $actual, 'Output must match example regexp');
+        } else {
+            $expect = $prefix . $html . self::SUFFIX;
+            $this->assertEquals($expect, $actual, 'Output must match example');
+        }
+    }
+
+    protected function checkHtmlOutput($outputHtml, $html, $regexp = false)
+    {
+        return $this->checkOutput($outputHtml, $html, $regexp, true);
+    }
+
+    public function testSimpleOutput()
+    {
+        $this->checkOutput(
+            'Foobar',
+            'Foobar'
+        );
+    }
+
+    public function testSimpleHtmlOutput()
+    {
+        /** @noinspection HtmlUnknownAttribute */
+        $this->checkHtmlOutput(
+            'OK - Teststatus <a href="http://localhost/test.php" target="_blank">Info</a>',
+            'OK - Teststatus <a href="http://localhost/test.php" target="_blank"[^>]*>Info</a>',
+            true
+        );
+    }
+
+    public function testMultilineHtmlOutput()
+    {
+        $input = array(
+            'Teststatus',
+            '<a href="http://localhost/test.php" target="_blank">Info</a><br/><br/>',
+            '<a href="http://localhost/test2.php" target="_blank">Info2</a>'
+        );
+        /** @noinspection HtmlUnknownAttribute */
+        $output = array(
+            'Teststatus',
+            '<a href="http://localhost/test.php" target="_blank"[^>]*>Info</a>',
+            '',
+            '',
+            '<a href="http://localhost/test2.php" target="_blank"[^>]*>Info2</a>'
+        );
+        $this->checkHtmlOutput(
+            join("\n", $input),
+            join("\n", $output),
+            true
+        );
+    }
+
+    public function testHtmlTable()
+    {
+        $this->markTestIncomplete();
+    }
+
+    public function testAllowedHtmlTags()
+    {
+        $this->markTestIncomplete();
+    }
+
+    public function testTextStatusTags()
+    {
+        foreach (self::$statusTags as $s) {
+            $l = strtolower($s);
+            $this->checkOutput(
+                sprintf('[%s] Test', $s),
+                sprintf('<span class="state-%s">[%s]</span> Test', $l, $s)
+            );
+            $this->checkOutput(
+                sprintf('(%s) Test', $s),
+                sprintf('<span class="state-%s">(%s)</span> Test', $l, $s)
+            );
+        }
+    }
+
+    public function testHtmlStatusTags()
+    {
+        $dummyHtml = '<a href="#"></a>';
+
+        foreach (self::$statusTags as $s) {
+            $l = strtolower($s);
+            $this->checkHtmlOutput(
+                sprintf('%s [%s] Test', $dummyHtml, $s),
+                sprintf('%s <span class="state-%s">[%s]</span> Test', $dummyHtml, $l, $s)
+            );
+            $this->checkHtmlOutput(
+                sprintf('%s (%s) Test', $dummyHtml, $s),
+                sprintf('%s <span class="state-%s">(%s)</span> Test', $dummyHtml, $l, $s)
+            );
+        }
+    }
+}