2019-08-25 21:45:52 +02:00
< ? php
/**
* Class to create and manage a Zip file .
*
* Inspired by CreateZipFile by Rochak Chauhan www . rochakchauhan . com ( http :// www . phpclasses . org / browse / package / 2322. html )
* and
* http :// www . pkware . com / documents / casestudies / APPNOTE . TXT Zip file specification .
*
* @ author A . Grandt
* @ see Distributed under " General Public License "
* @ version 1.1
*/
class Zip {
2019-11-12 21:25:37 +01:00
private $zipMemoryThreshold = 1048576 ; // Autocreate tempfile if the zip data exceeds 1048576 bytes (1 MB)
private $endOfCentralDirectory = " \x50 \x4b \x05 \x06 \x00 \x00 \x00 \x00 " ; //end of Central directory record
private $localFileHeader = " \x50 \x4b \x03 \x04 " ; // Local file header signature
private $centralFileHeader = " \x50 \x4b \x01 \x02 " ; // Central file header signature
private $zipData = null ;
private $zipFile = null ;
private $zipComment = null ;
private $cdRec = array (); // central directory
private $offset = 0 ;
private $isFinalized = false ;
private $streamChunkSize = 65536 ;
private $streamFilePath = null ;
private $streamTimeStamp = null ;
private $streamComment = null ;
private $streamFile = null ;
private $streamData = null ;
private $streamFileLength = 0 ;
/**
* Constructor .
*
* @ param $useZipFile boolean . Write temp zip data to tempFile ? Default false
*/
function __construct ( $useZipFile = false ) {
if ( $useZipFile ) {
$this -> zipFile = tmpfile ();
} else {
$this -> zipData = " " ;
}
}
function __destruct () {
if ( ! is_null ( $this -> zipFile )) {
fclose ( $this -> zipFile );
}
$this -> zipData = null ;
}
/**
* Set Zip archive comment .
*
* @ param string $newComment New comment . null to clear .
*/
public function setComment ( $newComment = null ) {
$this -> zipComment = $newComment ;
}
/**
* Set zip file to write zip data to .
* This will cause all present and future data written to this class to be written to this file .
* This can be used at any time , even after the Zip Archive have been finalized . Any previous file will be closed .
* Warning : If the given file already exists , it will be overwritten .
*
* @ param string $fileName
*/
public function setZipFile ( $fileName ) {
if ( file_exists ( $fileName )) {
unlink ( $fileName );
}
$fd = fopen ( $fileName , " x+b " );
if ( ! is_null ( $this -> zipFile )) {
rewind ( $this -> zipFile );
while ( ! feof ( $this -> zipFile )) {
fwrite ( $fd , fread ( $this -> zipFile , $this -> streamChunkSize ));
}
fclose ( $this -> zipFile );
} else {
fwrite ( $fd , $this -> zipData );
$this -> zipData = null ;
}
$this -> zipFile = $fd ;
}
/**
* Add an empty directory entry to the zip archive .
* Basically this is only used if an empty directory is added .
*
* @ param string $directoryPath Directory Path and name to be added to the archive .
* @ param int $timestamp ( Optional ) Timestamp for the added directory , if omitted or set to 0 , the current time will be used .
* @ param string $fileComment ( Optional ) Comment to be added to the archive for this directory . To use fileComment , timestamp must be given .
*/
public function addDirectory ( $directoryPath , $timestamp = 0 , $fileComment = null ) {
if ( $this -> isFinalized ) {
return ;
}
$this -> buildZipEntry ( $directoryPath , $fileComment , " \x00 \x00 " , " \x00 \x00 " , $timestamp , " \x00 \x00 \x00 \x00 " , 0 , 0 , 16 );
}
/**
* Add a file to the archive at the specified location and file name .
*
* @ param string $data File data .
* @ param string $filePath Filepath and name to be used in the archive .
* @ param int $timestamp ( Optional ) Timestamp for the added file , if omitted or set to 0 , the current time will be used .
* @ param string $fileComment ( Optional ) Comment to be added to the archive for this file . To use fileComment , timestamp must be given .
*/
public function addFile ( $data , $filePath , $timestamp = 0 , $fileComment = null ) {
if ( $this -> isFinalized ) {
return ;
}
$gzType = " \x08 \x00 " ; // Compression type 8 = deflate
$gpFlags = " \x02 \x00 " ; // General Purpose bit flags for compression type 8 it is: 0=Normal, 1=Maximum, 2=Fast, 3=super fast compression.
$dataLength = strlen ( $data );
$fileCRC32 = pack ( " V " , crc32 ( $data ));
$gzData = gzcompress ( $data );
$gzData = substr ( substr ( $gzData , 0 , strlen ( $gzData ) - 4 ), 2 ); // gzcompress adds a 2 byte header and 4 byte CRC we can't use.
// The 2 byte header does contain useful data, though in this case the 2 parameters we'd be interrested in will always be 8 for compression type, and 2 for General purpose flag.
$gzLength = strlen ( $gzData );
if ( $gzLength >= $dataLength ) {
$gzLength = $dataLength ;
$gzData = $data ;
$gzType = " \x00 \x00 " ; // Compression type 0 = stored
$gpFlags = " \x00 \x00 " ; // Compression type 0 = stored
}
if ( is_null ( $this -> zipFile ) && ( $this -> offset + $gzLength ) > $this -> zipMemoryThreshold ) {
$this -> zipFile = tmpfile ();
fwrite ( $this -> zipFile , $this -> zipData );
$this -> zipData = null ;
}
$this -> buildZipEntry ( $filePath , $fileComment , $gpFlags , $gzType , $timestamp , $fileCRC32 , $gzLength , $dataLength , 32 );
if ( is_null ( $this -> zipFile )) {
$this -> zipData .= $gzData ;
} else {
fwrite ( $this -> zipFile , $gzData );
}
}
/**
* Add a file to the archive at the specified location and file name .
*
* @ param string $dataFile File name / path .
* @ param string $filePath Filepath and name to be used in the archive .
* @ param int $timestamp ( Optional ) Timestamp for the added file , if omitted or set to 0 , the current time will be used .
* @ param string $fileComment ( Optional ) Comment to be added to the archive for this file . To use fileComment , timestamp must be given .
*/
public function addLargeFile ( $dataFile , $filePath , $timestamp = 0 , $fileComment = null ) {
if ( $this -> isFinalized ) {
return ;
}
$this -> openStream ( $filePath , $timestamp , $fileComment );
$fh = fopen ( $dataFile , " rb " );
while ( ! feof ( $fh )) {
$this -> addStreamData ( fread ( $fh , $this -> streamChunkSize ));
}
fclose ( $fh );
$this -> closeStream ();
}
/**
* Create a stream to be used for large entries .
*
* @ param string $filePath Filepath and name to be used in the archive .
* @ param int $timestamp ( Optional ) Timestamp for the added file , if omitted or set to 0 , the current time will be used .
* @ param string $fileComment ( Optional ) Comment to be added to the archive for this file . To use fileComment , timestamp must be given .
*/
public function openStream ( $filePath , $timestamp = 0 , $fileComment = null ) {
if ( $this -> isFinalized ) {
return ;
}
if ( is_null ( $this -> zipFile )) {
$this -> zipFile = tmpfile ();
fwrite ( $this -> zipFile , $this -> zipData );
$this -> zipData = null ;
}
if ( strlen ( $this -> streamFilePath ) > 0 ) {
closeStream ();
}
$this -> streamFile = tempnam ( sys_get_temp_dir (), 'Zip' );
$this -> streamData = gzopen ( $this -> streamFile , " w9 " );
$this -> streamFilePath = $filePath ;
$this -> streamTimestamp = $timestamp ;
$this -> streamFileComment = $fileComment ;
$this -> streamFileLength = 0 ;
}
public function addStreamData ( $data ) {
$length = gzwrite ( $this -> streamData , $data , strlen ( $data ));
if ( $length != strlen ( $data )) {
print " <p>Length mismatch</p> \n " ;
}
$this -> streamFileLength += $length ;
return $length ;
}
/**
* Close the current stream .
*/
public function closeStream () {
if ( $this -> isFinalized || strlen ( $this -> streamFilePath ) == 0 ) {
return ;
}
fflush ( $this -> streamData );
gzclose ( $this -> streamData );
$gzType = " \x08 \x00 " ; // Compression type 8 = deflate
$gpFlags = " \x02 \x00 " ; // General Purpose bit flags for compression type 8 it is: 0=Normal, 1=Maximum, 2=Fast, 3=super fast compression.
$file_handle = fopen ( $this -> streamFile , " rb " );
$stats = fstat ( $file_handle );
$eof = $stats [ 'size' ];
fseek ( $file_handle , $eof - 8 );
$fileCRC32 = fread ( $file_handle , 4 );
$dataLength = $this -> streamFileLength ; //$gzl[1];
$gzLength = $eof - 10 ;
$eof -= 9 ;
fseek ( $file_handle , 10 );
$this -> buildZipEntry ( $this -> streamFilePath , $this -> streamFileComment , $gpFlags , $gzType , $this -> streamTimestamp , $fileCRC32 , $gzLength , $dataLength , 32 );
while ( ! feof ( $file_handle )) {
fwrite ( $this -> zipFile , fread ( $file_handle , $this -> streamChunkSize ));
}
unlink ( $this -> streamFile );
$this -> streamFile = null ;
$this -> streamData = null ;
$this -> streamFilePath = null ;
$this -> streamTimestamp = null ;
$this -> streamFileComment = null ;
$this -> streamFileLength = 0 ;
}
/**
* Close the archive .
* A closed archive can no longer have new files added to it .
*/
public function finalize () {
if ( ! $this -> isFinalized ) {
if ( strlen ( $this -> streamFilePath ) > 0 ) {
$this -> closeStream ();
}
$cd = implode ( " " , $this -> cdRec );
$cdRec = $cd . $this -> endOfCentralDirectory
. pack ( " v " , sizeof ( $this -> cdRec ))
. pack ( " v " , sizeof ( $this -> cdRec ))
. pack ( " V " , strlen ( $cd ))
. pack ( " V " , $this -> offset );
if ( ! is_null ( $this -> zipComment )) {
$cdRec .= pack ( " v " , strlen ( $this -> zipComment )) . $this -> zipComment ;
} else {
$cdRec .= " \x00 \x00 " ;
}
if ( is_null ( $this -> zipFile )) {
$this -> zipData .= $cdRec ;
} else {
fwrite ( $this -> zipFile , $cdRec );
fflush ( $this -> zipFile );
}
$this -> isFinalized = true ;
$cd = null ;
$this -> cdRec = null ;
}
}
/**
* Get the handle ressource for the archive zip file .
* If the zip haven ' t been finalized yet , this will cause it to become finalized
*
* @ return zip file handle
*/
public function getZipFile () {
if ( ! $this -> isFinalized ) {
$this -> finalize ();
}
if ( is_null ( $this -> zipFile )) {
$this -> zipFile = tmpfile ();
fwrite ( $this -> zipFile , $this -> zipData );
$this -> zipData = null ;
}
rewind ( $this -> zipFile );
return $this -> zipFile ;
}
/**
* Get the zip file contents
* If the zip haven ' t been finalized yet , this will cause it to become finalized
*
* @ return zip data
*/
public function getZipData () {
if ( ! $this -> isFinalized ) {
$this -> finalize ();
}
if ( is_null ( $this -> zipFile )) {
return $this -> zipData ;
} else {
rewind ( $this -> zipFile );
$filestat = fstat ( $this -> zipFile );
return fread ( $this -> zipFile , $filestat [ 'size' ]);
}
}
/**
* Send the archive as a zip download
*
* @ param String $fileName The name of the Zip archive , ie . " archive.zip " .
* @ return void
*/
function sendZip ( $fileName ) {
if ( ! $this -> isFinalized ) {
$this -> finalize ();
}
if ( ! headers_sent ( $headerFile , $headerLine ) or die ( " <p><strong>Error:</strong> Unable to send file $fileName . HTML Headers have already been sent from <strong> $headerFile </strong> in line <strong> $headerLine </strong></p> " )) {
if ( ob_get_contents () === false or die ( " \n <p><strong>Error:</strong> Unable to send file <strong> $fileName .epub</strong>. Output buffer contains the following text (typically warnings or errors):<br> " . ob_get_contents () . " </p> " )) {
if ( ini_get ( 'zlib.output_compression' )) {
ini_set ( 'zlib.output_compression' , 'Off' );
}
header ( 'Pragma: public' );
header ( " Last-Modified: " . gmdate ( " D, d M Y H:i:s T " ));
header ( " Expires: 0 " );
header ( " Accept-Ranges: bytes " );
header ( " Connection: close " );
header ( " Content-Type: application/zip " );
header ( 'Content-Disposition: attachment; filename="' . $fileName . '";' );
header ( " Content-Transfer-Encoding: binary " );
header ( " Content-Length: " . $this -> getArchiveSize ());
if ( is_null ( $this -> zipFile )) {
echo $this -> zipData ;
} else {
rewind ( $this -> zipFile );
while ( ! feof ( $this -> zipFile )) {
echo fread ( $this -> zipFile , $this -> streamChunkSize );
}
}
}
}
}
public function getArchiveSize () {
if ( is_null ( $this -> zipFile )) {
return strlen ( $this -> zipData );
}
$filestat = fstat ( $this -> zipFile );
return $filestat [ 'size' ];
}
/**
* Calculate the 2 byte dostime used in the zip entries .
*
* @ param int $timestamp
* @ return 2 - byte encoded DOS Date
*/
private function getDosTime ( $timestamp = 0 ) {
$timestamp = ( int ) $timestamp ;
$date = ( $timestamp == 0 ? getdate () : getDate ( $timestamp ));
if ( $date [ " year " ] >= 1980 ) {
return pack ( " V " , (( $date [ " mday " ] + ( $date [ " mon " ] << 5 ) + (( $date [ " year " ] - 1980 ) << 9 )) << 16 ) |
(( $date [ " seconds " ] >> 1 ) + ( $date [ " minutes " ] << 5 ) + ( $date [ " hours " ] << 11 )));
}
return " \x00 \x00 \x00 \x00 " ;
}
/**
* Build the Zip file structures
*
* @ param unknown_type $filePath
* @ param unknown_type $fileComment
* @ param unknown_type $gpFlags
* @ param unknown_type $gzType
* @ param unknown_type $timestamp
* @ param unknown_type $fileCRC32
* @ param unknown_type $gzLength
* @ param unknown_type $dataLength
* @ param integer $extFileAttr 16 for directories , 32 for files .
*/
private function buildZipEntry ( $filePath , $fileComment , $gpFlags , $gzType , $timestamp , $fileCRC32 , $gzLength , $dataLength , $extFileAttr ) {
$filePath = str_replace ( " \\ " , " / " , $filePath );
$fileCommentLength = ( is_null ( $fileComment ) ? 0 : strlen ( $fileComment ));
$dosTime = $this -> getDosTime ( $timestamp );
$zipEntry = $this -> localFileHeader ;
$zipEntry .= " \x14 \x00 " ; // Version needed to extract
$zipEntry .= $gpFlags . $gzType . $dosTime . $fileCRC32 ;
$zipEntry .= pack ( " VV " , $gzLength , $dataLength );
$zipEntry .= pack ( " v " , strlen ( $filePath ) ); // File name length
$zipEntry .= " \x00 \x00 " ; // Extra field length
$zipEntry .= $filePath ; // FileName . Extra field
if ( is_null ( $this -> zipFile )) {
$this -> zipData .= $zipEntry ;
} else {
fwrite ( $this -> zipFile , $zipEntry );
}
$cdEntry = $this -> centralFileHeader ;
$cdEntry .= " \x00 \x00 " ; // Made By Version
$cdEntry .= " \x14 \x00 " ; // Version Needed to extract
$cdEntry .= $gpFlags . $gzType . $dosTime . $fileCRC32 ;
$cdEntry .= pack ( " VV " , $gzLength , $dataLength );
$cdEntry .= pack ( " v " , strlen ( $filePath )); // Filename length
$cdEntry .= " \x00 \x00 " ; // Extra field length
$cdEntry .= pack ( " v " , $fileCommentLength ); // File comment length
$cdEntry .= " \x00 \x00 " ; // Disk number start
$cdEntry .= " \x00 \x00 " ; // internal file attributes
$cdEntry .= pack ( " V " , $extFileAttr ); // External file attributes
$cdEntry .= pack ( " V " , $this -> offset ); // Relative offset of local header
$cdEntry .= $filePath ; // FileName . Extra field
if ( ! is_null ( $fileComment )) {
$cdEntry .= $fileComment ; // Comment
}
$this -> cdRec [] = $cdEntry ;
$this -> offset += strlen ( $zipEntry ) + $gzLength ;
}
2019-08-25 21:45:52 +02:00
}