Save file in local pc using PHP headers - javascript

I'm developing an SVG editor. I have to save the svg picture on the local disk. As you know, for safety reasons, it is impossible do it directly with javascript. So I decided to approach the problem with the server side help. I wrote the following PHP routine in a file called "savefile.php":
<?php
$mime = array('xls' => 'application/vnd.ms-excel', 'xml' => 'application/xml', 'html' => 'text/html', 'cvs' => 'text/plain', 'svg' => 'text/plain', 'txt' => 'text/plain', 'json' => 'text/plain', 'array' => 'text/plain');
if (isset($_POST['format']) && isset($_POST['filename']) && isset($_POST['content'])) {
$filename = $_POST['filename'];
$format = $_POST['format'];
$content = $_POST['content'];
$fullName = $filename . '.' . $format;
header('Pragma: public');
header("Content-Description: File Transfer");
header("Content-Transfer-Encoding: binary");
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header("Cache-Control: public");
header('Content-Type: ' . $mime[$format]);
header('Content-Disposition: attachment; filename="' . basename($fullName) . '"');
echo $content;
}
?>
On the server side I call, the PHP procedure, with this code:
var obj = {
format: 'svg',
filename: 'myfilename',
content: glb.Draw_active().GetDisegno().svg()
};
$.post("php/savefile.php",obj,function(data,status,xhr){console.log('Executed');});
When the software execute the above code, the browser should open the savefile window and wait the confirm from the user..... but nothing happens. I'm sure that the PHP routine is executed, it is also executed the callback routine, but nothing else.
Something is wrong, my suspect is on the javascript client-side code but I'm not able to find any documentation. May be someone have experience on this procedure?
Thanks a lot.

Thanks to #Quentin for the help I have solved the problem. The PHP code above is working, I had to change the Javascript part. Here the working code:
The SaveFile routine:
/*
#datatype : 'svg', 'xml', 'txt', 'xls', 'csv', 'html', etc see the list in PHP file
#filename : filename without extension
#exportServer : name of the PHP file on the server
#content : content of the file to save
*/
var saveFile = function(datatype, filename, exportServer,content){
var HInput = function(value, name, form) {
var I = document.createElement('input');
I.name = name;
I.value = value;
I.type = 'hidden';
form.appendChild(I);
},
HTextArea = function(value, name, form) {
var tA = document.createElement('textarea');
tA.name = name;
tA.value = value;
form.appendChild(tA);
};
var form = document.createElement('form');
HInput(filename, 'filename', form);
HInput(format, 'format', form);
HTextArea(content, 'content', form);
form.action = exportServer;
form.method = 'post';
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}
.... for my need I call it in this way:
saveFile('svg','myfilename',"php/savefile.php",data);
thats all...... ;)

Related

Why my ajax request always throws error?

Here is my code:
PHP:
public function export(Request $request){
$file = "export.txt";
if(isset($_POST["type"])){
file_put_contents("$file",$_POST["text"]);
}
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: text/plain'); // the appropriate header type for txt file
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;
}
}
JS:
$(document).on('click', '#export', function () {
var names = ['علی','فرید'];
var namess = names.join('\n');
$.ajax({
type: "post",
url: "/export",
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
type: "save",
text: namess
},
dataType: "json",
success: function(){
var href = '/export?filename=export.txt';
window.location = href;
},
error: function(){
alert('wrong');
}
});
})
Always error part executes. I mean it always alert wrong. How can I fix it? All I'm trying to do it making a .txt download file.
Noted that when I run this path: http://localhost:8000/export?filename=export.txt .. then export.txt will be downloaded.
You can download using this code:
window.location="export?filename=export.txt";
If you want to post data :
$('<form action="comments.php" method="POST"/>')
.append($('<input type="hidden" name="type" value="save">'))
.append($('<input type="hidden" name="text" value="'+ namess +'">'))
.appendTo($(document.body)) //it has to be added somewhere into the <body>
.submit();
Full code:
$(document).on('click', '#export', function () {
var names = ['علی','فرید'];
var namess = names.join('\n');
$('<form action="export?filename=export.txt" method="POST"/>')
.append($('<input type="hidden" name="type" value="save">'))
.append($('<input type="hidden" name="text" value="'+ namess +'">'))
.appendTo($(document.body)) //it has to be added somewhere into the <body>
.submit();
});
});
Basically Ajax is not usually used for file download however you can tune to make it feel and that is what you have done the only thing is when the request is successful us a "document.location" function from javascript to popup a new window for downloading a file. Besides for the error you are getting, try debugging your PHP code by playing around with your PHP header by starting with most important PHP Headers such as
header('Content-Type: text/plain');
header('Content-Disposition: attachment;filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
ob_clean();
flush();
readfile($fileName);
exit;
check you code works with minimal information if it works then start adding more header one at a time, this helps in resolving problem with minimal information.
Hope this helps.

Why JS opens a txt file instead of downloading it?

Here is my code:
Server Side:
public function export(){
$arr = array();
if($_POST["type"] == "save"){
$name = "export.txt";
file_put_contents("$name",$_POST["text"]);
$arr["type"] = "link";
$arr["url"] = "http://localhost:8000/{$name}";
}
return $arr;
}
Client Side:
$(document).on('click', '#export', function () {
var names = ["سعید خرمی", "فرید هادوی"];
var namess = names.join('\n');
$.ajax({
type: "post",
url: "/export",
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
type: "save",
text: namess
},
dataType: "json",
success: function(data){
var href = data.url;
window.location = href;
}
});
})
When I click on #export (button), it opens that .txt file (instead of downloading it). Something like this:
Noted that I use chrome .. Also it doesn't work in other browsers.
How can I force it to download that .txt file?
Change your success part like this,
success: function(data){
var href = download.php?filename=export.txt;
window.location = href;
}
And in your download.php:
Get the file name from the GET variable filename (say as $file).
Send the headers:
<?php
$file = $_GET['filename']; // get the filename
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: text/plain'); // the appropriate header type for txt file
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;
}
?>
Source: PHP Readfile Documentation

