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 🙂 |
