29 November 2011

Drupal Bootstrap Database


This is the 3rd article in the series covering DRUPAL v6 bootstrap process. As the bootstrap name implies, this phase covers initializing a connection to a database.

The phase begins by including 'database.inc', which is a database abstraction layer allowing developers to use different database servers with the same code base. One of the functions defined by this library is the db_set_active() function, which is invoked by the bootstrap database phase. This function is used to activate a database used by queries in later phases or during page building and rendering. The function begins by checking if the global variable $db_url is empty. Recall that in the phase #1 - DRUPAL_BOOTSTRAP_CONFIGURATION, this variable is set to the value specified in settings.php unless it is still set to the default string of 'mysql://username:password@localhost/databasename'. In that case, $db_url is simply set to empty string ''. This would indicate a first time installation, and as a result the 'install.inc' file would be included and user redirected to install.php.

Assuming installation had been previously completed, the function proceeds to set $db_conns associative array and initializes a connection to the database specified by the URL. It first determines the connection URL based on the named connection provided to the function as an argument. For the bootstrap phase, no argument is provided and consequently the named connection is set to 'default'. It is interesting to note that an array of connections can be defined in settings.php by defining $db_url as an associative array of key values where the keys are the named connections and the values are the connection URL's. This would be the case where multiple database servers are used to manage content for a site. Drupal expects one of the key values to be named 'default', however. The database type is then extracted from the URL and stored in $db_type global variable. For this example, you can assume that MYSQL is the specified type and therefore 'mysql' would be stored in the variable. Based on the type, the appropriate handler code is then included (again, for this example it would be 'database.mysql.inc'). This file contains the actual code to interface to the specified database.

A connection to the database is then initialized using the handler's db_connect() function using the URL as an argument. This function parses the URL into an array of components (i.e., scheme, host, username, password, and path). It then validates that the specified database type is supported by PHP. Finally, if the given URL contains URL-encoded info, it is then decoded.

A new connection is then opened using the parsed information previously extracted from the URL. If connection to the database was not successful, or if database name not present then Drupal redirects to an error page and terminates.

If everything was successful up until this point, utf8 character encoding support is enabled for this database connection. The connection resource type is then returned to db_set_active() to be assigned to $db_conns static associative array for the specified named connection ('default' for bootstrap phase). The $previous_name variable is set to the name of the previously active connection. In our case this is set to false as there is no previously active connection at this stage of the bootstrap process. The static $active_name is then set to the name of the current connection. Next the global $active_db variable is set with the current connection resource type identifier, which can then be used to execute various DB queries to handle the page request. Finally, the $previous_name variable is returned to the bootstrap function. This return value would be useful for situations where there was a pre-existing DB connection and one wanted to switch to another connection to perform certain actions and then restore the original one once completed. For the bootstrap phase, there shouldn't be a pre-existing connection and the return value is simply discarded.

Until next time.. Stay tuned!

29 October 2011

get XML Namespace Elements using PHP SimpleXML


PHP has a great SimpleXML library that converts XML to an object that can be processed with normal property selectors and array iterators. I’ve been using this quite a bit lately to process some XML documents.
The library documentation isn’t that great when it comes to processing Namespace Elements within your XML document. An example of such use case is when you are parsing an RSS feed that has XML Namespace elements.
Consider the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
....
  <item>
    <title>My Title</title>
    <description>My Item</description>
    <dc:publisher>ABC</dc:publisher>
    <dc:creator>DEF</dc:creator>
    <dc:date>2009-02-12T16:53:25Z</dc:date>
  </item>
  ...
</channel>
</rss>

For me to access things like the Title and Description elements, its as simple as:

1
2
3
4
5
6
$feed = file_get_contents("http://linkto.my.feed");
$xml = new SimpleXmlElement($feed);
foreach ($xml->channel->item as $entry){
  echo $entry->title;
  echo $entry->description;
}

But what if I want to access my namespace elements such as dc:publisher or dc:creator? You would think it ‘could’ be as simple as this:

1
2
3
4
5
6
7
//This doesn't work
...
foreach ($xml->channel->item as $entry){
  echo $entry->publisher;
  echo $entry->creator;
  ...
}

The code above doesn't work because the publisher and creator elements sit inside different namespaces. So how do we do this? If you recall, the second line of our feed had this:

1
.... xmlns:dc="http://purl.org/dc/elements/1.1/">

