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
- Registrar el metabox con add_meta_box.
- Renderizar el formulario del metabox, incluyendo un nonce con wp_nonce_field.
- Guardar los campos con el hook save_post, comprobando autosave, nonce y permisos del usuario.
- 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
- add_meta_box: define el metabox indicando ID, título, callback, post type, contexto y prioridad.
- get_post_meta: obtiene el valor actual para prellenar los campos del formulario.
- wp_nonce_field: añade un campo oculto con nonce que usaremos más tarde para verificar el origen del formulario.
- En la función de guardado:
- Se evita el guardado durante autosave porque DOING_AUTOSAVE puede sobreescribir datos.
- Se verifica que la variable nonce existe y es válida con wp_verify_nonce.
- Se comprueba que el usuario pueda editar el post con current_user_can.
- Se sanitizan los datos con sanitize_text_field, sanitize_textarea_field y validación manual para selects y checkboxes.
- 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
- Usar campos repetibles: para metadatos complejos se pueden guardar arrays serializados o usar meta con claves numéricas. Sanear cada elemento.
- WP REST API: exponer metadatos vía REST requiere registrar meta con register_post_meta y show_in_rest => true.
- CBX y select con opciones dinámicas: cargar opciones desde taxonomías o tablas externas y validarlas al guardar.
- 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 🙂 |