Como generar PDFs desde PHP y forzar descarga en WordPress en WordPress

Contents

Introducción

Generar PDFs desde PHP dentro de WordPress y forzar su descarga es una necesidad habitual: facturas, tickets, contratos o resúmenes que el usuario debe obtener en su equipo. Este artículo explica, con todo lujo de detalles, cómo hacerlo correctamente, qué bibliotecas usar, cómo integrarlo en un plugin o en un tema, y cómo forzar la descarga garantizando seguridad, compatibilidad y manejo correcto de cabeceras.

Resumen de enfoques

  • Generar y forzar descarga en memoria: crear el PDF en memoria y enviar al navegador (streaming), ideal para PDFs temporales o únicos.
  • Generar archivo en disco y luego forzar descarga: práctico si quieres conservar una copia o generar versiones para auditoría.
  • Usar biblioteca con función de descarga: algunas bibliotecas (TCPDF, Dompdf, FPDF con extensiones) permiten directamente emitir la descarga.

Bibliotecas recomendadas

  • Dompdf: buena para convertir HTML/CSS a PDF. Sencillo para plantillas HTML que ya uses en WordPress.
  • TCPDF: potente y completo, más código para maquetado pero muy estable.
  • FPDF o FPDI: ligero y útil si el PDF es programático (texto, imágenes, tablas).

Requisitos y buenas prácticas

  1. Protege el endpoint con nonces y/o capacidades (current_user_can) cuando el PDF incluya datos sensibles.
  2. No permitas rutas arbitrarias ni nombres de archivo no sanitizados (evita Path Traversal).
  3. Evita el output previo (whitespace, echoes) antes de enviar cabeceras. Usa ob_end_clean() cuando proceda.
  4. Usa Content-Length cuando sea posible para que el navegador muestre progreso correcto.
  5. Después de enviar el PDF siempre termina la ejecución (exit or die) para evitar añadir HTML adicional.

Ejemplo 1 — Usar Dompdf y forzar descarga (plugin sencillo)

Este ejemplo asume que has instalado Dompdf dentro de tu plugin usando Composer (vendor/autoload.php). Crea un archivo de plugin y añade este código. El endpoint será ?download_pdf=1ampid=123ampnonce=… y el plugin generará el PDF y lo enviará al navegador con descarga forzada.

 403 ) )
    }

    id = isset( _GET[id] ) ? intval( _GET[id] ) : 0
    if ( id <= 0 ) {
        wp_die( ID inválido, Error, array( response => 400 ) )
    }

    // Ejemplo de autorización: sólo usuarios logueados pueden descargar (ajusta según necesidad)
    if ( ! is_user_logged_in() ) {
        auth_redirect()
    }

    // Preparar contenido HTML para el PDF. Aquí puedes cargar plantilla con get_template_part o locate_template.
    html = 

Ejemplo PDF para ID . esc_html( id ) .

html .=

Generado en . esc_html( date( c ) ) .

// Cargar Dompdf (asegúrate de que vendor/autoload.php existe en el plugin) autoload = __DIR__ . /vendor/autoload.php if ( ! file_exists( autoload ) ) { wp_die( Autoload no encontrado. Instala Dompdf con Composer., Error, array( response => 500 ) ) } require_once autoload // Usa Dompdf options = new DompdfOptions() options->set(isRemoteEnabled, true) dompdf = new DompdfDompdf(options) dompdf->loadHtml( html ) dompdf->setPaper(A4, portrait) dompdf->render() // Nombre de archivo seguro filename = documento- . id . .pdf // Limpiar cualquier buffer y enviar cabeceras correctas if ( ob_get_length() ) { ob_end_clean() } // Forzar descarga: Output en Dompdf puede usarse directamente con D (descarga). // Pero para tener control de headers, tomaremos el output y lo enviaremos. pdfOutput = dompdf->output() header( Content-Type: application/pdf ) header( Content-Transfer-Encoding: binary ) // Manejo de filename para caracteres no ascii filename_utf8 = rawurlencode( filename ) header( Content-Disposition: attachment filename={filename} filename=UTF-8{filename_utf8} ) header( Content-Length: . strlen( pdfOutput ) ) // Cache-control header( Pragma: public ) header( Cache-Control: must-revalidate, post-check=0, pre-check=0 ) echo pdfOutput exit } // Función que genera enlace seguro (usa ejemplo en plantilla) function pdfg_get_download_link( id ) { nonce = wp_create_nonce( pdfg_download ) url = add_query_arg( array( download_pdf => 1, id => intval( id ), nonce => nonce, ), home_url( / ) ) return url } ?>

