Permalink
Fetching contributors…
Cannot retrieve contributors at this time
231 lines (172 sloc) 11 KB
---
title: Lesson 1: Creating a New Page
---

One of the simplest tasks in extending UserFrosting is to create a new page. If you're a beginner, you're probably used to creating a single .php file which contains the content of your page (e.g. barracks.php), that you then view by navigating to http://mysite.com/barracks.php.

UserFrosting on the other hand uses the front controller pattern, which gives you more flexibility and decouples the URLs for your pages from the actual code that generates their content. This means that the code for generating a page is no longer contained to a single file. For more information about how this works, please see Front Controllers and the Slim Microframework.

For this tutorial, we will create a simple page that lists all of the groups to which the current user belongs.

Let's start by creating the main route in public/index.php. You will see many blocks of code that look like this:

    $app->get('/dashboard/?', function () use ($app) {    
       // Access-controlled page
       if (!$app->user->checkAccess('uri_dashboard')){
           $app->notFound();
       }
       
       $app->render('dashboard.html', [
           'page' => [
               'author' =>         $app->site->author,
               'title' =>          "Dashboard",
               'description' =>    "Your user dashboard.",
               'alerts' =>         $app->alerts->getAndClearMessages()
           ]
       ]);          
    });

These are routes, which Slim uses to decide what to do when a visitor to your site goes to a particular URL. This route handles the /dashboard URL.

Let's create a route for our new page, right below this block of code. Let's call the URL account/groups. Why did I pick this name? Well, it's neat, and it explains precisely what it is that we're trying to access - a list of groups for my account. We can create a simple route like this:

    $app->get('/account/groups/?', function () use ($app) {   
        echo "For Aiur!";
    });

$app->get tells us that we're creating a GET route, because when anyone navigates to a web page in their browser, they are really issuing a GET request to the web server. Then we specify the URL we would like to route. The ? at the end of the route means that the trailing / is optional.

function () use ($app) defines a callback, which is basically a function that we can pass into another function as a parameter. This callback will contain all the code that should be executed when someone navigates to this URL.

So, let's visit our new route:

UserFrosting - creating a new page - 1

Alright, but not very impressive. All we did was echo a single line, which hopefully you already know how to do. Let's try to make this into a UserFrosting page!

Headers, Footers, and Navbars (oh my!)

To do this, we need to create a template. Templates are stored in userfrosting/templates/. Since we're creating a page for authenticated users, we'll make this a themed page. This will apply the logged-in user's theme to the style and layout of this page. In userfrosting/templates/themes/default/, create a new HTML file called account-groups.html. Add the following HTML:

    {% raw %}
    <!DOCTYPE html>
    <html lang="en">
    {% include 'components/head.html' %}
      
    <body>
        <div id="wrapper">
            {% include 'components/nav-account.html' %}
            <div id="page-wrapper">
                {% include 'components/alerts.html' %}
                
                <h1>The user's ID is {{user.id}}.</h1>
                
                {% include 'components/footer.html' %}    
            </div>
        </div>
    </body>
    </html>
    {% endraw %}

This is a basic, (nearly) empty user account page. We've included components/head.html, which contains the <head> tag and various CSS includes, components/nav-account.html, which renders the top and side navigation bars, and components/footer.html, which contains a footer message and various Javascript includes. components/alerts.html will display any messages that have been placed in the message stream.

The line

{% raw %}<h1>The user's ID is {{user.id}}.</h1>{% endraw %}

is our first bit of actual content. {% raw %}{{user.id}}{% endraw %} is a Twig variable. For convenience, UserFrosting automatically loads the current user into the Twig variable user. So, you can always access any property of the current user from Twig by using {% raw %}{{user.*}}{% endraw %}.

Going back to our route in index.php, let's make UserFrosting render our new template!

Change your new route to look like this:

    $app->get('/account/groups/?', function () use ($app) {   
       $app->render('account-groups.html', [
           'page' => [
               'author' =>         $app->site->author,
               'title' =>          "Account Groups",
               'description' =>    "A list of the groups to which you belong.",
               'alerts' =>         $app->alerts->getAndClearMessages()
           ]
       ]);  
    });

This is telling UserFrosting to tell Twig to look in userfrosting/templates/themes/default/ for a template called account-groups.html, and render it with the specified author, title, page description, and any messages that have been placed into the message stream.

If you're logged in, it should look like this:

UserFrosting - creating a new page - 2

