• Reading time:16 mins read
Powerful Chat System

Powerful Chat System – Lesson 9

Today I prepared new stage of our php/mysql based chat system. In our ninth lesson I added one important feature: public rooms. Now your members can talk in different public rooms. Right to create and remove rooms belong to administrators.

Today, as usual, we will publish only updated sources of our project. As you know, whole project is well structured: system classes is in ‘classes’ folder, all javascript files in ‘js’ folder, stylesheets in ‘css’ folder, all custom avatars in ‘data’ folder, images in ‘images’ folder, template files in ‘templates’ folder.

Live Demo

[sociallocker]

download in package

[/sociallocker]


Now – download the source files and lets start coding !


Step 1. SQL

I added one new table into our database ‘cs_rooms’. This table keeps records about rooms. Please execute next SQL:

CREATE TABLE `cs_rooms` (
  `id` int(11) unsigned NOT NULL auto_increment,
  `title` VARCHAR(255) NOT NULL,
  `owner` int(11) unsigned NOT NULL,
  `when` int(11) NOT NULL default '0',
  PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `cs_rooms` (`id`, `title`, `owner`, `when`) VALUES
(NULL, 'Room1', 3, 1338293810),
(NULL, 'Room2', 3, 1338293811),
(NULL, 'Room3', 3, 1338293812);

We have just added three rooms.

Step 2. HTML

I updated template of our main chat box. I added current room id in two places:

templates/chat.html

<form class="chat_submit_form">
    <div><input type="text" name="message" /><input type="submit" value="Submit" name="Submit" /></div>
    <div>
        <h3 class="error">Some error occurs during sending message</h3>
        <h3 class="success">Message successfully sent</h3>
        <h3 class="protect">Please wait 5 secs before adding next message</h3>
    </div>
    <input type="hidden" name="room" value="{room}" />
</form>
<script>
    var iRoom = {room};
</script>
<script src="js/chat.js"></script>

Our main page is affected too, There are several changes regarding rooms feature:

templates/main_page.html

<!DOCTYPE html>
<html lang="en" >
<head>
    <title>Powerful Chat System - Lesson 9</title>
    <link href="css/main.css" rel="stylesheet" type="text/css" />
    <link href="css/rooms.css" rel="stylesheet" type="text/css" />
    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
</head>
<body>
    <header>
        <h2>Powerful Chat System - Lesson 9</h2>
        <a href="https://dev-school.net/powerful-chat-system-lesson-9/" class="stuts">Back to original tutorial on <span>Dev School</span></a>
    </header>
    <div class="clear"></div>
    <div class="container" id="con1">
        {form}
        {avatar}
    </div>
    <div class="container" id="con2">
        {rooms}
        {new_room}

        <h2>{chat_name} Chat Block</h2>
        <div class="chat_messages">
            {chat}
        </div>
        {input}
    </div>

    <div class="sidebar">
        <div>
            <h2>Online Members Block</h2>
            {online_members}
        </div>
        <div>
            <h2>Last Members</h2>
            {profiles}
        </div>
    </div>

    <div class="priv_dock_wrap"></div>
    {priv_js}

</body>
</html>

And finally I added one new template file to manage with rooms (where we can create or remove current active room)

templates/new_room.html

<form class="new_room_form" method="post">
    <div><input type="text" name="title" /><input type="submit" value="Add room" name="submit" /></div>
    <input type="hidden" name="action" value="add_room" />
</form>
<form class="delete_room_form" method="post">
    <input type="submit" value="Delete current room" name="submit" />
    <input type="hidden" name="room_id" value="{room}" />
    <input type="hidden" name="action" value="delete_room" />
</form>
<div class="clear"></div>
<br /><hr />

Step 3. CSS

Of course, we should stylize the tabs for our rooms (each room – css tab):

css/rooms.css

.roomsHolder {
    font: bold 12px/35px verdana;
    overflow: hidden;
    position: relative;
}
.roomsHolder .shadow {
    background-color: #888888;
    height: 8px;
    left: 5%;
    position: absolute;
    top: -9px;
    width: 90%;

    /* css3 box shadow */
    -webkit-box-shadow: 0 0 10px #000000;
    -moz-box-shadow: 0 0 10px #000000;
    -o-box-shadow: 0 0 10px #000000;
    box-shadow: 0 0 10px #000000;
}
ul.rooms {
    display: block;
    float: right;
    height: 70px;
    list-style: none outside none;
    margin: 0;
    padding: 0 60px;
    position: relative;
}
ul.rooms li {
    float: left;
    margin: 0 5px 0 0;
}
ul.rooms li a {
    color: #DDDDDD;
    display: block;
    padding: 0 10px;
    text-decoration: none;

    /* css3 box shadow */
    -webkit-box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
    -moz-box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
    -o-box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
    box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);

        /* css radius */
    -webkit-border-bottom-right-radius: 10px;
    -webkit-border-bottom-left-radius: 10px;
    -moz-border-radius: 0 0 10px 10px;
    -o-border-radius: 0 0 10px 10px;
    border-radius: 0 0 10px 10px;

        /* css3 transition */
    -webkit-transition: all 0.2s ease-in-out;
    -moz-transition: all 0.2s ease-in-out;
    -o-transition: all 0.2s ease-in-out;
    transition: all 0.2s ease-in-out;
}
ul.rooms li a.red {
    background-color:#a00;
}
ul.rooms li a.orange {
    background-color:#da0;
}
ul.rooms li a.green {
    background-color:#060;
}
ul.rooms li a.blue {
    background-color:#00a;
}
ul.rooms li a.indigo {
    background-color:#2b0062;
}
ul.rooms li a.violet {
    background-color:#682bc2;
}
ul.rooms li a:hover, ul.rooms li a.active {
    background:#aaa;
    color:#fff;
    padding:10px 10px 0 10px;
}
ul.rooms li a.red:hover, ul.rooms li a.red.active {
    background-color:#c00;
}
ul.rooms li a.orange:hover, ul.rooms li a.orange.active {
    background-color:#fc0;
}
ul.rooms li a.green:hover, ul.rooms li a.green.active {
    background-color:#080;
}
ul.rooms li a.blue:hover, ul.rooms li a.blue.active {
    background-color:#00c;
}
ul.rooms li a.indigo:hover, ul.rooms li a.indigo.active {
    background-color:#5b1092;
}
ul.rooms li a.violet:hover, ul.rooms li a.violet.active {
    background-color:#8a2be2;
}
.new_room_form {
    float: left;
}
.delete_room_form {
    float: right;
}

