Cómo conocer quién está online en PHP

Seguramente os habéis cruzado, al navegar por alguna página web, con el típico widget que te indica quién está visitando la página en este justo instante. En páginas webs corporativas y tiendas online no es muy común ver componentes así, pero si hablamos de un foro, la cosa cambia. Se trata de un elemento que muestra información sobre las personas que están conectadas a la página en el mismo momento que tú. En este sencillo tutorial te mostraremos cómo implementar el típico “Quién está online” en tu sitio web. ¿Estás preparado? Pues vamos a ello.

El XHTML

Como es habitual, vamos a empezar con la parte del XHTML. El código que te presentamos aquí no es mucho, pero es todo lo que necesitamos para mostrar el trabajo que se hace desde el backend. El widget mostrará un panel deslizante con todos los datos de la geolocalización al pasar el ratón por encima.

demo.html

<div class="onlineWidget">

<div class="panel">

    <!-- Fetched with AJAX: -->

    <div class="geoRow">
    <div class="flag"><img src="who-is-online/img/famfamfam-countryflags/us.gif" width="16" height="11"></div>
    <div class="country" title="UNITED STATES">UNITED STATES</div>
    <div class="people">2</div>
    </div>

    <div class="geoRow">
    <div class="flag"><img src="who-is-online/img/famfamfam-countryflags/uk.gif" width="16" height="11"></div>
    <div class="country" title="UNITED KINGDOM">UNITED KINGDOM</div>
    <div class="people">1</div>
    </div>

</div>

<div class="count">8</div>
<div class="label">online</div>
<div class="arrow"></div>
</div>

Como puedes ver en el código de arriba, el contenedor principal “onlineWidget” contiene el panel deslizante (el div con nombre de clase “panel”), el número total de gente online (el div “count”), el label “online” y la flecha verde a la derecha.

El div panel se completará dinámicamente con AJAX con los países que más visitantes tienen online actualmente. El contenido por defecto de este div es un gif de cargando que rota, que es reemplazado con los datos de geolocalización una vez que la respuesta AJAX se haya completado (normalmente, en menos de un segundo). Volveremos a esto más adelante...

La base de datos

Vamos a echar un vistazo a cómo estructurar la base de datos para que se integre en nuestro proyecto, ya que es fundamental para el resto del script.

Todos los datos del widget se almacenan en la tabla tz_who_is_online. Consiste en seis campos (o columnas). El primer campo, ID, es la primary key de la tabla y además auto incremental. Después tenemos el campo IP, en el que guardaremos la IP del visitante (convertida en un entero con la función ip2long de PHP).

Después tenemos tres campos que se rellenarán con la API de Hostip: Country, CountryCode y City. El widget no utilizará el campo city por el momento, pero es bueno tenerlo en caso de que quieras implementarlo. Por último tenemos un campo DT llamado timestamp, que se actualiza en cada carga de página y nos habilita el trackear quién está online (los usuarios sin una página cargada en los últimos 10 minutos, seguramente habrán abandonado el sitio web).

El CSS

El widget está (casi) libre de imágenes, y solo se personaliza con CSS. Vamos a echarle un vistazo al estilo, definido en su hoja de estilos correspondiente. El código está dividido en dos partes para que sea más sencillo de seguir.

who-is-online/styles.css - Parte 1

 

.onlineWidget,.panel{

        /* Styling the widget and the sliding panel at once */

        background-color:#F9F9F9;
        border:2px solid #FFFFFF;
        height:25px;
        padding:4px 8px;
        position:relative;
        width:130px;

        cursor:pointer;

        /* CSS3 rules for rounded corners, box and text shadows: */

        -moz-border-radius:6px;
        -webkit-border-radius:6px;
        border-radius:6px;

        -moz-box-shadow:0 0 3px #CCCCCC;
        -webkit-box-shadow:0 0 3px #CCCCCC;
        box-shadow:0 0 3px #CCCCCC;

        text-shadow:0 2px 0 white;
}

.onlineWidget:hover{
        background-color:#fcfcfc;
}

.onlineWidget:hover .arrow{
        /* Changing the background image for the green arrow on hover: */
        background-position:bottom center;
}

.count{
        /* The total number of people online div */

        color:#777777;
        float:left;
        font-size:26px;
        font-weight:bold;
        margin-top:-3px;
        text-align:center;
        width:30px;
}