Html File Save - How to overcome the 2K limit on GET. (Note POST does not work)

I am saving a file constructed in javascript to the local internet downloads directory.
This works:
javascript
var filename = "mysave.txt"
var contents = "xyz";
document.location = "htmlsave.php?filename=" + filename + "&contents=" + contents;
htmlsave.php:
if(!isset($_GET)) {
exit(0);
}
$filename = $_GET['filename'];
$contents = $_GET['contents'];
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false); // required for certain browsers
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"" . $filename . "\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . strlen($contents));
header("Connection: close");
echo $contents;
This does not:
javascript:
var i;
var filename = "mysave.txt"
var contents = "";
for(i=0, contents = "";i<10000;i++){
contents += "x";
}
document.location = "htmlsave.php?filename=" + filename + "&contents=" + contents;
This obviously does not work due to the 2K limit on the url.
The error is:
Request-URI Too Long
The requested URL's length exceeds the capacity limit for this server.
Obviously you need to use POST, but how do you do it with POST? How do you do the 'document.location =' bit with POST data? You cannot do an ajax POST, (see below), I have tried it and it does not work.
The failed POST method:
javascript:
var filename = "mysave.txt"
var contents = "";
for(i=0, contents = "";i<10000;i++){
contents += "x";
}
dataARR = {
filename: filename,
contents: contents
};
var dataJSON = JSON.stringify(dataARR);
$.ajax({
type: "POST", // This for array
url: "htmlsave.php",
async: true,
cache: false,
crossDomain: false, // This needs to be true for other people
data: { myJson: dataJSON },
success: function (data) {
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
}
});
htmlsave.php
if(!isset($_POST) || !isset($_POST['myJson'])) {
exit(0);
}
$dataTotal = json_decode($_POST['myJson'], true);
$filename = $dataTotal['filename'];
$contents = $dataTotal['contents'];
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false); // required for certain browsers
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"" . $filename . "\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . strlen($contents));
header("Connection: close");
echo $contents;
So what can I do to save files greater than 2K?
php is not necessary to create, prompt user to save file created at javascript. You can use <a> element with download attribute set to filename, href set to objectURL of Blob containing file data to initiate prompt for user to save file.
If you want to use php you can utilize php://input, file_get_contents(), echo POSTed Blob or File object.
var i;
var filename = "mysave.txt"
var contents = "";
for(i=0, contents = "";i<10000;i++){
contents += "x";
}
var data = new Blob([contents], {type:"text/plain"});
var file = URL.createObjectURL(data);
var a = document.createElement("a");
a.download = filename;
a.href = file;
a.click();
Using file_ge_contents(), php://input
javascript
var i;
var filename = "mysave.txt"
var contents = "";
for(i=0, contents = "";i<10000;i++){
contents += "x";
}
var data = new Blob([contents], {type:"text/plain"});
var request = new XMLHttpRequest();
request.open("POST", "/path/to/server");
request.setRequestHeader("x-file-name", filename);
request.reponseType = "blob";
request.onload = function() {
console.log(this.response); // `echo`ed file
};
request.send(data);
php
echo file_get_contents("php://input");
See also Generate png-file with php from dataURI sent through ajax ; though note request.setRequestHeader("x-file-name", filename); should have been included at javascript portion of Answer for $tmpFilename = $_SERVER["HTTP_X_FILE_NAME"]; at php

extjs Hidden form after download not firing success

Scouring the net for how to separate the download data from the success/fail data when doing a download using a standardsubmit in extjs, and can't find anything specific about how to do this.
For example, I have a hidden form that I am using to force a download:
var hiddenForm = Ext.create('Ext.form.FormPanel', {
standardSubmit: true,
height: 0,
width: 0,
hidden: true
});
hiddenForm.getForm().submit({
url: 'downloadtxt.php',
method: 'POST',
success: function(form, action) {
console.log('success');
},
failure: function(form, action) {
console.log('failure');
}
});
and on the server in PHP I have this code:
$file_name = "test.txt";
$file_data = "The text to save in the file";
header("Content-Type: " . "text/plain");
header("Content-Length: " . strlen($file_data));
header("Content-Disposition: attachment; filename=\"" . $file_name . "\"");
$result["success"] = true;
$result["errors"] = array();
$result["data"] = $file_data;
echo json_encode($result);
In this case, the entire contents of the $result array gets put into the download file (effectively buggering the contents, and additionally the success/fail methods never get called in the submit() code. Now, if I change the PHP server code to:
$file_name = "test.txt";
$file_data = "The text to save in the file";
header("Content-Type: " . "text/plain");
header("Content-Length: " . strlen($file_data));
header("Content-Disposition: attachment; filename=\"" . $file_name . "\"");
echo $file_data;
then the downloaded file contains what I expected ("The text to save in the file"), but the success/fail methods still never get called in the submit() code. How is it possible to trigger the success/fail code, but yet not bugger up the contents of the download file?

Is it possible to save & retrieve the data filled in HTML form to/from xml file stored locally on PC?

I've recently made an HTML form to fill some service reports & now want to save & retrieve them on/from PC itself (preferably in xml format) (not on server).
I'hv checked it on w3scools /on this page too but still I couldn't get the desired solution.
Could you please inform me the possible ways to make it happen.
You can examine the Javascript behind this application (http://www.cloudformatter.com/Nimbus) like this page here:
Sample Nimbus Invoice
This code shows (1) data in the editor area being saved to local storage constantly, (2) option to make a pseudo HTML/XML file and download to disk (Save to ... Disk), (3) option to make that same package and save to storage on the web (New Nimble, Save Nimble). I am not going to go through all the code, the most relevant is here below for saving locally to disk a package stored in the variable "archive":
function SaveFile(archive) {
var textToBLOB = new Blob([archive.innerHTML], { type: 'text/xml' });
var sFileName = 'varypackage.html';
var newLink = document.createElement('a');
newLink.download = sFileName;
if (window.webkitURL !== null) {
newLink.href = window.webkitURL.createObjectURL(textToBLOB);
}
else {
newLink.href = window.URL.createObjectURL(textToBLOB);
newLink.style.display = 'none';
document.body.appendChild(newLink);
}
newLink.click();
}
Look here for all the code (which contains much more that just the saving/loading but it is in here): http://www.cloudformatter.com/Resources/pages/varyhtml/script/02_control.js
If I understand you, you want to store the data on the user's computer, is that right?
Then it would be the best to create an XML file and force download to user's computer.
(I'm using jQuery because it's fastest)
STEP 1: make an ajax request.
function downloadFormData(form){
var formData= $(form).serialize(); //form is an DOM element - the form you want to retrieve data from
$.ajax({
url:"save.php",
data: formData,
success: function (file) {
window.location.href = "download.php?path="+ file
}
});
}
2) Create a php file, name it as save.php with the following content in your root directory:
<?php
$xml_string = "<?xml version etc.?><data>";
foreach($_POST as $key=>$value)
$xml_string.='<'.$key.'>'.$value.'</'.$key.'>';
$xml_string.='</data>
$file = 'formData.xml';
// save to file
file_put_contents('./'.$file, $xml_string);
// return the filename
echo $file; exit;
5) Create a php file, name it as download.php with the following content in your root directory
<?php
$file = 'formData.xml'
// $file = trim($_GET['path']); if you use download.php for anything else
// force user to download the file
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/xml');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
unlink($file);
exit;
}
else {
echo "$file not found";
}
I hope you understand the way this works, because you definetly can't leave everything like that (then users could download any file they wanted), but i hope my answer will help you.
EDIT:
a PURE javascript solution
function createXMLandDownload(form){
var a = document.createElement("a");
a.download = "MyXml.xml"; //downloaded file name
a.addEventListener("click" , function (){
var xmlstring = '<?xml version="1.0" encoding="utf-8"?><data>';
$(form).find(":input").each(function(){
xmlstring+="<"+this.name+">"+this.value+"</"+this.name+">";
}
xmlstring+="</data>";
document.open('data:Application/octet-stream,' + encodeURIComponent(xmlstring ));
});
a.click();
}

Categories