Completed
Push — 3.4 ( e93a37...32d185 )
by Sam
11:21
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
 * Supports debugging and core error handling.
4
 *
5
 * Attaches custom methods to the default error handling hooks
6
 * in PHP. Currently, two levels of error are supported:
7
 *
8
 * - Notice
9
 * - Warning
10
 * - Error
11
 *
12
 * Uncaught exceptions are currently passed to the debug
13
 * reporter as standard PHP errors.
14
 *
15
 * Errors handled by this class are passed along to {@link SS_Log}.
16
 * For configuration information, see the {@link SS_Log}
17
 * class documentation.
18
 *
19
 * @todo add support for user defined config: Debug::die_on_notice(true | false)
20
 * @todo better way of figuring out the error context to display in highlighted source
21
 *
22
 * @package framework
23
 * @subpackage dev
24
 */
25
class Debug {
26
27
	/**
28
	 * @config
29
	 * @var String indicating the file where errors are logged.
30
	 * Filename is relative to the site root.
31
	 * The named file will have a terse log sent to it, and the full log (an
32
	 * encoded file containing backtraces and things) will go to a file of a similar
33
	 * name, but with the suffix ".full" added.
34
	 */
35
	private static $log_errors_to = null;
36
37
	/**
38
	 * @config
39
	 * @var string The header of the message shown to users on the live site when a fatal error occurs.
40
	 */
41
	private static $friendly_error_header = 'There has been an error';
42
43
	/**
44
	 * Set to true to enable friendly errors to set a http response code corresponding to the error.
45
	 * If left false then error pages will be served as HTTP 200.
46
	 *
47
	 * Will be removed in 4.0, and fixed to on.
48
	 *
49
	 * @config
50
	 * @var bool
51
	 */
52
	private static $friendly_error_httpcode = false;
53
54
	/**
55
	 * @config
56
	 * @var string The body of the message shown to users on the live site when a fatal error occurs.
57
	 */
58
	private static $friendly_error_detail = 'The website server has not been able to respond to your request.';
59
60
	/**
61
	 * Show the contents of val in a debug-friendly way.
62
	 * Debug::show() is intended to be equivalent to dprintr()
63
	 */
64
	public static function show($val, $showHeader = true) {
65
		if(!Director::isLive()) {
66
			if($showHeader) {
67
				$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...
68
				if(Director::is_ajax() || Director::is_cli())
69
					echo "Debug ($caller[class]$caller[type]$caller[function]() in " . basename($caller['file'])
70
						. ":$caller[line])\n";
71
				else
72
					echo "<div style=\"background-color: white; text-align: left;\">\n<hr>\n"
73
						. "<h3>Debug <span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()"
74
						. " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n";
75
			}
76
77
			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...
78
79
			if(!Director::is_ajax() && !Director::is_cli()) echo "</div>";
80
			else echo "\n\n";
81
		}
82
83
	}
84
85
	/**
86
	 * Returns the caller for a specific method
87
	 *
88
	 * @return array
89
	 */
90
	public static function caller() {
91
		$bt = debug_backtrace();
92
		$caller = isset($bt[2]) ? $bt[2] : array();
93
		$caller['line'] = $bt[1]['line'];
94
		$caller['file'] = $bt[1]['file'];
95
		if(!isset($caller['class'])) $caller['class'] = '';
96
		if(!isset($caller['type'])) $caller['type'] = '';
97
		if(!isset($caller['function'])) $caller['function'] = '';
98
		return $caller;
99
	}
100
101
	/**
102
	 * Close out the show dumper
103
	 *
104
	 * @param mixed $val
105
	 */
106
	public static function endshow($val) {
107
		if(!Director::isLive()) {
108
			$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...
109
			echo "<hr>\n<h3>Debug \n<span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()"
110
				. " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n";
111
			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...
112
			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...
113
		}
114
	}
115
116
	/**
117
	 * Quick dump of a variable.
118
	 *
119
	 * @param mixed $val
120
	 */
121
	public static function dump($val) {
122
		self::create_debug_view()->writeVariable($val, self::caller());
123
	}
124
125
	/**
126
	 * ??
127
	 *
128
	 * @param unknown_type $val
129
	 * @return unknown
130
	 */
131
	public static function text($val) {
132
		if(is_object($val)) {
133
			if(method_exists($val, 'hasMethod')) {
134
				$hasDebugMethod = $val->hasMethod('debug');
135
			} else {
136
				$hasDebugMethod = method_exists($val, 'debug');
137
			}
138
139
			if($hasDebugMethod) {
140
				return $val->debug();
141
			}
142
		}
143
144
		if(is_array($val)) {
145
			$result = "<ul>\n";
146
			foreach($val as $k => $v) {
147
				$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...
148
			}
149
			$val = $result . "</ul>\n";
150
151
		} else if (is_object($val)) {
152
			$val = var_export($val, true);
153
		} else if (is_bool($val)) {
154
			$val = $val ? 'true' : 'false';
155
			$val = '(bool) ' . $val;
156
		} else {
157
			if(!Director::is_cli() && !Director::is_ajax()) {
158
				$val = "<pre style=\"font-family: Courier new\">" . htmlentities($val, ENT_COMPAT, 'UTF-8')
159
					. "</pre>\n";
160
			}
161
		}
162
163
		return $val;
164
	}
165
166
	/**
167
	 * Show a debugging message
168
	 */
169
	public static function message($message, $showHeader = true) {
170
		if(!Director::isLive()) {
171
			$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...
172
			$file = basename($caller['file']);
173
			if(Director::is_cli()) {
174
				if($showHeader) echo "Debug (line $caller[line] of $file):\n ";
175
				echo $message . "\n";
176
			} else {
177
				echo "<p class=\"message warning\">\n";
178
				if($showHeader) echo "<b>Debug (line $caller[line] of $file):</b>\n ";
179
				echo Convert::raw2xml($message) . "</p>\n";
180
			}
181
		}
182
	}
183
184
	// Keep track of how many headers have been sent
185
	private static $headerCount = 0;
186
187
	/**
188
	 * Send a debug message in an HTTP header. Only works if you are
189
	 * on Dev, and headers have not yet been sent.
190
	 *
191
	 * @param string $msg
192
	 * @param string $prefix (optional)
193
	 * @return void
194
	 */
195
	public static function header($msg, $prefix = null) {
196
		if (Director::isDev() && !headers_sent()) {
197
			self::$headerCount++;
198
			header('SS-'.self::$headerCount.($prefix?'-'.$prefix:'').': '.$msg);
199
		}
200
	}
201
202
	/**
203
	 * Log to a standard text file output.
204
	 *
205
	 * @param $message string to output
206
	 */
207
	public static function log($message) {
208
		if (defined('BASE_PATH')) {
209
			$path = BASE_PATH;
210
		}
211
		else {
212
			$path = dirname(__FILE__) . '/../..';
213
		}
214
		$file = $path . '/debug.log';
215
		$now = date('r');
216
		$content = "\n\n== $now ==\n$message\n";
217
		file_put_contents($file, $content, FILE_APPEND);
218
	}
219
220
	/**
221
	 * Load error handlers into environment.
222
	 * Caution: The error levels default to E_ALL is the site is in dev-mode (set in main.php).
223
	 */
224
	public static function loadErrorHandlers() {
225
		set_error_handler('errorHandler', error_reporting());
226
		set_exception_handler('exceptionHandler');
227
	}
228
229
	public static function noticeHandler($errno, $errstr, $errfile, $errline, $errcontext) {
230
		if(error_reporting() == 0) return;
231
		ini_set('display_errors', 0);
232
233
		// Send out the error details to the logger for writing
234
		SS_Log::log(
235
			array(
236
				'errno' => $errno,
237
				'errstr' => $errstr,
238
				'errfile' => $errfile,
239
				'errline' => $errline,
240
				'errcontext' => $errcontext
241
			),
242
			SS_Log::NOTICE
243
		);
244
245
		if(Director::isDev()) {
246
			return self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Notice");
247
		} else {
248
			return false;
249
		}
250
	}
251
252
	/**
253
	 * Handle a non-fatal warning error thrown by PHP interpreter.
254
	 *
255
	 * @param unknown_type $errno
256
	 * @param unknown_type $errstr
257
	 * @param unknown_type $errfile
258
	 * @param unknown_type $errline
259
	 * @param unknown_type $errcontext
260
	 */
261
	public static function warningHandler($errno, $errstr, $errfile, $errline, $errcontext) {
262
		if(error_reporting() == 0) return;
263
		ini_set('display_errors', 0);
264
265
		// Send out the error details to the logger for writing
266
		SS_Log::log(
267
			array(
268
				'errno' => $errno,
269
				'errstr' => $errstr,
270
				'errfile' => $errfile,
271
				'errline' => $errline,
272
				'errcontext' => $errcontext
273
			),
274
			SS_Log::WARN
275
		);
276
277
		if(Director::isDev()) {
278
			return self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Warning");
279
		} else {
280
			return false;
281
		}
282
	}
283
284
	/**
285
	 * Handle a fatal error, depending on the mode of the site (ie: Dev, Test, or Live).
286
	 *
287
	 * Runtime execution dies immediately once the error is generated.
288
	 *
289
	 * @param unknown_type $errno
290
	 * @param unknown_type $errstr
291
	 * @param unknown_type $errfile
292
	 * @param unknown_type $errline
293
	 * @param unknown_type $errcontext
294
	 */
295
	public static function fatalHandler($errno, $errstr, $errfile, $errline, $errcontext) {
296
		ini_set('display_errors', 0);
297
298
		// Send out the error details to the logger for writing
299
		SS_Log::log(
300
			array(
301
				'errno' => $errno,
302
				'errstr' => $errstr,
303
				'errfile' => $errfile,
304
				'errline' => $errline,
305
				'errcontext' => $errcontext
306
			),
307
			SS_Log::ERR
308
		);
309
310
		if(Director::isDev() || Director::is_cli()) {
311
			self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Error");
312
		} else {
313
			self::friendlyError();
314
		}
315
		return false;
316
	}
317
318
	/**
319
	 * Render a user-facing error page, using the default HTML error template
320
	 * rendered by {@link ErrorPage} if it exists. Doesn't use the standard {@link SS_HTTPResponse} class
321
	 * the keep dependencies minimal.
322
	 *
323
	 * @uses ErrorPage
324
	 *
325
	 * @param int $statusCode HTTP Status Code (Default: 500)
326
	 * @param string $friendlyErrorMessage User-focused error message. Should not contain code pointers
327
	 *                                     or "tech-speak". Used in the HTTP Header and ajax responses.
328
	 * @param string $friendlyErrorDetail Detailed user-focused message. Is just used if no {@link ErrorPage} is found
329
	 *                                    for this specific status code.
330
	 * @return string HTML error message for non-ajax requests, plaintext for ajax-request.
331
	 */
332
	public static function friendlyError($statusCode=500, $friendlyErrorMessage=null, $friendlyErrorDetail=null) {
0 ignored issues
show
Coding Style introduced by
friendlyError 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...
333
		// Ensure the error message complies with the HTTP 1.1 spec
334
		if(!$friendlyErrorMessage) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $friendlyErrorMessage of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
335
			$friendlyErrorMessage = Config::inst()->get('Debug', 'friendly_error_header');
336
		}
337
		$friendlyErrorMessage = strip_tags(str_replace(array("\n", "\r"), '', $friendlyErrorMessage));
338
339
		if(!$friendlyErrorDetail) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $friendlyErrorDetail of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
340
			$friendlyErrorDetail = Config::inst()->get('Debug', 'friendly_error_detail');
341
		}
