Форсирование загрузки файлов в PHP – внутри рамки Joomla

У меня есть код PHP, который запускает запрос в базе данных, сохраняет результаты в файл csv и затем позволяет пользователю загружать файл. Проблема в том, что csv-файл содержит HTML-страницу вокруг фактического содержимого csv.

Здесь я уже прочитал все связанные с этим вопросы, включая этот . К сожалению, мой код существует в Joomla, поэтому, даже если я попытаюсь перенаправить на страницу, которая содержит только заголовки, Joomla автоматически окружает ее своим собственным навигационным кодом. Это происходит только во время загрузки; если я посмотрю на файл csv, который сохранен на сервере, он не содержит HTML.

Может ли кто-нибудь помочь мне с тем, чтобы заставить загружать фактический файл CSV, как он есть на сервере, а не как его редактирует браузер? Я попытался использовать расположение заголовка, например:

header('Location: ' . $filename); 

но он открывает файл в браузере, а не форсирует диалог сохранения.

Вот мой текущий код:

 //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-адрес выглядит следующим образом:

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

с фактической ссылкой для загрузки, выглядящей так:

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

БОЛЬШЕ РЕДАКТИРОВАНИЙ Вот снимок страницы, на которой находится код; сгенерированный файл по-прежнему втягивает html для подменю. Код для выбранной ссылки (Export as CSV) теперь

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

alt text

Теперь вот скриншот сгенерированного, сохраненного файла:

alt text

Он сократился во время загрузки здесь, но текст, выделенный желтым цветом, является html-кодом для subnav (список клиентов, добавление нового клиента, экспорт как csv). Вот как выглядит мой полный код сейчас; если бы я мог просто избавиться от этого последнего фрагмента html, это было бы прекрасно.

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

ОБНОВЛЕНИЕ ДЛЯ BJORN

Вот код (я думаю), который работал на меня. Используйте параметр RAW в ссылке, которая вызывает действие:

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

Поскольку это было процедурно, наша ссылка была в файле customer.php, который выглядит следующим образом:

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

Поэтому, когда пользователь нажал ссылку выше, файл export_csv.php автоматически включается. Этот файл содержит весь фактический код:

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

Это образец кода, который я только что приготовил, чтобы помочь вам. Используйте его как метод действий в вашем контроллере.

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

Одного этого будет недостаточно, потому что файл, который вы загружаете, по-прежнему будет содержать окружающий html. Чтобы избавиться от него и получить содержимое csv-файла, вам нужно добавить format = raw параметр в ваш запрос. В моем случае метод находится внутри компонента com_csvexample, поэтому URL-адрес будет:

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

РЕДАКТИРОВАТЬ

Во избежание использования промежуточного файла

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

с

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

Используя этот метод, вам нужно переместить код, который отправляет заголовки, прежде чем что-либо будет записано на выход. Вам также не потребуется вызов функции readfile .

Добавьте этот метод к контроллеру:

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

Затем добавьте новую модель под названием export.php, код ниже. Вам нужно будет изменить или расширить код в вашей ситуации.

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

Затем перейдите к вашему представлению по умолчанию view.php и добавьте пользовательский баттом, например

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

Удачи!

Вы можете использовать mod_cern_meta от Apache, чтобы добавлять заголовки HTTP в статические файлы. Content-Disposition: attachment . Необходимые файлы .htaccess и .meta могут быть созданы PHP.

Другой способ вывода CSV-данных в приложение Joomla – создать представление, используя формат CSV, а не HTML. То есть, создайте файл следующим образом:

компоненты / com_mycomp / вид / что-то / view.csv.php

И добавьте контент, подобный следующему:

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

Затем вы можете создавать ссылки для загрузки файлов следующим образом:

/index.php?option=com_mycomp&view=something&format=csv

Теперь вы были бы правы в вопросе «встроенной» части в Content-disposition. Если я правильно помню, когда писал этот код несколько лет назад, у меня были проблемы с опцией «вложения». Эта ссылка, которую я просто искал в Google, теперь была знакома как драйвер для нее: https://dotanything.wordpress.com/2008/05/30/content-disposition-attachment-vs-inline/ . Я использую «inline» с тех пор, и мне все еще предлагается сохранить файл соответствующим образом из любых браузеров, с которыми я тестирую. Я не пробовал использовать «привязанность» в последнее время, так что теперь это может нормально работать (ссылка уже 7 лет!)