Drupal 8 - sending files to browser via route module - file

I have a module, and inside this module, there should be a new route in the .routing.yml with:
path: '/file_exporter/{filename}'
defaults:
_controller: '\Drupal\file_exporter\Controller\ExportController::file_export'
Inside the ExportController, there is happening a bit magic, where a file is created depending on the user and other circumstances, and this works fine, and i have this file in a temp folder inside the module.
But how could i send it to the browser with drupal?
Target ist, that i have a link on another site to /fileexporter/file_123.xyz and a click on this link lets the browser directly download the new generated file_123.xyz
Is there a class which i could extend, or a function that i could use in Drupal 8 to send files direct to browsers via a Route and a Controller?

The trick is in using BinaryFileResponse.
Here's an example function that handles setting up the HTTP headers and content type and returns a BinaryFileResponse:
<?php
// $uri: the file you want to send, as a URI (e.g. private://somefile.txt)
// $ofilename: the output filename, this will be displayed in the browser.
// $contenttype: the mime content type for the browser
// $delete_after_send: delete the file once it's been sent to the browser
function mymodule_transfer_file($uri, $ofilename, $contenttype = NULL, $delete_after_send = FALSE) {
$mime = \Drupal::service('file.mime_type.guesser')->guess($uri);
$headers = array(
'Content-Type' => $mime . '; name="' . Unicode::mimeHeaderEncode(basename($uri)) . '"',
'Content-Length' => filesize($uri),
'Content-Disposition' => 'attachment; filename="' . Unicode::mimeHeaderEncode($ofilename) . '"',
'Cache-Control' => 'private',
);
if (isset($contenttype)) {
$headers['Content-Type'] = $contenttype;
}
if ($delete_after_send) {
// Delete after end of script.
drupal_register_shutdown_function('file_unmanaged_delete', $uri);
}
$uri_obj = new Uri($uri);
return new BinaryFileResponse($uri, 200, $headers, $uri_obj->getScheme() !== 'private');
}

Related

Where can i get the IPhone 11 Skin for CN1 Simulator?

The AppStore now require an IPhone 11 (or high IPhone X) skin for their 6.5inch metadata images. Please can someone point me in the direction to it, for use in my Codenameone simulator.
I am on the latest install (CN1 v6), which has up to IPhoneX.skin in my .codenameone folder, but i would like to future proof as much as i can, so go with the 11. Thanks
We don't have an iPhone 11 skin yet although you can file an RFE on that here. But this doesn't matter for most.
Most people use tools such as these to generate the screenshots in a portable way:
https://www.appstorescreenshot.com/
https://theapplaunchpad.com/
https://screenshot-maker.appinstitute.com/app-screenshot-maker/screenshot?platform=ios
I had the same problem. I use a different approach to get one or more screenshots as required by the stores. In short, I execute the app on "Browser Stack App Live" (that has several real devices, like the required iPhone 11), using a code that programmatically send me one or more screenshots of the app, using a REST request.
Note that on Browser Stack App Live it's not possible to send e-mails, that's why I created my own REST API for screenshot uploads.
It's easy, I tested the following solution before sharing it. On a temporary VPS with Apache + PHP installed, or on your local machine if you have a public IP, create the following upload.php, remembering to update $server_url with your actual url:
<?php
header('Content-Type: application/json; charset=utf-8');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: PUT, GET, POST");
$response = array();
$upload_dir = 'uploads/';
$server_url = 'https://www.example.com/mydir/';
if($_FILES['screenshot'])
{
$screenshot_name = $_FILES["screenshot"]["name"];
$screenshot_tmp_name = $_FILES["screenshot"]["tmp_name"];
$error = $_FILES["screenshot"]["error"];
if($error > 0){
$response = array(
"status" => "error",
"error" => true,
"message" => "Error uploading the file!"
);
}else
{
$random_name = rand(1000,1000000)."-".$screenshot_name;
$upload_name = $upload_dir.strtolower($random_name);
$upload_name = preg_replace('/\s+/', '-', $upload_name);
if(move_uploaded_file($screenshot_tmp_name , $upload_name)) {
$response = array(
"status" => "success",
"error" => false,
"message" => "File uploaded successfully",
"url" => $server_url."/".$upload_name
);
}else
{
$response = array(
"status" => "error",
"error" => true,
"message" => "Error uploading the file!"
);
}
}
}else{
$response = array(
"status" => "error",
"error" => true,
"message" => "No file was sent!"
);
}
echo json_encode($response);
?>
After that, mkdir uploads and chown the permissions of the php file and of the upload dir accordingly.
The server is ready.
On your app, add the following method (remember to change the String url value):
public static void sendMeScreenshot() {
Form form = Display.getInstance().getCurrent();
if (form != null) {
try {
Image screenshot = Image.createImage(form.getWidth(), form.getHeight());
form.paintComponent(screenshot.getGraphics(), true);
String file = FileSystemStorage.getInstance().getAppHomePath() + "/screenshot_" + System.currentTimeMillis() + ".png";
OutputStream output = FileSystemStorage.getInstance().openOutputStream(file);
ImageIO.getImageIO().save(screenshot, output, ImageIO.FORMAT_PNG, 1.0f);
String url = "https://www.example.com/mydir/upload.php";
MultipartRequest request = new MultipartRequest();
request.setUrl(url);
request.addData("screenshot", file, "multipart/form-data");
NetworkManager.getInstance().addToQueue(request);
} catch (IOException ex) {
Log.e(ex);
}
}
}
Finally, use a code like UITimer.timer(5000, false, hi, () -> sendMeScreenshot()); to get a screenshot of the Form you are interested to, after the wanted time.
Test in the Simulator, it should work. Add some logging and check the returned JSON in the Network Monitor. If it's all ok, open your app with Browser Stack App Live, selecting the wanted device (iPhone 11 in this case). You will find the screenshot (or screenshots) on the choosen upload dir of your VPS. You can download them with scp or open directly them in your browser.
This solution is useful if you don't own the required device, but keep in mind to don't keep online your upload.php to don't have security issues.

