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

58 pensamientos en “Formulario de contacto: Añadiendo el adjunto

  1. Bitacoras.com
  2. Subir archivos con PHP/jQuery (Ajax)
  3. Imagen de JonathanJonathan el dijo:

    Muy bueno tu ejemplo, me ayudo de mucho!

    Ahora si quiero que cambien el destinatario según la opción del Input Select, como lo puedo lograr?

  4. Imagen de Emilio Cobos Álvarez el dijo:

    Fácil, sólo tienes que hacer un switch con la variable que quieras :). Por ejemplo, si es el asunto:

    switch($asunto) {
    	case 'Pedir un tutorial':
    		$receptor = 'uncorreo@dominio.com';
    	break;
    	case 'cualquier cosa':
    		$receptor = 'otrocorreo@otracosa.com';
    	break;
    	// ...
    }
    • Imagen de carloscarlos el dijo:

      Hola amigos también tengo la misma inquietud seria bueno que hagas un video o aquí mismo un formulario con opciones desplegable y varios email receptores por favor seria de mucha utilidad.
      De ser posible bootstrap en una carpeta zip:
      proacle@outlook.es
      Busco socios para crear una red social si alguien se une a mi causa por favor contactarme.

  5. Imagen de jumasoljumasol el dijo:

    Sólo una cosa. Todo funciona perfectamente salvo por el hecho de que el contenido adjunto llega dañado por algún motivo. No se puede enviar nada porque llega vacío. Así, contenido .word, ,cuando lo abres llega en blanco.

    ¿Alguna opción?

    • Imagen de Emilio Cobos Álvarez el dijo:

      A mi me funciona correctamente (puedes probarlo con el mismo correo con el que has escrito el comentario y yo te readjunto el archivo).

      Es posible que superes el tamaño máximo de subida o algo por el estilo. Puedes probar poniendo al principio del script:

      ini_set('display_errors', 'On');

      para ver si sucede algo anormal en tu server.

      • Imagen de LuisLuis el dijo:

        Hola… muchas gracias por este formulario…
        Estoy tratando de implementarlo, pero me surge un error ya comentado… el archivo adjunto aparece con 0k…
        Donde puedo definir el tamaño de los adjuntos para evitar que sea ese el posible error?
        muchas gracias!!!

  6. Imagen de ernesto el dijo:

    Hola, el script es buenísimo lo acabo de implementar en mi web y me gusta mucho muy sencillo, cuando das a enviar si es una foto que pesa tarda en aparecer el cartel de mensaje enviado. me gustaria saber como se puede poner un gif animado o barra de progreso para indicar al que pulsó enviar que se esta subiendo la foto y enviando.

    Gracias.

  7. Imagen de DianaDiana el dijo:

    Hola Emilio,
    Probé tu formulario de envío de mails y anda bárbaro.
    Ahora intento con el que envía adjunto, podrías ver el link que no se puede descargar?
    Lo intenté armamr pero no entiendo el js, no me funciona y no sé si es por eso.
    Gracias!

  8. Imagen de Carlos el dijo:

    Emilio muy buen codigo la verdad que funciona muy bien solo tengo varias inquietudes que quizas tengan una solución:
    1) El código trabaja muy bien en un hosting que tiene plataforma linux con cpanel pero no funciona en un servidor windows con plesk paralles.
    2) Definitivamente no funciona en safari, ya lo probé en firefox, chrome y explores y todo marcha bien en estos

    Agradezco si hay alguna solución para estos dos inconvenientes, faltara alguna línea de código, la versión del pHp, aprecio cualquier ayuda

    • Imagen de Emilio Cobos Álvarez el dijo:

      Acabo de probar en safari para windows (Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2) y funciona perfectamente… ¿Me puedes dar más detalles?

      Sobre windows, testearé esta tarde, pero no debería de haber ningún problema, salvo la versión de php, o que no tenga las funciones de json_decode/json_encode

      • Imagen de carlos el dijo:

        Emilio gracias por la respuesta, cuando me refiero que el formulario no funciono en safari no es en si la parte de diseño, css, js, html etc. sino a la parte dinámica es decir el PHP, envio un el formulario y me dice que llene los campos (nombre, email, mesaje etc..) cuando en realidad estan llenos con la info y correctamente esto, solo pasa en safari, respecto al servidor windows igual es la parte dinámica el formulario se envía pero nunca llega caso contrario en un server linux que si funciona bien es decir todo lo que se envia llega y con adjuntos, ¿esta función que me permite hacer: json_decode/json_encode?

      • Imagen de Emilio Cobos Álvarez el dijo:

        A eso me refería yo también ;) Conseguí enviar un correo con imagen adjunta sin problemas.

        Sobre windows: Si se envía (el form muestra un mensaje de “Se ha enviado correctamente[…]”) pero el correo no llega, no es culpa del formulario, sino de:
        – El server que envía el correo (no lo envía correctamente, y el servidor de destino lo rechaza)
        – El server que recibe el correo (lo filtra, ya sea por la Ip del servidor remitente, por la dirección de correo que introduces en el formulario, por que considera al contenido spam…).

        Prueba a cambiar el destinatario a ver si así llega en windows. Sobre safari, yo solo te puedo decir que a mi me funciona sin problemas, mándame una captura del log de la consola por si hubiera algún error, pero si te manda los mensajes no debería.

  9. Imagen de Verónica RomeroVerónica Romero el dijo:

    Hola Emilio
    EStoy intentando usar tu codigo pero me sigue saliendo el mismo error. Me dice que el mensaje no ha sido enviado.. nose cual es el problema. Puedes ayudarme?

    Gracias!!!

      • Imagen de roderickroderick el dijo:

        que tal sr!.. excelente trabajo.. ahora una duda… necesito enviar 2 adjuntos en 2 diferentes campos.. trate repitiendo los bloques para cada uno, veo que para el adjunto label for=”asunto” y luego llamas al “adjunto” del campo
        Ahora.. en el messagge.php no se declara el “adjunto como variable principal sino hasta cuando llamas
        // Variables para manejar el adjunto… he hecho estos bloques en dos pasos para adjunto y adjunto2.. pero solo recibo el primero… he llamado desde el campo igual asunto2 con adjunto2 pero tampoco… hara falta algo especial para que lleguen los 2 en el mismo correo pero enviados desde 2 campos diferentes??…
        graciasss de antemano!!

  10. Imagen de oscaroscar el dijo:

    Hola amigo, perdona mi ignorancia pero soy nuevo en esto de PHP y demás. Me gustaría saber si los archivos descargados son listos para usar o hay que añadirle todas esas líneas de comandos que veo en tu web.
    Gracias

  11. Imagen de Kiara LabajosKiara Labajos el dijo:

    Que tal, he usado tu código y le doy enviar pero no envía nada, lo he probado en el hosting de la empresa a quien estoy diseñando la pagina web, pero no me funciona hace ratos llevo esperando que llegue el correo con el archivo adjunto o demora en llegar?

  12. Imagen de Alberto Escamilla el dijo:

    Hola Emilio, gran trabajo!!
    Realicé algunos cambios, sobre todo en la cantidad de campos y la css para acoplar al diseño que estoy realizando; en la PC de momento me ha funcionado bien, pero a la hora de probar desde la tableta, me envía u 500 internal server error. He probado el demo y funciona correctamente desde la tableta, podrías ayudarme?
    Saludos

    • Imagen de Emilio Cobos Álvarez el dijo:

      Es posible que sea porque estás intentando detectar el user-agent de alguna manera? Si te da un error al entrar desde la tablet y no desde el PC es lo primero que se me pasa…

      Lo primero a hacer suele ser mirar el error_log del servidor, ya que si es un error de PHP te indica la línea exacta. Si no, es un problema de configuración del servidor (.htaccess/httpd.conf).

      Si quieres ponme más detalles aquí o en mi formulario :)

  13. Imagen de carloscarlos el dijo:

    Excelente ya me funciona muy bien el formulario solo que no deseo subir cualquier tipo de archivo los que me interesa son solo PDF y doc que debe hacer para que solo admita estos dos formatos y con limite de peso de archivos

  14. Imagen de carloscarlos el dijo:

    Hola disculpa tanta consulta, me tiene loco esto: resulta que decidí cambiar esta función $titulo = “$area – $nombre – $pais”; para que en el subject me aparezcan esos datos y funciona perfectamente pero a nivel gmail, yahoo etc.. no llega con caracteres extraños pero en un servicio de email que se llama lotus notes de IBM, para dar un ejemplo me llega el subject de esta forma: Comercial – Juan Pérez – Costa Rica cuan en realidad se debe ver asi con la tilde Juan Pérez hecho de todo modicar los UTF etc… no se que mas hacer

  15. Imagen de anahianahi el dijo:

    hola. Gracias por tu aporte! tengo un problema, cuando quiero agregar mas campos no los recibo en el correo.
    Hice los cambios correspondientes en:
    index.php
    script.js
    hay que agregar los campos en algun otro lugar? o no es solo agregar campos y es algo que estoy obviando?

    graciaas

  16. Imagen de RenzoRenzo el dijo:

    Yo lo instale en un Server Apache dentro de un template de WordPress con mucha maña y envia y recibo los ajduntos y funciona el Form, EXCELLENTE !!

    Lo he integrado y he avanzado bastante, lo que me faltaria es como alguien dijo “que pueda subir varios archivos a la vez” y que muestre la carga con aquello de “progress” que mencionaste… seria fabuloso!

  17. Imagen de George Zapata el dijo:

    Buenos días
    Primero agradecerte por compartir el conocimiento.

    Segundo estoy algo engalletado con el funcionamiento del código, si puedes por favor hacer un video en YouTube explicando el mismo se lo agradecería.

    Ya que no sé cómo enviar un archivo adjunto desde un formulario con Ajax.

    Saludos y Dios lo bendiga.

  18. Imagen de Gustavo el dijo:

    Muy buena información, busque por todas partes y este es el mas completo y sencillo de editar.

    Una pregunta, se puede verificar que el archivo no pese mas de 2MB y que sea de tipo pdf?
    Y otra la validación puede hacerse antes de que suba el archivo?

    Bueno gracias por compartir,
    esta muy bueno tu sitio y muy completo.

    Saludos

  19. Imagen de juanjuan el dijo:

    hola! gracias por el contenido!
    tengo dudas a la hora de volcarlo en mi sitio:
    el index de mi sitio tiene que pasar a ser php? o lo que tiene que pasar a ser .php es la página que tiene incluido el formulario?

    en cualquier caso, cómo hago para colocar el código del formulario respetando el resto del diseño de mi sitio?
    digo, si pego TODO arriba de DOCTYPE no podré volcar el formulario en el espacio que yo quiero, entonces en qué parte del código hago la división?

    perdón si son preguntas muy estúpidas y/o erradas pero soy nuevo en esto!

    gracias!!!

    • Imagen de Emilio Cobos Álvarez el dijo:

      Este formulario está hecho para ser una página independiente en php. Se puede integrar con cualquier otro sistema, siempre que el archivo de destino de la solicitud sea el php… No obstante no te puedo ayudar si no me informas un poco más (si quieres cuéntame qué es lo que quieres hacer y mándame una url o un poco de código :P)

  20. Imagen de eric pedrazaeric pedraza el dijo:

    Hola mi buen, he probado tu código me funciona bien solo que no me manda el adjunto, cual crees que sea el problema? igual lo he hecho de otra forma pero no me manda el adjunto, encontre tu código con la esperanza de que lo mandara, ya vi que a muchos si les manda el archivo,..

  21. Imagen de RubenRuben el dijo:

    Hola, muchas gracias por el código, me sirve a la perfección para lo que estaba buscando.

    Ahora bien, tengo un gran problema, y es que no me envía el email, me sale imagen como Enviado pero nunca llega. Pensaba que el motivo era que al trasladar el código a mi web habia olvidado algun punto clave pero, probando el formulario directamente de tu descarga cambiando el email tampoco consigo hacerlo funcionar.

    He mirado y mirado el código para ver si con mi casi inexistente conocimiento php lo resolvía pero al final me he decidido por preguntar.

    Un saludo!

    • Imagen de Emilio Cobos Álvarez el dijo:

      Vale, puede que pasen varias cosas:

      La primera es que el mail se envíe correctamente, pero lo maten los filtros de spam. Esto es muy frecuente sobre todo en hostings gratuitos o con ips compartidas.

      Otra es que el mail esté capado en tu hosting, que es también bastante probable.

      Finalmente la otra solución que se me ocurre es que no hubieras cambiado bien el correo de destino, pero parece que no es el caso así que… Te va a tocar contactar con el hosting…

  22. Imagen de AlejandroAlejandro el dijo:

    Hola. Excelente código y tutorial. Me sirvió mucho.

    Solo necesito hacer un cambio que no logro realizarlo bien.
    Necesito poder adjuntar 5 archivos. Como puedo hacerlo?

    Muchas gracias. Saludos

    • Imagen de Emilio Cobos Álvarez el dijo:

      Tendrás que hacer un bucle sobre $_FILES[‘loquesea’]. Para que te deje adjuntar múltiples archivos el atributo name del <input type="file"> tiene que estar acabado en [], y ese mismo elemento tiene que tener el atributo multiple.

  23. Imagen de armando verdearmando verde el dijo:

    Saludos como puede redireccionar a mi pagina de inicio(index.html) despues de este mensaje: $mensaje_correcto = “El mensaje se envió correctamente, intentaré responderte lo antes posible”;. con ayuda de un boton de aceptar.

  24. Imagen de rafael salazar el dijo:

    Hola Emilio gracias por el codigo, fijate que ya lo instale en mi web pero a la hora de enviar solo aparece la leyenda (El mensaje se envió correctamente, intentaré responderte lo antes posible) y nunca llega ya le modifique el archivo index.php donde dice (// Aquí el email al que queres recibir el correo
    $receptor = “zwekitoe@hotmail.com”;) le puse la direccion de mi email que es hotmail y nunca me llegan :( me podrias apoyar? :) gracias

  25. Imagen de Esteban el dijo:

    Buenas, Emilio,
    Excelente post y veo que funciona muy bien a mucha gente. Tengo un problema que, cuando intento enviar, no pasa nada, tengo un hosting pago, y no sé si tendré algún problema adicional que no estoy viendo. Le he cambiado el nombre al archivo index.php pero no creo que sea por ahí donde va la cosa. Me gustaría que pudieras ver éste caso.
    http://www.unibelia.es./formulario.php

    muchas gracias!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Puedes usar las siguientes etiquetas y atributos HTML:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre data-language=""> <ul> <ol> <li>
Para poner código usa <pre data-language="[lenguaje]"><code>[código]</code></pre>, y no olvides escapar el HTMl.