Powerful Chat System – Lesson 3
Today we continue a series of articles on the creation of powerful chat system. Our third lesson will contain next elements: join form (with html5 validation). A long ago, I have already made similar validation. So you can refresh your memories if you like.
Today I will publish updated sources of our growing project (several files was modified, plus I expanded structure of database slightly). All project is well structured: system classes is in ‘classes’ folder, stylesheets in ‘css’ folder, template files in ‘templates’ folder, and – new folder ‘js’ for javascript files.
Live Demo
download in package
Now – download the source files and lets start coding !
Step 1. SQL
I have added new field into table of profiles where we will store its first names. So, please execute next SQL:
ALTER TABLE `cs_profiles` ADD `first_name` varchar(255) NOT NULL default '' AFTER `name`;
Step 2. HTML
I have updated template with login form. As you can see – I have expanded this markup and added join form here:
templates/logout_form.html
<div class="column"> <h3>Powerful Chat System Demonstration</h3> <p>Our chat will contain next features: registration, main chat, profiles with avatars, ranking system, private messaging, custom rooms, moderation / administration and possible something else (in future).</p> </div> <div class="column"> <div class="tabs_container"> <ul class="tabs"> <li class="active"><h3>Log In</h3></li> <li><h3>Join</h3></li> </ul> </div> <div class="nav_container"> <form class="login_form" action="index.php" method="post" id="1"> <label>Username:</label><input type="text" name="username"> <label>Password:</label><input type="password" name="password"> <input type="submit" name="Login" value="Login"> </form> <form class="join_form" action="index.php" method="post" id="2" style="display: none;"> <label>Full name:</label> <input type="text" id="username" name="username" placeholder="Your name" maxlength="128" title="Your name" required> <label>First name:</label> <input type="text" id="firstname" name="firstname" placeholder="Your first name" maxlength="128" title="Your first name"> <label>Last name:</label> <input type="text" id="lastname" name="lastname" placeholder="Your last name" maxlength="128" title="Your last name"> <label>Email:</label> <input type="text" id="email" name="email" placeholder="Your email" maxlength="128" title="Format: *@gmail.com" pattern=".*@gmail\.com" required> <label>Password:</label> <input type="text" id="password" name="password" maxlength="128" title="Password" required> <label>Repeat Password:</label> <input type="text" id="password" name="password" maxlength="128" title="Repeat password" required oninput="checkpass(this)"> <input type="submit" name="Join" value="Join"> </form> </div> </div> <script> $(document).ready(function(){ $('.tabs li h3').click(function () { $('.tabs li').removeClass('active'); $(this).parent().addClass('active'); $('.nav_container form').hide(); var index = $('.tabs li h3').index(this); $('.nav_container form').eq(index).show(); return false; }); }); function checkpass(input) { if (input.value != document.getElementById('password').value) { input.setCustomValidity('Password must match.'); } else { input.setCustomValidity(''); } } </script>
Please pay attention to HTML structure of join form, it contain several new attributes for html5 validation: placeholder, required and pattern. This is pretty interesting.
Step 3. CSS
This file has updated too
css/main.css
/* page layout */ *{ margin:0; padding:0; } body { background-color:#eee; color:#fff; font:14px/1.3 Arial,sans-serif; } header { background-color:#212121; box-shadow: 0 -1px 2px #111111; display:block; height:70px; position:relative; width:100%; z-index:100; } header h2{ font-size:22px; font-weight:normal; left:50%; margin-left:-400px; padding:22px 0; position:absolute; width:540px; } header a.stuts,a.stuts:visited{ border:none; text-decoration:none; color:#fcfcfc; font-size:14px; left:50%; line-height:31px; margin:23px 0 0 110px; position:absolute; top:0; } header .stuts span { font-size:22px; font-weight:bold; margin-left:5px; } /* main styles */ .container { background-color: #222; color: #bbb; margin: 20px auto; overflow: hidden; padding: 20px; position: relative; width: 800px; } .container h2 { color: #fff; margin-bottom: 10px; } .column { float: left; width: 48%; } .column:first-child { margin-right: 4%; } .column > * { color: #ddd; margin-bottom: 10px; } /* tabs section */ .tabs_container { margin: 0; } .tabs { overflow: hidden; } .tabs li { float: left; list-style: none; } .tabs li h3:first-child { margin-left: 10px } .tabs li h3 { border: 1px solid #ddd; border-bottom-width: 0; display: block; margin: 0 2px 0 0; padding: 6px 10px 4px } .tabs li.active h3 { background-color: #555; border: 1px solid #ddd; border-bottom-width: 0; -moz-border-radius: 4px 4px 0 0; -ms-border-radius: 4px 4px 0 0; -o-border-radius: 4px 4px 0 0; -webkit-border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0; } .tabs li h3:hover { background-color: #666; cursor: pointer; } .tabs li.active h3:hover { background-color: #555; cursor: normal; } .nav_container form { background-color: #555; display: block; padding: 15px; } .column h3 { color: #fff; } .login_form input,.login_form label, .join_form input,.join_form label { display: block; margin-bottom: 10px; } input[type=text], input[type=password], input[type=submit] { -moz-border-radius: 5px; -ms-border-radius: 5px; -o-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } input[type=text], input[type=password] { font-size: 16px; height: 25px; margin-right: 10px; width: 200px; } input[type=submit]{ cursor: pointer; font-size: 16px; font-weight: bold; height: 35px; padding: 5px; } /* chat block */ .chat_messages { border: 1px solid #888; box-shadow: 0 0 5px #AAA; color: #000; padding: 10px; } .chat_messages h2 { color: #fff; } .chat_messages .message { background-color: #fff; margin: 5px; padding: 5px; -moz-border-radius: 5px; -ms-border-radius: 5px; -o-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .chat_messages .message span { color: #444; font-size: 10px; margin-left: 10px; } .chat_submit_form { margin: 10px 0px; } .chat_submit_form div { float: left; width: 49%; } .chat_submit_form .error, .chat_submit_form .success, .chat_submit_form .protect { display: none; } .chat_submit_form .error { color: #f55; } .chat_submit_form .success { color: #5f5; } .chat_submit_form .protect { color: #55f; }
Step 4. PHP
Now, lets review php sources. Our index.php file has updated. I have added here join processing functional. Before actual joining, we have to check if someone else was joined with same email (this field should be unique of course)
index.php
<?php // set error reporting level if (version_compare(phpversion(), '5.3.0', '>=') == 1) error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED); else error_reporting(E_ALL & ~E_NOTICE); require_once('classes/Services_JSON.php'); require_once('classes/CMySQL.php'); // including service class to work with database require_once('classes/CLogin.php'); // including service class to work with login processing $sErrors = ''; // join processing if (! isset($_SESSION['member_id']) && $_POST['Join'] == 'Join') { $sUsername = $GLOBALS['MySQL']->escape($_POST['username']); $sFirstname = $GLOBALS['MySQL']->escape($_POST['firstname']); $sLastname = $GLOBALS['MySQL']->escape($_POST['lastname']); $sEmail = $GLOBALS['MySQL']->escape($_POST['email']); $sPassword = $GLOBALS['MySQL']->escape($_POST['password']); if ($sUsername && $sEmail && $sPassword) { // check if already exist $aProfile = $GLOBALS['MySQL']->getRow("SELECT * FROM `cs_profiles` WHERE `email`='{$sEmail}'"); if ($aProfile['id'] > 0) { $sErrors = '<h2>Another profile with same email already exist</h2>'; } else { // generate Salt and Cached password $sSalt = 'testing'; // TODO - we will add generation of salt in future $sPass = sha1(md5($sPassword) . $sSalt); // add new member into database $sSQL = " INSERT INTO `cs_profiles` SET `name` = '{$sUsername}', `first_name` = '{$sFirstname}', `last_name` = '{$sLastname}', `email` = '{$sEmail}', `password` = '{$sPass}', `salt` = '{$sSalt}', `status` = 'active', `role` = '1', `date_reg` = NOW(); "; $GLOBALS['MySQL']->res($sSQL); // autologin $GLOBALS['CLogin']->performLogin($sUsername, $sPassword); } } } // login system init and generation code $sLoginForm = $GLOBALS['CLogin']->getLoginBox(); $sChat = '<h2>You do not have rights to use chat</h2>'; $sInput = ''; if ($_SESSION['member_id'] && $_SESSION['member_status'] == 'active' && $_SESSION['member_role']) { require_once('classes/CChat.php'); // including service class to work with chat // get last messages $sChat = $GLOBALS['MainChat']->getMessages(); if ($_GET['action'] == 'get_last_messages') { // regular updating of messages in chat $oJson = new Services_JSON(); header('Content-type: application/json'); echo $oJson->encode(array('messages' => $sChat)); exit; } // get input form $sInput = $GLOBALS['MainChat']->getInputForm(); if ($_POST['message']) { // POST-ing of message $iRes = $GLOBALS['MainChat']->acceptMessage(); $oJson = new Services_JSON(); header('Content-type: application/json'); echo $oJson->encode(array('result' => $iRes)); exit; } } // draw common page echo strtr(file_get_contents('templates/main_page.html'), array('{form}' => $sLoginForm . $sErrors, '{chat}' => $sChat, '{input}' => $sInput));
As you can see – I have added chat functionality today.
classes/CLogin.php
<?php class CLogin { // constructor function CLogin() { session_start(); } // get login box function function getLoginBox() { if (isset($_GET['logout'])) { // logout processing if (isset($_SESSION['member_name']) && isset($_SESSION['member_pass'])) $this->performLogout(); } if ($_POST && $_POST['Login'] == 'Login' && $_POST['username'] && $_POST['password']) { // login processing if ($this->checkLogin($_POST['username'], $_POST['password'], false)) { // successful login $this->performLogin($_POST['username'], $_POST['password']); header( "Location:{$_SERVER['REQUEST_URI']}" ); exit; } else { // wrong login return file_get_contents('templates/login_form.html') . '<h2>Username or Password is incorrect</h2>'; } } else { // in case if we already logged (on refresh page): if (isset($_SESSION['member_name']) && $_SESSION['member_name'] && $_SESSION['member_pass']) { $aReplaces = array( '{name}' => $_SESSION['member_name'], '{status}' => $_SESSION['member_status'], '{role}' => $_SESSION['member_role'], ); return strtr(file_get_contents('templates/logout_form.html'), $aReplaces); } // otherwise - draw login form return file_get_contents('templates/login_form.html'); } } // perform login function performLogin($sName, $sPass) { $this->performLogout(); // make variables safe $sName = $GLOBALS['MySQL']->escape($sName); $aProfile = $GLOBALS['MySQL']->getRow("SELECT * FROM `cs_profiles` WHERE `name`='{$sName}'"); // $sPassEn = $aProfile['password']; $iPid = $aProfile['id']; $sSalt = $aProfile['salt']; $sStatus = $aProfile['status']; $sRole = $aProfile['role']; $sPass = sha1(md5($sPass) . $sSalt); $_SESSION['member_id'] = $iPid; $_SESSION['member_name'] = $sName; $_SESSION['member_pass'] = $sPass; $_SESSION['member_status'] = $sStatus; $_SESSION['member_role'] = $sRole; } // perform logout function performLogout() { unset($_SESSION['member_id']); unset($_SESSION['member_name']); unset($_SESSION['member_pass']); unset($_SESSION['member_status']); unset($_SESSION['member_role']); } // check login function checkLogin($sName, $sPass, $isHash = true) { // make variables safe $sName = $GLOBALS['MySQL']->escape($sName); $sPass = $GLOBALS['MySQL']->escape($sPass); $aProfile = $GLOBALS['MySQL']->getRow("SELECT * FROM `cs_profiles` WHERE `name`='{$sName}'"); $sPassEn = $aProfile['password']; if ($sName && $sPass && $sPassEn) { if (! $isHash) { $sSalt = $aProfile['salt']; $sPass = sha1(md5($sPass) . $sSalt); } return ($sPass == $sPassEn); } return false; } } $GLOBALS['CLogin'] = new CLogin();
Minor changes were made in this file too. The rest of the functional was not changed. We will continue enhance our chat soon.
Live Demo
download in archive
Conclusion
I hope that our new series of articles of chat system creation is useful and interesting for you. If you want to share your ideas, or you noticed any weakness – don’t hesitate to contact us. Good luck and welcome back!