2010-11-10 12 views
6

Mam trochę kodu PHP, który uruchamia zapytanie w bazie danych, zapisuje wyniki w pliku csv, a następnie pozwala użytkownikowi pobrać plik. Problem polega na tym, że plik csv zawiera HTML strony wokół rzeczywistej zawartości CSV.Wymuszanie pobierania pliku w PHP - wewnątrz struktury Joomla

Przeczytałem wszystkie powiązane pytania już tutaj, w tym this one. Niestety mój kod istnieje w Joomla, więc nawet jeśli spróbuję przekierować na stronę zawierającą tylko nagłówki, Joomla automatycznie otacza ją swoim własnym kodem nawigacyjnym. Dzieje się tak tylko w momencie pobierania; jeśli spojrzę na plik csv, który jest zapisany na serwerze, nie zawiera kodu HTML.

Czy ktoś może mi pomóc w sposób wymuszający pobranie rzeczywistego pliku csv, tak jak to jest na serwerze, a nie jak jest to edytowane przez przeglądarkę? Próbowałem przy użyciu lokalizacji nagłówka, tak:

header('Location: ' . $filename); 

ale otwiera plik w przeglądarce, raczej niż zmuszając zapisać okno.

Oto mój bieżący kod:

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

edytuje Pełny URL wygląda następująco:

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

z rzeczywistą odnośnik pobierania wygląda tak:

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

MOR E EDITS Oto ujęcie strony, na której jest kod; wygenerowany plik wciąż pobiera w html podmenu. Kod dla wybranego łącza (Eksportuj jako CSV) jest teraz

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

alt text

Teraz tutaj jest zrzut ekranu z wygenerowanym, zapisanego pliku:

alt text

To skurczyła się podczas przesłać tutaj, ale tekst podświetlony na żółto to kod html dla subnav (lista klientów, dodaj nowego klienta, eksportuj jako csv). Oto, jak wygląda teraz mój kompletny kod; gdybym mógł po prostu pozbyć się tego ostatniego fragmentu html, byłoby idealnie.

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

Aktualizacja dla Bjorn

Oto kod (chyba), który pracował dla mnie. Użyj RAW param w linku, który wywołuje działanie:

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

Ponieważ był proceduralny, nasz związek był w pliku o nazwie customers.php, który wygląda tak:

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

więc gdy użytkownik kliknął powyższy link, plik export_csv.php jest automatycznie dołączany.Ten plik zawiera cały aktualny kod:

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

Prawdopodobnie konieczne będzie wyłączenie przepisywania adresu URL Joomla dla tego konkretnego przypadku. Czy możesz pokazać pełne adresy URL i plik .htaccess? –

+0

Edytowanie OP w celu uwzględnienia żądanych informacji. – EmmyS

+0

Ahh okay, to jest inaczej: Joomla prawdopodobnie slapuje nagłówek/stopkę na wyjściu w pliku indeksu, nie można tego naprawić przez dodanie reguły w .htaccess ... Wymaga eksperta od Joomla do uporządkowania –

Odpowiedz

3

To jest kawałek przykładowego kodu, który właśnie przygotowałem, aby ci pomóc. Użyj go jako metody działania w kontrolerze.

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

Samo to nie wystarczy, ponieważ pobrany plik nadal będzie zawierać otaczający go html. Aby się go pozbyć i otrzymać tylko zawartość pliku csv, musisz dodać parametr format = raw do swojego żądania. W moim przypadku metoda jest wewnątrz komponentu com_csvexample, więc url byłoby:

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

EDIT

w celu uniknięcia stosowania plik pośredni zastąpić

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

z

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

Używanie tej metody y Musimy przenieść kod, który wysyła nagłówki, zanim cokolwiek zostanie zapisane na wyjściu. Nie będziesz także potrzebował połączenia z funkcją readfile.

+0

Właśnie przeanalizowałem twój kod nieco głębiej. Czy jest jakiś powód, dla którego chcesz, aby plik csv był przechowywany na serwerze lub czy został właśnie utworzony, abyś mógł później wysłać go do przeglądarki? Jeśli nie potrzebujesz go, aby pozostać na serwerze, możesz wypisać csv bezpośrednio do strumienia wyjściowego za pomocą echa, oszczędzając sobie wielu kłopotów ... Tylko pamiętaj, aby wysłać nagłówki najpierw :) – silvo

+0

To jest kod, który odziedziczyłem, więc Pracowałem z tym, co tam było. Naprawdę nie ma powodu, aby zatrzymać plik na serwerze. Czy mógłbyś rozwinąć wysyłanie za pomocą echa? Czy to nadal spowoduje utworzenie pliku, który użytkownik pobierze na swój komputer? A może po prostu wyświetli csv w przeglądarce? – EmmyS

+0

Proszę spojrzeć na moją udzieloną odpowiedź. Drukowanie csv bezpośrednio na wyjście nie różni się funkcjonalnie od tego, co robi teraz twój kod. Zachowanie przeglądarki zależy od wysyłanych nagłówków. Podczas wysyłania nagłówka informującego przeglądarkę, że zawartość jest w rzeczywistości załącznikiem, spowoduje to pobranie pliku. – silvo

3

Dodaj tę metodę do kontrolera:

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

Następnie dodać nowy model o nazwie export.php, poniższy kod. Będziesz musiał zmienić lub rozszerzyć kod do swojej sytuacji.

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


} 
?> 

Następnie przejdź do domyślnego widoku.php i dodaj niestandardową końcówkę, np.

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

Powodzenia!

+0

Dzięki, Martin, ale ten kod (który odziedziczyłem) jest wykonywany proceduralnie, nie w mvc. – EmmyS

0

Możesz użyć Apache's mod_cern_meta, aby dodać nagłówki HTTP do plików statycznych. Content-Disposition: attachment. Wymagane pliki .htaccess i .meta mogą być tworzone przez PHP.

0

Innym sposobem na dostarczanie danych CSV w aplikacji Joomla jest utworzenie widoku przy użyciu formatu CSV, a nie HTML. Oznacza to, że należy utworzyć plik następująco:

komponenty/com_mycomp/views/coś/view.csv.php

i dodaj zawartość podobny do następującego:

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

Następnie można utworzyć pliku linki do pobrania w następujący sposób:

/index.php?option=com_mycomp & view = coś Format & = CSV

teraz słusznie byłoby kwestionować część "inline" w dyspozycji treści. Jeśli dobrze pamiętam, pisząc ten kod kilka lat temu, miałem problem z opcją "załącznik". Ten link, który właśnie wypróbowałem, teraz wydawał mi się znajomy jako sterownik: https://dotanything.wordpress.com/2008/05/30/content-disposition-attachment-vs-inline/. Używałem 'inline' od tamtej pory i wciąż jestem proszony o zapisanie pliku odpowiednio z każdej przeglądarki, której testuję.Nie próbowałem ostatnio używać "załącznika", więc teraz może działać dobrze (link ma teraz 7 lat!)