funzionalità di esportazione CSV nel mio plugin
Devo implementare una funzionalità di "esporta come .csv" nel mio plugin. Il problema è che ottengo l'avviso "header already sent", perché quando viene chiamata la mia funzione "export", WordPress ha già iniziato il rendering.
L'ho impostato tramite un piccolo modulo nella schermata delle Impostazioni, il cui attributo action punta a un file (download.csv.php) nella cartella del mio plugin, che non dovrebbe essere visualizzato ma solo mostrare una finestra di dialogo per il download del dump CSV.
Ecco il mio codice.
Nella schermata delle Impostazioni del mio plugin:
<form method="post" id="download_form" action="<?php echo plugins_url('download.csv.php', __FILE__); ?>">
<input type="hidden" name="download" value="<?php echo get_home_path(); ?>" />
<input type="submit" name="download_csv" class="button-primary" value="<?php _e('Scarica il log (.csv)', $this->localizationDomain); ?>" />
</form>
E il file download.csv.php:
$core = $_POST['download'].'wp-load.php';
if(isset($_POST['download']) && is_file($core)){
require_once($core);
global $wpdb;
$rows = $wpdb->get_results('SELECT * FROM `'.$this->dbName.'`;', ARRAY_A);
if($rows){
require_once('classes/csvmaker.class.php');
$CSV = new CSVMaker();
$CSVHeader = array();
$CSVHeader['id'] = "ID";
$CSVHeader['when'] = "QUANDO";
$CSVHeader['who'] = "CHI";
$CSVHeader['description'] = "DESCRIZIONE";
$CSVHeader['type'] = "TIPO";
foreach($rows as $r){
$CSV->addEntry($r);
}
$file_name = plugin_dir_path(__FILE__).'data.csv';
file_put_contents($file_name, $CSV->buildDoc);
ob_start();
header("Expires: Mon, 1 Apr 1974 05:00:00 GMT");
header("Last-Modified: " . gmdate("D,d M YH:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
header("Content-type: application/CSV");
header("Content-Disposition: attachment; filename=$file_name.csv");
echo $CSV->buildDoc;
return ob_get_clean();
exit;
}
Cosa sto sbagliando?

L'inizio di ciò che stai facendo male può essere spiegato da questa citazione:
l'attributo action punta a un file (download.csv.php) nella mia cartella del plugin, che non dovrebbe essere visualizzato, ma solo mostrare una finestra di dialogo per il download del dump CSV.
e questo codice:
$core = $_POST['download'].'wp-load.php';
if(isset($_POST['download']) && is_file($core)){
require_once( $core );
Quello che stai fondamentalmente facendo è a) uscire dall'ambiente WordPress e accedere direttamente a una parte del tuo plugin e b) poi cercare di caricare l'ambiente WordPress dall'interno di quel file (e farlo in un modo estremamente insicuro, aggiungerei).
Invece di fare così, è meglio rimanere dall'inizio nell'ambiente WordPress e sovrascrivere l'output nel modo desiderato.
Un modo migliore sarebbe rimanere nell'admin, agganciarsi a admin_init, e rilevare quando hai bisogno di ottenere il tuo output CSV e restituire quello invece.
Quindi per il tuo form, fai qualcosa del genere:
<form method="post" id="download_form" action="">
<input type="submit" name="download_csv" class="button-primary" value="<?php _e('Scarica il log (.csv)', $this->localizationDomain); ?>" />
</form>
Nota che qui non viene usato alcun action. Questo significa che sta inviando nuovamente alla stessa pagina admin, senza modifiche. Ora, puoi rilevarlo in qualsiasi funzione hai collegato all'hook admin_init, così:
global $plugin_page;
if ( isset($_POST['download_csv']) && $plugin_page == 'whatever' ) {
echo "CIAO"; die;
}
La variabile globale $plugin_page sarà impostata su page=whatever che hai nella schermata delle impostazioni normale del tuo plugin. Invece di stampare "CIAO" come ho fatto qui, chiama una funzione nel tuo plugin per generare e restituire quel CSV correttamente, intestazioni e tutto, e poi die. O qualcosa del genere.
Nota che questo è semplificato e potrebbe essere insicuro. Potresti anche voler aggiungere un nonce e un controllo delle capacità qui, per assicurarti che l'utente sia autorizzato a scaricare questo CSV e abbia intenzione di farlo.

Ecco il codice completo aggiornato.
Nel tuo plugin aggiornato
<form method="post" id="download_form" action="">
<input type="submit" name="download_csv" class="button-primary" value="<?php _e('Scarica il log (.csv)', $this->localizationDomain); ?>" />
</form>
Ora nella tua function.php
add_action("admin_init", "download_csv");
function download_csv() {
if (isset($_POST['download_csv'])) {
global $wpdb;
$sql = "SELECT * FROM {$wpdb->prefix}table_name";
$rows = $wpdb->get_results($sql, 'ARRAY_A');
if ($rows) {
$csv_fields = array();
$csv_fields[] = "prima_colonna";
$csv_fields[] = 'seconda_colonna';
$output_filename = 'nome_file' .'.csv';
$output_handle = @fopen('php://output', 'w');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Description: File Transfer');
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename=' .
$output_filename);
header('Expires: 0');
header('Pragma: public');
$first = true;
// Converti i risultati in formato csv
foreach ($rows as $row) {
// Aggiungi intestazioni della tabella
if ($first) {
$titles = array();
foreach ($row as $key => $val) {
$titles[] = $key;
}
fputcsv($output_handle, $titles);
$first = false;
}
$leadArray = (array) $row; // Converti l'oggetto in array
// Aggiungi riga al file
fputcsv($output_handle, $leadArray);
}
//echo '<a href="'.$output_handle.'">test</a>';
// Chiudi lo stream del file di output
fclose($output_handle);
die();
}
}
}

Ho riscontrato alcuni problemi con i metodi sopra citati - add_action. Non volevo che il plugin eseguisse questa funzionalità automaticamente. Invece avevo bisogno di una soluzione per l'area amministrativa.
1. Ho scoperto che è necessario pulire la cache altrimenti l'output HTML finisce nel file csv.
2. Bisogna "terminare" il processo con exit.
Ecco la mia soluzione. Modifica le tabelle e i campi in base alle tue esigenze:
function Export()
{
global $wpdb;
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="export.csv"');
// pulisce il buffer di output
ob_end_clean();
$fp = fopen('php://output', 'w');
$header_row = array(
0 => 'etichetta campo 1',
1 => 'etichetta campo 2',
2 => 'etichetta campo 3',
);
fputcsv($fp, $header_row);
$Table_Name = $wpdb->prefix.'YourTable';
$sql_query = $wpdb->prepare("SELECT * FROM $Table_Name", 1) ;
$rows = $wpdb->get_results($sql_query, ARRAY_A);
if(!empty($rows))
{
foreach($rows as $Record)
{
$OutputRecord = array($Record['field1'],$Record['field2'],$Record['field3']);
fputcsv($fp, $OutputRecord);
}
}
fclose( $fp );
exit;
}
AGGIORNAMENTO: Da quando ho scritto questo codice, i dati dell'header HTML finiscono nel file se usi il codice tramite shortcode o chiamata diretta sul frontend. Usandolo nella sezione backend/amministrativa, funziona perfettamente.
