Advanced Search

View RSS Feed

traq

HedgeClipper [part 2]!

Rate this Entry
Hey guys, I hope everyone has had a great start to the new year.

If you're just joining us, we're making hedgeclipper: a user log-in system. Last time, we outlined the basics of how hedgeclipper will work, and wrote a controller function for the whole thing. This time, we're going to focus on checking if the user is logged in or not. Before we start demanding a username and password, we're going to check if our user already logged in on a previous page visit. Have a look:
PHP Code:
/**
 * this function checks if a user is currently logged in.
 *  if so, it sets up the user's info for hedgeclipper to use.
 * 
 * @return bool                 true if the user is logged in; false otherwise
 */
function hedgeclipper_checkLoggedIn(){
    
# hedgeclipper info is stored in session, so make sure one has been started.
    
if( !session_id() ){ session_start(); }

    
# check if there are any hooks to do
    
hedgeclipper_doHook'checkLoggedIn_start' );

    
# check if hedgeclipper info exists:
    
if( !empty( $_SESSION['hedgeclipper'] ) ){
        
# pull the data from the session;
        # hedgeclipper_userinfo() validates and stores it
        
if( hedgeclipper_userinfo$_SESSION['hedgeclipper'] ) ){
            
# now we can check if the user has logged in or not:
            
if( # start with a basic login check
                
hedgeclipper_userinfo'auth','last_login' ) > 
                
# and see if there's a hook also
                
&& hedgeclipper_doHook'checkLoggedIn_success' )
            ){
                
# all good.
                
return true;
            }
        }
    }
    
# if we get to this point, either the user is not logged in, or something went wrong.
    
hedgeclipper_doHook'checkLoggedIn_fail' );
    return 
false;

Hedgeclipper stores user info in the $_SESSION superglobal, so basically, this function just checks if there is any user login info present. last_login contains a unix timestamp, set when the user last logged in - if it's 0, the user hasn't logged in yet. If it's greater than 0, then we have a record of the user logging in, and we don't need to ask again.

There are several spots in this function where we call another function called hedgeclipper_doHook(). This is fun: if there is a function in the hedgeclipper options tied to a particular event, this will run that function and return the result. This allows us to add custom functionality without rewriting (and possibly breaking!) the actual hedgeclipper functions.

For example, we might want hedgeclipper to check not only if the user logged in, but that they logged in recently. No problem!
PHP Code:
# EXAMPLE USAGE - not part of hedgeclipper #
// write a custom check function:
function check_recentLogin(){
    
# check if login was within last hour
    
if( hedgeclipper_userinfo'auth','last_login' ) > time() + 60*60 ){
        return 
true;
    }
    
# not recent...  :(
    
return false;
}

// register our "hook" in options, under the "checkLoggedIn_success" event:
hedgeclipper_options( array( 'hooks'=>array( 'checkLoggedIn_success'=>'check_recentLogin' ) ) ); 
Now, if the user logged in more than an hour ago, this function will return FALSE and the login check will fail.

Another function that both these functions call is hedgeclipper_userinfo(). This one work similarly to the hedgeclipper_options() function, except that it doesn't manage options: it manages information about the user.

There are three categories of information to manage:
  • auth. Info about authentication (login status) and authorization (permissions).
  • user. Info about the user specifically (e.g., their name, email, etc.).
  • app. Arbitrary info that's not specifically about the user or their authorization, but you want to associate with the user and their session. User preferences might be a good example of info to store here.
PHP Code:
/**
 * this function organizes and stores all the info hedgeclipper needs.
 *
 * @param string $mode          determines category to use: "auth","user","app"
 *                              also accepts "all" to get the whole $userinfo array.
 * @param string|array $info    to *get* info, pass the key name
 *                              to *set* info, pass an array with "key"=>"new info"
 * @return string|bool          the info if requested, or TRUE if new info was set;
 *                              FALSE otherwise
 */
function hedgeclipper_userinfo$mode,$info=null ){
    
# static containers for each userinfo category
    
static $auth;
    static 
$user;
    static 
$app;

    
# set defaults for each userinfo category
    
if( empty( $auth ) ){ $auth hedgeclipper_userinfo_auth_default(); }
    if( empty( 
$user ) ){ $user hedgeclipper_userinfo_user_default(); }
    if( empty( 
$app  ) ){ $app  hedgeclipper_userinfo_app_default();  }

    
# check $mode:
    
switch( $mode ){
        
# use $auth array:
        
case 'auth':
            
# if $info is the name of an $auth key, return that item's value.
            
if( is_string$info ) && array_key_exists$auth[$info] ) ){
                return 
$auth[$info];
            }
            
# if $auth contains the name of an $auth key and a value, set the key to that value.
            
