Come posso forzare il download di un file nel backend di WordPress?

31 ott 2010, 18:29:28
Visualizzazioni: 59.3K
Voti: 34

Vorrei aggiungere un pulsante "Clicca per scaricare" a uno dei miei plugin WordPress, e non sono sicuro su quale hook utilizzare. Finora, l'aggancio di 'admin_init' a questo codice sembra funzionare:

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

Questo sembra funzionare, ma vorrei sapere se esiste una best practice da seguire.

Grazie, Dave

0
Tutte le risposte alla domanda 3
8
46

Se ho capito correttamente, vuoi avere un URL simile al seguente la cui risposta al browser sarà il contenuto che generi, cioè il tuo file .CSV e nessun contenuto generato da WordPress?

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

Penso che tu stia cercando l'hook 'template_redirect'. Puoi trovare 'template_redirect' in /wp-includes/template-loader.php che è un file che tutti gli sviluppatori WordPress dovrebbero conoscere; è breve e semplice e gestisce ogni caricamento di pagina non amministrativo, quindi assicurati di darci un'occhiata.

Aggiungi semplicemente il seguente codice al file functions.php del tuo tema o in un altro file che includi in 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();
  }
}

Nota il test per l'URL '/downloads/data.csv' controllando $_SERVER['REQUEST_URI']. Nota anche l'aggiunta di ,true,200 alla tua chiamata header() dove imposti il Content-type; questo perché WordPress avrà impostato il codice di stato 404 "Non trovato" poiché non riconosce l'URL. Non è un problema però, poiché true dice a header() di sostituire il 404 impostato da WordPress e di utilizzare invece il codice di stato HTTP 200 "OK".

Ecco come appare in FireFox (Nota lo screenshot non ha una directory virtuale /downloads/ perché dopo aver scattato e annotato lo screenshot è sembrata una buona idea aggiungere una directory virtuale '/downloads/'):

Screenshot di un URL di download per un file CSV
(fonte: mikeschinkel.com)

AGGIORNAMENTO

Se vuoi che il download venga gestito da un URL con prefisso /wp-admin/ per dare all'utente un'indicazione visiva che è protetto da un login, puoi farlo anche così; segue la descrizione di un modo per farlo.

Questa volta ho incapsulato il tutto in una classe chiamata DownloadCSV e ho creato una "capability" utente chiamata 'download_csv' per il ruolo 'administrator' (leggi su Ruoli e Capability qui) Potresti semplicemente sfruttare il ruolo predefinito 'export' se preferisci e, in tal caso, cerca e sostituisci 'download_csv' con 'export' e rimuovi la chiamata register_activation_hook() e la funzione activate(). A proposito, la necessità di un activation hook è uno dei motivi per cui ho spostato questo in un plugin invece di lasciarlo nel file functions.php del tema.*

Ho anche aggiunto un'opzione di menu "Download CSV" sotto il menu "Strumenti" usando add_submenu_page() e l'ho collegata alla capability 'download_csv'.

Infine ho scelto l'hook 'plugins_loaded' perché era l'hook appropriato più precoce che potevo usare. Potresti usare 'admin_init' ma quell'hook viene eseguito molto più tardi (1130° chiamata di hook rispetto alla 3° chiamata) quindi perché far fare a WordPress più lavoro inutile del necessario? (Ho usato il mio plugin Instrument Hooks per capire quale hook usare.)

Nell'hook verifico che il mio URL inizi con /wp-admin/tools.php controllando la variabile $pagenow, verifico che current_user_can('download_csv') e se tutto passa, allora controllo $_GET['download'] per vedere se contiene data.csv; se sì, eseguiamo praticamente lo stesso codice di prima. Ho anche rimosso ,true,200 dalla chiamata a header() nell'esempio precedente perché qui WordPress sa che è un URL valido quindi non ha ancora impostato lo status 404. Ecco il tuo codice:

<?php
/*
Plugin Name: Download 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',    // Menu Genitore
        'Download CSV',                // Titolo Pagina
        'Download CSV',                // Etichetta Menu
        'download_csv',                // Capability
        'tools.php?download=data.csv');// URL Opzione relativo 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();
}

Ed ecco uno screenshot del plugin attivato: Screenshot della pagina Plugin che mostra un plugin attivato
(fonte: mikeschinkel.com)

E infine ecco uno screenshot del download in corso: Screenshot del download di un file via URL da un'opzione del menu Strumenti di WordPress
(fonte: mikeschinkel.com)

1 nov 2010 13:09:54
Commenti

Mike, grazie per il tuo aiuto. L'unico problema con questa funzionalità è che vorrei che il file venisse scaricato dal backend. Sembra che template_redirect non funzioni sul backend, e se non dovessi usare admin_init, mi chiedo cosa dovrei usare invece. admin_init sembra funzionare per me al momento, potrei mantenerlo almeno nel breve termine. È una funzionalità minore che solo poche persone utilizzeranno.

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

@Dave Morris - Puoi definire cosa intendi con "back end"? Intendi sul server? Se sì, 'template_redirect' sicuramente viene eseguito sul server. Se no, sono totalmente confuso; puoi chiarire il problema? Grazie in anticipo.

MikeSchinkel MikeSchinkel
3 nov 2010 14:00:24

@Dave: Se intendi l'area di amministrazione con "back end", questo funzionerà comunque. L'URL di download inizia con /downloads/data.csv, che è un file inesistente, quindi il "front end" di WordPress gestirà questa richiesta e alla fine raggiungerà template-redirect. Devi solo creare un link nell'area di amministrazione che punti a questo URL frontale. (Va detto che in questo modo non ottieni la protezione del login di amministrazione gratuitamente - chiunque conosca l'URL può scaricare il file, ma forse c'è un modo semplice per risolvere questo problema?)

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

@Jan Fabry - Ah, ora capisco. Con "back end" intendeva dall'interno dell'amministrazione, giusto? Può usare la funzione current_user_can() con il codice sopra o adottare un altro approccio. Dopo questo commento aggiungerò un aggiornamento alla mia risposta.

MikeSchinkel MikeSchinkel
4 nov 2010 11:55:45

Sì, mi scuso, non ricevo avvisi via email da questo sito, quindi questo spiega il mio ritardo nel rispondere. Mi riferivo effettivamente all'area di amministrazione di WordPress quando ho detto "backend". Scusate. Proverò a usare template_redirect e vedrò cosa succede. Grazie! ~Dave

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

@Dave Morris - template_redirect è più per l'accesso esterno. Prova il secondo esempio che ho pubblicato.

MikeSchinkel MikeSchinkel
6 nov 2010 01:05:30

È fantastico. Mi hai risparmiato un sacco di problemi. Voglio solo chiarire per altri che potrebbero non aver bisogno della pagina del sottomenu che tutto ciò che devi fare è agganciare il metodo admin_menu della classe DownloadCSV all'hook dell'azione con la chiamata a add_action('admin_menu',array(__CLASS__,'admin_menu')); e aggiungere il tuo codice per forzare il download nel codice del metodo.

racl101 racl101
30 gen 2014 07:16:17

Se vuoi usare WP_Query per ottenere i dati per creare il tuo file scaricabile, usa l'hook 'admin_init' invece di 'plugins_loaded'. Altrimenti otterrai "Errore fatale: Chiamata a un membro funzione get_queried_object_id()".

Gustavo Daniel Gustavo Daniel
15 lug 2016 01:42:48
Mostra i restanti 3 commenti
0

un altro plugin utile per l'esportazione in CSV. potrebbe essere utile a qualcuno

<?php

class CSVExport
{
    /**
    * Costruttore
    */
    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;
        }

        // Aggiunge voci extra al menu per gli amministratori
        add_action('admin_menu', array($this, 'admin_menu'));

        // Crea endpoint
        add_filter('query_vars', array($this, 'query_vars'));
        add_action('parse_request', array($this, 'parse_request'));
    }

    /**
    * Aggiunge voci extra al menu per gli amministratori
    */
    public function admin_menu()
    {
        add_menu_page('Scarica Report', 'Scarica Report', 'manage_options', 'download_report', array($this, 'download_report'));
    }

    /**
    * Permette variabili di query personalizzate
    */
    public function query_vars($query_vars)
    {
        $query_vars[] = 'download_report';
        return $query_vars;
    }

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

    /**
    * Scarica il report
    */
    public function download_report()
    {
        echo '<div class="wrap">';
        echo '<div id="icon-tools" class="icon32">
        </div>';
        echo '<h2>Scarica Report</h2>';
        //$url = site_url();

        echo '<p>Esporta gli Utenti';
    }

    /**
    * Converte i dati in 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;
    }
}

// Istanzia un singleton di questo plugin
$csvExport = new CSVExport();
4 gen 2013 18:26:38
0

admin_init Hook o load-(page) Hook sembrano funzionare, WordPress non ha impostato l'header in questo stato. Sto usando load-(page) Hook perché viene eseguito quando una pagina del menu di amministrazione viene caricata. Puoi caricare il tuo script per una pagina specifica.

Puoi verificare load-(page) Hook sul WordPress Codex

Se stai usando admin_init Hook assicurati di verificare il nonce usando check_admin_referer oppure altri script potrebbero superare la condizione e ottenere l'output del tuo file da scaricare.

24 dic 2014 09:54:38