Search code examples
phpphp-ziparchive

Creating and downloading ZipArchive fails on PHP 5.1, works on PHP 5.4


The following class is failing with ZipArchive error 23 - "Entry has been deleted" when attempting to add a new empty dir that is nested in another added empty dir. Ex:

dir1:
    file1
    file2
    ...

works just fine, but something like:

dir1:
    dir2:
        file1
        ...
    file1
    file1
    ...

will fail when attempting to add dir1/dir2.

Downloader Class

class Downloader {

    var $path, $tmp, $zip, $txt, $selected;

    function __construct($selected) {
        global $webroot;

        $this->path = $webroot.'downloads/';
        $this->tmp = tempnam('../tmp', 'dln');
        $this->zip = new ZipArchive();
        $this->zip->open($this->tmp, ZipArchive::OVERWRITE);
        $this->txt = '';
        $this->selected = $selected;
    }

    public function incl_file($name) {
        $this->zip->addFile($this->path.$name, $name);
    }

    public function incl_dir($name) {
        $this->zip->addEmptyDir($name);
        $files = new DirectoryIterator($this->path.$name.'/');
        foreach ($files as $file) {
            if (!$file->isDot()) {
                if ($file->isDir()) {
                    $this->incl_dir($name.'/'.$file->getFilename());
                } else {
                    $this->incl_file($name.'/'.$file->getFilename());
                }
            }
        }
    }

    public function download($downloadObj) {
        $selected = preg_split('/,/', $this->selected);
        foreach ($selected as $name) {
            $this->txt .= file_get_contents($this->path.$name.'.snip');

            foreach ($downloadObj->files as $file) {
                if ($name == $file->name) {
                    // check to see if there are files to include
                    if (strlen($file->incl_files) > 0) {
                        // if so, get include directories
                        $incl_dirs = preg_split('/,/', $file->incl_dir);
                        // iterate include directories
                        foreach ($incl_dirs as $dir) {
                            // get include file groups
                            $incl_file_groups = preg_split('/,/', $file->incl_files);
                            // iterate include file groups
                            foreach ($incl_file_groups as $group) {
                                // get individual include files
                                $incl_files = preg_split('/\|/', $group);
                                // iterate individual include files
                                foreach ($incl_files as $f) {
                                    // check to see if all files in include dir should be inserted
                                    if ($f == '*') {
                                        $this->incl_dir($dir);
                                    } else {
                                        $path = '';
                                        if ($dir == "/") {
                                            $path = $f;
                                        } else {
                                            $path .= $dir.'/'.$f;
                                        }
                                        $this->incl_file($path);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        $this->txt = file_get_contents($this->path.'header.inc').$this->txt.file_get_contents($this->path.'footer.inc');
        $this->zip->addFromString('DownloadTemplate.xml', $this->txt);
        $this->zip->close();
        $filename = 'MyDownload.zip';
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
        header('Last-Modified: ' . gmdate('D,d M YH:i:s') . ' GMT');
        header('Cache-Control: no-cache, must-revalidate');
        header('Pragma: no-cache');
        header('Content-type: application/zip');
        header('Content-Disposition: attachment; filename='.$filename);
        header('Content-Transfer-Encoding: binary');
        readfile($this->tmp);
        unlink($this->tmp);
    }
}

Solution

  • After a lot of debugging and testing different options, it turns out that the problem was in the call to addEmptyDir(). For some reason that I still haven't found an answer to, the call was failing to add the directory in the Zip. However, I then discovered that the call was unnecessary altogether as addFile() will create the necessary dirs when given a relative path name, such as 'dir1/file1'. Knowing this I just removed the call for addEmptyDir() and was able to properly generate the zip file with appropriate directories.