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