Step 8 – Putting it all together. How do visitors access our pages

(created at: September 9, 2015; last update: November 27, 2015)
Well… This seems to be the last step in developing our multi-language site. We did all the things we needed to do to administer the pages, the posts and the categories. But what now? How do the visitors see our pages.

Because we already had the ability to add slugs to our contents, now we should allow the visitors to have a SEO friendly url when visiting a page. Let’s say that we saved a page that has “this-is-a-great-page-about-something” as slug. People should access the page by typing:

www.yourwebsite.com/this-is-a-great-page-about-something

But how do we set up our application to take care of the url? First we need to take a look at our routes.php file again (can be found inside application/config directory). We’ve worked on this file in a previous tutorial (Step 4.2 – Set up the language for a multilanguage site in CodeIgniter):

<?php

defined('BASEPATH') OR exit('No direct script access allowed');
$route['default_controller'] = 'welcome';
$route['404_override'] = '';
$route['translate_uri_dashes'] = TRUE;

$route['admin'] = 'admin/dashboard';

$route['^(\w{2})/(.*)$'] = '$2';
$route['^(\w{2})$'] = $route['default_controller'];

Now we will modify this bit so that when a url is passed, the routes.php would know to pass that url to a controller that will look for the slug inside our database:

<?php

defined('BASEPATH') OR exit('No direct script access allowed');

$route['default_controller'] = 'welcome';
$route['404_override'] = 'findcontent';
$route['translate_uri_dashes'] = TRUE;

$route['admin'] = 'admin/dashboard';

$controllers_methods = array(
    'en' => array(
        'welcome/list' => 'welcome/list',
        'welcome' => 'welcome'
    ),
    'fr' => array(
        'bienvenu/list' => 'welcome/list',
        'bienvenu' => 'welcome'
    )
);

//$route['^(\w{2})/(.*)$'] = '$2';
//$route['^(\w{2})$'] = $route['default_controller'];

$route['^(\w{2})/(.*)'] = function($language, $link) use ($controllers_methods)
{
    if(array_key_exists($language,$controllers_methods))
    {
        foreach ($controllers_methods[$language] as $key => $sym_link) {
            if (strrpos($link, $key, 0) !== FALSE) {
                $new_link = ltrim($link, $key);
                $new_link = $sym_link . $new_link;
                return $new_link;
            }
        }
    }
    return 'findcontent/index';
};
$route['^(\w{2})$'] = $route['default_controller'];

/* End of file routes.php */
/* Location: ./application/config/routes.php */

As you can see, we’ve first made sure that, whatever the language, when a link is accessed and points to an existing controller, the same controller is accessed. What I mean by this is the fact that if someone for example access the “www.yourwebsite.com/bienvenu/list“, the content will be served from the “welcome/list” controller/method. The same could be said about any other language that is not the default language (in this particular case, english). So you only have to append to the $controllers_methods array another key in the language you want to add.

Now, after we’ve made sure that all the controllers and methods are verified, if the address doesn’t point to any of these, we redirect the visitors to a “default” controller and method: “findcontent/index“. Once we did this, it’s time to create it. So what would this controller/method do? It will take the slug from our url and simply look if there is such a slug in our database.

So let’s create a file named Findcontent.php inside our application/controllers directory:

<?php

defined('BASEPATH') OR exit('No direct script access allowed');

class Findcontent extends Public_Controller
{

    function __construct()
    {
        parent::__construct();
        $this->load->model('slug_model');
        $this->load->model('content_model');
    }

