PHP question - download directories

Miscellaneous Forums/General Discussion/PHP question - download directories

I was wondering if any of you know a way in which I can download a directory to the client desktop without using .zip files?

I've developed an application that allows the client to view uploaded files within a directory by clicking on the name, but they are insistant that they want to be able to download the entire directory to their computer.

To make things worse, their server doesn't have support for any zip libs, so I can't think of any solution.

I have a solution for you :) (as long as the server has the required commands)
<?php
/* $Id: zip.lib.php 6461 2004-11-03 13:56:52Z garvinhicking $ */
// vim: expandtab sw=4 ts=4 sts=4:


/**
 * Zip file creation class.
 * Makes zip files.
 *
 * Based on :
 *
 *  <a href="http://www.zend.com/codex.php?id=535&single=1" target="_blank">http://www.zend.com/codex.php?id=535&single=1</a>
 *  By Eric Mueller <eric@...;
 *
 *  <a href="http://www.zend.com/codex.php?id=470&single=1" target="_blank">http://www.zend.com/codex.php?id=470&single=1</a>
 *  by Denis125 <webmaster@...;
 *
 *  a patch from Peter Listiak <mlady@...; for last modified
 *  date and time of the compressed file
 *
 * Official ZIP file format: <a href="http://www.pkware.com/appnote.txt" target="_blank">http://www.pkware.com/appnote.txt</a>
 *
 * @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($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)
        //$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;
    } // 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 'zipfile' class
?>


This is the phpymadmin zip library. You should be able to use it to build a zip.

Quality. You just saved me tons of time and from having to work into my holiday next week :-D

I'll get this tested tomorrow.

Tell me how it goes!

Well, got asked to do some other things yesterday, which meant more wasted time... Don't you just hate it when that happens... tsch!

Anyway, I half way got a solution; I got the files to compress, but they don't open because they are corrupted or not saved properly. Here is the code I added:

The class
<?php
/*  

Zip file creation class  
makes zip files on the fly...  

use the functions add_dir() and add_file() to build the zip file;  
see example code below  

by Eric Mueller  
[url]http://www.themepark.com[/url]  

v1.1 9-20-01  
  - added comments to example  

v1.0 2-5-01  

initial version with:  
  - class appearance  
  - add_file() and file() methods  
  - gzcompress() output hacking  
by Denis O.Philippov, [email]webmaster@...], [url]http://www.atlant.ru[/url]  

*/    

// official ZIP file format: [url]http://www.pkware.com/appnote.txt[/url]  

class zipfile     
{     

    var $datasec = array(); // array to store compressed data  
    var $ctrl_dir = array(); // central directory      
    var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00"; //end of Central directory record  
    var $old_offset = 0;    

    function add_dir($name)      

    // adds "directory" to archive - do this before putting any files in directory!  
    // $name - name of directory... like this: "path/"  
    // ...then you can add files using add_file with names like "path/file.txt"  
    {     
        $name = str_replace("\", "/", $name);     

        $fr = "\x50\x4b\x03\x04";    
        $fr .= "\x0a\x00";    // ver needed to extract  
        $fr .= "\x00\x00";    // gen purpose bit flag  
        $fr .= "\x00\x00";    // compression method  
        $fr .= "\x00\x00\x00\x00"; // last mod time and date  

        $fr .= pack("V",0); // crc32  
        $fr .= pack("V",0); //compressed filesize  
        $fr .= pack("V",0); //uncompressed filesize  
        $fr .= pack("v", strlen($name) ); //length of pathname  
        $fr .= pack("v", 0 ); //extra field length  
        $fr .= $name;     
        // end of "local file header" segment  

        // no "file data" segment for path  

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

        // ext. file attributes mirrors MS-DOS directory attr byte, detailed  
        // at [url]http://support.microsoft.com/support/kb/articles/Q125/0/19.asp[/url]  

        // now add to central record  
        $cdrec = "\x50\x4b\x01\x02";    
        $cdrec .="\x00\x00";    // version made by  
        $cdrec .="\x0a\x00";    // version needed to extract  
        $cdrec .="\x00\x00";    // gen purpose bit flag  
        $cdrec .="\x00\x00";    // compression method  
        $cdrec .="\x00\x00\x00\x00"; // last mod time & date  
        $cdrec .= pack("V",0); // crc32  
        $cdrec .= pack("V",0); //compressed filesize  
        $cdrec .= pack("V",0); //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  
        $ext = "\x00\x00\x10\x00";    
        $ext = "\xff\xff\xff\xff";     
        $cdrec .= pack("V", 16 ); //external file attributes  - 'directory' 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 array  
        $this -> ctrl_dir[] = $cdrec;     

            
    }    


    function add_file($data, $name)      

    // adds "file" to archive      
    // $data - file contents  
    // $name - name of file in archive. Add path if your want  

    {     
        $data = str_replace("\x0a", "\x0d\x0a", $data); 
        $name = str_replace("\", "/", $name);     
        //$name = str_replace("\", "\\\", $name);  

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

        $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;     
        // end of "local file header" segment  
            
        // "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 .="\x00\x00\x00\x00"; // 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  
//        echo "old offset is ".$this->old_offset.", new offset is $new_offset<br>";  
        $this -> old_offset = $new_offset;    

        $cdrec .= $name;     
        // optional extra field, file comment goes here  
        // save to central directory  
        $this -> ctrl_dir[] = $cdrec;     
    }    

    function file() { // dump out 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  
    }    
}  
?>


My code:
$zipfile = new zipfile();     

// add the subdirectory ... important!  
$zipfile -> add_dir("files/");    

// add the binary data stored in the string 'filedata'  
$var = array();
$filedata = array();
$var[] = "added_text.rtf";
$filedata[] = implode("",file("files/added_text.rtf")); 
$var[] = "cms_design.doc"; 
$filedata[] = implode("",file("files/cms_design.doc"));
$var[] = "icon_forum_france.jpg"; 
$filedata[] = implode("",file("files/icon_forum_france.jpg"));

for($i=0;$i<count($var);$i++)
	$zipfile->add_file($filedata[$i], "files/".$var[$i]);

// the next three lines force an immediate download of the zip file:  
header("Content-type: application/octet-stream");     
header("Content-disposition: attachment; filename=test.gz");     
print $zipfile->file(); 


Is there something I'm doing wrong? The idea is to set up each file data and name in corresponding arrays and then send them to the zip class.

The good news is that I got the code to work :-), so hopefully I'll be able to take the full week off without being asked to come into work :-D

Well done!

I had a simialr headache today making this..

http://www.cambridgenetworks.co.uk/capreport/plugininterface.html

It is the web side of a peer to peer remote support product I have developing at work. Basically each client workstation routes a zip containing log files through a program running on the clients server, which in turn uploads it to the php script. The php script opens the zip, parses the files and spits out a report and then emails it off depending on the status of the report.

All works through a plugin system in blitzmax / php which i am quite chuffed about.

The graph thing was giving me headaches earlier! Thankfully before I left work got it working fully. hurrah!

Cool. Did you use the GD library to do the graph?

I did indeed. Wrote a little wrapper for it infact.

<?
	$imagewidth = 200;
	$imageheight = 200;
	$image;
	$color;
	$font = 2;
	
