Eliminar un directorio y su contenido en PHP

Estos días en uno de los proyectos del curro (váis a disculpar que esta semana sólo haya publicado citas, pero ha sido una semana dura en lo laboral y peor en lo personal) he tenido que hacer una función que vacíe un directorio completamente y lo borre en PHP. Alguna dirá rmdir y al carajo… pero cualquier usuario de linux/sistemas unix sabe que a ese rmdir le tienes que poner un -r para que borre los archivos que contiene, como no hay -r en PHP lo vamos a crear nosotros:

<?php
    function rmDir_rf($carpeta)
    {
      foreach(glob($carpeta . "/*") as $archivos_carpeta){             
        if (is_dir($archivos_carpeta)){
          rmDir_rf($archivos_carpeta);
        } else {
        unlink($archivos_carpeta);
        }
      }
      rmdir($carpeta);
     }

Explicación: Usamos glob para accedera todo lo que hay en la carpeta y lo recorremos (foreach). Si encontramos un directorio (is_dir nos lo confirma) entonces tiramos de recursivdad y ejecutamos la propia función dentro de si misma. Si no es un directorio, pues se borra el archivo. Al acabar de patear se borra la carpeta, que ahora que está vacía sí puede ser eliminada por rmdir.

Esta funcionalidad irá incluída en mi librería de seguridad DonnieSecure (próximamente en GitHub)

PHPMailer en gallego

Español:

Si anteayer os comenté que había hecho una traducción al gallego de Colorbox que ya se había integrado en el proyecto en GitHub hoy toca comentar lo mismo de PHPMailer, clase para el envío de correos desde PHP. Una pequeña contribución por mi parte a proyectos libres de los que he echado mano en más de un desarrollo.

Galego:

Se antonte comentéi que fixera unha traducción ó galego do Colorbox que xa fora integrada no proxecto de GitHub hoxe toca facelo con PHPMailer, a popular clase para o envío de correos dende PHP. Unha pequena contribución pola miña parte a proxectos libres dos que ben deitei máis dunha vez nos meus desenvolvementos.

Comprobar si un número es par o impar con PHP

El otro día me preguntaron si había alguna función de PHP que devolviera si un número es par o impar, tipo is_odd, is_even o similar. Realmente es una comprobación que puedes hacer con una línea de código, así que no existe una función que lo haga. Si lo que quieres es simplemente imprimir en pantalla par e impar la solución es simple (supongamos que el número a comprobar está en la variable $number):

echo ($number % 2 ? 'Impar' : 'Par');  

Tan solo necesitas la operación de módulo, que devuelve el resto de la división entera del número entre 2. Como el resultado en caso de que sea par será 0 este será interpretado en los condicionales o en un operador ternario como un FALSE.

¿Lo queréis como función que devuelva true o false? Ok, es simple. Esta es una función que devuelve TRUE si es impar y false si es par :

function esImpar($number){
    return $number % 2;
}

Y si te quieres ir de guay hasta puedes usar el operador & a nivel de bit para hacerlo (para quedar más pro)

if($number & 1){  
    echo 'Impar';  
}else{  
    echo 'Par';  
} 

Ale, ya tenéis por ahí tres soluciones para comprobar si un número es par o impar.

Función PHP para hash de contraseñas con GOST y salt

Se está poniendo de moda GOST (así, sin H, que no es inglés) desde todo el asunto Snowden porque, según han empezado a decir algunos expertos en seguridad, es uno de los algoritmos que la NSA no ha logrado romper (al menos no se sospecha que lo lograran). Se trata de un algoritmo criptográfico parido por matemáticos soviéticos en los últimos años de la Guerra Fría, revisado posteriormente un par de ocasiones. Para más info, como siempre, la wikipedia.

El caso es que con el tema de la paranoia de la seguridad me han pedido una función para encriptar passwords segura en PHP, aunque en PHP 5.5 ya tengamos unas muy cómoda y seguras funciones para trabajar con passwords. Pero cliente manda, y ha leído que GOST tralarí y no se fía de algo que sea standar… así que me he hecho una función propia, que os comparto por si queréis usarla.

