125

Directory is something like:

home/
    file1.html
    file2.html
Another_Dir/
    file8.html
    Sub_Dir/
        file19.html

I am using the same PHP Zip class used in PHPMyAdmin http://trac.seagullproject.org/browser/branches/0.6-bugfix/lib/other/Zip.php . I'm not sure how to zip a directory rather than just a file. Here's what I have so far:

$aFiles = $this->da->getDirTree($target);
/* $aFiles is something like, path => filetime
Array
(
    [home] => 
    [home/file1.html] => 1251280379
    [home/file2.html] => 1251280377
    etc...
)

*/
$zip = & new Zip();
foreach( $aFiles as $fileLocation => $time ){
    $file = $target . "/" . $fileLocation;
    if ( is_file($file) ){
        $buffer = file_get_contents($file);
        $zip->addFile($buffer, $fileLocation);
    }
}
THEN_SOME_PHP_CLASS::toDownloadData($zip); // this bit works ok

but when I try to unzip the corresponding downloaded zip file I get "operation not permitted"

This error only happens when I try to unzip on my mac, when I unzip through the command line the file unzips ok. Do I need to send a specific content type on download, currently 'application/zip'

Alix Axel
  • 151,645
  • 95
  • 393
  • 500
ed209
  • 11,075
  • 19
  • 66
  • 82
  • This code does actually work - but for some reason you can't unzip it on Mac OS (unless you use CLI unzip). Zip file unstuffs ok on PC. – ed209 Aug 28 '09 at 10:07
  • this can help you http://codingbin.com/compressing-a-directory-of-files-with-php/ – Manoj Dhiman Sep 21 '17 at 20:09

12 Answers12

271

Here is a simple function that can compress any file or directory recursively, only needs the zip extension to be loaded.

function Zip($source, $destination)
{
    if (!extension_loaded('zip') || !file_exists($source)) {
        return false;
    }

    $zip = new ZipArchive();
    if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
        return false;
    }

    $source = str_replace('\\', '/', realpath($source));

    if (is_dir($source) === true)
    {
        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);

        foreach ($files as $file)
        {
            $file = str_replace('\\', '/', $file);

            // Ignore "." and ".." folders
            if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
                continue;

            $file = realpath($file);

            if (is_dir($file) === true)
            {
                $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
            }
            else if (is_file($file) === true)
            {
                $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
            }
        }
    }
    else if (is_file($source) === true)
    {
        $zip->addFromString(basename($source), file_get_contents($source));
    }

    return $zip->close();
}

Call it like this:

Zip('/folder/to/compress/', './compressed.zip');
Raohmaru
  • 140
  • 5
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
  • 1
    `Zip('/folder/to/compress/', './compressed.zip');`where should i put this? – woninana Feb 07 '11 at 13:25
  • 5
    Worked very well, my only question is that my script runs from a different location to the files to be zipped, therefore when I provide the 1st argument that full filepath location is used within the zip, like so: C:\wamp\www\export\pkg-1211.191011\pkg-1211.191011.zip, that full nested folder structure being inside the new archive. Is there a way to adapt the above script to only contain the files and directories I'm pointing at, and not the full path they come from? – danjah Oct 18 '11 at 23:15
  • @Danjah: I ported this from my framework (https://github.com/alixaxel/phunction/) but only adapted it to *nix systems, the issue you mention doesn't exist in the framework because the paths are normalized. Try replacing all forward slashes (`/`) with two backward slashes (`\\\`) - that should solve your problem (in Windows) I think. – Alix Axel Oct 18 '11 at 23:34
  • 4
    @Danjah: I've updated the code, should work for both *nix and Windows now. – Alix Axel Oct 18 '11 at 23:38
  • Excellent tip, thank you, it was almost perfect. I had to leave the 'new directory' slash as fwd, otherwise WinZip complained about not being able to view the archive in "Explorer Mode" but in "All Files" mode instead... which still didn't quite display correctly. The archive was correct upon unpacking though. But with the single fwd slash it is a) relative to zipped files and b) fully explorable in Windows. Thanks Alix. EDIT: Code update, even better, thanks again :D – danjah Oct 19 '11 at 00:06
  • Sorry, edit time expired on my last comment, I still get the full path unfortunately... – danjah Oct 19 '11 at 00:13
  • @Alix: Bingo! Explorable, relative Zip archive - thanks very much! – danjah Oct 19 '11 at 01:26
  • 6
    I wonder why this is using `file_get_contents` and adding strings. doesn't zip support adding files directly? – hakre Oct 31 '11 at 10:06
  • 1
    @hakre: It does, but there are some bugs. See http://www.php.net/manual/en/function.ziparchive-addfile.php#74297 and the other comments, another workaround would be to close and reopen the Zip file every *n* files but the `file_get_contents()` looks cleaner to me (although it can give problems if you try to zip huge files). – Alix Axel Oct 31 '11 at 14:35
  • 1
    while using this i had encountered a memory limit problem. replacing the addFromString with a addFile solved that issue. – yossi Jun 27 '12 at 09:28
  • @yossi: Yes, but read my comment to hakre - that may have problems too. – Alix Axel Jun 27 '12 at 12:58
  • I get "Warning: the Zip file is read-only. A file name in the archive is invalid and had to be fixed:" when I use this? – Gaʀʀʏ Nov 28 '12 at 22:07
  • 1
    Edit (past 5 min mark): Fixed by changing `$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));` to `$zip->addFromString(basename($file), file_get_contents($file));` For some reason, `str_replace` was not erasing the `$source` from the file, so it was including the fill filepath in the zip. – Gaʀʀʏ Nov 28 '12 at 22:16
  • @le_garry: Weird... But this answer has been edited beyond recognition. Would you mind trying this revision http://stackoverflow.com/revisions/993fbebb-3066-4188-99c6-c19a6537e8bf/view-source and telling me if you still have the same problem? – Alix Axel Nov 29 '12 at 08:38
  • 1
    Alix, I think the problem was the extra "/" appended to $source. I removed it (using the revision you posted), and the zip turned out exactly how I wanted to. When the "/" was still there, the zip file would have a file structure all the way to the base root of the server. Cheers! – Gaʀʀʏ Dec 04 '12 at 19:45
  • Is there a option to compress it on the fly? So the `zip` is not saved to the server. I've try to change some things and add the `Content-type` and `Content-Disposition` but then is the `zip` archive broken. –  May 20 '13 at 18:21
  • @GerritHoekstra: My code has changed beyond recognition, but how about you zip it to a temporary file? Wouldn't that work? – Alix Axel May 20 '13 at 18:38
  • how can one use this code to iterate through two directories and have them in the same zip? I just want to backup my images folder i.e. `/images/upload/` and `/cms/images/users/` – Alex Jul 08 '13 at 15:04
  • @Alex: Look into the PharData class. – Alix Axel Jul 08 '13 at 20:21
  • @winona you put it on your php script, preferrably on the same file where the function is defined. – gerrytan Aug 24 '13 at 03:31
  • Hi i tried this function and it zips my whole wamp directory up to my defined source. I've also tried all your revisions but it still does the same. BTW nice work! – Kiel Oct 14 '13 at 07:11
  • Update i just removed all the realpath for it to work. Will that be a problem when i move my project in a linux environment? – Kiel Oct 14 '13 at 07:25
  • 1
    Probably cleaner to add the `FileystemIterator::SKIP_DOTS` option to the `RecursiveDirectoryIterator` call instead of searching for `'.'` and `'..'` manually. – eaj Nov 26 '13 at 21:45
  • 4
    You have to replace all `'/'` with `DIRECTORY_SEPARATOR` to make it work on Windows, of course. Otherwise, you'll end up with the full path (including drive name) in your ZIP, e.g. `C:\Users\...`. – caw Dec 16 '13 at 14:02
  • sorry people.. i am using this script on a windows 2008 server and it is returning the fullpath into the zip file. i have tried changing '/' to DIRECTORY_SEPARATOR and also @le_garry solution and the old revision but none of them worked correctly.. the last two btw return me the fullpath with no files at the end of path, instead they are all in the root folder together at the parent of fullpath which is C:. Any ideas, this is quite annoying to browse 6 folders to reach the files – mstation Feb 20 '14 at 06:22
  • i resolved the issue i was having by changing all the '/' to '\\' – mstation Feb 24 '14 at 07:45
  • 1
    I love the function and it works great, however, when it makes the zip, it makes a huge line of folder starting at C:\ going through the Users folder and all the way down to the folder i wanted zipped which has all the files in it. – Darksaint2014 Jul 16 '14 at 13:35
  • Nevermind all, the double back slash fixed it. my fault – Darksaint2014 Jul 16 '14 at 13:52
  • 3
    The original code was/is broken and redundant. There's no need to replace `//` with `\ ` as this actually breaks the foreach on windows. If you use the built-in `DIRECTORY_SEPARATOR` as you should, there's no need to replace. The hardcoding of `/` is what was causing some users to have problems. I was confused for a bit as to why I was getting an empty archive. My revision will run fine under *nix and Windows. – DavidScherer Jan 14 '15 at 19:34
  • 2
    This was working, except it was creating a folder structure rooted from the /root drive on my Windows machine. I altered it to fix the problem and use DIRECTORY_SEPARATOR instead. [Here's the code.](http://pastebin.com/1dqbzAQx) – Jay El-Kaake Mar 17 '15 at 04:16
  • how can i give a array values to $source path.in array multiple file paths are there. – RaMeSh Mar 31 '15 at 14:03
  • 1
    Just a note for anyone else reading this. This recommendation uses will have memory usage issues when you are dealing with very large files. A better and MUCH cleaner solution can be found in this article http://ninad.pundaliks.in/blog/2011/05/recursively-zip-a-directory-with-php/ – lordg Sep 22 '15 at 18:49
  • This script is pretty good. Wouldn't it be possible to `unset` any variable in the process so there is no `memory_limit` issue? I don't know if it's the iterator or the zip object that's taking all the memory, but unsetting it and setting it back to where it was at (so it remains incremential) could be a good upgrade. – Vincent Poirier Jan 28 '16 at 15:14
  • @AlixAxel how can I add all files to a subdirectory of my zip archive? – Reza Feb 03 '17 at 16:21
  • terrible that in 2018 with PHP 7 we still have to jump through hoops... it's a 2 liner in python.. makes me want to switch – Robert Sinclair Dec 12 '18 at 21:51
  • If you have large files using `file_get_contents` may cause php to run out of memory. – Zortext Jun 02 '20 at 08:09
23

Yet another recursive directory tree archiving, implemented as an extension to ZipArchive. As a bonus, a single-statement tree compression helper function is included. Optional localname is supported, as in other ZipArchive functions. Error handling code to be added...

class ExtendedZip extends ZipArchive {

    // Member function to add a whole file system subtree to the archive
    public function addTree($dirname, $localname = '') {
        if ($localname)
            $this->addEmptyDir($localname);
        $this->_addTree($dirname, $localname);
    }

    // Internal function, to recurse
    protected function _addTree($dirname, $localname) {
        $dir = opendir($dirname);
        while ($filename = readdir($dir)) {
            // Discard . and ..
            if ($filename == '.' || $filename == '..')
                continue;

            // Proceed according to type
            $path = $dirname . '/' . $filename;
            $localpath = $localname ? ($localname . '/' . $filename) : $filename;
            if (is_dir($path)) {
                // Directory: add & recurse
                $this->addEmptyDir($localpath);
                $this->_addTree($path, $localpath);
            }
            else if (is_file($path)) {
                // File: just add
                $this->addFile($path, $localpath);
            }
        }
        closedir($dir);
    }

    // Helper function
    public static function zipTree($dirname, $zipFilename, $flags = 0, $localname = '') {
        $zip = new self();
        $zip->open($zipFilename, $flags);
        $zip->addTree($dirname, $localname);
        $zip->close();
    }
}

// Example
ExtendedZip::zipTree('/foo/bar', '/tmp/archive.zip', ZipArchive::CREATE);
Giorgio Barchiesi
  • 6,109
  • 3
  • 32
  • 36
13

I've edited Alix Axel's answer to take a third argrument, when setting this third argrument to true all the files will be added under the main directory rather than directly in the zip folder.

If the zip file exists the file will be deleted as well.

Example:

Zip('/path/to/maindirectory','/path/to/compressed.zip',true);

Third argrument true zip structure:

maindirectory
--- file 1
--- file 2
--- subdirectory 1
------ file 3
------ file 4
--- subdirectory 2
------ file 5
------ file 6

Third argrument false or missing zip structure:

file 1
file 2
subdirectory 1
--- file 3
--- file 4
subdirectory 2
--- file 5
--- file 6

Edited code:

function Zip($source, $destination, $include_dir = false)
{

    if (!extension_loaded('zip') || !file_exists($source)) {
        return false;
    }

    if (file_exists($destination)) {
        unlink ($destination);
    }

    $zip = new ZipArchive();
    if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
        return false;
    }
    $source = str_replace('\\', '/', realpath($source));

    if (is_dir($source) === true)
    {

        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);

        if ($include_dir) {

            $arr = explode("/",$source);
            $maindir = $arr[count($arr)- 1];

            $source = "";
            for ($i=0; $i < count($arr) - 1; $i++) { 
                $source .= '/' . $arr[$i];
            }

            $source = substr($source, 1);

            $zip->addEmptyDir($maindir);

        }

        foreach ($files as $file)
        {
            $file = str_replace('\\', '/', $file);

            // Ignore "." and ".." folders
            if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
                continue;

            $file = realpath($file);

            if (is_dir($file) === true)
            {
                $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
            }
            else if (is_file($file) === true)
            {
                $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
            }
        }
    }
    else if (is_file($source) === true)
    {
        $zip->addFromString(basename($source), file_get_contents($source));
    }

    return $zip->close();
}
Community
  • 1
  • 1
