Chat Simples com Banco de Dados

Este projeto foi encontrado por mim no site http://fazer-site.net/criando-chat-no-php-etapa-1/, foi distribuido livremente, por segurança eu fiz esta réplica no meu site, assim, há menos risco desse material ser perdido.

1. Introdução
Este projeto tem a finalidade de ensinar as funconalidas essenciais de um chat, por enquanto o layout não foi desenvolvido.
1.1. Funcionalidades Básicas do Chat
Descrição do que será feito, funcionalidades do chat.
1.1.1. O Sistema terá uma página inicial onde o visitante escolherá um nome e selecionará a sala que deseja entrar.
1.1.2. Após clicar no botão entrar, o usuário será redirecionado para a página de conversas. Nessa página serão listados todos os usuários que estão online.
1.1.3. Ainda na página de conversas haverá um campo para o usuário digitar o texto juntamente com um botão para envio do texto para a sala. Também será possível selecionar um usuário para que a mensagem seja direcionada para ele, porém a mensagem será pública e todos os outros usuários poderão vê-la.
1.1.4. As conversas deverão ser atualizadas a cada 6 segundos.
1.1.5. Deverá ter um link para o usuário poder sair da sala.
1.1.6. Quando o usuário sair da sala, uma mensagem deverá ser enviada para a respectiva sala avisando que o usuário saiu da sala. Esse seria o funcionamento básico do chat, porém há outros detalhes a serem levados em consideração. Vejamos:
1.1.7. Não poderão conter dois usuários com o mesmo nome na sala. Isso iria gerar confusão.
1.1.8. O sistema deve enviar uma mensagem dizendo que determinado usuário saiu da sala, mesmo quando ele não clicar no botão sair e fechar o navegador ou a página do
chat.
1.1.9. Embora as conversas devam ser atualizadas a cada 6 segundos, isso não pode ocorrer na página inteira. Isso iria irritar o usuário e tornar a conversa praticamente impossível, visto que ele teria seu texto interrompido a cada 6 segundos.
1.1.10. O usuário só poderá visualizar as conversas que foram enviadas depois que ele entrou na sala.
1.1.11. As interações mais antigas que 10 horas deverão ser excluídas para poupar espaço em banco de dados

2. Estrutura do Banco de Dados
O primeiro passo é criar um banco de dados com algum nome sugestivo, nesse projeto ele se chama chat.
O segundo passo é criar as tabelas que irão armazenar os dados:
sala.sql
id_sala(int) nm_sala
A tabela salas conterá duas colunas:
a) id_sala(int) – identificador da sala
b)  nm_sala(varchar 20) – nome da sala
A função da tabela salas é armazenar o identificador e o nome das salas. Nessa tabela você pode inserir quantas salas desejar.

usuarios.sql
id_usuario (int) nm_usuario id_sala dt_refresh

A tabela usuários conterá 4 colunas:
a) id_usuario (int) – identificador do usuario
b) nm_usuario (varchar 20) – nome do usuario
c) id_sala (int) – chave estrangeira provinda da coluna id_sala da tabela salas.
d) dt_refresh (datetime) – guarda a última data e hora que houve interação entre o chat e o respectivo usuário. A função da tabela usuários é servir como controle para sabermos quais usuários estão online, em quais salas eles entraram, quais são os nomes desses usuários e qual foi a última data e hora que determinado usuário interagiu com o sistema. Note que, nesse caso, interagir não significa que o usuário enviou mensagens no chat, mas simplesmente que a página está aberta e se atualizando (conectando ao servidor web e buscando as ultimas conversas).

interacoes.sql
nm_usuario id_sala dt_interacao ds_interacao nm_destinatario

A tabela interações conterá 5 colunas:
a) nm_usuario (varchar 20) – nome do usuáro que originou a interação. Note que interação, nesse caso, significa alguma ação em relação ao chat. Por exemplo: entrou na sala, saiu da sala, fala com alguém. Talvez você esteja se perguntando qual a razão de ter esse nome de usuário visto que a tabela usuários já possui esse dado. Bem, não podemos esquecer que a tabela usuário é uma tabela dinâmica, ou seja, os usuários de lá serão excluídos quando saírem da sala.
b) id_sala (int) – chave estrangeira provinda da coluna id_sala da tabela salas. Servirá para sabermos em qual sala, dada interação ocorreu.
c) dt_interacao (datetime) – data e hora que a interação ocorreu.
d) ds_interacao (varchar 500) – campo que irá armazenar o descritivo da interação, ou seja, a mensagem que os usuários enviaram e suas interações com o sistema.
e) nm_destinatario (varchar 200) – caso uma mensagem tenha sido enviada para um usuário específico, o nome desse usuário será armazenado nesse campo. È isso senhores. Por incrível que pareça, nosso banco de dados está pronto. Agora só precisamos fazer a parte mais legal do sistema, codificar ele.

Vejam o código do banco:
-- phpMyAdmin SQL Dump
-- version 3.2.0.1
-- http://www.phpmyadmin.net
--
-- Servidor: localhost
-- Tempo de Geração: Dez 11, 2010 as 07:13 PM
-- Versão do Servidor: 5.1.36
-- Versão do PHP: 5.3.0

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;

--
-- Banco de Dados: `chat`
--

-- --------------------------------------------------------

--
-- Estrutura da tabela `interacoes`
--

