Cakephp - how to make error pages have its own layouts? - cakephp

I wanna have a different layout for the page not found 404 page. How can i set a different layout for that page?

Savant from the IRC helped me out and he suggest in using beforeRender(){} in the app_controller
// Before Render
function beforeRender() {
if($this->name == 'CakeError') {
//$this->layout = 'error';
}
}
CakeError is a catchAll for errors :D

In CakePHP 2.2.2 I changed the ExceptionRenderer in core.php with my own, like this:
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer', // this is ExceptionRenderer by default
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer {
protected function _outputMessage($template) {
$this->controller->layout = 'error';
parent::_outputMessage($template);
}
}

Just you need to make layout changes in your error400.ctp file under /app/View/Errors/error400.ctp
Open that file and set layout by
<?php $this->layout=''; //set your layout here ?>

better to create an error.php file in your app folder
class AppError extends ErrorHandler {
function error404($params) {
$this->controller->layout = 'error';
parent::error404($params);
}
}
so you can avoid the if-testing at EVERY page render that savants' solution introduces

My solution for CakePHP 2.3
Change the ExceptionRenderer in core.php to use your own renderer.
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer',
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer
{
/**
* Overrided, to always use a bare controller.
*
* #param Exception $exception The exception to get a controller for.
* #return Controller
*/
protected function _getController($exception) {
if (!$request = Router::getRequest(true)) {
$request = new CakeRequest();
}
$response = new CakeResponse(array('charset' => Configure::read('App.encoding')));
$controller = new Controller($request, $response);
$controller->viewPath = 'Errors';
$controller->layout = 'error';
return $controller;
}
}
The advantage to this approach is that it ensures any exceptions thrown from AppController don't cause an endless loop when rendering the exception. Forces a basic rendering of the exception message every time.

