Generador de contraseñas en PHP

En PHP hay muchas formas de generar contraseñas, algunas propias del sistema. En ocasiones se usan sistemas sólo números. Con este sistema tienes disponible todos los caracteres que quieres como disponibles (mayúsculas, minúsculas, números, símbolos…) y la longitud (24, que en este caso la convierte en segura). Así que si necesitas restaurar la clave a un usuario, con esto le ofrecerás una clave segura.

<?php
$caracteres = '0123456789abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$#@!?=%-+*.[]{}_,;:<>|';
$caractereslong = strlen($caracteres);
$clave = '';
for($i = 0; $i < 24; $i++) {
  $clave .= $caracteres[rand(0, $caractereslong - 1)];
}
echo $clave;
?>

Categorías PHP

smushit.net, una API simple de smushit

Si te dedicas al mundo de Internet es muy probable que entre las aplicaciones que utilices se encuentre la de comprimir imágenes… pero el uso de algunas herramientas a veces se hace muy complejo o necesita de servidores con algunas configuraciones extrañas. Es por eso que, como yo mismo me he encontrado en esa situación y soy bastante asiduo a la herramienta de Smush.it de Yahoo! he querido simplificarme la vida y, de paso, os hago partícipes de ello a vosotros.

Que conste que es una simple prueba y que puede fallar (como todo en la vida). El servicio es smushit.net y lo que hace es simplificar la API existente eliminando los datos poco útiles.

El objetivo es que introduzcas por parámetro la URL de la imagen que quieres optimizar y te devuelva la URL de la imagen optimizada. De esta forma con un simple código en PHP, ASP o lo que te apetezca podrás hacer un “fopen” o un “fread” y te devolverá la URL que luego podrás descargar o hacer lo que te apetezca.

El ejemplo es sencillo http://smushit.net/?img=http://smushit.net/Tux.png.

Se aceptan sugerencias y demás… aunque, como digo, es una prueba por entretenerme a pasar el rato.

Google Maps + Foursquare

Si eres de los que le da igual que sepan donde estás en todo momento (o al menos donde accedes en Foursquare) tal vez te interese este pequeño mashup con el que podrás mostrar gracias a los mapas de Google Maps tu última localización de Foursquare.

¿Cómo se consigue esto? Pues tan sencillo como entrando en tu cuenta de Foursquare y luego visitando la sección de feeds. Allí encontrarás una de las opciones en formato KML.

Ahora tan sólo has de añadir esa dirección URL al final del mapa de Google. Por ejemplo:

<iframe scrolling="no" marginheight="0" marginwidth="0" width="300" height="300" src="http://maps.google.com/maps?f=q&source=s_q&hl=es&geocode=&q=http://feeds.foursquare.com/history/ABCDEF0123456789.kml?count=1&ie=latin1&output=embed"></iframe>

El parámetro “q” es el que lleva la URL de Foursquare, y el “count” es el número de puntos que quieres que se muestren. Si pones 1 indicará el último en el que hayas hecho checkin.

Cachear de forma sencilla una página dinámica en PHP

¿Te ha pasado alguna vez que tienes una web desde hace un montón de años, que la hiciste tú y a veces te da problemas de saturación por exceso de visitas? Pues a mi sí y, aunque llevaba tiempo pensando en alguna forma de cachear, que acababa siendo hipercompleja, hoy me ha dado por pensar alguna forma sencilla basándome en unas pruebas que había hecho hacía poco.

La cuestión es que sabía cómo hacerlo, iba por el camino, pero entre unas cosas y otras “nunca encontraba el momento”, hasta ahora. El sistema es bastante simple y en principio se podría aplicar a cualquier sitio. El código sería algo tal que este:

<?php
$md5 = md5($_SERVER["REQUEST_URI"]); // convertimos la URL a único identificador
$file = "cache/".$md5.".html"; // donde se guardará el fichero
$hora = filemtime($file); // comprobamos la hora del fichero si ya existiera
if(time() <= $hora+86400) { // asignamos un tiempo de cache de 86400 segundos
    include($file); // incluimos el contenido del fichero cacheado
    echo "<!-- ".date('YmdHis', $hora)." -->"; // (opcional) añadimos al pie de página la fecha-hora de la caché
    exit; // salimos
}
ob_start(); // abrimos la memoria
?>
AQUI VA LA WEB NORMAL
<?php
$fp = fopen($file, 'w+'); // abrimos el fichero de caché
fwrite($fp, ob_get_contents()); // guardamos el contenido de la página generada en el fichero
fclose($fp); // cerramos el fichero
ob_end_flush(); // devolvemos la página que se ha generado y cerramos la memoria
?>

Con este sistema podremos incrementar una página dinámica tranquilamente entre un 50% y un 1.000% la velocidad, dependiendo de la carga de base de datos o cálculo que tuviera anteriormente.

Combinar y reducir JavaScript

En muchas ocasiones me encuentro que tengo varios JavaScript en una página y, al final, se hace bastante pesado tener que gestionar múltiples ficheros. Además, otra cosa que me gusta es la de reducir al máximo el tamaño del fichero, y el hecho de poder combinarlos también permite reducirlos…

Es por esto que existe para PHP una pequeña biblioteca de funciones llamada JSmin-php que ayuda a gestionar esta situación tanto la de combinar como de minimizar.

Básicamente lo que hace esta biblioteca es leer todos los ficheros JS de una carpeta, combinarlos, comprimirlos y generar un fichero único cacheado.

require_once("jsmin.php");
$files = glob("/carpeta/js/*.js");
$js = "";
foreach($files as $file) {
  $js .= JSMin::minify(file_get_contents($file));
}
file_put_contents("/carpeta/combinado.js", $js);

