Flutter file upload with Dio empty in Laravel - php

I'm unable to upload files using the Dio plugin and I can not figure out where's the problem. In Laravel the request is always empty.
What have I done so far:
Double check if the file path really exists using existsSync() function
Changed the Content-Type to application/x-www-form-urlencoded
Validated if the file is actually being uploaded - seems yes (?)
This is my flutter code:
File myFile = new File('/storage/emulated/0/Download/demo.docx');
FormData form = new FormData.from({
'title': 'Just testing',
'file': new UploadFileInfo(myFile, 'demo.docx')
});
Before sending through POST i checked if the file exists and returns true
print(myFile.existsSync());
And set the Content-type properly
Response response = await Dio().post(
myUrl,
data: form,
options: new Options(
contentType: ContentType.parse("application/x-www-form-urlencoded"),
),
);
Printing the result of the form returns
I/flutter (27929): ----dio-boundary-0118165894
I/flutter (27929): Content-Disposition: form-data; name="title"
I/flutter (27929): ----dio-boundary-1759467036
I/flutter (27929): Content-Disposition: form-data; name="file"; filename="demo.docx"
I/flutter (27929): Content-Type: application/octet-stream
Which I believe indicates that the file is being uploaded.
Now in laravel whenever i output the content received it always comes null the key file, but the key title comes with data.
The code print_r(json_encode($request->all())) retrieves
{"title":"Just testing","file":{}}
The same goes for print_r(json_encode($request->file('file'))).
What am i missing?

Solved.
This took me a while to figure it out, but i end up realizing there's two problems with this approach:
Laravel $request is empty, but $_FILES is not
Sending multiple files can not be sent using arrays as the documentation tells
So, in order to achieve my goal which allows the user to select multiple files dynamically and upload them at the same time, here's the logic behind:
Flutter
The form must be created without setting the files right away:
FormData form = new FormData.from(
{
'title': 'Just testing',
});
Since the function .from is a Map<String, dynamic> values can be added after.
/*
* files = List<String> containing all the file paths
*
* It will end up like this:
* file_1 => $_FILES
* file_2 => $_FILES
* file_3 => $_FILES
*/
for (int i = 0; i < files.length; i++) {
form.add('file_' + i.toString(),
new UploadFileInfo(new File(files[i]), files[i].toString()));
}
There is no need to set up a different Content-Type, therefore this is enough:
Response response = await Dio().post(myUrl, data: form);
Laravel / PHP
Forget about accessing the file through $request->file() and instead use the old school approach.
$totalFiles = count($_FILES);
for ($i = 0; $i < $totalFiles; $i++)
{
$file = $_FILES['file_' . $i];
// handle the file normally ...
$fileName = basename($file['name']);
$fileInfo = pathinfo($file);
$fileExtension = $fileInfo['extension'];
move_uploaded_file($file['tmp_name'], $path);
}

I know this is an old post but this may help someone.
this solution works for me, upload multi-file to server use Flutter Dio library and Laravel as backend. correct me if I did it wrong.
Flutter
BaseOptions _dioOption({#required String token}) {
BaseOptions options = new BaseOptions(baseUrl: baseUrl, headers: {
Headers.acceptHeader: Headers.jsonContentType,
Headers.contentTypeHeader: Headers.jsonContentType,
"Authorization": "Bearer $token"
});
return options;
}
dioPostProduct( {#required ProductToUpload productToUpload,
#required String url, String token}) async {
//productToUpload.images is a List<File>
List<Object> filesData = new List<Object>();
for (final file in productToUpload.images) {
filesData.add(MultipartFile.fromFileSync(file.path,
filename: file.path.split('/').last));
}
FormData data = FormData.fromMap({
"subcategory_id": productToUpload.subcategory_id,
"name": productToUpload.name,
"detail": productToUpload.detail,
"price": productToUpload.price,
"condition_id": productToUpload.condition_id,
"images": filesData,
});
Dio dio = new Dio(_dioOption(token: token));
Response response;
response = await dio.post(url, data: data);
if (response.statusCode == 200) {
print(response.data);
}
}
Laravel
For php resize image I use library intervention
$images = Collection::wrap(request()->file('images'));
$directory = '/product_images'; //make sure directory is exist
foreach ($images as $image) {
$basename = Str::random();
$original = $basename . '.' . $image->getClientOriginalExtension();
$thumbnail = $basename . '_thumb.' . $image->getClientOriginalExtension();
Image::make($image)
->fit(400, 400)
->save(public_path($directory . '/' . $thumbnail));
$image->move(public_path($directory), $original);
}

Related

Flutter web file upload

void _uploadFile(Event event) async {
print("called");
final files = uploadInput.files;
final file = files![0];
_fileNameController.text = file.name;
// Get the file contents as a List<int>
final reader = new FileReader();
reader.readAsArrayBuffer(file);
await reader.onLoad.first;
final contents = reader.result as List<int>;
// Encode the file contents as a base64 string
final encodedFile = base64Encode(contents);
var response = await http.post(Uri.parse("http://url/img_test/img.php"),
body: jsonEncode(<String, String>{
'file': encodedFile,
})
);
print(response.body);
if (response.statusCode == 200) {
// Handle the success case
print("ok");
} else {
// Handle the error case
print("try");
}
}
I all ready set as file in body but till it shows
Error shows in flutter
Warning: Undefined array key "file" in M:\WEB\htdocs\img_test\img.php on line 7
PHP API:
<?php
move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $_FILES['file']['name']);
?>
your request post not send file, this string value, you can try decode in your php file like this
$base64string = "data:image/jpeg;base64,".$POST['file'];
list($type, $base64string) = explode(';', $base64string);
list(,$extension) = explode('/',$type);
list(,$base64string) = explode(',', $base64string);
$fileName = uniqid()'.'.$extension;
$imageFile = base64_decode($base64string);
file_put_contents($path.$fileName, $imageFile);

How to efficiently POST binary data from node.js to PHP

I'm using Node.js to send binary data to PHP. The POST data contains a JSON string, followed by newline, then the binary part.
Sending data from Node:
let binary = null;
if('binary' in msg)
{
binary = msg.binary;
delete msg.binary;
}
let buf = Buffer.from(JSON.stringify(msg) + (binary === null ? '' : '\n'));
if(binary !== null) buf = Buffer.concat([buf, binary]);
let response = await axios.post
(
url,
buf
);
...and receiving it in PHP:
$binary = null;
$in = file_get_contents('php://input');
$pos = strpos($in, "\n");
if($pos === false)
{
$_POST = json_decode($in, true);
}
else
{
$_POST = json_decode(substr($in, 0, $pos), true);
$binary = substr($in, $pos + 1);
}
This works, but I'm getting a warning:
PHP Warning: Unknown: Input variables exceeded 1000.
Is there any way to prevent PHP from trying to parse the POST data?
Separate file from json:
let formData = new FormData();
formData.append('file', fs.createReadStream(filepath));
formData.append('json', '{"jsonstring":"values"}');
axios.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
}).then((response) => {
fnSuccess(response);
}).catch((error) => {
fnFail(error);
});
And PHP
$jsonstring = $_POST['json'];
$json = json_decode($jsonstring,true); // array
$uploaddir = "path/to/uploads/";
$uploadfile = $uploaddir . basename( $_FILES['file']['name']);
if(move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile))
{
$uploadfile // is the path to file uploaded
}
I just discovered PUT. It does exactly what I'm looking for. Just have to change
axios.post(url, buf)
to
axios.put(url, buf)
On the PHP side, there's no attempt to decode anything - it is up to the script to interpret the data.
Although this allows me to do what I want, using it this way violates the HTTP specification. In my case that's not a big deal because it is being used internally, and it prevents some of PHP's unnecessary (for this situation) pre-processing and file I/O.

React-Native Base 64 Data to FileSystem issue

I have a very simple PHP file that returns my image as BASE64 data
<?php
require_once 'database_connections.php';
$data = json_decode(file_get_contents("php://input"));
$id = mysqli_real_escape_string($con, $data->id);
$page = mysqli_real_escape_string($con, $data->page);
$path = "../../images/" . $id . '/' . $page . '.jpg';
$imagedata = file_get_contents($path);
$base64 = base64_encode($imagedata);
echo($base64);
?>
In my react app, I am fetching this data like below
export const getImage = async () => {
try {
const response = await axios.post(
url,
{
id: '11bb2c1b-c262-4171-b614-d8af46898efb',
page: '001',
}
);
return response.data;
} catch (error) {
// handle error
console.error(error.message);
}
};
my base64 response like below:
/9j/2wCEAAEBAQEBAQEBAQECAQEBAgICAQECAgICAgICAgIDAgMDAw...
and finally my FileSystem code is like below:
const image = await getImage();
const filename = FileSystem.documentDirectory + 'imagetest.jpg';
await FileSystem.writeAsStringAsync(filename, image, {
encoding: FileSystem.EncodingType.Base64,
});
I'm not getting any error but my file is not being saved as I try. I have been working on this for hours but didn't solve my issue.
any advice would be appreciated!
Edit: I can show the base64 image in an Image component.
you wont be able to see your saved image file in FileSystem.documentDirectory , you need to
FileSystem.readDirectoryAsync(FileSystem.documentDirectory).then(data => {
data.forEach(filename_ => {
console.log("=cached image==***===" + filename_)
})
to check if it is there. FileSystem.documentDirectory is only "pragmatically accessible"

How to pass base64 encoded image to php?

I was wondering how I would get php script to retrieve my base64 encoded image then write to server? I tried doing a post dump from my php script and I keep getting a response that it is empty. I've tried following a few other stackoverflow guides on this but none of them use a factory afaik.
js
var app = angular.module("app", ["ui.bootstrap"]);
//http://stackoverflow.com/questions/18571001/file-upload-using-angularjs
app.factory('API', function ($http) {
return {
uploadImage: function (image) {
return $http.post('/js/upload.php', image);
}
}
});
app.controller('MainController',['$scope', '$http', 'API', function($scope, $http, API) {
$scope.imageUrl = "";
$scope.template = "";
$scope.templates = [
'select an option...',
'MakeGray',
'Canny'
];
$scope.template = $scope.templates[0];
$scope.add = function() {
var f = document.getElementById('fileToUpload').files[0]; // name of image
var files = document.getElementById('fileToUpload').files;
var r = new FileReader();
r.onload = function(event){
console.log(event.target.result);
}
r.onloadend = function(e) {
var data = e.target.result;
var formData = new FormData();
$("#img1").prop("src", data);
$("#img2").prop("src", data);
formData.append("fileToUpload", f,f.name);
API.uploadImage(formData)
.success(function (imgUrl) {
$scope.imageUrl = imgUrl;
})
.error (function (error) {
});
}
r.readAsDataURL(f);
}
}]);
php
<?php
if(isset($_FILES['fileToUpload'])){
$errors= array();
$file_name = $_FILES['fileToUpload']['name'];
$file_size =$_FILES['fileToUpload']['size'];
$file_tmp =$_FILES['fileToUpload']['tmp_name'];
$file_type=$_FILES['fileToUpload']['type'];
$file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$extensions = array("jpeg","jpg","png");
if(in_array($file_ext,$extensions )=== false){
$errors[]="image extension not allowed, please choose a JPEG or PNG file.";
}
if($file_size > 2097152){
$errors[]='File size cannot exceed 2 MB';
}
if(empty($errors)==true){
move_uploaded_file($file_tmp,"../uploads/".$file_name);
echo " uploaded file: " . "images/" . $file_name;
}else{
print_r($errors);
}
}
else{
$errors= array();
$errors[]="No image found";
print_r($errors);
}
?>
Angular have a particularity in concern the uploading.
First, you have to kno, angular's default transformRequest function will try to serialize our FormData object, so we override it with the identity function to leave the data intact.
Next, the default content-type header for POST requests is "application/json", so you must to change this because you want to upload a file.
By setting 'Content-Type': undefined, the browser sets the Content-Type to multipart/form-data himself and fills in the correct boundary.
Manually setting 'Content-Type': multipart/form-data will fail to fill in the boundary parameter of the request.
Look about others possible issues : https://docs.angularjs.org/api/ng/service/$http
Now you can get you image from $_POST global array of PHP.
Fixed code
uploadImage: function (formData)
{
return $http.post('js/upload.php', formData,
{
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
});
}
Note:
That the below service uses the FormData object which is not supported by IE9 and earlier.

Trying to save canvas data to server [duplicate]

I'm working on a generative art project where I would like to allow users to save the resulting images from an algorithm. The general idea is:
Create an image on an HTML5 Canvas using a generative algorithm
When the image is completed, allow users to save the canvas as an image file to the server
Allow the user to either download the image or add it to a gallery of pieces of produced using the algorithm.
However, I’m stuck on the second step. After some help from Google, I found this blog post, which seemed to be exactly what I wanted:
Which led to the JavaScript code:
function saveImage() {
var canvasData = canvas.toDataURL("image/png");
var ajax = new XMLHttpRequest();
ajax.open("POST", "testSave.php", false);
ajax.onreadystatechange = function() {
console.log(ajax.responseText);
}
ajax.setRequestHeader("Content-Type", "application/upload");
ajax.send("imgData=" + canvasData);
}
and corresponding PHP (testSave.php):
<?php
if (isset($GLOBALS["HTTP_RAW_POST_DATA"])) {
$imageData = $GLOBALS['HTTP_RAW_POST_DATA'];
$filteredData = substr($imageData, strpos($imageData, ",") + 1);
$unencodedData = base64_decode($filteredData);
$fp = fopen('/path/to/file.png', 'wb');
fwrite($fp, $unencodedData);
fclose($fp);
}
?>
But this doesn’t seem to do anything at all.
More Googling turns up this blog post which is based off of the previous tutorial. Not very different, but perhaps worth a try:
$data = $_POST['imgData'];
$file = "/path/to/file.png";
$uri = substr($data,strpos($data, ",") + 1);
file_put_contents($file, base64_decode($uri));
echo $file;
This one creates a file (yay) but it’s corrupted and doesn’t seem to contain anything. It also appears to be empty (file size of 0).
Is there anything really obvious that I’m doing wrong? The path where I’m storing my file is writable, so that isn’t an issue, but nothing seems to be happening and I’m not really sure how to debug this.
Edit
Following Salvidor Dali’s link I changed the AJAX request to be:
function saveImage() {
var canvasData = canvas.toDataURL("image/png");
var xmlHttpReq = false;
if (window.XMLHttpRequest) {
ajax = new XMLHttpRequest();
}
else if (window.ActiveXObject) {
ajax = new ActiveXObject("Microsoft.XMLHTTP");
}
ajax.open("POST", "testSave.php", false);
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
ajax.onreadystatechange = function() {
console.log(ajax.responseText);
}
ajax.send("imgData=" + canvasData);
}
And now the image file is created and isn’t empty! It seems as if the content type matters and that changing it to x-www-form-urlencoded allowed the image data to be sent.
The console returns the (rather large) string of base64 code and the datafile is ~140 kB. However, I still can’t open it and it seems to not be formatted as an image.
Here is an example of how to achieve what you need:
Draw something (taken from canvas tutorial)
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
// begin custom shape
context.beginPath();
context.moveTo(170, 80);
context.bezierCurveTo(130, 100, 130, 150, 230, 150);
context.bezierCurveTo(250, 180, 320, 180, 340, 150);
context.bezierCurveTo(420, 150, 420, 120, 390, 100);
context.bezierCurveTo(430, 40, 370, 30, 340, 50);
context.bezierCurveTo(320, 5, 250, 20, 250, 50);
context.bezierCurveTo(200, 5, 150, 20, 170, 80);
// complete custom shape
context.closePath();
context.lineWidth = 5;
context.fillStyle = '#8ED6FF';
context.fill();
context.strokeStyle = 'blue';
context.stroke();
</script>
Convert canvas image to URL format (base64)
// script
var dataURL = canvas.toDataURL();
Send it to your server via Ajax
$.ajax({
type: "POST",
url: "script.php",
data: {
imgBase64: dataURL
}
}).done(function(o) {
console.log('saved');
// If you want the file to be visible in the browser
// - please modify the callback in javascript. All you
// need is to return the url to the file, you just saved
// and than put the image in your browser.
});
Save base64 on your server as an image (here is how to do this in PHP, the same ideas is in every language. Server side in PHP can be found here):
I played with this two weeks ago, it's very simple. The only problem is that all the tutorials just talk about saving the image locally. This is how I did it:
1) I set up a form so I can use a POST method.
2) When the user is done drawing, he can click the "Save" button.
3) When the button is clicked I take the image data and put it into a hidden field. After that I submit the form.
document.getElementById('my_hidden').value = canvas.toDataURL('image/png');
document.forms["form1"].submit();
4) When the form is submited I have this small php script:
<?php
$upload_dir = somehow_get_upload_dir(); //implement this function yourself
$img = $_POST['my_hidden'];
$img = str_replace('data:image/png;base64,', '', $img);
$img = str_replace(' ', '+', $img);
$data = base64_decode($img);
$file = $upload_dir."image_name.png";
$success = file_put_contents($file, $data);
header('Location: '.$_POST['return_url']);
?>
I think you should convert the image to base64 and then to Blob and send it to the server. When you use base64 images, a lot of lines will be sent to server. With blob, it's only the file.
You can use this code bellow:
function dataURLtoBlob(dataURL) {
let array, binary, i, len;
binary = atob(dataURL.split(',')[1]);
array = [];
i = 0;
len = binary.length;
while (i < len) {
array.push(binary.charCodeAt(i));
i++;
}
return new Blob([new Uint8Array(array)], {
type: 'image/png'
});
};
And canvas code here:
const canvas = document.getElementById('canvas');
const file = dataURLtoBlob( canvas.toDataURL() );
After that you can use ajax with Form:
const fd = new FormData;
fd.append('image', file);
$.ajax({
type: 'POST',
url: '/url-to-save',
data: fd,
processData: false,
contentType: false
});
The code in CoffeeScript syntax:
dataURLtoBlob = (dataURL) ->
# Decode the dataURL
binary = atob(dataURL.split(',')[1])
# Create 8-bit unsigned array
array = []
i = 0
while i < binary.length
array.push binary.charCodeAt(i)
i++
# Return our Blob object
new Blob([ new Uint8Array(array) ], type: 'image/png')
And canvas code here:
canvas = document.getElementById('canvas')
file = dataURLtoBlob(canvas.toDataURL())
After that you can use ajax with Form:
fd = new FormData
# Append our Canvas image file to the form data
fd.append 'image', file
$.ajax
type: 'POST'
url: '/url-to-save'
data: fd
processData: false
contentType: false
Send canvas image to PHP:
var photo = canvas.toDataURL('image/jpeg');
$.ajax({
method: 'POST',
url: 'photo_upload.php',
data: {
photo: photo
}
});
Here's PHP script:
photo_upload.php
<?php
$data = $_POST['photo'];
list($type, $data) = explode(';', $data);
list(, $data) = explode(',', $data);
$data = base64_decode($data);
mkdir($_SERVER['DOCUMENT_ROOT'] . "/photos");
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/photos/".time().'.png', $data);
die;
?>
I've worked on something similar.
Had to convert canvas Base64-encoded image to Uint8Array Blob.
function b64ToUint8Array(b64Image) {
var img = atob(b64Image.split(',')[1]);
var img_buffer = [];
var i = 0;
while (i < img.length) {
img_buffer.push(img.charCodeAt(i));
i++;
}
return new Uint8Array(img_buffer);
}
var b64Image = canvas.toDataURL('image/jpeg');
var u8Image = b64ToUint8Array(b64Image);
var formData = new FormData();
formData.append("image", new Blob([ u8Image ], {type: "image/jpg"}));
var xhr = new XMLHttpRequest();
xhr.open("POST", "/api/upload", true);
xhr.send(formData);
If you want to save data that is derived from a Javascript canvas.toDataURL() function, you have to convert blanks into plusses. If you do not do that, the decoded data is corrupted:
<?php
$encodedData = str_replace(' ','+',$encodedData);
$decocedData = base64_decode($encodedData);
?>
http://php.net/manual/ro/function.base64-decode.php
In addition to Salvador Dali's answer:
on the server side don't forget that the data comes in base64 string format. It's important because in some programming languages you need to explisitely say that this string should be regarded as bytes not simple Unicode string.
Otherwise decoding won't work: the image will be saved but it will be an unreadable file.
I just made an imageCrop and Upload feature with
https://www.npmjs.com/package/react-image-crop
to get the ImagePreview ( the cropped image rendering in a canvas)
https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
canvas.toBlob(function(blob){...}, 'image/jpeg', 0.95);
I prefer sending data in blob with content type image/jpeg rather than toDataURL ( a huge base64 string`
My implementation for uploading to Azure Blob using SAS URL
axios.post(azure_sas_url, image_in_blob, {
headers: {
'x-ms-blob-type': 'BlockBlob',
'Content-Type': 'image/jpeg'
}
})

Categories