Apply PSR-12 code style (#3886)
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / core.functions.php
CommitLineData
a9229942 1<?php // @codingStandardsIgnoreFile
158bd3ca
TD
2/**
3 * @author Marcel Werk
7b7b9764 4 * @copyright 2001-2019 WoltLab GmbH
158bd3ca 5 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
e71525e4 6 * @package WoltLabSuite\Core
158bd3ca 7 */
5bc5a7e6
TD
8namespace {
9 use wcf\system\WCF;
2b6fbe94 10
5bc5a7e6 11 // set exception handler
d82507dc 12 set_exception_handler([WCF::class, 'handleException']);
5bc5a7e6 13 // set php error handler
d82507dc 14 set_error_handler([WCF::class, 'handleError'], E_ALL);
5bc5a7e6 15 // set shutdown function
d82507dc 16 register_shutdown_function([WCF::class, 'destruct']);
5bc5a7e6 17 // set autoload function
d82507dc 18 spl_autoload_register([WCF::class, 'autoload']);
ef47717c 19
9bfa7303
TD
20 spl_autoload_register(function ($className) {
21 /**
22 * @deprecated 5.3 This file is a compatibility layer mapping from Leafo\\ to ScssPhp\\
23 */
24 $leafo = 'Leafo\\';
25 if (substr($className, 0, strlen($leafo)) === $leafo) {
26 class_alias('ScssPhp\\'.substr($className, strlen($leafo)), $className, true);
27 }
28 });
29
ef47717c
MS
30 /**
31 * Escapes a string for use in sql query.
32 *
33 * @see \wcf\system\database\Database::escapeString()
34 * @param string $string
35 * @return string
36 */
5bc5a7e6
TD
37 function escapeString($string) {
38 return WCF::getDB()->escapeString($string);
39 }
f406809a
AE
40
41 /**
42 * Helper method to output debug data for all passed variables,
43 * uses `print_r()` for arrays and objects, `var_dump()` otherwise.
44 */
45 function wcfDebug() {
46 echo "<pre>";
47
48 $args = func_get_args();
49 $length = count($args);
50 if ($length === 0) {
a5cf4a28 51 echo "ERROR: No arguments provided.<hr>";
f406809a
AE
52 }
53 else {
54 for ($i = 0; $i < $length; $i++) {
55 $arg = $args[$i];
56
57 echo "<h2>Argument {$i} (" . gettype($arg) . ")</h2>";
58
59 if (is_array($arg) || is_object($arg)) {
60 print_r($arg);
61 }
62 else {
63 var_dump($arg);
64 }
65
66 echo "<hr>";
67 }
68 }
69
70 $backtrace = debug_backtrace();
71
72 // output call location to help finding these debug outputs again
73 echo "wcfDebug() called in {$backtrace[0]['file']} on line {$backtrace[0]['line']}";
74
75 echo "</pre>";
76
77 exit;
78 }
2b6fbe94 79
5bc5a7e6
TD
80 // define DOCUMENT_ROOT on IIS if not set
81 if (PHP_EOL == "\r\n") {
82 if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['SCRIPT_FILENAME'])) {
82d72850 83 $_SERVER['DOCUMENT_ROOT'] = str_replace('\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0 - strlen($_SERVER['PHP_SELF'])));
5bc5a7e6
TD
84 }
85 if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['PATH_TRANSLATED'])) {
82d72850 86 $_SERVER['DOCUMENT_ROOT'] = str_replace('\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0 - strlen($_SERVER['PHP_SELF'])));
5bc5a7e6 87 }
2b6fbe94 88
5bc5a7e6
TD
89 if (!isset($_SERVER['REQUEST_URI'])) {
90 $_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'], 1);
91 if (isset($_SERVER['QUERY_STRING'])) {
92 $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
93 }
94 }
95 }
416c2b51
AE
96
97 // setting global gzip compression breaks output buffering
98 if (@ini_get('zlib.output_compression')) {
ac81e92d 99 @ini_set('zlib.output_compression', '0');
416c2b51 100 }
745a1974
AE
101
102 if (!function_exists('is_countable')) {
a5525bc9
MS
103 function is_countable($var) {
104 return is_array($var) || $var instanceof Countable || $var instanceof ResourceBundle || $var instanceof SimpleXmlElement;
105 }
745a1974 106 }
dcb3a44c 107}
322ced57 108
2c1520ae
TD
109namespace wcf {
110 function getRequestId() {
111 if (!defined('WCF_REQUEST_ID_HEADER') || !WCF_REQUEST_ID_HEADER) return '';
112
113 return $_SERVER[WCF_REQUEST_ID_HEADER] ?? '';
114 }
3a046f18
TD
115
116 function getMinorVersion() {
117 return preg_replace('/^(\d+\.\d+)\..*$/', '\\1', WCF_VERSION);
118 }
2c1520ae
TD
119}
120
5bc5a7e6 121namespace wcf\functions\exception {
fc23fa20 122 use wcf\system\WCF;
b8e0376c 123 use wcf\system\exception\IExtraInformationException;
5bc5a7e6 124 use wcf\system\exception\SystemException;
5bc5a7e6
TD
125 use wcf\util\FileUtil;
126 use wcf\util\StringUtil;
15de71ce
TD
127
128 /**
129 * Logs the given Throwable.
130 *
131 * @param \Throwable|\Exception $e
132 * @param string $logFile The log file to use. If set to `null` the default log file will be used and the variable contents will be replaced by the actual path.
133 * @return string The ID of the log entry.
134 */
76e90abc 135 function logThrowable($e, &$logFile = null) {
8891d4b9 136 if ($logFile === null) $logFile = WCF_DIR . 'log/' . gmdate('Y-m-d', TIME_NOW) . '.txt';
b8149369 137 touch($logFile);
e4499881 138
45774969
TD
139 $stripNewlines = function ($item) {
140 return str_replace("\n", ' ', $item);
141 };
142
9b83d173 143 // don't forget to update ExceptionLogUtil / ExceptionLogViewPage, when changing the log file format
b8149369 144 $message = gmdate('r', TIME_NOW)."\n".
45774969 145 'Message: '.$stripNewlines($e->getMessage())."\n".
b8149369 146 'PHP version: '.phpversion()."\n".
67d7afc7 147 'WoltLab Suite version: '.WCF_VERSION."\n".
9b83d173 148 'Request URI: '.$stripNewlines(($_SERVER['REQUEST_METHOD'] ?? '').' '.($_SERVER['REQUEST_URI'] ?? '')).(\wcf\getRequestId() ? ' ('.\wcf\getRequestId().')' : '')."\n".
45774969
TD
149 'Referrer: '.$stripNewlines($_SERVER['HTTP_REFERER'] ?? '')."\n".
150 'User Agent: '.$stripNewlines($_SERVER['HTTP_USER_AGENT'] ?? '')."\n".
b8149369 151 'Peak Memory Usage: '.memory_get_peak_usage().'/'.FileUtil::getMemoryLimit()."\n";
33b728a6 152 $prev = $e;
b8149369
TD
153 do {
154 $message .= "======\n".
33b728a6 155 'Error Class: '.get_class($prev)."\n".
45774969 156 'Error Message: '.$stripNewlines($prev->getMessage())."\n".
cdb994ab 157 'Error Code: '.$stripNewlines($prev->getCode())."\n".
45774969 158 'File: '.$stripNewlines($prev->getFile()).' ('.$prev->getLine().')'."\n".
33b728a6 159 'Extra Information: '.($prev instanceof IExtraInformationException ? base64_encode(serialize($prev->getExtraInformation())) : '-')."\n".
72f128c3 160 'Stack Trace: '.json_encode(array_map(function ($item) {
b8149369
TD
161 $item['args'] = array_map(function ($item) {
162 switch (gettype($item)) {
163 case 'object':
164 return get_class($item);
165 case 'array':
166 return array_map(function () {
167 return '[redacted]';
168 }, $item);
403684df
TD
169 case 'resource':
170 return 'resource('.get_resource_type($item).')';
b8149369
TD
171 default:
172 return $item;
173 }
174 }, $item['args']);
e4499881 175
b8149369 176 return $item;
33b728a6 177 }, sanitizeStacktrace($prev, true)))."\n";
b8149369 178 }
33b728a6 179 while ($prev = $prev->getPrevious());
e4499881 180
b8149369
TD
181 // calculate Exception-ID
182 $exceptionID = sha1($message);
2b6fbe94 183 $entry = "<<<<<<<<".$exceptionID."<<<<\n".$message."<<<<\n\n";
e4499881 184
b8149369 185 file_put_contents($logFile, $entry, FILE_APPEND);
33b728a6
TD
186
187 // let the Exception know it has been logged
188 if (method_exists($e, 'finalizeLog') && is_callable([$e, 'finalizeLog'])) $e->finalizeLog($exceptionID, $logFile);
189
b8149369
TD
190 return $exceptionID;
191 }
2b6fbe94 192
15de71ce
TD
193 /**
194 * Pretty prints the given Throwable. It is recommended to `exit;`
195 * the request after calling this function.
196 *
197 * @param \Throwable|\Exception $e
198 * @throws \Exception
199 */
5bc5a7e6 200 function printThrowable($e) {
76e90abc 201 $exceptionID = logThrowable($e, $logFile);
2c1520ae 202 if (\wcf\getRequestId()) $exceptionID .= '/'.\wcf\getRequestId();
614e2466
AE
203
204 $exceptionTitle = $exceptionSubtitle = $exceptionExplanation = '';
205 $logFile = sanitizePath($logFile);
206 try {
04edc2ff
AE
207 if (WCF::getLanguage() !== null) {
208 $exceptionTitle = WCF::getLanguage()->get('wcf.global.exception.title', true);
209 $exceptionSubtitle = str_replace('{$exceptionID}', $exceptionID, WCF::getLanguage()->get('wcf.global.exception.subtitle', true));
210 $exceptionExplanation = str_replace('{$logFile}', $logFile, WCF::getLanguage()->get('wcf.global.exception.explanation', true));
211 }
614e2466 212 }
614e2466
AE
213 catch (\Throwable $e) {
214 // ignore
215 }
216
217 if (!$exceptionTitle || !$exceptionSubtitle || !$exceptionExplanation) {
218 // one or more failed, fallback to english
e8d7ee4d 219 $exceptionTitle = 'An error has occurred';
a7eb5dda 220 $exceptionSubtitle = 'Internal error code: <span class="exceptionInlineCodeWrapper"><span class="exceptionInlineCode">'.$exceptionID.'</span></span>';
614e2466
AE
221 $exceptionExplanation = <<<EXPLANATION
222<p class="exceptionSubtitle">What happened?</p>
223<p class="exceptionText">An error has occured while trying to handle your request and execution has been terminated. Please forward the above error code to the site administrator.</p>
224<p class="exceptionText">&nbsp;</p> <!-- required to ensure spacing after copy & paste -->
225<p class="exceptionText">
226 The error code can be used by an administrator to lookup the full error message in the Administration Control Panel via “Logs » Errors”.
819d7822 227 In addition the error has been written to the log file located at <span class="exceptionInlineCodeWrapper"><span class="exceptionInlineCode">{$logFile}</span></span> and can be accessed with a FTP program or similar.
614e2466
AE
228</p>
229<p class="exceptionText">&nbsp;</p> <!-- required to ensure spacing after copy & paste -->
230<p class="exceptionText">Notice: The error code was randomly generated and has no use beyond looking up the full message.</p>
231EXPLANATION;
232
233 }
234
235 /*
236 * A notice on the HTML used below:
237 *
238 * It might appear a bit weird to use <p> all over the place where semantically
239 * other elements would fit in way better. The reason behind this is that we avoid
240 * inheriting unwanted styles (e.g. exception displayed in an overlay) and that
241 * the output needs to be properly readable when copied & pasted somewhere.
242 *
243 * Besides the visual appearance, the output was built to provide a maximum of
244 * compatibility and readability when pasted somewhere else, e.g. a WYSIWYG editor
245 * without the potential of messing up the formatting and thus harming the readability.
246 */
5bc5a7e6
TD
247 ?><!DOCTYPE html>
248 <html>
249 <head>
962c9241 250 <?php if (!defined('EXCEPTION_PRIVACY') || EXCEPTION_PRIVACY !== 'private') { ?>
5bc5a7e6
TD
251 <title>Fatal Error: <?php echo StringUtil::encodeHTML($e->getMessage()); ?></title>
252 <?php } else { ?>
253 <title>Fatal Error</title>
254 <?php } ?>
614e2466
AE
255 <meta charset="utf-8">
256 <meta name="viewport" content="width=device-width, initial-scale=1">
5bc5a7e6 257 <style>
614e2466 258 .exceptionBody {
8972cde9
MW
259 background-color: rgb(250, 250, 250);
260 color: rgb(44, 62, 80);
614e2466
AE
261 margin: 0;
262 padding: 0;
5bc5a7e6 263 }
614e2466
AE
264
265 .exceptionContainer {
266 box-sizing: border-box;
267 font-family: 'Segoe UI', 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif;
268 font-size: 14px;
269 padding-bottom: 20px;
5bc5a7e6 270 }
614e2466
AE
271
272 .exceptionContainer * {
273 box-sizing: inherit;
614e2466
AE
274 line-height: 1.5em;
275 margin: 0;
276 padding: 0;
5bc5a7e6 277 }
614e2466
AE
278
279 .exceptionHeader {
8972cde9 280 background-color: rgb(58, 109, 156);
89130385 281 padding: 30px 0;
5bc5a7e6 282 }
614e2466
AE
283
284 .exceptionTitle {
285 color: #fff;
286 font-size: 28px;
287 font-weight: 300;
5bc5a7e6 288 }
614e2466
AE
289
290 .exceptionErrorCode {
291 color: #fff;
292 margin-top: .5em;
5bc5a7e6 293 }
614e2466
AE
294
295 .exceptionErrorCode .exceptionInlineCode {
3bfbe63f 296 background-color: rgb(43, 79, 113);
614e2466
AE
297 border-radius: 3px;
298 color: #fff;
299 font-family: monospace;
300 padding: 3px 10px;
301 white-space: nowrap;
5bc5a7e6 302 }
614e2466
AE
303
304 .exceptionSubtitle {
305 border-bottom: 1px solid rgb(238, 238, 238);
614e2466
AE
306 font-size: 24px;
307 font-weight: 300;
308 margin-bottom: 15px;
309 padding-bottom: 10px;
5bc5a7e6 310 }
614e2466
AE
311
312 .exceptionContainer > .exceptionBoundary {
313 margin-top: 30px;
5bc5a7e6 314 }
614e2466
AE
315
316 .exceptionText .exceptionInlineCodeWrapper {
317 border: 1px solid rgb(169, 169, 169);
318 border-radius: 3px;
319 padding: 2px 5px;
5bc5a7e6 320 }
614e2466
AE
321
322 .exceptionText .exceptionInlineCode {
323 font-family: monospace;
324 white-space: nowrap;
5bc5a7e6 325 }
614e2466
AE
326
327 .exceptionFieldTitle {
328 color: rgb(59, 109, 169);
5bc5a7e6 329 }
614e2466
AE
330
331 .exceptionFieldTitle .exceptionColon {
332 /* hide colon in browser, but will be visible after copy & paste */
333 opacity: 0;
5bc5a7e6 334 }
614e2466
AE
335
336 .exceptionFieldValue {
337 font-size: 18px;
3d8a295e 338 min-height: 1.5em;
614e2466
AE
339 }
340
341 .exceptionSystemInformation,
342 .exceptionErrorDetails,
343 .exceptionStacktrace {
344 list-style-type: none;
345 }
346
347 .exceptionSystemInformation > li:not(:first-child),
348 .exceptionErrorDetails > li:not(:first-child) {
349 margin-top: 10px;
350 }
351
352 .exceptionStacktrace {
353 display: block;
354 margin-top: 5px;
355 overflow: auto;
356 padding-bottom: 20px;
357 }
358
359 .exceptionStacktraceFile,
360 .exceptionStacktraceFile span,
361 .exceptionStacktraceCall,
362 .exceptionStacktraceCall span {
363 font-family: monospace !important;
364 white-space: nowrap !important;
365 }
366
367 .exceptionStacktraceCall + .exceptionStacktraceFile {
368 margin-top: 5px;
369 }
370
371 .exceptionStacktraceCall {
372 padding-left: 40px;
373 }
374
375 .exceptionStacktraceCall,
376 .exceptionStacktraceCall span {
377 color: rgb(102, 102, 102) !important;
378 font-size: 13px !important;
379 }
380
381 /* mobile */
382 @media (max-width: 767px) {
383 .exceptionBoundary {
384 min-width: 320px;
385 padding: 0 10px;
dd6a2623 386 }
614e2466
AE
387
388 .exceptionText .exceptionInlineCodeWrapper {
389 display: inline-block;
390 overflow: auto;
dd6a2623 391 }
614e2466
AE
392
393 .exceptionErrorCode .exceptionInlineCode {
394 font-size: 13px;
395 padding: 2px 5px;
dd6a2623 396 }
614e2466
AE
397 }
398
399 /* desktop */
400 @media (min-width: 768px) {
401 .exceptionBoundary {
402 margin: 0 auto;
403 max-width: 1400px;
404 min-width: 1200px;
405 padding: 0 10px;
406 }
407
408 .exceptionSystemInformation {
409 display: flex;
410 flex-wrap: wrap;
411 }
412
413 .exceptionSystemInformation1,
414 .exceptionSystemInformation3,
415 .exceptionSystemInformation5 {
416 flex: 0 0 200px;
417 margin: 0 0 10px 0 !important;
418 }
419
420 .exceptionSystemInformation2,
421 .exceptionSystemInformation4,
422 .exceptionSystemInformation6 {
423 flex: 0 0 calc(100% - 210px);
424 margin: 0 0 10px 10px !important;
425 max-width: calc(100% - 210px);
426 }
427
428 .exceptionSystemInformation1 { order: 1; }
429 .exceptionSystemInformation2 { order: 2; }
430 .exceptionSystemInformation3 { order: 3; }
431 .exceptionSystemInformation4 { order: 4; }
432 .exceptionSystemInformation5 { order: 5; }
433 .exceptionSystemInformation6 { order: 6; }
434
435 .exceptionSystemInformation .exceptionFieldValue {
436 overflow: hidden;
437 text-overflow: ellipsis;
438 white-space: nowrap;
439 }
440 }
441 </style>
442 </head>
443 <body class="exceptionBody">
444 <div class="exceptionContainer">
445 <div class="exceptionHeader">
446 <div class="exceptionBoundary">
447 <p class="exceptionTitle"><?php echo $exceptionTitle; ?></p>
448 <p class="exceptionErrorCode"><?php echo str_replace('{$exceptionID}', $exceptionID, $exceptionSubtitle); ?></p>
449 </div>
450 </div>
451
452 <div class="exceptionBoundary">
453 <?php echo $exceptionExplanation; ?>
5bc5a7e6 454 </div>
962c9241 455 <?php if (!defined('EXCEPTION_PRIVACY') || EXCEPTION_PRIVACY !== 'private') { ?>
614e2466
AE
456 <div class="exceptionBoundary">
457 <p class="exceptionSubtitle">System Information</p>
458 <ul class="exceptionSystemInformation">
459 <li class="exceptionSystemInformation1">
460 <p class="exceptionFieldTitle">PHP Version<span class="exceptionColon">:</span></p>
461 <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(phpversion()); ?></p>
462 </li>
463 <li class="exceptionSystemInformation3">
bb2a04c7 464 <p class="exceptionFieldTitle">WoltLab Suite Core<span class="exceptionColon">:</span></p>
614e2466
AE
465 <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(WCF_VERSION); ?></p>
466 </li>
467 <li class="exceptionSystemInformation5">
468 <p class="exceptionFieldTitle">Peak Memory Usage<span class="exceptionColon">:</span></p>
469 <p class="exceptionFieldValue"><?php echo round(memory_get_peak_usage() / 1024 / 1024, 3); ?>/<?php echo round(FileUtil::getMemoryLimit() / 1024 / 1024, 3); ?> MiB</p>
470 </li>
471 <li class="exceptionSystemInformation2">
472 <p class="exceptionFieldTitle">Request URI<span class="exceptionColon">:</span></p>
9b83d173 473 <p class="exceptionFieldValue"><?php if (isset($_SERVER['REQUEST_METHOD'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_METHOD']); ?> <?php if (isset($_SERVER['REQUEST_URI'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_URI']); ?></p>
614e2466
AE
474 </li>
475 <li class="exceptionSystemInformation4">
476 <p class="exceptionFieldTitle">Referrer<span class="exceptionColon">:</span></p>
477 <p class="exceptionFieldValue"><?php if (isset($_SERVER['HTTP_REFERER'])) echo StringUtil::encodeHTML($_SERVER['HTTP_REFERER']); ?></p>
478 </li>
479 <li class="exceptionSystemInformation6">
480 <p class="exceptionFieldTitle">User Agent<span class="exceptionColon">:</span></p>
481 <p class="exceptionFieldValue"><?php if (isset($_SERVER['HTTP_USER_AGENT'])) echo StringUtil::encodeHTML($_SERVER['HTTP_USER_AGENT']); ?></p>
482 </li>
483 </ul>
5bc5a7e6 484 </div>
614e2466 485
5bc5a7e6
TD
486 <?php
487 $first = true;
ef68e2f2
TD
488 $exceptions = [];
489 $current = $e;
490 do {
491 $exceptions[] = $current;
492 }
493 while ($current = $current->getPrevious());
494
495 $e = array_pop($exceptions);
5bc5a7e6
TD
496 do {
497 ?>
614e2466 498 <div class="exceptionBoundary">
ef68e2f2 499 <p class="exceptionSubtitle"><?php if (!empty($exceptions) && $first) { echo "Original "; } else if (empty($exceptions) && !$first) { echo "Final "; } ?>Error</p>
5bc5a7e6 500 <?php if ($e instanceof SystemException && $e->getDescription()) { ?>
614e2466 501 <p class="exceptionText"><?php echo $e->getDescription(); ?></p>
5bc5a7e6 502 <?php } ?>
614e2466 503 <ul class="exceptionErrorDetails">
0b46efcf
TD
504 <li>
505 <p class="exceptionFieldTitle">Error Type<span class="exceptionColon">:</span></p>
506 <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(get_class($e)); ?></p>
507 </li>
614e2466
AE
508 <li>
509 <p class="exceptionFieldTitle">Error Message<span class="exceptionColon">:</span></p>
510 <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML($e->getMessage()); ?></p>
511 </li>
512 <?php if ($e->getCode()) { ?>
513 <li>
514 <p class="exceptionFieldTitle">Error Code<span class="exceptionColon">:</span></p>
cdb994ab 515 <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML($e->getCode()); ?></p>
614e2466
AE
516 </li>
517 <?php } ?>
518 <li>
519 <p class="exceptionFieldTitle">File<span class="exceptionColon">:</span></p>
520 <p class="exceptionFieldValue" style="word-break: break-all"><?php echo StringUtil::encodeHTML(sanitizePath($e->getFile())); ?> (<?php echo $e->getLine(); ?>)</p>
521 </li>
522
5bc5a7e6
TD
523 <?php
524 if ($e instanceof SystemException) {
525 ob_start();
526 $e->show();
527 ob_end_clean();
2b6fbe94 528
5bc5a7e6
TD
529 $reflection = new \ReflectionClass($e);
530 $property = $reflection->getProperty('information');
531 $property->setAccessible(true);
532 if ($property->getValue($e)) {
533 throw new \Exception("Using the 'information' property of SystemException is not supported any more.");
534 }
535 }
b8e0376c
TD
536 if ($e instanceof IExtraInformationException) {
537 foreach ($e->getExtraInformation() as list($key, $value)) {
614e2466
AE
538 ?>
539 <li>
540 <p class="exceptionFieldTitle"><?php echo StringUtil::encodeHTML($key); ?><span class="exceptionColon">:</span></p>
541 <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML($value); ?></p>
542 </li>
543 <?php
b8e0376c
TD
544 }
545 }
5bc5a7e6 546 ?>
614e2466
AE
547 <li>
548 <p class="exceptionFieldTitle">Stack Trace<span class="exceptionColon">:</span></p>
549 <ul class="exceptionStacktrace">
550 <?php
551 $trace = sanitizeStacktrace($e);
614e2466
AE
552 for ($i = 0, $max = count($trace); $i < $max; $i++) {
553 ?>
554 <li class="exceptionStacktraceFile"><?php echo '#'.$i.' '.StringUtil::encodeHTML($trace[$i]['file']).' ('.$trace[$i]['line'].')'.':'; ?></li>
555 <li class="exceptionStacktraceCall">
556 <?php
557 echo $trace[$i]['class'].$trace[$i]['type'].$trace[$i]['function'].'(';
558 echo implode(', ', array_map(function ($item) {
559 switch (gettype($item)) {
560 case 'integer':
561 case 'double':
562 return $item;
563 case 'NULL':
564 return 'null';
565 case 'string':
aac60824 566 return "'".addcslashes(StringUtil::encodeHTML($item), "\\'")."'";
614e2466
AE
567 case 'boolean':
568 return $item ? 'true' : 'false';
569 case 'array':
570 $keys = array_keys($item);
571 if (count($keys) > 5) return "[ ".count($keys)." items ]";
572 return '[ '.implode(', ', array_map(function ($item) {
573 return $item.' => ';
574 }, $keys)).']';
575 case 'object':
576 return get_class($item);
403684df
TD
577 case 'resource':
578 return 'resource('.get_resource_type($item).')';
476ee399
MS
579 case 'resource (closed)':
580 return 'resource (closed)';
614e2466 581 }
ef47717c
MS
582
583 throw new \LogicException('Unreachable');
614e2466
AE
584 }, $trace[$i]['args']));
585 echo ')</li>';
586 }
587 ?>
588 </ul>
589 </li>
590 </ul>
5bc5a7e6
TD
591 </div>
592 <?php
593 $first = false;
ef68e2f2 594 } while ($e = array_pop($exceptions));
5bc5a7e6
TD
595 ?>
596 <?php } ?>
597 </div>
598 </body>
599 </html>
600 <?php
322ced57 601 }
5bc5a7e6 602
15de71ce
TD
603 /**
604 * Returns the stack trace of the given Throwable with sensitive
605 * information removed.
606 *
607 * @param \Throwable|\Exception $e
1524f8c0 608 * @param bool $ignorePaths If set to `true`: Don't call `sanitizePath`.
15de71ce
TD
609 * @return mixed[]
610 */
5bc5a7e6
TD
611 function sanitizeStacktrace($e, $ignorePaths = false) {
612 $trace = $e->getTrace();
2b6fbe94 613
5bc5a7e6 614 return array_map(function ($item) use ($ignorePaths) {
2b6fbe94
TD
615 if (!isset($item['file'])) $item['file'] = '[internal function]';
616 if (!isset($item['line'])) $item['line'] = '?';
617 if (!isset($item['class'])) $item['class'] = '';
618 if (!isset($item['type'])) $item['type'] = '';
335aae2b 619 if (!isset($item['args'])) $item['args'] = [];
2b6fbe94 620
5bc5a7e6 621 // strip database credentials
2b6fbe94
TD
622 if (preg_match('~\\\\?wcf\\\\system\\\\database\\\\[a-zA-Z]*Database~', $item['class']) || $item['class'] === 'PDO') {
623 if ($item['function'] === '__construct') {
624 $item['args'] = array_map(function () {
625 return '[redacted]';
626 }, $item['args']);
5bc5a7e6
TD
627 }
628 }
390ec505 629
5bc5a7e6
TD
630 if (!$ignorePaths) {
631 $item['args'] = array_map(function ($item) {
b8e0376c 632 if (!is_string($item)) return $item;
390ec505
TD
633
634 if (preg_match('~^('.preg_quote($_SERVER['DOCUMENT_ROOT'], '~').'|'.preg_quote(WCF_DIR, '~').')~', $item)) {
5bc5a7e6
TD
635 $item = sanitizePath($item);
636 }
2b6fbe94 637
390ec505 638 return $item;
5bc5a7e6 639 }, $item['args']);
390ec505 640
2b6fbe94 641 $item['file'] = sanitizePath($item['file']);
5bc5a7e6 642 }
390ec505 643
5bc5a7e6
TD
644 return $item;
645 }, $trace);
322ced57 646 }
390ec505 647
15de71ce
TD
648 /**
649 * Returns the given path relative to `WCF_DIR`, unless both,
650 * `EXCEPTION_PRIVACY` is `public` and the debug mode is enabled.
651 *
ef47717c 652 * @param string $path
15de71ce
TD
653 * @return string
654 */
5bc5a7e6 655 function sanitizePath($path) {
962c9241
TD
656 if (WCF::debugModeIsEnabled() && defined('EXCEPTION_PRIVACY') && EXCEPTION_PRIVACY === 'public') {
657 return $path;
658 }
390ec505 659
5bc5a7e6 660 return '*/'.FileUtil::removeTrailingSlash(FileUtil::getRelativePath(WCF_DIR, $path));
322ced57
AE
661 }
662}