From ca77388cd784a56aef62e898fe2f53912d2a3b7b Mon Sep 17 00:00:00 2001
From: Helmut Hummel <info@helhum.io>
Date: Wed, 25 May 2016 19:26:52 +0200
Subject: [PATCH] [BUGFIX] Fix output of DebugUtility

This change prettifies the output of the debug output
methods, by using the Extbase DebuggerUtility for that purpose.

The benefits are consistent, well readable, properly encoded output
for every case the class provides.

In the long run, this class should be replaced with
a more object oriented approach.

Resolves: #76302
Releases: master, 7.6
Change-Id: Iacbb48701f6c98139bd7db86795952a123e076da
Reviewed-on: https://review.typo3.org/48288
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
Reviewed-by: Helmut Hummel <helmut.hummel@typo3.org>
Tested-by: Helmut Hummel <helmut.hummel@typo3.org>
---
 .../core/Classes/Utility/DebugUtility.php     | 110 ++++++++++++----
 .../Tests/Unit/Utility/DebugUtilityTest.php   | 118 ++++++++++++++++++
 .../Classes/Utility/DebuggerUtility.php       |   4 +-
 .../ContentObjectRendererTest.php             |  21 ++--
 4 files changed, 216 insertions(+), 37 deletions(-)
 create mode 100644 typo3/sysext/core/Tests/Unit/Utility/DebugUtilityTest.php

diff --git a/typo3/sysext/core/Classes/Utility/DebugUtility.php b/typo3/sysext/core/Classes/Utility/DebugUtility.php
index b85ee472fcb0..6f88a58e776a 100644
--- a/typo3/sysext/core/Classes/Utility/DebugUtility.php
+++ b/typo3/sysext/core/Classes/Utility/DebugUtility.php
@@ -20,32 +20,45 @@ use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
  */
 class DebugUtility
 {
+    /**
+     * @var bool
+     */
+    static protected $plainTextOutput = true;
+
+    /**
+     * @var bool
+     */
+    static protected $ansiColorUsage = true;
+
     /**
      * Debug
      *
+     * Directly echos out debug information as HTML (or plain in CLI context)
+     *
      * @param string $var
      * @param string $header
      * @param string $group
-     * @return void
      */
     public static function debug($var = '', $header = '', $group = 'Debug')
     {
         // buffer the output of debug if no buffering started before
-        if (ob_get_level() == 0) {
+        if (ob_get_level() === 0) {
             ob_start();
         }
-        $debug = self::convertVariableToString($var);
-        if (TYPO3_MODE === 'BE' && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
+        if (TYPO3_MODE === 'BE' && !self::isCommandLine()) {
             $tabHeader = $header ?: 'Debug';
+            $debug = self::renderDump($var);
+            $debugPlain = PHP_EOL . self::renderDump($var, '', true, false);
             $script = '
 				(function debug() {
 					var message = ' . GeneralUtility::quoteJSvalue($debug) . ',
-						header = ' . GeneralUtility::quoteJSvalue($header) . ',
+						messagePlain = ' . GeneralUtility::quoteJSvalue($debugPlain) . ',
+						header = ' . GeneralUtility::quoteJSvalue($tabHeader) . ',
 						group = ' . GeneralUtility::quoteJSvalue($group) . ';
-					if (top.TYPO3.DebugConsole) {
+					if (top.TYPO3 && top.TYPO3.DebugConsole) {
 						top.TYPO3.DebugConsole.add(message, header, group);
 					} else {
-						var consoleMessage = [group, header, message].join(" | ");
+						var consoleMessage = [group, header, messagePlain].join(" | ");
 						if (typeof console === "object" && typeof console.log === "function") {
 							console.log(consoleMessage);
 						}
@@ -54,7 +67,7 @@ class DebugUtility
 			';
             echo GeneralUtility::wrapJS($script);
         } else {
-            echo $debug;
+            echo self::renderDump($var);
         }
     }
 
@@ -62,20 +75,12 @@ class DebugUtility
      * Converts a variable to a string
      *
      * @param mixed $variable
-     * @return string
+     * @return string plain, not HTML encoded string
      */
     public static function convertVariableToString($variable)
     {
-        if (is_array($variable)) {
-            $string = self::viewArray($variable);
-        } elseif (is_object($variable)) {
-            $string = json_encode($variable, true);
-        } elseif ((string)$variable !== '') {
-            $string = htmlspecialchars((string)$variable);
-        } else {
-            $string = '| debug |';
-        }
-        return $string;
+        $string = self::renderDump($variable, '', true, false);
+        return $string === '' ? '| debug |' : $string;
     }
 
     /**
@@ -87,7 +92,7 @@ class DebugUtility
      */
     public static function debugInPopUpWindow($debugVariable, $header = 'Debug', $group = 'Debug')
     {
-        $debugString = self::convertVariableToString($debugVariable);
+        $debugString = self::renderDump($debugVariable, sprintf('%s (%s)', $header, $group));
         $script = '
 			(function debug() {
 				var debugMessage = ' . GeneralUtility::quoteJSvalue($debugString) . ',
@@ -129,7 +134,7 @@ class DebugUtility
      * Displays the "path" of the function call stack in a string, using debug_backtrace
      *
      * @param bool $prependFileNames If set to true file names are added to the output
-     * @return string
+     * @return string plain, not HTML encoded string
      */
     public static function debugTrail($prependFileNames = false)
     {
@@ -154,11 +159,10 @@ class DebugUtility
      *
      * @param mixed $rows Array of arrays with similar keys
      * @param string $header Table header
-     * @return void Outputs to browser.
      */
     public static function debugRows($rows, $header = '')
     {
-        self::debug('<pre>' . DebuggerUtility::var_dump($rows, $header, 8, true, false, true), $header . '</pre>');
+        self::debug($rows, $header);
     }
 
     /**
@@ -190,7 +194,7 @@ class DebugUtility
      */
     public static function viewArray($array_in)
     {
-        return '<pre>' . DebuggerUtility::var_dump($array_in, '', 8, true, false, true) . '</pre>';
+        return self::renderDump($array_in);
     }
 
     /**
@@ -202,6 +206,62 @@ class DebugUtility
      */
     public static function printArray($array_in)
     {
-        echo self::viewArray($array_in);
+        echo self::renderDump($array_in);
+    }
+
+    /**
+     * Renders the dump according to the context, either for command line or as HTML output
+     *
+     * @param mixed $variable
+     * @param string $title
+     * @param bool|null $plainText
+     * @param bool|null $ansiColors
+     * @return string
+     */
+    protected static function renderDump($variable, $title = '', $plainText = null, $ansiColors = null)
+    {
+        $plainText = $plainText === null ? self::isCommandLine() && self::$plainTextOutput : $plainText;
+        $ansiColors = $ansiColors === null ? self::isCommandLine() && self::$ansiColorUsage : $ansiColors;
+        return trim(DebuggerUtility::var_dump($variable, $title, 8, $plainText, $ansiColors, true));
+    }
+
+    /**
+     * Checks some constants to determine if we are in CLI context
+     *
+     * @return bool
+     */
+    protected static function isCommandLine()
+    {
+        return (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI) || PHP_SAPI === 'cli';
+    }
+
+    /**
+     * Preset plaintext output
+     *
+     * Warning:
+     * This is NOT a public API method and must not be used in own extensions!
+     * This method is usually only used in tests to preset the output behaviour
+     *
+     * @internal
+     * @param bool $plainTextOutput
+     */
+    public static function usePlainTextOutput($plainTextOutput)
+    {
+        static::$plainTextOutput = $plainTextOutput;
+    }
+
+    /**
+     * Preset ansi color usage
+     *
+     * Warning:
+     * This is NOT a public API method and must not be used in own extensions!
+     * This method is usually only used in tests to preset the ansi color usage
+     *
+     * @internal
+     * @param bool $ansiColorUsage
+     */
+    public static function useAnsiColor($ansiColorUsage)
+    {
+        static::$ansiColorUsage = $ansiColorUsage;
     }
 }
diff --git a/typo3/sysext/core/Tests/Unit/Utility/DebugUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/DebugUtilityTest.php
new file mode 100644
index 000000000000..396514acd452
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Utility/DebugUtilityTest.php
@@ -0,0 +1,118 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Utility;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Core\Utility\DebugUtility;
+
+/**
+ * Testcase for class \TYPO3\CMS\Core\Utility\DebugUtility
+ */
+class DebugUtilityTest extends UnitTestCase
+{
+    protected function tearDown()
+    {
+        parent::tearDown();
+        DebugUtility::usePlainTextOutput(true);
+        DebugUtility::useAnsiColor(true);
+    }
+
+    /**
+     * @test
+     */
+    public function debugNotEncodesHtmlInputIfPlainText()
+    {
+        DebugUtility::usePlainTextOutput(true);
+        DebugUtility::useAnsiColor(false);
+
+        ob_start();
+        DebugUtility::debug('<script>alert(\'Hello world!\')</script>');
+        $output = ob_get_contents();
+        ob_end_clean();
+
+        $this->assertContains(
+            '<script>alert(\'Hello world!\')</script>',
+            $output
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function debugEncodesHtmlInputIfNoPlainText()
+    {
+        DebugUtility::usePlainTextOutput(false);
+        DebugUtility::useAnsiColor(false);
+
+        ob_start();
+        DebugUtility::debug('<script>alert(\'Hello world!\')</script>');
+        $output = ob_get_contents();
+        ob_end_clean();
+
+        $this->assertContains(
+            '&lt;script&gt;alert(\'Hello world!\')&lt;/script&gt;',
+            $output
+        );
+    }
+
+    /**
+     * @return array
+     */
+    public function convertVariableToStringReturnsVariableContentDataProvider()
+    {
+        $object = new \stdClass();
+        $object->foo = 42;
+        $object->bar = ['baz'];
+
+        return [
+            'Debug string' => [
+                'Hello world!',
+                '"Hello world!" (12 chars)',
+            ],
+            'Debug array' => [
+                [
+                    'foo',
+                    'bar',
+                    'baz' => [
+                        42,
+                    ],
+                ],
+                'array(3 items)' . PHP_EOL
+. '   0 => "foo" (3 chars)' . PHP_EOL
+. '   1 => "bar" (3 chars)' . PHP_EOL
+. '   baz => array(1 item)' . PHP_EOL
+. '      0 => 42 (integer)',
+            ],
+            'Debug object' => [
+                $object,
+                'stdClass prototype object' . PHP_EOL
+. '   foo => public 42 (integer)' . PHP_EOL
+. '   bar => public array(1 item)' . PHP_EOL
+. '      0 => "baz" (3 chars)'
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider convertVariableToStringReturnsVariableContentDataProvider
+     * @param mixed $variable
+     * @param string $expected
+     */
+    public function convertVariableToStringReturnsVariableContent($variable, $expected)
+    {
+        $this->assertSame($expected, DebugUtility::convertVariableToString($variable));
+    }
+}
diff --git a/typo3/sysext/extbase/Classes/Utility/DebuggerUtility.php b/typo3/sysext/extbase/Classes/Utility/DebuggerUtility.php
index e7fed4b16a2b..f1c15afbe06d 100644
--- a/typo3/sysext/extbase/Classes/Utility/DebuggerUtility.php
+++ b/typo3/sysext/extbase/Classes/Utility/DebuggerUtility.php
@@ -337,7 +337,7 @@ class DebuggerUtility
                 $property->setAccessible(true);
                 $visibility = ($property->isProtected() ? 'protected' : ($property->isPrivate() ? 'private' : 'public'));
                 if ($plainText) {
-                    $dump .= ' ' . self::ansiEscapeWrap($visibility, '42;30', $ansiColors) . ' ';
+                    $dump .= self::ansiEscapeWrap($visibility, '42;30', $ansiColors) . ' ';
                 } else {
                     $dump .= '<span class="extbase-debug-visibility">' . $visibility . '</span>';
                 }
@@ -427,7 +427,7 @@ class DebuggerUtility
             $css = '
 				<style type=\'text/css\'>
 					.extbase-debugger-tree{position:relative}
-					.extbase-debugger-tree input{position:absolute;top:0;left:0;height:14px;width:14px;margin:0;cursor:pointer;opacity:0;z-index:2}
+					.extbase-debugger-tree input{position:absolute !important;float: none !important;top:0;left:0;height:14px;width:14px;margin:0 !important;cursor:pointer;opacity:0;z-index:2}
 					.extbase-debugger-tree input~.extbase-debug-content{display:none}
 					.extbase-debugger-tree .extbase-debug-header:before{position:relative;top:3px;content:"";padding:0;line-height:10px;height:12px;width:12px;text-align:center;margin:0 3px 0 0;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyIDEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6Izg4ODg4ODt9PC9zdHlsZT48cGF0aCBpZD0iQm9yZGVyIiBjbGFzcz0ic3QwIiBkPSJNMTEsMTFIMFYwaDExVjExeiBNMTAsMUgxdjloOVYxeiIvPjxnIGlkPSJJbm5lciI+PHJlY3QgeD0iMiIgeT0iNSIgY2xhc3M9InN0MCIgd2lkdGg9IjciIGhlaWdodD0iMSIvPjxyZWN0IHg9IjUiIHk9IjIiIGNsYXNzPSJzdDAiIHdpZHRoPSIxIiBoZWlnaHQ9IjciLz48L2c+PC9zdmc+);display:inline-block}
 					.extbase-debugger-tree input:checked~.extbase-debug-content{display:inline}
diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
index e08c38d99852..6d2d87bdca69 100755
--- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Charset\CharsetConverter;
 use TYPO3\CMS\Core\Core\ApplicationContext;
 use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\TypoScript\TemplateService;
+use TYPO3\CMS\Core\Utility\DebugUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
@@ -3006,9 +3007,9 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $expectedResult = 'array(3items)0=>array(2items)uid=>1(integer)title=>"title1"(6chars)1=>array(2items)uid=>2(integer)title=>"title2"(6chars)2=>array(2items)uid=>3(integer)title=>""(0chars)';
         $GLOBALS['TSFE']->tmpl->rootLine = $rootline;
 
+        DebugUtility::useAnsiColor(false);
         $result = $this->subject->getData('debug:rootLine');
-        $cleanedResult = strip_tags($result);
-        $cleanedResult = str_replace("\r", '', $cleanedResult);
+        $cleanedResult = str_replace("\r", '', $result);
         $cleanedResult = str_replace("\n", '', $cleanedResult);
         $cleanedResult = str_replace("\t", '', $cleanedResult);
         $cleanedResult = str_replace(' ', '', $cleanedResult);
@@ -3031,9 +3032,9 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $expectedResult = 'array(3items)0=>array(2items)uid=>1(integer)title=>"title1"(6chars)1=>array(2items)uid=>2(integer)title=>"title2"(6chars)2=>array(2items)uid=>3(integer)title=>""(0chars)';
         $GLOBALS['TSFE']->rootLine = $rootline;
 
+        DebugUtility::useAnsiColor(false);
         $result = $this->subject->getData('debug:fullRootLine');
-        $cleanedResult = strip_tags($result);
-        $cleanedResult = str_replace("\r", '', $cleanedResult);
+        $cleanedResult = str_replace("\r", '', $result);
         $cleanedResult = str_replace("\n", '', $cleanedResult);
         $cleanedResult = str_replace("\t", '', $cleanedResult);
         $cleanedResult = str_replace(' ', '', $cleanedResult);
@@ -3054,9 +3055,9 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
 
         $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
 
+        DebugUtility::useAnsiColor(false);
         $result = $this->subject->getData('debug:data');
-        $cleanedResult = strip_tags($result);
-        $cleanedResult = str_replace("\r", '', $cleanedResult);
+        $cleanedResult = str_replace("\r", '', $result);
         $cleanedResult = str_replace("\n", '', $cleanedResult);
         $cleanedResult = str_replace("\t", '', $cleanedResult);
         $cleanedResult = str_replace(' ', '', $cleanedResult);
@@ -3077,9 +3078,9 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
 
         $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
 
+        DebugUtility::useAnsiColor(false);
         $result = $this->subject->getData('debug:register');
-        $cleanedResult = strip_tags($result);
-        $cleanedResult = str_replace("\r", '', $cleanedResult);
+        $cleanedResult = str_replace("\r", '', $result);
         $cleanedResult = str_replace("\n", '', $cleanedResult);
         $cleanedResult = str_replace("\t", '', $cleanedResult);
         $cleanedResult = str_replace(' ', '', $cleanedResult);
@@ -3099,9 +3100,9 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
 
         $expectedResult = 'array(1item)uid=>' . $uid . '(integer)';
 
+        DebugUtility::useAnsiColor(false);
         $result = $this->subject->getData('debug:page');
-        $cleanedResult = strip_tags($result);
-        $cleanedResult = str_replace("\r", '', $cleanedResult);
+        $cleanedResult = str_replace("\r", '', $result);
         $cleanedResult = str_replace("\n", '', $cleanedResult);
         $cleanedResult = str_replace("\t", '', $cleanedResult);
         $cleanedResult = str_replace(' ', '', $cleanedResult);
-- 
GitLab