if( is_array$info ) && (count$info ) == 2) && array_key_exists$auth[$info[0]] ) ){
                
$auth[$info[0]] = $info[1];
                return 
true;
            }
            
# otherwise, return false.
            
return false;
        
# use $user array:
        
case 'user':
            
# works the same way as "auth".
            
if( is_string$info ) && array_key_exists$user[$info] ) ){
                return 
$user[$info];
            }
            if( 
is_array$info ) && (count$info ) == 2) && array_key_exists$user[$info[0]] ) ){
                
$user[$info[0]] = $info[1];
                return 
true;
            }
            return 
false;
        
# use $app:
        
case 'app':
            
# works the same way, but allows new keys to be set arbitrarily.
            
if( is_string$info ) && array_key_exists$app[$info] ){
                return 
$app[$info];
            }
            if( 
is_array$info ) && (count$info ) == 2) ){
                
$app[$info[0]] = $info[1];
                return 
true;
            }
            return 
false;
        
# use all:
        
case 'all':
            
# set all new userinfo
            
if( is_array$info ) && isset( $info['auth'] ) && isset( $info['user'] ) && isset( $info['app'] ) ){
                
$auth $info['auth'];
                
$user $info['user'];
                
$app  $info['app'];
                return 
true;
            }
            return array( 
$auth,$user,$app );
        
# do nothing:
        
default: return;
    }

There are a few functions that this function uses to set up default values for "auth", "user", and "app". This is also where you can "hook" your own functions, if you want to add to the default items. Because this post is too long already, I'll link to those functions so you can check 'em out.

The last bit of code we'll look at today is hedgeclipper_checkLoggedIn()'s complementary function: where checkLoggedIn gets info from the session, hedgeclipper_rememberLoggedIn() will store info in the session.
PHP Code:
/**
 * this function updates the php session with hedgeclipper's info.
 */
function hedgeclipper_rememberLoggedIn(){
    
# update the "last activity" time
    
hedgeclipper_userinfo'auth',array( 'last_activity','now' ) );

    
# save all hedgeclipper userinfo to session
    
if( !session_id() ){ session_start(); }
    
$_SESSION['hedgeclipper'] = hedgeclipper_userinfo'all' );

Let's take a quick break from coding and look at some good ol'fashioned HTML. We're going to put our HTML in "template" files - that way, we can keep it organized and we can also edit or replace individual parts easily.
hedgeclippertemplate.loginform.html
HTML Code:
<!--------------------------------------------
html markup for hedgeclipper login form.
    there are two "placeholders" in the markup.
    the hedgeclipper functions will look for and replace them with dynamic values:
    -- %action% will be replaced by the appropriate URL to submit the form to.
    -- %token% will be replaced by a unique security token to help prevent brute force attacks.
-------------------------------------------->
<form class="hedgeclipper_login" action="%action%" method="post">
    <fieldset>
        <legend>log in with <b>hedgeclipper</b></legend>
        <input type="hidden" name="hedgeclipper[token]" value="%token%">
        <p><label for="hedgeclipper[email]">Email: </label><input type="email" name="hedgeclipper[email]" required>
        <p><label for="hedgeclipper[password]">Password: </label><input type="password" name="hedgeclipper[password]" required>
        <p><label>&nbsp;</label><input type="submit" value="Log In">
    </fieldset>
</form>
In your browser, the form will look something like this:

You might clean it up with some basic CSS:
Code:
.hedgeclipper_login{ border: 1px outset gray; background: #eee; font-family: sans-serif; }
.hedgeclipper_login label{ display: inline-block; width: 8em; }


That's a lot of stuff we covered...! Next time, we'll look how to handle actual login attempts, and also some database stuff!

See ya next time,

-Adrian

Submit "HedgeClipper [part 2]!" to del.icio.us Submit "HedgeClipper [part 2]!" to StumbleUpon Submit "HedgeClipper [part 2]!" to Google Submit "HedgeClipper [part 2]!" to Digg

Comments

  1. traq's Avatar
    Who's great idea was it to limit blog posts to 10,000 characters?

    I had to separate some function definitions. Don't forget to follow the links to see them.

    As always, questions and comments are encouraged!
  2. keyboard1333's Avatar
    Ummm... ddadmin's?
    Anyways.. Looking good Adrian, looking good!!!
    When I've got a chance a bit later I'll try to actually run through all the steps instead of just reading them..
  3. traq's Avatar
    yeah, maybe I just write too much.
  4. Beverleyh's Avatar
    Thanks traq. Christmas totally threw me off your php blog series so I just wanted to let you know that I'll be testing and trying things out properly once I've caught up again. On a go-slow at the mo (ruddy snow grinding everything to a halt here)
  5. traq's Avatar
    Well, guys, feel free to examine the code but realize that, as a whole, it won't "work" until we write quite a bit more.

    Thanks!