¿Cómo programar un formulario en WordPress sin utilizar plugins?

Porqué desarrollar un formulario en vez de usar un plugin 

Hay multitud de plugins para crear formularios, pero si eres programador de WordPress y lo único que necesitas es crear un par de formularios concretos puedes ofrecer una solución más personalizada creando un pequeño plugin de desarrollo propio, además ahorras la carga de otros plugins mucho más grandes, lo que redundará en la velocidad del sitio
Este plugin es muy mejorable pero creo que es un buen ejercicio para alguien que esté aprendiendo a desarrollar en WordPress.

Por donde empiezo ¿cuál es el mejor sitio para el código de mi formulario?

En WordPress hay tres sitios diferenciados para desarrollar tu código:

  • En el tema (bueno en el tema hijo): recomendable si lo que estás haciendo es un cambio en la presentación, o agregando una información a tus páginas en la que vayas a usar fundamentalmente funciones de WordPress, con poco código propio, algo de HTML, algo de CSS, quizás JS, poco PHP.
  • En el archivo functions.php del tema (del tema hijo): igual que el anterior pero vas a usar esos cambios en varias plantillas o el tamaño del código añadido justifica una función.
  • En un plugin: si vas a necesitar algo más de lógica, tienes que escribir bastante PHP y no es tanto una cuestión de estética o presentación como de mecánica o comportamiento, la cosa está pidiendo un plugin a gritos. Además un plugin lo vas a poder usar independientemente del tema que tengas activo. En esta caso, que estás desarrollando un formulario claramente necesitas un plugin.

Creación del plugin

Crear un plugin es tan sencillo como crear un directorio nuevo en la carpeta de plugins y en su interior un archivo con el mismo nombre que la carpeta y la extensión .php
El nombre de carpeta y archivo debe ser descriptivo pero corto, usando guiones en lugar de espacios, y para evitar colisiones con otros plugins le puedes colocar delante unas siglas propias o un nombre corto: tu alias, tu marca, etc.
En mi caso la carpeta será kfp-form-autoevaluacion y el archivo principal kfp-form-autoevaluacion.php. Pero te animo a poner tu sufijo propio, y si vas a usar el formulario para otra cosa también debes cambiar el resto del nombre.
La primera versión del archivo debe contener una cabecera mínima para que WordPress lo considere un plugin. Ese mínimo funcional es el Plugin Name que debe reflejar el nombre de la carpeta y del archivo principal del plugin. No olvides la etiqueta inicial <?php

<?php
/**
* Plugin Name: KFP Form Autoevaluacion
*/ 

Si ahora vas al menú Plugins del escritorio de WordPress debes ver allí tu plugin KFP Form Autoevaluacion, puedes activarlo y comprobar que no explota nada en tu web. Si quieres que tu plugin quede mejor documentado puedes añadir otros campos a la cabecera como Description, Author o Version

Empezando a pintar el formulario

Ya tienes claro que quieres desarrollar un formulario para recoger una serie de datos y grabarlos en una tabla, pero todavía no sabes dónde lo vas a colocar. La mejor forma de insertar un formulario en una página o un post de WordPress es hacerlo mediante un shortcode.

Un shortcode es una pequeña etiqueta de texto que va entre corchetes [ ]. Para insertarlo en una página tendrás que hacerlo desde el modo HTML del editor clásico o usando un bloque especial de Gutemberg que se llama shortcode. Aunque en las últimas versiones se puede escribir directamente el shortocode (siempre entre corchetes, claro).

Para programar un shortcode utilizas una función especial de WordPress a la que tienes que pasarle una función definida por ti que hará todo el trabajo. De momento el shortcode sólo va a pintar el formulario en la página o entrada donde lo coloques, sin capacidad de procesar el envío, ya llegarás a eso.

Para que sea más didáctico te iré poniendo el fragmento de código en el que irás trabajando, pero, si te pierdes en algún momento o quieres ver el resultado final antes de empezar, tienes todo el código en el repositorio de GitHub de KungFuPress.

