Creating ZIP files with PHP

UPDATE: You are better off using the library mentioned in this post.

As part of my previously mentioned OpenVPN CA I want to deliver keys, certs and config files to users in a single zip file that they can just extract onto their computers. PHP's own ZIP File Functions only supports reading zip files and not making them.

Some Googling discovered an article by John Coggeshall that can create zip files. It does this by creating the binary data on the fly and can output the zip files directly to the browser from memory or by writing it to disk.

I had some troubles getting hold of a usable version of this code since all these PHP code collection sites have this annoying habit of only showing the syntax highlighted versions of the code rather than give a download link. Eventually got one though and I figured I'll host a mirror of it here to help people out.

Using it is very simple, this is a quick sample that will create a ZIP file and add one directory and one file into then send it directly to the client.

<?
        require ("incl/zipfile.inc.php");

        $zipfile = new zipfile();
        
        $filedata = implode("", file("incl/zipfile.inc.php"));

        $zipfile->add_dir("incl/");
        $zipfile->add_file($filedata, "incl/zipfile.inc.php");

        header("Content-type: application/octet-stream");
        header("Content-disposition: attachment; filename=zipfile.zip");
        echo $zipfile->file();
?>

13 Comments

What's wrong with using gzcompress()/gzdeflate()/gzencode() in the zlib extension?

In the class it uses the gzcompress() function to compress the actual data, but this also puts all the various bits in to make a actual ZIP file as usable by Windows rather than gzip files.

some aprovement .. anyhow .. thx a lot

Hendrik

>> zipfile.inc.php datasec[] = $fr;

$new_offset = strlen(implode("", $this->datasec));

// ext. file attributes mirrors MS-DOS directory attr byte, detailed
// at http://support.microsoft.com/support/kb/articles/Q125/0/19.asp

// now add to central record
$cdrec = "\x50\x4b\x01\x02";
$cdrec .="\x00\x00"; // version made by
if (strtoupper($type) == "DIRECTORY") {
$cdrec .="\x0a\x00"; // version needed to extract
$cdrec .="\x00\x00"; // gen purpose bit flag
$cdrec .="\x00\x00"; // compression method
}
if (strtoupper($type) == "FILE") {
$cdrec .="\x14\x00"; // version needed to extract
$cdrec .="\x00\x00"; // gen purpose bit flag
$cdrec .="\x08\x00"; // compression method
}
$cdrec .="\x00\x00\x00\x00"; // last mod time & date
$cdrec .= pack("V",$crc); // crc32
$cdrec .= pack("V",$c_len); // compressed filesize
$cdrec .= pack("V",$unc_len); // uncompressed filesize
$cdrec .= pack("v", strlen($name) ); // length of filename
$cdrec .= pack("v", 0 ); // extra field length
$cdrec .= pack("v", 0 ); // file comment length
$cdrec .= pack("v", 0 ); // disk number start
$cdrec .= pack("v", 0 ); // internal file attributes

if (strtoupper($type) == "DIRECTORY") {
$cdrec .= pack("V", 16 ); // external file attributes - 'directory' bit set
}
if (strtoupper($type) == "FILE") {
$cdrec .= pack("V", 32 ); // external file attributes - 'archive' bit set
}

$cdrec .= pack("V", $this -> old_offset ); // relative offset of local header
// echo "old offset is ".$this->old_offset.", new offset is $new_offset";
$this -> old_offset = $new_offset;

$cdrec .= $name;
// optional extra field, file comment goes here
// save to central directory
$this -> ctrl_dir[] = $cdrec;
}

function file() {
// dump out file

$data = implode("", $this -> datasec);
$ctrldir = implode("", $this -> ctrl_dir);

return
$data.
$ctrldir.
$this -> eof_ctrl_dir.
pack("v", sizeof($this -> ctrl_dir)). // total # of entries "on this disk"
pack("v", sizeof($this -> ctrl_dir)). // total # of entries overall
pack("V", strlen($ctrldir)). // size of central dir
pack("V", strlen($data)). // offset to start of central dir
"\x00\x00"; // .zip file comment length
}
}

#--------------------------------------------- Hendrik Muus
# require ("zipfile.inc.php");
#
# $zipfile = new zipfile();
#
# $zipfile->add_entry("directory", "testme/", "");
# $zipfile->add_entry("file", "testme/test.file.bin", implode("", file("test.file.bin")));
#
# header("Content-Type: archive/zip\n\n"); // this gives you a nice open / save dialog ;o)
# header("Content-disposition: attachment; filename=zipfile.zip");
#
# echo $zipfile->file();
#---------------------------------------------

?>

Hi, can we use this class in commercial products ?

I don't see any licence info.

Unsure, but like I said on the first line of this posting, its shitty, use the other one, see the link right on top of this post.

Here you can find class + demo + class manual
http://www.smiledsoft.com/demos/phpzip/
for class which you will start using in minutes after installation.
Clear demos explain how to use every function.
A lot of features.
Free version is capable of creating ZIPs only.
Pro is capable to extract files and more.
Please visit
http://www.smiledsoft.com/demos/phpzip/

How can you put all the files of a directory into a zip file without knowing the files?

Hello,

Is there a limit to the number of files that can be zipped up as I am using this script and I get an error "Allowed memory size of 16777216 bytes exhausted" when trying it with a large number of files.

