-3

i have project in school this is payroll and i want to download a qr code generated by google api chart in jquery and the problem is how can i download the qr code? this is the code generated by google api chart

var codeFile = document.getElementsByClassName('codeimg')[0].setAttribute('src', https:chart.googleapis.com/chart?cht=qr&chl=${response.employee_id}&choe=UTF-8&chs=500x500)

this is my html code and code for downloading the qr code `

<div class="modal-dialog">
  <div class="modal-content">
    <div class="modal-header">
      <button type="button" class="close" data-dismiss="modal" aria-label="Close">
        <span aria-hidden="true">&times;</span></button>
      <h4 class="modal-title"><b><span class="employee_id"></span></b></h4>
    </div>
    <div class="modal-body">
      <form class="form-horizontal" method="POST" action="">
        <input type="hidden" class="empid" name="id">
        <div class="qr-code text-center">
          <p>QR CODE</p>
          <h2 class="del_employee_name" style="font-family: arial black;"></h2>
          <!--  <h3 id="employee_id"></h3> -->
          <img class="codeimg" height="200px" width="200px">
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-default btn-flat pull-left" data-dismiss="modal"><i class="fa fa-close"></i> Close</button>
          <button type="button" class="btn btn-primary btn-flat" name="download"><i class="fa fa-download"></i>download</button>

      </form>
      <?php
      $qrCodeData = file_get_contents($codeFile);
      $fileName = "qr_code.png";
      $filePath = "/path/to/save/img/" . $fileName;
      file_put_contents($filePath, $qrCodeData);   
      header("Content-Type: image/png");
      readfile($filePath);
      ?>
    </div>
  </div>
</div>
`

1 Answers1

0

There are a couple of ways you can do this, and I'll show you some of them.

In both of the answers, in order to transfer the remote file to our server, I'll be using a somewhat modified solution provided by this SO answer.

<?php
    // Let's use your QR image link
    $codeFile = 'https://chart.googleapis.com/chart?cht=qr&chl=${response.employee_id}&choe=UTF-8&chs=500x500';

    // Let's also set the headers for the request
    $options = [
        "https" => [
        "method" => "GET",
        "header" => "
            Accept-language: en".PHP_EOL.
            "Cookie: foo=bar".PHP_EOL.
            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8".PHP_EOL.
            "Connection: keep-alive"
        ]
    ];

    // https://www.php.net/manual/en/function.stream-context-create.php
    $context = stream_context_create($options);

    // https://www.php.net/manual/en/function.file-get-contents.php
    $qrCodeData = file_get_contents($codeFile, false, $context);

    $fileName = "qr_code.png";
    $filePath = "./path/to/save/img/" . $fileName;
    file_put_contents($filePath, $qrCodeData); 

    header('Content-Description: File Transfer');
    header("Content-Type: image/png");
    header('Content-Disposition: attachment; filename='.$fileName);
    header('Connection: Keep-Alive');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');

    readfile($filePath);
?>

$options and $context are used so that we can send the headers along with our request to the other server. To simplify it quite a bit, we do this to make our request more specific, and to make it easier for the remote server to understand and to know how to handle our request.

After getting the file, we are force downloading it, by specifying headers of our own. Please note that the headers and the filestream must be the only thing your server outputs, if you want to simply download the file with PHP. You also shouldn't have header(...) in the middle of your HTML - the use of header and other header-related functions (for example, setcookie(...)) is reserved to before outputing anything to the clients / users. The moment you echo / var_dump / sprintf anything, it's too late to use header(...).

If, however, you do want to output some HTML, and you want to have the user download the image by clicking on a link or a button (as it seems to me, by looking at your original code), then something like this would work.

<?php
    // Let's use your QR image link
    $codeFile = 'https://chart.googleapis.com/chart?cht=qr&chl=${response.employee_id}&choe=UTF-8&chs=500x500';

    // Let's also set the headers for the request
    $options = [
        "https" => [
        "method" => "GET",
        "header" => "
            Accept-language: en".PHP_EOL.
            "Cookie: foo=bar".PHP_EOL.
            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8".PHP_EOL.
            "Connection: keep-alive"
        ]
    ];

    $context = stream_context_create($options);

    // Let's supress the errors
    $qrCodeData = @file_get_contents($codeFile, false, $context);

    $fileName = "qr_code.png";
    $filePath = "./path/to/save/img/";
    $fullPath = $filePath . $fileName;
    file_put_contents($fullPath, $qrCodeData);

    // Look, Ma, no headers!
?>
<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
            <h4 class="modal-title"><b><span class="employee_id"></span></b></h4>
        </div>
        <div class="modal-body">
            <form class="form-horizontal" method="POST" action="">
                <input type="hidden" class="empid" name="id">
                <div class="qr-code text-center">
                    <p>QR CODE</p>
                    <h2 class="del_employee_name" style="font-family: arial black;"></h2>
                    <!--  <h3 id="employee_id"></h3> -->
                    <img src="<?php echo $fullPath;?>" height="200px" width="200px">
                    <a id="qrImg" href="<?php echo $fullPath;?>" download="<?=$fileName?>" style="display:none"></a>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default btn-flat pull-left" data-dismiss="modal"><i class="fa fa-close"></i> Close</button>
                    <button type="button" class="btn btn-primary btn-flat" name="download" id="dlBtn"><i class="fa fa-download"></i>download</button>
                </div>
            </form>
        </div>
    </div>
</div>
<script>
    const dlBtn = document.getElementById("dlBtn");
    dlBtn.addEventListener("click",dlImg);

    function dlImg(e) {
        e.preventDefault();

        let qrImg = document.getElementById("qrImg");
        qrImg.click();
    }
</script>

Points of interest:

  1. You need to download the remote file to your server, and keep it there, until the user downloads it. You could trigger an AJAX deletion of the file, if the user closes the modal (not a good thing, since the user can simply close the browser, yank the powersupply cord out of the socket, drop their phone and break it, etc). You could also setup a cronjob to delete these temporary files (once a day, for example), to avoid having them pile up
  2. The use of @ is to supress any errors that might show up. Not always (perhaps nevr) a good thing to do. It was used in this example to avoid potential checks and error handling, for simplicity. In reality, you would have to handle these - what if the file isn't a few KBs, but a few GBs? You would be triggering fatal / out-of-memory errors all over the place
  3. You should consider using cURL to download remote files, if they're large. Read more about cURL on the provided link, if you're not familiar with it
  4. You should always have some sort of validation for the things you download to your server - was it really an image? Are you sure it wasn't a malicious script? There are various ways of handling this. One very simple way would be like this:
function checkImg($source) {
    // check the image size
    $size = getimagesize($source);
    if($size === false) {
        unlink($source);
        return false;
    }

    // is it really an image?
    $sourceImg = @imagecreatefromstring(@file_get_contents($source));
    if ($sourceImg === false) {
        // not an image after we convert it?
        unlink($source);
        unlink($sourceImg);
        return false;
    }

    return true;
}

// let's try it out
$isImg = checkImage($fullPath);

if(!$isImg) {
    // do something
} else {
    // do something else
}

You could also build on this, by introducing additional checks (for example, whether the resolution matches the one you expect);

  1. Be sure to check caniuse to see which browsers support the use of the download attribute (used with a href in this example, from the HTML you have initially provided). At the time of posting this answer, only Opera mini and IE 11 do not support the download attribute, so if your target audience is not using these browsers, you should be in the clear.
FiddlingAway
  • 1,598
  • 3
  • 14
  • 30