user2019515
  • 4,495
  • 1
  • 29
  • 42
4

USAGE: thisfile.php?dir=./path/to/folder (After zipping, it starts download too:)

<?php
$exclude_some_files=
array(
        'mainfolder/folder1/filename.php',
        'mainfolder/folder5/otherfile.php'
);

//***************built from https://gist.github.com/ninadsp/6098467 ******
class ModifiedFlxZipArchive extends ZipArchive {
    public function addDirDoo($location, $name , $prohib_filenames=false) {
        if (!file_exists($location)) {  die("maybe file/folder path incorrect");}

        $this->addEmptyDir($name);
        $name .= '/';
        $location.= '/';
        $dir = opendir ($location);   // Read all Files in Dir

        while ($file = readdir($dir)){
            if ($file == '.' || $file == '..') continue;
            if (!in_array($name.$file,$prohib_filenames)){
                if (filetype( $location . $file) == 'dir'){
                    $this->addDirDoo($location . $file, $name . $file,$prohib_filenames );
                }
                else {
                    $this->addFile($location . $file, $name . $file);
                }
            }
        }
    }

    public function downld($zip_name){
        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_name) . ";" );
        header("Content-Transfer-Encoding: binary");
        header("Content-Length: " . filesize($zip_name));
        readfile($zip_name);
    }
}

