Building Sample Web Site Based On Zend Framework Series

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

Building Sample Web Site Based On Zend Framework Series
Zend Framework

Comments (21)

Permalink

Part 5 - user registration and activation finished. User login and logout. Zend_Auth.

In part 5 of this series we will finish the registration process (actually saving the user data to database), implement sending email with activation link, implement the actual activation of users. Then we will implement login and logout actions in the site, utilizing Zend_Auth. In the next part we will implement very simple access controll system - for now without Zend_Acl, because we still don’t have idea what resources and roles we will have in our system later. On this stage of the development we will have only ‘admin’ module, which can be accessed by users set as admin by flag in their profile. But more on this - later.

From part 4 we have registration form ready, but after submiting it - nothing happens, so new it is time to fill in some code to handle form submition. We will need new table in our DB:

CREATE TABLE `users` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(32) NOT NULL,
  `password` VARCHAR(64) NOT NULL,
  `email` VARCHAR(200) NOT NULL,
  `active` TINYINT(4) NOT NULL,
  `code` VARCHAR(40) NOT NULL,
  `last_login` DATETIME NOT NULL,
  `registered_on` DATETIME NOT NULL,
  `gender` ENUM('male','female') NOT NULL,
  `birthday` DATE NOT NULL,
  `real_name` VARCHAR(64) NOT NULL,
  `isAdmin` TINYINT(4) NOT NULL DEFAULT '0',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `username` (`username`),
  UNIQUE KEY `email` (`email`)
) ENGINE=INNODB;

Now we have to create our class Users, which will handle saving and retrieving user data to database.
Here is the code, comments are below:
application/models/Users.php:

< ?php
 
class Users extends Zend_Db_Table_Abstract  
{
    protected $_name = 'users';
 
    public static function computePasswordHash($password)
    {
        return hash('sha256', '21' . $password . 'eoka3b');
    }
 
     /**
     * Adds user to database, sends confirmation email
     *
     * @param string $username
     * @param string $password
     * @param string $email
     * @throws Zend_Db_Statement_Exception
     * @return int The id of the new user     
     */    
    public function add($username, $password, $email)
    {
        $activationCode = sha1(uniqid('xyz', true)); 
 
        $newUser = array(
            'username' => $username,
            'password' => $this->computePasswordHash($password),
            'email' => $email,
            'active' => 0,
            'code' => $activationCode,
            'registered_on' => new Zend_Db_Expr('NOW()')        
        );
 
        $userId = $this->insert($newUser);
 
        // send activation email
        $mailer = new Mailer();        
        $languageCode = (string) Zend_Registry::get('Zend_Translate')->getAdapter()->getLocale();               
        $activationLink = Zend_Registry::get('configuration')->general->url . '/user/activate/' . $userId . '/' . $activationCode;
        $mailer->sendRegistrationMail($email, $username, $activationLink, $languageCode);        
 
        return $userId;        
    }
 
    public function editProfile($userId, $profileData)
    {
        $userRowset = $this->find($userId);
        $user = $userRowset->current();
        if (!$user)
        {
            throw new Zend_Db_Table_Exception('User with id '.$userId.' is not present in the database');
        }
 
        foreach ($profileData as $k => $v)
        {
            if (in_array($k, $this->_cols))
            {
                if ($k == $this->_primary)
                {
                    throw new Zend_Db_Table_Exception('Id of user cannot be changed');
                }
                // special case - hash have to be computed for password 
                if ($k == 'password')
                {
                    $user->password = $this->computePasswordHash($v);
                }
                else
                {
                    $user->{$k} = $v;
                }
            }            
        }
 
        $user->save();
 
        return $this;              
    }
}

our class extends Zend_Db_Table_Abstract, so it inherits from there the functionality to save and retrieve data to and from our database. Because we need some higher level of abstraction we introduce some new public functions here - to add new user and to edit user profile. Also we have function to compute password hash - we will save passwords in the database in hashed format - with sha256 algorithm, prepending and appending the password with some random-looking but fixed strings. The idea behind that is if someone somehow gets access to the database and gets the hashes of the passwords - to prevent him from easy cracking and getting the actual passwords.

