4

This is my javascript request:

var XHR = new XMLHttpRequest();
XHR.responseType = 'blob';
XHR.addEventListener('readystatechange', function() {
    if (XHR.readyState == 4) {
        if (XHR.status == 200) {
            var blob = new Blob([XHR.response], {type: "application/mp3"});
            var url = window.URL.createObjectURL(blob);
            var a = document.createElement('a');
            a.href = url;
            a.download = 'Myfile.mp3';
            a.click();
            window.URL.revokeObjectURL(url);
        } else {
            document.getElementById(regidrs).innerHTML = "Error: " + XHR.responseText;
        }
    }
});

var FD  = new FormData();
FD.append('uuid', uuid);
XHR.open('POST', '/fetch');
XHR.send(FD);

This is my module routing:

my_module/my_module.routing.yml

my_module.content:
  path: '/fetch'
  defaults:
    _controller: '\Drupal\my_module\Controller\MyController::content'
    _title: 'Hello World'
  requirements:
    _permission: 'access content'

And my controller:

<?php

namespace Drupal\my_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Form\drupal_set_message;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Defines HelloController class.
 */
class MyController extends ControllerBase {

  /**
   * Display the markup.
   *
   * @return array
   *   Return markup array.
   */
  public function content() {
     ...
        $response= file_get_contents(....);
        $file = file_save_data($response, "public://myaudiofile.mp3", FILE_EXISTS_REPLACE);
     ...
     return [
      '#type' => 'markup',
      '#markup' => $response,
    ];
  }

}

But file_get_contents returns a mp3 file, and I need to pass this mp3 to the javascript httpresponse in order to donwload it. Saving $response to a file, I have checked the file is ok.

How can I do it?

2
  • 1
    Once the question gets answers, its sense cannot be changed, nor can more details be added. In both the cases, the given answers would be invalidated. Questions aren't for an extended back and forth between the user who ask the question and the users who answer. It doesn't work as with forums.
    – apaderno
    Oct 6, 2018 at 21:16
  • Ok. I'm sorry. I thought It was the right way to show my changes.
    – briast
    Oct 6, 2018 at 21:58

2 Answers 2

6

With the BinaryFileResponse class, you can return the content of a file in the response. The FileDownloadController class is an example of controller returning the content of a file. It's the controller for /system/files/{scheme}, /system/files/{filepath}, and /system/temporary.
See its FileDownloadController::download() method.

public function download(Request $request, $scheme = 'private') {
  $target = $request->query
    ->get('file');

  // Merge remaining path arguments into relative file path.
  $uri = $scheme . '://' . $target;
  if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {

    // Let other modules provide headers and controls access to the file.
    $headers = $this
      ->moduleHandler()
      ->invokeAll('file_download', [
      $uri,
    ]);
    foreach ($headers as $result) {
      if ($result == -1) {
        throw new AccessDeniedHttpException();
      }
    }
    if (count($headers)) {

      // \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond()
      // sets response as not cacheable if the Cache-Control header is not
      // already modified. We pass in FALSE for non-private schemes for the
      // $public parameter to make sure we don't change the headers.
      return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');
    }
    throw new AccessDeniedHttpException();
  }
  throw new NotFoundHttpException();
}

The ExportForm class is another example of how to return a file, even if it's a form class. What changes is how the response is used, but how the BinaryFileResponse class is used is still relevant for a controller.
The relevant code is the following one, in ExportForm::submitForm().

$response = new BinaryFileResponse($uri);
$response->setContentDisposition('attachment', $filename);
8
  • 2
    This is the proper way.
    – Kevin
    Oct 6, 2018 at 19:49
  • Thanks @kiamlaluno. I have tried to rewrite my code with your suggestions, but without success. I have edited the question in order to show you my changes.
    – briast
    Oct 6, 2018 at 20:08
  • @briast You just need to return an object of the BinaryFileResponse class. Which hooks the first controller invokes is a different matter, since it is the controller for private files.
    – apaderno
    Oct 6, 2018 at 21:23
  • Sorry, but I don't know how to do it. I have tried it in several ways without success. Would you mind if you put me an example. Thanks.
    – briast
    Oct 7, 2018 at 8:14
  • At least I found the way. The problem was in javascript side.Now it works. I would like to put the solution, but I'm not sure if I should add it as a new answer or update my question. Thanks.
    – briast
    Oct 8, 2018 at 6:41
0

You are 99% there, just return a redirect to the file:

class MyController extends ControllerBase {

  public function content() {
     ...
        $response = file_get_contents(....);
        $file = file_save_data($mp3, "public://myaudiofile.mp3", FILE_EXISTS_REPLACE);
     ...
    return new RedirectResponse( file_create_url('public://myaudiofile.mp3'));

  }

}
8
  • 1
    Thanks for your quick answer but It doesn't work. On javascript event, I get XHR.status error 500. In Drupal log I see the next error: Recoverable fatal error: Object of class Drupal\Core\Url could not be converted to string en Drupal\Component\Utility\UrlHelper::isExternal() .
    – briast
    Oct 6, 2018 at 14:20
  • I just corrected it. can you re-try, please? Oct 6, 2018 at 14:31
  • Briast. Can you try this method. api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Url.php/… Oct 6, 2018 at 14:33
  • Cesar, I have changed it but now the error is 404.
    – briast
    Oct 6, 2018 at 19:51
  • 404 would mean that the file is not there. Can you verify whether the file is really saved? Oct 6, 2018 at 20:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.