	function ImageStart($nwidth,$nheight) {
		global $imagewidth,$imageheight,$image;
		
		$imagewidth = $nwidth;
		$imageheight = $nheight;
		$image = imagecreate($imagewidth,$imageheight);
	}
	
	function UseAntialias($nantialias) {
		global $image;
		imageantialias($image,$nantialias);
	}
	
	function StringWidth($nstring) {
		global $font;
		return strlen($nstring) * imagefontwidth($font);
	}
	
	function StringHeight($nstring) {
		global $font;
		return imagefontheight($font);
	}
	
	function FontWidth() {
		global $font;
		return imagefontwidth($font);
	}
	
	function FontHeight() {
		global $font;
		return imagefontheight($font);
	}
	
	function ImageWidth() {
		global $imagewidth;
		return $imagewidth;
	}
	
	function ImageHeight() {
		global $imageheight;
		return $imageheight;
	}
	
	function OutputPNG() {
		global $image;
		header("Content-type: image/png");
		imagepng($image);
		imagedestroy($image);
	}
	
	function AllocateColor($nr,$ng,$nb) {
		global $image;
		return imagecolorallocate($image,$nr,$ng,$nb);
	}
	
	function SetColor($ncolor) {
		global $color;
		$color = $ncolor;
	}
	
	function SetFont($nfont) {
		global $font;
		$font = $nfont;
	}
	
	function DrawText($ntext,$nx,$ny,$nvertical=false) {
		global $image,$color,$font;
		if ($nvertical == false) {
			imagestring($image,$font,$nx,$ny,$ntext,$color);
		} else {
			imagestringup($image,$font,$nx,$ny,$ntext,$color);
		}
	}
	
	function SetLineWidth($nthickness) {
		global $image;
		imagesetthickness($image,$nthickness);
	}
	
	function DrawLine($nx1,$ny1,$nx2,$ny2) {
		global $image,$color;
		imageline($image,$nx1,$ny1,$nx2,$ny2,$color);
	}
	
	function DrawRect($nx,$ny,$nwidth,$nheight,$nfilled=true) {
		global $image,$color;
		
		$temp_x1 = $nx;
		$temp_y1 = $ny;
		$temp_x2 = $nx + $nwidth - 1;
		$temp_y2 = $ny + $nheight - 1;
		
		if ($nfilled) {
			//draw a filled rectangle
			imagefilledrectangle($image,$temp_x1,$temp_y1,$temp_x2,$temp_y2,$color);
		} else {
			//construct rectangle out of 4 lines
			//top
			imageline($image,$temp_x1,$temp_y1,$temp_x2,$temp_y1,$color);
			//bottom
			imageline($image,$temp_x1,$temp_y2,$temp_x2,$temp_y2,$color);
			//left
			imageline($image,$temp_x1,$temp_y1,$temp_x1,$temp_y2,$color);
			//right
			imageline($image,$temp_x2,$temp_y1,$temp_x2,$temp_y2,$color);
		}
	}
?>


Comes in handy :)