Hace ya unas cuantas entradas usamos PHP para crear un formulario de contacto.

Pues ayer me dije… ¿Y si en vez de hacer subir a la gente un archivo/imagen y copiar el link le dejo enviármelo como adjunto?

Los cambios en el código son notables (he unificado los mensajes de error entre el servidor y el Javascript, y sobre todo he modificado la forma de enviar mensajes).

Demo Descarga

Si tenéis problemas con la llegada de los mensajes (probad un par de veces insertando en el formulario emails distintos), echadle un ojo a este comentario de la entrada anterior. No debería, pero nunca se sabe…

El HTML

El archivo index.html dejará de existir, y pasará a llamarse index.php. ¿Por qué?, porque así podremos enviar el archivo a la misma dirección, y mostrar el formulario en caso de que no se haya podido enviar el archivo via AJAX (ni siquiera IE9 soporta FormData). En otras palabras: Encima del <!DOCTYPE html> pondremos:

<?php
	// Aquí el email al que queres recibir el correo
	$receptor = "ecoal95@gmail.com";

	// Podéis modificar los mensajes de error que aparecerán en los campos requeridos
	$mensajes_error = array(
		'email' => 'Introduce un e-mail válido',
		'mensaje' => 'Tienes que introducir un mensaje',
		'nombre' => 'Introduce un nombre'
	);

	// Igual con el mensaje cuando se envía correctamente
	$mensaje_correcto = "El mensaje se envió correctamente, intentaré responderte lo antes posible";

	$enviado = $_SERVER['REQUEST_METHOD'] === "POST" || isset($_GET['ajax']);

	// Si hemos recibido el formulario, incluimos 'message.php'
	if( $enviado ) {
		include('message.php');
	}
?>

Además, mostraremos los errores si se ha enviado sin AJAX con el siguiente código (en su respectivo lugar):

<div id="error"><?php if( $enviado && $errores !== null ) {
	echo '<ul><li>' . implode('</li><li>', $errores) . '</li></ul>';
} ?></div>

<!-- ... -->

<div id="correcto"><?php if( $enviado && $errores === null ) {
	echo $mensaje_correcto;
} ?></div>

Habrá que cambiar los atributos enctype y action del formulario, quedando así:

<form id="formulario" name="formulario" method="POST" action="" enctype="multipart/form-data">
	<!-- ... -->
</form>

Habrá que crear la manera de enviar el archivo. Ésto irá encima de <p class="submit">:

<p>
	<label for="asunto">Agregar un archivo <small>(opcional)</small></label>
	<input type="file" name="adjunto" id="adjunto"></input>
</p>

Para unificar los mensajes de error en el javascript y en el servidor, esos mensajes serán pasados desde index.php. Insertaremos lo siguiente encima del último <script>:

<script>
	window.ec_form_messages = {
		correcto: "<?php echo $mensaje_correcto ?>",
		error: <?php echo json_encode($mensajes_error) ?>
	}
</script>

El CSS

En el CSS el único cambio con respecto al anterior es que he agregado estas dos líneas:

small {
	font-size: 85%;
	font-weight: normal;
}
input[type="file"] {
	display: block;
}

El Javascript

Los cambios en el Javascript son simples:

  • Para mostrar los errores usamos el objeto creado anteriormente (window.ec_form_messages).
  • Usamos una forma más general de recopilar elementos y datos para no tener que cambiar el script cada vez que se añade un campo o se cambia su id.
  • Sin soporte para AJAX en IE 6/7. Símplemente uso los estándares, y creo que estos navegadores ya tienen un uso muy reducido como para incrementar el tamaño del archivo sin sentido. No obstante podrán enviar el formulario normalmente.
  • Para los navegadores más modernos (permiten enviar archivos via AJAX) usamos el objeto FormData. Si no, usamos una cadena de texto como en el anterior (siempre que no contenga archivo).
// No hay soporte necesario para enviar archivo con AJAX, dejamos la forma estándar
if( hasFile && ! window.FormData ) {
	return true;
}

// Si no, se envía normalmente
if( window.FormData ) {
	valores = new FormData(form);
} else {
	valores = convertirObjeto(valores);
}
enviarform(valores, hasFile);
preventEvent();

El PHP

Si no os habéis percatado, el archivo post.php ya no existe. He usado la mayoría de su código en un archivo message.php, que incluiremos si nos han mandado el formulario. Está todo en los comentarios, así que sólo lo pegaré para los curiosos que no se quieran descargar los archivos.

