Contents
Introducción
En este artículo encontrarás un tutorial completo y detallado para crear un exportador de JSON de contenido personalizado en WordPress usando PHP. Cubriremos desde la estructura básica de un plugin o snippet, hasta ejemplos prácticos para exportar entradas personalizadas (Custom Post Types), metadatos, taxonomías y archivos adjuntos. También veremos variantes: exportador desde el admin (descarga directa), endpoint REST y estrategias para manejar exportaciones grandes (streaming y paginación).
Enfoques posibles
- Admin export (descarga directa): añadir una página en el panel de administración que genere y entregue el archivo JSON para descarga.
- REST API endpoint: registrar una ruta REST que devuelva el JSON (útil para integraciones automáticas).
- WP-CLI: exportador CLI para tareas programadas o procesos en servidor (no cubierto en profundidad aquí).
Requisitos y buenas prácticas
- Usar un plugin propio o un mu-plugin en lugar de poner código en el theme cuando sea algo funcional.
- Comprobar permisos y usar nonces para solicitudes desde el admin.
- Sanitizar entradas y escapar salidas.
- Soportar paginación/streaming para evitar alcanzar límites de memoria.
- Usar JSON_PRETTY_PRINT y JSON_UNESCAPED_UNICODE para legibilidad y soporte de caracteres.
Estructura de archivos sugerida
mi-exportador-json/ |
mi-exportador-json/mi-exportador-json.php |
mi-exportador-json/includes/class-exporter.php |
1) Ejemplo: Registrar un Custom Post Type (opcional)
Si necesitas un CPT para probar el exportador, aquí tienes cómo registrarlo desde un plugin o functions.php:
lt?php // Registra un CPT simple para pruebas function me_register_cpt_book() { labels = array( name => Libros, singular_name => Libro, ) args = array( labels =gt labels, public =gt true, has_archive =gt true, show_in_rest =gt true, supports =gt array(title,editor,thumbnail,custom-fields), rewrite =gt array(slug =gt libros), ) register_post_type(libro, args) } add_action(init, me_register_cpt_book) ?gt
2) Exportador desde el admin (descarga directa)
Este ejemplo crea una página en el menú de administración desde la que se puede seleccionar el CPT y rango de fechas, genera el JSON y fuerza la descarga.
Archivo principal del plugin (mi-exportador-json.php)
lt?php / Plugin Name: MI Exportador JSON Description: Exporta contenido personalizado a JSON. Version: 1.0 Author: Ejemplo / if ( ! defined( ABSPATH ) ) { exit } require_once plugin_dir_path( __FILE__ ) . includes/class-exporter.php add_action(plugins_loaded, function(){ ME_Exporter::init() }) ?gt
Clase exportadora (includes/class-exporter.php)
La clase añade el menú, renderiza el formulario y procesa la exportación con verificación de permisos y nonce.
lt?php class ME_Exporter { public static function init() { add_action(admin_menu, array(__CLASS__, add_admin_menu)) add_action(admin_post_me_export_json, array(__CLASS__, handle_export)) } public static function add_admin_menu() { add_management_page( Exportar JSON, Exportar JSON, export, // capability recomendada me-export-json, array(__CLASS__, render_admin_page) ) } public static function render_admin_page() { // Obtener post types públicos post_types = get_post_types( array(public => true), objects ) ?> ltdiv class=wrapgt lth2gtExportar contenido a JSONlt/h2gt ltform method=post action=gt ltinput type=hidden name=action value=me_export_jsongt lttable class=form-tablegt lttrgt ltthgtltlabelgtTipo de contenidolt/labelgtlt/thgt lttdgt ltselect name=post_typegt lt?php foreach(post_types as pt): ?gt ltoption value=lt?php echo esc_attr(pt->name) ?gtgtlt?php echo esc_html(pt->label) ?gtlt/optiongt lt?php endforeach ?gt lt/selectgt lt/tdgt lt/trgt lttrgt ltthgtltlabelgtFecha desdelt/labelgtlt/thgt lttdgtltinput type=date name=date_from /gtlt/tdgt lt/trgt lttrgt ltthgtltlabelgtFecha hastalt/labelgtlt/thgt lttdgtltinput type=date name=date_to /gtlt/tdgt lt/trgt lt/tablegt ltp class=submitgtltbutton class=button button-primarygtGenerar JSONlt/buttongtlt/pgt lt/formgt lt/divgt lt?php } public static function handle_export() { // Permisos if ( ! current_user_can(export) ) { wp_die(No tiene permisos para exportar.) } // Nonce if ( ! isset(_POST[me_export_json_nonce]) ! wp_verify_nonce(_POST[me_export_json_nonce], me_export_json_action) ) { wp_die(Nonce no válido.) } post_type = isset(_POST[post_type]) ? sanitize_text_field(_POST[post_type]) : post date_from = ! empty(_POST[date_from]) ? sanitize_text_field(_POST[date_from]) : date_to = ! empty(_POST[date_to]) ? sanitize_text_field(_POST[date_to]) : // Preparar query args = array( post_type => post_type, posts_per_page => -1, post_status => any, date_query => array(), ) if ( date_from ) { args[date_query][] = array( after => date_from ) } if ( date_to ) { args[date_query][] = array( before => date_to ) } query = new WP_Query( args ) items = array() foreach ( query->posts as post ) { items[] = self::export_post_to_array( post ) } filename = sprintf(%s-%s.json, post_type, date(Y-m-d_His)) // Headers para descarga header(Content-Description: File Transfer) header(Content-Type: application/json charset=utf-8) header(Content-Disposition: attachment filename= . filename . ) header(Expires: 0) header(Cache-Control: must-revalidate) header(Pragma: public) echo wp_json_encode(items, JSON_PRETTY_PRINT JSON_UNESCAPED_UNICODE) exit } private static function export_post_to_array( post ) { // Datos básicos data = array( ID => post->ID, post_title => get_the_title(post), post_content => apply_filters(the_content, post->post_content), post_excerpt => post->post_excerpt, post_status => post->post_status, post_type => post->post_type, post_date => get_the_date(c, post), post_modified => get_the_modified_date(c, post), permalink => get_permalink(post), ) // Meta (todos los metadatos, limpiar claves que empiezan con _ no públicas si se desea) meta = get_post_meta( post->ID ) foreach ( meta as key => value ) { // Si se desea excluir metadatos internos: if ( strpos(key, _) === 0 ) { continue } data[meta][key] = maybe_unserialize(value) } // Taxonomías asociadas taxonomies = get_object_taxonomies( post->post_type ) foreach ( taxonomies as tax ) { terms = get_the_terms( post->ID, tax ) if ( is_wp_error(terms) empty(terms) ) { data[terms][tax] = array() } else { data[terms][tax] = wp_list_pluck(terms, name) } } // Imagen destacada (url) if ( has_post_thumbnail(post) ) { thumb_id = get_post_thumbnail_id(post) data[thumbnail] = wp_get_attachment_url(thumb_id) } else { data[thumbnail] = null } return data } } ?gt
Notas sobre este enfoque
- Para exportaciones pequeñas o moderadas es suficiente. Para sitios con miles de posts puede agotar memoria.
- Si necesitas incluir campos repetibles/ACF, usa get_post_meta para las claves correspondientes o las funciones ACF apropiadas.
3) Exportador vía REST API
Registrar una ruta REST es útil para integraciones. Este endpoint devolverá un JSON con las entradas filtradas.
lt?php add_action(rest_api_init, function() { register_rest_route(me/v1, /export/(?P[a-zA-Z0-9-_] ), array( methods =gt GET, callback =gt me_rest_export, permission_callback =gt function( request ) { // Requiere autenticación y capacidad export (o personalizar) return current_user_can(export) } )) }) function me_rest_export( request ) { post_type = request->get_param(post_type) date_from = request->get_param(date_from) date_to = request->get_param(date_to) args = array( post_type => post_type, posts_per_page => -1, post_status => any, ) if ( date_from date_to ) { date_query = array() if ( date_from ) date_query[] = array(after => date_from) if ( date_to ) date_query[] = array(before => date_to) args[date_query] = date_query } q = new WP_Query(args) items = array() foreach(q->posts as post) { items[] = ME_Exporter::export_post_to_array(post) // reutiliza la función } return rest_ensure_response(items) } ?gt
Consideraciones
- El endpoint devuelve JSON en el body. Para forzar descarga desde cliente, se puede solicitar y luego crear un archivo local con JavaScript o realizar una llamada desde servidor.
- Controlar permisos: aquí se ha exigido current_user_can(export), pero puedes restringirlo más.
4) Exportaciones grandes: streaming y paginación
Para evitar memoria insuficiente, conviene enviar el JSON en streaming por bloques (chunked) o exportar por páginas. A continuación un ejemplo sencillo de streaming que imprime una lista JSON elemento a elemento.
lt?php // Exportador streaming (ejecutar desde admin_post o handler propio) function me_stream_export( post_type = post ) { if ( ! current_user_can(export) ) { wp_die(No autorizado) } page = 1 per_page = 100 // ajustar según memoria/tiempo first = true // Headers header(Content-Type: application/json charset=utf-8) header(Content-Disposition: attachment filename=export-stream- . post_type . - . date(Ymd_His) . .json) // Desactivar buffer if ( function_exists(apache_setenv) ) { @apache_setenv(no-gzip, 1) } @ini_set(zlib.output_compression, Off) @ini_set(output_buffering, Off) @ini_set(max_execution_time, 0) echo [ while ( true ) { args = array( post_type => post_type, posts_per_page => per_page, paged => page, post_status => any, fields => ids, ) q = new WP_Query(args) if ( empty(q->posts) ) { break } foreach ( q->posts as post_id ) { post = get_post(post_id) item = ME_Exporter::export_post_to_array(post) // o función equivalente if ( ! first ) { echo , } echo wp_json_encode(item, JSON_UNESCAPED_UNICODE) flush() first = false } if ( q->max_num_pages lt= page ) { break } page } echo ] exit } ?gt
Consejos
- Evitar usar posts_per_page =gt -1 en sitios grandes.
- Usar flush() para enviar datos al cliente por partes.
- Incrementar tiempo de ejecución y memoria si es necesario, pero preferir paginar.
5) Incluir archivos adjuntos (media)
Normalmente se incluye la URL del attachment (wp_get_attachment_url). Si necesitas incluir el archivo en base64, ten en cuenta el alto consumo de memoria y tráfico.
// Dentro de export_post_to_array (ejemplo para incluir attachment como base64 - usar con precaución) attachments = get_attached_media(, post->ID) data[attachments] = array() foreach (attachments as att) { url = wp_get_attachment_url(att-gtID) file = get_attached_file(att-gtID) base64 = null if ( file_exists(file) filesize(file) lt 1024 1024 2 ) { // limitar a 2MB content = file_get_contents(file) base64 = data: . get_post_mime_type(att-gtID) . base64, . base64_encode(content) } data[attachments][] = array( ID =gt att-gtID, url =gt url, mime =gt get_post_mime_type(att-gtID), base64 =gt base64, ) }
6) Exportar campos personalizados y ACF
Para ACF puedes usar get_field() si ACF está activo, o get_post_meta() directamente. Ejemplo:
// Dentro de export_post_to_array if ( function_exists(get_field) ) { data[acf] = array( precio =gt get_field(precio, post->ID), autor =gt get_field(autor, post->ID), ) } else { data[meta][precio] = get_post_meta(post->ID, precio, true) }
7) Seguridad y permisos
- Capability: usar capacidades adecuadas como export o manage_options según el caso.
- Nonce: siempre verificar nonce para solicitudes desde formularios del admin.
- Sanitización: sanear parámetros GET/POST (sanitize_text_field, intval, etc.).
- Validar datos sensibles: no exportar metadatos internos (_edit_lock, etc.) a menos que sea necesario.
8) Pruebas y depuración
- Probar con distintos tamaños de dataset.
- Verificar que las URLs de medios sean accesibles y que los permisos de archivos en servidor sean correctos.
- Comprobar que las fechas, taxonomías y metadatos lleguen en el formato esperado.
9) Ejemplos de consumo del endpoint desde JavaScript (opcional)
Si quieres descargar desde el navegador el JSON devuelto por el endpoint REST y forzar descarga:
// Ejemplo básico para descargar el JSON desde el front (requiere autenticación si el endpoint la requiere). fetch(/wp-json/me/v1/export/libro, { credentials: include }) .then(resp =gt resp.blob()) .then(blob =gt { const url = window.URL.createObjectURL(blob) const a = document.createElement(a) a.href = url a.download = export-libro.json document.body.appendChild(a) a.click() a.remove() window.URL.revokeObjectURL(url) })
10) Consideraciones finales
Este conjunto de patrones te permite implementar un exportador de JSON robusto y adaptable. Para la mayoría de proyectos conviene:
- Implementar primero la versión admin (rápida de desarrollar).
- Si necesitas integraciones automáticas, añadir un endpoint REST con permisos adecuados.
- Para sitios grandes, priorizar paginación/streaming y limitar el tamaño de los archivos adjuntos incluidos en base64.
Con esto dispones de las herramientas y ejemplos necesarios para crear un exportador de JSON para contenido personalizado en WordPress usando PHP, seguro y escalable.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |