2009/08/28

Enviando emails con PHP



El envío de emails con PHP puede ser relativamente sencillo.
Pero, dependiendo de cómo sea el mensaje, las cosas pueden requerir algo más de trabajo.


Explorando en la red encontramos ejemplos que usan la función nativa mail(). Esto funciona bien para el caso relativamente sencillo de enviar ASCII (ó ISO-8859-1).

Si uno quiere enviar HTML o Unicode (UTF-8), o ASCII junto con HTML (como suelen hacer los servicios email), entonces las cosas se complican un poco, y los tutoriales son más escasos. Quizás optan por usar alguna biblioteca auxiliar, como PEARL. Es posible seguir haciéndolo con mail(), pero no encontrará mucho al respecto.

Las complicaciones aumentan un poco más si se quiere adjuntar un documento al mensaje. Sigue siendo posible seguir haciéndolo con mail(), pero 'tampoco encontrará mucha información sobre cómo hacerlo.

Para hacerlo, es importante fijarse en los headers que se envían. Estos contienen la descripción del contenido del mensaje. Y hay que colocar también ciertas marcas de separación en la estructura del mensaje.

Un mensaje ASCII + HTML tiene un mensaje con una estructura similar a:

(declaración de separador alterno)

--separador alterno
(descripción del contenido de texto)
(contenido de texto)

--separador alterno
(descripción de contenido html)
(contenido html)
--separador alterno--

Un mensaje ASCII + HTML + adjunto tiene un mensaje con una estructura similar a:

(declaracion de separador mixto)

--separador mixto

(declaración de separador alterno)

--separador alterno
(descripción del contenido de texto)
(contenido de texto)

--separador alterno
(descripción de contenido html)
(contenido html)
--separador alterno--

--separador mixto
(descripción de contenido adjunto)
(contenido adjunto)
--separador mixto--

Se puede observar que los separadores finales tienen un par de quiones extra al final (--).

Se puede comprobar esto explorando el código fuente de algunos email que se hayan recibido, o algunos simples que ilustren estos tres casos.

A continuación coloco el código de algunos scripts que desarrollé basado en las ideas que encontré en PHP: Sending Email (Text/HTML/Attachments) y mucha prueba y error hasta que funcionaron :)

simple_email.php


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?php
// akobashikawa@gmail.com, 20090828

$from = isset($_POST['from'])?$_POST['from']:'';
$to = isset($_POST['to'])?$_POST['to']:'';
$subject = isset($_POST['subject'])?$_POST['subject']:'';
$message = isset($_POST['message'])?$_POST['message']:'';
if (isset($_POST['submit'])) {
$headers = "From: $from\r\nReply-To: $from";
$mail_sent = mail( $to, $subject, $message, $headers );
}
?>
<title>Simple Email</title>
<style type="text/css">
</style>

<script type="text/javascript">
</script>
</head>
<body>
<h1><a href="simple_email.php">Simple Email</a></h1>
<form action="simple_email.php" method="POST">
From:<br/>
<input type="text" name="from" value="<?php echo $from; ?>"><br/>
To:<br/>
<input type="text" name="to" value="<?php echo $to; ?>"><br/>
Subject:<br/>
<input type="text" name="subject" value="<?php echo $subject; ?>"><br/>
Message:<br/>
<textarea name="message"><?php echo htmlentities($message); ?></textarea><br/>
<input type="submit" name="submit" value="Send"/>
</form>
<p>
<?php echo isset($mail_sent)?($mail_sent? "Mail sent":"Mail failed"):''; ?>
</p>
</body>
</html>


html_email.php


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?php
// akobashikawa@gmail.com, 20090828