<?php
	//-------------------------------------------------------------------
	// VARIABLES DEL MENSAJE
	$mensaje = preg_replace('/\n/','<br>',htmlspecialchars(urldecode($_POST['mensaje'])));
	$nombre = urldecode($_POST['nombre']);
	$email = urldecode($_POST['email']);
	$asunto = urldecode($_POST['asunto']);
	$fecha = date('c');

	// Título del mensaje
	$titulo = "Nuevo mensaje de $nombre desde el formulario de contacto";

	// El cuerpo del mensaje
	$data = "";


	// Definir si es una solicitud AJAX
	define('IS_AJAX', isset($_GET['ajax']) && $_GET['ajax'] === 'true');

	// Aquí almacenaremos los errores
	$errores = array();

	// Variables para manejar el adjunto
	$hay_adjunto = false;
	$adjunto = null;
	$boundary = null;

	/*
	 * Si hay archivos, hay que cambiar el inicio del mensaje y crear un separador (boundary)
	 */
	if( isset($_FILES['adjunto']) && $_FILES['adjunto']['error'] === 0) {
		$hay_adjunto = true;
		$adjunto = $_FILES['adjunto'];
		$boundary = md5(time());

		// Indicamos una separación
		$data .= "--".$boundary. "\r\n";

		// Y el comienzo del HTML
		$data .= "Content-Type: text/html; charset=\"utf-8\"\r\n";
		$data .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
	}

	/*
	 * Comprobaciones de los campos requeridos
	 */
	if( ! filter_var($email, FILTER_VALIDATE_EMAIL) )
		$errores[] = $mensajes_error['email'];

	if( ! isset($_POST['mensaje']) )
		$errores[] = $mensajes_error['mensaje'];

	if( ! isset($_POST['nombre']) )
		$errores[] = $mensajes_error['nombre'];

	// El mensaje HTML
	$data .= "<div class='mensaje'>
			<h1>Nuevo mensaje de $nombre</h1>
			<p><strong>Fecha:</strong> $fecha</p>
			<p><strong>Asunto:</strong> $asunto</p>
			<p><strong>Mensaje:</strong><br>$mensaje</p>
			<p><strong>Email:</strong> <a href='mailto:$email'>$email</a></p>
		</div>";

	// Las cabeceras empiezan igual
	$cabeceras = "MIME-Version: 1.0\r\n";
	$cabeceras .= "From: $nombre<$email>\r\n";
	$cabeceras .= "To: $receptor\r\n";

	// Si no hay errores probamos a enviar el archivo
	if( count($errores) === 0 ) {

		// Content-Type dependiente de si hay adjunto
		if( $hay_adjunto ) {
			$cabeceras .= "Content-Type: multipart/mixed; boundary=\"".$boundary."\"";

			// Si hay archivo
			// También añadimos al cuermo del mensaje un separador 
			$data .= "\r\n";
			$data .= "--" . $boundary . "\r\n";
			// Y el archivo con su correspondiende Content-Type (octet-stream para aplicaciones) y nombre
			$data .= "Content-Type: application/octet-stream; name=\"".$adjunto['name']."\"\r\n";
			$data .= "Content-Transfer-Encoding: base64\r\n";

			// Indicamos que es un adjunto
			$data .= "Content-Disposition: attachment\r\n\n\r";

			// Vamos con el adjunto: chunk_split transforma la cadena en base64 en estandar
			$data .= chunk_split(base64_encode(file_get_contents($adjunto['tmp_name']))) . "\r\n";

			// Acabamos el mensaje
			$data .= "--" . $boundary . "--";
		} else {
			// Si no lo hay nos bastará con decir que es un mensaje HTML
			$cabeceras .= "Content-type: text/html; charset=utf-8\r\n";
		}

		// Enviamos nuestro email y damos cuenta sy hay algún error
		if(mail($receptor, $titulo, $data, $cabeceras)) {
			// Si no hay ningún error, lo indicamos con null
			$errores = null;
		} else {
			// Si no indicamos que hubo un error
			$errores[] = "Hubo un error al enviar el e-mail";
		}
	}

	// Si es una solicitud AJAX, enviamos el JSON y no ejecutamos más código
	if( IS_AJAX ) {
		echo json_encode(array(
			'success' => $errores === null,
			'errors' => $errores,
			'has_files' => $hay_adjunto
		));
		exit;
	}
	// Si no ahora vendría el documento (index.php)
?>

Demo Descarga