0

I have a QR code PNG being dynamically generated. I can access it by going to www.example.com/events/qrgen, which causes it to be displayed in the browser correctly.

Now, I would like another option that downloads the PNG file. Here is what I've tried:

// this action displays a QR code in the browser
public function qrgen() {
    $this->layout = false;
    $this->response->type('image/png');
}

// this action SHOULD make the png download
public function download_qrgen() {
    $qrUrl = Router::url(['controller'=>'events', 'action'=>'qrgen']);
    $this->response->file($qrUrl, ['download'=>true, 'name'=>'qr']);
    return $this->response;
}

// in the "qrgen" view
QRcode::png('example value', null, "M", 4, 4);

But when I access www.example.com/events/download_qrgen, it gives an error "That page could not be found", and shows the CakeResponse->file(string, array) value as: /var/www/example.com//events/qrgen.

If I try a file in my webroot, it correctly downloads the file. But I can't seem to get it to download a dynamically generated png like this.

Dave
  • 28,833
  • 23
  • 113
  • 183

1 Answers1

1

CakeResponse::file() expects a file system path passed to it, not a URL, it's not going to make any HTTP requests to obtain the data.

You have to obtain the data yourself, and then either buffer it in a temporary file whose path in the filesystem you can pass to CakeResponse::file():

public function download_qrgen() {
    $this->layout = false;
    $qrcodeData = $this->_getViewObject()->render('qrgen');

    $temp = tmpfile();
    fwrite($temp, $qrcodeData);
    $filePath = stream_get_meta_data($temp)['uri'];

    $this->response->file($filePath, ['download' => true, 'name' => 'qr.png']);

    return $this->response;
}

or you prepare the response with the data and headers yourself:

public function download_qrgen() {
    $this->layout = false;
    $qrcodeData = $this->_getViewObject()->render('qrgen');

    $this->response->type('image/png');
    $this->response->download('qr.png');
    $this->response->length(strlen($qrcodeData));
    $this->response->header('Content-Transfer-Encoding', 'binary');
    $this->response->body($qrcodeData);

    return $this->response;
}

See also

ndm
  • 59,784
  • 9
  • 71
  • 110
  • Thanks @ndm The first seems the most promising (the 2nd quickly throws a Service Unavail error). The first one actually shows qr.png "Starting..." in the download try of Chrome, but then eventually stops and just goes to "Failed - File incomplete" (after about 30 seconds or so). It's a very small file (427 bytes), so I know it's not timing out for size/speed reasons. Any ideas? – Dave Oct 09 '20 at 16:38
  • 1
    @Dave Ah well it's just untested example that _should_ probably work. I'd have to check later against a 2.x test app. I'd suggest you check the error logs and make sure debug mode is enabled, immediate "crashes" is often just suppressed errors. – ndm Oct 09 '20 at 19:46
  • 1
    @Dave Yeah, I forgot how much 2.x sucked ;) `Controller::render()` populates and returns the response object. I've updated the examples. – ndm Oct 10 '20 at 00:13