CREATE TABLE IF NOT EXISTS `interacoes` (
  `nm_usuario` varchar(20) NOT NULL,
  `id_sala` int(11) NOT NULL,
  `dt_interacao` datetime NOT NULL,
  `ds_interacao` varchar(500) NOT NULL,
  `nm_destinatario` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Extraindo dados da tabela `interacoes`
--


-- --------------------------------------------------------

--
-- Estrutura da tabela `salas`
--

CREATE TABLE IF NOT EXISTS `salas` (
  `id_sala` int(11) NOT NULL AUTO_INCREMENT,
  `nm_sala` varchar(20) NOT NULL,
  PRIMARY KEY (`id_sala`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

--
-- Extraindo dados da tabela `salas`
--

INSERT INTO `salas` (`id_sala`, `nm_sala`) VALUES
(1, 'Crianças'),
(2, 'Adolescentes'),
(3, 'Homens e Mulheres');

-- --------------------------------------------------------

--
-- Estrutura da tabela `usuarios`
--

CREATE TABLE IF NOT EXISTS `usuarios` (
  `id_usuario` int(11) NOT NULL AUTO_INCREMENT,
  `nm_usuario` varchar(20) NOT NULL,
  `id_sala` int(11) NOT NULL,
  `dt_refresh` datetime NOT NULL,
  PRIMARY KEY (`id_usuario`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=26 ;

--
-- Extraindo dados da tabela `usuarios`
--

INSERT INTO `usuarios` (`id_usuario`, `nm_usuario`, `id_sala`, `dt_refresh`) VALUES
(22, 'Edir', 3, '2010-12-11 19:13:48');



2. Código Fonte
Esse chat PHP será composto por 5 arquivos.php, ó código está comentado.

2.1. config.php
O arquivo config.php, como o nome sugere, conterá as configurações para conexão com o banco de dados, onde:

$servidor: variável que conterá o servidor do teu banco de dados, no meu caso é localhost
$tipo_servidor: conterá o tipo do teu servidor, no meu caso é o Mysql.
$nome_do_banco: nome do teu banco de dados, aqui eu criei um banco chamado chat
$usuario: o usuario do banco de dados
$senha: a senha do banco de dados
<?php
//Informações para conexão com o banco de dados
$servidor = "localhost";
$tipo_servidor = "mysql";
$nome_do_banco = "chat";
$usuario = "root";
$senha = "";

//instancia um objeto da classe PDO chamado $conn
$conn = new PDO("$tipo_servidor:host=$servidor;dbname=$nome_do_banco",$usuario,$senha);

?>

Por fim, instancio um objeto com a conexão com o banco de dados. A partir de agora, poderei interagir com o banco de dados facilmente através da variável $conn. Note que estou usando PDO, caso você não sabia o que é PDO, leia este artigo aqui: O que é PDO

Obs:
PDO_MYSQL é um driver que implementa a interface PHP Data Objects (PDO) para acesso do PHP ao MySQL 3.x, 4.x e 5.x.
PDO_MYSQL tem a vantagens dosuporte nativo  a prepared statement presente no MySQL 4.1 e superior.
Conexão com o banco de dados com o PDO:
$con = new PDO("mysql:host=localhost;dbname=exercicio", "root", "senha");
Primeiro parâmetro: localhost, é o caminho do banco de dados.
Segundo parâmetro: exercicio, é o nome da base de dados
Terceiro parâmetro: root, é o login do banco de dados.
Quarto parâmetro: senha, é a senha do banco de dados.

2.2. functions.php
Este arquivo PHP conterá as funções que serão utilizadas no chat. Veja o código abaixo:

1.<?php
.2.//——————————————————————–Funções específicas do Chat:
.3.
.4.//Cria as interações na tabela interações.
.5.function interagir($from, $to, $sala, $chat){
.6.    global $conn;
.7.   
.8.    $now = date("Y-m-d H:i:s");
.9.   
.10.    $tbInt = $conn->prepare("insert into interacoes values(:nome, :sala, :data, :chat, :to)");
.11.    $tbInt->bindParam(":nome", $from, PDO::PARAM_STR);
.12.    $tbInt->bindParam(":sala", $sala, PDO::PARAM_INT);
.13.    $tbInt->bindParam(":data", $now, PDO::PARAM_STR);
.14.    $tbInt->bindParam(":chat", $chat, PDO::PARAM_STR);
.15.    $tbInt->bindParam(":to", $to, PDO::PARAM_STR);
.16.    $tbInt->execute();
.17.}
.18.
.19.//Insere o usuário na tabela ususarios, configura as sessões, cria a interação de entrada na sala e redireciona o usuário para a página principal do chat.
.20.function start_chat(){
.21.    global $conn, $nome, $sala;
.22.   
.23.    $unique_name = get_unique_name($nome,$nome);
.24.    $now = date("Y-m-d H:i:s");
.25.   
.26.    //Insere o usuario no banco
.27.    $insert = $conn->prepare("insert into usuarios(nm_usuario, id_sala, dt_refresh) values(:nm,:sala, :now)");
.28.    $insert->bindParam(":nm", $unique_name, PDO::PARAM_STR);
.29.    $insert->bindParam(":sala", $sala, PDO::PARAM_INT);
.30.    $insert->bindParam(":now", $now, PDO::PARAM_STR);      
.31.    $insert->execute();
.32.    $_SESSION["user"] = $conn->lastInsertId();
.33.    $_SESSION["user_name"] = $unique_name;
.34.    $_SESSION["sala"] = $sala;
.35.    $_SESSION["data_logon"] = $now;
.36.    interagir($_SESSION["user_name"], "", $_SESSION["sala"], "Entrou na sala.");
.37.    header("Location: chat.php");
.38.   
.39.}
.40.
.41.
.42.//Função que garante um nome única na respectiva sala. Caso o nome já existe, essa função insere um underline no final do nome e vai incrementando valores até que o nome gerado não tenha sido utiliado por outro usuário ativo na sala.
.43.function get_unique_name($nome_original, $nome_alterado, $repetido=1){
.44.    global $conn, $sala;
.45.   
.46.    $tbUsers = $conn->prepare("select count(*) as total from usuarios where id_sala =:sala and nm_usuario =:nm");
.47.    $tbUsers->bindParam(":sala",$sala, PDO::PARAM_INT);
.48.    $tbUsers->bindParam(":nm",$nome_alterado, PDO::PARAM_STR); 
.49.    $tbUsers->execute();
.50.    $linha = $tbUsers->fetch(PDO::FETCH_ASSOC);
.51.   
.52.    if($linha["total"]>0){
.53.        echo $nome_alterado;
.54.        echo "<br>";
.55.        return get_unique_name($nome_original, $nome_original . "_" . $repetido, ($repetido+1));
.56.    }else{
.57.        return $nome_alterado; 
.58.    }
.59.}
.60.
.61.
.62.// Exclui os usuários onde não houve refresh na página há mais de 16 segundos.
.63.function delete_offline_users(){
.64.    global $conn;
.65.   
.66.    $now = date("Y/m/d H:i:s");
.67.    $past16s = makeDataTime($now, 0,0,0,0,0,-16);
.68.   
.69.    //Seleciona usuarios ativos
.70.    $ativos = $conn->prepare("select id_usuario from usuarios where dt_refresh > :dt");
.71.    $ativos->bindParam(":dt", $past16s, PDO::PARAM_STR);
.72.    $ativos->execute();
.73.    $ativos = $ativos->fetchAll(PDO::FETCH_NUM|PDO::FETCH_COLUMN);
.74.    $ativos_ = "";
.75.   
.76.    if(count($ativos)<=0) return;
.77.   
.78.    $ativos = implode(",", $ativos);
.79.
.80.    //Pega dados dos usuarios e cria interacao de saída
.81.    $tbUser = $conn->prepare("select nm_usuario, id_sala from usuarios where id_usuario not in($ativos)");
.82.    $tbUser->execute();
.83.    while($l = $tbUser->fetch(PDO::FETCH_ASSOC)){
.84.        interagir($l["nm_usuario"], "", $l["id_sala"], "Saiu da sala.");
.85.    }
.86.   
.87.    //Exclui usuarios inativos
.88.    $del = $conn->prepare("delete from usuarios where id_usuario not in($ativos)");
.89.    $del->execute();   
.90.}
.91.
.92.//Exclui interações antigas, criadas há 10 horas atrás ou mais.
.93.function delete_old_entries(){
.94.    global $conn;
.95.   
.96.    $now = date("Y/m/d H:i:s");
.97.    $past10h = makeDataTime($now, 0,0,0,-10,0,0);
.98.   
.99.    //Excluir Interações antigas (10 horas)
.100.    $del = $conn->prepare("delete from interacoes where dt_interacao < :dt");
.101.    $del->bindParam(":dt", $past10h, PDO::PARAM_STR);
.102.    $del->execute();
.103.}
.104.
.105.//Como o nome sugere, retorna o nome de uma dada sala. Deve-se passar o id da sala que desejas o nome, como parâmetro.
.106.function pega_nome_sala($id_sala){
.107.    global $conn;
.108.   
.109.    $tbSala = $conn->prepare("select nm_sala from salas where id_sala=:id");
.110.    $tbSala->bindParam(":id", $id_sala, PDO::PARAM_INT);
.111.    $tbSala->execute();
.112.    $l = $tbSala->fetch(PDO::FETCH_ASSOC);
.113.    return $l["nm_sala"];
.114.}
.115.
.116.//——————————————————————–Funções gerais:
.117.function makeData($data, $anoConta,$mesConta,$diaConta){
.118.   $ano = substr($data,0,4);
.119.   $mes = substr($data,5,2);
.120.   $dia = substr($data,8,2);
.121.   return date(‘Y-m-d’,mktime (0, 0, 0, $mes+($mesConta), $dia+($diaConta), $ano+($anoConta)));
.122.}
.123.
.124.function makeDataTime($data, $anoConta,$mesConta,$diaConta, $horaConta, $minutoConta, $segundoConta){
.125.   $ano = substr($data,0,4);
.126.   $mes = substr($data,5,2);
.127.   $dia = substr($data,8,2);
.128.   $hora = substr($data,11,2);
.129.   $minuto = substr($data,14,2);
.130.   $segundo = substr($data,17,2);
.131.  
.132.   return date(‘Y-m-d H:i:s’,mktime ($hora+($horaConta), $minuto+($minutoConta), $segundo+($segundoConta), $mes+($mesConta), $dia+($diaConta), $ano+($anoConta))); 
.133.}
.134.
.135.function isSelected($campo, $varCampo){
.136.    if($campo==$varCampo) return " selected=selected ";
.137.    return "";
.138.}
.139.
.140.function isEmpty($campo,$name){
.141.    global $msg;
.142.    if(str_replace(" ","",$campo)==""){
.143.       $msg = "O campo " . $name . " não foi preenchido corretamente! A ação foi cancelada!";
.144.       return true;
.145.    }
.146.    return false;
.147.}
.148.
.149.
.150.function isValidEmail($value){
.151.    $pattern = "/^([a-zA-Z0-9])+([\.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-]+)+/";
.152.    return preg_match($pattern, $value);
.153.}
.154.
.155.
.156.function isDate($date){
.157.    $char = strpos($date, "/")!==false?"/":"-";
.158.    $date_array = explode($char,$date);
.159.    if(count($date_array)!=3) return false;
.160.    return checkdate($date_array[1],$date_array[0],$date_array[2])?($date_array[2] . "-" . $date_array[1] . "-" . $date_array[0]):false;
.161.}
.162.
.163.?>.<?php
//--------------------------------------------------------------------Funções específicas do Chat:

//Cria as interações na tabela interações.
function interagir($from, $to, $sala, $chat){
    global $conn;
   
    $now = date("Y-m-d H:i:s");
   
    $tbInt = $conn->prepare("insert into interacoes values(:nome, :sala, :data, :chat, :to)");
    $tbInt->bindParam(":nome", $from, PDO::PARAM_STR);
    $tbInt->bindParam(":sala", $sala, PDO::PARAM_INT);
    $tbInt->bindParam(":data", $now, PDO::PARAM_STR);
    $tbInt->bindParam(":chat", $chat, PDO::PARAM_STR);
    $tbInt->bindParam(":to", $to, PDO::PARAM_STR);
    $tbInt->execute();
}

//Insere o usuário na tabela ususarios, configura as sessões, cria a interação de entrada na sala e redireciona o usuário para a página principal do chat.
function start_chat(){
    global $conn, $nome, $sala;
   
    $unique_name = get_unique_name($nome,$nome);
    $now = date("Y-m-d H:i:s");
   
    //Insere o usuario no banco
    $insert = $conn->prepare("insert into usuarios(nm_usuario, id_sala, dt_refresh) values(:nm,:sala, :now)");
    $insert->bindParam(":nm", $unique_name, PDO::PARAM_STR);
    $insert->bindParam(":sala", $sala, PDO::PARAM_INT);   
    $insert->bindParam(":now", $now, PDO::PARAM_STR);       
    $insert->execute();
    $_SESSION["user"] = $conn->lastInsertId();
    $_SESSION["user_name"] = $unique_name;
    $_SESSION["sala"] = $sala;
    $_SESSION["data_logon"] = $now;
    interagir($_SESSION["user_name"], "", $_SESSION["sala"], "Entrou na sala.");
    header("Location: chat.php");
   
}


//Função que garante um nome única na respectiva sala. Caso o nome já existe, essa função insere um underline no final do nome e vai incrementando valores até que o nome gerado não tenha sido utiliado por outro usuário ativo na sala.
function get_unique_name($nome_original, $nome_alterado, $repetido=1){
    global $conn, $sala;
   
    $tbUsers = $conn->prepare("select count(*) as total from usuarios where id_sala =:sala and nm_usuario =:nm");
    $tbUsers->bindParam(":sala",$sala, PDO::PARAM_INT);
    $tbUsers->bindParam(":nm",$nome_alterado, PDO::PARAM_STR);   
    $tbUsers->execute();
    $linha = $tbUsers->fetch(PDO::FETCH_ASSOC);
   
    if($linha["total"]>0){
        echo $nome_alterado;
        echo "<br>";
        return get_unique_name($nome_original, $nome_original . "_" . $repetido, ($repetido+1));
    }else{
        return $nome_alterado;   
    }
}


// Exclui os usuários onde não houve refresh na página há mais de 16 segundos.
function delete_offline_users(){
    global $conn;
   
    $now = date("Y/m/d H:i:s");
    $past16s = makeDataTime($now, 0,0,0,0,0,-16);
   
    //Seleciona usuarios ativos
    $ativos = $conn->prepare("select id_usuario from usuarios where dt_refresh > :dt");
    $ativos->bindParam(":dt", $past16s, PDO::PARAM_STR);
    $ativos->execute();
    $ativos = $ativos->fetchAll(PDO::FETCH_NUM|PDO::FETCH_COLUMN);
    $ativos_ = "";
   
    if(count($ativos)<=0) return;
   
    $ativos = implode(",", $ativos);

    //Pega dados dos usuarios e cria interacao de saída
    $tbUser = $conn->prepare("select nm_usuario, id_sala from usuarios where id_usuario not in($ativos)");
    $tbUser->execute();
    while($l = $tbUser->fetch(PDO::FETCH_ASSOC)){
        interagir($l["nm_usuario"], "", $l["id_sala"], "Saiu da sala.");
    }
   
    //Exclui usuarios inativos
    $del = $conn->prepare("delete from usuarios where id_usuario not in($ativos)");
    $del->execute();   
}

//Exclui interações antigas, criadas há 10 horas atrás ou mais.
function delete_old_entries(){
    global $conn;
   
    $now = date("Y/m/d H:i:s");
    $past10h = makeDataTime($now, 0,0,0,-10,0,0);
   
    //Excluir Interações antigas (10 horas)
    $del = $conn->prepare("delete from interacoes where dt_interacao < :dt");
    $del->bindParam(":dt", $past10h, PDO::PARAM_STR);
    $del->execute();
}

//Como o nome sugere, retorna o nome de uma dada sala. Deve-se passar o id da sala que desejas o nome, como parâmetro.
function pega_nome_sala($id_sala){
    global $conn;
   
    $tbSala = $conn->prepare("select nm_sala from salas where id_sala=:id");
    $tbSala->bindParam(":id", $id_sala, PDO::PARAM_INT);
    $tbSala->execute();
    $l = $tbSala->fetch(PDO::FETCH_ASSOC);
    return $l["nm_sala"];
}

//--------------------------------------------------------------------Funções gerais:
function makeData($data, $anoConta,$mesConta,$diaConta){
   $ano = substr($data,0,4);
   $mes = substr($data,5,2);
   $dia = substr($data,8,2);
   return date('Y-m-d',mktime (0, 0, 0, $mes+($mesConta), $dia+($diaConta), $ano+($anoConta)));   
}

function makeDataTime($data, $anoConta,$mesConta,$diaConta, $horaConta, $minutoConta, $segundoConta){
   $ano = substr($data,0,4);
   $mes = substr($data,5,2);
   $dia = substr($data,8,2);
   $hora = substr($data,11,2);
   $minuto = substr($data,14,2);
   $segundo = substr($data,17,2);
  
   return date('Y-m-d H:i:s',mktime ($hora+($horaConta), $minuto+($minutoConta), $segundo+($segundoConta), $mes+($mesConta), $dia+($diaConta), $ano+($anoConta)));   
}

function isSelected($campo, $varCampo){
    if($campo==$varCampo) return " selected=selected ";
    return "";
}

function isEmpty($campo,$name){
    global $msg;
    if(str_replace(" ","",$campo)==""){
       $msg = "O campo " . $name . " não foi preenchido corretamente! A ação foi cancelada!";
       return true;   
    }
    return false;
}


function isValidEmail($value){
    $pattern = "/^([a-zA-Z0-9])+([\.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-]+)+/";
    return preg_match($pattern, $value);
}


function isDate($date){
    $char = strpos($date, "/")!==false?"/":"-";
    $date_array = explode($char,$date);
    if(count($date_array)!=3) return false;
    return checkdate($date_array[1],$date_array[0],$date_array[2])?($date_array[2] . "-" . $date_array[1] . "-" . $date_array[0]):false;
}

?>
Em cada função há um comentário explicando qual sua finalidade, mas se você ficou com alguma dúvida, pergunte nos comentários.

2.3. index.php
A index.php será a página de entrada, ou seja, onde o usuário irá digitar o nome dele e selecionar a sala desejada. Veja o código abaixo:

1.<?php
.2.session_start();
.3.require_once("config.php");
.4.require_once("functions.php");
.5.
.6.//Pega o nome e a sala que o usuário soliciou entrar
.7.$nome = isset($_POST["txtNome"])?strip_tags($_POST["txtNome"]):"";
.8.$sala = isset($_POST["slSala"])?(int)$_POST["slSala"]:1;
.9.
.10.//Se o nome não estiver em branco, executa uma rotina de limpeza delete_olde_entries() e inicia o chat.
.11.if(!empty($nome)){
.12.    delete_old_entries();
.13.    start_chat();
.14.}
.15.
.16.
.17.?>
.18.<html>
.19.<head>
.20.<title>Chat</title>
.21.<style>
.22..tab{
.23.    background–color:#000;
.24.    color:#FFF;
.25.    font–size:12px;
.26.    font–weight:bold;
.27.    padding:4px;
.28.}
.29.</style>
.30.</head>
.31.<body>
.32.<div style="text-align:center">
.33.
.34.<h1>Chat Online</h1>
.35.
.36.<hr />
.37.
.38.Escolha um Nickname e a Sala
.39.
.40.<form action="index.php" method="post">
.41.<table width="200" border="0" cellpadding="0" cellspacing="0" align="center">
.42.  <tr>
.43.    <td width="70" class="tab">Nome</td>
.44.    <td width="130">
.45.        <input type="text" name="txtNome" id="txtNome" />
.46.    </td>
.47.   </tr>
.48.  <tr>
.49.    <td class="tab">Sala</td>
.50.    <td>
.51.    <select name="slSala">
.52.    <?php
.53.    //Lista todas as salas cadastradas no banco de dados
.54.    $tbSala = $conn->prepare("select * from salas");
.55.    $tbSala->execute();
.56.    while($linha=$tbSala->fetch(PDO::FETCH_ASSOC)){
.57.        echo "<option value=’$linha[id_sala]‘>$linha[nm_sala]</option>";
.58.    }
.59.    ?>
.60.    </select>
.61.    </td>
.62.    </tr>
.63.  <tr>
.64.    <td> </td>
.65.    <td><label>
.66.      <input type="submit" name="btnEntrar" id="btnEntrar" value="Entrar" />
.67.    </label></td>
.68.    </tr>
.69.</table>
.70.</form>
.71.
.72.
.73.</div>
.74.</body>
.75.</html>.<?php
session_start();
require_once("config.php");
require_once("functions.php");

//Pega o nome e a sala que o usuário soliciou entrar
$nome = isset($_POST["txtNome"])?strip_tags($_POST["txtNome"]):"";
$sala = isset($_POST["slSala"])?(int)$_POST["slSala"]:1;

//Se o nome não estiver em branco, executa uma rotina de limpeza delete_olde_entries() e inicia o chat.
if(!empty($nome)){
    delete_old_entries();
    start_chat();
}


?>
<html>
<head>
<title>Chat</title>
<style>
.tab{
    background-color:#000;
    color:#FFF;
    font-size:12px;
    font-weight:bold;
    padding:4px;
}
</style>
</head>
<body>
<div style="text-align:center">

<h1>Chat Online</h1>

<hr />

Escolha um Nickname e a Sala

<form action="index.php" method="post">
<table width="200" border="0" cellpadding="0" cellspacing="0" align="center">
  <tr>
    <td width="70" class="tab">Nome</td>
    <td width="130">
        <input type="text" name="txtNome" id="txtNome" />
    </td>
   </tr>
  <tr>
    <td class="tab">Sala</td>
    <td>
    <select name="slSala">
    <?php
    //Lista todas as salas cadastradas no banco de dados
    $tbSala = $conn->prepare("select * from salas");
    $tbSala->execute();
    while($linha=$tbSala->fetch(PDO::FETCH_ASSOC)){
        echo "<option value='$linha[id_sala]'>$linha[nm_sala]</option>";
    }
    ?>
    </select>
    </td>
    </tr>
  <tr>
    <td> </td>
    <td><label>
      <input type="submit" name="btnEntrar" id="btnEntrar" value="Entrar" />
    </label></td>
    </tr>
</table>
</form>


</div>
</body>
</html>


Note que as parter importantes do código estão comentadas no próprio código. Em suma, após o usuário clicar no botão Entrar, o código executará duas funções principais:

delete_old_entries() – esta função fará excluir interações (conversas) antigas, de 10 horas atrás ou mais. O objetivo desta função é poupar espaço em banco de dados, afinal, para que ter conversas tão antigas armazenadas em teu banco de dados?

start_chat() – esta função vai inserir o novo usuario na tabela usuarios, criar a interação “Fulano Entrou na Sala” e na sequência irá redirecionar o usuário para a página chat.php

2.4. chat.php
A página chat.php é a página principal, que permitirá aos usuários presentes na mesma sala a comunicação. Veja o código abaixo:

1.<?php
.2.session_start();
.3.require_once("config.php");
.4.require_once("functions.php");
.5.
.6.//Verifica se a sessão existe
.7.if(!isset($_SESSION["user"])){
.8.    header("Location: index.php");
.9.    exit();
.10.}
.11.
.12.//Verifica se o usuário já foi excluído do banco
.13.$tbUser = $conn->prepare("select count(*) as total from usuarios where id_usuario=:id");
.14.$tbUser->bindParam(":id",$_SESSION["user"], PDO::PARAM_INT);
.15.$tbUser->execute();
.16.$linha = $tbUser->fetch(PDO::FETCH_ASSOC);
.17.if($linha["total"] < 1){
.18.    session_destroy();
.19.    header("Location: index.php");
.20.    exit();
.21.}
.22.
.23.//Pega o nome do destinatário da mensagem
.24.$to = isset($_POST["slUsers"])?$_POST["slUsers"]:"";
.25.
.26.//Verifica se o usuário enviou alguma mensagem, caso positivo, ele chama a função interagir passando os dados do respectivo usuário como parâmetro.
.27.
.28.if(isset($_POST["btnEnviar"]) && isset($_POST["txtMensagem"])){
.29.    interagir($_SESSION["user_name"], $to, $_SESSION["sala"], strip_tags($_POST["txtMensagem"]) );
.30.}
.31.
.32.?>
.33.<html>
.34.<head>
.35.<title>Chat</title>
.36.<style>
.37..tab{
.38.    background–color:#000;
.39.    color:#FFF;
.40.    font–size:12px;
.41.    font–weight:bold;
.42.    padding:4px;
.43.}
.44.</style>
.45.</head>
.46.<body>
.47.<div style="text-align:center">
.48.
.49.<h1>Chat Online</h1>
.50.
.51.<h2 style="color:#0C3">Você está na Sala <?php echo pega_nome_sala($_SESSION["sala"]);?> <a href="sair.php">Sair da Sala</a></h2>
.52.
.53.<hr />
.54.<form action="chat.php" method="post">
.55.<table width="709" border="1" align="center" cellpadding="0" cellspacing="0">
.56.  <tr>
.57.    <td width="516"><iframe src="interacao.php" width="500px" height="500px" frameborder="0" scrolling="yes"></iframe> </td>
.58.    <td width="4"> </td>
.59.    <td width="189"><?php require_once("users-online.php");?> </td>
.60.  </tr>
.61.  <tr>
.62.    <td colspan="3"><?php require_once("writing.php");?> </td>
.63.    </tr>
.64.</table>
.65.</form>
.66.</div>
.67.</body>
.68.</html>.<?php
session_start();
require_once("config.php");
require_once("functions.php");

//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
    header("Location: index.php");
    exit();
}

//Verifica se o usuário já foi excluído do banco
$tbUser = $conn->prepare("select count(*) as total from usuarios where id_usuario=:id");
$tbUser->bindParam(":id",$_SESSION["user"], PDO::PARAM_INT);
$tbUser->execute();
$linha = $tbUser->fetch(PDO::FETCH_ASSOC);
if($linha["total"] < 1){
    session_destroy();
    header("Location: index.php");
    exit();   
}

//Pega o nome do destinatário da mensagem
$to = isset($_POST["slUsers"])?$_POST["slUsers"]:"";

//Verifica se o usuário enviou alguma mensagem, caso positivo, ele chama a função interagir passando os dados do respectivo usuário como parâmetro.

if(isset($_POST["btnEnviar"]) && isset($_POST["txtMensagem"])){
    interagir($_SESSION["user_name"], $to, $_SESSION["sala"], strip_tags($_POST["txtMensagem"]) );
}

?>
<html>
<head>
<title>Chat</title>
<style>
.tab{
    background-color:#000;
    color:#FFF;
    font-size:12px;
    font-weight:bold;
    padding:4px;
}
</style>
</head>
<body>
<div style="text-align:center">

<h1>Chat Online</h1>

<h2 style="color:#0C3">Você está na Sala <?php echo pega_nome_sala($_SESSION["sala"]);?> <a href="sair.php">Sair da Sala</a></h2>

<hr />
<form action="chat.php" method="post">
<table width="709" border="1" align="center" cellpadding="0" cellspacing="0">
  <tr>
    <td width="516"><iframe src="interacao.php" width="500px" height="500px" frameborder="0" scrolling="yes"></iframe> </td>
    <td width="4"> </td>
    <td width="189"><?php require_once("users-online.php");?> </td>
  </tr>
  <tr>
    <td colspan="3"><?php require_once("writing.php");?> </td>
    </tr>
</table>
</form>
</div>
</body>
</html>

O código PHP do topo da página está comentado, então basta ler para entender, agora, no final temos um formulário, aí temos três detalhes:

a) Um iframe que abre o arquivo interacao.php. O objetivo de utilizar este iframe é fazer com que somente as interações sejam atualizadas de 6 em 6 segundos. Note que se todo o resto da página se auto atualizasse a cada 6 segundos, o usuário poderia ter o texto cortado enquanto digitava alguma mensagem, o que seria muito chato. Além disso, buscar os usuários ativos no banco de dados a cada 6 segundos consumiria muitos processamentes desnecessário.

b) Inclusão do arquivo users-online.php. Este arquivo apenas seleciona todos os usuarios que estão no banco de dados

c) Inclusão do arquivo writing.php. Inclui o campo de inserção da mensagem e também o botão de envio da mesma. Este código poderia ter sido posto junto com o arquivo chat.php, mas para deixar o código mais “Limpo”, preferí coloca-lo num arquivo separado.


2.5. interacao.php
A página interacao.php é atualizada de 6 em 6 segundos para manter a conversa sempre atualizada. Além disso, atualiza a data do update e monta todas as interações do usuário desde que entrou na sala. Veja o script abaixo:

1.<?php
.2.session_start();
.3.require_once("config.php");
.4.require_once("functions.php");
.5.
.6.//Verifica se a sessão existe
.7.if(!isset($_SESSION["user"])){
.8.    header("Location: index.php");
.9.    exit();
.10.}
.11.
.12.?>
.13.<html>
.14.<head>
.15.<META HTTP–EQUIV="Refresh" CONTENT="6;URL=interacao.php">
.16.</head>
.17.<body onLoad="pageScroll()">
.18.<script>
.19.function pageScroll() {
.20.                window.scrollBy(0,1000000); // horizontal and vertical scroll increments
.21.                //scrolldelay = setTimeout(‘pageScroll()’,100); // scrolls every 100 milliseconds
.22.}
.23.
.24.</script>
.25.<?php
.26.
.27.//Atualiza data do refresh
.28.$now = date("Y-m-d H:i:s");
.29.$tbUp = $conn->prepare("update usuarios set dt_refresh =:refresh where id_usuario=:id");
.30.$tbUp->bindParam(":refresh", $now, PDO::PARAM_STR);
.31.$tbUp->bindParam(":id", $_SESSION["user"], PDO::PARAM_INT);
.32.$tbUp->execute();
.33.
.34.//Exclui os usuários inativos
.35.delete_offline_users();
.36.
.37.//Lista todas as entradas desde que o usuario entrou.
.38.$tbChat = $conn->prepare("select nm_usuario, ds_interacao, nm_destinatario, DATE_FORMAT(dt_interacao, ‘%H:%i:%s‘) as dt_interacao from interacoes where dt_interacao >= :data and id_sala=:sala order by dt_interacao ASC");
.39.$tbChat->bindParam(":data", $_SESSION["data_logon"], PDO::PARAM_STR);
.40.$tbChat->bindParam(":sala", $_SESSION["sala"], PDO::PARAM_INT);
.41.$tbChat->execute();
.42.
.43.
.44.while($lChat = $tbChat->fetch(PDO::FETCH_ASSOC)){
.45.    $chat = $lChat["nm_destinatario"] == ""?strip_tags($lChat["ds_interacao"]):"fala com <strong>$lChat[nm_destinatario]:</strong><span style=’color:#006699′> " . strip_tags($lChat["ds_interacao"]) . "</span>";
.46.    echo "<div style=’font-family:tahoma;font-size:12px;padding:4px’>";
.47.    echo "<strong>$lChat[nm_usuario]</strong> $chat <span style=’color:#cccccc’>$lChat[dt_interacao]</span>";
.48.    echo "</div>";
.49.}
.50.
.51.?>
.52.</body>
.53.</html>.<?php
session_start();
require_once("config.php");
require_once("functions.php");

//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
    header("Location: index.php");
    exit();
}