$from = isset($_POST['from'])?$_POST['from']:'';
$to = isset($_POST['to'])?$_POST['to']:'';
$subject = isset($_POST['subject'])?$_POST['subject']:'';
$message = isset($_POST['message'])?$_POST['message']:'';
if (isset($_POST['submit'])) {
$random_hash = md5(date('r', time()));
?>
<?php ob_start(); ?>
--PHP-alt-<?php echo $random_hash; ?>
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: base64

<?php echo base64_encode(strip_tags($message)); ?>

--PHP-alt-<?php echo $random_hash; ?>
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: base64

<?php echo base64_encode($message); ?>

--PHP-alt-<?php echo $random_hash; ?>--
<?php $_message = ob_get_clean(); ?>
<?php
$headers = "From: $from\r\nReply-To: $from";
$headers .= "\r\n".'MIME-Version: 1.0';
$headers .= "\r\n"."Content-Type: multipart/alternative; boundary=\"PHP-alt-".$random_hash."\"";
$mail_sent = mail( $to, $subject, $_message, $headers );
}
?>
<title>HTML Email</title>
<style type="text/css">
</style>

<script type="text/javascript">
</script>
</head>
<body>
<h1><a href="html_email.php">HTML Email</a></h1>
<form action="html_email.php" method="POST">
From:<br/>
<input type="text" name="from" value="<?php echo $from; ?>"><br/>
To:<br/>
<input type="text" name="to" value="<?php echo $to; ?>"><br/>
Subject:<br/>
<input type="text" name="subject" value="<?php echo $subject; ?>"><br/>
Message:<br/>
<textarea name="message"><?php echo htmlentities($message); ?></textarea><br/>
<input type="submit" name="submit" value="Send"/>
</form>
<p>
<?php echo isset($mail_sent)?($mail_sent? "Mail sent":"Mail failed"):''; ?>
</p>
</body>
</html>


attachment_email.php


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?php
// akobashikawa@gmail.com, 20090828

$from = isset($_POST['from'])?$_POST['from']:'';
$to = isset($_POST['to'])?$_POST['to']:'';
$subject = isset($_POST['subject'])?$_POST['subject']:'';
$message = isset($_POST['message'])?$_POST['message']:'';
if (isset($_POST['submit'])) {
$random_hash = md5(date('r', time()));
if (isset($_FILES['uploadedfile'])) {
$filename = basename($_FILES['uploadedfile']['name']);
$filetype = $_FILES['uploadedfile']['type'];
$tmp_file = $_FILES['uploadedfile']['tmp_name'];
$attachment = chunk_split(base64_encode(file_get_contents($tmp_file)));
}
?>
<?php ob_start(); ?>
--PHP-mixed-<?php echo $random_hash; ?>
Content-Type: multipart/alternative; boundary="PHP-alt-<?php echo $random_hash; ?>"

--PHP-alt-<?php echo $random_hash; ?>
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: base64

<?php echo base64_encode(strip_tags($message)); ?>

--PHP-alt-<?php echo $random_hash; ?>
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: base64

<?php echo base64_encode($message); ?>

--PHP-alt-<?php echo $random_hash; ?>--

--PHP-mixed-<?php echo $random_hash; ?>
Content-Type: <?php echo $filetype; ?>; name="<?php echo $filename; ?>"
Content-Disposition: attachment; filename="<?php echo $filename; ?>"
Content-Transfer-Encoding: base64

<?php echo $attachment; ?>
--PHP-mixed-<?php echo $random_hash; ?>--

<?php $_message = ob_get_clean(); ?>
<?php
$headers = "From: $from\r\nReply-To: $from";
$headers .= "\r\n".'MIME-Version: 1.0';
$headers .= "\r\n"."Content-Type: multipart/mixed; boundary=\"PHP-mixed-".$random_hash."\"";
$mail_sent = mail( $to, $subject, $_message, $headers );
}
?>
<title>Attachment Email</title>

<style type="text/css">
</style>

<script type="text/javascript">
</script>
</head>
<body>
<h1><a href="attachment_email.php">Attachment Email</a></h1>
<form enctype="multipart/form-data" action="attachment_email.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
From:<br/>
<input type="text" name="from" value="<?php echo $from; ?>"><br/>
To:<br/>
<input type="text" name="to" value="<?php echo $to; ?>"><br/>
Subject:<br/>
<input type="text" name="subject" value="<?php echo $subject; ?>"><br/>
Message:<br/>
<textarea name="message"><?php echo htmlentities($message); ?></textarea><br/>
Attachment:<br/>
<input type="file" name="uploadedfile"/><br />
<input type="submit" name="submit" value="Send"/><br/>
</form>
<p>
<?php echo isset($mail_sent)?($mail_sent? "Mail sent":"Mail failed"):''; ?>
</p>
</body>
</html