    public function index()
    {
        $total_segments = $this->uri->total_segments();
        for($i=1;$i<=$total_segments;$i++)
        {
            $url = $this->uri->segment($i);
            if(!array_key_exists($url,$this->langs))
            {
                break;
            }
        }
        //$url = $this->uri->segment(1);
        if($slug = $this->slug_model->where(array('url'=>$url))->get())
        {
            if($slug->redirect != '0' || $slug->language_slug!=$this->current_lang)
            {
                if($slug->redirect!='0')
                {
                    $slug = $this->slug_model->get($slug->redirect);
                    if ($slug->language_slug == $this->default_lang)
                    {
                        redirect($slug->url, 'location', 301);
                    }
                    else
                    {
                        redirect($slug->language_slug . '/' . $slug->url, 'location',301);
                    }
                }
            }
            $content_id = $slug->content_id;
            $language_slug = $slug->language_slug;

            $content = $this->content_model->where('published','1')->with_translations('where:`language_slug` = \'' . $language_slug . '\'')->get($content_id);
            if($translation = $content->translations[0]) {

                /*
                echo '<pre>';
                print_r($content);
                echo '</pre>';
                exit;
                */
                $alternate_content = $this->slug_model->where(array('content_id' => $content_id, 'redirect' => '0'))->get_all();
                if (!empty($alternate_content))
                {
                    foreach($alternate_content as $link)
                    {
                        $this->langs[$link->language_slug]['alternate_link'] = (($link->language_slug!==$this->default_lang) ? '/'.$link->language_slug : '').'/'.$link->url;
                    }
                }
                $this->data['langs'] = $this->langs;
                $this->data['page_title'] = $translation->page_title;
                $this->data['page_description'] = $translation->page_description;
                $this->data['page_keywords'] = $translation->page_keywords;
                $this->data['title'] = $translation->title;
                $this->data['content'] = $translation->content;

                $this->render('public/'.$content->content_type . '_view');
            }
        }
        else
        {
            echo 'oups...';
            show_404();
            exit;
        }
    }
}

As you can see, we first take a look at our url to see if there is a language slug in it, and only if that part of url doesn’t exist in our url we know that that particular part is part of our slug that we need to find.

After that we look if the slug exists in our database. If it does, then we make sure that that slug is not redirected to another slug. If it is redirected, it is best to do a redirect to the new slug with a “moved permanently” code (301).

If the slug is not a redirect we look for the content, but we make sure that the content is in the language already set by the visitor. Once we’ve found out what type of content it is, we can simply call the according page template (assuming we have a template for pages, a template for posts, and a template for categories).

Disclaimer: I sure hope you can now make a website by yourselves starting from this series. If you still feel left in the dark, or something doesn’t work, there may be the possibility that I have forgot to mention something in any of my episodes. Go figure, this series was actually a work in progress. That means that if somewhere in my episodes I found that something from the previous episodes didn’t work, I returned and made some changes (I sure hope I’ve changed the tutorials accordingly). Anyways, if you liked the philosophy behind my tutorials, and you want to simply use what we’ve created together, you can at any time download the project from my Github Repository: https://github.com/avenirer/CodeIgniter-multilanguage-site

Do not feel discouraged if you want to put some questions. I am open to any doubts you may have, and I will answer them the best I can (as long as it’s not a particular problem, like developing a particular site).

