<?php // Update: Es existiert eine umfangreichere Version unter http://coding.binon.net/ZIP2

/*
Beispielaufruf:

$path2zip=$_SERVER['DOCUMENT_ROOT'].'/data';
$z=new ZIP();
$newzip=$z->zip($path2zip);

// Packt den angegeben Ordner inkl. der Unterordner. Der Name des Archive entspricht dem (extensionslosen) Namen des Ordners + '.zip' und der Ordnername ist im Archiv gesichert (-> 'data\file1.ext').

Weitere Beispiele:
$newzip=$z->zip($path2zip,'',TRUE,TRUE);  // Exakt dasselbe
$newzip=$z->zip($path2zip,'',TRUE,FALSE); // Ordnername wird nicht im Archiv gesichert
$newzip=$z->zip($path2zip,'',TRUE,'dir'); // Nicht der Ordnername, 'dir\' wird gesichert
$newzip=$z->zip($path2zip,'',FALSE,TRUE); // Unterverzeichnisse werden nicht archiviert
$newzip=$z->zip($path2zip,'my.zip');      // Archivname ist 'my.zip'
$newzip=$z->zip($path2zip,'/archive/');   // Archiv wird in Pfad '/archive' erstellt
$newzip=$z->zip($path2zip,'/dir/my.zip'); // '/dir/my.zip' wird erstellt
*/

class ZIP {
 
/* ZIP-Archiv erstellen 051012 */
 /* $source: Dateiliste (string array), Pfad oder leer (=aktuelles Verzeichnis) (string)
    $zipfile: Pfad & Dateiname des ZIP-Archivs, nur Pfad, nur Dateiname oder leer (string)
    $catch: Ggf. auch Unterverzeichnisse archivieren (boolean)
    $include_basedir: Basisverzeichnis im Archiv vermerken (boolean) oder beliebiger Pfad (string)
    Aufruf: $zip=new ZIP();
           $zip->zip([$path2zip[,$zipfilename[,$catch[,$include_basedir]]]]);
    Rueckgabe: Dateiname des ZIP-Archivs oder FALSE
 */
 
function zip($source='.',$zipfile='',$catch=TRUE,$include_basedir=TRUE) {

  
// Startbedingung: zLib muss vorhanden sein
  
if(function_exists('gzcompress')) {
   
// Variableninitalisierung
   
$worklist=array();
   
$prepath='';
   
$listtype='';
   
// Steuervariablen: Extension beachten bei automatischem ZIP-Dateinamen
   
$use_folder_ext=TRUE;
   
$use_file_ext=FALSE;
   
// Steuervariable: Vorhandene Eintraege ueberschreiben
   
$overwrite_entries=TRUE;

   
// Source-Parameter analysieren
   
if(is_array($source)) {
    
// Dateiliste wurde uebergeben: verwenden
    
$filelist=$source;
    
$listtype='entries';
    
// Ggf. Dateinamen des ZIP-Archivs bilden (aus dem Basisverzeichnis-Parameter)
    
if(!$zipfile && is_string($include_basedir) && strlen($include_basedir)) {
     
$include_basedir=unify_path($include_basedir);
     
$zipname=realDirname($include_basedir,1,FALSE);
     
$zipfile=getAbsPath(filename($zipname,!$use_folder_ext).'.zip');
    }
   } elseif(
abs_path($source)) {
    
// Ggf. Dateinamen des ZIP-Archivs bilden ...
    
if(!$zipfile) {
     
// ... aus dem Source-Verzeichnis-/Dateinamen (Ext nach Wunsch bewahren)
     
$zipfile=getAbsPath(filename($source,!(is_dir($source)?$use_folder_ext:$use_file_ext)).'.zip');
    } elseif(
is_path($zipfile)) {
     
// ... aus dem vorgegebenen Pfad + Source-Dateiname (Ext nach Wunsch bewahren)
     
$zipfile.=filename($source,!(is_dir($source)?$use_folder_ext:$use_file_ext)).'.zip';
    }
    if(
is_dir($source)) {
     
// Verzeichnis wurde uebergeben: Dateiliste erstellen
     
$filelist=filelist('',$source,'',($catch),FALSE);
     
$listtype='directory';
    } elseif(
is_file($source)) {
     
// Dateiname wurde uebergeben: in Dateiliste umwandeln
     
$filelist=array($source);
     
$listtype='entries';
    }
   }

   
// Abbrechen, wenn ungueltiger Source-Parameter
   
if(!$listtype || !abs_path($zipfile) || !isWritable($zipfile)) {
    
$result=FALSE;
   } else {
    
// Basisverzeichnis voranstellen?
    
if($include_basedir!==FALSE) {

     
// Als Basisverzeichnis das ...
     
if(is_string($include_basedir) && strlen($include_basedir)) {
      
// ... explizit uebergebene verwenden
      
$prepath=$include_basedir.'/';
     } elseif(
$listtype=='directory') {
      
// ... das tatsaechliche verwenden (sofern ein Verzeichnis archiviert werden soll)
      
$prepath=basename(($source)?realpath($source):getcwd()).'/';
     }

     
// Basisverzeichnis vereinheitlichen (inkl. Pfadtrenner am Ende)
     
if($prepath) { $prepath=str_replace('//','/',unify_path($prepath.'/')); }
    }

    
// Arbeitsliste aus Namen der ZIP-Eintraege (Key) und Server-Dateinamen (Value) erstellen
    
foreach($filelist as $filename) {
     
// Serverdateinamen ermitteln (wurde Verzeichnisname uebergeben, den Pfad mitgeben)
     
$serverFilename=(($listtype=='directory')?($source.'/'):'').$filename;
     if(
is_file($serverFilename)) {

      
// Wenn Dateiliste uebergeben wurde: Nur Dateinamen ohne Pfad sichern
      
if($listtype=='entries') { $filename=basename($filename); }

      
// Arbeitseintrag zusammenstellen
      
$worklist[$prepath.$filename]=$serverFilename;
     }
    }
    
// Die Dateiliste wird jetzt nicht mehr gebraucht
    
unset($filelist);

    
// Vorhandenes Archiv auswerten
    
if(file_exists($zipfile)) {
     
$zh=zip_open($zipfile);
     if(
$zh) {
      
// Alle Eintraege durchgehen
      
while($entry=zip_read($zh)) {
       if(
zip_entry_open($zh,$entry,'r')) {
        
// Name des Eintrags ermitteln
        
$entryFilename=zip_entry_name($entry);
        
// Existiert kein identischer Name in der Arbeitsliste oder Ueberschreiben ist ohnehin nicht erlaubt?
        
if(!isset($worklist[$entryFilename]) || $overwrite_entries===FALSE) {
         
// Ja, also vorhandene Datei in neues Archiv uebernehmen und ...
         
$this->addFile(zip_entry_read($entry,zip_entry_filesize($entry)),$entryFilename);
         
// ... Eintrag ggf. aus der worklist entfernen
         
if(isset($worklist[$entryFilename])) { unset($worklist[$entryFilename]); }
        }
        
zip_entry_close($entry);
       }
      }
      
zip_close($zh);
     }
    }

    
// Arbeitsliste durchgehen
    
foreach($worklist as $entryFilename => $serverFilename) {
     
// Archiv zusammenstellen
     
$this->addFile(file_get_contents($serverFilename),$entryFilename);
    }

    
// Archivdatei erstellen/ausgeben
    
$archive=$this->file();
    
$fh=@fopen($zipfile,'wb');
    if(
$fh) {
     
fwrite($fh,$archive,strlen($archive));
     
fclose($fh);
     
$result=$zipfile;
    } else {
     
$result=FALSE;
    }

   }
  } else {
   
$result=FALSE;
  }
  return 
$result;
 }

 
// =================== Zip file creation class (uses zLib) ===================

    
var $datasec      = array();
    var 
$ctrl_dir     = array();
    var 
$eof_ctrl_dir "\x50\x4b\x05\x06\x00\x00\x00\x00";
    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,9);
        