Lets take closer look at our public function add() - it takes 3 parameters - $username, $password and $email - strings. The code is quite simple - we generate random string activation code, which we will send to the user via email, then we construct an array for inserting in the users DB table and actually inserting the new row. Finally we send the email. We use not-yet-existing class - Mailer and its method - sendRegistrationMail. We will write these shortly. Also we have added new configuration lines in our config.ini file, one of which is general.url - which takes value something like “http://example.com” - the base url of our site - we use this to generate absolute URLs. We do not use $_SERVER['HTTP_HOST'] here, because later we may have different servers for different types of resources (for example our images may be hosted on different web server with different domain or subdomain). Full listing of our new config.ini will follow after a while, when we discus the Mailer class.

One more function we have in Users.php to comment - editProfile. It takes $userId as parameter along with an array $profileData, which contains the new, updated fields of the profile to save. User Id cannot be changed with this function, so we have explicit check for this - if trying to change the id we throw exception. For each element of the array $profileData we check if it is valid column from our users table - we do this by testing

if (in_array($k, $this->_cols))

in $this->_cols property we have an array, inherited by our base class, containing all columns for the database table. This way we can change our underlaying database schema without need to change some hardcoded values in this class.
It is important to note also that these two functions - both editProfile and add - will throw Zend_Db_Exception if unique constraints are broken - so if trying to insert new row with used already username or email address, or updating a row, changing username or email address to existing in other record.

Now lets see our Mailer class and after this - the new config.ini file:
application/models/Mailer.php

< ?php
 
require_once 'Zend/Mail.php';
require_once 'Zend/Mail/Transport/Smtp.php';
require_once 'Zend/Registry.php';
 
class Mailer
{
    /**
     * Directory path, where mail templates are located
     *
     * @var string
     */
    protected $templatesDir = '';
 
    /**
     * Constructor for the class, provide directory path where mail templates are saved
     *
     * @param string $templatesDir Directory path, where mail templates are located
     */
    public function __construct($templatesDir = 'languages/mailtemplates')
    {
        $this->templatesDir = $templatesDir;
 
        $mailConfig = Zend_Registry::get('configuration')->mail;
        if ($mailConfig->smtp)
        {
            $transport = new Zend_Mail_Transport_Smtp($mailConfig->host, $mailConfig->smtpconfig->toArray());
        }
        else
        {
            $transport = new Zend_Mail_Transport_Sendmail();
        }
 
        Zend_Mail::setDefaultTransport($transport);
 
    }
 
 
    public function sendRegistrationMail($emailAddress, $name, $activationLink, $languageCode)
    {
        $translate = Zend_Registry::get('Zend_Translate');
        $sitename = Zend_Registry::get('configuration')->general->sitename;
 
        $templatePath = Zend_Registry::get('siteRootDir') . '/' . $this->templatesDir . '/registration/' . $languageCode . '.txt';
        if (!is_file($templatePath))
        {            
            throw new My_Exception('Missing template for registration mail - language code: '.$languageCode);
        }
 
        $templateTxt = file_get_contents($templatePath);
 
        //replace tags: [name], [sitename], [activation link]
        $templateTxt = str_replace('[name]', $name, $templateTxt);        
        $templateTxt = str_replace('[sitename]', $sitename, $templateTxt);
        $templateTxt = str_replace('[activation_link]', $activationLink, $templateTxt);
 
        $mailer = new Zend_Mail('utf-8');
        $mailer->addTo($emailAddress, $name);
        $mailer->setSubject(sprintf($translate->_('Confirm your registeration in %s'), $sitename));
        $mailer->setBodyHtml($templateTxt, 'utf8');
        $mailer->setFrom(Zend_Registry::get('configuration')->mail->from);
        $mailer->send();
    }
}

