<?php
/*
* @package LabCMS
* @copyright (c) 2006 Alexander Levin aka Loki (loki_angel@mail.ru)
* @support http://labcms.ru
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/  
/* 
Zip file creation class 
makes zip files on the fly... 

use the functions add_dir() and add_file() to build the zip file; 
see example code below 

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

v1.1 9-20-01 
  - added comments to example 

v1.0 2-5-01 

initial version with: 
  - class appearance 
  - add_file() and file() methods 
  - gzcompress() output hacking 
by Denis O.Philippov, webmaster@atlant.ru, http://www.atlant.ru 

*/  

// official ZIP file format: http://www.pkware.com/appnote.txt 

class zipfile
{

	var $datasec = array(); // array to store compressed data 
	var $ctrl_dir = array(); // central directory	
	var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00"; //end of Central directory record 
	var $old_offset = 0;  

	// adds "directory" to archive - do this before putting any files in directory! 
	// $name - name of directory... like this: "path/" 
	// ...then you can add files using add_file with names like "path/file.txt" 	
	function add_dir($name)	
	{
		$name = str_replace("\\", "/", $name);   

		$fr = "\x50\x4b\x03\x04";  
		$fr .= "\x0a\x00";	// ver needed to extract 
		$fr .= "\x00\x00";	// gen purpose bit flag 
		$fr .= "\x00\x00";	// compression method 
		$fr .= "\x00\x00\x00\x00"; // last mod time and date 

		$fr .= pack("V", 0); // crc32 
		$fr .= pack("V", 0); //compressed filesize 
		$fr .= pack("V", 0); //uncompressed filesize 
		$fr .= pack("v", strlen($name)); //length of pathname 
		$fr .= pack("v", 0); //extra field length 
		$fr .= $name;   
		// end of "local file header" segment 

		// no "file data" segment for path 

		// "data descriptor" segment (optional but necessary if archive is not served as file) 
		$fr .= pack("V", $crc); //crc32 
		$fr .= pack("V", $c_len); //compressed filesize 
		$fr .= pack("V", $unc_len); //uncompressed filesize 

		// add this entry to array 
		$this -> 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 
		$cdrec .="\x0a\x00";	// version needed to extract 
		$cdrec .="\x00\x00";	// gen purpose bit flag 
		$cdrec .="\x00\x00";	// compression method 
		$cdrec .="\x00\x00\x00\x00"; // last mod time & date 
		$cdrec .= pack("V", 0); // crc32 
		$cdrec .= pack("V", 0); //compressed filesize 
		$cdrec .= pack("V", 0); //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 
		$ext = "\x00\x00\x10\x00";  
		$ext = "\xff\xff\xff\xff";   
		$cdrec .= pack("V", 16); //external file attributes  - 'directory' bit set 

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

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

		  
	}  

	// adds "file" to archive	
	// $data - file contents 
	// $name - name of file in archive. Add path if your want 
	function add_file($data, $name)	
	{   
		$name = str_replace("\\", "/", $name);   
		//$name = str_replace("\\", "\\\\", $name); 

		$fr = "\x50\x4b\x03\x04";  
		$fr .= "\x14\x00";	// ver needed to extract 
		$fr .= "\x00\x00";	// gen purpose bit flag 
		$fr .= "\x08\x00";	// compression method 
		$fr .= "\x00\x00\x00\x00"; // last mod time and date 

		$unc_len = strlen($data);   
		$crc = crc32($data);   
		$zdata = gzcompress($data);   
		$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug 
		$c_len = strlen($zdata);   
		$fr .= pack("V", $crc); // crc32 
		$fr .= pack("V", $c_len); //compressed filesize 
		$fr .= pack("V", $unc_len); //uncompressed filesize 
		$fr .= pack("v", strlen($name)); //length of filename 
		$fr .= pack("v", 0); //extra field length 
		$fr .= $name;   
		// end of "local file header" segment 
		  
		// "file data" segment 
		$fr .= $zdata;   

		// "data descriptor" segment (optional but necessary if archive is not served as file) 
		$fr .= pack("V", $crc); //crc32 
		$fr .= pack("V", $c_len); //compressed filesize 
		$fr .= pack("V", $unc_len); //uncompressed filesize 

		// add this entry to array 
		$this -> datasec[] = $fr;  

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

		// now add to central directory record 
		$cdrec = "\x50\x4b\x01\x02";  
		$cdrec .="\x00\x00";	// version made by 
		$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 
		$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<br>"; 
		$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 
	}  
}   