$c_len   strlen($zdata);
        
$zdata   substr(substr($zdata0strlen($zdata) - 4), 2); // fix crc bug
        
$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;
        
$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 .= $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 $new_offset;

        
$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

// end of the 'ZIP' class

// ---------------------------------------------------------------------------

/* Pfadtrenner (auf Unix-Format) vereinheitlichen 051004 */
function unify_path($path,$dos=FALSE) {
 return (
$dos)?str_replace('/','\\',$path):str_replace('\\','/',$path);
}

// ---------------------------------------------------------------------------

/* Ermittelt Dateinamen 050629 */
function filename($file,$nosuffix=TRUE) {
 
// Dateinamen ermitteln
 
$filename=realBasename($file);
 
// Wenn Dateiendung nicht erwuenscht ...
 
if($nosuffix) {
  
// ... Position der Endung ermitteln ...
  
$extPos=strrpos($filename,'.');
  
// ... und Dateinamen ggf. kuerzen
  
if($extPos && $extPos<strlen($filename)-1) { $filename=substr($filename,0,$extPos); }
 }
 return 
$filename;
}

// ---------------------------------------------------------------------------

/* Ermittelt einen Verzeichnisnamen nach Nummer, bzw. Anzahl der Verzeichnisse 051005 */
function realDirname($path,$part=-1,$last_is_file=TRUE) {
 
$result=FALSE;

 
// Pfad vereinheitlichen ...
 
$path=unify_path($path);
 
// ... und zerlegen
 
$folder=explode('/',$path);

 
// Ggf. Dateinamen entfernen
 
if($last_is_file) {
  
$filename=array_pop($folder);
  
$path=implode('/',$folder);
 }

 
// Ggf. vorhandenen "/" (oder "x:/") am Pfadanfang ausgleichen
 
if((strlen($path)>&& $path{0}=='/') || (strlen($path)>&& $path{1}==':')) { array_shift($folder); }

 
// Je nach Vorzeichen von links oder rechts zaehlen
 
if($part>0) {
  
$part-=1;
 } elseif(
$part<0) {
  
$part=sizeof($folder)+($part-((is_path($path))?1:0));
 } else {
  
// Anzahl der Verzeichnisse ermitteln (bei $part=0)
  
$result=sizeof($folder)-((is_path($path))?1:0);
 }

 
// Gewuenschten Verzeichnisteil ausschneiden (FALSE wenn nicht vorhanden)
 
if($result===FALSE) { $result=(!empty($folder[$part]))?$folder[$part]:FALSE; }

 return 
$result;
}