?>
<html>
<head>
<META HTTP-EQUIV="Refresh" CONTENT="6;URL=interacao.php">
</head>
<body onLoad="pageScroll()">
<script>
function pageScroll() {
                window.scrollBy(0,1000000); // horizontal and vertical scroll increments
                //scrolldelay = setTimeout('pageScroll()',100); // scrolls every 100 milliseconds
}

</script>
<?php

//Atualiza data do refresh
$now = date("Y-m-d H:i:s");
$tbUp = $conn->prepare("update usuarios set dt_refresh =:refresh where id_usuario=:id");
$tbUp->bindParam(":refresh", $now, PDO::PARAM_STR);
$tbUp->bindParam(":id", $_SESSION["user"], PDO::PARAM_INT);
$tbUp->execute();

//Exclui os usuários inativos
delete_offline_users();

//Lista todas as entradas desde que o usuario entrou.
$tbChat = $conn->prepare("select nm_usuario, ds_interacao, nm_destinatario, DATE_FORMAT(dt_interacao, '%H:%i:%s') as dt_interacao from interacoes where dt_interacao >= :data and id_sala=:sala order by dt_interacao ASC");
$tbChat->bindParam(":data", $_SESSION["data_logon"], PDO::PARAM_STR);
$tbChat->bindParam(":sala", $_SESSION["sala"], PDO::PARAM_INT);
$tbChat->execute();


