Wednesday, November 10, 2010

Home Page

While you can route your home page to a controller and action as we did previously, you may prefer to have a separate page set up as the leading page into your site. A good practice is to create a homepage controller and view to use as the main page to your site, this way all of the other controllers and views are tied specifically and only to the model to which they belong.

First create /app/controllers/homepages_controller.php
<?php
class HomepagesController extends AppController {

}
?>
Since the home page controller does not have an associated model, if we wanted to access any data we would define the models we want to access with the $uses variable. If the models are associated, such as our "User" model and "Carrier" model, it may not be necessary to include both, but if they are not related, such as the "Drug" model, but you want to include data from that table, you need to include both.
var $uses = array('User', 'Drug');
In our case we won't be using any data for the home page at this time so we need to create an empty array. We'll also need to create an index method.
var $uses = array();

function index() {
    $this->set('title_for_layout', 'Drug-Ed.com : Drug Education and Medication Adherence');
}
Next we need to create the home page view so create /app/views/homepages/index.ctp For now we'll leave this blank.

And the last thing we need to do is change our routing to go to the home page instead of the previously set up users page.

Edit /app/config/routes.php and replace
    Router::connect('/', array('controller'=>'users', 'action'=>'index'));
with
    Router::connect('/', array('controller'=>'homepages', 'action'=>'index'));
Now browse to http://drug-ed.com/ and verify it goes to the homepages index.

Form Focus

One thing I find annoying on web pages is when there is a form you are expected to fill out, such as a login form, and the focus is not on the first field.

Edit /app/views/users/login.ctp and add to the bottom of the page:
<script type="text/javascript">
document.getElementById('UserUsername').focus() 
</script>
Save and upload the file then browse to http://drug-ed.com/users/login and verify the username field has focus.

More Authentication

While most of the default settings for the Auth component are good enough, there are many things you can add and change to allow more flexibility. If the default setting (as seen in the examples below) is good enough, you don't need to add the code at all.

By default the Auth component uses the Security class for hashing the password and the Security class uses SHA1 for hashing. The Auth component automatically performs the hashing for password verification and user creation as long as there are both username and password fields.

As mentioned previously, these settings can go into the specific controllers, or in the app controller for use with all controllers.

You can use change the hashing method for the Security class as follows:
Security::setHash('md5'); // or sha256 or sha1 (default)
For the error message on invalid login use loginError.
$this->Auth->loginError = "Login failed. Invalid username or password.";
For the error message when trying to access a protected page use authError.
$this->Auth->authError = "You are not authorized to access that location.";
While the Auth component remembers what page you were trying to access before logging in, if you enter the site from an external link for example you can set what URL the user goes to after login, you can also set the URL after logout.
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');

Authentication

To set up a user login system for your CakePHP application, Cake comes with an authentication component built in. The Auth component requires a "user" table in the database with at least the fields of "username" and "password". Though they don't have to be named exactly that, it makes everything easier.

The first thing you need to do is to add the Auth component to your controllers. Since typically you want all your controllers to utilize the Auth component, it is best to create an App controller and include it in there.

Create /app/app_controller.php
<?php
class AppController extends Controller{
    var $components = array('Auth');
}
?>
If at any time you encounter trouble with your authentication, comment out this line to turn it off.

NOTE: When you explicitly define your components, you disable the default components in CakePHP, such as Session so if you want to use the session component, be sure to explicitly define it in your array.
    var $components = array('Auth','Session');
The next requirement is to have login() and logout methods in your users controller.

Exit /app/controllers/users_controller.php and add the login and logout methods as below.
    function login() {
        $this->set('title_for_layout', 'Login');
        /* If user is already logged in, redirect */
        if ($this->Auth->user()) {
            $this->redirect($this->Auth->redirect());
        }
    }

    function logout() {
        $this->redirect($this->Auth->logout());
    }
The last requirement is the view for the login action of the users controller.

Create /app/views/users/login.ctp
<?php
    echo $session->flash('auth');
    echo $form->create('User', array('action' => 'login'));
    echo $form->input('username');
    echo $form->input('password');
    echo $form->end('Login');
?>
The login view uses the form helper to create a form allowing entry of the username and password.

In a controller, the beforeFilter() method is called automatically before any other controller action. This is where you define the settings for your Auth component.

Edit /app/app_controller.php and add the beforeFilter method below.
function beforeFilter() {
    /* Set username and password fields if not using default 'username' and 'password' */
    $this->Auth->fields = array('username' => 'email', 'password' => 'password');
    /* What actions are allowed without authentication */
    $this->Auth->allow('*'); // Allow all defined methods
}
By default, the Auth component only allows the login and logout methods. Allowing all actions will allow you to create a user and set the password so you will be able to login once you lock the site down.