Step 4. PHP

Index page file was updated. I added all necessary functional to work with rooms: admins can create and remove rooms. Usual members (and admins too) can write messages into different rooms.

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
require_once('classes/CProfiles.php'); // including service class to work with profiles

$sErrors = '';
// join processing
if (! isset($_SESSION['member_id']) && $_POST['Join'] == 'Join') {
    $GLOBALS['CProfiles']->registerProfile();
}

// login system init and generation code
$sLoginForm = $GLOBALS['CLogin']->getLoginBox();

$sChat = '<h2>You do not have rights to use chat</h2>';
$sInput = $sPrivChatJs = $sRooms = '';
if ($_SESSION['member_id'] && $_SESSION['member_status'] == 'active' && $_SESSION['member_role']) {
    if ($_GET['action'] == 'update_last_nav') { // update last navigate time
        $iPid = (int)$_SESSION['member_id'];
        if ($iPid) {
            $GLOBALS['MySQL']->res("UPDATE `cs_profiles` SET `date_nav` = NOW() WHERE `id` = '{$iPid}'");
        }
        exit;
    }

    require_once('classes/CChat.php'); // including service class to work with chat

    if ($_GET['action'] == 'check_new_messages') { // check for new messages
        $iPid = (int)$_SESSION['member_id'];
        $iSender = $GLOBALS['MainChat']->getRecentMessage($iPid);

        if ($iSender) {
            $aSender = $GLOBALS['CProfiles']->getProfileInfo($iSender);
            $sName = ($aSender['first_name'] && $aSender['last_name']) ? $aSender['first_name'] . ' ' . $aSender['last_name'] : $aSender['name'];

            $oJson = new Services_JSON();
            header('Content-type: application/json');
            echo $oJson->encode(array('id' => $iSender, 'name' => $sName));
        }
        exit;
    }

    if ($_GET['action'] == 'get_private_messages') { // regular updating of messages in chat
        $sChat = $GLOBALS['MainChat']->getMessages((int)$_GET['recipient']);
        $oJson = new Services_JSON();
        header('Content-type: application/json');
        echo $oJson->encode(array('messages' => $sChat));
        exit;
    }

    $iRoom = (int)$_GET['room'];

    $sNewRoom = '';
    if ($_SESSION['member_role'] == 5) {
        $aRoomKeys = array('{room}' => $iRoom);
        $sNewRoom = strtr(file_get_contents('templates/new_room.html'), $aRoomKeys);
        if ($_POST['action'] == 'add_room' && $_POST['title'] != '') {
            $GLOBALS['MainChat']->addRoom($_POST['title']);
        }
        if ($_POST['action'] == 'delete_room' && (int)$_POST['room_id']) {
            $GLOBALS['MainChat']->deleteRoom($_POST['room_id']);
        }
    }

    // get all active rooms
    $sRooms = $GLOBALS['MainChat']->getRooms($iRoom);

    $sChatName = 'Main';
    if ($iRoom) {
        $aRoomInfo = $GLOBALS['MainChat']->getRoomInfo($iRoom);
        $sChatName = $aRoomInfo['title'];
    }

    // get last messages
    $sChat = $GLOBALS['MainChat']->getMessages(0, $iRoom);
    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;
    }

    // add avatar
    if ($_POST['action'] == 'add_avatar') {
        $iAvRes = $GLOBALS['CProfiles']->addAvatar();
        header('Content-Type: text/html; charset=utf-8');
        echo ($iAvRes == 1) ? '<h2 style="text-align:center">New avatar has been accepted, refresh main window to see it</h2>' : '';
        exit;
    }

    // get input form
    $sInput = $GLOBALS['MainChat']->getInputForm($iRoom);

    if ($_POST['message']) { // POST-ing of message
        $iPostRoom = (int)$_POST['room'];
        $iRes = $GLOBALS['MainChat']->acceptMessage($iPostRoom);

        $oJson = new Services_JSON();
        header('Content-type: application/json');
        echo $oJson->encode(array('result' => $iRes));
        exit;
    }

    if ($_POST['priv_message']) { // POST-ing of private messages
        $iRes = $GLOBALS['MainChat']->acceptPrivMessage();

        $oJson = new Services_JSON();
        header('Content-type: application/json');
        echo $oJson->encode(array('result' => $iRes));
        exit;
    }
    $sPrivChatJs = '<script src="js/priv_chat.js"></script>';
}

