picture of Mike Hall
Once upon a time there was a reasonably popular web-based chat room called Star Trekker chat. I happened into this chat thanks to a friend and even though Star Trek fans were hardly my favourite group of people I found that for the most part people in there were friendly and fun. But when Star Trekker shut down, thanks to its Perl backend eating server resources for lunch, these happy and kindly people were left with nowhere to go. It was fortunate that at that time I opened my own similar chat room and managed to attract much of the homeless traffic from Trekker. Wary of the resource problems caused by Perl, I was pleased when a friend introduced me to PHP.
This particular design of web-based chat uses variables posted from a form, processes them into HTML and writes them to a file. Put the form and the message file in a frameset and you have something that looks reasonably like a BeSeen chat room. Of course the advantage is, our chat room can be a little more clever than it's BeSeen cousin.
<form action="chat.php3" method="post">
Name : <input type="text" name="name"><br>
Message : <input type="text" name="message"><br>
<input type="submit" value="Send">
</form>
This is your basic form input. You'll probably want to pretty it up more than that, but to all intents and purposes, this is that you're dealing with. It sends two variables through to chat.php3 called $name and $message.
Before we deal with those variables, however, we need to extract the current contents of the message file, otherwise we'd only see one message at a time. Hardly a way to conduct a conversation. Being familiar as I am with the structure of my own message file, I know that each message is terminated by a newline character. This means I can use the file() function to read the message file into an array.
The message file is 12 lines long. Of those 12 lines, the line 1 is a set of headers, lines 2-11 are old messages and line 12 contains my footers.
All I am interested in is obtaining a string that contains most of those old messages.

<?php

// Read file into an array
$message_array file("messages.html");

// Compile the string
for ($counter 1$counter 10$counter++) {
    
$old_messages .= $message_array[$counter];
}

?>
When compiling the string, I initiated the for loop with $counter = 1 not $counter = 0 as is common. This is because I know that element 0 of $message_array contains my headers and I don't want those. Also, by setting the loop condition to $counter < 10 means that only elements 1 thru 9 of the array are read into the string. Of the other two elements, 11 contains my footers and 10 contains the oldest message. Both of which I want to remove so I only ever have 10 messages on screen at any given time. Altering the $counter < 10 expression allows you to vary the amount of messages retained.
Now I have my old messages I want to make the new message. We have our two variables $name and $message so writing a new message string is easy.

<?php 
$new_message 
"$name : $message<br>\n"
?>
We're nearly ready write our message file. All we need are headers and footers. Start simple with the headers:

<?php 

// It's important that there are no newline
// characters except at the end of the string.
// This keeps all the headers together.
$header "<html><body bgcolor=\"#000000\" text=\"#ffffff\">\n";

?>
We want the message screen to auto refresh so people viewing the site can see new posts. In preference to using JavaScript, I use an META refresh, principally because it's more likely to be supported client-side. I also don't want the search engines indexing my message file. So we refine $header to :

<?php 

$header 
"<html><head><meta http-equiv=\"refresh\" content=\"8\">".
    
"<meta name=\"robots\" content=\"noindex\"></head>".
    
"<body bgcolor=\"#000000\" text=\"#ffffff\">\n";

?>
In the file footer I tend to put a little copyright information as well as close the tags I opened in the header.

<?php 

$footer 
"<p align=\"center\"><font color=\"#000000\">".
    
"&copy; Mike Hall 2000</font></p></body></html>";

?>
Wrapping the copyright in <font color="#000000"> means that unless selected it'll be invisible against the equally #000000 background. This just stops it being intrusive.
Now we finally have all we need to write the new file :

<?php 

// Opens file for writing and truncates file length to zero.
$open_file fopen("messages.html""w");

// write file header...
fputs($open_file$header);

// ... new line...
// (stripSlashes because we don't want all
// our escape characters appearing in the
// message file)
fputs($open_filestripslashes($new_message));

// ... old lines ...
fputs($open_file$old_messages);

// ... and footer.
fputs($open_file$footer);

// Close the file when you're done. Don't forget to wash your hands
fclose($open_file);

?>
So we now have a very very basic web chat. Let's look at some of the features.
<form action="chat.php3">
Name : <input type="text" name="name"> Color: <input type="text" name="color"><br>
Message : <input type="text" name="message"><br>
<input type="submit" name="Send">
</form>
We've added a new input to the form, meaning we get a nice new variable to play with in the script. We read the old messages as before, but in compiling the new one, we use a little more HTML.

<?php 

$new_message 
"<font color=\"$color\">$name : $message</font><br>\n";

?>
And while we're thinking about it, we'll add a few more bells and whistles.

<?php 

$time 
date("H:i");
$new_message "<font color=\"$color\"><b><i>$name</i></b>".
    
" <font size=\"1\">($time)</font> : $message</font><br>\n";

?>
Now we're getting somewhere in terms of design. Another feature the regulars at my chat room enjoy is the ability to display email and URL link icons in their message. Two more form inputs were incorporated and the links processed thus :

<?php 