.label{
        /* The online label */

        float:left;
        font-size:10px;
        padding:7px 0 0 7px;
        text-transform:uppercase;
}

En el código anterior, puedes ver que le hemos dado estilo al widget y al panel deslizante a la vez. Esto es para asegurarnos de que tienen un estilo consistente, y que sea sencillo de modificar más adelante. Algunas reglas son únicas para el panel, de todas maneras, incluimos un conjunto de reglas específicas en la segunda parte del código.

También definimos el hover y el estilo del label y del div count.

who-is-online/styles.css - Parte 2

.arrow{
        /* The green arrow on the right */

        background:url(img/arrow.png) no-repeat top center;
        position:absolute;
        right:6px;

        width:25px;
        height:25px;
}

.panel{
        /* The slideout panel */

        position:absolute;
        cursor:default;

        bottom:50px;
        left:0;
        height:auto;
        display:none;
        margin:-2px;
        z-index:1000;
}

.preloader{
        /* The rotating gif preloader image */
        display:block;
        margin:10px auto;
}

.geoRow{
        /* The div that contains each country */

        height:16px;
        overflow:hidden;
        padding:2px 0;
}

.flag{
        float:left;
        margin:0 4px;
}

.country, .people{
        float:left;
        font-size:10px;
        padding:2px;
}

.country{
        width:85px;
        overflow:hidden;
}

.people{
        font-weight:bold;
}

En la segunda parte del código, damos estilo a los datos de geolocalización que se mostrarán en el panel deslizante, más tarde jQuery lo completará desde el backend. Con esto, podemos continuar al próximo paso.

El PHP

Aquí es donde ocurre la magia. PHP cuenta con la tarea de mantener la base de datos actualizada y obtener los datos IP de la API de Hostip. Más tarde se almacerá en cache para usarlos posteriormente en una cookie del ordenador visitante.

who-is-online/online.php

require "connect.php";
require "functions.php";

// We don't want web bots altering our stats:
if(is_bot()) die();

$stringIp = $_SERVER['REMOTE_ADDR'];
$intIp = ip2long($stringIp);

// Checking wheter the visitor is already marked as being online:
$inDB = mysql_query("SELECT 1 FROM tz_who_is_online WHERE ip=".$intIp);