// get profiles lists
$sProfiles = $GLOBALS['CProfiles']->getProfilesBlock();
$sOnlineMembers = $GLOBALS['CProfiles']->getProfilesBlock(10, true);

// get profile avatar
$sAvatar = $GLOBALS['CProfiles']->getProfileAvatarBlock();

// draw common page
$aKeys = array(
    '{chat_name}' => $sChatName,
    '{rooms}' => $sRooms,
    '{new_room}' => $sNewRoom,
    '{form}' => $sLoginForm . $sErrors,
    '{chat}' => $sChat,
    '{input}' => $sInput,
    '{profiles}' => $sProfiles,
    '{online_members}' => $sOnlineMembers,
    '{avatar}' => $sAvatar,
    '{priv_js}' => $sPrivChatJs
);
echo strtr(file_get_contents('templates/main_page.html'), $aKeys);

Next updated file – main chat class. I have added possibility to work with different rooms here too:

classes/CChat.php

<?php

class CChat {

    // constructor
    function CChat() {}

    // add a message to database
    function acceptMessage($iPostRoom = 0) {
        $sName = $GLOBALS['MySQL']->escape($_SESSION['member_name']);
        $iPid = (int)$_SESSION['member_id'];
        $sMessage = $GLOBALS['MySQL']->escape($_POST['message']);

        if ($iPid && $sName != '' && $sMessage != '') {
            $sSQL = "
                SELECT `id`
                FROM `cs_messages`
                WHERE `sender` = '{$iPid}' AND UNIX_TIMESTAMP( ) - `when` < 5
                    AND `room` = '{$iPostRoom}'
                LIMIT 1
            ";
            $iLastId = $GLOBALS['MySQL']->getOne($sSQL);
            if ($iLastId) return 2; // as protection from very often messages

            $bRes = $GLOBALS['MySQL']->res("INSERT INTO `cs_messages` SET `sender` = '{$iPid}', `message` = '{$sMessage}', `when` = UNIX_TIMESTAMP(), `room` = '{$iPostRoom}'");
            return ($bRes) ? 1 : 3;
        }
    }

