An Introduction: Dominating the User Login Form

In this first tutorial, you will learn how to:

  1. override the default presentation of the user login form, and create a custom template for it.
  2. pass an editable node into the user login form
  3. alter the form’s values (such as text, and instructions)
  4. make the form available to page.tpl.php — as though it was something as simple as a footer.

Moreover, I will show you how bloody simple it is. Folks who follow this tutorial should already have:

  1. knowledge of PHP
  2. the ability to lie about a lack of knowledge of PHP
  3. some familiarity of the kindergarden basics of drupal theming
  4. Be working with drupal 4.7+

Step One: Override the deafult user login form

Overriding is always done in the theme’s template.php file (if you don’t have a template.php file, you may create a it now). Obviously, before you can override anything, you must first locate what you are trying to override. The best way to do this is to search a module for the $form variable.

Of course, the user module has no shortage of forms. However, eventually we come across function user_login:

(modules/user.module on line 868).

<?php
function user_login($msg = '') {
//…(omitted scary code)
 
return drupal_get_form('user_login', $form);
}
?>

So there you have it, the top and the bottom of the function. That’s all you really need to override it. Here’s the code:

template.php

<?php
function phptemplate_user_login($form) {
  return
_phptemplate_callback('user_login', array('form' => $form));
}
?>

Notice the scary looking _return_phptemplate_callback(‘user_login’,array(‘form’ => $form));? Oooohhh… scary… gonna cry? Your choice is simple, either you can hide under a blanket, or you can take a look at the pattern:
_return_phptemplate_callback(‘[the name of your .tpl.php file]’,array(‘[the name of the variable sent to your .tpl.php file]’ => [‘the actual variable that was returned from the function this code calls’]));

Before moving on, I want to remind you that you aren’t stupid. This doesn’t make immediate sense to anyone. So relax, and look for patterns in colors: they hold some big keys.

I’m now going to ask you to create a new tpl.php file called user_login.tpl.php.

So just to review your theme’s files should be:
/themes/yourtheme/template.php
/themes/yourtheme/user_login.tpl.php

user_login.tpl.php

Insert the code:

<h1>Hello world!</h1>
<?php
    print_r
(form_render($form));
?>

If all has gone according to plan, www.example.com/user/login now greats you with:

Amazing how sometimes, things can just start to make sense. YOu don’t need to understand it; you don’t even need to agree with it. Just accept it, and continue.

Part 2: Node the form up

The user login form usually needs some additional info, or links that drupal does not provide by default. If you were an amateur, you’d probably hardcode that extra info into the theme. However, since you’re an expert, you’re going to stick an editible node into the login area instead. Title your node, “Join the Revolution”, and stick this in the body:

<p>"The temptation to quit will be greatest just before you are about to succeed."
<em>-an old Chinese saying</em></p>

Now, let’s stick that node in the login form. This is going to be a two part process

template.php: make the node available to the login form

<?php
function phptemplate_user_login($form) {
       
/* PAY ATTENTION, $login_node is a fully loaded node.
        Note that in my tutorial site's case, the node's ID was 1
        (the URL is example.com/node/1… …"1" = my node's ID).
        */
 
$login_node = node_load(array( 'nid' => 1));
  return
_phptemplate_callback('user_login', array(
       
'form' => $form,
       
// LOOK! this sends the $login_node variable to user_login
       
'login_node' => $login_node
   
));
}
?>

user_login.tpl.php: print the newly available variables.

<h1><?php print $login_node->title; //Yes, that was the node's title: ?></h1>
<?php
   
//Yes, that is the node's body:
   
print $login_node->body;
   
//try uncommenting this code below to see what else is available:
    // print "<pre>"; print_r(get_object_vars($login_node));print "</pre>";
?>

<?php
    print_r
(form_render($form));
?>

And like magic, there is your node — AND it can be changed by a non-programmer.

Part 3: Alter the form

Say we wanted to display our form horizontally. CSS could do most of the heavy lifting. Unfortunatly, the descriptions (e.g. “enter your username here”) kind of get in the way. If we were amateurs, we’d copy and paste the form directly into our template, and call it a solution. But remember kids, we’re experts and do things the right way ;-).

