Welcome Guest, Not a member yet? Register   Sign In
Custom 404 controller like any other controller
#1

[eluser]Henrik Pejer[/eluser]
Hello fellow CI code crunchers!

I decided to start my venture down the CI-path with a little fix for, as I'd call it, complete custom 404 controller page.

I searched the forum for how I should make a custom 404-page. It seemed like I should extend the Exceptions-class. The only problem is that the 404-event triggers before all the base classes are loaded: I have no controller, database etc.

I think the reason for this is based on security: not letting an ill typed URL somehow get run by CI as a normal controller.

But I realized that by extending not the Exceptions class but rather the Router-class, I was able to 'fool' CI to use one of my own controller, and let the system load as normal.

All I did was copy the '_validate_segments'-function and slightly modify it to return the custom controller I wanted to use as 404-page. My custom 404-controller is called 'custom404controller' and located in my application/controllers/-folder, like any other controller.

A word of advice: before you use this, test it thoroughly! Also make sure that by using this way of handling 404-errors, you do not compromise the security of you application. Also,this is for version 1.5.3 of CI.

This is the extension that seemed to do the trick:

Code:
class MY_Router extends CI_Router{
    
    function MY_Router(){
        parent::CI_Router();
    }
    
    function _validate_segments($segments)

    {

        // Does the requested controller exist in the root folder?

        if (file_exists(APPPATH.'controllers/'.$segments[0].EXT))

        {

            return $segments;

        }



        // Is the controller in a sub-folder?

        if (is_dir(APPPATH.'controllers/'.$segments[0]))

        {        

            // Set the directory and remove it from the segment array

            $this->set_directory($segments[0]);

            $segments = array_slice($segments, 1);

            

            if (count($segments) > 0)

            {

                // Does the requested controller exist in the sub-folder?

                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT))

                {
                    # HENRIK PEJER MOD: commented out the line below, added the line below that

                    #show_404();
                    return $this->custom_404();

                }

            }

            else

            {

                $this->set_class($this->default_controller);

                $this->set_method('index');

            

                // Does the default controller exist in the sub-folder?

                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))

                {

                    $this->directory = '';

                    return array();

                }

            

            }              

            return $segments;

        }

    

        // Can't find the requested controller...
        # HENRIK PEJER MOD: commented out the line below, added the line below that
        #show_404();
        return $this->custom_404();

    }
    
    function custom_404(){
        # return an array with the name of the controller we want as the 404-handler...
        return array('custom404controller');
    }
}

Any advice, tips or concerns regarding the code above would be very interesting to hear.

Take care and happy CI:ing!
#2

[eluser]sophistry[/eluser]
that's cool! thanks for sharing that code.

what would happen if you put an errordocument directive in your .htaccess file? would that solve this problem too? i have not tried it, but putting a single directive in the webserver config file is more appealling to me than extending the router class... just curious if you use apache and/or have tried something like this:

Code:
ErrorDocument 404 /page_not_found

and then make a page_not_found controller. would that directive run properly if put after the mod_rewrite stuff too? can the errordocument handle parameters in the parameters? hmnnn... just talking off the tips of my fingers...

also, before you go extending the core files you should note that the exceptions class show404() function logs the 404 error before displaying the page:
Code:
/**
     * 404 Page Not Found Handler
     *
     * @access    private
     * @param    string
     * @return    string
     */
    function show_404($page = '')
    {
        $heading = "404 Page Not Found";
        $message = "The page you requested was not found.";

        log_message('error', '404 Page Not Found --> '.$page);
        echo $this->show_error($heading, $message, 'error_404');
        exit;
    }

so, you could add this line to the custom404() function:
Code:
log_message('error', '404 Page Not Found --> '.$segment_string);

finally, you might want to pass the $segments argument to the custom404() method so that it can use the mistyped/erroneous controller name to help the user.

best... and let us know if you decide to try out the apache errordocument directive.
#3

[eluser]coolfactor[/eluser]
Extending the Exceptions class and overriding the show_404() method is another (non-hackery) way to handle this.
#4

[eluser]sophistry[/eluser]
I just found this page after looking up some stuff about mod_rewrite and errordocument:
webmasterworld forum on errordocument and mod_rewrite
#5

[eluser]Henrik Pejer[/eluser]
@sophistry:

The problem is that the server never gets the 404-error since all requests are routed through the index.php-file of CI. Its CI that send the 404-error, nothing else.

Something struck me: you could modify the show_404() function to redirect you to a custom error-page instead of extending the Routes_class. This way you can have a controller that will handle all you 404-errors

@Coolfactor:

I agree, its a much cleaner way of doing things, but the only problem I have with show_404 is that the complete CI-system has not loaded, so you can't directly in that function put calls to the db etc. Or you can, but it wouldn't be using CI-classes or you'd have to mimic the startup behaviour of CI in that function.

I think that modifying the show_404 to redirect to /custom404controller/ is much better, then what I did above. Its only a shame that I figured this out just now, instead of before I posted that code...

Thanks for you answers, sophistry and coolfactor!
#6

[eluser]Henrik Pejer[/eluser]
So this is what I've come up with (so far?):
Code:
class MY_Exceptions extends CI_Exceptions{
    
    function MY_Exceptions(){
        parent::CI_Exceptions();
    }
    
    function show_404($page=''){
        # Your code here...
        
        # end with redirect to the custom 404 handler
        # add on the end the request-uri (might only work with apache)
        header('Location: /custom404controller'.$_SERVER['REQUEST_URI']);
        exit;
    }
}

Instead of extending routes, I extend exceptions, like coolfactor and others suggest. To be able to use a custom 404 controller, though, I have to redirect.

There, now we have the possibility to have a controller that can handle 404-errors and also use the complete CI framework. I really should have thought this through one more time before posting my initial code, sorry for that!

Happy CI:ing!
#7

[eluser]Jérôme Jaglale[/eluser]
I need a config variable in order to build the redirect URL.
When I look at the logs, I can see ''Config Class Initialized". Is there a way to use the config variables directly in MY_exceptions ?

I tried
Code:
require_once(APPPATH."config/config.php");
but since the config array in the config file is not global.. :-(

UPDATE: the solution was in the Router class:
Code:
$this->config =& load_class('Config');
echo $this->config->item('base_url');
#8

[eluser]Jérôme Jaglale[/eluser]
Finally I think making a HTTP redirection is not user-friendly: the user doesn't know what is the wrong URL. Yes, of course you can pass the wrong URL as a parameter and display it on the page, but I would like to keep it simple.

I would like to make an internal redirecion. I mean, calling a standard method of a controller without making a HTTP redirection. Is there a way to do that directly in the function show_404 in MY_Exceptions class ?
#9

[eluser]Roger Glenn[/eluser]
I needed a way to redirect all 404 errors to my "sitemap" controller, and Henrik Pejer's solution in the first message of this thread worked beautifully.

Thanks Henrik!
#10

[eluser]Pygon[/eluser]
Or, if you want to get REALLY crazy, you could just have the error_404.php send redirect() and not modify anything, but it's really a matter of taste and requirements.




Theme © iAndrew 2016 - Forum software by © MyBB