En el caso en que no queramos leer todos los ficheros de una carpeta y sólo incluior unos pocos, podemos cambiar la línea por algo al que así:

$files = array("/carpeta/js/1.js", "/carpeta/js/2.js", "/carpeta/js/3.js");

De esta forma generaremos un único fichero. Personalmente creo que esto habría que ejecutarlo una vez de forma externa, y usar el fichero generado a mano… a menos que montemos un sistema que verifique las cachés o que permita cambiar este fichero sin que ocurra nada… que luego ya se sabe, cambios de versiones, etc.

Evita, con PHP, ataques XSS y SQL injection

Una de las cosas que normalmente no revisamos cuando creamos un sitio web es la vulnerabilidad que se tiene a ataques por URL por cosas como XSS e incluso a ataques a la base de datos por una mala configuración. Para esto normalmente se usa una revisión y se ejecuta, con PHP, la función htmlentities() que, gracias a eliminar el código HTML puede filtrar cosas como los <script>. Para solventar esto existe una cosa llamada Genius Open Source Libraries que, con unas simples funciones, permiten hacer una megalimpieza contra ataques de todo tipo.


require_once 'Core/sgConfig.inc.php';
// Output an unsafe string, presumably user input
$xss = '<script>alert(\'oh snap\');</script>';
echo 'If your entered your name as ' . $xss . ', we\'d be in trouble.<br>' . "\n";
// Sanitize that string, and output it safely
$htmlContentContext = sgSanitizer::sanitizeForHTMLContent($xss);
echo "But if we sanitize your name, " . $htmlContentContext . ", then all is well.<br>\n";
echo '<h2>HTML Attribute</h2>';
// We can also safely sanitize it for an HTML attribute context
$htmlAttributeContext = sgSanitizer::sanitizeForHTMLAttribute($xss);
echo 'Tainted strings can also be used in an <a href="http://google.com" title="' . $htmlAttributeContext . '">HTML attribute</a> context.<br>' . "\n";
echo '<h2>JavaScript string</h2>';
// And we can even make strings used in JavaScript safe
$jsString = '\';alert(1);var b =\'';
echo '<script type="text/javascript">
var a = \'' . $jsString . '\';
var aSafe = \'' . sgSanitizer::sanitizeForJS($jsString) . '\';
</script>';

En este sistema de limpieza descargable como proyecto Genius encontramos algunas funciones como sanitizeForHTMLContent (que limpia el contenido teniendo en cuenta que es código HTML), sanitizeForHTMLAttribute (que limpia como atributo de HTML, por ejemplo el “alt” de una imagen) o sanitizeForJS (que dejaría el código arreglado para poder ejecutarse como JavaScript).

Sin duda una biblioteca de funciones interesante a la hora de aumentar la seguridad de un sitio web.

DISTCHA, un antispam accesible

Una de las peculiaridades de los CAPTCHA es que habitualmente son bastante complejos de entender y ya no os digo si cuesta a alguien con todas sus facultades, a alguien que tenga falta parcial o total de alguno de sus sentidos.

Y es por eso que desde Francia Quebec (Canada) llega una propuesta llamada DISTCHA (Device Independent Slider Test to tell Computers and Humans Apart) y que es un sistema bastante parecido al de activación del iPhone, ese “slide” que moviéndolo de un lado a otro permite activar el teléfono.

Aunque aun se encuentra en una versión muy previa, y a falta de ser probado en muchas plataformas, sí que es cierto que en la mayor parte de los navegadores ya funciona.

Funciona gracias a jQuery y es bastante sencillo de implementar. Os dejo un ejemplo de código para que veáis cómo funciona y uséis su código si lo veis conveniente…

Cargar una página por bloques

Normalmente cuando desarrollamos una página web no nos preocupamos en exceso cómo enviamos la información a los usuarios que se conectan a ella, dejando que el sistema lo haga de forma automática. Eso no es del todo malo, pero hace que la paralelización de la que alguna vez os he hablado se pueda perder en parte.

Sabemos que en muchas ocasiones en la cabecera de una página incluimos los CSS y JS, y que estos pueden llegar a bloquear nuestra carga de página. Teniendo en cuenta que la cabecera en sí “no hace nada” ¿por qué no enviarla en cuanto la tengamos? Esto significaría que, una vez la mandemos, antes de pintar nada, y mientras calculamos el contenido de la página que verá el usuario, podemos enviar cierta información y el navegador puede comenzar la descarga de elementos externos como los que comentaba antes.

Para hacer esto, y como seguro que usaremos gzip, en PHP podríamos utilizar algunas funciones como ob_flush().

A partir de este momento, en el que ya hemos enviado la cabecera de la página, hay que decidir si nuestro sitio está preparado para ser enviado por partes. Normalmente las páginas tiene diversos bloques que no suelen estar contenidos dentro de otros. De esta forma podríamos dividir lo que se muestra al usuario en una cabecera, un contenido, un menú lateral y un pie. cada uno de estos bloques, al ser independiente del otro, podrían llevar, entre uno y otro, estas funciones flush, y así el sistema sería capaz de ir pintando la página sin dependencias entre cada uno de estos bloques, y la sensación de carga sería mayor, ya que estará “precargado” el CSS y JS, que se cargan antes de que se reciba toda la página.

El último de los bloques no necesita llevar este elemento. Eso sí, para que esto funcione realmente hace falta seguir las normas que dicen que los JavaScript que no afectan al “pintado” de la página se encuentren en el pie, y que los que sí afectan se encuentren en la cabecera.

Ahora sólo queda implementarlo y ver si hace efecto…

Cómo hacer muchas peticiones HTTP simultáneas en PHP

