Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\EventSubscriber;
|
Chris@0
|
4
|
Chris@17
|
5 use Drupal\Component\Render\FormattableMarkup;
|
Chris@0
|
6 use Drupal\Core\Config\ConfigFactoryInterface;
|
Chris@0
|
7 use Drupal\Core\StringTranslation\StringTranslationTrait;
|
Chris@0
|
8 use Drupal\Core\Utility\Error;
|
Chris@0
|
9 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
Chris@0
|
10 use Symfony\Component\HttpFoundation\Response;
|
Chris@0
|
11 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
Chris@0
|
12 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
Chris@0
|
13 use Symfony\Component\HttpKernel\KernelEvents;
|
Chris@0
|
14
|
Chris@0
|
15 /**
|
Chris@0
|
16 * Last-chance handler for exceptions: the final exception subscriber.
|
Chris@0
|
17 *
|
Chris@0
|
18 * This handler will catch any exceptions not caught elsewhere and report
|
Chris@0
|
19 * them as an error page.
|
Chris@0
|
20 *
|
Chris@0
|
21 * Each format has its own way of handling exceptions:
|
Chris@0
|
22 * - html: exception.default_html, exception.custom_page_html and
|
Chris@0
|
23 * exception.fast_404_html
|
Chris@0
|
24 * - json: exception.default_json
|
Chris@0
|
25 *
|
Chris@0
|
26 * And when the serialization module is installed, all serialization formats are
|
Chris@0
|
27 * handled by a single exception subscriber:: serialization.exception.default.
|
Chris@0
|
28 *
|
Chris@0
|
29 * This exception subscriber runs after all the above (it has a lower priority),
|
Chris@0
|
30 * which makes it the last-chance exception handler. It always sends a plain
|
Chris@0
|
31 * text response. If it's a displayable error and the error level is configured
|
Chris@0
|
32 * to be verbose, then a helpful backtrace is also printed.
|
Chris@0
|
33 */
|
Chris@0
|
34 class FinalExceptionSubscriber implements EventSubscriberInterface {
|
Chris@0
|
35 use StringTranslationTrait;
|
Chris@0
|
36
|
Chris@0
|
37 /**
|
Chris@0
|
38 * @var string
|
Chris@0
|
39 *
|
Chris@0
|
40 * One of the error level constants defined in bootstrap.inc.
|
Chris@0
|
41 */
|
Chris@0
|
42 protected $errorLevel;
|
Chris@0
|
43
|
Chris@0
|
44 /**
|
Chris@0
|
45 * The config factory.
|
Chris@0
|
46 *
|
Chris@0
|
47 * @var \Drupal\Core\Config\ConfigFactoryInterface
|
Chris@0
|
48 */
|
Chris@0
|
49 protected $configFactory;
|
Chris@0
|
50
|
Chris@0
|
51 /**
|
Chris@0
|
52 * Constructs a new FinalExceptionSubscriber.
|
Chris@0
|
53 *
|
Chris@0
|
54 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
Chris@0
|
55 * The configuration factory.
|
Chris@0
|
56 */
|
Chris@0
|
57 public function __construct(ConfigFactoryInterface $config_factory) {
|
Chris@0
|
58 $this->configFactory = $config_factory;
|
Chris@0
|
59 }
|
Chris@0
|
60
|
Chris@0
|
61 /**
|
Chris@0
|
62 * Gets the configured error level.
|
Chris@0
|
63 *
|
Chris@0
|
64 * @return string
|
Chris@0
|
65 */
|
Chris@0
|
66 protected function getErrorLevel() {
|
Chris@0
|
67 if (!isset($this->errorLevel)) {
|
Chris@0
|
68 $this->errorLevel = $this->configFactory->get('system.logging')->get('error_level');
|
Chris@0
|
69 }
|
Chris@0
|
70 return $this->errorLevel;
|
Chris@0
|
71 }
|
Chris@0
|
72
|
Chris@0
|
73 /**
|
Chris@0
|
74 * Handles exceptions for this subscriber.
|
Chris@0
|
75 *
|
Chris@0
|
76 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
|
Chris@0
|
77 * The event to process.
|
Chris@0
|
78 */
|
Chris@0
|
79 public function onException(GetResponseForExceptionEvent $event) {
|
Chris@0
|
80 $exception = $event->getException();
|
Chris@0
|
81 $error = Error::decodeException($exception);
|
Chris@0
|
82
|
Chris@0
|
83 // Display the message if the current error reporting level allows this type
|
Chris@0
|
84 // of message to be displayed, and unconditionally in update.php.
|
Chris@0
|
85 $message = '';
|
Chris@0
|
86 if ($this->isErrorDisplayable($error)) {
|
Chris@0
|
87 // If error type is 'User notice' then treat it as debug information
|
Chris@0
|
88 // instead of an error message.
|
Chris@0
|
89 // @see debug()
|
Chris@0
|
90 if ($error['%type'] == 'User notice') {
|
Chris@0
|
91 $error['%type'] = 'Debug';
|
Chris@0
|
92 }
|
Chris@0
|
93
|
Chris@0
|
94 $error = $this->simplifyFileInError($error);
|
Chris@0
|
95
|
Chris@0
|
96 unset($error['backtrace']);
|
Chris@0
|
97
|
Chris@0
|
98 if (!$this->isErrorLevelVerbose()) {
|
Chris@0
|
99 // Without verbose logging, use a simple message.
|
Chris@0
|
100
|
Chris@17
|
101 // We use \Drupal\Component\Render\FormattableMarkup directly here,
|
Chris@17
|
102 // rather than use t() since we are in the middle of error handling, and
|
Chris@17
|
103 // we don't want t() to cause further errors.
|
Chris@17
|
104 $message = new FormattableMarkup('%type: @message in %function (line %line of %file).', $error);
|
Chris@0
|
105 }
|
Chris@0
|
106 else {
|
Chris@0
|
107 // With verbose logging, we will also include a backtrace.
|
Chris@0
|
108
|
Chris@0
|
109 $backtrace_exception = $exception;
|
Chris@0
|
110 while ($backtrace_exception->getPrevious()) {
|
Chris@0
|
111 $backtrace_exception = $backtrace_exception->getPrevious();
|
Chris@0
|
112 }
|
Chris@0
|
113 $backtrace = $backtrace_exception->getTrace();
|
Chris@0
|
114 // First trace is the error itself, already contained in the message.
|
Chris@0
|
115 // While the second trace is the error source and also contained in the
|
Chris@0
|
116 // message, the message doesn't contain argument values, so we output it
|
Chris@0
|
117 // once more in the backtrace.
|
Chris@0
|
118 array_shift($backtrace);
|
Chris@0
|
119
|
Chris@0
|
120 // Generate a backtrace containing only scalar argument values.
|
Chris@0
|
121 $error['@backtrace'] = Error::formatBacktrace($backtrace);
|
Chris@17
|
122 $message = new FormattableMarkup('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $error);
|
Chris@0
|
123 }
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 $content = $this->t('The website encountered an unexpected error. Please try again later.');
|
Chris@0
|
127 $content .= $message ? '</br></br>' . $message : '';
|
Chris@0
|
128 $response = new Response($content, 500, ['Content-Type' => 'text/plain']);
|
Chris@0
|
129
|
Chris@0
|
130 if ($exception instanceof HttpExceptionInterface) {
|
Chris@0
|
131 $response->setStatusCode($exception->getStatusCode());
|
Chris@0
|
132 $response->headers->add($exception->getHeaders());
|
Chris@0
|
133 }
|
Chris@0
|
134 else {
|
Chris@0
|
135 $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR, '500 Service unavailable (with message)');
|
Chris@0
|
136 }
|
Chris@0
|
137
|
Chris@0
|
138 $event->setResponse($response);
|
Chris@0
|
139 }
|
Chris@0
|
140
|
Chris@0
|
141 /**
|
Chris@0
|
142 * {@inheritdoc}
|
Chris@0
|
143 */
|
Chris@0
|
144 public static function getSubscribedEvents() {
|
Chris@0
|
145 // Run as the final (very late) KernelEvents::EXCEPTION subscriber.
|
Chris@0
|
146 $events[KernelEvents::EXCEPTION][] = ['onException', -256];
|
Chris@0
|
147 return $events;
|
Chris@0
|
148 }
|
Chris@0
|
149
|
Chris@0
|
150 /**
|
Chris@0
|
151 * Checks whether the error level is verbose or not.
|
Chris@0
|
152 *
|
Chris@0
|
153 * @return bool
|
Chris@0
|
154 */
|
Chris@0
|
155 protected function isErrorLevelVerbose() {
|
Chris@0
|
156 return $this->getErrorLevel() === ERROR_REPORTING_DISPLAY_VERBOSE;
|
Chris@0
|
157 }
|
Chris@0
|
158
|
Chris@0
|
159 /**
|
Chris@0
|
160 * Wrapper for error_displayable().
|
Chris@0
|
161 *
|
Chris@0
|
162 * @param $error
|
Chris@0
|
163 * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
|
Chris@0
|
164 *
|
Chris@0
|
165 * @return bool
|
Chris@0
|
166 *
|
Chris@0
|
167 * @see \error_displayable
|
Chris@0
|
168 */
|
Chris@0
|
169 protected function isErrorDisplayable($error) {
|
Chris@0
|
170 return error_displayable($error);
|
Chris@0
|
171 }
|
Chris@0
|
172
|
Chris@0
|
173 /**
|
Chris@0
|
174 * Attempts to reduce error verbosity in the error message's file path.
|
Chris@0
|
175 *
|
Chris@0
|
176 * Attempts to reduce verbosity by removing DRUPAL_ROOT from the file path in
|
Chris@0
|
177 * the message. This does not happen for (false) security.
|
Chris@0
|
178 *
|
Chris@0
|
179 * @param $error
|
Chris@0
|
180 * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
|
Chris@0
|
181 *
|
Chris@0
|
182 * @return
|
Chris@0
|
183 * The updated $error.
|
Chris@0
|
184 */
|
Chris@0
|
185 protected function simplifyFileInError($error) {
|
Chris@0
|
186 // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
|
Chris@0
|
187 // in the message. This does not happen for (false) security.
|
Chris@0
|
188 $root_length = strlen(DRUPAL_ROOT);
|
Chris@0
|
189 if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) {
|
Chris@0
|
190 $error['%file'] = substr($error['%file'], $root_length + 1);
|
Chris@0
|
191 }
|
Chris@0
|
192 return $error;
|
Chris@0
|
193 }
|
Chris@0
|
194
|
Chris@0
|
195 }
|