// ---------------------------------------------------------------------------

/* Wahren Dateinamen ermitteln 050412 */
function realBasename($file) {
 
// Nur wirklichen Dateinamen ermitteln, nicht vom Pfad
 
return (is_path($file))?'':basename($file);
}

// ---------------------------------------------------------------------------

/* Zeichenkette ist ein Pfad? 050412 */
function is_path($file) {
 
// Letztes Zeichen ist "/" oder "\"?
 
$lastChar=substr($file,-1);
 return (
$lastChar=='/' || $lastChar=='\\');
}

// ---------------------------------------------------------------------------

/* Verzeichnis einlesen 050922 */
function filelist($path,$base='',$condition='',$recursive=FALSE,$headingslash=TRUE) {
 
$files=array();
 
// Verzeichnis oeffnen
 
$handle=opendir($base.$path);
 
// Wenn Verzeichnis geoeffnet:
 
if($handle) {
  
// Verzeichniseintraege durchgehen ...
  
while($entry=readdir($handle)) {
   
$filename=$path.'/'.$entry;
   
// ... und ggf. naechsten Eintrag holen ...
   
if($entry=='.' || $entry=='..') { continue; }
   
// ... bzw. Rekursion wenn Eintrag ein Unterverzeichnis ist
   
if(is_dir($base.$filename)) {
    if(
$recursive) {
     
$subdir=filelist($filename,$base,$condition,$recursive,$headingslash);
     if(
$subdir && sizeof($subdir)) {
      
$files=array_merge($files,$subdir);
     }
     unset(
$subdir);
    }
   } else {
    
// Dateinamen zu Array hinzufuegen
    
array_push($files,(($headingslash)?$filename:substr($filename,1)));
   }
  }
  
closedir($handle);
 }
 if(!
sizeof($files)) { $files=FALSE; }
 return 
$files;
}

// ---------------------------------------------------------------------------

/* Wandelt einen relativen in einen absoluten Pfad um 051005
   Beispiel: $path='dir/dir2/../dir2b';
             if(!abs_path($path) echo 'Ungueltiger Pfad';
   Im Fall des Gelingens, enthaelt $path jetzt '/actual_path/dir/dir2b'!
*/
function abs_path(&$path) {
 if(
$path==='') { $path='.'; }
  
// Pfadtrenner vereinheitlichen
  
$path=unify_path($path);
  
// Relative Basis in absolute Basis umwandeln
  
if((strlen($path)>&& $path{0}!='/') && !(strlen($path)>&& $path{1}==':') ) { 
   
$curdir=getcwd();
   if(
$curdir) {
    
$path=$curdir.'/'.$path;
   }
  }
  
// Dateipfad "normalisieren" und in einzelne Verzeichnisnamen zerlegen
  
$folder=explode('/',unify_path($path));
  
// Verzeichnisnamen durchgehen
  
for($i=0;$i<count($folder);$i++) {
   
// Verweis auf aktuelles oder uebergeordnetes Verzeichnis herausfiltern ...
   
if($folder[$i]=='.' || $folder[$i]=='..') {
    if(
$folder[$i]=='..') { unset($folder[$i]); $i--; }
    unset(
$folder[$i]); $i--;
    
// .. indem das Verzeichnisarray ggf. gekuerzt wird
    
$folder=array_slice($folder,0,count($folder));
   }
   
// Wurde im Pfad "vor" das Startverzeichnis verwiesen: FALSE zurueck
   
if($i<0) { return FALSE; }
  }
  
// Keine Fehler: Pfadvariable aktualisieren und TRUE zurueck
  
$path=implode('/',$folder);
  return 
TRUE;
}

// ---------------------------------------------------------------------------

/* Ermittelt den absoluten Pfad */
function getAbsPath($path) {
 if(!
abs_path($path)) { $path=''; }
 return 
$path;
}

// ---------------------------------------------------------------------------

/* Datei beschreib- oder anlegbar? */
function isWritable($file) {
 return (
file_exists($file))?is_writable($file):(file_exists(dirname($file))?is_writable(dirname($file)):FALSE);
}

?>