Voor een website wil ik graag de mogelijkheid creëren om een map (met afbeeldingen) te downloaden als zip. Nu heb ik hiervoor al wat gevonden op deze site: travis berry
Het grote probleem wat ik ooit heb gehad met het 'on the fly' aanmaken van zip files is extra output bits die er niet in horen. Je moet dus absoluut zorgen dat er geen enkele, maar dan ook geen enkele output wordt gegenereerd anders dan de zip. Check dus al je scripts die worden geinclude dat er nergens iets wordt verstuurd. Handigste manier is om net voor de echo in bovenstaand script even een exit te plaatsen en dan in de broncode van je browser te kijken of er enige output is. Is die er dan weet je in elk geval dat daar een probleem zit.
Verder kan ik weinig zeggen over bovenstaand script. Zelf gebruik ik heel andere methode gebaseerd op het script in 'zip.lib.php' wat je in de phpmyadmin directory kan vinden. Dat script werkte ook niet helemaal overigens, maar met enige aanpassingen heb ik er een class van kunnen maken die voor mij in elk geval wel altijd werkt. Mocht je bovenstaande dus niet aan de praat krijgen dan wil ik die class nog wel delen.
Ik gebruik altijd zonder problemen de ZipArchive, maar het is mogelijk dat deze class niet op alle servers werkt, omdat deze PECL vereist.
Mocht je server het wel ondersteunen, wil ik mijn functie ook wel delen
<?php
function Zip($source, $destination, $readme = '')
{
if (is_string($source)) $source_arr = array($source);
if (!extension_loaded('zip')){
return false;
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)){
return false;
}
Wat bedoel je met streamen? TS' script zal toch ook gewoon een download venster aanbieden?
Ik maak alleen een zip van een bepaalde map en sla die ergens anders op op de server en geef een download link weer. Dat laatste kun je natuurlijk ook vervangen door een 'attachment header', waarbij je dan een gelijk idee hebt als TS.
Wat ik bedoel is dat je niet eerst het bestand op de server hoeft op te slaan. Wat de TS precies wil weet ik ook niet, maar de reden dat ik ziparchive zelf niet gebruik is het feit dat ik geen manier kon vinden om zonder de zip op te slaan het als download aan te kunnen bieden. De vraag is dan ook meer mijn vraag dan dat het betrekking heeft op de vraag van de TS.
Ah zo, nee zover ik weet moet je wel de zip opslaan op de server.
Maar elk ander script zou ook iets opslaan, dan wel tijdelijk, dus in dat opzicht vind ik het niet erg en voor mijn doel vind ik het wel fijn juist.
Je kunt altijd een attachment header sturen met de zip en de zip verwijderen met unlink. Dat is inprincipe ook was TS' script doet.
Niet elk ander script dus. Ik heb zelf een class die de zip aanmaakt in geheugen en naar de browser pusht. Er wordt niets op de server opgeslagen. Niet ideaal voor elke situatie (want als de gebruiker de download niet accepteert is het bestand weg), maar voor mijn applicatie wel nodig. Hier is dus even de vraag wat de TS wil.
Hallo Erwin H,
bedankt voor je reactie. De code is nog helemaal clean, er wordt niets anders uitgegooid van te voren. Nog steeds krijg ik een error. Ik ben benieuwd naar wat je hebt.
En Michael ook bedankt voor je reactie.
Met deze Ziparchive en PECL wordt het erg ingewikkeld voor mij. Uiteraard wil ik het er wel mee gaan proberen. Ik ben al wat aan het kijken, maar is er niet een andere, eenvoudigere weg?
Een eenvoudigere weg is er niet. ziparchive is zo ongeveer het makkelijkste. Mijn class is alleen maar complexer, omdat je dan met de hand een zip bestand opbouwt. Aan de andere kant, wel helemaal kant en klaar:
<?php
/**
* This class creates a ZIP file, but in a string, so it can be streamed to the
* browser without first having to be saved on disk.
* This class is based on the zipfile class in zip.lib.php which can be found in
* the phpmyadmin directory. Some minor changes have been made to get it to work
* though.
*/
class Zip_Streamer_Class{
/**
* Array to store compressed data
* @var array
*/
private $datasec;
/**
* Central directory
* @var array
*/
private $ctrl_dir;
/**
* End of central directory record
* @var string
*/
private $eof_ctrl_dir;
/**
* Last offset position
* @var int
*/
private $old_offset;
/* -- constructor ----------------------------------------------------------- */
/**
* Constructor which initializes the properties
*/
public function __construct(){
$this->resetProperties();
}
/* -- private methods ------------------------------------------------------- */
/**
* 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 int $unixtime
* @return int the current date in a four byte DOS format
*/
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;
}
return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
}
/**
* Fully convert the time from unix timestamp.
* @param int $time
* @return string
*/
private function convertTime( $time ){
$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 . '";');
return $hexdtime;
}
/**
* Reset all properties to their initial value. This will effectively clear
* the contents of the zip.
*/
private function resetProperties(){
$this->datasec = array();
$this->ctrl_dir = array();
$this->eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
$this->old_offset = 0;
}
/* -- public methods -------------------------------------------------------- */
/**
* Adds "file" to archive. The file is given with the name that it should get
* in the archive and the contents of the file ($data). $time is the current
* timestamp (can be ommited)
* @param string $data
* @param string $name
* @param int $time
*/
public function addFile( $data, $name, $time = 0 ){
$name = str_replace('\\', '/', $name);
$hexdtime = $this->convertTime( $time );
/*$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($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;
// "file data" segment
$fr .= $zdata;
// "data descriptor" segment (optional but necessary if archive is not
// served as file)
// nijel(2004-10-19): this seems not to be needed at all and causes
// problems in some cases (bug #1037737)
// EH: doesn't work without this part....
$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', 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
$this->old_offset += strlen($fr);
$cdrec .= $name;
// optional extra field, file comment goes here
// save to central directory
$this->ctrl_dir[] = $cdrec;
}
/**
* Dumps out file
* @return string
*/
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
}
/**
* Empty all content that is added to the zip already. This will reset all
* values and clears everything.
*/
public function emptyZip(){
$this->resetProperties();
}
}
?>
Met enig commentaar, gebruik hoe je wilt (andere ook), maar zonder enige garantie ;-)
>>> Met deze Ziparchive en PECL wordt het erg ingewikkeld voor mij
Valt wel mee. Vaak is PECL al geïnstalleerd. Dus je zou even moeten kijken (script uitproberen) of dat bij jou ook het geval is. Verder is de functie klaar voor gebruik en kun je het natuurlijk wel aanpassen naar jouw wens.
<?php
/*
* Bron, bestemming (incl bestandsnaam), eventueel tekst (Komt in de zip als README.txt)
* Voorbeeld: Alle bestanden in / opslaan in archive.zip zonder README
*/
Zip('/', '/archive.zip', '');
?>