If you want to use Habari as a platform rather than just a blog, as I talked about in my previous post, it's likely you'll come across the situation where you want people to be able to register with your site. To allow this, I've written a simple registration plugin. Activate the plugin and put $theme->registration('group_name') in your template, and you'll get a form for new users to sign up, and they'll be placed in whatever group you specify, in less than 90 lines of code.

As usual, the plugin, which I've creatively named Register Plugin, is in the -extras repository, but I thought I'd talk about what it involves.

First up, when the plugin is activated, we create a UUID, a random string, that we'll use as a secret key. We store this as a site option, and we'll use it a bit later for hashing group names.

public function action_plugin_activation( $plugin_file ) { if ( Plugins::id_from_file(__FILE__) == Plugins::id_from_file($plugin_file) ) { // Store a secret key for hashing group names Options::set('register_secret', UUID::get()); } }

Next we use the FormUI class to create a registration form.

public function get_form( $group ) { // Create the registration form, and add a CSS class $form = new FormUI('registration'); $form->class[] = 'registration';

// Add a field for the email address // The 'null:null' tells FormUI not to store the submitted values $form->append('text', 'email', 'null:null', _t('Email'), 'formcontrol_text'); // Get FormUI to make sure it's a valid address $form->email->add_validator('validate_email'); // Add a field for the username $form->append('text', 'username', 'null:null', _t('Username'), 'formcontrol_text'); // Get FormUI to make sure the username is filled in and available $form->username->add_validator('validate_required'); $form->username->add_validator('validate_username'); // Add a password field, which is required $form->append('text', 'password', 'null:null', _t('Password'), 'formcontrol_text'); $form->password->add_validator('validate_required');

// Store the group to be added, all secreted up $form->append('hidden', 'group_name', 'group_name'); $group_hash = md5($group); $form->group_name->value = $group_hash; $form->append('hidden', 'group_digest', 'group_digest'); $form->group_digest->value = $this->hmac($group_hash); // Create the Register button $form->append('submit', 'register', _t('Register'), 'formcontrol_submit');

// Tell FormUI to process the submitted form // with the register_user function in this class $form->on_success( array( $this, 'register_user' ) ); // Return the form object return $form; }

Most of that is pretty straightforward FormUI stuff, which is documented on the wiki (though I do love how easy validation is). The interesting part is were we secret up the group name, because of course we don't want to have people adding themselves to the admin group. We send a hash of the group name, along with a digest of the hash. We create the digest as follows.

function hmac($data) { return md5(md5($data) . Options::get( 'title' ) . md5(Options::get('register_secret'))); }

We hash the data and secret, with a little salt, for which we've used the blog title. We send the digest and the hashed group name with the form, and test it when we get it back, like this.

public function register_user( $form ) { // Get all the groups $allowed_groups = array(); foreach ( UserGroups::get_all() as $group ) { $allowed_groups[] = $group->name; } // Make them accessible via the hashed group name $allowed_groups = array_combine( array_map('md5', $allowed_groups), $allowed_groups ); // Recreate the digest of the submitted group // If it matches the digest, we're all good $group = ''; if ($this->hmac($form->group_name->value) == $form->group_digest->value) { $group = UserGroup::get($allowed_groups[$form->group_name]); } else { die('You naughty hacker!'); }

// Create a new user, with the submitted data $user = new User( array( 'username' => $form->username, 'email' => $form->email, 'password' => Utils::crypt( $form->password ) ) ); // Add the user to the group if ( $user->insert() ) { $group->add($user); Session::notice( sprintf( _t( "Added user '%s'" ), $form->username ) ); } else { $dberror = DB::get_last_error(); Session::error( $dberror[2], 'adduser' ); } }

And the magic bit that makes it available as a theme function is this.

public function theme_registration( $theme, $group ) { $this->get_form($group)->out(); }

As I said, it's very simple at the moment. The only complexity is in all the hashing, for which I thank Matt Read (aka BigJibby).

Other things that I might add include moderated registrations, email confirmation, optionally disallowing users to be added to groups that have super user access, and a registration content type so that you don't have to edit templates at all. Hopefully I haven't made the simple plugin sound complex.

[Update: As Owen points out in the comments, as we can set options in the FormUI object that are stored locally and not tainted as user input, we can safely store the group name there, rather than passing it to the user encrypted. The registration plugin in the Habari extras repository now does this. Thanks, Owen.]