【问题标题】:Forcing file download in PHP - inside Joomla framework在 PHP 中强制文件下载 - 在 Joomla 框架内
【发布时间】:2010-11-10 20:58:13
【问题描述】:

我有一些 PHP 代码在数据库上运行查询,将结果保存到 csv 文件,然后允许用户下载文件。问题是,csv 文件包含实际 csv 内容周围的页面 HTML。

我已经阅读了这里的所有相关问题,包括this one。不幸的是,我的代码存在于 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;

编辑 完整的 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。所选链接的代码(导出为 CSV)现在是

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

现在这里是生成的保存文件的屏幕截图:

在此处上传时缩小了,但以黄色突出显示的文本是子导航的 html 代码(列出客户、添加新客户、导出为 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

因为这是程序性的,所以我们的链接在一个名为 customers.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;
}

因此,当用户单击上面的链接时,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);

【问题讨论】:

  • 您可能需要针对这种特定情况关闭 Joomla 的 URL 重写。你能显示一些完整的 URL 和你的 .htaccess 文件吗?
  • 编辑 OP 以包含请求的信息。
  • 啊,好吧,这是不同的:Joomla 可能会在索引文件的输出中添加页眉/页脚,这无法通过在 .htaccess 中添加规则来解决...需要 Joomla 专家整理
  • 不确定在我们的开发服务器上的何处查找 .htaccess 文件 - 在站点的根目录中,我们有一个名为 htaccess.txt 的文件,但我认为这不是您所指的.我们的现场网站上确实有一个,但我不想弄乱它,因为我不知道更改会影响其他什么。
  • 别在意 htaccess 文件,在这种情况下更改它无济于事。是的,最好不要惹它

标签: php joomla download


【解决方案1】:

这是我刚刚为帮助您而编写的一段示例代码。将其用作控制器中的操作方法。

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 函数。

【讨论】:

  • 我刚刚更深入地分析了您的代码。您是否有任何理由希望将 csv 文件存储在服务器上,或者它只是创建以便您稍后可以将其发送到浏览器?如果您不需要它留在服务器上,您可以将 csv 直接输出到带有 echo 的输出流中,为自己省去很多麻烦...只要记住先发送标头即可 :)
  • 这是我继承的代码,所以我正在使用那里的代码。确实没有理由将文件保存在服务器上。你能详细说明通过回声发送吗?这仍然会创建一个用户将下载到他们的计算机的文件吗?还是只会在浏览器中显示 csv?
  • 请看我修改后的答案。将 csv 直接打印到输出在功能上与您的代码现在所做的没有什么不同。浏览器的行为取决于您发送的标头。当您发送标头通知浏览器内容实际上是附件时,它将触发文件下载。
  • 谢谢,席尔沃。我正在为另一个客户处理危机,但一旦有机会我就会尝试一下。
  • 好的,我刚刚尝试了所有这些,它仍然会创建由 HTML 包围的文件。我正在为我的原始帖子添加带有注释的屏幕截图。
【解决方案2】:

将此方法添加到您的控制器:

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


}
?>

然后转到您的默认 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' );

祝你好运!

【讨论】:

  • 谢谢,马丁,但这段代码(我继承的)是按程序完成的,而不是在 mvc 中。
【解决方案3】:

您可以使用 Apache 的 mod_cern_meta 将 HTTP 标头添加到静态文件。 Content-Disposition: attachment。所需的.htaccess.meta 文件可以通过PHP 创建。

【讨论】:

    【解决方案4】:

    在 Joomla 应用程序中输出 CSV 数据的另一种方法是使用 CSV 而不是 HTML 格式创建视图。即创建一个文件如下:

    components/com_mycomp/views/something/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 中的“内联”部分提出质疑。如果我几年前在编写这段代码时没记错的话,我的“附件”选项有问题。我刚刚用谷歌搜索的这个链接现在似乎很熟悉它的驱动程序:https://dotanything.wordpress.com/2008/05/30/content-disposition-attachment-vs-inline/。从那以后我一直在使用“内联”,并且仍然提示从我测试的任何浏览器中适当地保存文件。我最近没有尝试过使用“附件”,所以它现在当然可以正常工作(那里的链接现在已经 7 岁了!)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-10
      • 1970-01-01
      • 1970-01-01
      • 2013-06-26
      相关资源
      最近更新 更多