Scripts
PHP/MySQL/JS/AJAX Chatbox
Ik heb me op een AJAX-project gestort om te kijken naar de (on)mogelijkheden en toepasbaarheid van AJAX binnen PHP applicaties. Tijdens de ontwikkeling heb ik o.a. enkele JS classes gemaakt die o.a. helpen met het uitvoeren van de AJAX-request, en het verwerken van de ontvangen XML data. Je kunt de broncode downloaden op: PHP/AJAX Chatbox Ik heb een protocol geschreven voor de server; deze is hier te downloaden. Ik ben benieuwd naar jullie feedback/suggesties!
phpmysqljsajax-chatbox
Server.php ontvangt POST data en verwerkt deze. Als resultaat geeft de server altijd een XML bericht terug.
Voorbeeld:
[code]<?xml version="1.0" encoding="utf8" ?>
<document>
<reply>
<code>401</code>
<text>No new messages available.</text>
</reply>
.... (overige XML DATA) ...
</document>[/code]
Ik heb er voor gekozen elke XML-output te voorzien van een reply-tag. Deze bevat een CODE en een TEXTuele omschrijving. Hierdoor kan het script dat de XML moet verwerken weten welke data het kan verwachten. Buiten de reply tag kan de XML output ook andere data bevatten.
--- server.php ---[code]<?php
/*
Author: Martijn Wieringa
E-mail: [email protected]
Website: www.php-solutions.nl
*/
session_cache_expire(2);
session_start();
if(isset($_SESSION['REMOTE_ADDR']))
{
if(strcmp($_SESSION['REMOTE_ADDR'], $_SERVER['REMOTE_ADDR']) !== 0)
{
sendXml(903, 'Error while validating your IP address.');
}
}
else
{
$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['ID'] = 0;
$_SESSION['NICKNAME'] = '';
$_SESSION['DATETIME'] = 0;
}
if(@mysql_connect('localhost', 'USERNAME', 'PASSWORD'))
{
if(@mysql_select_db('DATABASE'))
{
// Update activity
if($_SESSION['ID'])
{
$sql = "UPDATE `users` SET `datetime` = '" . date('YmdHis') . "' WHERE `id` = '" . $_SESSION['ID'] . "' LIMIT 1;";
mysql_query($sql);
}
doCleanup();
$action = (empty($_POST['action']) ? '' : $_POST['action']);
if(strcasecmp($action, 'read') === 0)
{
if($_SESSION['ID'])
{
doRead();
}
else
{
sendXml(903, 'You are not registered to the server.');
}
}
elseif(strcasecmp($action, 'write') === 0)
{
if($_SESSION['ID'])
{
doWrite();
}
else
{
sendXml(903, 'You are not registered to the server.');
}
}
elseif(strcasecmp($action, 'register') === 0)
{
doRegister();
}
elseif(strcasecmp($action, 'unregister') === 0)
{
if($_SESSION['ID'])
{
doUnregister();
}
else
{
sendXml(903, 'You are not registered to the server.');
}
}
else
{
sendXml(904, 'Unknown action!');
}
}
else
{
sendXml(902, 'Cannot select database.');
}
}
else
{
sendXml(902, 'Cannot connect to database server.');
}
// Read messages from database
function doRead()
{
$xml = '';
$session = session_id();
$remote_addr = $_SERVER['REMOTE_ADDR'];
$id = (isset($_POST['id']) ? intval($_POST['id']) : 0);
$iDate = max(date('YmdHis', strtotime('-2 minutes')), $_SESSION['DATETIME']);
$sql = "SELECT `t1`.`id`, `t1`.`datetime`, `t1`.`event`, `t1`.`text`, `t2`.`nickname` AS `from_name`, `t3`.`nickname` AS `to_name`
FROM `messages` AS `t1`
LEFT JOIN `users` AS `t2` ON `t1`.`from` = `t2`.`id`
LEFT JOIN `users` AS `t3` ON `t1`.`to` = `t3`.`id`
WHERE (`t1`.`id` > '" . mysql_real_escape_string($id) . "')
AND ((`t1`.`to` = '0') OR ((`t1`.`from` = '" . $_SESSION['ID'] . "') OR (`t1`.`to` = '" . $_SESSION['ID'] . "')))
AND (`t1`.`datetime` >= '" . $iDate . "')
ORDER BY `id` ASC;";
if($rs = mysql_query($sql))
{
if(mysql_num_rows($rs))
{
$data = '<list>';
while($r = mysql_fetch_assoc($rs))
{
$data .= '
<item>
<id>' . $r['id'] . '</id>
<datetime>' . $r['datetime'] . '</datetime>
<from>' . escapeXml($r['from_name']) . '</from>
<to>' . escapeXml($r['to_name']) . '</to>
<event>' . escapeXml($r['event']) . '</event>
<text>' . escapeXml($r['text']) . '</text>
</item>';
}
$data .= '
</list>';
sendXml(400, 'Sending message list.', $data);
}
else
{
sendXml(401, 'No new messages available.');
}
}
else
{
sendXml(902, 'Error while loading messages from database.');
}
}
// Write message to database
function doWrite()
{
$xml = '';
$session = session_id();
$remote_addr = $_SERVER['REMOTE_ADDR'];
$from_id = $_SESSION['ID'];
$to_id = 0;
$to = (empty($_POST['user']) ? '' : trim($_POST['user']));
$text = (empty($_POST['text']) ? '' : trim($_POST['text']));
if($text)
{
if(preg_match('/^[a-zA-Z0-9]{1,255}$/', $to))
{
$sql = "SELECT `id` FROM `users` WHERE `nickname` = '" . mysql_real_escape_string($to) . "' LIMIT 1;";
$rs = mysql_query($sql);
if($rs && (mysql_num_rows($rs) > 0))
{
$r = mysql_fetch_assoc($rs);
$to_id = $r['id'];
}
else
{
sendXml(302, 'Nickname not available for private messages.');
}
}
$sql = "INSERT INTO `messages` (`datetime`, `from`, `to`, `event`, `text`) VALUES ('" . date('YmdHis') . "', '" . mysql_real_escape_string($from_id) . "', '" . mysql_real_escape_string($to_id) . "', '', '" . mysql_real_escape_string($text) . "');";
if(mysql_query($sql))
{
sendXml(300, 'Message processed.');
}
else
{
sendXml(902, 'Error while adding message to database.');
}
}
else
{
sendXml(301, 'Message is empty.');
}
}
// Register to database
function doRegister()
{
$xml = '';
$session = session_id();
$remote_addr = $_SERVER['REMOTE_ADDR'];
$nickname = (empty($_POST['nickname']) ? '' : $_POST['nickname']);
if(preg_match('/^[a-zA-Z0-9]{1,255}$/', $nickname))
{
$sql = "SELECT * FROM `users` WHERE (`nickname` = '" . mysql_real_escape_string($nickname) . "') LIMIT 1;";
$rs = mysql_query($sql);
if($rs && (mysql_num_rows($rs) > 0))
{
sendXml(102, 'Nickname in use by another visitor.');
}
else
{
$sql = "INSERT INTO `users` (`nickname`, `datetime`, `ip`, `session`, `expired`) VALUES ('" . mysql_real_escape_string($nickname) . "', '" . date('YmdHis') . "', '" . mysql_real_escape_string($remote_addr) . "', '" . mysql_real_escape_string($session) . "', '0');";
if(mysql_query($sql))
{
$_SESSION['ID'] = mysql_insert_id();
$_SESSION['NICKNAME'] = $nickname;
$_SESSION['DATETIME'] = date('YmdHis');
$data = ' <user_list>';
$sql = "SELECT `nickname` FROM `users` WHERE (`expired` = '0') ORDER BY `nickname` ASC;";
$rs = mysql_query($sql);
if($rs && mysql_num_rows($rs))
{
while($r = mysql_fetch_assoc($rs))
{
$data .= '
<item>
<nickname>' . escapeXml($r['nickname']) . '</nickname>
</item>';
}
}
$data .= '
</user_list>';
$text = $nickname . ' has entered the chatroom.';
$sql = "INSERT INTO `messages` (`datetime`, `from`, `to`, `event`, `text`) VALUES ('" . date('YmdHis') . "', '" . mysql_real_escape_string($_SESSION['ID']) . "', '0', 'JOIN', '" . mysql_real_escape_string($text) . "');";
@mysql_query($sql);
sendXml(100, 'Nickname registered.', $data);
}
else
{
sendXml(902, 'Cannot add nickname to database.');
}
}
}
else
{
sendXml(101, 'Nickname invalid.');
}
return $xml;
}
function doUnregister()
{
$session = session_id();
$remote_addr = $_SERVER['REMOTE_ADDR'];
$text = $_SESSION['NICKNAME'] . ' has left the chatroom.';
$sql = "INSERT INTO `messages` (`datetime`, `from`, `to`, `event`, `text`) VALUES ('" . date('YmdHis') . "', '" . mysql_real_escape_string($_SESSION['ID']) . "', '0', 'QUIT', '" . mysql_real_escape_string($text) . "');";
if(mysql_query($sql))
{
$sql = "UPDATE `users` SET `expired` = '1' WHERE `id` = '" . $_SESSION['ID'] . "' LIMIT 1;";
mysql_query($sql);
session_destroy();
sendXml(200, 'You are no longer registered to the chatbox.');
}
else
{
sendXml(902, 'Error while closing chatbox.');
}
}
// Cleanup database
function doCleanup()
{
$iDate = date('YmdHis', strtotime('-2 minutes'));
$sql = "SELECT `id`, `nickname` FROM `users` WHERE (`datetime` < '" . $iDate . "') AND (`expired` = '0');";
$rs = mysql_query($sql);
if($rs && mysql_num_rows($rs))
{
while($r = mysql_fetch_assoc($rs))
{
$sql = "UPDATE `users` SET `expired` = '1' WHERE (`datetime` < '" . $iDate . "') AND (`expired` = '0') AND (`id` = '" . $r['id'] . "') LIMIT 1;";
mysql_query($sql);
if(mysql_affected_rows())
{
$sql = "INSERT INTO `messages` (`datetime`, `from`, `to`, `event`, `text`) VALUES ('" . date('YmdHis') . "', '" . mysql_real_escape_string($r['id']) . "', '0', 'QUIT', '" . mysql_real_escape_string($r['nickname']) . " was idle for too long.');";
mysql_query($sql);
}
}
}
$sql = "DELETE FROM `messages` WHERE `datetime` < '" . $iDate . "';";
mysql_query($sql);
$iDate = date('YmdHis', strtotime('-60 minutes'));
$sql = "DELETE FROM `users` WHERE `datetime` < '" . $iDate . "';";
mysql_query($sql);
}
// Output XML data
function sendXml($code, $message, $data = '')
{
$xml = '<?xml version="1.0" encoding="utf8" ?>
<document>
<reply>
<code>' . escapeXml($code) . '</code>
<text>' . escapeXml($message) . '</text>
</reply>' . ($data ? "\r\n" . $data : '') . '
</document>';
echo $xml;
exit;
}
// Escape XML characters from a string
function escapeXml($string)
{
return utf8_encode(str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string));
}
?>[/code]---
De client maakt nauwelijks gebruik van PHP, maar gebruikt JavaScript.
--- client.php ---[code]<?php
/*
Author: Martijn Wieringa
E-mail: [email protected]
Website: www.php-solutions.nl
*/
session_start();
?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Chatbox</title>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
<meta http-equiv="content-language" content="nl-nl">
<meta name="robots" content="index, follow">
<link href="css/default.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="output" id="output">Please enter your nickname, and press SEND to start the chat!</div>
<div class="users" id="users"></div>
<div class="input"><input id="input" class="textfield" type="text" value="" onkeyup="javascript: sendChatboxMessage(event, true);"> <input id="user" type="hidden" value=""> <input class="button" type="button" value="SEND" onclick="javascript: sendChatboxMessage(event, false);"> <input class="button" type="button" value="QUIT" onclick="javascript: quitChatbox();"></div>
<script src="js/php.js" type="text/javascript"></script>
<script src="js/util.js" type="text/javascript"></script>
<script src="js/xml.js" type="text/javascript"></script>
<script src="js/ajax.js" type="text/javascript"></script>
<script src="js/chatbox.js" type="text/javascript"></script>
</body>
</html>[/code]---
[b]php.js en util.js[/b]
php.js en util.js zijn algemene bibliotheken die tal van JS functies bevatten. Sommige scripts rusten op functies binnen deze bibliotheken. (Mogelijk pas ik dit in de toekomst nog aan).
[b]ajax.js[/b]
ajax.js is een Javascript-class die ik heb geschreven om AJAX requests eenvoudiger te verwerken. Er zit o.a. een QUEUE-mechanisme in zodat je browser nooit meerdere requests tegelijk uitvoert.
--- ajax.js ---[code]
/*
Author: Martijn Wieringa
E-mail: [email protected]
Website: www.php-solutions.nl
*/
function AjaxObject()
{
this.oHttp;
this.aQueue = new Array();
this.bActive = false;
this.sCurrentUrl = '';
this.sCurrentData = '';
this.sCurrentCallback = '';
this.init = function()
{
try
{
this.oHttp = new XMLHttpRequest();
}
catch(e1) // IE 6 or older
{
var aHttpVersions = new Array('MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP');
for(var i = 0; (i < aHttpVersions.length) && !this.oHttp; i++)
{
try
{
this.oHttp = new ActiveXObject(aHttpVersions[i]);
}
catch(e2)
{
}
}
}
if(!this.oHttp)
{
alert('This website uses AJAX, but your browser doesn\'t support the HttpRequest object.');
}
}
this.load = function(f_url, f_callback, f_data)
{
this.aQueue.push(new Array(f_url, f_callback, f_data));
this.__process();
}
this.isActive = function()
{
return this.bActive;
}
this.__process = function()
{
if(this.oHttp && !this.bActive)
{
this.bActive = true;
var a = this.aQueue.shift();
if(a) // Setup new request
{
this.sCurrentUrl = a[0];
this.sCurrentCallback = a[1];
this.sCurrentData = a[2];
var sMethod = 'GET';
if(this.sCurrentData)
{
sMethod = 'POST';
}
else
{
this.sCurrentData = null;
}
try
{
this.oHttp.open(sMethod, this.sCurrentUrl, true);
// Define headers
if(sMethod == 'POST')
{
this.oHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
this.oHttp.setRequestHeader('Cache-Control', 'no-cache');
this.oHttp.setRequestHeader('COOKIE', document.cookie);
this.oHttp.setRequestHeader('X_USERAGENT', 'AJAX');
this.oHttp.onreadystatechange = function() { AJAX.__callback(); };
this.oHttp.send(this.sCurrentData);
}
catch(e1)
{
alert('Cannot connect to ' + this.sCurrentUrl);
this.bActive = false;
// Process next request in queue
this.__process();
}
}
else // Queue empty
{
this.bActive = false;
}
}
}
this.__callback = function()
{
if(this.oHttp)
{
if(this.sCurrentCallback && this.oHttp.readyState == 4)
{
if(this.oHttp.status == 200)
{
try
{
if(this.sCurrentCallback)
{
this.sCurrentCallback(this.oHttp.responseText);
}
}
catch(e1)
{
alert('Error while processing ' + this.sCurrentUrl + ':\n' + e1.toString());
}
}
else
{
alert('Error while loading ' + this.sCurrentUrl + ':\n' + this.oHttp.statusText);
}
this.bActive = false;
// Process next request in queue
this.__process();
}
}
}
this.init();
}
var AJAX = new AjaxObject();[/code]---
[b]xml.js[/b]
xml.js is een Javascript-class die ik heb geschreven om XML data eenvoudiger te verwerken.
--- xml.js ---[code]
/*
Author: Martijn Wieringa
E-mail: [email protected]
Website: www.php-solutions.nl
*/
function XmlObject(f_data)
{
this.sData = f_data;
this.iOffset = 0;
this.setData = function(f_data)
{
this.sXmlData = f_data;
}
// Get value of node
this.getValue = function(f_tagname, f_update_offset)
{
var sTagOpen = '<' + f_tagname + '>';
var sTagClose = '</' + f_tagname + '>';
var iStart = PHP.strpos(this.sData, sTagOpen, this.iOffset);
if(iStart == false)
{
return '';
}
else
{
iStart += sTagOpen.length;
}
var iEnd = PHP.strpos(this.sData, sTagClose, iStart);
if(iEnd == false)
{
iEnd = this.sData.length;
}
if(f_update_offset == true)
{
this.iOffset = iEnd + sTagClose.length;
}
return PHP.substr(this.sData, iStart, iEnd - iStart);
}
this.getOffset = function()
{
return this.iOffset;
}
this.setOffset = function(f_offset)
{
if(isNaN(f_offset))
{
var sTagname = '<' + f_offset + '>';
var iOffset = PHP.strpos(this.sData, sTagname, this.iOffset);
if(iOffset == false)
{
return false;
}
else
{
this.iOffset = iOffset + sTagname.length;
}
}
else
{
this.iOffset = f_offset;
}
return true;
}
}[/code]---
[b]chatbox.js[/b]
Deze bevat alle functies die de boel aan elkaar binden. Zeer chatbox-specifieke functies dus die de communicatie tussen client en server controleren.
--- chatbox.js ---[code]
/*
Author: Martijn Wieringa
E-mail: [email protected]
Website: www.php-solutions.nl
*/
var CHATBOX_REGISTERED = false;
var CHATBOX_MESSAGE_ID = 0;
var CHATBOX_TIMER = null;
var CHATBOX_USERS = new Array();
var CHATBOX_NICKNAME = '';
function getChatboxMessages()
{
if(CHATBOX_REGISTERED && (AJAX.isActive() == false))
{
AJAX.load('server.php', __getChatboxMessages_callback, 'action=read&id=' + CHATBOX_MESSAGE_ID);
}
}
function sendChatboxMessage(f_event, f_check_keycode)
{
if(window.event)
{
f_event = window.event;
}
try
{
var sText = PHP.trim(document.getElementById('input').value);
var sUser = PHP.trim(document.getElementById('user').value);
if((sText != '') && ((f_check_keycode == false) || (f_event.keyCode == 13)))
{
if(CHATBOX_REGISTERED)
{
AJAX.load('server.php', __sendChatboxMessage_callback, 'action=write&user=' + PHP.urlencode(sUser) + '&text=' + PHP.urlencode(sText));
}
else
{
CHATBOX_NICKNAME = sText;
AJAX.load('server.php', __sendChatboxRegister_callback, 'action=register&nickname=' + PHP.urlencode(sText));
}
document.getElementById('input').value = '';
}
}
catch(e1)
{
__addMessage('0', '~', CHATBOX_NICKNAME, 'ERROR', 'Error while sending message.');
__setChatboxTimer(1000);
}
}
function setPrivate(f_user)
{
document.getElementById('user').value = f_user;
__drawUsers();
}
function quitChatbox()
{
if(CHATBOX_REGISTERED)
{
AJAX.load('server.php', __quitChatbox_callback, 'action=unregister');
}
}
function __setChatboxTimer(f_timeout)
{
if(isNaN(f_timeout))
{
f_timeout = 4000;
}
if(CHATBOX_TIMER)
{
clearTimeout(CHATBOX_TIMER);
}
if(CHATBOX_REGISTERED)
{
CHATBOX_TIMER = setTimeout('getChatboxMessages()', f_timeout);
}
}
function __getChatboxMessages_callback(f_data)
{
var oXml = new XmlObject(f_data);
var sReplyCode = oXml.getValue('code');
var sReplyText = oXml.getValue('text');
if(sReplyCode == '400')
{
// Process message list
while(oXml.setOffset('item'))
{
CHATBOX_MESSAGE_ID = oXml.getValue('id');
var _date = PHP.unescapexml(oXml.getValue('date'));
var _from = PHP.unescapexml(oXml.getValue('from'));
var _to = PHP.unescapexml(oXml.getValue('to'));
var _event = PHP.unescapexml(oXml.getValue('event'));
var _text = PHP.unescapexml(oXml.getValue('text'));
if(_event == 'JOIN')
{
__addUser(_from);
__addMessage(_date, '~', _to, _event, _text);
}
else if(_event == 'QUIT')
{
__delUser(_from);
__addMessage(_date, '~', _to, _event, _text);
}
else
{
__addMessage(_date, _from, _to, _event, _text);
}
}
}
else if(sReplyCode != '401')
{
__addMessage('0', '~', CHATBOX_NICKNAME, 'ERROR', 'Error [' + sReplyCode + ']: ' + sReplyText);
}
__setChatboxTimer();
}
function __sendChatboxMessage_callback(f_data)
{
var oXml = new XmlObject(f_data);
var sReplyCode = oXml.getValue('code');
var sReplyText = oXml.getValue('text');
if(sReplyCode != '300')
{
__addMessage('0', '~', CHATBOX_NICKNAME, 'ERROR', 'Error [' + sReplyCode + ']: ' + sReplyText);
}
__setChatboxTimer(1000);
}
function __sendChatboxRegister_callback(f_data)
{
var oXml = new XmlObject(f_data);
var sReplyCode = oXml.getValue('code');
var sReplyText = oXml.getValue('text');
if(sReplyCode == '100')
{
CHATBOX_REGISTERED = true;
// Process user list
while(oXml.setOffset('item'))
{
var _nickname = PHP.unescapexml(oXml.getValue('nickname'));
__addUser(_nickname);
}
__setChatboxTimer(1000);
}
else
{
__addMessage('0', '~', CHATBOX_NICKNAME, 'ERROR', 'Error [' + sReplyCode + ']: ' + sReplyText);
}
}
function __quitChatbox_callback(f_data)
{
var oXml = new XmlObject(f_data);
var sReplyCode = oXml.getValue('code');
var sReplyText = oXml.getValue('text');
CHATBOX_REGISTERED = false;
if(sReplyCode == '200')
{
__addMessage('0', '~', CHATBOX_NICKNAME, 'QUIT', 'You have left the chatbox.');
}
else
{
__addMessage('0', '~', CHATBOX_NICKNAME, 'ERROR', 'Error [' + sReplyCode + ']: ' + sReplyText);
}
}
function __addMessage(f_date, f_from, f_to, f_event, f_text)
{
document.getElementById('output').innerHTML = document.getElementById('output').innerHTML + '<br>\n<span class="' + (f_event ? f_event.toLowerCase() : f_to ? 'private' : 'public') + '">[' + PHP.escapehtml(f_from) + '] ' + PHP.stringtohtml(f_text) + '</span>';
document.getElementById('output').scrollTop = document.getElementById('output').scrollHeight;
}
function __addUser(f_user)
{
var bUserFound = false;
for(var i = 0; i < CHATBOX_USERS.length; i++)
{
if(CHATBOX_USERS[i] == f_user)
{
bUserFound = true;
break;
}
}
if(bUserFound == false)
{
CHATBOX_USERS[CHATBOX_USERS.length] = f_user;
CHATBOX_USERS.sort();
__drawUsers();
}
}
function __delUser(f_user)
{
var bUserFound = false;
var users = new Array();
for(var i = 0; i < CHATBOX_USERS.length; i++)
{
if(CHATBOX_USERS[i] == f_user)
{
bUserFound = true;
}
else
{
users[users.length] = CHATBOX_USERS[i];
}
}
if(bUserFound)
{
CHATBOX_USERS = users;
__drawUsers();
}
}
function __drawUsers()
{
var sUserlist = '';
var sUser = document.getElementById('user').value;
sUserlist += '<span' + ((sUser == '') ? ' class="active"' : '') + ' onclick="setPrivate(\'\')">-</span>';
for(var i = 0; i < CHATBOX_USERS.length; i++)
{
sUserlist += '<br>\n<span' + ((sUser == CHATBOX_USERS[i]) ? ' class="active"' : '') + ' onclick="setPrivate(\'' + CHATBOX_USERS[i] + '\')">' + PHP.substr(CHATBOX_USERS[i], 0, 8) + '</span>';
}
document.getElementById('users').innerHTML = sUserlist;
}
[/code]---
Reacties
0