Como añadir metabox y guardar meta campos con PHP y nonce en WordPress

Contents

Introducción

Este artículo muestra paso a paso cómo añadir un metabox en el editor de WordPress y cómo guardar los metacampos de forma segura usando nonces y buenas prácticas de sanitización y capacidad de usuario. Se incluyen ejemplos completos en PHP listos para copiar y pegar en el archivo functions.php de un tema o en un plugin. Se explica cada comprobación de seguridad necesaria para evitar problemas con autosave, CSRF y permisos.

Resumen del flujo

  1. Registrar el metabox con add_meta_box.
  2. Renderizar el formulario del metabox, incluyendo un nonce con wp_nonce_field.
  3. Guardar los campos con el hook save_post, comprobando autosave, nonce y permisos del usuario.
  4. Sanitizar los datos antes de guardarlos con update_post_meta y escapar la salida cuando se muestre.

Motivación de las comprobaciones de seguridad

  • Nonce: evita ataques CSRF verificando que el formulario viene de la interfaz de administración válida.
  • DOING_AUTOSAVE: evita sobrescribir meta durante auto-guardados automáticos.
  • current_user_can(edit_post, post_id): comprueba que el usuario actual tiene permisos para editar el post.
  • Sanitización: garantiza que solo guardes datos fiables en la base de datos.

Ejemplo completo: campo de texto, textarea, checkbox y select

Ejemplo práctico con:

  • Metabox en post type post.
  • Campos: título corto (text), descripción (textarea), destacado (checkbox), origen (select).
  • Nonce y todas las comprobaciones de seguridad en la función de guardado.
lt?php
// 1) Registrar el metabox
add_action(add_meta_boxes, mi_plugin_registrar_metabox)
function mi_plugin_registrar_metabox() {
    add_meta_box(
        mi_metabox_id,                // ID
        Datos extra del post,         // Título
        mi_plugin_metabox_callback,   // Callback
        post,                         // Pantalla (post type)
        advanced,                     // Contexto
        high                          // Prioridad
    )
}

// 2) Renderizar el metabox (HTML del formulario)
function mi_plugin_metabox_callback(post) {
    // Recuperar valores guardados
    texto_corto = get_post_meta(post-gtID, _mi_texto_corto, true)
    descripcion = get_post_meta(post-gtID, _mi_descripcion, true)
    destacado   = get_post_meta(post-gtID, _mi_destacado, true)
    origen      = get_post_meta(post-gtID, _mi_origen, true)

    // Nonce para seguridad
    wp_nonce_field(mi_plugin_nonce_action, mi_plugin_nonce_field)
    ?gt

    ltpgt
      ltlabel for=mi_texto_cortogtTexto corto:lt/labelgtltbrgt
      ltinput type=text id=mi_texto_corto name=mi_texto_corto value=lt?php echo esc_attr(texto_corto) ?gt style=width:100% /gt
    lt/pgt

    ltpgt
      ltlabel for=mi_descripciongtDescripción:lt/labelgtltbrgt
      lttextarea id=mi_descripcion name=mi_descripcion rows=5 style=width:100%gtlt?php echo esc_textarea(descripcion) ?gtlt/textareagt
    lt/pgt

    ltpgt
      ltlabelgt
        ltinput type=checkbox name=mi_destacado value=1 lt?php checked(destacado, 1) ?gt /gt
        Destacado
      lt/labelgt
    lt/pgt

    ltpgt
      ltlabel for=mi_origengtOrigen:lt/labelgtltbrgt
      ltselect id=mi_origen name=mi_origengt
        ltoption value= lt?php selected(origen, ) ?gtgt-- Seleccione --lt/optiongt
        ltoption value=web lt?php selected(origen, web) ?gtgtWeblt/optiongt
        ltoption value=newsletter lt?php selected(origen, newsletter) ?gtgtNewsletterlt/optiongt
        ltoption value=otro lt?php selected(origen, otro) ?gtgtOtrolt/optiongt
      lt/selectgt
    lt/pgt

    lt?php
}