Download large files using React and AspnetCore to open browser save (headers)

I'm struggling with the problem of downloading large files using a web API in asp net core and frontend in React.
When the file starts downloading it doesn't show the dialog browser (Save As File Dialog) until it downloads the file in memory and next gives the possibility to save it.
When the file is bigger, like 200MB, the user can't choose where to save the file before start downloading it and see the download progress in the browser tab.
This is to use with react frontend and web API in aspnet core.
After reading for some hours I couldn't find the solution.
I can't use a link to download the file because I need to authenticate with a token.
Maybe I am missing any configuration or setup in my backend/frontend?
Any suggestions or advice will be very appreciated.
The method of my web api:
[HttpGet("{fileName}")]
[Authorize]
public IActionResult GetFile(string fileName)
{
Stream stream = _fileManager.GetFileContent(fileName);
// Response...
ContentDisposition cd = new ContentDisposition("attachment")
{
FileName = fileName,
// DispositionType = "attachment; filename=" + fileName + ";",
Inline = false // false = prompt the user for downloading; true = browser to try to show the file inline
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
return File(stream, MediaTypeNames.Application.Zip);
}
The frontend using axios to get it.
async function Download() {
var authToken = await authService.Token();
console.log("calling request", process.env.REACT_APP_API_URL + "files/GetFile/iPro.zip");
if (authToken) {
BioAxios.get("/files/GetFile/file.zip", {
responseType: "blob",
headers: {
Authorization: "Bearer ".concat(authToken.access_token),
"Content-Type": "application/zip"
}
}).then(response => {
// Log somewhat to show that the browser actually exposes the custom HTTP header
console.log("Full Response", response);
// Let the user save the file.
FileSaver.saveAs(response.data, "file.zip");
});
}
}

CSRF issues when integrating TinyMCE image upload with CakePHP 3.8

I'm using CakePHP 3.8 to create a CMS for a website. I need a simple WYSIWYG editor with image upload. I'd previously used CKEditor, but was having problems getting the image upload working, so thought I'd try TinyMCE instead.
So, I downloaded TinyMCE 5 (with all standard plugins), linked it in in the head section of my page, and created a form with a TinyMCE textarea like this:
<fieldset>
<legend>New Page</legend>
<?php
echo $this->Flash->render();
echo $this->Form->create($newpage);
echo $this->Form->control('title');
echo $this->Form->control('content',
array('label' => 'Page Content',
'type' => 'textarea',
'id' => 'editor_area'));
echo $this->Form->button('Save');
echo $this->Form->end();
?>
</fieldset>
<script>
tinymce.init({
selector:'#editor_area',
height: 500,
menubar: false,
images_upload_url: '<?php echo IMG_UPLOAD_URL ?>',
toolbar: [
'undo redo | cut copy paste | styleselect | bold italic underline removeformat | alignleft aligncenter alignright | charmap | bullist numlist | link image'
],
plugins: ['advlist lists link autolink image charmap imagetools code']
});
</script>
This works fine, text area appears with the editor etc. The upload url in images_upload_url points to the following UploadsController.php (I've left out the details for brevity; can add them in if needed):
<?php
namespace App\Controller\Admin;
use App\Controller\AppController;
class UploadsController extends AppController
{
public function uploadImage() {
$result = array();
$result['success'] = 'success';
// Process file upload
return $this->response->withType('application/json')
->withStringBody(json_encode($result));
}
}
When I upload an image, I get the following error in the console:
Failed to load resource: the server responded with a status of 403 (Forbidden)
The output from CakePHP shows the error:
Error: CSRF token mismatch.
The debugger shows that the POST includes the following:
Cookie: CAKEPHP=dvsktjv7vp8la5nv7dv19634d1; csrfToken=53e5718e13a1e963d51f9c93c48471a478b35c02b565d6f0699cd2a335775c2b17986cfc2cc587ff7343a6573e3eb2e498a9cb962397599c023417d1dfa9506c; ckCsrfToken=7l2PEC0g06819qQcLwdX5ul7E7jNRa3r61jENt2x
I'm not sure where to go from here.
(Or if there's a more straightforward way to include a free/inexpensive WYSIWYG editor with a decent image/file uploader, I'm open to suggestions! It's a website for a school, so budget is very small and can't be a monthly cost.)
The cookie data is only one part of the CSRF protection mechanism, the client needs to send the CSRF token in either the request data or the X-CSRF-Token header too.
I'm not overly familiar with TinyMCE image uploads, but looking at the docs, you'll probably need a custom upload handler, where you can add additional data, the CSRF token that is.
Taking the example from the TinyMCE docs, the handler could look something like this, where the CSRF token is appended to the form data:
images_upload_handler: function (blobInfo, success, failure) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', <?= json_encode(IMG_UPLOAD_URL) ?>);
xhr.onload = function() {
var json;
if (xhr.status != 200) {
failure('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location != 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}
success(json.location);
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
// append CSRF token in the form data
formData.append('_csrfToken', <?= json_encode($this->request->getParam('_csrfToken')) ?>);
xhr.send(formData);
}
Also according to the docs the response JSON must contain a property named location that contains the web path to the uploaded file, that might be in the code that you've left out, mentioning it just in case.
See also
TinyMCE Docs > Introduction & getting started > Uploading images and files
TinyMCE Docs > Configuration reference > Image & file upload options > images_upload_handler
Cookbook > Security > Cross Site Request Forgery (CSRF) Middleware

Ionic 2 binary file has different content after download

I am using the ionic 2 framework and the cordova file plugin.
I am downloading a pdf file from a server, using the php readfile($path) method, on the client side, i am using Http from angular/http.
Now the problem is, that after the download and a save using the writeFile(path, file,data,options) method from ionic-native/file, the content of the file is not the same as the content from the server.
Notice: Left the output file and right the server version, both printed with the cat command.
I have no idea why the content is not the same.
The http call:
this.http.post(httpEndpoint + ":8280/download", data, headers).subscribe(response => {
me.proceedFile(response.text(), file, fileManager, opener);
});
Here the save:
private proceedFile(data:string, file:File, fileManager:FileManger,
opener:FileOpener) {
...
const me = this;
const putFile = function () {
fileManager.writeFile(fileManager.dataDirectory ,"data/" +
strid+"/" + file.name, data, true).then(_ => {
});
};
...
}
Regards Liz3

Angular + Laravel Delete File

I'm building a REST-full web-app using Laravel 5.2 (for the back-end) and AngularJS for the font-end. I connect to the Laravel back-end by the use of an API. Now I have stumbled upon the following problem: I can properly upload an image, but deleting it again is not working.
The files are uploaded into the Larvel public/images/uploaded/ folder.
This is my Angular Service (the http request fired when clicking the 'delete' button) where the variable imageToDelete is the relative path to the image.. So Far so good, the request is firing and the imageToDelete variable is populated.
function deleteProfileImage(imageToDelete) {
return $http({
method: 'DELETE',
url: '/api/pictures/' + imageToDelete
})
.then(deleteProfileImageSuccess)
.catch(deleteProfileImageError);
function deleteProfileImageSuccess(response) {
$log.info('Deleting profile picture Success.');
console.log(response);
return response;
}
function deleteProfileImageError(error) {
$log.info('Deleting profile picture failed because: ' + error.data);
return error;
}
}
This angular HTTP request fires a DELETE request to the following function in my Laravel Controller.
public function destroy($imageToDelete)
{
if(Storage::delete($imageToDelete)) {
return response()->json(['success' => 'success', 'message' => 'File Deleted']);
} else {
return response()->json(['error' => 'Deleting Image failed.'])
->setStatusCode(Response::HTTP_BAD_REQUEST);
}
}
And here, the Storage::delete($imageToDelete) does nothing. It does not delete the file provided with the Angular DELETE request.
Some things I have already tried:
Working with File::delete() instead of Storage::delete()
Working with unlink() instead op the Laravel Facades;
Sending the imageToDelete as data with the HTTP DELETE request (so not in the URL).
But all without success.
How can I make Laravel (PHP) delete the image?
Thank you for helping!
You can try
$storage = Storage::find($imageToDelete);
if($storage->delete()) {
return response()->json(['success' => 'success', 'message' => 'File Deleted']);
}
or
if(Storage::destroy($imageToDelete)) {
return response()->json(['success' => 'success', 'message' => 'File Deleted']);
}
or
$deleteImage = Storage::where('id', $imageToDelete)->delete();
if($deleteImage){
return response()->json(['success' => 'success', 'message' => 'File Deleted']);
}
You might need to check your image path. When you use laravel storage or file facde your file path should be absolute path for example public_path($imageToDelete)

Resources