33
votes

I'm working with AngularJS and I'm trying to generate a PDF in php. This is what I have in my controller.js:

$scope.downloadPDF = function(){
    $http.post('/download-pdf', { fid: $routeParams.fid })
        .success(function(data, status, headers, config){
            console.log("success");
        })
        .error(function(data, status, headers, config){
            console.log("error");
        });
};

In my php file I have the following to create a PDF with FPDF library:

function download_pdf()
{
    $id = $_POST['fid'];

    $pdf = new FPDF();

    $pdf->AddPage();
    $pdf->SetFont('Arial','B',16);
    $pdf->Cell(40,10,'Hello World!');

    header('Content-Type: application/pdf');
    header('Content-Disposition: attachment; filename=' . $id . '.pdf');

    $pdf->Output('Order123.pdf', 'D');
}

But the request is responding this instead of open a save dialog to save my pdf.

%PDF-1.3 3 0 obj <> endobj 4 0 obj <> stream x3Rðâ2Ð35W(çr QÐw3T04Ó30PISp êZ*[¤(hx¤æää+çå¤(j*dÔ7W endstream endobj 1 0 obj < endobj 5 0 obj < endobj 2 0 obj << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI] /Font << /F1 5 0 R > /XObject << > > endobj 6 0 obj << /Producer (FPDF 1.7) /CreationDate (D:20150611094522) > endobj 7 0 obj << /Type /Catalog /Pages 1 0 R > endobj xref 0 8 0000000000 65535 f 0000000228 00000 n 0000000416 00000 n 0000000009 00000 n 0000000087 00000 n 0000000315 00000 n 0000000520 00000 n 0000000595 00000 n trailer << /Size 8 /Root 7 0 R /Info 6 0 R > startxref 644 %%EOF

I've used the PHPExcel library and this worked:

$objWriter = PHPExcel_IOFactory::createWriter($ea, 'Excel2007');

// We'll be outputting an excel file
header('Content-type: application/vnd.ms-excel');

// It will be called Submission on [date_now].xls
header('Content-Disposition: attachment; filename="' . $filename . '.xls' . '"');

// Write file to the browser
$objWriter->save('php://output');

Now how can I make this work for my PDF?

UPDATE:

I've edited my code to this:

$pdf = new FPDF();

$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello World!');

$filename = DXS_VKGROUP_PLUGIN_LIB_DIR . 'uploads/' . $_POST['fid'] . '.pdf';

$pdf->Output($filename, 'F'); // Save file locally

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize($filename));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="' . $filename . '"');

$handle = fopen($filename, 'rb');
fpassthru($handle);
fclose($handle);

The file is saved locally but the download doesn't work. It doesn't get my a dialog to save the pdf. What am I doing wrong?

UPDATE 2

I've now tried to change application-download in application/pdf. He saves the file locally but I don't get a download dialog box.

The response looks like this (when I check Network in Chrome):

%PDF-1.3 3 0 obj <> endobj 4 0 obj <> stream x3Rðâ2Ð35W(çr QÐw3T04Ó30PISp êZ*[¤(hx¤æää+çå¤(j*dÔ7W endstream endobj 1 0 obj < endobj 5 0 obj < endobj 2 0 obj << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI] /Font << /F1 5 0 R > /XObject << > > endobj 6 0 obj << /Producer (FPDF 1.7) /CreationDate (D:20150617090832) > endobj 7 0 obj << /Type /Catalog /Pages 1 0 R > endobj xref 0 8 0000000000 65535 f 0000000228 00000 n 0000000416 00000 n 0000000009 00000 n 0000000087 00000 n 0000000315 00000 n 0000000520 00000 n 0000000595 00000 n trailer << /Size 8 /Root 7 0 R /Info 6 0 R > startxref 644 %%EOF

8
If your web browser settings doesn't have "Ask where to save each file before downloading" (Chrome) option or similar(Firefox - Always ask where to save files) ticked , then the browser won't show the Save As dialog even if you set the header of the file as application-download or Content-Disposition: attachment . The file will start downloading automatically. Something like github.com/eligrey/FileSaver.js might be helpful in this casePrayag Verma
What happens if you use header('Content-Type: application/octet-stream'); instead of application/pdf ?Darren
@Darren I checked , that too leads to file download process starting automatically without any prompt to save itPrayag Verma
Did you try: this or this?Denys Denysiuk
Why did you let the bounty expire? Are none of the answers useful, or ... ?Ja͢ck

8 Answers

6
votes

UPDATE

The method of using FileSaver.js also doesn't work , there seems to be no way to forcefully invoke the native Save As Dialog via JavaScript alone , only exception being the saveAs execCommand for IE. Check Does execCommand SaveAs work in Firefox?

Include FileSaver.js as dependency in your project

Change the downloadPDF function as follows

 $scope.downloadPDF = function() {
    $http.post('/download-pdf', {
        fid: $routeParams.fid
      }, {
        responseType: 'arraybuffer'
      })
      .success(function(data, status, headers, config) {
        // Convert response data to Blob
        var file = new Blob([data], {
          type: 'application/pdf'
        });
        // Call the File Saver method to save the above Blob,this will show Save As dialog
        saveAs(file, "test.pdf");
      })
      .error(function(data, status, headers, config) {
        console.log("error");
      });

 };

The Blob object will work in most modern browser but there is limited support for IE < 10 , check https://github.com/eligrey/FileSaver.js#supported-browsers for polyfills

Also remove Content-Disposition: attachment and Content-Type: application-download headers as we don't want browser to natively handle the download process

Here is a working demo http://plnkr.co/edit/9r1tehQ94t5vkqZfZuQC?p=preview , it shows GET request to a PDF

3
votes

Let me suggest you one thing.

By AJAX only you cannot download any file. You need to create .zip file first with ajax call and after that gaining that file path you have to open that file using any command.

Here one example that works for me: :)

$http.post('www.abc.com/zipcreate', {'id': id}).then(function (zipurl) {
                    $window.location = zipurl;
            });

Surely, It works. Try once. :)

EDIT:

Or you can use

$window.location = 'abc.com/link';

in 'abc.com/link' : use below code:

  header("Content-type: application/octet-stream");
  header("Content-Disposition: attachment; filename=".$name);
  header("Pragma: no-cache");
  header("Expires: 0");
  readfile($file);
  exit;

You will get your file in download-list.

2
votes

According to the documentation you can set the 2nd parameter to D:

$pdf->Output('Order123.pdf', 'D');

If you want to handle all the details yourself using a saved file, this is how I've presented PDFs to the browser for download from PHP:

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize('file.pdf'));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="file.pdf"');
$handle = fopen('file.pdf', 'rb');
fpassthru($handle);
fclose($handle);

Update:

Please verify that the PDF file is actually being made. Does the web server process have write access to that path? Is FPDF being included properly? I made this on a test VM and it presented a file 1.pdf for download, which opened and contained "Hello World!":

<?php

require('/var/www/html/fpdf17/fpdf.php');

$_POST = array('fid' => '1');

$pdf = new FPDF();

$pdf->AddPage();
$pdf->SetFont('Arial', 'B', 16);
$pdf->Cell(40, 10, 'Hello World!');

$filename = '/tmp/' . $_POST['fid'] . '.pdf';

$pdf->Output($filename, 'F'); // Save file locally

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize($filename));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');

$handle = fopen($filename, 'rb');
fpassthru($handle);
fclose($handle);

unlink($filename);

?>
2
votes

With ajax it is not possible.
Instead what you can do is, hit your action through javascript form submit.

e.g., document.getElementById(formID).action= actionName.

document.getElementById(formID).submit();  

Here the formID is your form's id and the actionName will be '/download-pdf' as you have specified.

In your action class - set response headers

getResponse().setContentType("application/pdf");
getResponse().setHeader("Content-Disposition",  " attachment; filename= "+"pdffile.pdf");

This will cause your file to be downloaded with filename = pdffile.pdf.

2
votes
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"" . basename($file_url) . "\"");

