Formulario de contacto simple con validación de campos

23 ago 2016, 17:16:45
Vistas: 14.8K
Votos: 2

Estoy intentando crear un formulario de contacto muy simple que pueda mostrar mediante un shortcode. Tengo el siguiente código, pero no parece validar correctamente e incluso permite enviar el formulario sin ingresar ningún dato. ¿Alguien podría explicarme cómo validar el formulario? También me gustaría agregar una trampa honeypot ya que son mejores que los captchas en mi opinión, pero soy bastante nuevo en PHP. Cualquier ayuda sería genial, gracias.

    function html_form_code() {
    echo '<form action="' . esc_url( $_SERVER['REQUEST_URI'] ) . '" method="post">';
    echo '<p>';
    echo 'Tu nombre (requerido) <br />';
    echo '<input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="' . ( isset( $_POST["cf-name"] ) ? esc_attr( $_POST["cf-name"] ) : '' ) . '" size="40" />';
    echo '</p>';
    echo '<p>';
    echo 'Tu email (requerido) <br />';
    echo '<input type="email" name="cf-email" value="' . ( isset( $_POST["cf-email"] ) ? esc_attr( $_POST["cf-email"] ) : '' ) . '" size="40" />';
    echo '</p>';
    echo '<p>';
    echo 'Tu mensaje (requerido) <br />';
    echo '<textarea rows="10" cols="35" name="cf-message">' . ( isset( $_POST["cf-message"] ) ? esc_attr( $_POST["cf-message"] ) : '' ) . '</textarea>';
    echo '</p>';
    echo '<input type="text" name="content" id="content" value="" class="hpot" />';
    echo '<p><input type="submit" name="cf-submitted" value="Enviar"/></p>';
    echo '</form>';
}

function deliver_mail() {

  $errors = new WP_Error();
if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
  $errors->add( 'cheater', 'Lo siento, este campo no debe estar lleno. ¿Intentas hacer trampa?' );
}
if ( isset( $_POST[ 'cf-name' ] ) && $_POST[ 'cf-name' ] == '' ) {
  $errors->add('error', 'Por favor ingresa un nombre válido.' );
}

if ( isset( $_POST[ 'cf-email' ] ) && $_POST[ 'cf-email' ] == '' ) {
  $errors->add('error', 'Por favor ingresa un email válido.' );
}

if ( isset( $_POST[ 'cf-message' ] ) && $_POST[ 'cf-message' ] == '' ) {
  $errors->add('error', 'Por favor ingresa un mensaje válido.' );
}

if ( empty( $errors->errors ) ){
  deliver_mail();
}
else {
  echo 'Por favor completa los campos requeridos';
}


    // si se hace clic en el botón de enviar, envía el correo
    if ( isset( $_POST['cf-submitted'] ) ) {

        // sanitizar valores del formulario
        $name    = sanitize_text_field( $_POST["cf-name"] );
        $email   = sanitize_email( $_POST["cf-email"] );
        $message = esc_textarea( $_POST["cf-message"] );

        // obtener la dirección de correo del administrador
        $to = get_option( 'admin_email' );

        $headers = "From: $name <$email>" . "\r\n";

        // Si el correo se procesó para enviar, muestra un mensaje de éxito
        if ( wp_mail( $to, $message, $headers ) ) {
            echo '<div class=cf-success>';
            echo '<p>Gracias por contactarnos '. $name .', un miembro de nuestro equipo se pondrá en contacto contigo pronto.</p>';
            echo '</div>';
        } else {
            echo '<div class=cf-error>';
            echo '<p>Ocurrió un error inesperado</p>';
            echo '</div>';
        }
    }
}

function cf_contact_form() {
    ob_start();
    deliver_mail();
    html_form_code();

    return ob_get_clean();
}

add_shortcode( 'contact_form', 'cf_contact_form' );

¿Sería como lo anterior? Gracias.

