Completed
Push — master ( 004bb8...99f98e )
by Daniel
12:30
created

Debug::caller()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 16
nop 0
dl 0
loc 10
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Dev;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Core\Convert;
7
use SilverStripe\Core\Injector\Injector;
8
use SilverStripe\ORM\DB;
9
use SilverStripe\Security\Permission;
10
use SilverStripe\Security\Security;
11
12
/**
13
 * Supports debugging and core error handling.
14
 *
15
 * Attaches custom methods to the default error handling hooks
16
 * in PHP. Currently, two levels of error are supported:
17
 *
18
 * - Notice
19
 * - Warning
20
 * - Error
21
 *
22
 * Uncaught exceptions are currently passed to the debug
23
 * reporter as standard PHP errors.
24
 *
25
 * Errors handled by this class are passed along to {@link SS_Log}.
26
 * For configuration information, see the {@link SS_Log}
27
 * class documentation.
28
 *
29
 * @todo add support for user defined config: Debug::die_on_notice(true | false)
30
 * @todo better way of figuring out the error context to display in highlighted source
31
 */
32
class Debug {
33
34
	/**
35
	 * Show the contents of val in a debug-friendly way.
36
	 * Debug::show() is intended to be equivalent to dprintr()
37
	 *
38
	 * @param mixed $val
39
	 * @param bool $showHeader
40
	 */
41
	public static function show($val, $showHeader = true) {
42
		if(!Director::isLive()) {
43
			if($showHeader) {
44
				$caller = Debug::caller();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
45
				if(Director::is_ajax() || Director::is_cli())
46
					echo "Debug ($caller[class]$caller[type]$caller[function]() in " . basename($caller['file'])
47
						. ":$caller[line])\n";
48
				else
49
					echo "<div style=\"background-color: white; text-align: left;\">\n<hr>\n"
50
						. "<h3>Debug <span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()"
51
						. " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n";
52
			}
53
54
			echo Debug::text($val);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
55
56
			if(!Director::is_ajax() && !Director::is_cli()) echo "</div>";
57
			else echo "\n\n";
58
		}
59
60
	}
61
62
	/**
63
	 * Returns the caller for a specific method
64
	 *
65
	 * @return array
66
	 */
67
	public static function caller() {
68
		$bt = debug_backtrace();
69
		$caller = isset($bt[2]) ? $bt[2] : array();
70
		$caller['line'] = $bt[1]['line'];
71
		$caller['file'] = $bt[1]['file'];
72
		if(!isset($caller['class'])) $caller['class'] = '';
73
		if(!isset($caller['type'])) $caller['type'] = '';
74
		if(!isset($caller['function'])) $caller['function'] = '';
75
		return $caller;
76
	}
77
78
	/**
79
	 * Close out the show dumper
80
	 *
81
	 * @param mixed $val
82
	 */
83
	public static function endshow($val) {
84
		if(!Director::isLive()) {
85
			$caller = Debug::caller();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
86
			echo "<hr>\n<h3>Debug \n<span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()"
87
				. " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n";
88
			echo Debug::text($val);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
89
			die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method endshow() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
90
		}
91
	}
92
93
	/**
94
	 * Quick dump of a variable.
95
	 *
96
	 * @param mixed $val
97
	 */
98
	public static function dump($val) {
99
		echo self::create_debug_view()->renderVariable($val, self::caller());
100
	}
101
102
	/**
103
	 * @param mixed $val
104
	 * @return string
105
	 */
106
	public static function text($val) {
107
		if(is_object($val)) {
108
			if(method_exists($val, 'hasMethod')) {
109
				$hasDebugMethod = $val->hasMethod('debug');
110
			} else {
111
				$hasDebugMethod = method_exists($val, 'debug');
112
			}
113
114
			if($hasDebugMethod) {
115
				return $val->debug();
116
			}
117
		}
118
119
		if(is_array($val)) {
120
			$result = "<ul>\n";
121
			foreach($val as $k => $v) {
122
				$result .= "<li>$k = " . Debug::text($v) . "</li>\n";
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
123
			}
124
			$val = $result . "</ul>\n";
125
126
		} else if (is_object($val)) {
127
			$val = var_export($val, true);
128
		} else if (is_bool($val)) {
129
			$val = $val ? 'true' : 'false';
130
			$val = '(bool) ' . $val;
131
		} else {
132
			if(!Director::is_cli() && !Director::is_ajax()) {
133
				$val = "<pre style=\"font-family: Courier new\">" . htmlentities($val, ENT_COMPAT, 'UTF-8')
134
					. "</pre>\n";
135
			}
136
		}
137
138
		return $val;
139
	}
140
141
	/**
142
	 * Show a debugging message
143
	 *
144
	 * @param string $message
145
	 * @param bool $showHeader
146
	 */
147
	public static function message($message, $showHeader = true) {
148
		if(!Director::isLive()) {
149
			$caller = Debug::caller();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
150
			$file = basename($caller['file']);
151
			if(Director::is_cli()) {
152
				if($showHeader) echo "Debug (line $caller[line] of $file):\n ";
153
				echo $message . "\n";
154
			} else {
155
				echo "<p class=\"message warning\">\n";
156
				if($showHeader) echo "<b>Debug (line $caller[line] of $file):</b>\n ";
157
				echo Convert::raw2xml($message) . "</p>\n";
158
			}
159
		}
160
	}
161
162
	/**
163
	 * Create an instance of an appropriate DebugView object.
164
	 *
165
	 * @return DebugView
166
	 */
167
	public static function create_debug_view() {
168
		$service = Director::is_cli() || Director::is_ajax()
169
			? 'SilverStripe\\Dev\\CliDebugView'
170
			: 'SilverStripe\\Dev\\DebugView';
171
		return Injector::inst()->get($service);
172
	}
173
174
	/**
175
	 * Check if the user has permissions to run URL debug tools,
176
	 * else redirect them to log in.
177
	 */
178
	public static function require_developer_login() {
0 ignored issues
show
Coding Style introduced by
require_developer_login uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
require_developer_login uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
179
		if(Director::isDev())	{
180
			return;
181
		}
182
		if(isset($_SESSION['loggedInAs'])) {
183
			// We have to do some raw SQL here, because this method is called in Object::defineMethods().
184
			// This means we have to be careful about what objects we create, as we don't want Object::defineMethods()
185
			// being called again.
186
			// This basically calls Permission::checkMember($_SESSION['loggedInAs'], 'ADMIN');
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
187
188
			// @TODO - Rewrite safely using DataList::filter
189
			$memberID = $_SESSION['loggedInAs'];
190
			$permission = DB::prepared_query('
191
				SELECT "ID" FROM "Permission"
192
				INNER JOIN "Group_Members" ON "Permission"."GroupID" = "Group_Members"."GroupID"
193
				WHERE "Permission"."Code" = ?
194
				AND "Permission"."Type" = ?
195
				AND "Group_Members"."MemberID" = ?',
196
				array(
197
					'ADMIN', // Code
198
					Permission::GRANT_PERMISSION, // Type
199
					$memberID // MemberID
200
				)
201
			)->value();
202
203
			if($permission) return;
204
		}
205
206
		// This basically does the same as
207
		// Security::permissionFailure(null, "You need to login with developer access to make use of debugging tools.")
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
208
		// We have to do this because of how early this method is called in execution.
209
		$_SESSION['SilverStripe\\Security\\Security']['Message']['message']
210
			= "You need to login with developer access to make use of debugging tools.";
211
		$_SESSION['SilverStripe\\Security\\Security']['Message']['type'] =  'warning';
212
		$_SESSION['BackURL'] = $_SERVER['REQUEST_URI'];
213
		header($_SERVER['SERVER_PROTOCOL'] . " 302 Found");
214
		header("Location: " . Director::baseURL() . Security::login_url());
215
		die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method require_developer_login() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
216
	}
217
}
218