Teo M
Teo M

Reputation: 377

how to get a zip with $.ajax from php server

I'm trying to send a zip generated by a php server when requested by jquery's $.ajax.

This is my code:

php:

        $file = tempnam($a_folder_path, "zip");

        $zip = new ZipArchive();
        $zip->open($file, ZipArchive::OVERWRITE);
        $zip->addFile($path_to_json, 'data.json');
        $zip->close();

        rename($file, $file . '.zip');

        $filename = basename($file . '.zip');
        $filepath = $file . '.zip';
        while (ob_get_level()) {
            ob_end_clean();
        }
        header("Pragma: public");
        header("Expires: 0");
        header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");
        header("Content-Description: File Transfer");
        header("Content-type: application/octet-stream");
        header("Content-Disposition: attachment; filename=\"".$filename."\"");
        header("Content-Transfer-Encoding: binary");
        header("Content-Length: ".filesize($filepath));
        ob_end_flush();
        echo file_get_contents($filepath);
        //@readfile($filepath);

javascript:

$.ajax(
        {
            url:    myUrl,                
            type:   'POST',                
            data:   {
                "leData"     : "some_data"
            },
            context: document.body,
            cache:  false,
            success: function(data) {
                console.log(data);
                console.log(data.length);
                var bytes = new Uint8Array(data.length);
                for (var i=0; i<data.length; i++) {
                    bytes[i] = data.charCodeAt(i);
                }
                blob = new Blob([bytes], {type: "application/zip"})
                saveAs(blob, "test.zip");//trying to access data with FileSave.js
                zip.load(data);//trying to access data with JSZip.js
                jsonData = JSON.parse(zip.file('shouldBeThere.json').asText());
            },
            error: function() {
                alert('error');
            }
        }
    );

What happens:

  1. The server creates the zip file I ask it to, and this file isn't corrupted. It contains, among others, shouldBeThere.json.
  2. The server sends data to the frontend.
  3. The console.log(data); line in javascript prints a string almost identical from what I get by opening the zip file created on the server with a text editor.
  4. The console.log(data.length); line in javascript prints a number smaller than the content length of the response header according to chrome's devtools. Maybe a hint to a data corruption.
  5. saveAs creates a zip containing the file it's meant to, with the right name, but when I try to unzip it 7zip shows an error: "an attempt was made to move the file pointer before the beginning of the file". 6.JSZip seems to load the data but then zip.file('shouldBeThere.json') is null.

The problem is I have no idea if the problem comes from php or from javascript. I don't know if php is sending a corrupted zip or if javascript isn't reading it right.

I have tried all the php headers combinations and methods i've found on the internet. I have also tried to do things differently in javascript: using an ArrayBuffer instead of Uint8Array, passing bytes instead of data to zip.load(), using {type: "application/octet-stream"} in Blob().

Upvotes: 7

Views: 10128

Answers (3)

stefo91
stefo91

Reputation: 618

I update your JS code for JSZip 3+ version. It's more easy. I hope i'll be useful to someone.

$.ajax(
    {
    url : url,
    type: "POST",
    data: $('#form').serialize(),
    cache:  false,
    dataType: 'text',         
    responseType :'blob',
    mimeType: 'text/plain; charset=x-user-defined',
    success: function(data) {
        JSZip.loadAsync(data)
        .then(function (zip){
            return zip.generateAsync({type: "blob"})})
        .then(function (blob){
            var date = new Date();
            saveAs(blob, date.getFullYear() + "" + (date.getMonth() + 1) + "" + date.getDate() + "" +  date.getHours() + "" + date.getMinutes() + "" + date.getSeconds()+".zip");
        });
    },
    error: function() {
         alert('error');
    }
);

Upvotes: 0

Yulia Grel
Yulia Grel

Reputation: 1

I was searching for a solution for following problem: "Sending a request on php url for creating a zip of a directory and downloading it by using ajax response."

Following code works:

PHP part for zip:

// Function for creating a zip of a directory
function zipFilesAndDownload($directory, $file_names) {
$zip = new ZipArchive();

if ($zip->open("../temp/$directory.zip", ZIPARCHIVE::CREATE) !== TRUE) {
    exit("Error on creating '../temp/$directory.zip'");
}

foreach ($file_names as $file) {
    $zip->addFile($file, substr($file, strrpos($file, "/") + 1));
}
$zip->close();
readfile("../temp/$directory.zip");
unlink("../temp/$directory.zip");
}

// Code for creating array of filenames
$directory = $_POST['directory']; // e.g. a directory with ID "123"
$dirToZip = "../uploaddir/$directory";
if ($handle = opendir($dirToZip)) {
   $file_names = array();
   while (($file = readdir($handle)) !== false) {
       if ($file != "." && $file != "..") {
           array_push($file_names, "$dirToZip/$file");
       }
   }
   closedir($handle);
   zipFilesAndDownload($directory, $file_names);
}

JS part:

 $(document).on('click', '#downloadDirectory', function () {

      var directory = '123';
      $.ajax({
         type: 'POST',
         url: '../createZip.php',
         data: {"directory": directory},
         dataType: 'text',
         mimeType: 'text/plain; charset=x-user-defined',
         success: function (data) {
                var bytes = new Uint8Array(data.length);
                for (var i = 0; i < data.length; i++) {
                    bytes[i] = data.charCodeAt(i);
                }
                blob = new Blob([bytes], {type: "application/zip"})
                    saveAs(blob, "pictures.zip"); // "saveAs" function is provided in FileSaver.js
                }
      });
});

Upvotes: 0

Teo M
Teo M

Reputation: 377

I finally found a solution: It has to be specified to ajax the received data type, and then convert this unicode data into characters. Here is my new javascript code:

$.ajax(
    {
        url:    myUrl,                
        type:   'POST',                
        data:   {
            "leData"     : "some_data"
        },
        context: document.body,
        cache:  false,
        dataType: 'text',                                   //solution code
        mimeType: 'text/plain; charset=x-user-defined',     //solution code
        success: function(data) {
            console.log(data);
            console.log(data.length);
            newContent = "";                                //solution code
            for (var i = 0; i < data.length; i++) {         //solution code
                newContent += String.fromCharCode(data.charCodeAt(i) & 0xFF); //solution code
            } 
            var bytes = new Uint8Array(newContent.length);                     //modified
            for (var i=0; i<newContent.length; i++) {                          //modified
                bytes[i] = newContent.charCodeAt(i);                           //modified
            }
            blob = new Blob([bytes], {type: "application/zip"})
            saveAs(blob, "test.zip");
            zip.load(newContent);                                              //modified
            jsonData = JSON.parse(zip.file('shouldBeThere.json').asText());
        },
        error: function() {
            alert('error');
        }
    }
);

My php code was fine, it even worked without headers. Here is the minimal php code I need:

 $file = tempnam($a_folder_path, "zip");

    $zip = new ZipArchive();
    $zip->open($file, ZipArchive::OVERWRITE);
    $zip->addFile($path_to_json, 'data.json');
    $zip->close();

    rename($file, $file . '.zip');

    echo file_get_contents($file . '.zip');

Solution inspired by this

Upvotes: 12

Related Questions