También recibo este error al intentar ejecutar las validaciones de campo:

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /Applications/MAMP/htdocs/centenary-framework/wp-content/themes/cent_framework/assets/inc/core/contact-form.php on line 147

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /Applications/MAMP/htdocs/centenary-framework/wp-includes/load.php on line 671

No estoy muy seguro de lo que significa esto. =(

EDITADO

Estos problemas ya han sido solucionados. Estoy pegando el código funcional a continuación con la esperanza de que ayude a alguien en el futuro. ¡Muchas gracias a todos los que ayudaron!

// Marcado del formulario
 function html_form_code()
 {
     ?>

 <form action="<?php esc_url($_SERVER['REQUEST_URI']);
     ?>" method="post">
   <p>Tu nombre (requerido)<br />
     <input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="<?php isset($_POST['cf-name']) ? esc_attr($_POST['cf-name']) : '';
     ?>" size="40" />
   </p>
   <p>Tu email (requerido)<br />
     <input type="email" name="cf-email" value="<?php isset($_POST['cf-email']) ? esc_attr($_POST['cf-email']) : '';
     ?>" size="40" />
   </p>
   <p>Tu mensaje (requerido)<br />
     <textarea rows="10" cols="35" name="cf-message"><?php isset($_POST['cf-message']) ? esc_attr($_POST['cf-message']) : '';
     ?></textarea>
   </p>
   <p><input type="submit" name="cf-submitted" value="Enviar"/></p>
 </form>

 <?php

 }

// Validación del formulario
 function my_validate_form()
 {
     $errors = new WP_Error();

     if (isset($_POST[ 'content' ]) && $_POST[ 'content' ] !== '') {
         $errors->add('cheater', 'Lo siento, este campo no debe estar lleno. ¿Intentas hacer trampa?');
     }

     if (isset($_POST[ 'cf-name' ]) && $_POST[ 'cf-name' ] == '') {
         $errors->add('name_error', 'Por favor ingresa un nombre válido.');
     }

     if (isset($_POST[ 'cf-email' ]) && $_POST[ 'cf-email' ] == '') {
         $errors->add('email_error', 'Por favor ingresa un email válido.');
     }

     if (isset($_POST[ 'cf-message' ]) && $_POST[ 'cf-message' ] == '') {
         $errors->add('message_error', 'Por favor ingresa un mensaje válido.');
     }

     return $errors;
 }

// Envío del formulario
 function deliver_mail($args = array())
 {

  // Este array $default es una forma de inicializar algunos valores predeterminados que serán sobrescritos por nuestro array $args.
  // Podríamos agregar más claves según sea necesario y es una buena forma de ver qué parámetros estamos usando en nuestra función.
  // Solo será sobrescrito con los valores de nuestro array $args si las claves están presentes en $args.
  // Esto usa la función wp_parse_args() de WordPress.
  $defaults = array(
    'name' => '',
    'email' => '',
    'message' => '',
    'to' => get_option('admin_email'), // obtener el email del administrador
  );

     $args = wp_parse_args($args, $defaults);

     $headers = "From: {$args['name']}  <{$args['email']}>"."\r\n";

  // Enviar email devuelve true en éxito, false en caso contrario
  if (wp_mail($args['to'], $args['message'], $headers)) {
      return;
  } else {
      return false;
  }
 }

// Sanitización del formulario
function my_sanitize_field($input)
{
    return trim(stripslashes(sanitize_text_field($input)));
}

// Mensaje de éxito del formulario
function my_form_message()
{
    global $errors;
    if (is_wp_error($errors) && empty($errors->errors)) {
        echo '<div class="cf-success">';
        echo '<p>Gracias por contactarnos '.$_POST['cf-name'].', un miembro de nuestro equipo se pondrá en contacto contigo pronto.</p>';
        echo '</div>';

    // Vaciar $_POST porque ya enviamos el email
    $_POST = '';
    } else {
        if (is_wp_error($errors) && !empty($errors->errors)) {
            $error_messages = $errors->get_error_messages();
            foreach ($error_messages as $k => $message) {
                echo '<div class="cf-error '.$k.'">';
                echo '<p>'.$message.'</p>';
                echo '</div>';
            }
        }
    }
}

// Shortcode del formulario
add_shortcode('contact_form', 'cf_contact_form');
function cf_contact_form()
{
    ob_start();

    my_form_message();
    html_form_code();

    return ob_get_clean();
}

// Validación de errores
add_action('init', 'my_cf_form');
function my_cf_form()
{
    if (isset($_POST['cf-submitted'])) {
        global $errors;
        $errors = my_validate_form();
        if (empty($errors->errors)) {
            $args = array(
         'name' => my_sanitize_field($_POST['cf-name']),
         'email' => my_sanitize_field($_POST['cf-email']),
         'message' => my_sanitize_field($_POST['cf-message']),
       );
            deliver_mail($args);
        } else {
            return $errors;
        }
    }
}
0
Todas las respuestas a la pregunta 2
10

No tienes ningún mecanismo de validación.

Tu lógica debería seguir más o menos estas líneas:

  • Enviar el formulario
  • Verificar los campos enviados ($_POST) contra los valores esperados
  • Si todo está correcto, enviar
  • Si algo no es como se esperaba, registrar el error (puedes usar WP_Error()) y reconstruir el formulario mostrando el mensaje de error (y tal vez rellenando los campos con los valores previos "correctos").

Todo lo que veo aquí es que sanitizas las entradas, pero en realidad no validas si tus entradas tienen los valores que esperas (ej. email válido, teléfono, longitud del nombre, etc.).

Envías tu correo sin importar si los campos tienen los valores esperados. Tu else mostrará un error SÓLO si wp_mail() falla, no si tus campos tienen valores válidos o no.

Para añadir un honeypot, simplemente necesitas agregar un campo oculto en tu formulario que esperas que esté vacío.

Por ejemplo, en tu HTML:

<input type="text" name="content" id="content" value="" class="hpot" />

Luego, cuando validas las entradas del formulario, esperas que ese campo esté vacío.

Usando la clase WP_Error puedes agregar errores al objeto para luego usarlos e informar al usuario o lo que necesites.

$errors = new WP_Error();
if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
  $errors->add( 'cheater', 'Lo siento, este campo no debería estar lleno. ¿Estás intentando hacer trampa?' );
}

Así que el chequeo en PHP anterior es una forma que podrías usar para validar tu formulario. Simplemente agregas algunas sentencias if con los valores esperados de tu formulario (por supuesto esto puede expandirse con funciones que validen tu entrada). Luego, si usas la clase WP_Error, agregando al objeto si se encuentran errores, solo tienes que hacer un chequeo final antes de enviar.

if ( empty( $errors->errors ) ){
  deliver_mail();
}
else {
  // Aquí puedes usar tu variable $_POST para rellenar el formulario con los valores  
  // aceptados del formulario (tendrías que actualizar tu html_form_code() para aceptar un argumento) 
  // o simplemente recargar la página de contacto mostrando un mensaje de error.
}

EDITADO

Ok, aquí hay un ejemplo más completo

CSS

Agrega esto a tu CSS para que el campo no se muestre en el navegador

.hpot {
  display: none;
}

PHP

Aquí hay otra forma de escribir tu función html, es más fácil de leer

function html_form_code() { ?>

<form action="<?php esc_url( $_SERVER['REQUEST_URI'] ); ?>" method="post">
  <p>Tu Nombre (requerido)<br />
    <input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="<?php isset( $_POST["cf-name"] ) ? esc_attr( $_POST["cf-name"] ) : ''; ?>" size="40" />
  </p>
  <p>Tu Email (requerido)<br />
    <input type="email" name="cf-email" value="<?php isset( $_POST["cf-email"] ) ? esc_attr( $_POST["cf-email"] ) : ''; ?>" size="40" />
  </p>
  <p>Tu Mensaje (requerido)<br />
    <textarea rows="10" cols="35" name="cf-message"><?php isset( $_POST["cf-message"] ) ? esc_attr( $_POST["cf-message"] ) : ''; ?></textarea>
  </p>
  <p><input type="submit" name="cf-submitted" value="Enviar"/></p>
</form>

<?php } 