342
343
		if(!headers_sent()) {
344
			// Allow toggle between legacy behaviour and correctly setting HTTP response code
345
			// In 4.0 this should be fixed to always set this response code.
346
			if(Config::inst()->get('Debug', 'friendly_error_httpcode') || !Controller::has_curr()) {
347
				header($_SERVER['SERVER_PROTOCOL'] . " $statusCode $friendlyErrorMessage");
348
			}
349
		}
350
351
		if(Director::is_ajax()) {
352
			echo $friendlyErrorMessage;
353
		} else {
354
			if(!headers_sent()) header('Content-Type: text/html');
355
			if(class_exists('ErrorPage')){
356
				$errorFilePath = ErrorPage::get_filepath_for_errorcode(
357
					$statusCode,
358
					class_exists('Translatable') ? Translatable::get_current_locale() : null
359
				);
360
				if(file_exists($errorFilePath)) {
361
					$content = file_get_contents($errorFilePath);
362
					// $BaseURL is left dynamic in error-###.html, so that multi-domain sites don't get broken
363
					echo str_replace('$BaseURL', Director::absoluteBaseURL(), $content);
364
				}
365
			} else {
366
				$renderer = new DebugView();
367
				$renderer->writeHeader();
368
				$renderer->writeInfo("Website Error", $friendlyErrorMessage, $friendlyErrorDetail);
369
370
				if(Email::config()->admin_email) {
371
					$mailto = Email::obfuscate(Email::config()->admin_email);
372
					$renderer->writeParagraph('Contact an administrator: ' . $mailto . '');
373
				}
374
375
				$renderer->writeFooter();
376
			}
377
		}
