One of the most common request of people learning PHP is that they want to make a member system thing. That's what we're going to do, only cooler. I'm going to try and teach you some basic concepts of JavaScript and PHP so that you can take this code and make it a hundred times better by adding new features.
The tutorial isn't meant to be pretty, except for the JavaScript effects. I don't use much color, and it's standard yucky formatting. It's your job to make it look nice.
The tutorial will cover setting up the database, and making basic front and backends. The features are:
- Setting up the database, backend (Part 1)
- Registering with E-mail verification (Part 1)
- Logging in and out (Part 1)
- Private Messaging (Part 2)
- Changing password, E-mail, and name (Part 2)
- Simple admin area to modify + delete users (Part 3)
Before we start to code, we need to design how our system is going to work. The configuration file will be the core, do-everything file. It connects to the database, includes the necesscary file, gets a users details, and makes sure the user can view whatever page they are at. The included file will store all of our functions, these functions perform things such as logging in, registering, sending PM's, or modifying user's details.
We will have global variable, this will be $user. $user will be loaded by the config file, and will store the current users details. If the user is a guest, it will have dummy data. $user is an array and can be used anywhere as long as the config file was included.
Protecting pages is also easy. There is a small permissions system in place, there are 4 levels. -1 = non-activated user, 0 = guest, 1 = user, and 2 = admin. This permission level is stored in $user under the name admin. Our config file will check for a constant called MIN_AUTH_LEVEL, if it exists, it will compare the user's level to the MIN_AUTH_LEVEL. If the user's auth level is to low, they get a error, otherwise they see the content. This makes it easy to protect pages from guests and protect the admin panel.
Enough talk, you should get how the system will work, now let's set up the database. We're going to require three tables, one for the PM's, one for the user's, and one for the activation keys.
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`key` varchar(32) NOT NULL DEFAULT '',
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
CREATE TABLE `messages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`from` varchar(255) NOT NULL DEFAULT '0',
`to` varchar(255) NOT NULL DEFAULT '0',
`message` longtext NOT NULL,
`title` varchar(255) NOT NULL DEFAULT '',
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`read` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL DEFAULT '',
`password` varchar(32) NOT NULL DEFAULT '',
`name` varchar(255) NOT NULL DEFAULT '',
`email` varchar(255) NOT NULL DEFAULT '',
`ip` varchar(255) NOT NULL DEFAULT '',
`admin` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
Now that we have the tables set up, we can create our config file. As we said earlier, this file will contain database information, get user information, and check permissions. This file is not meant to be accesed directly, it will be included by other files (like login, index, etc.) You might want to read up on constants and the ternary operator.
File: config.php
//Database
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'members');
mysql_connect(DB_HOST, DB_USER, DB_PASS);
mysql_select_db(DB_NAME);
//Site URL + Name
define('SITE_URL', 'http://site.com');
define('EMAIL_URL', 'site.com');
define('SITE_NAME', 'YourSite');
//Include functions
if(!@include("./member-functions.php"))
{
include("../member-functions.php");
}
//Userdata
$username = (@$_SESSION['username']) ? @$_SESSION['username'] : @$_COOKIE['username'];
$password = (@$_SESSION['password']) ? @$_SESSION['password'] : @$_COOKIE['password'];
$user = userdata($username, $password);
//Activation Checks
if($user['admin'] == '-1' && !defined('REQUIRE_ACTIVATION'))
{
die("Sorry, this account has not been activated. Upon registration, we sent an E-mail to {$user['email']} containing a link that activates your account.");
}
//Auth Check
if(defined('MIN_AUTH_LEVEL'))
{
if(MIN_AUTH_LEVEL > $user['admin'])
{
die("You are trying to view a page you do not have permission to view! If you think this is an error, try <a href='". SITE_URL ."login.php'>logging in</a>. This page may require <a href='". SITE_URL ."login.php?do=reg'>registration</a> in order to view.");
}
}
?>
Yea, that's a lot of code, but it does just what we said, so you should be able to understand it quite easily. You may be wondering about the userdata() function, no, it's not a standard PHP function, but it's one declared in member-functions.php. This function returns an array of the user's details, which go into $user.
Next we're going to look at member-functions.php, there is still no UI here, just PHP. It's a lot of code, even in chunks. I've seperated it into three categories: user functions, PM functions, and just general functions. First is the user functions, these will login, logout, register, modify details. The register function needs its own explanation simply becuase it's a little more complicated than the rest.
File: member-functions.php
{
if(empty($username) || empty($password) || empty($email) || empty($name) || strlen($password) < 6)
{
$return = '0|Please complete <strong>all</strong> fields and ensure your password is atleast six characters in length!';
}
else
{
$query = mysql_query("INSERT INTO `users` (`username`, `password`, `name`, `email`, `ip`, `admin`) VALUES ('$username', '". md5($password) ."', '$name', '$email', '". $_SERVER['REMOTE_ADDR'] ."', '-1')");
if($query)
{
//last inserted ID
$id = mysql_insert_id();
//Generate MD5 hash key
$key = generate_key(6);
//Put together the key string
$key_string = "key=$key&id=$id";
//Insert into database
$query = mysql_query("INSERT INTO `keys` (`user_id` , `key`) VALUES ('$id', '$key')");
//Send account activation E-mail
mail($email, 'Account Activation at'. SITE_NAME, "Please click the following link to activate your account:\n--------\n". SITE_URL ."activate.php?". $key_string, "From: staff@". EMAIL_URL);
$return = '1|Thank you for registering at '. SITE_NAME .'! In order for your account to become active, you must verify your E-mail address. We have sent you an E-mail to '. $email .' containing a link to activate your account. Once clicked, your account will be activated.('. $key_string .')';
}
else
{
$return = '0|An error occured during your registration process, please retry in a minute or two as it could simply be a systems glitch.('. mysql_error() .')';
}
}
return $return;
}
//Checks if a given field in the users table is taken
function is_taken($field, $value)
{
$query = mysql_query("SELECT `id` FROM `users` WHERE `$field` = '$value'");
if(mysql_num_rows($query) > 0)
{
return true;
}
else
{
return false;
}
}
//Checks user + pass, then logs in
function login($username, $password, $cookie = true)
{
if(check_login($username, $password))
{
$_SESSION['username'] = $username;
$_SESSION['password'] = $password;
if($cookie)
{
//Sets cookies
setcookie('username', $username, time()+15000);
setcookie('password', $password, time()+15000);
}
return true;
}
else
{
return false;
}
}
//Checks login details
function check_login($username, $password)
{
$query = mysql_query("SELECT `id` FROM `users` WHERE `username` = '$username' AND `password` = '$password'") or die(mysql_error());
if(mysql_num_rows($query) > 0)
{
return true;
}
else
{
return false;
}
}
//Destroy all sessions and cookies
function logout()
{
session_destroy();
setcookie('username', $username, time()-15000);
setcookie('password', $password, time()-15000);
return true;
}
//Updates a users details in the database
function edit_user($id, $username, $name, $email, $admin)
{
$query = mysql_query("UPDATE `users` SET `username` = '$username', `name` = '$name', `email` = '$email', `admin` = '$admin' WHERE `id` = '$id'");
if($query)
{
return true;
}
else
{
return false;
}
}
//Remove user from database
function delete_user($id)
{
$query = mysql_query("DELETE FROM `users` WHERE `id` = '$id'");
if($query)
{
return true;
}
else
{
return false;
}
}
//Gets an array of all user data from the database
function userdata($id, $pass)
{
if(!check_login($id, $pass))
{
//Fill array with guest data
$user = array('id' => '0', 'username' => 'Guest', 'password' => '', 'name' => 'Guest', 'email' => '', 'ip' => $_SERVER['REMOTE_ADDR'], 'admin' => '0');
}
else
{
$query = mysql_query("SELECT `username`, `password`, `name`, `email`, `ip`, `admin`, `id` FROM `users` WHERE `username` = '$id'");
$r = mysql_fetch_row($query);
$user = array('id' => $r[6], 'username' => $r[0], 'password' => $r[1], 'name' => $r[2], 'email' => $r[3], 'ip' => $r[4], 'admin' => $r[5]);
}
return $user;
}
Please, read the comments to understand the code, that's what they're there for. You'll see the generate_key() function later on, it basically generates a unique key for, how complex!
Next up is the PM functions. This really only consists of SELECT's, INSERT's, and DELETE's. Nothing to hard here, we just have to remember to maintain ownership of PM's.
File: member-functions.php (continued)
function sendpm($fid, $tid, $title, $message)
{
if(mysql_query("INSERT INTO `messages` (`from`, `to`, `message`, `title`) VALUES ('$fid', '$tid', '". htmlspecialchars($message) ."', '". htmlspecialchars($title) ."')"))
{
return true;
}
else
{
return false;
}
}
//Counts number of unread PM's
function count_unread($uid)
{
$query = mysql_query("SELECT COUNT(*) FROM `messages` WHERE `to` = '$uid' AND `read` = '0'") or die(mysql_error());
$r = mysql_fetch_row($query);
return $r[0];
}
//Marks a PM as read
function mark_read($id)
{
$query = mysql_query("UPDATE `messages` SET `read` = '1' WHERE `id` = '$id'");
}
//Delete's a PM
function delete_pm($id, $uid, $password)
{
global $user;
if($user['password'] == $password)
{
//Make sure to check it's theirs
$query = mysql_query("DELETE FROM `messages` WHERE `id` = '$id' AND `to` = '$uid'") or die(mysql_error());
return true;
}
else
{
return false;
}
}
//Fetches a PM, checks owner, returns array
function get_pm($id)
{
global $user;
$query = mysql_query("SELECT * FROM `messages` WHERE `id` = '$id'");
$pm = mysql_fetch_assoc($query);
if($pm['to'] !== $user['username'])
{
return false;
}
else
{
return $pm;
}
}
//Get all PM's for a user
function listpms($uid)
{
$pms = array();
$query = mysql_query("SELECT * FROM `messages` WHERE `to` = '$uid' ORDER BY `time` DESC");
while($r = mysql_fetch_assoc($query))
{
$pms[$r['id']] = array('id' => $r['id'], 'to' => $uid, 'from' => $r['from'], 'title' => $r['title'], 'message' => $r['message']);
}
return $pms;
}
Last up are a few AJAX-related functions that just done one tiny job. Very easy to understand.
File: member-functions.php (continued)
{
$query = mysql_query("UPDATE `users` SET `name` = '$name' WHERE `id` = '$id'");
return true;
}
//Edit only the E-Mail (AJAX)
function edit_email($email, $id)
{
$query = mysql_query("UPDATE `users` SET `email` = '$email' WHERE `id` = '$id'");
return true;
}
//Edit only the password (AJAX)
function edit_password($password, $id)
{
//check hash
if(strlen($password) !== 32)
{
$password = md5($password);
}
$query = mysql_query("UPDATE `users` SET `password` = '$password' WHERE `id` = '$id'");
if($query)
{
$_SESSION['password'] = $password;
$_COOKIE['password'] = $password;
return true;
}
else
{
return false;
}
}
Woops, almost forget our 3 utility/general/whatever functions. These are there to generate the key, list all users, and count the number of entries in a table.
File: member-functions.php
function generate_key($length)
{
return(md5(substr(str_shuffle('qwertyuiopasdfghjklmnbvcxz0987612345'), 0, $length)));
}
//Get every user
function list_all_users()
{
$query = mysql_query("SELECT * FROM `users`");
$users = array();
while($r = mysql_fetch_assoc($query))
{
$users[$r['id']] = $r;
}
return $users;
}
//Get a count of the entries in a given table
function count_tbl($tbl)
{
$query = mysql_query("SELECT COUNT(*) FROM `$tbl`");
$r = mysql_fetch_row($query);
return $r[0];
}
The last part of the backend is the AJAX handler, it receives the requests from the scripts and handles them. Should be easy to get. If you're wondering what $_POST['m'] is, it stands for method. Or, what are we going to do? Edit the password, update the user, delete a PM. . . We'll call our functions created earlier to make everything easy
File: ajax.php
session_start();
//Load up database, functions, user info
include('./config.php');
if(@$_POST['m'] == 'login')
{
if(login($_POST['username'], md5($_POST['password'])))
{ //success
echo "1";
}
else
{ //failure hmph
echo "0";
}
}
//in place editor is GET
else if(@$_GET['m'] == 'optsave')
{
if($_GET['type'] == 'email')
{
edit_email($_POST['value'], $_GET['id']);
}
else
{
edit_name($_POST['value'], $_GET['id']);
}
//AJAX will update field to this value
echo $_POST['value'];
}
else if($_POST['m'] == 'pwd')
{
if(md5($_POST['current']) == $user['password'])
{
if(edit_password(md5($_POST['new']), $_POST['id']))
{ //success
echo "Thanks, your password has been updated!";
}
else
{ //hmm
echo "An error occured when changing your password, please try again.";
}
}
else
{ //little shady
echo "The password you provided does not match your current password, please try again.";
}
}
else if($_POST['m'] == 'check')
{
//Checks username/email in the DB
if(is_taken($_POST['type'], $_POST['value']))
{ //sucess
echo "0";
}
else
{ //failure
echo "1";
}
}
else if($_POST['m'] == 'reg')
{
$reg = register($_POST['username'], $_POST['password'], $_POST['email'], $_SERVER['REMOTE_ADDR'], $_POST['name']);
echo $reg; //Give the registration message (error/success)
}
else if($_POST['m'] == 'delpm')
{
if(delete_pm($_POST['id'], $_POST['uid'], $_POST['password']))
{//sucess
echo "1";
}
else
{ //failure
echo "0";
}
}
else if($_POST['m'] == 'deluser')
{
if($user['password'] == $_POST['password'] && $user['admin'] == '2')
{
if(delete_user($_POST['id']))
{ //sucess
echo "1";
}
else
{ //failure
echo "0";
}
}
}
//Edit users
else if($_POST['m'] == 'edituser')
{
//Ensure user is admin
if($user['password'] == $_POST['password'] && $user['admin'] == '2')
{
if(edit_user($_POST['id'], $_POST['username'], $_POST['name'], $_POST['email'], $_POST['auth']))
{ //sucess
echo "1";
}
else
{ //failure
echo "0";
}
}
}
?>
Finally, we can start the frontend!
We're going to start with the login, and break it up into two parts. The PHP and JavaScript. First is the JavaScript, which will send out requests to login, and register.
Logging In JavaScript:
File: login.php | JavaScript code
{
if($F('password') == '' || $F('username') == '')
{
alert("Please fill in all fields before submitting.");
return;
}
//AJAX request options
var opt = {
method:'post',
postBody:'m=login&username=' + $F('username') + '&password=' + $F('password'),
onSuccess: function(t) { /* check on success */ checkLogin(t); },
onLoading: function() { /* Disable login button with message */$('login_button').value = 'Authencating'; $('login_button').disabled = true; }
}
//Send request
new Ajax.Request('ajax.php', opt);
}
function checkLogin(t)
{ //t object is automatically passed
if(t.responseText == "1")
{
//Show main screen
window.location = 'index.php';
}
else
{
//Give a error message, reenable login button
$('response').innerHTML = '<strong>Authencation Failed!</strong>';
Element.show('response');
$('login_button').value = 'Login';
$('login_button').disabled = false;
}
}
We're sending the request to our AJAX handler, and having the function checkLogin() handle the results of that. Either show us the main page, or give an error. The next part is a little more complicated, because we do the same thing, and add something that goes and checks if a username/email is already existant. If it is, the box will be highlighted red!
{
var opt = {
method:'post',
postBody:'m=check&type=' + var_n + '&value=' + $F(field_n),
onSuccess: function (t) { update_check(t, field_n); },
onLoading: function() {}
}
new Ajax.Request('ajax.php', opt);
}
function update_check(t, field_n)
{
if(t.responseText == "0")
{
//The name already exists, show message
$(field_n + '_check').innerHTML = '<img src="./images/error-small.png" /> That already exists!';
$(field_n).style.border = '1px red solid';
$(field_n).focus();
}
else
{
//Good to go!
$(field_n + '_check').innerHTML = '';
$(field_n).style.border = '1px green solid';
}
}
function register()
{
//Basic error checking
if($F('email') == '' || $F('name') == '' || $F('confirm_password') == '' || $F('password') == '' || $F('username') == '')
{
alert("Please fill in all fields.");
return;
}
else if($F('password') !== $F('confirm_password'))
{
alert("Please ensure your passwords match!");
$F('confirm_password').focus();
}
else
{
//Construct the AJAX object, add all form fields in the register_form to the paramaters
var opt = {
method:'post',
postBody:'m=reg&' + Form.serialize('register_form'),
onSuccess: function(t) { $('register_button').value = "register"; $('register_button').disabled = false; handle_register(t); },
onLoading: function() { $('register_button').value = "Working..."; $('register_button').disabled = true; }
}
new Ajax.Request('ajax.php', opt);
}
}
function handle_register(t)
{
var data = t.responseText.split("|");
//Give either the success or error message.
if(data[0] == "0")
{
$('register_message').innerHTML = data[1];
}
else
{
$('register_main').innerHTML = data[1];
}
}
</script>
</head>
<body>
<?php echo $con; ?>
</body>
</html>
<?php
ob_end_flush();
?>
Now we can move on to the PHP part. Note: This goes before the JavaScript code! We'll use the $_GET['do'] variable to determine what exactly to do (hehe).
File: login.php | PHP Code
session_start();
include('./config.php');
if($_GET['do'] == '' || $_GET['do'] == 'login')
{
if($user['username'] == 'Guest')
{
$con = '<h2>Login</h2>
<form name="login_form">
Username: <input type="text" id="username" /><br /><br />
Password: <input type="password" id="password" /><br /><br />
<input id="login_button" type="button" onclick="login();" value="Login" /></form>
<div id="response" style="display:none;"></div>
<a href="?do=reg">Register Here</a>';
$title = 'Login';
}
else
{
$con = "<h2>Error</h2>
<p>Looks like you're already logged in. Did you want to <a href='?do=logout'>logout</a>?";
$title = "Erorr";
}
$footer = "<p><a href='index.php'>Home</a> - <a href='login.php?do=reg'>Register</a></p>";
}
The $con, $title, and $footer variables will be placed in the HTML at the end of the file, you'll see. Simple, show login form, or an error, saying you're already logged in. Logging out is rather simple.
{
logout();
$con = "<h2>Logged Out</h2>
<p>All cookies have been cleared and you were sucessfully logged out of our systems.";
$title = "Logged Out";
$footer = "<p><a href='login.php'>Login</a>";
}
Oh yea, did I lose you?
Last, but certainly not least, is the registration form. Nothing but some HTML.
{
$con = '<h2>Register</h2>
<div id="register_main">
<form id="register_form">
Desired Username: <input type="text" name="username" onblur="check_value(\'username\', \'username\');" id="username" /><span id="username_check"></span><br /><br />
Password: <input type="password" name="password" id="password" /><br /><br />
Confirm Password: <input type="password" name="confirm_password" id="confirm_password" /><br /><br />
E-Mail Address: <input type="text" name="email" onblur="check_value(\'email\', \'email\');" id="email" /> <span id="email_check"></span><br /><br />
Name (first only): <input type="text" name="name" id="name" /><br /><br />
<input type="button" id="register_button" value="register" onclick="register();" /></form></div>
<div id="register_message" style="color:red; font-weight:bold;"></div>
<em>All fields are required - Confirmation sent to E-mail address</em>';
$title = "Register";
$footer = "<p><a href='login.php'>Login</a>";
}
Now we're going to finish part 1 with the registration activation script, this is pretty simple, just check if the key exists, if it does, update the user to active.
define('REQUIRE_ACTIVATION', false);
include('./config.php');
$key = $_GET['key'];
$id = $_GET['id'];
$query = mysql_query("SELECT * FROM `keys` WHERE `key` = '$key' AND `user_id` = '$id'");
if(mysql_num_rows($query) > 0)
{
//Key exists.
mysql_query("UPDATE `users` SET `admin` = '1' WHERE `id` = '$id'");
$con = "<h2>Activated</h2>
<p>Thanks, your account has been fully activated. You may now proceed to <a href='login.php'>login</a>.</p>";
$title = "Account Activated";
}
else
{
$con = "<h2>Error</h2>
<p>Uh-oh! The key and ID you provided us are incorrect, please go back and check your confirmation E-mail.";
$title = "Activation Error";
}
?><html>
<head>
<title><?php echo $title; ?></title>
</head>
<body>
<?php echo $con, $footer; ?>
</body>
</html>
This concludes part 1. A quick recap, we setup our database, config file, and the backend functions. We also set up the logging in, out, and registration parts of things. All that's left is the settings manager, PM system, and admin backend. There are 2 more parts.
A complete recap which includes overviews of all the tutorial, more information on script.aculo.us, and a zip of the source can be found here.
Tagged with: JavaScript, php, Javascript-Tutorials, PHP-Tutorials,