while($lChat = $tbChat->fetch(PDO::FETCH_ASSOC)){
    $chat = $lChat["nm_destinatario"] == ""?strip_tags($lChat["ds_interacao"]):"fala com <strong>$lChat[nm_destinatario]:</strong><span style='color:#006699'> " . strip_tags($lChat["ds_interacao"]) . "</span>";
    echo "<div style='font-family:tahoma;font-size:12px;padding:4px'>";
    echo "<strong>$lChat[nm_usuario]</strong> $chat <span style='color:#cccccc'>$lChat[dt_interacao]</span>";
    echo "</div>";
}

?>
</body>
</html>
users-online.php do Chat
Pega todos os usuários que estão no banco de dados, ou seja, que estão online:

1.<?php
.2.//Verifica se a sessão existe
.3.if(!isset($_SESSION["user"])){
.4.    header("Location: index.php");
.5.    exit();
.6.}
.7.
.8.//Cria a lista com todos os usuários online na respectiva sala.
.9.
.10.$tbUsers = $conn->prepare("select * from usuarios where id_sala=:sala");
.11.$tbUsers->bindParam(":sala", $_SESSION["sala"], PDO::PARAM_STR);
.12.$tbUsers->execute();
.13.
.14.echo "<select name=’slUsers’ id=’slUsers’ size=’30’ style=’width:180px’ >";
.15.while($l_users = $tbUsers->fetch(PDO::FETCH_ASSOC)){
.16.    echo "<option value=’$l_users[nm_usuario]‘ " . isSelected($to, $l_users["nm_usuario"]) . ">$l_users[nm_usuario]</option>";
.17.}
.18.echo "</select>";
.19.?>.<?php
//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
    header("Location: index.php");
    exit();
}