Ok, now let's see if we can pull the group names for the user and print them nicely. Change your template file to look like this:

    {% raw %}
    <!DOCTYPE html>
    <html lang="en">
    {% include 'components/head.html' %}
      
    <body>
        <div id="wrapper">
            {% include 'components/nav-account.html' %}
            <div id="page-wrapper">
                {% include 'components/alerts.html' %}
                
                <h1>User Groups</h1>
                <ul class="list-group">
                {% for group in groups %}
                    <li class="list-group-item">
                          <i class="{{group.icon}} fa-fw"></i> {{group.name}}
                    </li>
                {% endfor %}
                </ul>
                <br>
                {% include 'components/footer.html' %}    
            </div>
        </div>
    </body>
    </html>
    {% endraw %}

The {% raw %}{% for ... %}{% endraw %} tag is Twig's syntax for a for loop, which you close with a {% raw %}{% endfor %}{% endraw %} tag. groups is a Twig variable, which we now need to define in our route. So now, change your route to:

    $app->get('/account/groups/?', function () use ($app) {   
       $app->render('account-groups.html', [
           'page' => [
               'author' =>         $app->site->author,
               'title' =>          "Account Groups",
               'description' =>    "A list of the groups to which you belong.",
               'alerts' =>         $app->alerts->getAndClearMessages()
           ],
           'groups' => $app->user->getGroups()
       ]);  
    });

That extra line $app->user->getGroups() loads a list of all the groups (as Group objects) that the currently logged in user ($app->user) belongs to, and passes them into the Twig variable groups.

Great, now your page should look like this:

UserFrosting - creating a new page - 3

Access Control

Alright, one last thing. We need to control who can access this page! As it stands, this is a publicly accessible page. We can control access to the page through an authorization hook, like so:

    $app->get('/account/groups/?', function () use ($app) {   
       // Access-controlled page
       if (!$app->user->checkAccess('uri_account-groups')){
           $app->notFound();
       }   
       
       $app->render('account-groups.html', [
           'page' => [
               'author' =>         $app->site->author,
               'title' =>          "Account Groups",
               'description' =>    "A list of the groups to which you belong.",
               'alerts' =>         $app->alerts->getAndClearMessages()
           ],
           'groups' => $app->user->getGroups()
       ]);  
    });

The function $app->user->checkAccess checks whether or not the current user has access to the authorization hook uri_account-groups, and returns a 404 error if they don't.

To define the hook, and specify which users and groups have access to it, we need to add it to our database. Specifically, we need to modify either uf_authorize_group or uf_authorize_user.

Let's suppose we want to allow access to this page for an entire group. Then we go into uf_authorize_group (for example, through phpMyAdmin), and insert a record:

id group_id hook conditions
x 1 uri_account-groups always()

group_id is the group that we are authorizing access for, for example, the Users group. uri_account-groups is the hook that we are defining, and conditions lets us use an expression to limit the conditions under which members of this group have access. To always allow access for this group, simply use the condition always().

Cool! Now anyone who is a member of group 1, that is the Users group, will be able to access your new page. Anyone who isn't a member, including unauthenticated users, will be shown a 404 error page.

Linking to Your Page in the Navbar

What's that, you want your page to show up in the sidebar? Well, ok.

Head on over to userfrosting/templates/themes/default/menus/sidebar.html. You'll see some links like:

    {% raw %}
    {% if checkAccess('uri_users') %}
    <li>
      <a href="{{site.uri.public}}/users"><i class="fa fa-users fa-fw"></i> {{ translate("MENU_USERS") }}</a>
    </li>
    {% endif %}
    {% endraw %}

The checkAccess function in Twig works just like $app->user->checkAccess. This will let you display a menu item only when the user has access to that page.

{% raw %}{{site.uri.public}}{% endraw %} is the root URL of your site, which UserFrosting automatically detects and loads into the global site variable.

The translate function allows for multilanguage support. If you want to have different versions of the text for this menu item depending on the user's locale, you can define a message id in the various language files in userfrosting/locales, and translate will select the appropriate message for the current user's locale. If you don't care about multilanguage support, you can simply put the text here that you'd like the menu item to appear as:

    {% raw %}
    {% if checkAccess('uri_account-groups') %}
    <li>
      <a href="{{site.uri.public}}/account/groups"><i class="fa fa-tags fa-fw"></i> My Groups</a>
    </li>
    {% endif %}
    {% endraw %}

And that's it! I hope this was helpful!