In Kohana 2 you can override the default error page by creating a custom kohana_error_page.php file and putting it in your view directory. However, this overrides ALL errors not just 404's. In the case of 500 errors, I'd still like to get the friendly orange kohana error page.
Does anyone have experience in doing this?
You can do this in KO2 quite easily with hooks. If you look at events you'll find system.404 which you'll need to replace using something like:
<?php defined('SYSPATH') or die('No direct script access.');
// Replace the default kohana 404
Event::replace('system.404', array('Kohana', 'show_404'),
array('hook_404', 'show'));
class hook_404 {
public function show()
{
// first param is URI of page, second param is template to use
Kohana::show_404(FALSE, 'custom_404');
}
}
Save this in the hooks directory in your app folder (or in a module). Don't forget to enable hooks in your config:
$config['enable_hooks'] = TRUE;
And add your custom 404 view: views/custom_404.php.
Note: these won't display if you have $config['display_errors'] set to FALSE in your config.php (which it probably should be if you're IN_PRODUCTION right?). For that you need to output something and die eg. replace Kohana::show_404 with the following:
require Kohana::find_file('views', 'custom_404');
die();
I have not tested this!! So backup your code before you do this.
If I'm not mistaken, Kohana2 has hard-coded exception handling and there is no pretty way to add new exceptions. To work around that, you will have to make change in the core.
In the file system/i18n/en_US/errors.php
add new entry:
E_PAGE_ACCESS_DENIED => array( 1, 'Access Denied', ' -- message text-- ')
In the file system/i18n/en_US/core.php
add new entry:
'page_access_denied' => 'You are not permitted to access %s .'
In the system/core/Koahana.php:
near the top of Kohana::setup() method, add new constant:
define('E_PAGE_ACCESS_DENIED', 69);
register the the event for your custom error ( somewhere near the end of same Kohana::setup() you will see registration for 404th error) :
Event::add('system.403', array('Kohana', 'show_403'));
next, find the location for Kohana::show_404() and create you own method:
public static function show_403($page = FALSE, $template = FALSE)
{
throw new Kohana_403_Exception($page, $template);
}
scroll down to the bottom of the file .. there you will find the class definition for the Error_404_Exception ... make one for 403. Don't forget to:
define new variable protected $template = 'file_name_for_template';
protected $code = E_PAGE_ACCESS_DENIED;
Exception::__construct(Kohana::lang('core.page_access_denied', $page));
header('HTTP/1.1 403 Forbidden');
the template file will have to be located at system/views/
Now you should be able to call Event::run('system.403'); from anywhere in the application.
Related
I'm new to OOP and MVC with PHP, and I'm currently learning by making my own custom MVC app for testing purposes. I’m not using Symfony yet, but I’ve integrated Twig and I have a problem with it when I call a 404 error outside a controller, in the two following cases :
When requested page doesn't exist on server (like http://localhost/test/)
When requested URL doesn't match with an existing controller and method (like http://localhost/test/task/)
I've got the following error :
Fatal error: Uncaught Error: Class 'Twig\Loader\FilesystemLoader' not found in /Applications/XAMPP/xamppfiles/htdocs/librairies/Renderer.php:32
Stack trace: #0 /Applications/XAMPP/xamppfiles/htdocs/librairies/Renderer.php(21): Renderer::loadTwig()
#1 /Applications/XAMPP/xamppfiles/htdocs/librairies/Http.php(26): Renderer::render('errors/404', Array)
#2 /Applications/XAMPP/xamppfiles/htdocs/librairies/Application.php(35): Http::error404()
#3 /Applications/XAMPP/xamppfiles/htdocs/index.php(9): Application::process()
#4 {main} thrown in /Applications/XAMPP/xamppfiles/htdocs/librairies/Renderer.php on line 32
However, if I call the right controller with an existing method, everything works fine (like http://localhost/article/show/123).
If I call 404 error inside a controller method (e.g. if the $_GET ID of an article does not exist in the DB), everything works fine too and my 404 error template is correctly rendered.
How I call a 404 error ?
I use a static method Http::error404() that render my 404 error Twig template.
class Http
{
/**
* Display a 404 error - page not found
*
* #return void
*/
public static function error404(): void
{
header('HTTP/1.1 404 Not Found');
$pageTitle = "Erreur 404 - Page non-trouvée";
Renderer::render('errors/404', compact('pageTitle'));
exit;
}
}
Application class
My App use a mini-router named Application. It checks if the controller and the called method exists. If not, call 404 error with Http::error404(), and it is in this case that the above fatal error appears.
class Application
{
/**
* This is the app process, called in index.php file.
* Use controllers and methods directly in URL.
* URL are rewritten by .htaccess file at app root.
*
* Usage : https://example.com/controller/task/{optional_parameter}
* Ex : https://example.com/article/show/145
*
* #return void
*/
public static function process()
{
// By default, call homepage
$controllerName = 'ArticleController';
$task = 'home';
if (!empty($_GET['controller'])) {
$controllerName = ucfirst($_GET['controller'] . 'controller');
}
if (!empty($_GET['task'])) {
$task = $_GET['task'];
}
$controllerPath = "\controllers\\" . $controllerName;
// Check if this controller & method exists
if (!method_exists($controllerPath, $task)) {
Http::error404(); // <-- Here, i'm not in a controller (line 35)
}
$controller = new $controllerPath();
$controller->$task();
}
}
There is a strange thing here : if I define any controller before calling 404 error, everything works as I would like and my 404 error template is correctly rendered. But it’s not a clean and elegant solution...
// Check if this controller & method exists
if (!method_exists($controllerPath, $task)) {
new \Controllers\ArticleController(); // <-- Not clean, but solve my fatal error
Http::error404();
}
The same thing happens in my 404.php file, called by the server and declared in the .htaccess when a file is called and it doesn’t exist. My 404 PHP file just contains few lines :
require_once 'librairies/autoload.php';
Http::error404();
If I define any controller, it's working perfectly and fatal error disappears.
require_once 'librairies/autoload.php';
new Controllers\ArticleController();
Http::error404();
Render Class
This is my rendering class. The fatal error appears in this file, as shown in line 32 below, when Http class with error404 method render errors/404 Twig template.
require_once 'librairies/autoload.php';
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFunction;
class Renderer
{
/**
* Print a HTML template with $var injection
*
* #param string $path
* #param array $var
*/
public static function render(string $path, $var = [])
{
$template = self::loadTwig()->load("$path.html.twig");
echo $template->display($var);
}
/**
* Load Twig
*
* #return Environment
*/
public static function loadTwig()
{
$loader = new FilesystemLoader('templates'); // <-- Line 32 is here !
$twig = new Environment($loader, [
'auto_reload' => true,
'autoescape' => 'html'
]);
$twig->addFunction(
new TwigFunction('notificationDisplay', function() { return Notification::display(); })
);
return $twig;
}
}
I have only added the code that I think is related to my issue. Do not hesitate to ask if there are any points that need to be clarified.
I’ve been looking for a solution to this error for seven days and haven’t found a solution. Your help is my last resort. Thank you very much!
I removed all the many unnecessary require_once autoloader in app files, and kept only the one in index.php, as indicated by #NicoHaase.
From that point, Twig is no longer found and the same error message appears on all requests !
The solution was easy to find : in my index.php file, I was using the wrong autoloader. I was calling my custom autoloader instead of the one from Composer...
I just change :
require_once 'librairies/autoload.php';
\Application::process();
To :
require_once 'vendor/autoload.php';
\Application::process();
And everything works fine now ! Thanks to #NicoHaase and #DarkBee for putting me on the right track !
I'm trying to force all of my errors to a single custom page in Cakephp(2.0) but most of the tutorials that I find are for very specific error codes. I want it to show just a single error page so what I first did was to point all of the errors of specific error status to a single error page. But after further testing, I noticed that other codes were not being captured.
I need my code to capture all of the http error codes to a single error page.
This is my code:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'AppExceptionRenderer',
'log' => true
));
And my AppExceptionRenderer:
<?php
App::uses('ExceptionRenderer', 'Error');
class AppExceptionRenderer extends ExceptionRenderer {
public function internalError($error) {
$this->controller->beforeFilter();
$this->controller->set('title_for_layout', 'Internal Server Error');
$this->controller->render('/Errors/error500');
$this->controller->response->send();
... All the other error handlers are similar to this, but very limited since there are a lot of client and server errors
} ?>
My template pages seems to be working just fine, so I did not include it.
How do I alter the code to cover every http status code instead? Or is it not possible?
You have to override the following methods:
ExceptionRenderer::error400($error)
ExceptionRenderer::error500($error)
Alternatively, you can override ExceptionRenderer:_outputMessage($template) with something like:
protected function _outputMessage($template) {
$this->controller->layout = 'my_error';
parent::_outputMessage($template);
}
And add the file /app/View/Layouts/my_error.ctp as a new custom layout.
I'm looking for a way to forward the error handling in my Yii 1.1.14 app. The scenario comes as follows:
Assuming I have two modules: ClientModule at /client/, AdminModule at /admin/. If an url is resolved to belong to any controller in that module, the controller is loaded, and the errorHandler is reassigned to a module-level error handler like this:
public function beforeControllerAction($controller, $action)
{
Yii::app()->errorHandler->errorAction='admin/error';
return parent::beforeControllerAction($controller, $action);
}
In this way, an action exists at 'admin/admin/error' (which in turn is manually specified to be resolved to 'admin/error'), which will handle the error with a boilerplate like:
public function actionError() {
if($error=Yii::app()->errorHandler->error)
{
if(Yii::app()->request->isAjaxRequest)
echo $error['message'];
else
$this->render('application.views.error.index', $error);
}
}
Meanwhile, the analogous code (for both functions) exist in the other module (There's also a ClientModule and a ClientController with an actionError mapped automatically to 'client/client/error', remapped to 'client/error').
I have no trouble at all with these code chunks. My issue starts now:
If I input an url which cannot be resolved, even when the prefix is a module prefix (e.g. 'client/invalid/url' or 'client/client/invalid'), the controller will not be created (since it does not exist - or, as in the second example, the controller exists but not the action), and so beforeController will not be called, and so the custom error handler (in this example: the corresponding to ClientModule which sets $aClientController->actionError) will not be assigned. Result: the default ErrorController handling the unresolved url error.
So, questions:
Is there any way I could map an unresolved url (404) error to certain module, depending on the prefix? (it is safe, in my case, to assume prefixes, since I have not set any module as default).
Alternatively: is there a way to, being in the ErrorController->actionIndex(), forward the error handling to one of those controllers (i.e. moving to admin/admin/error and client/client/error while keeping the Yii::app()->errorHandler->error state)?
Edit - Footnote: Why should I use another controller if the error handling code is the same? Because I have additional data provided by those controllers which is used in the layout (e.g. menu, head menu).
Found the answer, again (my juniority has no limits - and the method name I needed had the exact name: forward!!).
Using the CController->forward('module/controller/action') inside the ErrorController->actionError method I could dispatch everywhere. I also changed another business logic condition and have not the custom error handler assignment in the modules constructors anymore, but I still use those controllers as forward targets of 'error/index' depending on the logged user type (i.e. an error occurred and the user was a client => forward to client/client/error, while being admin led to admin/admin/error, and being no logged user performed the error handling with error/index as usual).
public function actionIndex() {
if (Yii::app()->user->getState('client'))
{
$this->forward('client/client/error');
}
if (Yii::app()->user->getState('staff'))
{
$this->forward('admin/admin/error');
}
//usual boilerplate here
if($error=Yii::app()->errorHandler->error)
{
if(Yii::app()->request->isAjaxRequest)
echo $error['message'];
else
$this->render('index', $error);
}
}
I have a problem in CodeIgniter, and that is that when an image is not found on the server, the instance of a controller is created (besides the one that called the view).
I know all this can sound confusing, so this is the code to observe what I'm saying. I did this changes to a clean 2.1.0 CI version:
Add a controller to override the 404 error page, I added this one:
// add application/controllers/Errors.php
Class Errors extends CI_Controller {
public function error_404() {
echo 'error';
}
}
// change routes.php
$route['404_override'] = 'Errors/error_404';
Use a controller that isn’t the default one with an unexisting image, I used this:
// add application/controllers/Foo.php
Class Foo extends CI_Controller {
public function index() {
echo '<img src="doesntexist.png" />';
}
}
I couldn’t figure out another way of debugging it, so I created a log to write the events on CodeIgniter.php:
// add on CodeIgniter.php line 356
$path = 'log.txt'; //Place log where you can find it
$file = fopen($path, 'a');
fwrite($file, "Calling method {$class}/{$method} with request {$_SERVER['REQUEST_URI']}\r\n");
fclose($file);
With this, the log that generates visiting the index function is the following:
Calling method Foo/index with request /test/index.php/Foo
Calling method Errors/error_404 with request /test/index.php/doesntexist.png
Which is the problem I have, an instance of the Error class is created.
that is that when an image is not found on the server, the instance of a controller is created
Not really. What I believe is happening is that, since you're using a relative path for the image (and calling it directly inside a controller, which is wrong because you're ouputting something before headers), your browser attach the image directly to the CI url, thus making this request to the server:
index.php/doesntexist.png
Which is (correctly) interpreted by CI as a request to a controller, which doesn't exists, and therefore it issues the error class.
You could do, in your actual code (I'd put the images in a view, though):
echo '<img src="/doesntexist.png" />'
using an absoluth path, or using the base_url() method from the url helper:
echo '<img src="'.base_url().'doesntexist.png" />
This should tell the server to fetch the right request (/test/doesntexist.png) and won't trigger that error.
CodeIgniter has /system/application/errors/error_404.php which is displayed when there is a 404 due to what is effectively a "controller not found" condition. However, for my project, I really need this error to be handled just like it is when a method is missing from the controller class. In that case, I show a normal view with a pretty "Page not found: Perhaps you meant this?..." page complete with database-generated navigation etc.
My thoughts are that I could do one of two things:
Create a header("Location: /path/to/error_page") call to redirect to an existing (or special) controller's 404 handler
Add some kind of default router to handle it.
What is the best way to achieve the required result? Are there any pitfalls to watch out for?
I use CodeIgniter with Smarty. I have an additional function in my Smarty class called notfound(). Calling notfound() sets the correct header location to a 404 page and then displays a 404 template. The template has an overrideable title and message so it's very versatile. Here's some sample code:
Smarty.class.php
function not_found() {
header('HTTP/1.1 404 Not Found');
if (!$this->get_template_vars('page_title')) {
$this->assign('page_title', 'Page not found');
}
$this->display('not-found.tpl');
exit;
}
In a controller I can do something like this:
$this->load->model('article_model');
$article = $this->article_model->get_latest();
if ($article) {
$this->smarty->assign('article', $article);
$this->smarty->view('article');
} else {
$this->smarty->assign('title', Article not found');
$this->smarty->not_found();
}
Likewise, I can change the code in /system/application/error/error_404.php to:
$CI =& get_instance();
$CI->cismarty->not_found();
It works great, uses a small amount of code, and doesn't duplicate 404 functionality for different types of missing entities.
I think you could do a similar thing with the built-in CodeIgniter views. The important thing is to spit out the header before you do your view stuff.
Update: I use a custom Smarty wrapper similar to the one described here:
Using Smarty with CodeIgniter