Volver

Formulario de contacto (Hidrotecno)

¿Por qué lo hacemos a mano y tan difícil, si se puede con plugins?

WordPress es el CMS más popular de internet y tiene a disposición un sin fin de plugins para hacer de todo, incluyendo un simple formulario de contacto, sin embargo, “lo barato sale caro” Mucha de estas supuestas solución traen consigo problemas de seguridad (Brechas, puertas traseras), y como empresa ya hemos tenido malas experiencias con estos plugins de terceros. Por este mortivos hemos diseñado nuestra propia solución usando el mismo core de WordPress y API de google reCAPCHA de forma directa para evitar futuros inconvenientes.

 

¿Cómo funciona la solución del formulario?

Vamos a tener un formulario de contacto que se recepcionarán en una función personalizada, donde revisamos si los datos cumple con el formato adecuado y que no tenga código o script maliciosos, posteriormente validamos con reCAPTCHA bot y finalmente guardamos en base datos. Luego mandamos al correo los datos del formulario creado y complementamos la funcionalidad con un plugins personalizados que permite descargar todos los datos recopilados en un documento de excel.

 

Debido al nivel de complejidad de este componente, solo hablaremos en este artículo de la parte principal.

 

Los demás módulos serán explicados por separado en los siguientes artículos:

1. Verificación de formulario con reCAPTCHA,

2. Envío de correo con información del formulario,

3. Plugin personalizado para descargar los datos del custom post type en un archivo excel.

 

Archivos y construcción del componente.

 

Lo primero que debemos hacer es crear un custom post type, una taxonomía personalizada que usaremos para guardar las opciones de los select

Luego creamos el formulario y lo mostramos por medio del template de la página de contactos.

 

 

 

Sí, hacemos todo bien, al llenar los datos y enviar el formulario podremos ver la información en administrador en el menú de Contacto

 

Archivos Usados

1.

post-contact.php

PHP

functions/posts/

Setting

1.1. En este código vamos a crear el custom post type nuevo y para eso necesitamos las sigientes variables.

Si deseas ver más iconos, puede buscarlo aquí https://developer.wordpress.org/resource/dashicons/ 

1.2. También debemos tener en cuenta que los nombres de las funciones deben ser únicos en todo proyecto, para asegurarnos de eso, vamos a construir los nombres de las funciones con el nombre del custom post y caso que lo usemos para otro tipo de post deberemos cambiarlo.

Código:

<?php
//variables
$variables = [
       'post_type' => 'post_contact',
       'icon' => 'dashicons-buddicons-buddypress-logo',
       'label_single' => 'Contacto',
       'label_plural' => 'Contactos',
       'position_menu' => 7,
];

