1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: namespace Cake\Error;
16:
17: use Cake\Controller\Controller;
18: use Cake\Core\App;
19: use Cake\Core\Configure;
20: use Cake\Core\Exception\Exception as CakeException;
21: use Cake\Core\Exception\MissingPluginException;
22: use Cake\Event\Event;
23: use Cake\Http\Exception\HttpException;
24: use Cake\Http\Response;
25: use Cake\Http\ServerRequest;
26: use Cake\Http\ServerRequestFactory;
27: use Cake\Routing\DispatcherFactory;
28: use Cake\Routing\Router;
29: use Cake\Utility\Inflector;
30: use Cake\View\Exception\MissingTemplateException;
31: use Exception;
32: use PDOException;
33:
34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50:
51: class ExceptionRenderer implements ExceptionRendererInterface
52: {
53: 54: 55: 56: 57:
58: protected $error;
59:
60: 61: 62: 63: 64:
65: protected $controller;
66:
67: 68: 69: 70: 71:
72: protected $template = '';
73:
74: 75: 76: 77: 78:
79: protected $method = '';
80:
81: 82: 83: 84: 85: 86:
87: protected $request = null;
88:
89: 90: 91: 92: 93: 94: 95: 96:
97: public function __construct(Exception $exception, ServerRequest $request = null)
98: {
99: $this->error = $exception;
100: $this->request = $request;
101: $this->controller = $this->_getController();
102: }
103:
104: 105: 106: 107: 108: 109: 110:
111: protected function _unwrap($exception)
112: {
113: return $exception instanceof PHP7ErrorException ? $exception->getError() : $exception;
114: }
115:
116: 117: 118: 119: 120: 121: 122: 123: 124:
125: protected function _getController()
126: {
127: $request = $this->request;
128: $routerRequest = Router::getRequest(true);
129:
130:
131: if ($request === null) {
132: $request = $routerRequest ?: ServerRequestFactory::fromGlobals();
133: }
134:
135:
136:
137: if ($request->getParam('controller') === false && $routerRequest !== null) {
138: $request = $request->withAttribute('params', $routerRequest->getAttribute('params'));
139: }
140:
141: $response = new Response();
142: $controller = null;
143:
144: try {
145: $namespace = 'Controller';
146: $prefix = $request->getParam('prefix');
147: if ($prefix) {
148: if (strpos($prefix, '/') === false) {
149: $namespace .= '/' . Inflector::camelize($prefix);
150: } else {
151: $prefixes = array_map(
152: 'Cake\Utility\Inflector::camelize',
153: explode('/', $prefix)
154: );
155: $namespace .= '/' . implode('/', $prefixes);
156: }
157: }
158:
159: $class = App::className('Error', $namespace, 'Controller');
160: if (!$class && $namespace !== 'Controller') {
161: $class = App::className('Error', 'Controller', 'Controller');
162: }
163:
164:
165: $controller = new $class($request, $response);
166: $controller->startupProcess();
167: $startup = true;
168: } catch (Exception $e) {
169: $startup = false;
170: }
171:
172:
173:
174:
175: if ($startup === false && !empty($controller) && isset($controller->RequestHandler)) {
176: try {
177: $event = new Event('Controller.startup', $controller);
178: $controller->RequestHandler->startup($event);
179: } catch (Exception $e) {
180: }
181: }
182: if (empty($controller)) {
183: $controller = new Controller($request, $response);
184: }
185:
186: return $controller;
187: }
188:
189: 190: 191: 192: 193:
194: public function render()
195: {
196: $exception = $this->error;
197: $code = $this->_code($exception);
198: $method = $this->_method($exception);
199: $template = $this->_template($exception, $method, $code);
200: $unwrapped = $this->_unwrap($exception);
201:
202: if (method_exists($this, $method)) {
203: return $this->_customMethod($method, $unwrapped);
204: }
205:
206: $message = $this->_message($exception, $code);
207: $url = $this->controller->getRequest()->getRequestTarget();
208: $response = $this->controller->getResponse();
209:
210: if ($exception instanceof CakeException) {
211: foreach ((array)$exception->responseHeader() as $key => $value) {
212: $response = $response->withHeader($key, $value);
213: }
214: }
215: $response = $response->withStatus($code);
216:
217: $viewVars = [
218: 'message' => $message,
219: 'url' => h($url),
220: 'error' => $unwrapped,
221: 'code' => $code,
222: '_serialize' => ['message', 'url', 'code']
223: ];
224:
225: $isDebug = Configure::read('debug');
226: if ($isDebug) {
227: $viewVars['trace'] = Debugger::formatTrace($unwrapped->getTrace(), [
228: 'format' => 'array',
229: 'args' => false
230: ]);
231: $viewVars['file'] = $exception->getFile() ?: 'null';
232: $viewVars['line'] = $exception->getLine() ?: 'null';
233: $viewVars['_serialize'][] = 'file';
234: $viewVars['_serialize'][] = 'line';
235: }
236: $this->controller->set($viewVars);
237:
238: if ($unwrapped instanceof CakeException && $isDebug) {
239: $this->controller->set($unwrapped->getAttributes());
240: }
241: $this->controller->response = $response;
242:
243: return $this->_outputMessage($template);
244: }
245:
246: 247: 248: 249: 250: 251: 252:
253: protected function _customMethod($method, $exception)
254: {
255: $result = call_user_func([$this, $method], $exception);
256: $this->_shutdown();
257: if (is_string($result)) {
258: $result = $this->controller->response->withStringBody($result);
259: }
260:
261: return $result;
262: }
263:
264: 265: 266: 267: 268: 269:
270: protected function _method(Exception $exception)
271: {
272: $exception = $this->_unwrap($exception);
273: list(, $baseClass) = namespaceSplit(get_class($exception));
274:
275: if (substr($baseClass, -9) === 'Exception') {
276: $baseClass = substr($baseClass, 0, -9);
277: }
278:
279: $method = Inflector::variable($baseClass) ?: 'error500';
280:
281: return $this->method = $method;
282: }
283:
284: 285: 286: 287: 288: 289: 290:
291: protected function _message(Exception $exception, $code)
292: {
293: $exception = $this->_unwrap($exception);
294: $message = $exception->getMessage();
295:
296: if (!Configure::read('debug') &&
297: !($exception instanceof HttpException)
298: ) {
299: if ($code < 500) {
300: $message = __d('cake', 'Not Found');
301: } else {
302: $message = __d('cake', 'An Internal Error Has Occurred.');
303: }
304: }
305:
306: return $message;
307: }
308:
309: 310: 311: 312: 313: 314: 315: 316:
317: protected function _template(Exception $exception, $method, $code)
318: {
319: $exception = $this->_unwrap($exception);
320: $isHttpException = $exception instanceof HttpException;
321:
322: if (!Configure::read('debug') && !$isHttpException || $isHttpException) {
323: $template = 'error500';
324: if ($code < 500) {
325: $template = 'error400';
326: }
327:
328: return $this->template = $template;
329: }
330:
331: $template = $method ?: 'error500';
332:
333: if ($exception instanceof PDOException) {
334: $template = 'pdo_error';
335: }
336:
337: return $this->template = $template;
338: }
339:
340: 341: 342: 343: 344: 345:
346: protected function _code(Exception $exception)
347: {
348: $code = 500;
349:
350: $exception = $this->_unwrap($exception);
351: $errorCode = $exception->getCode();
352: if ($errorCode >= 400 && $errorCode < 600) {
353: $code = $errorCode;
354: }
355:
356: return $code;
357: }
358:
359: 360: 361: 362: 363: 364:
365: protected function _outputMessage($template)
366: {
367: try {
368: $this->controller->render($template);
369:
370: return $this->_shutdown();
371: } catch (MissingTemplateException $e) {
372: $attributes = $e->getAttributes();
373: if (isset($attributes['file']) && strpos($attributes['file'], 'error500') !== false) {
374: return $this->_outputMessageSafe('error500');
375: }
376:
377: return $this->_outputMessage('error500');
378: } catch (MissingPluginException $e) {
379: $attributes = $e->getAttributes();
380: if (isset($attributes['plugin']) && $attributes['plugin'] === $this->controller->getPlugin()) {
381: $this->controller->setPlugin(null);
382: }
383:
384: return $this->_outputMessageSafe('error500');
385: } catch (Exception $e) {
386: return $this->_outputMessageSafe('error500');
387: }
388: }
389:
390: 391: 392: 393: 394: 395: 396:
397: protected function _outputMessageSafe($template)
398: {
399: $helpers = ['Form', 'Html'];
400: $this->controller->helpers = $helpers;
401: $builder = $this->controller->viewBuilder();
402: $builder->setHelpers($helpers, false)
403: ->setLayoutPath('')
404: ->setTemplatePath('Error');
405: $view = $this->controller->createView('View');
406:
407: $this->controller->response = $this->controller->response
408: ->withType('html')
409: ->withStringBody($view->render($template, 'error'));
410:
411: return $this->controller->response;
412: }
413:
414: 415: 416: 417: 418: 419: 420:
421: protected function _shutdown()
422: {
423: $this->controller->dispatchEvent('Controller.shutdown');
424: $dispatcher = DispatcherFactory::create();
425: $eventManager = $dispatcher->getEventManager();
426: foreach ($dispatcher->filters() as $filter) {
427: $eventManager->on($filter);
428: }
429: $args = [
430: 'request' => $this->controller->request,
431: 'response' => $this->controller->response
432: ];
433: $result = $dispatcher->dispatchEvent('Dispatcher.afterDispatch', $args);
434:
435: return $result->getData('response');
436: }
437:
438: 439: 440: 441: 442: 443:
444: public function __get($name)
445: {
446: $protected = [
447: 'error',
448: 'controller',
449: 'template',
450: 'method',
451: ];
452: if (in_array($name, $protected, true)) {
453: deprecationWarning(sprintf(
454: 'ExceptionRenderer::$%s is now protected and should no longer be accessed in public context.',
455: $name
456: ));
457: }
458:
459: return $this->{$name};
460: }
461:
462: 463: 464: 465: 466: 467: 468:
469: public function __set($name, $value)
470: {
471: $protected = [
472: 'error',
473: 'controller',
474: 'template',
475: 'method',
476: ];
477: if (in_array($name, $protected, true)) {
478: deprecationWarning(sprintf(
479: 'ExceptionRenderer::$%s is now protected and should no longer be accessed in public context.',
480: $name
481: ));
482: }
483:
484: $this->{$name} = $value;
485: }
486:
487: 488: 489: 490: 491: 492:
493: public function __debugInfo()
494: {
495: return [
496: 'error' => $this->error,
497: 'request' => $this->request,
498: 'controller' => $this->controller,
499: 'template' => $this->template,
500: 'method' => $this->method,
501: ];
502: }
503: }
504: