Introducción
Este artículo presenta un tutorial detallado para WordPress que explica cómo importar un archivo CSV con validación y feedback en PHP. Se describe un flujo seguro y práctico, con código listo para usar dentro de un plugin mínimo que añade una página en el área de administración. El objetivo es validar cada fila del CSV, importar solamente las filas válidas y proporcionar retroalimentación clara de los errores para poder corregir los datos y volver a intentar la importación.
Requisitos previos
- Instalación de WordPress con permisos de administrador.
- Conocimientos básicos de PHP y del API de WordPress (acciones, capacidades, inserción de posts).
- Archivo CSV con codificación UTF-8 y separador por comas (o ajustar el separador en el código si fuera necesario).
- Acceso al directorio de plugins para colocar el archivo PHP del plugin o conocimiento básico para crear un plugin.
Diseño general del proceso
- Formulario en el admin que permite subir el CSV y contiene un nonce de seguridad.
- Procesamiento en un handler (admin_post) que valida el nonce y la capacidad del usuario.
- Subida temporal del archivo con wp_handle_upload para mayor seguridad.
- Lectura del CSV con fgetcsv y validación por fila: campos obligatorios, formatos (email, numérico, fecha), unicidad, etc.
- Inserción de los registros válidos (ej. wp_insert_post) y recopilación de errores por fila para feedback.
- Almacenamiento del resultado (por ejemplo en transient) y redirección a la página de administración con una clave para mostrar los detalles del proceso.
Consideraciones de seguridad
- Usar wp_nonce_field y check_admin_referer para prevenir CSRF.
- Comprobar current_user_can(manage_options) para limitar la acción a administradores u otro rol apropiado.
- Usar wp_handle_upload con test_form => false cuando se trata de subidas desde admin_post y validar el resultado.
- Sanitizar y validar todos los campos antes de usarlos en la base de datos (esc_html, sanitize_email, intval, etc.).
- No confiar en los datos del CSV: validación exhaustiva y manejo de errores robusto.
Ejemplo práctico: plugin mínimo para importar CSV con validación y feedback
A continuación se muestra un plugin completo (archivo único) que añade una página bajo Herramientas → Importar CSV. El plugin procesa el CSV, valida cada fila y guarda los resultados en un transient para mostrarlos después en la misma pantalla.
Código del plugin (guardar como, por ejemplo, csv-importer-validacion.php en la carpeta de plugins)
Resultados no disponibles o caducados.
} else {
echo Resumen de la importación
echo Total filas | Importadas | Errores |
echo
echo . intval(results[total]) . |
echo . intval(results[imported]) . |
echo . intval(count(results[errors])) . |
echo
echo
if (!empty(results[errors])) {
echo Errores por fila
echo Fila | Motivo | Datos |
foreach (results[errors] as err) {
echo
echo . intval(err[row]) . |
echo . esc_html(err[message]) . |
echo . esc_html(err[data]) .
|
echo
}
echo
}
// Borrar transient al mostrar (opcional)
delete_transient(key)
}
}
// Formulario de subida
?>
Subir archivo CSV
csv-import, result_key => ), admin_url(tools.php))
wp_safe_redirect(redirect)
exit
}
require_once ABSPATH . wp-admin/includes/file.php
overrides = array(test_form => false)
uploaded = wp_handle_upload(_FILES[csv_file], overrides)
if (empty(uploaded) !empty(uploaded[error])) {
redirect = add_query_arg(array(page => csv-import, result_key => ), admin_url(tools.php))
wp_safe_redirect(redirect)
exit
}
file = uploaded[file]
handle = fopen(file, r)
if (handle === false) {
redirect = add_query_arg(array(page => csv-import, result_key => ), admin_url(tools.php))
wp_safe_redirect(redirect)
exit
}
// Definir cabeceras esperadas
expected_headers = array(title, email, price, date)
row_num = 0
total = 0
imported = 0
errors = array()
// Leer cabecera del CSV
headers = fgetcsv(handle)
row_num
if (headers === false) {
fclose(handle)
redirect = add_query_arg(array(page => csv-import, result_key => ), admin_url(tools.php))
wp_safe_redirect(redirect)
exit
}
// Normalizar encabezados (trim y lower)
norm_headers = array_map(function(h){ return strtolower(trim(h)) }, headers)
// Comprobar que contiene los campos obligatorios
foreach (expected_headers as h) {
if (!in_array(h, norm_headers)) {
fclose(handle)
errors[] = array(row => 0, message => Falta columna obligatoria: {h}, data => implode(,, headers))
results = array(total => 0, imported => 0, errors => errors)
key = csv_import_ . time()
set_transient(key, results, 600)
redirect = add_query_arg(array(page => csv-import, result_key => key), admin_url(tools.php))
wp_safe_redirect(redirect)
exit
}
}
// Mapear posición de cada columna esperada
map = array()
foreach (norm_headers as idx => h) {
map[h] = idx
}
// Procesar cada fila
while ((data = fgetcsv(handle)) !== false) {
row_num
total
// Saltar filas vacías
is_empty = true
foreach (data as c) {
if (trim(c) !== ) { is_empty = false break }
}
if (is_empty) continue
// Recuperar valores por nombre
title = isset(data[map[title]]) ? trim(data[map[title]]) :
email = isset(data[map[email]]) ? trim(data[map[email]]) :
price = isset(data[map[price]]) ? trim(data[map[price]]) :
date = isset(data[map[date]]) ? trim(data[map[date]]) :
// Validaciones
row_errors = array()
if (title === ) {
row_errors[] = Título vacío
} else {
// Comprobar unicidad del título (opcional)
if (get_page_by_title(title, OBJECT, post)) {
row_errors[] = Ya existe un post con ese título
}
}
if (email === !filter_var(email, FILTER_VALIDATE_EMAIL)) {
row_errors[] = Email inválido
}
// Price debe ser numérico y mayor o igual a 0
price_clean = str_replace(,, ., price)
if (price_clean === !is_numeric(price_clean)) {
row_errors[] = Precio inválido
} else {
price_val = floatval(price_clean)
if (price_val < 0) row_errors[] = Precio negativo
}
// Fecha en formato YYYY-MM-DD
dt = DateTime::createFromFormat(Y-m-d, date)
if (date === dt === false dt->format(Y-m-d) !== date) {
row_errors[] = Fecha inválida (esperado YYYY-MM-DD)
}
if (!empty(row_errors)) {
errors[] = array(
row => row_num,
message => implode( , row_errors),
data => implode(, , data)
)
continue
}
// Si todo OK: insertar post (ejemplo) y meta con precio y email
postarr = array(
post_title => wp_strip_all_tags(title),
post_content => ,
post_status => publish,
post_type => post,
)
post_id = wp_insert_post(postarr, true)
if (is_wp_error(post_id)) {
errors[] = array(row => row_num, message => Error al crear el post: . post_id->get_error_message(), data => implode(, , data))
continue
}
// Guardar meta
update_post_meta(post_id, import_email, sanitize_email(email))
update_post_meta(post_id, import_price, floatval(price_val))
update_post_meta(post_id, import_date, dt->format(Y-m-d))
imported
}
fclose(handle)
// Preparar resultados y guardarlos en transient para mostrarlos en la página
results = array(
total => total,
imported => imported,
errors => errors
)
key = csv_import_ . time()
set_transient(key, results, 600)
// Redirigir de vuelta a la página del plugin con la clave de resultados
redirect = add_query_arg(array(page => csv-import, result_key => key), admin_url(tools.php))
wp_safe_redirect(redirect)
exit
}
Explicación del código
- Se añade un submenú en Herramientas con add_submenu_page para mostrar un formulario de subida.
- El formulario apunta a admin-post.php con action=csv_import y contiene wp_nonce_field(csv_import).
- El handler registrado en admin_post_csv_import valida nonce y permisos y usa wp_handle_upload para gestionar el archivo.
- Se lee el CSV con fgetcsv. La primera fila se trata como cabecera y se normaliza para mapear las columnas por nombre.
- Se aplican validaciones (campo requerido, email válido, precio numérico y fecha en formato YYYY-MM-DD). Los errores de cada fila se acumulan con el número de fila y los datos originales.
- Los registros válidos se insertan como posts y se añade meta con update_post_meta. En tu proyecto real, puedes adaptar para crear custom post types, usuarios o cualquier otra entidad.
- Para mostrar feedback se almacenan los resultados en un transient y se redirige a la página del plugin incluyendo la clave. La página recupera el transient y muestra un resumen y una tabla de errores.
Formato de CSV de ejemplo
Cabecera esperada (orden no necesario, pero los nombres deben existir): title,email,price,date
title,email,price,date
Producto 1,usuario1@ejemplo.com,29.99,2025-01-15
Producto 2,usuario2@ejemplo.com,9.50,2025-02-01
Producto 3,invalid-email,15.00,2025-02-30
En el ejemplo anterior, la tercera fila tendría errores: email inválido y fecha inexistente (2025-02-30).
Buenas prácticas y mejoras recomendadas
- Adaptar las validaciones a los requerimientos reales (por ejemplo, comprobar relaciones con taxonomías, talles, stock, etc.).
- Soportar archivos grandes mediante procesamiento por lotes y uso de WP Cron o un sistema de colas para evitar timeouts.
- Añadir una vista previa (preview) antes de insertar para que el administrador confirme los cambios.
- Proporcionar un CSV de muestra descargable para que los usuarios sepan el formato exacto.
- Registrar logs de importación y permitir reintentos o correcciones desde la interfaz.
- Si se espera subir muchos archivos o archivos muy grandes, limitar tipos MIME permitidos y revisar límites de PHP (upload_max_filesize, post_max_size).
Conclusión
Con este enfoque se obtiene una importación segura y controlada: el usuario recibe feedback preciso sobre qué filas fallaron y por qué, y sólo se importan los registros que superan las validaciones. El ejemplo de plugin sirve como base que se puede extender para soportar estructuras de datos más complejas, custom post types, taxonomías y procesos por lotes para grandes volúmenes.
Enlaces útiles
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂
|
¡Si te ha servido el artículo ayúdame compartiendolo en algún sitio! Pero si no te ha sido útil o tienes dudas déjame un comentario! 🙂
Relacionado