Tu función deliver_mail no debería escuchar $_POST y no debería sanitizar. Como nota adicional, usar el email del usuario como cabecera from podría causar problemas con algunos ISP, porque el email se envía desde tu dominio y ahora un email se envía desde tu dominio pero con un dominio no coincidente en la dirección from (podría verse como spam). Usa una dirección de tu dominio aquí (como no-reply@ejemplo.com) y establece el email del usuario en el cuerpo del mensaje (en el mensaje). También podrías establecerlo como un campo reply-to para conveniencia.

function deliver_mail( $args = array() ) {

  // Este array $default es una forma de inicializar algunos valores por defecto que serán sobrescritos por nuestro array $args.
  // Podríamos agregar más claves según necesitemos y es una buena forma de ver qué parámetros estamos usando en nuestra función.
  // Solo será sobrescrito con los valores de nuestro array $args si las claves están presentes en $args.
  // Esto usa la función wp_parse_args() de WP.
  $defaults = array(
    'name'    => '',
    'email'   => '',
    'message' => '',
    'to'      => get_option( 'admin_email' ), // obtener la dirección de email del administrador
  );

  $args = wp_parse_args( $args, $defaults );

  $headers = "From: {$args['name']} <{$args['email']}>" . "\r\n";

  // Enviar email devuelve true en éxito, false en caso contrario
  if( wp_mail( $args['to'], $args['message'], $headers ) ) {
    return;
  }
  else {
    return false;
  }
}

Tu función de validación

function my_validate_form() {

  $errors = new WP_Error();

  if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
    $errors->add( 'cheater', 'Lo siento, este campo no debería estar lleno. ¿Estás intentando hacer trampa?' );
  }

  if ( isset( $_POST[ 'cf-name' ] ) && $_POST[ 'cf-name' ] == '' ) {
    $errors->add('name_error', 'Por favor ingresa un nombre válido.' );
  }

  if ( isset( $_POST[ 'cf-email' ] ) && $_POST[ 'cf-email' ] == '' ) {
    $errors->add('email_error', 'Por favor ingresa un email válido.' );
  }

  if ( isset( $_POST[ 'cf-message' ] ) && $_POST[ 'cf-message' ] == '' ) {
    $errors->add('message_error', 'Por favor ingresa un mensaje válido.' );
  }

  return $errors;
}

Tu función de sanitización. Aquí hay una función general de sanitización que recorta espacios en blanco y escapa html, pero esto podría ser más complejo dependiendo de los campos de entrada que tengas. Pero creo que para tu propósito es suficiente.

function my_sanitize_field( $input ){

  return trim( stripslashes( sanitize_text_field ( $input ) ) );

}

Mostrando tu mensaje de éxito/error, podrías usar esto para obtener el objeto WP_Error

function my_form_message(){

  global $errors;
  if( is_wp_errors( $errors ) && empty( $errors->errors ) ){

    echo '<div class="cf-success">';
    echo '<p>Gracias por contactarnos '. $_POST['cf-name'] .', un miembro de nuestro equipo se pondrá en contacto contigo pronto.</p>';
    echo '</div>';

    //Vaciar $_POST porque ya enviamos el email
    $_POST = '';

  }
  else {

  if( is_wp_errors( $errors ) && ! empty( $errors->errors ) ){

    $error_messages = $errors->get_error_messages(); 
    foreach( $error_messages as $k => $message ){
        echo '<div class="cf-error ' . $k . '">';
        echo '<p>' . $message . '</p>';
        echo '</div>';

    }

  }

}

Finalmente tu función de shortcode

add_shortcode( 'contact_form', 'cf_contact_form' );
function cf_contact_form() {

  ob_start();

  my_form_message();
  html_form_code();

  return ob_get_clean();
}

Y enganchando a init para escuchar $_POST antes de renderizar nuestro formulario y mostrar los $errors si encontramos alguno o enviar si todo está bien.

add_action( 'init', 'my_cf_form');
function my_cf_form(){

  if( isset( $_POST['cf-submitted'] ) ) {

    global $errors;
    $errors = my_validate_form(); 
    if( empty( $errors->errors ) ){

       $args = array(
         'name'    => my_sanitize_field( $_POST['cf-name'] ),
         'email'   => my_sanitize_field( $_POST['cf-email'] ),
         'message' => my_sanitize_field( $_POST['cf-message'] ),
       );
       deliver_mail( $args );
    }
    else {
      return $errors;
    } 
  }
}

Recuerda siempre prefijar tus funciones para no entrar en conflicto con nombres de funciones de otros plugins.

23 ago 2016 17:53:34
Comentarios

Perdón por ser un idiota, ¿dónde ejecuto mis verificaciones? Gracias

user53340 user53340
23 ago 2016 18:12:03

Si eres nuevo en PHP, no eres un idiota, simplemente tienes que aprender estas cosas :P - En cuanto a dónde hacer tus verificaciones, necesitas modificar tu función deliver_mail(). No escuchas tu $_POST allí. En realidad, tu función debería hacer solo una cosa (o tener solo una lógica), así que si tu función se usa para enviar correos, entonces no verificas el contenido allí (¿tiene sentido?). Podrías verificar tu $_POST justo antes de validar tu formulario. Actualizaré mi respuesta con algo de código.

bynicolas bynicolas
23 ago 2016 18:21:11

He actualizado mi código a como supongo que debería ser, pero supongo que no porque todavía no funciona. lol También recibí un error de memoria cuando envié el formulario y luego actualicé la página nuevamente, así que supongo que no estoy saliendo del código correctamente o algo así?

user53340 user53340
23 ago 2016 18:25:53

Además, cuando la página se actualiza después del envío del formulario, los campos aún están llenos con la información, ¿es correcto? Mi entendimiento era que sanear el formulario debería eliminar las entradas del usuario en los campos. ¿O estoy entendiendo mal el uso de sanitizar?

user53340 user53340
23 ago 2016 18:30:27

Ok, esto terminó siendo una edición más extensa de lo anticipado, pero creo que puede ser una buena referencia para ti. Ten en cuenta que no ha sido probado, así que si encuentras algún error, avísame y trataremos de solucionarlo. Échale un vistazo y dime qué piensas. Respecto a que tus campos siguen llenos después del envío, es porque no limpias tu variable $_POST y la usas para rellenar tu formulario. Mi código debería encargarse de eso.

bynicolas bynicolas
23 ago 2016 19:51:32

¡Guau! Esa es una respuesta sólida si alguna vez he visto una. Creo que tienes razón, definitivamente será muy beneficioso para otros que se encuentren con esto. ¡Gracias por todo el tiempo que dedicaste a esto! Es realmente útil. Tengo una pregunta, ¿qué hace la variable $k? ¡Gracias de nuevo!

user53340 user53340
23 ago 2016 20:59:39

$k es la clave del array $errors->errors, también conocida como el slug del error. Recuerda cuando configuraste tu mensaje de error $errors->add('name_error', 'Por favor ingresa un nombre válido.' );, pues aquí, $k sería name_error. En realidad es el código de error asignado para ese error específico. Lo agregué como una clase HTML para que sea más fácil darle estilos diferentes a cada error, si alguna vez surge la necesidad. Me alegra que esta respuesta haya sido de ayuda.

bynicolas bynicolas
23 ago 2016 21:23:05

Lamento molestarte de nuevo. Estoy recibiendo un error de sintaxis para $headers, dice que se esperaba un string o variable pero se encontró ' '. Eliminé las comillas alrededor del nombre y el email, pero luego obtengo un "unexpected end of file" que creo se debe a que la función my_form_message() no está cerrada. Así que la cerré y obtuve un error fatal debido a is_wp_errors y ahora estoy bastante confundido =S

user53340 user53340
24 ago 2016 12:02:49

es difícil decirlo desde aquí, pero puedo ver algo extraño al definir tu variable $headers. Como estás usando un nombre de variable (array) en tu string, necesitas indicarle explícitamente al analizador de PHP dónde termina la variable usando llaves { }. Así que prueba esto y debería funcionar: $headers = "From: {$args['name']} <{$args['email']}>\r\n";

bynicolas bynicolas
24 ago 2016 17:34:33

Gracias por responderme. Había un par de problemas que ahora han sido resueltos. He actualizado mi código anterior. ¡Gracias nuevamente por toda tu ayuda con esto!

user53340 user53340
24 ago 2016 17:40:08
Mostrar los 5 comentarios restantes
5

Como eres relativamente nuevo en PHP, aquí tienes un buen ejemplo de un honeypot simple del lado del servidor.

Lo que harás es configurar un campo de texto oculto en tu formulario y verificar si no está vacío para atrapar el spam.

HTML del Formulario

<label for="honeypot" class="bot">Deja en blanco si eres humano</label>
<input type="text" id="honeypot" name="honeypot" class="bot" />

CSS

.bot { display:none }

PHP (simplificado)

 if ( $_POST() ) {
    $name = strip_tags(urldecode(trim($_POST['name'])));
    // O
    $name = test_input($_POST['name']; 
    $honeypot = $_POST('honeypot');
    // Solo verifica SI hay algo en él.
    if ($honeypot){
      $msg = "Eres un bot";
    } else {
      $msg = "Éxito";
      // Procesamiento adicional
      if ($name){
          mail(...)
      }
    }
    echo $msg;
 }

Después de una breve búsqueda también encontré esta función que podrías usar.

function test_input($data) {
  $data = trim($data);
  $data = stripslashes($data);
  $data = htmlspecialchars($data);
  return $data;
}
23 ago 2016 17:40:58
Comentarios

Gracias por tu mensaje. Eso es definitivamente útil, mi principal problema es la falta de validación del formulario. Actualmente simplemente me permite enviar el formulario sin importar si lleno algo o no. Y me preocupa que no esté verificando inyecciones de código, etc. Eso es lo principal que busco. ¿Sería cuestión de seguir el mismo principio que para el honeypot? Gracias de nuevo.

user53340 user53340
23 ago 2016 17:44:31

Actualizaré el PHP con pruebas de validación adicionales, pero nunca debes confiar en la entrada del usuario y debes sanitizar/validar de todas las formas posibles.

Greg McMullen Greg McMullen
23 ago 2016 17:47:20

Gracias. Estoy buscando hacerlo lo más seguro posible, pero como digo, soy bastante nuevo en PHP así que soy consciente de estas cosas, solo que no tengo idea de cómo implementar las medidas de seguridad. Sé que puedes consultar los campos de entrada para evitar que alguien intente hackear el formulario, etc., pero no sé cómo se haría eso. Gracias nuevamente por tu ayuda.

user53340 user53340
23 ago 2016 17:50:07

¿Estás guardando esto en una base de datos, o simplemente recibiendo el correo electrónico? Por el post original, parece que solo estás haciendo esto último.

Greg McMullen Greg McMullen
23 ago 2016 17:54:27

Sí, solo quiero que me llegue por correo. No debería ir a la base de datos, pero tampoco quiero que haga a mi sitio vulnerable. Tuve ese problema con Gravity Forms hace un tiempo. ¡Gracias por la actualización!

user53340 user53340
23 ago 2016 17:59:29