We introduce new directory here - languages/mailtemplates - there we will save the templates for our email messages, which our application sends. For every mail type (registration email, lost password email and so on) we have different file for each language of the site. The construction of Mailer constructs Zend_Mail_Transport class for sendmail or smtp according to our settings and sets it as a default transport for Zend_Mail. sendMailRegistration loads the according template and replaces some tags into it, then creates instance of Zend_Mail and sends the new email. Nothing complex. Here is the config.ini file:
configuration/config.ini

[main]

;General settings
general.sitename=ZFSite
general.url=http://example.com

;Database connection settings
db.adapter=Mysqli
db.params.host=localhost
db.params.username=zfsite
db.params.password=123456
db.params.dbname=zfsite

;Mail transport settings

;mail.smpt - when false - sendmail is used
mail.smtp=true
mail.host=127.0.0.1
mail.smtpconfig.name=localhost
mail.smtpconfig.port=25
;mail.smtpconfig.auth = plain | login | crammd5
mail.smtpconfig.auth=
mail.smtpconfig.username=
mail.smtpconfig.password=
mail.from=no-reply@example.com

And the simple code for My_Exception class:
library/My/Exception.php

< ?php
 
require_once 'Zend/Exception.php';
 
class My_Exception extends Exception
{}

Our mailing system supports both smtp and sendmail methods, and when using smtp we have to set also host, port, authentication if any.