// Register Custom Post Type
function post_type_contact($variables) {
       $post_type = $variables['post_type'];
       $icon = $variables['icon'];
       $label_single = $variables['label_single'];
       $label_plural = $variables['label_plural'];
       $position_menu = $variables['position_menu'];

       $labels = array(
              'name'                  => _x( $label_plural , 'Post Type General Name', $label_single.'_domain' ),
              'singular_name'         => _x( $label_single, 'Post Type Singular Name', $label_single.'_domain' ),
              'menu_name'             => __( $label_plural , $label_single.'_domain' ),
              'name_admin_bar'        => __( $label_plural , $label_single.'_domain' ),
              'archives'              => __( 'Archivo '.$label_plural , $label_single.'_domain' ),
              'attributes'            => __( 'Atributos '.$label_single, $label_single.'_domain' ),
              'parent_item_colon'     => __( $label_single.' padre:', $label_single.'_domain' ),
              'all_items'             => __( 'Todos', $label_single.'_domain' ),
              'add_new_item'          => __( 'Agregar nueva', $label_single.'_domain' ),
              'add_new'               => __( 'Agregar', $label_single.'_domain' ),
              'new_item'              => __( 'Nueva', $label_single.'_domain' ),
              'edit_item'             => __( 'Editar', $label_single.'_domain' ),
              'update_item'           => __( 'Actualizar', $label_single.'_domain' ),
              'view_item'             => __( 'Ver '.$label_single, $label_single.'_domain' ),
              'view_items'            => __( 'Ver '.$label_single, $label_single.'_domain' ),
              'search_items'          => __( 'Buscar '.$label_single, $label_single.'_domain' ),
              'not_found'             => __( 'No encontrado', $label_single.'_domain' ),
              'not_found_in_trash'    => __( 'No encontrado en la papelera', $label_single.'_domain' ),
              'featured_image'        => __( 'Imagen destacada', $label_single.'_domain' ),
              'set_featured_image'    => __( 'Asignar imagen destacada', $label_single.'_domain' ),
              'remove_featured_image' => __( 'Remover imagen', $label_single.'_domain' ),
              'use_featured_image'    => __( 'Usar como imagen destacada', $label_single.'_domain' ),
              'insert_into_item'      => __( 'Insertar en '.$label_single, $label_single.'_domain' ),
              'uploaded_to_this_item' => __( 'Subir a '.$label_single, $label_single.'_domain' ),
              'items_list'            => __( 'Lista '.$label_single, $label_single.'_domain' ),
              'items_list_navigation' => __( 'Navegación '.$label_single, $label_single.'_domain' ),
              'filter_items_list'     => __( 'Filtro '.$label_single, $label_single.'_domain' ),
       );
       $args = array(
              'label'                 => __( $label_single, $label_single.'_domain' ),
              'description'           => __( 'Contenido de '.$label_single, $label_single.'_domain' ),
              'labels'                => $labels,
              'supports'              => array( 'title', 'editor' ),
              'taxonomies'            => array(),
              'hierarchical'          => false,
              'public'                => true,
              'show_ui'               => true,
              'show_in_menu'          => true,
              'menu_position'         => $position_menu,
              'menu_icon'             => $icon,
              'show_in_admin_bar'     => false,
              'show_in_nav_menus'     => false,
              'can_export'            => true,
              'has_archive'           => true,
              'exclude_from_search'   => true,
              'publicly_queryable'    => false,
              'capability_type'       => 'page',
              'show_in_rest'          => false,
        'capabilities' => array(
            'create_posts' => 'do_not_allow',
        ),
        'map_meta_cap' =>true,
       );
       register_post_type( $post_type, $args );

}
add_action( 'init', function() use ($variables){
       post_type_contact($variables);
});

2.

tax-contact-interest.php

PHP

wp-content/themes/depura_theme/functions/posts/

Setting

2.1 Creamos una taxonomía personalizada para guardar las opciones para campo de Temas de interés.

 

2.2. En este archivo usamos las siguientes variables

2.3. En las funciones vamos a usar el nombre de post type y el de la taxonomía.

 

Código

<?php
//Variables
$variables = [
    'post_type' => 'post_contact',
    'taxonomy_name' => 'interest',
    'label_single' => 'Tema de interés',
    'label_plural' => 'Temas de interés',
    'all_filter' => 'Todos los temas',
];

// Registrar la taxonomía personalizada
function taxonomy_contact_interest($variables) {
    $post_type = $variables['post_type'];
    $taxonomy_name = $variables['taxonomy_name'];
    $label_single = $variables['label_single'];
    $label_plural = $variables['label_plural'];

    $labels = array(
        'name'                       => $label_plural,
        'singular_name'              => 'Nueva '. $label_single,
        'menu_name'                  => $label_plural,
        'all_items'                  => 'Todas las '. $label_plural,
        'edit_item'                  => 'Editar '. $label_single,
        'view_item'                  => 'Ver '. $label_single,
        'update_item'                => 'Actualizar '. $label_single,
        'add_new_item'               => 'Agregar Nuevo '. $label_single,
        'new_item_name'              => 'Nombre del Nuevo '. $label_single,
        'parent_item'                => $label_single.' Padre',
        'parent_item_colon'          => $label_single.' Padre:',
        'search_items'               => 'Buscar '. $label_plural,
        'popular_items'              => $label_plural. ' Populares',
        'separate_items_with_commas' => 'Separar '.$label_plural.' con comas',
        'add_or_remove_items'        => 'Agregar o remover '. $label_single,
        'choose_from_most_used'      => 'Elegir los '. $label_plural,
        'not_found'                  => 'No se encontraron '. $label_plural,
    );

    $args = array(
        'labels'            => $labels,
        'hierarchical'      => false,
        'public'            => false,
        'show_ui'           => true,
        'show_admin_column' => true,
        'query_var'         => false,
        'rewrite'           => false
    );

    register_taxonomy($taxonomy_name, [$post_type], $args);
}
add_action('init', function() use ($variables){
       taxonomy_contact_interest($variables);
});

