Fix for optimized php zip class that now works on windows and mac

Hi,

I was using the php zip class of Rochak Chauhan for a while but because of the high memory usage I switched to an optimized version of this class from ironhawk.

Unfortunately both implementations where creating zip files that where not working properly on mac. I compared the implementations against some other classes and found out that a section what printed twice which causes mac to fail.
the other zip implementation also where using proper time stamps and I merged this two implementation and added some stuff I needed. So the code below has the following differences to the original implementations:

  • The created zip files where not working on Mac. Now they do. A couple of elements where added twice!
  • It also does not require php 5 anymore. It works also fine on php 4.
  • Compression can be turned off to get more speed if this is important for you.
  • I also added the patch from Peter Listiak <mlady@users.sourceforge.net> for last modified date and time of the compressed file.

The code I used is based on an older class then the one currently published on phpclasses.org. The newer one do also contain directory functions. You would have to merge this if you need them.

You can download the example below here:  zip_class.zip


<?php
/**
* Original source was created by Rochak Chauhan, www.rochakchauhan.com.
* But it used a lot of memory so it was modified.
* The original code kept the continuesly growing .zip stream in memory and in addition
* adding a new file to the stream required filesize*4 bytes.

* Now the growing .zip stream is not kept in memory, it is written to the file continuesly.
* Adding a new file to the .zip requires filesize*2 bytes of memory.
* Plus some more memory required to store the .zip entries -
* this is written only at the end of the process.
*
* 20.2.2011: Fixed by Michael Dempfle (www.tinywebgallery.com).
*            - The created zip files do now work on Mac.
*            - It also does not require php 5 anymore. It works also fine on php 4.
*            - Compression can be turned off to get more speed if this is important for you.
*            - I also added the patch from Peter Listiak <mlady@users.sourceforge.net>
*              for last modified date and time of the compressed file.
*
* This code based on an older class then the one currently published on phpclasses.org.
* The newer one do also contain
* directory functions. You would have to merge this if you need them.
*
* @author ironhawk, Rochak Chauhan  www.rochakchauhan.com
* @package zip
*/
class ZipFile {

var $centralDirectory = array(); // central directory
var $endOfCentralDirectory = "x50x4bx05x06x00x00x00x00"; //end of Central directory record
var $oldOffset = 0;

var $fileHandle;
var $compressedDataLength = 0;

function unix2DosTime($unixtime = 0) {
$timearray = ($unixtime == 0) ? getdate() : getdate($unixtime); if ($timearray['year'] < 1980) {
$timearray['year'] = 1980;
$timearray['mon'] = 1;
$timearray['mday'] = 1;
$timearray['hours'] = 0;
$timearray['minutes'] = 0;
$timearray['seconds'] = 0; }
return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
}

/**
* Creates a new ZipFile object
*
* @param resource $_fileHandle file resource opened using fopen() with "w+" mode
* @return ZipFile
*/
function ZipFile($_fileHandle)
{
$this->fileHandle = $_fileHandle;
}

/**
* Adds a new file to the .zip in the specified .zip folder - previously created using addDirectory()!
*
* @param string $directoryName full path of the previously created .zip folder the file is inserted into
* @param string $filePath full file path on the disk
* @return void
*/
function addFile($filePath, $directoryName, $doCompress = true)   {

// reading content into memory
$data = file_get_contents($filePath);

// adding the time
$time = 0;
$dtime = dechex($this->unix2DosTime($time));
$hexdtime = 'x' . $dtime[6] . $dtime[7] . 'x' . $dtime[4] . $dtime[5] . 'x' . $dtime[2] . $dtime[3] . 'x' . $dtime[0] . $dtime[1];
eval('$hexdtime = "' . $hexdtime . '";');

// create some descriptors
$directoryName = str_replace("", "/", $directoryName);
$feedArrayRow = "x50x4bx03x04";
$feedArrayRow .= "x14x00";
$feedArrayRow .= "x00x00";
$feedArrayRow .= "x08x00";
$feedArrayRow .= $hexdtime;
$uncompressedLength = strlen($data);

// compression of the data
$compression = crc32($data);
// at this point filesize*2 memory is required for a moment but it will be released immediatelly
// once the compression itself done
// compression does not work with mac - I use the compression only to download multiple file so I skip it!
if ($doCompress) {
$data = gzcompress($data);
}
// manipulations
$data = substr($data, 2, strlen($data) - 6);

// writing some info
$compressedLength = strlen($data);
// Compression does not work with mac
if ($doCompress) {
$feedArrayRow .= pack("V",$compression);
$feedArrayRow .= pack("V",$compressedLength);
$feedArrayRow .= pack("V",$uncompressedLength);
}
$feedArrayRow .= pack("v", strlen($directoryName) );
$feedArrayRow .= pack("v", 0 );
$feedArrayRow .= $directoryName;
fwrite($this->fileHandle, $feedArrayRow);
$this->compressedDataLength += strlen($feedArrayRow);

// writing out the compressed content
fwrite($this->fileHandle, $data);
$this->compressedDataLength += $compressedLength;

// some more info...
// The part below cause the mac to fail!
//   $feedArrayRow = pack("V",$compression);
//   $feedArrayRow .= pack("V",$compressedLength);
//   $feedArrayRow .= pack("V",$uncompressedLength);
//fwrite($this->fileHandle, $feedArrayRow);
//$this->compressedDataLength += strlen($feedArrayRow);
$newOffset = $this->compressedDataLength;

// adding entry
$addCentralRecord = "x50x4bx01x02";
$addCentralRecord .="x00x00";
$addCentralRecord .="x14x00";
$addCentralRecord .="x00x00";
$addCentralRecord .="x08x00";
$addCentralRecord .= $hexdtime;
$addCentralRecord .= pack("V",$compression);
$addCentralRecord .= pack("V",$compressedLength);
$addCentralRecord .= pack("V",$uncompressedLength);
$addCentralRecord .= pack("v", strlen($directoryName) );
$addCentralRecord .= pack("v", 0 );
$addCentralRecord .= pack("v", 0 );
$addCentralRecord .= pack("v", 0 );
$addCentralRecord .= pack("v", 0 );
$addCentralRecord .= pack("V", 32 );
$addCentralRecord .= pack("V", $this->oldOffset );
$this->oldOffset = $newOffset;
$addCentralRecord .= $directoryName;
$this->centralDirectory[] = $addCentralRecord;

}

/**
* Close the .zip - we do not add more stuff
*
* @param boolean $closeFileHandle if true the file resource will be closed too
*/
function close($closeFileHandle = true) {

$controlDirectory = implode("", $this->centralDirectory);

fwrite($this->fileHandle, $controlDirectory);
fwrite($this->fileHandle, $this->endOfCentralDirectory);
fwrite($this->fileHandle, pack("v", sizeof($this->centralDirectory)));
fwrite($this->fileHandle, pack("v", sizeof($this->centralDirectory)));
fwrite($this->fileHandle, pack("V", strlen($controlDirectory)));
fwrite($this->fileHandle, pack("V", $this->compressedDataLength));
fwrite($this->fileHandle, "x00x00");

if($closeFileHandle)
fclose($this->fileHandle);
}
}
?>