<?
function getSecurePass($password_plano, $username){
    if(strlen($password_plano)<8){
	//exigimos un mínimo de 8 caracteres
        $response = array("aceptado"=>FALSE, "Resultado"=>"Error: Contraseña demasiado corta");
    }else{	
                if(strlen($password_plano) % 2){
                     $salt0="@fr!87$"; //aquí metemos una cadena, la que vosotros prefiráis
                }else{
                     $salt0="~m¿0kL" //aquí metemos otra, y según sea par o impar la longitud cogerá la que corresponda
                }		
		$salt1 = substr($password_plano, strlen($texto_plano)-6, 5);
		$salt2 = substr(md5($username), 6, 6);
		$salt3 = substr($username, strlen($username),-2);
		$arrayPss = str_split($password_plano,(strlen($password_plano)/2)+1);
		$hash = hash('gost', $salt3.$arrayPss[0].$salt0.$salt1.$arrayPss[1].$salt3.$salt2);
		sleep(1);
		$response = array("aceptado"=>True, "Resultado"=>$hash);
	}
    reutrn $response;
}

?>

Y para complementar el sistema de login contra ataques por fuerza bruta es una buena idea ralentizar la respuesta desde el servidor. ¿Y cómo hacerlo sin darle la vara al usuario mucho? Haciendo una función de login que espere, pero sólo cuando el password sea erróneo. En este caso no desarrollé nada y copié un ejemplo desde php.net que ahí os pego (en mi caso lo adapté para usarlo con el resto del sistema de login desarrollado, en los comentarios del enlace está la función original son su explicación):

<?php
public function handle_login() {
    if($uid = user::check_password($_REQUEST['email'], $_REQUEST['password'])) {
        return self::authenticate_user($uid);
    }
    else {
        // delay failed output by 2 seconds
        // to prevent bruit force attacks
        sleep(2);
        return self::login_failed();
    }
}
?>

Espero que os sirva de ayuda a la hora de asegurar vuestras contraseñas.

Poblar un combo dinámicamente con jQuery y JSON

Es habitual que, creando formularios, nos encontremos con la situación de tener dos combos (o campo select si lo preferís) y que uno tenga que cargar/modificar sus datos según el resultado seleccionado en el otro, de forma dinámica. Esto es posible con Javascript, y muy cómodo si utilizamos JSON y jQuery.

Os planteo un ejemplo simple: tenemos dos combos, uno con provincias y otro con ayuntamientos. El marcado HTML va a ser más o menos tal que así:

<select id="provincias" name="provincias">
  <option value=""></option>
  <option value="1">A Coruña</option>
  <option value="2">Lugo</option>
  <option value="3">Ourense</option>
  <option value="4">Pontevedra</option>
</select>
<select id="poblaciones" name="poblaciones" disabled="disabled">
</select>

Como véis, al momento de cargar la página el campo provincias tendrá las cuatro provincias gallegas, y el campo poblaciones estará desactivado. ¿Ahora qué necesitamos? Pues primero necesitamos un script que nos saque las poblaciones de la base de datos y nos las envíe como un JSON. Cualquier lenguaje de lado del servidor nos vale, para el ejemplo va a ser PHP (pero vamos, que podría aplicarse con Java, Ruby, node.js, Python…). Crearemos un script llamado getPoblacionesJson.php

<?php
include 'conexionbd.php';
if ($mysqli -> multi_query("CALL sp_GetPoblaciones(" . $_GET['pr'] . ")")) {
	$poblaciones = array();
	do {
		if ($result = $mysqli -> store_result()) {
			while ($fila = $result -> fetch_assoc()) {				
				$poblaciones[$fila['Id']] = $fila['Nombre'];
			}
		}
	} while($mysqli->next_result());
	print_r(json_encode($poblaciones));
}
?>

En este caso veis que lo que hacemos es llamar a un procedimiento almacenado que nos devuelve las provincias, recorremos el resultado y vamos guardándolo en un array. Finalmente lo convertimos a JSON y lo imprimios para que lo recoja la función de Javascript. Si os estáis preguntando cómo va el procedimiento almacenado, es una simple select en MySQL, tal que así:

DELIMITER $$
CREATE PROCEDURE sp_GetPoblaciones(IN provincia int)
begin
	SELECT Id, Nombre FROM poblaciones WHERE (provincia is null or IdProvincia = provincia) ORDER BY Nombre;
end $$
DELIMITER ;

Ok, tenemos entonces el script del servidor, el marcado y el procedimiento en la base de datos. ¿Qué nos queda? Pues el Javascript, vitaminado con jQuery para ganar productividad:

$("#provincias").change(function() {
	$("#poblaciones").empty();
	$.getJSON('http://localhost/getPoblacionesJson.php?pr='+$("#provincias").val(),function(data){
		console.log(JSON.stringify(data));
		$.each(data, function(k,v){
			$("#poblaciones").append("<option value=\""+k+"\">"+v+"</option>");
		}).removeAttr("disabled");
	});
});

La idea es simple: Si se registra algún cambio en el combo de provincias vaciamos el combo de poblaciones y lo rellenamos con los nuevos datos, obtenidos mediante la función getJSON() y que recorreremos con each() como un conjunto de claves/valores. Finalmente, por si está desactivado, lo reactivamos. He hecho un console.log por si queréis ver cómo funciona la cosa en la consola de javascript de Chrome o del Firebug.

Espero que os sirva de ayuda esta entrada para trabajar con combos dinámicos.

Diferencias entre isset(), empty() e is_null() en PHP

Entre las múltiples funciones que existen en PHP para testear los valores de una variable encontramos tres que resultan de gran ayuda para conocer si una variable está definida y/o es nula, que son isset(), empty() e is_null(). Rehaciendo código de alguna gente en mantenimientos o actualizaciones de proyectos me he encontrado con dos hechos:

  1. Muchos no las usan (y luego vienen los errores por usar una variable vacía)
  2. Muchos no tienen muy claro cómo y cuándo usarlas

La cosa es muy sencilla si se explica bien. Vamos a ver en qué consisten las tres, y veréis qué rápido lo entendéis:

  • isset(): Determina si una variable ha sido definida y no es nula. Devuelve FALSE en caso de le pasemos una variable sin definir, una variable definida pero sin valor o con el valor puesto a null, y en el resto de casos devuelve TRUE.
  • empty(): Determina si la variable tiene un valor vacío, por llamarlo de alguna forma. En este caso devuelve TRUE en muchos supuestos: una cadena vacía (es decir «», si es una cadena con un espacio en blanco tal que » » devolverá FALSE), un número 0 (sea tanto un entero, un float o una cadena con el número cero tal que «0»), una variable con el valor FALSE, una variable con el valor NULL, una variable definida pero sin valor y un array vacío. Al contrario que isset(), no puede evaluar variables que no hayan sido definidas.
  • is_null(): En cierto modo es la complementaria a isset(), ya que devuelve TRUE en caso de que la variable sea NULL o que sea una variable definida pero sin valor, y en el resto de los casos FALSE. La diferencia es que no puede evaluar variables que no estén definidas, provocará un error.

Como podéis ver no es especialmente complicado comprender el funcionamiento de estas tres funciones, y su uso os salvará de muchos quebraderos de cabeza. Por ejemplo trabajando con variables pasadas por formularios o recogidas de una tabla de base de datos que acepte nulos, que todavía hay gente que va a lo loco, así, sin comprobación ni condón… y luego vienen las rupturas en el código.

Envío masivo de email a lista de contacto con PHPMailer

Seguimos con el PHPMailer tras la introducción y la entrada sobre usar Gmail. Ahora, y recordando un poco el ejemplo de cómo automatizar acciones con cURL, vamos a ver la forma de enviar un correo a una lista de direcciones guardada en una base de datos.

La idea es simple, si recordáis cómo era el script de php de los anteriores ejemplos todo consiste en hacer lo mismo pero con una llamada a la base de datos y un bucle de envíos. En el ejemplo de abajo ya podéis ver el código, explicado punto por punto. en este caso no voy a usar el ejemplo que viene en los docs de la librería, porque está anticuado, usando mysql en lugar de mysqli (es más, creo que lo voy a subir a GitHub):

