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
- Protege el endpoint con nonces y/o capacidades (current_user_can) cuando el PDF incluya datos sensibles.
- No permitas rutas arbitrarias ni nombres de archivo no sanitizados (evita Path Traversal).
- Evita el output previo (whitespace, echoes) antes de enviar cabeceras. Usa ob_end_clean() cuando proceda.
- Usa Content-Length cuando sea posible para que el navegador muestre progreso correcto.
- 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
- Valida permisos: evita que usuarios no autorizados descarguen PDFs con datos ajenos.
- Protege las rutas: guarda PDFs temporales en directorios fuera de la web público cuando sea posible.
- Limita tamaño y tiempo de vida de PDFs temporales limpia con cron o wp_cron.
- 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 🙂 |