The usage is really simple:


<?php
/**
*  Example how to create the zip.
*
*  Thanks to Rochak Chauhan for the initial implementation and
*  Attila Wind for the memory optimization.
*  I only fixed and extended the class. So the main credits goes to the guys above.
*
*  The TWG Flash Uploader is using this class as well.
*
* 20.2.2011: Fixed by Michael Dempfle (www.tinywebgallery.com).
*            - The created zip files where not working on Mac. Now they do. A couple of elements where added twice!
*            - It also does not require php 5 anymore. It works also fine on php 4.
*            - Compression can be turned off to get more speed if this is important for you.
*            - I also added the patch from Peter Listiak <mlady@users.sourceforge.net>
*              for last modified date and time of the compressed file.
*
*/

include("zip.class.php");
$fileonserver = "filename.txt";
$filename = "example.zip";
$fd = fopen ($filename, "wb");
$createZip = new ZipFile($fd);
$createZip -> addFile($fileonserver, $fileonserver, true);
$createZip -> close();

?>

This new zip class is also included in TFU 2.13.

Hope this helps someone ;).

Have fun using TFU,

Michael

13 Comments

  1. Max

    This sounds very useful, but how do you compress a directory and all sub dirs. I cant seem to easily combine the two scripts

  2. But it really actually is. Only the method to add files where modified + 2 functions added.

    If I have some time and need the functionality I maybe do this merge.

  3. Max

    If you could that would be great, or if you could just give a few pointers that would be good too. I am in desperate need of this and would be greatful for any extra help.

    Max

  4. @Max
    Just focus on the part where to add the files. This is where I made the changes. The directory stuff and how to add this is still like on the original verson.

    – Michael

  5. Serene

    Is your example working on Mac? Encounter error “Decompression failed” on mac.

  6. Scott

    This is fantastic to have a class like this. My question is, is there a possibility to add multiple files to one zip package that the user can download? If so, how would I add these files to the package?

  7. Amit

    I am using your class but I am getting this error on the mac system “decompression failed ” .Could you guide me to how I can resolved this error. I have to download the images

  8. Chris

    Trying to get these changes merged back into the original, but there is almost nothing that remains of the original. For a non programmer like me, just trying to fix what we have, this became very difficult with all the extra changes.
    Would it be possible for you to provide a modified version of the original where it just fixes the performance issue, and is usable with macs? I know that isn’t likely, but you never know unless you ask.
    I will keep working at it, but not sure I will have any luck.

  9. My class is about 7 years old. There are for sure much better and faster libs already available.
    So your class does work? So why merging it back?

    Best, Michael

Leave a Reply

Your email address will not be published. Required fields are marked *