En una plantilla o shortcode puedes usar la función pdfg_get_download_link( id ) para mostrar un enlace:


Notas sobre este enfoque

  • Usar isRemoteEnabled en Dompdf permite cargar imágenes externas (siempre validar).
  • Si el PDF es grande, considera guardar en disco temporal y luego usar readfile para menor uso de memoria.

Ejemplo 2 — TCPDF: usar la opción integrada para forzar descarga

TCPDF incluye un método Output que recibe un modo. El modo D fuerza la descarga inmediata. Ejemplo minimalista dentro de WordPress:

AddPage()
pdf->SetFont(helvetica, , 12)
pdf->Write(0, Texto de ejemplo para TCPDF)
filename = reporte-tcpdf.pdf
// D fuerza descarga
pdf->Output( filename, D )
// No debe haber echo ni HTML después
exit
?>

Ejemplo 3 — Generar el archivo en disco y servir con readfile()

Útil para mantener historiales. Generas el archivo en una carpeta protegida (fuera de la raíz pública si es posible), luego lo sirves con cabeceras y readfile.

stream(, array(Attachment => 0)) // ejemplo no usado
file_put_contents( path, dompdf->output() )

// Ahora servirlo de forma segura
if ( ! file_exists( path ) ) {
    wp_die( Archivo no encontrado, Error, array( response => 404 ) )
}

// Comprobaciones de seguridad aquí (nonce, permisos, etc.)
if ( ob_get_length() ) {
    ob_end_clean()
}

filesize = filesize( path )
filename = basename( path )
// Forzar descarga
header( Content-Description: File Transfer )
header( Content-Type: application/pdf )
header( Content-Disposition: attachment filename= . filename .  filename=UTF-8 . rawurlencode( filename ) )
header( Content-Transfer-Encoding: binary )
header( Expires: 0 )
header( Cache-Control: must-revalidate )
header( Pragma: public )
header( Content-Length:  . filesize )
readfile( path )
exit
?>

Cabeceras importantes y recomendaciones

  • Content-Type: application/pdf
  • Content-Disposition: attachment filename=archivo.pdf filename=UTF-8%encoded% para nombres con UTF-8
  • Content-Length: longitud en bytes cuando se conoce
  • Eliminar cualquier salida previa con ob_get_length() y ob_end_clean()
  • Si usas plugins de cache o Nginx/Apache con buffer, comprueba que no interfieran con las cabeceras (excluye la URL del cache si es necesario).

Seguridad y escalabilidad

  1. Valida permisos: evita que usuarios no autorizados descarguen PDFs con datos ajenos.
  2. Protege las rutas: guarda PDFs temporales en directorios fuera de la web público cuando sea posible.
  3. Limita tamaño y tiempo de vida de PDFs temporales limpia con cron o wp_cron.
  4. Rate limiting si el endpoint se va a exponer públicamente y puede generar cargas pesadas.

Depuración y problemas comunes

  • Errores de headers enviados: suele ocurrir por output previo (whitespace en archivos PHP). Revisa BOM o includes que hagan echo.
  • PDF corrupto: puede deberse a texto HTML adicional al final. Asegúrate de exit después de enviar PDF.
  • Imágenes no muestran en Dompdf: activa isRemoteEnabled y usa rutas absolutas o data URIs.
  • Problemas con nombres con acentos: usa rawurlencode en filename y doble cabecera filename por compatibilidad con navegadores viejos y nuevos.

Ejemplo práctico: Shortcode que genera un enlace y permite descarga (resumen)

Shortcode [pdf_download id=123] que muestra un enlace con nonce seguro:

 0 ), atts, pdf_download )
    id = intval( atts[id] )
    if ( id <= 0 ) {
        return 
    }
    nonce = wp_create_nonce( pdfg_download )
    url = add_query_arg( array(
        download_pdf => 1,
        id => id,
        nonce => nonce,
    ), home_url( / ) )
    return lta href= . esc_url( url ) . gtDescargar PDFlt/agt
} )
?>

Conclusión

Generar PDFs y forzar su descarga en WordPress es totalmente factible y flexible. La elección de la biblioteca depende de si trabajas con HTML/CSS (Dompdf), un maquetado más programático (TCPDF, FPDF) o necesitas manipular plantillas existentes. Siempre aplica validaciones, nonces y controles de permisos, evita outputs previos y usa las cabeceras correctas para una experiencia de descarga robusta y compatible con navegadores.

Enlaces útiles



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 *