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