// Mostrar en el dropdown el termino para filtrar en todos los posts
function dropdown_contact_interest($variables) {
    global $typenow;
    $post_type = $variables['post_type'];
    $taxonomy_name = $variables['taxonomy_name'];
    $all_filter = $variables['all_filter'];

    if ($typenow == $post_type) {
        $terms = get_terms( array(
            'taxonomy' => $taxonomy_name,
            'object_type' => $post_type,
            'hide_empty' => false,
        ) );
        if ($terms) {
            echo '<select name="' . $taxonomy_name . '" id="' . $taxonomy_name . '">';
            echo '<option value="">'.$all_filter.'</option>';
            foreach ($terms as $term) {
                echo '<option value="' . $term->slug . '">' . $term->name . '</option>';
            }
            echo '</select>';
        }
    }
}
add_action('restrict_manage_posts', function() use ($variables){
       dropdown_contact_interest($variables);
});

//Agregar el termino al filtro de todos los posts
function filter_contact_interest($variables) {
    global $typenow, $wp_query;
    $post_type = $variables['post_type'];
    $taxonomy_name = $variables['taxonomy_name'];

    if ($typenow == $post_type && isset( $_GET[$taxonomy_name] ) && $_GET[$taxonomy_name] != '' ) {
        $term_slug = sanitize_text_field( $_GET[$taxonomy_name] );
        $wp_query->query_vars['tax_query'] = [[
            'taxonomy' => $taxonomy_name,
            'field'    => 'slug',
            'terms'    => $term_slug,
        ]];
    }
}
add_action('parse_query', function() use ($variables){
       filter_contact_interest($variables);
});

 

3.

contact-tpl.php

PHP

wp-content/themes/depura_theme/templates/

Template

3.1 En este template vamos a llamar el archivo que tiene el formulario.

 

Código

<?php
/**
 * @file
 * Template Name: Contact.
 */
get_template_part('includes/header');
?>
<section>
    <div class="container">
        <div class="row">
            <div class="col-lg-6">
            </div>
            <div class="col-lg-6">
                <?php get_template_part('includes/form_contact'); ?>
            </div>
        </div>
    </div>
</section>
<section>
    <?php the_content() ?>
</section>
<?php
get_template_part('includes/footer');
?>

 

4.

form_contact.php

PHP

wp-content/themes/depura_theme/includes/

Form

4.1. De los atributos de formulario cabe resaltar lo siguiente

id Lo usaremos para identificar el formulario en el archivo que recibe los datos, con method guardamos el tipo petición “enviar (post)”, ya que estamos usando el protocolo de API REST.

La URL de la API donde se va a mandar los datos se guarda el atributo action, la cual obtenemos con la función  admin_url( 'admin-post.php' )

 

4.2. En los input lo más importante es la propiedad name, porque a través de esta obtendremos el valor.

4.3. Otra cosa a tener en cuenta es que si deseamos que el campo sea obligatorio, colocamos la propiedad required

4.4. En el caso del select, vamos a obtener la option de los términos de la taxonomía personalizada interest.

4.5. En los botones tenemos guardado, de forma oculta(hidden), la uri que nos servirá para regresar a la página actual, el botón de submit para enviar los datos

4.6. Y lo más relevante, el input action que en su propiedad value guarda el nombre de la función que procesará los datos del formulario.

