I'm trying to send some files from my server but after zipping, sending and downloading the files on the client side the files inside the zip are corrupted.
I've tried to save and open the files directly from the backend to verify that the files are ok and it worked.
my php code:
public function download(Procedure $procedureId, UserInfo $userId,Request $request)
{
$userFiles = $userId->files()->where("procedure_id",$procedureId->id)->get()->toArray();
$collectFiles = collect($userFiles);
$files = $collectFiles->map(function($file){
return ['path'=>$file['path'],'fileName'=>$file['real_name']];
});
// $procedureUserInfo = ProcedureUserInfo::find($request->procedureUserInfoId)->attributesToArray();
// $signedPdf = $procedureUserInfo->pdfPath;
$zip = new ZipArchive;
$zipFile ='app/public/compressedZips/'."{$procedureId->name}_{$userId->firstName}.zip";
if( $zip->open(storage_path($zipFile),ZipArchive::CREATE)===TRUE){
foreach($files as $file){
$filePath = Storage::disk("public")->path($file['path']);
$zip->addFile($filePath,$file["fileName"]);
}
$zip->close();
}
return response()->download(storage_path($zipFile),
"{$procedureId->name}_{$userId->firstName}".Carbon::now().".zip"
,["Content-Type"=>'application/zip','Content-Transfer-Encoding'=>"binary"]);
}
my client code (reactjs):
const downloadFile = async ({procedureUserInfoId, procedureTypetype, userId,procedureId,})=> {
try {
let url = "";
store.dispatch(startLoading("downloading..."));
const { data } = await Axios.get(
`procedures/files/${procedureId}/${userId}/download`,
{
params: { procedureUserInfoId, procedureId },
headers: { Authorization: `Bearer ${store.getState().auth?.token}` },
responseType: "arraybuffer",
}
);
if (data) {
let bytes = new Uint8Array(data);
const blob = new Blob([data], { type: "application/zip" });
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.click();
}
} catch (error) {
console.log(error);
// store.dispatch(setError());
} finally {
store.dispatch(stopLoading());
}
};
Related
i am using document picker to upload an image via php.
this is my js code:
const [singleFile, setSingleFile] = useState(null);
const uploadImage = async () => {
// Check if any file is selected or not
if (singleFile != null) {
// If file selected then create FormData
const fileToUpload = singleFile;
const data = new FormData();
data.append('name', 'imgup');
data.append('attachement_file', fileToUpload);
axios.post(''+ALL.API_URL+'/sellwithus/upload.php', data, {
headers: {
'Content-Type': 'multipart/form-data; ',
}
})
.then((response) => {
console.log(response);
})
} else {
// If no file selected the show alert
alert('Please Select File first');
}
};
the select code:
const selectFile = async () => {
// Opening Document Picker to select one file
try {
const res = await DocumentPicker.pick({
// Provide which type of file you want user to pick
type: [DocumentPicker.types.images],
// There can me more options as well
// DocumentPicker.types.allFiles
// DocumentPicker.types.images
// DocumentPicker.types.plainText
// DocumentPicker.types.audio
// DocumentPicker.types.pdf
});
// Printing the log realted to the file
console.log('res : ' + JSON.stringify(res));
// Setting the state to show single file attributes
setSingleFile(res);
} catch (err) {
setSingleFile(null);
// Handling any exception (If any)
if (DocumentPicker.isCancel(err)) {
// If user canceled the document selection
alert('Canceled');
} else {
// For Unknown Error
alert('Unknown Error: ' + JSON.stringify(err));
throw err;
}
}
};
this is the res result:
console.log(JSON.stringify(res));
res
:[{"size":1454366,"fileCopyUri":null,"name":"D0BED0E3-4567-41DA-9B21-8C409E355A87.JPG","uri":"file:///Users/saeedmatar/Library/Developer/CoreSimulator/Devices/098A7371-530E-4667-AAAF-80EAE97F9A9E/data/Containers/Data/Application/06A2878B-D812-4B3C-BEF0-2E40DBFE9A27/tmp/org.reactjs.native.example.JelApp-Inbox/D0BED0E3-4567-41DA-9B21-8C409E355A87.JPG"}]
this is my php code:
$_POST = json_decode(file_get_contents("php://input"),true);
$imageData=$_POST["_parts"][1][1][0];
file_put_contents('uploads/image.JPG', $imageData["uri"]);
the image that uploaded is 0 mb and not appearing.
how can i use uri to upload the image?
File uri returned by react-native-document-picker is a reference in the device app local cache and can't be used to upload data.
Fetch and upload document BLOB data.
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
resolve(xhr.response);
};
xhr.onerror = function (e) {
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET", [DOCUMENT_PATH_URI_HERE], true);
xhr.send(null);
});
// code to submit blob data
// We're done with the blob, close and release it
blob.close();
I am using ajax to send a request to a laravel controller with multiple images and other,
Everything work on local environement but when I upload to the production environement, the upload fails with the error
Failed to load resource:/property/images/add:1 the server responded with a status of 403 ()
Here is the ajax code
$.ajaxSetup({
headers: {'X-CSRF-TOKEN': $('input[name=_token]').val()}
});
$('#images').change(function () {
event.preventDefault();
let image_upload = new FormData();
let TotalImages = $('#images')[0].files.length; //Total Images
let images = $('#images')[0];
let p_id = $('input[name=p_id]').val();
for (let i = 0; i < TotalImages; i++) {
image_upload.append('images[]', images.files[i]);
}
image_upload.append('TotalImages', TotalImages);
image_upload.append('p_id', p_id);
$.ajax({
method: 'POST',
url: '{{ route('image.add') }}',
data: image_upload,
contentType: false,
processData: false,
success: function (images) {
Swal.fire(
'Success!',
'Images uploaded successfully',
'success'
);
$('#images').reset();
},
error: function () {
Swal.fire(
'Failed!',
'An error occured please try again',
'error'
);
$('#images').reset();
}
});
});
and this is the controller code
public function store(Request $request)
{
//check if an image has been selected
if ($request->images) {
$total=$request->TotalImages;
$images = $request->images;
foreach($images as $image) {
$photo = new Photo;
$photo->p_id = $request->p_id;
$image_temp = $image;
//echo $image_temp; die;
if ($image_temp->isValid()) {
$extension = $image_temp->getClientOriginalExtension();
$filename = 'bks'.mt_rand(000, 9999999999) . '.' . $extension;
$filepath = 'uploads/property/large/' . $filename;
$webimagefilepath = 'uploads/property/small/' . $filename;
//upload the image
Image::make($image_temp)->resize(600, 600)->save($filepath);
Image::make($image_temp)->resize(200, 200)->save($webimagefilepath);
$photo->path = $filename;
$photo->alt_text = "Book sasa property image";
$photo->save();
}
}
}
return response()->json("Success");
}
I am using named routes and the name is the one used in the ajax url.
What could I be doing wrong and how can I solve it?
Consider a site which allows user to store files (pdf, docx, jpeg, png, gif only). Part of the html:
<ul>
<li>lola.doc</li>
<li>lola.pdf</li>
<li>lola.jpeg</li>
<li>lola.docx</li>
</ul>
When a user clicks on any of the above, the file either opens or a save dialpg appears. This is fine.
Now I want user to be able to select some of these files (which are on the server). The files will be zipped and echo back to user with a prompt to save. I cannot use above, so I have this option:
html:
<select class="multiple_select " multiple>
<option value="../folder/lola.doc">lola.doc</option>
<option value="../folder/lola.pdf">lola.pdf</option>
<option value="../folder/lola.jpeg">lola.jpeg</option>
<option value="../folder/lola.docx">lola.docx</option>
</select>
<button id="btn" type="button">Download</button>
js:
js:
$('#btn').on('click', function() {
var options_selected = $('select').find('option:selected');
options_selected_le = options_selected.length;
var i;
var options_selected_arr = [];
var options_names_arr = [];
for (i=0; i<options_selected_le; i++) {
options_selected_arr.push(options_selected.eq(i).val());
options_names_arr.push(options_selected.eq(i).text());
}
var fd = new FormData();
fd.append('zipname', zipname);
fd.append('options_selected_arr', JSON.stringify(options_selected_arr));
fd.append('options_names_arr', JSON.stringify(options_names_arr));
$.ajax({
url: 'download_multiple_files.php',
type: 'post',
data: fd,
cache: false,
contentType: false,
processData: false,
beforeSend: function(xhr) {
xhr.setRequestHeader("X-Download", "yes");
},
success: function(response){
alert(response); //I am sure this is wrong
// Do I need js to handle zip file here. I guess php should automatically do this
}
});
});
<?php
session_start();
require 'server_conn.php'; // for connection and holds test_input function
// do some security checks ...
$zipname = 'file.zip';
$arr = json_decode($_POST['options_selected_arr']);
$file_arr = [];
foreach ($arr as $obj) {
array_push($files_arr, test_input($obj));
}
$arr = json_decode($_POST['options_names_arr']);
$files_names_arr = [];
foreach ($arr as $obj) {
array_push($files_names_arr, test_input($obj));
}
$zip = new ZipArchive;
$zip->open($zipname, ZipArchive::CREATE);
for ($i=0; $i<$c; $i++) {
$zip->addFile($file_arr[$i], $files_names_arr[$i]);
}
$zip->close();
header('Content-Type: application/zip');
header('Content-Length: ' . filesize($zipname));
header('Content-Disposition: attachment; filename="file.zip"');
readfile($zipname);
unlink($zipname);
?>
Response from server is giberish and there is no error indication. I suspect my php is defective.
I have solved this using 2 methods:
Method 1:
JSZip without php (Each select option already contains file path as value)
The advantage of this method: It does not store the new zip file on the server, so storage is not a problem.
I believe using blob will also allow ziping large files, max size I don't know.
To use this method, one needs to download Filesaver, jszip and jszip utility and add following lines to the html doc body
<script src="../js/lib/jszip.min.js"></script>
<script src="../js/lib/jszip-utils.min.js"></script>
<script src="../js/lib/FileSaver.js"></script>
The js script makes use of Promisejs, which I haven't studied before (but will now do). Below is the js:
$('#btn').on('click', function() {
function urlToPromise(url) {
return new Promise(function(resolve, reject) {
JSZipUtils.getBinaryContent(url, function (err, data) {
if(err) {
reject(err);
} else {
resolve(data);
}
});
});
}
var options_selected = $('select').find('option:selected');
options_selected_le = options_selected.length;
var zipname = 'file.zip';
var Promise = window.Promise;
if (!Promise) {
Promise = JSZip.external.Promise;
}
var i;
var zip = new JSZip();
for (i=0; i<options_selected_le; i++) {
var url = options_selected.eq(i).val();
var filename = options_selected.eq(i).text();
zip.file(filename, urlToPromise(url), {binary:true});
}
zip.generateAsync({type:"blob"}).then(function callback(blob) {
//see FileSaver.js
saveAs(blob, zipname);
//alert('success');
}, function (e) {
alert('Error zipping file(s). Retry');
});
});
Method 2:
Using js and PHP:
First create a folder on the server to hold the zip file, I name the folder 'archive'
This is why I may not vote for this method.
New js:
$('#btn').on('click', function() {
var options_selected = $('select').find('option:selected');
options_selected_le = options_selected.length;
var zipname = 'file.zip';
var fd = new FormData();
fd.append('zipname', zipname);
fd.append('options_selected_arr', JSON.stringify(options_selected_arr));
fd.append('options_names_arr', JSON.stringify(options_names_arr));
$.ajax ({
url: 'download_multiple_files.php',
type: 'post',
data: fd,
cache: false,
contentType: false,
processData: false,
success: function(response){
window.location = response;
}
});
});
New php:
<?php
session_start();
// connect to server, scan input data and do some security checks ...
$zipname = 'file.zip';
$arr = json_decode($_POST['options_selected_arr']);
$file_arr = [];
foreach ($arr as $obj) {
array_push($files_arr, test_input($obj));
}
$arr = json_decode($_POST['options_names_arr']);
$files_names_arr = [];
foreach ($arr as $obj) {
array_push($files_names_arr, test_input($obj));
}
$zip = new ZipArchive();
$path = '/archive/'.$zipname;
if ($zip->open($path, ZipArchive::CREATE)!==TRUE) {
echo 'Cannot zip files'; die;
}
$c = count($file_arr);
for ($i=0; $i<$c; $i++) {
$zip->addFile($file_arr[$i], $files_names_arr[$i]);
}
$zip->close();
echo $path;
mysqli_close($conn);
?>
This will force save dialog to appear. Two pending challenges I have for this method are:
Prevent a new window to open
The save dialog appears with download as file name but without extension .zip. So user should type .zip along with the name. I would prefer the computed zip filename to appear in the save dialog
I want to send a docx file to the client in the response of a get request:
Here's the Laravel controller code:
public function file($id)
{
$dlink = resource_path('temp/' . $id . '.docx');
$headers = [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
return response()->download($dlink, $id . '.docx', $headers);
}
VueJS code:
axios.get(`${location.protocol}//${location.host}/api/download/${response.data}`,
{ responseType: "arraybuffer" }
)
.then(response => {
this.downloadFile(response);
})
.catch(err => alert(err));
downloadFile(response) {
var newBlob = new Blob([response.body], {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(newBlob);
return;
}
const data = window.URL.createObjectURL(newBlob);
var link = document.createElement("a");
link.href = data;
link.download = "resume.docx";
link.click();
setTimeout(function() {
window.URL.revokeObjectURL(data);
}, 100);
}
It doesn't show any error. but downloads a corrupted 9bytes docx file.
changing response.body to response.data did the job.
The form has text input and file-input. I studied the tutorial from here.
This is my add.component.ts file:-
import { AdminPage } from '../../../_models/admin.page.model';
import { AdminPageService } from '../../../_admin_service/admin.page';
import { ImageUploadService } from '../../../_common_service/image.upload';
export class AddComponent implements OnInit, AfterViewInit {
.............
.............
adminPageModel = new AdminPage('', '', '', '','');
constructor(private route: ActivatedRoute,
private router: Router,
private _adminPage: AdminPageService,
private _imageUpload: ImageUploadService,
fb: FormBuilder,
private _flashMessagesService: FlashMessagesService) {
this.addPageFormGroup = fb.group({
'title' : [null, Validators.compose([Validators.required])],
'meta_keyword': [null, Validators.required],
'meta_description': [null, Validators.required],
'image':[],
'desc': [null, Validators.required]
});
}
formImageUpload(event){
this._imageUpload.onFileChange(event,this.addPageFormGroup);
}
submitAddPage(value:any){
this.addPageFormGroup.get('desc').setValue($('.Editor-editor').html());
const adminPageModule = this._imageUpload.prepareSave(this.addPageFormGroup);
this._adminPage.postAdminPageAdd(adminPageModule).subscribe(
data => {
this.responseStatus = data;
if(this.responseStatus.status == 1)
{
this._flashMessagesService.show(this.responseStatus.message, { cssClass: 'alert-success', timeout: 2000 });
}
else
{
this._flashMessagesService.show(this.responseStatus.message, { cssClass: 'alert-danger', timeout: 2000 });
}
},
err => {
console.log(err)
},
() => {}
);
this.status = true;
}
}
This is the image.upload.ts service file, where we are setting the formdata from the form:-
#Injectable()
export class ImageUploadService {
constructor() {}
onFileChange(event, formHasImage:any) {
if(event.target.files.length > 0) {
let file = event.target.files[0];
formHasImage.get('image').setValue(file);
}
}
prepareSave(formHasImage): any {
let input = new FormData();
input.append('image', formHasImage.get('image').value);
input.append('title', formHasImage.get('title').value);
input.append('desc', formHasImage.get('desc').value);
input.append('meta_keyword', formHasImage.get('meta_keyword').value);
input.append('meta_description', formHasImage.get('meta_description').value);
console.log(input);
return input;
}
}
This is the admin.page.ts service file where we are hitting the API. This is made by referring to this answer here.
#Injectable()
export class AdminPageService {
http : Http;
actionUrl : string;
admin_page_add_url: string;
postAdminPageAddData: AdminPage;
adminPageAddResponse:Object= [];
constructor(public _http: Http) {
this.http = _http;
this.admin_page_add_url = 'http://localhost/angproject/phpscript/adminpage2.php';
}
// The form Data is being sent as parameter
postAdminPageAdd(postAdminPageAddFormData: any) {
let headers = new Headers();
headers.append('enctype', 'multipart/form-data');
headers.append('Accept', 'application/json');
this.actionUrl = this.admin_page_add_url;
return this.http.post(this.actionUrl,
{ postAdminPageAddFormData },
{ headers: headers })
.map(res => res.json()).share();
}
}
This is the server side php file where we are sending the data. This is made on the accepted answer here:-
<?php
error_reporting(E_ALL);
header("Access-Control-Allow-Origin: http://localhost:4200");
header("Access-Control-Allow-Headers: Content-Type, enctype");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
header('Content-Type: application/json; charset=utf-8');
header('enctype: multipart/form-data');
include('connection.php');
$error = array();
if(isset($_FILES['image']))
{
$image = 'Image Exists';
}
else
{
$error[] = "Image was not entered";
$image = '';
}
if(isset($_POST['title']) && !empty($_POST['title']))
$title = $_POST['title'];
else
$error[] = "Title was not entered";
if(empty($error))
{
$response['status'] = 1;
$response['message'] = $image;
$response['error'] = $conn->error;
}
else
{
$response['status'] = 0;
$response['message'] = "Parameter missing";
$response['error'] = $error;
}
$respond = json_encode($response);
echo $respond;
exit;
?>
My issue is, I am always getting this json response:-
{
"status": 0,
"message": "Parameter missing",
"error": [
"Image was not entered",
"Title was not entered"
]
}
It seems like the formdata aren't being sent to the server end. What am I doing wrong here? Mind it, I have other process too, to submit the form. But in that case, I can send data to server successfully by not using formdata and hence, I can't implement file-upload in that method.
Note: When I do console.log(input), I get this:-
You have two problems with your AdminPageService's postAdminPageAdd method.
First, Headers.append() does not mutate the Headers object, it returns a new Headers object with the original headers and the new one. So you need to do something like:
let headers = new Headers();
headers = headers.append('enctype', 'multipart/form-data');
headers = headers.append('Accept', 'application/json');
Second, the FormData object in the post should not be surrounded with curly brackets - it should work if you do:
return this.http.post(
this.actionUrl,
postAdminPageAddFormData,
{ headers: headers }
).map(res => res.json()).share();
Try appending file directly to FormData object.
#Injectable()
export class ImageUploadService {
file: File;
constructor() {}
onFileChange(event, formHasImage:any) {
if(event.target.files.length > 0) {
file = event.target.files[0];
}
}
prepareSave(formHasImage): any {
let input = new FormData();
input.append('image', this.file);
input.append('title', formHasImage.get('title').value);
input.append('desc', formHasImage.get('desc').value);
input.append('meta_keyword', formHasImage.get('meta_keyword').value);
input.append('meta_description', formHasImage.get('meta_description').value);
console.log(input);
return input;
}
}