// 3) Guardar los datos del metabox
add_action(save_post, mi_plugin_guardar_metabox_datos)
function mi_plugin_guardar_metabox_datos(post_id) {
    // 3.1 Comprobar autosave
    if (defined(DOING_AUTOSAVE) ampamp DOING_AUTOSAVE) {
        return
    }

    // 3.2 Comprobar nonce
    if (!isset(_POST[mi_plugin_nonce_field])  !wp_verify_nonce(_POST[mi_plugin_nonce_field], mi_plugin_nonce_action)) {
        return
    }

    // 3.3 Comprobar permisos del usuario
    if (!current_user_can(edit_post, post_id)) {
        return
    }

    // 3.4 Sanitizar y guardar cada campo
    if (isset(_POST[mi_texto_corto])) {
        texto = sanitize_text_field(_POST[mi_texto_corto])
        update_post_meta(post_id, _mi_texto_corto, texto)
    } else {
        delete_post_meta(post_id, _mi_texto_corto)
    }

    if (isset(_POST[mi_descripcion])) {
        desc = sanitize_textarea_field(_POST[mi_descripcion])
        update_post_meta(post_id, _mi_descripcion, desc)
    } else {
        delete_post_meta(post_id, _mi_descripcion)
    }

    // Checkbox: guardar 1 o eliminar meta
    if (!empty(_POST[mi_destacado])) {
        update_post_meta(post_id, _mi_destacado, 1)
    } else {
        delete_post_meta(post_id, _mi_destacado)
    }

    // Select: sanitizar contra valores permitidos
    valores_permitidos = array(web, newsletter, otro, )
    if (isset(_POST[mi_origen]) ampamp in_array(_POST[mi_origen], valores_permitidos, true)) {
        update_post_meta(post_id, _mi_origen, _POST[mi_origen])
    } else {
        delete_post_meta(post_id, _mi_origen)
    }
}
?gt

Explicación detallada del código

  1. add_meta_box: define el metabox indicando ID, título, callback, post type, contexto y prioridad.
  2. get_post_meta: obtiene el valor actual para prellenar los campos del formulario.
  3. wp_nonce_field: añade un campo oculto con nonce que usaremos más tarde para verificar el origen del formulario.
  4. En la función de guardado:
    1. Se evita el guardado durante autosave porque DOING_AUTOSAVE puede sobreescribir datos.
    2. Se verifica que la variable nonce existe y es válida con wp_verify_nonce.
    3. Se comprueba que el usuario pueda editar el post con current_user_can.
    4. Se sanitizan los datos con sanitize_text_field, sanitize_textarea_field y validación manual para selects y checkboxes.
    5. Se usa update_post_meta para escribir o delete_post_meta si el campo está vacío y no se quiere dejar meta vacío.

Cómo mostrar los metacampos en la plantilla (frontend)

Al mostrar datos en el frontend siempre escapar la salida según el contexto. Ejemplos:

lt?php
// Dentro del loop de WordPress o con un post definido
texto_corto = get_post_meta(get_the_ID(), _mi_texto_corto, true)
descripcion = get_post_meta(get_the_ID(), _mi_descripcion, true)
destacado   = get_post_meta(get_the_ID(), _mi_destacado, true)
origen      = get_post_meta(get_the_ID(), _mi_origen, true)

// Escapar para HTML simple
if (texto_corto) {
    echo lth4gt . esc_html(texto_corto) . lt/h4gt
}

if (descripcion) {
    echo ltpgt . wp_kses_post(wpautop(descripcion)) . lt/pgt
}

// Checkbox como etiqueta
if (destacado === 1) {
    echo ltspan class=mi-destacadogtDestacadolt/spangt
}

// Origen
if (origen) {
    echo ltsmallgtOrigen:  . esc_html(origen) . lt/smallgt
}
?gt

Buenas prácticas y recomendaciones

  • Prefijar las keys de meta (por ejemplo: _mi_texto_corto) para evitar colisiones con otros plugins/temas.
  • Evitar guardar HTML sin sanitizar si se necesita HTML permitido usar wp_kses o funciones similares.
  • No confiar en datos del cliente siempre validar y sanitizar en el servidor antes de guardar.
  • Eliminar meta innecesaria con delete_post_meta para no dejar basura en la base de datos.
  • Considerar usar una clase o namespace si se desarrolla un plugin para evitar conflictos y mantener el código organizado.

Errores comunes y cómo solucionarlos

  • No verificar nonce: permite CSRF. Solución: incluir wp_nonce_field y verificar con wp_verify_nonce.
  • No comprobar DOING_AUTOSAVE: guardados inesperados. Solución: retornar si DOING_AUTOSAVE es true.
  • No comprobar permisos: usuarios sin permiso podrían modificar datos. Solución: usar current_user_can(edit_post, post_id).
  • No sanitizar datos: riesgo de XSS o datos inválidos en la base de datos. Solución: usar sanitize_text_field, sanitize_textarea_field, esc_url_raw, intval, etc.

Puntos avanzados y extensiones

  1. Usar campos repetibles: para metadatos complejos se pueden guardar arrays serializados o usar meta con claves numéricas. Sanear cada elemento.
  2. WP REST API: exponer metadatos vía REST requiere registrar meta con register_post_meta y show_in_rest => true.
  3. CBX y select con opciones dinámicas: cargar opciones desde taxonomías o tablas externas y validarlas al guardar.
  4. Uso de clases para encapsular la lógica del metabox y permitir reusabilidad y pruebas unitarias.

Resumen final

Crear metaboxes y guardar meta campos en WordPress correctamente implica respetar varias comprobaciones de seguridad: nonce, autosave, permisos y sanitización de datos. El ejemplo proporcionado muestra un flujo completo con distintos tipos de campos y todas las comprobaciones necesarias para un desarrollo seguro y mantenible.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Deja una respuesta

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