Casi todo el código va comentado, así que espero que lo vayas entendiendo a medida que lo vas escribiendo, si no entiendes porqué se hace algo concreto te ruego me lo preguntes usando los comentarios para mejorar este tutorial.

Lo único que no he comentado es el parámetro action del formulario, que llama a la función get_the_permalink de WordPress, esta función devuelve la url (o permalink) de la entrada actual, con lo que cuando un usuario envíe el formulario se volverá a llamar a la entrada actual, por lo que aparecerá de nuevo el formulario. Más adelante pondrás un aviso para informar al usuario del resultado de su envío.

<?php
/**
* Plugin Name: KFP Form Autoevaluacion
*/ 

// Define el shortcode y lo asocia a una función
add_shortcode('kfp_aspirante_form', 'Kfp_Aspirante_form');

/** 
 * Define la función que ejecutará el shortcode
 * De momento sólo pinta un formulario que no hace nada
 * 
 * @return string
 */
function Kfp_Aspirante_form() 
{
    // Esta función de PHP activa el almacenamiento en búfer de salida (output buffer)
    // Cuando termine el formulario lo imprime con la función ob_get_clean
    ob_start();
    ?>
    <form action="<?php get_the_permalink(); ?>" method="post" id="form_aspirante
class="cuestionario"">
        <div class="form-input">
            <label for="nombre">Nombre</label>
            <input type="text" name="nombre" id="nombre" required>
        </div>
        <div class="form-input">
            <label for='correo'>Correo</label>
            <input type="email" name="correo" id="correo" required>
        </div>
        <div class="form-input">
            <label for="nivel_html">¿Cuál es tu nivel de HTML?</label>
            <input type="radio" name="nivel_html" value="1" required> Nada
            <br><input type="radio" name="nivel_html" value="2" required> Estoy 
                aprendiendo
            <br><input type="radio" name="nivel_html" value="3" required> Tengo 
                experiencia
            <br><input type="radio" name="nivel_html" value="4" required> Lo 
                domino al dedillo
        </div>
        <div class="form-input">
            <label for="nivel_css">¿Cuál es tu nivel de CSS?</label>
            <input type="radio" name="nivel_css" value="1" required> Nada
            <br><input type="radio" name="nivel_css" value="2" required> Estoy 
                aprendiendo
            <br><input type="radio" name="nivel_css" value="3" required> Tengo 
                experiencia
            <br><input type="radio" name="nivel_css" value="4" required> Lo 
                domino al dedillo
        </div>
        <div class="form-input">
            <label for="nivel_js">¿Cuál es tu nivel de JavaScript?</label>
            <input type="radio" name="nivel_js" value="1" required> Nada
            <br><input type="radio" name="nivel_js" value="2" required> Estoy 
                aprendiendo
            <br><input type="radio" name="nivel_js" value="3" required> Tengo 
                experiencia
            <br><input type="radio" name="nivel_js" value="4" required> Lo domino 
al dedillo
        </div>
        <div class="form-input">
            <label for="aceptacion">La información facilitada se tratará 
            con respeto y admiración.</label>
            <input type="checkbox" id="aceptacion" name="aceptacion" 
value="1" required> Entiendo y acepto las condiciones
        </div>
        <div class="form-input">
            <input type="submit" value="Enviar">
        </div>
    </form>
    <?php
    
    // Devuelve el contenido del buffer de salida
    return ob_get_clean();
}

Comprueba que lo estás haciendo correctamente poniendo en una entrada de tu blog el shortcode [kfp_aspirante_form].

Cuando vayas a tu entrada debes ver un formulario como el de la imagen.

Bueno, no es lo más estético que habrás visto en formularios, pero… ¡es el tuyo! 😉

Un poco de estilo

Si te duelen los ojos de ver este formulario puedes insertar una pequeña hoja de estilo para adecentarlo desde el propio plugin. Crea un archivo dentro de la carpeta del plugin con el nombre style.css y el siguiente contenido:

form.cuestionario {
    width: 600px;
}
form.cuestionario label {
    font-weight: bold;
    display: block;
    margin-bottom: 5px;
}
form.cuestionario .form-input {
    padding: 1em;
}
form.cuestionario .form-input input[type='text'],
form.cuestionario .form-input input[type='email'] {
    display: block;
    width: 100%;
}
p.exito {
    font-size: 1.5em;
    padding: 1em;
    border: 2px solid green;
    background: lightgreen;
}
p.error {
    font-size: 1.5em;
    padding: 1em;
    border: 2px solid red;
    background: #ff8787;
}

Luegon inserta la función wp_enqueue_style dentro de la función Kfp_Aspirante_form antes de abrir el buffer de salida ob_start que imprime el formulario.

    // Carga esta hoja de estilo para poner más bonito el formulario
    wp_enqueue_style('css_aspirante', plugins_url('style.css', __FILE__));
    
    // Esta función de PHP activa el almacenamiento en búfer de salida (output buffer)
    // Cuando termine el formulario lo imprime con la función ob_get_clean
    ob_start();

Crear la tabla para recoger los datos

Casi todo el contenido de un blog de WordPress, ya sean entradas, páginas o incluso las imágenes; se graban en la misma tabla de la base de datos: la tabla wp_posts. Esto tiene sus ventajas y sus incovenientes, y quizás para grabar los datos de un formulario lo más adecuado sería crear un custom post type o tipo de contenido personalizado con sus correspondientes custom fields o campos personalizados.

En este caso he preferido crear una tabla aparte para recoger los datos. El momento idoneo para crear esta tabla o comprobar que ya existe es en el momento en que se activa el propio plugin. Para ello puedes usar la función register_activation_hook y una sencilla consulta SQL de creación de tabla. Tienes un buen manual de MySQL en Wikilibros y si quieres algo más rápido mira el artículo sobre SQL en Wikipedia.

Puedes poner este código en cualquier lugar del archivo del plugin, yo he preferido ponerlo casi al principio, detrás de la cabecera y antes de definir el shortcode.

// Cuando el plugin se active se crea la tabla para recoger los datos si no existe
register_activation_hook(__FILE__, 'Kfp_Aspirante_init');

/**
 * Crea la tabla para recoger los datos del formulario
 *
 * @return void
 */
function Kfp_Aspirante_init() 
{
    global $wpdb; // Este objeto global permite acceder a la base de datos de WP
    // Crea la tabla sólo si no existe
    // Utiliza el mismo prefijo del resto de tablas
    $tabla_aspirantes = $wpdb->prefix . 'aspirante';
    // Utiliza el mismo tipo de orden de la base de datos
    $charset_collate = $wpdb->get_charset_collate();
    // Prepara la consulta
    $query = "CREATE TABLE IF NOT EXISTS $tabla_aspirantes (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        nombre varchar(40) NOT NULL,
        correo varchar(100) NOT NULL,
        nivel_html smallint(4) NOT NULL,
        nivel_css smallint(4) NOT NULL,
        nivel_js smallint(4) NOT NULL,
        aceptacion smallint(4) NOT NULL,
        created_at datetime NOT NULL,
        UNIQUE (id)
        ) $charset_collate;";
    // La función dbDelta permite crear tablas de manera segura se
    // define en el archivo upgrade.php que se incluye a continuación
    include_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($query); // Lanza la consulta para crear la tabla de manera segura
}

Una vez tecleado este código (recuerda que siempre recomiendo teclear el código en lugar de copia y pegar) debes desactivar el plugin y volver a activarlo para que se ejecute la función register_activation_hook. Si todo ha ido bien deberías poder ver en tu base de datos la nueva tabla (algo como wp_aspirante).

Grabar los datos