//set memory limits
set_time_limit(3000);
ini_set('max_execution_time', 3000);
ini_set('memory_limit','100M');
$new_zip_filename='down_zip_file_'.rand(1,1000000).'.zip';  
// Download action
if (isset($_GET['dir']))    {
    $za = new ModifiedFlxZipArchive;
    //create an archive
    if  ($za->open($new_zip_filename, ZipArchive::CREATE)) {
        $za->addDirDoo($_GET['dir'], basename($_GET['dir']), $exclude_some_files); $za->close();
    }else {die('cantttt');}

if (isset($_GET['dir']))    {
    $za = new ModifiedFlxZipArchive;
    //create an archive
    if  ($za->open($new_zip_filename, ZipArchive::CREATE)) {
        $za->addDirDoo($_GET['dir'], basename($_GET['dir']), $exclude_some_files); $za->close();
    }else {die('cantttt');}

    //download archive
    //on the same execution,this made problems in some hostings, so better redirect
    //$za -> downld($new_zip_filename);
    header("location:?fildown=".$new_zip_filename); exit;
}   
if (isset($_GET['fildown'])){
    $za = new ModifiedFlxZipArchive;
    $za -> downld($_GET['fildown']);
}
?>
T.Todua
  • 53,146
  • 19
  • 236
  • 237
2

Try this link <-- MORE SOURCE CODE HERE

/** Include the Pear Library for Zip */
include ('Archive/Zip.php');

/** Create a Zipping Object...
* Name of zip file to be created..
* You can specify the path too */
$obj = new Archive_Zip('test.zip');
/**
* create a file array of Files to be Added in Zip
*/
$files = array('black.gif',
'blue.gif',
);

/**
* creating zip file..if success do something else do something...
* if Error in file creation ..it is either due to permission problem (Solution: give 777 to that folder)
* Or Corruption of File Problem..
*/

if ($obj->create($files)) {
// echo 'Created successfully!';
} else {
//echo 'Error in file creation';
}

?>; // We'll be outputting a ZIP
header('Content-type: application/zip');

// It will be called test.zip
header('Content-Disposition: attachment; filename="test.zip"');

//read a file and send
readfile('test.zip');
?>;
Phill Pafford
  • 83,471
  • 91
  • 263
  • 383
1

Here Is my code For Zip the folders and its sub folders and its files and make it downloadable in zip Format

function zip()
 {
$source='path/folder'// Path To the folder;
$destination='path/folder/abc.zip'// Path to the file and file name ; 
$include_dir = false;
$archive = 'abc.zip'// File Name ;

if (!extension_loaded('zip') || !file_exists($source)) {
    return false;
}

if (file_exists($destination)) {
    unlink ($destination);
}

$zip = new ZipArchive;

if (!$zip->open($archive, ZipArchive::CREATE)) {
    return false;
}
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true)
{

    $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);

    if ($include_dir) {

        $arr = explode("/",$source);
        $maindir = $arr[count($arr)- 1];

        $source = "";
        for ($i=0; $i < count($arr) - 1; $i++) { 
            $source .= '/' . $arr[$i];
        }

        $source = substr($source, 1);

        $zip->addEmptyDir($maindir);

    }

    foreach ($files as $file)
    {
        $file = str_replace('\\', '/', $file);

        // Ignore "." and ".." folders
        if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
            continue;

        $file = realpath($file);

        if (is_dir($file) === true)
        {
            $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
        }
        else if (is_file($file) === true)
        {
            $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
        }
    }
}
else if (is_file($source) === true)
{
    $zip->addFromString(basename($source), file_get_contents($source));
}
$zip->close();

