My csv export displaying html, how to get rid of? - php

I've seen this asked before and I am having trouble getting this to work properly after trying a number of solutions. The problem is I can't get my data to export into a csv format properly. Before I added my ob_end_clean it would export out to a csv with html, now it doesn't give me a csv, just text.
Here is my code on the file that is being required.
if (isset($_POST["hidden"])) {
$list = array (
array('aaa', 'bbb', 'ccc', 'dddd'),
array('123', '456', '789'),
array('"aaa"', '"bbb"')
);
$fp = fopen('php://output','w');
foreach ($list as $row) {
ob_end_clean();
fputcsv($fp, $row);
}
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=data.csv');
}
Right now when I do my export, the data gets put back on the screen similar to a var_dump(). I just simply want this to go to a csv file without having html all over it.

Got it working!
I invoked my csv code before anything on the page. :) Then I did my connection to my table, then did my logic for my code. I didn't have an ob_start or ob_flush on my main file which made a big difference. I had the ob_clean before the while loop and then I did an exit() after declaring the header. Hopefully, this explains it well.
Here is my code.
if (isset($_POST["hidden"])) {
$sql = "SELECT * FROM `newsletter`";
$result = mysql_query($sql);
ob_end_clean();
$fp = fopen('php://output','w');
while ($list = mysql_fetch_assoc($result)) {
fputcsv($fp, $list);
}
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=data.csv');
exit();
}

Place, ob_end_clean(); before you output the csv.
ob_end_clean() meaning:
"Clean (erase) the output buffer and turn off output buffering" - PHP manual.