    // add a private message to database
    function acceptPrivMessage() {
        $sName = $GLOBALS['MySQL']->escape($_SESSION['member_name']);
        $iPid = (int)$_SESSION['member_id'];
        $iRecipient = (int)$_POST['recipient'];
        $sMessage = $GLOBALS['MySQL']->escape($_POST['priv_message']);

        if ($iPid && $iRecipient && $sName != '' && $sMessage != '') {
            $sSQL = "
                SELECT `id`
                FROM `cs_messages`
                WHERE `sender` = '{$iPid}' AND `recipient` = '{$iRecipient}' AND UNIX_TIMESTAMP( ) - `when` < 5
                    AND `room` = 0
                LIMIT 1
            ";
            $iLastId = $GLOBALS['MySQL']->getOne($sSQL);
            if ($iLastId) return 2; // as protection from very often messages

            $bRes = $GLOBALS['MySQL']->res("INSERT INTO `cs_messages` SET `sender` = '{$iPid}', `recipient` = '{$iRecipient}', `message` = '{$sMessage}', `when` = UNIX_TIMESTAMP()");
            return ($bRes) ? 1 : 3;
        }
    }

    // return input text form
    function getInputForm($iRoom = 0) {
        $aKeys = array(
            '{room}' => $iRoom
        );
        return strtr(file_get_contents('templates/chat.html'), $aKeys);
    }

    // get last 10 messages
    function getMessages($iRecipient = 0, $iRoom = 0) {
        $sRecipientSQL = 'WHERE `recipient` = 0';
        if ($iRecipient > 0) {
            $iPid = (int)$_SESSION['member_id'];
            $sRecipientSQL = "WHERE (`sender` = '{$iRecipient}' && `recipient` = '{$iPid}') || (`recipient` = '{$iRecipient}' && `sender` = '{$iPid}')";
        }

        $sRecipientSQL .= " AND `room` = '{$iRoom}'";

        $sSQL = "
            SELECT `a` . * , `cs_profiles`.`name`,  `cs_profiles`.`id` as 'pid' , UNIX_TIMESTAMP( ) - `a`.`when` AS 'diff'
            FROM `cs_messages` AS `a`
            INNER JOIN `cs_profiles` ON `cs_profiles`.`id` = `a`.`sender`
            {$sRecipientSQL}
            ORDER BY `a`.`id` DESC
            LIMIT 10 
        ";
        $aMessages = $GLOBALS['MySQL']->getAll($sSQL);
        asort($aMessages);

        // create list of messages
        $sMessages = '';
        foreach ($aMessages as $i => $aMessage) {
            $sExStyles = $sExJS = '';
            $iDiff = (int)$aMessage['diff'];
            if ($iDiff < 7) { // less than 7 seconds
                $sExStyles = 'style="display:none;"';
                $sExJS = "<script> $('#message_{$aMessage['id']}').fadeIn('slow'); </script>";
            }

            $sWhen = date("H:i:s", $aMessage['when']);
            $sAvatar = $GLOBALS['CProfiles']->getProfileAvatar($aMessage['pid']);
            $sMessages .= '<div class="message" id="message_'.$aMessage['id'].'" '.$sExStyles.'><b><a href="profile.php?id='.$aMessage['pid'].'" target="_blank"><img src="'. $sAvatar .'">' . $aMessage['name'] . ':</a></b> ' . $aMessage['message'] . '<span>(' . $sWhen . ')</span></div>' . $sExJS;
        }
        return $sMessages;
    }

    function getRecentMessage($iPid) {
        if ($iPid) {
            $sSQL = "
                SELECT `a` . * , `cs_profiles`.`name`,  `cs_profiles`.`id` as 'pid' , UNIX_TIMESTAMP( ) - `a`.`when` AS 'diff'
                FROM `cs_messages` AS `a`
                INNER JOIN `cs_profiles` ON `cs_profiles`.`id` = `a`.`sender`
                WHERE `recipient` = '{$iPid}' AND `room` = 0
                ORDER BY `a`.`id` DESC
                LIMIT 1
            ";

            $aMessage = $GLOBALS['MySQL']->getRow($sSQL);
            $iDiff = (int)$aMessage['diff'];
            if ($iDiff < 7) { // less than 7 seconds, = new
                return (int)$aMessage['sender'];
            }
            return;
        }
    }

    function getRandColor() {
        $aColors = array('red', 'blue', 'green', 'orange', 'indigo', 'violet');
        shuffle($aColors);
        return $aColors[0];
    }

    function getRooms($iRoom = 0) {
        $sSQL = "
            SELECT * 
            FROM `cs_rooms`
            WHERE 1
        ";
        $aRooms = $GLOBALS['MySQL']->getAll($sSQL);

        $sRooms = '';
        foreach ($aRooms as $i => $aRoom) {
            $sActive = ($iRoom == $aRoom['id']) ? ' active' : '';
            $sColor = $this->getRandColor();
            $sRooms .= '<li><a class="'.$sColor.$sActive.'" href="index.php?room='.$aRoom['id'].'">'.$aRoom['title'].'</a></li>';
        }

        $sMainActive = ($iRoom == 0) ? ' active' : '';
        $sColor = $this->getRandColor();

        return <<<EOF
<div class="roomsHolder">
    <ul class="rooms">
        <li><a class="{$sColor}{$sMainActive}" href="index.php">Main</a></li>
        {$sRooms}
    </ul>
    <div class="shadow"></div>
</div>
<div class="clear"></div>
EOF;
    }

    function getRoomInfo($i) {
        $sSQL = "
            SELECT * 
            FROM `cs_rooms`
            WHERE `id` = '{$i}'
        ";
        $aInfos = $GLOBALS['MySQL']->getAll($sSQL);
        return $aInfos[0];
    }

    function addRoom($sTitleParam) {
        $sTitle = $GLOBALS['MySQL']->escape($sTitleParam);
        $iPid = (int)$_SESSION['member_id'];

        if ($iPid && $sTitle) {
            return $GLOBALS['MySQL']->res("INSERT INTO `cs_rooms` SET `title` = '{$sTitle}', `owner` = '{$iPid}', `when` = UNIX_TIMESTAMP()");
        }
    }
    function deleteRoom($sRoomParam) {
        $iRoom = (int)$sRoomParam;

        if ($iRoom) {
            $GLOBALS['MySQL']->res("DELETE FROM `cs_messages` WHERE `room` = '{$iRoom}'");
            return $GLOBALS['MySQL']->res("DELETE FROM `cs_rooms` WHERE `id` = '{$iRoom}' LIMIT 1");
        }
    }
}

$GLOBALS['MainChat'] = new CChat();

Most of the functions (acceptMessage, acceptPrivateMessage, getInputForm, getMessages and getRecentMessages) are working properly with the rooms now. Plus I added several new functions (getRooms, getRoomInfo, addRoom and deleteRoom) to work with rooms. They are not complicated functions.

Step 5. Javascript

And, last one updated file is:

js/chat.js

$(function() {
    getMessages = function() {
        $.getJSON('index.php?action=get_last_messages&room='+iRoom, function(data) {
            if (data.messages) {
                $('.chat_messages').html(data.messages);
            }

            // get recent chat messages in loop
            setTimeout(function() {
               getMessages();
            }, 5000);
        });
    }
    getMessages();

    $('.chat_submit_form').submit(function() { 
        $.post('index.php', { message: $('.chat_submit_form input[name=message]').val(), room: $('.chat_submit_form input[name=room]').val() }, 
            function(data){
                if (data.result == 1) {
                    $('.chat_submit_form .success').fadeIn('slow', function () {
                        $(this).delay(1000).fadeOut('slow'); 
                    }); 
                } else if (data.result == 2) {
                    $('.chat_submit_form .protect').fadeIn('slow', function () {
                        $(this).delay(1000).fadeOut('slow'); 
                    }); 
                } else {
                    $('.chat_submit_form .error').fadeIn('slow', function () {
                        $(this).delay(1000).fadeOut('slow'); 
                    }); 
                }
            }
        );
        return false; 
    });

    // Update last navigation time feature
    updateLastNav = function() {
        $.getJSON('index.php?action=update_last_nav', function() {

            // refresh last nav time
            setTimeout(function(){
               updateLastNav();
            }, 180000); // 3 mins
        });
    }
    updateLastNav();
});

I modified this file in order to receive valid messages (of current active room), and to send messages to certain rooms.


Live Demo

Conclusion

I suppose that our chat script is quite ready. If you have any ideas (what will be useful to implement here) – you are always welcome to share your ideas. Don’t hesitate to contact us. Good luck and welcome back!