header('Content-Type: application/zip');
header('Content-disposition: attachment; filename='.$archive);
header('Content-Length: '.filesize($archive));
readfile($archive);
unlink($archive);
}

If Any Issue With the Code Let Me know.

nitin
  • 89
  • 10
0

I needed to run this Zip function in Mac OSX

so I would always zip that annoying .DS_Store.

I adapted https://stackoverflow.com/users/2019515/user2019515 by including additionalIgnore files.

function zipIt($source, $destination, $include_dir = false, $additionalIgnoreFiles = array())
{
    // Ignore "." and ".." folders by default
    $defaultIgnoreFiles = array('.', '..');

    // include more files to ignore
    $ignoreFiles = array_merge($defaultIgnoreFiles, $additionalIgnoreFiles);

    if (!extension_loaded('zip') || !file_exists($source)) {
        return false;
    }

    if (file_exists($destination)) {
        unlink ($destination);
    }

    $zip = new ZipArchive();
        if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
        return false;
        }
    $source = str_replace('\\', '/', realpath($source));

    if (is_dir($source) === true)
    {

        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);

        if ($include_dir) {

            $arr = explode("/",$source);
            $maindir = $arr[count($arr)- 1];

            $source = "";
            for ($i=0; $i < count($arr) - 1; $i++) { 
                $source .= '/' . $arr[$i];
            }

            $source = substr($source, 1);

            $zip->addEmptyDir($maindir);

        }

        foreach ($files as $file)
        {
            $file = str_replace('\\', '/', $file);

            // purposely ignore files that are irrelevant
            if( in_array(substr($file, strrpos($file, '/')+1), $ignoreFiles) )
                continue;

            $file = realpath($file);

            if (is_dir($file) === true)
            {
                $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
            }
            else if (is_file($file) === true)
            {
                $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
            }
        }
    }
    else if (is_file($source) === true)
    {
        $zip->addFromString(basename($source), file_get_contents($source));
    }

    return $zip->close();
}