378
		return false;
379
	}
380
381
	/**
382
	 * Create an instance of an appropriate DebugView object.
383
	 *
384
	 * @return DebugView
385
	 */
386
	public static function create_debug_view() {
387
		$service = Director::is_cli() || Director::is_ajax()
388
			? 'CliDebugView'
389
			: 'DebugView';
390
		return Injector::inst()->get($service);
391
	}
392
393
	/**
394
	 * Render a developer facing error page, showing the stack trace and details
395
	 * of the code where the error occured.
396
	 *
397
	 * @param unknown_type $errno
398
	 * @param unknown_type $errstr
399
	 * @param unknown_type $errfile
400
	 * @param unknown_type $errline
401
	 * @param unknown_type $errcontext
402
	 */
403
	public static function showError($errno, $errstr, $errfile, $errline, $errcontext, $errtype) {
0 ignored issues
show
Coding Style introduced by
showError 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...
Coding Style introduced by
showError uses the super-global variable $_REQUEST 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...
404
		if(!headers_sent()) {
405
			$errText = "$errtype at line $errline of $errfile";
406
			$errText = str_replace(array("\n","\r")," ",$errText);
407
408
			if(!headers_sent()) header($_SERVER['SERVER_PROTOCOL'] . " 500 $errText");
409
410
			// if error is displayed through ajax with CliDebugView, use plaintext output
411
			if(Director::is_ajax()) {
412
				header('Content-Type: text/plain');
413
			}
414
		}
415
416
		$reporter = self::create_debug_view();
417
418
		// Coupling alert: This relies on knowledge of how the director gets its URL, it could be improved.
419
		$httpRequest = null;
420
		if(isset($_SERVER['REQUEST_URI'])) {
421
			$httpRequest = $_SERVER['REQUEST_URI'];
422
		} elseif(isset($_REQUEST['url'])) {
423
			$httpRequest = $_REQUEST['url'];
424
		}
425
		if(isset($_SERVER['REQUEST_METHOD'])) $httpRequest = $_SERVER['REQUEST_METHOD'] . ' ' . $httpRequest;
426
427
		$reporter->writeHeader($httpRequest);
0 ignored issues
show
Unused Code introduced by
The call to DebugView::writeHeader() has too many arguments starting with $httpRequest.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
428
		$reporter->writeError($httpRequest, $errno, $errstr, $errfile, $errline, $errcontext);
429
430
		if(file_exists($errfile)) {
431
			$lines = file($errfile);
432
433
			// Make the array 1-based
434
			array_unshift($lines,"");
435
			unset($lines[0]);
436
437
			$offset = $errline-10;
438
			$lines = array_slice($lines, $offset, 16, true);
439
			$reporter->writeSourceFragment($lines, $errline);
440
		}
441
		$reporter->writeTrace(($errcontext ? $errcontext : debug_backtrace()));
442
		$reporter->writeFooter();
443
		return false;
444
	}
