Función de exportar CSV en mi plugin de WordPress

28 ago 2012, 01:39:59
Vistas: 15.1K
Votos: 2

Necesito construir una función de "exportar como .csv" en mi plugin. El problema es que recibo la advertencia de cabeceras ya enviadas, porque cuando se llama a mi función "exportar", WordPress ya ha comenzado a renderizar.

La forma en que lo configuré es mediante un pequeño formulario en la pantalla de Configuración, cuyo atributo action apunta a un archivo (download.csv.php) en la carpeta de mi plugin, que no debería mostrarse, solo debería mostrar un diálogo de descarga de archivo para el volcado CSV.

Aquí está mi código.

En la pantalla de Configuración de mi 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('Descargar el registro (.csv)', $this->localizationDomain); ?>" />
        </form>

Y el archivo 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'] = "CUANDO";
        $CSVHeader['who'] = "QUIÉN";
        $CSVHeader['description'] = "DESCRIPCIÓN";
        $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;

    } 

¿Qué estoy haciendo mal?

0
Todas las respuestas a la pregunta 3
1

El inicio de lo que estás haciendo mal puede explicarse con esta cita:

cuyo atributo action apunta a un archivo (download.csv.php) en mi carpeta de plugin, que no debería mostrarse, solo activar un diálogo de descarga para el volcado CSV.

y este código:

$core = $_POST['download'].'wp-load.php';
if(isset($_POST['download']) && is_file($core)){
    require_once( $core );

Básicamente lo que estás haciendo es a) salir del entorno de WordPress y acceder directamente a una parte de tu plugin y b) luego intentar cargar el entorno de WordPress desde dentro de ese archivo (y haciéndolo de una manera extremadamente insegura, por cierto).

En lugar de hacer eso, es mejor permanecer dentro del entorno de WordPress desde el principio y sobrescribir la salida para que sea como tú deseas.

Una mejor manera sería permanecer en el administrador, enganchar a admin_init, y detectar cuándo necesitas obtener tu salida CSV para devolverla en su lugar.

Así que para tu formulario, haz algo como esto:

<form method="post" id="download_form" action="">
            <input type="submit" name="download_csv" class="button-primary" value="<?php _e('Descargar el registro (.csv)', $this->localizationDomain); ?>" />
    </form>

Observa que aquí no se usa action. Esto significa que se está enviando de vuelta a tu misma página de administración, sin cambios. Ahora, puedes detectar eso en cualquier función que tengas conectada al gancho de acción admin_init, así:

global $plugin_page;
if ( isset($_POST['download_csv']) && $plugin_page == 'whatever' ) {
    echo "HOLA"; die;
}

La variable global $plugin_page estará configurada a page=whatever que tengas en tu pantalla de configuración normal del plugin. En lugar de imprimir "HOLA" como hice aquí, llama a una función en tu plugin para generar y enviar ese CSV correctamente, cabeceras y todo, y luego die. O algo por el estilo.

Ten en cuenta que esto es simplista y puede ser inseguro. También puedes querer hacer una verificación de nonce y de capacidades aquí, para asegurarte de que el usuario tiene permiso para descargar este CSV y tenía la intención de hacerlo.

28 ago 2012 01:59:39
Comentarios

Increíble. Sabía que lo estaba haciendo mal, pero tus explicaciones me pusieron en marcha. ¡¡Muchas gracias!!

pixeline pixeline
28 ago 2012 02:31:08
1

Código completo actualizado.

En la actualización de tu plugin

<form method="post" id="download_form" action="">

<input type="submit" name="download_csv" class="button-primary" value="<?php _e('Descargar el registro (.csv)', $this->localizationDomain); ?>" />

</form>

Ahora en tu 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[] = "primera_columna";
        $csv_fields[] = 'segunda_columna';

        $output_filename = 'nombre_archivo' .'.csv';
        $output_handle = @fopen('php://output', 'w');

        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Content-Description: Transferencia de archivo');
        header('Content-type: text/csv');
        header('Content-Disposition: attachment; filename=' . 
        $output_filename);
        header('Expires: 0');
        header('Pragma: public');

        $first = true;
       // Convertir resultados a formato csv
        foreach ($rows as $row) {

       // Añadir encabezados de tabla
            if ($first) {

               $titles = array();

                foreach ($row as $key => $val) {

                    $titles[] = $key;

                }

                fputcsv($output_handle, $titles);

                $first = false;
            }

            $leadArray = (array) $row; // Convertir el Objeto a un array
            // Añadir fila al archivo
            fputcsv($output_handle, $leadArray);
        }

        //echo '<a href="'.$output_handle.'">test</a>';

        // Cerrar el flujo del archivo de salida
        fclose($output_handle);

        die();
    }
  }
}
2 may 2017 16:46:51
Comentarios

Especialmente gracias por la parte de add_action("admin_init", "download_csv"); :)

Jasom Dotnet Jasom Dotnet
1 jun 2017 10:18:16
0

Tuve algunos problemas con los métodos anteriores - add_action. No quería que el plugin ejecutara esta funcionalidad automáticamente. En su lugar, necesitaba una solución desde el área de administración.

1. Descubrí que hay que limpiar la caché o el código HTML aparece en el archivo CSV.
2. Es necesario hacer "exit" para terminar el proceso.

Aquí está mi solución. Cambia las tablas y campos según tus necesidades:

 function Export()
 {
  global $wpdb; 

  header('Content-Type: text/csv');
  header('Content-Disposition: attachment; filename="export.csv"');

 // limpiar el buffer de salida
 ob_end_clean();

 $fp = fopen('php://output', 'w');

 $header_row = array(
    0 => 'etiqueta campo 1',
    1 => 'etiqueta campo 2',
    2 => 'etiqueta campo 3',
     );


 fputcsv($fp, $header_row); 

 $Table_Name   = $wpdb->prefix.'TuTabla'; 
 $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['campo1'],$Record['campo2'],$Record['campo3']);   
  fputcsv($fp, $OutputRecord);       
   }
  }

fclose( $fp );
exit;
}

ACTUALIZACIÓN: Desde que escribí esto, si usas este código mediante shortcode o llamada directa desde el frontend, los datos del header HTML aparecen en el archivo. Si lo usas en el backend / área de administración, funciona perfectamente.

2 abr 2019 23:42:07