Best way to initiate a download? - php

On a PHP-based web site, I want to send users a download package after they have filled out a short form. The site-initiated download should be similar to sites like download.com, which say "your download will begin in a moment."
A couple of possible approaches I know about, and browser compatibility (based on a quick test):
1) Do a window.open pointing to the new file.
- FireFox 3 blocks this.
- IE6 blocks this.
- IE7 blocks this.
2) Create an iframe pointing to the new file.
- FireFox 3 seems to think this is OK. (Maybe it's because I already accepted it once?)
- IE6 blocks this.
- IE7 blocks this.
How can I do this so that at least these three browsers will not object?
Bonus: is there a method that doesn't require browser-conditional statements?
(I believe that download.com employs both methods conditionally, but I can't get either one to work.)
Responses and Clarifications:
Q: "Why not point the current window to the file?"
A: That might work, but in this particular case, I want to show them some other content while their download starts - for example, "would you like to donate to this project?"
UPDATE: I have abandoned this approach. See my answer below for reasons.

You can also do a meta refresh, which most browsers support. Download.com places one in a noscript tag.
<meta http-equiv="refresh" content="5;url=/download.php?doc=123.zip"/>

Update: I have decided to abandon this approach, and instead just present the user with a link to the actual file. My reasoning is this:
My initial attempts at a server-initiated download were blocked by the browser. That got me thinking: "the browser is right. How does it know that this is a legitimate download? It should block a download that isn't obviously user-initiated."
Any method that I can use for a server-initiated download could also be used by someone who wants to send malware. Therefore, downloads should only happen when the user specifically requests the file by clicking on a link for it.
You're free to disagree, and if you still want to initiate a download, hopefully this thread will help you do it.

I usually just have a PHP script that outputs the file directly to the browser with the appropriate Content-Type
if(file_exists($filename)) {
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, pre-check=0");
header("Cache-Control: private", false);
header("Content-Type: " . $content-type);
header("Content-Disposition: attachment; filename=\"" . basename($filename) . "\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($filename));
readfile("$filename");
}else{
print "ERROR: the file " . basename($filename) . " could not be downloaded because it did not exist.";
}
The only disadvantage is that, since this sets the HTTP header, it has be called before you have any other output.
But you can have a link to the PHP download page and it will cause the browser to pop up a download box without messing up the content of the current page.

One catch is that you may encounter issues with IE (version 6 in particular) if the headers are not set up "correctly".
Ensure you set the right Content-Type, but also consider setting the Cache options for IE (at least) to allow caching. If the file is one the user can open rather than save (e.g. an MS Word document) early versions of IE need to cache the file, as they hand off the "open" request to the applicable app, pointing to the file that was downloaded in the cache.
There's also a related issue, if the IE6 user's cache is full, it won't properly save the file (thus when the applicable app gets the hand off to open it, it will complain the file is corrupt.
You may also want to turn of any gzip actions on the downloads too (for IE)
IE6/IE7 both have issues with large downloads (e.g. 4.x Gigs...) not a likely scenario since IE doesn't even have a download manager, but something to be aware of.
Finally, IE6 sometimes doesn't nicely handle a download "push" if it was initiated from within a nested iframe. I'm not exactly sure what triggers the issue, but I find it is easier with IE6 to avoid this scenario.

Hoi!
#Nathan:
I decided to do exactly that: Have my "getfile.php" load all necessary stuff and then do a
header("Location: ./$path/$filename");
to let the browser itself and directly do whatever it thinks is correct do with the file. This even works fine in Opera with me.
But this will be a problem in environments, where no direct access to the files is allowed, in that case you will have to find a different way! (Thank Discordia my files are public PDFs!)
Best regards, Basty

How about changing the location to point to the new file? (e.g. by changing window.location)

I've always just made an iframe which points to the file.
<iframe src="/download.exe" frameborder="0" height="0" width="0">Click here to download.</iframe>

Regarding not pointing the current window to the download.
In my experience you can still show your "please donate" page, since downloads (as long as they send the correct headers) don't actually update the browser window.
I do this for csv exports on one of my sites, and as far as the user is concerned it just pops up a safe file window.
So i would recommend a simple meta-redirect as Soldarnal showed.

Just to summarise, you have 2 goals:
start download process
show user a page with a donate options
To achieve this I would do the following:
When your user submits the form, he gets the resulting page with a donate options and a text saying that his download will start in 5 seconds. And in the head section of this page you put the META code as Soldarnal said:
<meta http-equiv="refresh" content="5;url=/download.php?doc=123.zip>
And that's all.

<a href="normaldownload.zip" onclick="use_dhtml_or_ajax_to_display_page()">
Current page is unaffected if download is saved. Just ensure that download doesn't open in the same window (proper MIME type or Content-Disposition) and you'll be able to show anything.
See more complete answer

You can use Javascript/jQuery to initiate the download. Here's an example - you can get rid of the Ajax request and just use the setTimeout() block.
$("btnDownloadCSV").on('click', function() {
$.ajax({
url: "php_backend/get_download_url",
type: 'post',
contentType: "application/x-www-form-urlencoded",
data: {somedata: "somedata"},
success: function(data) {
// If iFrame already exists, remove it.
if($("[id^='iframeTempCSV_"]).length) {
$("[id^='iframeTempCSV_"]).remove();
}
setTimeout(function() {
// If I'm creating an iframe with the same id, it will permit download only the first time.
// So randHashId appended to ID to trick the browser.
var randHashId = Math.random().toString(36).substr(2);
// Create a fresh iFrame for auto-downloading CSV
$('<iframe id="iframeTempCSV_'+randHashId+'" style="display:none;" src="'+data.filepath+'"></iframe>').appendTo('body');
}, 1000);
},
error: function(xhr, textStatus, errorThrown) {
console.error("Error downloading...");
}
});
});

Related

"download" attribute on <a> tag doesn't work on IE, safari or Opera

are there any standard way to make something similar?? I just want a way to download xml files from the server. Please help me!
No, as to my knowledge there is no way to do this using HTML.
You have to fix it on the target page. If a certain HTTP header is sent, the browser will offer a page for download instead of displaying it. This should work in every major browser. The necessary header is Content-Type: octet-stream. How you send this depends on your setup.
You can always send it by configuring your web server to do so, but how exacly depends on which web server you are using.
If, on the other hand, your XML file is generated by a PHP script, it's easy. Just add the following line before anything else is written, so preferably to the top of said script:
header('Content-Type: application/octet-stream');
If it's a static XML file... well, you could make a "proxy file" for that. Add a PHP file with the following content:
<?php
header('Content-Type: application/octet-stream');
// This "fakes" the file name, so the downloaded file isn't called
// "download_xml_file.php" or whatever you name the script.
header('Content-Disposition: attachment; filename=my_xml_file.xml');
readfile('path_to_the_actual_xml_file.xml');
?>
But try to avoid this hack. It's unnecessary bloat and it will break browser caching.
navigator.msSaveBlob(blob, filename)
https://msdn.microsoft.com/sv-se/library/windows/apps/hh772331
Unfortunately I don't know a way to do it in Safari.
Here you have a table with the browsers and thier compatibility with attribute download that Mike posted you in a comment: http://caniuse.com/download
And the actual tag with attribute is (just for sure you typing it right):
<a href="your_path_to_file" download>Download Me!</a>
-- it will work only in firefox, chrome and opera as it is in a table.
The download attribute only works in Firefox and Chrome. It will not work with IE, safari or Opera.

force Download file in php?

I want to force download a pdf,doc or docx file.
With the following code,Pdf files get opened in my tab instead of getting downloaded.
I have a table having download link in every row.I want to download file on click of this link.
foreach($a as $id = > $item) {
echo '<tr><td><a href="http://staging.experiencecommerce.com/ecsite-v3/uploads/'.substr($item['f_resume'], 63).'" ">';
//Note:substr($item['f_resume'], 63) is file_name
echo '</a></td><td>'.$item['f_date'].'</td></tr>';
}
I went through some Question on SO with same problem and tried their solution,but in Vain.
When I included the solution inside foreach,the page downloads file on load and when I place the solution outside ,the Php script gets downloaded.
Where am I going wrong?
You can set headers that will force downloading:
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename="filenamehere.pdf"');
If you're not using PHP to provide content of that files you can set headers using eg. .htaccess (requires mod_headers).
<FilesMatch ".pdf$">
FileETag None
<ifModule mod_headers.c>
Header set Content-Type "application/force-download"
</ifModule>
</FilesMatch>
After our whole chat session I think we can leave this answer here, just for future reference:
As seen in your initial post, once you click the link, you relinquish all control to the browser so it will treat the file as it sees fit. Usually this involves trying to find whatever application or plugin the system can find to treat your file.
Whenever you want to force the download of the file all you have to do is divorce the presentation itself from the task at hand. In this particular case:
1 - Create a new script that will identify the file via parameters passed and force the download on it, as seen on the examples at this site php.net/manual/en/function.readfile.php.
2 - Rework the presentation so the links do no longer point to the file itself, but to the new script with the appropriate parameters (like, for example, download_file.php?file_id=#FILE_ID#).
3 - Treat the case in which the file can not be found by, for example, die("The file could not be found") before setting the headers.
One word of advice: do not use the file location as a parameter!!!. Use instead something that you can retrieve from a database to then collect the file location. If you pass the file location itself as a parameter nothing is stopping me from doing this:
http://yoursite.com/download_file.php?file=download_file.php
http://yoursite.com/download_file.php?file=index.php
http://yoursite.com/download_file.php?file=whatever_file_there_is
With the adequate circumstances, like autodetection of the xtype for the requested file, it would allow me to access your code and exploit any possible flaws
One second and final note of advice: php can only output one thing at once. If you want it to output a website you can't output a pdf file afterwards. That's why - among other reasons - you divorce the different tasks at hand and also, that's why everything went awry when you tried directly including the download script after each link was printed.
If it helps, imagine php not as your usual real-time programming language, but as a printer. It will print everything you tell it to and serve it in reasonably sized chunks. There's no stopping it until the end is reached, there's no possible exploring two opposite branching code paths unless you call the script again with the appropriate conditions.
Hope the chat helped you.

How can I pass a very long string from one page to another?

I'm making a data visualisation tool that works using SVG, d3.js and JQuery. I am currently making a feature to export (and download) as an SVG file:
// Code on main page
var svg = $("#svg-wrap").html();
var win = window.open("export.php?svg=" + svg, '_blank'); // _blank means export.php opens in a new tab
win.focus;
// Code in export.php
<?php
ob_start();
header("Content-Type: application/octet-stream");
header("Content-disposition: attachment; filename=data.svg");
$svg = $_GET["svg"];
echo stripslashes($svg);
?>
This doesn't work though, because although some of the SVG is passed through, the full code is too long for a query string (or so it seems).
Is there some way that I can fix this? I could use compression, but that would only shrink it up to a limit and I think it would probably still be too long - the SVG code could be hundreds of lines :( .
Most UAs put limits on GET requests. Use POST instead.
You can store it in session variable and access it on other page... but not the best practice though...
You may want to use window.postMessage to handle the communication between your main app window and your popup, this way you could just stay on the client side only.
It is hard to tell the limitation,
a browser can have a limit, for post vs get
a proxy can have a limit, for post vs get
a web server can have a limit, for post vs get
the application itself can set a limit...
etc
As suggested, you could go with writing a svg file and send the url instead.
How big is the svg file?

Codeigniter Force download IE

I am having trouble with the download helper in ie..basically I built a site that dynamically creates pdf invoices and pdf proofs in both cases the force download works great in firefox, chrome and opera. In IE it fails everytime and I get the following error:
Unable to download $filename from mysite.com
Unable to open this Internet site. The requested site is either unavailable or cannot be found. Please try again later.
To begin the force_download I have a anchor target _blank with a url that directs to the following controller:
function view_uploaded_file($order = 0, $name = NULL){
$this->load->helper('directory');
$params['where'] = array('id' => id_clean($order));
$data['order'] = $this->MOrders->get($params);
if($data['order']->id < 1){
redirect('admin/orders');
}
$name = db_clean(urldecode($name));
$map = directory_map('./uploads/customer_order_uploads/'.$data['order']->user_id.'/'.$data['order']->id, 1);
if(is_array($map) && in_array($name, $map)){
$this->load->helper('download');
$data = file_get_contents('./uploads/customer_order_uploads/'.$data['order']->user_id.'/'.$data['order']->id.'/'.urldecode($name));
force_download($name, $data);
} else {
redirect('admin/orders');
}
}
Originally I thought maybe a problem with MY IE but I am able to download PDFs on other sites. I then thought that it could be a problem with codeigniters download helper but I see they already made special provisions for IE in the helper.
If you have any ideas please let me know. Thank you.
Frankly I am not sure why we bothered with a helper for downloads in code igniter.
It's not entirely hard to do in pure php:
This Wonderful Question/Answer outlines how to do it quite nicely.
The real thing to remember is the content-disposition: attachment part of the headers. It's what tells the browser that the file should be downloaded & saved vs. trying to show it in the browser.
All browsers handle things differently, maybe you have something in your IE install that's overriding the behaviour but if you follow the instructions in the linked article, you should get files downloaded correctly in all browsers.
Essentially there are 3 things we need to tell the browser:
Content Type
File Name
How to treat the incoming data
(Optional Fourth, if you have it) File Size (Content-Length)
Then you just dump that data right out to the output buffer.
Response
In response to your replies, it's probably a security feature to not automatically download something in a popup window, probably one of the new things IE introduced to combat their previous security holes.
Well I have found atleast a temporary fix for the problem. All my links for force downloads were target _blank..once I created standard non pop out links the file downloads worked in IE. There is probably some type of work around but I just also realized there is really no need for a pop up window for the download anyway..the download dialog box already serves that purpose.

Start download automatically when a user navigates to another page

I was wondering how to accomplish an effect I've seen on several websites, where I click a download now link, it takes me to another page, and the download starts automatically. What is the best way to accomplish this?
Redirect to a page which emits the following headers:
header("Content-Disposition: attachment; filename=$filename");
header("Content-Length: $length");
See this post about the restrictions on $filename.
edit in response to andr's answer, the php equivalent of redirect-after-x-seconds would be:
header("Refresh: 2; url=start_download.php");
(although you should officially specify a complete URL, I think) where start_download.php would contain the two lines above.
First you show the page with some content (please wait, blah blah) and then redirect to the file itself or to the script which outputs the file.
The redirect is done either via meta tag or javascript:
html: <meta http-equiv="refresh" content="5;url=http://example.com/foo.zip" /> where «5» is in seconds
js on page load: setTimeout("location.href=http://example.com/foo.zip", 5000) where «5000» is in milliseconds.
If you choose to output the file via php script, follow mvds's answer.

Categories