Or using BLOB:

 $scope.downloadPDF = function() {
    $http.post('/download-pdf', {
        fid: $routeParams.fid
      }, {
        responseType: 'arraybuffer'
      })
      .success(function(data, status, headers, config) {

        var file = new Blob([data], {
          type: 'application/pdf'
        });
        var url = URL.createObjectURL(blob);
        window.location.href = url;
      })
      .error(function(data, status, headers, config) {
        console.log("error");
      });

 };

Or create a element with download attribute:

$scope.downloadPDF = function() {
    $http.get('http://46.101.139.188/demo.pdf', {
      }, {
        responseType: 'arraybuffer'
      })
      .success(function(data, status, headers, config) {

        var file = new Blob([data], {
          type: 'application/pdf'
        });
        var url = URL.createObjectURL(file);
        var a = document.createElement('a');
        a.setAttribute('download', 'download');
        a.setAttribute('href', url);

         var event = new MouseEvent('click', {
          'view': window,
          'bubbles': true,
          'cancelable': true
        });
        a.dispatchEvent(event);
      })
      .error(function(data, status, headers, config) {
        console.log("error");
      });
 };

plnkr demo

1
votes

This is not possible with (only) AJAX.

There are two main techniques you could use here, both without using JavaScript to perform the actual download. The general idea is that you redirect the browser to a resource that will provide the desired document; if you're redirecting to a download it will not leave the page but show the Save dialog instead. A similar topic is discussed in this question.

GET Request

location.href = '/download-pdf?fid=' + encodeURIComponent($routeParams.fid);

Adjust your server-side script to use $_GET['fid'].

POST Request

The server creates a resource and responds with a URL where the resource can be found:

$http.post('/download-pdf', { fid: $routeParams.fid })
    .success(function(data, status, headers, config) {
        // follow location provided by server
        location.href = data.redirect_url;
        console.log("success");
    })
    .error(function(data, status, headers, config){
        console.log("error");
    });

Adjust your server-side script to return an appropriate JSON response after creating the new resource.

0
votes

I did it in the following manner and it works well for me:

$(document).ready(function () {
            $('#generaPdf').click(function (e) {
            e.preventDefault();
            var name = $('#name').val();
            $.ajax
                ({
                    type: "POST",
                    url: "pdf.php",
                    data: { "name": name },
                    success: function (data) {
                        alert("todo ok")
                        location.href = "pdf.php"
                        // $('.result').html(data);
                        // $('#contactform')[0].reset();
                    }
                });
            });
        });

My html:

<button type="submit" class="btn btn-primary" id="generaPdf"><i class="fas fa-link"></i> Generar documento para impresión</button>
-1
votes

Send the document to a given destination: browser, file or string. In the case of browser, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.

The method first calls Close() if necessary to terminate the document.

Parameters

name:

The name of the file. If not specified, the document will be sent to the browser (destination I) with the name doc.pdf.

dest

Destination where to send the document. It can take one of the following values:

  • I: send the file inline to the browser. The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.
  • D: send to the browser and force a file download with the name given by name.
  • F: save to a local file with the name given by name (may include a path).
  • S: return the document as a string. name is ignored.