<?php // Achtung: Es existiert eine einfachere Version unter http://coding.binon.net/ZIP

class ZIP {
 
/* ZIP-Archiv erstellen 051015 - under construction! */
 /* $source: Dateiliste (string array), Pfad oder leer (=aktuelles Verzeichnis) (string)
    $zipfile: Pfad & Dateiname des ZIP-Archivs, nur Pfad, nur Dateiname oder leer (=wird automatisch gebildet) (string)
              Ein existentes Archiv wird erweitert. %DOWNLOAD% (optional zu Pfad/Namen) fuer Download.
    $exclude: Dateien ausschliessen. FALSE: Keine Dateien ausschliessen, ggf. auch Unterverzeichnisse
              archivieren; TRUE: Vordefinierte Dateien ausschliessen; String-Array: Liste der
              auszuschliessenden Dateien. Folgende Schluesselworte sind ebenfalls moeglich:
               %DIR% (Verzeichnisse), %SCRIPT% (ZIP-Script), %ARCHIVE% (ZIP-Archiv), %HIDDEN% (versteckte Dateien)
              *.ext (Dateien mit der Endung 'ext')
    $save_basedir: Basisverzeichnis im Archiv vermerken (boolean) oder beliebiger Pfad (string)
    Aufruf: $zip=new ZIP();
           $zip->zip([$path2zip[,$zipfilename[,$exclude[,$save_basedir]]]]);
    Rueckgabe: Dateiname des ZIP-Archivs oder FALSE
 */
 
function zip($source='.',$zipfile='',$exclude=TRUE,$save_basedir=TRUE) {

  
// Startbedingung: zLib muss vorhanden sein
  
if(function_exists('gzcompress')) {
   
// Variableninitalisierung
   
$worklist=array();
   
$prepath='';
   
$listtype='';
   if(
$source==='') { $source='.'; }
   
// Steuervariablen: Extension beachten bei automatischem ZIP-Dateinamen
   
$default['use_folder_ext']=FALSE;
   
$default['use_file_ext']=FALSE;
   
// Steuervariablen: Spezielle Dateien automatisch ausschliessen
   
$default['exclude_subdir']=FALSE;
   
$default['exclude_zipscript']=TRUE;
   
$default['exclude_ziparchive']=TRUE;
   
$default['exclude_hidden_files']=TRUE;
   
// Steuervariable: Vorhandene Eintraege ueberschreiben
   
$default['overwrite_entries']=TRUE;

   
// ZipFile-Parameter analysieren
   
$download_zip=(strpos($zipfile,'%DOWNLOAD%')!==FALSE)?TRUE:FALSE;
   if(
$download_zip) { $zipfile=str_replace('%DOWNLOAD%','',$zipfile); }    
   
// 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($save_basedir) && strlen($save_basedir)) {
     
$save_basedir=unify_path($save_basedir);
     
$zipname=realDirname($save_basedir,1,FALSE);
     
$zipfile=getAbsPath(filename($zipname,!$default['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)?$default['use_folder_ext']:$default['use_file_ext'])).'.zip');
    } elseif(
is_path($zipfile)) {
     
// ... aus dem vorgegebenen Pfad + Source-Dateiname (Ext nach Wunsch bewahren)
     
$zipfile.=filename($source,!(is_dir($source)?$default['use_folder_ext']:$default['use_file_ext'])).'.zip';
    }
    if(
is_dir($source)) {
     
// Verzeichnis wurde uebergeben: Dateiliste erstellen (ggf. ohne Unterverzeichnisse: Wenn %DIR% in $exclude oder, wenn $exlude TRUE ist, den Default-Wert)
     
$filelist=filelist('',$source,'',((is_array($exclude))?!in_array('%DIR%',$exclude):(($exclude)?!$default['exclude_subdir']:TRUE)),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($save_basedir!==FALSE) {

     
// Als Basisverzeichnis das ...
     
if(is_string($save_basedir) && strlen($save_basedir)) {
      
// ... explizit uebergebene verwenden
      
$prepath=$save_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
     
if($listtype=='directory') {
      
// Wenn Verzeichnis uebergeben wurde: den Pfad mitgeben
      
$serverFilename=$source.'/'.$filename;
      
// Ggf. Dateien ausschliessen
      
if($default['exclude_zipscript'] && $serverFilename==$_SERVER['DOCUMENT_ROOT'].$_SERVER['PHP_SELF']) { $serverFilename=''; }
      if(
$default['exclude_ziparchive'] && $serverFilename==$zipfile) { $serverFilename=''; }
      if(
$default['exclude_hidden_files'] && substr(basename($serverFilename),0,1)=='.') { $serverFilename=''; }
     } else {
      
// Wenn Dateiliste uebergeben wurde: Dateiname ist so korrekt, aber ...
      
$serverFilename=$filename;
      
// ... nur den Namen, nicht den Pfad sichern
      
$filename=basename($filename);
     }

     if(
$serverFilename && is_file($serverFilename)) {
      
// 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]) || $default['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
    
if($download_zip) {
     
header('Content-Type: application/zip');
     
header('Content-Disposition: inline; filename='.basename($zipfile).';');
     
header('Content-Transfer-Encoding: binary');
     
header('Content-Length: '.strlen($this->file()));
     
header('Expires: Sat, 01 Jan 2000 12:00:00 GMT');
     echo 
$this->file();
     
$result=TRUE;
    } else {
     
$fh=@fopen($zipfile,'wb');
     if(
$fh) {
      
fwrite($fh,$this->file(),strlen($this->file()));
      
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);
}

?>