Come posso forzare il download di un file nel backend di WordPress?
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

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/'
):
(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:
(fonte: mikeschinkel.com)
E infine ecco uno screenshot del download in corso:
(fonte: mikeschinkel.com)

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

@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 - 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.

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 - template_redirect
è più per l'accesso esterno. Prova il secondo esempio che ho pubblicato.

È 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.

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();

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.
