¿Cómo puedo forzar la descarga de un archivo en el backend de WordPress?

31 oct 2010, 18:29:28
Vistas: 59.3K
Votos: 34

Me gustaría agregar un botón "Click para descargar" a uno de mis plugins de WordPress, y no estoy seguro de qué hook usar. Hasta ahora, enganchar 'admin_init' a este código parece funcionar:

// Configurar los headers para forzar la descarga
header("Content-type: application/x-msdownload");
header("Content-Disposition: attachment; filename=data.csv");
header("Pragma: no-cache");
header("Expires: 0");
echo 'data';
exit();

Esto parece funcionar, pero solo quiero ver si existe alguna mejor práctica.

Gracias, Dave

0
Todas las respuestas a la pregunta 3
8
46

Si te entiendo correctamente, deseas tener una URL similar a la siguiente cuya respuesta al navegador será el contenido que generes, es decir, tu archivo .CSV y sin contenido generado por WordPress?

http://example.com/download/data.csv

Creo que estás buscando el hook 'template_redirect'. Puedes encontrar 'template_redirect' en /wp-includes/template-loader.php, que es un archivo que todos los desarrolladores de WordPress deberían conocer; es corto y sencillo y enruta cada carga de página que no sea de administración, así que asegúrate de echarle un vistazo.

Solo añade lo siguiente al archivo functions.php de tu tema o en otro archivo que incluyas en functions.php:

add_action('template_redirect','yoursite_template_redirect');
function yoursite_template_redirect() {
  if ($_SERVER['REQUEST_URI']=='/downloads/data.csv') {
    header("Content-type: application/x-msdownload",true,200);
    header("Content-Disposition: attachment; filename=data.csv");
    header("Pragma: no-cache");
    header("Expires: 0");
    echo 'data';
    exit();
  }
}

Observa la comprobación de la URL '/downloads/data.csv' mediante la inspección de $_SERVER['REQUEST_URI']. También observa el añadido de ,true,200 a tu llamada header() donde estableces el Content-type; esto es porque WordPress habrá establecido el código de estado 404 "No encontrado" porque no reconoce la URL. No hay problema, ya que el true le dice a header() que reemplace el 404 que WordPress había establecido y que use el código de estado HTTP 200 "OK" en su lugar.

Y así es como se ve en FireFox (Nota: la captura de pantalla no tiene un directorio virtual /downloads/ porque después de tomar y anotar la captura de pantalla pareció una buena idea añadir un directorio virtual '/downloads/'):

Captura de pantalla de una URL de descarga para un archivo CSV
(fuente: mikeschinkel.com)

ACTUALIZACIÓN

Si deseas que la descarga se maneje desde una URL que tenga el prefijo /wp-admin/ para dar al usuario la indicación visual de que está protegida por un inicio de sesión, también puedes hacerlo; a continuación se describe una forma.

Esta vez lo encapsulé en una clase llamada DownloadCSV, y creé una "capacidad" de usuario llamada 'download_csv' para el rol de 'administrator' (lee sobre Roles y Capacidades aquí). Podrías simplemente aprovechar el rol predefinido 'export' si lo prefieres, y si es así, solo busca y reemplaza 'download_csv' con 'export' y elimina la llamada a register_activation_hook() y la función activate(). Por cierto, la necesidad de un hook de activación es una de las razones por las que trasladé esto a un plugin en lugar de mantenerlo en el archivo functions.php del tema.*

También añadí una opción de menú "Descargar CSV" en el menú "Herramientas" usando add_submenu_page() y la vinculé a la capacidad 'download_csv'.

Por último, elegí el hook 'plugins_loaded' porque era el hook apropiado más temprano que podía usar. Podrías usar 'admin_init' pero ese hook se ejecuta mucho más tarde (llamada 1130 del hook vs. la llamada 3 del hook), así que ¿por qué dejar que WordPress haga más trabajo innecesario del necesario? (Usé mi plugin Instrument Hooks para averiguar qué hook usar.)