Uno de los problemas que habitualmente nos pueden frenar la carga de un sitio es si leemos mucha información de varios sitios de forma simultánea, como podría ser la lectura de varios feeds. Y es que habitualmente se usa la función file_get_contents() que tiene una cosa: es síncrona, es decir, hay que ejecutarla, esperar a que finalice, y volver a ejecutarla… pero ¿por qué esperar a qué acabe de leer para hacer otra llamada?

Para hacerlo podemos utilizar las funciones curl_multi_* que básicamente permiten hacer muchas llamadas cURL en muy poco tiempo. No es del todo asíncrono y en paralelo, pero casi casi se podría considerar como una opción.

El tiempo total de estas peticiones será la de la más lenta de todas, de forma que si tenemos algo como esto:

  • URL 1: 2,3 segundos
  • URL 2: 0,3 segundos
  • URL 3: 1,4 segundos
  • URL 4: 1,8 segundos
  • URL 5: 3,0 segundos

de la forma normal tardaríamos 8,8 segundos 8que es la suma de todos, aunque habría que sumarle los tiempos de conexión y desconexión), y de la forma que propongo se tardaría un poco más de 3,0 segundos, que es el tiempo de la más lenta de todas.

¿Cómo hacer esto?

<?php
function PeticionMultiple($urls, $opciones = array()) {
  $curly = array();
  $resultado = array();
  $pm = curl_multi_init();
  foreach ($urls as $id => $d) {
    $curly[$id] = curl_init();
    if(is_array($d) && !empty($d['url'])) {
      $url = $d['url']
    } else {
      $url = $d;
    }
    curl_setopt($curly[$id], CURLOPT_URL, $url);
    curl_setopt($curly[$id], CURLOPT_HEADER, 0);
    curl_setopt($curly[$id], CURLOPT_RETURNTRANSFER, 1);
    if (!empty($d['post'])) {
      curl_setopt($curly[$id], CURLOPT_POST, 1);
      curl_setopt($curly[$id], CURLOPT_POSTFIELDS, $d['post']);
    }
    if (!empty($opciones)) {
      curl_setopt_array($curly[$id], $opciones);
    }
    curl_multi_add_handle($pm, $curly[$id]);
  }
  $ejecutando = null;
  do {
    curl_multi_exec($pm, $ejecutando);
  } while($ejecutando > 0);
  foreach($curly as $id => $c) {
    $resultado[$id] = curl_multi_getcontent($c);
    curl_multi_remove_handle($pm, $c);
  }
  curl_multi_close($pm);
  return $resultado;
}
?>

Y el código de invocación:

<?php
$urls = array(
  'http://javiercasares.com/feed/atom/',
  'http://www.ethek.com/feed/atom/',
  'http://www.tumanitas.com/feed/'
);
$r = PeticionMultiple($urls);
print_r($r);
?>

Con esto, en principio, conseguiríamos múltiples peticiones para leer feeds de muchos sitios lo más rápido posible…

Relacionado: interesante la función EpicCurl del código EpiCode.

Open Standard Media (OSM) Player

Uno de los poyos que siempre me he encontrado a la hora de poner un reproductor de vídeo en la web es que en la mayoría de casos sólo aceptaba vídeos flash (.flv). La cosa es que con el HTML 5 y con jQuery se han montado un reproductor llamado Open Standard Media que tiene muy buena pinta, es código abierto y gratuito.

Entre otras cosas, permite el uso de HTML 5, soporta los nuevos elementos audio y video del HTML 5 con los formatos estándar, para el resto de formatos monta un reproductor Flash, se le puede cambiar el diseño de una forma sencilla gracias al uso de ThemeRoller, permite la integraciónd e vídeos de Vimeo y Youtube (simplemente indicando la URL), tiene la opción de listados de vídeos.

La instalación es muy sencilla, ya que sólo hay que añadir un par de JavaScript en la cabecera, poner el código básico del reproductor, y al final de la página, puedes incorporar un listado de los vídeos o una llamada a un XML que los tenga. Aun así, creo que la opción más sencilla es la de usar la librería en PHP, que con unas pocas líneas funcionaría:

<?php
include("OSMPlayer.php");
$player = new OSMPlayer(array(
  'file' => 'http://www.mysite.com/files/myvideo.flv',
  'image' => 'http://www.mysite.com/files/myimage.jpg',
  'disablePlaylist' => true
));
?>
<html>
  <head>
    <title>Open Standard Media (OSM) Player: PHP Demo</title>
    <script type="text/javascript" src="jquery-ui/js/jquery.js"></script>
<?php
print $player->getHeader();
?>
  </head>
  <body>
    <h2>Open Standard Media (OSM) Player</h2><br/>
<?php
print $player->getPlayer();
?>
  </body>
</html>

Con esto tendríamos un reproductor sencillo, aunque también se puede complicar un poco si le añadimos una lista de reproducción que nos permita visualizar decenas de vídeos… o si queremos modificar algunos parámetros, aquí está la lista de todos los disponibles…

face.com, la API que detecta caras en fotos

Aún no sé la utilidad de esta herramienta, pero como pronto, me ha parecido genial algo así… quizá podría servir para que al subir una foto a una red social el sistema pueda llegar a etiquetar a alguien de forma automática.

Y es que face.com ha abierto su API a desarrolladores, y la verdad es que para ser abierto y gratuito, tiene buena pinta y da cierta información…

Lo bueno de este sitio es que tienen varios ejemplos interesantes para varios lenguajes de programación e incluso integración con facebook y twitter.

La idea es que con una simple petición a través de la librería en PHP, como podría ser:

$face->faces_detect('http://farm3.static.flickr.com/2566/3896283279_0209be7a67.jpg');

o una petición vía REST:

http://api.face.com/faces/detect.xml?api_key=4b4b4c6d54c37&api_secret=&urls=http://farm3.static.flickr.com/2566/3896283279_0209be7a67.jpg

