I have a PHP script that generate and download CSV file. Following is the code of that script:
<?php
$cars = array(
array("Volvo",22,1888),
array("BMW",15,13),
array("Saab",5,2),
array("Land Rover",17,15)
);
// output headers so that the file is downloaded rather than displayed
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=csvfile.csv');
// create a file pointer connected to the output stream
$output = fopen('php://output', 'w');
// output the column headings
fputcsv($output, array('Car', 'Year', 'Miles' ));
//Loop through the array and add to the csv
foreach ($cars as $row) {
fputcsv($output, $row);
}
exit;
?>
This script work fine when I access it directly, But my aim is to use this script in a WordPress AJAX call, I have created the AJAX using WordPress AJAX API as following:
add_action( 'wp_ajax_export_timeline', 'export_timeline' );
And then same PHP code (that is pasted above) is written in callback function export_timeline, but instead of generating CSV and downloading against the AJAX call, printed array is returned in response. There is no error in AJAX in call, I have tested with echoing other string, its responding fine.
But in case of upper mentioned script, I think PHP headers are not working in callback function, because instead of generating and downloading CSV, its echoing the array in response. Any help is appriciated.
To my knowledge PHP headers do not work with AJAX calls:
what you can do is create csv data in your php code and echo that as a response to your callback.
Your php code must return a json as
echo json_encode(['data' => $data, 'status' => 'success' ]);
$data variable must have valid CSV formatted data.
In your javascript you can use the following for CSV download:
function download_csv(){
var make_download = (function () {
var $a = $('<a>', {
'class': 'csv-downloader',
'style': 'display: none'
});
$('body').find('.csv-downloader').remove();
$('body').append($a);
return function (data, fileName) {
const blob = new Blob([data], {type: "octet/stream"}),
url = window.URL.createObjectURL(blob);
$a[0].href = url;
$a[0].download = fileName;
$a[0].click();
window.URL.revokeObjectURL(url);
};
}());
$.when($.ajax({
dataType: 'json',
method: 'POST',
url: URL, // url here
})).then((response) => {
if (response.status == 'success') {
make_download(response.data, `my-file.csv`);
}
})
}
Try to directly call the url, without an ajax call, then it should work.
Try to modify headers as below
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=csvfile.csv" );
header("Content-Transfer-Encoding: binary");
Related
I have looked through a few similar questions but can't quite find what I am looking for (please don't mark this as duplicate as I did try to find an answer without posting a question)
When the user clicks on a button, an ajax request is sent to the controller where I am getting data back from the model. I am then converting it to a csv format and on success of the ajax call I want the file to download. I have everything working except the download part. I have seen some examples where you just redirect but that doesn't download anything, it shows a new page with the results.
$( '.spExcel' ).on('click', function() {
$.ajax({
url: url + '/Widgets/exportSpExcel',
type: 'POST',
})
.done(function (data) {
window.location.assign(data);
})
});
PHP:
if($_SERVER['REQUEST_METHOD'] === 'POST') {
$results = $this->DashboardModel->listPeople();
$filename = 'People_' . date('dmY') . '.csv';
header("Content-Description: File Transfer");
header("Content-Type: application/csv");
header("Content-Disposition: attachment; filename=$filename");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
$handle = fopen('php://output', 'w');
$header = array("Name", "Contact Number");
fputcsv($handle, $header);
foreach ($results as $result):
fputcsv($handle, $result);
endforeach;
fclose($handle);
}
Ajax isn't capable of writing a downloaded file - the browser has to do that itself. You could use window.open() but that would open the file in a new tab or window, which would then close immediately. That can look messy - it works but isn't ideal.
The simplest way to deal with this is to make the link download the response directly, without trying to use Ajax. Change the link to suit your needs, but it would be something like this...
<a href="/Widgets/exportSpExcel" class="spExcel" download>click to download</a>
Just add the download attribute to a link. It really is that simple :)
I am trying to export and download a csv file through php. I have done exactly what was suggested in Export to CSV via PHP
I can see my array dump in the response but the csv file is just not downloading. Please Help.
Here is my code:
function download_send_headers($filename) {
// disable caching
$now = gmdate("D, d M Y H:i:s");
header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
header("Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate");
header("Last-Modified: {$now} GMT");
// force download
Header('Content-Description: File Transfer');
header("Content-Type: application/force-download");
// header("Content-Type: application/octet-stream");
// header("Content-Type: application/download");
// disposition / encoding on response body
header("Content-Disposition: attachment;filename={$filename}");
header("Content-Transfer-Encoding: binary");
}
function array2csv(array &$array)
{
// if (count($array) == 0) {
// return null;
// }
ob_start();
$df = fopen("php://output", 'w');
fputcsv($df, array_keys(reset($array)));
foreach ($array as $row) {
fputcsv($df, $row);
}
fclose($df);
return ob_get_clean();
}
Here is how im using it:
download_send_headers("data_export_" . date("Y-m-d") . ".csv");
echo array2csv($modsucc);
die();
This is javascript function:
function exporttocsv(filter){
var fd = new FormData();
fd.append('filter', filter);
fd.append("form", "export_to_csv");
$.ajax({
url: getBaseURL()+'assets/handler/OrderManagementHandler.php',
type: 'POST',
data: fd,
enctype: 'multipart/form-data',
processData: false,
contentType: false,
})
.done(function(res) {
})
.fail(function() {
});
}
Handler:
case 'export_to_csv':
$controller->exportToCSV($_POST);
break;
Controller:
public function exportToCSV($data){
$filter = $data['filter'];
$mod = new OrderManagementModel();
$modsucc = $mod->exportToCSV($filter);
if($modsucc){
// var_dump(ini_get('output_buffering'));
//var_dump($modsucc);
download_send_headers("data_export_" . date("Y-m-d") . ".csv");
echo array2csv($modsucc);
die();
}
}
Your code not work because you use ajax and you cant download files with ajax itself, simple way is this:
...
if($modsucc){
$file = /* directory */"data_export_" . date("Y-m-d") . ".csv";
$df = fopen(file, 'w');
fputcsv($df, array_keys(reset($array)));
foreach ($array as $row) {
fputcsv($df, $row);
}
fclose($df);
echo $file;
}
...
this will save file, and in your ajax done function:
window.open(res);
this will open new window with address to previously saved file or
window.location.href = res;
this will redirect you to address where the file was saved
to force download you could do it like this:
//force-download.php
if(file_exists($_GET['file'])){
download_send_headers("data_export_" . date("Y-m-d") . ".csv");
echo file_get_contents($_GET['file']); // warning: unsafe !! session for example will be better
}
this will send headers for force download and read data from disk where data was previosly saved and echo them
and in your ajax done function:
window.open('force-download.php?file=' + res);
or
window.location.href = 'force-download.php?file=' + res;
this use address where force download headers will be sent
Another possibility is, change $_POST to $_GET and instead of using ajax just redirect to url and it will work with your old code
Your code work, only think what could be wrong is if your server dont have enabled output buffering and you output something before calling function download_send_headers
I am creating a web app in my company. The user can click on a button and an csv is created with MySQL data.
So far so god.
In jquery, when the user clicks the button it redirect to:
document.location.href = '/SDR/SDRJSON.php?type=' + id;
On PHP the csv file is created:
I connect to the database and create a the csv file:
while($row = $stmt->fetch(PDO::FETCH_NUM))
{
array_push($csv, $row);
}
$fp = fopen('file.csv', 'w');
foreach ($csv as $row) {
fputcsv($fp, $row, ';');
}
$FileName = 'PEW_'.$CountryCode;
fclose($fp);
header('Content-Encoding: UTF-8');
header('Content-type: text/csv; charset=UTF-8');
header("Content-Disposition: attachment; filename='".$FileName."'.csv");
header("Pragma: public");
header("Expires: 0");
echo "\xEF\xBB\xBF"; // UTF-8 BOM
readfile('file.csv');
On the page where the button is, the user clicks there and the page starts waiting for the server and then the csv file starts downloading.
For small files is ok, because it is instantaneous. But for larger files it takes like 10 / 15 seconds. Is it possible to show a message while the page waits for the server?
I don't Think PHP can echo while the csv is being made ... What you could do is split the "Document Formation" and "Document Download" into two parts.
Let Ajax Make a query for the CSV to be made . And when that has been completed the PHP (Document Formation) will echo the Path of the File.
Then After that You can use document.location.href to Newly Created File.
I ll give the code
ajax-code
$('#sample-button').click(function(){
$.ajax({
url : '/SDR/SDRJSON.php?type=' + id,
success : function(data){
if(data.url)
{
var urlToDownload = data.url;
alert("File is ready for download");
document.location.href = "http://www.domain.com/file/path/"+data.url;
// Make sure data.url has the full path or append the data.url
// with some strings to make sure the full path is reflected
// into document.location.href ...
}
else
{
alert("Something went wrong");
}
}
});
alert("CSV is being prepared Please wait... ");
});
documentFormation.php
while($row = $stmt->fetch(PDO::FETCH_NUM))
{
array_push($csv, $row);
}
$FileName = 'PEW_'.$CountryCode;
$fp = fopen($FileName, 'w');
foreach ($csv as $row) {
fputcsv($fp, $row, ';');
}
fclose($fp);
$response = array();
$response['url'] = $FileName;
echo json_encode($response); // You can handle rest of the cases where to display errors (if you have any)
// Your DocumentFormation PHP Ends here. No need for header() or readFile.
If you dont want the file to stay on server , Edit the document href to This PHP passing 'path' as the parameter
document.location.href = "documentDownload.php?path="+data.url;
documentDownload.php
$path = $_GET['path'];
$filename = end(explode("/" , $path));
header('Content-Encoding: UTF-8');
header('Content-type: text/csv; charset=UTF-8');
//Assuming $filename is like 'xyz.csv'
header("Content-Disposition: attachment; filename='".$filename);
header("Pragma: public");
header("Expires: 0");
echo "\xEF\xBB\xBF"; // UTF-8 BOM
// Reading file contents
readfile('Relative Path to File'.$path);
// Deleting after Read
unlink('Relative Path to File'.$path); // To Delete right after reading
I'm making an AJAX call to a site which generates a query and then saves it to a .txt file.
This file should be downloaded after the AJAX was done and close that download window.
Howether IE closes it automatically and then tries to close the mainwindow, which shouldn't be closed.
Meanwhile Chrome only closes the download window which is what IE should do aswell..
Is there a workaround for this?
function start(){
$.ajax({
url: ('query.php'),
type: 'POST',
async:false,
data: {
id: id,
intnr: intnr
},
dataType: "html"
})
.done (function(response) { window.open('download.php'); window.close('download.php'); })
.fail (function(xhr, textStatus, errorThrown) { alert(xhr.responseText); })
;
}
download.php is just :
<?php
header ( 'Content-Type: text/html');
header ( "Content-Disposition: 'attachment'; filename='File.txt'");
include ('/xx/xx/query.txt');
?>
EDIT : Workaround but it is working now..
shortened function to
.done (function(response) { var download_window = window.open('download.php'); })
added into download.php
<script>
var s = navigator.userAgent;
if(s.indexOf("Chrome") > -1 == true)
{
window.open('', '_self', '');
window.close();
}
</script>
How about this then:
.done (function(response) {
var download_window = window.open('download.php');
download_window.close();
})
.. should make IE not close anything else.
This doesn't really answer your question, but offers an alternative.
Try something like this in the jQuery code:
.done (function(response) { window.location.href = "download.php"; })
.. and add headers to force download in download.php:
header("Content-Description: File Transfer");
header("Content-Type: application/download");
header("Content-Type: application/force-download");
// Removed this from my code.
// header("Content-Type: application/octet-stream");
// Added this for yours.. not sure exactly what's optimal for your case.
header("Content-Type: text/html");
header("Content-Transfer-Encoding: binary");
header("Content-Disposition: attachment; filename=File.txt");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Expires: 0");
header("Pragma: public");
ob_clean();
flush();
readfile("/xx/xx/query.txt");
In my project, this php code was run after clicking a submit button (form) and if I recall correctly, it just showed a download dialogue box without updating the address bar or showing an empty page.
This will work if it is rad window
function GetRadWindow() {
var oWindow = null;
if (window.radWindow)
oWindow = window.radWindow;
else if (window.frameElement && window.frameElement.radWindow)
oWindow = window.frameElement.radWindow;
return oWindow;
}
function selfClose(){
GetRadWindow().close();
}
i want to create a downloadscript which allows Force Download of JPGs.
This is my php script:
<?php
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-Description: File Transfer");
header("Content-Type: image/jpg");
header('Content-Disposition: attachment; filename="'.basename($GET['a']).'"');
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize(($GET['a']));
readfile(($GET['a']);
?>
This is a code segment of my js code:
function downloadFile(a){
document.location = "download.php?a="+ a;
}
With this code sample nothing happens. If i append the result into a HTML-tag, it shows the content of the file.
Any ideas how to teach the browser to download this file?
EDIT: SCRIPT UPDATE
You can't download files with ajax. So, if you have something that should happen on ajax, you should return url in response and apply it like document.location = "url"to start download process.
One note here. As I remember, browser will block file download if it is initiated not by user click. So, this will work fine:
.click(function(){
document.location = "download url"
})
But if it is started not by user click, it will be blocked. So, code like this:
.click(function(){
$.ajax({...,
success:function(download_url_from_server){
document.location = download_url_from_server;
}});
})
will be blocked by browser. So, if you want to pass some data with a post, you may submit a form into hidden iframe or to blank page using <form target="...":
function checkToken(token){
var $form = $("#downloadForm");
if ($form.length == 0) {
$form = $("<form>").attr({ "target": "_blank", "id": "downloadForm", "method": "POST", "action": "script.php" }).hide();
$("body").append($form);
}
$form.find("input").remove();
var args = { a: "checkToken", b: token }
for (var field in args) {
$form.append($("<input>").attr({"value":args[field], "name":field}));
}
$form.submit();
}
And in script.php you need to execute code from download.php immediately, if token is Ok, or do a redirect to download script:
header("Location: download.php?a=" . $filename)
Setting the mime type to image/jpeg will most probably not work. So, you need application/octet-stream instead to force the download.
Replace the content type header in your php with the following:
header('Content-Type: application/octet-stream');
Also, One nice solution instead of using document.location is to inject an iframe. Use the following function in your success callback
function downloadFile(url)
{
var iframe;
iframe = document.getElementById("download-container");
if (iframe === null)
{
iframe = document.createElement('iframe');
iframe.id = "download-container";
iframe.style.visibility = 'hidden';
document.body.appendChild(iframe);
}
iframe.src = url;
}
It seems you have errors in your script.
First of all, correct speliing for GET variable is $_GET['a'], not $GET['a'].
The second issue here is that you have extra opening parenthesis, when I copied your code, I received 500 Internal Server Error response.
If we correct mistakes, it seems to work fine.
Just try corrected version of your code.
<?php
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-Description: File Transfer");
header("Content-Type: image/jpg");
header('Content-Disposition: attachment; filename="'.basename($_GET['a']).'"');
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($_GET['a']));
readfile($_GET['a']);
?>
You're getting it confused a bit. As FAngel pointed out, you can't download files via AJAX. What you need to do is redirect the user to another page that then has your above PHP code in it. That PHP code should then allow the user to download the file directly. What you're attempting is absolutely possible, you just need to approach it from another direction, ie not with AJAX.
You can force download file with Mouse middle event:
const url = "https://www.google.com.vn/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png";
const forceDownload = url => {
try {
const link = document.createElement('a');
const fileName = url.substring(url.lastIndexOf('/') + 1, url.length);
const event = new MouseEvent( "click", { "button": 1, "which": 1 });
link.href = url;
link.download = fileName;
link.dispatchEvent(event);
} catch(e) {
document.location = url;
}
}
forceDownload(url);