Ya tienes definido el formulario y la tabla preparada, ¿no sería maravilloso qué cuando accedas al formulario y rellenes los datos estos se guarden en la tabla? Pues eso es lo que vas a hacer ahora.

Debes escribir este código dentro de la función Kfp_Aspirante_form que creaste al definir el shortcode. Puedes ponerlo hacia el principio o el final, pero dentro de la función, yo he preferido ponerlo al principio, delante del formulario que acabas de crear.

Lo primero que deberías hacer es comprobar que vienen rellenos todos los datos que vengan marcados como required en el formulario, se supone que sin esos no tendría sentido guardar la información en la base de datos. En este caso todos lo son, así que se comprueban todos. Esta comprobación la acabas de realizar, precisamente con el required, en el lado del cliente, pero como no todos nuestros visitantes van a acceder con un navegador actualizado lo debes volver a comprobar aquí también. Esto te va a pasar en más ocasiones, todo lo que compruebes o sanees en el lado del cliente debes volver a comprobarlo en el lado del servidor

add_shortcode('kfp_aspirante_form', 'Kfp_Aspirante_form');

/** 
 * Define la función que ejecutará el shortcode
 * Comprueba si se han enviado los datos desde el formulario
 * y pinta el formulario
 *
 * @return string
 */
function Kfp_Aspirante_form() 
{
    global $wpdb; // Este objeto global permite acceder a la base de datos de WP
    // Si viene del formulario  graba en la base de datos
    // Cuidado con el último igual de la condición del if que es doble
    if ($_POST['nombre'] != '' 
        AND is_email($_POST['correo'])
        AND $_POST['nivel_html'] != '' 
        AND $_POST['nivel_css'] != ''
        AND $_POST['nivel_js'] != ''       
        AND $_POST['aceptacion'] == '1' 
    ) {
        $tabla_aspirantes = $wpdb->prefix . 'aspirante'; 
        $nombre = sanitize_text_field($_POST['nombre']);
        $correo = $_POST['correo'];
        $nivel_html = (int)$_POST['nivel_html'];
        $nivel_css = (int)$_POST['nivel_css'];
        $nivel_js = (int)$_POST['nivel_js'];
        $aceptacion = (int)$_POST['aceptacion'];
        $created_at = date('Y-m-d H:i:s');
        $wpdb->insert(
            $tabla_aspirantes,
            array(
                'nombre' => $nombre,
                'correo' => $correo,
                'nivel_html' => $nivel_html,
                'nivel_css' => $nivel_css,
                'nivel_js' => $nivel_js,
                'aceptacion' => $aceptacion,
                'created_at' => $created_at,
            )
        );
        echo "<p class='exito'><b>Tus datos han sido registrados</b>. Gracias 
            por tu interés. En breve contactaré contigo.<p>";
    }
// Esto que viene ya lo debes tener escrito del primer paso    
    ob_start();
    ?>
<form action="<?php get_the_permalink(); ?>" method="post" id="form_aspirante" 
    class="cuestionario">
// ...y continúa el formulario que escribiste 

Graba el código en tu editor y recarga la página donde estás mostrando el formulario, si todo ha ido bien, debería aparecer el mensaje de confirmación y en la base de datos deberías ver la información grabada.

Validando y saneando datos

Observa que en el código que acabas de teclear se hacen varias comprobaciones de los datos antes de grabarlos. Enumero las funciones utilizadas:

  • sanitize_text_field: impide que puedan ir etiquetas o caractéres «peligrosos» dentro de un campo de texto.
  • is_email: función de WordPress para validar que una cadena es un correo bien conformado.
  • (int): es un «cast» de php que intenta convertir la variable que viene a continuación en una variable de tipo int. Sería un saneado.

Por último, en todo formulario que desarrolles debes intentar evitar un ataque de CSRF (Cross-Site Request Forgery) . Para ello vas a insertar un campo nonce que te ayude a comprobar que el formulario que se está procesando viene de tu propia web utilizando las funciones wp_nonce de WordPress. Tienes una buena explicación de lo que es un ataque de este tipo en el artículo «Cross-site Request Forgery: Dos ejemplos para entenderlo».

Así que tienes que introducir este código (o uno parecido) justo después de la etiqueta form y antes del primer input.

<form action="<?php get_the_permalink(); ?>" method="post" id="form_aspirante" 
      class="cuestionario">
    <?php wp_nonce_field('graba_aspirante', 'aspirante_nonce'); ?>

Por último tienes que incluir una condición más cuando estás comprobando si vienen todos los datos requeridos del formulario

AND $_POST['aceptacion'] == '1'
AND wp_verify_nonce($_POST['aspirante_nonce'], 'graba_aspirante')

Sobre el tema de validación y saneado estoy preparando un artículo que espero publicar próximamente.

Bola extra: cumplir con RGPD en tú formulario

Una cosa que muchos plugins de formulario (al menos la versión gratuita) no hacen, es cumplir con los requisitos básicos de RGPD (Reglamento General de Protección de Datos) al solicitar la aceptación de «Política de Privacidad»: guardar el nombre de la persona que da su consentimiento, el acepto, la dirección IP, la fecha y la hora.

Aunque lo incluyo en el código completo del plugin, que te puedes descargar desde Github, te dejo como ejercicio el desarrollarlo por tu cuenta. Teniendo en cuenta que ya guarda el nombre, el «acepto» y la fecha con la hora, sólo faltaría guardar la IP.

Que yo conozca no hay ninguna función en WordPress que te devuelva la IP del usuario así que te animo a buscar en la web alguna solución para implementar tu propia función dentro del plugin, a la que puedas llamar antes de grabar los datos, para que te devuelva la IP del usuario. Si no te aclaras con esto puedes ver en el repositorio de GitHub como lo he implementado yo (mira al final del archivo).

Crear un menú de administración para el plugin

Por último necesitarás alguna forma de consultar los datos recogidos por el formulario que se han ido almacenando en la base de datos. Como estos datos solo deben estar accesibles a los administradores del sitio vas a crear un menú específico dentro del escritorio de WordPress.

Para ello usarás el hook admin_menu que llamará a la función Kfp_Aspirante_menu.

Esta función a su vez utiliza la función add_menu_page de WordPress, encargada de crear el menú si el usuario tiene privilegios suficientes. La función add_menu_page recibe varios argumentos:

  • El título de la página que se cargará al seleccionar el menú.
  • El nómbre del elemento del menú.
  • La capacidad (o privilegio) que tiene que tener el usuario para que se cargue el menú, en este caso manage_options.
  • El slug o alias que aparecererá en la URL de la página de administración.
  • El nombre de la función que construirá la página que se cargará cuando se seleccione el menú.
  • El icono que aparecerá asociado al menú. Hay varias formas de hacerlo, para simplificar vas a usar un dashicon, en este caso dashicon-feedback
  • La posisición del elemento en el menú, en este caso he puesto 75. Aunque esto depende de la versión de WordPress y de los plugins que tengas ya instalados, para no marearte lo mejor es no ponerlo y saldrá el último
// El hook "admin_menu" permite agregar un nuevo item al menú de administración
add_action("admin_menu", "Kfp_Aspirante_menu");

/**
 * Agrega el menú del plugin al escritorio de WordPress
 *
 * @return void
 */
function Kfp_Aspirante_menu() 
{
    add_menu_page(
        'Formulario Aspirantes', 'Aspirantes', 'manage_options', 
        'kfp_aspirante_menu', 'Kfp_Aspirante_admin', 'dashicons-feedback', 75
    );
}

Tras grabar el código ve al escritorio de WordPress y allí debe aparecer un nuevo elemento de menú con el nombre Aspirantes, al pulsar sobre él se carga un panel a la derecha en el que, de momento, no aparece nada. Ahora vas a mostrar ahí los datos que has capturado desde el formulario (espero que hayas rellenado el formulario unas cuantas veces para tener algo que mostrar).

Crear una tabla para mostrar los datos del plugin desde el escritorio de WordPress

Uno de los argumentos de la función add_menu_page es el nombre de una función que pinte el panel o página de administración que se va a cargar cuando pulses sobre tu nuevo menú. Esa función , que vas a definir ahora, es la encargada de generar el código HTML que aparecerá en la página.

La función Kfp_Aspirante_admin hará cuatro cosas:

  • Imprimir la cabecera de una tabla HTML para mostrar los datos
  • Ejecutar una consulta SQL para obtener los datos desde la tabla de la base de datos generada por el propio plugin.
  • Recorrer con un bucle los registros obtenidos para irlos pintando en la tabla HTML
  • Imprimir el cierre de la tabla HTML

Observa que cuando vas a mostrar los campos de la base de datos escapas su contenido para que el usuario no reciba ninguna sorpresa. A pesar de todas las precauciones que has tomado en la fase de recogida de datos nunca puedes estar 100% seguro de que alguien haya inyectado algo en tus tablas, así que debes escapar en salida.

/**
 * Crea el contenido del panel de administración para el plugin
 *
 * @return void
 */
function Kfp_Aspirante_admin()
{
    global $wpdb;
    $tabla_aspirantes = $wpdb->prefix . 'aspirante';
    echo '<div class="wrap"><h1>Lista de aspirantes</h1>';
    echo '<table class="wp-list-table widefat fixed striped">';
    echo '<thead><tr><th width="30%">Nombre</th><th width="20%">Correo</th>
        <th>HTML</th><th>CSS</th><th>JS</th>
        <th>PHP</th><th>WP</th><th>Total</th></tr></thead>';
    echo '<tbody id="the-list">';
    $aspirantes = $wpdb->get_results("SELECT * FROM $tabla_aspirantes");
    foreach ( $aspirantes as $aspirante ) {
        $nombre = esc_textarea($aspirante->nombre);
        $correo = esc_textarea($aspirante->correo);
        $motivacion = esc_textarea($aspirante->motivacion);
        $nivel_html = (int)$aspirante->nivel_html;
        $nivel_css = (int)$aspirante->nivel_css;
        $nivel_js = (int)$aspirante->nivel_js;
        $nivel_php = (int)$aspirante->nivel_php;
        $nivel_wp = (int)$aspirante->nivel_wp;
        $total = $nivel_html + $nivel_css + $nivel_js + $nivel_php + $nivel_wp;
        echo "<tr><td><a href='#' title='$motivacion'>$nombre</a></td>
            <td>$correo</td><td>$nivel_html</td><td>$nivel_css</td>
            <td>$nivel_js</td><td>$nivel_php</td><td>$nivel_wp</td>
            <td>$total</td></tr>";
    }
    echo '</tbody></table></div>';
}

Guarda el código del plugin y selecciona de nuevo el menú Aspirante, si todo ha ido bien deberías poder ver los datos recogidos.

Para nota

Para crear las tablas del panel de administración se podría usar la clase WP_List_Table, aunque hay que tomar ciertas precauciones. Hay un buen ejemplo en https://clearintelligence.mx/wp-list-table-una-guia-paso-a-paso/ y otro en https://www.smashingmagazine.com/2011/11/native-admin-tables-wordpress/

Reflexiones finales

Espero que hayas podido seguir este tutorial sin mayor problema y que tu formulario esté funcionando a la perfección, ¡me encantaría verlo en acción si me pasas la URL!

Te animo a explorar otros temas a partir de lo que has aprendido con esta práctica. Piensa en algo que necesites resolver con WordPress desarrollando un plugin sencillo de este estilo e intenta solucionarlo.

Si te quedas atascado en algún punto no dudes en preguntarme tus dudas y juntos podemos intentarlo. Si observas algún error o se te ocurre algo que mejorar tampoco te lo guardes.

¡Espero impaciente tus comentarios!

Referencias

Deja un comentario

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