//Cria a lista com todos os usuários online na respectiva sala.

$tbUsers = $conn->prepare("select * from usuarios where id_sala=:sala");
$tbUsers->bindParam(":sala", $_SESSION["sala"], PDO::PARAM_STR);
$tbUsers->execute();

echo "<select name='slUsers' id='slUsers' size='30' style='width:180px' >";
while($l_users = $tbUsers->fetch(PDO::FETCH_ASSOC)){
    echo "<option value='$l_users[nm_usuario]' " . isSelected($to, $l_users["nm_usuario"]) . ">$l_users[nm_usuario]</option>";
}
echo "</select>";
?>

2.6. writing.php
Adicione o campo texto e o botão de envio da mensagem:

1.<?php
.2.//Verifica se a sessão existe
.3.if(!isset($_SESSION["user"])){
.4.    header("Location: index.php");
.5.    exit();
.6.}
.7.
.8.?>
.9.
.10.<table width="558" border="0" cellpadding="0" cellspacing="0">
.11.  <tr>
.12.    <td width="377"><label>
.13.      <textarea name="txtMensagem" cols="60" rows="3" id="txtMensagem"></textarea>
.14.    </label></td>
.15.    <td width="27"> </td>
.16.    <td width="154"><input type="submit" name="btnEnviar" id="btnEnviar" value="Enviar Mensagem" style="padding-left:4px" /></td>
.17.  </tr>
.18.</table>.<?php
//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
    header("Location: index.php");
    exit();
}

