<?php
/**
 * $Id$
 * Created on 9. mai. 2007
 * Created by Audun Larsen (audun@munio.no)
 *
 * Copyright 2007 Audun Larsen
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
 *
 * Originally Based on :
 *  http://www.weberdev.com/get_example-4066.html 
 *  By Hasin Hayder <countdraculla@gmail.com>
 *  
 *  http://www.zend.com/codex.php?id=535&single=1
 *  By Eric Mueller <eric@themepark.com>
 *
 *  http://www.zend.com/codex.php?id=470&single=1
 *  by Denis125 <webmaster@atlant.ru>
 *
 *  a patch from Peter Listiak <mlady@users.sourceforge.net> for last modified
 *  date and time of the compressed file
 *
 *  Official ZIP file format: http://www.pkware.com/appnote.txt
 * 
 * Usage example:
 * 
 * $ziper = new zipfile();
 * $ziper->addFiles(array("test/bunny.png","cookiemonster.php", 'avatar.png'));
 * $ziper->save('filename.zip');
 * 
 * 
 * @access  public
 */
 
class zipfile {
    
/**
     * Array to store compressed data
     *
     * @var  array    $datasec
     */
    
var $datasec      = array();

    
/**
     * Central directory
     *
     * @var  array    $ctrl_dir
     */
    
var $ctrl_dir     = array();

    
/**
     * End of central directory record
     *
     * @var  string   $eof_ctrl_dir
     */
    
var $eof_ctrl_dir "\x50\x4b\x05\x06\x00\x00\x00\x00";

    
/**
     * Last offset position
     *
     * @var  integer  $old_offset
     */
    
var $old_offset   0;


    
/**
     * Converts an Unix timestamp to a four byte DOS date and time format (date
     * in high two bytes, time in low two bytes allowing magnitude comparison).
     *
     * @param  integer  the current Unix timestamp
     *
     * @return integer  the current date in a four byte DOS format
     *
     * @access private
     */
    
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;
        } 
// end if

        
return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
                (
$timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
    } 
// end of the 'unix2DosTime()' method


    /**
     * Adds "file" to archive
     *
     * @param  string   file contents
     * @param  string   name of the file in the archive (may contains the path)
     * @param  integer  the current timestamp
     *
     * @access public
     */
    
function addFile($data$name$time 0) {
        
$name     str_replace('\\''/'$name);

        
$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 '";');

        
$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   .= $hexdtime;             // last mod time and date

        // "local file header" segment
        
$unc_len strlen($data);
        
$crc     crc32($data);
        
$zdata   gzcompress($data);
        
$zdata   substr(substr($zdata0strlen($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;

        
// "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;

        
// 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 .= $hexdtime;                 // 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');             // extra field length
        
$cdrec .= pack('v');             // file comment length
        
$cdrec .= pack('v');             // disk number start
        
$cdrec .= pack('v');             // internal file attributes
        
$cdrec .= pack('V'32 );            // external file attributes - 'archive' bit set

        
$cdrec .= pack('V'$this -> old_offset ); // relative offset of local header
        
$this -> old_offset += strlen($fr);

        
$cdrec .= $name;

        
// optional extra field, file comment goes here
        // save to central directory
        
$this -> ctrl_dir[] = $cdrec;
    } 
// end of the 'addFile()' method


    /**
     * Dumps out file
     *
     * @return  string  the zipped file
     *
     * @access public
     */
    
function 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
    
// end of the 'file()' method
   

    /**
     * A Wrapper of original addFile Function
     *
     * Created By Hasin Hayder at 29th Jan, 1:29 AM
     * Modified by Audun Larsen
     *
     * @param array An Array of files with relative/absolute path to be added in Zip File     * 
     * @return boolean
     * @access public
     */
    
function addFiles($files) {    
        if(!
is_array($files)) {
            return 
false;
        }
        foreach(
$files as $file) {        
            if (
is_file($file)) { //directory check        
                
$data file_get_contents($file);
                
$this->addFile($data,$file);                
            }
        }
        return 
true;
    }
   
    
/**
     * A Wrapper of original file Function
     *
     * Created By Hasin Hayder at 29th Jan, 1:29 AM
     * Modified by Audun Larsen
     * 
     * @param string Filename to save the file as
     * @return boolean
     * @access public
     */
    
function save($file) {
        
$fp=@fopen($file,'w');
        if(
$fp !== false) {
            
fwrite($fp,$this->file());
            
fclose($fp);
            return 
true;        
        }        
        return 
false;
    }
    
    
/**
     * Output the zipfile directly to the browser
     * 
     * Created by Audun Larsen
     *  
     * @param string Output filename
     * @access public
     */
     
     
function output($filename) {
         if(
substr($filename,strlen($filename)-44) != '.zip') {
             
$filename $filename.'.zip';
         }
         
         
// We'll be outputting a Zip file
        
header('Content-type: application/zip');
        
header('Content-Disposition: attachment; filename='.$filename);
        
        echo 
$this->file();
        exit();
     }

   

// end of the 'zipfile' class