Cada uno de estos scripts es una aplicación completa. Se presenta un formulario simple y se hace el envío de email.

Algo que es importante tener en cuenta al enviar Unicode es usar base64 para codificar el mensaje.

Y al enviar un adjunto es importante identificar el tipo de contenido, así como el nombre del archivo. Si no se coloca filename, además de name, en la descripción, el cliente email no reconocerá el adjunto (al menos no GMail).

Recuerde que para que funcione el envío, PHP debe estar configurado para reconocer algún servicio email, usualmente en el servidor local.

2009/08/19

Construyendo código

Al desarrollar una aplicación web me voy dando cuenta que el proceso es similar a cuando se dibuja, o se pinta algo. Pero también es útil pensar que es similar a cuando se construye algo.

De mismo modo que se ponen cimientos y una estructura base, uso un framework.

Uno podría hacer un framework aceptable. Afortunadamente hay frameworks estandar que puedo reutilizar y me ahorran mucho trabajo.

Pero quienes han hecho alguna vez un framework (quizás como yo, sin saber que así se llamaba), saben que es simplemente una forma de organización que va surgiendo conforme resuelves el problema. Es como un patrón de lugares comunes que van apareciendo en tu modo de resolver las cosas. Incluso aunque no hagas nada a propósito, el sólo hecho de intentar resolver el problema produce un framework.

Pienso que los framework estandar surgieron del mismo modo, pero por alguna razón (su simplicidad, elegancia, robustez, lo que sea), les pareció tan bueno a otros desarrolladores como para dedicarse a la tarea de mantenerlo y mejorarlo como un proyecto en común, un estandar.

Aún usando un framework estandar, para hacer algo nuevo se sigue la secuencia del bosquejo, funcionamiento, optimización. Es similar a levantar estructuras auxiliares, luego la estructura en sí, y después ir puliendo el trabajo.

Esa secuencia es como algo natural. Es demasiado complicado y propenso a errores tratar de desarrollar directamente 'en limpio'. Es como tratar de construir sin andamios, ni estructuras auxiliares, ni bosquejos, ni líneas guía.

El código va creciendo, simplificándose, creciendo, simplificándose. Pero entonces uno nota un problema. Si ya se tiene código funcional optimizado, las mejoras que se le puede hacer son relativamente pequeñas, del mismo modo que las correcciones que se pueden hacer sobre un edificio habitado, o sobre un dibujo ya terminado.

Sería interesante que se pudiera trabajar sobre el código original, con sus estructuras auxiliares y código aún sin optimizar, donde el espíritu de la solución es más palpable, y luego, de algún modo, iniciar un proceso de optimización estándar, una especie de compilación, que produzca el código optimizado para producción.

2009/08/18

Pintando código

Actualmente desarrollo una aplicación web que es un tablero de anuncios de trabajo. El lenguaje de programación es PHP. El framework, CodeIgniter.

He realizado todos los aspectos del desarrollo. Organicé los requerimientos, modelé las tablas, implementé los modelos, los controladores, y las vistas.
Trato de seguir el principio KISS, de mantener las cosas tan simples como sean posibles, pero no suele ser algo fácil.

Muchas veces he tenido la tentación de hacer algo que quede bien, para el futuro, o para cierto público exigente que me imagino debo complacer. Paso algo de tiempo divagando, pero eventualmente (afortunadamente) retomo la forma KISS.

Me parece que cuando uno ve código profesional, y está comenzando, puede tener la falsa impresión de que ese código se produce directamente tal cual lo vemos. Cuando vamos ganando experiencia es que notamos que es necesario pasar por soluciones intermedias para llegar a la solución final.

Pensándolo bien, así ocurre en otros tipos de desarrollo. Y puede que sea la forma natural de hacerlo.

