2010-11-10 8 views
6

Tengo un código PHP que ejecuta una consulta en una base de datos, guarda los resultados en un archivo csv, y luego le permite al usuario descargar el archivo. El problema es que el archivo csv contiene páginas HTML alrededor del contenido csv real.Forzando la descarga de archivos en PHP - dentro de Joomla framework

He leído todas las preguntas relacionadas aquí, incluyendo this one. Lamentablemente, mi código existe en Joomla, por lo que incluso si trato de redireccionar a una página que no contiene más que encabezados, Joomla automáticamente lo rodea con su propio código de navegación. Esto solo ocurre en el momento de la descarga; si miro el archivo csv que está guardado en el servidor, no contiene el HTML.

¿Alguien puede ayudarme con una forma de forzar una descarga del archivo csv real tal como está en el servidor, en lugar de como lo está editando el navegador? He intentado usar la ubicación de cabecera, así:

header('Location: ' . $filename); 

pero se abre el archivo en el navegador, en lugar de forzar el diálogo Guardar.

Aquí está mi código actual:

//set dynamic filename 
$filename = "customers.csv"; 
//open file to write csv 
$fp = fopen($filename, 'w'); 

//get all data 
$query = "select 
    c.firstname,c.lastname,c.email as customer_email, 
    a.email as address_email,c.phone as customer_phone, 
    a.phone as address_phone, 
    a.company,a.address1,a.address2,a.city,a.state,a.zip, c.last_signin 
    from {$dbpre}customers c 
    left join {$dbpre}customers_addresses a on c.id = a.customer_id order by c.last_signin desc"; 

$votes = mysql_query($query) or die ("File: " . __FILE__ . "<br />Line: " . __LINE__ . "<p>{$query}<p>" . mysql_error()); 
$counter = 1; 
while ($row = mysql_fetch_array($votes,1)) { 
    //put header row 
    if ($counter == 1){ 
     $headerRow = array(); 
     foreach ($row as $key => $val) 
      $headerRow[] = $key; 
     fputcsv($fp, $headerRow); 
    } 
    //put data row 
    fputcsv($fp, $row); 
    $counter++; 
} 

//close file 
fclose($fp); 

//redirect to file 
header("Content-type: application/octet-stream"); 
header("Content-Disposition: attachment; filename=".$filename); 
header("Content-Transfer-Encoding: binary"); 
readfile($filename); 
exit; 

EDITS URL completa se ve así:

http://mysite.com/administrator/index.php?option=com_eimcart&task=customers 

con el enlace de descarga real con este aspecto:

http://mysite.com/administrator/index.php?option=com_eimcart&task=customers&subtask=export 

MOR E EDICIONES Aquí hay una foto de la página en la que se encuentra el código; el archivo generado todavía está tirando en el html para el submenú. El código para el enlace seleccionado (Exportar como CSV) es ahora

index.php?option=com_eimcart&task=customers&subtask=export&format=raw 

alt text

Ahora aquí está una captura de pantalla del archivo generado, guardada:

alt text

Se redujo durante el cargue aquí, pero el texto resaltado en amarillo es el código html para el subnav (enumere clientes, agregue nuevos clientes, exporte como csv). Así es como se ve mi código completo ahora; si pudiera deshacerme de ese último bit de html, sería perfecto.

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

      $query = "select c.firstname,c.lastname,c.email as customer_email, 
         a.email as address_email,c.phone as customer_phone, 
         a.phone as address_phone, a.company, a.address1, 
         a.address2,a.city,a.state,a.zip,c.last_signin 

         from {$dbpre}customers c 
         left join {$dbpre}customers_addresses a on c.id = a.customer_id 
         order by c.last_signin desc"; 

      $votes = mysql_query($query) or die ("File: " . __FILE__ . "<br />Line: " . __LINE__ . "<p>{$query}<p>" . mysql_error()); 
      $counter = 1; 

      //redirect to file 
      header("Content-type: application/octet-stream"); 
      header("Content-Disposition: attachment; filename=customers.csv"); 
      header("Content-Transfer-Encoding: binary"); 

      while ($row = mysql_fetch_array($votes,1)) { 

        //put header row 
        if ($counter == 1){ 
          $headerRow = array(); 
          foreach ($row as $key => $val) 
            $headerRow[] = $key; 

          fputcsv($fp, $headerRow); 
        } 

        //put data row 
        fputcsv($fp, $row); 
       $counter++; 
      } 

      //close file 
      fclose($fp); 

ACTUALIZACIÓN Björn

Aquí está el código (creo) que trabajó para mí. Utilizar el parámetro RAW en el enlace que llama a la acción:

index.php?option=com_eimcart&task=customers&subtask=export&format=raw 

Debido a que este era de procedimiento, era nuestro enlace en un archivo llamado clientes.php, que se ve así:

switch ($r['subtask']){ 
    case 'add': 
    case 'edit': 
     //if the form is submitted then go to validation 
       include("subnav.php"); 
     if ($r['custFormSubmitted'] == "true") 
      include("validate.php"); 
     else 
      include("showForm.php"); 
     break; 

    case 'delete': 
       include("subnav.php"); 
     include("process.php"); 
      break; 

    case 'resetpass': 
       include("subnav.php"); 
     include("resetpassword"); 
      break; 

    case 'export': 
     include("export_csv.php"); 
      break; 


    default: 
       include("subnav.php"); 
     include("list.php"); 
     break; 
} 

Así que cuando una usuario hizo clic en el enlace de arriba, el archivo export_csv.php se incluye automáticamente.Ese archivo contiene todo el código real:

<? 
header("Content-type: application/octet-stream"); 
header("Content-Disposition: attachment; filename=customers.csv"); 
header("Content-Transfer-Encoding: binary"); 
$fp= fopen("php://output", 'w'); 


//get all data 
$query = "select 
    c.firstname,c.lastname,c.email as customer_email, 
    a.email as address_email,c.phone as customer_phone, 
    a.phone as address_phone, 
    a.company,a.address1,a.address2,a.city,a.state,a.zip, c.last_signin 

    from {$dbpre}customers c 

    left join {$dbpre}customers_addresses a on c.id = a.customer_id order by c.last_signin desc"; 


$votes = mysql_query($query) or die ("File: " . __FILE__ . "<br />Line: " . __LINE__ . "<p>{$query}<p>" . mysql_error()); 
$counter = 1; 

while ($row = mysql_fetch_array($votes,1)) { 

    //put header row 
    if ($counter == 1){ 
     $headerRow = array(); 
     foreach ($row as $key => $val) 
      $headerRow[] = $key; 

     fputcsv($fp, $headerRow); 
    } 

    //put data row 
    fputcsv($fp, $row); 
    $counter++; 
} 

//close file 
fclose($fp); 
+0

Probablemente necesite desactivar la reescritura de URL de Joomla para este caso específico. ¿Puedes mostrar algunas URL completas y tu archivo .htaccess? –

+0

Editando OP para incluir la información solicitada. – EmmyS

+0

Ahh bien, eso es diferente: Joomla probablemente golpea el encabezado/pie de página en la salida en el archivo de índice, esto no se puede arreglar agregando una regla en .htaccess ... Necesita un experto en Joomla para resolver –

Respuesta

3

Esta es una pieza de código de ejemplo que acabo de cocinado hasta ayudarte. Úselo como un método de acción en su controlador.

function get_csv() { 
     $file = JPATH_ADMINISTRATOR . DS . 'test.csv'; 

     // Test to ensure that the file exists. 
     if(!file_exists($file)) die("I'm sorry, the file doesn't seem to exist."); 

     // Send file headers 
     header("Content-type: text/csv"); 
     header("Content-Disposition: attachment;filename=test.csv"); 

     // Send the file contents. 
     readfile($file); 
    } 

Esto solo no será suficiente, porque el archivo que descargue aún contendrá el html que lo rodea. Para deshacerse de él y solo recibir el contenido del archivo csv, debe agregar format = raw parameter a su solicitud. En mi caso, el método está dentro del componente com_csvexample, por lo que la URL sería:

/index.php?option=com_csvexample&task=get_csv&format=raw 

EDITAR

Con el fin de evitar el uso de un archivo intermedio sustituir

//set dynamic filename 
$filename = "customers.csv"; 
//open file to write csv 
$fp = fopen($filename, 'w'); 

con

//open the output stream for writing 
//this will allow using fputcsv later in the code 
$fp= fopen("php://output", 'w'); 

Usando este método y Tienes que mover el código que envía los encabezados antes de escribir algo en la salida. Tampoco necesitará la llamada a la función readfile.

+0

Acabo de analizar tu código un poco más en profundidad. ¿Hay alguna razón por la que desee que el archivo csv se almacene en el servidor o simplemente se haya creado para que pueda enviarlo al navegador más adelante? Si no lo necesita para permanecer en el servidor, puede enviar la csv directamente a la secuencia de salida con eco, ahorrándose muchos problemas ... Simplemente recuerde enviar los encabezados primero :) – silvo

+0

Este es el código que heredé, por lo que Estaba trabajando con lo que estaba allí. Realmente no hay razón para guardar el archivo en el servidor. ¿Podrías dar más detalles sobre el envío a través de echo? ¿Seguirá creando un archivo que el usuario descargará en su computadora? ¿O solo mostrará la csv en el navegador? – EmmyS

+0

Por favor, eche un vistazo a mi respuesta ammended. Imprimir el csv directamente a la salida no es diferente funcionalmente de lo que su código está haciendo ahora. El comportamiento del navegador depende de los encabezados que envíe. Como está enviando un encabezado informando al navegador que el contenido es de hecho un archivo adjunto, activará la descarga de un archivo. – silvo

3

Añadir este método para su controlador:

function exportcsv() { 
    $model = & $this->getModel('export'); 
    $model->exportToCSV(); 
} 

A continuación, agregue un nuevo modelo llamado export.php, código de abajo. Tendrá que cambiar o extender el código a su situación.

<?php 
/** 
* @package TTVideo 
* @author Martin Rose 
* @website www.toughtomato.com 
* @version 2.0 
* @copyright Copyright (C) 2010 Open Source Matters. All rights reserved. 
* @license http://www.gnu.org/copyleft/gpl.html GNU/GPL 
*/ 

//No direct acesss 
defined('_JEXEC') or die(); 
jimport('joomla.application.component.model'); 
jimport('joomla.filesystem.file'); 
jimport('joomla.filesystem.archive'); 
jimport('joomla.environment.response'); 

class TTVideoModelExport extends JModel 
{ 

    function exportToCSV() { 
    $files = array(); 
    $file = $this->__createCSVFile('#__ttvideo'); 
    if ($file != '') $files[] .= $file; 
    $file = $this->__createCSVFile('#__ttvideo_ratings'); 
    if ($file != '') $files[] .= $file; 
    $file = $this->__createCSVFile('#__ttvideo_settings'); 
    if ($file != '') $files[] .= $file; 
    // zip up csv files to be delivered 
    $random = rand(1, 99999); 
    $archive_filename = JPATH_SITE.DS.'tmp'.DS.'ttvideo_'. strval($random) .'_'.date('Y-m-d').'.zip'; 
    $this->__zip($files, $archive_filename); 
    // deliver file 
    $this->__deliverFile($archive_filename); 
    // clean up 
    JFile::delete($archive_filename); 
    foreach($files as $file) JFile::delete(JPATH_SITE.DS.'tmp'.DS.$file); 
    } 

    private function __createCSVFile($table_name) { 
    $db = $this->getDBO(); 
    $csv_output = ''; 

    // get table column names 
    $db->setQuery("SHOW COLUMNS FROM `$table_name`"); 
    $columns = $db->loadObjectList(); 

    foreach ($columns as $column) { 
     $csv_output .= $column->Field.'; '; 
    } 
    $csv_output .= "\n"; 

    // get table data 
    $db->setQuery("SELECT * FROM `$table_name`"); 
    $rows = $db->loadObjectList(); 
    $num_rows = count($rows); 
    if ($num_rows > 0) { 
     foreach($rows as $row) { 
     foreach($row as $col_name => $value) { 
      $csv_output .= $value.'; '; 
     } 
     $csv_output .= "\n"; 
     } 
    } 
    $filename = substr($table_name, 3).'.csv'; 
    $file = JPATH_SITE.DS.'tmp'.DS.$filename; 
    // write file to temp directory 
    if (JFile::write($file, $csv_output)) return $filename; 
    else return ''; 
    } 

    private function __deliverFile($archive_filename) { 
    $filesize = filesize($archive_filename); 
    JResponse::setHeader('Content-Type', 'application/zip'); 
    JResponse::setHeader('Content-Transfer-Encoding', 'Binary'); 
    JResponse::setHeader('Content-Disposition', 'attachment; filename=ttvideo_'.date('Y-m-d').'.zip'); 
    JResponse::setHeader('Content-Length', $filesize); 
    echo JFile::read($archive_filename); 
    } 

    /* creates a compressed zip file */ 
    private function __zip($files, $destination = '') { 
    $zip_adapter = & JArchive::getAdapter('zip'); // compression type 
    $filesToZip[] = array(); 
    foreach ($files as $file) { 
     $data = JFile::read(JPATH_SITE.DS.'tmp'.DS.$file); 
     $filesToZip[] = array('name' => $file, 'data' => $data); 
    } 
    if (!$zip_adapter->create($destination, $filesToZip, array())) { 
     global $mainframe; 
     $mainframe->enqueueMessage('Error creating zip file.', 'message'); 
    } 
    } 


} 
?> 

Luego vaya a su view.php predeterminado y agregue un botón personalizado, p. Ej.

// custom export to set raw format for download 
$bar = & JToolBar::getInstance('toolbar'); 
$bar->appendButton('Link', 'export', 'Export CSV', 'index.php?option=com_ttvideo&task=export&format=raw'); 

¡Buena suerte!

+0

Gracias, Martin, pero este código (que heredé) se hace procesalmente, no en mvc. – EmmyS

0

Puede usar Apache's mod_cern_meta para agregar encabezados HTTP a archivos estáticos. Content-Disposition: attachment. Los archivos requeridos .htaccess y .meta pueden ser creados por PHP.

0

Otra forma de generar datos CSV en una aplicación Joomla es crear una vista utilizando CSV en lugar de formato HTML. Es decir, crear un archivo de la siguiente manera:

componentes/com_mycomp/views/algo/view.csv.php

y añada contenido similar al siguiente:

<?php 
// No direct access 
defined('_JEXEC') or die; 

jimport('joomla.application.component.view'); 

class MyCompViewSomething extends JViewLegacy // Assuming a recent version of Joomla! 
{   
    function display($tpl = null) 
    { 
     // Set document properties 
     $document = &JFactory::getDocument(); 
     $document->setMimeEncoding('text/csv'); 

     JResponse::setHeader('Content-disposition', 'inline; filename="something.csv"', true); 

     // Output UTF-8 BOM 
     echo "\xEF\xBB\xBF"; 

     // Output some data 
     echo "field1, field2, 'abc 123', foo, bar\r\n"; 
    } 
} 
?> 

continuación, puede crear archivos descarga enlaces de la siguiente manera:

/index.php?option=com_mycomp & vista = algo formato csv = &

Ahora, estarías en lo cierto al cuestionar la parte 'en línea' en la disposición del contenido. Si recuerdo correctamente al escribir este código hace algunos años, tuve problemas con la opción 'archivo adjunto'. Este enlace que acabo de buscar en Google ahora me resulta familiar como el controlador para él: https://dotanything.wordpress.com/2008/05/30/content-disposition-attachment-vs-inline/. He estado usando 'en línea' desde entonces y todavía me piden que guarde el archivo de manera apropiada desde cualquier navegador con el que realice la prueba.No he intentado usar 'attachment' en cualquier momento recientemente, por lo que puede funcionar bien ahora, por supuesto (¡el enlace ya tiene 7 años!)