22 comments

    1. You are right… That was a bit of overdoing it… Didn’t pay much attention. Will change that in a moment. Thank you.

  1. Hi Adrian,
    Thanks so much for the tutorial. Very clear and complete, it definitely helped me understand and work fast with CI3, way better than the News tutorial from the doc !

  2. Hello Adrian,
    sorry, i don’t understand how to show posts or pages to normal not logged visitors. Maybe by url call like
    “mysite.com/pages/1” ?
    Thank you

    1. Well… as you should clearly see from this tutorial, the Findcontent controller is serving the views depending on the urls it receives. It looks for those urls inside the slugs table, after it finds out to which content type the url is referring, it simply serves the appropriate view, be it page, post, or category. You can, of course, create different methods depending on the content type inside this controller, and let the controller call the appropriate methods which will serve the content the way you want it too. The way I’ve imagined it is a SEO-friendly way, with no intermediate segments to be repeated, like in your example (../page/1/…).

  3. Severity: Error
    Message: Call to a member function logged_in() on a non-object
    Filename: _parts/admin_master_header_view.php
    Line Number: 14

    i dont know why this problem happen and i follow all your steps

  4. Hello! Thank you for a great tutorial! But I’ve little bit confused with categories. I’ve created a “news” category and add some posts with this category, how I can show all my posts to users on page e.g. “/news/”? Because view of category consist of “title” and “content’.

    1. Once you have the category, you can simply retrieve the posts that belong to that category using the models.

  5. Hello! Thank you for a great tutorial! I need to learn about multi-level comments, please .. can you make an step(tutorial) about comments?

  6. Hello all of what you teach about CI looks great and done with involvement and passion.

    I’m wondering about a new tutorial that could make sense, since it imply to think about implementing also a knowledge base, notifications for users, replies through email etc.

    A ticket system.

    Notifications are intended as , users and operators will write tickets (messages) and the counterpart will receive email notifications and e.g. users will have the chance/option to reply the ticket messages WITHOUT logging into the ticket system and instead they will reply the notification email.

    Tickets will be assigned to operators and to departments etc etc.

    This system would involve a lot of challenges and could be become an interesting advanced Codeigniter 3 tutorial

    What do you think about it?

    Thank you for your tutorials !
    though, I agree with your recent post http://avenir.ro/why-develop-a-web-app-with-codeigniter-in-2016/ since I’m new to this argument and I’ve read about various frameworks and CI 3 looks a great and effective synthesis, fast to get ready to develop and not dispersive able to drive important projects keeping them fast and light. Yes it requires you to tackle with php language… why not???? That is the part that everyone should appreciate. you’ll know what is going on…
    If you allow, about teaching, in my learning curve, I have found fundamental this teach about OOP (also for the simplicity with which it has been written) http://www.killerphp.com/tutorials/object-oriented-php/ , understanding OOP is basic for people that begins from scratch.

    R.

  7. Of what i understood that any category,post or page URL can be accessed from frontend like that http://www.domain.com/slug-of-any-content-type

    But what if slug was not inserted then we wont be able to access that content (category-post-page) at all ?

    If so then i have another question is how can i make a condition in Findcontent.php or routes.php to check if for example a category slug was empty then it redirect be to default url like http://www.domain.com/categroy/30

    1. If you’ve followed the tutorial you should see that the slug is automatically created by the cms if no slug is provided (it is created from the title of the content). There is no chance for the cms to create the same slug for two or more contents because it first verifies if that slug exists and if it does it appends a number at the end of the slug.

  8. Thank you so much .. ok that’s my mistake i didn’t totally follow the tutorial but now i see the problem is when i was adding a page title in Arabic (my language) it wasn’t saved but when i tried again in english it was saved just fine.In a CMS like WordPress i used to see arabic slugs get saved encoded something like this %d8%ba%d9%8a%d8%b1-%d9%85%d8%b5%d9%86%d9%81

    Is there a chance to let Arabic slugs be available in the urls ?

    1. I think i need to edit line 63 in admin/Content.php controller

      $slug = (strlen($this->input->post(‘slug’)) > 0) ? url_title($this->input->post(‘slug’),’-‘,TRUE) : url_title(convert_accented_characters($title),’-‘,TRUE);

  9. /* This condition is not necessary */
    if($slug->redirect != ‘0’ || $slug->language_slug!=$this->current_lang)
    {
    /* choose one of two conditions */
    if($slug->redirect != ‘0’ ) {…}
    }

    1. Why would you think that? You want to serve an url without making sure that that url belongs to a certain language. If you don’t care about SEO you can do whatever you like 🙂

  10. HI, very good tutorial, but i have one question, i created secondary menu in the admin dashboard but i don’t understand how to add it to the page to show? for example if i want to have 2 menus (top-menu and side-menu). Thank you for your answer 🙂

    1. As you may have seen in the public controller the $top_menu that is called inside the header view is created inside the render method: $this->data[‘top_menu’] = $this->menus_creator->get_menu(‘top-menu’,$this->current_lang,’bootstrap_menu’);

      So if, for example you want to create a $side_menu variable you simply duplicate that line and change top-menu to whatever name you made for that menu:
      $this->data[‘side_menu’] = $this->menus_creator->get_menu(‘side-menu’,$this->current_lang,’bootstrap_menu’);

  11. Great tutorial , pretty easy to understand.
    But I have a question about routing, and dynamic pages.
    How to manage pages whose routes like this: ‘(:any)-p(:num)’ => ‘frontend/product/$2’ ?
    or even like this
    ‘catalog/(:num)/(:any)’ => ‘frontend/catalog/groups/$1/$2’,
    ‘catalog/(:num)’ => ‘frontend/catalog/models/$1’,
    ‘catalog’ => ‘frontend/catalog’, <- only this works

Leave a Reply

Your email address will not be published. Required fields are marked *

No spam? * Time limit is exhausted. Please reload CAPTCHA.