<?php
//lo primero cargar la librería
require '../PHPMailerAutoload.php';
//lo segundo, crear el objeto mail
$mail = new PHPMailer();
//Vamos a meter el cuerpo (cargado desde un html externo) en una variable
$body = file_get_contents('contents.html');
//definimos el uso de smtp
$mail->isSMTP();
//definimos el servidor que aloja nuestro correo
$mail->Host = 'smtp.tuserver.com';
//activamos la autenticación smtp
$mail->SMTPAuth = true;
//Con esta línea dejamos abierta la conexión al servidor smtp
$mail->SMTPKeepAlive = true; 
//Definimos la seguridad, si nuestro server lo permite lo mejor es usar tls
$mail->SMTPSecure = 'tls';
//el puerto cambia según la seguridad. Para tls este, para ssl 456 y sin seguridad el 25
$mail->Port = 587;
//definimos usuario y contraseña
$mail->Username = 'tucorreo@tudominio.com';
$mail->Password = 'tu contraseña';
//ahora definimos remitente y si hace falta, réplica
$mail->setFrom('miwebe@midominio.com', 'Lista');
$mail->addReplyTo('miwebe@midominio.com', 'Lista');

$mail->Subject = "Ejemplo de lista con phpmailer";

//Vamos ahora a crear un objeto de conexión a la base de datos
//Tras eso, vamos a recuperar los datos que necesitamos:
$mysqli = new mysqli('server', 'username', 'password', 'db');
$mysqli->set_charset("utf8")
$result = $mysqli->query("SELECT nombre, email FROM mailinglist");

//ahora en bluce vamos recorriendo los resultados y enviando correos

while ($row = $result->fetch_array()) {
    $mail->AltBody = 'Para ver el menasje, please use un  visor de email compatible con HTML';
    $mail->msgHTML($body);
    $mail->addAddress($row['email'], $row['nombre']);
    
    if (!$mail->send()) {
        echo "Mailer Error:" . $mail->ErrorInfo . "<br />";
        break; //forzamos la salida del bucle en caso de error
    } 
    // Limpiamos los datos par próximos envíos
    $mail->clearAddresses();
    $mail->clearAttachments();
}

Como podéis ver no es demasiado complejo gestionar una lista de correo desde PHP y MySQL.

Display inline-block en Explorer 6, Explorer 7 y Explorer 8

Si maquetáis web es posible que utilizáseis alguna vez la propiedad display:inline-block en vuestro CSS a la hora de posicionar divs o elementos de lista, ya que funciona de una forma muy cómoda. ¿Problemas de usarla? Que en en las versiones anteriores a Explorer 8 no va (este inclusive).

Actualmente, por suerte, casi nadie tiene Explorer 7 (y menos Explorer 6) y los que queden se irán al guano cuando Microsoft deje de dar soporte a XP en 2014, pero todavía hay varios Windows Vista con Explorer 8 rulando por ahí, además también hay administraciones, clínicas o empresas con equipos arcaicos y sin actualizar. ¿Solución? Algunos te dirán «no la uses»… pues no, hay un simple hack, que podéis ver en el ejemplo:

elementoAPosicionar {
    display: inline-block;
    *display: inline;
    zoom: 1;
}

Con esto usamos un Safe CSS Hack para que IE7 interprete bien nuestro CSS. Otra posibilidad es que no funcione por culpa de la declaración del DOCTYPE (Explorer es muy puñetero). Si pasáis de Explorer 7 y sólo os preocupa el 8, basta añadir esto al principio del archivo HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

Y creo que si lo declaráis como HTML5 con esta cabecera también funciona:

<!DOCTYPE html>

Por suerte a partir de Explorer 9 muchas de estas cosas se han ido estandarizando, y de momento en Explorer10 lo único que me ha fallado ha sido una animación CSS3, lo cual respecto a hace años es un enorme avance para los chicos de Microsoft. Igual en IE12 ya no hace falta hacer un CSS específico para Explorer.

Enviar un email con PHPMailer usando una cuenta de Gmail

Ya hace un tiempo hablamos aquí de cómo enviar correos desde un script de php usando PHPMailer. He decidido hacer una serie de artículos ampliando un poco más el tema.

Una cuestión que puede ser interesante, sobre todo para aquellos que a lo mejor están en un hosting gratuito y no tienen cuentas de correo vinculadas a su dominio, es utilizar una cuenta de Gmail para enviar los correos. Ya que Gmail nos permiter usarlo como cualquier otro correo SMTP esto es factible.

Configurar PHPMailer para usar Gmail es simple, y podéis ver cómo se haría en el siguiente ejemplo (va comentado línea a línea):

/*Lo primero es añadir al script la clase phpmailer desde la ubicación en que esté*/
require '../class.phpmailer.php';