?>

<table width="558" border="0" cellpadding="0" cellspacing="0">
  <tr>
    <td width="377"><label>
      <textarea name="txtMensagem" cols="60" rows="3" id="txtMensagem"></textarea>
    </label></td>
    <td width="27"> </td>
    <td width="154"><input type="submit" name="btnEnviar" id="btnEnviar" value="Enviar Mensagem" style="padding-left:4px" /></td>
  </tr>
</table>
A página users-online.php e writing.php, juntamente com chat.php só são atualizadas quando o usuário envia uma nova mensagem.

Sair.php do Chat
Last but not least, temos o arquivo sair.php. Sua função é encerrar a sessão e redirecionar o usuário para a página de login. Veja o código abaixo:

1.<?php
.2.//Destoi a sessão e redireciona o usuário para a página de início do chat.
.3.session_start();
.4.session_destroy();
.5.header("Location: index.php");
.6.?>


Os usuários não são limpos automaticamente, só são limpos quando você clica em sair da sala, mas se você simplesmente fechar a janela, e não sair da sala, quando você for entrar de novo com o mesmo nick, vai aparece seu nick lá como se você estivesse logado, e seu nick atual ficará com seu_nick_1
E outra coisa,  os assentos e caracteres especiais não vão.
E também quando você clicar em um nick, parece que você está falando com aquela pessoa, mas para você voltar a falar com todos não tem um botão escrito todos.

Dica:
Sobre o usuário, ao fechar o navegador, permanecer no banco, tu poderia chamar a função delete_offline_users(); antes de fazer login do usuario na página de login.