Código

<form
  class="form-contact"
  id="frm-contact" method="post"
  action="<?php echo admin_url( 'admin-post.php' ) ?>"
>
  <div class="row g-3">
    <input
      class="form-contact__text"
      type="text" name="name" id="name"
      size="40" maxlength="40" minlength="3"
      placeholder="<?php echo 'Nombre *' ?>"
      required
    >
    <input
      class="form-contact__text"
      type="email" name="email" id="email"
      size="40" maxlength="40" minlength="3"
      placeholder="<?php echo 'Correo Electrónico *'?>"
      required
    >
    <select class="form-contact__select" name="interest" id="interest">
      <option value="" class="placeholder">
        <?php echo 'Tema de interés' ?>
      </option>
      <?php
      $terms = get_terms( array(
        'taxonomy' => 'interest',
        'object_type' => 'post_contact',
        'hide_empty' => true,
      ) );
      foreach ($terms as $term):?>
        <option value="<?php echo $term->slug ?>">
          <?php echo $term->name ?>
        </option>
      <?php endforeach; ?>
    </select>
    <textarea class="form-contact__textarea" cols="40" rows="10" maxlength="500" minlength="3" aria-invalid="false" placeholder="Mensaje" name="message"></textarea>
    <div>
      <input type="hidden" name="uri" value="<?php echo get_permalink()?>">
      <input type="hidden" name="action" value="process_form_contact">
      <input class="form-contact__submit" type="submit" name="submit" value="Enviar">
    </div>
  </div>
</form>

 

 

5.

receive-contact.php

PHP

wp-content/themes/depura_theme/functions/posts/

Setting

5.1. En este archivo vamos a recibir y procesar el formulario, donde para esto usaremos dos hook admin_post_nopriv y admin_post_process los cuales son el administrador de post para usuario no registrados y usuarios registrados respectivamente.

5.2. La segunda parte del nombre del hook, debe ser igual al valor del input action del formulario.

 

5.3. El objeto $_POST contiene el valor todos los datos enviados en el formulario, los cuales vamos a obtener por medio de la propiedad name de los inputs.

 

5.4. Las funciones sanitize nos permiten validar y evitar ataques por inyección de código.

5.5. Por último, mandamos la información a la base de datos y redireccionamos a la página en donde estaba el formulario.

 

Código

<?php
add_action('admin_post_nopriv_process_form_contact', 'receive_contact');
add_action('admin_post_process_form_contact', 'receive_contact');

function receive_contact()
{
       //variables
       $uri = sanitize_url( $_POST['uri'] );
       $name = sanitize_text_field($_POST['name']);
       $email = sanitize_email($_POST['email']);
       $interest_form = sanitize_text_field($_POST['interest']);
       $interest_form__term = get_term_by('slug', $interest_form, 'interest', 'contact_post_type');
       $message = sanitize_text_field($_POST['message']);

       //Cuerpo del mensaje
       $msg = "<strong>Tema de Interés: </strong>" . $interest_form__term->name . "\n";
       $msg .= "<strong>Nombre: </strong>" . $name . "\n";
       $msg .= "<strong>Correo: </strong>" . $email . "\n";
       $msg .= $message;

       //Guardar el post en DB
       $post_id = wp_insert_post([
              'post_title'	=> 'Mensaje de ' . $name,
              'post_type'	=> 'post_contact',
              'post_content'	=> $msg,
              'post_status' 	=> 'private',
       ]);
       wp_set_object_terms($post_id, $interest_form, 'interest', false);

       wp_redirect($uri);
}

 

Avatar

Autor

Elan Francisco P. Asprilla
Desarrollador Frontend

Artículos relacionados

Galería Carrusel (Palmas)

Este componente nos permite mostrar un grupo de...

Grid Icons (HIDROTECNO)

Este componente “Grid Icons” se...

Alias de git

Hay forma de acortar los comandos de git, para...

¿Cómo configurar la página que se ve en la URL Raíz?

Para configurar qué página se verá en el...