if(!mysql_num_rows($inDB))
{
        // This user is not in the database, so we must fetch
        // the geoip data and insert it into the online table:

        if($_COOKIE['geoData'])
        {
                // A "geoData" cookie has been previously set by the script, so we will use it

                // Always escape any user input, including cookies:
                list($city,$countryName,$countryAbbrev) = explode('|',mysql_real_escape_string(strip_tags($_COOKIE['geoData'])));
        }
        else
        {
                // Making an API call to Hostip:

                $xml = file_get_contents('http://api.hostip.info/?ip='.$stringIp);

                $city = get_tag('gml:name',$xml);
                $city = $city[1];

                $countryName = get_tag('countryName',$xml);
                $countryName = $countryName[0];

                $countryAbbrev = get_tag('countryAbbrev',$xml);
                $countryAbbrev = $countryAbbrev[0];

                // Setting a cookie with the data, which is set to expire in a month:
                setcookie('geoData',$city.'|'.$countryName.'|'.$countryAbbrev, time()+60*60*24*30,'/');
        }

        $countryName = str_replace('(Unknown Country?)','UNKNOWN',$countryName);

        mysql_query("   INSERT INTO tz_who_is_online (ip,city,country,countrycode)
                                        VALUES(".$intIp.",'".$city."','".$countryName."', '".$countryAbbrev."')");
}
else
{
        // If the visitor is already online, just update the dt value of the row:
        mysql_query("UPDATE tz_who_is_online SET dt=NOW() WHERE ip=".$intIp);
}

// Removing entries not updated in the last 10 minutes:
mysql_query("DELETE FROM tz_who_is_online WHERE dt<SUBTIME(NOW(),'0 0:10:0')");

// Counting all the online visitors:
list($totalOnline) = mysql_fetch_array(mysql_query("SELECT COUNT(*) FROM tz_who_is_online"));

// Outputting the number as plain text:
echo $totalOnline;

Este script de PHP será llamado por jQuery con el fin de contabilizar el número actual de personas online y rellenar el div count. Resumiendo, este script inserta la IP del visitante en la base de datos y extrae los datos de su ubicación en base a ella.

La mejor estrategia para una mejor organización del back-end, es hacer las llamadas a la API (que tardan bastante) por cada usuario, cuando visiten el sitio por primera vez.

La otra alternativa sería la de almacenar sólo las direcciones IP de los visitantes y resolver los datos de geolocalización una vez que se muestren en el panel. Esto significaría resolver un gran número de direcciones IP de forma simultánea, lo que haría que el scrip se colapsase y entraríamos en la lista negra de la API. Esto no mola nada.

Puede resolver los datos de geolocalización con la API del HostIP abriendo una conexión en una URL similar a esta: http://api.hostip.info/?ip=128.128.128.128. Esto devolverá una respuesta XML válida, que contiene todo tipo de datos, incluyendo el nombre del país y la ciudad asociadas con la IP, la abreviatura del país e incluso las coordenadas absolutas. Vamos a extraer estos datos con la función file_get_contents() de PHP para guardar la información que necesitamos.

who-is-online/geodata.php

require "connect.php";
require "functions.php";

// We don't want web bots accessing this page:
if(is_bot()) die();

// Selecting the top 15 countries with the most visitors:
$result = mysql_query("    SELECT countryCode,country, COUNT(*) AS total
                        FROM tz_who_is_online
                        GROUP BY countryCode
                        ORDER BY total DESC
                        LIMIT 15");

while($row=mysql_fetch_assoc($result))
{
    echo '
    <div class="geoRow">
        <div class="flag"><img src="who-is-online/img/famfamfam-countryflags/'.strtolower($row['countryCode']).'.gif" width="16" height="11" /></div>
        <div class="country" title="'.htmlspecialchars($row['country']).'">'.$row['country'].'</div>
        <div class="people">'.$row['total'].'</div>
    </div>
    ';
}

Llamamos a Geodata.php con jQuery para completar el panel deslizante con los datos de la geolocalización. Este archivo, básicamente, extrae la información con una consulta GROUP BY, que agrupa a los usuarios individuales por país y ordena las filas resultantes en orden descendente, con los países más populares en la parte superior.

Para los íconos, utilizamos el conjunto iconos de banderas famfamfam, que es de dominio público. Una gran carcaterística de la API HostIP, es que devuelve el código del país en un formato estándar de dos letras, que también es utilizado por el conjunto de iconos famfamfam. Esto significa que en el bucle while, es sencillo encontrar la bandera a mostrar, solo hay que poner la abreviatura del país y añadir la extensión jpg.

El jQuery

Javascript gestiona las respuestas de AJAX y el panel deslizante. Esto sería una tarea de enormes proporciones mediante JS puro, por lo que vamos a utilizar la versión más reciente de la librería jQuery.

El código sería el siguiente:

who-is-online/widget.js

$(document).ready(function(){
        // This function is executed once the document is loaded

        // Caching the jQuery selectors:
        var count = $('.onlineWidget .count');
        var panel = $('.onlineWidget .panel');
        var timeout;

        // Loading the number of users online into the count div with the load AJAX method:
        count.load('who-is-online/online.php');

        $('.onlineWidget').hover(
                function(){
                        // Setting a custom 'open' event on the sliding panel:

                        clearTimeout(timeout);
                        timeout = setTimeout(function(){panel.trigger('open');},500);
                },
                function(){
                        // Custom 'close' event:

                        clearTimeout(timeout);
                        timeout = setTimeout(function(){panel.trigger('close');},500);
                }
        );

        var loaded=false;       // A flag which prevents multiple AJAX calls to geodata.php;

        // Binding functions to custom events:

        panel.bind('open',function(){
                panel.slideDown(function(){
                        if(!loaded)
                        {
                                // Loading the countries and the flags
                                // once the sliding panel is shown:

                                panel.load('who-is-online/geodata.php');
                                loaded=true;
                        }
                });
        }).bind('close',function(){
                panel.slideUp();
        });

});

Te habrás quedado un poco perplejo con el uso del setTimeout en el menú. Esto se hace para tener un poco de delay entre el hover del ratón y la apertura real del panel deslizante. De esta manera, los movimientos involuntarios del cursor del ratón sobre el widget no dispararán el evento open, y una vez abierto, no se cerrará de inmediato una vez que el ratón quite su posicionamiento del elemento.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP