2010-11-10 4 views
6

J'ai du code PHP qui exécute une requête sur une base de données, enregistre les résultats dans un fichier csv, puis permet à l'utilisateur de télécharger le fichier. Le problème est, le fichier csv contient la page HTML autour du contenu réel de CSV.Forcer le téléchargement de fichiers en PHP - dans le framework Joomla

J'ai déjà lu toutes les questions connexes, y compris this one. Malheureusement, mon code existe dans Joomla, donc même si j'essaye de rediriger vers une page qui ne contient que des en-têtes, Joomla l'entoure automatiquement avec son propre code de navigation. Cela ne se produit qu'au moment du téléchargement. Si je regarde le fichier csv enregistré sur le serveur, il ne contient pas le code HTML.

Quelqu'un peut-il m'aider à forcer le téléchargement du fichier csv actuel tel qu'il est sur le serveur plutôt que d'être modifié par le navigateur? Je l'ai essayé d'utiliser l'emplacement d'en-tête, comme ceci:

header('Location: ' . $filename); 

mais il ouvre le fichier dans le navigateur, plutôt que de forcer la boîte de dialogue Enregistrer.

Voici mon code actuel:

//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 complète ressemble à ceci:

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

avec le lien de téléchargement réel qui ressemble à ceci:

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

MOR E EDITS Voici une photo de la page sur laquelle se trouve le code; le fichier généré tire toujours le html pour le sous-menu. Le code pour le lien sélectionné (Exporter au format CSV) est maintenant

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

alt text

Maintenant, voici une capture d'écran du produit, fichier enregistré:

alt text

Il a diminué au cours de la télécharger ici, mais le texte surligné en jaune est le code html pour le subnav (liste des clients, ajouter un nouveau client, exporter en tant que csv). Voici à quoi ressemble mon code complet maintenant; si je pouvais me débarrasser de ce dernier morceau de HTML, ce serait parfait.

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

MISE À JOUR POUR BJORN

Voici le code (je pense) qui a fonctionné pour moi. Utilisez le RAW dans le lien param qui appelle l'action:

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

Parce que c'était la procédure, notre lien était dans un fichier appelé customers.php, qui ressemble à ceci:

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; 
} 

Alors, quand un l'utilisateur a cliqué sur le lien ci-dessus, le fichier export_csv.php est automatiquement inclus.Ce fichier contient tout le code réel:

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

Vous devrez probablement désactiver la réécriture d'URL de Joomla pour ce cas spécifique. Pouvez-vous montrer des URL complètes et votre fichier .htaccess? –

+0

Modification de OP pour inclure les informations demandées. – EmmyS

+0

Ahh okay, c'est différent: Joomla tape probablement l'entête/pied de page sur la sortie dans le fichier d'index, ceci ne peut pas être corrigé en ajoutant une règle dans .htaccess ... Un expert Joomla doit trier –

Répondre

3

Ceci est un morceau de code exemple que je viens de cuire à vous aider. Utilisez-le comme une méthode d'action dans votre contrôleur. Cela ne suffira pas, car le fichier que vous téléchargez contiendra toujours le code HTML qui l'entoure. Pour s'en débarrasser et ne recevoir que le contenu du fichier csv, vous devez ajouter le paramètre format = raw à votre demande. Dans mon cas, la méthode est à l'intérieur du composant com_csvexample, de sorte que l'URL serait:

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

EDIT

Afin d'éviter d'utiliser un substitut fichier intermédiaire

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

avec

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

En utilisant cette méthode y Vous devez déplacer le code qui envoie les en-têtes avant que quelque chose ne soit écrit dans la sortie. Vous n'aurez également pas besoin de l'appel de la fonction readfile.

+0

Je viens d'analyser votre code un peu plus en profondeur. Y at-il une raison pour laquelle vous voulez que le fichier csv soit stocké sur le serveur ou est-il simplement créé pour que vous puissiez l'envoyer au navigateur plus tard? Si vous n'en avez pas besoin pour rester sur le serveur, vous pouvez envoyer le csv directement au flux de sortie avec echo, vous épargnant beaucoup de problèmes ... N'oubliez pas d'envoyer les en-têtes en premier :) – silvo

+0

Ceci est le code dont j'ai hérité Je travaillais avec ce qui était là. Il n'y a vraiment aucune raison de conserver le fichier sur le serveur. Pourriez-vous élaborer sur l'envoi par écho? Cela créera-t-il toujours un fichier que l'utilisateur téléchargera sur son ordinateur? Ou va-t-il simplement afficher le csv dans le navigateur? – EmmyS

+0

S'il vous plaît jeter un oeil à ma réponse ammended. Imprimer le csv directement à la sortie n'est pas différent fonctionnellement que ce que fait votre code maintenant. Le comportement du navigateur dépend des en-têtes que vous envoyez. Comme vous envoyez un en-tête informant le navigateur que le contenu est en fait une pièce jointe, il déclenchera un téléchargement de fichier. – silvo

3

Ajouter cette méthode à votre contrôleur:

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

Puis ajouter un nouveau modèle appelé export.php, le code ci-dessous. Vous devrez modifier ou étendre le code à votre situation.

<?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'); 
    } 
    } 


} 
?> 

Ensuite, passez à votre view.php par défaut et ajoutez un buttom personnalisé, par ex.

// 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'); 

Bonne chance!

+0

Merci, Martin, mais ce code (dont j'ai hérité) est fait procéduralement, pas en mvc. – EmmyS

0

Vous pouvez utiliser le mod_cern_meta d'Apache pour ajouter des en-têtes HTTP à des fichiers statiques. Content-Disposition: attachment. Les fichiers .htaccess et .meta requis peuvent être créés par PHP.

0

Une autre façon de générer des données CSV dans une application Joomla est de créer une vue en utilisant CSV plutôt que le format HTML. Autrement dit, créer un fichier comme suit:

composants/com_mycomp/vues/quelque chose/view.csv.php

Et ajouter un contenu similaire à ce qui suit:

<?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"; 
    } 
} 
?> 

Ensuite, vous pouvez créer un fichier liens de téléchargement comme suit:

/index.php?option=com_mycomp & view = quelque chose de format & = csv

maintenant, vous auriez raison de questionner la partie 'en ligne' dans la disposition du contenu. Si je me souviens bien en écrivant ce code il y a quelques années, j'ai eu des problèmes avec l'option 'attachement'. Ce lien que je viens de googler maintenant semblait familier comme le pilote pour cela: https://dotanything.wordpress.com/2008/05/30/content-disposition-attachment-vs-inline/. J'utilise 'inline' depuis et je suis toujours invité à enregistrer le fichier de manière appropriée à partir de tous les navigateurs avec lesquels je teste.Je n'ai pas essayé d'utiliser 'attachement' récemment, donc ça peut bien fonctionner maintenant (le lien a 7 ans maintenant!)

Questions connexes