Se puede obtener un código tal que así:

<?xml version="1.0" encoding="utf-8"?>
 <response>
  <photos list='true'>
   <photo>
    <url>http://farm3.static.flickr.com/2566/3896283279_0209be7a67.jpg</url>
    <pid>F@53f98f0f3ffeef58413190e657e297ff_4b4b4c6d54c37</pid>
    <width>333</width>
    <height>500</height>
    <tags list='true'>
     <tag>
      <tid>TEMP_F@53f98f0f3ffeef58413190e657e297ff_4b4b4c6d54c37_51.05_60.40_0</tid>
      <threshold></threshold>
      <uids>
      </uids>
      <label></label>
      <confirmed></confirmed>
      <manual></manual>
      <tagger_id></tagger_id>
      <width>11.71</width>
      <height>7.8</height>
      <center>
       <x>51.05</x>
       <y>60.4</y>
      </center>
      <eye_left>
       <x>49.18</x>
       <y>59.7</y>
      </eye_left>
      <eye_right>
       <x>52.5</x>
       <y>59.46</y>
      </eye_right>
      <mouth_left>
       <x>50.16</x>
       <y>62.03</y>
      </mouth_left>
      <mouth_center>
       <x>51.61</x>
       <y>61.86</y>
      </mouth_center>
      <mouth_right>
       <x>52.65</x>
       <y>61.86</y>
      </mouth_right>
      <nose>
       <x>51.55</x>
       <y>60.53</y>
      </nose>
      <ear_left></ear_left>
      <ear_right></ear_right>
      <chin></chin>
      <yaw>26.44</yaw>
      <roll>-6.19</roll>
      <pitch>19.47</pitch>
      <attributes >
       <face>
        <value>true</value>
        <confidence>0.7862</confidence>
       </face>
       <gender>
        <value>male</value>
        <confidence>95</confidence>
       </gender>
       <glasses>
        <value>true</value>
        <confidence>96</confidence>
       </glasses>
       <smiling>
        <value>true</value>
        <confidence>93</confidence>
       </smiling>
      </attributes>
     </tag>
    </tags>
   </photo>
  </photos>
  <status>success</status>
  <usage>
   <used>1</used>
   <remaining>unlimited</remaining>
   <limit>unlimited</limit>
   <reset_time_text>unlimited</reset_time_text>
   <reset_time>0</reset_time>
  </usage>
 </response>

Este código te envía la información de la fotografía, y los puntos que reflejan cada una de las caras, indicando los ojos, boca, nariz, orejas, mentón, y la curvatura y/o posición de la cara, si es hombre o mujer, si lleva gafas o no y si está sonriendo.

Formas de validar un correo electrónico

Una de las cosas que habitualmente es necesario hacer es validar cuentas de correo. Hay muchas cosas a hacer, pero una de las primeras es saber si la cuenta de correo está “bien formada”. Un artículo muy interesante sobre distintas formas de verificar cuentas, basadas en una serie de expresiones que sí han de validar, y otras que no han de hacerlo.

La expresión que en principio se ajusta más a todas las posibilidades es la siguiente:

/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i

Con esto tendríamos un código similar a este:

<?php
$texto = "prueba@ejemplo.com";
$expresion = "/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i";
preg_match($expresion , $texto , $encuentros);
print_r($encuentros);
?>

De todas formas, quizá habría que hacer otra cosa, que es verificar si realmente la cuenta existe… y es que una cosa es que la cuenta de correo esté bien formada y otra es que pueda hacer una prueba de envío de correo sin necesidad de enviar. Para eso podríamos usar una clase como esta que se conecta a la máquina remota para verificar la existencia de la cuenta…

