Current Record: Customizing_Theme_Based_on_IP_Address #103

This article deals with the following topics: Using field__pullValue/field__pushValue methods to customize how a field is edited and ...

Current Record: Customizing_Theme_Based_on_IP_Address #103

This article deals with the following topics: Using field__pullValue/field__pushValue methods to customize how a field is edited and ...

Customizing Theme Based on IP Address

[Permalink]

This article deals with the following topics:

  1. Using field__pullValue/field__pushValue methods to customize how a field is edited and stored in the database.
  2. Using beforeHandleRequest to modify the application settings based on the user's IP address
  3. Storing ranges of IP addresses in the database.

Last week I set up a site that needed to have slightly different behavior depending on the IP address of the remote user. E.g. If a user is connecting from an IP address between 192.168.0.0 to 192.168.0.255 we would use a different theme than if they were connecting from a different block. In addition to different themes, we also to use different types of authentication depending on which IP block the user is visiting from.

This requirement is actually common for systems that are syndicated by different organizations and should behave slightly differently depending on which organization the user belongs to (assuming they are connecting from that organization's network).

To implement this behavior we need to solve one issue.

How do we store a range of IP addresses in the database so that they can be queried easily to match if the user's IP address falls in that range.

    
It turns out that this is quite easy to do, since IP addresses are actually just a 4 digit number (base 256). So we can easily convert this number to base 10 and store it as a regular unsigned integer in the database. In addition, both PHP and MySQL provide functions to convert from an IP address to an integer and back.

The PHP functions are called long2ip and ip2long respectively.

So we have stored a start IP address and an end IP address as integers, we could simply query the database to see if a given IP address falls in that range as follows:

$intIP = ip2long($_SERVER['REMOTE_ADDR']);
$sql = sprintf('select * from ip_blocks where start_ip<=%u and end_ip>=%u', $intIP, $intIP);
... etc....

It is important to note that you need to use the sprintf() function for converting $intIP into a string because PHP will convert it to an integer which could overflow if you leave it to do a default conversion.

    
For the above query, we assuming a table with a definition like:

create table ip_blocks (
    ip_block_id int(11) not null primary key,
    start_ip unsigned int(11) not null
    end_ip unsigned int(11),
    key (start_ip),
    key (end_ip)
)
    
Now if we attach some information to the IP block, like the theme that should be used, we can check the user IP address against the available IP blocks at the beginning of each request to set the theme for that request. We will use the beforeHandleRequest method of the Application Delegate Class to house this because it allows us to set things like the theme or change the user's action.

e.g.

class conf_ApplicationDelegate {
    function beforeHandleRequest(){
        $app = Dataface_Application::getInstance();
        
        // Get a reference to the current query so we can
        // alter it if necessary.
        $query =& $app->getQuery();
        
        
        // Get the user's IP address and covert it to a long int.
        $intIP = ip2long($_SERVER['REMOTE_ADDR']);
        $sql = sprintf('select `theme` from ip_blocks where start_ip<=%u and end_ip>=%u', $intIP, $intIP);
        
        $res = mysql_query($sql, df_db());
        if ( !$res ) throw new Exception(mysql_error(df_db()));
        
        $row = mysql_fetch_row($res);
        
        // If we didn't find any valid IP ranges, let's redirect the
        // user to a different action to let them know that
        // they're not welcome here.
        if ( !$row ){
            $query['-action'] = 'not_welcome';
                // This assumes that we have defined an action
                // called "not_welcome"
                
            return;
        }
        
        $theme = $row[0];
        $themePath = 'themes/'.basename($theme);
        // Check that the theme exists.
        if ( $theme and file_exists($themePath) ){
            df_register_skin($theme, $themePath);
        }
    }
}

The above snippet makes use of the df_register_skin? method that registers a theme to be used dynamically. The first parameter is the theme name, and the second parameter is the path to the theme.

This code works great if we already have IP addresses stored properly as integers in our database, but how do we make it so users can work with the IP addresses as IP addresses and not integers? We need to be able to transform from integer to IP address to display in the edit record form, and then convert the resulting IP address back to an integer for storage in the database. Xataface provides two delegate class methods precisely for this purpose:

  1. field__pullValue - Transforms a value as stored in the database to a format that is preferred in the edit form.
  2. field__pushValue - Transforms a value entered into the edit form into a format that is preferred by the database.

So, in our delegate class we would have:

<?php
class tables_ip_blocks {

    /**
     * @param Dataface_Record $record The record we are pushing the value
     *        into
     * @param HTML_QuickForm_element $el The QuickForm widget that we are 
     *      retrieving the value from.
     */
    function start_ip__pushValue($record, $el){
        $val = ip2long($el->getValue());
        if ( $val !== false ){
            return sprintf('%u', $val );
        }
        return null;
    }
    
    function start_ip__pullValue($record, $el){
        $val = $record->val('start_ip');
        if ( $val )
            return long2ip($val);
        return $val;
    }
    
    function end_ip__pushValue($record, $el){
        $val = ip2long($el->getValue());
        if ( $val !== false ){
            return sprintf('%u', $val );
        }
        return null;
    }
    
    function end_ip__pullValue($record, $el){
        $val = $record->val('end_ip');
        if ($val){
            return long2ip($val);
        }
        return $val;
    }
    
    
}

The field__pullValue and field__pushValue method should be inverses of each other.

Note that support for field__pullValue and field__pushValue are supported by Xataface since version 0.6, however support for them with the grid widget was not added until Xataface version 1.3.

    
Now, on the edit form, users can enter and edit IP addresses in the normal format, but they will be converted to unsigned integers for storage in the database.

There is only one thing remaining, though. In the list view still shows the integer version of the IP addresses. We want them to show them as regular IP addresses. So we add field__display? methods to our delegate class:

function start_ip__display($record){
    $val = $record->val('start_ip');
    if ( $val )
        return long2ip($val);
    return $val;
}

function end_ip__display($record){
    $val = $record->val('end_ip');
    if ($val){
        return long2ip($val);
    }
    return $val;
}

References

blog comments powered by Disqus
Powered by Xataface
(c) 2005-2024 All rights reserved