Thanks

Do somebody nkow how could we ask the script to copy the generated zipped file somewhere else on the server rather than user the headers to force the download of the file?

The poster commented in his alternate solution:

"I did some more testing with the code I posted yesterday and found it isn't 100% compatible with some unzip programs. Works with unix unzip, Mac OS X default tool, WinZip, WinRAR but annoyingly not with the default XP zip folder thing."


I had this problem as well but then I discovered that I was accidentally putting superfluous data in my zip file -- some php warning messages were being included as part of the zip file that the user was downloading.

I cleaned up the warnings and now my zip files are working even with the default XP zip folder thing (I don't actually have a better name for that than the one used above ;).

If you have a problem with the zip file you create using this script open the zip file with wordpad and take a look to see if you aren't accidentally putting in data that you don't need.

W

Does this lib support password protection for the zip.?

Problem at hand was to use PEAR Spreadsheet Excel Writer to have php generate multiple spreadsheets, put them in a zip file, and provide the zip file as a download to a user. After about 2 days I got it all to work including using IE.

Basically, first I generate the spreadsheets and save them in a tmp location on the server. Then I insert them into a zip file class one by one and delete the original spreadsheet files. After repeating the process for a few spreadsheets, I dump the zip file.

Hope it helps someone. Here's the code:

===================================================================
export.php

require_once 'class.zipfile.php';
require_once 'Spreadsheet/Excel/Writer.php';

// Generate zip file with containg current spreadsheet file
$zipfile = new zipfile();
//$filedata = 'testing direct input';

// Zip file http headers
$user_browser = $root->check_user_browser();
if ($user_browser == 'IE') header("Content-type: application/zip");
else header("Content-type: application/octet-stream");
header("Content-disposition: attachment; filename=\"filename.zip\"");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
header("Pragma: public"); // IE doesn't work without this

foreach($report_names as $name) {
$filename = $name;

// Generate report for current name and save to /tmp
include './export.excelfile.php';

// Add temp spreadsheet from the include above to the zip file
$filedata = implode("", file('/tmp/'.$filename.'.xls'));

// Add the filedata to the zip file with the correct filename
$zipfile->add_file($filedata, $filename.'.xls');

// delete the current file from temporary storage
unlink('/tmp/'.$filename.'.xls');
}

// Send the zip file to browser
echo $zipfile->file();

===================================================================
export.excelfile.php
Note: "/tmp" must be world writable, ie. apache must be able to write (777)

// Save current spreadsheet to the server hard drive
unset($workbook);
$workbook = new Spreadsheet_Excel_Writer('/tmp/'.$filename.'.xls');
$workbook->setVersion(8);
$workbook->setTempDir(/tmp);

// Set styles and formats
include "export.format.php";
$worksheet =& $workbook->addWorksheet('sheet_name');
$worksheet->setLandscape();

// Grab actual data
$query = 'SELECT DISTINCT * FROM `' . $table_name . '`';
$res = $db->_query($query);

// Data Header Row and similar for data
for ($i = 0; $i mysql_num_fields($res); ++$i) {
$worksheet->writeString(0, $i, $db->mysql_field_name($res, $i), $formats[DATA_HEADER]);
}

// Close the file
$workbook->close();

===================================================================
export.format.php

// defines and custom colours
define('DATA_HEADER', 1);
$workbook->setCustomColor(63, 117, 151, 170); // header text color

// Global style and font
$formats = array();
$common_format = array('font' => 'Arial',
'size' => 8,
'align' => 'center',
'valign' => 'bottom',
'textwrap' => 1
);

// EXCEL DATA HEADER
$formats[DATA_HEADER] =& $workbook->addformat($common_format);
$formats[DATA_HEADER]->setColor(1); // text color
$formats[DATA_HEADER]->setBold();
$formats[DATA_HEADER]->setFgColor(63); // cell color
$formats[DATA_HEADER]->setBorderColor(60); // border styling
$formats[DATA_HEADER]->setTop(2);
$formats[DATA_HEADER]->setBottom(2);
$formats[DATA_HEADER]->setLeft(1);
$formats[DATA_HEADER]->setRight(1);

===================================================================
class.zipfile.php

by Eric Mueller
http://www.themepark.com

http://www.devco.net/archives/2005/05/24/creating_zip_files_with_php.php
http://www.devco.net/code/zipfile.inc.txt

but where is the zipfile.inc.php

Leave a comment

Recent Entries

  • flashpolicyd 2.0

    I wrote a multi threaded server for Adobe Flash Policy requests, some background from Adobe:Since policy files were first introduced, Flash Player has recognized /crossdomain.xml...

  • Adventures with Ruby

    Some more about my continuing experiences with ruby, in my last post I saidthe language does what you'd expect and as you'll see in my...

  • New programming language of choice - Ruby

    I have fallen out of love with Perl some time ago, I cannot point to one specific thing about it that put me off, I...

  • On working from home

    I've not been posting much here, work has been incredibly manic the last while, especially I need to still finish off my SSO posts with...

  • Rework of puppet facts for /etc/facts.txt

    Previously I blogged a custom fact that reads /etc/facts.txt to build up some custom facts for use in Puppet manifests, well I've since learned a...

Close