Part 6 - (Very) Simple ACL plugin and simple AJAX with JQuery

In this short part I’ll show you very simple approach for securing your admin area. And when I say very simple I really mean it. After this I will show you how with the use of JQuery we can easy add AJAX functionality to our application. I know about the integration with Dojo in the new releases of Zend Framework, but I really prefer JQuery to Dojo in my work, and I’m sure in the manual there will be nice examples about the usage of Dojo, so if you are interested to work with JQuery - you can see how simple you can use it with Zend Framework. If you won’t use JQuery - just skip the second half of this part - you won’t lose anything vital to the project.

So first we will add one more module to our application next to the default one - the admin module. Under the application/modules/ directory we create new subdirectory - admin. Then we add 2 subdirectories under application/modules/admin/ - controllers and views. We add empty IndexController.php file under application/modules/admin/controllers - we will add code here shortly. Then under the application/modules/admin/views/ we add 3 subfolders - filters, helpers and scripts. Under application/modules/admin/views/scripts we add one subfolder - index - this will hold the views for our Index Controller.

If you are lost with so many directory creations - here is what I have now :)

Directory structure for admin module

We will leave the meaningfull code in this admin module until the final part of this series, what we want now is to have placeholder text only. Before we have actual administration tools we want this area secure. So we add only one action - the default one. IndexController.php is really simple:

application/modules/admin/controllers/IndexController.php:

< ?php
 
class Admin_IndexController extends Zend_Controller_Action 
{
    public function indexAction()
    {
 
    }
}

And we will need just as simple view script:

application/modules/admin/views/scripts/index/index.phtml:

admin stuff here

Ok, now our brand new admin module is available at “http://localhost/admin” assuming you are developing on localhost with port 80 :). You can try it and you will see that you can open it even if you are not logged in. Lets change this. Recall that in our users DB table we have isAdmin field. I hope that you have some registered users by now, if you don’t have - please register one now and activate him (or ‘her’ if you prefer, but I will use ‘him’ because it is more natural). Now open your phpMyAdmin or whatever you use for DB administration and find the row in your users table for this user - this will be our administrator :). Change the isAdmin column in this row to be 1. If your user have username ‘admin’ you can use this:

UPDATE `users` SET `isAdmin`=1 WHERE `username`='admin';

Now we will write simple front controller plugin, which will make sure that only users with isAdmin flag set to 1 can access our admin module.

/application/Lib/Controller/Plugin/ACL.php

< ?php
 
class Lib_Controller_Plugin_ACL extends Zend_Controller_Plugin_Abstract 
{
    /**
     * Called before an action is dispatched by Zend_Controller_Dispatcher.
     *
     * This callback allows for proxy or filter behavior.  By altering the
     * request and resetting its dispatched flag (via
     * {@link Zend_Controller_Request_Abstract::setDispatched() setDispatched(false)}),
     * the current action may be skipped.
     * 
     * In this version we have only one rule - for access to 'admin' module we require 'isAdmin'
     * flag of the user to be set to true (this is very simple, but for our current needs is enough)
     *
     * @param  Zend_Controller_Request_Abstract $request
     * @return void
     */
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $module = $request->getModuleName();
        if ($module == 'admin' && (!Zend_Registry::get('defSession')->currentUser || !Zend_Registry::get('defSession')->currentUser->isAdmin))
        {
            // redirect to index if do not have access 
            // (possible to redirect to some 'access denied' page if needed)
            $request->setModuleName('default')->setControllerName('index')->setActionName('index');            
        }
    }
}

Thats all - on preDispatch (that is before each action is executed) we check the request object for the module name. If it is ‘admin’ and the current user is not admin - we replace the module name, controller name and action name with default/index/index. Better solution is to have special “access denied” page, but I’ll keep the things here as simple as possible. Now we just need to register this plugin with the front controller and we are done:

(in bootstrap.php):

$frontController->registerPlugin(new Lib_Controller_Plugin_ACL());

That’s all - you can open http://localhost/admin (again assuming that the project is on your localhost with port 80) and see the “admin stuff here” message only if you are logged in with user, who have isAdmin flag set to 1.

——————————****************************—————————

:) To part 2 of this part 6.

If you don’t want to use JQuery you can skip this. What is JQuery? A fast, concise, library that simplifies how to traverse HTML documents, handle events, perform animations, and add AJAX. At least they say it is this :). We want to use JQuery to add some form validation in the registration form.

So we download JQuery from JQuery site. Then put the jquery.js file in /public/js directory. Then create subdirectory there - /public/js/registration, here we will place our code for form validation. Actually here it is:
/public/js/register/form.js

$(document).ready(function() {
    $("#username").bind('keyup', usernameCheck);
    $("#email").bind('keyup', emailCheck);
 
});
 
function usernameCheck()
{
    var username = $("#username").val();
 
    if (username.length < 3)
    {
        $("#username_help").html('This field should be at least 3 characters long');
    }
    else if (username.length > 32)
    {
        $("#username_help").html('This field should be maximum 32 characters long');
    }
    else
    {
	    postObject = new Object;
	    postObject.username = username;
	    $.post('/user/checkUsernameAjax/', postObject, 
	      function(data){
	        if (data.valid)
	        {
	            $("#username_help").html('');            
	        }
	        else
	        {
	            $("#username_help").html('This username is already taken');
	        }
	      }, "json" );
	 }
}
 
function emailCheck()
{
    var email = $("#email").val();
 
    {
        postObject = new Object;
        postObject.email = email;
        $.post('/user/checkEmailAjax/', postObject, 
          function(data){
            if (data.valid)
            {
                $("#email_help").html('');            
            }
            else
            {
                $("#email_help").html('This email is already registered');
            }
          }, "json" );
     }
}