//Crear una instancia de PHPMailer
$mail = new PHPMailer();
//Definir que vamos a usar SMTP
$mail->IsSMTP();
//Esto es para activar el modo depuración. En entorno de pruebas lo mejor es 2, en producción siempre 0
// 0 = off (producción)
// 1 = client messages
// 2 = client and server messages
$mail->SMTPDebug  = 0;
//Ahora definimos gmail como servidor que aloja nuestro SMTP
$mail->Host       = 'smtp.gmail.com';
//El puerto será el 587 ya que usamos encriptación TLS
$mail->Port       = 587;
//Definmos la seguridad como TLS
$mail->SMTPSecure = 'tls';
//Tenemos que usar gmail autenticados, así que esto a TRUE
$mail->SMTPAuth   = true;
//Definimos la cuenta que vamos a usar. Dirección completa de la misma
$mail->Username   = "tucuenta@gmail.com";
//Introducimos nuestra contraseña de gmail
$mail->Password   = "tucontraseña";
//Definimos el remitente (dirección y, opcionalmente, nombre)
$mail->SetFrom('tucuenta@gmail.com', 'Mi nombre');
//Esta línea es por si queréis enviar copia a alguien (dirección y, opcionalmente, nombre)
$mail->AddReplyTo('replyto@correoquesea.com','El de la réplica');
//Y, ahora sí, definimos el destinatario (dirección y, opcionalmente, nombre)
$mail->AddAddress('destinatario@sucorreo.com', 'El Destinatario');
//Definimos el tema del email
$mail->Subject = 'Esto es un correo de prueba';
//Para enviar un correo formateado en HTML lo cargamos con la siguiente función. Si no, puedes meterle directamente una cadena de texto.
$mail->MsgHTML(file_get_contents('correomaquetado.html'), dirname(ruta_al_archivo));
//Y por si nos bloquean el contenido HTML (algunos correos lo hacen por seguridad) una versión alternativa en texto plano (también será válida para lectores de pantalla)
$mail->AltBody = 'This is a plain-text message body';
//Enviamos el correo
if(!$mail->Send()) {
  echo "Error: " . $mail->ErrorInfo;
} else {
  echo "Enviado!";
}

Esencialmente es casi lo mismo que viene en los DOCS que acompañan a los archivos, pero traducido.

Espero que os sirva. Próximamente más PHPMailer, y más programación web.

Automatizar la ejecución de scripts PHP en servidores Linux con cron y cURL

En el pasado ya hablamos en este blog de automatizar tareas en MySQL y de hacer uso de cURL para procesar formularios y enviarlos a un servicio web REST. ¿Y si os digo que cURL también se puede usar para atomatizar la ejecución de scripts PHP?

Seguro que más de una vez has pensado «Molaba que mi página enviara un correo a todos aquellos usuarios que están de cumpleaños» o cosas así. Puedes pensar que da mucho la vara, pero para nada. No sé si lo comenté la otra vez, pero cURL además de poder ser usado desde PHP también puede ser llamado desde la línea de comandos, así que nos permite, desde una consola, llamar a un script de php por protocolo http (bueno, de hecho cURL puede usar varios protocolos como FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE o LDAP) para que se ejecute. Ahora a esto súmale que en un servidor Linux tenemos cron, que nos permite automatizar tareas, como la ejecución de archivos. Solución: programamos que cron ejecute la la llamada a nuestro script de PHP mediante cURL todos los días a una hora concreta.

Veamos un ejemplo en el que ejecutamos un script llamado cumple.php (imagináos que sea un script que felicita el cumpleaños a los usuarios de nuestra web, nos da igual el contenido del script, aquí la clave es automatizar la llamada) todos los días un minuto después de medianoche. Basta con editar el fichero de cron (por ejemplo con crontab -e) y añadir esta línea:

1 0 * * * /usr/bin/curl http://alojamiento/miweb/cumple.php

Bueno, el primer parámetro es el minuto, el segundo la hora y luego irían día del mes, mes y día de la semana (que en este caso van con * porque queremos que sea todos los días, no un día en concreto). Tras los parámetros temporales metemos la orden a ejecutar. En este caso llamamos a cURL (que suele estar en la carpeta /usr/bin/) seguido de la dirección donde esté el script para que sea llamado por protocolo http.

Para mis info sobre cURL puedes buscar aquí, y sobre cron mismamente te ayuda la Wikipedia.