SO to ignore the .DS_Store from zip, you run

zipIt('/path/to/folder', '/path/to/compressed.zip', false, array('.DS_Store'));

Community
  • 1
  • 1
Kim Stacks
  • 10,202
  • 35
  • 151
  • 282
0

Great solution but for my Windows I need make a modifications. Below the modify code

function Zip($source, $destination){

if (!extension_loaded('zip') || !file_exists($source)) {
    return false;
}

$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
    return false;
}

$source = str_replace('\\', '/', realpath($source));

if (is_dir($source) === true)
{
    $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);

    foreach ($files as $file)
    {
        $file = str_replace('\\', '/', $file);

        // Ignore "." and ".." folders
        if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
            continue;

        if (is_dir($file) === true)
        {
            $zip->addEmptyDir(str_replace($source . '/', '', $file));
        }
        else if (is_file($file) === true)
        {

            $str1 = str_replace($source . '/', '', '/'.$file);
            $zip->addFromString($str1, file_get_contents($file));

        }
    }
}
else if (is_file($source) === true)
{
    $zip->addFromString(basename($source), file_get_contents($source));
}

return $zip->close();
}
Juan
  • 2,073
  • 3
  • 22
  • 37
0

This code works for both windows and linux.

function Zip($source, $destination)
{
if (!extension_loaded('zip') || !file_exists($source)) {
    return false;
}

$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
    return false;
}

if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    DEFINE('DS', DIRECTORY_SEPARATOR); //for windows
} else {
    DEFINE('DS', '/'); //for linux
}


$source = str_replace('\\', DS, realpath($source));

if (is_dir($source) === true)
{
    $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
    echo $source;
    foreach ($files as $file)
    {
        $file = str_replace('\\',DS, $file);
        // Ignore "." and ".." folders
        if( in_array(substr($file, strrpos($file, DS)+1), array('.', '..')) )
            continue;

        $file = realpath($file);

        if (is_dir($file) === true)
        {
            $zip->addEmptyDir(str_replace($source . DS, '', $file . DS));
        }
        else if (is_file($file) === true)
        {
            $zip->addFromString(str_replace($source . DS, '', $file), file_get_contents($file));
        }
        echo $source;
    }
}
else if (is_file($source) === true)
{
    $zip->addFromString(basename($source), file_get_contents($source));
}

return $zip->close();
}
Ammar Qala
  • 63
  • 1
  • 8
0

Here's my version base on Alix's, works on Windows and hopefully on *nix too:

function addFolderToZip($source, $destination, $flags = ZIPARCHIVE::OVERWRITE)
{
    $source = realpath($source);
    $destination = realpath($destination);

    if (!file_exists($source)) {
        die("file does not exist: " . $source);
    }

    $zip = new ZipArchive();
    if (!$zip->open($destination, $flags )) {
        die("Cannot open zip archive: " . $destination);
    }

    $files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);

    $sourceWithSeparator = $source . DIRECTORY_SEPARATOR;
    foreach ($files as $file)
    {
        // Ignore "." and ".." folders
        if(in_array(substr($file,strrpos($file, DIRECTORY_SEPARATOR)+1),array('.', '..')))
            continue;

        if (is_dir($file) === true)
        {
            $zip->addEmptyDir(
                str_replace($sourceWithSeparator, '', $file . DIRECTORY_SEPARATOR));
        }
        else if (is_file($file) === true)
        {
            $zip->addFile($file, str_replace($sourceWithSeparator, '', $file));
        }
    }

    return $zip->close();
}
Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
0

Here is the simple, easy to read, recursive function that works very well:

function zip_r($from, $zip, $base=false) {
    if (!file_exists($from) OR !extension_loaded('zip')) {return false;}
    if (!$base) {$base = $from;}
    $base = trim($base, '/');
    $zip->addEmptyDir($base);
    $dir = opendir($from);
    while (false !== ($file = readdir($dir))) {
        if ($file == '.' OR $file == '..') {continue;}

        if (is_dir($from . '/' . $file)) {
            zip_r($from . '/' . $file, $zip, $base . '/' . $file);
        } else {
            $zip->addFile($from . '/' . $file, $base . '/' . $file);
        }
    }
    return $zip;
}
$from = "/path/to/folder";
$base = "basezipfolder";
$zip = new ZipArchive();
$zip->open('zipfile.zip', ZIPARCHIVE::CREATE);
$zip = zip_r($from, $zip, $base);
$zip->close();
Tarik
  • 4,270
  • 38
  • 35
0

Following @user2019515 answer, I needed to handle exclusions to my archive. here is the resulting function with an example.

Zip Function :

function Zip($source, $destination, $include_dir = false, $exclusions = false){
    // Remove existing archive
    if (file_exists($destination)) {
        unlink ($destination);
    }

    $zip = new ZipArchive();
    if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
        return false;
    }
    $source = str_replace('\\', '/', realpath($source));
    if (is_dir($source) === true){
        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
        if ($include_dir) {
            $arr = explode("/",$source);
            $maindir = $arr[count($arr)- 1];
            $source = "";
            for ($i=0; $i < count($arr) - 1; $i++) {
                $source .= '/' . $arr[$i];
            }
            $source = substr($source, 1);
            $zip->addEmptyDir($maindir);
        }
        foreach ($files as $file){
            // Ignore "." and ".." folders
            $file = str_replace('\\', '/', $file);
            if(in_array(substr($file, strrpos($file, '/')+1), array('.', '..'))){
                continue;
            }

            // Add Exclusion
            if(($exclusions)&&(is_array($exclusions))){
                if(in_array(str_replace($source.'/', '', $file), $exclusions)){
                    continue;
                }
            }

            $file = realpath($file);
            if (is_dir($file) === true){
                $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
            } elseif (is_file($file) === true){
                $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
            }
        }
    } elseif (is_file($source) === true){
        $zip->addFromString(basename($source), file_get_contents($source));
    }
    return $zip->close();
}

How to use it :

function backup(){
    $backup = 'tmp/backup-'.$this->site['version'].'.zip';
    $exclusions = [];
    // Excluding an entire directory
    $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('tmp/'), RecursiveIteratorIterator::SELF_FIRST);
    foreach ($files as $file){
        array_push($exclusions,$file);
    }
    // Excluding a file
    array_push($exclusions,'config/config.php');
    // Excluding the backup file
    array_push($exclusions,$backup);
    $this->Zip('.',$backup, false, $exclusions);
}
L. Ouellet
  • 133
  • 1
  • 8