<?php
class SMTP_validateEmail {
  var $sock;
  var $user;
  var $domain;
  var $domains;
  var $port = 25;
  var $max_conn_time = 30;
  var $max_read_time = 5;
  var $from_user = 'pruebas@ejemplo.com';
  var $from_domain = 'localhost';
  var $nameservers = array(
    '192.168.0.1'
  );
  var $debug = false;
  function SMTP_validateEmail($emails = false, $sender = false) {
    if ($emails) {
      $this->setEmails($emails);
    }
    if ($sender) {
      $this->setSenderEmail($sender);
    }
  }
  function _parseEmail($email) {
    $parts = explode('@', $email);
    $domain = array_pop($parts);
    $user= implode('@', $parts);
    return array($user, $domain);
  }
  function setEmails($emails) {
    foreach($emails as $email) {
      list($user, $domain) = $this->_parseEmail($email);
      if (!isset($this->domains[$domain])) {
        $this->domains[$domain] = array();
      }
      $this->domains[$domain][] = $user;
    }
  }
  function setSenderEmail($email) {
    $parts = $this->_parseEmail($email);
    $this->from_user = $parts[0];
    $this->from_domain = $parts[1];
  }
  function validate($emails = false, $sender = false) {
    $results = array();
    if ($emails) {
      $this->setEmails($emails);
    }
    if ($sender) {
      $this->setSenderEmail($sender);
    }
    foreach($this->domains as $domain=>$users) {
      $mxs = array();
      list($hosts, $mxweights) = $this->queryMX($domain);
      for($n=0; $n < count($hosts); $n++) {
        $mxs[$hosts[$n]] = $mxweights[$n];
      }
      asort($mxs);
      array_push($mxs, $this->domain);
      $this->debug(print_r($mxs, 1));
      $timeout = $this->max_conn_time/count($hosts);
      while(list($host) = each($mxs)) {
        $this->debug("try $host:$this->port\n");
        if ($this->sock = fsockopen($host, $this->port, $errno, $errstr, (float) $timeout)) {
          stream_set_timeout($this->sock, $this->max_read_time);
          break;
        }
      }
      if ($this->sock) {
        $reply = fread($this->sock, 2082);
        $this->debug("< <<\n$reply");
        preg_match('/^([0-9]{3}) /ims', $reply, $matches);
        $code = isset($matches[1]) ? $matches[1] : '';
        if($code != '220') {
          foreach($users as $user) {
            $results[$user.'@'.$domain] = false;
          }
          continue;
        }
        $this->send("HELO ".$this->from_domain);
        $this->send("MAIL FROM: < ".$this->from_user.'@'.$this->from_domain.">");
        foreach($users as $user) {
          $reply = $this->send("RCPT TO: < ".$user.'@'.$domain.">");
          preg_match('/^([0-9]{3}) /ims', $reply, $matches);
          $code = isset($matches[1]) ? $matches[1] : '';
          if ($code == '250') {
            $results[$user.'@'.$domain] = true;
          } elseif ($code == '451' || $code == '452') {
            $results[$user.'@'.$domain] = true;
          } else {
            $results[$user.'@'.$domain] = false;
          }
        }
        $this->send("quit");
        fclose($this->sock);
      }
    }
    return $results;
  }
  function send($msg) {
    fwrite($this->sock, $msg."\r\n");
    $reply = fread($this->sock, 2082);
    $this->debug(">>>\n$msg\n");
    $this->debug("< <<\n$reply");
    return $reply;
  }
  function queryMX($domain) {
    $hosts = array();
    $mxweights = array();
    if (function_exists('getmxrr')) {
      getmxrr($domain, $hosts, $mxweights);
    } else {
      require_once 'Net/DNS.php'; // http://pear.php.net/package/Net_DNS/
      $resolver = new Net_DNS_Resolver();
      $resolver->debug = $this->debug;
      $resolver->nameservers = $this->nameservers;  
      $resp = $resolver->query($domain, 'MX');
      if ($resp) {
        foreach($resp->answer as $answer) {
          $hosts[] = $answer->exchange;
          $mxweights[] = $answer->preference;
        }
      }
    }
    return array($hosts, $mxweights);
  }
  function microtime_float() {
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
  }
  function debug($str) {
    if ($this->debug) {
      echo htmlentities($str);
    }
  }
}
?>
Categorías PHP

Buscando desarrollador web

Hoy comenzamos el día con una oferta de trabajo, que tal y como está la cosa, no es moco de pavo. El perfil es de desarrollador PHP y MySQL.

Quién se incorpore estará bajo mi supervisión y se dedicará principalmente a la continuidad de un proyecto que he estado elaborando estos últimos meses, por lo que si le gusta la emoción no ha podido caer en un sitio mejor…

También es interesante que tenga las ideas claras y que le guste Internet, ya que este proyecto se mueve a una velocidad bastante rápida y la evolución puede necesitar ir a la misma velocidad.

Con respecto a la jornada laboral, puede ser media jornada (si fuera de mañanas sería de 09:30 a 13:30; en caso de ser de tardes sería de 15:00 a 19:00) o jornada completa (de lunes a jueves de 09:30 a 19:30 y los viernes de 09:30 a 14:30). La zona de trabajo es Plaça de les Glories en Barcelona.

La retribución ahora mismo no la sé, pero lo más probable es que sea por convenio (que si no recuerdo mal, está por encima de lo mileurista).

Para enviar el curriculum y lo que queráis (algún proyecto que hayáis hecho de ejemplo o similar si os parece conveniente) podéis hacerlo en la cuenta de correo simple@kisslab.com donde una amable secretaria os atenderá (NOTA: la amable secretaria puede no ser tan amable, puede no ser secretaria, puede ser un tío e incluso diría que podría ser yo mismo).

Ethek integra Live Messenger

He de reconocer que si hay un software que todo el mundo utiliza es Windows Live Messenger y no cabe duda de que cualquier integración de un sitio web sólo puede levar a ser un acierto.

Llevo varios meses planteándome integrar algún sistema de estos tan de moda ahora, y aposté en Ethek y en Windows Live ID, más concretamente en Windows Live Messenger Web. Estos días atrás alguien hizo que la chispa saltara y que mis pruebas en una máquina perdida del universo se acabasen convirtiendo en una versión beta que ha durado tan sólo 2 horas, para pasar a producción en cuanto tenía todas las funcionalidades en línea.

¿Qué es lo que he hecho?

Básicamente se resumen en 3 elementos:

  1. Integrar la Windows Live Messenger Bar (al pie de página).
  2. Integrar el Compartir elementos con los contactos.
  3. Añadir Windows Live Alerts.

Messenger Login

De todas las cosas, la más compleja ha sido la primera. El sistema necesita la instalación y configuración de la máquina de una forma especial, pero que permite navegar por toda la web sin perder las charlas abiertas. Además, nosotros no tenemos control en absoluto sobre el usuario o clave, de forma que es totalmente transparente para el usuario, ya que utiliza y puede dar permiso o quitarlo para que al acceder al sitio se conecte o no.

Barra de Messenger

Un elemento que, desde el punto de vista de marketing mola bastante, es lo de compartir. Con un clic, te permite seleccionar a los que están conectados y enviarles un mensaje diciéndoles que estás leyendo un artículo en Ethek. Además, se abre la ventana en ese momento y puedes mantener la charla allí mismo.

Compartir con contactos de Messenger

Lo último, lo de las alertas, está bien, ya que es el feed integrado en el messenger, de forma que, cuando se publique algo en la web le aparecerá una alerta a los usuarios que lo hayan aceptado.

Windows Live Alertas

Este es un primer paso que creo interesante dentro del ecosistema Ethek, y, aunque aún quedan dudas en este lanzamiento, pronto, seguro, que os cuento más detalles sobre como va avanzando.