This simplest way I know of is to create this function in your AppController:
function appError($method, $messages)
{
}
You can then do whatever you want with the error, display it however you like, or not display it at all, send an email etc.. (I'm not sure if this method if still valid.)
There is also an option of creating app_error.php in your app root, with class AppError extends ErrorHandler in it, which enables you to override all kinds of errors. But I haven't done this yet, so I can't tell you more about it.
See cake/libs/error.php and cake/libs/object.php and of course The Book for more info.
Edit: Forgot to mention, once you caught the error, there's nothing preventing you to - for example - store the error in session, redirect to your "error handling controller", and then display it in your controller however you want.

Related

CakePHP 3.5 redirect missing controller exception

I am struggeling with the missing controller exception thrown by CakePHP.
Once an unknown controller is called, it should redirect on my login page.
First I tried to route to my standart login page once there is an controller/action unknown.
//default routing
$routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']);
$routes->connect('/:controller/:action/*', [], ['routeClass' => 'InflectedRoute']);
$routes->connect('/', ['controller' => 'Users', 'action' => 'login']);
//redirect if controller is not known
$routes->connect('/*', ['controller' => 'Users', 'action' => 'login']);
However, this did not work at all, so I googled alot and it turned out that you should catch an the missing controller excpetion instead of rerouting. I can't find out where the error can be caught and it's very little written about it.
Did anyone already have something to do with it?
Short answer
As far as I know catching the MissinControllerException can only be done by customizing the Error Handling flow in Cakephp
Longer answer
To achieve the result you want to need to do the following.
If you are using Middleware in your Application class you need to disable the Error Handling middleware by commenting out this line:
->add(ErrorHandlerMiddleware::class)
Next you need to overwrite the default CakePHP Error handler with a custom one. So in src\Error create the file AppError.php with contents similar to this:
class AppError extends ErrorHandler
{
public function _displayException($exception)
{
if($exception instanceof MissingControllerException){
$response = new Response();
$response = $response->withLocation(Router::url(
['controller'=>'Users', 'action'=>'login'])
);
$emitter = new ResponseEmitter();
$emitter->emit($response);
}else{
parent::_displayException($exception);
}
}
}
Finally, in your bootstrap.php file you need to register your new Error handler. As per docs something like this needs to be added:
$errorHandler = new \App\Error\AppError();
$errorHandler->register();
Potential problems
Doing this redirect will hide away all of your missing controller exceptions. This may cause you trouble in case of typo's in the URL, since you will no longer get a clear error, but instead be redirected to the login page. The error.log file should still show you the original exception.
Good luck!
Potential Problems 2 - CakePHP >= 3.6.x
As pointed out by ndm in the comments disabling the Error Handling Middleware is not always a good idea, especially for CakePHP 3.6. In this case a better solution is to extend the ErrorHandling Middleware and register that.
You could try to handle this exception using custom Exception Renderer:
In src/Error create a new file, named eg MyExceptionRenderer.php, and handle missing controller exception there:
namespace App\Error;
use Cake\Error\ExceptionRenderer;
class MyExceptionRenderer extends ExceptionRenderer
{
public function missingController($error)
{
return $this->controller->redirect("/");
}
}
You will need also to enable this custom renderer in config/app.php:
'Error' => [
'errorLevel' => E_ALL,
'exceptionRenderer' => 'App\Error\MyExceptionRenderer',
'skipLog' => [],
'log' => true,
'trace' => true,
],
With this, when MissingControllerException will be raised, user will be redirected, in this case to main page.
More info can be found here:Error Handling

CakePHP error log - can I exclude 404 errors?

The error log of my CakePHP app is full of 404 errors. Can I exclude these MissingControllerExceptions from appearing in the error log? Using Cake 2.3.
versions above 2.4 (as mentioned by null) have an undocumented feature for this purpose.
just modify your exception config:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'ExceptionRenderer',
'log' => true,
'skipLog'=>array(
'MissingControllerException'
)
));
skipLog takes names of exception classes to be excluded from log.
Simply redirecting or removing these URLs is not going to cut it.
A busy site will get hit by hundreds of "random" 404s every day, most from far east countries checking for exploits or URLs such as "/wp-admin".
Logging these with a full stack trace is completely unnecessary
Solution
You can override the default error handler in CakePHP, and log to app/tmp/logs/404.log instead.
In your app/Config/core.php file, define the class you want to handle exceptions:
Configure::write('Error', array(
'handler' => 'MyCustomErrorHandler::handleError',
'level' => E_ALL & ~E_DEPRECATED,
'trace' => true
));
Create the class within app/Lib/Error and include using App::uses in your app/Config/bootstrap.php file:
App::uses('MyCustomErrorHandler', 'Lib/Error');
Make an exact copy of the original ErrorHandler class, just change the class name, and somewhere within the handleException method check which exception is being thrown, and log somewhere else. It will look a bit like this;
App::uses('ErrorHandler', 'Error');
class MyCustomErrorHandler {
public static function handleException(Exception $exception) {
// some code...
if (in_array(get_class($exception), array('MissingControllerException', 'MissingActionException', 'PrivateActionException', 'NotFoundException'))) {
$log = '404';
$message = sprintf("[%s]", get_class($exception));
}
// more code...
}
}
Building on robmcvey's solution, here is what I had to do for CakePHP 2.6.
Configuration
In app/Config/core.php update the Exception configuration:
Configure::write('Exception', array(
'handler' => 'AppErrorHandler::handleException',
'renderer' => 'ExceptionRenderer',
'log' => true
));
In app/Config/bootstrap.php add a CakeLog configuration:
CakeLog::config('not_found', array(
'engine' => 'FileLog',
'types' => array('404'),
'file' => '404',
));
The type of 404 is the logging level it will listen in on for anything being written to logs e.g. CakeLog::write('404', 'That was not found.');
Extend ErrorHandler
Create the file app/Lib/Error/AppErrorHandler.php. Here we will extend Cake's ErrorHandler, overwritting three methods; handleException(), _getMessage(), and _log().
<?php
class AppErrorHandler extends ErrorHandler {
/**
* List of Cake Exception classes to record to specified log level.
*
* #var array
*/
protected static $_exceptionClasses = array(
'MissingControllerException' => '404',
'MissingActionException' => '404',
'PrivateActionException' => '404',
'NotFoundException' => '404'
);
public static function handleException(Exception $exception) {
$config = Configure::read('Exception');
self::_log($exception, $config);
$renderer = isset($config['renderer']) ? $config['renderer'] : 'ExceptionRenderer';
if ($renderer !== 'ExceptionRenderer') {
list($plugin, $renderer) = pluginSplit($renderer, true);
App::uses($renderer, $plugin . 'Error');
}
try {
$error = new $renderer($exception);
$error->render();
} catch (Exception $e) {
set_error_handler(Configure::read('Error.handler')); // Should be using configured ErrorHandler
Configure::write('Error.trace', false); // trace is useless here since it's internal
$message = sprintf("[%s] %s\n%s", // Keeping same message format
get_class($e),
$e->getMessage(),
$e->getTraceAsString()
);
self::$_bailExceptionRendering = true;
trigger_error($message, E_USER_ERROR);
}
}
/**
* Generates a formatted error message
*
* #param Exception $exception Exception instance
* #return string Formatted message
*/
protected static function _getMessage($exception) {
$message = '';
if (php_sapi_name() !== 'cli') {
$request = Router::getRequest();
if ($request) {
$message .= $request->here() . " Not Found";
}
}
$message .= "\nStack Trace:\n" . $exception->getTraceAsString() . "\n";
return $message;
}
/**
* Handles exception logging
*
* #param Exception $exception The exception to render.
* #param array $config An array of configuration for logging.
* #return bool
*/
protected static function _log(Exception $exception, $config) {
if (!empty(self::$_exceptionClasses)) {
foreach ((array)self::$_exceptionClasses as $class => $level) {
if ($exception instanceof $class) {
return CakeLog::write($level, self::_getMessage($exception));
}
}
}
return parent::_log();
}
}
You can customize the $_exceptionClasses array to catch which exceptions you want and send them to different logs. The _getMessage() method has been simplified to remove the attributes.
Result
Random URLs like /exploitable-plugin will now log to tmp/logs/404.log.
2015-04-01 16:37:54 404: /exploitable-plugin Not Found
Stack Trace:
#0 /var/example.com/app/index.php(146): Dispatcher->dispatch(Object(CakeRequest), Object(CakeResponse))
#1 {main}
I assume that your viewers hit those pages from results of search engines. Since you're a webmaster of this page, you could try to use something like Google removal tools https://www.google.com/webmasters/tools/removals . So there won't be any more liks to these pages. It is a little costly in time, but I don't see a way to exclude missing controller actions form logging.
Anyway here you have everything on logging in CakePHP: https://www.google.com/webmasters/tools/removals?pli=1
Edit:
Actually I'd found a way, but it might make you trouble in the future if you forgot about it. Add this to the end of bootstrap.php :
$matches = array();
preg_match('/^\/(.+?)\//', $_SERVER['REQUEST_URI'],$matches);
if(!App::import('Controller', $matches[1])){
die('404 Error![some_random_chars_so_you_can_easy_find_it_in_the_future]') ;
}