Por ejemplo, una pintura que vemos colgada sobre una pared no es realizada directamente en limpio. Antes hubo un bosquejo, posiblemente sucio y horrible de ver, pero necesario para que llegara a existir. Un bosquejo que fue cambiado y corregido muchas veces antes de dar la primera pincelada. Luego, los colores se fueron aplicando por capas y zonas, de acuerdo a ciertos principios y técnicas, y también sufriendo cambios y correcciones mientras la imagen se aproximaba al ideal imaginado.

No tengo mucha técnica dibujando y mucho menos pintando, sin embargo, recuerdo algunas experiencias.

Recuerdo que, al comienzo, sin mucha guía, hubo veces que intentaba obtener directamente la versión final de una obra. Me concentraba en un área pequeña hasta completarla y luego avanzaba a un área siguiente.
No pasaba mucho tiempo hasta que notaba que algo andaba mal con el rostro que intentaba reproducir. Quizás cada parte por separado estuviera bien, pero el conjunto estaba, como se dice, descuadrado. Descubrí que me iba mejor si trazaba antes algunas líneas guía, que me ayudaran a recordar el tamaño, la posición y la forma aproximada de los ojos, la nariz, las orejas, y los labios, y luego recién desarrollar cada una de esas partes; es decir, bosquejar primero y desarrollar después.
Si se trataba de una imagen de cuerpo entero o donde aparecieran varios personajes, el bosquejo era mucho mejor opción que intentar lograr la composición directamente.

Puede ser que la costumbre de copiar dibujos es la que nos habituó a pensar que hacer una imagen por áreas, secuencialmente, era la única forma.
Pero es cuando uno trata de hacer una composición propia que descubre que es mejor trabajar por capas, iteradamente, mejorando cada vez la capa anterior.

Bailar es parecido. Al menos para mí. Aunque hay personas que insisten en tener memorizados pasos perfectos antes de comenzar a ensamblarlos, yo creo que es mucho mejor tener primero la secuencia, es decir el bosquejo de todo el baile y sus movimientos generales.
De ese modo puedo aproximarme con mas facilidad y menos frustraciones a la idea, y sentir cierta armonía que guía y ayuda a ir puliendo los pasos.

Planear la ruta de un largo viaje también es parecido. Es agotador, y hasta iluso, tener de antemano el detalle de cada tramo de la ruta, porque la práctica está llena de imprevistos. Es mejor hacer un bosquejo general, indicar los hitos principales, e ir desarrollando los tramos a medida que vamos avanzando.

Y para programar, he descubierto que también es similar.
No es malo hacer código repetitivo, usar muchas variables, ni otras cosas que muchos temen como si fueran pecados capitales.
No es malo, del mismo modo que no es malo garabatear una hoja cuando se está bosquejando.
Algunas veces para llegar a cierta posición, incluso puede ser necesario crear material auxiliar que luego se desecha.
Al inicio, lo principal no es la perfección ni la optimización, sino que simplemente funcione. Primero que funcione, luego se optimiza, me parece que dice un dicho de los hacker.

Claro que presentar código repetitivo, con demasiadas variables, o no optimizado, como versión final de un producto es como presentar un bosquejo en lugar del cuadro terminado. No te tomarán en serio, a menos que seas Leonardo.

Además, para desarrollar algo, es importante comenzar. No quedarse atorado en la trampa del perfeccionismo, ni esperar que todas las estrellas estén alineadas para dar el primer paso. Simplemente comenzar; dar el primer trivial paso; escribir código sucio quizá, pero que funcione. Luego se podrá factorizar, reorganizar, simplificar.

Factorizar es extraer el factor común de una expresión, de modo que se forma una expresión más simple o menos repetitiva. Es lo que se hace en álgebra.
Si alguien lo recuerda, no es conveniente factorizar una expresión cada vez que se le agrega algo; suele ser mejor esperar hasta que todos los términos estén presentes, para ver mejor el patrón. Si uno hace una factorización prematura, suele requerir un esfuerzo adicional desatar lo ya atado para ingresar el cambio y luego volverlo a atar.

De modo similar en programación. Es mejor no optimizar prematuramente el código, sino esperar hasta que esté completo para ver mejor el patrón, y luego factorizar. La optimización prematura puede fácilmente complicar el desarrollo. El código optimizado es mucho más difícil de depurar. Se debe optimizar, pero a su tiempo.