Widget Quién está en línea con PHP, MySQL y jQuery

Para el tutorial de esta semana, estamos echando un vistazo a nuestra bandeja de entrada siempre tan interesante. Todo comenzó con una carta de uno de nuestros lectores hace un par de semanas:

Taylor, siempre nos alegramos cuando recibimos buenas ideas de tutoriales, así que hoy estamos haciendo exactamente eso:un "Quién está en línea " Widget con PHP, MySQL y jQuery. Mostrará la cantidad de visitantes que están viendo su sitio actualmente y, gracias a la API gratuita de IP a ubicación de Hostip, incluso podrá detectar el país de donde provienen sus visitantes y mostrarlo en un panel deslizable.

Paso 1 - XHTML

Como de costumbre, comenzamos con la parte XHTML. El código presentado aquí puede no parecer gran cosa, pero es todo lo que necesitamos para mostrar todo el trabajo realizado por el backend. El widget presenta un elegante panel deslizable con todos los datos de geolocalización, que se muestran al pasar el mouse por encima.

demostración.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 puede ver en el marcado anterior, el div del contenedor principal - "onlineWidget " contiene el panel deslizable (el div con el nombre de clase "panel "), el número total de personas en línea (el "recuento " div), el "en línea " etiqueta y la flecha verde a la derecha.

AJAX llena dinámicamente el div del panel con los países con la mayor cantidad de visitantes actualmente en línea. El contenido predeterminado de este div es un precargador de gif giratorio, que se reemplaza con los datos geográficos una vez que se completa la solicitud AJAX (generalmente en menos de un segundo). Volveremos a esto en un momento.

Paso 2:la base de datos

A diferencia de la rutina habitual, aquí vamos a echar un vistazo a cómo está estructurada la base de datos, ya que es fundamental para el resto del script.

Todos los datos del widget se almacenan en tz_who_is_online mesa. Consta de seis campos (o columnas). El primero, ID, es un campo estándar de clave primaria/incremento automático. Después de esto, está el campo IP que almacena la dirección IP del visitante (convertida a un número entero de antemano con ip2long Función PHP).

Después de esto, hay tres campos obtenidos por la API de Hostip: Country, CountryCode y Ciudad . El widget no está usando el campo de la ciudad en este momento, pero es bueno tenerlo en caso de que alguien quiera implementarlo. El último es el DT el campo de marca de tiempo, que se actualiza en cada carga de página y nos permite rastrear quién está en línea (los usuarios que no cargaron una página en los últimos 10 minutos probablemente hayan abandonado el sitio).

Paso 3 - CSS

El widget está (casi) libre de imágenes y solo está diseñado con CSS. Echemos un vistazo al estilo, tal como se define en styles.css . El código está dividido en dos partes, por lo que es más fácil de seguir.

quién-está-en-línea/estilos.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 primer paso anterior, puede ver que diseñamos el widget y el panel deslizable En seguida. Esto es para asegurar que tengan un estilo consistente que sea fácil de cambiar más adelante. Sin embargo, algunas reglas son exclusivas del panel, por lo que incluimos un conjunto de reglas específicas en la segunda parte del código.

También definimos el estado de desplazamiento y le damos estilo a la etiqueta y contar divisiones.

quién-está-en-línea/estilos.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 archivo, diseñamos cómo se presentan los datos de geolocalización en el panel deslizable, después de que jQuery los obtiene del back-end. Con esto podemos continuar con el siguiente paso.

Paso 4 - PHP

Aquí es donde ocurre la magia. PHP tiene que mantener actualizada la base de datos de usuarios en línea y obtener datos de IP a ubicación de la API de Hostip . Esto luego se almacena en caché para uso futuro en una cookie en la PC de los visitantes.

quien-esta-en-linea/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 PHP es llamado inicialmente por jQuery para llenar el div de conteo con el número actual de personas en línea. Detrás de escena, sin embargo, este script escribe la IP del visitante en la base de datos y resuelve sus datos de IP a ubicación.

Esta es la mejor estrategia para organizar el back-end, ya que mantenemos las llamadas a la API (que cuestan bastante tiempo) distribuidas a cada usuario cuando visitan el sitio por primera vez.

La otra alternativa sería almacenar solo las IP de los visitantes y poner en cola los datos de geolocalización una vez que se muestre el panel. Esto significaría resolver una gran cantidad de direcciones IP simultáneamente, lo que haría que el script no respondiera y nos pusiera en la lista negra de la API. Totalmente no genial.

Puede poner en cola la API de Hostip abriendo una conexión a una URL similar a esta:http://api.hostip.info/?ip=128.128.128.128 . Devuelve una respuesta XML válida, que contiene todo tipo de datos, incluido un país y ciudad nombre asociado a la IP, país abreviatura e incluso coordenadas absolutas . Estamos obteniendo estos datos con PHP file_get_contents() y extrayendo los bits de información que necesitamos.

quién-está-en-línea/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>
    ';
}

Geodatos.php es obtenido por jQuery para llenar el panel deslizable con datos de ubicación. Este archivo básicamente pone en cola la base de datos con un GROUP BY consulta, 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 de la bandera, estamos usando el conjunto de íconos de la bandera de famfamfam, que se publica como dominio público. Una gran cosa acerca de la API de Hostip es que devuelve el código de país en un formato estándar de dos letras, que también es compartido por el conjunto de iconos de famfamfam. Esto significa que en el ciclo while, es realmente fácil encontrar la bandera apropiada para mostrar, simplemente bajando el caso de la abreviatura del país almacenada en la base de datos y agregando un gif extensión.

Paso 5:jQuery

JavaScript gestiona las solicitudes de AJAX y desliza el panel. Esta sería una tarea abrumadora solo con JS puro, razón por la cual estamos usando la versión más reciente de la biblioteca jQuery.

Ahora echemos un vistazo a cómo se ve el código.

quién-está-en-línea/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();
    });

});

Es posible que esté un poco perplejo con el uso de setTimeout en el menú. Esto está hecho, por lo que tenemos un poco de retraso entre el movimiento del mouse y la apertura real del panel deslizable. De esta forma, los movimientos involuntarios del cursor del mouse sobre el widget no activarán el evento de apertura y, una vez abierto, no cerrará. inmediatamente una vez que el ratón lo deja.

¡Con esto nuestro widget está listo!

Configuración de la demostración

En este punto, probablemente desee tomar el widget y colocarlo en su sitio. Para que funcione, debe ejecutar el código SQL que se encuentra en table.sql en el archivo de descarga. Creará el tz_who_is_online tabla en su base de datos, que es utilizada por el widget. Más tarde, debe cargar los archivos en su servidor e incluir widget.js a la sección principal de su página (junto con la biblioteca jQuery). Después de esto, debe completar sus datos de inicio de sesión de MySQL en connect.php y finalmente agregue el marcado de demo.html a su página web.

Conclusión

Tener acceso a datos en tiempo real en la base de usuarios de su sitio es un sueño para cualquier webmaster. Herramientas como Google Analytics brindan una gran perspectiva sobre el alcance de su sitio, pero carecen de la sensación de tiempo real que un widget simple como este puede proporcionar.

¿Qué opinas? ¿Cómo modificarías este código?