Merge pull request #6440 from open-sausages/pulls/4.0/json-detection

ENHANCEMENT: Debug class emits plain text for application/json requests
This commit is contained in:
Daniel Hensby 2017-07-04 13:42:42 +01:00 committed by GitHub
commit 64005bff91
7 changed files with 391 additions and 86 deletions

View File

@ -428,7 +428,7 @@ class HTTPRequest implements ArrayAccess
{
return (
$this->requestVar('ajax') ||
$this->getHeader('X-Requested-With') && $this->getHeader('X-Requested-With') == "XMLHttpRequest"
$this->getHeader('X-Requested-With') === "XMLHttpRequest"
);
}

View File

@ -3,6 +3,8 @@
namespace SilverStripe\Dev;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert;
/**
* A basic HTML wrapper for stylish rendering of a developement info view.
@ -129,4 +131,76 @@ class CliDebugView extends DebugView
return $output;
}
/**
* Similar to renderVariable() but respects debug() method on object if available
*
* @param mixed $val
* @param array $caller
* @param bool $showHeader
* @return string
*/
public function debugVariable($val, $caller, $showHeader = true)
{
$text = $this->debugVariableText($val);
if ($showHeader) {
$callerFormatted = $this->formatCaller($caller);
return "Debug ($callerFormatted)\n{$text}\n\n";
} else {
return $text;
}
}
/**
* Get debug text for this object
*
* @param mixed $val
* @return string
*/
public function debugVariableText($val)
{
// Check debug
if (ClassInfo::hasMethod($val, 'debug')) {
return $val->debug();
}
// Format as array
if (is_array($val)) {
$result = '';
foreach ($val as $key => $valItem) {
$valText = $this->debugVariableText($valItem);
$result .= "$key = $valText\n";
}
return $result;
}
// Format object
if (is_object($val)) {
return var_export($val, true);
}
// Format bool
if (is_bool($val)) {
return '(bool) ' . ($val ? 'true' : 'false');
}
// Format text
if (is_string($val)) {
return wordwrap($val, self::config()->columns);
}
// Other
return var_export($val, true);
}
public function renderMessage($message, $caller, $showHeader = true)
{
$header = '';
if ($showHeader) {
$file = basename($caller['file']);
$line = $caller['line'];
$header .= "Debug (line {$line} of {$file}):\n";
}
return $header . "{$message}\n\n";
}
}

View File

@ -3,7 +3,7 @@
namespace SilverStripe\Dev;
use SilverStripe\Control\Director;
use SilverStripe\Core\Convert;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\DB;
use SilverStripe\Security\Permission;
@ -35,33 +35,21 @@ class Debug
/**
* Show the contents of val in a debug-friendly way.
* Debug::show() is intended to be equivalent to dprintr()
* Does not work on live mode.
*
* @param mixed $val
* @param bool $showHeader
* @param HTTPRequest|null $request
*/
public static function show($val, $showHeader = true)
public static function show($val, $showHeader = true, HTTPRequest $request = null)
{
if (!Director::isLive()) {
if ($showHeader) {
$caller = Debug::caller();
if (Director::is_ajax() || Director::is_cli()) {
echo "Debug ($caller[class]$caller[type]$caller[function]() in " . basename($caller['file'])
. ":$caller[line])\n";
} else {
echo "<div style=\"background-color: white; text-align: left;\">\n<hr>\n"
. "<h3>Debug <span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()"
. " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n";
}
}
echo Debug::text($val);
if (!Director::is_ajax() && !Director::is_cli()) {
echo "</div>";
} else {
echo "\n\n";
}
// Don't show on live
if (Director::isLive()) {
return;
}
echo static::create_debug_view($request)
->debugVariable($val, static::caller(), $showHeader);
}
/**
@ -88,118 +76,136 @@ class Debug
}
/**
* Close out the show dumper
* Close out the show dumper.
* Does not work on live mode
*
* @param mixed $val
* @param bool $showHeader
* @param HTTPRequest $request
*/
public static function endshow($val)
public static function endshow($val, $showHeader = true, HTTPRequest $request = null)
{
if (!Director::isLive()) {
$caller = Debug::caller();
echo "<hr>\n<h3>Debug \n<span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()"
. " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n";
echo Debug::text($val);
die();
// Don't show on live
if (Director::isLive()) {
return;
}
echo static::create_debug_view($request)
->debugVariable($val, static::caller(), $showHeader);
die();
}
/**
* Quick dump of a variable.
* Note: This method will output in live!
*
* @param mixed $val
* @param HTTPRequest $request Current request to influence output format
*/
public static function dump($val)
public static function dump($val, HTTPRequest $request = null)
{
echo self::create_debug_view()->renderVariable($val, self::caller());
echo self::create_debug_view($request)
->renderVariable($val, self::caller());
}
/**
* Get debug text for this object
*
* @param mixed $val
* @param HTTPRequest $request
* @return string
*/
public static function text($val)
public static function text($val, HTTPRequest $request = null)
{
if (is_object($val)) {
if (method_exists($val, 'hasMethod')) {
$hasDebugMethod = $val->hasMethod('debug');
} else {
$hasDebugMethod = method_exists($val, 'debug');
}
if ($hasDebugMethod) {
return $val->debug();
}
}
if (is_array($val)) {
$result = "<ul>\n";
foreach ($val as $k => $v) {
$result .= "<li>$k = " . Debug::text($v) . "</li>\n";
}
$val = $result . "</ul>\n";
} elseif (is_object($val)) {
$val = var_export($val, true);
} elseif (is_bool($val)) {
$val = $val ? 'true' : 'false';
$val = '(bool) ' . $val;
} else {
if (!Director::is_cli() && !Director::is_ajax()) {
$val = "<pre style=\"font-family: Courier new\">" . htmlentities($val, ENT_COMPAT, 'UTF-8')
. "</pre>\n";
}
}
return $val;
return static::create_debug_view($request)
->debugVariableText($val);
}
/**
* Show a debugging message
* Show a debugging message.
* Does not work on live mode
*
* @param string $message
* @param bool $showHeader
* @param HTTPRequest|null $request
*/
public static function message($message, $showHeader = true)
public static function message($message, $showHeader = true, HTTPRequest $request = null)
{
if (!Director::isLive()) {
$caller = Debug::caller();
$file = basename($caller['file']);
if (Director::is_cli()) {
if ($showHeader) {
echo "Debug (line $caller[line] of $file):\n ";
}
echo $message . "\n";
} else {
echo "<p class=\"message warning\">\n";
if ($showHeader) {
echo "<b>Debug (line $caller[line] of $file):</b>\n ";
}
echo Convert::raw2xml($message) . "</p>\n";
}
// Don't show on live
if (Director::isLive()) {
return;
}
echo static::create_debug_view($request)
->renderMessage($message, static::caller(), $showHeader);
}
/**
* Create an instance of an appropriate DebugView object.
*
* @param HTTPRequest $request Optional request to target this view for
* @return DebugView
*/
public static function create_debug_view()
public static function create_debug_view(HTTPRequest $request = null)
{
$service = Director::is_cli() || Director::is_ajax()
? CliDebugView::class
: DebugView::class;
$service = static::supportsHTML($request)
? DebugView::class
: CliDebugView::class;
return Injector::inst()->get($service);
}
/**
* Determine if the given request supports html output
*
* @param HTTPRequest $request
* @return bool
*/
protected static function supportsHTML(HTTPRequest $request = null)
{
// No HTML output in CLI
if (Director::is_cli()) {
return false;
}
// Get current request if registered
if (!$request && Injector::inst()->has(HTTPRequest::class)) {
$request = Injector::inst()->get(HTTPRequest::class);
}
if (!$request) {
return false;
}
// Request must include text/html
$accepted = $request->getAcceptMimetypes(false);
// Explicit opt in
if (in_array('text/html', $accepted)) {
return true;
};
// Implicit opt-out
if (in_array('application/json', $accepted)) {
return false;
}
// Fallback to wildcard comparison
if (in_array('*/*', $accepted)) {
return true;
}
return false;
}
/**
* Check if the user has permissions to run URL debug tools,
* else redirect them to log in.
*/
public static function require_developer_login()
{
// Don't require login for dev mode
if (Director::isDev()) {
return;
}
if (isset($_SESSION['loggedInAs'])) {
// We have to do some raw SQL here, because this method is called in Object::defineMethods().
// This means we have to be careful about what objects we create, as we don't want Object::defineMethods()

View File

@ -5,6 +5,7 @@ namespace SilverStripe\Dev;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injectable;
@ -378,4 +379,77 @@ class DebugView
return $output;
}
public function renderMessage($message, $caller, $showHeader = true)
{
$header = '';
if ($showHeader) {
$file = basename($caller['file']);
$line = $caller['line'];
$header .= "<b>Debug (line {$line} of {$file}):</b>\n";
}
return "<p class=\"message warning\">\n" . $header . Convert::raw2xml($message) . "</p>\n";
}
/**
* Similar to renderVariable() but respects debug() method on object if available
*
* @param mixed $val
* @param array $caller
* @param bool $showHeader
* @return string
*/
public function debugVariable($val, $caller, $showHeader = true)
{
$text = $this->debugVariableText($val);
if ($showHeader) {
$callerFormatted = $this->formatCaller($caller);
return "<div style=\"background-color: white; text-align: left;\">\n<hr>\n"
. "<h3>Debug <span style=\"font-size: 65%\">($callerFormatted)</span>\n</h3>\n"
. $text
. "</div>";
} else {
return $text;
}
}
/**
* Get debug text for this object
*
* @param mixed $val
* @return string
*/
public function debugVariableText($val)
{
// Check debug
if (ClassInfo::hasMethod($val, 'debug')) {
return $val->debug();
}
// Format as array
if (is_array($val)) {
$result = '';
foreach ($val as $key => $valueItem) {
$keyText = Convert::raw2xml($key);
$valueText = $this->debugVariableText($valueItem);
$result .= "<li>{$keyText} = {$valueText}</li>\n";
}
return "<ul>\n{$result}</ul>\n";
}
// Format object
if (is_object($val)) {
return var_export($val, true);
}
// Format bool
if (is_bool($val)) {
return '(bool) ' . ($val ? 'true' : 'false');
}
// Format text
$html = Convert::raw2xml($val);
return "<pre style=\"font-family: Courier new, serif\">{$html}</pre>\n";
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace SilverStripe\Dev\Tests;
use SilverStripe\Dev\CliDebugView;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\Tests\DebugViewTest\ObjectWithDebug;
class CLIDebugViewTest extends SapphireTest
{
protected $caller = null;
protected function setUp()
{
parent::setUp();
$this->caller = [
'line' => 17,
'file' => __FILE__,
'args' => [],
'type' => '->',
'class' => __CLASS__,
'function' => __FUNCTION__,
];
}
public function testDebugVariable()
{
$view = new CliDebugView();
$this->assertEquals(
<<<EOS
Debug (CLIDebugViewTest.php:17 - SilverStripe\Dev\Tests\CLIDebugViewTest::setUp())
string
EOS
,
$view->debugVariable('string', $this->caller)
);
$this->assertEquals(
<<<EOS
Debug (CLIDebugViewTest.php:17 - SilverStripe\Dev\Tests\CLIDebugViewTest::setUp())
key = value
another = text
EOS
,
$view->debugVariable([ 'key' => 'value', 'another' => 'text' ], $this->caller)
);
$this->assertEquals(
<<<EOS
Debug (CLIDebugViewTest.php:17 - SilverStripe\Dev\Tests\CLIDebugViewTest::setUp())
SilverStripe\Dev\Tests\DebugViewTest\ObjectWithDebug::debug() custom content
EOS
,
$view->debugVariable(new ObjectWithDebug(), $this->caller)
);
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace SilverStripe\Dev\Tests;
use SilverStripe\Dev\DebugView;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\Tests\DebugViewTest\ObjectWithDebug;
class DebugViewTest extends SapphireTest
{
protected $caller = null;
protected function setUp()
{
parent::setUp();
$this->caller = [
'line' => 17,
'file' => __FILE__,
'args' => [],
'type' => '->',
'class' => __CLASS__,
'function' => __FUNCTION__,
];
}
public function testDebugVariable()
{
$view = new DebugView();
$this->assertEquals(
<<<EOS
<div style="background-color: white; text-align: left;">
<hr>
<h3>Debug <span style="font-size: 65%">(DebugViewTest.php:17 - SilverStripe\Dev\Tests\DebugViewTest::setUp())</span>
</h3>
<pre style="font-family: Courier new, serif">string</pre>
</div>
EOS
,
$view->debugVariable('string', $this->caller)
);
$this->assertEquals(
<<<EOS
<div style="background-color: white; text-align: left;">
<hr>
<h3>Debug <span style="font-size: 65%">(DebugViewTest.php:17 - SilverStripe\Dev\Tests\DebugViewTest::setUp())</span>
</h3>
<ul>
<li>key = <pre style="font-family: Courier new, serif">value</pre>
</li>
<li>another = <pre style="font-family: Courier new, serif">text</pre>
</li>
</ul>
</div>
EOS
,
$view->debugVariable([ 'key' => 'value', 'another' => 'text' ], $this->caller)
);
$this->assertEquals(
<<<EOS
<div style="background-color: white; text-align: left;">
<hr>
<h3>Debug <span style="font-size: 65%">(DebugViewTest.php:17 - SilverStripe\Dev\Tests\DebugViewTest::setUp())</span>
</h3>
SilverStripe\Dev\Tests\DebugViewTest\ObjectWithDebug::debug() custom content</div>
EOS
,
$view->debugVariable(new ObjectWithDebug(), $this->caller)
);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace SilverStripe\Dev\Tests\DebugViewTest;
use SilverStripe\Dev\TestOnly;
class ObjectWithDebug implements TestOnly
{
public function debug()
{
return __CLASS__ . '::debug() custom content';
}
}