How to [recursively] Zip a directory in PHP?

PhpDirectoryZipDirectory StructureRecursion

Php Problem Overview


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'

Php Solutions


Solution 1 - Php

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');

Solution 2 - Php

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

Solution 3 - Php

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();
}

Solution 4 - Php

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']);
}
?>

Solution 5 - Php

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');
?>;

Solution 6 - Php

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.

Solution 7 - Php

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'));

Solution 8 - Php

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();
}

Solution 9 - Php

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();
}

Solution 10 - Php

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();
}

Solution 11 - Php

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();

Solution 12 - Php

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);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questioned209View Question on Stackoverflow
Solution 1 - PhpAlix AxelView Answer on Stackoverflow
Solution 2 - PhpGiorgio BarchiesiView Answer on Stackoverflow
Solution 3 - Phpuser2019515View Answer on Stackoverflow
Solution 4 - PhpT.ToduaView Answer on Stackoverflow
Solution 5 - PhpPhill PaffordView Answer on Stackoverflow
Solution 6 - PhpnitinView Answer on Stackoverflow
Solution 7 - PhpKim StacksView Answer on Stackoverflow
Solution 8 - PhpJuanView Answer on Stackoverflow
Solution 9 - PhpAmmar QalaView Answer on Stackoverflow
Solution 10 - PhpOhad SchneiderView Answer on Stackoverflow
Solution 11 - PhpTarikView Answer on Stackoverflow
Solution 12 - PhpL. OuelletView Answer on Stackoverflow