|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004 |
- <?php
-
- namespace PhpZip;
-
- use PhpZip\Constants\UnixStat;
- use PhpZip\Constants\ZipCompressionLevel;
- use PhpZip\Constants\ZipCompressionMethod;
- use PhpZip\Constants\ZipEncryptionMethod;
- use PhpZip\Constants\ZipOptions;
- use PhpZip\Constants\ZipPlatform;
- use PhpZip\Exception\InvalidArgumentException;
- use PhpZip\Exception\ZipEntryNotFoundException;
- use PhpZip\Exception\ZipException;
- use PhpZip\IO\Stream\ResponseStream;
- use PhpZip\IO\Stream\ZipEntryStreamWrapper;
- 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;
- use PhpZip\Model\ZipInfo;
- use PhpZip\Util\FilesUtil;
- use PhpZip\Util\StringUtil;
- use Psr\Http\Message\ResponseInterface;
- use Symfony\Component\Finder\Finder;
- use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo;
-
- /**
- * Create, open .ZIP files, modify, get info and extract files.
- *
- * Implemented support traditional PKWARE encryption and WinZip AES encryption.
- * Implemented support ZIP64.
- * Support ZipAlign functional.
- *
- * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
- *
- * @author Ne-Lexa alexey@nelexa.ru
- * @license MIT
- */
- class ZipFile implements ZipFileInterface
- {
- /** @var array default mime types */
- private static $defaultMimeTypes = [
- 'zip' => 'application/zip',
- 'apk' => 'application/vnd.android.package-archive',
- 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- 'epub' => 'application/epub+zip',
- 'jar' => 'application/java-archive',
- 'odt' => 'application/vnd.oasis.opendocument.text',
- 'pptx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
- 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
- 'xpi' => 'application/x-xpinstall',
- ];
-
- /** @var ZipContainer */
- protected $zipContainer;
-
- /** @var ZipReader|null */
- private $reader;
-
- /**
- * ZipFile constructor.
- */
- public function __construct()
- {
- $this->zipContainer = $this->createZipContainer(null);
- }
-
- /**
- * @param resource $inputStream
- * @param array $options
- *
- * @return ZipReader
- */
- protected function createZipReader($inputStream, array $options = [])
- {
- return new ZipReader($inputStream, $options);
- }
-
- /**
- * @return ZipWriter
- */
- protected function createZipWriter()
- {
- 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.
- *
- * @param string $filename
- * @param array $options
- *
- * @throws ZipException if can't open file
- *
- * @return ZipFile
- */
- public function openFile($filename, array $options = [])
- {
- if (!file_exists($filename)) {
- throw new ZipException("File {$filename} does not exist.");
- }
-
- if (!($handle = @fopen($filename, 'rb'))) {
- throw new ZipException("File {$filename} can't open.");
- }
-
- return $this->openFromStream($handle, $options);
- }
-
- /**
- * Open zip archive from raw string data.
- *
- * @param string $data
- * @param array $options
- *
- * @throws ZipException if can't open temp stream
- *
- * @return ZipFile
- */
- public function openFromString($data, array $options = [])
- {
- if ($data === null || $data === '') {
- throw new InvalidArgumentException('Empty string passed');
- }
-
- if (!($handle = fopen('php://temp', 'r+b'))) {
- // @codeCoverageIgnoreStart
- throw new ZipException('A temporary resource cannot be opened for writing.');
- // @codeCoverageIgnoreEnd
- }
- fwrite($handle, $data);
- rewind($handle);
-
- return $this->openFromStream($handle, $options);
- }
-
- /**
- * Open zip archive from stream resource.
- *
- * @param resource $handle
- * @param array $options
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function openFromStream($handle, array $options = [])
- {
- $this->reader = $this->createZipReader($handle, $options);
- $this->zipContainer = $this->createZipContainer($this->reader->read());
-
- return $this;
- }
-
- /**
- * @return string[] returns the list files
- */
- public function getListFiles()
- {
- // strval is needed to cast entry names to string type
- return array_map('strval', array_keys($this->zipContainer->getEntries()));
- }
-
- /**
- * @return int returns the number of entries in this ZIP file
- */
- public function count()
- {
- return $this->zipContainer->count();
- }
-
- /**
- * Returns the file comment.
- *
- * @return string|null the file comment
- */
- public function getArchiveComment()
- {
- return $this->zipContainer->getArchiveComment();
- }
-
- /**
- * Set archive comment.
- *
- * @param string|null $comment
- *
- * @return ZipFile
- */
- public function setArchiveComment($comment = null)
- {
- $this->zipContainer->setArchiveComment($comment);
-
- return $this;
- }
-
- /**
- * Checks if there is an entry in the archive.
- *
- * @param string $entryName
- *
- * @return bool
- */
- public function hasEntry($entryName)
- {
- return $this->zipContainer->hasEntry($entryName);
- }
-
- /**
- * Returns ZipEntry object.
- *
- * @param string $entryName
- *
- * @throws ZipEntryNotFoundException
- *
- * @return ZipEntry
- */
- public function getEntry($entryName)
- {
- return $this->zipContainer->getEntry($entryName);
- }
-
- /**
- * Checks that the entry in the archive is a directory.
- * Returns true if and only if this ZIP entry represents a directory entry
- * (i.e. end with '/').
- *
- * @param string $entryName
- *
- * @throws ZipEntryNotFoundException
- *
- * @return bool
- */
- public function isDirectory($entryName)
- {
- return $this->getEntry($entryName)->isDirectory();
- }
-
- /**
- * Returns entry comment.
- *
- * @param string $entryName
- *
- * @throws ZipEntryNotFoundException
- * @throws ZipException
- *
- * @return string
- */
- public function getEntryComment($entryName)
- {
- return $this->getEntry($entryName)->getComment();
- }
-
- /**
- * Set entry comment.
- *
- * @param string $entryName
- * @param string|null $comment
- *
- * @throws ZipException
- * @throws ZipEntryNotFoundException
- *
- * @return ZipFile
- */
- public function setEntryComment($entryName, $comment = null)
- {
- $this->getEntry($entryName)->setComment($comment);
-
- return $this;
- }
-
- /**
- * Returns the entry contents.
- *
- * @param string $entryName
- *
- * @throws ZipException
- * @throws ZipEntryNotFoundException
- *
- * @return string
- */
- public function getEntryContents($entryName)
- {
- $zipData = $this->zipContainer->getEntry($entryName)->getData();
-
- if ($zipData === null) {
- throw new ZipException(sprintf('No data for zip entry %s', $entryName));
- }
-
- return $zipData->getDataAsString();
- }
-
- /**
- * @param string $entryName
- *
- * @throws ZipException
- * @throws ZipEntryNotFoundException
- *
- * @return resource
- */
- public function getEntryStream($entryName)
- {
- $resource = ZipEntryStreamWrapper::wrap($this->zipContainer->getEntry($entryName));
- rewind($resource);
-
- return $resource;
- }
-
- /**
- * Get info by entry.
- *
- * @param string|ZipEntry $entryName
- *
- * @throws ZipEntryNotFoundException
- * @throws ZipException
- *
- * @return ZipInfo
- */
- public function getEntryInfo($entryName)
- {
- return new ZipInfo($this->zipContainer->getEntry($entryName));
- }
-
- /**
- * Get info by all entries.
- *
- * @return ZipInfo[]
- */
- public function getAllInfo()
- {
- $infoMap = [];
-
- foreach ($this->zipContainer->getEntries() as $name => $entry) {
- $infoMap[$name] = new ZipInfo($entry);
- }
-
- return $infoMap;
- }
-
- /**
- * @return ZipEntryMatcher
- */
- public function matcher()
- {
- return $this->zipContainer->matcher();
- }
-
- /**
- * Returns an array of zip records (ex. for modify time).
- *
- * @return ZipEntry[] array of raw zip entries
- */
- public function getEntries()
- {
- return $this->zipContainer->getEntries();
- }
-
- /**
- * Extract the archive contents (unzip).
- *
- * Extract the complete archive or the given files to the specified destination.
- *
- * @param string $destDir location where to extract the files
- * @param array|string|null $entries entries to extract
- * @param array $options extract options
- * @param array $extractedEntries if the extractedEntries argument
- * is present, then the specified
- * array will be filled with
- * information about the
- * extracted entries
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function extractTo($destDir, $entries = null, array $options = [], &$extractedEntries = [])
- {
- if (!file_exists($destDir)) {
- throw new ZipException(sprintf('Destination %s not found', $destDir));
- }
-
- if (!is_dir($destDir)) {
- throw new ZipException('Destination is not directory');
- }
-
- if (!is_writable($destDir)) {
- throw new ZipException('Destination is not writable directory');
- }
-
- if ($extractedEntries === null) {
- $extractedEntries = [];
- }
-
- $defaultOptions = [
- ZipOptions::EXTRACT_SYMLINKS => false,
- ];
- /** @noinspection AdditionOperationOnArraysInspection */
- $options += $defaultOptions;
-
- $zipEntries = $this->zipContainer->getEntries();
-
- if (!empty($entries)) {
- if (\is_string($entries)) {
- $entries = (array) $entries;
- }
-
- if (\is_array($entries)) {
- $entries = array_unique($entries);
- $zipEntries = array_intersect_key($zipEntries, array_flip($entries));
- }
- }
-
- if (empty($zipEntries)) {
- return $this;
- }
-
- /** @var int[] $lastModDirs */
- $lastModDirs = [];
-
- krsort($zipEntries, \SORT_NATURAL);
-
- $symlinks = [];
- $destDir = rtrim($destDir, '/\\');
-
- foreach ($zipEntries as $entryName => $entry) {
- $unixMode = $entry->getUnixMode();
- $entryName = FilesUtil::normalizeZipPath($entryName);
- $file = $destDir . \DIRECTORY_SEPARATOR . $entryName;
-
- $extractedEntries[$file] = $entry;
- $modifyTimestamp = $entry->getMTime()->getTimestamp();
- $atime = $entry->getATime();
- $accessTimestamp = $atime === null ? null : $atime->getTimestamp();
-
- $dir = $entry->isDirectory() ? $file : \dirname($file);
-
- if (!is_dir($dir)) {
- $dirMode = $entry->isDirectory() ? $unixMode : 0755;
-
- if ($dirMode === 0) {
- $dirMode = 0755;
- }
-
- if (!mkdir($dir, $dirMode, true) && !is_dir($dir)) {
- // @codeCoverageIgnoreStart
- throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
- // @codeCoverageIgnoreEnd
- }
- chmod($dir, $dirMode);
- }
-
- $parts = explode('/', rtrim($entryName, '/'));
- $path = $destDir . \DIRECTORY_SEPARATOR;
-
- foreach ($parts as $part) {
- if (!isset($lastModDirs[$path]) || $lastModDirs[$path] > $modifyTimestamp) {
- $lastModDirs[$path] = $modifyTimestamp;
- }
-
- $path .= $part . \DIRECTORY_SEPARATOR;
- }
-
- if ($entry->isDirectory()) {
- $lastModDirs[$dir] = $modifyTimestamp;
-
- continue;
- }
-
- $zipData = $entry->getData();
-
- if ($zipData === null) {
- continue;
- }
-
- if ($entry->isUnixSymlink()) {
- $symlinks[$file] = $zipData->getDataAsString();
-
- continue;
- }
-
- /** @noinspection PhpUsageOfSilenceOperatorInspection */
- if (!($handle = @fopen($file, 'w+b'))) {
- // @codeCoverageIgnoreStart
- throw new ZipException(
- sprintf(
- 'Cannot extract zip entry %s. File %s cannot open for write.',
- $entry->getName(),
- $file
- )
- );
- // @codeCoverageIgnoreEnd
- }
-
- try {
- $zipData->copyDataToStream($handle);
- } catch (ZipException $e) {
- unlink($file);
-
- throw $e;
- }
- fclose($handle);
-
- if ($unixMode === 0) {
- $unixMode = 0644;
- }
- chmod($file, $unixMode);
-
- if ($accessTimestamp !== null) {
- /** @noinspection PotentialMalwareInspection */
- touch($file, $modifyTimestamp, $accessTimestamp);
- } else {
- touch($file, $modifyTimestamp);
- }
- }
-
- $allowSymlink = (bool) $options[ZipOptions::EXTRACT_SYMLINKS];
-
- foreach ($symlinks as $linkPath => $target) {
- if (!FilesUtil::symlink($target, $linkPath, $allowSymlink)) {
- unset($extractedEntries[$linkPath]);
- }
- }
-
- krsort($lastModDirs, \SORT_NATURAL);
-
- foreach ($lastModDirs as $dir => $lastMod) {
- touch($dir, $lastMod);
- }
-
- ksort($extractedEntries);
-
- return $this;
- }
-
- /**
- * Add entry from the string.
- *
- * @param string $entryName zip entry name
- * @param string $contents string contents
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}.
- * If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function addFromString($entryName, $contents, $compressionMethod = null)
- {
- $entryName = $this->normalizeEntryName($entryName);
-
- if ($contents === null) {
- throw new InvalidArgumentException('Contents is null');
- }
-
- $contents = (string) $contents;
- $length = \strlen($contents);
-
- if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) {
- if ($length < 512) {
- $compressionMethod = ZipCompressionMethod::STORED;
- } else {
- $mimeType = FilesUtil::getMimeTypeFromString($contents);
- $compressionMethod = FilesUtil::isBadCompressionMimeType($mimeType) ?
- ZipCompressionMethod::STORED :
- ZipCompressionMethod::DEFLATED;
- }
- }
-
- $zipEntry = new ZipEntry($entryName);
- $zipEntry->setData(new ZipNewData($zipEntry, $contents));
- $zipEntry->setUncompressedSize($length);
- $zipEntry->setCompressionMethod($compressionMethod);
- $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setUnixMode(0100644);
- $zipEntry->setTime(time());
-
- $this->addZipEntry($zipEntry);
-
- return $this;
- }
-
- /**
- * @param string $entryName
- *
- * @return string
- */
- protected function normalizeEntryName($entryName)
- {
- if ($entryName === null) {
- throw new InvalidArgumentException('Entry name is null');
- }
-
- $entryName = ltrim((string) $entryName, '\\/');
-
- if (\DIRECTORY_SEPARATOR === '\\') {
- $entryName = str_replace('\\', '/', $entryName);
- }
-
- if ($entryName === '') {
- throw new InvalidArgumentException('Empty entry name');
- }
-
- return $entryName;
- }
-
- /**
- * @param Finder $finder
- * @param array $options
- *
- * @throws ZipException
- *
- * @return ZipEntry[]
- */
- public function addFromFinder(Finder $finder, array $options = [])
- {
- $defaultOptions = [
- ZipOptions::STORE_ONLY_FILES => false,
- ZipOptions::COMPRESSION_METHOD => null,
- ZipOptions::MODIFIED_TIME => null,
- ];
- /** @noinspection AdditionOperationOnArraysInspection */
- $options += $defaultOptions;
-
- if ($options[ZipOptions::STORE_ONLY_FILES]) {
- $finder->files();
- }
-
- $entries = [];
-
- foreach ($finder as $fileInfo) {
- if ($fileInfo->isReadable()) {
- $entry = $this->addSplFile($fileInfo, null, $options);
- $entries[$entry->getName()] = $entry;
- }
- }
-
- return $entries;
- }
-
- /**
- * @param \SplFileInfo $file
- * @param string|null $entryName
- * @param array $options
- *
- * @throws ZipException
- *
- * @return ZipEntry
- */
- public function addSplFile(\SplFileInfo $file, $entryName = null, array $options = [])
- {
- if ($file instanceof \DirectoryIterator) {
- throw new InvalidArgumentException('File should not be \DirectoryIterator.');
- }
- $defaultOptions = [
- ZipOptions::COMPRESSION_METHOD => null,
- ZipOptions::MODIFIED_TIME => null,
- ];
- /** @noinspection AdditionOperationOnArraysInspection */
- $options += $defaultOptions;
-
- if (!$file->isReadable()) {
- throw new InvalidArgumentException(sprintf('File %s is not readable', $file->getPathname()));
- }
-
- if ($entryName === null) {
- if ($file instanceof SymfonySplFileInfo) {
- $entryName = $file->getRelativePathname();
- } else {
- $entryName = $file->getBasename();
- }
- }
-
- $entryName = $this->normalizeEntryName($entryName);
- $entryName = $file->isDir() ? rtrim($entryName, '/\\') . '/' : $entryName;
-
- $zipEntry = new ZipEntry($entryName);
- $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX);
-
- $zipData = null;
- $filePerms = $file->getPerms();
-
- if ($file->isLink()) {
- $linkTarget = $file->getLinkTarget();
- $lengthLinkTarget = \strlen($linkTarget);
-
- $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED);
- $zipEntry->setUncompressedSize($lengthLinkTarget);
- $zipEntry->setCompressedSize($lengthLinkTarget);
- $zipEntry->setCrc(crc32($linkTarget));
- $filePerms |= UnixStat::UNX_IFLNK;
-
- $zipData = new ZipNewData($zipEntry, $linkTarget);
- } elseif ($file->isFile()) {
- if (isset($options[ZipOptions::COMPRESSION_METHOD])) {
- $compressionMethod = $options[ZipOptions::COMPRESSION_METHOD];
- } elseif ($file->getSize() < 512) {
- $compressionMethod = ZipCompressionMethod::STORED;
- } else {
- $compressionMethod = FilesUtil::isBadCompressionFile($file->getPathname()) ?
- ZipCompressionMethod::STORED :
- ZipCompressionMethod::DEFLATED;
- }
-
- $zipEntry->setCompressionMethod($compressionMethod);
-
- $zipData = new ZipFileData($zipEntry, $file);
- } elseif ($file->isDir()) {
- $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED);
- $zipEntry->setUncompressedSize(0);
- $zipEntry->setCompressedSize(0);
- $zipEntry->setCrc(0);
- }
-
- $zipEntry->setUnixMode($filePerms);
-
- $timestamp = null;
-
- if (isset($options[ZipOptions::MODIFIED_TIME])) {
- $mtime = $options[ZipOptions::MODIFIED_TIME];
-
- if ($mtime instanceof \DateTimeInterface) {
- $timestamp = $mtime->getTimestamp();
- } elseif (is_numeric($mtime)) {
- $timestamp = (int) $mtime;
- } elseif (\is_string($mtime)) {
- $timestamp = strtotime($mtime);
-
- if ($timestamp === false) {
- $timestamp = null;
- }
- }
- }
-
- if ($timestamp === null) {
- $timestamp = $file->getMTime();
- }
-
- $zipEntry->setTime($timestamp);
- $zipEntry->setData($zipData);
-
- $this->addZipEntry($zipEntry);
-
- return $zipEntry;
- }
-
- /**
- * @param ZipEntry $zipEntry
- */
- protected function addZipEntry(ZipEntry $zipEntry)
- {
- $this->zipContainer->addEntry($zipEntry);
- }
-
- /**
- * Add entry from the file.
- *
- * @param string $filename destination file
- * @param string|null $entryName zip Entry name
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}.
- * If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function addFile($filename, $entryName = null, $compressionMethod = null)
- {
- if ($filename === null) {
- throw new InvalidArgumentException('Filename is null');
- }
-
- $this->addSplFile(
- new \SplFileInfo($filename),
- $entryName,
- [
- ZipOptions::COMPRESSION_METHOD => $compressionMethod,
- ]
- );
-
- return $this;
- }
-
- /**
- * Add entry from the stream.
- *
- * @param resource $stream stream resource
- * @param string $entryName zip Entry name
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}.
- * If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function addFromStream($stream, $entryName, $compressionMethod = null)
- {
- if (!\is_resource($stream)) {
- throw new InvalidArgumentException('Stream is not resource');
- }
-
- $entryName = $this->normalizeEntryName($entryName);
- $zipEntry = new ZipEntry($entryName);
- $fstat = fstat($stream);
-
- if ($fstat !== false) {
- $unixMode = $fstat['mode'];
- $length = $fstat['size'];
-
- if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) {
- if ($length < 512) {
- $compressionMethod = ZipCompressionMethod::STORED;
- } else {
- rewind($stream);
- $bufferContents = stream_get_contents($stream, min(1024, $length));
- rewind($stream);
- $mimeType = FilesUtil::getMimeTypeFromString($bufferContents);
- $compressionMethod = FilesUtil::isBadCompressionMimeType($mimeType) ?
- ZipCompressionMethod::STORED :
- ZipCompressionMethod::DEFLATED;
- }
- $zipEntry->setUncompressedSize($length);
- }
- } else {
- $unixMode = 0100644;
-
- if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) {
- $compressionMethod = ZipCompressionMethod::DEFLATED;
- }
- }
-
- $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setUnixMode($unixMode);
- $zipEntry->setCompressionMethod($compressionMethod);
- $zipEntry->setTime(time());
- $zipEntry->setData(new ZipNewData($zipEntry, $stream));
-
- $this->addZipEntry($zipEntry);
-
- return $this;
- }
-
- /**
- * Add an empty directory in the zip archive.
- *
- * @param string $dirName
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function addEmptyDir($dirName)
- {
- $dirName = $this->normalizeEntryName($dirName);
- $dirName = rtrim($dirName, '\\/') . '/';
-
- $zipEntry = new ZipEntry($dirName);
- $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED);
- $zipEntry->setUncompressedSize(0);
- $zipEntry->setCompressedSize(0);
- $zipEntry->setCrc(0);
- $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setUnixMode(040755);
- $zipEntry->setTime(time());
-
- $this->addZipEntry($zipEntry);
-
- return $this;
- }
-
- /**
- * Add directory not recursively to the zip archive.
- *
- * @param string $inputDir Input directory
- * @param string $localPath add files to this directory, or the root
- * @param int|null $compressionMethod Compression method.
- *
- * Use {@see ZipCompressionMethod::STORED}, {@see
- * ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function addDir($inputDir, $localPath = '/', $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));
- }
- $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR;
-
- $directoryIterator = new \DirectoryIterator($inputDir);
-
- return $this->addFilesFromIterator($directoryIterator, $localPath, $compressionMethod);
- }
-
- /**
- * Add recursive directory to the zip archive.
- *
- * @param string $inputDir Input directory
- * @param string $localPath add files to this directory, or the root
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED}, {@see
- * ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- *
- * @see ZipCompressionMethod::STORED
- * @see ZipCompressionMethod::DEFLATED
- * @see ZipCompressionMethod::BZIP2
- */
- public function addDirRecursive($inputDir, $localPath = '/', $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));
- }
- $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR;
-
- $directoryIterator = new \RecursiveDirectoryIterator($inputDir);
-
- return $this->addFilesFromIterator($directoryIterator, $localPath, $compressionMethod);
- }
-
- /**
- * 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 {@see ZipCompressionMethod::STORED}, {@see
- * ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- *
- * @see ZipCompressionMethod::STORED
- * @see ZipCompressionMethod::DEFLATED
- * @see ZipCompressionMethod::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);
-
- $this->doAddFiles($path, $files, $localPath, $compressionMethod);
-
- return $this;
- }
-
- /**
- * Add files from glob pattern.
- *
- * @param string $inputDir Input directory
- * @param string $globPattern glob pattern
- * @param string $localPath add files to this directory, or the root
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
- */
- public function addFilesFromGlob($inputDir, $globPattern, $localPath = '/', $compressionMethod = null)
- {
- return $this->addGlob($inputDir, $globPattern, $localPath, false, $compressionMethod);
- }
-
- /**
- * Add files from glob pattern.
- *
- * @param string $inputDir Input directory
- * @param string $globPattern glob pattern
- * @param string $localPath add files to this directory, or the root
- * @param bool $recursive recursive search
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- *
- * @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;
- }
-
- $this->doAddFiles($inputDir, $filesFound, $localPath, $compressionMethod);
-
- return $this;
- }
-
- /**
- * Add files recursively from glob pattern.
- *
- * @param string $inputDir Input directory
- * @param string $globPattern glob pattern
- * @param string $localPath add files to this directory, or the root
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
- */
- public function addFilesFromGlobRecursive($inputDir, $globPattern, $localPath = '/', $compressionMethod = null)
- {
- return $this->addGlob($inputDir, $globPattern, $localPath, true, $compressionMethod);
- }
-
- /**
- * Add files from regex pattern.
- *
- * @param string $inputDir search files in this directory
- * @param string $regexPattern regex pattern
- * @param string $localPath add files to this directory, or the root
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- *
- * @internal param bool $recursive Recursive search
- */
- public function addFilesFromRegex($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null)
- {
- return $this->addRegex($inputDir, $regexPattern, $localPath, false, $compressionMethod);
- }
-
- /**
- * Add files from regex pattern.
- *
- * @param string $inputDir search files in this directory
- * @param string $regexPattern regex pattern
- * @param string $localPath add files to this directory, or the root
- * @param bool $recursive recursive search
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}.
- * If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- private function addRegex(
- $inputDir,
- $regexPattern,
- $localPath = '/',
- $recursive = true,
- $compressionMethod = null
- ) {
- $regexPattern = (string) $regexPattern;
-
- if (empty($regexPattern)) {
- throw new InvalidArgumentException('The regex pattern is not specified');
- }
- $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));
- }
- $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR;
-
- $files = FilesUtil::regexFileSearch($inputDir, $regexPattern, $recursive);
-
- if (empty($files)) {
- return $this;
- }
-
- $this->doAddFiles($inputDir, $files, $localPath, $compressionMethod);
-
- return $this;
- }
-
- /**
- * @param string $fileSystemDir
- * @param array $files
- * @param string $zipPath
- * @param int|null $compressionMethod
- *
- * @throws ZipException
- */
- private function doAddFiles($fileSystemDir, array $files, $zipPath, $compressionMethod = null)
- {
- $fileSystemDir = rtrim($fileSystemDir, '/\\') . \DIRECTORY_SEPARATOR;
-
- if (!empty($zipPath) && \is_string($zipPath)) {
- $zipPath = trim($zipPath, '\\/') . '/';
- } else {
- $zipPath = '/';
- }
-
- /**
- * @var string $file
- */
- foreach ($files as $file) {
- $filename = str_replace($fileSystemDir, $zipPath, $file);
- $filename = ltrim($filename, '\\/');
-
- if (is_dir($file) && FilesUtil::isEmptyDir($file)) {
- $this->addEmptyDir($filename);
- } elseif (is_file($file)) {
- $this->addFile($file, $filename, $compressionMethod);
- }
- }
- }
-
- /**
- * Add files recursively from regex pattern.
- *
- * @param string $inputDir search files in this directory
- * @param string $regexPattern regex pattern
- * @param string $localPath add files to this directory, or the root
- * @param int|null $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED},
- * {@see ZipCompressionMethod::DEFLATED} or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- *
- * @internal param bool $recursive Recursive search
- */
- public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null)
- {
- return $this->addRegex($inputDir, $regexPattern, $localPath, true, $compressionMethod);
- }
-
- /**
- * Add array data to archive.
- * Keys is local names.
- * Values is contents.
- *
- * @param array $mapData associative array for added to zip
- */
- public function addAll(array $mapData)
- {
- foreach ($mapData as $localName => $content) {
- $this[$localName] = $content;
- }
- }
-
- /**
- * Rename the entry.
- *
- * @param string $oldName old entry name
- * @param string $newName new entry name
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function rename($oldName, $newName)
- {
- if ($oldName === null || $newName === null) {
- throw new InvalidArgumentException('name is null');
- }
- $oldName = ltrim((string) $oldName, '\\/');
- $newName = ltrim((string) $newName, '\\/');
-
- if ($oldName !== $newName) {
- $this->zipContainer->renameEntry($oldName, $newName);
- }
-
- return $this;
- }
-
- /**
- * Delete entry by name.
- *
- * @param string $entryName zip Entry name
- *
- * @throws ZipEntryNotFoundException if entry not found
- *
- * @return ZipFile
- */
- public function deleteFromName($entryName)
- {
- $entryName = ltrim((string) $entryName, '\\/');
-
- if (!$this->zipContainer->deleteEntry($entryName)) {
- throw new ZipEntryNotFoundException($entryName);
- }
-
- return $this;
- }
-
- /**
- * Delete entries by glob pattern.
- *
- * @param string $globPattern Glob pattern
- *
- * @return ZipFile
- * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
- */
- public function deleteFromGlob($globPattern)
- {
- if ($globPattern === null || !\is_string($globPattern) || empty($globPattern)) {
- throw new InvalidArgumentException('The glob pattern is not specified');
- }
- $globPattern = '~' . FilesUtil::convertGlobToRegEx($globPattern) . '~si';
- $this->deleteFromRegex($globPattern);
-
- return $this;
- }
-
- /**
- * Delete entries by regex pattern.
- *
- * @param string $regexPattern Regex pattern
- *
- * @return ZipFile
- */
- public function deleteFromRegex($regexPattern)
- {
- if ($regexPattern === null || !\is_string($regexPattern) || empty($regexPattern)) {
- throw new InvalidArgumentException('The regex pattern is not specified');
- }
- $this->matcher()->match($regexPattern)->delete();
-
- return $this;
- }
-
- /**
- * Delete all entries.
- *
- * @return ZipFile
- */
- public function deleteAll()
- {
- $this->zipContainer->deleteAll();
-
- return $this;
- }
-
- /**
- * Set compression level for new entries.
- *
- * @param int $compressionLevel
- *
- * @return ZipFile
- *
- * @see ZipCompressionLevel::NORMAL
- * @see ZipCompressionLevel::SUPER_FAST
- * @see ZipCompressionLevel::FAST
- * @see ZipCompressionLevel::MAXIMUM
- */
- public function setCompressionLevel($compressionLevel = ZipCompressionLevel::NORMAL)
- {
- $compressionLevel = (int) $compressionLevel;
-
- foreach ($this->zipContainer->getEntries() as $entry) {
- $entry->setCompressionLevel($compressionLevel);
- }
-
- return $this;
- }
-
- /**
- * @param string $entryName
- * @param int $compressionLevel
- *
- * @throws ZipException
- *
- * @return ZipFile
- *
- * @see ZipCompressionLevel::NORMAL
- * @see ZipCompressionLevel::SUPER_FAST
- * @see ZipCompressionLevel::FAST
- * @see ZipCompressionLevel::MAXIMUM
- */
- public function setCompressionLevelEntry($entryName, $compressionLevel)
- {
- $compressionLevel = (int) $compressionLevel;
- $this->getEntry($entryName)->setCompressionLevel($compressionLevel);
-
- return $this;
- }
-
- /**
- * @param string $entryName
- * @param int $compressionMethod Compression method.
- * Use {@see ZipCompressionMethod::STORED}, {@see ZipCompressionMethod::DEFLATED}
- * or
- * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
- *
- * @throws ZipException
- *
- * @return ZipFile
- *
- * @see ZipCompressionMethod::STORED
- * @see ZipCompressionMethod::DEFLATED
- * @see ZipCompressionMethod::BZIP2
- */
- public function setCompressionMethodEntry($entryName, $compressionMethod)
- {
- $this->zipContainer
- ->getEntry($entryName)
- ->setCompressionMethod($compressionMethod)
- ;
-
- return $this;
- }
-
- /**
- * zipalign is optimization to Android application (APK) files.
- *
- * @param int|null $align
- *
- * @return ZipFile
- *
- * @see https://developer.android.com/studio/command-line/zipalign.html
- */
- public function setZipAlign($align = null)
- {
- $this->zipContainer->setZipAlign($align);
-
- return $this;
- }
-
- /**
- * Set password to all input encrypted entries.
- *
- * @param string $password Password
- *
- * @return ZipFile
- */
- public function setReadPassword($password)
- {
- $this->zipContainer->setReadPassword($password);
-
- return $this;
- }
-
- /**
- * Set password to concrete input entry.
- *
- * @param string $entryName
- * @param string $password Password
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function setReadPasswordEntry($entryName, $password)
- {
- $this->zipContainer->setReadPasswordEntry($entryName, $password);
-
- return $this;
- }
-
- /**
- * Sets a new password for all files in the archive.
- *
- * @param string $password Password
- * @param int|null $encryptionMethod Encryption method
- *
- * @return ZipFile
- */
- public function setPassword($password, $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256)
- {
- $this->zipContainer->setWritePassword($password);
-
- if ($encryptionMethod !== null) {
- $this->zipContainer->setEncryptionMethod($encryptionMethod);
- }
-
- return $this;
- }
-
- /**
- * Sets a new password of an entry defined by its name.
- *
- * @param string $entryName
- * @param string $password
- * @param int|null $encryptionMethod
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function setPasswordEntry($entryName, $password, $encryptionMethod = null)
- {
- $this->getEntry($entryName)->setPassword($password, $encryptionMethod);
-
- return $this;
- }
-
- /**
- * Disable encryption for all entries that are already in the archive.
- *
- * @return ZipFile
- */
- public function disableEncryption()
- {
- $this->zipContainer->removePassword();
-
- return $this;
- }
-
- /**
- * Disable encryption of an entry defined by its name.
- *
- * @param string $entryName
- *
- * @return ZipFile
- */
- public function disableEncryptionEntry($entryName)
- {
- $this->zipContainer->removePasswordEntry($entryName);
-
- return $this;
- }
-
- /**
- * Undo all changes done in the archive.
- *
- * @return ZipFile
- */
- public function unchangeAll()
- {
- $this->zipContainer->unchangeAll();
-
- return $this;
- }
-
- /**
- * Undo change archive comment.
- *
- * @return ZipFile
- */
- public function unchangeArchiveComment()
- {
- $this->zipContainer->unchangeArchiveComment();
-
- return $this;
- }
-
- /**
- * Revert all changes done to an entry with the given name.
- *
- * @param string|ZipEntry $entry Entry name or ZipEntry
- *
- * @return ZipFile
- */
- public function unchangeEntry($entry)
- {
- $this->zipContainer->unchangeEntry($entry);
-
- return $this;
- }
-
- /**
- * Save as file.
- *
- * @param string $filename Output filename
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function saveAsFile($filename)
- {
- $filename = (string) $filename;
-
- $tempFilename = $filename . '.temp' . uniqid('', false);
-
- if (!($handle = @fopen($tempFilename, 'w+b'))) {
- throw new InvalidArgumentException(sprintf('Cannot open "%s" for writing.', $tempFilename));
- }
- $this->saveAsStream($handle);
-
- $reopen = false;
-
- if ($this->reader !== null) {
- $meta = $this->reader->getStreamMetaData();
-
- if ($meta['wrapper_type'] === 'plainfile' && isset($meta['uri'])) {
- $readFilePath = realpath($meta['uri']);
- $writeFilePath = realpath($filename);
-
- if ($readFilePath !== false && $writeFilePath !== false && $readFilePath === $writeFilePath) {
- $this->reader->close();
- $reopen = true;
- }
- }
- }
-
- if (!@rename($tempFilename, $filename)) {
- if (is_file($tempFilename)) {
- unlink($tempFilename);
- }
-
- throw new ZipException(sprintf('Cannot move %s to %s', $tempFilename, $filename));
- }
-
- if ($reopen) {
- return $this->openFile($filename);
- }
-
- return $this;
- }
-
- /**
- * Save as stream.
- *
- * @param resource $handle Output stream resource
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function saveAsStream($handle)
- {
- if (!\is_resource($handle)) {
- throw new InvalidArgumentException('handle is not resource');
- }
- ftruncate($handle, 0);
- $this->writeZipToStream($handle);
- fclose($handle);
-
- return $this;
- }
-
- /**
- * Output .ZIP archive as attachment.
- * Die after output.
- *
- * @param string $outputFilename Output filename
- * @param string|null $mimeType Mime-Type
- * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
- *
- * @throws ZipException
- */
- public function outputAsAttachment($outputFilename, $mimeType = null, $attachment = true)
- {
- $outputFilename = (string) $outputFilename;
-
- if ($mimeType === null) {
- $mimeType = $this->getMimeTypeByFilename($outputFilename);
- }
-
- if (!($handle = fopen('php://temp', 'w+b'))) {
- throw new InvalidArgumentException('php://temp cannot open for write.');
- }
- $this->writeZipToStream($handle);
- $this->close();
-
- $size = fstat($handle)['size'];
-
- $headerContentDisposition = 'Content-Disposition: ' . ($attachment ? 'attachment' : 'inline');
-
- if (!empty($outputFilename)) {
- $headerContentDisposition .= '; filename="' . basename($outputFilename) . '"';
- }
-
- header($headerContentDisposition);
- header('Content-Type: ' . $mimeType);
- header('Content-Length: ' . $size);
-
- rewind($handle);
-
- try {
- echo stream_get_contents($handle, -1, 0);
- } finally {
- fclose($handle);
- }
- }
-
- /**
- * @param string $outputFilename
- *
- * @return string
- */
- protected function getMimeTypeByFilename($outputFilename)
- {
- $outputFilename = (string) $outputFilename;
- $ext = strtolower(pathinfo($outputFilename, \PATHINFO_EXTENSION));
-
- if (!empty($ext) && isset(self::$defaultMimeTypes[$ext])) {
- return self::$defaultMimeTypes[$ext];
- }
-
- return self::$defaultMimeTypes['zip'];
- }
-
- /**
- * Output .ZIP archive as PSR-7 Response.
- *
- * @param ResponseInterface $response Instance PSR-7 Response
- * @param string $outputFilename Output filename
- * @param string|null $mimeType Mime-Type
- * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
- *
- * @throws ZipException
- *
- * @return ResponseInterface
- */
- public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null, $attachment = true)
- {
- $outputFilename = (string) $outputFilename;
-
- if ($mimeType === null) {
- $mimeType = $this->getMimeTypeByFilename($outputFilename);
- }
-
- if (!($handle = fopen('php://temp', 'w+b'))) {
- throw new InvalidArgumentException('php://temp cannot open for write.');
- }
- $this->writeZipToStream($handle);
- $this->close();
- rewind($handle);
-
- $contentDispositionValue = ($attachment ? 'attachment' : 'inline');
-
- if (!empty($outputFilename)) {
- $contentDispositionValue .= '; filename="' . basename($outputFilename) . '"';
- }
-
- $stream = new ResponseStream($handle);
- $size = $stream->getSize();
-
- if ($size !== null) {
- /** @noinspection CallableParameterUseCaseInTypeContextInspection */
- $response = $response->withHeader('Content-Length', (string) $size);
- }
-
- return $response
- ->withHeader('Content-Type', $mimeType)
- ->withHeader('Content-Disposition', $contentDispositionValue)
- ->withBody($stream)
- ;
- }
-
- /**
- * @param resource $handle
- *
- * @throws ZipException
- */
- protected function writeZipToStream($handle)
- {
- $this->onBeforeSave();
-
- $this->createZipWriter()->write($handle);
- }
-
- /**
- * Returns the zip archive as a string.
- *
- * @throws ZipException
- *
- * @return string
- */
- public function outputAsString()
- {
- if (!($handle = fopen('php://temp', 'w+b'))) {
- throw new InvalidArgumentException('php://temp cannot open for write.');
- }
- $this->writeZipToStream($handle);
- rewind($handle);
-
- try {
- return stream_get_contents($handle);
- } finally {
- fclose($handle);
- }
- }
-
- /**
- * Event before save or output.
- */
- protected function onBeforeSave()
- {
- }
-
- /**
- * Close zip archive and release input stream.
- */
- public function close()
- {
- if ($this->reader !== null) {
- $this->reader->close();
- $this->reader = null;
- }
- $this->zipContainer = $this->createZipContainer(null);
- gc_collect_cycles();
- }
-
- /**
- * Save and reopen zip archive.
- *
- * @throws ZipException
- *
- * @return ZipFile
- */
- public function rewrite()
- {
- if ($this->reader === null) {
- throw new ZipException('input stream is null');
- }
-
- $meta = $this->reader->getStreamMetaData();
-
- if ($meta['wrapper_type'] !== 'plainfile' || !isset($meta['uri'])) {
- throw new ZipException('Overwrite is only supported for open local files.');
- }
-
- return $this->saveAsFile($meta['uri']);
- }
-
- /**
- * Release all resources.
- */
- public function __destruct()
- {
- $this->close();
- }
-
- /**
- * Offset to set.
- *
- * @see http://php.net/manual/en/arrayaccess.offsetset.php
- *
- * @param string $entryName the offset to assign the value to
- * @param string|\DirectoryIterator|\SplFileInfo|resource $contents the value to set
- *
- * @throws ZipException
- *
- * @see ZipFile::addFromString
- * @see ZipFile::addEmptyDir
- * @see ZipFile::addFile
- * @see ZipFile::addFilesFromIterator
- */
- public function offsetSet($entryName, $contents)
- {
- if ($entryName === null) {
- throw new InvalidArgumentException('Key must not be null, but must contain the name of the zip entry.');
- }
- $entryName = ltrim((string) $entryName, '\\/');
-
- if ($entryName === '') {
- throw new InvalidArgumentException('Key is empty, but must contain the name of the zip entry.');
- }
-
- if ($contents instanceof \DirectoryIterator) {
- $this->addFilesFromIterator($contents, $entryName);
- } elseif ($contents instanceof \SplFileInfo) {
- $this->addSplFile($contents, $entryName);
- } elseif (StringUtil::endsWith($entryName, '/')) {
- $this->addEmptyDir($entryName);
- } elseif (\is_resource($contents)) {
- $this->addFromStream($contents, $entryName);
- } else {
- $this->addFromString($entryName, (string) $contents);
- }
- }
-
- /**
- * Offset to unset.
- *
- * @see http://php.net/manual/en/arrayaccess.offsetunset.php
- *
- * @param string $entryName the offset to unset
- *
- * @throws ZipEntryNotFoundException
- */
- public function offsetUnset($entryName)
- {
- $this->deleteFromName($entryName);
- }
-
- /**
- * Return the current element.
- *
- * @see http://php.net/manual/en/iterator.current.php
- *
- * @throws ZipException
- *
- * @return mixed can return any type
- *
- * @since 5.0.0
- */
- public function current()
- {
- return $this->offsetGet($this->key());
- }
-
- /**
- * Offset to retrieve.
- *
- * @see http://php.net/manual/en/arrayaccess.offsetget.php
- *
- * @param string $entryName the offset to retrieve
- *
- * @throws ZipException
- *
- * @return string|null
- */
- public function offsetGet($entryName)
- {
- return $this->getEntryContents($entryName);
- }
-
- /**
- * Return the key of the current element.
- *
- * @see http://php.net/manual/en/iterator.key.php
- *
- * @return mixed scalar on success, or null on failure
- *
- * @since 5.0.0
- */
- public function key()
- {
- return key($this->zipContainer->getEntries());
- }
-
- /**
- * Move forward to next element.
- *
- * @see http://php.net/manual/en/iterator.next.php
- * @since 5.0.0
- */
- public function next()
- {
- next($this->zipContainer->getEntries());
- }
-
- /**
- * Checks if current position is valid.
- *
- * @see http://php.net/manual/en/iterator.valid.php
- *
- * @return bool The return value will be casted to boolean and then evaluated.
- * Returns true on success or false on failure.
- *
- * @since 5.0.0
- */
- public function valid()
- {
- return $this->offsetExists($this->key());
- }
-
- /**
- * Whether a offset exists.
- *
- * @see http://php.net/manual/en/arrayaccess.offsetexists.php
- *
- * @param string $entryName an offset to check for
- *
- * @return bool true on success or false on failure.
- * The return value will be casted to boolean if non-boolean was returned.
- */
- public function offsetExists($entryName)
- {
- return $this->hasEntry($entryName);
- }
-
- /**
- * Rewind the Iterator to the first element.
- *
- * @see http://php.net/manual/en/iterator.rewind.php
- * @since 5.0.0
- */
- public function rewind()
- {
- reset($this->zipContainer->getEntries());
- }
- }
|