If you are still using scaffolding at this point however, scaffold methods are not allowed without being explicitly defined.

Edit /app/app_controller.php and change the $this->Auth->allow() command as follows:
    $this->Auth->allow('*', 'add');
Browse to http://drug-ed.com/users/add/ and create a new user with a properly hashed password so you can login.

Edit /app/app_controller.php and comment out the $this->Auth->allow() command to prevent all access to the site unless signed in. Browse to any page then login it verify it works.

If you are not allowing all actions and are explicitly defining the actions you are allowing, remember that for static pages the default action is 'display' and is required for authorization to view those pages.

Tuesday, November 9, 2010

Pagination

With a short list of records, the basic view is good enough, however, once the number of records exceeds 50 or so, it is helpful to use CakePHP's built in pagination to display pages on multiple pages. First edit /app/controllers/users_controller.php and replace this line
$this->set('users',$this->User->find('all'));
with this one
$this->set('users', $this->paginate());
Save and upload the file. Now if you browse to http://drug-ed.com/ it still works because the data pulled from the database is the same, we just haven't taken advantage of the pagination features yet. Edit /app/views/users/index.ctp and replace
    <th>ID</th>
    <th>Username</th>
    <th>First Name</th>
    <th>Last Name</th>
    <th>Email</th>
with
    <th><?php echo $this->Paginator->sort('id');?></th>
    <th><?php echo $this->Paginator->sort('username');?></th>
    <th><?php echo $this->Paginator->sort('first_name');?></th>
    <th><?php echo $this->Paginator->sort('last_name');?></th>
    <th><?php echo $this->Paginator->sort('email');?></th>
Then after the users table within the users division add
    <p>
    <?php
        echo $this->Paginator->counter(array('format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)));
    ?>
    </p>
    <div class="paging">
        <?php echo $this->Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?>
        | <?php echo $this->Paginator->numbers();?>
        | <?php echo $this->Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?>
    </div>
Save and upload the file then browse to http://drug-ed.com/ to verify that it works. You can now click on the headers of each column to sort the users, as well as view them on multiple pages, though it requires more than 20 records for a new page.

Elements

Elements are HTML elements that you will use repeatedly within different views. You create the element then include it within your view code. They are stored within the /app/views/elements/ directory.

Create /app/views/elements/banner.ctp
<?php
echo "<div class=\"clear centerText\">
<script type=\"text/javascript\"><!--
google_ad_client = \"pub-9280152324836673\";
google_ad_slot = \"5630351766\";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type=\"text/javascript\"
src=\"http://pagead2.googlesyndication.com/pagead/show_ads.js\">
</script></div>";
?>
Save and upload the file then edit /app/views/users/index.ctp Add the following line at the bottom of the file:
<?php echo $this->element('banner'); ?>
Save and upload the file then browse to http://drug-ed.com/ to verify it works. Since the element only exists on the /app/views/users/index.ctp page, it will only appear on that page. If you include the element on the /app/views/layouts/default.ctp page, it will display on every page.

Remove that line from the bottom of /app/views/users/index.ctp and add it to the "footer" div of the /app/views/layouts/default.ctp page.

Static Pages

Not all pages in your site will be dynamic. Some pages, such as an "About" page, will be static and CakePHP has a Pages controller for just that purpose. The pages controller is located by default in /cake/libs/controllers/pages_controller.php If you need to edit it for any reason, copy the file to /app/controllers/

Static pages are stored in /app/views/pages/ and displayed using the display action. By default they are addressed using site root/pages/page name.

Create any static pages with the .ctp extension and include any html formatting you want in your page.

Create /app/views/pages/about.ctp
<?php $this->set("title_for_layout", 'About Drug-ed.com'); ?>
<h2>About Drug-Ed.com</h2>
<p>Drug-Ed.com is a free drug education and medication reminder service for your daily medication needs. Set up your drug schedule online and have reminders emailed to you or texted to your mobile phone daily. We want to make taking your medication as easy and hassle free as possible.</p>
Remember when I said we should change the page title in the controller? Well with static pages it is easier to just set them in the page itself.

Save and upload the file then browse to http://drug-ed.com/pages/about

When using the HTML helper to create links to static pages remember that pages is the controller and display is the action so they either must be explicitly declared, or create a hard link instead.
<?php echo $this->Html->link('Downloads', array('controller' => 'pages', 'action'=>'display', 'downloads')); ?>
<?php echo $this->Html->link('Downloads', '/pages/downloads'); ?>