Por cierto, no puedo poner la mano en el fuego, pero me atrevería a decir que esta es la primera web en España que utiliza este sistema… aunque, no puedo confirmarlo, ya que no parece haber ningún sitio donde haya un listado.

oEmbed, WordPress 2.9 y el editor de imágenes

Como muchos ya sabréis soy aficionado a tener una versión “alpha” de WordPress en mi blog personal, y de esa forma ir probando en tiempo real cómo es de estable el sistema antes de hacer las actualizaciones pertinentes en el resto de sitios de los que soy responsable… Y ahora que no creo que tarde mucho en salir esta nueva versión, me gustaría hacer un repaso de algunas características interesantes.

Antes de nada, he de decir que en muchos casos (no sé si es el autoguardado o qué) cuando voy a guardar un borrador o similar se pierde como el ID o algo y acaba fallando, total, que hay que volver a la lista y reabrir esa entrada (habiendo perdido el título y tags, normalmente).

El primero de los detalles que quiero comentar es el editor de imágenes que incluye. No es una barbaridad de editor, porque la verdad es que no supera al Scissors, pero es lo suficientemente sencillo y útil (sobretodo a la hora de redimensionar imágenes). Además, también permite gestionar cómo será el thumbnail por defecto de la entrada, que de eso ahora os hablo.

Wordpress 2.9 - Editor de Imágenes

Como decía, existe otra novedad que es la de los “thumbnails”. Se supone que se puede asignar una pequeña imagen por defecto a las entradas, creándola en base a una de las imágenes que subamos (como se puede ver en la imagen de arriba, en la parte inferior derecha). Para ello ha aparecido una nueva función:

the_post_image(); // Miniatura
the_post_image('thumbnail'); // Miniatura
the_post_image('medium'); // Mediana

Además de esto, quiero hacer referencia al oEmbed, que a muchos les va a ser muy útil, aunque todavía estoy investigando al 100% cómo funciona. La idea era que se utilizasen unos tags específicos, pero parece que se puede poner una dirección URL directa (y seguro que aparecerán plugins que ayuden a ello).

Por ejemplo, si ponemos una dirección de Flickr, la imagen debería aparecer automáticamente, sin necesidad de indicar el código HTML correspondiente (el <img src…> de toda la vida, vaya).

http://www.flickr.com/photos/juancasares/3562950871/

Si se pone esa dirección, a mi, personalmente, ahora mismo me falla la vista previa, como si no pudiera acceder a la imagen…

Según la documentación de Flickr, el código que habría que usar realmente es:

http://flickr.com/services/oembed?url=http://www.flickr.com/photos/juancasares/3562950871/&format=json&maxwidth=200

En este caso se puede omitir el “format”, y el “maxwidth”, si no se indica, es el que se haya configurado en las opciones del WordPress (que hay una pestaña donde se puede configurar esto).

Los parámetros que soporta el oEmbed son:

  • url: que es la dirección del elemento a incrustar (obligatorio)
  • maxwidth: el ancho máximo del objeto.
  • maxheight: la altura máxima del objeto.
  • format: el formato de respuesta

A parte de Flickr, también podríamos incrustar vídeos de Youtube:

Como comentaba antes, simplemente poniendo una dirección como la siguiente debería funcionar, aunque por ahora me falla y no carga la página… de todas formas, el código correcto sería:

http://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=HJEoNtN7j04

Algunos de los proveedores que ofrecen ya el servicio de oEmbed son:

  • Clearspring Widgets:
  • Flickr:
  • Hulu: {ext}
  • My Opera:
  • oohEmbed: http://oohembed.com/oohembed/
  • Poll Everywhere:
  • Qik: {ext}
  • Revision3:
  • Viddler:
  • Vimeo: {ext}
  • YouTube:

Por cierto, para los geeks que quieran implementar esto del oEmbed en su propio CMS o servicio, existe una librería en PHP además de soporte PHP.

Nota final: parece que para que todo funcione correctamente hay que instalar este plugin llamado oohEmbed y se supone que, junto a WordPress 2.9, la cosa funciona… (aunque a mi me sigue sin ir).

Parsear el robots.txt con PHP

Cuando construyes un robots de Internet has de tener en cuenta dos elementos:

  • Tener tu propio “User-Agent”
  • Cumplir el estándar de robots.txt

Para el primer elemento hay una forma muy sencilla de ejecutar un código en PHP:

ini_set("user_agent", "Agente 1.0 ");

Con esto ya consigues que, cuando se haya una petición desde PHP hacia cualquier sitio, en vez de llevar el agente del propio PHP se genere uno con los datos que tú indiques.

Aunque, quizá lo más interesante es tener un sistema de parseo de los ficheros de robots.txt. Para ello hemos de leer todas las líneas del fichero y procesarlas, sobretodo si coinciden con nuestro agente.

La idea es crear una función que consulte si tenemos acceso o no a esa dirección URL. Se puede mejorar con un sistema de caché y similar, pero lo pondré sencillo para dar unos primeros pasos.