445
446
	/**
447
	 * Utility method to render a snippet of PHP source code, from selected file
448
	 * and highlighting the given line number.
449
	 *
450
	 * @param string $errfile
451
	 * @param int $errline
452
	 */
453
	public static function showLines($errfile, $errline) {
454
		$lines = file($errfile);
455
		$offset = $errline-10;
456
		$lines = array_slice($lines, $offset, 16);
457
		echo '<pre>';
458
		$offset++;
459
		foreach($lines as $line) {
460
			$line = htmlentities($line, ENT_COMPAT, 'UTF-8');
461
			if ($offset == $errline) {
462
				echo "<span>$offset</span> <span class=\"error\">$line</span>";
463
			} else {
464
				echo "<span>$offset</span> $line";
465
			}
466
			$offset++;
467
		}
468
		echo '</pre>';
469
	}
470
471
	/**
472
	 * Check if the user has permissions to run URL debug tools,
473
	 * else redirect them to log in.
474
	 */
475
	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...
476
		if(Director::isDev())	{
477
			return;
478
		}
479
		if(isset($_SESSION['loggedInAs'])) {
480
			// We have to do some raw SQL here, because this method is called in Object::defineMethods().
481
			// This means we have to be careful about what objects we create, as we don't want Object::defineMethods()
482
			// being called again.
483
			// 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...
484
485
			// @TODO - Rewrite safely using DataList::filter
486
			$memberID = $_SESSION['loggedInAs'];
487
			$permission = DB::prepared_query('
488
				SELECT "ID" FROM "Permission"
489
				INNER JOIN "Group_Members" ON "Permission"."GroupID" = "Group_Members"."GroupID"
490
				WHERE "Permission"."Code" = ?
491
				AND "Permission"."Type" = ?
492
				AND "Group_Members"."MemberID" = ?',
493
				array(
494
					'ADMIN', // Code
495
					Permission::GRANT_PERMISSION, // Type
496
					$memberID // MemberID
497
				)
498
			)->value();
499
500
			if($permission) return;
501
		}