The array_values for the username and password forms are as follows:

[13] => Array
        (
            [#type] => password
            [#title] => Password
            [#description] => KILL ME KILL ME KILL ME
            [#required] => 1
            [#attributes] => Array
                (
                    [tabindex] => 2
                )

            [#tree] => 
            [#parents] => Array
                (
                    [0] => pass
                )

            [#weight] => 0.001
            [#input] => 1
            [#size] => 30
            [#name] => edit[pass]
            [#id] => edit-pass
            [#value] => 
        )

As you can see, #description really wants to be killed in this case. As you are no doubt aware, this is going to take a lot of work to do… I mean, look WHAT we have to do to kill it.

Template.php

<?php
function phptemplate_user_login($form) {
 
$login_node = node_load(array( 'nid' => 1));
 
//We're removing the description text before the form is rendered
 
$form['pass']['#description'] = '';
 
$form['name']['#description'] = '';
 
//A friendlier name for our submit button:
 
$form['submit']['#value'] = 'Get yer ass in here';
  return
_phptemplate_callback('user_login', array(
       
'form' => $form,
       
'login_node' => $login_node
   
));
}
?>

You’ll also note that we changed the submit button to “get yer ass in here”. That is because we understand the importance of friendly interfaces. Now let’s rearrange our user_login.tpl.php file, and add some CSS:

user_login.tpl.php

<div id="user_login">
<h1><?php print $login_node->title; //Yes, that was the node's title: ?></h1>
<?php
//try uncommenting this code below to see what else is available:
// print "<pre>"; print_r(array_values($form));print "</pre>";
   
print_r(form_render($form['name']));
   
print_r(form_render($form['pass']));
   
print_r(form_render($form));
?>

<?php
//Yes, that is the node's body:
print $login_node->body;
//try uncommenting this code below to see what else is available:
// print "<pre>"; print_r(get_object_vars($login_node));print "</pre>";
?>

</div>

And now the CSS… put it in style.css for now:
#user_login {
font-size:11px;
        padding:6px 12px;
}
#user_login p {
font-size:9px;
}
#user_login h1,
#user_login .form-item,
#user_login label,
#user_login input.form-text {
display:inline;
}

The Final, Step

Folks, this is where it gets hard. Now, we want to display our new login form on every page. Be strong, you can do this: 1)open up page.tpl.php, and insert the following code directly below the “body” html tag.

<body>
<?php print $user_login_form;?>

Now we add the final step, we call the user_login() function from template.php, and pass it to page.tpl.php. Here it is.

template.php

<?php
function phptemplate_user_login($form) {
 
$login_node = node_load(array( 'nid' => 1));
 
//We're removing the description text before the form is rendered
 
$form['pass']['#description'] = '';
 
$form['name']['#description'] = '';
 
//A friendlier name for our submit button:
 
$form['submit']['#value'] = 'Get yer ass in here';
  return
_phptemplate_callback('user_login', array(
       
'form' => $form,
       
'login_node' => $login_node
   
));
}
// Will explain later.
function  _phptemplate_variables($hook, $vars) {
  global
$user;
  switch(
$hook) {
          case
'page' :
       
// SUPER IMPORTANT: These rules prevent you from crashing your server :-)
       
if (arg(0) != "user" && $user->uid == "0") {
           
$vars['user_login_form'] = user_login($msg = '');
        }
        break;
  }
  return
$vars;
}
?>

VERY IMPORTANT: YOU MUST DISABLE ANY ACTIVE user login BLOCKS — otherwise, it won’t work.

And the final product:

Final Word

As the title of this tutorial suggests, this is merely an introduction. Expect part 2 in the near future.

Questions are encouraged. Also, I’ve provided a working version of this tutorial’s code.

Download a working theme based on this tutorial

Comments

Note that "form_render is

Note that "form_render is depreciated in Drupal 5.0 in favour of drupal_render. Replace the call to form_render with a call to drupal_render."

http://drupal.org/node/107459

---------------
Tnanks for all the info nad help Nick!

Thanks for helping me update

Thanks for helping me update it!

no display at top of page

The following code:

<?php print $user_login_form;?>

doesn't print a login form at the top of my page like yours. I disabled all login blocks but nothing prints out, not even an error message.

same here..

Actually, the code prints just this: "Array" at the top of the page. If you change <?php print $user_login_form;?> to <?php print_r($user_login_form); ?>, you'll see that the entire form object gets printed, which means we're not doing it right :( Probably this is due to differences between Drupal versions we use and the version Nick was using when he wrote this. Nick, we'd appreciate it a bunch if you shed some light :-)

webforms help

the web forms works in this manner and displays the fields in this manner below...

Label 1 :
Textbox 1
Label 2 :
Textbox 2

It would be great if I can just adjust it to be...so plzz can any one help so as to change it to this manner

Label 1 : Textbox 1
Label 2 : Textbox 2

"Download a working theme based on this tutorial" link missing

Hi, it looks like the above link at the bottom of the article is missing. Could someone repost the link.

Thanks,

Nearly working...

I've got this to work up until the very last addition to template.php which then breaks the whole site. Any obvious reasons why this might happen with Drupal 5.2? I've changed all the form_render's... I could really use this if I can just get it finished.

i dont think the userlogin

i dont think the userlogin is themed now :). Can't drupal developers write an acceptable theming system for the end users? (like wordpress or ee)

Part 2: Node the form up

I'm new to Drupal, so I never experienced the old Drupal setup+names. If I read it right Drupal 5+ has renamed some things. Like nodes are named different since Drupal 5 (correct me if I'm wrong :) ).

In "Part 2: Node the form up" it says to create a node, but which option/item do I need to add? Is it equivelant to a page/story/block/etc? I'm stuck atm at that point :( 'cause I don't know how to add a node to the loginpart...

N00b here using Drupal 5.2

N00b here using Drupal 5.2 and trying your tutorial. It works great, right up until the

<?php
print $user_login_form;
?>

line in page.tpl.php - then it simply outputs the word Array, right under the body tag where the tutorial instructs you to put it.

"Ah ha!" I thought... this is because I need to drupal_render it... Nope. If I use drupal_render($user_login_form) then I get warnings from form.inc, probably because of the user login template file already drupal_render'ing the login form? So I commented out print_r(drupal_render($form)); in user_login.tpl.php and it almost works, except now it renders everything but the tag so the form cannot be submitted.

D'oh!

I'm sure I'm missing something stupid here?? I can't work out how to get this to function as it does in Drupal 4.7...

Same Problem

I'm stuck at this point as well!
print $user_login_form outputs 'Array', so then I tried print_r($user_login_form), which yielded a 3-part array with only username,password and submit in it, this doesn't look at all like the array I was working with in user_login.tpl.php.

Maybe we need a different variable name in the latest drupal 5.2? I also had to substitute form_render with drupal_render everywhere...

what about garland theme?

I have been trying to alter the Garland theme for Drupal 5.1. I have managed to style the login but I wanted to load an entirely new page in (aka Advanced Front Page module) that doesn't have the page styling around the login box. Is there any way to do this?

At the moment it's a login box that inherits the Garland theme ie fonts etc. and in contained within the Garland frame set.

I want to have a login page that only displays the template and its' styles and does not inherit Garland's styles.

This is the function that I'm using in the template file

function phptemplate_user_login($form) {
return _phptemplate_callback('page-login', array('form' => $form));
}

This function crashed Drupal:

function _phptemplate_variables($hook, $variables = array()) {
switch ($hook) {
case 'page':
global $user;
if (arg(0) == 'user'){
if (!$user->uid) { //check to see if the user is logged in. If not display the special login page layout
$variables['template_file'] = 'page-login';
}
elseif (arg(1) == 'login' || arg(1) == 'register' || arg(1) == 'password' ) {
$variables['template_file'] = 'page-login';
}
elseif (arg(0) == NULL || arg(1) == NULL){ //check to see if the user name has been entered
$variables['template_file'] = 'page-login';
}
}
break;
}

return $variables;
}

form da form

This tutorial is quite interesting and it allows to create a completely custom login page.

I would just like to add a couple of considerations:

1) Same logic can be applied to user-login-block!

2) Even if it does the trick,

Try and look at this:

function user_login_block() {
  $form = array(
    '#action' => url($_GET['q'], drupal_get_destination()),
    '#id' => 'user-login-form',
    '#base' => 'user_login',
  );
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
    '#maxlength' => USERNAME_MAX_LENGTH,
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
    '#maxlength' => 60,
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['submit'] = array('#type' => 'submit',
    '#value' => t('Log in'),
  );
  $items = array();
  if (variable_get('user_register', 1)) {
    $items[] = l(t('Create new account'), 'user/register', array('title' => t('Create a new user account.')));
  }
  $items[] = l(t('Request new password'), 'user/password', array('title' => t('Request new password via e-mail.')));
  $form['links'] = array('#value' => theme('item_list', $items));
  return $form;
}

This is the user_login_block function in Drupal 5 (should be more or less the same in 4.7)

This function returns a $form array which holds some pre-cooked values. Believe it or not, customers are more than willing to make a mess of your life. For instance, they would like to "change" the Create new account string. There are two methods for doing so:

Method a: at the very beginning of your work, create a dupe of the english language localization and change all the strings according to your customers' mood.

Method b: you're at the end of your work, your customer has entered tons of contents in a multilanguage installation, and NOW it looks like the "Create new account" doesn't properly convey the message of a prompt and easy user registration. So, let's change it, it will not make you lose your nights. You HAVE TO OVERRIDE system function.

By the way, method b is preferrable in another situation: let's say you're translating a string in this place, and the same string should not be replaced elsewhere... there's no escape to method b.

So, at this very point, let's tweak phptemplate_user_login_block function like this:

function phptemplate_user_login_block($form) {
$items = array();
  if (variable_get('user_register', 1)) {
    $items[] = l(t('Register'), 'user/register', array('title' => t('Create a new user account.')));
  }
  $items[] = l(t('Forgot your password?'), 'user/password', array('title' => t('Request new password via e-mail.')));
  $form['links'] = array('#value' => theme('item_list', $items));

  return _phptemplate_callback('user-login-block', array('form' => $form));
}

Simply grab the desired piece of code from the original user_login_block function and replace it with your desired values.

What about BUTTON instead of SUBMIT?

For styling reasons, I want to use this article.
See also push my button.

Great tutorial btw!

awesome tutorial, now as to placement...

Nick, you've written a terrific tutorial. Thanks so much. Easy to follow and it worked like a charm. I've now got a full theme-able login box. Tremendous.

My question though: could you give me the quick rundown on how I can place this in my theme as if it was a block? To be more specific, I want the login to be in my sidebar in amongst the blocks the populate it, second from the top actually (which is an ad unit). So while its great that I've now got this block that I can call at will and have on all pages, how can I get it to show up inside a list of blocks?

I was thinking of just leaving the default login block enabled and having it call my new node/login mash-up, but then I don't really have full theme control of the box that shows up as it pulls in my block settings.

Am I making sense? How would you do this? Thanks. I'm a Drupal dummy with an extensive Wordpress background and your tutorials are exactly what I was looking for to get started parsing through how drupal works.

form_render() function is now drupal_render() in 5.0+

Please note that the form_render() function has been "rendered" obsolete in drupal 5.x. It is now drupal_render() (issue source)

Drupal 5

Drupal 5 users should note form_render is now drupal_render.

r0g.

Well I guess as you get

Well I guess as you get older (hahahahaha) its only family that remembers special occasions. And yes to Vicki and my amazing boyfriend, you are my family! Especially at this time family is the most important

Got a question and a slight problem.

That slight problem being that the template zip doesn't seem to want to download correctly...Not a big deal since the tutorial worked fine. My only question pertains to the "create a new user", and "Request password via e-mail" links. I wanted them to either go inline with the form, or move them next to the login/password fields. I assumed that the css class .item-list, would control this behaviour, but it doesn't (or I am not doing something right...). Anyone have any ideas?

You're problem is that you

You're problem is that you assume things make sense. The sooner you get out of this habit the better for you. Those links are included on the user modules hook_block() function. However, they are not included in the form. My advice is to simply hardcode these links into the form's $form['#prefix']. or $form['#suffix'] array, and since you've hardcoded them, you'll theoretically have as much control as you want. Let me know if not a word I said made sense.

user messages not being handled?

Here's the issue I've run into... When a user submits an incorrect user/pass pair... no message is displayed... the first time. The second time an incorrect user/pass pair is entered... a message *is* displayed. I tried debugging this... but my drupal knowledge is a little too thin. Thoughts?

user messages not being handled? I have the same problem

hi,
im having the same problem.
did someone manged to solve it???

Just Wondering

Is there something wrong with the code snippets here? I only see partial code, not in a code box like in the other parts.

There was something very

There was something very wrong, I accidently used TinyMCE to make a small change, and in turn destroyed the code examples. Thank god for google cache!

Duplicate IDs

Nothing big, but when you add this <div id="user_login"> to user_login.tpl.php you should choose an ID other than "user_login". The form for the user/pass has the ID "user_login" resulting in duplicate IDs. There are bigger problems in Life than dupe IDs, but why not just use <div id="user_login_wrapper">, <div id="user_login_container"> or something else unique.

Heh… you’re right. I

Heh... you're right. I didn't actually see that it outputted an extra div id "user_login". I actually just used that div because I'm easily confused, and tend to name CSS classes after the php variables for consistancy. Its always scary for me to realize that other people read things as closely as I do.

Oops! Can't get part 2 to work.

It just ignores me. I'm a total newbie. I created a story with title and content. Then I created two files, template.php and login_form.tpl.php . With contents copied and pasted from your tutorial. I changed the node number to 2, which was the node number of the story. Nada. Zip. Have tried this in 4.7.2. My friend tried it in 4.7.0. Then I downloaded your tutorial files and while I get the change in the button text, I don't get any other changes. Have tried this only in 4.7.2. I haven't gone any further. Was successful with 'Hello World'. Is there something that being an utter newbie is really obvious to you seasoned coders that I didn't get?

Then I created two files,

Then I created two files, template.php and login_form.tpl.php . With contents copied and pasted from your tutorial. I changed the node number to 2, which was the node number of the story.

You got the first one template.php right. Try renaming "login_form.tpl.php" to "user_login.tpl.php". I realize I specified otherwise. As a newbie, the best lesson I can teach you is that the so called experts are even more incompetent than you.;-)

(I just fixed the typo that is the cause of your error.) 

4.7

can you tell me why 4.7 is required? You'd think it'd be the same function, and same templating system (phptemplate) in 4.6? Thanks!

4.6 does not have the forms

4.6 does not have the forms API. The tutorial, in particular, is completely dependent on the forms api.

why this works

The above tutorial is a lot of fun, but leaves out an important point (at least something I just discovered by looking at api.drupal.org)- the reason that: function phptemplate_user_login($form) works is that a default theme function name is constructed from the form ID unless the #theme element of the form is set.

But why oh why?!

Is it a mistake or is it actualy necessary to put a print_r before form_render? print_r(form_render($form)); OR form_render($form); ??

print_r() or apparently

print_r() or apparently print() -- but yes, you do have to print the output if you are rendering the form via phptemplate.

stupid question

stupid question: got a screenshot of what it looks like after login? ps - I think you did/didn't close a div or something on this page because the book nav is hugenormous. At first I thought you were getting all web 2.0 on us.

Dude: man, I am web 2.0!* 

Dude: man, I am web 2.0!* 

Let me guess...

Let me guess: You have a different tpl.php for any node that has "Dominating" in the title with special hooks so you can make your fonts bigger?

You’ve guess wrong. but

You've guess wrong. but come to think of it, that's a good idea!