function robots($url, $useragent=false) {
 
  // leemos la URL a revisar y sacamos la info
  $parseado = parse_url($url);
 
  // creamos un array de agentes posibles, incluyendo siempre el *
  $agentes = array(preg_quote('*'));
 
  // Si enviamos unos agentes por parámetro los revisamos
  // los agentes se pueden pasar por parámetro separados por "pipelines"
  if($useragent) $agentes[] = preg_quote($useragent);
  $agentes = implode('|', $agentes);
 
  // leemos el fichero robots.txt
  $robotstxt = @file("http://{$parsed['host']}/robots.txt");
  // y si no existe, se acabó todo
  if(!$robotstxt) return true;
 
  $reglas = array();
  $aplicadas = false;
  foreach($robotstxt as $linea) {
    if(!$linea = trim($linea)) continue;
 
    // Comprobamos si la línea hace referencia a un Agente
    if(preg_match('/User-agent:(.*)/i', $linea, $coincidencia)) {
      $aplicadas = preg_match("/($agentes)/i", $coincidencia[1]);
    }
 
    // Comprobamos si la línea hace referencia a una Regla
    if($aplicadas && preg_match('/Disallow:(.*)/i', $linea, $registro)) {
      if(!$registro[1]) return true;
      $reglas[] = preg_quote(trim($registro[1]), '/');
    }
  }
 
  // Comprobamos cada una de las reglas para la dirección que estamos revisando
  foreach($reglas as $regla) {
    if(preg_match("/^$regla/", $parseado['path'])) return false;
  }
 
  // Si no hay que filtrar nada, devolvemos "true" sino, ya habremos devuelto "false"
  return true;
}

Ahora sólo faltaría hacer un llamada:

$url = "http://www.ejemplo.ext/preba.html";
if(robots($url, "Agente")) {
  $contenido = file_get_contents($url);
}

phpBB, contenidos duplicados y etiqueta Canonical

Siempre he defendido phpBB como un buen sistema de foros, pero a veces creo que está pensando más en la Edad Media que para la era Internet. Y es que si hay una cosa que no me gusta absolutamente nada, es que las URL puede cambiar de forma estrepitosa según el navegante que venga, o buscador, además de poder generarse una de combinaciones bestiales.

Hace unas semanas tuve que actualizar un foro muy antiguo (versión 2 algo) a la nueva versión (la 3 algo), y me encontré con que, hace unos 4-5 años, le hice unos cambios por temas de SEO en el que las URL quedaban “chulas”. El problema es “deshacer todo el SEO que había hecho” para así, a partir de ahora, poder actualizar el software sin necesidad de ningún MOD raro de esos que se pueden montar…

Total, que tras la actualización han empezado a salir decenas de contenidos duplicados que no sabía cómo arreglar… hasta hoy que he encontrado una forma “razonable”. Lógicamente, esta forma razonable va a depender mucho de cada uno y de lo que pretenda con su foro, pero creo que para la mayoría de mortales servirá.

Y la forma razonable que planteo es sólo indexar estas 2 cosas:

  • Foros: sólo la primera página de cada foro.
  • Temas: indexar todos los temas y sus paginaciones

El resto (fichas de usuarios, combinaciones raras de foros o temas, paneles de gestión, ayudas, y demás) quedarían fuera de este sistema, ya que, a priori, no sirven para mucho, a la hora de aparecer en los resultados de búsqueda.

Lo bueno de este sistema, es que sirve para cualquier “style” (diseño) del phpBB, así que cualquiera lo debería de poder utilizar sin problema. Para ello, lo primero que hemos de hacer es activar las capacidades PHP del phpBB en los “style”. Para ello hay que:

  1. Entrar en el panel de Administradores
  2. Ir a la pestaña General -> Configuración del servidor -> Configuración de seguridad
  3. Permitir PHP en plantillas (Si se habilita esta opción, PHP e INCLUDEPHP serán convertidos en las plantillas) activar a SÍ.
  4. Aceptamos y ya tenemos el primero de los pasos… ya que ahora podemos programar dentro de los “diseños”.

Ahora hemos de ir al “diseño” que tengamos activo… para ello entraremos en Estilos -> Plantillas -> Editar (de la plantilla actual) -> overall_header.html. Allí buscaremos la línea del <title>...</title> y la sustituiremos por esto siguiente:

Descargar directamente como TXT (es posible que haya un problema con las comillas en el código siguiente y en el TXT funcionará correctamente).


<!-- PHP -->
if( $_SERVER["PHP_SELF"] == "/viewtopic.php" ) {
<!-- ENDPHP -->
<title>{TOPIC_TITLE}</title>
<!-- PHP -->
} elseif( $_SERVER["PHP_SELF"] == "/viewforum.php" ) {
<!-- ENDPHP -->
<title>{SITENAME}: {FORUM_NAME}</title>
<!-- PHP -->
} else {
<!-- ENDPHP -->
<title>{PAGE_TITLE} en {SITENAME}</title>
<!-- PHP -->
}
if( $_GET['p'] || $_GET['sid'] || $_GET['highlight'] ) {
echo "<meta name=\"robots\" content=\"noindex\" />\n";
} elseif( $_GET['f'] && $_GET['t'] && ($_GET['start']!=0) ) {
echo "<link rel=\"canonical\" href=\"http://".$_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF']."?f=".$_GET['f']."&t=".$_GET['t']."&start=".$_GET['start']."\" />\n";
} elseif( $_GET['f'] && $_GET['t'] && ($_GET['start']==0) ) {
echo "<link rel=\"canonical\" href=\"http://".$_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF']."?f=".$_GET['f']."&t=".$_GET['t']."\" />\n";
} elseif( $_GET['f'] && $_GET['t'] ) {
echo "<link rel=\"canonical\" href=\"http://".$_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF']."?f=".$_GET['f']."&t=".$_GET['t']."\" />\n";
} elseif( $_GET['f'] && ($_GET['start']!=0) ) {
echo "<link rel=\"canonical\" href=\"http://".$_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF']."?f=".$_GET['f']."&start=".$_GET['start']."\" />\n";
} elseif( $_GET['f'] && ($_GET['start']==0) ) {
echo "<link rel=\"canonical\" href=\"http://".$_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF']."?f=".$_GET['f']."\" />\n";
} elseif( $_SERVER["PHP_SELF"] == "/" ) {
echo "";
} else {
echo "<meta name=\"robots\" content=\"noindex\" />\n";
}
<!-- ENDPHP -->

