From 50bd7b98389c3db4465cbacb36c968fe75204702 Mon Sep 17 00:00:00 2001
From: MatMasIt <34745996+MatMasIt@users.noreply.github.com>
Date: Mon, 15 Feb 2021 12:47:02 +0100
Subject: [PATCH] Vendor bunch upload of nelexa library
---
vendor/nelexa/zip/LICENSE | 21 +
vendor/nelexa/zip/README.RU.md | 816 +++++++
vendor/nelexa/zip/README.md | 832 +++++++
vendor/nelexa/zip/composer.json | 60 +
vendor/nelexa/zip/src/Constants/DosAttrs.php | 33 +
.../nelexa/zip/src/Constants/DosCodePage.php | 105 +
.../src/Constants/GeneralPurposeBitFlag.php | 71 +
vendor/nelexa/zip/src/Constants/UnixStat.php | 84 +
.../zip/src/Constants/ZipCompressionLevel.php | 54 +
.../src/Constants/ZipCompressionMethod.php | 102 +
.../nelexa/zip/src/Constants/ZipConstants.php | 99 +
.../zip/src/Constants/ZipEncryptionMethod.php | 93 +
.../nelexa/zip/src/Constants/ZipOptions.php | 62 +
.../nelexa/zip/src/Constants/ZipPlatform.php | 53 +
.../nelexa/zip/src/Constants/ZipVersion.php | 81 +
.../zip/src/Exception/Crc32Exception.php | 71 +
.../Exception/InvalidArgumentException.php | 14 +
.../zip/src/Exception/RuntimeException.php | 14 +
.../Exception/ZipAuthenticationException.php | 13 +
.../zip/src/Exception/ZipCryptoException.php | 14 +
.../Exception/ZipEntryNotFoundException.php | 40 +
.../nelexa/zip/src/Exception/ZipException.php | 15 +
.../Exception/ZipUnsupportMethodException.php | 10 +
.../Filter/Cipher/Pkware/PKCryptContext.php | 419 ++++
.../Pkware/PKDecryptionStreamFilter.php | 118 +
.../Pkware/PKEncryptionStreamFilter.php | 128 ++
.../Cipher/WinZipAes/WinZipAesContext.php | 166 ++
.../WinZipAesDecryptionStreamFilter.php | 187 ++
.../WinZipAesEncryptionStreamFilter.php | 158 ++
.../zip/src/IO/Stream/ResponseStream.php | 338 +++
.../src/IO/Stream/ZipEntryStreamWrapper.php | 309 +++
vendor/nelexa/zip/src/IO/ZipReader.php | 898 ++++++++
vendor/nelexa/zip/src/IO/ZipWriter.php | 886 ++++++++
.../nelexa/zip/src/Model/Data/ZipFileData.php | 81 +
.../nelexa/zip/src/Model/Data/ZipNewData.php | 132 ++
.../zip/src/Model/Data/ZipSourceFileData.php | 172 ++
.../zip/src/Model/EndOfCentralDirectory.php | 93 +
.../src/Model/Extra/ExtraFieldsCollection.php | 276 +++
.../Fields/AbstractUnicodeExtraField.php | 133 ++
.../Extra/Fields/ApkAlignmentExtraField.php | 176 ++
.../src/Model/Extra/Fields/AsiExtraField.php | 302 +++
.../Fields/ExtendedTimestampExtraField.php | 446 ++++
.../Extra/Fields/JarMarkerExtraField.php | 118 +
.../Model/Extra/Fields/NewUnixExtraField.php | 237 ++
.../src/Model/Extra/Fields/NtfsExtraField.php | 339 +++
.../Model/Extra/Fields/OldUnixExtraField.php | 327 +++
.../Extra/Fields/UnicodeCommentExtraField.php | 76 +
.../Extra/Fields/UnicodePathExtraField.php | 77 +
.../Extra/Fields/UnrecognizedExtraField.php | 116 +
.../Extra/Fields/WinZipAesExtraField.php | 387 ++++
.../Model/Extra/Fields/Zip64ExtraField.php | 311 +++
.../zip/src/Model/Extra/ZipExtraDriver.php | 107 +
.../zip/src/Model/Extra/ZipExtraField.php | 63 +
.../zip/src/Model/ImmutableZipContainer.php | 72 +
vendor/nelexa/zip/src/Model/ZipContainer.php | 386 ++++
vendor/nelexa/zip/src/Model/ZipData.php | 28 +
vendor/nelexa/zip/src/Model/ZipEntry.php | 1573 +++++++++++++
.../nelexa/zip/src/Model/ZipEntryMatcher.php | 206 ++
vendor/nelexa/zip/src/Model/ZipInfo.php | 266 +++
vendor/nelexa/zip/src/Util/CryptoUtil.php | 77 +
.../nelexa/zip/src/Util/DateTimeConverter.php | 99 +
vendor/nelexa/zip/src/Util/FileAttribUtil.php | 108 +
vendor/nelexa/zip/src/Util/FilesUtil.php | 450 ++++
.../Iterator/IgnoreFilesFilterIterator.php | 66 +
.../IgnoreFilesRecursiveFilterIterator.php | 74 +
vendor/nelexa/zip/src/Util/PackUtil.php | 69 +
vendor/nelexa/zip/src/Util/StringUtil.php | 48 +
vendor/nelexa/zip/src/ZipFile.php | 2004 +++++++++++++++++
vendor/nelexa/zip/src/ZipFileInterface.php | 902 ++++++++
69 files changed, 16761 insertions(+)
create mode 100644 vendor/nelexa/zip/LICENSE
create mode 100644 vendor/nelexa/zip/README.RU.md
create mode 100644 vendor/nelexa/zip/README.md
create mode 100644 vendor/nelexa/zip/composer.json
create mode 100644 vendor/nelexa/zip/src/Constants/DosAttrs.php
create mode 100644 vendor/nelexa/zip/src/Constants/DosCodePage.php
create mode 100644 vendor/nelexa/zip/src/Constants/GeneralPurposeBitFlag.php
create mode 100644 vendor/nelexa/zip/src/Constants/UnixStat.php
create mode 100644 vendor/nelexa/zip/src/Constants/ZipCompressionLevel.php
create mode 100644 vendor/nelexa/zip/src/Constants/ZipCompressionMethod.php
create mode 100644 vendor/nelexa/zip/src/Constants/ZipConstants.php
create mode 100644 vendor/nelexa/zip/src/Constants/ZipEncryptionMethod.php
create mode 100644 vendor/nelexa/zip/src/Constants/ZipOptions.php
create mode 100644 vendor/nelexa/zip/src/Constants/ZipPlatform.php
create mode 100644 vendor/nelexa/zip/src/Constants/ZipVersion.php
create mode 100644 vendor/nelexa/zip/src/Exception/Crc32Exception.php
create mode 100644 vendor/nelexa/zip/src/Exception/InvalidArgumentException.php
create mode 100644 vendor/nelexa/zip/src/Exception/RuntimeException.php
create mode 100644 vendor/nelexa/zip/src/Exception/ZipAuthenticationException.php
create mode 100644 vendor/nelexa/zip/src/Exception/ZipCryptoException.php
create mode 100644 vendor/nelexa/zip/src/Exception/ZipEntryNotFoundException.php
create mode 100644 vendor/nelexa/zip/src/Exception/ZipException.php
create mode 100644 vendor/nelexa/zip/src/Exception/ZipUnsupportMethodException.php
create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKCryptContext.php
create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php
create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php
create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php
create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php
create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php
create mode 100644 vendor/nelexa/zip/src/IO/Stream/ResponseStream.php
create mode 100644 vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php
create mode 100644 vendor/nelexa/zip/src/IO/ZipReader.php
create mode 100644 vendor/nelexa/zip/src/IO/ZipWriter.php
create mode 100644 vendor/nelexa/zip/src/Model/Data/ZipFileData.php
create mode 100644 vendor/nelexa/zip/src/Model/Data/ZipNewData.php
create mode 100644 vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php
create mode 100644 vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php
create mode 100644 vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php
create mode 100644 vendor/nelexa/zip/src/Model/ImmutableZipContainer.php
create mode 100644 vendor/nelexa/zip/src/Model/ZipContainer.php
create mode 100644 vendor/nelexa/zip/src/Model/ZipData.php
create mode 100644 vendor/nelexa/zip/src/Model/ZipEntry.php
create mode 100644 vendor/nelexa/zip/src/Model/ZipEntryMatcher.php
create mode 100644 vendor/nelexa/zip/src/Model/ZipInfo.php
create mode 100644 vendor/nelexa/zip/src/Util/CryptoUtil.php
create mode 100644 vendor/nelexa/zip/src/Util/DateTimeConverter.php
create mode 100644 vendor/nelexa/zip/src/Util/FileAttribUtil.php
create mode 100644 vendor/nelexa/zip/src/Util/FilesUtil.php
create mode 100644 vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php
create mode 100644 vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php
create mode 100644 vendor/nelexa/zip/src/Util/PackUtil.php
create mode 100644 vendor/nelexa/zip/src/Util/StringUtil.php
create mode 100644 vendor/nelexa/zip/src/ZipFile.php
create mode 100644 vendor/nelexa/zip/src/ZipFileInterface.php
diff --git a/vendor/nelexa/zip/LICENSE b/vendor/nelexa/zip/LICENSE
new file mode 100644
index 0000000..b38ec97
--- /dev/null
+++ b/vendor/nelexa/zip/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016-2020 Ne-Lexa
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/nelexa/zip/README.RU.md b/vendor/nelexa/zip/README.RU.md
new file mode 100644
index 0000000..0b90797
--- /dev/null
+++ b/vendor/nelexa/zip/README.RU.md
@@ -0,0 +1,816 @@
+`PhpZip`
+========
+`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/)
+[![License](https://poser.pugx.org/nelexa/zip/license)](https://packagist.org/packages/nelexa/zip)
+
+[English Documentation](README.md)
+
+Содержание
+----------
+- [Функционал](#Features)
+- [Требования](#Requirements)
+- [Установка](#Installation)
+- [Примеры](#Examples)
+- [Глоссарий](#Glossary)
+- [Документация](#Documentation)
+ + [Обзор методов класса `\PhpZip\ZipFile`](#Documentation-Overview)
+ + [Создание/Открытие ZIP-архива](#Documentation-Open-Zip-Archive)
+ + [Чтение записей из архива](#Documentation-Open-Zip-Entries)
+ + [Перебор записей/Итератор](#Documentation-Zip-Iterate)
+ + [Получение информации о записях](#Documentation-Zip-Info)
+ + [Добавление записей в архив](#Documentation-Add-Zip-Entries)
+ + [Удаление записей из архива](#Documentation-Remove-Zip-Entries)
+ + [Работа с записями и с архивом](#Documentation-Entries)
+ + [Работа с паролями](#Documentation-Password)
+ + [zipalign - выравнивание архива для оптимизации Android пакетов (APK)](#Documentation-ZipAlign-Usage)
+ + [Отмена изменений](#Documentation-Unchanged)
+ + [Сохранение файла или вывод в браузер](#Documentation-Save-Or-Output-Entries)
+ + [Закрытие архива](#Documentation-Close-Zip-Archive)
+- [Запуск тестов](#Running-Tests)
+- [История изменений](#Changelog)
+- [Обновление версий](#Upgrade)
+ + [Обновление с версии 2 до версии 3.0](#Upgrade-v2-to-v3)
+
+### Функционал
+- Открытие и разархивирование ZIP-архивов.
+- Создание ZIP-архивов.
+- Модификация ZIP-архивов.
+- Чистый php (не требуется расширение `php-zip` и класс `\ZipArchive`).
+- Поддерживается сохранение архива в файл, вывод архива в браузер или вывод в виде строки, без сохранения в файл.
+- Поддерживаются комментарии архива и комментарии отдельных записей.
+- Получение подробной информации о каждой записи в архиве.
+- Поддерживаются только следующие методы сжатия:
+ + Без сжатия (Stored).
+ + Deflate сжатие.
+ + BZIP2 сжатие при наличии расширения `php-bz2`.
+- Поддержка `ZIP64` (размер файла более 4 GB или количество записей в архиве более 65535).
+- Встроенная поддержка выравнивания архива для оптимизации Android пакетов (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
+- Работа с паролями для PHP 5.5
+ > **Внимание!**
+ >
+ > Для 32-bit систем, в данный момент не поддерживается метод шифрование `Traditional PKWARE Encryption (ZipCrypto)`.
+ > Используйте метод шифрования `WinZIP AES Encryption`, когда это возможно.
+ + Установка пароля для чтения архива глобально или для некоторых записей.
+ + Изменение пароля архива, в том числе и для отдельных записей.
+ + Удаление пароля архива глобально или для отдельных записей.
+ + Установка пароля и/или метода шифрования, как для всех, так и для отдельных записей в архиве.
+ + Установка разных паролей и методов шифрования для разных записей.
+ + Удаление пароля для всех или для некоторых записей.
+ + Поддержка методов шифрования `Traditional PKWARE Encryption (ZipCrypto)` и `WinZIP AES Encryption (128, 192 или 256 bit)`.
+ + Установка метода шифрования для всех или для отдельных записей в архиве.
+
+### Требования
+- `PHP` >= 5.5 (предпочтительно 64-bit).
+- Опционально php-расширение `bzip2` для поддержки BZIP2 компрессии.
+- Опционально php-расширение `openssl` или `mcrypt` для `WinZip Aes Encryption` шифрования.
+
+### Установка
+`composer require nelexa/zip`
+
+Последняя стабильная версия: [![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip)
+
+### Примеры
+```php
+// создание нового архива
+$zipFile = new \PhpZip\ZipFile();
+try{
+ $zipFile
+ ->addFromString('zip/entry/filename', "Is file content") // добавить запись из строки
+ ->addFile('/path/to/file', 'data/tofile') // добавить запись из файла
+ ->addDir(__DIR__, 'to/path/') // добавить файлы из директории
+ ->saveAsFile($outputFilename) // сохранить архив в файл
+ ->close(); // закрыть архив
+
+ // открытие архива, извлечение файлов, удаление файлов, добавление файлов, установка пароля и вывод архива в браузер.
+ $zipFile
+ ->openFile($outputFilename) // открыть архив из файла
+ ->extractTo($outputDirExtract) // извлечь файлы в заданную директорию
+ ->deleteFromRegex('~^\.~') // удалить все скрытые (Unix) файлы
+ ->addFromString('dir/file.txt', 'Test file') // добавить новую запись из строки
+ ->setPassword('password') // установить пароль на все записи
+ ->outputAsAttachment('library.jar'); // вывести в браузер без сохранения в файл
+}
+catch(\PhpZip\Exception\ZipException $e){
+ // обработка исключения
+}
+finally{
+ $zipFile->close();
+}
+```
+Другие примеры можно посмотреть в папке `tests/`.
+
+### Глоссарий
+**Запись в ZIP-архиве (Zip Entry)** - файл или папка в ZIP-архиве. У каждой записи в архиве есть определённые свойства, например: имя файла, метод сжатия, метод шифрования, размер файла до сжатия, размер файла после сжатия, CRC32 и другие.
+
+### Документация
+#### Обзор методов класса `\PhpZip\ZipFile`
+- [ZipFile::__construct](#Documentation-ZipFile-__construct) - инициализацирует ZIP-архив.
+- [ZipFile::addAll](#Documentation-ZipFile-addAll) - добавляет все записи из массива.
+- [ZipFile::addDir](#Documentation-ZipFile-addDir) - добавляет файлы из директории по указанному пути без вложенных директорий.
+- [ZipFile::addDirRecursive](#Documentation-ZipFile-addDirRecursive) - добавляет файлы из директории по указанному пути c вложенными директориями.
+- [ZipFile::addEmptyDir](#Documentation-ZipFile-addEmptyDir) - добавляет в ZIP-архив новую директорию.
+- [ZipFile::addFile](#Documentation-ZipFile-addFile) - добавляет в ZIP-архив файл по указанному пути.
+- [ZipFile::addSplFile](#Documentation-ZipFile-addSplFile) - добавляет объект `\SplFileInfo` в zip-архив.
+- [ZipFile::addFromFinder](#Documentation-ZipFile-addFromFinder) - добавляет файлы из `Symfony\Component\Finder\Finder` в zip архив.
+- [ZipFile::addFilesFromIterator](#Documentation-ZipFile-addFilesFromIterator) - добавляет файлы из итератора директорий.
+- [ZipFile::addFilesFromGlob](#Documentation-ZipFile-addFilesFromGlob) - добавляет файлы из директории в соответствии с glob шаблоном без вложенных директорий.
+- [ZipFile::addFilesFromGlobRecursive](#Documentation-ZipFile-addFilesFromGlobRecursive) - добавляет файлы из директории в соответствии с glob шаблоном c вложенными директориями.
+- [ZipFile::addFilesFromRegex](#Documentation-ZipFile-addFilesFromRegex) - добавляет файлы из директории в соответствии с регулярным выражением без вложенных директорий.
+- [ZipFile::addFilesFromRegexRecursive](#Documentation-ZipFile-addFilesFromRegexRecursive) - добавляет файлы из директории в соответствии с регулярным выражением c вложенными директориями.
+- [ZipFile::addFromStream](#Documentation-ZipFile-addFromStream) - добавляет в ZIP-архив запись из потока.
+- [ZipFile::addFromString](#Documentation-ZipFile-addFromString) - добавляет файл в ZIP-архив, используя его содержимое в виде строки.
+- [ZipFile::close](#Documentation-ZipFile-close) - закрывает ZIP-архив.
+- [ZipFile::count](#Documentation-ZipFile-count) - возвращает количество записей в архиве.
+- [ZipFile::deleteFromName](#Documentation-ZipFile-deleteFromName) - удаляет запись по имени.
+- [ZipFile::deleteFromGlob](#Documentation-ZipFile-deleteFromGlob) - удаляет записи в соответствии с glob шаблоном.
+- [ZipFile::deleteFromRegex](#Documentation-ZipFile-deleteFromRegex) - удаляет записи в соответствии с регулярным выражением.
+- [ZipFile::deleteAll](#Documentation-ZipFile-deleteAll) - удаляет все записи в ZIP-архиве.
+- [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption) - отключает шифрования всех записей, находящихся в архиве.
+- [ZipFile::disableEncryptionEntry](#Documentation-ZipFile-disableEncryptionEntry) - отключает шифрование записи по её имени.
+- [ZipFile::extractTo](#Documentation-ZipFile-extractTo) - извлекает содержимое архива в заданную директорию.
+- [ZipFile::getAllInfo](#Documentation-ZipFile-getAllInfo) - возвращает подробную информацию обо всех записях в архиве.
+- [ZipFile::getArchiveComment](#Documentation-ZipFile-getArchiveComment) - возвращает комментарий ZIP-архива.
+- [ZipFile::getEntryComment](#Documentation-ZipFile-getEntryComment) - возвращает комментарий к записи, используя её имя.
+- [ZipFile::getEntryContent](#Documentation-ZipFile-getEntryContent) - возвращает содержимое записи.
+- [ZipFile::getEntryInfo](#Documentation-ZipFile-getEntryInfo) - возвращает подробную информацию о записи в архиве.
+- [ZipFile::getListFiles](#Documentation-ZipFile-getListFiles) - возвращает список файлов архива.
+- [ZipFile::hasEntry](#Documentation-ZipFile-hasEntry) - проверяет, присутствует ли запись в архиве.
+- [ZipFile::isDirectory](#Documentation-ZipFile-isDirectory) - проверяет, является ли запись в архиве директорией.
+- [ZipFile::matcher](#Documentation-ZipFile-matcher) - выборка записей в архиве для проведения операций над выбранными записями.
+- [ZipFile::openFile](#Documentation-ZipFile-openFile) - открывает ZIP-архив из файла.
+- [ZipFile::openFromString](#Documentation-ZipFile-openFromString) - открывает ZIP-архив из строки.
+- [ZipFile::openFromStream](#Documentation-ZipFile-openFromStream) - открывает ZIP-архив из потока.
+- [ZipFile::outputAsAttachment](#Documentation-ZipFile-outputAsAttachment) - выводит ZIP-архив в браузер.
+- [ZipFile::outputAsResponse](#Documentation-ZipFile-outputAsResponse) - выводит ZIP-архив, как Response PSR-7.
+- [ZipFile::outputAsString](#Documentation-ZipFile-outputAsString) - выводит ZIP-архив в виде строки.
+- [ZipFile::rename](#Documentation-ZipFile-rename) - переименовывает запись по имени.
+- [ZipFile::rewrite](#Documentation-ZipFile-rewrite) - сохраняет изменения и заново открывает изменившийся архив.
+- [ZipFile::saveAsFile](#Documentation-ZipFile-saveAsFile) - сохраняет архив в файл.
+- [ZipFile::saveAsStream](#Documentation-ZipFile-saveAsStream) - записывает архив в поток.
+- [ZipFile::setArchiveComment](#Documentation-ZipFile-setArchiveComment) - устанавливает комментарий к ZIP-архиву.
+- [ZipFile::setCompressionLevel](#Documentation-ZipFile-setCompressionLevel) - устанавливает уровень сжатия для всех файлов, находящихся в архиве.
+- [ZipFile::setCompressionLevelEntry](#Documentation-ZipFile-setCompressionLevelEntry) - устанавливает уровень сжатия для определённой записи в архиве.
+- [ZipFile::setCompressionMethodEntry](#Documentation-ZipFile-setCompressionMethodEntry) - устанавливает метод сжатия для определённой записи в архиве.
+- [ZipFile::setEntryComment](#Documentation-ZipFile-setEntryComment) - устанавливает комментарий к записи, используя её имя.
+- [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword) - устанавливает пароль на чтение открытого запароленного архива для всех зашифрованных записей.
+- [ZipFile::setReadPasswordEntry](#Documentation-ZipFile-setReadPasswordEntry) - устанавливает пароль на чтение конкретной зашифрованной записи открытого запароленного архива.
+- ~~ZipFile::withNewPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::setPassword](#Documentation-ZipFile-setPassword).
+- [ZipFile::setPassword](#Documentation-ZipFile-setPassword) - устанавливает новый пароль для всех файлов, находящихся в архиве.
+- [ZipFile::setPasswordEntry](#Documentation-ZipFile-setPasswordEntry) - устанавливает новый пароль для конкретного файла.
+- [ZipFile::setZipAlign](#Documentation-ZipFile-setZipAlign) - устанавливает выравнивание архива для оптимизации APK файлов (Android packages).
+- [ZipFile::unchangeAll](#Documentation-ZipFile-unchangeAll) - отменяет все изменения, сделанные в архиве.
+- [ZipFile::unchangeArchiveComment](#Documentation-ZipFile-unchangeArchiveComment) - отменяет изменения в комментарии к архиву.
+- [ZipFile::unchangeEntry](#Documentation-ZipFile-unchangeEntry) - отменяет изменения для конкретной записи архива.
+- ~~ZipFile::withoutPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption).
+- ~~ZipFile::withReadPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword).
+
+#### Создание/Открытие ZIP-архива
+**ZipFile::__construct** - Инициализацирует ZIP-архив.
+```php
+$zipFile = new \PhpZip\ZipFile();
+```
+ **ZipFile::openFile** - открывает ZIP-архив из файла.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->openFile('file.zip');
+```
+ **ZipFile::openFromString** - открывает ZIP-архив из строки.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->openFromString($stringContents);
+```
+ **ZipFile::openFromStream** - открывает ZIP-архив из потока.
+```php
+$stream = fopen('file.zip', 'rb');
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->openFromStream($stream);
+```
+#### Чтение записей из архива
+ **ZipFile::count** - возвращает количество записей в архиве.
+```php
+$zipFile = new \PhpZip\ZipFile();
+
+$count = count($zipFile);
+// или
+$count = $zipFile->count();
+```
+ **ZipFile::getListFiles** - возвращает список файлов архива.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$listFiles = $zipFile->getListFiles();
+
+// Пример содержимого массива:
+// array (
+// 0 => 'info.txt',
+// 1 => 'path/to/file.jpg',
+// 2 => 'another path/',
+// )
+```
+ **ZipFile::getEntryContent** - возвращает содержимое записи.
+```php
+// $entryName = 'path/to/example-entry-name.txt';
+$zipFile = new \PhpZip\ZipFile();
+
+$contents = $zipFile[$entryName];
+// или
+$contents = $zipFile->getEntryContents($entryName);
+```
+ **ZipFile::hasEntry** - проверяет, присутствует ли запись в архиве.
+```php
+// $entryName = 'path/to/example-entry-name.txt';
+$zipFile = new \PhpZip\ZipFile();
+
+$hasEntry = isset($zipFile[$entryName]);
+// или
+$hasEntry = $zipFile->hasEntry($entryName);
+```
+ **ZipFile::isDirectory** - проверяет, является ли запись в архиве директорией.
+```php
+// $entryName = 'path/to/';
+$zipFile = new \PhpZip\ZipFile();
+
+$isDirectory = $zipFile->isDirectory($entryName);
+```
+ **ZipFile::extractTo** - извлекает содержимое архива в заданную директорию.
+Директория должна существовать.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->extractTo($directory);
+```
+Можно извлечь только некоторые записи в заданную директорию.
+Директория должна существовать.
+```php
+$extractOnlyFiles = [
+ 'filename1',
+ 'filename2',
+ 'dir/dir/dir/'
+];
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->extractTo($toDirectory, $extractOnlyFiles);
+```
+#### Перебор записей/Итератор
+`ZipFile` является итератором.
+Можно перебрать все записи, через цикл `foreach`.
+```php
+foreach($zipFile as $entryName => $contents){
+ echo "Файл: $entryName" . PHP_EOL;
+ echo "Содержимое: $contents" . PHP_EOL;
+ echo '-----------------------------' . PHP_EOL;
+}
+```
+Можно использовать паттерн `Iterator`.
+```php
+$iterator = new \ArrayIterator($zipFile);
+while ($iterator->valid())
+{
+ $entryName = $iterator->key();
+ $contents = $iterator->current();
+
+ echo "Файл: $entryName" . PHP_EOL;
+ echo "Содержимое: $contents" . PHP_EOL;
+ echo '-----------------------------' . PHP_EOL;
+
+ $iterator->next();
+}
+```
+#### Получение информации о записях
+ **ZipFile::getArchiveComment** - возвращает комментарий ZIP-архива.
+```php
+$commentArchive = $zipFile->getArchiveComment();
+```
+ **ZipFile::getEntryComment** - возвращает комментарий к записи, используя её имя.
+```php
+$commentEntry = $zipFile->getEntryComment($entryName);
+```
+ **ZipFile::getEntryInfo** - возвращает подробную информацию о записи в архиве.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipInfo = $zipFile->getEntryInfo('file.txt');
+```
+ **ZipFile::getAllInfo** - возвращает подробную информацию обо всех записях в архиве.
+```php
+$zipAllInfo = $zipFile->getAllInfo();
+```
+#### Добавление записей в архив
+
+Все методы добавления записей в ZIP-архив позволяют указать метод сжатия содержимого.
+
+Доступны следующие методы сжатия:
+- `\PhpZip\Constants\ZipCompressionMethod::STORED` - без сжатия
+- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate сжатие
+- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2`
+
+ **ZipFile::addFile** - добавляет в ZIP-архив файл по указанному пути из файловой системы.
+```php
+$zipFile = new \PhpZip\ZipFile();
+// $file = '...../file.ext';
+$zipFile->addFile($file);
+
+// можно указать имя записи в архиве (если null, то используется последний компонент из имени файла)
+$zipFile->addFile($file, $entryName);
+
+// можно указать метод сжатия
+$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+
+**ZipFile::addSplFile"** - добавляет объект `\SplFileInfo` в zip-архив.
+```php
+// $file = '...../file.ext';
+// $entryName = 'file2.ext'
+$zipFile = new \PhpZip\ZipFile();
+
+$splFile = new \SplFileInfo('README.md');
+
+$zipFile->addSplFile($splFile);
+$zipFile->addSplFile($splFile, $entryName);
+// or
+$zipFile[$entryName] = new \SplFileInfo($file);
+
+// установить метод сжатия
+$zipFile->addSplFile($splFile, $entryName, $options = [
+ \PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED,
+]);
+```
+
+**ZipFile::addFromFinder"** - добавляет файлы из `Symfony\Component\Finder\Finder` в zip архив.
+https://symfony.com/doc/current/components/finder.html
+```php
+$finder = new \Symfony\Component\Finder\Finder();
+$finder
+ ->files()
+ ->name('*.{jpg,jpeg,gif,png}')
+ ->name('/^[0-9a-f]\./')
+ ->contains('/lorem\s+ipsum$/i')
+ ->in('path');
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addFromFinder($finder, $options = [
+ \PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED,
+ \PhpZip\Constants\ZipOptions::MODIFIED_TIME => new \DateTimeImmutable('-1 day 5 min')
+]);
+```
+ **ZipFile::addFromString** - добавляет файл в ZIP-архив, используя его содержимое в виде строки.
+```php
+$zipFile = new \PhpZip\ZipFile();
+
+$zipFile[$entryName] = $contents;
+// или
+$zipFile->addFromString($entryName, $contents);
+
+// можно указать метод сжатия
+$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+ **ZipFile::addFromStream** - добавляет в ZIP-архив запись из потока.
+```php
+// $stream = fopen(..., 'rb');
+
+$zipFile->addFromStream($stream, $entryName);
+
+// можно указать метод сжатия
+$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+ **ZipFile::addEmptyDir** - добавляет в ZIP-архив новую (пустую) директорию.
+```php
+// $path = "path/to/";
+
+$zipFile->addEmptyDir($path);
+// или
+$zipFile[$path] = null;
+```
+ **ZipFile::addAll** - добавляет все записи из массива.
+```php
+$entries = [
+ 'file.txt' => 'file contents', // запись из строки данных
+ 'empty dir/' => null, // пустой каталог
+ 'path/to/file.jpg' => fopen('..../filename', 'r'), // запись из потока
+ 'path/to/file.dat' => new \SplFileInfo('..../filename'), // запись из файла
+];
+
+$zipFile->addAll($entries);
+```
+ **ZipFile::addDir** - добавляет файлы из директории по указанному пути без вложенных директорий.
+```php
+$zipFile->addDir($dirName);
+
+// можно указать путь в архиве в который необходимо поместить записи
+$localPath = "to/path/";
+$zipFile->addDir($dirName, $localPath);
+
+// можно указать метод сжатия
+$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+ **ZipFile::addDirRecursive** - добавляет файлы из директории по указанному пути c вложенными директориями.
+```php
+$zipFile->addDirRecursive($dirName);
+
+// можно указать путь в архиве в который необходимо поместить записи
+$localPath = "to/path/";
+$zipFile->addDirRecursive($dirName, $localPath);
+
+// можно указать метод сжатия
+$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+ **ZipFile::addFilesFromIterator** - добавляет файлы из итератора директорий.
+```php
+// $directoryIterator = new \DirectoryIterator($dir); // без вложенных директорий
+// $directoryIterator = new \RecursiveDirectoryIterator($dir); // с вложенными директориями
+
+$zipFile->addFilesFromIterator($directoryIterator);
+
+// можно указать путь в архиве в который необходимо поместить записи
+$localPath = "to/path/";
+$zipFile->addFilesFromIterator($directoryIterator, $localPath);
+// или
+$zipFile[$localPath] = $directoryIterator;
+
+// можно указать метод сжатия
+$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+Пример добавления файлов из директории в архив с игнорированием некоторых файлов при помощи итератора директорий.
+```php
+$ignoreFiles = [
+ "file_ignore.txt",
+ "dir_ignore/sub dir ignore/"
+];
+
+// $directoryIterator = new \DirectoryIterator($dir); // без вложенных директорий
+// $directoryIterator = new \RecursiveDirectoryIterator($dir); // с вложенными директориями
+
+// используйте \PhpZip\Util\Iterator\IgnoreFilesFilterIterator для не рекурсивного поиска
+$ignoreIterator = new \PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator(
+ $directoryIterator,
+ $ignoreFiles
+);
+
+$zipFile->addFilesFromIterator($ignoreIterator);
+```
+ **ZipFile::addFilesFromGlob** - добавляет файлы из директории в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)) без вложенных директорий.
+```php
+$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> добавить все .jpg, .jpeg, .png и .gif файлы
+
+$zipFile->addFilesFromGlob($dir, $globPattern);
+
+// можно указать путь в архиве в который необходимо поместить записи
+$localPath = "to/path/";
+$zipFile->addFilesFromGlob($dir, $globPattern, $localPath);
+
+// можно указать метод сжатия
+$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+ **ZipFile::addFilesFromGlobRecursive** - добавляет файлы из директории в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)) c вложенными директориями.
+```php
+$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> добавить все .jpg, .jpeg, .png и .gif файлы
+
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern);
+
+// можно указать путь в архиве в который необходимо поместить записи
+$localPath = "to/path/";
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath);
+
+// можно указать метод сжатия
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+ **ZipFile::addFilesFromRegex** - добавляет файлы из директории в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression) без вложенных директорий.
+```php
+$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярного выражения -> добавить все .jpg, .jpeg, .png и .gif файлы
+
+$zipFile->addFilesFromRegex($dir, $regexPattern);
+
+// можно указать путь в архиве в который необходимо поместить записи
+$localPath = "to/path/";
+$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath);
+
+// можно указать метод сжатия
+$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+ **ZipFile::addFilesFromRegexRecursive** - добавляет файлы из директории в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression) с вложенными директориями.
+```php
+$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярного выражения -> добавить все .jpg, .jpeg, .png и .gif файлы
+
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern);
+
+// можно указать путь в архиве в который необходимо поместить записи
+$localPath = "to/path/";
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath);
+
+// можно указать метод сжатия
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
+```
+#### Удаление записей из архива
+ **ZipFile::deleteFromName** - удаляет запись по имени.
+```php
+$zipFile->deleteFromName($entryName);
+```
+ **ZipFile::deleteFromGlob** - удаляет записи в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)).
+```php
+$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> удалить все .jpg, .jpeg, .png и .gif файлы
+
+$zipFile->deleteFromGlob($globPattern);
+```
+ **ZipFile::deleteFromRegex** - удаляет записи в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression).
+```php
+$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярному выражения -> удалить все .jpg, .jpeg, .png и .gif файлы
+
+$zipFile->deleteFromRegex($regexPattern);
+```
+ **ZipFile::deleteAll** - удаляет все записи в ZIP-архиве.
+```php
+$zipFile->deleteAll();
+```
+#### Работа с записями и с архивом
+ **ZipFile::rename** - переименовывает запись по имени.
+```php
+$zipFile->rename($oldName, $newName);
+```
+ **ZipFile::setCompressionLevel** - устанавливает уровень сжатия для всех файлов, находящихся в архиве.
+
+> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._
+
+По умолчанию используется уровень сжатия 5 (`\PhpZip\Constants\ZipCompressionLevel::NORMAL`) или уровень сжатия, определённый в архиве для Deflate сжатия.
+
+Поддерживаются диапазон значений от 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) до 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`). Чем выше число, тем лучше и дольше сжатие.
+```php
+$zipFile->setCompressionLevel(\PhpZip\Constants\ZipCompressionLevel::MAXIMUM);
+```
+ **ZipFile::setCompressionLevelEntry** - устанавливает уровень сжатия для определённой записи в архиве.
+
+Поддерживаются диапазон значений от 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) до 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`). Чем выше число, тем лучше и дольше сжатие.
+```php
+$zipFile->setCompressionLevelEntry($entryName, \PhpZip\Constants\ZipCompressionLevel::MAXIMUM);
+```
+ **ZipFile::setCompressionMethodEntry** - устанавливает метод сжатия для определённой записи в архиве.
+
+Доступны следующие методы сжатия:
+- `\PhpZip\Constants\ZipCompressionMethod::STORED` - без сжатия
+- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate сжатие
+- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2`
+```php
+$zipFile->setCompressionMethodEntry($entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED);
+```
+ **ZipFile::setArchiveComment** - устанавливает комментарий к ZIP-архиву.
+```php
+$zipFile->setArchiveComment($commentArchive);
+```
+ **ZipFile::setEntryComment** - устанавливает комментарий к записи, используя её имя.
+```php
+$zipFile->setEntryComment($entryName, $comment);
+```
+ **ZipFile::matcher** - выборка записей в архиве для проведения операций над выбранными записями.
+```php
+$matcher = $zipFile->matcher();
+```
+Выбор файлов из архива по одному:
+```php
+$matcher
+ ->add('entry name')
+ ->add('another entry');
+```
+Выбор нескольких файлов в архиве:
+```php
+$matcher->add([
+ 'entry name',
+ 'another entry name',
+ 'path/'
+]);
+```
+Выбор файлов по регулярному выражению:
+```php
+$matcher->match('~\.jpe?g$~i');
+```
+Выбор всех файлов в архиве:
+```php
+$matcher->all();
+```
+count() - получает количество выбранных записей:
+```php
+$count = count($matcher);
+// или
+$count = $matcher->count();
+```
+getMatches() - получает список выбранных записей:
+```php
+$entries = $matcher->getMatches();
+// пример содержимого: ['entry name', 'another entry name'];
+```
+invoke() - выполняет пользовательскую функцию над выбранными записями:
+```php
+// пример
+$matcher->invoke(function($entryName) use($zipFile) {
+ $newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
+ $zipFile->rename($entryName, $newName);
+});
+```
+Функции для работы над выбранными записями:
+```php
+$matcher->delete(); // удалет выбранные записи из ZIP-архива
+$matcher->setPassword($password); // устанавливает новый пароль на выбранные записи
+$matcher->setPassword($password, $encryptionMethod); // устанавливает новый пароль и метод шифрования на выбранные записи
+$matcher->setEncryptionMethod($encryptionMethod); // устанавливает метод шифрования на выбранные записи
+$matcher->disableEncryption(); // отключает шифрование для выбранных записей
+```
+#### Работа с паролями
+
+Реализована поддержка методов шифрования:
+- `\PhpZip\Constants\ZipEncryptionMethod::PKWARE` - Traditional PKWARE encryption
+- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256` - WinZip AES encryption 256 bit (рекомендуемое)
+- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_192` - WinZip AES encryption 192 bit
+- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_128` - WinZip AES encryption 128 bit
+
+ **ZipFile::setReadPassword** - устанавливает пароль на чтение открытого запароленного архива для всех зашифрованных записей.
+
+> _Установка пароля не является обязательной для добавления новых записей или удаления существующих, но если вы захотите извлечь контент или изменить метод/уровень сжатия, метод шифрования или изменить пароль, то в этом случае пароль необходимо указать._
+```php
+$zipFile->setReadPassword($password);
+```
+ **ZipFile::setReadPasswordEntry** - устанавливает пароль на чтение конкретной зашифрованной записи открытого запароленного архива.
+```php
+$zipFile->setReadPasswordEntry($entryName, $password);
+```
+ **ZipFile::setPassword** - устанавливает новый пароль для всех файлов, находящихся в архиве.
+
+> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._
+```php
+$zipFile->setPassword($password);
+```
+Можно установить метод шифрования:
+```php
+$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256;
+$zipFile->setPassword($password, $encryptionMethod);
+```
+ **ZipFile::setPasswordEntry** - устанавливает новый пароль для конкретного файла.
+```php
+$zipFile->setPasswordEntry($entryName, $password);
+```
+Можно установить метод шифрования:
+```php
+$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256;
+$zipFile->setPasswordEntry($entryName, $password, $encryptionMethod);
+```
+ **ZipFile::disableEncryption** - отключает шифрования всех записей, находящихся в архиве.
+
+> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._
+```php
+$zipFile->disableEncryption();
+```
+ **ZipFile::disableEncryptionEntry** - отключает шифрование записи по её имени.
+```php
+$zipFile->disableEncryptionEntry($entryName);
+```
+#### zipalign
+ **ZipFile::setZipAlign** - устанавливает выравнивание архива для оптимизации APK файлов (Android packages).
+
+Метод добавляет паддинги незашифрованным и не сжатым записям, для оптимизации расхода памяти в системе Android. Рекомендуется использовать для `APK` файлов. Файл может незначительно увеличиться.
+
+Этот метод является альтернативой вызова команды `zipalign -f -v 4 filename.zip`.
+
+Подробнее можно ознакомиться по [ссылке](https://developer.android.com/studio/command-line/zipalign.html).
+```php
+// вызовите до сохранения или вывода архива
+$zipFile->setZipAlign(4);
+```
+#### Отмена изменений
+ **ZipFile::unchangeAll** - отменяет все изменения, сделанные в архиве.
+```php
+$zipFile->unchangeAll();
+```
+ **ZipFile::unchangeArchiveComment** - отменяет изменения в комментарии к архиву.
+```php
+$zipFile->unchangeArchiveComment();
+```
+ **ZipFile::unchangeEntry** - отменяет изменения для конкретной записи архива.
+```php
+$zipFile->unchangeEntry($entryName);
+```
+#### Сохранение файла или вывод в браузер
+ **ZipFile::saveAsFile** - сохраняет архив в файл.
+```php
+$zipFile->saveAsFile($filename);
+```
+ **ZipFile::saveAsStream** - записывает архив в поток.
+```php
+// $fp = fopen($filename, 'w+b');
+
+$zipFile->saveAsStream($fp);
+```
+ **ZipFile::outputAsString** - выводит ZIP-архив в виде строки.
+```php
+$rawZipArchiveBytes = $zipFile->outputAsString();
+```
+ **ZipFile::outputAsAttachment** - выводит ZIP-архив в браузер.
+
+При выводе устанавливаются необходимые заголовки, а после вывода завершается работа скрипта.
+```php
+$zipFile->outputAsAttachment($outputFilename);
+```
+Можно установить MIME-тип:
+```php
+$mimeType = 'application/zip'
+$zipFile->outputAsAttachment($outputFilename, $mimeType);
+```
+ **ZipFile::outputAsResponse** - выводит ZIP-архив, как Response [PSR-7](http://www.php-fig.org/psr/psr-7/).
+
+Метод вывода может использоваться в любом PSR-7 совместимом фреймворке.
+```php
+// $response = ....; // instance Psr\Http\Message\ResponseInterface
+$zipFile->outputAsResponse($response, $outputFilename);
+```
+Можно установить MIME-тип:
+```php
+$mimeType = 'application/zip'
+$zipFile->outputAsResponse($response, $outputFilename, $mimeType);
+```
+Пример для Slim Framework:
+```php
+$app = new \Slim\App;
+$app->get('/download', function ($req, $res, $args) {
+ $zipFile = new \PhpZip\ZipFile();
+ $zipFile['file.txt'] = 'content';
+ return $zipFile->outputAsResponse($res, 'file.zip');
+});
+$app->run();
+```
+ **ZipFile::rewrite** - сохраняет изменения и заново открывает изменившийся архив.
+```php
+$zipFile->rewrite();
+```
+#### Закрытие архива
+ **ZipFile::close** - закрывает ZIP-архив.
+```php
+$zipFile->close();
+```
+### Запуск тестов
+Установите зависимости для разработки.
+```bash
+composer install --dev
+```
+Запустите тесты:
+```bash
+vendor/bin/phpunit -v -c phpunit.xml
+```
+### История изменений
+История изменений на [странице релизов](https://github.com/Ne-Lexa/php-zip/releases).
+
+### Обновление версий
+#### Обновление с версии 2 до версии 3.0
+Обновите мажорную версию в файле `composer.json` до `^3.0`.
+```json
+{
+ "require": {
+ "nelexa/zip": "^3.0"
+ }
+}
+```
+Затем установите обновления с помощью `Composer`:
+```bash
+composer update nelexa/zip
+```
+Обновите ваш код для работы с новой версией:
+- Класс `ZipOutputFile` объединён с `ZipFile` и удалён.
+ + Замените `new \PhpZip\ZipOutputFile()` на `new \PhpZip\ZipFile()`
+- Статичиская инициализация методов стала не статической.
+ + Замените `\PhpZip\ZipFile::openFromFile($filename);` на `(new \PhpZip\ZipFile())->openFile($filename);`
+ + Замените `\PhpZip\ZipOutputFile::openFromFile($filename);` на `(new \PhpZip\ZipFile())->openFile($filename);`
+ + Замените `\PhpZip\ZipFile::openFromString($contents);` на `(new \PhpZip\ZipFile())->openFromString($contents);`
+ + Замените `\PhpZip\ZipFile::openFromStream($stream);` на `(new \PhpZip\ZipFile())->openFromStream($stream);`
+ + Замените `\PhpZip\ZipOutputFile::create()` на `new \PhpZip\ZipFile()`
+ + Замените `\PhpZip\ZipOutputFile::openFromZipFile($zipFile)` на `(new \PhpZip\ZipFile())->openFile($filename);`
+- Переименуйте методы:
+ + `addFromFile` в `addFile`
+ + `setLevel` в `setCompressionLevel`
+ + `ZipFile::setPassword` в `ZipFile::withReadPassword`
+ + `ZipOutputFile::setPassword` в `ZipFile::withNewPassword`
+ + `ZipOutputFile::disableEncryptionAllEntries` в `ZipFile::withoutPassword`
+ + `ZipOutputFile::setComment` в `ZipFile::setArchiveComment`
+ + `ZipFile::getComment` в `ZipFile::getArchiveComment`
+- Изменились сигнатуры для методов `addDir`, `addFilesFromGlob`, `addFilesFromRegex`.
+- Удалены методы:
+ + `getLevel`
+ + `setCompressionMethod`
+ + `setEntryPassword`
diff --git a/vendor/nelexa/zip/README.md b/vendor/nelexa/zip/README.md
new file mode 100644
index 0000000..44ea792
--- /dev/null
+++ b/vendor/nelexa/zip/README.md
@@ -0,0 +1,832 @@
+`PhpZip`
+========
+`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/)
+[![License](https://poser.pugx.org/nelexa/zip/license)](https://packagist.org/packages/nelexa/zip)
+
+[Russian Documentation](README.RU.md)
+
+Table of contents
+-----------------
+- [Features](#Features)
+- [Requirements](#Requirements)
+- [Installation](#Installation)
+- [Examples](#Examples)
+- [Glossary](#Glossary)
+- [Documentation](#Documentation)
+ + [Overview of methods of the class `\PhpZip\ZipFile`](#Documentation-Overview)
+ + [Creation/Opening of ZIP-archive](#Documentation-Open-Zip-Archive)
+ + [Reading entries from the archive](#Documentation-Open-Zip-Entries)
+ + [Iterating entries](#Documentation-Zip-Iterate)
+ + [Getting information about entries](#Documentation-Zip-Info)
+ + [Adding entries to the archive](#Documentation-Add-Zip-Entries)
+ + [Deleting entries from the archive](#Documentation-Remove-Zip-Entries)
+ + [Working with entries and archive](#Documentation-Entries)
+ + [Working with passwords](#Documentation-Password)
+ + [zipalign - alignment tool for Android (APK) files](#Documentation-ZipAlign-Usage)
+ + [Undo changes](#Documentation-Unchanged)
+ + [Saving a file or output to a browser](#Documentation-Save-Or-Output-Entries)
+ + [Closing the archive](#Documentation-Close-Zip-Archive)
+- [Running the tests](#Running-Tests)
+- [Changelog](#Changelog)
+- [Upgrade](#Upgrade)
+ + [Upgrade version 2 to version 3.0](#Upgrade-v2-to-v3)
+
+### Features
+- Opening and unzipping zip files.
+- Creating ZIP-archives.
+- Modifying ZIP archives.
+- Pure php (not require extension `php-zip` and class `\ZipArchive`).
+- It supports saving the archive to a file, outputting the archive to the browser, or outputting it as a string without saving it to a file.
+- Archival comments and comments of individual entry are supported.
+- Get information about each entry in the archive.
+- Only the following compression methods are supported:
+ + No compressed (Stored).
+ + Deflate compression.
+ + BZIP2 compression with the extension `php-bz2`.
+- Support for `ZIP64` (file size is more than 4 GB or the number of entries in the archive is more than 65535).
+- Built-in support for aligning the archive to optimize Android packages (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
+- Working with passwords for PHP 5.5
+ > **Attention!**
+ >
+ > For 32-bit systems, the `Traditional PKWARE Encryption (ZipCrypto)` encryption method is not currently supported.
+ > Use the encryption method `WinZIP AES Encryption`, whenever possible.
+ + Set the password to read the archive for all entries or only for some.
+ + Change the password for the archive, including for individual entries.
+ + Delete the archive password for all or individual entries.
+ + Set the password and/or the encryption method, both for all, and for individual entries in the archive.
+ + Set different passwords and encryption methods for different entries.
+ + Delete the password for all or some entries.
+ + Support `Traditional PKWARE Encryption (ZipCrypto)` and `WinZIP AES Encryption` encryption methods.
+ + Set the encryption method for all or individual entries in the archive.
+
+### Requirements
+- `PHP` >= 5.5 (preferably 64-bit).
+- Optional php-extension `bzip2` for BZIP2 compression.
+- Optional php-extension `openssl` or `mcrypt` for `WinZip Aes Encryption` support.
+
+### Installation
+`composer require nelexa/zip`
+
+Latest stable version: [![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip)
+
+### Examples
+```php
+// create new archive
+$zipFile = new \PhpZip\ZipFile();
+try{
+ $zipFile
+ ->addFromString('zip/entry/filename', 'Is file content') // add an entry from the string
+ ->addFile('/path/to/file', 'data/tofile') // add an entry from the file
+ ->addDir(__DIR__, 'to/path/') // add files from the directory
+ ->saveAsFile($outputFilename) // save the archive to a file
+ ->close(); // close archive
+
+ // open archive, extract, add files, set password and output to browser.
+ $zipFile
+ ->openFile($outputFilename) // open archive from file
+ ->extractTo($outputDirExtract) // extract files to the specified directory
+ ->deleteFromRegex('~^\.~') // delete all hidden (Unix) files
+ ->addFromString('dir/file.txt', 'Test file') // add a new entry from the string
+ ->setPassword('password') // set password for all entries
+ ->outputAsAttachment('library.jar'); // output to the browser without saving to a file
+}
+catch(\PhpZip\Exception\ZipException $e){
+ // handle exception
+}
+finally{
+ $zipFile->close();
+}
+```
+Other examples can be found in the `tests/` folder
+
+### Glossary
+**Zip Entry** - file or folder in a ZIP-archive. Each entry in the archive has certain properties, for example: file name, compression method, encryption method, file size before compression, file size after compression, CRC32 and others.
+
+### Documentation:
+#### Overview of methods of the class `\PhpZip\ZipFile`
+- [ZipFile::__construct](#Documentation-ZipFile-__construct) - initializes the ZIP archive.
+- [ZipFile::addAll](#Documentation-ZipFile-addAll) - adds all entries from an array.
+- [ZipFile::addDir](#Documentation-ZipFile-addDir) - adds files to the archive from the directory on the specified path without subdirectories.
+- [ZipFile::addDirRecursive](#Documentation-ZipFile-addDirRecursive) - adds files to the archive from the directory on the specified path with subdirectories.
+- [ZipFile::addEmptyDir](#Documentation-ZipFile-addEmptyDir) - add a new directory.
+- [ZipFile::addFile](#Documentation-ZipFile-addFile) - adds a file to a ZIP archive from the given path.
+- [ZipFile::addSplFile](#Documentation-ZipFile-addSplFile) - adds a `\SplFileInfo` to a ZIP archive.
+- [ZipFile::addFromFinder](#Documentation-ZipFile-addFromFinder) - adds files from the `Symfony\Component\Finder\Finder` to a ZIP archive.
+- [ZipFile::addFilesFromIterator](#Documentation-ZipFile-addFilesFromIterator) - adds files from the iterator of directories.
+- [ZipFile::addFilesFromGlob](#Documentation-ZipFile-addFilesFromGlob) - adds files from a directory by glob pattern without subdirectories.
+- [ZipFile::addFilesFromGlobRecursive](#Documentation-ZipFile-addFilesFromGlobRecursive) - adds files from a directory by glob pattern with subdirectories.
+- [ZipFile::addFilesFromRegex](#Documentation-ZipFile-addFilesFromRegex) - adds files from a directory by PCRE pattern without subdirectories.
+- [ZipFile::addFilesFromRegexRecursive](#Documentation-ZipFile-addFilesFromRegexRecursive) - adds files from a directory by PCRE pattern with subdirectories.
+- [ZipFile::addFromStream](#Documentation-ZipFile-addFromStream) - adds a entry from the stream to the ZIP archive.
+- [ZipFile::addFromString](#Documentation-ZipFile-addFromString) - adds a file to a ZIP archive using its contents.
+- [ZipFile::close](#Documentation-ZipFile-close) - close the archive.
+- [ZipFile::count](#Documentation-ZipFile-count) - returns the number of entries in the archive.
+- [ZipFile::deleteFromName](#Documentation-ZipFile-deleteFromName) - deletes an entry in the archive using its name.
+- [ZipFile::deleteFromGlob](#Documentation-ZipFile-deleteFromGlob) - deletes a entries in the archive using glob pattern.
+- [ZipFile::deleteFromRegex](#Documentation-ZipFile-deleteFromRegex) - deletes a entries in the archive using PCRE pattern.
+- [ZipFile::deleteAll](#Documentation-ZipFile-deleteAll) - deletes all entries in the ZIP archive.
+- [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption) - disable encryption for all entries that are already in the archive.
+- [ZipFile::disableEncryptionEntry](#Documentation-ZipFile-disableEncryptionEntry) - disable encryption of an entry defined by its name.
+- [ZipFile::extractTo](#Documentation-ZipFile-extractTo) - extract the archive contents.
+- [ZipFile::getAllInfo](#Documentation-ZipFile-getAllInfo) - returns detailed information about all entries in the archive.
+- [ZipFile::getArchiveComment](#Documentation-ZipFile-getArchiveComment) - returns the Zip archive comment.
+- [ZipFile::getEntryComment](#Documentation-ZipFile-getEntryComment) - returns the comment of an entry using the entry name.
+- [ZipFile::getEntryContent](#Documentation-ZipFile-getEntryContent) - returns the entry contents using its name.
+- [ZipFile::getEntryInfo](#Documentation-ZipFile-getEntryInfo) - returns detailed information about the entry in the archive.
+- [ZipFile::getListFiles](#Documentation-ZipFile-getListFiles) - returns list of archive files.
+- [ZipFile::hasEntry](#Documentation-ZipFile-hasEntry) - checks if there is an entry in the archive.
+- [ZipFile::isDirectory](#Documentation-ZipFile-isDirectory) - checks that the entry in the archive is a directory.
+- [ZipFile::matcher](#Documentation-ZipFile-matcher) - selecting entries in the archive to perform operations on them.
+- [ZipFile::openFile](#Documentation-ZipFile-openFile) - opens a zip-archive from a file.
+- [ZipFile::openFromString](#Documentation-ZipFile-openFromString) - opens a zip-archive from a string.
+- [ZipFile::openFromStream](#Documentation-ZipFile-openFromStream) - opens a zip-archive from the stream.
+- [ZipFile::outputAsAttachment](#Documentation-ZipFile-outputAsAttachment) - outputs a ZIP-archive to the browser.
+- [ZipFile::outputAsResponse](#Documentation-ZipFile-outputAsResponse) - outputs a ZIP-archive as PSR-7 Response.
+- [ZipFile::outputAsString](#Documentation-ZipFile-outputAsString) - outputs a ZIP-archive as string.
+- [ZipFile::rename](#Documentation-ZipFile-rename) - renames an entry defined by its name.
+- [ZipFile::rewrite](#Documentation-ZipFile-rewrite) - save changes and re-open the changed archive.
+- [ZipFile::saveAsFile](#Documentation-ZipFile-saveAsFile) - saves the archive to a file.
+- [ZipFile::saveAsStream](#Documentation-ZipFile-saveAsStream) - writes the archive to the stream.
+- [ZipFile::setArchiveComment](#Documentation-ZipFile-setArchiveComment) - set the comment of a ZIP archive.
+- [ZipFile::setCompressionLevel](#Documentation-ZipFile-setCompressionLevel) - set the compression level for all files in the archive.
+- [ZipFile::setCompressionLevelEntry](#Documentation-ZipFile-setCompressionLevelEntry) - sets the compression level for the entry by its name.
+- [ZipFile::setCompressionMethodEntry](#Documentation-ZipFile-setCompressionMethodEntry) - sets the compression method for the entry by its name.
+- [ZipFile::setEntryComment](#Documentation-ZipFile-setEntryComment) - set the comment of an entry defined by its name.
+- [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword) - set the password for the open archive.
+- [ZipFile::setReadPasswordEntry](#Documentation-ZipFile-setReadPasswordEntry) - sets a password for reading of an entry defined by its name.
+- ~~ZipFile::withNewPassword~~ - is an deprecated method, use the [ZipFile::setPassword](#Documentation-ZipFile-setPassword) method.
+- [ZipFile::setPassword](#Documentation-ZipFile-setPassword) - sets a new password for all files in the archive.
+- [ZipFile::setPasswordEntry](#Documentation-ZipFile-setPasswordEntry) - sets a new password of an entry defined by its name.
+- [ZipFile::setZipAlign](#Documentation-ZipFile-setZipAlign) - sets the alignment of the archive to optimize APK files (Android packages).
+- [ZipFile::unchangeAll](#Documentation-ZipFile-unchangeAll) - undo all changes done in the archive.
+- [ZipFile::unchangeArchiveComment](#Documentation-ZipFile-unchangeArchiveComment) - undo changes to the archive comment.
+- [ZipFile::unchangeEntry](#Documentation-ZipFile-unchangeEntry) - undo changes of an entry defined by its name.
+- ~~ZipFile::withoutPassword~~ - is an deprecated method, use the [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption) method.
+- ~~ZipFile::withReadPassword~~ - is an deprecated method, use the [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword) method.
+
+#### Creation/Opening of ZIP-archive
+**ZipFile::__construct** - initializes the ZIP archive.
+```php
+$zipFile = new \PhpZip\ZipFile();
+```
+ **ZipFile::openFile** - opens a zip-archive from a file.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->openFile('file.zip');
+```
+ **ZipFile::openFromString** - opens a zip-archive from a string.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->openFromString($stringContents);
+```
+ **ZipFile::openFromStream** - opens a zip-archive from the stream.
+```php
+$stream = fopen('file.zip', 'rb');
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->openFromStream($stream);
+```
+#### Reading entries from the archive
+ **ZipFile::count** - returns the number of entries in the archive.
+```php
+$zipFile = new \PhpZip\ZipFile();
+
+$count = count($zipFile);
+// or
+$count = $zipFile->count();
+```
+ **ZipFile::getListFiles** - returns list of archive files.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$listFiles = $zipFile->getListFiles();
+
+// example array contents:
+// array (
+// 0 => 'info.txt',
+// 1 => 'path/to/file.jpg',
+// 2 => 'another path/',
+// 3 => '0',
+// )
+```
+ **ZipFile::getEntryContent** - returns the entry contents using its name.
+```php
+// $entryName = 'path/to/example-entry-name.txt';
+$zipFile = new \PhpZip\ZipFile();
+
+$contents = $zipFile[$entryName];
+// or
+$contents = $zipFile->getEntryContents($entryName);
+```
+ **ZipFile::hasEntry** - checks if there is an entry in the archive.
+```php
+// $entryName = 'path/to/example-entry-name.txt';
+$zipFile = new \PhpZip\ZipFile();
+
+$hasEntry = isset($zipFile[$entryName]);
+// or
+$hasEntry = $zipFile->hasEntry($entryName);
+```
+ **ZipFile::isDirectory** - checks that the entry in the archive is a directory.
+```php
+// $entryName = 'path/to/';
+$zipFile = new \PhpZip\ZipFile();
+
+$isDirectory = $zipFile->isDirectory($entryName);
+```
+ **ZipFile::extractTo** - extract the archive contents.
+The directory must exist.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->extractTo($directory);
+```
+Extract some files to the directory.
+The directory must exist.
+```php
+// $toDirectory = '/tmp';
+$extractOnlyFiles = [
+ 'filename1',
+ 'filename2',
+ 'dir/dir/dir/'
+];
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->extractTo($toDirectory, $extractOnlyFiles);
+```
+#### Iterating entries
+`ZipFile` is an iterator.
+Can iterate all the entries in the `foreach` loop.
+```php
+foreach($zipFile as $entryName => $contents){
+ echo "Filename: $entryName" . PHP_EOL;
+ echo "Contents: $contents" . PHP_EOL;
+ echo '-----------------------------' . PHP_EOL;
+}
+```
+Can iterate through the `Iterator`.
+```php
+$iterator = new \ArrayIterator($zipFile);
+while ($iterator->valid())
+{
+ $entryName = $iterator->key();
+ $contents = $iterator->current();
+
+ echo "Filename: $entryName" . PHP_EOL;
+ echo "Contents: $contents" . PHP_EOL;
+ echo '-----------------------------' . PHP_EOL;
+
+ $iterator->next();
+}
+```
+#### Getting information about entries
+ **ZipFile::getArchiveComment** - returns the Zip archive comment.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$commentArchive = $zipFile->getArchiveComment();
+```
+ **ZipFile::getEntryComment** - returns the comment of an entry using the entry name.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$commentEntry = $zipFile->getEntryComment($entryName);
+```
+ **ZipFile::getEntryInfo** - returns detailed information about the entry in the archive
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipInfo = $zipFile->getEntryInfo('file.txt');
+```
+ **ZipFile::getAllInfo** - returns detailed information about all entries in the archive.
+```php
+$zipAllInfo = $zipFile->getAllInfo();
+```
+#### Adding entries to the archive
+
+All methods of adding entries to a ZIP archive allow you to specify a method for compressing content.
+
+The following methods of compression are available:
+- `\PhpZip\Constants\ZipCompressionMethod::STORED` - no compression
+- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate compression
+- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 compression with the extension `ext-bz2`
+
+ **ZipFile::addFile** - adds a file to a ZIP archive from the given path.
+```php
+$zipFile = new \PhpZip\ZipFile();
+// $file = '...../file.ext';
+// $entryName = 'file2.ext'
+$zipFile->addFile($file);
+
+// you can specify the name of the entry in the archive (if null, then the last component from the file name is used)
+$zipFile->addFile($file, $entryName);
+
+// you can specify a compression method
+$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+
+**ZipFile::addSplFile"** - adds a `\SplFileInfo` to a ZIP archive.
+```php
+// $file = '...../file.ext';
+// $entryName = 'file2.ext'
+$zipFile = new \PhpZip\ZipFile();
+
+$splFile = new \SplFileInfo('README.md');
+
+$zipFile->addSplFile($splFile);
+$zipFile->addSplFile($splFile, $entryName);
+// or
+$zipFile[$entryName] = new \SplFileInfo($file);
+
+// set compression method
+$zipFile->addSplFile($splFile, $entryName, $options = [
+ \PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED,
+]);
+```
+
+**ZipFile::addFromFinder"** - adds files from the `Symfony\Component\Finder\Finder` to a ZIP archive.
+https://symfony.com/doc/current/components/finder.html
+```php
+$finder = new \Symfony\Component\Finder\Finder();
+$finder
+ ->files()
+ ->name('*.{jpg,jpeg,gif,png}')
+ ->name('/^[0-9a-f]\./')
+ ->contains('/lorem\s+ipsum$/i')
+ ->in('path');
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addFromFinder($finder, $options = [
+ \PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED,
+ \PhpZip\Constants\ZipOptions::MODIFIED_TIME => new \DateTimeImmutable('-1 day 5 min')
+]);
+```
+ **ZipFile::addFromString** - adds a file to a ZIP archive using its contents.
+```php
+$zipFile = new \PhpZip\ZipFile();
+
+$zipFile[$entryName] = $contents;
+// or
+$zipFile->addFromString($entryName, $contents);
+
+// you can specify a compression method
+$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+ **ZipFile::addFromStream** - adds a entry from the stream to the ZIP archive.
+```php
+$zipFile = new \PhpZip\ZipFile();
+// $stream = fopen(..., 'rb');
+
+$zipFile->addFromStream($stream, $entryName);
+// or
+$zipFile[$entryName] = $stream;
+
+// you can specify a compression method
+$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+ **ZipFile::addEmptyDir** - add a new directory.
+```php
+$zipFile = new \PhpZip\ZipFile();
+// $path = "path/to/";
+$zipFile->addEmptyDir($path);
+// or
+$zipFile[$path] = null;
+```
+ **ZipFile::addAll** - adds all entries from an array.
+```php
+$entries = [
+ 'file.txt' => 'file contents', // add an entry from the string contents
+ 'empty dir/' => null, // add empty directory
+ 'path/to/file.jpg' => fopen('..../filename', 'rb'), // add an entry from the stream
+ 'path/to/file.dat' => new \SplFileInfo('..../filename'), // add an entry from the file
+];
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addAll($entries);
+```
+ **ZipFile::addDir** - adds files to the archive from the directory on the specified path without subdirectories.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addDir($dirName);
+
+// you can specify the path in the archive to which you want to put entries
+$localPath = 'to/path/';
+$zipFile->addDir($dirName, $localPath);
+
+// you can specify a compression method
+$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+ **ZipFile::addDirRecursive** - adds files to the archive from the directory on the specified path with subdirectories.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addDirRecursive($dirName);
+
+// you can specify the path in the archive to which you want to put entries
+$localPath = 'to/path/';
+$zipFile->addDirRecursive($dirName, $localPath);
+
+// you can specify a compression method
+$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+ **ZipFile::addFilesFromIterator** - adds files from the iterator of directories.
+```php
+// $directoryIterator = new \DirectoryIterator($dir); // without subdirectories
+// $directoryIterator = new \RecursiveDirectoryIterator($dir); // with subdirectories
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addFilesFromIterator($directoryIterator);
+
+// you can specify the path in the archive to which you want to put entries
+$localPath = 'to/path/';
+$zipFile->addFilesFromIterator($directoryIterator, $localPath);
+// or
+$zipFile[$localPath] = $directoryIterator;
+
+// you can specify a compression method
+$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+Example with some files ignoring:
+```php
+$ignoreFiles = [
+ 'file_ignore.txt',
+ 'dir_ignore/sub dir ignore/'
+];
+
+// $directoryIterator = new \DirectoryIterator($dir); // without subdirectories
+// $directoryIterator = new \RecursiveDirectoryIterator($dir); // with subdirectories
+// use \PhpZip\Util\Iterator\IgnoreFilesFilterIterator for non-recursive search
+
+$zipFile = new \PhpZip\ZipFile();
+$ignoreIterator = new \PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator(
+ $directoryIterator,
+ $ignoreFiles
+);
+
+$zipFile->addFilesFromIterator($ignoreIterator);
+```
+ **ZipFile::addFilesFromGlob** - adds files from a directory by [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) without subdirectories.
+```php
+$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addFilesFromGlob($dir, $globPattern);
+
+// you can specify the path in the archive to which you want to put entries
+$localPath = 'to/path/';
+$zipFile->addFilesFromGlob($dir, $globPattern, $localPath);
+
+// you can specify a compression method
+$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+ **ZipFile::addFilesFromGlobRecursive** - adds files from a directory by [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) with subdirectories.
+```php
+$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern);
+
+// you can specify the path in the archive to which you want to put entries
+$localPath = 'to/path/';
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath);
+
+// you can specify a compression method
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+ **ZipFile::addFilesFromRegex** - adds files from a directory by [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression) without subdirectories.
+```php
+$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->addFilesFromRegex($dir, $regexPattern);
+
+// you can specify the path in the archive to which you want to put entries
+$localPath = 'to/path/';
+$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath);
+
+// you can specify a compression method
+$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+ **ZipFile::addFilesFromRegexRecursive** - adds files from a directory by [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression) with subdirectories.
+```php
+$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files
+
+
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern);
+
+// you can specify the path in the archive to which you want to put entries
+$localPath = 'to/path/';
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath);
+
+// you can specify a compression method
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
+$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
+```
+#### Deleting entries from the archive
+ **ZipFile::deleteFromName** - deletes an entry in the archive using its name.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->deleteFromName($entryName);
+```
+ **ZipFile::deleteFromGlob** - deletes a entries in the archive using [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)).
+```php
+$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> delete all .jpg, .jpeg, .png and .gif files
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->deleteFromGlob($globPattern);
+```
+ **ZipFile::deleteFromRegex** - deletes a entries in the archive using [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression).
+```php
+$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> delete all .jpg, .jpeg, .png and .gif files
+
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->deleteFromRegex($regexPattern);
+```
+ **ZipFile::deleteAll** - deletes all entries in the ZIP archive.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->deleteAll();
+```
+#### Working with entries and archive
+ **ZipFile::rename** - renames an entry defined by its name.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->rename($oldName, $newName);
+```
+ **ZipFile::setCompressionLevel** - set the compression level for all files in the archive.
+
+> _Note that this method does not apply to entries that are added after this method is run._
+
+By default, the compression level is 5 (`\PhpZip\Constants\ZipCompressionLevel::NORMAL`) or the compression level specified in the archive for Deflate compression.
+
+The values range from 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) to 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`) are supported. The higher the number, the better and longer the compression.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->setCompressionLevel(\PhpZip\Constants\ZipCompressionLevel::MAXIMUM);
+```
+ **ZipFile::setCompressionLevelEntry** - sets the compression level for the entry by its name.
+
+The values range from 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) to 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`) are supported. The higher the number, the better and longer the compression.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->setCompressionLevelEntry($entryName, \PhpZip\Constants\ZipCompressionLevel::FAST);
+```
+ **ZipFile::setCompressionMethodEntry** - sets the compression method for the entry by its name.
+
+The following compression methods are available:
+- `\PhpZip\Constants\ZipCompressionMethod::STORED` - No compression
+- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate compression
+- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 compression with the extension `ext-bz2`
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->setCompressionMethodEntry($entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED);
+```
+ **ZipFile::setArchiveComment** - set the comment of a ZIP archive.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->setArchiveComment($commentArchive);
+```
+ **ZipFile::setEntryComment** - set the comment of an entry defined by its name.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$zipFile->setEntryComment($entryName, $comment);
+```
+ **ZipFile::matcher** - selecting entries in the archive to perform operations on them.
+```php
+$zipFile = new \PhpZip\ZipFile();
+$matcher = $zipFile->matcher();
+```
+Selecting files from the archive one at a time:
+```php
+$matcher
+ ->add('entry name')
+ ->add('another entry');
+```
+Select multiple files in the archive:
+```php
+$matcher->add([
+ 'entry name',
+ 'another entry name',
+ 'path/'
+]);
+```
+Selecting files by regular expression:
+```php
+$matcher->match('~\.jpe?g$~i');
+```
+Select all files in the archive:
+```php
+$matcher->all();
+```
+count() - gets the number of selected entries:
+```php
+$count = count($matcher);
+// or
+$count = $matcher->count();
+```
+getMatches() - returns a list of selected entries:
+```php
+$entries = $matcher->getMatches();
+// example array contents: ['entry name', 'another entry name'];
+```
+invoke() - invoke a callable function on selected entries:
+```php
+// example
+$matcher->invoke(static function($entryName) use($zipFile) {
+ $newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
+ $zipFile->rename($entryName, $newName);
+});
+```
+Functions for working on the selected entries:
+```php
+$matcher->delete(); // remove selected entries from a ZIP archive
+$matcher->setPassword($password); // sets a new password for the selected entries
+$matcher->setPassword($password, $encryptionMethod); // sets a new password and encryption method to selected entries
+$matcher->setEncryptionMethod($encryptionMethod); // sets the encryption method to the selected entries
+$matcher->disableEncryption(); // disables encryption for selected entries
+```
+#### Working with passwords
+
+Implemented support for encryption methods:
+- `\PhpZip\Constants\ZipEncryptionMethod::PKWARE` - Traditional PKWARE encryption (legacy)
+- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256` - WinZip AES encryption 256 bit (recommended)
+- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_192` - WinZip AES encryption 192 bit
+- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_128` - WinZip AES encryption 128 bit
+
+ **ZipFile::setReadPassword** - set the password for the open archive.
+
+> _Setting a password is not required for adding new entries or deleting existing ones, but if you want to extract the content or change the method / compression level, the encryption method, or change the password, in this case the password must be specified._
+```php
+$zipFile->setReadPassword($password);
+```
+ **ZipFile::setReadPasswordEntry** - gets a password for reading of an entry defined by its name.
+```php
+$zipFile->setReadPasswordEntry($entryName, $password);
+```
+ **ZipFile::setPassword** - sets a new password for all files in the archive.
+
+> _Note that this method does not apply to entries that are added after this method is run._
+```php
+$zipFile->setPassword($password);
+```
+You can set the encryption method:
+```php
+$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256;
+$zipFile->setPassword($password, $encryptionMethod);
+```
+ **ZipFile::setPasswordEntry** - sets a new password of an entry defined by its name.
+```php
+$zipFile->setPasswordEntry($entryName, $password);
+```
+You can set the encryption method:
+```php
+$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256;
+$zipFile->setPasswordEntry($entryName, $password, $encryptionMethod);
+```
+ **ZipFile::disableEncryption** - disable encryption for all entries that are already in the archive.
+
+> _Note that this method does not apply to entries that are added after this method is run._
+```php
+$zipFile->disableEncryption();
+```
+ **ZipFile::disableEncryptionEntry** - disable encryption of an entry defined by its name.
+```php
+$zipFile->disableEncryptionEntry($entryName);
+```
+#### zipalign
+ **ZipFile::setZipAlign** - sets the alignment of the archive to optimize APK files (Android packages).
+
+This method adds padding to unencrypted and not compressed entries, to optimize memory consumption in the Android system. It is recommended to use for `APK` files. The file may grow slightly.
+
+This method is an alternative to executing the `zipalign -f -v 4 filename.zip`.
+
+More details can be found on the [link](https://developer.android.com/studio/command-line/zipalign.html).
+```php
+$zipFile->setZipAlign(4);
+```
+#### Undo changes
+ **ZipFile::unchangeAll** - undo all changes done in the archive.
+```php
+$zipFile->unchangeAll();
+```
+ **ZipFile::unchangeArchiveComment** - undo changes to the archive comment.
+```php
+$zipFile->unchangeArchiveComment();
+```
+ **ZipFile::unchangeEntry** - undo changes of an entry defined by its name.
+```php
+$zipFile->unchangeEntry($entryName);
+```
+#### Saving a file or output to a browser
+ **ZipFile::saveAsFile** - saves the archive to a file.
+```php
+$zipFile->saveAsFile($filename);
+```
+ **ZipFile::saveAsStream** - writes the archive to the stream.
+```php
+// $fp = fopen($filename, 'w+b');
+
+$zipFile->saveAsStream($fp);
+```
+ **ZipFile::outputAsString** - outputs a ZIP-archive as string.
+```php
+$rawZipArchiveBytes = $zipFile->outputAsString();
+```
+ **ZipFile::outputAsAttachment** - outputs a ZIP-archive to the browser.
+```php
+$zipFile->outputAsAttachment($outputFilename);
+```
+You can set the Mime-Type:
+```php
+$mimeType = 'application/zip';
+$zipFile->outputAsAttachment($outputFilename, $mimeType);
+```
+ **ZipFile::outputAsResponse** - outputs a ZIP-archive as [PSR-7 Response](http://www.php-fig.org/psr/psr-7/).
+
+The output method can be used in any PSR-7 compatible framework.
+```php
+// $response = ....; // instance Psr\Http\Message\ResponseInterface
+$zipFile->outputAsResponse($response, $outputFilename);
+```
+You can set the Mime-Type:
+```php
+$mimeType = 'application/zip';
+$zipFile->outputAsResponse($response, $outputFilename, $mimeType);
+```
+ **ZipFile::rewrite** - save changes and re-open the changed archive.
+```php
+$zipFile->rewrite();
+```
+#### Closing the archive
+ **ZipFile::close** - close the archive.
+```php
+$zipFile->close();
+```
+### Running the tests
+Install the dependencies for the development:
+```bash
+composer install --dev
+```
+Run the tests:
+```bash
+vendor/bin/phpunit
+```
+### Changelog
+Changes are documented in the [releases page](https://github.com/Ne-Lexa/php-zip/releases).
+
+### Upgrade
+#### Upgrade version 2 to version 3.0
+Update the major version in the file `composer.json` to `^3.0`.
+```json
+{
+ "require": {
+ "nelexa/zip": "^3.0"
+ }
+}
+```
+Then install updates using `Composer`:
+```bash
+composer update nelexa/zip
+```
+Update your code to work with the new version:
+- Class `ZipOutputFile` merged to `ZipFile` and removed.
+ + `new \PhpZip\ZipOutputFile()` to `new \PhpZip\ZipFile()`
+- Static initialization methods are now not static.
+ + `\PhpZip\ZipFile::openFromFile($filename);` to `(new \PhpZip\ZipFile())->openFile($filename);`
+ + `\PhpZip\ZipOutputFile::openFromFile($filename);` to `(new \PhpZip\ZipFile())->openFile($filename);`
+ + `\PhpZip\ZipFile::openFromString($contents);` to `(new \PhpZip\ZipFile())->openFromString($contents);`
+ + `\PhpZip\ZipFile::openFromStream($stream);` to `(new \PhpZip\ZipFile())->openFromStream($stream);`
+ + `\PhpZip\ZipOutputFile::create()` to `new \PhpZip\ZipFile()`
+ + `\PhpZip\ZipOutputFile::openFromZipFile(\PhpZip\ZipFile $zipFile)` > `(new \PhpZip\ZipFile())->openFile($filename);`
+- Rename methods:
+ + `addFromFile` to `addFile`
+ + `setLevel` to `setCompressionLevel`
+ + `ZipFile::setPassword` to `ZipFile::withReadPassword`
+ + `ZipOutputFile::setPassword` to `ZipFile::withNewPassword`
+ + `ZipOutputFile::disableEncryptionAllEntries` to `ZipFile::withoutPassword`
+ + `ZipOutputFile::setComment` to `ZipFile::setArchiveComment`
+ + `ZipFile::getComment` to `ZipFile::getArchiveComment`
+- Changed signature for methods `addDir`, `addFilesFromGlob`, `addFilesFromRegex`.
+- Remove methods:
+ + `getLevel`
+ + `setCompressionMethod`
+ + `setEntryPassword`
+
+
diff --git a/vendor/nelexa/zip/composer.json b/vendor/nelexa/zip/composer.json
new file mode 100644
index 0000000..b1b5927
--- /dev/null
+++ b/vendor/nelexa/zip/composer.json
@@ -0,0 +1,60 @@
+{
+ "name": "nelexa/zip",
+ "type": "library",
+ "description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
+ "keywords": [
+ "zip",
+ "unzip",
+ "archive",
+ "extract",
+ "winzip",
+ "zipalign",
+ "ziparchive"
+ ],
+ "homepage": "https://github.com/Ne-Lexa/php-zip",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ne-Lexa",
+ "email": "alexey@nelexa.ru",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": "^5.5.9 || ^7.0",
+ "ext-zlib": "*",
+ "psr/http-message": "^1.0",
+ "paragonie/random_compat": "*",
+ "symfony/finder": "^3.0|^4.0|^5.0"
+ },
+ "require-dev": {
+ "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"
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpZip\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "PhpZip\\Tests\\": "tests/"
+ }
+ },
+ "suggest": {
+ "ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt",
+ "ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
+ "ext-bz2": "Needed to support BZIP2 compression",
+ "ext-fileinfo": "Needed to get mime-type file"
+ },
+ "minimum-stability": "stable",
+ "scripts": {
+ "php:fix": "php .php_cs --force",
+ "php:fix:debug": "php .php_cs"
+ }
+}
diff --git a/vendor/nelexa/zip/src/Constants/DosAttrs.php b/vendor/nelexa/zip/src/Constants/DosAttrs.php
new file mode 100644
index 0000000..5bebe65
--- /dev/null
+++ b/vendor/nelexa/zip/src/Constants/DosAttrs.php
@@ -0,0 +1,33 @@
+ 'Stored',
+ 1 => 'Shrunk',
+ 2 => 'Reduced compression factor 1',
+ 3 => 'Reduced compression factor 2',
+ 4 => 'Reduced compression factor 3',
+ 5 => 'Reduced compression factor 4',
+ 6 => 'Imploded',
+ 7 => 'Reserved for Tokenizing compression algorithm',
+ self::DEFLATED => 'Deflated',
+ 9 => 'Enhanced Deflating using Deflate64(tm)',
+ 10 => 'PKWARE Data Compression Library Imploding',
+ 11 => 'Reserved by PKWARE',
+ self::BZIP2 => 'BZIP2',
+ 13 => 'Reserved by PKWARE',
+ 14 => 'LZMA',
+ 15 => 'Reserved by PKWARE',
+ 16 => 'Reserved by PKWARE',
+ 17 => 'Reserved by PKWARE',
+ 18 => 'File is compressed using IBM TERSE (new)',
+ 19 => 'IBM LZ77 z Architecture (PFS)',
+ 96 => 'WinZip JPEG Compression',
+ 97 => 'WavPack compressed data',
+ 98 => 'PPMd version I, Rev 1',
+ self::WINZIP_AES => 'AES Encryption',
+ ];
+
+ /**
+ * @param int $value
+ *
+ * @return string
+ */
+ public static function getCompressionMethodName($value)
+ {
+ return isset(self::$ZIP_COMPRESSION_METHODS[$value]) ?
+ self::$ZIP_COMPRESSION_METHODS[$value] :
+ 'Unknown Method';
+ }
+
+ /**
+ * @return int[]
+ */
+ public static function getSupportMethods()
+ {
+ static $methods;
+
+ if ($methods === null) {
+ $methods = [
+ self::STORED,
+ self::DEFLATED,
+ ];
+
+ if (\extension_loaded('bz2')) {
+ $methods[] = self::BZIP2;
+ }
+ }
+
+ return $methods;
+ }
+
+ /**
+ * @param int $compressionMethod
+ *
+ * @throws ZipUnsupportMethodException
+ */
+ public static function checkSupport($compressionMethod)
+ {
+ $compressionMethod = (int) $compressionMethod;
+
+ if (!\in_array($compressionMethod, self::getSupportMethods(), true)) {
+ throw new ZipUnsupportMethodException(sprintf(
+ 'Compression method %d (%s) is not supported.',
+ $compressionMethod,
+ self::getCompressionMethodName($compressionMethod)
+ ));
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Constants/ZipConstants.php b/vendor/nelexa/zip/src/Constants/ZipConstants.php
new file mode 100644
index 0000000..39fca0f
--- /dev/null
+++ b/vendor/nelexa/zip/src/Constants/ZipConstants.php
@@ -0,0 +1,99 @@
+ */
+ private static $ENCRYPTION_METHODS = [
+ self::NONE => 'no encryption',
+ self::PKWARE => 'Traditional PKWARE encryption',
+ self::WINZIP_AES_128 => 'WinZip AES-128',
+ self::WINZIP_AES_192 => 'WinZip AES-192',
+ self::WINZIP_AES_256 => 'WinZip AES-256',
+ ];
+
+ /**
+ * @param int $value
+ *
+ * @return string
+ */
+ public static function getEncryptionMethodName($value)
+ {
+ $value = (int) $value;
+
+ return isset(self::$ENCRYPTION_METHODS[$value]) ?
+ self::$ENCRYPTION_METHODS[$value] :
+ 'Unknown Encryption Method';
+ }
+
+ /**
+ * @param int $encryptionMethod
+ *
+ * @return bool
+ */
+ public static function hasEncryptionMethod($encryptionMethod)
+ {
+ return isset(self::$ENCRYPTION_METHODS[$encryptionMethod]);
+ }
+
+ /**
+ * @param int $encryptionMethod
+ *
+ * @return bool
+ */
+ public static function isWinZipAesMethod($encryptionMethod)
+ {
+ return \in_array(
+ (int) $encryptionMethod,
+ [
+ self::WINZIP_AES_256,
+ self::WINZIP_AES_192,
+ self::WINZIP_AES_128,
+ ],
+ true
+ );
+ }
+
+ /**
+ * @param int $encryptionMethod
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function checkSupport($encryptionMethod)
+ {
+ $encryptionMethod = (int) $encryptionMethod;
+
+ if (!self::hasEncryptionMethod($encryptionMethod)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Encryption method %d is not supported.',
+ $encryptionMethod
+ ));
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Constants/ZipOptions.php b/vendor/nelexa/zip/src/Constants/ZipOptions.php
new file mode 100644
index 0000000..c5d9671
--- /dev/null
+++ b/vendor/nelexa/zip/src/Constants/ZipOptions.php
@@ -0,0 +1,62 @@
+ 'MS-DOS',
+ 1 => 'Amiga',
+ 2 => 'OpenVMS',
+ self::OS_UNIX => 'Unix',
+ 4 => 'VM/CMS',
+ 5 => 'Atari ST',
+ 6 => 'HPFS (OS/2, NT 3.x)',
+ 7 => 'Macintosh',
+ 8 => 'Z-System',
+ 9 => 'CP/M',
+ 10 => 'Windows NTFS or TOPS-20',
+ 11 => 'MVS or NTFS',
+ 12 => 'VSE or SMS/QDOS',
+ 13 => 'Acorn RISC OS',
+ 14 => 'VFAT',
+ 15 => 'alternate MVS',
+ 16 => 'BeOS',
+ 17 => 'Tandem',
+ 18 => 'OS/400',
+ self::OS_MAC_OSX => 'OS/X (Darwin)',
+ 30 => 'AtheOS/Syllable',
+ ];
+
+ /**
+ * @param int $platform
+ *
+ * @return string
+ */
+ public static function getPlatformName($platform)
+ {
+ return isset(self::$platforms[$platform]) ? self::$platforms[$platform] : 'Unknown';
+ }
+}
diff --git a/vendor/nelexa/zip/src/Constants/ZipVersion.php b/vendor/nelexa/zip/src/Constants/ZipVersion.php
new file mode 100644
index 0000000..58c090d
--- /dev/null
+++ b/vendor/nelexa/zip/src/Constants/ZipVersion.php
@@ -0,0 +1,81 @@
+expectedCrc = $expected;
+ $this->actualCrc = $actual;
+ }
+
+ /**
+ * Returns expected crc.
+ *
+ * @return int
+ */
+ public function getExpectedCrc()
+ {
+ return $this->expectedCrc;
+ }
+
+ /**
+ * Returns actual crc.
+ *
+ * @return int
+ */
+ public function getActualCrc()
+ {
+ return $this->actualCrc;
+ }
+}
diff --git a/vendor/nelexa/zip/src/Exception/InvalidArgumentException.php b/vendor/nelexa/zip/src/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..24ccc22
--- /dev/null
+++ b/vendor/nelexa/zip/src/Exception/InvalidArgumentException.php
@@ -0,0 +1,14 @@
+getName() : $entryName;
+ parent::__construct(sprintf(
+ 'Zip Entry "%s" was not found in the archive.',
+ $entryName
+ ));
+ $this->entryName = $entryName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntryName()
+ {
+ return $this->entryName;
+ }
+}
diff --git a/vendor/nelexa/zip/src/Exception/ZipException.php b/vendor/nelexa/zip/src/Exception/ZipException.php
new file mode 100644
index 0000000..e59ec60
--- /dev/null
+++ b/vendor/nelexa/zip/src/Exception/ZipException.php
@@ -0,0 +1,15 @@
+keys = [
+ 305419896,
+ 591751049,
+ 878082192,
+ ];
+
+ foreach (unpack('C*', $password) as $b) {
+ $this->updateKeys($b);
+ }
+ }
+
+ /**
+ * @param string $header
+ * @param int $checkByte
+ *
+ * @throws ZipAuthenticationException
+ */
+ public function checkHeader($header, $checkByte)
+ {
+ $byte = 0;
+
+ foreach (unpack('C*', $header) as $byte) {
+ $byte = ($byte ^ $this->decryptByte()) & 0xff;
+ $this->updateKeys($byte);
+ }
+
+ if ($byte !== $checkByte) {
+ throw new ZipAuthenticationException(sprintf('Invalid password'));
+ }
+ }
+
+ /**
+ * @param string $content
+ *
+ * @return string
+ */
+ public function decryptString($content)
+ {
+ $decryptContent = '';
+
+ foreach (unpack('C*', $content) as $byte) {
+ $byte = ($byte ^ $this->decryptByte()) & 0xff;
+ $this->updateKeys($byte);
+ $decryptContent .= \chr($byte);
+ }
+
+ return $decryptContent;
+ }
+
+ /**
+ * Decrypt byte.
+ *
+ * @return int
+ */
+ private function decryptByte()
+ {
+ $temp = $this->keys[2] | 2;
+
+ return (($temp * ($temp ^ 1)) >> 8) & 0xffffff;
+ }
+
+ /**
+ * Update keys.
+ *
+ * @param int $charAt
+ */
+ private function updateKeys($charAt)
+ {
+ $this->keys[0] = $this->crc32($this->keys[0], $charAt);
+ $this->keys[1] += ($this->keys[0] & 0xff);
+ $this->keys[1] = PackUtil::toSignedInt32($this->keys[1] * 134775813 + 1);
+ $this->keys[2] = PackUtil::toSignedInt32($this->crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
+ }
+
+ /**
+ * Update crc.
+ *
+ * @param int $oldCrc
+ * @param int $charAt
+ *
+ * @return int
+ */
+ private function crc32($oldCrc, $charAt)
+ {
+ return (($oldCrc >> 8) & 0xffffff) ^ self::$CRC_TABLE[($oldCrc ^ $charAt) & 0xff];
+ }
+
+ /**
+ * @param string $content
+ *
+ * @return string
+ */
+ public function encryptString($content)
+ {
+ $encryptContent = '';
+
+ foreach (unpack('C*', $content) as $val) {
+ $encryptContent .= pack('c', $this->encryptByte($val));
+ }
+
+ return $encryptContent;
+ }
+
+ /**
+ * @param int $byte
+ *
+ * @return int
+ */
+ private function encryptByte($byte)
+ {
+ $tempVal = $byte ^ $this->decryptByte() & 0xff;
+ $this->updateKeys($byte);
+
+ return $tempVal;
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php
new file mode 100644
index 0000000..97b1276
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php
@@ -0,0 +1,118 @@
+params['entry'])) {
+ return false;
+ }
+
+ if (!($this->params['entry'] instanceof ZipEntry)) {
+ throw new \RuntimeException('ZipEntry expected');
+ }
+ /** @var ZipEntry $entry */
+ $entry = $this->params['entry'];
+ $password = $entry->getPassword();
+
+ if ($password === null) {
+ return false;
+ }
+
+ $this->size = $entry->getCompressedSize();
+
+ // init context
+ $this->context = new PKCryptContext($password);
+
+ // init check byte
+ if ($entry->isDataDescriptorEnabled()) {
+ $this->checkByte = ($entry->getDosTime() >> 8) & 0xff;
+ } else {
+ $this->checkByte = ($entry->getCrc() >> 24) & 0xff;
+ }
+
+ $this->readLength = 0;
+ $this->readHeader = false;
+
+ return true;
+ }
+
+ /**
+ * Decryption filter.
+ *
+ * @param resource $in
+ * @param resource $out
+ * @param int $consumed
+ * @param bool $closing
+ *
+ * @throws ZipException
+ *
+ * @return int
+ *
+ * @todo USE FFI in php 7.4
+ */
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ $buffer = $bucket->data;
+ $this->readLength += $bucket->datalen;
+
+ if ($this->readLength > $this->size) {
+ $buffer = substr($buffer, 0, $this->size - $this->readLength);
+ }
+
+ if (!$this->readHeader) {
+ $header = substr($buffer, 0, PKCryptContext::STD_DEC_HDR_SIZE);
+ $this->context->checkHeader($header, $this->checkByte);
+
+ $buffer = substr($buffer, PKCryptContext::STD_DEC_HDR_SIZE);
+ $this->readHeader = true;
+ }
+
+ $bucket->data = $this->context->decryptString($buffer);
+
+ $consumed += $bucket->datalen;
+ stream_bucket_append($out, $bucket);
+ }
+
+ return \PSFS_PASS_ON;
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php
new file mode 100644
index 0000000..cd54145
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php
@@ -0,0 +1,128 @@
+params['entry'], $this->params['size'])) {
+ return false;
+ }
+
+ if (!($this->params['entry'] instanceof ZipEntry)) {
+ throw new \RuntimeException('ZipEntry expected');
+ }
+ /** @var ZipEntry $entry */
+ $entry = $this->params['entry'];
+ $password = $entry->getPassword();
+
+ if ($password === null) {
+ return false;
+ }
+
+ $this->size = (int) $this->params['size'];
+
+ // init keys
+ $this->context = new PKCryptContext($password);
+
+ $crc = $entry->isDataDescriptorRequired() || $entry->getCrc() === ZipEntry::UNKNOWN ?
+ ($entry->getDosTime() & 0x0000ffff) << 16 :
+ $entry->getCrc();
+
+ try {
+ $headerBytes = random_bytes(PKCryptContext::STD_DEC_HDR_SIZE);
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
+ }
+
+ $headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xff);
+ $headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff);
+
+ $this->headerBytes = $headerBytes;
+ $this->writeLength = 0;
+ $this->writeHeader = false;
+
+ return true;
+ }
+
+ /**
+ * Encryption filter.
+ *
+ * @param resource $in
+ * @param resource $out
+ * @param int $consumed
+ * @param bool $closing
+ *
+ * @return int
+ *
+ * @todo USE FFI in php 7.4
+ */
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ $buffer = $bucket->data;
+ $this->writeLength += $bucket->datalen;
+
+ if ($this->writeLength > $this->size) {
+ $buffer = substr($buffer, 0, $this->size - $this->writeLength);
+ }
+
+ $data = '';
+
+ if (!$this->writeHeader) {
+ $data .= $this->context->encryptString($this->headerBytes);
+ $this->writeHeader = true;
+ }
+
+ $data .= $this->context->encryptString($buffer);
+
+ $bucket->data = $data;
+
+ $consumed += $bucket->datalen;
+ stream_bucket_append($out, $bucket);
+ }
+
+ return \PSFS_PASS_ON;
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php
new file mode 100644
index 0000000..2691160
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php
@@ -0,0 +1,166 @@
+iv = str_repeat("\0", self::IV_SIZE);
+ $keyStrengthBytes = (int) ($encryptionStrengthBits / 8);
+ $hashLength = $keyStrengthBytes * 2 + self::PASSWORD_VERIFIER_SIZE * 8;
+
+ $hash = hash_pbkdf2(
+ 'sha1',
+ $password,
+ $salt,
+ self::ITERATION_COUNT,
+ $hashLength,
+ true
+ );
+
+ $this->key = substr($hash, 0, $keyStrengthBytes);
+ $sha1Mac = substr($hash, $keyStrengthBytes, $keyStrengthBytes);
+ $this->hmacContext = hash_init('sha1', \HASH_HMAC, $sha1Mac);
+ $this->passwordVerifier = substr($hash, 2 * $keyStrengthBytes, self::PASSWORD_VERIFIER_SIZE);
+ }
+
+ /**
+ * @return string
+ */
+ public function getPasswordVerifier()
+ {
+ return $this->passwordVerifier;
+ }
+
+ public function updateIv()
+ {
+ for ($ivCharIndex = 0; $ivCharIndex < self::IV_SIZE; $ivCharIndex++) {
+ $ivByte = \ord($this->iv[$ivCharIndex]);
+
+ if (++$ivByte === 256) {
+ // overflow, set this one to 0, increment next
+ $this->iv[$ivCharIndex] = "\0";
+ } else {
+ // no overflow, just write incremented number back and abort
+ $this->iv[$ivCharIndex] = \chr($ivByte);
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param string $data
+ *
+ * @return string
+ */
+ public function decryption($data)
+ {
+ hash_update($this->hmacContext, $data);
+
+ return CryptoUtil::decryptAesCtr($data, $this->key, $this->iv);
+ }
+
+ /**
+ * @param string $data
+ *
+ * @return string
+ */
+ public function encrypt($data)
+ {
+ $encryptionData = CryptoUtil::encryptAesCtr($data, $this->key, $this->iv);
+ hash_update($this->hmacContext, $encryptionData);
+
+ return $encryptionData;
+ }
+
+ /**
+ * @param string $authCode
+ *
+ * @throws ZipAuthenticationException
+ */
+ public function checkAuthCode($authCode)
+ {
+ $hmac = $this->getHmac();
+
+ // check authenticationCode
+ if (strcmp($hmac, $authCode) !== 0) {
+ throw new ZipAuthenticationException('Authenticated WinZip AES entry content has been tampered with.');
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getHmac()
+ {
+ return substr(
+ hash_final($this->hmacContext, true),
+ 0,
+ self::FOOTER_SIZE
+ );
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php
new file mode 100644
index 0000000..7839172
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php
@@ -0,0 +1,187 @@
+params['entry'])) {
+ return false;
+ }
+
+ if (!($this->params['entry'] instanceof ZipEntry)) {
+ throw new \RuntimeException('ZipEntry expected');
+ }
+ $this->entry = $this->params['entry'];
+
+ if (
+ $this->entry->getPassword() === null ||
+ !$this->entry->isEncrypted() ||
+ !$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID)
+ ) {
+ return false;
+ }
+
+ $this->buffer = '';
+
+ return true;
+ }
+
+ /**
+ * @param resource $in
+ * @param resource $out
+ * @param int $consumed
+ * @param bool $closing
+ *
+ * @throws ZipAuthenticationException
+ *
+ * @return int
+ */
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ $this->buffer .= $bucket->data;
+ $this->readLength += $bucket->datalen;
+
+ if ($this->readLength > $this->entry->getCompressedSize()) {
+ $this->buffer = substr($this->buffer, 0, $this->entry->getCompressedSize() - $this->readLength);
+ }
+
+ // read header
+ if ($this->context === null) {
+ /**
+ * @var WinZipAesExtraField|null $winZipExtra
+ */
+ $winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID);
+
+ if ($winZipExtra === null) {
+ throw new RuntimeException('$winZipExtra is null');
+ }
+ $saltSize = $winZipExtra->getSaltSize();
+ $headerSize = $saltSize + WinZipAesContext::PASSWORD_VERIFIER_SIZE;
+
+ if (\strlen($this->buffer) < $headerSize) {
+ return \PSFS_FEED_ME;
+ }
+
+ $salt = substr($this->buffer, 0, $saltSize);
+ $passwordVerifier = substr($this->buffer, $saltSize, WinZipAesContext::PASSWORD_VERIFIER_SIZE);
+ $password = $this->entry->getPassword();
+
+ if ($password === null) {
+ throw new RuntimeException('$password is null');
+ }
+ $this->context = new WinZipAesContext($winZipExtra->getEncryptionStrength(), $password, $salt);
+ unset($password);
+
+ // Verify password.
+ if ($passwordVerifier !== $this->context->getPasswordVerifier()) {
+ throw new ZipAuthenticationException('Invalid password');
+ }
+
+ $this->encBlockPosition = 0;
+ $this->encBlockLength = $this->entry->getCompressedSize() - $headerSize - WinZipAesContext::FOOTER_SIZE;
+
+ $this->buffer = substr($this->buffer, $headerSize);
+ }
+
+ // encrypt data
+ $plainText = '';
+ $offset = 0;
+ $len = \strlen($this->buffer);
+ $remaining = $this->encBlockLength - $this->encBlockPosition;
+
+ if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) {
+ return \PSFS_FEED_ME;
+ }
+ $limit = min($len, $remaining);
+
+ if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) {
+ $limit -= ($limit % WinZipAesContext::BLOCK_SIZE);
+ }
+
+ while ($offset < $limit) {
+ $this->context->updateIv();
+ $length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset);
+ $data = substr($this->buffer, 0, $length);
+ $plainText .= $this->context->decryption($data);
+ $offset += $length;
+ $this->buffer = substr($this->buffer, $length);
+ }
+ $this->encBlockPosition += $offset;
+
+ if (
+ $this->encBlockPosition === $this->encBlockLength &&
+ \strlen($this->buffer) === WinZipAesContext::FOOTER_SIZE
+ ) {
+ $this->authenticationCode = $this->buffer;
+ $this->buffer = '';
+ }
+
+ $bucket->data = $plainText;
+ $consumed += $bucket->datalen;
+ stream_bucket_append($out, $bucket);
+ }
+
+ return \PSFS_PASS_ON;
+ }
+
+ /**
+ * @see http://php.net/manual/en/php-user-filter.onclose.php
+ *
+ * @throws ZipAuthenticationException
+ */
+ public function onClose()
+ {
+ $this->buffer = '';
+
+ if ($this->context !== null) {
+ $this->context->checkAuthCode($this->authenticationCode);
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php
new file mode 100644
index 0000000..12d12fe
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php
@@ -0,0 +1,158 @@
+params['entry'])) {
+ return false;
+ }
+
+ if (!($this->params['entry'] instanceof ZipEntry)) {
+ throw new \RuntimeException('ZipEntry expected');
+ }
+ $this->entry = $this->params['entry'];
+
+ if (
+ $this->entry->getPassword() === null ||
+ !$this->entry->isEncrypted() ||
+ !$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID)
+ ) {
+ return false;
+ }
+
+ $this->size = (int) $this->params['size'];
+ $this->context = null;
+ $this->buffer = '';
+
+ return true;
+ }
+
+ /**
+ * @param resource $in
+ * @param resource $out
+ * @param int $consumed
+ * @param bool $closing
+ *
+ * @return int
+ */
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ $this->buffer .= $bucket->data;
+ $this->remaining += $bucket->datalen;
+
+ if ($this->remaining > $this->size) {
+ $this->buffer = substr($this->buffer, 0, $this->size - $this->remaining);
+ $this->remaining = $this->size;
+ }
+
+ $encryptionText = '';
+
+ // write header
+ if ($this->context === null) {
+ /**
+ * @var WinZipAesExtraField|null $winZipExtra
+ */
+ $winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID);
+
+ if ($winZipExtra === null) {
+ throw new RuntimeException('$winZipExtra is null');
+ }
+ $saltSize = $winZipExtra->getSaltSize();
+
+ try {
+ $salt = random_bytes($saltSize);
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
+ }
+ $password = $this->entry->getPassword();
+
+ if ($password === null) {
+ throw new RuntimeException('$password is null');
+ }
+ $this->context = new WinZipAesContext(
+ $winZipExtra->getEncryptionStrength(),
+ $password,
+ $salt
+ );
+
+ $encryptionText .= $salt . $this->context->getPasswordVerifier();
+ }
+
+ // encrypt data
+ $offset = 0;
+ $len = \strlen($this->buffer);
+ $remaining = $this->remaining - $this->size;
+
+ if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) {
+ return \PSFS_FEED_ME;
+ }
+ $limit = max($len, $remaining);
+
+ if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) {
+ $limit -= ($limit % WinZipAesContext::BLOCK_SIZE);
+ }
+
+ while ($offset < $limit) {
+ $this->context->updateIv();
+ $length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset);
+ $encryptionText .= $this->context->encrypt(
+ substr($this->buffer, 0, $length)
+ );
+ $offset += $length;
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ if ($remaining === 0) {
+ $encryptionText .= $this->context->getHmac();
+ }
+
+ $bucket->data = $encryptionText;
+ $consumed += $bucket->datalen;
+
+ stream_bucket_append($out, $bucket);
+ }
+
+ return \PSFS_PASS_ON;
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/Stream/ResponseStream.php b/vendor/nelexa/zip/src/IO/Stream/ResponseStream.php
new file mode 100644
index 0000000..e016103
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/Stream/ResponseStream.php
@@ -0,0 +1,338 @@
+ [
+ 'r' => true,
+ 'w+' => true,
+ 'r+' => true,
+ 'x+' => true,
+ 'c+' => true,
+ 'rb' => true,
+ 'w+b' => true,
+ 'r+b' => true,
+ 'x+b' => true,
+ 'c+b' => true,
+ 'rt' => true,
+ 'w+t' => true,
+ 'r+t' => true,
+ 'x+t' => true,
+ 'c+t' => true,
+ 'a+' => true,
+ ],
+ 'write' => [
+ 'w' => true,
+ 'w+' => true,
+ 'rw' => true,
+ 'r+' => true,
+ 'x+' => true,
+ 'c+' => true,
+ 'wb' => true,
+ 'w+b' => true,
+ 'r+b' => true,
+ 'x+b' => true,
+ 'c+b' => true,
+ 'w+t' => true,
+ 'r+t' => true,
+ 'x+t' => true,
+ 'c+t' => true,
+ 'a' => true,
+ 'a+' => true,
+ ],
+ ];
+
+ /** @var resource */
+ private $stream;
+
+ /** @var int|null */
+ private $size;
+
+ /** @var bool */
+ private $seekable;
+
+ /** @var bool */
+ private $readable;
+
+ /** @var bool */
+ private $writable;
+
+ /** @var string|null */
+ private $uri;
+
+ /**
+ * @param resource $stream stream resource to wrap
+ *
+ * @throws \InvalidArgumentException if the stream is not a stream resource
+ */
+ public function __construct($stream)
+ {
+ if (!\is_resource($stream)) {
+ throw new \InvalidArgumentException('Stream must be a resource');
+ }
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
+ $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ /**
+ * Get stream metadata as an associative array or retrieve a specific key.
+ *
+ * The keys returned are identical to the keys returned from PHP's
+ * stream_get_meta_data() function.
+ *
+ * @see http://php.net/manual/en/function.stream-get-meta-data.php
+ *
+ * @param string $key specific metadata to retrieve
+ *
+ * @return array|mixed|null Returns an associative array if no key is
+ * provided. Returns a specific key value if a key is provided and the
+ * value is found, or null if the key is not found.
+ */
+ public function getMetadata($key = null)
+ {
+ if (!$this->stream) {
+ return $key ? null : [];
+ }
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+
+ /**
+ * Reads all data from the stream into a string, from the beginning to end.
+ *
+ * This method MUST attempt to seek to the beginning of the stream before
+ * reading data and read the stream until the end is reached.
+ *
+ * Warning: This could attempt to load a large amount of data into memory.
+ *
+ * This method MUST NOT raise an exception in order to conform with PHP's
+ * string casting operations.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if (!$this->stream) {
+ return '';
+ }
+ $this->rewind();
+
+ return (string) stream_get_contents($this->stream);
+ }
+
+ /**
+ * Seek to the beginning of the stream.
+ *
+ * If the stream is not seekable, this method will raise an exception;
+ * otherwise, it will perform a seek(0).
+ *
+ * @throws \RuntimeException on failure
+ *
+ * @see http://www.php.net/manual/en/function.fseek.php
+ * @see seek()
+ */
+ public function rewind()
+ {
+ $this->seekable && rewind($this->stream);
+ }
+
+ /**
+ * Get the size of the stream if known.
+ *
+ * @return int|null returns the size in bytes if known, or null if unknown
+ */
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!$this->stream) {
+ return null;
+ }
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri !== null) {
+ clearstatcache(true, $this->uri);
+ }
+ $stats = fstat($this->stream);
+
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the current position of the file read/write pointer.
+ *
+ * @throws \RuntimeException on error
+ *
+ * @return int Position of the file pointer
+ */
+ public function tell()
+ {
+ return $this->stream ? ftell($this->stream) : false;
+ }
+
+ /**
+ * Returns true if the stream is at the end of the stream.
+ *
+ * @return bool
+ */
+ public function eof()
+ {
+ return !$this->stream || feof($this->stream);
+ }
+
+ /**
+ * Returns whether or not the stream is seekable.
+ *
+ * @return bool
+ */
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ /**
+ * Seek to a position in the stream.
+ *
+ * @see http://www.php.net/manual/en/function.fseek.php
+ *
+ * @param int $offset Stream offset
+ * @param int $whence Specifies how the cursor position will be calculated
+ * based on the seek offset. Valid values are identical to the built-in
+ * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
+ * offset bytes SEEK_CUR: Set position to current location plus offset
+ * SEEK_END: Set position to end-of-stream plus offset.
+ *
+ * @throws \RuntimeException on failure
+ */
+ public function seek($offset, $whence = \SEEK_SET)
+ {
+ $this->seekable && fseek($this->stream, $offset, $whence);
+ }
+
+ /**
+ * Returns whether or not the stream is writable.
+ *
+ * @return bool
+ */
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ /**
+ * Write data to the stream.
+ *
+ * @param string $string the string that is to be written
+ *
+ * @throws \RuntimeException on failure
+ *
+ * @return int returns the number of bytes written to the stream
+ */
+ public function write($string)
+ {
+ $this->size = null;
+
+ return $this->writable ? fwrite($this->stream, $string) : false;
+ }
+
+ /**
+ * Returns whether or not the stream is readable.
+ *
+ * @return bool
+ */
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ /**
+ * Read data from the stream.
+ *
+ * @param int $length Read up to $length bytes from the object and return
+ * them. Fewer than $length bytes may be returned if underlying stream
+ * call returns fewer bytes.
+ *
+ * @throws \RuntimeException if an error occurs
+ *
+ * @return string returns the data read from the stream, or an empty string
+ * if no bytes are available
+ */
+ public function read($length)
+ {
+ return $this->readable ? fread($this->stream, $length) : '';
+ }
+
+ /**
+ * Returns the remaining contents in a string.
+ *
+ * @throws \RuntimeException if unable to read or an error occurs while
+ * reading
+ *
+ * @return string
+ */
+ public function getContents()
+ {
+ return $this->stream ? stream_get_contents($this->stream) : '';
+ }
+
+ /**
+ * Closes the stream when the destructed.
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * Closes the stream and any underlying resources.
+ */
+ public function close()
+ {
+ if (\is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+
+ /**
+ * Separates any underlying resources from the stream.
+ *
+ * After the stream has been detached, the stream is in an unusable state.
+ *
+ * @return resource|null Underlying PHP stream, if any
+ */
+ public function detach()
+ {
+ $result = $this->stream;
+ $this->stream = null;
+ $this->size = null;
+ $this->uri = null;
+ $this->readable = false;
+ $this->writable = false;
+ $this->seekable = false;
+
+ return $result;
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php b/vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php
new file mode 100644
index 0000000..aa13f07
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php
@@ -0,0 +1,309 @@
+ [
+ 'entry' => $entry,
+ ],
+ ]
+ );
+
+ $uri = self::PROTOCOL . '://' . $entry->getName();
+ $fp = fopen($uri, 'r+b', false, $context);
+
+ if ($fp === false) {
+ throw new \RuntimeException('Error open ' . $uri);
+ }
+
+ return $fp;
+ }
+
+ /**
+ * Opens file or URL.
+ *
+ * This method is called immediately after the wrapper is
+ * initialized (f.e. by {@see fopen()} and {@see file_get_contents()}).
+ *
+ * @param string $path specifies the URL that was passed to
+ * the original function
+ * @param string $mode the mode used to open the file, as detailed
+ * for {@see fopen()}
+ * @param int $options Holds additional flags set by the streams
+ * API. It can hold one or more of the
+ * following values OR'd together.
+ * @param string $opened_path if the path is opened successfully, and
+ * STREAM_USE_PATH is set in options,
+ * opened_path should be set to the
+ * full path of the file/resource that
+ * was actually opened
+ *
+ * @throws ZipException
+ *
+ * @return bool
+ *
+ * @see https://www.php.net/streamwrapper.stream-open
+ */
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ if ($this->context === null) {
+ throw new \RuntimeException('stream context is null');
+ }
+ $streamOptions = stream_context_get_options($this->context);
+
+ if (!isset($streamOptions[self::PROTOCOL]['entry'])) {
+ throw new \RuntimeException('no stream option ["' . self::PROTOCOL . '"]["entry"]');
+ }
+ $zipEntry = $streamOptions[self::PROTOCOL]['entry'];
+
+ if (!$zipEntry instanceof ZipEntry) {
+ throw new \RuntimeException('invalid stream context');
+ }
+
+ $zipData = $zipEntry->getData();
+
+ if ($zipData === null) {
+ throw new ZipException(sprintf('No data for zip entry "%s"', $zipEntry->getName()));
+ }
+ $this->fp = $zipData->getDataAsStream();
+
+ return $this->fp !== false;
+ }
+
+ /**
+ * Read from stream.
+ *
+ * This method is called in response to {@see fread()} and {@see fgets()}.
+ *
+ * Note: Remember to update the read/write position of the stream
+ * (by the number of bytes that were successfully read).
+ *
+ * @param int $count how many bytes of data from the current
+ * position should be returned
+ *
+ * @return false|string If there are less than count bytes available,
+ * return as many as are available. If no more data
+ * is available, return either FALSE or
+ * an empty string.
+ *
+ * @see https://www.php.net/streamwrapper.stream-read
+ */
+ public function stream_read($count)
+ {
+ return fread($this->fp, $count);
+ }
+
+ /**
+ * Seeks to specific location in a stream.
+ *
+ * This method is called in response to {@see fseek()}.
+ * The read/write position of the stream should be updated according
+ * to the offset and whence.
+ *
+ * @param int $offset the stream offset to seek to
+ * @param int $whence Possible values:
+ * {@see \SEEK_SET} - Set position equal to offset bytes.
+ * {@see \SEEK_CUR} - Set position to current location plus offset.
+ * {@see \SEEK_END} - Set position to end-of-file plus offset.
+ *
+ * @return bool return TRUE if the position was updated, FALSE otherwise
+ *
+ * @see https://www.php.net/streamwrapper.stream-seek
+ */
+ public function stream_seek($offset, $whence = \SEEK_SET)
+ {
+ return fseek($this->fp, $offset, $whence) === 0;
+ }
+
+ /**
+ * Retrieve the current position of a stream.
+ *
+ * This method is called in response to {@see fseek()} to determine
+ * the current position.
+ *
+ * @return int should return the current position of the stream
+ *
+ * @see https://www.php.net/streamwrapper.stream-tell
+ */
+ public function stream_tell()
+ {
+ $pos = ftell($this->fp);
+
+ if ($pos === false) {
+ throw new \RuntimeException('Cannot get stream position.');
+ }
+
+ return $pos;
+ }
+
+ /**
+ * Tests for end-of-file on a file pointer.
+ *
+ * This method is called in response to {@see feof()}.
+ *
+ * @return bool should return TRUE if the read/write position is at
+ * the end of the stream and if no more data is available
+ * to be read, or FALSE otherwise
+ *
+ * @see https://www.php.net/streamwrapper.stream-eof
+ */
+ public function stream_eof()
+ {
+ return feof($this->fp);
+ }
+
+ /**
+ * Retrieve information about a file resource.
+ *
+ * This method is called in response to {@see fstat()}.
+ *
+ * @return array
+ *
+ * @see https://www.php.net/streamwrapper.stream-stat
+ * @see https://www.php.net/stat
+ * @see https://www.php.net/fstat
+ */
+ public function stream_stat()
+ {
+ return fstat($this->fp);
+ }
+
+ /**
+ * Flushes the output.
+ *
+ * This method is called in response to {@see fflush()} and when the
+ * stream is being closed while any unflushed data has been written to
+ * it before.
+ * If you have cached data in your stream but not yet stored it into
+ * the underlying storage, you should do so now.
+ *
+ * @return bool should return TRUE if the cached data was successfully
+ * stored (or if there was no data to store), or FALSE
+ * if the data could not be stored
+ *
+ * @see https://www.php.net/streamwrapper.stream-flush
+ */
+ public function stream_flush()
+ {
+ return fflush($this->fp);
+ }
+
+ /**
+ * Truncate stream.
+ *
+ * Will respond to truncation, e.g., through {@see ftruncate()}.
+ *
+ * @param int $new_size the new size
+ *
+ * @return bool returns TRUE on success or FALSE on failure
+ *
+ * @see https://www.php.net/streamwrapper.stream-truncate
+ */
+ public function stream_truncate($new_size)
+ {
+ return ftruncate($this->fp, (int) $new_size);
+ }
+
+ /**
+ * Write to stream.
+ *
+ * This method is called in response to {@see fwrite().}
+ *
+ * Note: Remember to update the current position of the stream by
+ * number of bytes that were successfully written.
+ *
+ * @param string $data should be stored into the underlying stream
+ *
+ * @return int should return the number of bytes that were successfully stored, or 0 if none could be stored
+ *
+ * @see https://www.php.net/streamwrapper.stream-write
+ */
+ public function stream_write($data)
+ {
+ $bytes = fwrite($this->fp, $data);
+
+ return $bytes === false ? 0 : $bytes;
+ }
+
+ /**
+ * Retrieve the underlaying resource.
+ *
+ * This method is called in response to {@see stream_select()}.
+ *
+ * @param int $cast_as can be {@see STREAM_CAST_FOR_SELECT} when {@see stream_select()}
+ * is callingstream_cast() or {@see STREAM_CAST_AS_STREAM} when
+ * stream_cast() is called for other uses
+ *
+ * @return resource
+ */
+ public function stream_cast($cast_as)
+ {
+ return $this->fp;
+ }
+
+ /**
+ * Close a resource.
+ *
+ * This method is called in response to {@see fclose()}.
+ * All resources that were locked, or allocated, by the wrapper should be released.
+ *
+ * @see https://www.php.net/streamwrapper.stream-close
+ */
+ public function stream_close()
+ {
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/ZipReader.php b/vendor/nelexa/zip/src/IO/ZipReader.php
new file mode 100644
index 0000000..f445bf8
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/ZipReader.php
@@ -0,0 +1,898 @@
+size = fstat($inStream)['size'];
+ $this->inStream = $inStream;
+
+ /** @noinspection AdditionOperationOnArraysInspection */
+ $options += $this->getDefaultOptions();
+ $this->options = $options;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getDefaultOptions()
+ {
+ return [
+ ZipOptions::CHARSET => null,
+ ];
+ }
+
+ /**
+ * @throws ZipException
+ *
+ * @return ImmutableZipContainer
+ */
+ public function read()
+ {
+ if ($this->size < ZipConstants::END_CD_MIN_LEN) {
+ throw new ZipException('Corrupt zip file');
+ }
+
+ $endOfCentralDirectory = $this->readEndOfCentralDirectory();
+ $entries = $this->readCentralDirectory($endOfCentralDirectory);
+
+ return new ImmutableZipContainer($entries, $endOfCentralDirectory->getComment());
+ }
+
+ /**
+ * @return array
+ */
+ public function getStreamMetaData()
+ {
+ return stream_get_meta_data($this->inStream);
+ }
+
+ /**
+ * Read End of central directory record.
+ *
+ * end of central dir signature 4 bytes (0x06054b50)
+ * number of this disk 2 bytes
+ * number of the disk with the
+ * start of the central directory 2 bytes
+ * total number of entries in the
+ * central directory on this disk 2 bytes
+ * total number of entries in
+ * the central directory 2 bytes
+ * size of the central directory 4 bytes
+ * offset of start of central
+ * directory with respect to
+ * the starting disk number 4 bytes
+ * .ZIP file comment length 2 bytes
+ * .ZIP file comment (variable size)
+ *
+ * @throws ZipException
+ *
+ * @return EndOfCentralDirectory
+ */
+ protected function readEndOfCentralDirectory()
+ {
+ if (!$this->findEndOfCentralDirectory()) {
+ throw new ZipException('Invalid zip file. The end of the central directory could not be found.');
+ }
+
+ $positionECD = ftell($this->inStream) - 4;
+ $sizeECD = $this->size - ftell($this->inStream);
+ $buffer = fread($this->inStream, $sizeECD);
+
+ $unpack = unpack(
+ 'vdiskNo/vcdDiskNo/vcdEntriesDisk/' .
+ 'vcdEntries/VcdSize/VcdPos/vcommentLength',
+ substr($buffer, 0, 18)
+ );
+
+ if (
+ $unpack['diskNo'] !== 0 ||
+ $unpack['cdDiskNo'] !== 0 ||
+ $unpack['cdEntriesDisk'] !== $unpack['cdEntries']
+ ) {
+ throw new ZipException(
+ 'ZIP file spanning/splitting is not supported!'
+ );
+ }
+ // .ZIP file comment (variable sizeECD)
+ $comment = null;
+
+ if ($unpack['commentLength'] > 0) {
+ $comment = substr($buffer, 18, $unpack['commentLength']);
+ }
+
+ // Check for ZIP64 End Of Central Directory Locator exists.
+ $zip64ECDLocatorPosition = $positionECD - ZipConstants::ZIP64_END_CD_LOC_LEN;
+ fseek($this->inStream, $zip64ECDLocatorPosition);
+ // zip64 end of central dir locator
+ // signature 4 bytes (0x07064b50)
+ if ($zip64ECDLocatorPosition > 0 && unpack(
+ 'V',
+ fread($this->inStream, 4)
+ )[1] === ZipConstants::ZIP64_END_CD_LOC) {
+ if (!$this->isZip64Support()) {
+ throw new ZipException('ZIP64 not supported this archive.');
+ }
+
+ $positionECD = $this->findZip64ECDPosition();
+ $endCentralDirectory = $this->readZip64EndOfCentralDirectory($positionECD);
+ $endCentralDirectory->setComment($comment);
+ } else {
+ $endCentralDirectory = new EndOfCentralDirectory(
+ $unpack['cdEntries'],
+ $unpack['cdPos'],
+ $unpack['cdSize'],
+ false,
+ $comment
+ );
+ }
+
+ return $endCentralDirectory;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function findEndOfCentralDirectory()
+ {
+ $max = $this->size - ZipConstants::END_CD_MIN_LEN;
+ $min = $max >= 0xffff ? $max - 0xffff : 0;
+ // Search for End of central directory record.
+ for ($position = $max; $position >= $min; $position--) {
+ fseek($this->inStream, $position);
+ // end of central dir signature 4 bytes (0x06054b50)
+ if (unpack('V', fread($this->inStream, 4))[1] !== ZipConstants::END_CD) {
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Read Zip64 end of central directory locator and returns
+ * Zip64 end of central directory position.
+ *
+ * number of the disk with the
+ * start of the zip64 end of
+ * central directory 4 bytes
+ * relative offset of the zip64
+ * end of central directory record 8 bytes
+ * total number of disks 4 bytes
+ *
+ * @throws ZipException
+ *
+ * @return int Zip64 End Of Central Directory position
+ */
+ protected function findZip64ECDPosition()
+ {
+ $diskNo = unpack('V', fread($this->inStream, 4))[1];
+ $zip64ECDPos = PackUtil::unpackLongLE(fread($this->inStream, 8));
+ $totalDisks = unpack('V', fread($this->inStream, 4))[1];
+
+ if ($diskNo !== 0 || $totalDisks > 1) {
+ throw new ZipException('ZIP file spanning/splitting is not supported!');
+ }
+
+ return $zip64ECDPos;
+ }
+
+ /**
+ * Read zip64 end of central directory locator and zip64 end
+ * of central directory record.
+ *
+ * zip64 end of central dir
+ * signature 4 bytes (0x06064b50)
+ * size of zip64 end of central
+ * directory record 8 bytes
+ * version made by 2 bytes
+ * version needed to extract 2 bytes
+ * number of this disk 4 bytes
+ * number of the disk with the
+ * start of the central directory 4 bytes
+ * total number of entries in the
+ * central directory on this disk 8 bytes
+ * total number of entries in the
+ * central directory 8 bytes
+ * size of the central directory 8 bytes
+ * offset of start of central
+ * directory with respect to
+ * the starting disk number 8 bytes
+ * zip64 extensible data sector (variable size)
+ *
+ * @param int $zip64ECDPosition
+ *
+ * @throws ZipException
+ *
+ * @return EndOfCentralDirectory
+ */
+ protected function readZip64EndOfCentralDirectory($zip64ECDPosition)
+ {
+ fseek($this->inStream, $zip64ECDPosition);
+
+ $buffer = fread($this->inStream, ZipConstants::ZIP64_END_OF_CD_LEN);
+
+ if (unpack('V', $buffer)[1] !== ZipConstants::ZIP64_END_CD) {
+ throw new ZipException('Expected ZIP64 End Of Central Directory Record!');
+ }
+
+ $data = unpack(
+// 'Psize/vversionMadeBy/vextractVersion/' .
+ 'VdiskNo/VcdDiskNo',
+ substr($buffer, 16, 8)
+ );
+
+ $cdEntriesDisk = PackUtil::unpackLongLE(substr($buffer, 24, 8));
+ $entryCount = PackUtil::unpackLongLE(substr($buffer, 32, 8));
+ $cdSize = PackUtil::unpackLongLE(substr($buffer, 40, 8));
+ $cdPos = PackUtil::unpackLongLE(substr($buffer, 48, 8));
+
+// $platform = ZipPlatform::fromValue(($data['versionMadeBy'] & 0xFF00) >> 8);
+// $softwareVersion = $data['versionMadeBy'] & 0x00FF;
+
+ if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $entryCount !== $cdEntriesDisk) {
+ throw new ZipException('ZIP file spanning/splitting is not supported!');
+ }
+
+ if ($entryCount < 0 || $entryCount > 0x7fffffff) {
+ throw new ZipException('Total Number Of Entries In The Central Directory out of range!');
+ }
+
+ // skip zip64 extensible data sector (variable sizeEndCD)
+
+ return new EndOfCentralDirectory(
+ $entryCount,
+ $cdPos,
+ $cdSize,
+ true
+ );
+ }
+
+ /**
+ * Reads the central directory from the given seekable byte channel
+ * and populates the internal tables with ZipEntry instances.
+ *
+ * The ZipEntry's will know all data that can be obtained from the
+ * central directory alone, but not the data that requires the local
+ * file header or additional data to be read.
+ *
+ * @param EndOfCentralDirectory $endCD
+ *
+ * @throws ZipException
+ *
+ * @return ZipEntry[]
+ */
+ protected function readCentralDirectory(EndOfCentralDirectory $endCD)
+ {
+ $entries = [];
+
+ $cdOffset = $endCD->getCdOffset();
+ fseek($this->inStream, $cdOffset);
+
+ if (!($cdStream = fopen('php://temp', 'w+b'))) {
+ // @codeCoverageIgnoreStart
+ throw new ZipException('A temporary resource cannot be opened for writing.');
+ // @codeCoverageIgnoreEnd
+ }
+ stream_copy_to_stream($this->inStream, $cdStream, $endCD->getCdSize());
+ rewind($cdStream);
+ for ($numEntries = $endCD->getEntryCount(); $numEntries > 0; $numEntries--) {
+ $zipEntry = $this->readZipEntry($cdStream);
+
+ $entryName = $zipEntry->getName();
+
+ /** @var UnicodePathExtraField|null $unicodePathExtraField */
+ $unicodePathExtraField = $zipEntry->getExtraField(UnicodePathExtraField::HEADER_ID);
+
+ if ($unicodePathExtraField !== null && $unicodePathExtraField->getCrc32() === crc32($entryName)) {
+ $unicodePath = $unicodePathExtraField->getUnicodeValue();
+
+ if ($unicodePath !== null) {
+ $unicodePath = str_replace('\\', '/', $unicodePath);
+
+ if (
+ $unicodePath !== '' &&
+ substr_count($entryName, '/') === substr_count($unicodePath, '/')
+ ) {
+ $entryName = $unicodePath;
+ }
+ }
+ }
+
+ $entries[$entryName] = $zipEntry;
+ }
+
+ return $entries;
+ }
+
+ /**
+ * Read central directory entry.
+ *
+ * central file header signature 4 bytes (0x02014b50)
+ * version made by 2 bytes
+ * version needed to extract 2 bytes
+ * general purpose bit flag 2 bytes
+ * compression method 2 bytes
+ * last mod file time 2 bytes
+ * last mod file date 2 bytes
+ * crc-32 4 bytes
+ * compressed size 4 bytes
+ * uncompressed size 4 bytes
+ * file name length 2 bytes
+ * extra field length 2 bytes
+ * file comment length 2 bytes
+ * disk number start 2 bytes
+ * internal file attributes 2 bytes
+ * external file attributes 4 bytes
+ * relative offset of local header 4 bytes
+ *
+ * file name (variable size)
+ * extra field (variable size)
+ * file comment (variable size)
+ *
+ * @param resource $stream
+ *
+ * @throws ZipException
+ *
+ * @return ZipEntry
+ */
+ protected function readZipEntry($stream)
+ {
+ if (unpack('V', fread($stream, 4))[1] !== ZipConstants::CENTRAL_FILE_HEADER) {
+ throw new ZipException('Corrupt zip file. Cannot read zip entry.');
+ }
+
+ $unpack = unpack(
+ 'vversionMadeBy/vversionNeededToExtract/' .
+ 'vgeneralPurposeBitFlag/vcompressionMethod/' .
+ 'VlastModFile/Vcrc/VcompressedSize/' .
+ 'VuncompressedSize/vfileNameLength/vextraFieldLength/' .
+ 'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/' .
+ 'VexternalFileAttributes/VoffsetLocalHeader',
+ fread($stream, 42)
+ );
+
+ if ($unpack['diskNumberStart'] !== 0) {
+ throw new ZipException('ZIP file spanning/splitting is not supported!');
+ }
+
+ $generalPurposeBitFlags = $unpack['generalPurposeBitFlag'];
+ $isUtf8 = ($generalPurposeBitFlags & GeneralPurposeBitFlag::UTF8) !== 0;
+
+ $name = fread($stream, $unpack['fileNameLength']);
+
+ $createdOS = ($unpack['versionMadeBy'] & 0xFF00) >> 8;
+ $softwareVersion = $unpack['versionMadeBy'] & 0x00FF;
+
+ $extractedOS = ($unpack['versionNeededToExtract'] & 0xFF00) >> 8;
+ $extractVersion = $unpack['versionNeededToExtract'] & 0x00FF;
+
+ $dosTime = $unpack['lastModFile'];
+
+ $comment = null;
+
+ if ($unpack['fileCommentLength'] > 0) {
+ $comment = fread($stream, $unpack['fileCommentLength']);
+ }
+
+ // decode code page names
+ $fallbackCharset = null;
+
+ if (!$isUtf8 && isset($this->options[ZipOptions::CHARSET])) {
+ $charset = $this->options[ZipOptions::CHARSET];
+
+ $fallbackCharset = $charset;
+ $name = DosCodePage::toUTF8($name, $charset);
+
+ if ($comment !== null) {
+ $comment = DosCodePage::toUTF8($comment, $charset);
+ }
+ }
+
+ $zipEntry = ZipEntry::create(
+ $name,
+ $createdOS,
+ $extractedOS,
+ $softwareVersion,
+ $extractVersion,
+ $unpack['compressionMethod'],
+ $generalPurposeBitFlags,
+ $dosTime,
+ $unpack['crc'],
+ $unpack['compressedSize'],
+ $unpack['uncompressedSize'],
+ $unpack['internalFileAttributes'],
+ $unpack['externalFileAttributes'],
+ $unpack['offsetLocalHeader'],
+ $comment,
+ $fallbackCharset
+ );
+
+ if ($unpack['extraFieldLength'] > 0) {
+ $this->parseExtraFields(
+ fread($stream, $unpack['extraFieldLength']),
+ $zipEntry,
+ false
+ );
+
+ /** @var Zip64ExtraField|null $extraZip64 */
+ $extraZip64 = $zipEntry->getCdExtraField(Zip64ExtraField::HEADER_ID);
+
+ if ($extraZip64 !== null) {
+ $this->handleZip64Extra($extraZip64, $zipEntry);
+ }
+ }
+
+ $this->loadLocalExtraFields($zipEntry);
+ $this->handleExtraEncryptionFields($zipEntry);
+ $this->handleExtraFields($zipEntry);
+
+ return $zipEntry;
+ }
+
+ /**
+ * @param string $buffer
+ * @param ZipEntry $zipEntry
+ * @param bool $local
+ *
+ * @return ExtraFieldsCollection
+ */
+ protected function parseExtraFields($buffer, ZipEntry $zipEntry, $local = false)
+ {
+ $collection = $local ?
+ $zipEntry->getLocalExtraFields() :
+ $zipEntry->getCdExtraFields();
+
+ if (!empty($buffer)) {
+ $pos = 0;
+ $endPos = \strlen($buffer);
+
+ while ($endPos - $pos >= 4) {
+ /** @var int[] $data */
+ $data = unpack('vheaderId/vdataSize', substr($buffer, $pos, 4));
+ $pos += 4;
+
+ if ($endPos - $pos - $data['dataSize'] < 0) {
+ break;
+ }
+ $bufferData = substr($buffer, $pos, $data['dataSize']);
+ $headerId = $data['headerId'];
+
+ /** @var string|ZipExtraField|null $className */
+ $className = ZipExtraDriver::getClassNameOrNull($headerId);
+
+ try {
+ if ($className !== null) {
+ try {
+ $extraField = $local ?
+ \call_user_func([$className, 'unpackLocalFileData'], $bufferData, $zipEntry) :
+ \call_user_func([$className, 'unpackCentralDirData'], $bufferData, $zipEntry);
+ } catch (\Throwable $e) {
+ // skip errors while parsing invalid data
+ continue;
+ }
+ } else {
+ $extraField = new UnrecognizedExtraField($headerId, $bufferData);
+ }
+ $collection->add($extraField);
+ } finally {
+ $pos += $data['dataSize'];
+ }
+ }
+ }
+
+ return $collection;
+ }
+
+ /**
+ * @param Zip64ExtraField $extraZip64
+ * @param ZipEntry $zipEntry
+ */
+ protected function handleZip64Extra(Zip64ExtraField $extraZip64, ZipEntry $zipEntry)
+ {
+ $uncompressedSize = $extraZip64->getUncompressedSize();
+ $compressedSize = $extraZip64->getCompressedSize();
+ $localHeaderOffset = $extraZip64->getLocalHeaderOffset();
+
+ if ($uncompressedSize !== null) {
+ $zipEntry->setUncompressedSize($uncompressedSize);
+ }
+
+ if ($compressedSize !== null) {
+ $zipEntry->setCompressedSize($compressedSize);
+ }
+
+ if ($localHeaderOffset !== null) {
+ $zipEntry->setLocalHeaderOffset($localHeaderOffset);
+ }
+ }
+
+ /**
+ * Read Local File Header.
+ *
+ * local file header signature 4 bytes (0x04034b50)
+ * version needed to extract 2 bytes
+ * general purpose bit flag 2 bytes
+ * compression method 2 bytes
+ * last mod file time 2 bytes
+ * last mod file date 2 bytes
+ * crc-32 4 bytes
+ * compressed size 4 bytes
+ * uncompressed size 4 bytes
+ * file name length 2 bytes
+ * extra field length 2 bytes
+ * file name (variable size)
+ * extra field (variable size)
+ *
+ * @param ZipEntry $entry
+ *
+ * @throws ZipException
+ */
+ protected function loadLocalExtraFields(ZipEntry $entry)
+ {
+ $offsetLocalHeader = $entry->getLocalHeaderOffset();
+
+ fseek($this->inStream, $offsetLocalHeader);
+
+ if (unpack('V', fread($this->inStream, 4))[1] !== ZipConstants::LOCAL_FILE_HEADER) {
+ throw new ZipException(sprintf('%s (expected Local File Header)', $entry->getName()));
+ }
+
+ fseek($this->inStream, $offsetLocalHeader + ZipConstants::LFH_FILENAME_LENGTH_POS);
+ $unpack = unpack('vfileNameLength/vextraFieldLength', fread($this->inStream, 4));
+ $offsetData = ftell($this->inStream)
+ + $unpack['fileNameLength']
+ + $unpack['extraFieldLength'];
+
+ fseek($this->inStream, $unpack['fileNameLength'], \SEEK_CUR);
+
+ if ($unpack['extraFieldLength'] > 0) {
+ $this->parseExtraFields(
+ fread($this->inStream, $unpack['extraFieldLength']),
+ $entry,
+ true
+ );
+ }
+
+ $zipData = new ZipSourceFileData($this, $entry, $offsetData);
+ $entry->setData($zipData);
+ }
+
+ /**
+ * @param ZipEntry $zipEntry
+ *
+ * @throws ZipException
+ */
+ private function handleExtraEncryptionFields(ZipEntry $zipEntry)
+ {
+ if ($zipEntry->isEncrypted()) {
+ if ($zipEntry->getCompressionMethod() === ZipCompressionMethod::WINZIP_AES) {
+ /** @var WinZipAesExtraField|null $extraField */
+ $extraField = $zipEntry->getExtraField(WinZipAesExtraField::HEADER_ID);
+
+ if ($extraField === null) {
+ throw new ZipException(
+ sprintf(
+ 'Extra field 0x%04x (WinZip-AES Encryption) expected for compression method %d',
+ WinZipAesExtraField::HEADER_ID,
+ $zipEntry->getCompressionMethod()
+ )
+ );
+ }
+ $zipEntry->setCompressionMethod($extraField->getCompressionMethod());
+ $zipEntry->setEncryptionMethod($extraField->getEncryptionMethod());
+ } else {
+ $zipEntry->setEncryptionMethod(ZipEncryptionMethod::PKWARE);
+ }
+ }
+ }
+
+ /**
+ * Handle extra data in zip records.
+ *
+ * This is a special method in which you can process ExtraField
+ * and make changes to ZipEntry.
+ *
+ * @param ZipEntry $zipEntry
+ */
+ protected function handleExtraFields(ZipEntry $zipEntry)
+ {
+ }
+
+ /**
+ * @param ZipSourceFileData $zipFileData
+ *
+ * @throws ZipException
+ * @throws Crc32Exception
+ *
+ * @return resource
+ */
+ public function getEntryStream(ZipSourceFileData $zipFileData)
+ {
+ $outStream = fopen('php://temp', 'w+b');
+ $this->copyUncompressedDataToStream($zipFileData, $outStream);
+ rewind($outStream);
+
+ return $outStream;
+ }
+
+ /**
+ * @param ZipSourceFileData $zipFileData
+ * @param resource $outStream
+ *
+ * @throws Crc32Exception
+ * @throws ZipException
+ */
+ public function copyUncompressedDataToStream(ZipSourceFileData $zipFileData, $outStream)
+ {
+ if (!\is_resource($outStream)) {
+ throw new InvalidArgumentException('outStream is not resource');
+ }
+
+ $entry = $zipFileData->getSourceEntry();
+
+// if ($entry->isDirectory()) {
+// throw new InvalidArgumentException('Streams not supported for directories');
+// }
+
+ if ($entry->isStrongEncryption()) {
+ throw new ZipException('Not support encryption zip.');
+ }
+
+ $compressionMethod = $entry->getCompressionMethod();
+
+ fseek($this->inStream, $zipFileData->getOffset());
+
+ $filters = [];
+
+ $skipCheckCrc = false;
+ $isEncrypted = $entry->isEncrypted();
+
+ if ($isEncrypted) {
+ if ($entry->getPassword() === null) {
+ throw new ZipException('Can not password from entry ' . $entry->getName());
+ }
+
+ if (ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) {
+ /** @var WinZipAesExtraField|null $winZipAesExtra */
+ $winZipAesExtra = $entry->getExtraField(WinZipAesExtraField::HEADER_ID);
+
+ if ($winZipAesExtra === null) {
+ throw new ZipException(
+ sprintf('WinZip AES must contain the extra field %s', WinZipAesExtraField::HEADER_ID)
+ );
+ }
+ $compressionMethod = $winZipAesExtra->getCompressionMethod();
+
+ WinZipAesDecryptionStreamFilter::register();
+ $cipherFilterName = WinZipAesDecryptionStreamFilter::FILTER_NAME;
+
+ if ($winZipAesExtra->isV2()) {
+ $skipCheckCrc = true;
+ }
+ } else {
+ PKDecryptionStreamFilter::register();
+ $cipherFilterName = PKDecryptionStreamFilter::FILTER_NAME;
+ }
+ $encContextFilter = stream_filter_append(
+ $this->inStream,
+ $cipherFilterName,
+ \STREAM_FILTER_READ,
+ [
+ 'entry' => $entry,
+ ]
+ );
+
+ if (!$encContextFilter) {
+ throw new \RuntimeException('Not apply filter ' . $cipherFilterName);
+ }
+ $filters[] = $encContextFilter;
+ }
+
+ // hack, see https://groups.google.com/forum/#!topic/alt.comp.lang.php/37_JZeW63uc
+ $pos = ftell($this->inStream);
+ rewind($this->inStream);
+ fseek($this->inStream, $pos);
+
+ $contextDecompress = null;
+ switch ($compressionMethod) {
+ case ZipCompressionMethod::STORED:
+ // file without compression, do nothing
+ break;
+
+ case ZipCompressionMethod::DEFLATED:
+ if (!($contextDecompress = stream_filter_append(
+ $this->inStream,
+ 'zlib.inflate',
+ \STREAM_FILTER_READ
+ ))) {
+ throw new \RuntimeException('Could not append filter "zlib.inflate" to stream');
+ }
+ $filters[] = $contextDecompress;
+
+ break;
+
+ case ZipCompressionMethod::BZIP2:
+ if (!($contextDecompress = stream_filter_append(
+ $this->inStream,
+ 'bzip2.decompress',
+ \STREAM_FILTER_READ
+ ))) {
+ throw new \RuntimeException('Could not append filter "bzip2.decompress" to stream');
+ }
+ $filters[] = $contextDecompress;
+
+ break;
+
+ default:
+ throw new ZipException(
+ sprintf(
+ '%s (compression method %d (%s) is not supported)',
+ $entry->getName(),
+ $compressionMethod,
+ ZipCompressionMethod::getCompressionMethodName($compressionMethod)
+ )
+ );
+ }
+
+ $limit = $zipFileData->getUncompressedSize();
+
+ $offset = 0;
+ $chunkSize = 8192;
+
+ try {
+ if ($skipCheckCrc) {
+ while ($offset < $limit) {
+ $length = min($chunkSize, $limit - $offset);
+ $buffer = fread($this->inStream, $length);
+
+ if ($buffer === false) {
+ throw new ZipException(sprintf('Error reading the contents of entry "%s".', $entry->getName()));
+ }
+ fwrite($outStream, $buffer);
+ $offset += $length;
+ }
+ } else {
+ $contextHash = hash_init('crc32b');
+
+ while ($offset < $limit) {
+ $length = min($chunkSize, $limit - $offset);
+ $buffer = fread($this->inStream, $length);
+
+ if ($buffer === false) {
+ throw new ZipException(sprintf('Error reading the contents of entry "%s".', $entry->getName()));
+ }
+ fwrite($outStream, $buffer);
+ hash_update($contextHash, $buffer);
+ $offset += $length;
+ }
+
+ $expectedCrc = (int) hexdec(hash_final($contextHash));
+
+ if ($expectedCrc !== $entry->getCrc()) {
+ throw new Crc32Exception($entry->getName(), $expectedCrc, $entry->getCrc());
+ }
+ }
+ } finally {
+ for ($i = \count($filters); $i > 0; $i--) {
+ stream_filter_remove($filters[$i - 1]);
+ }
+ }
+ }
+
+ /**
+ * @param ZipSourceFileData $zipData
+ * @param resource $outStream
+ */
+ public function copyCompressedDataToStream(ZipSourceFileData $zipData, $outStream)
+ {
+ if ($zipData->getCompressedSize() > 0) {
+ fseek($this->inStream, $zipData->getOffset());
+ stream_copy_to_stream($this->inStream, $outStream, $zipData->getCompressedSize());
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ protected function isZip64Support()
+ {
+ return \PHP_INT_SIZE === 8; // true for 64bit system
+ }
+
+ public function close()
+ {
+ if (\is_resource($this->inStream)) {
+ fclose($this->inStream);
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+}
diff --git a/vendor/nelexa/zip/src/IO/ZipWriter.php b/vendor/nelexa/zip/src/IO/ZipWriter.php
new file mode 100644
index 0000000..b5cc8a8
--- /dev/null
+++ b/vendor/nelexa/zip/src/IO/ZipWriter.php
@@ -0,0 +1,886 @@
+zipContainer = clone $container;
+ }
+
+ /**
+ * @param resource $outStream
+ *
+ * @throws ZipException
+ */
+ public function write($outStream)
+ {
+ if (!\is_resource($outStream)) {
+ throw new \InvalidArgumentException('$outStream must be resource');
+ }
+ $this->beforeWrite();
+ $this->writeLocalBlock($outStream);
+ $cdOffset = ftell($outStream);
+ $this->writeCentralDirectoryBlock($outStream);
+ $cdSize = ftell($outStream) - $cdOffset;
+ $this->writeEndOfCentralDirectoryBlock($outStream, $cdOffset, $cdSize);
+ }
+
+ protected function beforeWrite()
+ {
+ }
+
+ /**
+ * @param resource $outStream
+ *
+ * @throws ZipException
+ */
+ protected function writeLocalBlock($outStream)
+ {
+ $zipEntries = $this->zipContainer->getEntries();
+
+ foreach ($zipEntries as $zipEntry) {
+ $this->writeLocalHeader($outStream, $zipEntry);
+ $this->writeData($outStream, $zipEntry);
+
+ if ($zipEntry->isDataDescriptorEnabled()) {
+ $this->writeDataDescriptor($outStream, $zipEntry);
+ }
+ }
+ }
+
+ /**
+ * @param resource $outStream
+ * @param ZipEntry $entry
+ *
+ * @throws ZipException
+ */
+ protected function writeLocalHeader($outStream, ZipEntry $entry)
+ {
+ // todo in 4.0 version move zipalign functional to ApkWriter class
+ if ($this->zipContainer->isZipAlign()) {
+ $this->zipAlign($outStream, $entry);
+ }
+
+ $relativeOffset = ftell($outStream);
+ $entry->setLocalHeaderOffset($relativeOffset);
+
+ if ($entry->isEncrypted() && $entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) {
+ $entry->enableDataDescriptor(true);
+ }
+
+ $dd = $entry->isDataDescriptorRequired() ||
+ $entry->isDataDescriptorEnabled();
+
+ $compressedSize = $entry->getCompressedSize();
+ $uncompressedSize = $entry->getUncompressedSize();
+
+ $entry->getLocalExtraFields()->remove(Zip64ExtraField::HEADER_ID);
+
+ if ($compressedSize > ZipConstants::ZIP64_MAGIC || $uncompressedSize > ZipConstants::ZIP64_MAGIC) {
+ $entry->getLocalExtraFields()->add(
+ new Zip64ExtraField($uncompressedSize, $compressedSize)
+ );
+
+ $compressedSize = ZipConstants::ZIP64_MAGIC;
+ $uncompressedSize = ZipConstants::ZIP64_MAGIC;
+ }
+
+ $compressionMethod = $entry->getCompressionMethod();
+ $crc = $entry->getCrc();
+
+ if ($entry->isEncrypted() && ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) {
+ /** @var WinZipAesExtraField|null $winZipAesExtra */
+ $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
+
+ if ($winZipAesExtra === null) {
+ $winZipAesExtra = WinZipAesExtraField::create($entry);
+ }
+
+ if ($winZipAesExtra->isV2()) {
+ $crc = 0;
+ }
+ $compressionMethod = ZipCompressionMethod::WINZIP_AES;
+ }
+
+ $extra = $this->getExtraFieldsContents($entry, true);
+ $name = $entry->getName();
+ $dosCharset = $entry->getCharset();
+
+ if ($dosCharset !== null && !$entry->isUtf8Flag()) {
+ $name = DosCodePage::fromUTF8($name, $dosCharset);
+ }
+
+ $nameLength = \strlen($name);
+ $extraLength = \strlen($extra);
+
+ $size = $nameLength + $extraLength;
+
+ if ($size > 0xffff) {
+ throw new ZipException(
+ sprintf(
+ '%s (the total size of %s bytes for the name, extra fields and comment exceeds the maximum size of %d bytes)',
+ $entry->getName(),
+ $size,
+ 0xffff
+ )
+ );
+ }
+
+ $extractedBy = ($entry->getExtractedOS() << 8) | $entry->getExtractVersion();
+
+ fwrite(
+ $outStream,
+ pack(
+ 'VvvvVVVVvv',
+ // local file header signature 4 bytes (0x04034b50)
+ ZipConstants::LOCAL_FILE_HEADER,
+ // version needed to extract 2 bytes
+ $extractedBy,
+ // general purpose bit flag 2 bytes
+ $entry->getGeneralPurposeBitFlags(),
+ // compression method 2 bytes
+ $compressionMethod,
+ // last mod file time 2 bytes
+ // last mod file date 2 bytes
+ $entry->getDosTime(),
+ // crc-32 4 bytes
+ $dd ? 0 : $crc,
+ // compressed size 4 bytes
+ $dd ? 0 : $compressedSize,
+ // uncompressed size 4 bytes
+ $dd ? 0 : $uncompressedSize,
+ // file name length 2 bytes
+ $nameLength,
+ // extra field length 2 bytes
+ $extraLength
+ )
+ );
+
+ if ($nameLength > 0) {
+ fwrite($outStream, $name);
+ }
+
+ if ($extraLength > 0) {
+ fwrite($outStream, $extra);
+ }
+ }
+
+ /**
+ * @param resource $outStream
+ * @param ZipEntry $entry
+ *
+ * @throws ZipException
+ */
+ private function zipAlign($outStream, ZipEntry $entry)
+ {
+ if (!$entry->isDirectory() && $entry->getCompressionMethod() === ZipCompressionMethod::STORED) {
+ $entry->removeExtraField(ApkAlignmentExtraField::HEADER_ID);
+
+ $extra = $this->getExtraFieldsContents($entry, true);
+ $extraLength = \strlen($extra);
+ $name = $entry->getName();
+
+ $dosCharset = $entry->getCharset();
+
+ if ($dosCharset !== null && !$entry->isUtf8Flag()) {
+ $name = DosCodePage::fromUTF8($name, $dosCharset);
+ }
+ $nameLength = \strlen($name);
+
+ $multiple = ApkAlignmentExtraField::ALIGNMENT_BYTES;
+
+ if (StringUtil::endsWith($name, '.so')) {
+ $multiple = ApkAlignmentExtraField::COMMON_PAGE_ALIGNMENT_BYTES;
+ }
+
+ $offset = ftell($outStream);
+
+ $dataMinStartOffset =
+ $offset +
+ ZipConstants::LFH_FILENAME_POS +
+ $extraLength +
+ $nameLength;
+
+ $padding =
+ ($multiple - ($dataMinStartOffset % $multiple))
+ % $multiple;
+
+ if ($padding > 0) {
+ $dataMinStartOffset += ApkAlignmentExtraField::MIN_SIZE;
+ $padding =
+ ($multiple - ($dataMinStartOffset % $multiple))
+ % $multiple;
+
+ $entry->getLocalExtraFields()->add(
+ new ApkAlignmentExtraField($multiple, $padding)
+ );
+ }
+ }
+ }
+
+ /**
+ * Merges the local file data fields of the given ZipExtraFields.
+ *
+ * @param ZipEntry $entry
+ * @param bool $local
+ *
+ * @throws ZipException
+ *
+ * @return string
+ */
+ protected function getExtraFieldsContents(ZipEntry $entry, $local)
+ {
+ $local = (bool) $local;
+ $collection = $local ?
+ $entry->getLocalExtraFields() :
+ $entry->getCdExtraFields();
+ $extraData = '';
+
+ foreach ($collection as $extraField) {
+ if ($local) {
+ $data = $extraField->packLocalFileData();
+ } else {
+ $data = $extraField->packCentralDirData();
+ }
+ $extraData .= pack(
+ 'vv',
+ $extraField->getHeaderId(),
+ \strlen($data)
+ );
+ $extraData .= $data;
+ }
+
+ $size = \strlen($extraData);
+
+ if ($size > 0xffff) {
+ throw new ZipException(
+ sprintf(
+ 'Size extra out of range: %d. Extra data: %s',
+ $size,
+ $extraData
+ )
+ );
+ }
+
+ return $extraData;
+ }
+
+ /**
+ * @param resource $outStream
+ * @param ZipEntry $entry
+ *
+ * @throws ZipException
+ */
+ protected function writeData($outStream, ZipEntry $entry)
+ {
+ $zipData = $entry->getData();
+
+ if ($zipData === null) {
+ if ($entry->isDirectory()) {
+ return;
+ }
+
+ throw new ZipException(sprintf('No zip data for entry "%s"', $entry->getName()));
+ }
+
+ // data write variants:
+ // --------------------
+ // * data of source zip file -> copy compressed data
+ // * store - simple write
+ // * store and encryption - apply encryption filter and simple write
+ // * deflate or bzip2 - apply compression filter and simple write
+ // * (deflate or bzip2) and encryption - create temp stream and apply
+ // compression filter to it, then apply encryption filter to root
+ // stream and write temp stream data.
+ // (PHP cannot apply the filter for encryption after the compression
+ // filter, so a temporary stream is created for the compressed data)
+
+ if ($zipData instanceof ZipSourceFileData && !$zipData->hasRecompressData($entry)) {
+ // data of source zip file -> copy compressed data
+ $zipData->copyCompressedDataToStream($outStream);
+
+ return;
+ }
+
+ $entryStream = $zipData->getDataAsStream();
+
+ if (stream_get_meta_data($entryStream)['seekable']) {
+ rewind($entryStream);
+ }
+
+ $uncompressedSize = $entry->getUncompressedSize();
+
+ $posBeforeWrite = ftell($outStream);
+ $compressionMethod = $entry->getCompressionMethod();
+
+ if ($entry->isEncrypted()) {
+ if ($compressionMethod === ZipCompressionMethod::STORED) {
+ $contextFilter = $this->appendEncryptionFilter($outStream, $entry, $uncompressedSize);
+ $checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize);
+ } else {
+ $compressStream = fopen('php://temp', 'w+b');
+ $contextFilter = $this->appendCompressionFilter($compressStream, $entry);
+ $checksum = $this->writeAndCountChecksum($entryStream, $compressStream, $uncompressedSize);
+
+ if ($contextFilter !== null) {
+ stream_filter_remove($contextFilter);
+ $contextFilter = null;
+ }
+
+ rewind($compressStream);
+
+ $compressedSize = fstat($compressStream)['size'];
+ $contextFilter = $this->appendEncryptionFilter($outStream, $entry, $compressedSize);
+
+ stream_copy_to_stream($compressStream, $outStream);
+ }
+ } else {
+ $contextFilter = $this->appendCompressionFilter($outStream, $entry);
+ $checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize);
+ }
+
+ if ($contextFilter !== null) {
+ stream_filter_remove($contextFilter);
+ $contextFilter = null;
+ }
+
+ // my hack {@see https://bugs.php.net/bug.php?id=49874}
+ fseek($outStream, 0, \SEEK_END);
+ $compressedSize = ftell($outStream) - $posBeforeWrite;
+
+ $entry->setCompressedSize($compressedSize);
+ $entry->setCrc($checksum);
+
+ if (!$entry->isDataDescriptorEnabled()) {
+ if ($uncompressedSize > ZipConstants::ZIP64_MAGIC || $compressedSize > ZipConstants::ZIP64_MAGIC) {
+ /** @var Zip64ExtraField|null $zip64ExtraLocal */
+ $zip64ExtraLocal = $entry->getLocalExtraField(Zip64ExtraField::HEADER_ID);
+
+ // if there is a zip64 extra record, then update it;
+ // if not, write data to data descriptor
+ if ($zip64ExtraLocal !== null) {
+ $zip64ExtraLocal->setCompressedSize($compressedSize);
+ $zip64ExtraLocal->setUncompressedSize($uncompressedSize);
+
+ $posExtra = $entry->getLocalHeaderOffset() + ZipConstants::LFH_FILENAME_POS + \strlen($entry->getName());
+ fseek($outStream, $posExtra);
+ fwrite($outStream, $this->getExtraFieldsContents($entry, true));
+ } else {
+ $posGPBF = $entry->getLocalHeaderOffset() + 6;
+ $entry->enableDataDescriptor(true);
+ fseek($outStream, $posGPBF);
+ fwrite(
+ $outStream,
+ pack(
+ 'v',
+ // general purpose bit flag 2 bytes
+ $entry->getGeneralPurposeBitFlags()
+ )
+ );
+ }
+
+ $compressedSize = ZipConstants::ZIP64_MAGIC;
+ $uncompressedSize = ZipConstants::ZIP64_MAGIC;
+ }
+
+ $posChecksum = $entry->getLocalHeaderOffset() + 14;
+
+ /** @var WinZipAesExtraField|null $winZipAesExtra */
+ $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
+
+ if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) {
+ $checksum = 0;
+ }
+
+ fseek($outStream, $posChecksum);
+ fwrite(
+ $outStream,
+ pack(
+ 'VVV',
+ // crc-32 4 bytes
+ $checksum,
+ // compressed size 4 bytes
+ $compressedSize,
+ // uncompressed size 4 bytes
+ $uncompressedSize
+ )
+ );
+ fseek($outStream, 0, \SEEK_END);
+ }
+ }
+
+ /**
+ * @param resource $inStream
+ * @param resource $outStream
+ * @param int $size
+ *
+ * @return int
+ */
+ private function writeAndCountChecksum($inStream, $outStream, $size)
+ {
+ $contextHash = hash_init('crc32b');
+ $offset = 0;
+
+ while ($offset < $size) {
+ $read = min(self::CHUNK_SIZE, $size - $offset);
+ $buffer = fread($inStream, $read);
+ fwrite($outStream, $buffer);
+ hash_update($contextHash, $buffer);
+ $offset += $read;
+ }
+
+ return (int) hexdec(hash_final($contextHash));
+ }
+
+ /**
+ * @param resource $outStream
+ * @param ZipEntry $entry
+ *
+ * @throws ZipUnsupportMethodException
+ *
+ * @return resource|null
+ */
+ protected function appendCompressionFilter($outStream, ZipEntry $entry)
+ {
+ $contextCompress = null;
+ switch ($entry->getCompressionMethod()) {
+ case ZipCompressionMethod::DEFLATED:
+ if (!($contextCompress = stream_filter_append(
+ $outStream,
+ 'zlib.deflate',
+ \STREAM_FILTER_WRITE,
+ ['level' => $entry->getCompressionLevel()]
+ ))) {
+ throw new \RuntimeException('Could not append filter "zlib.deflate" to out stream');
+ }
+ break;
+
+ case ZipCompressionMethod::BZIP2:
+ if (!($contextCompress = stream_filter_append(
+ $outStream,
+ 'bzip2.compress',
+ \STREAM_FILTER_WRITE,
+ ['blocks' => $entry->getCompressionLevel(), 'work' => 0]
+ ))) {
+ throw new \RuntimeException('Could not append filter "bzip2.compress" to out stream');
+ }
+ break;
+
+ case ZipCompressionMethod::STORED:
+ // file without compression, do nothing
+ break;
+
+ default:
+ throw new ZipUnsupportMethodException(
+ sprintf(
+ '%s (compression method %d (%s) is not supported)',
+ $entry->getName(),
+ $entry->getCompressionMethod(),
+ ZipCompressionMethod::getCompressionMethodName($entry->getCompressionMethod())
+ )
+ );
+ }
+
+ return $contextCompress;
+ }
+
+ /**
+ * @param resource $outStream
+ * @param ZipEntry $entry
+ * @param int $size
+ *
+ * @return resource|null
+ */
+ protected function appendEncryptionFilter($outStream, ZipEntry $entry, $size)
+ {
+ $encContextFilter = null;
+
+ if ($entry->isEncrypted()) {
+ if ($entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) {
+ PKEncryptionStreamFilter::register();
+ $cipherFilterName = PKEncryptionStreamFilter::FILTER_NAME;
+ } else {
+ WinZipAesEncryptionStreamFilter::register();
+ $cipherFilterName = WinZipAesEncryptionStreamFilter::FILTER_NAME;
+ }
+ $encContextFilter = stream_filter_append(
+ $outStream,
+ $cipherFilterName,
+ \STREAM_FILTER_WRITE,
+ [
+ 'entry' => $entry,
+ 'size' => $size,
+ ]
+ );
+
+ if (!$encContextFilter) {
+ throw new \RuntimeException('Not apply filter ' . $cipherFilterName);
+ }
+ }
+
+ return $encContextFilter;
+ }
+
+ /**
+ * @param resource $outStream
+ * @param ZipEntry $entry
+ */
+ protected function writeDataDescriptor($outStream, ZipEntry $entry)
+ {
+ $crc = $entry->getCrc();
+
+ /** @var WinZipAesExtraField|null $winZipAesExtra */
+ $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
+
+ if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) {
+ $crc = 0;
+ }
+
+ fwrite(
+ $outStream,
+ pack(
+ 'VV',
+ // data descriptor signature 4 bytes (0x08074b50)
+ ZipConstants::DATA_DESCRIPTOR,
+ // crc-32 4 bytes
+ $crc
+ )
+ );
+
+ if (
+ $entry->isZip64ExtensionsRequired() ||
+ $entry->getLocalExtraFields()->has(Zip64ExtraField::HEADER_ID)
+ ) {
+ $dd =
+ // compressed size 8 bytes
+ PackUtil::packLongLE($entry->getCompressedSize()) .
+ // uncompressed size 8 bytes
+ PackUtil::packLongLE($entry->getUncompressedSize());
+ } else {
+ $dd = pack(
+ 'VV',
+ // compressed size 4 bytes
+ $entry->getCompressedSize(),
+ // uncompressed size 4 bytes
+ $entry->getUncompressedSize()
+ );
+ }
+
+ fwrite($outStream, $dd);
+ }
+
+ /**
+ * @param resource $outStream
+ *
+ * @throws ZipException
+ */
+ protected function writeCentralDirectoryBlock($outStream)
+ {
+ foreach ($this->zipContainer->getEntries() as $outputEntry) {
+ $this->writeCentralDirectoryHeader($outStream, $outputEntry);
+ }
+ }
+
+ /**
+ * Writes a Central File Header record.
+ *
+ * @param resource $outStream
+ * @param ZipEntry $entry
+ *
+ * @throws ZipException
+ */
+ protected function writeCentralDirectoryHeader($outStream, ZipEntry $entry)
+ {
+ $compressedSize = $entry->getCompressedSize();
+ $uncompressedSize = $entry->getUncompressedSize();
+ $localHeaderOffset = $entry->getLocalHeaderOffset();
+
+ $entry->getCdExtraFields()->remove(Zip64ExtraField::HEADER_ID);
+
+ if (
+ $localHeaderOffset > ZipConstants::ZIP64_MAGIC ||
+ $compressedSize > ZipConstants::ZIP64_MAGIC ||
+ $uncompressedSize > ZipConstants::ZIP64_MAGIC
+ ) {
+ $zip64ExtraField = new Zip64ExtraField();
+
+ if ($uncompressedSize >= ZipConstants::ZIP64_MAGIC) {
+ $zip64ExtraField->setUncompressedSize($uncompressedSize);
+ $uncompressedSize = ZipConstants::ZIP64_MAGIC;
+ }
+
+ if ($compressedSize >= ZipConstants::ZIP64_MAGIC) {
+ $zip64ExtraField->setCompressedSize($compressedSize);
+ $compressedSize = ZipConstants::ZIP64_MAGIC;
+ }
+
+ if ($localHeaderOffset >= ZipConstants::ZIP64_MAGIC) {
+ $zip64ExtraField->setLocalHeaderOffset($localHeaderOffset);
+ $localHeaderOffset = ZipConstants::ZIP64_MAGIC;
+ }
+
+ $entry->getCdExtraFields()->add($zip64ExtraField);
+ }
+
+ $extra = $this->getExtraFieldsContents($entry, false);
+ $extraLength = \strlen($extra);
+
+ $name = $entry->getName();
+ $comment = $entry->getComment();
+
+ $dosCharset = $entry->getCharset();
+
+ if ($dosCharset !== null && !$entry->isUtf8Flag()) {
+ $name = DosCodePage::fromUTF8($name, $dosCharset);
+
+ if ($comment) {
+ $comment = DosCodePage::fromUTF8($comment, $dosCharset);
+ }
+ }
+
+ $commentLength = \strlen($comment);
+
+ $compressionMethod = $entry->getCompressionMethod();
+ $crc = $entry->getCrc();
+
+ /** @var WinZipAesExtraField|null $winZipAesExtra */
+ $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
+
+ if ($winZipAesExtra !== null) {
+ if ($winZipAesExtra->isV2()) {
+ $crc = 0;
+ }
+ $compressionMethod = ZipCompressionMethod::WINZIP_AES;
+ }
+
+ fwrite(
+ $outStream,
+ pack(
+ 'VvvvvVVVVvvvvvVV',
+ // central file header signature 4 bytes (0x02014b50)
+ ZipConstants::CENTRAL_FILE_HEADER,
+ // version made by 2 bytes
+ ($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(),
+ // version needed to extract 2 bytes
+ ($entry->getExtractedOS() << 8) | $entry->getExtractVersion(),
+ // general purpose bit flag 2 bytes
+ $entry->getGeneralPurposeBitFlags(),
+ // compression method 2 bytes
+ $compressionMethod,
+ // last mod file datetime 4 bytes
+ $entry->getDosTime(),
+ // crc-32 4 bytes
+ $crc,
+ // compressed size 4 bytes
+ $compressedSize,
+ // uncompressed size 4 bytes
+ $uncompressedSize,
+ // file name length 2 bytes
+ \strlen($name),
+ // extra field length 2 bytes
+ $extraLength,
+ // file comment length 2 bytes
+ $commentLength,
+ // disk number start 2 bytes
+ 0,
+ // internal file attributes 2 bytes
+ $entry->getInternalAttributes(),
+ // external file attributes 4 bytes
+ $entry->getExternalAttributes(),
+ // relative offset of local header 4 bytes
+ $localHeaderOffset
+ )
+ );
+
+ // file name (variable size)
+ fwrite($outStream, $name);
+
+ if ($extraLength > 0) {
+ // extra field (variable size)
+ fwrite($outStream, $extra);
+ }
+
+ if ($commentLength > 0) {
+ // file comment (variable size)
+ fwrite($outStream, $comment);
+ }
+ }
+
+ /**
+ * @param resource $outStream
+ * @param int $centralDirectoryOffset
+ * @param int $centralDirectorySize
+ */
+ protected function writeEndOfCentralDirectoryBlock(
+ $outStream,
+ $centralDirectoryOffset,
+ $centralDirectorySize
+ ) {
+ $cdEntriesCount = \count($this->zipContainer);
+
+ $cdEntriesZip64 = $cdEntriesCount > 0xffff;
+ $cdSizeZip64 = $centralDirectorySize > ZipConstants::ZIP64_MAGIC;
+ $cdOffsetZip64 = $centralDirectoryOffset > ZipConstants::ZIP64_MAGIC;
+
+ $zip64Required = $cdEntriesZip64
+ || $cdSizeZip64
+ || $cdOffsetZip64;
+
+ if ($zip64Required) {
+ $zip64EndOfCentralDirectoryOffset = ftell($outStream);
+
+ // find max software version, version needed to extract and most common platform
+ list($softwareVersion, $versionNeededToExtract) = array_reduce(
+ $this->zipContainer->getEntries(),
+ static function (array $carry, ZipEntry $entry) {
+ $carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF);
+ $carry[1] = max($carry[1], $entry->getExtractVersion() & 0xFF);
+
+ return $carry;
+ },
+ [ZipVersion::v10_DEFAULT_MIN, ZipVersion::v45_ZIP64_EXT]
+ );
+
+ $createdOS = $extractedOS = ZipPlatform::OS_DOS;
+ $versionMadeBy = ($createdOS << 8) | max($softwareVersion, ZipVersion::v45_ZIP64_EXT);
+ $versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, ZipVersion::v45_ZIP64_EXT);
+
+ // write zip64 end of central directory signature
+ fwrite(
+ $outStream,
+ pack(
+ 'V',
+ // signature 4 bytes (0x06064b50)
+ ZipConstants::ZIP64_END_CD
+ )
+ );
+ // size of zip64 end of central
+ // directory record 8 bytes
+ fwrite($outStream, PackUtil::packLongLE(ZipConstants::ZIP64_END_OF_CD_LEN - 12));
+ fwrite(
+ $outStream,
+ pack(
+ 'vvVV',
+ // version made by 2 bytes
+ $versionMadeBy & 0xFFFF,
+ // version needed to extract 2 bytes
+ $versionExtractedBy & 0xFFFF,
+ // number of this disk 4 bytes
+ 0,
+ // number of the disk with the
+ // start of the central directory 4 bytes
+ 0
+ )
+ );
+
+ fwrite(
+ $outStream,
+ // total number of entries in the
+ // central directory on this disk 8 bytes
+ PackUtil::packLongLE($cdEntriesCount) .
+ // total number of entries in the
+ // central directory 8 bytes
+ PackUtil::packLongLE($cdEntriesCount) .
+ // size of the central directory 8 bytes
+ PackUtil::packLongLE($centralDirectorySize) .
+ // offset of start of central
+ // directory with respect to
+ // the starting disk number 8 bytes
+ PackUtil::packLongLE($centralDirectoryOffset)
+ );
+
+ // write zip64 end of central directory locator
+ fwrite(
+ $outStream,
+ pack(
+ 'VV',
+ // zip64 end of central dir locator
+ // signature 4 bytes (0x07064b50)
+ ZipConstants::ZIP64_END_CD_LOC,
+ // number of the disk with the
+ // start of the zip64 end of
+ // central directory 4 bytes
+ 0
+ ) .
+ // relative offset of the zip64
+ // end of central directory record 8 bytes
+ PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset) .
+ // total number of disks 4 bytes
+ pack('V', 1)
+ );
+ }
+
+ $comment = $this->zipContainer->getArchiveComment();
+ $commentLength = $comment !== null ? \strlen($comment) : 0;
+
+ fwrite(
+ $outStream,
+ pack(
+ 'VvvvvVVv',
+ // end of central dir signature 4 bytes (0x06054b50)
+ ZipConstants::END_CD,
+ // number of this disk 2 bytes
+ 0,
+ // number of the disk with the
+ // start of the central directory 2 bytes
+ 0,
+ // total number of entries in the
+ // central directory on this disk 2 bytes
+ $cdEntriesZip64 ? 0xffff : $cdEntriesCount,
+ // total number of entries in
+ // the central directory 2 bytes
+ $cdEntriesZip64 ? 0xffff : $cdEntriesCount,
+ // size of the central directory 4 bytes
+ $cdSizeZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectorySize,
+ // offset of start of central
+ // directory with respect to
+ // the starting disk number 4 bytes
+ $cdOffsetZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectoryOffset,
+ // .ZIP file comment length 2 bytes
+ $commentLength
+ )
+ );
+
+ if ($comment !== null && $commentLength > 0) {
+ // .ZIP file comment (variable size)
+ fwrite($outStream, $comment);
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Data/ZipFileData.php b/vendor/nelexa/zip/src/Model/Data/ZipFileData.php
new file mode 100644
index 0000000..43590c5
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Data/ZipFileData.php
@@ -0,0 +1,81 @@
+isFile()) {
+ throw new ZipException('$fileInfo is not a file.');
+ }
+
+ if (!$fileInfo->isReadable()) {
+ throw new ZipException('$fileInfo is not readable.');
+ }
+
+ $this->file = $fileInfo;
+ $zipEntry->setUncompressedSize($fileInfo->getSize());
+ }
+
+ /**
+ * @throws ZipException
+ *
+ * @return resource returns stream data
+ */
+ public function getDataAsStream()
+ {
+ if (!$this->file->isReadable()) {
+ throw new ZipException(sprintf('The %s file is no longer readable.', $this->file->getPathname()));
+ }
+
+ return fopen($this->file->getPathname(), 'rb');
+ }
+
+ /**
+ * @throws ZipException
+ *
+ * @return string returns data as string
+ */
+ public function getDataAsString()
+ {
+ if (!$this->file->isReadable()) {
+ throw new ZipException(sprintf('The %s file is no longer readable.', $this->file->getPathname()));
+ }
+
+ return file_get_contents($this->file->getPathname());
+ }
+
+ /**
+ * @param resource $outStream
+ *
+ * @throws ZipException
+ */
+ public function copyDataToStream($outStream)
+ {
+ try {
+ $stream = $this->getDataAsStream();
+ stream_copy_to_stream($stream, $outStream);
+ } finally {
+ fclose($stream);
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Data/ZipNewData.php b/vendor/nelexa/zip/src/Model/Data/ZipNewData.php
new file mode 100644
index 0000000..e149638
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Data/ZipNewData.php
@@ -0,0 +1,132 @@
+ array of resource ids and the number of class clones
+ */
+ private static $guardClonedStream = [];
+
+ /** @var ZipEntry */
+ private $zipEntry;
+
+ /** @var resource */
+ private $stream;
+
+ /**
+ * ZipStringData constructor.
+ *
+ * @param ZipEntry $zipEntry
+ * @param string|resource $data
+ */
+ public function __construct(ZipEntry $zipEntry, $data)
+ {
+ $this->zipEntry = $zipEntry;
+
+ if (\is_string($data)) {
+ $zipEntry->setUncompressedSize(\strlen($data));
+
+ if (!($handle = fopen('php://temp', 'w+b'))) {
+ // @codeCoverageIgnoreStart
+ throw new \RuntimeException('A temporary resource cannot be opened for writing.');
+ // @codeCoverageIgnoreEnd
+ }
+ fwrite($handle, $data);
+ rewind($handle);
+ $this->stream = $handle;
+ } elseif (\is_resource($data)) {
+ $this->stream = $data;
+ }
+
+ $resourceId = (int) $this->stream;
+ self::$guardClonedStream[$resourceId] =
+ isset(self::$guardClonedStream[$resourceId]) ?
+ self::$guardClonedStream[$resourceId] + 1 :
+ 0;
+ }
+
+ /**
+ * @return resource returns stream data
+ */
+ public function getDataAsStream()
+ {
+ if (!\is_resource($this->stream)) {
+ throw new \LogicException(sprintf('Resource has been closed (entry=%s).', $this->zipEntry->getName()));
+ }
+
+ return $this->stream;
+ }
+
+ /**
+ * @return string returns data as string
+ */
+ public function getDataAsString()
+ {
+ $stream = $this->getDataAsStream();
+ $pos = ftell($stream);
+
+ try {
+ rewind($stream);
+
+ return stream_get_contents($stream);
+ } finally {
+ fseek($stream, $pos);
+ }
+ }
+
+ /**
+ * @param resource $outStream
+ */
+ public function copyDataToStream($outStream)
+ {
+ $stream = $this->getDataAsStream();
+ rewind($stream);
+ stream_copy_to_stream($stream, $outStream);
+ }
+
+ /**
+ * @see https://php.net/manual/en/language.oop5.cloning.php
+ */
+ public function __clone()
+ {
+ $resourceId = (int) $this->stream;
+ self::$guardClonedStream[$resourceId] =
+ isset(self::$guardClonedStream[$resourceId]) ?
+ self::$guardClonedStream[$resourceId] + 1 :
+ 1;
+ }
+
+ /**
+ * The stream will be closed when closing the zip archive.
+ *
+ * The method implements protection against closing the stream of the cloned object.
+ *
+ * @see ZipFile::close()
+ */
+ public function __destruct()
+ {
+ $resourceId = (int) $this->stream;
+
+ if (isset(self::$guardClonedStream[$resourceId]) && self::$guardClonedStream[$resourceId] > 0) {
+ self::$guardClonedStream[$resourceId]--;
+
+ return;
+ }
+
+ if (\is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php b/vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php
new file mode 100644
index 0000000..c53df05
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php
@@ -0,0 +1,172 @@
+zipReader = $zipReader;
+ $this->offset = $offsetData;
+ $this->sourceEntry = $zipEntry;
+ $this->compressedSize = $zipEntry->getCompressedSize();
+ $this->uncompressedSize = $zipEntry->getUncompressedSize();
+ }
+
+ /**
+ * @param ZipEntry $entry
+ *
+ * @return bool
+ */
+ public function hasRecompressData(ZipEntry $entry)
+ {
+ return $this->sourceEntry->getCompressionLevel() !== $entry->getCompressionLevel() ||
+ $this->sourceEntry->getCompressionMethod() !== $entry->getCompressionMethod() ||
+ $this->sourceEntry->isEncrypted() !== $entry->isEncrypted() ||
+ $this->sourceEntry->getEncryptionMethod() !== $entry->getEncryptionMethod() ||
+ $this->sourceEntry->getPassword() !== $entry->getPassword() ||
+ $this->sourceEntry->getCompressedSize() !== $entry->getCompressedSize() ||
+ $this->sourceEntry->getUncompressedSize() !== $entry->getUncompressedSize() ||
+ $this->sourceEntry->getCrc() !== $entry->getCrc();
+ }
+
+ /**
+ * @throws ZipException
+ *
+ * @return resource returns stream data
+ */
+ public function getDataAsStream()
+ {
+ if (!\is_resource($this->stream)) {
+ $this->stream = $this->zipReader->getEntryStream($this);
+ }
+
+ return $this->stream;
+ }
+
+ /**
+ * @throws ZipException
+ *
+ * @return string returns data as string
+ */
+ public function getDataAsString()
+ {
+ $autoClosable = $this->stream === null;
+
+ $stream = $this->getDataAsStream();
+ $pos = ftell($stream);
+
+ try {
+ rewind($stream);
+
+ return stream_get_contents($stream);
+ } finally {
+ if ($autoClosable) {
+ fclose($stream);
+ $this->stream = null;
+ } else {
+ fseek($stream, $pos);
+ }
+ }
+ }
+
+ /**
+ * @param resource $outputStream Output stream
+ *
+ * @throws ZipException
+ * @throws Crc32Exception
+ */
+ public function copyDataToStream($outputStream)
+ {
+ if (\is_resource($this->stream)) {
+ rewind($this->stream);
+ stream_copy_to_stream($this->stream, $outputStream);
+ } else {
+ $this->zipReader->copyUncompressedDataToStream($this, $outputStream);
+ }
+ }
+
+ /**
+ * @param resource $outputStream Output stream
+ */
+ public function copyCompressedDataToStream($outputStream)
+ {
+ $this->zipReader->copyCompressedDataToStream($this, $outputStream);
+ }
+
+ /**
+ * @return ZipEntry
+ */
+ public function getSourceEntry()
+ {
+ return $this->sourceEntry;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCompressedSize()
+ {
+ return $this->compressedSize;
+ }
+
+ /**
+ * @return int
+ */
+ public function getUncompressedSize()
+ {
+ return $this->uncompressedSize;
+ }
+
+ /**
+ * @return int
+ */
+ public function getOffset()
+ {
+ return $this->offset;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __destruct()
+ {
+ if (\is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php b/vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php
new file mode 100644
index 0000000..d312cfe
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php
@@ -0,0 +1,93 @@
+entryCount = $entryCount;
+ $this->cdOffset = $cdOffset;
+ $this->cdSize = $cdSize;
+ $this->zip64 = $zip64;
+ $this->comment = $comment;
+ }
+
+ /**
+ * @param string|null $comment
+ */
+ public function setComment($comment)
+ {
+ $this->comment = $comment;
+ }
+
+ /**
+ * @return int
+ */
+ public function getEntryCount()
+ {
+ return $this->entryCount;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCdOffset()
+ {
+ return $this->cdOffset;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCdSize()
+ {
+ return $this->cdSize;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getComment()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isZip64()
+ {
+ return $this->zip64;
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php b/vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php
new file mode 100644
index 0000000..9cc2020
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php
@@ -0,0 +1,276 @@
+collection);
+ }
+
+ /**
+ * Returns the Extra Field with the given Header ID or null
+ * if no such Extra Field exists.
+ *
+ * @param int $headerId the requested Header ID
+ *
+ * @return ZipExtraField|null the Extra Field with the given Header ID or
+ * if no such Extra Field exists
+ */
+ public function get($headerId)
+ {
+ $this->validateHeaderId($headerId);
+
+ return isset($this->collection[$headerId]) ? $this->collection[$headerId] : null;
+ }
+
+ /**
+ * @param int $headerId
+ */
+ private function validateHeaderId($headerId)
+ {
+ if ($headerId < 0 || $headerId > 0xffff) {
+ throw new \InvalidArgumentException('$headerId out of range');
+ }
+ }
+
+ /**
+ * Stores the given Extra Field in this collection.
+ *
+ * @param ZipExtraField $extraField the Extra Field to store in this collection
+ *
+ * @return ZipExtraField the Extra Field previously associated with the Header ID of
+ * of the given Extra Field or null if no such Extra Field existed
+ */
+ public function add(ZipExtraField $extraField)
+ {
+ $headerId = $extraField->getHeaderId();
+
+ $this->validateHeaderId($headerId);
+ $this->collection[$headerId] = $extraField;
+
+ return $extraField;
+ }
+
+ /**
+ * @param ZipExtraField[] $extraFields
+ */
+ public function addAll(array $extraFields)
+ {
+ foreach ($extraFields as $extraField) {
+ $this->add($extraField);
+ }
+ }
+
+ /**
+ * @param ExtraFieldsCollection $collection
+ */
+ public function addCollection(self $collection)
+ {
+ $this->addAll($collection->collection);
+ }
+
+ /**
+ * @return ZipExtraField[]
+ */
+ public function getAll()
+ {
+ return $this->collection;
+ }
+
+ /**
+ * Returns Extra Field exists.
+ *
+ * @param int $headerId the requested Header ID
+ *
+ * @return bool
+ */
+ public function has($headerId)
+ {
+ return isset($this->collection[$headerId]);
+ }
+
+ /**
+ * Removes the Extra Field with the given Header ID.
+ *
+ * @param int $headerId the requested Header ID
+ *
+ * @return ZipExtraField|null the Extra Field with the given Header ID or null
+ * if no such Extra Field exists
+ */
+ public function remove($headerId)
+ {
+ $this->validateHeaderId($headerId);
+
+ if (isset($this->collection[$headerId])) {
+ $ef = $this->collection[$headerId];
+ unset($this->collection[$headerId]);
+
+ return $ef;
+ }
+
+ return null;
+ }
+
+ /**
+ * Whether a offset exists.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetexists.php
+ *
+ * @param int $offset an offset to check for
+ *
+ * @return bool true on success or false on failure
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->collection[(int) $offset]);
+ }
+
+ /**
+ * Offset to retrieve.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetget.php
+ *
+ * @param int $offset the offset to retrieve
+ *
+ * @return ZipExtraField|null
+ */
+ public function offsetGet($offset)
+ {
+ return isset($this->collection[$offset]) ? $this->collection[$offset] : null;
+ }
+
+ /**
+ * Offset to set.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetset.php
+ *
+ * @param mixed $offset the offset to assign the value to
+ * @param ZipExtraField $value the value to set
+ */
+ public function offsetSet($offset, $value)
+ {
+ if (!$value instanceof ZipExtraField) {
+ throw new \InvalidArgumentException('value is not instanceof ' . ZipExtraField::class);
+ }
+ $this->add($value);
+ }
+
+ /**
+ * Offset to unset.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetunset.php
+ *
+ * @param mixed $offset the offset to unset
+ */
+ public function offsetUnset($offset)
+ {
+ $this->remove($offset);
+ }
+
+ /**
+ * Return the current element.
+ *
+ * @see http://php.net/manual/en/iterator.current.php
+ *
+ * @return ZipExtraField
+ */
+ public function current()
+ {
+ return current($this->collection);
+ }
+
+ /**
+ * Move forward to next element.
+ *
+ * @see http://php.net/manual/en/iterator.next.php
+ */
+ public function next()
+ {
+ next($this->collection);
+ }
+
+ /**
+ * Return the key of the current element.
+ *
+ * @see http://php.net/manual/en/iterator.key.php
+ *
+ * @return int scalar on success, or null on failure
+ */
+ public function key()
+ {
+ return key($this->collection);
+ }
+
+ /**
+ * 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.
+ */
+ public function valid()
+ {
+ return key($this->collection) !== null;
+ }
+
+ /**
+ * Rewind the Iterator to the first element.
+ *
+ * @see http://php.net/manual/en/iterator.rewind.php
+ */
+ public function rewind()
+ {
+ reset($this->collection);
+ }
+
+ public function clear()
+ {
+ $this->collection = [];
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $formats = [];
+
+ foreach ($this->collection as $key => $value) {
+ $formats[] = (string) $value;
+ }
+
+ return implode("\n", $formats);
+ }
+
+ /**
+ * If clone extra fields.
+ */
+ public function __clone()
+ {
+ foreach ($this->collection as $k => $v) {
+ $this->collection[$k] = clone $v;
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php
new file mode 100644
index 0000000..dd6f566
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php
@@ -0,0 +1,133 @@
+crc32 = (int) $crc32;
+ $this->unicodeValue = (string) $unicodeValue;
+ }
+
+ /**
+ * @return int the CRC32 checksum of the filename or comment as
+ * encoded in the central directory of the zip file
+ */
+ public function getCrc32()
+ {
+ return $this->crc32;
+ }
+
+ /**
+ * @param int $crc32
+ */
+ public function setCrc32($crc32)
+ {
+ $this->crc32 = (int) $crc32;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUnicodeValue()
+ {
+ return $this->unicodeValue;
+ }
+
+ /**
+ * @param string $unicodeValue the UTF-8 encoded name to set
+ */
+ public function setUnicodeValue($unicodeValue)
+ {
+ $this->unicodeValue = $unicodeValue;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException on error
+ *
+ * @return static
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ if (\strlen($buffer) < 5) {
+ throw new ZipException('Unicode path extra data must have at least 5 bytes.');
+ }
+
+ $data = unpack('Cversion/Vcrc32', $buffer);
+
+ if ($data['version'] !== self::DEFAULT_VERSION) {
+ throw new ZipException(sprintf('Unsupported version [%d] for Unicode path extra data.', $data['version']));
+ }
+
+ $unicodeValue = substr($buffer, 5);
+
+ return new static($data['crc32'], $unicodeValue);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException on error
+ *
+ * @return static
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ return self::unpackLocalFileData($buffer, $entry);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ return pack(
+ 'CV',
+ self::DEFAULT_VERSION,
+ $this->crc32
+ ) .
+ $this->unicodeValue;
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ return $this->packLocalFileData();
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php
new file mode 100644
index 0000000..69c26b8
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php
@@ -0,0 +1,176 @@
+multiple = $multiple;
+ $this->padding = $padding;
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * @return int
+ */
+ public function getMultiple()
+ {
+ return $this->multiple;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPadding()
+ {
+ return $this->padding;
+ }
+
+ /**
+ * @param int $multiple
+ */
+ public function setMultiple($multiple)
+ {
+ $this->multiple = (int) $multiple;
+ }
+
+ /**
+ * @param int $padding
+ */
+ public function setPadding($padding)
+ {
+ $this->padding = (int) $padding;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException
+ *
+ * @return ApkAlignmentExtraField
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ $length = \strlen($buffer);
+
+ if ($length < 2) {
+ // This is APK alignment field.
+ // FORMAT:
+ // * uint16 alignment multiple (in bytes)
+ // * remaining bytes -- padding to achieve alignment of data which starts after
+ // the extra field
+ throw new ZipException(
+ 'Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.'
+ );
+ }
+ $multiple = unpack('v', $buffer)[1];
+ $padding = $length - 2;
+
+ return new self($multiple, $padding);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException on error
+ *
+ * @return ApkAlignmentExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ return self::unpackLocalFileData($buffer, $entry);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ return pack('vx' . $this->padding, $this->multiple);
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ return $this->packLocalFileData();
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return sprintf(
+ '0x%04x APK Alignment: Multiple=%d Padding=%d',
+ self::HEADER_ID,
+ $this->multiple,
+ $this->padding
+ );
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php
new file mode 100644
index 0000000..3bf62b9
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php
@@ -0,0 +1,302 @@
+mode = $mode;
+ $this->uid = $uid;
+ $this->gid = $gid;
+ $this->link = $link;
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws Crc32Exception
+ *
+ * @return static
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ $givenChecksum = unpack('V', $buffer)[1];
+ $buffer = substr($buffer, 4);
+ $realChecksum = crc32($buffer);
+
+ if ($givenChecksum !== $realChecksum) {
+ throw new Crc32Exception('Asi Unix Extra Filed Data', $givenChecksum, $realChecksum);
+ }
+
+ $data = unpack('vmode/VlinkSize/vuid/vgid', $buffer);
+ $link = '';
+
+ if ($data['linkSize'] > 0) {
+ $link = substr($buffer, 10);
+ }
+
+ return new self($data['mode'], $data['uid'], $data['gid'], $link);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws Crc32Exception
+ *
+ * @return AsiExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ return self::unpackLocalFileData($buffer, $entry);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ $data = pack(
+ 'vVvv',
+ $this->mode,
+ \strlen($this->link),
+ $this->uid,
+ $this->gid
+ ) . $this->link;
+
+ return pack('V', crc32($data)) . $data;
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ return $this->packLocalFileData();
+ }
+
+ /**
+ * Name of linked file.
+ *
+ * @return string name of the file this entry links to if it is a
+ * symbolic link, the empty string otherwise
+ */
+ public function getLink()
+ {
+ return $this->link;
+ }
+
+ /**
+ * Indicate that this entry is a symbolic link to the given filename.
+ *
+ * @param string $link name of the file this entry links to, empty
+ * string if it is not a symbolic link
+ */
+ public function setLink($link)
+ {
+ $this->link = (string) $link;
+ $this->mode = $this->getPermissionsMode($this->mode);
+ }
+
+ /**
+ * Is this entry a symbolic link?
+ *
+ * @return bool true if this is a symbolic link
+ */
+ public function isLink()
+ {
+ return !empty($this->link);
+ }
+
+ /**
+ * Get the file mode for given permissions with the correct file type.
+ *
+ * @param int $mode the mode
+ *
+ * @return int the type with the mode
+ */
+ protected function getPermissionsMode($mode)
+ {
+ $type = 0;
+
+ if ($this->isLink()) {
+ $type = UnixStat::UNX_IFLNK;
+ } elseif (($mode & UnixStat::UNX_IFREG) !== 0) {
+ $type = UnixStat::UNX_IFREG;
+ } elseif (($mode & UnixStat::UNX_IFDIR) !== 0) {
+ $type = UnixStat::UNX_IFDIR;
+ }
+
+ return $type | ($mode & self::PERM_MASK);
+ }
+
+ /**
+ * Is this entry a directory?
+ *
+ * @return bool true if this entry is a directory
+ */
+ public function isDirectory()
+ {
+ return ($this->mode & UnixStat::UNX_IFDIR) !== 0 && !$this->isLink();
+ }
+
+ /**
+ * @return int
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * @param int $mode
+ */
+ public function setMode($mode)
+ {
+ $this->mode = $this->getPermissionsMode($mode);
+ }
+
+ /**
+ * @return int
+ */
+ public function getUserId()
+ {
+ return $this->uid;
+ }
+
+ /**
+ * @param int $uid
+ */
+ public function setUserId($uid)
+ {
+ $this->uid = (int) $uid;
+ }
+
+ /**
+ * @return int
+ */
+ public function getGroupId()
+ {
+ return $this->gid;
+ }
+
+ /**
+ * @param int $gid
+ */
+ public function setGroupId($gid)
+ {
+ $this->gid = (int) $gid;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return sprintf(
+ '0x%04x ASI: Mode=%o UID=%d GID=%d Link="%s',
+ self::HEADER_ID,
+ $this->mode,
+ $this->uid,
+ $this->gid,
+ $this->link
+ );
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php
new file mode 100644
index 0000000..909c783
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php
@@ -0,0 +1,446 @@
+flags = (int) $flags;
+ $this->modifyTime = $modifyTime;
+ $this->accessTime = $accessTime;
+ $this->createTime = $createTime;
+ }
+
+ /**
+ * @param int|null $modifyTime
+ * @param int|null $accessTime
+ * @param int|null $createTime
+ *
+ * @return ExtendedTimestampExtraField
+ */
+ public static function create($modifyTime, $accessTime, $createTime)
+ {
+ $flags = 0;
+
+ if ($modifyTime !== null) {
+ $modifyTime = (int) $modifyTime;
+ $flags |= self::MODIFY_TIME_BIT;
+ }
+
+ if ($accessTime !== null) {
+ $accessTime = (int) $accessTime;
+ $flags |= self::ACCESS_TIME_BIT;
+ }
+
+ if ($createTime !== null) {
+ $createTime = (int) $createTime;
+ $flags |= self::CREATE_TIME_BIT;
+ }
+
+ return new self($flags, $modifyTime, $accessTime, $createTime);
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @return ExtendedTimestampExtraField
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ $length = \strlen($buffer);
+ $flags = unpack('C', $buffer)[1];
+ $offset = 1;
+
+ $modifyTime = null;
+ $accessTime = null;
+ $createTime = null;
+
+ if (($flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT) {
+ $modifyTime = unpack('V', substr($buffer, $offset, 4))[1];
+ $offset += 4;
+ }
+
+ // Notice the extra length check in case we are parsing the shorter
+ // central data field (for both access and create timestamps).
+ if ((($flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT) && $offset + 4 <= $length) {
+ $accessTime = unpack('V', substr($buffer, $offset, 4))[1];
+ $offset += 4;
+ }
+
+ if ((($flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT) && $offset + 4 <= $length) {
+ $createTime = unpack('V', substr($buffer, $offset, 4))[1];
+ }
+
+ return new self($flags, $modifyTime, $accessTime, $createTime);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @return ExtendedTimestampExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ return self::unpackLocalFileData($buffer, $entry);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ $data = '';
+
+ if (($this->flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT && $this->modifyTime !== null) {
+ $data .= pack('V', $this->modifyTime);
+ }
+
+ if (($this->flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT && $this->accessTime !== null) {
+ $data .= pack('V', $this->accessTime);
+ }
+
+ if (($this->flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT && $this->createTime !== null) {
+ $data .= pack('V', $this->createTime);
+ }
+
+ return pack('C', $this->flags) . $data;
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * Note: even if bit1 and bit2 are set, the Central data will still
+ * not contain access/create fields: only local data ever holds those!
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ $cdLength = 1 + ($this->modifyTime !== null ? 4 : 0);
+
+ return substr($this->packLocalFileData(), 0, $cdLength);
+ }
+
+ /**
+ * Gets flags byte.
+ *
+ * The flags byte tells us which of the three datestamp fields are
+ * present in the data:
+ * bit0 - modify time
+ * bit1 - access time
+ * bit2 - create time
+ *
+ * Only first 3 bits of flags are used according to the
+ * latest version of the spec (December 2012).
+ *
+ * @return int flags byte indicating which of the
+ * three datestamp fields are present
+ */
+ public function getFlags()
+ {
+ return $this->flags;
+ }
+
+ /**
+ * Returns the modify time (seconds since epoch) of this zip entry,
+ * or null if no such timestamp exists in the zip entry.
+ *
+ * @return int|null modify time (seconds since epoch) or null
+ */
+ public function getModifyTime()
+ {
+ return $this->modifyTime;
+ }
+
+ /**
+ * Returns the access time (seconds since epoch) of this zip entry,
+ * or null if no such timestamp exists in the zip entry.
+ *
+ * @return int|null access time (seconds since epoch) or null
+ */
+ public function getAccessTime()
+ {
+ return $this->accessTime;
+ }
+
+ /**
+ * Returns the create time (seconds since epoch) of this zip entry,
+ * or null if no such timestamp exists in the zip entry.
+ *
+ * Note: modern linux file systems (e.g., ext2)
+ * do not appear to store a "create time" value, and so
+ * it's usually omitted altogether in the zip extra
+ * field. Perhaps other unix systems track this.
+ *
+ * @return int|null create time (seconds since epoch) or null
+ */
+ public function getCreateTime()
+ {
+ return $this->createTime;
+ }
+
+ /**
+ * Returns the modify time as a \DateTimeInterface
+ * of this zip entry, or null if no such timestamp exists in the zip entry.
+ * The milliseconds are always zeroed out, since the underlying data
+ * offers only per-second precision.
+ *
+ * @return \DateTimeInterface|null modify time as \DateTimeInterface or null
+ */
+ public function getModifyDateTime()
+ {
+ return self::timestampToDateTime($this->modifyTime);
+ }
+
+ /**
+ * Returns the access time as a \DateTimeInterface
+ * of this zip entry, or null if no such timestamp exists in the zip entry.
+ * The milliseconds are always zeroed out, since the underlying data
+ * offers only per-second precision.
+ *
+ * @return \DateTimeInterface|null access time as \DateTimeInterface or null
+ */
+ public function getAccessDateTime()
+ {
+ return self::timestampToDateTime($this->accessTime);
+ }
+
+ /**
+ * Returns the create time as a a \DateTimeInterface
+ * of this zip entry, or null if no such timestamp exists in the zip entry.
+ * The milliseconds are always zeroed out, since the underlying data
+ * offers only per-second precision.
+ *
+ * Note: modern linux file systems (e.g., ext2)
+ * do not appear to store a "create time" value, and so
+ * it's usually omitted altogether in the zip extra
+ * field. Perhaps other unix systems track $this->.
+ *
+ * @return \DateTimeInterface|null create time as \DateTimeInterface or null
+ */
+ public function getCreateDateTime()
+ {
+ return self::timestampToDateTime($this->createTime);
+ }
+
+ /**
+ * Sets the modify time (seconds since epoch) of this zip entry
+ * using a integer.
+ *
+ * @param int|null $unixTime unix time of the modify time (seconds per epoch) or null
+ */
+ public function setModifyTime($unixTime)
+ {
+ $this->modifyTime = $unixTime;
+ $this->updateFlags();
+ }
+
+ private function updateFlags()
+ {
+ $flags = 0;
+
+ if ($this->modifyTime !== null) {
+ $flags |= self::MODIFY_TIME_BIT;
+ }
+
+ if ($this->accessTime !== null) {
+ $flags |= self::ACCESS_TIME_BIT;
+ }
+
+ if ($this->createTime !== null) {
+ $flags |= self::CREATE_TIME_BIT;
+ }
+ $this->flags = $flags;
+ }
+
+ /**
+ * Sets the access time (seconds since epoch) of this zip entry
+ * using a integer.
+ *
+ * @param int|null $unixTime Unix time of the access time (seconds per epoch) or null
+ */
+ public function setAccessTime($unixTime)
+ {
+ $this->accessTime = $unixTime;
+ $this->updateFlags();
+ }
+
+ /**
+ * Sets the create time (seconds since epoch) of this zip entry
+ * using a integer.
+ *
+ * @param int|null $unixTime Unix time of the create time (seconds per epoch) or null
+ */
+ public function setCreateTime($unixTime)
+ {
+ $this->createTime = $unixTime;
+ $this->updateFlags();
+ }
+
+ /**
+ * @param int|null $timestamp
+ *
+ * @return \DateTimeInterface|null
+ */
+ private static function timestampToDateTime($timestamp)
+ {
+ try {
+ return $timestamp !== null ? new \DateTimeImmutable('@' . $timestamp) : null;
+ } catch (\Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $args = [self::HEADER_ID];
+ $format = '0x%04x ExtendedTimestamp:';
+
+ if ($this->modifyTime !== null) {
+ $format .= ' Modify:[%s]';
+ $args[] = date(\DATE_W3C, $this->modifyTime);
+ }
+
+ if ($this->accessTime !== null) {
+ $format .= ' Access:[%s]';
+ $args[] = date(\DATE_W3C, $this->accessTime);
+ }
+
+ if ($this->createTime !== null) {
+ $format .= ' Create:[%s]';
+ $args[] = date(\DATE_W3C, $this->createTime);
+ }
+
+ return vsprintf($format, $args);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php
new file mode 100644
index 0000000..e1683aa
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php
@@ -0,0 +1,118 @@
+getEntries();
+
+ if (!empty($zipEntries)) {
+ foreach ($zipEntries as $zipEntry) {
+ $zipEntry->removeExtraField(self::HEADER_ID);
+ }
+ // set jar execute bit
+ reset($zipEntries);
+ $zipEntry = current($zipEntries);
+ $zipEntry->getCdExtraFields()[] = new self();
+ }
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ return '';
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ return '';
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException on error
+ *
+ * @return JarMarkerExtraField
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ if (!empty($buffer)) {
+ throw new ZipException("JarMarker doesn't expect any data");
+ }
+
+ return new self();
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException on error
+ *
+ * @return JarMarkerExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ return self::unpackLocalFileData($buffer, $entry);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return sprintf('0x%04x Jar Marker', self::HEADER_ID);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php
new file mode 100644
index 0000000..807de72
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php
@@ -0,0 +1,237 @@
+version = (int) $version;
+ $this->uid = (int) $uid;
+ $this->gid = (int) $gid;
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException
+ *
+ * @return NewUnixExtraField
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ $length = \strlen($buffer);
+
+ if ($length < 3) {
+ throw new ZipException(sprintf('X7875_NewUnix length is too short, only %s bytes', $length));
+ }
+ $offset = 0;
+ $data = unpack('Cversion/CuidSize', $buffer);
+ $offset += 2;
+ $uidSize = $data['uidSize'];
+ $gid = self::readSizeIntegerLE(substr($buffer, $offset, $uidSize), $uidSize);
+ $offset += $uidSize;
+ $gidSize = unpack('C', $buffer[$offset])[1];
+ $offset++;
+ $uid = self::readSizeIntegerLE(substr($buffer, $offset, $gidSize), $gidSize);
+
+ return new self($data['version'], $gid, $uid);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException
+ *
+ * @return NewUnixExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ return self::unpackLocalFileData($buffer, $entry);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ return pack(
+ 'CCVCV',
+ $this->version,
+ 4, // UIDSize
+ $this->uid,
+ 4, // GIDSize
+ $this->gid
+ );
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ return $this->packLocalFileData();
+ }
+
+ /**
+ * @param string $data
+ * @param int $size
+ *
+ * @throws ZipException
+ *
+ * @return int
+ */
+ private static function readSizeIntegerLE($data, $size)
+ {
+ $format = [
+ 1 => 'C', // unsigned byte
+ 2 => 'v', // unsigned short LE
+ 4 => 'V', // unsigned int LE
+ ];
+
+ if (!isset($format[$size])) {
+ throw new ZipException(sprintf('Invalid size bytes: %d', $size));
+ }
+
+ return unpack($format[$size], $data)[1];
+ }
+
+ /**
+ * @return int
+ */
+ public function getUid()
+ {
+ return $this->uid;
+ }
+
+ /**
+ * @param int $uid
+ */
+ public function setUid($uid)
+ {
+ $this->uid = $uid & 0xffffffff;
+ }
+
+ /**
+ * @return int
+ */
+ public function getGid()
+ {
+ return $this->gid;
+ }
+
+ /**
+ * @param int $gid
+ */
+ public function setGid($gid)
+ {
+ $this->gid = $gid & 0xffffffff;
+ }
+
+ /**
+ * @return int
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return sprintf(
+ '0x%04x NewUnix: UID=%d GID=%d',
+ self::HEADER_ID,
+ $this->uid,
+ $this->gid
+ );
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php
new file mode 100644
index 0000000..213e925
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php
@@ -0,0 +1,339 @@
+modifyNtfsTime = (int) $modifyNtfsTime;
+ $this->accessNtfsTime = (int) $accessNtfsTime;
+ $this->createNtfsTime = (int) $createNtfsTime;
+ }
+
+ /**
+ * @param \DateTimeInterface $modifyDateTime
+ * @param \DateTimeInterface $accessDateTime
+ * @param \DateTimeInterface $createNtfsTime
+ *
+ * @return NtfsExtraField
+ */
+ public static function create(
+ \DateTimeInterface $modifyDateTime,
+ \DateTimeInterface $accessDateTime,
+ \DateTimeInterface $createNtfsTime
+ ) {
+ return new self(
+ self::dateTimeToNtfsTime($modifyDateTime),
+ self::dateTimeToNtfsTime($accessDateTime),
+ self::dateTimeToNtfsTime($createNtfsTime)
+ );
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException
+ *
+ * @return NtfsExtraField
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ if (\PHP_INT_SIZE === 4) {
+ throw new ZipException('not supported for php-32bit');
+ }
+
+ $buffer = substr($buffer, 4);
+
+ $modifyTime = 0;
+ $accessTime = 0;
+ $createTime = 0;
+
+ while ($buffer || $buffer !== '') {
+ $unpack = unpack('vtag/vsizeAttr', $buffer);
+
+ if ($unpack['tag'] === self::TIME_ATTR_TAG && $unpack['sizeAttr'] === self::TIME_ATTR_SIZE) {
+ // refactoring will be needed when php 5.5 support ends
+ $modifyTime = PackUtil::unpackLongLE(substr($buffer, 4, 8));
+ $accessTime = PackUtil::unpackLongLE(substr($buffer, 12, 8));
+ $createTime = PackUtil::unpackLongLE(substr($buffer, 20, 8));
+
+ break;
+ }
+ $buffer = substr($buffer, 4 + $unpack['sizeAttr']);
+ }
+
+ return new self($modifyTime, $accessTime, $createTime);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException
+ *
+ * @return NtfsExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ return self::unpackLocalFileData($buffer, $entry);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ $data = pack('Vvv', 0, self::TIME_ATTR_TAG, self::TIME_ATTR_SIZE);
+ // refactoring will be needed when php 5.5 support ends
+ $data .= PackUtil::packLongLE($this->modifyNtfsTime);
+ $data .= PackUtil::packLongLE($this->accessNtfsTime);
+ $data .= PackUtil::packLongLE($this->createNtfsTime);
+
+ return $data;
+ }
+
+ /**
+ * @return int
+ */
+ public function getModifyNtfsTime()
+ {
+ return $this->modifyNtfsTime;
+ }
+
+ /**
+ * @param int $modifyNtfsTime
+ */
+ public function setModifyNtfsTime($modifyNtfsTime)
+ {
+ $this->modifyNtfsTime = (int) $modifyNtfsTime;
+ }
+
+ /**
+ * @return int
+ */
+ public function getAccessNtfsTime()
+ {
+ return $this->accessNtfsTime;
+ }
+
+ /**
+ * @param int $accessNtfsTime
+ */
+ public function setAccessNtfsTime($accessNtfsTime)
+ {
+ $this->accessNtfsTime = (int) $accessNtfsTime;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCreateNtfsTime()
+ {
+ return $this->createNtfsTime;
+ }
+
+ /**
+ * @param int $createNtfsTime
+ */
+ public function setCreateNtfsTime($createNtfsTime)
+ {
+ $this->createNtfsTime = (int) $createNtfsTime;
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ return $this->packLocalFileData();
+ }
+
+ /**
+ * @return \DateTimeInterface
+ */
+ public function getModifyDateTime()
+ {
+ return self::ntfsTimeToDateTime($this->modifyNtfsTime);
+ }
+
+ /**
+ * @param \DateTimeInterface $modifyTime
+ */
+ public function setModifyDateTime(\DateTimeInterface $modifyTime)
+ {
+ $this->modifyNtfsTime = self::dateTimeToNtfsTime($modifyTime);
+ }
+
+ /**
+ * @return \DateTimeInterface
+ */
+ public function getAccessDateTime()
+ {
+ return self::ntfsTimeToDateTime($this->accessNtfsTime);
+ }
+
+ /**
+ * @param \DateTimeInterface $accessTime
+ */
+ public function setAccessDateTime(\DateTimeInterface $accessTime)
+ {
+ $this->accessNtfsTime = self::dateTimeToNtfsTime($accessTime);
+ }
+
+ /**
+ * @return \DateTimeInterface
+ */
+ public function getCreateDateTime()
+ {
+ return self::ntfsTimeToDateTime($this->createNtfsTime);
+ }
+
+ /**
+ * @param \DateTimeInterface $createTime
+ */
+ public function setCreateDateTime(\DateTimeInterface $createTime)
+ {
+ $this->createNtfsTime = self::dateTimeToNtfsTime($createTime);
+ }
+
+ /**
+ * @param float $timestamp Float timestamp
+ *
+ * @return int
+ */
+ public static function timestampToNtfsTime($timestamp)
+ {
+ return (int) (((float) $timestamp * 10000000) - self::EPOCH_OFFSET);
+ }
+
+ /**
+ * @param \DateTimeInterface $dateTime
+ *
+ * @return int
+ */
+ public static function dateTimeToNtfsTime(\DateTimeInterface $dateTime)
+ {
+ return self::timestampToNtfsTime((float) $dateTime->format('U.u'));
+ }
+
+ /**
+ * @param int $ntfsTime
+ *
+ * @return float Float unix timestamp
+ */
+ public static function ntfsTimeToTimestamp($ntfsTime)
+ {
+ return (float) (($ntfsTime + self::EPOCH_OFFSET) / 10000000);
+ }
+
+ /**
+ * @param int $ntfsTime
+ *
+ * @return \DateTimeInterface
+ */
+ public static function ntfsTimeToDateTime($ntfsTime)
+ {
+ $timestamp = self::ntfsTimeToTimestamp($ntfsTime);
+ $dateTime = \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6f', $timestamp));
+
+ if ($dateTime === false) {
+ throw new InvalidArgumentException('Cannot create date/time object for timestamp ' . $timestamp);
+ }
+
+ return $dateTime;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $args = [self::HEADER_ID];
+ $format = '0x%04x NtfsExtra:';
+
+ if ($this->modifyNtfsTime !== 0) {
+ $format .= ' Modify:[%s]';
+ $args[] = $this->getModifyDateTime()->format(\DATE_ATOM);
+ }
+
+ if ($this->accessNtfsTime !== 0) {
+ $format .= ' Access:[%s]';
+ $args[] = $this->getAccessDateTime()->format(\DATE_ATOM);
+ }
+
+ if ($this->createNtfsTime !== 0) {
+ $format .= ' Create:[%s]';
+ $args[] = $this->getCreateDateTime()->format(\DATE_ATOM);
+ }
+
+ return vsprintf($format, $args);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php
new file mode 100644
index 0000000..1f69487
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php
@@ -0,0 +1,327 @@
+accessTime = $accessTime;
+ $this->modifyTime = $modifyTime;
+ $this->uid = $uid;
+ $this->gid = $gid;
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @return OldUnixExtraField
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ $length = \strlen($buffer);
+
+ $accessTime = $modifyTime = $uid = $gid = null;
+
+ if ($length >= 4) {
+ $accessTime = unpack('V', $buffer)[1];
+ }
+
+ if ($length >= 8) {
+ $modifyTime = unpack('V', substr($buffer, 4, 4))[1];
+ }
+
+ if ($length >= 10) {
+ $uid = unpack('v', substr($buffer, 8, 2))[1];
+ }
+
+ if ($length >= 12) {
+ $gid = unpack('v', substr($buffer, 10, 2))[1];
+ }
+
+ return new self($accessTime, $modifyTime, $uid, $gid);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @return OldUnixExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ $length = \strlen($buffer);
+
+ $accessTime = $modifyTime = null;
+
+ if ($length >= 4) {
+ $accessTime = unpack('V', $buffer)[1];
+ }
+
+ if ($length >= 8) {
+ $modifyTime = unpack('V', substr($buffer, 4, 4))[1];
+ }
+
+ return new self($accessTime, $modifyTime, null, null);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ $data = '';
+
+ if ($this->accessTime !== null) {
+ $data .= pack('V', $this->accessTime);
+
+ if ($this->modifyTime !== null) {
+ $data .= pack('V', $this->modifyTime);
+
+ if ($this->uid !== null) {
+ $data .= pack('v', $this->uid);
+
+ if ($this->gid !== null) {
+ $data .= pack('v', $this->gid);
+ }
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ $data = '';
+
+ if ($this->accessTime !== null) {
+ $data .= pack('V', $this->accessTime);
+
+ if ($this->modifyTime !== null) {
+ $data .= pack('V', $this->modifyTime);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getAccessTime()
+ {
+ return $this->accessTime;
+ }
+
+ /**
+ * @param int|null $accessTime
+ */
+ public function setAccessTime($accessTime)
+ {
+ $this->accessTime = $accessTime;
+ }
+
+ /**
+ * @return \DateTimeInterface|null
+ */
+ public function getAccessDateTime()
+ {
+ try {
+ return $this->accessTime === null ? null :
+ new \DateTimeImmutable('@' . $this->accessTime);
+ } catch (\Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getModifyTime()
+ {
+ return $this->modifyTime;
+ }
+
+ /**
+ * @param int|null $modifyTime
+ */
+ public function setModifyTime($modifyTime)
+ {
+ $this->modifyTime = $modifyTime;
+ }
+
+ /**
+ * @return \DateTimeInterface|null
+ */
+ public function getModifyDateTime()
+ {
+ try {
+ return $this->modifyTime === null ? null :
+ new \DateTimeImmutable('@' . $this->modifyTime);
+ } catch (\Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getUid()
+ {
+ return $this->uid;
+ }
+
+ /**
+ * @param int|null $uid
+ */
+ public function setUid($uid)
+ {
+ $this->uid = $uid;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getGid()
+ {
+ return $this->gid;
+ }
+
+ /**
+ * @param int|null $gid
+ */
+ public function setGid($gid)
+ {
+ $this->gid = $gid;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $args = [self::HEADER_ID];
+ $format = '0x%04x OldUnix:';
+
+ if (($modifyTime = $this->getModifyDateTime()) !== null) {
+ $format .= ' Modify:[%s]';
+ $args[] = $modifyTime->format(\DATE_ATOM);
+ }
+
+ if (($accessTime = $this->getAccessDateTime()) !== null) {
+ $format .= ' Access:[%s]';
+ $args[] = $accessTime->format(\DATE_ATOM);
+ }
+
+ if ($this->uid !== null) {
+ $format .= ' UID=%d';
+ $args[] = $this->uid;
+ }
+
+ if ($this->gid !== null) {
+ $format .= ' GID=%d';
+ $args[] = $this->gid;
+ }
+
+ return vsprintf($format, $args);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php
new file mode 100644
index 0000000..d8280b6
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php
@@ -0,0 +1,76 @@
+getUnicodeValue()
+ );
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php
new file mode 100644
index 0000000..047b2d0
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php
@@ -0,0 +1,77 @@
+getUnicodeValue()
+ );
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php
new file mode 100644
index 0000000..699c193
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php
@@ -0,0 +1,116 @@
+headerId = (int) $headerId;
+ $this->data = (string) $data;
+ }
+
+ /**
+ * @param int $headerId
+ */
+ public function setHeaderId($headerId)
+ {
+ $this->headerId = $headerId;
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return $this->headerId;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ throw new RuntimeException('Unsupport parse');
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ throw new RuntimeException('Unsupport parse');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function packLocalFileData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function packCentralDirData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * @return string
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * @param string $data
+ */
+ public function setData($data)
+ {
+ $this->data = (string) $data;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $args = [$this->headerId, $this->data];
+ $format = '0x%04x Unrecognized Extra Field: "%s"';
+
+ return vsprintf($format, $args);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php
new file mode 100644
index 0000000..0a50dca
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php
@@ -0,0 +1,387 @@
+ */
+ private static $encryptionStrengths = [
+ self::KEY_STRENGTH_128BIT => 128,
+ self::KEY_STRENGTH_192BIT => 192,
+ self::KEY_STRENGTH_256BIT => 256,
+ ];
+
+ /** @var array */
+ private static $MAP_KEY_STRENGTH_METHODS = [
+ self::KEY_STRENGTH_128BIT => ZipEncryptionMethod::WINZIP_AES_128,
+ self::KEY_STRENGTH_192BIT => ZipEncryptionMethod::WINZIP_AES_192,
+ self::KEY_STRENGTH_256BIT => ZipEncryptionMethod::WINZIP_AES_256,
+ ];
+
+ /** @var int Integer version number specific to the zip vendor */
+ private $vendorVersion = self::VERSION_AE1;
+
+ /** @var int Integer mode value indicating AES encryption strength */
+ private $keyStrength = self::KEY_STRENGTH_256BIT;
+
+ /** @var int The actual compression method used to compress the file */
+ private $compressionMethod;
+
+ /**
+ * @param int $vendorVersion Integer version number specific to the zip vendor
+ * @param int $keyStrength Integer mode value indicating AES encryption strength
+ * @param int $compressionMethod The actual compression method used to compress the file
+ *
+ * @throws ZipUnsupportMethodException
+ */
+ public function __construct($vendorVersion, $keyStrength, $compressionMethod)
+ {
+ $this->setVendorVersion($vendorVersion);
+ $this->setKeyStrength($keyStrength);
+ $this->setCompressionMethod($compressionMethod);
+ }
+
+ /**
+ * @param ZipEntry $entry
+ *
+ * @throws ZipUnsupportMethodException
+ *
+ * @return WinZipAesExtraField
+ */
+ public static function create(ZipEntry $entry)
+ {
+ $keyStrength = array_search($entry->getEncryptionMethod(), self::$MAP_KEY_STRENGTH_METHODS, true);
+
+ if ($keyStrength === false) {
+ throw new InvalidArgumentException('Not support encryption method ' . $entry->getEncryptionMethod());
+ }
+
+ // WinZip 11 will continue to use AE-2, with no CRC, for very small files
+ // of less than 20 bytes. It will also use AE-2 for files compressed in
+ // BZIP2 format, because this format has internal integrity checks
+ // equivalent to a CRC check built in.
+ //
+ // https://www.winzip.com/win/en/aes_info.html
+ $vendorVersion = (
+ $entry->getUncompressedSize() < 20 ||
+ $entry->getCompressionMethod() === ZipCompressionMethod::BZIP2
+ ) ?
+ self::VERSION_AE2 :
+ self::VERSION_AE1;
+
+ $field = new self($vendorVersion, $keyStrength, $entry->getCompressionMethod());
+
+ $entry->getLocalExtraFields()->add($field);
+ $entry->getCdExtraFields()->add($field);
+
+ return $field;
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException on error
+ *
+ * @return WinZipAesExtraField
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ $size = \strlen($buffer);
+
+ if ($size !== self::DATA_SIZE) {
+ throw new ZipException(
+ sprintf(
+ 'WinZip AES Extra data invalid size: %d. Must be %d',
+ $size,
+ self::DATA_SIZE
+ )
+ );
+ }
+
+ $data = unpack('vvendorVersion/vvendorId/ckeyStrength/vcompressionMethod', $buffer);
+
+ if ($data['vendorId'] !== self::VENDOR_ID) {
+ throw new ZipException(
+ sprintf(
+ 'Vendor id invalid: %d. Must be %d',
+ $data['vendorId'],
+ self::VENDOR_ID
+ )
+ );
+ }
+
+ return new self(
+ $data['vendorVersion'],
+ $data['keyStrength'],
+ $data['compressionMethod']
+ );
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException
+ *
+ * @return WinZipAesExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ return self::unpackLocalFileData($buffer, $entry);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ return pack(
+ 'vvcv',
+ $this->vendorVersion,
+ self::VENDOR_ID,
+ $this->keyStrength,
+ $this->compressionMethod
+ );
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ return $this->packLocalFileData();
+ }
+
+ /**
+ * Returns the vendor version.
+ *
+ * @return int
+ *
+ * @see WinZipAesExtraField::VERSION_AE2
+ * @see WinZipAesExtraField::VERSION_AE1
+ */
+ public function getVendorVersion()
+ {
+ return $this->vendorVersion;
+ }
+
+ /**
+ * Sets the vendor version.
+ *
+ * @param int $vendorVersion the vendor version
+ *
+ * @see WinZipAesExtraField::VERSION_AE2
+ * @see WinZipAesExtraField::VERSION_AE1
+ */
+ public function setVendorVersion($vendorVersion)
+ {
+ $vendorVersion = (int) $vendorVersion;
+
+ if (!\in_array($vendorVersion, self::$allowVendorVersions, true)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Unsupport WinZip AES vendor version: %d',
+ $vendorVersion
+ )
+ );
+ }
+ $this->vendorVersion = $vendorVersion;
+ }
+
+ /**
+ * Returns vendor id.
+ *
+ * @return int
+ */
+ public function getVendorId()
+ {
+ return self::VENDOR_ID;
+ }
+
+ /**
+ * @return int
+ */
+ public function getKeyStrength()
+ {
+ return $this->keyStrength;
+ }
+
+ /**
+ * Set key strength.
+ *
+ * @param int $keyStrength
+ */
+ public function setKeyStrength($keyStrength)
+ {
+ $keyStrength = (int) $keyStrength;
+
+ if (!isset(self::$encryptionStrengths[$keyStrength])) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Key strength %d not support value. Allow values: %s',
+ $keyStrength,
+ implode(', ', array_keys(self::$encryptionStrengths))
+ )
+ );
+ }
+ $this->keyStrength = $keyStrength;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCompressionMethod()
+ {
+ return $this->compressionMethod;
+ }
+
+ /**
+ * @param int $compressionMethod
+ *
+ * @throws ZipUnsupportMethodException
+ */
+ public function setCompressionMethod($compressionMethod)
+ {
+ $compressionMethod = (int) $compressionMethod;
+ ZipCompressionMethod::checkSupport($compressionMethod);
+ $this->compressionMethod = $compressionMethod;
+ }
+
+ /**
+ * @return int
+ */
+ public function getEncryptionStrength()
+ {
+ return self::$encryptionStrengths[$this->keyStrength];
+ }
+
+ /**
+ * @return int
+ */
+ public function getEncryptionMethod()
+ {
+ $keyStrength = $this->getKeyStrength();
+
+ if (!isset(self::$MAP_KEY_STRENGTH_METHODS[$keyStrength])) {
+ throw new InvalidArgumentException('Invalid encryption method');
+ }
+
+ return self::$MAP_KEY_STRENGTH_METHODS[$keyStrength];
+ }
+
+ /**
+ * @return bool
+ */
+ public function isV1()
+ {
+ return $this->vendorVersion === self::VERSION_AE1;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isV2()
+ {
+ return $this->vendorVersion === self::VERSION_AE2;
+ }
+
+ /**
+ * @return int
+ */
+ public function getSaltSize()
+ {
+ return (int) ($this->getEncryptionStrength() / 8 / 2);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return sprintf(
+ '0x%04x WINZIP AES: VendorVersion=%d KeyStrength=0x%02x CompressionMethod=%s',
+ __CLASS__,
+ $this->vendorVersion,
+ $this->keyStrength,
+ $this->compressionMethod
+ );
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php
new file mode 100644
index 0000000..4393a9c
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php
@@ -0,0 +1,311 @@
+uncompressedSize = $uncompressedSize;
+ $this->compressedSize = $compressedSize;
+ $this->localHeaderOffset = $localHeaderOffset;
+ $this->diskStart = $diskStart;
+ }
+
+ /**
+ * Returns the Header ID (type) of this Extra Field.
+ * The Header ID is an unsigned short integer (two bytes)
+ * which must be constant during the life cycle of this object.
+ *
+ * @return int
+ */
+ public function getHeaderId()
+ {
+ return self::HEADER_ID;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException on error
+ *
+ * @return Zip64ExtraField
+ */
+ public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
+ {
+ $length = \strlen($buffer);
+
+ if ($length === 0) {
+ // no local file data at all, may happen if an archive
+ // only holds a ZIP64 extended information extra field
+ // inside the central directory but not inside the local
+ // file header
+ return new self();
+ }
+
+ if ($length < 16) {
+ throw new ZipException(
+ 'Zip64 extended information must contain both size values in the local file header.'
+ );
+ }
+
+ $uncompressedSize = PackUtil::unpackLongLE(substr($buffer, 0, 8));
+ $compressedSize = PackUtil::unpackLongLE(substr($buffer, 8, 8));
+
+ return new self($uncompressedSize, $compressedSize);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param string $buffer the buffer to read data from
+ * @param ZipEntry|null $entry
+ *
+ * @throws ZipException
+ *
+ * @return Zip64ExtraField
+ */
+ public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
+ {
+ if ($entry === null) {
+ throw new RuntimeException('zipEntry is null');
+ }
+
+ $length = \strlen($buffer);
+ $remaining = $length;
+
+ $uncompressedSize = null;
+ $compressedSize = null;
+ $localHeaderOffset = null;
+ $diskStart = null;
+
+ if ($entry->getUncompressedSize() === ZipConstants::ZIP64_MAGIC) {
+ if ($remaining < 8) {
+ throw new ZipException('ZIP64 extension corrupt (no uncompressed size).');
+ }
+ $uncompressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
+ $remaining -= 8;
+ }
+
+ if ($entry->getCompressedSize() === ZipConstants::ZIP64_MAGIC) {
+ if ($remaining < 8) {
+ throw new ZipException('ZIP64 extension corrupt (no compressed size).');
+ }
+ $compressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
+ $remaining -= 8;
+ }
+
+ if ($entry->getLocalHeaderOffset() === ZipConstants::ZIP64_MAGIC) {
+ if ($remaining < 8) {
+ throw new ZipException('ZIP64 extension corrupt (no relative local header offset).');
+ }
+ $localHeaderOffset = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
+ $remaining -= 8;
+ }
+
+ if ($remaining === 4) {
+ $diskStart = unpack('V', substr($buffer, $length - $remaining, 4))[1];
+ }
+
+ return new self($uncompressedSize, $compressedSize, $localHeaderOffset, $diskStart);
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return string the data
+ */
+ public function packLocalFileData()
+ {
+ if ($this->uncompressedSize !== null || $this->compressedSize !== null) {
+ if ($this->uncompressedSize === null || $this->compressedSize === null) {
+ throw new \InvalidArgumentException(
+ 'Zip64 extended information must contain both size values in the local file header.'
+ );
+ }
+
+ return $this->packSizes();
+ }
+
+ return '';
+ }
+
+ /**
+ * @return string
+ */
+ private function packSizes()
+ {
+ $data = '';
+
+ if ($this->uncompressedSize !== null) {
+ $data .= PackUtil::packLongLE($this->uncompressedSize);
+ }
+
+ if ($this->compressedSize !== null) {
+ $data .= PackUtil::packLongLE($this->compressedSize);
+ }
+
+ return $data;
+ }
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return string the data
+ */
+ public function packCentralDirData()
+ {
+ $data = $this->packSizes();
+
+ if ($this->localHeaderOffset !== null) {
+ $data .= PackUtil::packLongLE($this->localHeaderOffset);
+ }
+
+ if ($this->diskStart !== null) {
+ $data .= pack('V', $this->diskStart);
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getUncompressedSize()
+ {
+ return $this->uncompressedSize;
+ }
+
+ /**
+ * @param int|null $uncompressedSize
+ */
+ public function setUncompressedSize($uncompressedSize)
+ {
+ $this->uncompressedSize = $uncompressedSize;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getCompressedSize()
+ {
+ return $this->compressedSize;
+ }
+
+ /**
+ * @param int|null $compressedSize
+ */
+ public function setCompressedSize($compressedSize)
+ {
+ $this->compressedSize = $compressedSize;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getLocalHeaderOffset()
+ {
+ return $this->localHeaderOffset;
+ }
+
+ /**
+ * @param int|null $localHeaderOffset
+ */
+ public function setLocalHeaderOffset($localHeaderOffset)
+ {
+ $this->localHeaderOffset = $localHeaderOffset;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getDiskStart()
+ {
+ return $this->diskStart;
+ }
+
+ /**
+ * @param int|null $diskStart
+ */
+ public function setDiskStart($diskStart)
+ {
+ $this->diskStart = $diskStart;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $args = [self::HEADER_ID];
+ $format = '0x%04x ZIP64: ';
+ $formats = [];
+
+ if ($this->uncompressedSize !== null) {
+ $formats[] = 'SIZE=%d';
+ $args[] = $this->uncompressedSize;
+ }
+
+ if ($this->compressedSize !== null) {
+ $formats[] = 'COMP_SIZE=%d';
+ $args[] = $this->compressedSize;
+ }
+
+ if ($this->localHeaderOffset !== null) {
+ $formats[] = 'OFFSET=%d';
+ $args[] = $this->localHeaderOffset;
+ }
+
+ if ($this->diskStart !== null) {
+ $formats[] = 'DISK_START=%d';
+ $args[] = $this->diskStart;
+ }
+ $format .= implode(' ', $formats);
+
+ return vsprintf($format, $args);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php b/vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php
new file mode 100644
index 0000000..e1332f0
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php
@@ -0,0 +1,107 @@
+
+ * @psalm-var array>
+ */
+ private static $implementations = [
+ ApkAlignmentExtraField::HEADER_ID => ApkAlignmentExtraField::class,
+ AsiExtraField::HEADER_ID => AsiExtraField::class,
+ ExtendedTimestampExtraField::HEADER_ID => ExtendedTimestampExtraField::class,
+ JarMarkerExtraField::HEADER_ID => JarMarkerExtraField::class,
+ NewUnixExtraField::HEADER_ID => NewUnixExtraField::class,
+ NtfsExtraField::HEADER_ID => NtfsExtraField::class,
+ OldUnixExtraField::HEADER_ID => OldUnixExtraField::class,
+ UnicodeCommentExtraField::HEADER_ID => UnicodeCommentExtraField::class,
+ UnicodePathExtraField::HEADER_ID => UnicodePathExtraField::class,
+ WinZipAesExtraField::HEADER_ID => WinZipAesExtraField::class,
+ Zip64ExtraField::HEADER_ID => Zip64ExtraField::class,
+ ];
+
+ private function __construct()
+ {
+ }
+
+ /**
+ * @param string|ZipExtraField $extraField ZipExtraField object or class name
+ */
+ public static function register($extraField)
+ {
+ if (!is_a($extraField, ZipExtraField::class, true)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ '$extraField "%s" is not implements interface %s',
+ (string) $extraField,
+ ZipExtraField::class
+ )
+ );
+ }
+ self::$implementations[\call_user_func([$extraField, 'getHeaderId'])] = $extraField;
+ }
+
+ /**
+ * @param int|string|ZipExtraField $extraType ZipExtraField object or class name or extra header id
+ *
+ * @return bool
+ */
+ public static function unregister($extraType)
+ {
+ $headerId = null;
+
+ if (\is_int($extraType)) {
+ $headerId = $extraType;
+ } elseif (is_a($extraType, ZipExtraField::class, true)) {
+ $headerId = \call_user_func([$extraType, 'getHeaderId']);
+ } else {
+ return false;
+ }
+
+ if (isset(self::$implementations[$headerId])) {
+ unset(self::$implementations[$headerId]);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param int $headerId
+ *
+ * @return string|null
+ */
+ public static function getClassNameOrNull($headerId)
+ {
+ $headerId = (int) $headerId;
+
+ if ($headerId < 0 || $headerId > 0xffff) {
+ throw new \InvalidArgumentException('$headerId out of range: ' . $headerId);
+ }
+
+ if (isset(self::$implementations[$headerId])) {
+ return self::$implementations[$headerId];
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php b/vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php
new file mode 100644
index 0000000..ce69aaf
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php
@@ -0,0 +1,63 @@
+entries = $entries;
+ $this->archiveComment = $archiveComment;
+ }
+
+ /**
+ * @return ZipEntry[]
+ */
+ public function &getEntries()
+ {
+ return $this->entries;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getArchiveComment()
+ {
+ return $this->archiveComment;
+ }
+
+ /**
+ * Count elements of an object.
+ *
+ * @see https://php.net/manual/en/countable.count.php
+ *
+ * @return int The custom count as an integer.
+ * The return value is cast to an integer.
+ */
+ public function count()
+ {
+ return \count($this->entries);
+ }
+
+ /**
+ * When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties.
+ * Any properties that are references to other variables, will remain references.
+ * Once the cloning is complete, if a __clone() method is defined,
+ * then the newly created object's __clone() method will be called, to allow any necessary properties that need to
+ * be changed. NOT CALLABLE DIRECTLY.
+ *
+ * @see https://php.net/manual/en/language.oop5.cloning.php
+ */
+ public function __clone()
+ {
+ foreach ($this->entries as $key => $value) {
+ $this->entries[$key] = clone $value;
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/ZipContainer.php b/vendor/nelexa/zip/src/Model/ZipContainer.php
new file mode 100644
index 0000000..6cfe87e
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/ZipContainer.php
@@ -0,0 +1,386 @@
+getEntries() as $entryName => $entry) {
+ $entries[$entryName] = clone $entry;
+ }
+ $archiveComment = $sourceContainer->getArchiveComment();
+ }
+ parent::__construct($entries, $archiveComment);
+ $this->sourceContainer = $sourceContainer;
+ }
+
+ /**
+ * @return ImmutableZipContainer|null
+ */
+ public function getSourceContainer()
+ {
+ return $this->sourceContainer;
+ }
+
+ /**
+ * @param ZipEntry $entry
+ */
+ public function addEntry(ZipEntry $entry)
+ {
+ $this->entries[$entry->getName()] = $entry;
+ }
+
+ /**
+ * @param string|ZipEntry $entry
+ *
+ * @return bool
+ */
+ public function deleteEntry($entry)
+ {
+ $entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
+
+ if (isset($this->entries[$entry])) {
+ unset($this->entries[$entry]);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string|ZipEntry $old
+ * @param string|ZipEntry $new
+ *
+ * @throws ZipException
+ *
+ * @return ZipEntry New zip entry
+ */
+ public function renameEntry($old, $new)
+ {
+ $old = $old instanceof ZipEntry ? $old->getName() : (string) $old;
+ $new = $new instanceof ZipEntry ? $new->getName() : (string) $new;
+
+ if (isset($this->entries[$new])) {
+ throw new InvalidArgumentException('New entry name ' . $new . ' is exists.');
+ }
+
+ $entry = $this->getEntry($old);
+ $newEntry = $entry->rename($new);
+
+ $this->deleteEntry($entry);
+ $this->addEntry($newEntry);
+
+ return $newEntry;
+ }
+
+ /**
+ * @param string|ZipEntry $entryName
+ *
+ * @throws ZipEntryNotFoundException
+ *
+ * @return ZipEntry
+ */
+ public function getEntry($entryName)
+ {
+ $entry = $this->getEntryOrNull($entryName);
+
+ if ($entry !== null) {
+ return $entry;
+ }
+
+ throw new ZipEntryNotFoundException($entryName);
+ }
+
+ /**
+ * @param string|ZipEntry $entryName
+ *
+ * @return ZipEntry|null
+ */
+ public function getEntryOrNull($entryName)
+ {
+ $entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
+
+ return isset($this->entries[$entryName]) ? $this->entries[$entryName] : null;
+ }
+
+ /**
+ * @param string|ZipEntry $entryName
+ *
+ * @return bool
+ */
+ public function hasEntry($entryName)
+ {
+ $entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
+
+ return isset($this->entries[$entryName]);
+ }
+
+ /**
+ * Delete all entries.
+ */
+ public function deleteAll()
+ {
+ $this->entries = [];
+ }
+
+ /**
+ * Delete entries by regex pattern.
+ *
+ * @param string $regexPattern Regex pattern
+ *
+ * @return ZipEntry[] Deleted entries
+ */
+ public function deleteByRegex($regexPattern)
+ {
+ if (empty($regexPattern)) {
+ throw new InvalidArgumentException('The regex pattern is not specified');
+ }
+
+ /** @var ZipEntry[] $found */
+ $found = [];
+
+ foreach ($this->entries as $entryName => $entry) {
+ if (preg_match($regexPattern, $entryName)) {
+ $found[] = $entry;
+ }
+ }
+
+ foreach ($found as $entry) {
+ $this->deleteEntry($entry);
+ }
+
+ return $found;
+ }
+
+ /**
+ * Undo all changes done in the archive.
+ */
+ public function unchangeAll()
+ {
+ $this->entries = [];
+
+ if ($this->sourceContainer !== null) {
+ foreach ($this->sourceContainer->getEntries() as $entry) {
+ $this->entries[$entry->getName()] = clone $entry;
+ }
+ }
+ $this->unchangeArchiveComment();
+ }
+
+ /**
+ * Undo change archive comment.
+ */
+ public function unchangeArchiveComment()
+ {
+ $this->archiveComment = null;
+
+ if ($this->sourceContainer !== null) {
+ $this->archiveComment = $this->sourceContainer->archiveComment;
+ }
+ }
+
+ /**
+ * Revert all changes done to an entry with the given name.
+ *
+ * @param string|ZipEntry $entry Entry name or ZipEntry
+ *
+ * @return bool
+ */
+ public function unchangeEntry($entry)
+ {
+ $entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
+
+ if (
+ $this->sourceContainer !== null &&
+ isset($this->entries[$entry], $this->sourceContainer->entries[$entry])
+ ) {
+ $this->entries[$entry] = clone $this->sourceContainer->entries[$entry];
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Entries sort by name.
+ *
+ * Example:
+ * ```php
+ * $zipContainer->sortByName(static function (string $nameA, string $nameB): int {
+ * return strcmp($nameA, $nameB);
+ * });
+ * ```
+ *
+ * @param callable $cmp
+ */
+ public function sortByName(callable $cmp)
+ {
+ uksort($this->entries, $cmp);
+ }
+
+ /**
+ * Entries sort by entry.
+ *
+ * Example:
+ * ```php
+ * $zipContainer->sortByEntry(static function (ZipEntry $a, ZipEntry $b): int {
+ * return strcmp($a->getName(), $b->getName());
+ * });
+ * ```
+ *
+ * @param callable $cmp
+ */
+ public function sortByEntry(callable $cmp)
+ {
+ uasort($this->entries, $cmp);
+ }
+
+ /**
+ * @param string|null $archiveComment
+ */
+ public function setArchiveComment($archiveComment)
+ {
+ if ($archiveComment !== null && $archiveComment !== '') {
+ $archiveComment = (string) $archiveComment;
+ $length = \strlen($archiveComment);
+
+ if ($length > 0xffff) {
+ throw new InvalidArgumentException('Length comment out of range');
+ }
+ }
+ $this->archiveComment = $archiveComment;
+ }
+
+ /**
+ * @return ZipEntryMatcher
+ */
+ public function matcher()
+ {
+ return new ZipEntryMatcher($this);
+ }
+
+ /**
+ * Specify a password for extracting files.
+ *
+ * @param string|null $password
+ */
+ public function setReadPassword($password)
+ {
+ if ($this->sourceContainer !== null) {
+ foreach ($this->sourceContainer->entries as $entry) {
+ if ($entry->isEncrypted()) {
+ $entry->setPassword($password);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param string $entryName
+ * @param string $password
+ *
+ * @throws ZipEntryNotFoundException
+ * @throws ZipException
+ */
+ public function setReadPasswordEntry($entryName, $password)
+ {
+ if (!isset($this->sourceContainer->entries[$entryName])) {
+ throw new ZipEntryNotFoundException($entryName);
+ }
+
+ if ($this->sourceContainer->entries[$entryName]->isEncrypted()) {
+ $this->sourceContainer->entries[$entryName]->setPassword($password);
+ }
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getZipAlign()
+ {
+ return $this->zipAlign;
+ }
+
+ /**
+ * @param int|null $zipAlign
+ */
+ public function setZipAlign($zipAlign)
+ {
+ $this->zipAlign = $zipAlign === null ? null : (int) $zipAlign;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isZipAlign()
+ {
+ return $this->zipAlign !== null;
+ }
+
+ /**
+ * @param string|null $writePassword
+ */
+ public function setWritePassword($writePassword)
+ {
+ $this->matcher()->all()->setPassword($writePassword);
+ }
+
+ /**
+ * Remove password.
+ */
+ public function removePassword()
+ {
+ $this->matcher()->all()->setPassword(null);
+ }
+
+ /**
+ * @param string|ZipEntry $entryName
+ */
+ public function removePasswordEntry($entryName)
+ {
+ $this->matcher()->add($entryName)->setPassword(null);
+ }
+
+ /**
+ * @param int $encryptionMethod
+ */
+ public function setEncryptionMethod($encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256)
+ {
+ $this->matcher()->all()->setEncryptionMethod($encryptionMethod);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/ZipData.php b/vendor/nelexa/zip/src/Model/ZipData.php
new file mode 100644
index 0000000..30f8289
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/ZipData.php
@@ -0,0 +1,28 @@
+setName($name, $charset);
+
+ $this->cdExtraFields = new ExtraFieldsCollection();
+ $this->localExtraFields = new ExtraFieldsCollection();
+ }
+
+ /**
+ * This method only internal use.
+ *
+ * @param string $name
+ * @param int $createdOS
+ * @param int $extractedOS
+ * @param int $softwareVersion
+ * @param int $extractVersion
+ * @param int $compressionMethod
+ * @param int $gpbf
+ * @param int $dosTime
+ * @param int $crc
+ * @param int $compressedSize
+ * @param int $uncompressedSize
+ * @param int $internalAttributes
+ * @param int $externalAttributes
+ * @param int $offsetLocalHeader
+ * @param string|null $comment
+ * @param string|null $charset
+ *
+ * @return ZipEntry
+ *
+ * @internal
+ *
+ * @noinspection PhpTooManyParametersInspection
+ */
+ public static function create(
+ $name,
+ $createdOS,
+ $extractedOS,
+ $softwareVersion,
+ $extractVersion,
+ $compressionMethod,
+ $gpbf,
+ $dosTime,
+ $crc,
+ $compressedSize,
+ $uncompressedSize,
+ $internalAttributes,
+ $externalAttributes,
+ $offsetLocalHeader,
+ $comment,
+ $charset
+ ) {
+ $entry = new self($name);
+ $entry->createdOS = (int) $createdOS;
+ $entry->extractedOS = (int) $extractedOS;
+ $entry->softwareVersion = (int) $softwareVersion;
+ $entry->extractVersion = (int) $extractVersion;
+ $entry->compressionMethod = (int) $compressionMethod;
+ $entry->generalPurposeBitFlags = (int) $gpbf;
+ $entry->dosTime = (int) $dosTime;
+ $entry->crc = (int) $crc;
+ $entry->compressedSize = (int) $compressedSize;
+ $entry->uncompressedSize = (int) $uncompressedSize;
+ $entry->internalAttributes = (int) $internalAttributes;
+ $entry->externalAttributes = (int) $externalAttributes;
+ $entry->localHeaderOffset = (int) $offsetLocalHeader;
+ $entry->setComment($comment);
+ $entry->setCharset($charset);
+ $entry->updateCompressionLevel();
+
+ return $entry;
+ }
+
+ /**
+ * Set entry name.
+ *
+ * @param string $name New entry name
+ * @param string|null $charset
+ *
+ * @return ZipEntry
+ */
+ private function setName($name, $charset = null)
+ {
+ if ($name === null) {
+ throw new InvalidArgumentException('zip entry name is null');
+ }
+
+ $name = ltrim((string) $name, '\\/');
+
+ if ($name === '') {
+ throw new InvalidArgumentException('Empty zip entry name');
+ }
+
+ $name = (string) $name;
+ $length = \strlen($name);
+
+ if ($length > 0xffff) {
+ throw new InvalidArgumentException('Illegal zip entry name parameter');
+ }
+
+ $this->setCharset($charset);
+
+ if ($this->charset === null && !StringUtil::isASCII($name)) {
+ $this->enableUtf8Name(true);
+ }
+ $this->name = $name;
+ $this->isDirectory = ($length = \strlen($name)) >= 1 && $name[$length - 1] === '/';
+ $this->externalAttributes = $this->isDirectory ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE;
+
+ if ($this->extractVersion !== self::UNKNOWN) {
+ $this->extractVersion = max(
+ $this->extractVersion,
+ $this->isDirectory ?
+ ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO :
+ ZipVersion::v10_DEFAULT_MIN
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string|null $charset
+ *
+ * @return ZipEntry
+ *
+ * @see DosCodePage::getCodePages()
+ */
+ public function setCharset($charset = null)
+ {
+ if ($charset !== null && $charset === '') {
+ throw new InvalidArgumentException('Empty charset');
+ }
+ $this->charset = $charset;
+
+ return $this;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * @param string $newName New entry name
+ *
+ * @return ZipEntry new {@see ZipEntry} object with new name
+ *
+ * @internal
+ */
+ public function rename($newName)
+ {
+ $newEntry = clone $this;
+ $newEntry->setName($newName);
+
+ $newEntry->removeExtraField(UnicodePathExtraField::HEADER_ID);
+
+ return $newEntry;
+ }
+
+ /**
+ * Returns the ZIP entry name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return ZipData|null
+ *
+ * @internal
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * @param ZipData|null $data
+ *
+ * @internal
+ */
+ public function setData($data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * @return int Get platform
+ *
+ * @deprecated Use {@see ZipEntry::getCreatedOS()}
+ */
+ public function getPlatform()
+ {
+ @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getCreatedOS()', \E_USER_DEPRECATED);
+
+ return $this->getCreatedOS();
+ }
+
+ /**
+ * @param int $platform
+ *
+ * @return ZipEntry
+ *
+ * @deprecated Use {@see ZipEntry::setCreatedOS()}
+ */
+ public function setPlatform($platform)
+ {
+ @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setCreatedOS()', \E_USER_DEPRECATED);
+
+ return $this->setCreatedOS($platform);
+ }
+
+ /**
+ * @return int platform
+ */
+ public function getCreatedOS()
+ {
+ return $this->createdOS;
+ }
+
+ /**
+ * Set platform.
+ *
+ * @param int $platform
+ *
+ * @return ZipEntry
+ */
+ public function setCreatedOS($platform)
+ {
+ $platform = (int) $platform;
+
+ if ($platform < 0x00 || $platform > 0xff) {
+ throw new InvalidArgumentException('Platform out of range');
+ }
+ $this->createdOS = $platform;
+
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getExtractedOS()
+ {
+ return $this->extractedOS;
+ }
+
+ /**
+ * Set extracted OS.
+ *
+ * @param int $platform
+ *
+ * @return ZipEntry
+ */
+ public function setExtractedOS($platform)
+ {
+ $platform = (int) $platform;
+
+ if ($platform < 0x00 || $platform > 0xff) {
+ throw new InvalidArgumentException('Platform out of range');
+ }
+ $this->extractedOS = $platform;
+
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getSoftwareVersion()
+ {
+ if ($this->softwareVersion === self::UNKNOWN) {
+ return $this->getExtractVersion();
+ }
+
+ return $this->softwareVersion;
+ }
+
+ /**
+ * @param int $softwareVersion
+ *
+ * @return ZipEntry
+ */
+ public function setSoftwareVersion($softwareVersion)
+ {
+ $this->softwareVersion = (int) $softwareVersion;
+
+ return $this;
+ }
+
+ /**
+ * Version needed to extract.
+ *
+ * @return int
+ *
+ * @deprecated Use {@see ZipEntry::getExtractVersion()}
+ */
+ public function getVersionNeededToExtract()
+ {
+ @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getExtractVersion()', \E_USER_DEPRECATED);
+
+ return $this->getExtractVersion();
+ }
+
+ /**
+ * Version needed to extract.
+ *
+ * @return int
+ */
+ public function getExtractVersion()
+ {
+ if ($this->extractVersion === self::UNKNOWN) {
+ if (ZipEncryptionMethod::isWinZipAesMethod($this->encryptionMethod)) {
+ return ZipVersion::v51_ENCR_AES_RC2_CORRECT;
+ }
+
+ if ($this->compressionMethod === ZipCompressionMethod::BZIP2) {
+ return ZipVersion::v46_BZIP2;
+ }
+
+ if ($this->isZip64ExtensionsRequired()) {
+ return ZipVersion::v45_ZIP64_EXT;
+ }
+
+ if (
+ $this->compressionMethod === ZipCompressionMethod::DEFLATED ||
+ $this->isDirectory ||
+ $this->encryptionMethod === ZipEncryptionMethod::PKWARE
+ ) {
+ return ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO;
+ }
+
+ return ZipVersion::v10_DEFAULT_MIN;
+ }
+
+ return $this->extractVersion;
+ }
+
+ /**
+ * Set version needed to extract.
+ *
+ * @param int $version
+ *
+ * @return ZipEntry
+ *
+ * @deprecated Use {@see ZipEntry::setExtractVersion()}
+ */
+ public function setVersionNeededToExtract($version)
+ {
+ @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setExtractVersion()', \E_USER_DEPRECATED);
+
+ return $this->setExtractVersion($version);
+ }
+
+ /**
+ * Set version needed to extract.
+ *
+ * @param int $version
+ *
+ * @return ZipEntry
+ */
+ public function setExtractVersion($version)
+ {
+ $this->extractVersion = max(ZipVersion::v10_DEFAULT_MIN, (int) $version);
+
+ return $this;
+ }
+
+ /**
+ * Returns the compressed size of this entry.
+ *
+ * @return int
+ */
+ public function getCompressedSize()
+ {
+ return $this->compressedSize;
+ }
+
+ /**
+ * Sets the compressed size of this entry.
+ *
+ * @param int $compressedSize the Compressed Size
+ *
+ * @return ZipEntry
+ *
+ * @internal
+ */
+ public function setCompressedSize($compressedSize)
+ {
+ $compressedSize = (int) $compressedSize;
+
+ if ($compressedSize < self::UNKNOWN) {
+ throw new InvalidArgumentException('Compressed size < ' . self::UNKNOWN);
+ }
+ $this->compressedSize = $compressedSize;
+
+ return $this;
+ }
+
+ /**
+ * Returns the uncompressed size of this entry.
+ *
+ * @return int
+ *
+ * @deprecated Use {@see ZipEntry::getUncompressedSize()}
+ */
+ public function getSize()
+ {
+ @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getUncompressedSize()', \E_USER_DEPRECATED);
+
+ return $this->getUncompressedSize();
+ }
+
+ /**
+ * Sets the uncompressed size of this entry.
+ *
+ * @param int $size the (Uncompressed) Size
+ *
+ * @return ZipEntry
+ *
+ * @deprecated Use {@see ZipEntry::setUncompressedSize()}
+ *
+ * @internal
+ */
+ public function setSize($size)
+ {
+ @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setUncompressedSize()', \E_USER_DEPRECATED);
+
+ return $this->setUncompressedSize($size);
+ }
+
+ /**
+ * Returns the uncompressed size of this entry.
+ *
+ * @return int
+ */
+ public function getUncompressedSize()
+ {
+ return $this->uncompressedSize;
+ }
+
+ /**
+ * Sets the uncompressed size of this entry.
+ *
+ * @param int $uncompressedSize the (Uncompressed) Size
+ *
+ * @return ZipEntry
+ *
+ * @internal
+ */
+ public function setUncompressedSize($uncompressedSize)
+ {
+ $uncompressedSize = (int) $uncompressedSize;
+
+ if ($uncompressedSize < self::UNKNOWN) {
+ throw new InvalidArgumentException('Uncompressed size < ' . self::UNKNOWN);
+ }
+ $this->uncompressedSize = $uncompressedSize;
+
+ return $this;
+ }
+
+ /**
+ * Return relative Offset Of Local File Header.
+ *
+ * @return int
+ */
+ public function getLocalHeaderOffset()
+ {
+ return $this->localHeaderOffset;
+ }
+
+ /**
+ * @param int $localHeaderOffset
+ *
+ * @return ZipEntry
+ *
+ * @internal
+ */
+ public function setLocalHeaderOffset($localHeaderOffset)
+ {
+ $localHeaderOffset = (int) $localHeaderOffset;
+
+ if ($localHeaderOffset < 0) {
+ throw new InvalidArgumentException('Negative $localHeaderOffset');
+ }
+ $this->localHeaderOffset = $localHeaderOffset;
+
+ return $this;
+ }
+
+ /**
+ * Return relative Offset Of Local File Header.
+ *
+ * @return int
+ *
+ * @deprecated Use {@see ZipEntry::getLocalHeaderOffset()}
+ */
+ public function getOffset()
+ {
+ @trigger_error(
+ __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getLocalHeaderOffset()',
+ \E_USER_DEPRECATED
+ );
+
+ return $this->getLocalHeaderOffset();
+ }
+
+ /**
+ * @param int $offset
+ *
+ * @return ZipEntry
+ *
+ * @deprecated Use {@see ZipEntry::setLocalHeaderOffset()}
+ *
+ * @internal
+ */
+ public function setOffset($offset)
+ {
+ @trigger_error(
+ __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setLocalHeaderOffset()',
+ \E_USER_DEPRECATED
+ );
+
+ return $this->setLocalHeaderOffset($offset);
+ }
+
+ /**
+ * Returns the General Purpose Bit Flags.
+ *
+ * @return int
+ */
+ public function getGeneralPurposeBitFlags()
+ {
+ return $this->generalPurposeBitFlags;
+ }
+
+ /**
+ * Sets the General Purpose Bit Flags.
+ *
+ * @param int $gpbf general purpose bit flags
+ *
+ * @return ZipEntry
+ *
+ * @internal
+ */
+ public function setGeneralPurposeBitFlags($gpbf)
+ {
+ $gpbf = (int) $gpbf;
+
+ if ($gpbf < 0x0000 || $gpbf > 0xffff) {
+ throw new InvalidArgumentException('general purpose bit flags out of range');
+ }
+ $this->generalPurposeBitFlags = $gpbf;
+ $this->updateCompressionLevel();
+
+ return $this;
+ }
+
+ private function updateCompressionLevel()
+ {
+ if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
+ $bit1 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG1);
+ $bit2 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG2);
+
+ if ($bit1 && !$bit2) {
+ $this->compressionLevel = ZipCompressionLevel::MAXIMUM;
+ } elseif (!$bit1 && $bit2) {
+ $this->compressionLevel = ZipCompressionLevel::FAST;
+ } elseif ($bit1 && $bit2) {
+ $this->compressionLevel = ZipCompressionLevel::SUPER_FAST;
+ } else {
+ $this->compressionLevel = ZipCompressionLevel::NORMAL;
+ }
+ }
+ }
+
+ /**
+ * @param int $mask
+ * @param bool $enable
+ *
+ * @return ZipEntry
+ */
+ private function setGeneralBitFlag($mask, $enable)
+ {
+ if ($enable) {
+ $this->generalPurposeBitFlags |= $mask;
+ } else {
+ $this->generalPurposeBitFlags &= ~$mask;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param int $mask
+ *
+ * @return bool
+ */
+ private function isSetGeneralBitFlag($mask)
+ {
+ return ($this->generalPurposeBitFlags & $mask) === $mask;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isDataDescriptorEnabled()
+ {
+ return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR);
+ }
+
+ /**
+ * Enabling or disabling the use of the Data Descriptor block.
+ *
+ * @param bool $enabled
+ */
+ public function enableDataDescriptor($enabled = true)
+ {
+ $this->setGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR, (bool) $enabled);
+ }
+
+ /**
+ * @param bool $enabled
+ */
+ public function enableUtf8Name($enabled)
+ {
+ $this->setGeneralBitFlag(GeneralPurposeBitFlag::UTF8, (bool) $enabled);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isUtf8Flag()
+ {
+ return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::UTF8);
+ }
+
+ /**
+ * Returns true if and only if this ZIP entry is encrypted.
+ *
+ * @return bool
+ */
+ public function isEncrypted()
+ {
+ return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isStrongEncryption()
+ {
+ return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::STRONG_ENCRYPTION);
+ }
+
+ /**
+ * Sets the encryption property to false and removes any other
+ * encryption artifacts.
+ *
+ * @return ZipEntry
+ */
+ public function disableEncryption()
+ {
+ $this->setEncrypted(false);
+ $this->removeExtraField(WinZipAesExtraField::HEADER_ID);
+ $this->encryptionMethod = ZipEncryptionMethod::NONE;
+ $this->password = null;
+ $this->extractVersion = self::UNKNOWN;
+
+ return $this;
+ }
+
+ /**
+ * Sets the encryption flag for this ZIP entry.
+ *
+ * @param bool $encrypted
+ *
+ * @return ZipEntry
+ */
+ private function setEncrypted($encrypted)
+ {
+ $encrypted = (bool) $encrypted;
+ $this->setGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION, $encrypted);
+
+ return $this;
+ }
+
+ /**
+ * Returns the compression method for this entry.
+ *
+ * @return int
+ *
+ * @deprecated Use {@see ZipEntry::getCompressionMethod()}
+ */
+ public function getMethod()
+ {
+ @trigger_error(
+ __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getCompressionMethod()',
+ \E_USER_DEPRECATED
+ );
+
+ return $this->getCompressionMethod();
+ }
+
+ /**
+ * Returns the compression method for this entry.
+ *
+ * @return int
+ */
+ public function getCompressionMethod()
+ {
+ return $this->compressionMethod;
+ }
+
+ /**
+ * Sets the compression method for this entry.
+ *
+ * @param int $method
+ *
+ * @throws ZipUnsupportMethodException
+ *
+ * @return ZipEntry
+ *
+ * @deprecated Use {@see ZipEntry::setCompressionMethod()}
+ */
+ public function setMethod($method)
+ {
+ @trigger_error(
+ __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setCompressionMethod()',
+ \E_USER_DEPRECATED
+ );
+
+ return $this->setCompressionMethod($method);
+ }
+
+ /**
+ * Sets the compression method for this entry.
+ *
+ * @param int $compressionMethod
+ *
+ * @throws ZipUnsupportMethodException
+ *
+ * @return ZipEntry
+ *
+ * @see ZipCompressionMethod::STORED
+ * @see ZipCompressionMethod::DEFLATED
+ * @see ZipCompressionMethod::BZIP2
+ */
+ public function setCompressionMethod($compressionMethod)
+ {
+ $compressionMethod = (int) $compressionMethod;
+
+ if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) {
+ throw new InvalidArgumentException('method out of range: ' . $compressionMethod);
+ }
+
+ ZipCompressionMethod::checkSupport($compressionMethod);
+
+ $this->compressionMethod = $compressionMethod;
+ $this->updateCompressionLevel();
+ $this->extractVersion = self::UNKNOWN;
+
+ return $this;
+ }
+
+ /**
+ * Get Unix Timestamp.
+ *
+ * @return int
+ */
+ public function getTime()
+ {
+ if ($this->getDosTime() === self::UNKNOWN) {
+ return self::UNKNOWN;
+ }
+
+ return DateTimeConverter::msDosToUnix($this->getDosTime());
+ }
+
+ /**
+ * Get Dos Time.
+ *
+ * @return int
+ */
+ public function getDosTime()
+ {
+ return $this->dosTime;
+ }
+
+ /**
+ * Set Dos Time.
+ *
+ * @param int $dosTime
+ *
+ * @return ZipEntry
+ */
+ public function setDosTime($dosTime)
+ {
+ $dosTime = (int) $dosTime;
+
+ if (\PHP_INT_SIZE === 8) {
+ if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
+ throw new InvalidArgumentException('DosTime out of range');
+ }
+ }
+
+ $this->dosTime = $dosTime;
+
+ return $this;
+ }
+
+ /**
+ * Set time from unix timestamp.
+ *
+ * @param int $unixTimestamp
+ *
+ * @return ZipEntry
+ */
+ public function setTime($unixTimestamp)
+ {
+ if ($unixTimestamp !== self::UNKNOWN) {
+ $this->setDosTime(DateTimeConverter::unixToMsDos($unixTimestamp));
+ } else {
+ $this->dosTime = 0;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the external file attributes.
+ *
+ * @return int the external file attributes
+ */
+ public function getExternalAttributes()
+ {
+ return $this->externalAttributes;
+ }
+
+ /**
+ * Sets the external file attributes.
+ *
+ * @param int $externalAttributes the external file attributes
+ *
+ * @return ZipEntry
+ */
+ public function setExternalAttributes($externalAttributes)
+ {
+ $this->externalAttributes = (int) $externalAttributes;
+
+ if (\PHP_INT_SIZE === 8) {
+ if ($externalAttributes < 0x00000000 || $externalAttributes > 0xffffffff) {
+ throw new InvalidArgumentException('external attributes out of range: ' . $externalAttributes);
+ }
+ }
+
+ $this->externalAttributes = $externalAttributes;
+
+ return $this;
+ }
+
+ /**
+ * Returns the internal file attributes.
+ *
+ * @return int the internal file attributes
+ */
+ public function getInternalAttributes()
+ {
+ return $this->internalAttributes;
+ }
+
+ /**
+ * Sets the internal file attributes.
+ *
+ * @param int $internalAttributes the internal file attributes
+ *
+ * @return ZipEntry
+ */
+ public function setInternalAttributes($internalAttributes)
+ {
+ $internalAttributes = (int) $internalAttributes;
+
+ if ($internalAttributes < 0x0000 || $internalAttributes > 0xffff) {
+ throw new InvalidArgumentException('internal attributes out of range');
+ }
+ $this->internalAttributes = $internalAttributes;
+
+ return $this;
+ }
+
+ /**
+ * Returns true if and only if this ZIP entry represents a directory entry
+ * (i.e. end with '/').
+ *
+ * @return bool
+ */
+ final public function isDirectory()
+ {
+ return $this->isDirectory;
+ }
+
+ /**
+ * @return ExtraFieldsCollection
+ */
+ public function getCdExtraFields()
+ {
+ return $this->cdExtraFields;
+ }
+
+ /**
+ * @param int $headerId
+ *
+ * @return ZipExtraField|null
+ */
+ public function getCdExtraField($headerId)
+ {
+ return $this->cdExtraFields->get((int) $headerId);
+ }
+
+ /**
+ * @param ExtraFieldsCollection $cdExtraFields
+ *
+ * @return ZipEntry
+ */
+ public function setCdExtraFields(ExtraFieldsCollection $cdExtraFields)
+ {
+ $this->cdExtraFields = $cdExtraFields;
+
+ return $this;
+ }
+
+ /**
+ * @return ExtraFieldsCollection
+ */
+ public function getLocalExtraFields()
+ {
+ return $this->localExtraFields;
+ }
+
+ /**
+ * @param int $headerId
+ *
+ * @return ZipExtraField|null
+ */
+ public function getLocalExtraField($headerId)
+ {
+ return $this->localExtraFields[(int) $headerId];
+ }
+
+ /**
+ * @param ExtraFieldsCollection $localExtraFields
+ *
+ * @return ZipEntry
+ */
+ public function setLocalExtraFields(ExtraFieldsCollection $localExtraFields)
+ {
+ $this->localExtraFields = $localExtraFields;
+
+ return $this;
+ }
+
+ /**
+ * @param int $headerId
+ *
+ * @return ZipExtraField|null
+ */
+ public function getExtraField($headerId)
+ {
+ $headerId = (int) $headerId;
+ $local = $this->getLocalExtraField($headerId);
+
+ if ($local === null) {
+ return $this->getCdExtraField($headerId);
+ }
+
+ return $local;
+ }
+
+ /**
+ * @param int $headerId
+ *
+ * @return bool
+ */
+ public function hasExtraField($headerId)
+ {
+ $headerId = (int) $headerId;
+
+ return
+ isset($this->localExtraFields[$headerId]) ||
+ isset($this->cdExtraFields[$headerId]);
+ }
+
+ /**
+ * @param int $headerId
+ */
+ public function removeExtraField($headerId)
+ {
+ $headerId = (int) $headerId;
+
+ $this->cdExtraFields->remove($headerId);
+ $this->localExtraFields->remove($headerId);
+ }
+
+ /**
+ * @param ZipExtraField $zipExtraField
+ */
+ public function addExtraField(ZipExtraField $zipExtraField)
+ {
+ $this->addLocalExtraField($zipExtraField);
+ $this->addCdExtraField($zipExtraField);
+ }
+
+ /**
+ * @param ZipExtraField $zipExtraField
+ */
+ public function addLocalExtraField(ZipExtraField $zipExtraField)
+ {
+ $this->localExtraFields->add($zipExtraField);
+ }
+
+ /**
+ * @param ZipExtraField $zipExtraField
+ */
+ public function addCdExtraField(ZipExtraField $zipExtraField)
+ {
+ $this->cdExtraFields->add($zipExtraField);
+ }
+
+ /**
+ * Returns comment entry.
+ *
+ * @return string
+ */
+ public function getComment()
+ {
+ return $this->comment !== null ? $this->comment : '';
+ }
+
+ /**
+ * Set entry comment.
+ *
+ * @param string|null $comment
+ *
+ * @return ZipEntry
+ */
+ public function setComment($comment)
+ {
+ if ($comment !== null) {
+ $commentLength = \strlen($comment);
+
+ if ($commentLength > 0xffff) {
+ throw new InvalidArgumentException('Comment too long');
+ }
+
+ if ($this->charset === null && !StringUtil::isASCII($comment)) {
+ $this->enableUtf8Name(true);
+ }
+ }
+ $this->comment = $comment;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isDataDescriptorRequired()
+ {
+ return ($this->getCrc() | $this->getCompressedSize() | $this->getUncompressedSize()) === self::UNKNOWN;
+ }
+
+ /**
+ * Return crc32 content or 0 for WinZip AES v2.
+ *
+ * @return int
+ */
+ public function getCrc()
+ {
+ return $this->crc;
+ }
+
+ /**
+ * Set crc32 content.
+ *
+ * @param int $crc
+ *
+ * @return ZipEntry
+ *
+ * @internal
+ */
+ public function setCrc($crc)
+ {
+ $this->crc = (int) $crc;
+
+ return $this;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Set password and encryption method from entry.
+ *
+ * @param string|null $password
+ * @param int|null $encryptionMethod
+ *
+ * @return ZipEntry
+ */
+ public function setPassword($password, $encryptionMethod = null)
+ {
+ if (!$this->isDirectory) {
+ if ($password === null || $password === '') {
+ $this->password = null;
+ $this->disableEncryption();
+ } else {
+ $this->password = (string) $password;
+
+ if ($encryptionMethod === null && $this->encryptionMethod === ZipEncryptionMethod::NONE) {
+ $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256;
+ }
+
+ if ($encryptionMethod !== null) {
+ $this->setEncryptionMethod($encryptionMethod);
+ }
+ $this->setEncrypted(true);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getEncryptionMethod()
+ {
+ return $this->encryptionMethod;
+ }
+
+ /**
+ * Set encryption method.
+ *
+ * @param int|null $encryptionMethod
+ *
+ * @return ZipEntry
+ *
+ * @see ZipEncryptionMethod::NONE
+ * @see ZipEncryptionMethod::PKWARE
+ * @see ZipEncryptionMethod::WINZIP_AES_256
+ * @see ZipEncryptionMethod::WINZIP_AES_192
+ * @see ZipEncryptionMethod::WINZIP_AES_128
+ */
+ public function setEncryptionMethod($encryptionMethod)
+ {
+ if ($encryptionMethod === null) {
+ $encryptionMethod = ZipEncryptionMethod::NONE;
+ }
+
+ $encryptionMethod = (int) $encryptionMethod;
+ ZipEncryptionMethod::checkSupport($encryptionMethod);
+ $this->encryptionMethod = $encryptionMethod;
+
+ $this->setEncrypted($this->encryptionMethod !== ZipEncryptionMethod::NONE);
+ $this->extractVersion = self::UNKNOWN;
+
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCompressionLevel()
+ {
+ return $this->compressionLevel;
+ }
+
+ /**
+ * @param int $compressionLevel
+ *
+ * @return ZipEntry
+ */
+ public function setCompressionLevel($compressionLevel)
+ {
+ $compressionLevel = (int) $compressionLevel;
+
+ if ($compressionLevel === self::UNKNOWN) {
+ $compressionLevel = ZipCompressionLevel::NORMAL;
+ }
+
+ if (
+ $compressionLevel < ZipCompressionLevel::LEVEL_MIN ||
+ $compressionLevel > ZipCompressionLevel::LEVEL_MAX
+ ) {
+ throw new InvalidArgumentException(
+ 'Invalid compression level. Minimum level ' .
+ ZipCompressionLevel::LEVEL_MIN . '. Maximum level ' . ZipCompressionLevel::LEVEL_MAX
+ );
+ }
+ $this->compressionLevel = $compressionLevel;
+
+ $this->updateGbpfCompLevel();
+
+ return $this;
+ }
+
+ /**
+ * Update general purpose bit flogs.
+ */
+ private function updateGbpfCompLevel()
+ {
+ if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
+ $bit1 = false;
+ $bit2 = false;
+
+ switch ($this->compressionLevel) {
+ case ZipCompressionLevel::MAXIMUM:
+ $bit1 = true;
+ break;
+
+ case ZipCompressionLevel::FAST:
+ $bit2 = true;
+ break;
+
+ case ZipCompressionLevel::SUPER_FAST:
+ $bit1 = true;
+ $bit2 = true;
+ break;
+ // default is ZipCompressionLevel::NORMAL
+ }
+
+ $this->generalPurposeBitFlags |= ($bit1 ? GeneralPurposeBitFlag::COMPRESSION_FLAG1 : 0);
+ $this->generalPurposeBitFlags |= ($bit2 ? GeneralPurposeBitFlag::COMPRESSION_FLAG2 : 0);
+ }
+ }
+
+ /**
+ * Sets Unix permissions in a way that is understood by Info-Zip's
+ * unzip command.
+ *
+ * @param int $mode mode an int value
+ *
+ * @return ZipEntry
+ */
+ public function setUnixMode($mode)
+ {
+ $mode = (int) $mode;
+ $this->setExternalAttributes(
+ ($mode << 16)
+ // MS-DOS read-only attribute
+ | (($mode & UnixStat::UNX_IWUSR) === 0 ? DosAttrs::DOS_HIDDEN : 0)
+ // MS-DOS directory flag
+ | ($this->isDirectory() ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE)
+ );
+ $this->createdOS = ZipPlatform::OS_UNIX;
+
+ return $this;
+ }
+
+ /**
+ * Unix permission.
+ *
+ * @return int the unix permissions
+ */
+ public function getUnixMode()
+ {
+ $mode = 0;
+
+ if ($this->createdOS === ZipPlatform::OS_UNIX) {
+ $mode = ($this->externalAttributes >> 16) & 0xFFFF;
+ } elseif ($this->hasExtraField(AsiExtraField::HEADER_ID)) {
+ /** @var AsiExtraField $asiExtraField */
+ $asiExtraField = $this->getExtraField(AsiExtraField::HEADER_ID);
+ $mode = $asiExtraField->getMode();
+ }
+
+ if ($mode > 0) {
+ return $mode;
+ }
+
+ return $this->isDirectory ? 040755 : 0100644;
+ }
+
+ /**
+ * Offset MUST be considered in decision about ZIP64 format - see
+ * description of Data Descriptor in ZIP File Format Specification.
+ *
+ * @return bool
+ */
+ public function isZip64ExtensionsRequired()
+ {
+ return $this->compressedSize > ZipConstants::ZIP64_MAGIC
+ || $this->uncompressedSize > ZipConstants::ZIP64_MAGIC;
+ }
+
+ /**
+ * Returns true if this entry represents a unix symlink,
+ * in which case the entry's content contains the target path
+ * for the symlink.
+ *
+ * @return bool true if the entry represents a unix symlink,
+ * false otherwise
+ */
+ public function isUnixSymlink()
+ {
+ return ($this->getUnixMode() & UnixStat::UNX_IFMT) === UnixStat::UNX_IFLNK;
+ }
+
+ /**
+ * @return \DateTimeInterface
+ */
+ public function getMTime()
+ {
+ /** @var NtfsExtraField|null $ntfsExtra */
+ $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
+
+ if ($ntfsExtra !== null) {
+ return $ntfsExtra->getModifyDateTime();
+ }
+
+ /** @var ExtendedTimestampExtraField|null $extendedExtra */
+ $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
+
+ if ($extendedExtra !== null && ($mtime = $extendedExtra->getModifyDateTime()) !== null) {
+ return $mtime;
+ }
+
+ /** @var OldUnixExtraField|null $oldUnixExtra */
+ $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
+
+ if ($oldUnixExtra !== null && ($mtime = $oldUnixExtra->getModifyDateTime()) !== null) {
+ return $mtime;
+ }
+
+ $timestamp = $this->getTime();
+
+ try {
+ return new \DateTimeImmutable('@' . $timestamp);
+ } catch (\Exception $e) {
+ throw new RuntimeException('Error create DateTime object with timestamp ' . $timestamp, 1, $e);
+ }
+ }
+
+ /**
+ * @return \DateTimeInterface|null
+ */
+ public function getATime()
+ {
+ /** @var NtfsExtraField|null $ntfsExtra */
+ $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
+
+ if ($ntfsExtra !== null) {
+ return $ntfsExtra->getAccessDateTime();
+ }
+
+ /** @var ExtendedTimestampExtraField|null $extendedExtra */
+ $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
+
+ if ($extendedExtra !== null && ($atime = $extendedExtra->getAccessDateTime()) !== null) {
+ return $atime;
+ }
+
+ /** @var OldUnixExtraField|null $oldUnixExtra */
+ $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
+
+ if ($oldUnixExtra !== null) {
+ return $oldUnixExtra->getAccessDateTime();
+ }
+
+ return null;
+ }
+
+ /**
+ * @return \DateTimeInterface|null
+ */
+ public function getCTime()
+ {
+ /** @var NtfsExtraField|null $ntfsExtra */
+ $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
+
+ if ($ntfsExtra !== null) {
+ return $ntfsExtra->getCreateDateTime();
+ }
+
+ /** @var ExtendedTimestampExtraField|null $extendedExtra */
+ $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
+
+ if ($extendedExtra !== null) {
+ return $extendedExtra->getCreateDateTime();
+ }
+
+ return null;
+ }
+
+ public function __clone()
+ {
+ $this->cdExtraFields = clone $this->cdExtraFields;
+ $this->localExtraFields = clone $this->localExtraFields;
+
+ if ($this->data !== null) {
+ $this->data = clone $this->data;
+ }
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/ZipEntryMatcher.php b/vendor/nelexa/zip/src/Model/ZipEntryMatcher.php
new file mode 100644
index 0000000..9b91ba7
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/ZipEntryMatcher.php
@@ -0,0 +1,206 @@
+zipContainer = $zipContainer;
+ }
+
+ /**
+ * @param string|ZipEntry|string[]|ZipEntry[] $entries
+ *
+ * @return ZipEntryMatcher
+ */
+ public function add($entries)
+ {
+ $entries = (array) $entries;
+ $entries = array_map(
+ static function ($entry) {
+ return $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
+ },
+ $entries
+ );
+ $this->matches = array_values(
+ array_map(
+ 'strval',
+ array_unique(
+ array_merge(
+ $this->matches,
+ array_keys(
+ array_intersect_key(
+ $this->zipContainer->getEntries(),
+ array_flip($entries)
+ )
+ )
+ )
+ )
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @param string $regexp
+ *
+ * @return ZipEntryMatcher
+ *
+ * @noinspection PhpUnusedParameterInspection
+ */
+ public function match($regexp)
+ {
+ array_walk(
+ $this->zipContainer->getEntries(),
+ /**
+ * @param ZipEntry $entry
+ * @param string $entryName
+ */
+ function (ZipEntry $entry, $entryName) use ($regexp) {
+ if (preg_match($regexp, $entryName)) {
+ $this->matches[] = (string) $entryName;
+ }
+ }
+ );
+ $this->matches = array_unique($this->matches);
+
+ return $this;
+ }
+
+ /**
+ * @return ZipEntryMatcher
+ */
+ public function all()
+ {
+ $this->matches = array_map(
+ 'strval',
+ array_keys($this->zipContainer->getEntries())
+ );
+
+ return $this;
+ }
+
+ /**
+ * Callable function for all select entries.
+ *
+ * Callable function signature:
+ * function(string $entryName){}
+ *
+ * @param callable $callable
+ */
+ public function invoke(callable $callable)
+ {
+ if (!empty($this->matches)) {
+ array_walk(
+ $this->matches,
+ /** @param string $entryName */
+ static function ($entryName) use ($callable) {
+ $callable($entryName);
+ }
+ );
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getMatches()
+ {
+ return $this->matches;
+ }
+
+ public function delete()
+ {
+ array_walk(
+ $this->matches,
+ /** @param string $entryName */
+ function ($entryName) {
+ $this->zipContainer->deleteEntry($entryName);
+ }
+ );
+ $this->matches = [];
+ }
+
+ /**
+ * @param string|null $password
+ * @param int|null $encryptionMethod
+ */
+ public function setPassword($password, $encryptionMethod = null)
+ {
+ array_walk(
+ $this->matches,
+ /** @param string $entryName */
+ function ($entryName) use ($password, $encryptionMethod) {
+ $entry = $this->zipContainer->getEntry($entryName);
+
+ if (!$entry->isDirectory()) {
+ $entry->setPassword($password, $encryptionMethod);
+ }
+ }
+ );
+ }
+
+ /**
+ * @param int $encryptionMethod
+ */
+ public function setEncryptionMethod($encryptionMethod)
+ {
+ array_walk(
+ $this->matches,
+ /** @param string $entryName */
+ function ($entryName) use ($encryptionMethod) {
+ $entry = $this->zipContainer->getEntry($entryName);
+
+ if (!$entry->isDirectory()) {
+ $entry->setEncryptionMethod($encryptionMethod);
+ }
+ }
+ );
+ }
+
+ public function disableEncryption()
+ {
+ array_walk(
+ $this->matches,
+ /** @param string $entryName */
+ function ($entryName) {
+ $entry = $this->zipContainer->getEntry($entryName);
+
+ if (!$entry->isDirectory()) {
+ $entry->disableEncryption();
+ }
+ }
+ );
+ }
+
+ /**
+ * Count elements of an object.
+ *
+ * @see http://php.net/manual/en/countable.count.php
+ *
+ * @return int the custom count as an integer
+ *
+ * @since 5.1.0
+ */
+ public function count()
+ {
+ return \count($this->matches);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Model/ZipInfo.php b/vendor/nelexa/zip/src/Model/ZipInfo.php
new file mode 100644
index 0000000..42eebbf
--- /dev/null
+++ b/vendor/nelexa/zip/src/Model/ZipInfo.php
@@ -0,0 +1,266 @@
+entry = $entry;
+ }
+
+ /**
+ * @param ZipEntry $entry
+ *
+ * @return string
+ *
+ * @deprecated Use {@see ZipPlatform::getPlatformName()}
+ */
+ public static function getPlatformName(ZipEntry $entry)
+ {
+ return ZipPlatform::getPlatformName($entry->getExtractedOS());
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->entry->getName();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isFolder()
+ {
+ return $this->entry->isDirectory();
+ }
+
+ /**
+ * @return int
+ */
+ public function getSize()
+ {
+ return $this->entry->getUncompressedSize();
+ }
+
+ /**
+ * @return int
+ */
+ public function getCompressedSize()
+ {
+ return $this->entry->getCompressedSize();
+ }
+
+ /**
+ * @return int
+ */
+ public function getMtime()
+ {
+ return $this->entry->getMTime()->getTimestamp();
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getCtime()
+ {
+ $ctime = $this->entry->getCTime();
+
+ return $ctime === null ? null : $ctime->getTimestamp();
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getAtime()
+ {
+ $atime = $this->entry->getATime();
+
+ return $atime === null ? null : $atime->getTimestamp();
+ }
+
+ /**
+ * @return string
+ */
+ public function getAttributes()
+ {
+ $externalAttributes = $this->entry->getExternalAttributes();
+
+ if ($this->entry->getCreatedOS() === ZipPlatform::OS_UNIX) {
+ $permission = (($externalAttributes >> 16) & 0xFFFF);
+
+ return FileAttribUtil::getUnixMode($permission);
+ }
+
+ return FileAttribUtil::getDosMode($externalAttributes);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEncrypted()
+ {
+ return $this->entry->isEncrypted();
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getComment()
+ {
+ return $this->entry->getComment();
+ }
+
+ /**
+ * @return int
+ */
+ public function getCrc()
+ {
+ return $this->entry->getCrc();
+ }
+
+ /**
+ * @return string
+ *
+ * @deprecated use \PhpZip\Model\ZipInfo::getMethodName()
+ */
+ public function getMethod()
+ {
+ return $this->getMethodName();
+ }
+
+ /**
+ * @return string
+ */
+ public function getMethodName()
+ {
+ return ZipCompressionMethod::getCompressionMethodName($this->entry->getCompressionMethod());
+ }
+
+ /**
+ * @return string
+ */
+ public function getEncryptionMethodName()
+ {
+ return ZipEncryptionMethod::getEncryptionMethodName($this->entry->getEncryptionMethod());
+ }
+
+ /**
+ * @return string
+ */
+ public function getPlatform()
+ {
+ return ZipPlatform::getPlatformName($this->entry->getExtractedOS());
+ }
+
+ /**
+ * @return int
+ */
+ public function getVersion()
+ {
+ return $this->entry->getExtractVersion();
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getEncryptionMethod()
+ {
+ $encryptionMethod = $this->entry->getEncryptionMethod();
+
+ return $encryptionMethod === ZipEncryptionMethod::NONE ? null : $encryptionMethod;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getCompressionLevel()
+ {
+ return $this->entry->getCompressionLevel();
+ }
+
+ /**
+ * @return int
+ */
+ public function getCompressionMethod()
+ {
+ return $this->entry->getCompressionMethod();
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ return [
+ 'name' => $this->getName(),
+ 'folder' => $this->isFolder(),
+ 'size' => $this->getSize(),
+ 'compressed_size' => $this->getCompressedSize(),
+ 'modified' => $this->getMtime(),
+ 'created' => $this->getCtime(),
+ 'accessed' => $this->getAtime(),
+ 'attributes' => $this->getAttributes(),
+ 'encrypted' => $this->isEncrypted(),
+ 'encryption_method' => $this->getEncryptionMethod(),
+ 'encryption_method_name' => $this->getEncryptionMethodName(),
+ 'comment' => $this->getComment(),
+ 'crc' => $this->getCrc(),
+ 'method_name' => $this->getMethodName(),
+ 'compression_method' => $this->getCompressionMethod(),
+ 'platform' => $this->getPlatform(),
+ 'version' => $this->getVersion(),
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $ctime = $this->entry->getCTime();
+ $atime = $this->entry->getATime();
+ $comment = $this->getComment();
+
+ return __CLASS__ . ' {'
+ . 'Name="' . $this->getName() . '", '
+ . ($this->isFolder() ? 'Folder, ' : '')
+ . 'Size="' . FilesUtil::humanSize($this->getSize()) . '"'
+ . ', Compressed size="' . FilesUtil::humanSize($this->getCompressedSize()) . '"'
+ . ', Modified time="' . $this->entry->getMTime()->format(\DATE_W3C) . '", '
+ . ($ctime !== null ? 'Created time="' . $ctime->format(\DATE_W3C) . '", ' : '')
+ . ($atime !== null ? 'Accessed time="' . $atime->format(\DATE_W3C) . '", ' : '')
+ . ($this->isEncrypted() ? 'Encrypted, ' : '')
+ . ($comment !== null ? 'Comment="' . $comment . '", ' : '')
+ . (!empty($this->crc) ? 'Crc=0x' . dechex($this->crc) . ', ' : '')
+ . 'Method name="' . $this->getMethodName() . '", '
+ . 'Attributes="' . $this->getAttributes() . '", '
+ . 'Platform="' . $this->getPlatform() . '", '
+ . 'Version=' . $this->getVersion()
+ . '}';
+ }
+}
diff --git a/vendor/nelexa/zip/src/Util/CryptoUtil.php b/vendor/nelexa/zip/src/Util/CryptoUtil.php
new file mode 100644
index 0000000..852a5e1
--- /dev/null
+++ b/vendor/nelexa/zip/src/Util/CryptoUtil.php
@@ -0,0 +1,77 @@
+> 1);
+
+ /**
+ * Convert a 32 bit integer DOS date/time value to a UNIX timestamp value.
+ *
+ * @param int $dosTime Dos date/time
+ *
+ * @return int Unix timestamp
+ */
+ public static function msDosToUnix($dosTime)
+ {
+ if ($dosTime <= self::MIN_DOS_TIME) {
+ $dosTime = 0;
+ } elseif ($dosTime > self::MAX_DOS_TIME) {
+ $dosTime = self::MAX_DOS_TIME;
+ }
+// date_default_timezone_set('UTC');
+ return mktime(
+ (($dosTime >> 11) & 0x1f), // hours
+ (($dosTime >> 5) & 0x3f), // minutes
+ (($dosTime << 1) & 0x3e), // seconds
+ (($dosTime >> 21) & 0x0f), // month
+ (($dosTime >> 16) & 0x1f), // day
+ ((($dosTime >> 25) & 0x7f) + 1980) // year
+ );
+ }
+
+ /**
+ * Converts a UNIX timestamp value to a DOS date/time value.
+ *
+ * @param int $unixTimestamp the number of seconds since midnight, January 1st,
+ * 1970 AD UTC
+ *
+ * @return int a DOS date/time value reflecting the local time zone and
+ * rounded down to even seconds
+ * and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME
+ */
+ public static function unixToMsDos($unixTimestamp)
+ {
+ if ($unixTimestamp < 0) {
+ throw new \InvalidArgumentException('Negative unix timestamp: ' . $unixTimestamp);
+ }
+
+ $date = getdate($unixTimestamp);
+ $dosTime = (
+ (($date['year'] - 1980) << 25) |
+ ($date['mon'] << 21) |
+ ($date['mday'] << 16) |
+ ($date['hours'] << 11) |
+ ($date['minutes'] << 5) |
+ ($date['seconds'] >> 1)
+ );
+
+ if ($dosTime <= self::MIN_DOS_TIME) {
+ $dosTime = 0;
+ }
+
+ return $dosTime;
+ }
+}
diff --git a/vendor/nelexa/zip/src/Util/FileAttribUtil.php b/vendor/nelexa/zip/src/Util/FileAttribUtil.php
new file mode 100644
index 0000000..06247ae
--- /dev/null
+++ b/vendor/nelexa/zip/src/Util/FileAttribUtil.php
@@ -0,0 +1,108 @@
+isDir() ? 'rmdir' : 'unlink');
+ $function($fileInfo->getPathname());
+ }
+ @rmdir($dir);
+ }
+
+ /**
+ * Convert glob pattern to regex pattern.
+ *
+ * @param string $globPattern
+ *
+ * @return string
+ */
+ public static function convertGlobToRegEx($globPattern)
+ {
+ // Remove beginning and ending * globs because they're useless
+ $globPattern = trim($globPattern, '*');
+ $escaping = false;
+ $inCurrent = 0;
+ $chars = str_split($globPattern);
+ $regexPattern = '';
+
+ foreach ($chars as $currentChar) {
+ switch ($currentChar) {
+ case '*':
+ $regexPattern .= ($escaping ? '\\*' : '.*');
+ $escaping = false;
+ break;
+
+ case '?':
+ $regexPattern .= ($escaping ? '\\?' : '.');
+ $escaping = false;
+ break;
+
+ case '.':
+ case '(':
+ case ')':
+ case '+':
+ case '|':
+ case '^':
+ case '$':
+ case '@':
+ case '%':
+ $regexPattern .= '\\' . $currentChar;
+ $escaping = false;
+ break;
+
+ case '\\':
+ if ($escaping) {
+ $regexPattern .= '\\\\';
+ $escaping = false;
+ } else {
+ $escaping = true;
+ }
+ break;
+
+ case '{':
+ if ($escaping) {
+ $regexPattern .= '\\{';
+ } else {
+ $regexPattern = '(';
+ $inCurrent++;
+ }
+ $escaping = false;
+ break;
+
+ case '}':
+ if ($inCurrent > 0 && !$escaping) {
+ $regexPattern .= ')';
+ $inCurrent--;
+ } elseif ($escaping) {
+ $regexPattern = '\\}';
+ } else {
+ $regexPattern = '}';
+ }
+ $escaping = false;
+ break;
+
+ case ',':
+ if ($inCurrent > 0 && !$escaping) {
+ $regexPattern .= '|';
+ } elseif ($escaping) {
+ $regexPattern .= '\\,';
+ } else {
+ $regexPattern = ',';
+ }
+ break;
+ default:
+ $escaping = false;
+ $regexPattern .= $currentChar;
+ }
+ }
+
+ return $regexPattern;
+ }
+
+ /**
+ * Search files.
+ *
+ * @param string $inputDir
+ * @param bool $recursive
+ * @param array $ignoreFiles
+ *
+ * @return array Searched file list
+ */
+ public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
+ {
+ if ($recursive) {
+ $directoryIterator = new \RecursiveDirectoryIterator($inputDir);
+
+ if (!empty($ignoreFiles)) {
+ $directoryIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
+ }
+ $iterator = new \RecursiveIteratorIterator($directoryIterator);
+ } else {
+ $directoryIterator = new \DirectoryIterator($inputDir);
+
+ if (!empty($ignoreFiles)) {
+ $directoryIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
+ }
+ $iterator = new \IteratorIterator($directoryIterator);
+ }
+
+ $fileList = [];
+
+ foreach ($iterator as $file) {
+ if ($file instanceof \SplFileInfo) {
+ $fileList[] = $file->getPathname();
+ }
+ }
+
+ return $fileList;
+ }
+
+ /**
+ * Search files from glob pattern.
+ *
+ * @param string $globPattern
+ * @param int $flags
+ * @param bool $recursive
+ *
+ * @return array Searched file list
+ */
+ public static function globFileSearch($globPattern, $flags = 0, $recursive = true)
+ {
+ $flags = (int) $flags;
+ $recursive = (bool) $recursive;
+ $files = glob($globPattern, $flags);
+
+ if (!$recursive) {
+ return $files;
+ }
+
+ foreach (glob(\dirname($globPattern) . \DIRECTORY_SEPARATOR . '*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) {
+ // Unpacking the argument via ... is supported starting from php 5.6 only
+ /** @noinspection SlowArrayOperationsInLoopInspection */
+ $files = array_merge($files, self::globFileSearch($dir . \DIRECTORY_SEPARATOR . basename($globPattern), $flags, $recursive));
+ }
+
+ return $files;
+ }
+
+ /**
+ * Search files from regex pattern.
+ *
+ * @param string $folder
+ * @param string $pattern
+ * @param bool $recursive
+ *
+ * @return array Searched file list
+ */
+ public static function regexFileSearch($folder, $pattern, $recursive = true)
+ {
+ if ($recursive) {
+ $directoryIterator = new \RecursiveDirectoryIterator($folder);
+ $iterator = new \RecursiveIteratorIterator($directoryIterator);
+ } else {
+ $directoryIterator = new \DirectoryIterator($folder);
+ $iterator = new \IteratorIterator($directoryIterator);
+ }
+
+ $regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
+ $fileList = [];
+
+ foreach ($regexIterator as $file) {
+ if ($file instanceof \SplFileInfo) {
+ $fileList[] = $file->getPathname();
+ }
+ }
+
+ return $fileList;
+ }
+
+ /**
+ * Convert bytes to human size.
+ *
+ * @param int $size Size bytes
+ * @param string|null $unit Unit support 'GB', 'MB', 'KB'
+ *
+ * @return string
+ */
+ public static function humanSize($size, $unit = null)
+ {
+ if (($unit === null && $size >= 1 << 30) || $unit === 'GB') {
+ return number_format($size / (1 << 30), 2) . 'GB';
+ }
+
+ if (($unit === null && $size >= 1 << 20) || $unit === 'MB') {
+ return number_format($size / (1 << 20), 2) . 'MB';
+ }
+
+ if (($unit === null && $size >= 1 << 10) || $unit === 'KB') {
+ return number_format($size / (1 << 10), 2) . 'KB';
+ }
+
+ return number_format($size) . ' bytes';
+ }
+
+ /**
+ * Normalizes zip path.
+ *
+ * @param string $path Zip path
+ *
+ * @return string
+ */
+ public static function normalizeZipPath($path)
+ {
+ return implode(
+ \DIRECTORY_SEPARATOR,
+ array_filter(
+ explode('/', (string) $path),
+ static function ($part) {
+ return $part !== '.' && $part !== '..';
+ }
+ )
+ );
+ }
+
+ /**
+ * Returns whether the file path is an absolute path.
+ *
+ * @param string $file A file path
+ *
+ * @return bool
+ *
+ * @see source symfony filesystem component
+ */
+ public static function isAbsolutePath($file)
+ {
+ return strspn($file, '/\\', 0, 1)
+ || (
+ \strlen($file) > 3 && ctype_alpha($file[0])
+ && $file[1] === ':'
+ && strspn($file, '/\\', 2, 1)
+ )
+ || parse_url($file, \PHP_URL_SCHEME) !== null;
+ }
+
+ /**
+ * @param string $target
+ * @param string $path
+ * @param bool $allowSymlink
+ *
+ * @return bool
+ */
+ public static function symlink($target, $path, $allowSymlink)
+ {
+ if (\DIRECTORY_SEPARATOR === '\\' || !$allowSymlink) {
+ return file_put_contents($path, $target) !== false;
+ }
+
+ return symlink($target, $path);
+ }
+
+ /**
+ * @param string $file
+ *
+ * @return bool
+ */
+ public static function isBadCompressionFile($file)
+ {
+ $badCompressFileExt = [
+ 'dic',
+ 'dng',
+ 'f4v',
+ 'flipchart',
+ 'h264',
+ 'lrf',
+ 'mobi',
+ 'mts',
+ 'nef',
+ 'pspimage',
+ ];
+
+ $ext = strtolower(pathinfo($file, \PATHINFO_EXTENSION));
+
+ if (\in_array($ext, $badCompressFileExt, true)) {
+ return true;
+ }
+
+ $mimeType = self::getMimeTypeFromFile($file);
+
+ return self::isBadCompressionMimeType($mimeType);
+ }
+
+ /**
+ * @param string $mimeType
+ *
+ * @return bool
+ */
+ public static function isBadCompressionMimeType($mimeType)
+ {
+ static $badDeflateCompMimeTypes = [
+ 'application/epub+zip',
+ 'application/gzip',
+ 'application/vnd.debian.binary-package',
+ 'application/vnd.oasis.opendocument.graphics',
+ 'application/vnd.oasis.opendocument.presentation',
+ 'application/vnd.oasis.opendocument.text',
+ 'application/vnd.oasis.opendocument.text-master',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.rn-realmedia',
+ 'application/x-7z-compressed',
+ 'application/x-arj',
+ 'application/x-bzip2',
+ 'application/x-hwp',
+ 'application/x-lzip',
+ 'application/x-lzma',
+ 'application/x-ms-reader',
+ 'application/x-rar',
+ 'application/x-rpm',
+ 'application/x-stuffit',
+ 'application/x-tar',
+ 'application/x-xz',
+ 'application/zip',
+ 'application/zlib',
+ 'audio/flac',
+ 'audio/mpeg',
+ 'audio/ogg',
+ 'audio/vnd.dolby.dd-raw',
+ 'audio/webm',
+ 'audio/x-ape',
+ 'audio/x-hx-aac-adts',
+ 'audio/x-m4a',
+ 'audio/x-m4a',
+ 'audio/x-wav',
+ 'image/gif',
+ 'image/heic',
+ 'image/jp2',
+ 'image/jpeg',
+ 'image/png',
+ 'image/vnd.djvu',
+ 'image/webp',
+ 'image/x-canon-cr2',
+ 'video/ogg',
+ 'video/webm',
+ 'video/x-matroska',
+ 'video/x-ms-asf',
+ 'x-epoc/x-sisx-app',
+ ];
+
+ if (\in_array($mimeType, $badDeflateCompMimeTypes, true)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $file
+ *
+ * @return string
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+ public static function getMimeTypeFromFile($file)
+ {
+ if (\function_exists('mime_content_type')) {
+ return mime_content_type($file);
+ }
+
+ return 'application/octet-stream';
+ }
+
+ /**
+ * @param string $contents
+ *
+ * @return string
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+ public static function getMimeTypeFromString($contents)
+ {
+ $contents = (string) $contents;
+ $finfo = new \finfo(\FILEINFO_MIME);
+ $mimeType = $finfo->buffer($contents);
+
+ if ($mimeType === false) {
+ $mimeType = 'application/octet-stream';
+ }
+
+ return explode(';', $mimeType)[0];
+ }
+}
diff --git a/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php b/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php
new file mode 100644
index 0000000..c13734e
--- /dev/null
+++ b/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php
@@ -0,0 +1,66 @@
+ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
+ }
+
+ /**
+ * Check whether the current element of the iterator is acceptable.
+ *
+ * @see http://php.net/manual/en/filteriterator.accept.php
+ *
+ * @return bool true if the current element is acceptable, otherwise false
+ *
+ * @since 5.1.0
+ */
+ public function accept()
+ {
+ /**
+ * @var \SplFileInfo $fileInfo
+ */
+ $fileInfo = $this->current();
+ $pathname = str_replace('\\', '/', $fileInfo->getPathname());
+
+ foreach ($this->ignoreFiles as $ignoreFile) {
+ // handler dir and sub dir
+ if ($fileInfo->isDir()
+ && StringUtil::endsWith($ignoreFile, '/')
+ && StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
+ ) {
+ return false;
+ }
+
+ // handler filename
+ if (StringUtil::endsWith($pathname, $ignoreFile)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php b/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php
new file mode 100644
index 0000000..8935127
--- /dev/null
+++ b/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php
@@ -0,0 +1,74 @@
+ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
+ }
+
+ /**
+ * Check whether the current element of the iterator is acceptable.
+ *
+ * @see http://php.net/manual/en/filteriterator.accept.php
+ *
+ * @return bool true if the current element is acceptable, otherwise false
+ *
+ * @since 5.1.0
+ */
+ public function accept()
+ {
+ /**
+ * @var \SplFileInfo $fileInfo
+ */
+ $fileInfo = $this->current();
+ $pathname = str_replace('\\', '/', $fileInfo->getPathname());
+
+ foreach ($this->ignoreFiles as $ignoreFile) {
+ // handler dir and sub dir
+ if ($fileInfo->isDir()
+ && $ignoreFile[\strlen($ignoreFile) - 1] === '/'
+ && StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
+ ) {
+ return false;
+ }
+
+ // handler filename
+ if (StringUtil::endsWith($pathname, $ignoreFile)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return IgnoreFilesRecursiveFilterIterator
+ */
+ public function getChildren()
+ {
+ return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles);
+ }
+}
diff --git a/vendor/nelexa/zip/src/Util/PackUtil.php b/vendor/nelexa/zip/src/Util/PackUtil.php
new file mode 100644
index 0000000..653fab7
--- /dev/null
+++ b/vendor/nelexa/zip/src/Util/PackUtil.php
@@ -0,0 +1,69 @@
+= 506030) {
+ return pack('P', $longValue);
+ }
+
+ $left = 0xffffffff00000000;
+ $right = 0x00000000ffffffff;
+
+ $r = ($longValue & $left) >> 32;
+ $l = $longValue & $right;
+
+ return pack('VV', $l, $r);
+ }
+
+ /**
+ * @param string $value
+ *
+ * @return int
+ */
+ public static function unpackLongLE($value)
+ {
+ if (\PHP_VERSION_ID >= 506030) {
+ return unpack('P', $value)[1];
+ }
+ $unpack = unpack('Va/Vb', $value);
+
+ return $unpack['a'] + ($unpack['b'] << 32);
+ }
+
+ /**
+ * Cast to signed int 32-bit.
+ *
+ * @param int $int
+ *
+ * @return int
+ */
+ public static function toSignedInt32($int)
+ {
+ if (\PHP_INT_SIZE === 8) {
+ $int &= 0xffffffff;
+
+ if ($int & 0x80000000) {
+ return $int - 0x100000000;
+ }
+ }
+
+ return $int;
+ }
+}
diff --git a/vendor/nelexa/zip/src/Util/StringUtil.php b/vendor/nelexa/zip/src/Util/StringUtil.php
new file mode 100644
index 0000000..bce2a17
--- /dev/null
+++ b/vendor/nelexa/zip/src/Util/StringUtil.php
@@ -0,0 +1,48 @@
+ '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());
+ }
+}
diff --git a/vendor/nelexa/zip/src/ZipFileInterface.php b/vendor/nelexa/zip/src/ZipFileInterface.php
new file mode 100644
index 0000000..07108d1
--- /dev/null
+++ b/vendor/nelexa/zip/src/ZipFileInterface.php
@@ -0,0 +1,902 @@
+