Enviar emails con Laravel
Me he propuesto aprender un framework de PHP. El objetivo realizar algunos de los proyectos que tengo en mente, ya que wordpress en ocasiones se me queda pequeño. He intentado utilizar OctoberCms, que aunque no es un framework, esta basado en uno muy de moda y al parecer de fácil uso: Laravel. Desafortunadamente he chocado contra un muro de ignorancia por mi parte. El desconocimiento de conceptos avanzados de programación orientada a objetos y la poca documentación para dummies existente sobre OctoberCms, me ha conducido a dejar, por el momento, esta fantástica herramienta para crear sitios y volcarme al estudio de Laravel, que si cuenta con montones de documentación y una amplia comunidad de desarrolladores por detrás que dan lo mejor de si para difundir el uso de este framework.
Es cierto que existen tutoriales para realizar casi todo lo que uno se proponga. Por lo tanto no voy a inventar la rueda y pretender que lo que diga aquí no este explicado en multitud de webs. Ese no es mi objetivo, sino el de recopilar una serie de ejemplos sobre como realizar determinadas tareas y tenerlas agrupadas en un mismo lugar. De esa manera contaré con ellas para cuando las necesite y estarán disponibles para todos aquellos interesados que por alguna razón aterricen en este espacio.
Voy a comenzar en el mismo punto donde me di cuenta que necesitaba ayuda. Estaba intentando enviar un simple email con OctoberCms y fui incapaz de hacerlo. Este tutorial es una guía de como hacerlo con Laravel.
Esta realizado siguiendo esta magnifica guía de vídeo tutoriales, publicada por Manuel J. Dávila que me ha servido para ir aprendiendo. Los mencionados vídeos están geniales pero hecho de menos un lugar donde poder consultar los códigos que ha ido mostrando el autor. Esos códigos por lo tanto los veréis aquí, exactamente los mismos, con alguna que otra pequeña modificación de mi parte. Como dije anteriormente para tenerlos a mano y para uso de cualquiera que los necesite. Sin más dilatación vamos a comenzar.
Voy a iniciar en el tutorial número 7, por las razones que ya exprese, pero sino conocen mucho de este framework os aconsejo que veáis los vídeos tutoriales desde el inicio.
- Abrir el fichero mail.php que se encuentra en: app/config
El autor realiza los cambios sobre este mismo fichero pero como estoy trabajando en un entorno local lo que hice fue copiar el mismo para la carpera app/config/local. - Configurarlo de la siguiente manera:
'host' => 'smtp.gmail.com', 'from' => array('address' => '[email protected]', 'name' => 'Nombre'), 'username' => 'tuusername', 'password' => 'tucontraseña',
Aquí quiero detenerme un segundo. Yo tengo configurado como aplicación de SMTP el Exim. Los datos que agreguemos en el fichero mail.php deben ser los mismos que están en la configuración del Exim, tal y como se muestra en este enlace: http://dajul.com/2009/06/08/configurar-exim4-con-gmail-o-google-apps/. De no hacerlo así a mi, al menos, no me enviaba los emails.
- Crear una vista para enviar el formulario que se llamará contacto.blade.php que esta en app/views. Siguiendo el tutorial la misma estaría dentro de una carpeta llamada HomeController, donde estarían esta vista y otras que se han ido creando a lo largo del tutorial:
- Crear el formulario:
{{ Form::open(array ( 'action' => 'HomeController@contacto', 'method' => 'POST', 'role' => 'form', )) }} //campos del formulario aquí.... {{ Form::close() }}
Toda la documentación al respecto la podéis ver aquí: http://laravel.com/api/4.2/
- Agregar los campos del formulario:
<div class="form-group"> {{ Form::label('name', 'Nombre:') }} {{ Form::input('text', 'name', null, array('class' => 'form-control')) }} </div> <div class="form-group"> {{ Form::label('email', 'Email:') }} {{ Form::input('email', 'email', null, array('class' => 'form-control')) }} </div> <div class="form-group"> {{ Form::label('subject', 'Asunto:') }} {{ Form::input('text', 'subject', null, array('class' => 'form-control')) }} </div> <div class="form-group"> {{ Form::label('msg', 'Mensaje:') }} {{ Form::textarea('msg', null, array('class' => 'form-control')) }} </div> {{ Form::input('hidden', 'contacto') }} {{ Form::input('submit', null, 'Enviar', array('class' => 'btn btn-primary')) }}
En el vídeo solo agregan los label: {{ Form::label(‘Nombre:’) }}. Yo he agregado el atributo for al label para cuando se de clic sobre el nombre se seleccione el campo, quedando como lo veis arriba.
- En este momento si visualizamos nuestro formulario debería verse así:
Comento que estoy utilizando el framework Bootstrap para dar forma e interactividad a los ejemplos.
- Crear la plantilla del email. Las mismas están ubicadas en app/views/emails. Esta carpeta se instala por defecto en Laravel. Creamos un fichero nuevo con el nombre contacto.blade.php y le introducimos el código:
<html> <head></head> <body> <p>Nombre del contacto: {{$name}}</p> <p>Email del contacto: {{$email}}</p> <p>Asunto: {{$subject}}</p> <p>Mensaje:</p> {{$msg}} </body> </html>
- Ahora le toca el turno al controlador. Esta ubicado en app/controllers y se nombra: HomeController.php
- Contiene lo siguiente:
public function contacto() { $mensaje = null; if (isset($_POST['contacto'])) { $data = array ( 'name' => Input::get('name'), 'email' => Input::get('email'), 'subject' => Input::get('subject'), 'msg' => Input::get('msg') ); $fromEmail = '[email protected]'; $fromName = 'Nombre'; Mail::send('emails.contacto', $data, function($message) use ($fromName, $fromEmail) { $message->to($fromEmail, $fromName); $message->from($fromEmail, $fromName); $message->subject('Nuevo mensaje de contacto'); }); $mensaje = '<div class="text-info">Mensaje enviado con éxito</div>'; } return View::make('HomeController.contacto', array('mensaje' => $mensaje)); }
- En la vista agregar la variable mensaje antes del inicio del formulario:
{{$mensaje}} - Antes de probar el envío debemos agregar la siguiente línea en el fichero route.php que se encuentra en: app/
Route::post('/contacto', array('as'=>'contacto', 'uses'=>'HomeController@contacto'));
En el tutorial esto no se explica, cuando lo probé me dio un error y después de buscar di con la solución. La vista contacto esta ruteada como Get, es decir, todos los parámetros se están pasando por este método y con el envío del formulario se hacen por Post, por eso da error. La solución es agregarle también a la vista ese método de envío y problema resuelto. Si alguien puede explicarlo mejor genial.
- Probar el envío y todo debe funcionar correctamente.
Ahora vamos a validar el formulario. Para ello tenemos que ir modificando el controlador y la vista. La documentación de este apartado la podéis consultar en: http://laravel.com/docs/4.2/validation
- Ir al controlador HomeController y en la acción contacto, agregar encima de la variable $data lo siguiente:
$rules = array ( 'name' => 'required|regex:/^[a-zA-Z\s]*$/|min:3|max:80', 'email' => 'required|email|between:3,80', 'subject' => 'required|regex:/^[a-zA-Z\s0-9]+$/|min:3|max:80', 'msg' => 'required|between:3,500', );
- Crear la instancia de la clase Validator:
$validator = Validator::make(Input::All(), $rules);
- Crear una condicional para que si pasa la validación envié los datos o de lo contrario devuelva a la vista los errores. En estos momentos nuestra acción debería estar así:
public function contacto() { $mensaje = null; if (isset($_POST['contacto'])) /* Campo oculto contacto */ { $rules = array ( 'name' => 'required|regex:/^[a-zA-Z\s]*$/|min:3|max:80', 'email' => 'required|email|between:3,80', 'subject' => 'required|regex:/^[a-zA-Z\s0-9]+$/|min:3|max:80', 'msg' => 'required|between:3,500', ); $validator = Validator::make(Input::All(), $rules, $messages); if ($validator->passes()) { $data = array ( 'name' => Input::get('name'), 'email' => Input::get('email'), 'subject' => Input::get('subject'), 'msg' => Input::get('msg') ); $fromEmail = '[email protected]'; $fromName = 'escael'; Mail::send('emails.contacto', $data, function($message) use ($fromName, $fromEmail) { $message->to($fromEmail, $fromName); $message->from($fromEmail, $fromName); $message->subject('Nuevo mensaje de contacto'); }); $mensaje = 'Mensaje enviado con éxito'; } else { return Redirect::back()->withInput()->withErrors($validator); } } return View::make('HomeController.contacto', array('mensaje' => $mensaje)); }
- Ir a la vista contacto y cambiar donde esta:
{{ Form::input('text', 'name', null, array('class' => 'form-control')) }}
por:
{{ Form::input('text', 'name', Input::old('name'), array('class' => 'form-control')) }}
- Incluir los errores en la vista:
<div class="bg-danger">{{ $errors->first('name') }}</div>
Lo mismo en todos los campos
- Probar el formulario. Devuelve los errores en inglés, para cambiarlos a español, realizar los siguientes cambios en el controlador:
$messages = array ( 'name.required' => 'El campo es requerido', 'name.regex' => 'Solo letras y espacios', 'name.min' => 'Mínimo de 3 caracteres', 'name.max' => 'Máximo de 80 caracteres', 'email.required' => 'El campo es requerido', 'email.email' => 'El formato de email es incorrecto', 'email.between' => 'Entre 30 y 80 caracteres', 'subject.required' => 'El campo es requerido', 'subject.regex' => 'Solo letras y números', 'subject.min' => 'Mínimo de 3 caracteres', 'subject.max' => 'Máximo de 80 caracteres', 'msg.required' => 'El campo es requerido', 'msg.between' => 'Entre 3 y 500 caracteres', );
y en la instancia de la clase Validator agregar lo siguiente:
$validator = Validator::make(Input::All(), $rules, $messages);
- Probar el formulario. Devuelve los errores en español.
En este punto ya nuestro formulario debería funcionar correctamente pero para mejorarlo aún faltan un par de cosillas. Por un lado hacer el envío por Ajax, ya que representa una mejora en la interfaz del usuario y agregarle un captcha para evitar que los spiders que campean por la web se den gusto enviando nuestro formulario con basuras.
- Incluir id al formulario y a los campos de error, además de cambiar el input submit por un botón:
{{ Form::open(array ( 'action' => 'HomeController@contacto', 'method' => 'POST', 'role' => 'form', 'id' => 'form' )) }} <div class="bg-danger" id="_name">{{ $errors->first('name') }}</div> {{ Form::input('button', null, 'Enviar', array('class' => 'btn btn-primary', 'id' => 'btn')) }}
- Agregar en los meta el siguiente código:
$(function () { function send_ajax() { $.ajax({ url: 'contacto', type: 'POST', data: $("#form").serialize(), success: function(datos) { $("#_email, #_name, #_subject, #_msg").text(''); if (datos.success == false) { $.each(datos.errors, function(index, value) { $("#_"+index).text(value); }); } else { document.getElementById('form').reset(); $("#mensaje").text('Mensaje enviado con éxito'); } } }); } $("#btn").on("click", function() { send_ajax(); }); });
En el div donde se muestra el mensaje de enviado agregarle el siguiente id:
<div id="mensaje" class="bg-info">{{$mensaje}}</div>
Que es donde se va a pintar el mensaje de enviado.
- Ir al controlador y donde se capturan los errores agregar el siguiente código:
if (Request::ajax()) { return Response::json ([ 'success' => false, 'errors' => $validator->getMessageBag()->toArray() ]); } else { return Redirect::back()->withInput()->withErrors($validator); }
Incluir un captcha de google
- Instalar el captcha desde el repositorio: https://github.com/greggilbert/recaptcha
- Agregar al composer.json la siguiente línea:
"greggilbert/recaptcha": "dev-master"
- Ir al terminal y teclear la siguiente sentencia:
composer composer update --dev
A continuación:
- Ir la carpeta local ubicada dentro de config, para utilizar el de nuestro entorno local, en el fichero app.php donde tenemos los provedores agregamos lo siguiente:
'Greggilbert\Recaptcha\RecaptchaServiceProvider'
Vamos nuevamente al terminal y ejecutamos
php artisan config:publish greggilbert/recaptcha
Abrimos el fichero app/config/packages/greggilbert/recaptcha/config.php y veremos que necesitamos incluir dos claves, una publica y otra privada. Para ellos vamos a Google, damos click en GetRecaptcha, nos registramos, agregamos nuestro entorno local en la url, si estamos trabajando sobre localhost o 127.0.0.1 nos sirve cualquier sitio nuestro. Yo tuve que poner mi virtualhost para que me funcionara. Una vez hecho esto nos da nuestras claves publicas y privadas para agregarlas al config.php del recaptcha.
- Ir la carpeta de lenguaje ubicada en app/lang/en/ y en el fichero validation.php, agregar al final la siguiente línea:
"recaptcha" => 'The :attribute field is not correct.',
- Ir la vista de contacto y al final del textarea de mensaje agregar:
{{ Form::captcha() }} <div class="bg-danger" id="_recaptcha_response_field">{{ $errors->first('recaptcha_response_field') }}</div>
En el script del cabezal agregar el identificador en:
$("#_email, #_name, #_subject, #_msg, #_recaptcha_response_field").text('');
- En el controlador agregar la reglas de validación y los mensajes en español:
'recaptcha_response_field' => 'required|recaptcha',
'recaptcha_response_field.required' => 'El campo captcha es requerido', 'recaptcha_response_field.recaptcha' => 'Captcha incorrecto',
Para ir terminando esta interminable entrada voy a dejar los códigos completos de la vista y del controlador:
Vista:
<div id="mensaje" class="bg-info">{{$mensaje}}</div> {{ Form::open(array ( 'action' => 'HomeController@contacto', 'method' => 'POST', 'role' => 'form', 'id' => 'form' )) }} <div class="form-group"> {{ Form::label('name', 'Nombre:') }} {{ Form::input('text', 'name', Input::old('name'), array('class' => 'form-control')) }} <div class="bg-danger" id="_name">{{ $errors->first('name') }}</div> </div> <div class="form-group"> {{ Form::label('email', 'Email:') }} {{ Form::input('email', 'email', Input::old('email'), array('class' => 'form-control')) }} <div class="bg-danger" id="_email">{{ $errors->first('email') }}</div> </div> <div class="form-group"> {{ Form::label('subject', 'Asunto:') }} {{ Form::input('text', 'subject', Input::old('subject'), array('class' => 'form-control')) }} <div class="bg-danger" id="_subject">{{ $errors->first('subject') }}</div> </div> <div class="form-group"> {{ Form::label('msg', 'Mensaje:') }} {{ Form::textarea('msg', Input::old('msg'), array('class' => 'form-control')) }} <div class="bg-danger" id="_msg">{{ $errors->first('msg') }}</div> </div> {{ Form::captcha() }} <div class="bg-danger" id="_recaptcha_response_field">{{ $errors->first('recaptcha_response_field') }}</div> {{ Form::input('hidden', 'contacto') }} {{ Form::input('button', null, 'Enviar', array('class' => 'btn btn-primary', 'id' => 'btn')) }} {{ Form::close() }}
Script JS de la vista:
$(function () { function send_ajax() { $.ajax({ url: 'contacto', type: 'POST', data: $("#form").serialize(), success: function(datos) { $("#_email, #_name, #_subject, #_msg, #_recaptcha_response_field").text(''); if (datos.success == false) { $.each(datos.errors, function(index, value) { $("#_"+index).text(value); }); } else { document.getElementById('form').reset(); $("#mensaje").text('Mensaje enviado con éxito'); } } }); } $("#btn").on("click", function() { send_ajax(); }); });
Controlador:
public function contacto() { $mensaje = null; if (isset($_POST['contacto'])) /* Campo oculto contacto */ { $rules = array ( 'name' => 'required|regex:/^[a-zA-Z\s]*$/|min:3|max:80', 'email' => 'required|email|between:3,80', 'subject' => 'required|regex:/^[a-zA-Z\s0-9]+$/|min:3|max:80', 'msg' => 'required|between:3,500', 'recaptcha_response_field' => 'required|recaptcha', ); $messages = array ( 'name.required' => 'El campo es requerido', 'name.regex' => 'Solo letras y espacios', 'name.min' => 'Mínimo de 3 caracteres', 'name.max' => 'Máximo de 80 caracteres', 'email.required' => 'El campo es requerido', 'email.email' => 'El formato de email es incorrecto', 'email.between' => 'Entre 30 y 80 caracteres', 'subject.required' => 'El campo es requerido', 'subject.regex' => 'Solo letras y números', 'subject.min' => 'Mínimo de 3 caracteres', 'subject.max' => 'Máximo de 80 caracteres', 'msg.required' => 'El campo es requerido', 'msg.between' => 'Entre 3 y 500 caracteres', 'recaptcha_response_field.required' => 'El campo captcha es requerido', 'recaptcha_response_field.recaptcha' => 'Captcha incorrecto', ); $validator = Validator::make(Input::All(), $rules, $messages); if ($validator->passes()) { $data = array ( 'name' => Input::get('name'), 'email' => Input::get('email'), 'subject' => Input::get('subject'), 'msg' => Input::get('msg') ); $fromEmail = '[email protected]'; $fromName = 'Nombre'; Mail::send('emails.contacto', $data, function($message) use ($fromName, $fromEmail) { $message->to($fromEmail, $fromName); $message->from($fromEmail, $fromName); $message->subject('Nuevo mensaje de contacto'); }); $mensaje = 'Mensaje enviado con éxito'; } else { if (Request::ajax()) { return Response::json ([ 'success' => false, 'errors' => $validator->getMessageBag()->toArray() ]); } else { return Redirect::back()->withInput()->withErrors($validator); } } } return View::make('HomeController.contacto', array('mensaje' => $mensaje)); }
Si tenéis alguna duda de como funciona algo dentro del código os aconsejo que veáis los tutoriales, ahí todo esta explicado al detalle.
Laravel \\ Ajax, Contact Form, jQuery, Laravel, PHP, Recaptcha \\ No hay comentarios