Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Utility / Error.php
1 <?php
2
3 namespace Drupal\Core\Utility;
4
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Component\Utility\Xss;
7 use Drupal\Core\Database\DatabaseExceptionWrapper;
8
9 /**
10  * Drupal error utility class.
11  */
12 class Error {
13
14   /**
15    * The error severity level.
16    *
17    * @var int
18    */
19   const ERROR = 3;
20
21   /**
22    * An array of blacklisted functions.
23    *
24    * @var array
25    */
26   protected static $blacklistFunctions = ['debug', '_drupal_error_handler', '_drupal_exception_handler'];
27
28   /**
29    * Decodes an exception and retrieves the correct caller.
30    *
31    * @param \Exception|\Throwable $exception
32    *   The exception object that was thrown.
33    *
34    * @return array
35    *   An error in the format expected by _drupal_log_error().
36    */
37   public static function decodeException($exception) {
38     $message = $exception->getMessage();
39
40     $backtrace = $exception->getTrace();
41     // Add the line throwing the exception to the backtrace.
42     array_unshift($backtrace, ['line' => $exception->getLine(), 'file' => $exception->getFile()]);
43
44     // For PDOException errors, we try to return the initial caller,
45     // skipping internal functions of the database layer.
46     if ($exception instanceof \PDOException || $exception instanceof DatabaseExceptionWrapper) {
47       // The first element in the stack is the call, the second element gives us
48       // the caller. We skip calls that occurred in one of the classes of the
49       // database layer or in one of its global functions.
50       $db_functions = ['db_query', 'db_query_range'];
51       while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
52         ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
53           in_array($caller['function'], $db_functions))) {
54         // We remove that call.
55         array_shift($backtrace);
56       }
57       if (isset($exception->query_string, $exception->args)) {
58         $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
59       }
60     }
61
62     $caller = static::getLastCaller($backtrace);
63
64     return [
65       '%type' => get_class($exception),
66       // The standard PHP exception handler considers that the exception message
67       // is plain-text. We mimic this behavior here.
68       '@message' => $message,
69       '%function' => $caller['function'],
70       '%file' => $caller['file'],
71       '%line' => $caller['line'],
72       'severity_level' => static::ERROR,
73       'backtrace' => $backtrace,
74       '@backtrace_string' => $exception->getTraceAsString(),
75     ];
76   }
77
78   /**
79    * Renders an exception error message without further exceptions.
80    *
81    * @param \Exception|\Throwable $exception
82    *   The exception object that was thrown.
83    *
84    * @return string
85    *   An error message.
86    */
87   public static function renderExceptionSafe($exception) {
88     $decode = static::decodeException($exception);
89     $backtrace = $decode['backtrace'];
90     unset($decode['backtrace']);
91     // Remove 'main()'.
92     array_shift($backtrace);
93
94     // Even though it is possible that this method is called on a public-facing
95     // site, it is only called when the exception handler itself threw an
96     // exception, which normally means that a code change caused the system to
97     // no longer function correctly (as opposed to a user-triggered error), so
98     // we assume that it is safe to include a verbose backtrace.
99     $decode['@backtrace'] = Error::formatBacktrace($backtrace);
100     return new FormattableMarkup('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $decode);
101   }
102
103   /**
104    * Gets the last caller from a backtrace.
105    *
106    * @param array $backtrace
107    *   A standard PHP backtrace. Passed by reference.
108    *
109    * @return array
110    *   An associative array with keys 'file', 'line' and 'function'.
111    */
112   public static function getLastCaller(array &$backtrace) {
113     // Errors that occur inside PHP internal functions do not generate
114     // information about file and line. Ignore black listed functions.
115     while (($backtrace && !isset($backtrace[0]['line'])) ||
116       (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], static::$blacklistFunctions))) {
117       array_shift($backtrace);
118     }
119
120     // The first trace is the call itself.
121     // It gives us the line and the file of the last call.
122     $call = $backtrace[0];
123
124     // The second call gives us the function where the call originated.
125     if (isset($backtrace[1])) {
126       if (isset($backtrace[1]['class'])) {
127         $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
128       }
129       else {
130         $call['function'] = $backtrace[1]['function'] . '()';
131       }
132     }
133     else {
134       $call['function'] = 'main()';
135     }
136
137     return $call;
138   }
139
140   /**
141    * Formats a backtrace into a plain-text string.
142    *
143    * The calls show values for scalar arguments and type names for complex ones.
144    *
145    * @param array $backtrace
146    *   A standard PHP backtrace.
147    *
148    * @return string
149    *   A plain-text line-wrapped string ready to be put inside <pre>.
150    */
151   public static function formatBacktrace(array $backtrace) {
152     $return = '';
153
154     foreach ($backtrace as $trace) {
155       $call = ['function' => '', 'args' => []];
156
157       if (isset($trace['class'])) {
158         $call['function'] = $trace['class'] . $trace['type'] . $trace['function'];
159       }
160       elseif (isset($trace['function'])) {
161         $call['function'] = $trace['function'];
162       }
163       else {
164         $call['function'] = 'main';
165       }
166
167       if (isset($trace['args'])) {
168         foreach ($trace['args'] as $arg) {
169           if (is_scalar($arg)) {
170             $call['args'][] = is_string($arg) ? '\'' . Xss::filter($arg) . '\'' : $arg;
171           }
172           else {
173             $call['args'][] = ucfirst(gettype($arg));
174           }
175         }
176       }
177
178       $line = '';
179       if (isset($trace['line'])) {
180         $line = " (Line: {$trace['line']})";
181       }
182
183       $return .= $call['function'] . '(' . implode(', ', $call['args']) . ")$line\n";
184     }
185
186     return $return;
187   }
188
189 }