En el hook, verifico que mi URL comience con /wp-admin/tools.php inspeccionando la variable $pagenow, verifico que current_user_can('download_csv') y si eso pasa, entonces pruebo $_GET['download'] para ver si contiene data.csv; si es así, ejecutamos prácticamente el mismo código que antes. También elimino el ,true,200 de la llamada a header() en el ejemplo anterior porque aquí WordPress sabe que es una URL válida, así que aún no había establecido el estado 404. Así que aquí está tu código:

<?php
/*
Plugin Name: Descargar CSV
Author: Mike Schinkel
Author URI: http://mikeschinkel.com
 */
if (!class_exists('DownloadCSV')) {
  class DownloadCSV {
    static function on_load() {
      add_action('plugins_loaded',array(__CLASS__,'plugins_loaded'));
      add_action('admin_menu',array(__CLASS__,'admin_menu'));
      register_activation_hook(__FILE__,array(__CLASS__,'activate'));
    }
    static function activate() {
      $role = get_role('administrator');
      $role->add_cap('download_csv');
    }
    static function admin_menu() {
      add_submenu_page('tools.php',    // Menú Padre
        'Descargar CSV',               // Título de la Página
        'Descargar CSV',               // Etiqueta de la Opción del Menú
        'download_csv',                // Capacidad
        'tools.php?download=data.csv');// URL de la Opción relativa a /wp-admin/
    }
    static function plugins_loaded() {
      global $pagenow;
      if ($pagenow=='tools.php' && 
          current_user_can('download_csv') && 
          isset($_GET['download'])  && 
          $_GET['download']=='data.csv') {
        header("Content-type: application/x-msdownload");
        header("Content-Disposition: attachment; filename=data.csv");
        header("Pragma: no-cache");
        header("Expires: 0");
        echo 'data';
        exit();
      }
    }
  }
  DownloadCSV::on_load();
}

Y aquí hay una captura de pantalla del plugin activado: Captura de pantalla de la página de plugins mostrando un plugin activado
(fuente: mikeschinkel.com)

Y finalmente, aquí hay una captura de pantalla del disparador de la descarga: Captura de pantalla de la descarga de un archivo por URL desde una opción del menú Herramientas del administrador de WordPress
(fuente: mikeschinkel.com)

1 nov 2010 13:09:54
Comentarios

Mike, gracias por tu ayuda. El único problema con esta función es que me gustaría que el archivo se descargara desde el backend. Parece que template_redirect no funciona en el backend, y si no se supone que debo usar admin_init, me pregunto qué debería usar en su lugar. admin_init parece funcionar por ahora, podría quedarme con eso al menos a corto plazo. Es una función menor que solo van a usar unas pocas personas.

Dave Morris Dave Morris
3 nov 2010 04:37:54

@Dave Morris - ¿Puedes definir a qué te refieres con "back end"? ¿Te refieres al servidor? Si es así, 'template_redirect' definitivamente se ejecuta en el servidor. Si no, estoy totalmente confundido; ¿puedes aclarar la preocupación? Gracias de antemano.

MikeSchinkel MikeSchinkel
3 nov 2010 14:00:24

@Dave: Si te refieres al área de administración como "back end", esto seguirá funcionando. La URL de descarga comienza con /downloads/data.csv, que es un archivo inexistente, por lo que el "front end" de WordPress manejará esta solicitud y eventualmente llegará a template-redirect. Simplemente creas un enlace en el área de administración que apunte a esta URL del front. (Debe mencionarse que de esta manera no obtienes la protección de inicio de sesión de administrador de forma gratuita: cualquiera que conozca la URL puede descargar el archivo, pero tal vez haya una manera fácil de solucionar eso?)

Jan Fabry Jan Fabry
3 nov 2010 22:41:37

@Jan Fabry - Ah, ahora entiendo. Por "back end" se refería desde dentro del administrador, ¿verdad? Puede usar la función current_user_can() con el código anterior o tomar otro enfoque. Después de este comentario agregaré una actualización a mi respuesta.

MikeSchinkel MikeSchinkel
4 nov 2010 11:55:45

Sí, me disculpo, no estoy recibiendo alertas por correo electrónico de este sitio, por eso mi demora en responder. Me refería al área de administración de WordPress cuando dije "backend". Lo siento por eso. Intentaré usar template_redirect y veré qué sucede. ¡Gracias! ~Dave

