PageRenderTime 296ms CodeModel.GetById 95ms app.highlight 66ms RepoModel.GetById 83ms app.codeStats 1ms

/lib/spout/src/Spout/Writer/Common/Helper/ZipHelper.php

https://bitbucket.org/moodle/moodle
PHP | 217 lines | 92 code | 24 blank | 101 comment | 5 complexity | fc625de6e839b8ca6fb39bd1f9341c43 MD5 | raw file
  1<?php
  2
  3namespace Box\Spout\Writer\Common\Helper;
  4
  5use Box\Spout\Writer\Common\Creator\InternalEntityFactory;
  6
  7/**
  8 * Class ZipHelper
  9 * This class provides helper functions to create zip files
 10 */
 11class ZipHelper
 12{
 13    const ZIP_EXTENSION = '.zip';
 14
 15    /** Controls what to do when trying to add an existing file */
 16    const EXISTING_FILES_SKIP = 'skip';
 17    const EXISTING_FILES_OVERWRITE = 'overwrite';
 18
 19    /** @var InternalEntityFactory Factory to create entities */
 20    private $entityFactory;
 21
 22    /**
 23     * @param InternalEntityFactory $entityFactory Factory to create entities
 24     */
 25    public function __construct($entityFactory)
 26    {
 27        $this->entityFactory = $entityFactory;
 28    }
 29
 30    /**
 31     * Returns a new ZipArchive instance pointing at the given path.
 32     *
 33     * @param string $tmpFolderPath Path of the temp folder where the zip file will be created
 34     * @return \ZipArchive
 35     */
 36    public function createZip($tmpFolderPath)
 37    {
 38        $zip = $this->entityFactory->createZipArchive();
 39        $zipFilePath = $tmpFolderPath . self::ZIP_EXTENSION;
 40
 41        $zip->open($zipFilePath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
 42
 43        return $zip;
 44    }
 45
 46    /**
 47     * @param \ZipArchive $zip An opened zip archive object
 48     * @return string Path where the zip file of the given folder will be created
 49     */
 50    public function getZipFilePath(\ZipArchive $zip)
 51    {
 52        return $zip->filename;
 53    }
 54
 55    /**
 56     * Adds the given file, located under the given root folder to the archive.
 57     * The file will be compressed.
 58     *
 59     * Example of use:
 60     *   addFileToArchive($zip, '/tmp/xlsx/foo', 'bar/baz.xml');
 61     *   => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
 62     *
 63     * @param \ZipArchive $zip An opened zip archive object
 64     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
 65     * @param string $localFilePath Path of the file to be added, under the root folder
 66     * @param string $existingFileMode Controls what to do when trying to add an existing file
 67     * @return void
 68     */
 69    public function addFileToArchive($zip, $rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
 70    {
 71        $this->addFileToArchiveWithCompressionMethod(
 72            $zip,
 73            $rootFolderPath,
 74            $localFilePath,
 75            $existingFileMode,
 76            \ZipArchive::CM_DEFAULT
 77        );
 78    }
 79
 80    /**
 81     * Adds the given file, located under the given root folder to the archive.
 82     * The file will NOT be compressed.
 83     *
 84     * Example of use:
 85     *   addUncompressedFileToArchive($zip, '/tmp/xlsx/foo', 'bar/baz.xml');
 86     *   => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
 87     *
 88     * @param \ZipArchive $zip An opened zip archive object
 89     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
 90     * @param string $localFilePath Path of the file to be added, under the root folder
 91     * @param string $existingFileMode Controls what to do when trying to add an existing file
 92     * @return void
 93     */
 94    public function addUncompressedFileToArchive($zip, $rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
 95    {
 96        $this->addFileToArchiveWithCompressionMethod(
 97            $zip,
 98            $rootFolderPath,
 99            $localFilePath,
100            $existingFileMode,
101            \ZipArchive::CM_STORE
102        );
103    }
104
105    /**
106     * Adds the given file, located under the given root folder to the archive.
107     * The file will NOT be compressed.
108     *
109     * Example of use:
110     *   addUncompressedFileToArchive($zip, '/tmp/xlsx/foo', 'bar/baz.xml');
111     *   => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
112     *
113     * @param \ZipArchive $zip An opened zip archive object
114     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
115     * @param string $localFilePath Path of the file to be added, under the root folder
116     * @param string $existingFileMode Controls what to do when trying to add an existing file
117     * @param int $compressionMethod The compression method
118     * @return void
119     */
120    protected function addFileToArchiveWithCompressionMethod($zip, $rootFolderPath, $localFilePath, $existingFileMode, $compressionMethod)
121    {
122        if (!$this->shouldSkipFile($zip, $localFilePath, $existingFileMode)) {
123            $normalizedFullFilePath = $this->getNormalizedRealPath($rootFolderPath . '/' . $localFilePath);
124            $zip->addFile($normalizedFullFilePath, $localFilePath);
125
126            if (self::canChooseCompressionMethod()) {
127                $zip->setCompressionName($localFilePath, $compressionMethod);
128            }
129        }
130    }
131
132    /**
133     * @return bool Whether it is possible to choose the desired compression method to be used
134     */
135    public static function canChooseCompressionMethod()
136    {
137        // setCompressionName() is a PHP7+ method...
138        return (method_exists(new \ZipArchive(), 'setCompressionName'));
139    }
140
141    /**
142     * @param \ZipArchive $zip An opened zip archive object
143     * @param string $folderPath Path to the folder to be zipped
144     * @param string $existingFileMode Controls what to do when trying to add an existing file
145     * @return void
146     */
147    public function addFolderToArchive($zip, $folderPath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
148    {
149        $folderRealPath = $this->getNormalizedRealPath($folderPath) . '/';
150        $itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
151
152        foreach ($itemIterator as $itemInfo) {
153            $itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
154            $itemLocalPath = str_replace($folderRealPath, '', $itemRealPath);
155
156            if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) {
157                $zip->addFile($itemRealPath, $itemLocalPath);
158            }
159        }
160    }
161
162    /**
163     * @param \ZipArchive $zip
164     * @param string $itemLocalPath
165     * @param string $existingFileMode
166     * @return bool Whether the file should be added to the archive or skipped
167     */
168    protected function shouldSkipFile($zip, $itemLocalPath, $existingFileMode)
169    {
170        // Skip files if:
171        //   - EXISTING_FILES_SKIP mode chosen
172        //   - File already exists in the archive
173        return ($existingFileMode === self::EXISTING_FILES_SKIP && $zip->locateName($itemLocalPath) !== false);
174    }
175
176    /**
177     * Returns canonicalized absolute pathname, containing only forward slashes.
178     *
179     * @param string $path Path to normalize
180     * @return string Normalized and canonicalized path
181     */
182    protected function getNormalizedRealPath($path)
183    {
184        $realPath = realpath($path);
185
186        return str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
187    }
188
189    /**
190     * Closes the archive and copies it into the given stream
191     *
192     * @param \ZipArchive $zip An opened zip archive object
193     * @param resource $streamPointer Pointer to the stream to copy the zip
194     * @return void
195     */
196    public function closeArchiveAndCopyToStream($zip, $streamPointer)
197    {
198        $zipFilePath = $zip->filename;
199        $zip->close();
200
201        $this->copyZipToStream($zipFilePath, $streamPointer);
202    }
203
204    /**
205     * Streams the contents of the zip file into the given stream
206     *
207     * @param string $zipFilePath Path of the zip file
208     * @param resource $pointer Pointer to the stream to copy the zip
209     * @return void
210     */
211    protected function copyZipToStream($zipFilePath, $pointer)
212    {
213        $zipFilePointer = fopen($zipFilePath, 'r');
214        stream_copy_to_stream($zipFilePointer, $pointer);
215        fclose($zipFilePointer);
216    }
217}