what we do here is to add 2 event listeners - to username and email fields. When the user types something there we will check if it is valid. The code, written with JQuery is so easy to read, that I feel stupid to explain lines of code such

    if (username.length < 3)
    {
        $("#username_help").html('This field should be at least 3 characters long');
    }
    else if (username.length > 32)
    {
        $("#username_help").html('This field should be maximum 32 characters long');
    }

What is more interesting is the way we make AJAX (asynchronous) call to our application to check if the username (or the email) is not already taken. I use POST request here to prevent caching of the response, because if I use GET with something like “http://localhost/user/checkUsernameAjax/johny” to check if “johny” is available, then some cache machines, proxy servers or even your own browser will cache the response that johny is available, and after 3 minutes when some other johny actually register himself in the site, you will still get the message that “johny” is available if you check. We want to prevent this so use POST. Other way is to use GET with URL like “http://localhost/user/checkUsernameAjax/johny/someRandomNumberHere”. Anyway, as there are many ways to accomplish something we just pick the POST and go ahead. If you are new to JQuery, the sintax of $.post function may seems a little wired, but using anonymous functions is very common language structure in JQuery, so better get used to it. Of course we could have named function as callback here - which checks if data.valid is true or false, and for some more complex functions may be it is better to have named function. For our single IF-ELSE block function this approach is best.

What is missing from the picture is the “/user/checkUsernameAjax/” and “/user/checkEmailAjax/” actions in the user action controller. If you expect something very very complex now, think again :)

in /application/modules/default/controllers/UserController.php:

    public function checkusernameajaxAction()
    {
        $this->_helper->layout->disableLayout();
 
        $username = $this->getRequest()->getParam('username');
        $usersTable = new Users();
        $select = $usersTable->select();
        $select->where('username = ?', $username);
        $rows = $usersTable->fetchAll($select);
        if ($rows->count())
            $valid = false;
        else
            $valid = true;
 
        $this->_helper->viewRenderer->setNoRender(); 
        $data = array('valid'=>$valid);
        $json = Zend_Json::encode($data);
        echo $json;
    }
 
    public function checkemailajaxAction()
    {
        $this->_helper->layout->disableLayout();
 
        $email = $this->getRequest()->getParam('email');
        $usersTable = new Users();
        $select = $usersTable->select();
        $select->where('email = ?', $email);
        $rows = $usersTable->fetchAll($select);
        if ($rows->count())
            $valid = false;
        else
            $valid = true;
 
        $this->_helper->viewRenderer->setNoRender(); 
        $data = array('valid'=>$valid);
        $json = Zend_Json::encode($data);
        echo $json;
    }

Here we want to return JSON encoded array with one boolean element - ‘valid’. So 1) we disable layout. 2) get the username or the email value which we have to check if is available 3) run the DB query 4) tell the view renderer action helper to not render any view 5) encode our answer and finally 6) print the answer to the client.

Now we will add ‘username_help’ and ‘email_help’ divs to the HTML markup of the registration form. Remember from our form.js that we write our help messages to this two divs. In the getRegistrationForm function in UserController.php we add this lines where we define the username and email fields:

$username->addDecorator(array('ajaxDiv' => 'HtmlTag'), array('tag'=>'div', 'placement'=>'append', 'id'=>'username_help', 'class'=>'errors'));
$email->addDecorator(array('ajaxDiv' => 'HtmlTag'), array('tag'=>'div', 'placement'=>'append', 'id'=>'email_help', 'class'=>'errors'));

The function is too big to paste here, and these are only 2 new lines, so I’ll better paste the context only:

$username->addValidator($validatorNotEmpty, true)->setRequired(true)->setLabel('Username')
        ->addFilter($filterTrim)
        ->addValidator($validatorAlnum)
        ->addValidator($validatorStringLength)
        ->addValidator($validatorUniqueUsername);                
        $username->addDecorator(array('ajaxDiv' => 'HtmlTag'), array('tag'=>'div', 'placement'=>'append', 'id'=>'username_help', 'class'=>'errors'));
        $form->addElement($username);
$email->addValidator($validatorNotEmpty, true)->setRequired(true)->setLabel('Email Address')
        ->addFilter($filterTrim)
        ->addValidator($validatorEmail)
        ->addValidator($validatorUniqueEmail);
        $email->addDecorator(array('ajaxDiv' => 'HtmlTag'), array('tag'=>'div', 'placement'=>'append', 'id'=>'email_help', 'class'=>'errors'));
        $form->addElement($email);

And finally we need to tell the browser to load jquery.js and form.js files. We will use headScript view helper for this. At the end of the registerAction() function add these two lines:

        $this->view->headScript()->appendFile('/js/jquery.js');
        $this->view->headScript()->appendFile('/js/register/form.js');

and in application/layouts/main.phtml in the head section of the html add this:

< ?php echo $this->headScript(); ?>

Now it should be working.

To say this again - if you are reading this and do not know anything about JQuery, but you are excited how with just 10 lines of javascript code and some very simple php functions we added AJAX functionality to our application - read some more about JScript. What we have done here is really simple, after you understand it you will understand the great power, that you have in your hands.

There. I hope you enjoyed our time together today. You know it seems harder and harder to just sit back and enjoy the finer things in life. Well, until next time…ta ta!

(oops, here is the code after this part: part6.rar)

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • bodytext
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google
  • Fark
  • Furl
  • Live
  • Ma.gnolia
  • Reddit
  • Spurl
  • Technorati