In the website I'm building, I'm trying to provide users the ability to export and download data in KML format. I've built a PHP script to generate a kml file from a string. It works reasonably well, until requests get too long and then I get 414 errors.
The thing is, I'm pretty sure I'm going at this the wrong way (I'm new to php). Rather than sending my data as a string, which can get to multiple tens of thousands of characters long, I should be sending a file generated by javascript to the php script which would send it back to the user or something like this. Is it possible?
If not, what other options do I have?
Here's my php script :
if($_SERVER['REQUEST_METHOD']=='POST') {
$text = $_POST['text'];
$text = str_replace('\r\n', PHP_EOL, $text);
$text = str_replace('\n', PHP_EOL, $text);
$text = str_replace('\t', "\t", $text);
$text = str_replace('_HASH_', "#", $text);
$filename = $_POST['filename'];
$tmpName = tempnam(sys_get_temp_dir(), $filename);
$file = fopen($tmpName, 'w');
fwrite($file, $text);
fclose($file);
header('Content-Description: File Transfer');
header("Content-Type: application/octet-stream");
//header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename=' . $filename . '.kml');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($tmpName));
ob_clean();
flush();
readfile($tmpName);
unlink($tmpName);
exit;
}
* EDIT *
Ok, I changed the php script to use POST instead of GET which should clear up the length issue. But this has shown me another problem.
I'm using dojo to write my website. I'm doing an xhr request with POST method. This request comes back successfully. In the success handler, I'm navigating to the same url I passed to the request by setting the src of a hidden iFrame (so as to avoid reloading the page). The results in a GET request which doesn't work with my script (POST).Furthermore, I believe this means I'm running the php script twice which makes no sense.
Should I just navigate directly to the url of the php script with required parameters? In this case, how do I navigate to a url using a POST request?
I'm a bit confused about all of this, it probably shows too!
Thanks,
* SOLUTION *
mguimard sent me on the right track to solve my issue. Here's the bit of code I used to send my POST request while avoiding to realod the page:
download: function(text) {
var ifr = document.createElement('iframe');
ifr.setAttribute('name', "target");
ifr.style.display = 'none';
document.body.appendChild(ifr);
var f = document.createElement('form');
f.setAttribute('method',"post");
f.setAttribute('action',"saveFileAs.php");
f.setAttribute('target',"target");
addField(f, "filename", "myFileName");
addField(f, "text", text);
f.submit();
ifr.onload = cleanUp;
function cleanUp(){
document.body.removeChild(ifr);
ifr = null;
}
function addField(form, name, value) {
var i = document.createElement("input");
i.setAttribute('name',name);
i.setAttribute('type','text');
i.value = value;
form.appendChild(i);
}
}
Ggilmann
Related
I am writing my output to the text file and then downloading it using php but issue is that it is saving the output but it is also saving the whole structure of HTML into the textfile also. I don't know why its happening tried to solve it but did'nt figure out how.
I want to save output from fwrite($fh, $formatted_url."\n");
Below is my code:
function get_m3u8_video_segment($url,$portnum=80,$from,$to)
{
$file_name="urlscan.txt";
$fh = fopen($file_name, 'w') or die("Unable to open file!");
for ($x = $from; $x <= $to; $x++)
{
$formatted_url="{$url}:{$portnum}/s-{$x}.m3u8";
//echo "URL is: $formatted_url <br>";
//$contents = file_get_contents($formatted_url);
$contents = get_web_page( $formatted_url );
if ((strpos($contents, 'not found') !== false)||(strpos($contents, 'EXTM3U') !== false))
{
echo" $formatted_url<br>";
fwrite($fh, $formatted_url."\n");
}
}
//header download
header("Content-Disposition: attachment; filename=\"" . $file_name . "\"");
header("Content-Type: application/force-download");
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header("Content-Type: text/plain");
}
get_m3u8_video_segment($url,$portnum,$from,$to);
}
If there is other HTML content elsewhere in your PHP script then this will also be outputted as it normally is, except in this case it will become part of the downloaded file. If you don't want that to happen then you have to stop your script with an exit(); command after you have output the content you actually want. In your script, it looks like you can probably do this just after the call to the function. (But if you have already output some HTML before this, you'll need to alter your script more substantially.)
N.B. I'm surprised you aren't getting a warning about headers being already sent? That normally happens if you try to set headers after you've already echoed some content. Check your log files. Normally you are supposed to output the headers first.
Also, unless you are wanting to keep it for some other purpose, there is no use in saving anything to urlscan.txt - it is not playing any part in your download process. And it would get overwritten every time this script is executed anyway. The headers will cause the browser to treat the output contents (i.e. anything which the PHP script sends to the regular output) as a text file - but this is not the same file as the text file on your server's disk, and its contents can be different.
You happen to be outputting similar content (via echo" $formatted_url<br>";) as you are adding to the urlscan file (via fwrite($fh, $formatted_url."\n");) and I think this may be confusing you into thinking that you're outputting the contents of urlscan.txt, but you aren't - your PHP headers are telling the browser to treat the output of your script (which would normally just go onto the browser window as a HTML page) as a file - but it's a) a new file, and b) actually isn't a file at all until it reaches the browser, it's just a response to a HTTP request. The browser turns it into a file on the client machine because of how it interprets the headers.
Another thing: the content you output needs to be in text format, not HTML, so you need to change the <br> in your echo to a \n.
Lastly, you're outputting the content-type header twice, which is nonsense. A HTTP request or response can only have one content type. In this case, text/plain is the valid MIME type, the other one is not real.
Taking into account all of the above, your code would probably be better written as:
function get_m3u8_video_segment($url, $portnum=80, $from, $to)
{
//header download
$file_name="urlscan.txt";
header("Content-Disposition: attachment; filename=\"" . $file_name . "\"");
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header("Content-Type: text/plain");
for ($x = $from; $x <= $to; $x++)
{
$formatted_url="{$url}:{$portnum}/s-{$x}.m3u8";
$contents = get_web_page( $formatted_url );
if ((strpos($contents, 'not found') !== false)||(strpos($contents, 'EXTM3U') !== false))
{
echo" $formatted_url\n";
}
}
}
get_m3u8_video_segment($url, $portnum, $from, $to);
exit();
I have a js script that saves an image as .jpg in a specific folder on the server:
$data = substr($_POST['imageData'], strpos($_POST['imageData'], ",") + 1);
$decodedData = base64_decode($data);
$fp = fopen("imgdownload/cc.jpg", 'wb');
fwrite($fp, $decodedData);
fclose($fp);
The next step would be for the user to save it on his/her disk, the best would be to open a dialog box "save as" and choose the name and location, but just forcing download to a set location would be a dream.
I've tried:
$file = 'imgdownload/cc.jpg';
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: image/jpg');
header('Content-Disposition: attachment; filename='.basename($file));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}
And other things without success. What am I doing wrong and how to do it right?
EDIT - this is my JS:
savePicture: function() {
$(this.el).find('canvas').attr('id', 'myCanvas');
var data = document.getElementById("myCanvas").toDataURL();
$.post("api/process.php", {
imageData: data
}, function(data) {
//window.location = data;
});
},
You cannot trigger a download in response to a background AJAX request. You'll have to direct the main browser to the URL where the download occurs. E.g., in your AJAX callback:
window.location = '/download.php';
That means you'll need to store the file server-side in the AJAX upload request to then have it available for download in the separate following request to download.php somewhere.
Edit
added a line to hash the password in the example so people don't get distracted.
I have a server-side php file that checks posts parameters for validation purposes and downloads a dynamically generated file if the parameters are valid. If the parameters are invalid the php returns json with the reason why. I can't seem to find a way to call my php from javascript and handle both cases.
My php looks something like this(simplified):
PHP
<?php
if(isset($_POST['Username']) && isset($_POST['Password']))
{
$dbPassword = *sql statement*
$password = hash("sha256", $_POST['Password']);
if($dbPassword==$password)
{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename= 'somefile.zip');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize('somefile.zip'));
ob_clean();
flush();
$bytesRead = readfile('somefile.zp');
}
else
{
$returnArray = array();
$returnArray['Status']='Invalid'
return json_encode[$returnArray];
}
}
?>
I have tried this with an a href link using GET which downloads the file but also downloads the json as a text file, which is not what I want. I would also prefer to user POST.
The second method I tried was using a form post which downloads but I don't know if its possible to get a json response with this method.
Javascript
function submitform()
{
var theForm = document.forms['myForm'];
theForm.setAttribute("method", "post");
var input = document.createElement('input');
input.type = 'hidden';
input.name = "UserName";
input.value = "someuser";
theForm.appendChild(input);
theForm.submit();
}
Lastly I've tried using ajax and while i get back the file data, i dont' know if its possible to make the browser download it as a file. What i've read suggests that you can't.
I got stuck with the code left by the first programmer of the current webpage that I'm on. He create his own custom framework.
My problem is that I have created a function for file export cause there is a feature wherein I should export the query to textfile so client/user download it automatically via forcing the browser to download the generated file: heres the code of my force download.
Note: I have already search with others forums as well as at Stackoverflow but I can't find an answer. This is my current code:
if(!defined('APPLICATION_RUNNING')){
header("HTTP/1.0 404 Not Found");
die('acces denied');
}
if(defined('ANNOUNCE')){
echo "\n<!-- loaded: ".__FILE__." -->\n";
}
if(!class_exists('APP_Ajax')){
class APP_Ajax extends APP_Base_Ajax
{
function textfiles()
{
global $apptemplate, $appform, $appdb;
if(!$appform->form_valid($this->post['formid'])) // if not b\valid form
{
json_return_error(5);
}
else
{
if(!empty($this->post['form'])) //if the post of form is not empty
{
$e = array();
}
else
{
json_return_error(6);//alert if form is empty
}
if(empty($this->post['form']['rowid']))
{
json_return_error(252); // if rowid is empty then raise error
}
else
{
$rets = $this->post['form']['rowid'];
$ids = explode(',',$rets);
foreach ($ids as $kids)
{
$sql = pg_query("SELECT * from tbl_patient WHERE id='$kids'");
$info = pg_fetch_assoc($sql);
$text[] = ucwords($info['firstname']." ".$info['mi']." ".$info['lastname']).",".$info['medicalrecordnumber'].",".$info['referraldate'];
}
$output = implode( "\r\n" , $text );
$randfile = rand(12345689,999999999)."_".date('m-d-Y');
$f = fopen("templates/default/exports/".$randfile.".txt", "w");
fwrite($f, $output);
fclose($f);
$file_name = ABS_PATH.'templates/default/exports/'.$randfile.'.txt';
header('Pragma: public'); // required
header('Expires: 0');// no cache
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Last-Modified: '.gmdate ('D, d M Y H:i:s', filemtime ($file_name)).' GMT');
header('Cache-Control: private',false);
header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename="'.basename($file_name).'"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.filesize($file_name));
header('Connection: close');
readfile($file_name);// push it out
}
}
}
}
The code above produce no error, the file where created on the path I create but
the problem is that "No save as" or something, the browser do not download a file. When I use httpfox to view the headers, etc, etc, the result of the query is displayed on httpfox content tab, so nothings wrong with the code except that I want the browser to show the Download file(Save as dialog) so the user can save it to there Hard drives..
Note 2#: When I create a download link like Download, when I click the download link, the page returns me to the main page and no downloads at all.
Please give me a script or advice on how to work on this stuff. Thank you in advance.
Tell me if I'm missing something here.
Oops forgot to tell you, the website I'm on it do not allow external access to file for security that's why I can't link the export file it to other page for download. :)
I try export data from database to .csv. When I click export link I don't see save window in browser for a very long time if there is quite a lot amount of data. It can be quite confusing if the script looks like hanging for some time and after quite a long time one can see save window in browser.
The code is something like this in controller:
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();
$fileName = $list->list_name . '.csv';
$this->getResponse()->setHeader('Content-Type', 'text/csv; charset=utf-8')
->setHeader('Content-Disposition', 'attachment; filename="'. $fileName . '"');
$contacts = new Contact();
$contacts->export($listId);
Export method reads records one by one and prints it something like this:
$fp = fopen('php://output', 'w');
foreach ($mongodbCursor as $subscriber) {
$row = formRow($subscriber);
fputcsv($fp, $row);
}
I see on some applications that save winow appear almost immediately and when you click save you see progress of downloading.
I tried to replace:
$this->getResponse()->setHeader('Content-Type', 'text/csv; charset=utf-8')
->setHeader('Content-Disposition', 'attachment; filename="'. $fileName . '"');
with this one:
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="'. $fileName . '"');
It didn't help so far.
I wonder if it's possible to send headers before all data are read one by one from database?
Thank your for your assistance.
Hmm I'm not familiar with php://output, my application writes my information with fopen,fwrite,fclose to a temporary file afterwards I give it out with similiar header(); options.
$filesize = filesize("tmp/export.csv");
header("Content-Type: text/csv");
header("Content-Disposition: attachment; filename=\"export.csv\"");
header("Conent-Length: $filesize");
readfile("tmp/export.csv");
unlink("tmp/export.csv");
exit;
This one gives the download window of your browser instantly.
You could try to do this:
call the header function instead of $this->getResponse()->setHeader() (the response content might be saved in a buffer and outputed only when it is completed - the time the export finishes)
try to echo the content directly instead of writing to php://output (if you set the headers before that, everything you echo will be placed in the generated CSV file)
EDIT
Replace fputcsv with a function like print_row below
function print_row($row) {
echo '"' . implode('","', $row) . '"' . "\r\n";
}
The function gets the first parameter as an array, adds " and , and echoes to content.
// $file = path of file
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
Source: http://php.net/manual/en/function.readfile.php
Try using application/force-download instead of text/csv in your Content-Type value.
header("Content-Type: application/force-download; name=\"" . $fileName . "\"");
This will force an actual download box instead of just showing the content in the browser.
Here is some documentation from MIME Application:
Mime application/force-download, which is typically expressed as mime application/x-force-download, is typically used in conjunction with PHP code to trigger a download box rather than for displaying content in a Web browser.
For example, if you had a file on your website that you want the user to download rather than view in the Web browser, you could enter the appropriate mime application/force-download code in the file’s header content type field. Note that mime application/force-download is not a registered MIME type. In addition, when used in PHP code, the preferred spelling of mime application/force-download contains the “x-” prefix.
I don't know if this a good solution, I didn't explore it much, but it seems it's working.
It cotroller after I add some data to buffer after headers, before exporting.
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="'. $fileName . '"');
echo str_pad('', ini_get('output_buffering'));
ob_flush();
flush();
Model_Subscriber1::exportList($listId);
To make it works in controller I added in Zend Bootstrap.php:
/**
* hint to the dispatcher that it should not use output buffering to capture output generated by action controllers.
* By default, the dispatcher captures any output and appends it to the response object body content.
*/
protected function _initFront()
{
$frontController = Zend_Controller_Front::getInstance();
$frontController->setParam('disableOutputBuffering', 1);
}
It look like in this case I get download window in browser quickly and then data are exporting which could take quite a long time. I don't know if this solution is acceptable now. I'd be glad to here your opinion about it.
It cause issues with adding empty data to export file.
So, I change it controller to.
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="'. $fileName . '"');
Model_Subscriber1::exportList($listId);
And chang function for export to something like this:
foreach ($mongodbCursor as $subscriber) {
$row = formRow($subscriber);
echo '"' . implode('","', $row) . '"' . "\r\n";
ob_flush();
flush();
}
Your function may want to call flush / ob_flush just before long operation and let the HTTP header send to client before long process.