3

I have folder named "data". This "data" folder contains a file "filecontent.txt" and another folder named "Files". The "Files" folder contains a "info.txt" file. So it is a folder inside folder structure.

I have to zip this folder "data"(using php) along with the file and folder inside it, and download the zipped file.

I have tried the examples available at http://www.php.net/manual/en/zip.examples.php These examples did not work. My PHP version is 5.2.10

Please help.

I have written this code.

<?php
$zip = new ZipArchive;
if ($zip->open('check/test2.zip',ZIPARCHIVE::CREATE) === TRUE) {
    if($zip->addEmptyDir('newDirectory')) {
        echo 'Created a new directory';
    } else {
        echo 'Could not create directory';
    }
    $zipfilename="test2.zip";
    $zipname="check/test2.zip";

    header('Content-Type: application/zip');
    header('Content-disposition: attachment; filename=check/test1.zip');    //header('Content-Length: ' . filesize( $zipfilename));
    readfile($zipname);  //$zip->close(); } else { echo failed';
}
?>

file downloaded but could not unzip

Bainternet
  • 569
  • 6
  • 18
Sangam254
  • 3,415
  • 11
  • 33
  • 43
  • 2
    What do you mean by *did not work*? – fabrik Mar 30 '11 at 09:15
  • possible duplicate of [How to zip a whole folder using PHP](http://stackoverflow.com/questions/4914750/how-to-zip-a-whole-folder-using-php) – mario Mar 30 '11 at 09:26
  • I mean that when I run the script at url, no error occurs but zipping or download does not happen. I cant figure out what is going wrong.. – Sangam254 Mar 30 '11 at 09:43

5 Answers5

5

You need to recursively add files in the directory. Something like this (untested):

function createZipFromDir($dir, $zip_file) {
    $zip = new ZipArchive;
    if (true !== $zip->open($zip_file, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)) {
        return false;
    }
    zipDir($dir, $zip);
    return $zip;
}

function zipDir($dir, $zip, $relative_path = DIRECTORY_SEPARATOR) {
    $dir = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    if ($handle = opendir($dir)) {
        while (false !== ($file = readdir($handle))) {
            if (file === '.' || $file === '..') {
                continue;
            }
            if (is_file($dir . $file)) {
                $zip->addFile($dir . $file, $file);
            } elseif (is_dir($dir . $file)) {
                zipDir($dir . $file, $zip, $relative_path . $file);
            }
        }
    }
    closedir($handle);
}

Then call $zip = createZipFromDir('/tmp/dir', 'files.zip');

For even more win I'd recommend reading up on the SPL DirectoryIterator here

chriso
  • 2,552
  • 1
  • 20
  • 16
  • Thank u for the code. I tried it. But when I run the script at url, no error occurs but zipping or download does not happen. I cant figure out what is going wrong.. – Sangam254 Mar 30 '11 at 09:44
  • The code zips the file but doesn't send it. You need to set the appropriate headers (see the other answer) then call `fpassthru($zip_file)` – chriso Mar 30 '11 at 09:55
  • You made a small mistake in line 14 it should be `$file` instead of `file`. – phrogg Jan 23 '19 at 08:42
4

========= The only solution for me ! ! !==========

Puts all subfolders and sub-files with their structure:

<?php
$the_folder = 'path/foldername';
$zip_file_name = 'archived_name.zip';


$download_file= true;
//$delete_file_after_download= true; doesnt work!!


class FlxZipArchive extends ZipArchive {
    /** Add a Dir with Files and Subdirs to the archive;;;;; @param string $location Real Location;;;;  @param string $name Name in Archive;;; @author Nicolas Heimann;;;; @access private  **/

    public function addDir($location, $name) {
        $this->addEmptyDir($name);

        $this->addDirDo($location, $name);
     } // EO addDir;

    /**  Add Files & Dirs to archive;;;; @param string $location Real Location;  @param string $name Name in Archive;;;;;; @author Nicolas Heimann
     * @access private   **/
    private function addDirDo($location, $name) {
        $name .= '/';
        $location .= '/';

        // Read all Files in Dir
        $dir = opendir ($location);
        while ($file = readdir($dir))
        {
            if ($file == '.' || $file == '..') continue;
            // Rekursiv, If dir: FlxZipArchive::addDir(), else ::File();
            $do = (filetype( $location . $file) == 'dir') ? 'addDir' : 'addFile';
            $this->$do($location . $file, $name . $file);
        }
    } // EO addDirDo();
}

$za = new FlxZipArchive;
$res = $za->open($zip_file_name, ZipArchive::CREATE);
if($res === TRUE) 
{
    $za->addDir($the_folder, basename($the_folder));
    $za->close();
}
else  { echo 'Could not create a zip archive';}

if ($download_file)
{
    ob_get_clean();
    header("Pragma: public");
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Cache-Control: private", false);
    header("Content-Type: application/zip");
    header("Content-Disposition: attachment; filename=" . basename($zip_file_name) . ";" );
    header("Content-Transfer-Encoding: binary");
    header("Content-Length: " . filesize($zip_file_name));
    readfile($zip_file_name);

    //deletes file when its done...
    //if ($delete_file_after_download) 
    //{ unlink($zip_file_name); }
}
?>
T.Todua
  • 53,146
  • 19
  • 236
  • 237
2

I had to do the same thing a few days ago and here is what I did.

1) Retrieve File/Folder structure and fill an array of items. Each item is either a file or a folder, if it's a folder, retrieve its content as items the same way.

2) Parse that array and generate the zip file.

Put my code below, you will of course have to adapt it depending on how your application was made.

// Get files
$items['items'] = $this->getFilesStructureinFolder($folderId);

$archiveName = $baseDir . 'temp_' . time(). '.zip';

if (!extension_loaded('zip')) {
    dl('zip.so');
}

//all files added now
$zip = new ZipArchive();
$zip->open($archiveName, ZipArchive::OVERWRITE);

$this->fillZipRecursive($zip, $items);

$zip->close();

//outputs file
if (!file_exists($archiveName)) {
    error_log('File doesn\'t exist.');
    echo 'Folder is empty';
    return;
}


header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false);
header("Content-Type: application/zip");
header("Content-Disposition: attachment; filename=" . basename($archiveName) . ";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($archiveName));
readfile($archiveName);

//deletes file when its done...
unlink($archiveName);

Methods used to fill & parse:

/**
 * 
 * Gets all the files recursively within a folder and keeps the structure.
 * 
 * @param   int     $folderId   The id of the folder from which we start the search
 * @return  array   $tree       The data files/folders data structure within the given folder id
 */
public function getFilesStructureinFolder($folderId) {
    $result = array();

    $query = $this->db->query('SELECT * FROM xx WHERE deleted = 0 AND status = 1 AND parent_folder_id = ? ORDER BY name ASC', $folderId);

    $folders = $query->result();

    foreach($folders as $folder) {
        $folderItem = array();
        $folderItem['type']     = 'folder';
        $folderItem['obj']      = $folder;  
        $folderItem['items']    = $this->getFilesStructureinFolder($folder->id);
        $result[]               = $folderItem;
    }

    $query = $this->db->query('SELECT * FROM xx WHERE deleted = 0 AND xx = ? AND status = 1 ORDER BY name ASC', $folderId);

    $files = $query->result();

    foreach ($files as $file) {
        $fileItem = array();
        $fileItem['type']   = 'file';
        $fileItem['obj']    = $file;    
        $result[]           = $fileItem;
    }

    return $result;
}

/**
 * Fills zip file recursively
 * 
 * @param ZipArchive    $zip        The zip archive we are filling
 * @param Array         $items      The array representing the file/folder structure
 * @param String        $zipPath    Local path within the zip
 * 
 */
public function fillZipRecursive($zip, $items, $zipPath = '') {
    $baseDir = $this->CI->config->item('xxx');

    foreach ($items['items'] as $item) {

        //Item is a file
        if ($item['type'] == 'file') {
            $file = $item['obj'];
            $fileName = $baseDir . '/' . $file->fs_folder_id . '/' . $file->file_name;

            if (trim($file->file_name) == '' || !file_exists($fileName))
                continue;

            $zip->addFile($fileName, $zipPath.''.$file->file_name);
        }

        //Item is a folder
        else if ($item['type'] == 'folder') {
            $folder     = $item['obj'];

            $zip->addEmptyDir($zipPath.''.$folder->name);

            //Folder probably has items in it!
            if (!empty($item['items']))
                $this->fillZipRecursive($zip, $item, $zipPath.'/'.$folder->name.'/');
        }
    }
} 
Deratrius
  • 679
  • 10
  • 21
  • I have written this code. $zip = new ZipArchive; if ($zip->open('check/test2.zip',ZIPARCHIVE::CREATE) === TRUE) { if($zip->addEmptyDir('newDirectory')) { echo 'Created a new directory'; } else { echo 'Could not create directory'; } $zipfilename="test2.zip"; $zipname="check/test2.zip"; header('Content-Type: application/zip'); header('Content-disposition: attachment; filename=check/test1.zip'); //header('Content-Length: ' . filesize( $zipfilename)); readfile($zipname); //$zip->close(); } else { echo failed'; } file downloaded but could not unzip – Sangam254 Mar 30 '11 at 10:25
  • You shouldn't use echo before using header, if you need debug information you can use error_log(). – Deratrius Mar 30 '11 at 11:10
1

See the linked duplicates. Another often overlooked and particular lazy option would be:

exec("zip -r data.zip data/");
header("Content-Type: application/zip");
readfile("data.zip");    // must be a writeable location though
mario
  • 144,265
  • 20
  • 237
  • 291
  • Lazy? Put in some platform detection and call it short and sweet :D – Christian Mar 30 '11 at 09:30
  • On most hosting servers exec is disabled in PHP due to security reasons. – Vladislav Rastrusny Mar 30 '11 at 09:31
  • @FractalizeR: I would not call them "most", but "low-end". And it's not very indicative for a good security approach either. (I've yet to see one where you cannot circumvent it by placing your own PHP interpreter into cgi-bin.) – mario Mar 30 '11 at 09:33
  • I tried this code. it downloads but no data in the file when i open the downloaded file. I use a mac system. Can this be a problem? – Sangam254 Mar 30 '11 at 09:41
  • @Sangam254: Yes. It needs the `zip` commandline tool installed. There are also multiple variations of it. If you have a Mac version it might need different parameters. (Also: take care that the output file must be in a writable location in any case.) – mario Mar 30 '11 at 09:45
  • @mario I meant shared webhostings where you cannot get your own shell. All administration is done via something like CPanel. And for those it's a security measure. At least I think so. – Vladislav Rastrusny Mar 30 '11 at 10:19
1

Use the TbsZip class to create a new zip Archive. TbsZip is simple, it uses no temporary files, no zip EXE, it has no dependency, and has a Download feature that flushes the archive as a download file.

You just have to loop under the folder tree and add all files in the archive, and then flush it.

Code example:

$zip = new clsTbsZip(); // instantiate the class
$zip->CreateNew(); // create a virtual new zip archive
foreach (...) { // your loop to scann the folder tree
  ...
  // add the file in the archive
  $zip->FileAdd($FileInnerName, $LocalFilePath, TBSZIP_FILE);
}
// flush the result as an HTTP download
$zip->Flush(TBSZIP_DOWNLOAD, 'my_archive.zip');

Files added in the archive will be compressed sequentially during the Flush() method. So your archive can contain numerous sub-files, this won't increase the PHP memory.

Skrol29
  • 5,402
  • 1
  • 20
  • 25