Con esto básicamente hacemos varias cosas:

  • Poner un título concreto para los temas (indexable)
  • Poner un título concreto para los foros (indexable)
  • Poner el título genérico para el resto de elementos (no indexable)
  • Controlar parámetros que generan duplicados (p, sid, highlight) y decirle que esas páginas “no las indexe”.
  • Controlar las páginas indexables (con sus parámetros correctos) y darles la URL con el meta-tag Canonical para que siempre tengan el mismo formato.
  • Controlar que el resto de páginas no se indexen.

El hecho de usar la etiqueta Canonical ha sido básicamente porque, aunque el phpBB genera URL del tipo viewtopic.php?f=2&t=100, puede ser que algún listillo ponga los parámetros de otra forma, y que ese enlace genere otros incorrectos…

En fin, como digo, yo lo he probado con el phpBB 3 y me funciona… aunque, lógicamente, se aceptan todo tipo de sugerencias, combinaciones de URL y demás, porque seguro que me he dejado alguna cosa y el código se puede mejorar (si alguien sabe alguna cosa, que lo comente y actualizaré el código encantado).

ACTUALIZACIÓN: faltaba que se pudiera indexar la página principal… así que he añadido un “if” por ahí para controlar esa página…

Herramientas básicas: Gregarius

Una de las preguntas que de tanto en tanto alguien me hace es la de qué lector de feeds utilizo. Y la verdad es que muchas veces no sé si vale la pena contestar o inventármelo.

Habitualmente la gente utiliza Bloglines, Google Reader, Netvibes… pero claro, ¿cómo voy a usar yo una cosa de esas? Yo he de complicarme la vida y usar Gregarius.

Es un lector de feeds que se instala en el servidor, no es de escritorio, sino vía web (algo que facilita que esté actualizado siempre en todos sitios). Y su peculiaridad y lo que hace que lo siga usando es que, a diferencia de otros lectores:

  • Puedo ver todo lo pendiente de lectura en una misma pantalla.
  • Si leo algo no se elimina… eso sí, lo puedo “borrar” (además, con AJAX que desaparece de pantalla) con un simple clic.
  • Permite organizar por carpetas y demás, así que puedo leer “por temas” todo mezclado.
  • La única cosa que no lleva y que yo me he hecho es ponerle un cron para que se actualice cada hora.

Existe una demo de Gregarius por si alguien queire ver como es y funciona… tiene usuario y clave y otras cosas y pijadas, aunque, como digo, sobretodo va bien para cuando has de trabajar con feeds de forma habitual, no simplemente como lector…

Sitios que “roban” contenidos

Hace ya un tiempo comenté los problemas que tenía con contenidos duplicados por utilizar Feedburner y la imposibilidad de filtrarlos, y que de ahí comencé a utilizar el plugin de WordPress llamado ©Feed.

La cuestión es que después d un tiempo, tengo una pequeña lista de IP de servicios de esos que almacenan una copia de los contenidos de los feeds para mostrarlos en su sitio web, lo que podría llegar a afectar a la multiplicidad de nuestros contenidos por la red sin que sea una simple mención, sino copia.

ServicioDirección IP ServicioDirección IP
Wikio84.55.184.93Blodico / DigestServices67.212.188.186
Feedshow88.191.13.89Feedage64.34.174.17
UrlFan70.165.48.167MobiType91.121.143.209
Notifixious75.101.147.247Tecnomeme / Webremix64.13.192.34
WASALive88.191.36.95Toluu67.228.53.210
Blogged74.54.159.180flitic78.129.128.90
Hedatu84.20.10.71Portal-seo80.33.138.93
Rssindicato92.243.2.141Multiplica81.19.100.35
Iluminatia69.16.236.104Planet Google64.111.122.23
Xgil66.232.112.166Newsbeet216.86.147.191
Planetaki67.207.138.103WASALive91.121.94.113
Keegy74.52.131.116Dilo Ya!82.146.52.192
Linkandoo91.200.140.50Limlom64.62.174.98
BlogDimension85.31.105.37Blogtok67.43.168.85
DigestServices72.232.181.242UrlFan207.88.140.130
UrlFan70.165.48.162Personalbee67.111.14.203
Jordo Media216.227.208.228Feeddigest75.126.158.42
mytalk.at / Personalbee67.111.14.202RSSmicro209.216.63.55
Newstin195.39.35.193iPlural67.205.3.218
ElPelao67.111.14.204telefonos-moviles.net77.232.72.161
Cognomarket212.227.114.90Fooglesfunny202.106.63.4

La lista de IP y servicios ha sido extraida de una mezcla de blogs que controlo y en los que está el plugin que comentaba, por lo que he podido sacar las direcciones e investigar un poco.

Que conste que hay más sitios que roban contenidos, pero lo hacen a través de otros servicios que, lógicamente, por la repercusión que tienen en lectores reales, no he añadido a la lista…

Cómo saber la IP real del usuario con PHP

<?php
function SacarIP() {
  if($_SERVER) {
    if($_SERVER["HTTP_X_FORWARDED_FOR"]) {
      $realip = $_SERVER["HTTP_X_FORWARDED_FOR"];
    } elseif ($_SERVER["HTTP_CLIENT_IP"]) {
      $realip = $_SERVER["HTTP_CLIENT_IP"];
    } else {
      $realip = $_SERVER["REMOTE_ADDR"];
    }
  } else {
    if(getenv("HTTP_X_FORWARDED_FOR")) {
      $realip = getenv("HTTP_X_FORWARDED_FOR");
    } elseif(getenv("HTTP_CLIENT_IP")) {
      $realip = getenv("HTTP_CLIENT_IP");
    } else {
      $realip = getenv("REMOTE_ADDR");
    }
  }
  return $realip;
}
echo SacarIP();
?>