Cake: Access the controller in a static method in a component

we have a static method in a Cake component. The task was to redirect the user to the login page, if this component throws a specific error. The current (working) solution is:
class SomeComponent extends Component {
static $controllerObject;
function startup(&$controller) {
SomeComponent::$controllerObject =& $controller;
}
(...)
public static function decodeResponse($response) {
if($response == null || trim($response) == '') {
return null;
}
$result = json_decode($response, true);
if($result == null) {
throw new FatalErrorException('Could not parse api server response: ' . $response);
}
if(isset($result['error'])) {
if ($result['error'] === "IncorrectCredentialsException") {
self::$controllerObject->Session->destroy();
self::$controllerObject->Session->setFlash(__('Your session has ended. Please log in again.', true), 'default', array(), 'error');
self::$controllerObject->redirect(array(
'language' => Configure::read('Config.language'),
'controller' => 'users',
'action' => 'login'
));
}
else { throw new ApiServerException($result); }
}
return $result;
}
However, my team colleague, who is responsible for the software quality, doesn't find this solution satisfying. He says: "pls find a better way to pass the controller to the decode method. Setting the controller as static variable is not the best way".
Is there any better way to do this?
I think the problem is that your method does two different things: decoding and error handling. Instead of handling the IncorrectCredentialsException inside your method, I would move this functionality to where you handle the other exceptions and just throw an IncorrectCredentialsException in your method. With this change you no longer need to access the controller in your static method.

Set page title when handling errors in CakePHP

I need to change page title from default "Error" when handling errors like 404. So I need to put my the title in the variable $title_for_layout for my Layout. I tried to create custom error handling function by changing configuration in app/Config/core.php and setting the page title as in controllers
Configure::write('Error.handler', function($code, $description, $file = null, $line = null, $context = null) {
$this->set('title_for_layout', 'Vyskytla sa chyba');
});
As I expected, I got a PHP error (line 59 is the second line in the code sample)
Fatal error: Using $this when not in object context in /var/www/web/app/Config/core.php on line 59
So how I can set the title for my default.ctp layout?
Thanks.
In CakePHP 2.0, you can try the following code to achieve the same you needed.
Try this:
/app/Config/core.php
Exception render need to set as an AppExceptionRender. Example:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'AppExceptionRenderer',
'log' => true
));
/app/Controller/ErrorsController.php
class ErrorsController extends AppController {
public $name = 'Errors';
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('error404');
}
public function error404() {
//$this->layout = 'default';
$this->set('title_for_layout', 'Vyskytla sa chyba');
}
}
/app/Lib/Error/AppExceptionRenderer.php
App::uses('ExceptionRenderer', 'Error');
class AppExceptionRenderer extends ExceptionRenderer {
public function notFound($error) {
$this->controller->redirect(array('controller' => 'errors', 'action' => 'error404'));
}
}
/app/View/Errors/error404.ctp
<div class="inner404">
<h2>404 Error - Page Not Found</h2>
</div>
Insert it where you need: throw new NotFoundException();
Ref: CakePHP 2.0 - How to make custom error pages?
For < CakePHP 2.x:
If you create a custome error page view in app/views/errors then in a
php section on that error view page you can use:
$this->setLayout("Title for the error page here");
Then when you see the error page, it will have your title. Again, that
is if you set a custom error page.
Here is another way to do the same you needed.
// Create an error.php file in your /app folder with the following code:
<?php
class AppError extends ErrorHandler {
function error404($params) {
$this->controller->layout = "error";
$this->set('title_for_layout', 'Vyskytla sa chyba');
parent::error404($params);
}
}
?>

