Ver código fonte

Implemented the ability to override the instance of ZipContainer.

ZipContainer will be cloned before writing the zip file.
Tested custom ZipWriter, ZipReader, ZipContainer and ZipFile.
Ne-Lexa 6 anos atrás
pai
commit
5ec656fde4

+ 3 - 3
.travis.yml

@@ -2,15 +2,15 @@ language: php
 
 env:
   global:
-    - ZIPALIGN_INSTALL=false
     - COVERAGE=false
-    - PHPUNIT_FLAGS="-v -c phpunit.xml --testsuite only_fast_tests"
+    - PHPUNIT_FLAGS="-v -c phpunit.xml"
 
 matrix:
   include:
     - php: 5.5
       os: linux
       dist: trusty
+      env: ZIPALIGN_INSTALL=false
 
     - php: 5.6
       os: linux
@@ -40,7 +40,7 @@ matrix:
     - php: 7.4
       os: linux
       dist: bionic
-      env: COVERAGE=true ZIPALIGN_INSTALL=true PHPUNIT_FLAGS="-v -c phpunit.xml --testsuite only_fast_tests --coverage-clover=coverage.clover"
+      env: COVERAGE=true ZIPALIGN_INSTALL=true PHPUNIT_FLAGS="-v -c phpunit.xml --coverage-clover=coverage.clover"
 
 before_install:
   - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi

+ 1 - 0
README.RU.md

@@ -3,6 +3,7 @@
 `PhpZip` - php библиотека для продвинутой работы с ZIP-архивами.
 
 [![Build Status](https://travis-ci.org/Ne-Lexa/php-zip.svg?branch=master)](https://travis-ci.org/Ne-Lexa/php-zip)
+[![Code Coverage](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/?branch=master)
 [![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip)
 [![Total Downloads](https://poser.pugx.org/nelexa/zip/downloads)](https://packagist.org/packages/nelexa/zip)
 [![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.5-8892BF.svg)](https://php.net/)

+ 1 - 0
README.md

@@ -3,6 +3,7 @@
 `PhpZip` is a php-library for extended work with ZIP-archives.
 
 [![Build Status](https://travis-ci.org/Ne-Lexa/php-zip.svg?branch=master)](https://travis-ci.org/Ne-Lexa/php-zip)
+[![Code Coverage](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/?branch=master)
 [![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip)
 [![Total Downloads](https://poser.pugx.org/nelexa/zip/downloads)](https://packagist.org/packages/nelexa/zip)
 [![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.5-8892BF.svg)](https://php.net/)

+ 1 - 0
composer.json

@@ -31,6 +31,7 @@
         "ext-bz2": "*",
         "ext-openssl": "*",
         "ext-fileinfo": "*",
+        "ext-xml": "*",
         "guzzlehttp/psr7": "^1.6",
         "phpunit/phpunit": "^4.8|^5.7",
         "symfony/var-dumper": "^3.0|^4.0|^5.0"

+ 6 - 0
phpunit.xml

@@ -22,6 +22,12 @@
         </testsuite>
     </testsuites>
 
+    <groups>
+        <exclude>
+            <group>large</group>
+        </exclude>
+    </groups>
+
     <filter>
         <whitelist>
             <directory>src</directory>

+ 3 - 1
src/IO/ZipWriter.php

@@ -39,7 +39,9 @@ class ZipWriter
      */
     public function __construct(ZipContainer $container)
     {
-        $this->zipContainer = $container;
+        // we clone the container so that the changes made to
+        // it do not affect the data in the ZipFile class
+        $this->zipContainer = clone $container;
     }
 
     /**

+ 14 - 3
src/ZipFile.php

@@ -20,6 +20,7 @@ use PhpZip\IO\ZipReader;
 use PhpZip\IO\ZipWriter;
 use PhpZip\Model\Data\ZipFileData;
 use PhpZip\Model\Data\ZipNewData;
+use PhpZip\Model\ImmutableZipContainer;
 use PhpZip\Model\ZipContainer;
 use PhpZip\Model\ZipEntry;
 use PhpZip\Model\ZipEntryMatcher;
@@ -68,7 +69,7 @@ class ZipFile implements ZipFileInterface
      */
     public function __construct()
     {
-        $this->zipContainer = new ZipContainer();
+        $this->zipContainer = $this->createZipContainer(null);
     }
 
     /**
@@ -90,6 +91,16 @@ class ZipFile implements ZipFileInterface
         return new ZipWriter($this->zipContainer);
     }
 
+    /**
+     * @param ImmutableZipContainer|null $sourceContainer
+     *
+     * @return ZipContainer
+     */
+    protected function createZipContainer(ImmutableZipContainer $sourceContainer = null)
+    {
+        return new ZipContainer($sourceContainer);
+    }
+
     /**
      * Open zip archive from file.
      *
@@ -151,7 +162,7 @@ class ZipFile implements ZipFileInterface
     public function openFromStream($handle, array $options = [])
     {
         $this->reader = $this->createZipReader($handle, $options);
-        $this->zipContainer = new ZipContainer($this->reader->read());
+        $this->zipContainer = $this->createZipContainer($this->reader->read());
 
         return $this;
     }
@@ -1769,7 +1780,7 @@ class ZipFile implements ZipFileInterface
         if ($this->reader !== null) {
             $this->reader->close();
             $this->reader = null;
-            $this->zipContainer = new ZipContainer();
+            $this->zipContainer = $this->createZipContainer(null);
         }
     }
 

+ 60 - 0
tests/CustomZipFormatTest.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace PhpZip\Tests;
+
+use PhpZip\Exception\ZipEntryNotFoundException;
+use PhpZip\Exception\ZipException;
+use PhpZip\Tests\Internal\Epub\EpubFile;
+
+/**
+ * Checks the ability to create own file-type class, reader, writer and container.
+ *
+ * @see http://www.epubtest.org/test-books source epub files
+ *
+ * @internal
+ *
+ * @small
+ */
+final class CustomZipFormatTest extends ZipTestCase
+{
+    /**
+     * @throws ZipException
+     */
+    public function testEpub()
+    {
+        $epubFile = new EpubFile();
+        $epubFile->openFile(__DIR__ . '/resources/Advanced-v1.0.0.epub');
+        self::assertSame($epubFile->getRootFile(), 'EPUB/package.opf');
+        self::assertSame($epubFile->getMimeType(), 'application/epub+zip');
+        $epubInfo = $epubFile->getEpubInfo();
+        self::assertSame($epubInfo->toArray(), [
+            'title' => 'Advanced Accessibility Tests: Extended Descriptions',
+            'creator' => 'DAISY Consortium Transition to EPUB 3 and DIAGRAM Standards WG',
+            'language' => 'en-US',
+            'publisher' => 'DAISY Consortium and DIAGRAM Center',
+            'description' => 'Tests for accessible extended descriptions of images in EPUBs',
+            'rights' => 'This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike (CC BY-NC-SA) license.',
+            'date' => '2019-01-03',
+            'subject' => 'extended-descriptions',
+        ]);
+        $epubFile->deleteFromName('mimetype');
+        self::assertFalse($epubFile->hasEntry('mimetype'));
+
+        try {
+            $epubFile->getMimeType();
+            self::fail('deleted mimetype');
+        } catch (ZipEntryNotFoundException $e) {
+            self::assertSame('Zip Entry "mimetype" was not found in the archive.', $e->getMessage());
+        }
+        $epubFile->saveAsFile($this->outputFilename);
+        self::assertFalse($epubFile->hasEntry('mimetype'));
+        $epubFile->close();
+
+        self::assertCorrectZipArchive($this->outputFilename);
+
+        $epubFile->openFile($this->outputFilename);
+        // file appended in EpubWriter before write
+        self::assertTrue($epubFile->hasEntry('mimetype'));
+        $epubFile->close();
+    }
+}

+ 98 - 0
tests/Internal/Epub/EpubFile.php

@@ -0,0 +1,98 @@
+<?php
+
+/** @noinspection PhpComposerExtensionStubsInspection */
+
+namespace PhpZip\Tests\Internal\Epub;
+
+use PhpZip\Constants\ZipPlatform;
+use PhpZip\Exception\ZipEntryNotFoundException;
+use PhpZip\Exception\ZipException;
+use PhpZip\IO\ZipReader;
+use PhpZip\IO\ZipWriter;
+use PhpZip\Model\ImmutableZipContainer;
+use PhpZip\Model\ZipContainer;
+use PhpZip\Model\ZipEntry;
+use PhpZip\ZipFile;
+
+/**
+ * Class EpubFile.
+ *
+ * @property EpubZipContainer $zipContainer
+ */
+class EpubFile extends ZipFile
+{
+    /**
+     * @return ZipWriter
+     */
+    protected function createZipWriter()
+    {
+        return new EpubWriter($this->zipContainer);
+    }
+
+    /**
+     * @param resource $inputStream
+     * @param array    $options
+     *
+     * @return ZipReader
+     */
+    protected function createZipReader($inputStream, array $options = [])
+    {
+        return new EpubReader($inputStream, $options);
+    }
+
+    /**
+     * @param ImmutableZipContainer|null $sourceContainer
+     *
+     * @return ZipContainer
+     */
+    protected function createZipContainer(ImmutableZipContainer $sourceContainer = null)
+    {
+        return new EpubZipContainer($sourceContainer);
+    }
+
+    /**
+     * @param ZipEntry $zipEntry
+     */
+    protected function addZipEntry(ZipEntry $zipEntry)
+    {
+        $zipEntry->setCreatedOS(ZipPlatform::OS_DOS);
+        $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX);
+        parent::addZipEntry($zipEntry);
+    }
+
+    /**
+     * @throws ZipEntryNotFoundException
+     *
+     * @return string
+     */
+    public function getMimeType()
+    {
+        return $this->zipContainer->getMimeType();
+    }
+
+    public function getEpubInfo()
+    {
+        return new EpubInfo($this->getEntryContents($this->getRootFile()));
+    }
+
+    /**
+     * @throws ZipException
+     *
+     * @return string
+     */
+    public function getRootFile()
+    {
+        $entryName = 'META-INF/container.xml';
+        $contents = $this->getEntryContents($entryName);
+        $doc = new \DOMDocument();
+        $doc->loadXML($contents);
+        $xpath = new \DOMXPath($doc);
+        $rootFile = $xpath->evaluate('string(//@full-path)');
+
+        if ($rootFile === '') {
+            throw new ZipException('Incorrect ' . $entryName . ' file format');
+        }
+
+        return $rootFile;
+    }
+}

+ 159 - 0
tests/Internal/Epub/EpubInfo.php

@@ -0,0 +1,159 @@
+<?php
+
+/** @noinspection PhpComposerExtensionStubsInspection */
+
+namespace PhpZip\Tests\Internal\Epub;
+
+use PhpZip\Exception\ZipException;
+
+/**
+ * Class EpubInfo.
+ *
+ * @see http://idpf.org/epub/30/spec/epub30-publications.html
+ */
+class EpubInfo
+{
+    /** @var string|null */
+    private $title;
+
+    /** @var string|null */
+    private $creator;
+
+    /** @var string|null */
+    private $language;
+
+    /** @var string|null */
+    private $publisher;
+
+    /** @var string|null */
+    private $description;
+
+    /** @var string|null */
+    private $rights;
+
+    /** @var string|null */
+    private $date;
+
+    /** @var string|null */
+    private $subject;
+
+    /**
+     * EpubInfo constructor.
+     *
+     * @param $xmlContents
+     *
+     * @throws ZipException
+     */
+    public function __construct($xmlContents)
+    {
+        $doc = new \DOMDocument();
+        $doc->loadXML($xmlContents);
+        $xpath = new \DOMXpath($doc);
+        $xpath->registerNamespace('root', 'http://www.idpf.org/2007/opf');
+        $metaDataNodeList = $xpath->query('//root:metadata');
+
+        if (\count($metaDataNodeList) !== 1) {
+            throw new ZipException('Invalid .opf file format');
+        }
+        $metaDataNode = $metaDataNodeList->item(0);
+
+        $title = $xpath->evaluate('string(//dc:title)', $metaDataNode);
+        $creator = $xpath->evaluate('string(//dc:creator)', $metaDataNode);
+        $language = $xpath->evaluate('string(//dc:language)', $metaDataNode);
+        $publisher = $xpath->evaluate('string(//dc:publisher)', $metaDataNode);
+        $description = $xpath->evaluate('string(//dc:description)', $metaDataNode);
+        $rights = $xpath->evaluate('string(//dc:rights)', $metaDataNode);
+        $date = $xpath->evaluate('string(//dc:date)', $metaDataNode);
+        $subject = $xpath->evaluate('string(//dc:subject)', $metaDataNode);
+
+        $this->title = empty($title) ? null : $title;
+        $this->creator = empty($creator) ? null : $creator;
+        $this->language = empty($language) ? null : $language;
+        $this->publisher = empty($publisher) ? null : $publisher;
+        $this->description = empty($description) ? null : $description;
+        $this->rights = empty($rights) ? null : $rights;
+        $this->date = empty($date) ? null : $date;
+        $this->subject = empty($subject) ? null : $subject;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getCreator()
+    {
+        return $this->creator;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getLanguage()
+    {
+        return $this->language;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getPublisher()
+    {
+        return $this->publisher;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getRights()
+    {
+        return $this->rights;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getDate()
+    {
+        return $this->date;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getSubject()
+    {
+        return $this->subject;
+    }
+
+    /**
+     * @return array
+     */
+    public function toArray()
+    {
+        return [
+            'title' => $this->title,
+            'creator' => $this->creator,
+            'language' => $this->language,
+            'publisher' => $this->publisher,
+            'description' => $this->description,
+            'rights' => $this->rights,
+            'date' => $this->date,
+            'subject' => $this->subject,
+        ];
+    }
+}

+ 21 - 0
tests/Internal/Epub/EpubReader.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace PhpZip\Tests\Internal\Epub;
+
+use PhpZip\IO\ZipReader;
+
+/**
+ * Class EpubReader.
+ */
+class EpubReader extends ZipReader
+{
+    /**
+     * @return bool
+     *
+     * @see https://github.com/w3c/epubcheck/issues/334
+     */
+    protected function isZip64Support()
+    {
+        return false;
+    }
+}

+ 66 - 0
tests/Internal/Epub/EpubWriter.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace PhpZip\Tests\Internal\Epub;
+
+use PhpZip\Constants\ZipCompressionMethod;
+use PhpZip\Constants\ZipPlatform;
+use PhpZip\Exception\ZipUnsupportMethodException;
+use PhpZip\IO\ZipWriter;
+use PhpZip\Model\Data\ZipNewData;
+use PhpZip\Model\ZipEntry;
+
+/**
+ * Class EpubWriter.
+ *
+ * @property EpubZipContainer $zipContainer
+ */
+class EpubWriter extends ZipWriter
+{
+    /**
+     * @throws ZipUnsupportMethodException
+     */
+    protected function beforeWrite()
+    {
+        parent::beforeWrite();
+
+        if (!$this->zipContainer->hasEntry('mimetype')) {
+            $zipEntry = new ZipEntry('mimetype');
+            $zipEntry->setCreatedOS(ZipPlatform::OS_DOS);
+            $zipEntry->setExtractedOS(ZipPlatform::OS_DOS);
+            $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED);
+            $zipEntry->setData(new ZipNewData($zipEntry, 'application/epub+zip'));
+            $this->zipContainer->addEntry($zipEntry);
+        }
+
+        $this->sortEntries();
+    }
+
+    private function sortEntries()
+    {
+        $this->zipContainer->sortByEntry(
+            static function (ZipEntry $a, ZipEntry $b) {
+                if (strcasecmp($a->getName(), 'mimetype') === 0) {
+                    return -1;
+                }
+
+                if (strcasecmp($b->getName(), 'mimetype') === 0) {
+                    return 1;
+                }
+
+                if ($a->isDirectory() && $b->isDirectory()) {
+                    return strcmp($a->getName(), $b->getName());
+                }
+
+                if ($a->isDirectory()) {
+                    return -1;
+                }
+
+                if ($b->isDirectory()) {
+                    return 1;
+                }
+
+                return strcmp($a->getName(), $b->getName());
+            }
+        );
+    }
+}

+ 22 - 0
tests/Internal/Epub/EpubZipContainer.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace PhpZip\Tests\Internal\Epub;
+
+use PhpZip\Exception\ZipEntryNotFoundException;
+use PhpZip\Model\ZipContainer;
+
+/**
+ * Class EpubZipContainer.
+ */
+class EpubZipContainer extends ZipContainer
+{
+    /**
+     * @throws ZipEntryNotFoundException
+     *
+     * @return string
+     */
+    public function getMimeType()
+    {
+        return $this->getEntry('mimetype')->getData()->getDataAsString();
+    }
+}

+ 17 - 0
tests/ZipFileTest.php

@@ -2418,4 +2418,21 @@ class ZipFileTest extends ZipTestCase
         static::assertSame($zipFile->getEntry($newEntryName)->getCompressionMethod(), ZipCompressionMethod::STORED);
         $zipFile->close();
     }
+
+    /**
+     * @throws ZipEntryNotFoundException
+     * @throws ZipException
+     */
+    public function testCloneZipContainerInZipWriter()
+    {
+        $zipFile = new ZipFile();
+        $zipFile['file 1'] = 'contents';
+        $zipEntryBeforeWrite = $zipFile->getEntry('file 1');
+        $zipFile->saveAsFile($this->outputFilename);
+        $zipAfterBeforeWrite = $zipFile->getEntry('file 1');
+
+        static::assertEquals($zipAfterBeforeWrite, $zipEntryBeforeWrite);
+
+        $zipFile->close();
+    }
 }

BIN
tests/resources/Advanced-v1.0.0.epub