The logic for it to work is to construct your php script so that it:
first echoes all the html/javascript... content intended for the browser page like echo "<html...>;" (this already uses php's output buffer behind the scenes)
after that cleans the php's output buffer so far, with ob_end_clean() (depending how ob_start() was called this may prevent the previous export) or ob_clean() which just sends content so far to the browser and cleans the buffer without turning it off.
lastly uses this clean output buffer again to export any further content (as downloadable csv in our case) to the browser, like shown above by wowzuzz. So if any html is echoed by the script after that, it will be included in the csv as well.

Related

Export to CSV using fputcsv()

Im trying to make a CSV export from data entered in an array on my website. I was using this question to help me. I am getting the data that should be in the CSV echoed on my website but not exported to a file. This is the code that I took from the question:
header( "Content-Type: text/csv;charset=utf-8" );
header( "Content-Disposition: attachment;filename=\"$filename\"" );
header("Pragma: no-cache");
header("Expires: 0");
$fp= fopen('php://output', 'w');
foreach ($data as $fields){
fputcsv($fp, $fields);
}
fclose($fp);
exit();
I dont exactly understand what the header() functions are doing. How would I get this to download to a file?
if it helps my array is in this format:
$data = array(dataset1(array, of, data), dataset2(array, of, data), dataset#(array, of, data));
EDIT:My $data array is in a session varible and the reason it wasnt downloading was because there I had session_start() and some includes at the top. Instead of downloading it would echo to the screen but if I remove this it downloads at the cost of there being no data to export. Anyone have a solution to this?
The header() function is sending HTTP headers to your browser with the respective values.
It then sends the CSV data to the output stream which the browser interprets as a downloadable file due to the headers.

PHP download CSV

Stuck on what is likely a silly problem and only posting after reading several related threads.
Have a page with a lot going on, one of the form options I'm trying to add is so the user can select to download array results in CSV. Problem is HTML header info is coming through in addition to the CSV data I want.
Code is:
function Array2Csv($result, $filename){
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=' .$filename);
$output = fopen('php://output', 'w');
while($row = mysql_fetch_assoc($result)) {
fputcsv($output, $row,'|','"');
}
}
Problem is the result file includes BOTH undesired markup (headers and scripting references) in addition to the CSV itself. Desired output should only include the CSV data.
You have send the the header before any output was send. Disable view and layout.
See also http://php.net/manual/en/function.header.php

Download large CSV file to browser while it is being generated

I have a script that generates a large CSV file using fputcsv and sends it to the browser. It works, but the browser doesn't show the file download prompt (or start downloading the file) until the whole CSV file has been generated serverside, which takes a long time.
Instead, I'd like the download to begin while the remainder of the file has still being generated. I know this is possible because it's how the 'Export database' option in PHPMyAdmin works - the download starts as soon as you click the 'export' button even if your database is huge.
How can I tweak my existing code, below, to let the download begin immediately?
$csv = 'title.csv';
header( "Content-Type: text/csv;charset=utf-8" );
header( "Content-Disposition: attachment;filename=\"$csv\"" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
$fp = fopen('php://output', 'w');
fputcsv($fp, array_keys($array), ';', '"');
foreach ($array as $fields)
{
fputcsv($fp, $fields, ';', '"');
}
fclose($fp);
exit();
Empirically, it seems that when receiving responses featuring a Content-Disposition: attachment header, different browsers will show the file download dialog at the following moments:
Firefox shows the dialog as soon as it receives the headers
Internet Explorer shows the dialog once it has received the headers plus 255 bytes of the response body.
Chromium shows the dialog once it has received the headers plus 1023 bytes of the response body.
Our objectives, then, are as follows:
Flush the first kilobyte of the response body to the browser as soon as possible, so that Chrome users see the file download dialog at the earliest possible moment.
Thereafter, regularly send more content to the browser.
Standing in the way of these objectives are, potentially, multiple levels of buffering, which you can try to fight in different ways.
PHP's output_buffer
If you have output_buffering set to a value other than Off, PHP will automatically create an output buffer which stores all output your script tries to send to the response body. You can prevent this by ensuring that you have output_buffering set to Off from your php.ini file, or from a webserver config file like apache.conf or nginx.conf. Alternatively, you can turn off the output buffer, if one exists, at the start of your script using ob_end_flush() or ob_end_clean():
if (ob_get_level()) {
ob_end_clean();
}
Buffering done by your webserver
Once your output gets past the PHP output buffer, it may be buffered by your webserver. You can try to get around this by calling flush() regularly (e.g. every 100 lines), although the PHP manual is hesitant about providing any guarantees, listing some particular cases where this may fail:
flush
...
Flushes the write buffers of PHP and whatever backend PHP is using (CGI, a web server, etc). This attempts to push current output all the way to the browser with a few caveats.
flush() may not be able to override the buffering scheme of your web server ...
Several servers, especially on Win32, will still buffer the output from your script until it terminates before transmitting the results to the browser.
Server modules for Apache like mod_gzip may do buffering of their own that will cause flush() to not result in data being sent immediately to the client.
You can alternatively have PHP call flush() automatically every time you try to echo any output, by calling ob_implicit_flush at the start of your script - though beware that if you have gzip enabled via a mechanism that respects flush() calls, such as Apache's mod_deflate module, this regular flushing will cripple its compression attempts and probably result in your 'compressed' output being larger than if it were uncompressed. Explicitly calling flush() every n lines of output, for some modest but non-tiny n, is thus perhaps a better practice.
Putting it all together, then, you should probably tweak your script to look something like this:
<?php
if (ob_get_level()) {
ob_end_clean();
}
$csv = 'title.csv';
header( "Content-Type: text/csv;charset=utf-8" );
header( "Content-Disposition: attachment;filename=\"$csv\"" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
flush(); // Get the headers out immediately to show the download dialog
// in Firefox
$array = get_your_csv_data(); // This needs to be fast, of course
$fp = fopen('php://output', 'w');
fputcsv($fp, array_keys($array), ';', '"');
foreach ($array as $i => $fields)
{
fputcsv($fp, $fields, ';', '"');
if ($i % 100 == 0) {
flush(); // Attempt to flush output to the browser every 100 lines.
// You may want to tweak this number based upon the size of
// your CSV rows.
}
}
fclose($fp);
?>
If this doesn't work, then I don't think there's anything more you can do from your PHP code to try to resolve the problem - you need to figure out what's causing your web server to buffer your output and try to solve that using your server's configuration files.
have not tested this. try to flush the script after n number of data rows.
flush();
Try Mark Amery's answer, but just emphasize on the statement:
$array = get_your_csv_data(); // This needs to be fast, of course
If you're fetching huge number of records, fetch them by chunks (every 1000 records for example).
So:
Fetch 1000 records
Output them
Repeat
I think you are looking for the octet-stream header.
$csv = 'title.csv';
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment;filename=\"$csv\"" );
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');
header('Expires: 0');
$fp = fopen('php://output', 'w');
fputcsv($fp, array_keys($array), ';', '"');
foreach ($array as $fields)
{
fputcsv($fp, $fields, ';', '"');
}
fclose($fp);
exit();

Write to PHP output buffer and then download CSV from buffer

I need to write a CSV file to the PHP output buffer and then download that file to the client's computer after it's done writing. (I wanted to just write it on the server and download it which was working, but it turns out I won't have write access on production servers).
I have the following PHP script:
$basic_info = fopen("php://output", 'w');
$basic_header = array(HEADER_ITEMS_IN_HERE);
#fputcsv($basic_info, $basic_header);
while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) {
#fputcsv($basic_info, $user_row);
}
#fclose($basic_info);
header('Content-Description: File Transfer');
header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=test.csv');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize("php://output"));
ob_clean();
flush();
readfile("php://output");
I'm not sure what to do. The CSV file downloads but displays nothing. I assume it has something to do with the ordering of my ob_clean() and flush() commands, but I'm not sure what's the best way to order these things.
Any help is appreciated.
You're doing a little too much. Create the script with the sole purpose of outputting the CSV. Just print it out directly to the screen. Don't worry about headers or buffers or php://output or anything like that yet.
Once you've confirmed that you're printing the data out to the screen appropriately, just add these headers at the beginning:
<?php
header("Content-disposition: attachment; filename=test.csv");
header("Content-Type: text/csv");
?>
... confirm that that downloads the file appropriately. Then you can add the other headers if you like (the headers I included above are those I've used myself without any extra cruft to get this working, the others are basically for efficiency and cache control, some of which may already be handled appropriately by your server, and may or may not be important for your particular application).
If you want, use output buffering with ob_start() and ob_get_clean() to get the output contents into a string which you can then use to fill out Content-Length.
As mentioned in my comments of Edson's answer, I expected a "headers already sent" warning at the last line of code:
header('Content-Length: '.$streamSize);
since output is written before this header is sent, but his example works ok.
Some investigation leads me to to following conclusions:
At the time you use an output buffer (weither a user one, or the
default PHP one), you may send HTTP headers and content the way you
want. You know that any protocol require to send headers before body
(thus the term "header"), but when you use an ouput buffer layer, PHP
will take care of this for you. Any PHP function playing with output
headers (header(), setcookie(), session_start()) will in fact use the
internal sapi_header_op() function which just fills in the headers
buffer. When you then write output, using say printf(), it writes into
the output buffer (assuming one). When the output buffer is to be
sent, PHP starts sending the headers first, and then the body. PHP
takes care of everything for you. If you dont like this behavior, you
have no other choice than disabling any output buffer layer.
and
The default size of the PHP buffer under most configurations is 4096
bytes (4KB) which means PHP buffers can hold data up to 4KB. Once this
limit is exceeded or PHP code execution is finished, buffered content
is automatically sent to whatever back end PHP is being used (CGI,
mod_php, FastCGI). Output buffering is always Off in PHP-CLI.
Edson's code works because the output buffer did not automatically get flushed because it doesn't exceed the buffer size (and the script isn't terminated obviously before the last header is sent).
As soon as the data in the output buffer exceeds the buffer size, the warning will be raised. Or in his example, when the data of
$get_users_stmt->fetch(PDO::FETCH_ASSOC)
is too large.
To prevent this, you should manage the output buffering yourself with the ob_start() and ob_end_flush(); like below:
// Turn on output buffering
ob_start();
// Define handle to output stream
$basic_info = fopen("php://output", 'w');
// Define and write header row to csv output
$basic_header = array('Header1', 'Header2');
fputcsv($basic_info, $basic_header);
$count = 0; // Auxiliary variable to write csv header in a different way
// Get data for remaining rows and write this rows to csv output
while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) {
if ($count == 0) {
// Write select column's names as CSV header
fputcsv($basic_info, array_keys($user_row));
} else {
//Write data row
fputcsv($basic_info, $user_row);
}
$count++;
}
// Get size of output after last output data sent
$streamSize = ob_get_length();
//Close the filepointer
fclose($basic_info);
// Send the raw HTTP headers
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=test.csv');
header('Expires: 0');
header('Cache-Control: no-cache');
header('Content-Length: '. ob_get_length());
// Flush (send) the output buffer and turn off output buffering
ob_end_flush();
You're still bound to other limits, though.
I did some tweaks to your code.
Moved headers before any output, as suggested by PHP's doc;
Remember that header() must be called before any actual output is
sent, either by normal HTML tags, blank lines in a file, or from PHP
Removed some headers that didn't make much of a change;
Commented another option of writing csv header using the select column's names;
Now content length works;
There is no need to echo $basic_info as it is already at output buffer and we redirected it inside a file through headers;
Removed # (PHP Error Control Operator) as it may cause overhead, don't have a link to show you right now but you might find it if you search. You should think twice before silencing errors, most times it should be fixed instead of silenced.
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=test.csv');
header('Expires: 0');
header('Cache-Control: no-cache');
$basic_info = fopen("php://output", 'w');
$basic_header = array(HEADER_ITEMS_IN_HERE);
fputcsv($basic_info, $basic_header);
$count = 0; // auxiliary variable to write csv header in a different way
while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) {
// Write select column's names as CSV header
if ($count == 0) {
fputcsv($basic_info, array_keys($user_row));
}
fputcsv($basic_info, $user_row);
$count++;
}
// get size of output after last output data sent
$streamSize = ob_get_length();
fclose($basic_info);
header('Content-Length: '.$streamSize);

how to serve output as file without saving it on server

I'm serving some records from a MySQL database using PHP's fputcsv() by creating a file on the server, filling it, then linking to it on the next page.
This works and is great but as this could be sensitive data, I don't want a buch of files hanging about on the server when they were created for (probably) a one-time download.
So what I want to know is this: is there a way to create this file & serve it for download without actually writing a permanent file on the server?
For instance could I create a comma separated string instead of using fputcsv() and serve that with the right headers in an output buffer?
The obvious move is to delete the file but I need to wait until the client downloads it first so that makes it a little difficult to decide when to do it.
Any suggestions welcome
The code:
$fp = fopen($filename, 'w');
fputcsv($fp, array("Last Name", "First Name"));
foreach ($result as $fields)
{
fputcsv($fp, $fields);
}
fclose($fp);
http://php.net/manual/en/function.fputcsv.php
fputcsv() is a fabulous little function, so I wouldn't abandon it.
Instead, I suggest you play around with PHP's built-in I/O Wrappers
You, can, for example, do this to "stream" your CSV data line-by-line (subject to various output buffers, but that's another story):
<?php
header('Content-type: text/csv; charset=UTF-8');
header('Content-disposition: attachment; filename=report.csv');
$fp = fopen('php://output','w');
foreach($arrays as $array) fputcsv($fp, $array);
That works great, but if something goes wrong, your users will have a broken download.
So, if you don't have too much data, you can just write to an in-memory stream, just swap out php://output with php://memory and move things around:
<?php
$fp = fopen('php://memory','rw');
// our generateData() function might throw an exception, in which case
// we want to fail gracefully, not send the user a broken/incomplete csv.
try {
while($row = generateData()) fputcsv($fp, $row);
}catch(\Exception $e){
// display a nice page to your user and exit/return
}
// SUCCESS! - so now we have CSV data in memory. Almost like we'd spooled it to a file
// on disk, but we didn't touch the disk.
//rewind our file handle
rewind($fp);
//send output
header('Content-type: text/csv; charset=UTF-8');
header('Content-disposition: attachment; filename=report.csv');
stream_get_contents($fp);
Rather than that, why not just have your page echo out a csv mime type and then echo out the file to the user?
It works a charm, the file is never created and passed as a one off to the client.
Something like this:
header("Content-type: application/csv");
header("Content-Disposition: attachment; filename=file.csv");
header("Pragma: no-cache");
header("Expires: 0");
echo "col1,col2";
for($i=0; $i<25;$i++)
{
echo "key :".$i.", ".($i*$i)."\r\n";
}
You should be able to test that out as is and see how it works.
The added beauty is that most users will be directed to download the file rather than opening it, so the user doesn't even leave the page (most of the time).

Categories