CakePHP: Prevent Auth component's "authError" message on homepage

I have a CakePHP project where I modified "app/config/routes.php" so that the root points to the "Users" controller's "dashboard" action. In other words, these two URLs go to the same place:
http://example.com/
http://example.com/users/dashboard
I have the "Auth" component set up in my "App" controller like so:
class AppController extends Controller {
var $components = array('Auth', 'Session');
function beforeFilter() {
$this->Auth->authorize = 'controller';
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'dashboard');
if ($this->Auth->user()) {
$this->set('logged_in', true);
}
else {
$this->set('logged_in', false);
}
}
}
I want it so that if a non-authenticated user goes straight to http://example.com/users/dashboard , they are taken to the login page with the "Auth" component's "authError" message showing, but if they go to http://example.com/ , they are taken to the login page without the "Auth" component's "authError" message showing. Is this possible?
I resolved this by putting the following code in my "Users" controller's "login" action:
if ($this->Session->read('Auth.redirect') == $this->webroot && $this->Session->read('Message.auth.message') == $this->Auth->authError) {
$this->Session->delete('Message.auth');
}
been looking for somthing like that for a long time! Thank you.
I had to make a little change then $this->webroot is not "/":
if (str_replace("//","/",$this->webroot.$this->Session->read('Auth.redirect')) == $this->webroot && $this->Session->read('Message.auth.message') == $this->Auth->authError) {
$this->Session->delete('Message.auth');
}
Well, I don't understand why sometimes you show the error and why sometimes not.. but you can afford this creating an isAuthorized method and modifying all the logic of the default AuthComponent behavior.
Open your Auth component and check for method "startup()". There, at it's last line, you will se this:
$this->Session->setFlash($this->authError, $this->flashElement, array(), 'auth');
$controller->redirect($controller->referer(), null, true);
This is the part responsible for displaying the error.
Before it, you will se...
if ($this->isAuthorized($type)) {
return true;
}
So you can change your isAuthorized method to change this message when you want.
Is a lot of work for (I think..) nothing.
PS. There may be a simpler way to be ignoring me
If you really wants to prevent authError message on homepage and simple redirect to login page then you have to put false as parameter of authError
class AppController extends Controller {
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authError' => false
]);
}
}

Resources