Before we see the registerAction method we will see the updated version of getRegistrationForm method of UserController class:

    private function getRegistrationForm(Users $users)
    {
        $form = new Zend_Form();
 
        $form->setAction('/user/register')->setMethod('post')->setAttrib('id', 'register');
 
        $filterTrim = new Zend_Filter_StringTrim();
        $validatorNotEmpty = new Zend_Validate_NotEmpty();
        $validatorNotEmpty->setMessage(__('This field is required, you cannot leave it empty'));
 
 
        $username = new Zend_Form_Element_Text('username');
        $validatorAlnum = new Zend_Validate_Alnum();
        $validatorAlnum->setMessage(__('You can use only latin letters and numbers'));
        $validatorStringLength = new Zend_Validate_StringLength(3, 32);
        $validatorStringLength->setMessages(array(
        Zend_Validate_StringLength::TOO_SHORT => __('Your username have to be between 3 and 32 symbols long'),
        Zend_Validate_StringLength::TOO_LONG => __('Your username have to be between 3 and 32 symbols long'),
        )
        );
        $validatorUniqueUsername = new My_Validate_DbUnique($users, 'username');
        $validatorUniqueUsername->setMessage(__('This username is already registered, please choose another one.'));
        $username->addValidator($validatorNotEmpty, true)->setRequired(true)->setLabel('Username')
        ->addFilter($filterTrim)
        ->addValidator($validatorAlnum)
        ->addValidator($validatorStringLength)
        ->addValidator($validatorUniqueUsername);
        $form->addElement($username);
 
        /**
         * @todo Change this wired error messages to something more user friendly, or even use simple email regex matching validator
         */
        $email = new Zend_Form_Element_Text('email');
        $validatorHostname = new Zend_Validate_Hostname();
        $validatorHostname->setMessages(
        array(
        Zend_Validate_Hostname::IP_ADDRESS_NOT_ALLOWED  => __("'%value%' appears to be an IP address, but IP addresses are not allowed"),
        Zend_Validate_Hostname::UNKNOWN_TLD             => __("'%value%' appears to be a DNS hostname but cannot match TLD against known list"),
        Zend_Validate_Hostname::INVALID_DASH            => __("'%value%' appears to be a DNS hostname but contains a dash (-) in an invalid position"),
        Zend_Validate_Hostname::INVALID_HOSTNAME_SCHEMA => __("'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'"),
        Zend_Validate_Hostname::UNDECIPHERABLE_TLD      => __("'%value%' appears to be a DNS hostname but cannot extract TLD part"),
        Zend_Validate_Hostname::INVALID_HOSTNAME        => __("'%value%' does not match the expected structure for a DNS hostname"),
        Zend_Validate_Hostname::INVALID_LOCAL_NAME      => __("'%value%' does not appear to be a valid local network name"),
        Zend_Validate_Hostname::LOCAL_NAME_NOT_ALLOWED  => __("'%value%' appears to be a local network name but local network names are not allowed")
        )
        );
 
        $validatorEmail = new Zend_Validate_EmailAddress(Zend_Validate_Hostname::ALLOW_DNS, false, $validatorHostname);
        $validatorEmail->setMessages(
        array(
        Zend_Validate_EmailAddress::INVALID            => __("'%value%' is not a valid email address"),
        Zend_Validate_EmailAddress::INVALID_HOSTNAME   => __("'%hostname%' is not a valid hostname for email address '%value%'"),
        Zend_Validate_EmailAddress::INVALID_MX_RECORD  => __("'%hostname%' does not appear to have a valid MX record for the email address '%value%'"),
        Zend_Validate_EmailAddress::DOT_ATOM           => __("'%localPart%' not matched against dot-atom format"),
        Zend_Validate_EmailAddress::QUOTED_STRING      => __("'%localPart%' not matched against quoted-string format"),
        Zend_Validate_EmailAddress::INVALID_LOCAL_PART => __("'%localPart%' is not a valid local part for email address '%value%'")
        )
        );
        $validatorUniqueEmail = new My_Validate_DbUnique($users, 'email');
        $validatorUniqueEmail->setMessage(__('This email address is already registered, please choose another one.'));
        $email->addValidator($validatorNotEmpty, true)->setRequired(true)->setLabel('Email Address')
        ->addFilter($filterTrim)
        ->addValidator($validatorEmail)
        ->addValidator($validatorUniqueEmail);
        $form->addElement($email);
 
        $password = new Zend_Form_Element_Password('password');
        $password->addValidator($validatorNotEmpty, true)->setRequired(true)->setLabel('Password')
        ->addValidator(new Zend_Validate_StringLength(3));
        $form->addElement($password);
 
        $password2 = new Zend_Form_Element_Password('password2');
        $validatorPassword = new My_Validate_PasswordConfirmation('password');
        $validatorPassword->setMessage(__('Passwords do not match'));
        $password2->setLabel('Confirm Password')->addValidator($validatorPassword);
        $form->addElement($password2);
 
        $gender = new Zend_Form_Element_Select('gender');
        $gender->setLabel('Gender')
        ->addMultiOption('',' ')->addMultiOption('male',__('male'))->addMultiOption('female',__('female'));
        $form->addElement($gender);
 
        $date = new My_Form_Element_DateSelects('birthday');
        $validatorDate = new Zend_Validate_Date();
        $validatorDate->setMessages(
        array(
        Zend_Validate_Date::NOT_YYYY_MM_DD => __("'%value%' is not of the format YYYY-MM-DD"),
        Zend_Validate_Date::INVALID        => __("'%value%' does not appear to be a valid date"),
        Zend_Validate_Date::FALSEFORMAT    => __("'%value%' does not fit given date format")
        )
        );
        $date->setLabel('Birthdate')->addValidator($validatorDate);
        $date->setShowEmptyValues(true)->setStartEndYear(1900, date("Y")-7)->setReverseYears(true);
 
        $form->addElement($date);
 
        $realName = new Zend_Form_Element_Text('realname');
        $realName->setLabel('Real Name')->addFilter($filterTrim);
        $form->addElement($realName);
 
        $validatorNotEmptyAgreement = new Zend_Validate_NotEmpty();
        $validatorNotEmptyAgreement->setMessage(__('You have to accept our terms and conditions before you register'));
        $agreement = new Zend_Form_Element_Checkbox(