Ne-Lexa / php-zip

PhpZip is a php-library for extended work with ZIP-archives.
MIT License
491 stars 60 forks source link

Doesn't create folder structure #29

Closed andre-paulo98 closed 5 years ago

andre-paulo98 commented 5 years ago
Q A
Library version(s) affected: 3.1
PHP version(s): 7.2.9
OS (with bit depth): Ubuntu 16.04 and Windows 10 1803 (both 64bit)

Description
For some reason, the zip that this library creates its not recognized in some programs, did a zipinfo on the zip that was generated and this was the output: automatic Then on my computer I unziped it and zip it back (with 7zip manually) and this was the output: manual

How to reproduce
I've tried $zip->addDirRecursive() and $zip->addFilesFromIterator() and got the same result on both cases.

Additional context
The zips that were used: manual.zip automatic.zip

Ne-Lexa commented 5 years ago

Adding intermediate directories is not require to create an archive. At the moment, directory entries are added only for directories without files.

Report what problems you are having with the file and what programs do not work with it.

$ unzip -t automatic.zip 
Archive:  automatic.zip
    testing: data/flint_to_gravel/advancements/recipes/building_blocks/gravel.json   OK
    testing: data/flint_to_gravel/recipes/gravel.json   OK
    testing: pack.mcmeta              OK
No errors detected in compressed data of automatic.zip.
$ unzip -t manual.zip 
Archive:  manual.zip
    testing: data/                    OK
    testing: data/flint_to_gravel/    OK
    testing: data/flint_to_gravel/advancements/   OK
    testing: data/flint_to_gravel/advancements/recipes/   OK
    testing: data/flint_to_gravel/advancements/recipes/building_blocks/   OK
    testing: data/flint_to_gravel/advancements/recipes/building_blocks/gravel.json   OK
    testing: data/flint_to_gravel/recipes/   OK
    testing: data/flint_to_gravel/recipes/gravel.json   OK
    testing: pack.mcmeta              OK
No errors detected in compressed data of manual.zip.
andre-paulo98 commented 5 years ago

Its some internal program that I've and for some reason this needs to have the folder structure to understand the zip correctly. I'll try to manually add the folders. I'll keep you updated

Ne-Lexa commented 5 years ago

In your case, you can extend the class. I'll think about adding an option to create a folder structure.

<?php

use PhpZip\Exception\ZipException;
use PhpZip\Util\FilesUtil;
use PhpZip\ZipFileInterface;

require __DIR__ . '/vendor/autoload.php';

class CustomZipFile extends \PhpZip\ZipFile
{
    /**
     * Add directories from directory iterator.
     *
     * @param \Iterator $iterator Directory iterator.
     * @param string $localPath Add files to this directory, or the root.
     * @param int|null $compressionMethod Compression method.
     *                 Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
     *                 If null, then auto choosing method.
     * @return ZipFileInterface
     * @throws ZipException
     * @see ZipFileInterface::METHOD_STORED
     * @see ZipFileInterface::METHOD_DEFLATED
     * @see ZipFileInterface::METHOD_BZIP2
     */
    public function addFilesFromIterator(
        \Iterator $iterator,
        $localPath = '/',
        $compressionMethod = null
    )
    {
        $localPath = (string)$localPath;
        if ($localPath !== '') {
            $localPath = trim($localPath, '\\/');
        } else {
            $localPath = '';
        }

        $iterator = $iterator instanceof \RecursiveIterator ?
            new \RecursiveIteratorIterator($iterator) :
            new \IteratorIterator($iterator);
        /**
         * @var string[] $files
         * @var string $path
         */
        $files = [];
        foreach ($iterator as $file) {
            if ($file instanceof \SplFileInfo) {
                if ($file->getBasename() === '..') {
                    continue;
                }
                if ($file->getBasename() === '.') {
                    $files[] = dirname($file->getPathname());
                } else {
                    $files[] = $file->getPathname();
                }
            }
        }
        if (empty($files)) {
            return $this;
        }

        natcasesort($files);
        $path = array_shift($files);
        foreach ($files as $file) {
            $relativePath = str_replace($path, $localPath, $file);
            $relativePath = ltrim($relativePath, '\\/');
            if (is_dir($file)) {
                $this->addEmptyDir($relativePath);
            } elseif (is_file($file)) {
                $this->addFile($file, $relativePath, $compressionMethod);
            }
        }
        return $this;
    }

    /**
     * Add files from glob pattern.
     *
     * @param string $inputDir Input directory
     * @param string $globPattern Glob pattern.
     * @param string|null $localPath Add files to this directory, or the root.
     * @param bool $recursive Recursive search.
     * @param int|null $compressionMethod Compression method.
     *                 Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
     *                 If null, then auto choosing method.
     * @return ZipFileInterface
     * @throws ZipException
     * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
     */
    private function addGlob(
        $inputDir,
        $globPattern,
        $localPath = '/',
        $recursive = true,
        $compressionMethod = null
    )
    {
        if ($inputDir === null) {
            throw new InvalidArgumentException('Input dir is null');
        }
        $inputDir = (string)$inputDir;
        if ($inputDir === '') {
            throw new InvalidArgumentException('The input directory is not specified');
        }
        if (!is_dir($inputDir)) {
            throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir));
        }
        $globPattern = (string)$globPattern;
        if (empty($globPattern)) {
            throw new InvalidArgumentException('The glob pattern is not specified');
        }

        $inputDir = rtrim($inputDir, '/\\') . DIRECTORY_SEPARATOR;
        $globPattern = $inputDir . $globPattern;

        $filesFound = FilesUtil::globFileSearch($globPattern, GLOB_BRACE, $recursive);
        if ($filesFound === false || empty($filesFound)) {
            return $this;
        }
        if ($localPath !== null && is_string($localPath)) {
            $localPath = trim($localPath, '/\\') . '/';
        } else {
            $localPath = '/';
        }

        /**
         * @var string $file
         */
        foreach ($filesFound as $file) {
            $filename = str_replace($inputDir, $localPath, $file);
            $filename = ltrim($filename, '\\/');
            if (is_dir($file)) {
                $this->addEmptyDir($filename);
            } elseif (is_file($file)) {
                $this->addFile($file, $filename, $compressionMethod);
            }
        }
        return $this;
    }
}

$outputFilename = 'manual.zip';

$zipFile = new CustomZipFile();
$zipFile->addDirRecursive(__DIR__ . '/manual');
$zipFile->saveAsFile($outputFilename);
$zipFile->close();

passthru('unzip -t ' . escapeshellarg($outputFilename));

Output:

Archive:  manual.zip
    testing: data/                    OK
    testing: data/flint_to_gravel/    OK
    testing: data/flint_to_gravel/advancements/   OK
    testing: data/flint_to_gravel/advancements/recipes/   OK
    testing: data/flint_to_gravel/advancements/recipes/building_blocks/   OK
    testing: data/flint_to_gravel/advancements/recipes/building_blocks/gravel.json   OK
    testing: data/flint_to_gravel/recipes/   OK
    testing: data/flint_to_gravel/recipes/gravel.json   OK
    testing: pack.mcmeta              OK
No errors detected in compressed data of manual.zip.
andre-paulo98 commented 5 years ago

Using the class you made it does create the structure and the program can indeed read it. Appreciate your help!