So we know from above that anything in the dc namespace refers this URL: http://purl.org/dc/elements/1.1. Now that we know this, we can easily do this:

1
2
3
4
5
6
7
8
9
10
$feed = file_get_contents("http://linkto.my.feed");
$xml = new SimpleXmlElement($feed);
foreach ($xml->channel->item as $entry){
  echo $entry->title;
  echo $entry->description;
  //Use that namespace
  $dc = $entry->children(‘http://purl.org/dc/elements/1.1/');
  echo $dc->publisher;
  echo $dc->creator;
}

That would work. Now a cleaner way is to read the namespace URI form the document itself using the getNamespaces method:

1
2
3
4
5
6
7
8
9
10
...
foreach ($xml->channel->item as $entry){
  ...
  //Use that namespace
  $namespaces = $entry->getNameSpaces(true);
  //Now we don't have the URL hard-coded
  $dc = $entry->children($namespaces['dc']);
  echo $dc->publisher;
  echo $dc->creator;
}

That's it! I found this useful when getting an RSS feed using SimpleXML and wanting to parse the XML Namespace elements.

28 September 2011

Drupal bootstrap early page cache


The previous article in this series focused on phase 1 of the Drupal bootstrap process (DRUPAL_BOOTSTRAP_CONFIGURATION). This article will now focus on the second phase - DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE.

The early page cache phase is one that might not accomplish much for some Drupal sites, but for some others can play an important role in improving a site's overall performance by minimizing latency when delivering content to a user's browser. This is accomplished by caching already rendered pages for anonymous users (i.e. haven't logged in). This phase is run early in the bootstrap process as it doesn't rely on any later bootstrap phases (used to gather content and render pages) and its purpose is to keep latency to a minimum - in other words the sooner it executes, the faster a user receives the requested content.

There are two forms of caching with Drupal: non-database and database. The early page cache relies on non-database mechanisms, such as file system and in-memory caching. Memcached (http://memcached.org) is one example of an in-memory cache. It stores data - in our case rendered pages - using key-value pairs in a hash table kept in memory. I've not had the opportunity to use this type of caching mechanism with Drupal, and for some this isn't even an option. The reason for this is that shared hosting sites would need to have installed Memcached servers and required PHP extension in their shared environment. For security reasons this is not usually done, and thus this option is all but limited to dedicated hosts or virtual private servers (VPS).

For my analysis, I ended trying a file system-based mechanism called faspath_fscache. This Drupal modules uses the server's file system to cache rendered pages. Using the file system can be faster than a database - especially on a very loaded server in a shared environment.

In order to use this module (http://drupal.org/project/fastpath_fscache), you need to download and install it as you would with any other module. (Note* You need to manually add configuration parameters in the settings.php file, since we cannot rely on the database to store configuration as database is only bootstrapped later. It would be nice to use the drupal_rewrite_settings() function in install.inc instead of relying on users to edit the file by hand, however.) EDIT: Unfortunately the drupal_rewrite_settings() function is limited to constants and string type variables, and is really only intended to be used during the initial setup of Drupal. So the only option is to modify the file by hand.

The configuration parameters set the page_cache_fastpath flag, specify the path location of the module's implementation of the caching interface (e.g. cache_set() and cache_get() functions), and specify a path for the file cache.

With the module installed and configured, the DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE will now be able to perform some meaningful function. It will first begin by including the cache implementation file (as specified earlier in settings.php). It will then test to see if page_cache_fastpath flag has been set and if so test the result returned by page_cache_fastpath() function (implemented by the module). If both are true, the page is served to the user; otherwise, bootstrapping continues to later phases. The page_cache_fastpath() function simply checks to see that a form was not submitted or that the user was not logged in. In either case, a cached page should not be served. It will then check to see if the page being requested is currently stored in the file system, and will serve it if present.

In my limited time using early page cache, I did see a noticeable improvement (70%) in page loading time when compared against non-cached. I ran tests using Proxy Sniffer (http://www.proxy-sniffer.com/) on my local development environment using faspath_fscache and page load dropped from 3.5 sec average to approximately 1 sec. That may not seem like much to some, but in this day and age information delivery is paramount and users don't like to wait longer than they have to in order to see the content they've requested. I also ran some tests using Drupal's out-of-box database caching mechanism, and it actually performed slightly better that the file system caching. This test was also done on my development environment, but it would be interesting to test on a larger system that is hosting several sites to see if the results are comparable. In any event, if your site's performance is important you should definitely consider some form of caching to improve page loading time for your users. Until next time, keep IT simple.

The next article will focus on the DRUPAL_BOOTSTRAP_DATABASE phase. Stay tuned!

Drupal 6 Bootstrap Process


When investigating a recent rash of spam user registration on my site I began looking through Drupal core to gain a better understanding of its inner workings and how modules could be used to guard against these nuisances. Like every journey there is a beginning, and for me it all started with index.php.

This is not a very big file, but it plays a vital role because access to content on any Drupal site (via HTTP GET or POST commands) is all directed to this one file. It starts off simply enough by including a bootstrap PHP file (bootstrap.inc). This file in turn defines several constants referenced throughout the Drupal code base as well as several core utility functions. Then magic begins The drupal-bootstrap function is called with the constant DRUPAL_BOOTSTRAP_FULL as argument (the function and constant previously defined by the afore-mentioend include file). This function serves to execute a series of phases in order to process all requests coming to the server. How many phases are executed depends on the argument given to the function. In the case of the index.php, the highest phase is used. The drupal_bootstrap function executes every phase in turn from low to high until it reaches the one it was invoked with. The order and names of these phases (as defined by the constants) are shown below.



This series of articles will focus on each of these phases and attempt to explain what's going on in more detail.

PHASE1 - DRUPAL_BOOTSTRAP_CONFIGURATION

This first phase is fairly straightfoward and as its name suggests it will initialize system configuration. However, before doing so it calls drupal_unset_globals() to unset all disallowed global variables. This is essentially everything except the superglobals _ENV, _GET, _POST, _COOKIE, _FILES, _SERVER, _REQUEST, and _GLOBALS. Starting from PHP 4.2.0 the automatic registration of globals was deprecated as it was deemed to cause potential vulnerabilities.

The next step in the phase is to create a timer to track the start of the processing for the page of content being requested.

The final step is the invocation of the conf_init() function, which is used to load the system configuration and set various global variables (eg. base URL, database URL, path, etc...). The following are the steps taken by the conf_init() function:

  • validate the HTTP_HOST header sent by the client setting it to '' (empty string) if older browser (pre HTTP/1.1) did not send it
  • checks for existence of settings.php file and if present includes it. It follows a specific sequence of directory traversal using both the HTTP HOST provided and the script's path to locate settings.php file. (eg. directory /sites/www.example.com would be searched before /sites/example.com). The Drupal API http://api.drupal.org/api/function/conf_path/6 has a more detailed example. If the initial directory traversal doesn't yield any results, it will default to using the /sites/default path
  • sets the global database url variable $db_url (this will be required by subsequent bootstrap phases that require database access)
  • sets the global base url variable $base_url (defined in settings.php or created automatically from _SERVER array)
  • sets the global session name variable and cookie domain variable, $session_name and $cookie_domain respectively
  • calls session_name() to set session name called 'SESS' with the md5 hash of the $session_name variable appended to it

The next article will focus on the DRUPAL BOOTSTRAP EARLY PAGE CACHE phase

03 May 2011

login script with php mysql jquery


Introduction

Today we are making a cool & simple login / registration system. It will give you the ability to easily create a member-only area on your site and provide an easy registration process.

It is going to be PHP driven and store all the registrations into a MySQL database.


Step 1 – MySQL

First we have to create the table that will hold all the registrations. This code is available in table.sql.

table.sql

01--
02-- Table structure for table `tz_members`
03--
04 
05CREATE TABLE `tz_members` (
06  `id` int(11) NOT NULL auto_increment,
07  `usr` varchar(32) collate utf8_unicode_ci NOT NULL default '',
08  `pass` varchar(32) collate utf8_unicode_ci NOT NULL default '',
09  `email` varchar(255) collate utf8_unicode_ci NOT NULL default '',
10  `regIP` varchar(15) collate utf8_unicode_ci NOT NULL default '',
11  `dt` datetime NOT NULL default '0000-00-00 00:00:00',
12  PRIMARY KEY  (`id`),
13  UNIQUE KEY `usr` (`usr`)
14) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Notice that we've defined the id as an integer with auto_increment – it is automatically assigned to every site member. Also, we've defined usr as an unique key – no two users with the same usernames are allowed.

We later use this in the registration to determine whether the username has been taken.

After you create the table, do not forget to fill in your database credentials in connect.php so you can run the demo on your own server.

Step 2 – XHTML

demo.php

001<!-- Panel -->
002<div id="toppanel">
003 
004<div id="panel">
005<div class="content clearfix">
006<div class="left">
007<h1>The Sliding jQuery Panel</h1>
008<h2>A register/login solution</h2>
009<p class="grey">You are free to use this login and registration system in you sites!</p>
010<h2>A Big Thanks</h2>
011
012</div>
013 
014<?php
015if(!$_SESSION['id']):
016// If you are not logged in
017?>
018 
019<div class="left">
020<!-- Login Form -->
021<form class="clearfix" action="" method="post">
022<h1>Member Login</h1>
023 
024<?php
025if($_SESSION['msg']['login-err'])
026{
027    echo '<div class="err">'.$_SESSION['msg']['login-err'].'</div>';
028    unset($_SESSION['msg']['login-err']);
029    // This will output login errors, if any
030}
031?>
032 
033<label class="grey" for="username">Username:</label>
034<input class="field" type="text" name="username" id="username" value="" size="23" />
035<label class="grey" for="password">Password:</label>
036<input class="field" type="password" name="password" id="password" size="23" />
037<label><input name="rememberMe" id="rememberMe" type="checkbox" checked="checked" value="1" /> &nbsp;Remember me</label>
038<div class="clear"></div>
039<input type="submit" name="submit" value="Login" class="bt_login" />
040</form>
041 
042</div>
043 
044<div class="left right">
045 
046<!-- Register Form -->
047 
048<form action="" method="post">
049<h1>Not a member yet? Sign Up!</h1>
050 
051<?php
052 
053if($_SESSION['msg']['reg-err'])
054{
055    echo '<div class="err">'.$_SESSION['msg']['reg-err'].'</div>';
056    unset($_SESSION['msg']['reg-err']);
057    // This will output the registration errors, if any
058}
059 
060if($_SESSION['msg']['reg-success'])
061{
062    echo '<div class="success">'.$_SESSION['msg']['reg-success'].'</div>';
063    unset($_SESSION['msg']['reg-success']);
064    // This will output the registration success message
065}
066 
067?>
068 
069<label class="grey" for="username">Username:</label>
070<input class="field" type="text" name="username" id="username" value="" size="23" />
071<label class="grey" for="email">Email:</label>
072<input class="field" type="text" name="email" id="email" size="23" />
073<label>A password will be e-mailed to you.</label>
074<input type="submit" name="submit" value="Register" class="bt_register" />
075</form>
076 
077</div>
078 
079<?php
080else:
081// If you are logged in
082?>
083 
084<div class="left">
085<h1>Members panel</h1>
086<p>You can put member-only data here</p>
087<a href="registered.php">View a special member page</a>
088<p>- or -</p>
089<a href="?logoff">Log off</a>
090</div>
091<div class="left right">
092</div>
093 
094<?php
095endif;
096// Closing the IF-ELSE construct
097?>
098 
099</div>
100</div> <!-- /login -->
101 
102<!-- The tab on top -->
103<div class="tab">
104<ul class="login">
105<li class="left">&nbsp;</li>
106<li>Hello <?php echo $_SESSION['usr'] ? $_SESSION['usr'] : 'Guest';?>!</li>
107<li class="sep">|</li>
108<li id="toggle">
109<a id="open" class="open" href="#"><?php echo $_SESSION['id']?'Open Panel':'Log In | Register';?></a>
110<a id="close" style="display: none;" class="close" href="#">Close Panel</a>
111</li>
112<li class="right">&nbsp;</li>
113</ul>
114 
115</div> <!-- / top -->
116</div> <!--panel -->

At several places in this code, there are some PHP operators that check whether $_SESSION['usr'] or $_SESSION['id'] are defined. This is true only if the page visitor is logged in the site, which allows us to show specific content to site members. We will cover it in detail in a moment.

After the form, we put the rest of the page.

01<div class="pageContent">
02 
03<div id="main">
04 
05<div class="container">
06<h1>A Cool Login System</h1>
07<h2>Easy registration management with PHP &amp; jQuery</h2>
08</div>
09 
10<div class="container">
11<p>This is a ...</p>
12<div class="clear"></div>
13 
14</div>
15 
16</div>

Nothing special here. Lets continue with the PHP backend.

The login system

The login system

Step 3 – PHP

It is time to convert the form into a complete registration and login system. To achieve it, we will need more than the usual amount of PHP. I'll divide the code into two parts.

If you plan to add more code, it would be a good idea to split it into several files which are included when needed. This aids the development of large projects and allows code reuse in different parts of a site.

But lets see how we've done it here.

demo.php

01define('INCLUDE_CHECK',true);
02 
03require 'connect.php';
04require 'functions.php';
05 
06// Those two files can be included only if INCLUDE_CHECK is defined
07 
08session_name('tzLogin');
09// Starting the session
10 
11session_set_cookie_params(2*7*24*60*60);
12// Making the cookie live for 2 weeks
13 
14session_start();
15 
16if($_SESSION['id'] && !isset($_COOKIE['tzRemember']) && !$_SESSION['rememberMe'])
17{
18    // If you are logged in, but you don't have the tzRemember cookie (browser restart)
19    // and you have not checked the rememberMe checkbox:
20 
21    $_SESSION = array();
22    session_destroy();
23 
24    // Destroy the session
25}
26 
27if(isset($_GET['logoff']))
28{
29    $_SESSION = array();
30    session_destroy();
31    header("Location: demo.php");
32    exit;
33}
34 
35if($_POST['submit']=='Login')
36{
37    // Checking whether the Login form has been submitted
38 
39    $err = array();
40    // Will hold our errors
41 
42    if(!$_POST['username'] || !$_POST['password'])
43        $err[] = 'All the fields must be filled in!';
44 
45    if(!count($err))
46    {
47        $_POST['username'] = mysql_real_escape_string($_POST['username']);
48        $_POST['password'] = mysql_real_escape_string($_POST['password']);
49        $_POST['rememberMe'] = (int)$_POST['rememberMe'];
50 
51        // Escaping all input data
52 
53        $row = mysql_fetch_assoc(mysql_query("SELECT id,usr FROM tz_members WHERE usr='{$_POST['username']}' AND pass='".md5($_POST['password'])."'"));
54 
55        if($row['usr'])
56        {
57            // If everything is OK login
58 
59            $_SESSION['usr']=$row['usr'];
60            $_SESSION['id'] = $row['id'];
61            $_SESSION['rememberMe'] = $_POST['rememberMe'];
62 
63            // Store some data in the session
64 
65            setcookie('tzRemember',$_POST['rememberMe']);
66            // We create the tzRemember cookie
67        }
68        else $err[]='Wrong username and/or password!';
69    }
70 
71    if($err)
72        $_SESSION['msg']['login-err'] = implode('<br />',$err);
73        // Save the error messages in the session
74 
75    header("Location: demo.php");
76    exit;
77}

Here the tzRemember cookie acts as a control whether we should log-off users that have not marked the "remember me" checkbox. If the cookie is not present (due to browser restart) and the visitor has not checked the remember me option, we destroy the session.

The session itself is kept alive for two weeks (as set by session_set_cookie_params).

Lets see the second part of demo.php.

01else if($_POST['submit']=='Register')
02{
03    // If the Register form has been submitted
04    $err = array();
05 
06    if(strlen($_POST['username'])<4 || strlen($_POST['username'])>32)
07    {
08        $err[]='Your username must be between 3 and 32 characters!';
09    }
10 
11    if(preg_match('/[^a-z0-9\-\_\.]+/i',$_POST['username']))
12    {
13        $err[]='Your username contains invalid characters!';
14    }
15 
16    if(!checkEmail($_POST['email']))
17    {
18        $err[]='Your email is not valid!';
19    }
20 
21    if(!count($err))
22    {
23        // If there are no errors
24        $pass = substr(md5($_SERVER['REMOTE_ADDR'].microtime().rand(1,100000)),0,6);
25        // Generate a random password
26 
27        $_POST['email'] = mysql_real_escape_string($_POST['email']);
28        $_POST['username'] = mysql_real_escape_string($_POST['username']);
29        // Escape the input data
30 
31        mysql_query("   INSERT INTO tz_members(usr,pass,email,regIP,dt)
32                    VALUES(
33                    '".$_POST['username']."',
34                    '".md5($pass)."',
35                    '".$_POST['email']."',
36                    '".$_SERVER['REMOTE_ADDR']."',
37                    NOW()
38        )");
39 
40        if(mysql_affected_rows($link)==1)
41        {
42            send_mail(  'xyz@example.com',
43                    $_POST['email'],
44                    'Registration System Demo - Your New Password',
45                    'Your password is: '.$pass);
46                    $_SESSION['msg']['reg-success']='We sent you an email with your new password!';
47        }
48        else $err[]='This username is already taken!';
49    }
50 
51    if(count($err))
52    {
53        $_SESSION['msg']['reg-err'] = implode('<br />',$err);
54    }
55 
56    header("Location: demo.php");
57    exit;
58}
59 
60$script = '';
61if($_SESSION['msg'])
62{
63    // The script below shows the sliding panel on page load
64    $script = '
65    <script type="text/javascript">
66    $(function(){
67        $("div#panel").show();
68        $("#toggle a").toggle();
69    });
70    </script>';
71}

We store all the encountered errors in an $err array, which is later assigned to a $_SESSION variable. This allows it to be accessible after a browser redirect.

You may have noticed on some sites, that when you submit a form and later refresh the page, the data is sent all over again. This could become problematic as it could lead to a double registrations and unnecessary server load.

xyz@example.com

We use the header function to prevent this, by redirecting the browser to the same page. This starts a fresh view of the page, without the browser associating it with a form submit. The result is that, on page refresh, no data is sent.

But as we use $_SESSION to store all the encountered errors it is important that we unset these variables, once we show the errors to the user. Otherwise they will be shown on every page view (the highlighted lines on the XHTML part of the tutorial).

Also notice how we create an additional script (lines 60-70 of the second part of the PHP code) which shows the panel on page load, so that the messages are visible to the user.

Now lets take a look at the CSS.

The registration / login system

The registration / login system

Step 4 – CSS

The sliding panel comes with its own style sheet. This means we are only left with creating the page styles.

demo.css

01body,h1,h2,h3,p,quote,small,form,input,ul,li,ol,label{
02    /* The reset rules */
03    margin:0px;
04    padding:0px;
05}
06 
07body{
08    color:#555555;
09    font-size:13px;
10    background: #eeeeee;
11    font-family:Arial, Helvetica, sans-serif;
12    width: 100%;
13}
14 
15h1{
16    font-size:28px;
17    font-weight:bold;
18    font-family:"Trebuchet MS",Arial, Helvetica, sans-serif;
19    letter-spacing:1px;
20}
21 
22h2{
23    font-family:"Arial Narrow",Arial,Helvetica,sans-serif;
24    font-size:10px;
25    font-weight:normal;
26    letter-spacing:1px;
27    padding-left:2px;
28    text-transform:uppercase;
29    white-space:nowrap;
30    margin-top:4px;
31    color:#888888;
32}
33 
34#main p{
35    padding-bottom:8px;
36}
37 
38.clear{
39    clear:both;
40}
41 
42#main{
43    width:800px;
44    /* Centering it in the middle of the page */
45    margin:60px auto;
46}
47 
48.container{
49    margin-top:20px;
50 
51    background:#FFFFFF;
52    border:1px solid #E0E0E0;
53    padding:15px;
54 
55    /* Rounded corners */
56    -moz-border-radius:20px;
57    -khtml-border-radius: 20px;
58    -webkit-border-radius: 20px;
59    border-radius:20px;
60}
61 
62.err{
63    color:red;
64}
65 
66.success{
67    color:#00CC00;
68}
69 
70a, a:visited {
71    color:#00BBFF;
72    text-decoration:none;
73    outline:none;
74}
75 
76a:hover{
77    text-decoration:underline;
78}
79 
80.tutorial-info{
81    text-align:center;
82    padding:10px;
83}

Step 5 – jQuery

The sliding panel comes with its own jQuery files.

demo.php

01<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
02<script src="login_panel/js/slide.js" type="text/javascript"></script>
03 
04<?php echo $script; ?>

First we include the jQuery library from Google's CDN. Later comes a special fix for IE6 PNG transparency issues and lastly the panel's script is included.

At the bottom of the page is the $script variable – it shows the panel on page load if needed.

With this our cool login system is complete!

Conclusion

Today we learned how to use a fantastic form component and turn it into a functional log in / registration system.

You are free to built upon this code and modify it any way you see fit.



Download Here