502
503
		// This basically does the same as
504
		// 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...
505
		// We have to do this because of how early this method is called in execution.
506
		$_SESSION['Security']['Message']['message']
507
			= "You need to login with developer access to make use of debugging tools.";
508
		$_SESSION['Security']['Message']['type'] =  'warning';
509
		$_SESSION['BackURL'] = $_SERVER['REQUEST_URI'];
510
		header($_SERVER['SERVER_PROTOCOL'] . " 302 Found");
511
		header("Location: " . Director::baseURL() . Security::login_url());
512
		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...
513
	}
514
}
515
516
517
518
519
520
521
522
523
524
525
/**
526
 * Generic callback, to catch uncaught exceptions when they bubble up to the top of the call chain.
527
 *
528
 * @ignore
529
 * @param Exception $exception
530
 */
531
function exceptionHandler($exception) {
532
	$errno = E_USER_ERROR;
533
	$type = get_class($exception);
534
	$message = "Uncaught " . $type . ": " . $exception->getMessage();
535
	$file = $exception->getFile();
536
	$line = $exception->getLine();
537
	$context = $exception->getTrace();
538
	Debug::fatalHandler($errno, $message, $file, $line, $context);
539
	exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The function exceptionHandler() 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...
540
}
541
542
/**
543
 * Generic callback to catch standard PHP runtime errors thrown by the interpreter
544
 * or manually triggered with the user_error function. Any unknown error codes are treated as
545
 * fatal errors.
546
 * Caution: The error levels default to E_ALL if the site is in dev-mode (set in main.php).
547
 *
548
 * @ignore
549
 * @param int $errno
550
 * @param string $errstr
551
 * @param string $errfile
552
 * @param int $errline
553
 */
554
function errorHandler($errno, $errstr, $errfile, $errline) {
555
	switch($errno) {
556
		case E_NOTICE:
557
		case E_USER_NOTICE:
558
		case E_DEPRECATED:
559
		case E_USER_DEPRECATED:
560
		case E_STRICT:
561
			return Debug::noticeHandler($errno, $errstr, $errfile, $errline, debug_backtrace());
562
563
		case E_WARNING:
564
		case E_CORE_WARNING:
565
		case E_USER_WARNING:
566
		case E_RECOVERABLE_ERROR:
567
			return Debug::warningHandler($errno, $errstr, $errfile, $errline, debug_backtrace());
568
569
		case E_ERROR:
570
		case E_CORE_ERROR:
571
		case E_USER_ERROR:
572
		default:
573
			Debug::fatalHandler($errno, $errstr, $errfile, $errline, debug_backtrace());
574
			exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The function errorHandler() 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...
575
	}
576
}
577