Comment [récursivement] compresser un répertoire en PHP?

118

Le répertoire est quelque chose comme:

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

J'utilise la même classe PHP Zip utilisée dans PHPMyAdmin http://trac.seagullproject.org/browser/branches/0.6-bugfix/lib/other/Zip.php . Je ne sais pas comment compresser un répertoire plutôt qu'un simple fichier. Voici ce que j'ai jusqu'à présent:

$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

mais lorsque j'essaye de décompresser le fichier zip téléchargé correspondant, j'obtiens "opération non autorisée"

Cette erreur ne se produit que lorsque j'essaie de décompresser sur mon mac, lorsque je décompresse via la ligne de commande, le fichier se décompresse ok. Dois-je envoyer un type de contenu spécifique lors du téléchargement, actuellement 'application / zip'

ed209
la source
Ce code fonctionne réellement - mais pour une raison quelconque, vous ne pouvez pas le décompresser sur Mac OS (à moins que vous n'utilisiez le décompression CLI). Le fichier Zip se décompose sur PC.
ed209

Réponses:

253

Voici une fonction simple qui peut compresser n'importe quel fichier ou répertoire de manière récursive, n'a besoin que de l'extension zip pour être chargée.

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

Appelez ça comme ça:

Zip('/folder/to/compress/', './compressed.zip');
Alix Axel
la source
5
A très bien fonctionné, ma seule question est que mon script s'exécute à partir d'un emplacement différent vers les fichiers à compresser, donc lorsque je fournis le 1er argument, l'emplacement du chemin de fichier complet est utilisé dans le zip, comme ceci: C: \ wamp \ www \ export \ pkg-1211.191011 \ pkg-1211.191011.zip, cette structure complète de dossiers imbriqués se trouvant dans la nouvelle archive. Existe-t-il un moyen d'adapter le script ci-dessus pour qu'il ne contienne que les fichiers et répertoires vers lesquels je pointe, et non le chemin complet d'où ils proviennent?
danjah
4
@Danjah: J'ai mis à jour le code, devrait fonctionner à la fois pour * nix et Windows maintenant.
Alix Axel
5
Je me demande pourquoi cela utilise file_get_contentset ajoute des chaînes. zip ne prend-il pas en charge l'ajout de fichiers directement?
hakre
4
Vous devez tout remplacer '/'par DIRECTORY_SEPARATORpour que cela fonctionne sous Windows, bien sûr. Sinon, vous vous retrouverez avec le chemin complet (y compris le nom du lecteur) dans votre ZIP, par exemple C:\Users\....
caw
3
Le code d'origine était / est cassé et redondant. Il n'est pas nécessaire de remplacer //par \ car cela brise réellement le foreach sur Windows. Si vous utilisez le module intégré DIRECTORY_SEPARATORcomme vous le devriez, il n'est pas nécessaire de le remplacer. C'est le codage en dur /qui causait des problèmes à certains utilisateurs. J'étais un peu confus quant à la raison pour laquelle j'obtenais une archive vide. Ma révision fonctionnera correctement sous * nix et Windows.
DavidScherer
18

Encore un autre archivage d'arborescence de répertoires récursif, implémenté comme une extension de ZipArchive. En prime, une fonction d'aide à la compression d'arbre à instruction unique est incluse. Le nom local facultatif est pris en charge, comme dans les autres fonctions ZipArchive. Code de gestion des erreurs à ajouter ...

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
la source
Belle réponse Giorgio! Il donne de meilleurs résultats que Zip () sur les fenêtres pour l'arborescence. merci
RafaSashi
11

J'ai édité la réponse d' Alix Axel pour prendre un troisième argument, en définissant ce troisième argument surtrue tous les fichiers seront ajoutés sous le répertoire principal plutôt que directement dans le dossier zip.

Si le fichier zip existe, le fichier sera également supprimé.

Exemple:

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

Structure truezip du troisième argument :

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

Troisième argument falseou structure zip manquante:

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

Code édité:

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();
}
user2019515
la source
Je vous remercie! J'avais besoin d'inclure le répertoire principal dans ma situation.
Kim Stacks
votre fonction ne fonctionne pas seulement le répertoire principal (racine) ajoute et rien
Krishna Torque
Je sais que cela a été répondu il y a longtemps. Est-il possible d'avoir un nom personnalisé pour 'maindirectory' au lieu du nom d'origine.
Vaibhav Sidapara
@VaibhavSidapara Croyez que cela devrait être possible en changeant $maindirle nom préféré.
user2019515
Excellente réponse, m'a beaucoup aidé. J'ai ajouté un quatrième argument à cette fonction pour inclure des exclusions. J'ajouterai le code final comme autre réponse à cette question.
L. Ouellet le
4

UTILISATION: thisfile.php? Dir =. / Chemin / vers / dossier (après la compression, le téléchargement commence également :)

<?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
la source
2

Essayez ce lien <- PLUS DE CODE SOURCE ICI

/** 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
la source
1

Voici mon code pour Zip les dossiers et ses sous-dossiers et ses fichiers et le rendre téléchargeable au format zip

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

En cas de problème avec le code, faites-le moi savoir.

nitine
la source
0

J'avais besoin d'exécuter cette fonction Zip sous Mac OSX

donc je fermerais toujours ce .DS_Store ennuyeux.

J'ai adapté https://stackoverflow.com/users/2019515/user2019515 en incluant des fichiers additionalIgnore.

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 pour ignorer le .DS_Store de zip, vous exécutez

zipIt ('/ chemin / vers / dossier', '/path/to/compressed.zip', false, array ('. DS_Store'));

Kim Stacks
la source
0

Excellente solution mais pour mon Windows, j'ai besoin de faire des modifications. Sous le code de modification

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
la source
0

Ce code fonctionne à la fois pour Windows et 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
la source
0

Voici ma base de version sur Alix, fonctionne sur Windows et, espérons-le, sur * nix aussi:

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
la source
0

Voici la fonction récursive simple, facile à lire qui fonctionne très bien:

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
la source
0

Suite à la réponse @ user2019515, je devais gérer les exclusions de mes archives. voici la fonction résultante avec un exemple.

Fonction Zip:

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

Comment l'utiliser :

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
la source