Dave Morris Dave Morris
6 nov 2010 00:47:30

@Dave Morris - template_redirect es más para acceso externo. Prueba el segundo ejemplo que publiqué.

MikeSchinkel MikeSchinkel
6 nov 2010 01:05:30

Esto es excelente. Me ahorraste muchos problemas. Solo quiero aclarar para otros que podrían no necesitar la página de submenú que todo lo que necesitas hacer es enganchar el método admin_menu de la clase DownloadCSV al gancho de acción con la llamada a add_action('admin_menu',array(__CLASS__,'admin_menu')); y agregar tu código para forzar la descarga en el código del método.

racl101 racl101
30 ene 2014 07:16:17

Si deseas usar WP_Query para obtener datos y crear tu archivo descargable, usa el gancho 'admin_init' en lugar de 'plugins_loaded'. De lo contrario, obtendrás el error "Fatal error: Call to a member function get_queried_object_id()".

Gustavo Daniel Gustavo Daniel
15 jul 2016 01:42:48
Mostrar los 3 comentarios restantes
0

Un plugin más útil para exportar a CSV. Puede ser útil para alguien

<?php

class CSVExport
{
    /**
    * Constructor
    */
    public function __construct()
    {
        if(isset($_GET['download_report']))
        {
            $csv = $this->generate_csv();

            header("Pragma: public");
            header("Expires: 0");
            header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
            header("Cache-Control: private", false);
            header("Content-Type: application/octet-stream");
            header("Content-Disposition: attachment; filename=\"report.csv\";" );
            header("Content-Transfer-Encoding: binary");

            echo $csv;
            exit;
        }

        // Añadir elementos extra de menú para administradores
        add_action('admin_menu', array($this, 'admin_menu'));

        // Crear endpoints
        add_filter('query_vars', array($this, 'query_vars'));
        add_action('parse_request', array($this, 'parse_request'));
    }

    /**
    * Añadir elementos extra de menú para administradores
    */
    public function admin_menu()
    {
        add_menu_page('Descargar Reporte', 'Descargar Reporte', 'manage_options', 'download_report', array($this, 'download_report'));
    }

    /**
    * Permitir variables de consulta personalizadas
    */
    public function query_vars($query_vars)
    {
        $query_vars[] = 'download_report';
        return $query_vars;
    }

    /**
    * Analizar la solicitud
    */
    public function parse_request(&$wp)
    {
        if(array_key_exists('download_report', $wp->query_vars))
        {
            $this->download_report();
            exit;
        }
    }

    /**
    * Descargar reporte
    */
    public function download_report()
    {
        echo '<div class="wrap">';
        echo '<div id="icon-tools" class="icon32">
        </div>';
        echo '<h2>Descargar Reporte</h2>';
        //$url = site_url();

        echo '<p>Exportar los Usuarios';
    }

    /**
    * Convertir datos a CSV
    */
    public function generate_csv()
    {
    $csv_output = '';
    $table = 'users';

    $result = mysql_query("SHOW COLUMNS FROM ".$table."");

    $i = 0;
    if (mysql_num_rows($result) > 0) {
        while ($row = mysql_fetch_assoc($result)) {
            $csv_output = $csv_output . $row['Field'].",";
            $i++;
        }
    }
    $csv_output .= "\n";

    $values = mysql_query("SELECT * FROM ".$table."");
    while ($rowr = mysql_fetch_row($values)) {
        for ($j=0;$j<$i;$j++) {
            $csv_output .= $rowr[$j].",";
        }
        $csv_output .= "\n";
    }

    return $csv_output;
    }
}

// Instanciar un singleton de este plugin
$csvExport = new CSVExport();
4 ene 2013 18:26:38
0

El hook admin_init o el hook load-(página) parecen funcionar, WordPress no ha establecido cabeceras en este estado. Estoy usando el hook load-(página) porque se ejecuta cuando se carga una página del menú de administración. Puedes cargar tu script para una página específica.

Puedes revisar el hook load-(página) en el Codex de WordPress

Si estás usando el hook admin_init, asegúrate de verificar el nonce usando check_admin_referer o algún otro script podría pasar la condición y generar la salida de tu archivo de descarga.

24 dic 2014 09:54:38