if($url
    
$link_html .= " <a href=\"$url\" target=\"_new\">".
        
"<font face=\"wingdings\">2</font></a>";
if(
$mail
    
$link_html .= " <a href=\"$mail\" target=\"_new\">".
        
"<font face=\"wingdings\">*</font></a>";

$new_message "<font color=\"$color\"><b><i>$name</i></b>".
    
" $link_html <font size=\"1\">($time)</font> : $message</font><br>\n";

?>
Again, we could just could just leave things at that, but there are certain security issues. What is to stop someone entering nasty HTML into the message box? A little JavaScript? A little VBScript? Even something as simple as a 5,000k JPEG image can do harm. Refreshing every eight seconds on the screens of heaven-knows how many people across the globe. Could be murder on your bandwidth - not something we want. We could remove all HTML and PHP elements using the strip_tags() function, but I want the chatters to be able to use basic HTML in their posts. Basic elements like <i>, <b> and <font> that can be used to spruce up a message.
For almost two years I used a complicated series of regex statements to screen out the nasty HTML. However I found that I was more or less constantly adding to this filter, until it was taking up most of my code! Frustrated by inefficient code I was again rescued by a friend who suggested approaching the problem from the other direction. Instead of telling the script what HTML it can use, tell it what it can't.
htmlspecialchars() is a much under-used PHP function. It replaces certain characters with their HTML entities. So " becomes &quot;, & becomes &amp;, < becomes &lt; and > becomes &gt;. By running the $new_message variable through htmlspecialchars() I turn ...
<iframe src="http://www.microsoft.com">
... into ...
&lt;iframe src=&quot;http://www.microsoft.com&quot;&gt;
... rendering it useless. A series of string replace functions can then re-enable certain tags. Then comes the clever part. We use str_replace() to undo some of what htmlspecialchars() did.

<?php 

$message 
htmlspecialchars($message);

$message str_replace("&gt;"">"$message);
$message str_replace("&lt;b>""<b>"$message);
$message str_replace("&lt;/b>""</b>"$message);
$message str_replace("&lt;i>""<i>"$message);
$message str_replace("&lt;/i>""</i>"$message);
$message str_replace("&lt;font ""<font "$message);
$message str_replace("&lt;/font>""</font>"$message);

?>
And so on. There are cleverer ways of doing this using eregi_replace() but I don't want to complicate matters.
We have to make sure we run the $name, $color, $url and $mail through this filter too, otherwise the malicious users can enter code that way. Save yourself work and bundle the filter off in a function.

<?php 

$name 
filterHTML($name);
$message filterHTML($message);
$color filterHTML($color);
$url filterHTML($url);
$mail filterHTML($mail);

?>
One last thing we should address is how to deal with troublemakers. This is a particular problem if you end up with a popular chat. It's a sad fact we have to face up to - people are frequently jerks. And because of this we have to make sure that only the right kind of people get into our chat room.
One idea is a login system. Store usernames and passwords in a MySQL database and make users register before they can access your chat. The other idea is to log the IP of troublemakers and prevent that IP posting.
This second system is flawed to a certain extent, in that malicious users can switch between any number of proxies to change their IP. And as most ISP's assign dynamic IP addresses, even the stupid ones can just reconnect and get access to the chat.
Most "casual" troublemakers won't be bothered about going to all that effort just to put the wind up a handful of individials. Once "banned" they'll never bother coming back.
So our "banned" IPs are logged in a file called banned.ban. Each IP is terminated by a newline character so as before we can use the file() function to read the file into an array.
$banned_array = file("banned.ban");
Now we have the file we need to cross-reference it with the $REMOTE_ADDR variable so we can tell if the user trying to post a message is banned or not. Simplicity itself :

<?php 

for ($counter=0;$counter<sizeof($banned_array);$counter++) {
    if (
$banned_array[$counter] == $REMOTE_ADDR) {
        print(
"<font color=\"red\" face=\"arial\" align=\"center\">".
            
"You have been banned from this chat</font>");
        exit;
    }
}

?>
The exit command will stop immediately the execution of the script. Place your ban checks before you start performing operations on the POSTed variables and your banned user can't use the chat.
With a mind to accounting in some way for the problem of dynamic IP addresses, it's probably an idea to check the IP block the IP belongs to. A simple function makes makes this easy.

<?php 

function makeMask($ip) {
    
// remember to escape the . so PHP doesn't think it's a concatenation
    
$ip_array explode("\."$ip);
    
$ip_mask "$ip_array[0]\.$ip_array[1]\.$ip_array[2]";
    return 
$ip_mask;
}

?>
Then we replace the looped if with:

<?php 

for ($counter=0;$counter<sizeof($banned_array);$counter++) {
    if (
makeMask($REMOTE_ADDR) == makeMask($banned_array[$counter])) {
        print(
"<font color=\"red\" face=\"arial\" align=\"center\">".
            
"You have been banned from this chat</font>");
        exit;
    }
}

?>
... we have some protection against dynamic IPs.
Finally we need a way to get the troublemaker's IP in the first place. I do this by logging $name and $REMOTE_ADDR in a file called iplist.html. At a separate, secret URL I can view the message and monitor the IP addresses at the same time. This has the added bonus of being able to spot impersonators - a common crime in these places.
iplist.html is created in much the same way as messages.html. First we extract the current values from iplist.html, we strip out the header, footer and oldest IP record and then create a new record, new header and new footer. To make the layout more clear, I used a table.

<?php 

$header 
"<html><body bgcolor=\"#000000\" text=\"#ffffff\"><table border=\"0\">\n";
$footer "</table></body></html>";
$new_ip "<tr><td>$name</td><td>$REMOTE_ADDR</td></tr>\n";

$ip_array file("iplist.html");
for (
$counter 1$counter 20$counter++) 
    
$old_ips.= $ip_array[$counter];

?>
Simply write that to the disk the same way as we did the message file and there we have it. A simple web-based chat room. Better cross platform compatibility than Java and no need for anything more than a web browser - I'm told that even the Dreamcast works with this!
Somethings you might want to try yourself include combining common pieces of code into functions, writing a script that will automatically add troublemakers to the banned list and writing a regex expression that scans a message text for URL's and e-mail addresses and automatically turning them into likes (as Outlook Express and ICQ do).
Play around, have fun, experiment. I did. This is how I started in PHP and now I've made a career of it. Happy Chatting.
--Mike